commit f268454989b0b3f03b674d19b9612887eb0aa985 Author: Alexandre Ratchov Date: Thu Aug 19 22:38:45 2010 +0200 import from OpenBSD diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..40caffa --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +# CVS default ignores begin +tags +TAGS +.make.state +.nse_depinfo +*~ +#* +.#* +,* +_$* +*$ +*.old +*.bak +*.BAK +*.orig +*.rej +.del-* +*.a +*.olb +*.o +*.obj +*.so +*.exe +*.Z +*.elc +*.ln +core +# CVS default ignores end diff --git a/aucat/abuf.c b/aucat/abuf.c new file mode 100644 index 0000000..5e565bc --- /dev/null +++ b/aucat/abuf.c @@ -0,0 +1,619 @@ +/* $OpenBSD: abuf.c,v 1.22 2010/06/04 06:15:28 ratchov Exp $ */ +/* + * Copyright (c) 2008 Alexandre Ratchov + * + * 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. It has one reader and one writer. The abuf + * structure is used to interconnect audio processing units (aproc + * structures). + * + * 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. + */ +/* + * TODO + * + * use blocks instead of frames for WOK and ROK macros. If necessary + * (unlikely) define reader block size and writer blocks size to + * ease pipe/socket implementation + */ +#include +#include +#include +#include +#include + +#include "abuf.h" +#include "aparams.h" +#include "aproc.h" +#include "conf.h" +#ifdef DEBUG +#include "dbg.h" +#endif + +#ifdef DEBUG +void +abuf_dbg(struct abuf *buf) +{ + if (buf->wproc) { + aproc_dbg(buf->wproc); + } else { + dbg_puts("none"); + } + dbg_puts(buf->inuse ? "=>" : "->"); + if (buf->rproc) { + aproc_dbg(buf->rproc); + } else { + dbg_puts("none"); + } +} + +void +abuf_dump(struct abuf *buf) +{ + abuf_dbg(buf); + dbg_puts(": used = "); + dbg_putu(buf->used); + dbg_puts("/"); + dbg_putu(buf->len); + dbg_puts(" start = "); + dbg_putu(buf->start); + dbg_puts("\n"); +} +#endif + +struct abuf * +abuf_new(unsigned nfr, struct aparams *par) +{ + struct abuf *buf; + unsigned len, bpf; + + bpf = aparams_bpf(par); + len = nfr * bpf; + buf = malloc(sizeof(struct abuf) + len); + if (buf == NULL) { +#ifdef DEBUG + dbg_puts("couldn't allocate abuf of "); + dbg_putu(nfr); + dbg_puts("fr * "); + dbg_putu(bpf); + dbg_puts("bpf\n"); + dbg_panic(); +#else + err(1, "malloc"); +#endif + } + buf->bpf = bpf; + buf->cmin = par->cmin; + buf->cmax = par->cmax; + buf->inuse = 0; + + /* + * fill fifo pointers + */ + buf->len = nfr; + buf->used = 0; + buf->start = 0; + buf->rproc = NULL; + buf->wproc = NULL; + buf->duplex = NULL; + return buf; +} + +void +abuf_del(struct abuf *buf) +{ + if (buf->duplex) + buf->duplex->duplex = NULL; +#ifdef DEBUG + if (buf->rproc || buf->wproc) { + abuf_dbg(buf); + dbg_puts(": can't delete referenced buffer\n"); + dbg_panic(); + } + if (ABUF_ROK(buf)) { + /* + * XXX : we should call abort(), here. + * However, poll() doesn't seem to return POLLHUP, + * so the reader is never destroyed; instead it appears + * as blocked. Fix file_poll(), if fixable, and add + * a call to abord() here. + */ + if (debug_level >= 3) { + abuf_dbg(buf); + dbg_puts(": deleting non-empty buffer, used = "); + dbg_putu(buf->used); + dbg_puts("\n"); + } + } +#endif + free(buf); +} + +/* + * Clear buffer contents. + */ +void +abuf_clear(struct abuf *buf) +{ +#ifdef DEBUG + if (debug_level >= 3) { + abuf_dbg(buf); + dbg_puts(": cleared\n"); + } +#endif + buf->used = 0; + buf->start = 0; +} + +/* + * Get a pointer to the readable block at the given offset. + */ +unsigned char * +abuf_rgetblk(struct abuf *buf, unsigned *rsize, unsigned ofs) +{ + unsigned count, start, used; + + start = buf->start + ofs; + used = buf->used - ofs; + if (start >= buf->len) + start -= buf->len; +#ifdef DEBUG + if (start >= buf->len || used > buf->used) { + abuf_dump(buf); + dbg_puts(": rgetblk: bad ofs = "); + dbg_putu(ofs); + dbg_puts("\n"); + dbg_panic(); + } +#endif + count = buf->len - start; + if (count > used) + count = used; + *rsize = count; + return (unsigned char *)buf + sizeof(struct abuf) + start * buf->bpf; +} + +/* + * Discard the block at the start postion. + */ +void +abuf_rdiscard(struct abuf *buf, unsigned count) +{ +#ifdef DEBUG + if (count > buf->used) { + abuf_dump(buf); + dbg_puts(": rdiscard: bad count = "); + dbg_putu(count); + dbg_puts("\n"); + dbg_panic(); + } + if (debug_level >= 4) { + abuf_dbg(buf); + dbg_puts(": discard("); + dbg_putu(count); + dbg_puts(")\n"); + } +#endif + buf->used -= count; + buf->start += count; + if (buf->start >= buf->len) + buf->start -= buf->len; +} + +/* + * Commit the data written at the end postion. + */ +void +abuf_wcommit(struct abuf *buf, unsigned count) +{ +#ifdef DEBUG + if (count > (buf->len - buf->used)) { + abuf_dump(buf); + dbg_puts(": rdiscard: bad count = "); + dbg_putu(count); + dbg_puts("\n"); + dbg_panic(); + } + if (debug_level >= 4) { + abuf_dbg(buf); + dbg_puts(": commit("); + dbg_putu(count); + dbg_puts(")\n"); + } +#endif + buf->used += count; +} + +/* + * Get a pointer to the writable block at offset ofs. + */ +unsigned char * +abuf_wgetblk(struct abuf *buf, unsigned *rsize, unsigned ofs) +{ + unsigned end, avail, count; + + + end = buf->start + buf->used + ofs; + if (end >= buf->len) + end -= buf->len; +#ifdef DEBUG + if (end >= buf->len) { + abuf_dump(buf); + dbg_puts(": wgetblk: bad ofs = "); + dbg_putu(ofs); + dbg_puts("\n"); + dbg_panic(); + } +#endif + avail = buf->len - (buf->used + ofs); + count = buf->len - end; + if (count > avail) + count = avail; + *rsize = count; + return (unsigned char *)buf + sizeof(struct abuf) + end * buf->bpf; +} + +/* + * Flush buffer either by dropping samples or by calling the aproc + * call-back to consume data. Return 0 if blocked, 1 otherwise. + */ +int +abuf_flush_do(struct abuf *buf) +{ + struct aproc *p; + + p = buf->rproc; + if (!p) + return 0; +#ifdef DEBUG + if (debug_level >= 4) { + aproc_dbg(p); + dbg_puts(": in\n"); + } +#endif + return p->ops->in(p, buf); +} + +/* + * Fill the buffer either by generating silence or by calling the aproc + * call-back to provide data. Return 0 if blocked, 1 otherwise. + */ +int +abuf_fill_do(struct abuf *buf) +{ + struct aproc *p; + + p = buf->wproc; + if (!p) + return 0; +#ifdef DEBUG + if (debug_level >= 4) { + aproc_dbg(p); + dbg_puts(": out\n"); + } +#endif + return p->ops->out(p, buf); +} + +/* + * Notify the reader that there will be no more input (producer + * disappeared) and destroy the buffer. + */ +void +abuf_eof_do(struct abuf *buf) +{ + struct aproc *p; + + p = buf->rproc; + if (p) { + buf->rproc = NULL; + LIST_REMOVE(buf, ient); + buf->inuse++; +#ifdef DEBUG + if (debug_level >= 4) { + aproc_dbg(p); + dbg_puts(": eof\n"); + } +#endif + p->ops->eof(p, buf); + buf->inuse--; + } + abuf_del(buf); +} + +/* + * Notify the writer that the buffer has no more consumer, + * and destroy the buffer. + */ +void +abuf_hup_do(struct abuf *buf) +{ + struct aproc *p; + + if (ABUF_ROK(buf)) { +#ifdef DEBUG + if (debug_level >= 3) { + abuf_dbg(buf); + dbg_puts(": hup: lost "); + dbg_putu(buf->used); + dbg_puts(" bytes\n"); + } +#endif + buf->used = 0; + } + p = buf->wproc; + if (p != NULL) { + buf->wproc = NULL; + LIST_REMOVE(buf, oent); + buf->inuse++; +#ifdef DEBUG + if (debug_level >= 3) { + aproc_dbg(p); + dbg_puts(": hup\n"); + } +#endif + p->ops->hup(p, buf); + buf->inuse--; + } + abuf_del(buf); +} + +/* + * Notify the read end of the buffer that there is input available + * and that data can be processed again. + */ +int +abuf_flush(struct abuf *buf) +{ + if (buf->inuse) { +#ifdef DEBUG + if (debug_level >= 4) { + abuf_dbg(buf); + dbg_puts(": flush blocked (inuse)\n"); + } +#endif + } else { + buf->inuse++; + for (;;) { + if (!abuf_flush_do(buf)) + break; + } + buf->inuse--; + if (ABUF_HUP(buf)) { + abuf_hup_do(buf); + return 0; + } + } + return 1; +} + +/* + * Notify the write end of the buffer that there is room and data can be + * written again. This routine can only be called from the out() + * call-back of the reader. + * + * Return 1 if the buffer was filled, and 0 if eof condition occured. The + * reader must detach the buffer on EOF condition, since its aproc->eof() + * call-back will never be called. + */ +int +abuf_fill(struct abuf *buf) +{ + if (buf->inuse) { +#ifdef DEBUG + if (debug_level >= 4) { + abuf_dbg(buf); + dbg_puts(": fill blocked (inuse)\n"); + } +#endif + } else { + buf->inuse++; + for (;;) { + if (!abuf_fill_do(buf)) + break; + } + buf->inuse--; + if (ABUF_EOF(buf)) { + abuf_eof_do(buf); + return 0; + } + } + return 1; +} + +/* + * Run a read/write loop on the buffer until either the reader or the + * writer blocks, or until the buffer reaches eofs. We can not get hup here, + * since hup() is only called from terminal nodes, from the main loop. + * + * NOTE: The buffer may disappear (ie. be free()ed) if eof is reached, so + * do not keep references to the buffer or to its writer or reader. + */ +void +abuf_run(struct abuf *buf) +{ + int canfill = 1, canflush = 1; + + if (buf->inuse) { +#ifdef DEBUG + if (debug_level >= 4) { + abuf_dbg(buf); + dbg_puts(": run blocked (inuse)\n"); + } +#endif + return; + } + buf->inuse++; + for (;;) { + if (canfill) { + if (!abuf_fill_do(buf)) + canfill = 0; + else + canflush = 1; + } else if (canflush) { + if (!abuf_flush_do(buf)) + canflush = 0; + else + canfill = 1; + } else + break; + } + buf->inuse--; + if (ABUF_EOF(buf)) { + abuf_eof_do(buf); + return; + } + if (ABUF_HUP(buf)) { + abuf_hup_do(buf); + return; + } +} + +/* + * Notify the reader that there will be no more input (producer + * disappeared). The buffer is flushed and eof() is called only if all + * data is flushed. + */ +void +abuf_eof(struct abuf *buf) +{ +#ifdef DEBUG + if (debug_level >= 3) { + abuf_dbg(buf); + dbg_puts(": eof requested\n"); + } + if (buf->wproc == NULL) { + abuf_dbg(buf); + dbg_puts(": eof, no writer\n"); + dbg_panic(); + } +#endif + LIST_REMOVE(buf, oent); + buf->wproc = NULL; + if (buf->rproc != NULL) { + if (!abuf_flush(buf)) + return; + if (ABUF_ROK(buf)) { + /* + * Could not flush everything, the reader will + * have a chance to delete the abuf later. + */ +#ifdef DEBUG + if (debug_level >= 3) { + abuf_dbg(buf); + dbg_puts(": eof, blocked (drain)\n"); + } +#endif + return; + } + } + if (buf->inuse) { +#ifdef DEBUG + if (debug_level >= 3) { + abuf_dbg(buf); + dbg_puts(": eof, blocked (inuse)\n"); + } +#endif + return; + } + abuf_eof_do(buf); +} + +/* + * Notify the writer that the buffer has no more consumer, + * and that no more data will accepted. + */ +void +abuf_hup(struct abuf *buf) +{ +#ifdef DEBUG + if (debug_level >= 3) { + abuf_dbg(buf); + dbg_puts(": hup requested\n"); + } + if (buf->rproc == NULL) { + abuf_dbg(buf); + dbg_puts(": hup, no reader\n"); + dbg_panic(); + } +#endif + buf->rproc = NULL; + LIST_REMOVE(buf, ient); + if (buf->wproc != NULL) { + if (buf->inuse) { +#ifdef DEBUG + if (debug_level >= 3) { + abuf_dbg(buf); + dbg_puts(": eof, blocked (inuse)\n"); + } +#endif + return; + } + } + abuf_hup_do(buf); +} + +/* + * Notify the reader of the change of its real-time position + */ +void +abuf_ipos(struct abuf *buf, int delta) +{ + struct aproc *p = buf->rproc; + + if (p && p->ops->ipos) { + buf->inuse++; +#ifdef DEBUG + if (debug_level >= 4) { + aproc_dbg(p); + dbg_puts(": ipos delta = "); + dbg_puti(delta); + dbg_puts("\n"); + } +#endif + p->ops->ipos(p, buf, delta); + buf->inuse--; + } + if (ABUF_HUP(buf)) + abuf_hup_do(buf); +} + +/* + * Notify the writer of the change of its real-time position + */ +void +abuf_opos(struct abuf *buf, int delta) +{ + struct aproc *p = buf->wproc; + + if (p && p->ops->opos) { + buf->inuse++; +#ifdef DEBUG + if (debug_level >= 4) { + aproc_dbg(p); + dbg_puts(": opos delta = "); + dbg_puti(delta); + dbg_puts("\n"); + } +#endif + p->ops->opos(p, buf, delta); + buf->inuse--; + } + if (ABUF_HUP(buf)) + abuf_hup_do(buf); +} diff --git a/aucat/abuf.h b/aucat/abuf.h new file mode 100644 index 0000000..6ce365d --- /dev/null +++ b/aucat/abuf.h @@ -0,0 +1,123 @@ +/* $OpenBSD: abuf.h,v 1.22 2010/04/06 20:07:01 ratchov Exp $ */ +/* + * Copyright (c) 2008 Alexandre Ratchov + * + * 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 + +#include + +#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 */ +#define MIDI_MSGMAX 16 /* max size of MIDI messaage */ + +struct aproc; +struct aparams; + +struct abuf { + LIST_ENTRY(abuf) ient; /* reader's list of inputs entry */ + LIST_ENTRY(abuf) oent; /* writer's list of outputs entry */ + + /* + * fifo parameters + */ + unsigned bpf; /* bytes per frame */ + unsigned cmin, cmax; /* channel range of this buf */ + unsigned start; /* offset where data starts */ + unsigned used; /* valid data */ + unsigned len; /* size of the ring */ + struct aproc *rproc; /* reader */ + struct aproc *wproc; /* writer */ + struct abuf *duplex; /* link to buffer of the other direction */ + unsigned inuse; /* in abuf_{flush,fill,run}() */ + unsigned tickets; /* max data to (if throttling) */ + + /* + * Misc reader aproc-specific per-buffer parameters. + */ + union { + struct { + int weight; /* dynamic range */ + int maxweight; /* max dynamic range allowed */ + unsigned vol; /* volume within the dynamic range */ + unsigned done; /* frames ready */ + unsigned xrun; /* underrun policy */ + int drop; /* frames to drop on next read */ + } mix; + struct { + unsigned st; /* MIDI running status */ + unsigned used; /* bytes used from ``msg'' */ + unsigned idx; /* actual MIDI message size */ + unsigned len; /* MIDI message length */ + unsigned char msg[MIDI_MSGMAX]; + } midi; + } r; + + /* + * Misc reader aproc-specific per-buffer parameters. + */ + union { + struct { + unsigned todo; /* frames to process */ + } mix; + struct { + unsigned done; /* frames copied */ + unsigned xrun; /* overrun policy */ + int silence; /* silence to add on next write */ + } sub; + } w; +}; + +/* + * the buffer contains at least one frame. This macro should + * be used to check if the buffer can be flushed + */ +#define ABUF_ROK(b) ((b)->used > 0) + +/* + * there's room for at least one frame + */ +#define ABUF_WOK(b) ((b)->len - (b)->used > 0) + +/* + * the buffer is empty and has no writer anymore + */ +#define ABUF_EOF(b) (!ABUF_ROK(b) && (b)->wproc == NULL) + +/* + * the buffer has no reader anymore, note that it's not + * enough the buffer to be disconnected, because it can + * be not yet connected buffer (eg. socket play buffer) + */ +#define ABUF_HUP(b) (!ABUF_WOK(b) && (b)->rproc == NULL) + +struct abuf *abuf_new(unsigned, struct aparams *); +void abuf_del(struct abuf *); +void abuf_dbg(struct abuf *); +void abuf_clear(struct abuf *); +unsigned char *abuf_rgetblk(struct abuf *, unsigned *, unsigned); +unsigned char *abuf_wgetblk(struct abuf *, unsigned *, unsigned); +void abuf_rdiscard(struct abuf *, unsigned); +void abuf_wcommit(struct abuf *, unsigned); +int abuf_fill(struct abuf *); +int abuf_flush(struct abuf *); +void abuf_eof(struct abuf *); +void abuf_hup(struct abuf *); +void abuf_run(struct abuf *); +void abuf_ipos(struct abuf *, int); +void abuf_opos(struct abuf *, int); + +#endif /* !defined(ABUF_H) */ diff --git a/aucat/amsg.h b/aucat/amsg.h new file mode 100644 index 0000000..552afa7 --- /dev/null +++ b/aucat/amsg.h @@ -0,0 +1,115 @@ +/* $OpenBSD: amsg.h,v 1.17 2010/06/05 12:45:48 ratchov Exp $ */ +/* + * Copyright (c) 2008 Alexandre Ratchov + * + * 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 AMSG_H +#define AMSG_H + +#include + +/* + * WARNING: since the protocol may be simultaneously used by static + * binaries or by different versions of a shared library, we are not + * allowed to change the packet binary representation in a backward + * incompatible way. + * + * Especially, make sure the amsg_xxx structures are not larger + * than 32 bytes. + */ +struct amsg { +#define AMSG_ACK 0 /* ack for START/STOP */ +#define AMSG_GETPAR 1 /* get the current parameters */ +#define AMSG_SETPAR 2 /* set the current parameters */ +#define AMSG_START 3 /* request the server to start the stream */ +#define AMSG_STOP 4 /* request the server to stop the stream */ +#define AMSG_DATA 5 /* data block */ +#define AMSG_POS 6 /* initial position */ +#define AMSG_MOVE 7 /* position changed */ +#define AMSG_GETCAP 8 /* get capabilities */ +#define AMSG_SETVOL 9 /* set volume */ +#define AMSG_HELLO 10 /* say hello, check versions and so ... */ +#define AMSG_BYE 11 /* ask server to drop connection */ + uint32_t cmd; + uint32_t __pad; + union { + struct amsg_par { + uint8_t legacy_mode; /* compat for old libs */ +#define AMSG_IGNORE 0 /* loose sync */ +#define AMSG_SYNC 1 /* resync after xrun */ +#define AMSG_ERROR 2 /* kill the stream */ + uint8_t xrun; /* one of above */ + uint8_t bps; /* bytes per sample */ + uint8_t bits; /* actually used bits */ + uint8_t msb; /* 1 if MSB justified */ + uint8_t le; /* 1 if little endian */ + uint8_t sig; /* 1 if signed */ + uint8_t __pad1; + uint16_t pchan; /* play channels */ + uint16_t rchan; /* record channels */ + uint32_t rate; /* frames per second */ + uint32_t bufsz; /* total buffered frames */ + uint32_t round; + uint32_t appbufsz; /* client side bufsz */ + uint32_t _reserved[1]; /* for future use */ + } par; + struct amsg_cap { + uint32_t rate; /* native rate */ + uint32_t _reserved2[1]; /* for future use */ + uint16_t rchan; /* native rec channels */ + uint16_t pchan; /* native play channels */ + uint8_t bits; /* native bits per sample */ + uint8_t bps; /* native ytes per sample */ + uint8_t _reserved[10]; /* for future use */ + } cap; + struct amsg_data { +#define AMSG_DATAMAX 0x1000 + uint32_t size; + } data; + struct amsg_ts { + int32_t delta; + } ts; + struct amsg_vol { + uint32_t ctl; + } vol; + struct amsg_hello { +#define AMSG_PLAY 0x1 /* audio playback */ +#define AMSG_REC 0x2 /* audio recording */ +#define AMSG_MIDIIN 0x4 /* MIDI thru input */ +#define AMSG_MIDIOUT 0x8 /* MIDI thru output */ +#define AMSG_MON 0x10 /* audio monitoring */ +#define AMSG_RECMASK (AMSG_REC | AMSG_MON) /* can record ? */ + uint16_t proto; /* protocol type */ +#define AMSG_VERSION 3 + uint8_t version; /* protocol version */ + uint8_t reserved1[5]; /* for future use */ + char opt[12]; /* profile name */ + char who[12]; /* hint for leases */ + } hello; + } u; +}; + +/* + * Initialize an amsg structure: fill all fields with 0xff, so the read + * can test which fields were set. + */ +#define AMSG_INIT(m) do { memset((m), 0xff, sizeof(struct amsg)); } while (0) + +/* + * Since the structure is memset to 0xff, the MSB can be used to check + * if any field was set. + */ +#define AMSG_ISSET(x) (((x) & (1 << (8 * sizeof(x) - 1))) == 0) + +#endif /* !defined(AMSG_H) */ diff --git a/aucat/aparams.c b/aucat/aparams.c new file mode 100644 index 0000000..ad3c2a1 --- /dev/null +++ b/aucat/aparams.c @@ -0,0 +1,290 @@ +/* $OpenBSD: aparams.c,v 1.10 2010/01/10 21:47:41 ratchov Exp $ */ +/* + * Copyright (c) 2008 Alexandre Ratchov + * + * 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 "aparams.h" +#ifdef DEBUG +#include "dbg.h" +#endif + +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 +}; + +/* + * Fake parameters for byte-streams + */ +struct aparams aparams_none = { 1, 0, 0, 0, 0, 0, 0, 0 }; + +/* + * 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 = NATIVE_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, unsigned cmin, unsigned cmax, unsigned rate) +{ + par->bps = 2; /* 2 bytes per sample */ + par->bits = 16; /* 16 significant bits per sample */ + par->sig = 1; /* samples are signed */ + par->le = NATIVE_LE; + par->msb = 1; /* msb justified */ + par->cmin = cmin; + par->cmax = cmax; + par->rate = rate; +} + +#ifdef DEBUG +/* + * Print the format/channels/encoding on stderr. + */ +void +aparams_dbg(struct aparams *par) +{ + char enc[ENCMAX]; + + aparams_enctostr(par, enc); + dbg_puts(enc); + dbg_puts(","); + dbg_putu(par->cmin); + dbg_puts(":"); + dbg_putu(par->cmax); + dbg_puts(","); + dbg_putu(par->rate); +} +#endif + +/* + * Return true if both encodings are the same. + */ +int +aparams_eqenc(struct aparams *par1, struct aparams *par2) +{ + if (par1->bps != par2->bps || + par1->bits != par2->bits || + par1->sig != par2->sig) + return 0; + if ((par1->bits != 8 * par1->bps) && par1->msb != par2->msb) + return 0; + if (par1->bps > 1 && par1->le != par2->le) + return 0; + return 1; +} + +/* + * Return true if both parameters are the same. + */ +int +aparams_eq(struct aparams *par1, struct aparams *par2) +{ + if (!aparams_eqenc(par1, par2) || + par1->cmin != par2->cmin || + par1->cmax != par2->cmax || + par1->rate != par2->rate) + return 0; + return 1; +} + +/* + * Return true if first channel range includes second range. + */ +int +aparams_subset(struct aparams *subset, struct aparams *set) +{ + return subset->cmin >= set->cmin && subset->cmax <= set->cmax; +} + +/* + * Grow channels range and sample rate of ``set'' in order ``subset'' to + * become an actual subset of it. + */ +void +aparams_grow(struct aparams *set, struct aparams *subset) +{ + if (set->cmin > subset->cmin) + set->cmin = subset->cmin; + if (set->cmax < subset->cmax) + set->cmax = subset->cmax; + if (set->rate < subset->rate) + set->rate = subset->rate; +} + +/* + * Return true if rates are the same. + */ +int +aparams_eqrate(struct aparams *p1, struct aparams *p2) +{ + /* XXX: allow 1/9 halftone of difference */ + return p1->rate == p2->rate; +} + + +/* + * Return the number of bytes per frame with the given parameters. + */ +unsigned +aparams_bpf(struct aparams *par) +{ + return (par->cmax - par->cmin + 1) * par->bps; +} + +void +aparams_copyenc(struct aparams *dst, struct aparams *src) +{ + dst->sig = src->sig; + dst->le = src->le; + dst->msb = src->msb; + dst->bits = src->bits; + dst->bps = src->bps; +} diff --git a/aucat/aparams.h b/aucat/aparams.h new file mode 100644 index 0000000..c36195e --- /dev/null +++ b/aucat/aparams.h @@ -0,0 +1,90 @@ +/* $OpenBSD: aparams.h,v 1.8 2009/09/27 11:51:20 ratchov Exp $ */ +/* + * Copyright (c) 2008 Alexandre Ratchov + * + * 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 APARAMS_H +#define APARAMS_H + +#include + +#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 */ + +/* + * Maximum size of the encording string (the longest possible + * encoding is ``s24le3msb''). + */ +#define ENCMAX 10 + +#if BYTE_ORDER == LITTLE_ENDIAN +#define NATIVE_LE 1 +#elif BYTE_ORDER == BIG_ENDIAN +#define NATIVE_LE 0 +#else +/* not defined */ +#endif + +/* + * Default bytes per sample for the given bits per sample. + */ +#define APARAMS_BPS(bits) (((bits) <= 8) ? 1 : (((bits) <= 16) ? 2 : 4)) + +/* + * Encoding specification. + */ +struct aparams { + unsigned bps; /* bytes per sample */ + unsigned bits; /* actually used bits */ + unsigned le; /* 1 if little endian, 0 if big endian */ + unsigned sig; /* 1 if signed, 0 if unsigned */ + unsigned msb; /* 1 if msb justified, 0 if lsb justified */ + unsigned cmin, cmax; /* provided/consumed channels */ + unsigned rate; /* frames per second */ +}; + +/* + * Samples are numbers in the interval [-1, 1[, note that 1, the upper + * boundary is excluded. We represent them in 16-bit signed fixed point + * numbers, so that we can do all multiplications and divisions in + * 32-bit precision without having to deal with overflows. + */ + +#define ADATA_SHIFT (8 * sizeof(short) - 1) +#define ADATA_UNIT (1 << ADATA_SHIFT) +#define ADATA_MAX (ADATA_UNIT - 1) +#define ADATA_MUL(x,y) (((x) * (y)) >> ADATA_SHIFT) + +#define MIDI_MAXCTL 127 +#define MIDI_TO_ADATA(m) (aparams_ctltovol[m]) + +extern int aparams_ctltovol[128]; +extern struct aparams aparams_none; + +void aparams_init(struct aparams *, unsigned, unsigned, unsigned); +void aparams_dbg(struct aparams *); +int aparams_eqrate(struct aparams *, struct aparams *); +int aparams_eqenc(struct aparams *, struct aparams *); +int aparams_eq(struct aparams *, struct aparams *); +int aparams_subset(struct aparams *, struct aparams *); +void aparams_grow(struct aparams *, struct aparams *); +unsigned aparams_bpf(struct aparams *); +int aparams_strtoenc(struct aparams *, char *); +int aparams_enctostr(struct aparams *, char *); +void aparams_copyenc(struct aparams *, struct aparams *); + +#endif /* !defined(APARAMS_H) */ diff --git a/aucat/aproc.c b/aucat/aproc.c new file mode 100644 index 0000000..be615b3 --- /dev/null +++ b/aucat/aproc.c @@ -0,0 +1,2329 @@ +/* $OpenBSD: aproc.c,v 1.59 2010/05/07 07:15:50 ratchov Exp $ */ +/* + * Copyright (c) 2008 Alexandre Ratchov + * + * 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. + */ +/* + * aproc structures are simple audio processing units. They are + * interconnected by abuf structures and form a kind of circuit. aproc + * structure have call-backs that do the actual processing. + * + * This module implements the following processing units: + * + * - rpipe: read end of an unix file (pipe, socket, device...) + * + * - wpipe: write end of an unix file (pipe, socket, device...) + * + * - mix: mix N inputs -> 1 output + * + * - sub: from 1 input -> extract/copy N outputs + * + * - conv: converts/resamples/remaps a single stream + * + * - resamp: resample streams in native format + * + */ +#include +#include +#include + +#include "abuf.h" +#include "aparams.h" +#include "aproc.h" +#include "conf.h" +#include "file.h" +#include "midi.h" +#ifdef DEBUG +#include "dbg.h" +#endif + +/* + * Same as ABUF_ROK(), but consider that a buffer is + * readable if there's silence pending to be inserted + */ +#define MIX_ROK(buf) (ABUF_ROK(buf) || (buf)->r.mix.drop < 0) + +/* + * Same as ABUF_WOK(), but consider that a buffer is + * writeable if there are samples to drop + */ +#define SUB_WOK(buf) (ABUF_WOK(buf) || (buf)->w.sub.silence < 0) + +#ifdef DEBUG +void +aproc_dbg(struct aproc *p) +{ + dbg_puts(p->ops->name); + dbg_puts("("); + dbg_puts(p->name); + dbg_puts(")"); +} + +int +zomb_in(struct aproc *p, struct abuf *ibuf) +{ + aproc_dbg(p); + dbg_puts(": in: terminated\n"); + dbg_panic(); + return 0; +} + + +int +zomb_out(struct aproc *p, struct abuf *obuf) +{ + aproc_dbg(p); + dbg_puts(": out: terminated\n"); + dbg_panic(); + return 0; +} + +void +zomb_eof(struct aproc *p, struct abuf *ibuf) +{ + aproc_dbg(p); + dbg_puts(": eof: terminated\n"); + dbg_panic(); +} + +void +zomb_hup(struct aproc *p, struct abuf *obuf) +{ + aproc_dbg(p); + dbg_puts(": hup: terminated\n"); + dbg_panic(); +} + +void +zomb_newin(struct aproc *p, struct abuf *ibuf) +{ + aproc_dbg(p); + dbg_puts(": newin: terminated\n"); + dbg_panic(); +} + +void +zomb_newout(struct aproc *p, struct abuf *obuf) +{ + aproc_dbg(p); + dbg_puts(": newout: terminated\n"); + dbg_panic(); +} + +void +zomb_ipos(struct aproc *p, struct abuf *ibuf, int delta) +{ + aproc_dbg(p); + dbg_puts(": ipos: terminated\n"); + dbg_panic(); +} + +void +zomb_opos(struct aproc *p, struct abuf *obuf, int delta) +{ + aproc_dbg(p); + dbg_puts(": opos: terminated\n"); + dbg_panic(); +} + +struct aproc_ops zomb_ops = { + "zomb", + zomb_in, + zomb_out, + zomb_eof, + zomb_hup, + zomb_newin, + zomb_newout, + zomb_ipos, + zomb_opos, + NULL +}; +#endif + +struct aproc * +aproc_new(struct aproc_ops *ops, char *name) +{ + struct aproc *p; + + p = malloc(sizeof(struct aproc)); + if (p == NULL) + err(1, name); + LIST_INIT(&p->ins); + LIST_INIT(&p->outs); + p->name = name; + p->ops = ops; + p->refs = 0; + p->flags = 0; + return p; +} + +void +aproc_del(struct aproc *p) +{ + struct abuf *i; + + if (!(p->flags & APROC_ZOMB)) { +#ifdef DEBUG + if (debug_level >= 3) { + aproc_dbg(p); + dbg_puts(": terminating...\n"); + } +#endif + if (p->ops->done) { +#ifdef DEBUG + if (debug_level >= 3) { + aproc_dbg(p); + dbg_puts(": done\n"); + } +#endif + p->ops->done(p); + } + while (!LIST_EMPTY(&p->ins)) { + i = LIST_FIRST(&p->ins); + abuf_hup(i); + } + while (!LIST_EMPTY(&p->outs)) { + i = LIST_FIRST(&p->outs); + abuf_eof(i); + } + p->flags |= APROC_ZOMB; + } + if (p->refs > 0) { +#ifdef DEBUG + if (debug_level >= 3) { + aproc_dbg(p); + dbg_puts(": free delayed\n"); + p->ops = &zomb_ops; + } +#endif + return; + } +#ifdef DEBUG + if (debug_level >= 3) { + aproc_dbg(p); + dbg_puts(": freed\n"); + } +#endif + free(p); +} + +void +aproc_setin(struct aproc *p, struct abuf *ibuf) +{ + LIST_INSERT_HEAD(&p->ins, ibuf, ient); + ibuf->rproc = p; + if (p->ops->newin) + p->ops->newin(p, ibuf); +} + +void +aproc_setout(struct aproc *p, struct abuf *obuf) +{ + LIST_INSERT_HEAD(&p->outs, obuf, oent); + obuf->wproc = p; + if (p->ops->newout) + p->ops->newout(p, obuf); +} + +void +aproc_ipos(struct aproc *p, struct abuf *ibuf, int delta) +{ + struct abuf *obuf; + + LIST_FOREACH(obuf, &p->outs, oent) { + abuf_ipos(obuf, delta); + } +} + +void +aproc_opos(struct aproc *p, struct abuf *obuf, int delta) +{ + struct abuf *ibuf; + + LIST_FOREACH(ibuf, &p->ins, ient) { + abuf_opos(ibuf, delta); + } +} + +int +aproc_inuse(struct aproc *p) +{ + struct abuf *i; + + LIST_FOREACH(i, &p->ins, ient) { + if (i->inuse) + return 1; + } + LIST_FOREACH(i, &p->outs, oent) { + if (i->inuse) + return 1; + } + return 0; +} + +int +aproc_depend(struct aproc *p, struct aproc *dep) +{ + struct abuf *i; + + if (p == dep) + return 1; + LIST_FOREACH(i, &p->ins, ient) { + if (i->wproc && aproc_depend(i->wproc, dep)) + return 1; + } + return 0; +} + +int +rfile_do(struct aproc *p, unsigned todo, unsigned *done) +{ + struct abuf *obuf = LIST_FIRST(&p->outs); + struct file *f = p->u.io.file; + unsigned char *data; + unsigned n, count, off; + + off = p->u.io.partial; + data = abuf_wgetblk(obuf, &count, 0); + if (count > todo) + count = todo; + n = file_read(f, data + off, count * obuf->bpf - off); + if (n == 0) + return 0; + n += off; + p->u.io.partial = n % obuf->bpf; + count = n / obuf->bpf; + if (count > 0) + abuf_wcommit(obuf, count); + if (done) + *done = count; + return 1; +} + +int +rfile_in(struct aproc *p, struct abuf *ibuf_dummy) +{ + struct abuf *obuf = LIST_FIRST(&p->outs); + struct file *f = p->u.io.file; + + if (!ABUF_WOK(obuf) || !(f->state & FILE_ROK)) + return 0; + if (!rfile_do(p, obuf->len, NULL)) + return 0; + if (!abuf_flush(obuf)) + return 0; + return 1; +} + +int +rfile_out(struct aproc *p, struct abuf *obuf) +{ + struct file *f = p->u.io.file; + + if (f->state & FILE_RINUSE) + return 0; + if (!ABUF_WOK(obuf) || !(f->state & FILE_ROK)) + return 0; + if (!rfile_do(p, obuf->len, NULL)) + return 0; + return 1; +} + +void +rfile_done(struct aproc *p) +{ + struct file *f = p->u.io.file; + struct abuf *obuf; + + if (f == NULL) + return; + /* + * disconnect from file structure + */ + f->rproc = NULL; + p->u.io.file = NULL; + + /* + * all buffers must be detached before deleting f->wproc, + * because otherwise it could trigger this code again + */ + obuf = LIST_FIRST(&p->outs); + if (obuf) + abuf_eof(obuf); + if (f->wproc) { + aproc_del(f->wproc); + } else + file_del(f); + +#ifdef DEBUG + if (debug_level >= 2 && p->u.io.partial > 0) { + aproc_dbg(p); + dbg_puts(": "); + dbg_putu(p->u.io.partial); + dbg_puts(" bytes lost in partial read\n"); + } +#endif +} + +void +rfile_eof(struct aproc *p, struct abuf *ibuf_dummy) +{ + aproc_del(p); +} + +void +rfile_hup(struct aproc *p, struct abuf *obuf) +{ + aproc_del(p); +} + +struct aproc_ops rfile_ops = { + "rfile", + rfile_in, + rfile_out, + rfile_eof, + rfile_hup, + NULL, /* newin */ + NULL, /* newout */ + aproc_ipos, + aproc_opos, + rfile_done +}; + +struct aproc * +rfile_new(struct file *f) +{ + struct aproc *p; + + p = aproc_new(&rfile_ops, f->name); + p->u.io.file = f; + p->u.io.partial = 0; + f->rproc = p; + return p; +} + +void +wfile_done(struct aproc *p) +{ + struct file *f = p->u.io.file; + struct abuf *ibuf; + + if (f == NULL) + return; + /* + * disconnect from file structure + */ + f->wproc = NULL; + p->u.io.file = NULL; + + /* + * all buffers must be detached before deleting f->rproc, + * because otherwise it could trigger this code again + */ + ibuf = LIST_FIRST(&p->ins); + if (ibuf) + abuf_hup(ibuf); + if (f->rproc) { + aproc_del(f->rproc); + } else + file_del(f); +#ifdef DEBUG + if (debug_level >= 2 && p->u.io.partial > 0) { + aproc_dbg(p); + dbg_puts(": "); + dbg_putu(p->u.io.partial); + dbg_puts(" bytes lost in partial write\n"); + } +#endif +} + +int +wfile_do(struct aproc *p, unsigned todo, unsigned *done) +{ + struct abuf *ibuf = LIST_FIRST(&p->ins); + struct file *f = p->u.io.file; + unsigned char *data; + unsigned n, count, off; + + off = p->u.io.partial; + data = abuf_rgetblk(ibuf, &count, 0); + if (count > todo) + count = todo; + n = file_write(f, data + off, count * ibuf->bpf - off); + if (n == 0) + return 0; + n += off; + p->u.io.partial = n % ibuf->bpf; + count = n / ibuf->bpf; + if (count > 0) + abuf_rdiscard(ibuf, count); + if (done) + *done = count; + return 1; +} +int +wfile_in(struct aproc *p, struct abuf *ibuf) +{ + struct file *f = p->u.io.file; + + if (f->state & FILE_WINUSE) + return 0; + if (!ABUF_ROK(ibuf) || !(f->state & FILE_WOK)) + return 0; + if (!wfile_do(p, ibuf->len, NULL)) + return 0; + return 1; +} + +int +wfile_out(struct aproc *p, struct abuf *obuf_dummy) +{ + struct abuf *ibuf = LIST_FIRST(&p->ins); + struct file *f = p->u.io.file; + + if (!abuf_fill(ibuf)) + return 0; + if (!ABUF_ROK(ibuf) || !(f->state & FILE_WOK)) + return 0; + if (!wfile_do(p, ibuf->len, NULL)) + return 0; + return 1; +} + +void +wfile_eof(struct aproc *p, struct abuf *ibuf) +{ + aproc_del(p); +} + +void +wfile_hup(struct aproc *p, struct abuf *obuf_dummy) +{ + aproc_del(p); +} + +struct aproc_ops wfile_ops = { + "wfile", + wfile_in, + wfile_out, + wfile_eof, + wfile_hup, + NULL, /* newin */ + NULL, /* newout */ + aproc_ipos, + aproc_opos, + wfile_done +}; + +struct aproc * +wfile_new(struct file *f) +{ + struct aproc *p; + + p = aproc_new(&wfile_ops, f->name); + p->u.io.file = f; + p->u.io.partial = 0; + f->wproc = p; + return p; +} + +/* + * Drop as much as possible samples from the reader end, + * negative values mean ``insert silence''. + */ +void +mix_drop(struct abuf *buf, int extra) +{ + unsigned count; + + buf->r.mix.drop += extra; + while (buf->r.mix.drop > 0) { + count = buf->r.mix.drop; + if (count > buf->used) + count = buf->used; + if (count == 0) { +#ifdef DEBUG + if (debug_level >= 4) { + abuf_dbg(buf); + dbg_puts(": drop: no data\n"); + } +#endif + return; + } + abuf_rdiscard(buf, count); + buf->r.mix.drop -= count; +#ifdef DEBUG + if (debug_level >= 4) { + abuf_dbg(buf); + dbg_puts(": dropped "); + dbg_putu(count); + dbg_puts(", to drop = "); + dbg_putu(buf->r.mix.drop); + dbg_puts("\n"); + } +#endif + } +} + +/* + * Append the necessary amount of silence, in a way + * obuf->w.mix.todo doesn't exceed the given value + */ +void +mix_bzero(struct abuf *obuf, unsigned maxtodo) +{ + short *odata; + unsigned ocount, todo; + + if (obuf->w.mix.todo >= maxtodo) + return; + todo = maxtodo - obuf->w.mix.todo; + odata = (short *)abuf_wgetblk(obuf, &ocount, obuf->w.mix.todo); + if (ocount > todo) + ocount = todo; + if (ocount == 0) + return; + memset(odata, 0, ocount * obuf->bpf); + obuf->w.mix.todo += ocount; +#ifdef DEBUG + if (debug_level >= 4) { + abuf_dbg(obuf); + dbg_puts(": bzero("); + dbg_putu(obuf->w.mix.todo); + dbg_puts(")\n"); + } +#endif +} + +/* + * Mix an input block over an output block. + */ +unsigned +mix_badd(struct abuf *ibuf, struct abuf *obuf) +{ + short *idata, *odata; + unsigned cmin, cmax; + unsigned i, j, cc, istart, inext, onext, ostart; + unsigned scount, icount, ocount; + int vol; + +#ifdef DEBUG + if (debug_level >= 4) { + abuf_dbg(ibuf); + dbg_puts(": badd: done = "); + dbg_putu(ibuf->r.mix.done); + dbg_puts("/"); + dbg_putu(obuf->w.mix.todo); + dbg_puts(", drop = "); + dbg_puti(ibuf->r.mix.drop); + dbg_puts("\n"); + } +#endif + /* + * Insert silence for xrun correction + */ + while (ibuf->r.mix.drop < 0) { + icount = -ibuf->r.mix.drop; + mix_bzero(obuf, ibuf->r.mix.done + icount); + ocount = obuf->w.mix.todo - ibuf->r.mix.done; + if (ocount == 0) + return 0; + scount = (icount < ocount) ? icount : ocount; + ibuf->r.mix.done += scount; + ibuf->r.mix.drop += scount; + } + + /* + * Calculate the maximum we can read. + */ + idata = (short *)abuf_rgetblk(ibuf, &icount, 0); + if (icount == 0) + return 0; + + /* + * Calculate the maximum we can write. + */ + odata = (short *)abuf_wgetblk(obuf, &ocount, ibuf->r.mix.done); + if (ocount == 0) + return 0; + + scount = (icount < ocount) ? icount : ocount; + mix_bzero(obuf, scount + ibuf->r.mix.done); + + vol = (ibuf->r.mix.weight * ibuf->r.mix.vol) >> ADATA_SHIFT; + cmin = obuf->cmin > ibuf->cmin ? obuf->cmin : ibuf->cmin; + cmax = obuf->cmax < ibuf->cmax ? obuf->cmax : ibuf->cmax; + ostart = cmin - obuf->cmin; + istart = cmin - ibuf->cmin; + onext = obuf->cmax - cmax + ostart; + inext = ibuf->cmax - cmax + istart; + cc = cmax - cmin + 1; + odata += ostart; + idata += istart; + for (i = scount; i > 0; i--) { + for (j = cc; j > 0; j--) { + *odata += (*idata * vol) >> ADATA_SHIFT; + idata++; + odata++; + } + odata += onext; + idata += inext; + } + abuf_rdiscard(ibuf, scount); + ibuf->r.mix.done += scount; + +#ifdef DEBUG + if (debug_level >= 4) { + abuf_dbg(ibuf); + dbg_puts(": badd: done = "); + dbg_putu(ibuf->r.mix.done); + dbg_puts("/"); + dbg_putu(obuf->w.mix.todo); + dbg_puts("\n"); + } +#endif + return scount; +} + +/* + * Handle buffer underrun, return 0 if stream died. + */ +int +mix_xrun(struct aproc *p, struct abuf *i) +{ + struct abuf *obuf = LIST_FIRST(&p->outs); + unsigned fdrop, remain; + + if (i->r.mix.done > 0) + return 1; + if (i->r.mix.xrun == XRUN_ERROR) { + abuf_hup(i); + return 0; + } + fdrop = obuf->w.mix.todo; +#ifdef DEBUG + if (debug_level >= 3) { + abuf_dbg(i); + dbg_puts(": underrun, dropping "); + dbg_putu(fdrop); + dbg_puts(" + "); + dbg_putu(i->r.mix.drop); + dbg_puts("\n"); + } +#endif + i->r.mix.done += fdrop; + if (i->r.mix.xrun == XRUN_SYNC) + mix_drop(i, fdrop); + else { + remain = fdrop % p->u.mix.round; + if (remain) + remain = p->u.mix.round - remain; + mix_drop(i, -(int)remain); + fdrop += remain; +#ifdef DEBUG + if (debug_level >= 3) { + abuf_dbg(i); + dbg_puts(": underrun, adding "); + dbg_putu(remain); + dbg_puts("\n"); + } +#endif + abuf_opos(i, -(int)fdrop); + if (i->duplex) { +#ifdef DEBUG + if (debug_level >= 3) { + abuf_dbg(i->duplex); + dbg_puts(": full-duplex resync\n"); + } +#endif + sub_silence(i->duplex, -(int)fdrop); + abuf_ipos(i->duplex, -(int)fdrop); + } + } + return 1; +} + +int +mix_in(struct aproc *p, struct abuf *ibuf) +{ + struct abuf *i, *inext, *obuf = LIST_FIRST(&p->outs); + unsigned odone; + unsigned maxwrite; + unsigned scount; + +#ifdef DEBUG + if (debug_level >= 4) { + aproc_dbg(p); + dbg_puts(": used = "); + dbg_putu(ibuf->used); + dbg_puts("/"); + dbg_putu(ibuf->len); + dbg_puts(", done = "); + dbg_putu(ibuf->r.mix.done); + dbg_puts("/"); + dbg_putu(obuf->w.mix.todo); + dbg_puts("\n"); + } +#endif + if (!MIX_ROK(ibuf)) + return 0; + scount = 0; + odone = obuf->len; + for (i = LIST_FIRST(&p->ins); i != NULL; i = inext) { + inext = LIST_NEXT(i, ient); + if (i->r.mix.drop >= 0 && !abuf_fill(i)) + continue; /* eof */ + mix_drop(i, 0); + scount += mix_badd(i, obuf); + if (odone > i->r.mix.done) + odone = i->r.mix.done; + } + if (LIST_EMPTY(&p->ins) || scount == 0) + return 0; +#ifdef DEBUG + if (debug_level >= 4) { + aproc_dbg(p); + dbg_puts(": maxwrite = "); + dbg_putu(p->u.mix.maxlat); + dbg_puts(" - "); + dbg_putu(p->u.mix.lat); + dbg_puts(" = "); + dbg_putu(p->u.mix.maxlat - p->u.mix.lat); + dbg_puts("\n"); + } +#endif + maxwrite = p->u.mix.maxlat - p->u.mix.lat; + if (maxwrite > 0) { + if (odone > maxwrite) + odone = maxwrite; + p->u.mix.lat += odone; + LIST_FOREACH(i, &p->ins, ient) { + i->r.mix.done -= odone; + } + abuf_wcommit(obuf, odone); + obuf->w.mix.todo -= odone; + if (APROC_OK(p->u.mix.mon)) + mon_snoop(p->u.mix.mon, obuf, obuf->used - odone, odone); + if (!abuf_flush(obuf)) + return 0; /* hup */ + } + return 1; +} + +int +mix_out(struct aproc *p, struct abuf *obuf) +{ + struct abuf *i, *inext; + unsigned odone; + unsigned maxwrite; + unsigned scount; + +#ifdef DEBUG + if (debug_level >= 4) { + aproc_dbg(p); + dbg_puts(": used = "); + dbg_putu(obuf->used); + dbg_puts("/"); + dbg_putu(obuf->len); + dbg_puts(", todo = "); + dbg_putu(obuf->w.mix.todo); + dbg_puts("/"); + dbg_putu(obuf->len); + dbg_puts("\n"); + } +#endif + if (!ABUF_WOK(obuf)) + return 0; +#ifdef DEBUG + if (debug_level >= 4) { + aproc_dbg(p); + dbg_puts(": maxwrite = "); + dbg_putu(p->u.mix.maxlat); + dbg_puts(" - "); + dbg_putu(p->u.mix.lat); + dbg_puts(" = "); + dbg_putu(p->u.mix.maxlat - p->u.mix.lat); + dbg_puts("\n"); + } +#endif + maxwrite = p->u.mix.maxlat - p->u.mix.lat; + if (maxwrite > obuf->w.mix.todo) { + if ((p->flags & (APROC_QUIT | APROC_DROP)) == APROC_DROP) + mix_bzero(obuf, maxwrite); + } + scount = 0; + odone = obuf->len; + for (i = LIST_FIRST(&p->ins); i != NULL; i = inext) { + inext = LIST_NEXT(i, ient); + if (i->r.mix.drop >= 0 && !abuf_fill(i)) + continue; /* eof */ + mix_drop(i, 0); + if (maxwrite > 0 && !MIX_ROK(i)) { + if (p->flags & APROC_DROP) { + if (!mix_xrun(p, i)) + continue; + } + } else + scount += mix_badd(i, obuf); + if (odone > i->r.mix.done) + odone = i->r.mix.done; + } + if (LIST_EMPTY(&p->ins) && obuf->w.mix.todo == 0) { + if (p->flags & APROC_QUIT) { + aproc_del(p); + return 0; + } + if (!(p->flags & APROC_DROP)) + return 0; + } + if (odone > obuf->w.mix.todo) + odone = obuf->w.mix.todo; + if (odone > maxwrite) + odone = maxwrite; + if (odone > 0) { + p->u.mix.lat += odone; + LIST_FOREACH(i, &p->ins, ient) { + i->r.mix.done -= odone; + } + abuf_wcommit(obuf, odone); + obuf->w.mix.todo -= odone; + if (APROC_OK(p->u.mix.mon)) + mon_snoop(p->u.mix.mon, obuf, obuf->used - odone, odone); + } + if (LIST_EMPTY(&p->ins)) + p->u.mix.idle += odone; + if (scount == 0) + return 0; + return 1; +} + +void +mix_eof(struct aproc *p, struct abuf *ibuf) +{ + struct abuf *i, *inext, *obuf = LIST_FIRST(&p->outs); + unsigned odone; + + mix_setmaster(p); + + if (!aproc_inuse(p)) { +#ifdef DEBUG + if (debug_level >= 3) { + aproc_dbg(p); + dbg_puts(": running other streams\n"); + } +#endif + /* + * Find a blocked input. + */ + odone = obuf->len; + for (i = LIST_FIRST(&p->ins); i != NULL; i = inext) { + inext = LIST_NEXT(i, ient); + if (!abuf_fill(i)) + continue; + if (MIX_ROK(i) && i->r.mix.done < obuf->w.mix.todo) { + abuf_run(i); + return; + } + if (odone > i->r.mix.done) + odone = i->r.mix.done; + } + /* + * No blocked inputs. Check if output is blocked. + */ + if (LIST_EMPTY(&p->ins) || odone == obuf->w.mix.todo) + abuf_run(obuf); + } +} + +void +mix_hup(struct aproc *p, struct abuf *obuf) +{ + aproc_del(p); +} + +void +mix_newin(struct aproc *p, struct abuf *ibuf) +{ + p->u.mix.idle = 0; + ibuf->r.mix.done = 0; + ibuf->r.mix.vol = ADATA_UNIT; + ibuf->r.mix.weight = ADATA_UNIT; + ibuf->r.mix.maxweight = ADATA_UNIT; + ibuf->r.mix.xrun = XRUN_IGNORE; + ibuf->r.mix.drop = 0; +} + +void +mix_newout(struct aproc *p, struct abuf *obuf) +{ +#ifdef DEBUG + if (debug_level >= 3) { + aproc_dbg(p); + dbg_puts(": newin, will use "); + dbg_putu(obuf->len); + dbg_puts("\n"); + } +#endif + obuf->w.mix.todo = 0; +} + +void +mix_opos(struct aproc *p, struct abuf *obuf, int delta) +{ + p->u.mix.lat -= delta; +#ifdef DEBUG + if (debug_level >= 4) { + aproc_dbg(p); + dbg_puts(": opos: lat = "); + dbg_puti(p->u.mix.lat); + dbg_puts("/"); + dbg_puti(p->u.mix.maxlat); + dbg_puts("\n"); + } +#endif + if (APROC_OK(p->u.mix.ctl)) + ctl_ontick(p->u.mix.ctl, delta); + aproc_opos(p, obuf, delta); + if (APROC_OK(p->u.mix.mon)) + p->u.mix.mon->ops->ipos(p->u.mix.mon, NULL, delta); +} + +struct aproc_ops mix_ops = { + "mix", + mix_in, + mix_out, + mix_eof, + mix_hup, + mix_newin, + mix_newout, + aproc_ipos, + mix_opos, + NULL +}; + +struct aproc * +mix_new(char *name, int maxlat, unsigned round) +{ + struct aproc *p; + + p = aproc_new(&mix_ops, name); + p->u.mix.idle = 0; + p->u.mix.lat = 0; + p->u.mix.round = round; + p->u.mix.maxlat = maxlat; + p->u.mix.ctl = NULL; + p->u.mix.mon = NULL; + return p; +} + +/* + * Normalize input levels. + */ +void +mix_setmaster(struct aproc *p) +{ + unsigned n; + struct abuf *i, *j; + int weight; + + /* + * count the number of inputs. If a set of inputs + * uses channels that have no intersection, they are + * counted only once because they don't need to + * share their volume + */ + n = 0; + LIST_FOREACH(i, &p->ins, ient) { + j = LIST_NEXT(i, ient); + for (;;) { + if (j == NULL) { + n++; + break; + } + if (i->cmin > j->cmax || i->cmax < j->cmin) + break; + j = LIST_NEXT(j, ient); + } + } + LIST_FOREACH(i, &p->ins, ient) { + weight = ADATA_UNIT / n; + if (weight > i->r.mix.maxweight) + weight = i->r.mix.maxweight; + i->r.mix.weight = weight; +#ifdef DEBUG + if (debug_level >= 3) { + abuf_dbg(i); + dbg_puts(": setmaster: "); + dbg_puti(i->r.mix.weight); + dbg_puts("/"); + dbg_puti(i->r.mix.maxweight); + dbg_puts("\n"); + } +#endif + } +} + +void +mix_clear(struct aproc *p) +{ + struct abuf *obuf = LIST_FIRST(&p->outs); + + p->u.mix.lat = 0; + obuf->w.mix.todo = 0; +} + +void +mix_prime(struct aproc *p) +{ + struct abuf *obuf = LIST_FIRST(&p->outs); + unsigned todo, count; + + for (;;) { + if (!ABUF_WOK(obuf)) + break; + todo = p->u.mix.maxlat - p->u.mix.lat; + mix_bzero(obuf, todo); + count = obuf->w.mix.todo; + if (count > todo) + count = todo; + if (count == 0) + break; + obuf->w.mix.todo -= count; + p->u.mix.lat += count; + abuf_wcommit(obuf, count); + if (APROC_OK(p->u.mix.mon)) + mon_snoop(p->u.mix.mon, obuf, 0, count); + abuf_flush(obuf); + } +#ifdef DEBUG + if (debug_level >= 3) { + aproc_dbg(p); + dbg_puts(": prime: lat/maxlat="); + dbg_puti(p->u.mix.lat); + dbg_puts("/"); + dbg_puti(p->u.mix.maxlat); + dbg_puts("\n"); + } +#endif +} + +/* + * Gracefully terminate the mixer: raise the APROC_QUIT flag + * and let the rest of the code do the job. If there are neither + * inputs nor uncommited data, then terminate right away + */ +void +mix_quit(struct aproc *p) +{ + struct abuf *obuf = LIST_FIRST(&p->outs); + + p->flags |= APROC_QUIT; + + /* + * eof the last input will trigger aproc_del() + */ + if (!LIST_EMPTY(&p->ins) || obuf->w.mix.todo > 0) + return; + aproc_del(p); +} + +/* + * Append as much as possible silence on the writer end + */ +void +sub_silence(struct abuf *buf, int extra) +{ + unsigned char *data; + unsigned count; + + buf->w.sub.silence += extra; + if (buf->w.sub.silence > 0) { + data = abuf_wgetblk(buf, &count, 0); + if (count >= buf->w.sub.silence) + count = buf->w.sub.silence; + if (count == 0) { +#ifdef DEBUG + if (debug_level >= 4) { + abuf_dbg(buf); + dbg_puts(": no space for silence\n"); + } +#endif + return; + } + memset(data, 0, count * buf->bpf); + abuf_wcommit(buf, count); + buf->w.sub.silence -= count; +#ifdef DEBUG + if (debug_level >= 4) { + abuf_dbg(buf); + dbg_puts(": appended "); + dbg_putu(count); + dbg_puts(", remaining silence = "); + dbg_putu(buf->w.sub.silence); + dbg_puts("\n"); + } +#endif + } +} + +/* + * Copy data from ibuf to obuf. + */ +void +sub_bcopy(struct abuf *ibuf, struct abuf *obuf) +{ + short *idata, *odata; + unsigned cmin, cmax; + unsigned i, j, cc, istart, inext, onext, ostart; + unsigned icount, ocount, scount; + + /* + * Drop samples for xrun correction + */ + if (obuf->w.sub.silence < 0) { + scount = -obuf->w.sub.silence; + if (scount > ibuf->used) + scount = ibuf->used; + obuf->w.sub.done += scount; + obuf->w.sub.silence += scount; + } + + idata = (short *)abuf_rgetblk(ibuf, &icount, obuf->w.sub.done); + if (icount == 0) + return; + odata = (short *)abuf_wgetblk(obuf, &ocount, 0); + if (ocount == 0) + return; + cmin = obuf->cmin > ibuf->cmin ? obuf->cmin : ibuf->cmin; + cmax = obuf->cmax < ibuf->cmax ? obuf->cmax : ibuf->cmax; + ostart = cmin - obuf->cmin; + istart = cmin - ibuf->cmin; + onext = obuf->cmax - cmax; + inext = ibuf->cmax - cmax + istart; + cc = cmax - cmin + 1; + idata += istart; + scount = (icount < ocount) ? icount : ocount; + for (i = scount; i > 0; i--) { + for (j = ostart; j > 0; j--) + *odata++ = 0x1111; + for (j = cc; j > 0; j--) { + *odata = *idata; + odata++; + idata++; + } + for (j = onext; j > 0; j--) + *odata++ = 0x2222; + idata += inext; + } + abuf_wcommit(obuf, scount); + obuf->w.sub.done += scount; +#ifdef DEBUG + if (debug_level >= 4) { + abuf_dbg(obuf); + dbg_puts(": bcopy "); + dbg_putu(scount); + dbg_puts("\n"); + } +#endif +} + +/* + * Handle buffer overruns. Return 0 if the stream died. + */ +int +sub_xrun(struct aproc *p, struct abuf *i) +{ + struct abuf *ibuf = LIST_FIRST(&p->ins); + unsigned fdrop, remain; + + if (i->w.sub.done > 0) + return 1; + if (i->w.sub.xrun == XRUN_ERROR) { + abuf_eof(i); + return 0; + } + fdrop = ibuf->used; +#ifdef DEBUG + if (debug_level >= 3) { + abuf_dbg(i); + dbg_puts(": overrun, silence "); + dbg_putu(fdrop); + dbg_puts(" + "); + dbg_putu(i->w.sub.silence); + dbg_puts("\n"); + } +#endif + i->w.sub.done += fdrop; + if (i->w.sub.xrun == XRUN_SYNC) + sub_silence(i, fdrop); + else { + remain = fdrop % p->u.sub.round; + if (remain) + remain = p->u.sub.round - remain; + sub_silence(i, -(int)remain); + fdrop += remain; +#ifdef DEBUG + if (debug_level >= 3) { + abuf_dbg(i); + dbg_puts(": overrun, adding "); + dbg_putu(remain); + dbg_puts("\n"); + } +#endif + + abuf_ipos(i, -(int)fdrop); + if (i->duplex) { +#ifdef DEBUG + if (debug_level >= 3) { + abuf_dbg(i->duplex); + dbg_puts(": full-duplex resync\n"); + } +#endif + mix_drop(i->duplex, -(int)fdrop); + abuf_opos(i->duplex, -(int)fdrop); + } + } + return 1; +} + +int +sub_in(struct aproc *p, struct abuf *ibuf) +{ + struct abuf *i, *inext; + unsigned idone; + + if (!ABUF_ROK(ibuf)) + return 0; + idone = ibuf->len; + for (i = LIST_FIRST(&p->outs); i != NULL; i = inext) { + inext = LIST_NEXT(i, oent); + sub_silence(i, 0); + if (!SUB_WOK(i)) { + if (p->flags & APROC_DROP) { + if (!sub_xrun(p, i)) + continue; + } + } else + sub_bcopy(ibuf, i); + if (idone > i->w.sub.done) + idone = i->w.sub.done; + if (!abuf_flush(i)) + continue; + } + if (LIST_EMPTY(&p->outs)) { + if (p->flags & APROC_QUIT) { + aproc_del(p); + return 0; + } + if (!(p->flags & APROC_DROP)) + return 0; + idone = ibuf->used; + p->u.sub.idle += idone; + } + if (idone == 0) + return 0; + LIST_FOREACH(i, &p->outs, oent) { + i->w.sub.done -= idone; + } + abuf_rdiscard(ibuf, idone); + abuf_opos(ibuf, idone); + p->u.sub.lat -= idone; + return 1; +} + +int +sub_out(struct aproc *p, struct abuf *obuf) +{ + struct abuf *ibuf = LIST_FIRST(&p->ins); + struct abuf *i, *inext; + unsigned idone; + + if (!SUB_WOK(obuf)) + return 0; + if (!abuf_fill(ibuf)) + return 0; /* eof */ + idone = ibuf->len; + for (i = LIST_FIRST(&p->outs); i != NULL; i = inext) { + inext = LIST_NEXT(i, oent); + sub_silence(i, 0); + sub_bcopy(ibuf, i); + if (idone > i->w.sub.done) + idone = i->w.sub.done; + if (!abuf_flush(i)) + continue; + } + if (LIST_EMPTY(&p->outs) || idone == 0) + return 0; + LIST_FOREACH(i, &p->outs, oent) { + i->w.sub.done -= idone; + } + abuf_rdiscard(ibuf, idone); + abuf_opos(ibuf, idone); + p->u.sub.lat -= idone; + return 1; +} + +void +sub_eof(struct aproc *p, struct abuf *ibuf) +{ + aproc_del(p); +} + +void +sub_hup(struct aproc *p, struct abuf *obuf) +{ + struct abuf *i, *inext, *ibuf = LIST_FIRST(&p->ins); + unsigned idone; + + if (!aproc_inuse(p)) { +#ifdef DEBUG + if (debug_level >= 3) { + aproc_dbg(p); + dbg_puts(": running other streams\n"); + } +#endif + /* + * Find a blocked output. + */ + idone = ibuf->len; + for (i = LIST_FIRST(&p->outs); i != NULL; i = inext) { + inext = LIST_NEXT(i, oent); + if (!abuf_flush(i)) + continue; + if (SUB_WOK(i) && i->w.sub.done < ibuf->used) { + abuf_run(i); + return; + } + if (idone > i->w.sub.done) + idone = i->w.sub.done; + } + /* + * No blocked outputs. Check if input is blocked. + */ + if (LIST_EMPTY(&p->outs) || idone == ibuf->used) + abuf_run(ibuf); + } +} + +void +sub_newout(struct aproc *p, struct abuf *obuf) +{ + p->u.sub.idle = 0; + obuf->w.sub.done = 0; + obuf->w.sub.xrun = XRUN_IGNORE; + obuf->w.sub.silence = 0; +} + +void +sub_ipos(struct aproc *p, struct abuf *ibuf, int delta) +{ + p->u.sub.lat += delta; +#ifdef DEBUG + if (debug_level >= 4) { + aproc_dbg(p); + dbg_puts(": ipos: lat = "); + dbg_puti(p->u.sub.lat); + dbg_puts("/"); + dbg_puti(p->u.sub.maxlat); + dbg_puts("\n"); + } +#endif + if (APROC_OK(p->u.sub.ctl)) + ctl_ontick(p->u.sub.ctl, delta); + aproc_ipos(p, ibuf, delta); +} + +struct aproc_ops sub_ops = { + "sub", + sub_in, + sub_out, + sub_eof, + sub_hup, + NULL, + sub_newout, + sub_ipos, + aproc_opos, + NULL +}; + +struct aproc * +sub_new(char *name, int maxlat, unsigned round) +{ + struct aproc *p; + + p = aproc_new(&sub_ops, name); + p->u.sub.idle = 0; + p->u.sub.lat = 0; + p->u.sub.round = round; + p->u.sub.maxlat = maxlat; + p->u.sub.ctl = NULL; + return p; +} + +void +sub_clear(struct aproc *p) +{ + p->u.sub.lat = 0; +} + +/* + * Convert one block. + */ +void +resamp_bcopy(struct aproc *p, struct abuf *ibuf, struct abuf *obuf) +{ + unsigned inch; + short *idata; + unsigned oblksz; + unsigned ifr; + unsigned onch; + int s1, s2, diff; + short *odata; + unsigned iblksz; + unsigned ofr; + unsigned c; + short *ctxbuf, *ctx; + unsigned ctx_start; + unsigned icount, ocount; + + /* + * Calculate max frames readable at once from the input buffer. + */ + idata = (short *)abuf_rgetblk(ibuf, &icount, 0); + ifr = icount; + + odata = (short *)abuf_wgetblk(obuf, &ocount, 0); + ofr = ocount; + + /* + * Partially copy structures into local variables, to avoid + * unnecessary indirections; this also allows the compiler to + * order local variables more "cache-friendly". + */ + diff = p->u.resamp.diff; + inch = ibuf->cmax - ibuf->cmin + 1; + iblksz = p->u.resamp.iblksz; + onch = obuf->cmax - obuf->cmin + 1; + oblksz = p->u.resamp.oblksz; + ctxbuf = p->u.resamp.ctx; + ctx_start = p->u.resamp.ctx_start; + + /* + * Start conversion. + */ +#ifdef DEBUG + if (debug_level >= 4) { + aproc_dbg(p); + dbg_puts(": resamp starting diff = "); + dbg_puti(diff); + dbg_puts(", ifr = "); + dbg_putu(ifr); + dbg_puts(", ofr = "); + dbg_putu(ofr); + dbg_puts(" fr\n"); + } +#endif + for (;;) { + if (diff < 0) { + if (ifr == 0) + break; + ctx_start ^= 1; + ctx = ctxbuf + ctx_start; + for (c = inch; c > 0; c--) { + *ctx = *idata++; + ctx += RESAMP_NCTX; + } + diff += oblksz; + ifr--; + } else if (diff > 0) { + if (ofr == 0) + break; + ctx = ctxbuf; + for (c = onch; c > 0; c--) { + s1 = ctx[ctx_start]; + s2 = ctx[ctx_start ^ 1]; + ctx += RESAMP_NCTX; + *odata++ = s1 + (s2 - s1) * diff / (int)oblksz; + } + diff -= iblksz; + ofr--; + } else { + if (ifr == 0 || ofr == 0) + break; + ctx = ctxbuf + ctx_start; + for (c = onch; c > 0; c--) { + *odata++ = *ctx; + ctx += RESAMP_NCTX; + } + ctx_start ^= 1; + ctx = ctxbuf + ctx_start; + for (c = inch; c > 0; c--) { + *ctx = *idata++; + ctx += RESAMP_NCTX; + } + diff -= iblksz; + diff += oblksz; + ifr--; + ofr--; + } + } + p->u.resamp.diff = diff; + p->u.resamp.ctx_start = ctx_start; +#ifdef DEBUG + if (debug_level >= 4) { + aproc_dbg(p); + dbg_puts(": resamp done delta = "); + dbg_puti(diff); + dbg_puts(", ifr = "); + dbg_putu(ifr); + dbg_puts(", ofr = "); + dbg_putu(ofr); + dbg_puts(" fr\n"); + } +#endif + /* + * Update FIFO pointers. + */ + icount -= ifr; + ocount -= ofr; + abuf_rdiscard(ibuf, icount); + abuf_wcommit(obuf, ocount); +} + +int +resamp_in(struct aproc *p, struct abuf *ibuf) +{ + struct abuf *obuf = LIST_FIRST(&p->outs); + + if (!ABUF_WOK(obuf) || !ABUF_ROK(ibuf)) + return 0; + resamp_bcopy(p, ibuf, obuf); + if (!abuf_flush(obuf)) + return 0; + return 1; +} + +int +resamp_out(struct aproc *p, struct abuf *obuf) +{ + struct abuf *ibuf = LIST_FIRST(&p->ins); + + if (!abuf_fill(ibuf)) + return 0; + if (!ABUF_WOK(obuf) || !ABUF_ROK(ibuf)) + return 0; + resamp_bcopy(p, ibuf, obuf); + return 1; +} + +void +resamp_eof(struct aproc *p, struct abuf *ibuf) +{ + aproc_del(p); +} + +void +resamp_hup(struct aproc *p, struct abuf *obuf) +{ + aproc_del(p); +} + +void +resamp_ipos(struct aproc *p, struct abuf *ibuf, int delta) +{ + struct abuf *obuf = LIST_FIRST(&p->outs); + long long ipos; + + ipos = (long long)delta * p->u.resamp.oblksz + p->u.resamp.idelta; + p->u.resamp.idelta = ipos % p->u.resamp.iblksz; + abuf_ipos(obuf, ipos / (int)p->u.resamp.iblksz); +} + +void +resamp_opos(struct aproc *p, struct abuf *obuf, int delta) +{ + struct abuf *ibuf = LIST_FIRST(&p->ins); + long long opos; + + opos = (long long)delta * p->u.resamp.iblksz + p->u.resamp.odelta; + p->u.resamp.odelta = opos % p->u.resamp.oblksz; + abuf_opos(ibuf, opos / p->u.resamp.oblksz); +} + +struct aproc_ops resamp_ops = { + "resamp", + resamp_in, + resamp_out, + resamp_eof, + resamp_hup, + NULL, + NULL, + resamp_ipos, + resamp_opos, + NULL +}; + +struct aproc * +resamp_new(char *name, unsigned iblksz, unsigned oblksz) +{ + struct aproc *p; + unsigned i; + + p = aproc_new(&resamp_ops, name); + p->u.resamp.iblksz = iblksz; + p->u.resamp.oblksz = oblksz; + p->u.resamp.diff = 0; + p->u.resamp.idelta = 0; + p->u.resamp.odelta = 0; + p->u.resamp.ctx_start = 0; + for (i = 0; i < NCHAN_MAX * RESAMP_NCTX; i++) + p->u.resamp.ctx[i] = 0; +#ifdef DEBUG + if (debug_level >= 3) { + aproc_dbg(p); + dbg_puts(": new "); + dbg_putu(iblksz); + dbg_puts("/"); + dbg_putu(oblksz); + dbg_puts("\n"); + } +#endif + return p; +} + +/* + * Convert one block. + */ +void +enc_bcopy(struct aproc *p, struct abuf *ibuf, struct abuf *obuf) +{ + unsigned nch, scount, icount, ocount; + unsigned f; + short *idata; + int s; + unsigned oshift; + int osigbit; + unsigned obps; + unsigned i; + unsigned char *odata; + int obnext; + int osnext; + + /* + * Calculate max frames readable at once from the input buffer. + */ + idata = (short *)abuf_rgetblk(ibuf, &icount, 0); + if (icount == 0) + return; + odata = abuf_wgetblk(obuf, &ocount, 0); + if (ocount == 0) + return; + scount = (icount < ocount) ? icount : ocount; + nch = ibuf->cmax - ibuf->cmin + 1; +#ifdef DEBUG + if (debug_level >= 4) { + aproc_dbg(p); + dbg_puts(": bcopy "); + dbg_putu(scount); + dbg_puts(" fr / "); + dbg_putu(nch); + dbg_puts(" ch\n"); + } +#endif + /* + * Partially copy structures into local variables, to avoid + * unnecessary indirections; this also allows the compiler to + * order local variables more "cache-friendly". + */ + oshift = p->u.conv.shift; + osigbit = p->u.conv.sigbit; + obps = p->u.conv.bps; + obnext = p->u.conv.bnext; + osnext = p->u.conv.snext; + + /* + * Start conversion. + */ + odata += p->u.conv.bfirst; + for (f = scount * nch; f > 0; f--) { + s = *idata++; + s <<= 16; + s >>= oshift; + s ^= osigbit; + for (i = obps; i > 0; i--) { + *odata = (unsigned char)s; + s >>= 8; + odata += obnext; + } + odata += osnext; + } + + /* + * Update FIFO pointers. + */ + abuf_rdiscard(ibuf, scount); + abuf_wcommit(obuf, scount); +} + +int +enc_in(struct aproc *p, struct abuf *ibuf) +{ + struct abuf *obuf = LIST_FIRST(&p->outs); + + if (!ABUF_WOK(obuf) || !ABUF_ROK(ibuf)) + return 0; + enc_bcopy(p, ibuf, obuf); + if (!abuf_flush(obuf)) + return 0; + return 1; +} + +int +enc_out(struct aproc *p, struct abuf *obuf) +{ + struct abuf *ibuf = LIST_FIRST(&p->ins); + + if (!abuf_fill(ibuf)) + return 0; + if (!ABUF_WOK(obuf) || !ABUF_ROK(ibuf)) + return 0; + enc_bcopy(p, ibuf, obuf); + return 1; +} + +void +enc_eof(struct aproc *p, struct abuf *ibuf) +{ + aproc_del(p); +} + +void +enc_hup(struct aproc *p, struct abuf *obuf) +{ + aproc_del(p); +} + +struct aproc_ops enc_ops = { + "enc", + enc_in, + enc_out, + enc_eof, + enc_hup, + NULL, + NULL, + aproc_ipos, + aproc_opos, + NULL +}; + +struct aproc * +enc_new(char *name, struct aparams *par) +{ + struct aproc *p; + + p = aproc_new(&enc_ops, name); + p->u.conv.bps = par->bps; + p->u.conv.sigbit = par->sig ? 0 : 1 << (par->bits - 1); + if (par->msb) { + p->u.conv.shift = 32 - par->bps * 8; + } else { + p->u.conv.shift = 32 - par->bits; + } + if (!par->le) { + p->u.conv.bfirst = par->bps - 1; + p->u.conv.bnext = -1; + p->u.conv.snext = 2 * par->bps; + } else { + p->u.conv.bfirst = 0; + p->u.conv.bnext = 1; + p->u.conv.snext = 0; + } +#ifdef DEBUG + if (debug_level >= 3) { + aproc_dbg(p); + dbg_puts(": new "); + aparams_dbg(par); + dbg_puts("\n"); + } +#endif + return p; +} + +/* + * Convert one block. + */ +void +dec_bcopy(struct aproc *p, struct abuf *ibuf, struct abuf *obuf) +{ + unsigned nch, scount, icount, ocount; + unsigned f; + unsigned ibps; + unsigned i; + int s = 0xdeadbeef; + unsigned char *idata; + int ibnext; + int isnext; + int isigbit; + unsigned ishift; + short *odata; + + /* + * Calculate max frames readable at once from the input buffer. + */ + idata = abuf_rgetblk(ibuf, &icount, 0); + if (icount == 0) + return; + odata = (short *)abuf_wgetblk(obuf, &ocount, 0); + if (ocount == 0) + return; + scount = (icount < ocount) ? icount : ocount; + nch = obuf->cmax - obuf->cmin + 1; +#ifdef DEBUG + if (debug_level >= 4) { + aproc_dbg(p); + dbg_puts(": bcopy "); + dbg_putu(scount); + dbg_puts(" fr / "); + dbg_putu(nch); + dbg_puts(" ch\n"); + } +#endif + /* + * Partially copy structures into local variables, to avoid + * unnecessary indirections; this also allows the compiler to + * order local variables more "cache-friendly". + */ + ibps = p->u.conv.bps; + ibnext = p->u.conv.bnext; + isigbit = p->u.conv.sigbit; + ishift = p->u.conv.shift; + isnext = p->u.conv.snext; + + /* + * Start conversion. + */ + idata += p->u.conv.bfirst; + for (f = scount * nch; f > 0; f--) { + for (i = ibps; i > 0; i--) { + s <<= 8; + s |= *idata; + idata += ibnext; + } + idata += isnext; + s ^= isigbit; + s <<= ishift; + s >>= 16; + *odata++ = s; + } + + /* + * Update FIFO pointers. + */ + abuf_rdiscard(ibuf, scount); + abuf_wcommit(obuf, scount); +} + +int +dec_in(struct aproc *p, struct abuf *ibuf) +{ + struct abuf *obuf = LIST_FIRST(&p->outs); + + if (!ABUF_WOK(obuf) || !ABUF_ROK(ibuf)) + return 0; + dec_bcopy(p, ibuf, obuf); + if (!abuf_flush(obuf)) + return 0; + return 1; +} + +int +dec_out(struct aproc *p, struct abuf *obuf) +{ + struct abuf *ibuf = LIST_FIRST(&p->ins); + + if (!abuf_fill(ibuf)) + return 0; + if (!ABUF_WOK(obuf) || !ABUF_ROK(ibuf)) + return 0; + dec_bcopy(p, ibuf, obuf); + return 1; +} + +void +dec_eof(struct aproc *p, struct abuf *ibuf) +{ + aproc_del(p); +} + +void +dec_hup(struct aproc *p, struct abuf *obuf) +{ + aproc_del(p); +} + +struct aproc_ops dec_ops = { + "dec", + dec_in, + dec_out, + dec_eof, + dec_hup, + NULL, + NULL, + aproc_ipos, + aproc_opos, + NULL +}; + +struct aproc * +dec_new(char *name, struct aparams *par) +{ + struct aproc *p; + + p = aproc_new(&dec_ops, name); + p->u.conv.bps = par->bps; + p->u.conv.sigbit = par->sig ? 0 : 1 << (par->bits - 1); + if (par->msb) { + p->u.conv.shift = 32 - par->bps * 8; + } else { + p->u.conv.shift = 32 - par->bits; + } + if (par->le) { + p->u.conv.bfirst = par->bps - 1; + p->u.conv.bnext = -1; + p->u.conv.snext = 2 * par->bps; + } else { + p->u.conv.bfirst = 0; + p->u.conv.bnext = 1; + p->u.conv.snext = 0; + } +#ifdef DEBUG + if (debug_level >= 3) { + aproc_dbg(p); + dbg_puts(": new "); + aparams_dbg(par); + dbg_puts("\n"); + } +#endif + return p; +} + +/* + * Convert one block. + */ +void +join_bcopy(struct aproc *p, struct abuf *ibuf, struct abuf *obuf) +{ + unsigned h, hops; + unsigned inch, inext; + short *idata; + unsigned onch, onext; + short *odata; + int scale; + unsigned c, f, scount, icount, ocount; + + /* + * Calculate max frames readable at once from the input buffer. + */ + idata = (short *)abuf_rgetblk(ibuf, &icount, 0); + if (icount == 0) + return; + odata = (short *)abuf_wgetblk(obuf, &ocount, 0); + if (ocount == 0) + return; + scount = icount < ocount ? icount : ocount; + inch = ibuf->cmax - ibuf->cmin + 1; + onch = obuf->cmax - obuf->cmin + 1; + if (2 * inch <= onch) { + hops = onch / inch; + inext = inch * hops; + onext = onch - inext; + for (f = scount; f > 0; f--) { + h = hops; + for (;;) { + for (c = inch; c > 0; c--) + *odata++ = *idata++; + if (--h == 0) + break; + idata -= inch; + } + for (c = onext; c > 0; c--) + *odata++ = 0; + } + } else if (inch >= 2 * onch) { + hops = inch / onch; + inext = inch - onch * hops; + scale = ADATA_UNIT / hops; + inch -= onch + inext; + hops--; + for (f = scount; f > 0; f--) { + for (c = onch; c > 0; c--) + *odata++ = (*idata++ * scale) + >> ADATA_SHIFT; + for (h = hops; h > 0; h--) { + odata -= onch; + for (c = onch; c > 0; c--) + *odata++ += (*idata++ * scale) + >> ADATA_SHIFT; + } + idata += inext; + } + } else { +#ifdef DEBUG + aproc_dbg(p); + dbg_puts(": nothing to do\n"); + dbg_panic(); +#endif + } +#ifdef DEBUG + if (debug_level >= 4) { + aproc_dbg(p); + dbg_puts(": bcopy "); + dbg_putu(scount); + dbg_puts(" fr\n"); + } +#endif + abuf_rdiscard(ibuf, scount); + abuf_wcommit(obuf, scount); +} + +int +join_in(struct aproc *p, struct abuf *ibuf) +{ + struct abuf *obuf = LIST_FIRST(&p->outs); + + if (!ABUF_WOK(obuf) || !ABUF_ROK(ibuf)) + return 0; + join_bcopy(p, ibuf, obuf); + if (!abuf_flush(obuf)) + return 0; + return 1; +} + +int +join_out(struct aproc *p, struct abuf *obuf) +{ + struct abuf *ibuf = LIST_FIRST(&p->ins); + + if (!abuf_fill(ibuf)) + return 0; + if (!ABUF_WOK(obuf) || !ABUF_ROK(ibuf)) + return 0; + join_bcopy(p, ibuf, obuf); + return 1; +} + +void +join_eof(struct aproc *p, struct abuf *ibuf) +{ + aproc_del(p); +} + +void +join_hup(struct aproc *p, struct abuf *obuf) +{ + aproc_del(p); +} + +struct aproc_ops join_ops = { + "join", + join_in, + join_out, + join_eof, + join_hup, + NULL, + NULL, + aproc_ipos, + aproc_opos, + NULL +}; + +struct aproc * +join_new(char *name) +{ + struct aproc *p; + + p = aproc_new(&join_ops, name); +#ifdef DEBUG + if (debug_level >= 3) { + aproc_dbg(p); + dbg_puts("\n"); + } +#endif + return p; +} + +/* + * Commit and flush part of the output buffer + */ +void +mon_flush(struct aproc *p) +{ + struct abuf *obuf = LIST_FIRST(&p->outs); + unsigned count; + +#ifdef DEBUG + if (debug_level >= 4) { + aproc_dbg(p); + dbg_puts(": delta = "); + dbg_puti(p->u.mon.delta); + dbg_puts("/"); + dbg_putu(p->u.mon.bufsz); + dbg_puts(" pending = "); + dbg_puti(p->u.mon.pending); + dbg_puts("\n"); + } +#endif + if (p->u.mon.delta <= 0 || p->u.mon.pending == 0) + return; + count = p->u.mon.delta; + if (count > p->u.mon.pending) + count = p->u.mon.pending; + abuf_wcommit(obuf, count); + p->u.mon.pending -= count; + p->u.mon.delta -= count; + abuf_flush(obuf); +} + +/* + * Copy one block. + */ +void +mon_snoop(struct aproc *p, struct abuf *ibuf, unsigned pos, unsigned todo) +{ + struct abuf *obuf = LIST_FIRST(&p->outs); + unsigned scount, icount, ocount; + short *idata, *odata; + +#ifdef DEBUG + if (debug_level >= 4) { + aproc_dbg(p); + dbg_puts(": snoop "); + dbg_putu(pos); + dbg_puts(".."); + dbg_putu(todo); + dbg_puts("\n"); + } +#endif + if (!abuf_flush(obuf)) + return; + + while (todo > 0) { + /* + * Calculate max frames readable at once from the input buffer. + */ + idata = (short *)abuf_rgetblk(ibuf, &icount, pos); + odata = (short *)abuf_wgetblk(obuf, &ocount, p->u.mon.pending); + scount = (icount < ocount) ? icount : ocount; +#ifdef DEBUG + if (debug_level >= 4) { + aproc_dbg(p); + dbg_puts(": snooping "); + dbg_putu(scount); + dbg_puts(" fr\n"); + } + if (scount == 0) { + dbg_puts("monitor xrun, not allowed\n"); + dbg_panic(); + } +#endif + memcpy(odata, idata, scount * obuf->bpf); + p->u.mon.pending += scount; + todo -= scount; + pos += scount; + } + mon_flush(p); +} + +int +mon_in(struct aproc *p, struct abuf *ibuf) +{ +#ifdef DEBUG + dbg_puts("monitor can't have inputs to read\n"); + dbg_panic(); +#endif + return 0; +} + +/* + * put the monitor into ``empty'' state + */ +void +mon_clear(struct aproc *p) +{ + p->u.mon.pending = 0; + p->u.mon.delta = 0; +} + +int +mon_out(struct aproc *p, struct abuf *obuf) +{ + /* + * can't trigger monitored stream to produce data + */ + return 0; +} + +void +mon_eof(struct aproc *p, struct abuf *ibuf) +{ +#ifdef DEBUG + dbg_puts("monitor can't have inputs to eof\n"); + dbg_panic(); +#endif +} + +void +mon_hup(struct aproc *p, struct abuf *obuf) +{ + aproc_del(p); +} + +void +mon_ipos(struct aproc *p, struct abuf *ibuf, int delta) +{ + aproc_ipos(p, ibuf, delta); + p->u.mon.delta += delta; + mon_flush(p); +} + +struct aproc_ops mon_ops = { + "mon", + mon_in, + mon_out, + mon_eof, + mon_hup, + NULL, + NULL, + mon_ipos, + aproc_opos, + NULL +}; + +struct aproc * +mon_new(char *name, unsigned bufsz) +{ + struct aproc *p; + + p = aproc_new(&mon_ops, name); + p->u.mon.pending = 0; + p->u.mon.delta = 0; + p->u.mon.bufsz = bufsz; +#ifdef DEBUG + if (debug_level >= 3) { + aproc_dbg(p); + dbg_puts(": new\n"); + } +#endif + return p; +} diff --git a/aucat/aproc.h b/aucat/aproc.h new file mode 100644 index 0000000..16b6e48 --- /dev/null +++ b/aucat/aproc.h @@ -0,0 +1,274 @@ +/* $OpenBSD: aproc.h,v 1.37 2010/06/04 06:15:28 ratchov Exp $ */ +/* + * Copyright (c) 2008 Alexandre Ratchov + * + * 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 APROC_H +#define APROC_H + +#include + +#include "aparams.h" +#include "file.h" + +struct abuf; +struct aproc; +struct file; + +struct aproc_ops { + /* + * Name of the ops structure, ie type of the unit. + */ + char *name; + + /* + * The state of the given input abuf changed (eg. an input block + * is ready for processing). This function must get the block + * from the input, process it and remove it from the buffer. + * + * Processing the block will result in a change of the state of + * OTHER buffers that are attached to the aproc (eg. the output + * buffer was filled), thus this routine MUST notify ALL aproc + * structures that are waiting on it; most of the time this + * means just calling abuf_flush() on the output buffer. + */ + int (*in)(struct aproc *, struct abuf *); + + /* + * The state of the given output abuf changed (eg. space for a + * new output block was made available) so processing can + * continue. This function must process more input in order to + * fill the output block. + * + * Producing a block will result in the change of the state of + * OTHER buffers that are attached to the aproc, thus this + * routine MUST notify ALL aproc structures that are waiting on + * it; most of the time this means calling abuf_fill() on the + * source buffers. + * + * Before filling input buffers (using abuf_fill()), this + * routine must ALWAYS check for eof condition, and if needed, + * handle it appropriately and call abuf_hup() to free the input + * buffer. + */ + int (*out)(struct aproc *, struct abuf *); + + /* + * The input buffer is empty and we can no more receive data + * from it. The buffer will be destroyed as soon as this call + * returns so the abuf pointer will stop being valid after this + * call returns. There's no need to drain the buffer because the + * in() call-back was just called before. + * + * If this call reads and/or writes data on other buffers, + * abuf_flush() and abuf_fill() must be called appropriately. + */ + void (*eof)(struct aproc *, struct abuf *); + + /* + * The output buffer can no more accept data (it should be + * considered as full). After this function returns, it will be + * destroyed and the "abuf" pointer will be no more valid. + */ + void (*hup)(struct aproc *, struct abuf *); + + /* + * A new input was connected. + */ + void (*newin)(struct aproc *, struct abuf *); + + /* + * A new output was connected + */ + void (*newout)(struct aproc *, struct abuf *); + + /* + * Real-time record position changed (for input buffer), + * by the given amount of _frames_. + */ + void (*ipos)(struct aproc *, struct abuf *, int); + + /* + * Real-time play position changed (for output buffer), + * by the given amount of _frames_. + */ + void (*opos)(struct aproc *, struct abuf *, int); + + /* + * Destroy the aproc, called just before to free the + * aproc structure. + */ + void (*done)(struct aproc *); +}; + +/* + * The aproc structure represents a simple audio processing unit; they are + * interconnected by abuf structures and form a kind of "circuit". The circuit + * cannot have loops. + */ +struct aproc { + char *name; /* for debug purposes */ + struct aproc_ops *ops; /* call-backs */ + LIST_HEAD(, abuf) ins; /* list of inputs */ + LIST_HEAD(, abuf) outs; /* list of outputs */ + unsigned refs; /* extern references */ +#define APROC_ZOMB 1 /* destroyed but not freed */ +#define APROC_QUIT 2 /* try to terminate if unused */ +#define APROC_DROP 4 /* xrun if capable */ + unsigned flags; + union { /* follow type-specific data */ + struct { /* file/device io */ + struct file *file; /* file to read/write */ + unsigned partial; /* bytes of partial frame */ + } io; + struct { + unsigned idle; /* frames since idleing */ + unsigned round; /* block size, for xruns */ + int lat; /* current latency */ + int maxlat; /* max latency allowed */ + unsigned abspos; /* frames produced */ + struct aproc *ctl; /* MIDI control/sync */ + struct aproc *mon; /* snoop output */ + } mix; + struct { + unsigned idle; /* frames since idleing */ + unsigned round; /* block size, for xruns */ + int lat; /* current latency */ + int maxlat; /* max latency allowed */ + unsigned abspos; /* frames consumed */ + struct aproc *ctl; + } sub; + struct { + int delta; /* time position */ + unsigned bufsz; /* buffer size (latency) */ + unsigned pending; /* uncommited samples */ + } mon; + struct { +#define RESAMP_NCTX 2 + unsigned ctx_start; + short ctx[NCHAN_MAX * RESAMP_NCTX]; + unsigned iblksz, oblksz; + int diff; + int idelta, odelta; /* remainder of resamp_xpos */ + } resamp; + struct { + int bfirst; /* bytes to skip at startup */ + unsigned bps; /* bytes per sample */ + unsigned 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 */ + } conv; + struct { + struct abuf *owner; /* current input stream */ + struct timo timo; /* timout for throtteling */ + } thru; + struct { + struct dev *dev; /* controlled device */ +#define CTL_NSLOT 8 +#define CTL_NAMEMAX 8 + unsigned serial; +#define CTL_OFF 0 /* ignore MMC messages */ +#define CTL_STOP 1 /* stopped, can't start */ +#define CTL_START 2 /* attempting to start */ +#define CTL_RUN 3 /* started */ + unsigned tstate; + unsigned origin; /* MTC start time */ + unsigned fps; /* MTC frames per second */ +#define MTC_FPS_24 0 +#define MTC_FPS_25 1 +#define MTC_FPS_30 3 + unsigned fps_id; /* one of above */ + unsigned hr; /* MTC hours */ + unsigned min; /* MTC minutes */ + unsigned sec; /* MTC seconds */ + unsigned fr; /* MTC frames */ + unsigned qfr; /* MTC quarter frames */ + int delta; /* rel. to the last MTC tick */ + struct ctl_slot { + struct ctl_ops { + void (*vol)(void *, unsigned); + void (*start)(void *); + void (*stop)(void *); + void (*loc)(void *, unsigned); + void (*quit)(void *); + } *ops; + void *arg; + unsigned unit; + char name[CTL_NAMEMAX]; + unsigned serial; + unsigned vol; + unsigned tstate; + } slot[CTL_NSLOT]; + } ctl; + } u; +}; + +/* + * Check if the given pointer is a valid aproc structure. + * + * aproc structures are not free()'d immediately, because + * there may be pointers to them, instead the APROC_ZOMB flag + * is set which means that they should not be used. When + * aprocs reference counter reaches zero, they are actually + * freed + */ +#define APROC_OK(p) ((p) && !((p)->flags & APROC_ZOMB)) + + +struct aproc *aproc_new(struct aproc_ops *, char *); +void aproc_del(struct aproc *); +void aproc_dbg(struct aproc *); +void aproc_setin(struct aproc *, struct abuf *); +void aproc_setout(struct aproc *, struct abuf *); +int aproc_depend(struct aproc *, struct aproc *); + +void aproc_ipos(struct aproc *, struct abuf *, int); +void aproc_opos(struct aproc *, struct abuf *, int); + +struct aproc *rfile_new(struct file *); +struct aproc *wfile_new(struct file *); +struct aproc *mix_new(char *, int, unsigned); +struct aproc *sub_new(char *, int, unsigned); +struct aproc *resamp_new(char *, unsigned, unsigned); +struct aproc *enc_new(char *, struct aparams *); +struct aproc *dec_new(char *, struct aparams *); +struct aproc *join_new(char *); +struct aproc *mon_new(char *, unsigned); + +int rfile_in(struct aproc *, struct abuf *); +int rfile_out(struct aproc *, struct abuf *); +void rfile_eof(struct aproc *, struct abuf *); +void rfile_hup(struct aproc *, struct abuf *); +void rfile_done(struct aproc *); +int rfile_do(struct aproc *, unsigned, unsigned *); + +int wfile_in(struct aproc *, struct abuf *); +int wfile_out(struct aproc *, struct abuf *); +void wfile_eof(struct aproc *, struct abuf *); +void wfile_hup(struct aproc *, struct abuf *); +void wfile_done(struct aproc *); +int wfile_do(struct aproc *, unsigned, unsigned *); + +void mix_setmaster(struct aproc *); +void mix_clear(struct aproc *); +void mix_prime(struct aproc *); +void mix_quit(struct aproc *); +void mix_drop(struct abuf *, int); +void sub_silence(struct abuf *, int); +void sub_clear(struct aproc *); +void mon_snoop(struct aproc *, struct abuf *, unsigned, unsigned); +void mon_clear(struct aproc *); + +#endif /* !defined(APROC_H) */ diff --git a/aucat/aucat.1 b/aucat/aucat.1 new file mode 100644 index 0000000..c64b27c --- /dev/null +++ b/aucat/aucat.1 @@ -0,0 +1,633 @@ +.\" $OpenBSD: aucat.1,v 1.73 2010/07/31 08:48:01 ratchov Exp $ +.\" +.\" Copyright (c) 2006 Alexandre Ratchov +.\" +.\" 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: July 31 2010 $ +.Dt AUCAT 1 +.Os +.Sh NAME +.Nm aucat +.Nd audio server and stream manipulation tool +.Sh SYNOPSIS +.Nm aucat +.Bk -words +.Op Fl dlnu +.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 h Ar fmt +.Op Fl i Ar file +.Op Fl j Ar flag +.Op Fl m Ar mode +.Op Fl o Ar file +.Op Fl q Ar device +.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 x Ar policy +.Op Fl z Ar nframes +.Ek +.Sh DESCRIPTION +.Nm +is an audio utility which can simultaneously play and record +any number of audio +.Em streams +on any number of audio devices, +possibly controlled through MIDI. +It can also act as an audio server, in which case streams +correspond to client connections rather than plain files. +.Pp +Audio devices are independent. +A list of streams is attached to each audio device, +as well as an optional list of MIDI ports to control the device. +A typical invocation of +.Nm +consists in providing streams to play and record, +and possibly the audio device to use, if the default is not desired. +.Pp +This also applies to server mode, except that streams are created +dynamically when clients connect to the server. +Thus, instead of actual streams (paths to plain files), +templates for client streams (sub-device names) must be provided. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl a Ar flag +Control whether +.Nm +opens the audio device only when needed or keeps it open all the time. +If the flag is +.Va on +then the device 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 device, or the device to be disconnected. +The default is +.Va on . +.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. +.It Xo +.Fl C Ar min : Ns Ar max , +.Fl c Ar min : Ns Ar max +.Xc +The range of stream channel numbers for recording and playback directions, +respectively. +The default is 0:1, i.e. stereo. +.It Fl d +Increase log verbosity. +.Nm +logs on stderr until it daemonizes. +.It Fl e Ar enc +Encoding of the playback or recording stream (see below). +The default is signed, 16-bit, native byte order. +.It Fl f Ar device +Add this +.Xr sndio 7 +audio device to devices used for playing and/or recording. +Preceding streams +.Pq Fl ios , +control MIDI ports +.Pq Fl q , +and per-device options +.Pq Fl abz +apply to this device. +Device mode and parameters are determined from streams +attached to it. +.It Fl h Ar fmt +File format of the playback or record stream (see below). +The default is auto. +.It Fl i Ar file +Add this file to the list of streams to play. +If the option argument is +.Sq - +then standard input will be used. +.It Fl j Ar flag +Control whether stream channels are joined or expanded if +the stream number of channels is not equal to the device number of channels. +If the flag is +.Va off +then stream 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 stream channel may be sent on multiple device channels, +or multiple stream channels may be sent to a single device channel. +For instance, this feature could be used to request mono streams to +be sent on multiple outputs or to record a stereo input into a mono stream. +The default is +.Ar on . +.It Fl l +Detach and become a daemon. +.It Fl m Ar mode +Set the stream 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 stream cannot be used for both recording and monitoring. +The default is +.Ar play , Ns Ar rec +(i.e. full-duplex). +.It Fl n +Loopback mode. +Instead of using audio devices, send input streams +to the output, processing them on the fly. +This mode is useful to mix, demultiplex, resample or reencode +audio files offline. +.It Fl o Ar file +Add this file to the list of recording streams. +If the option argument is +.Sq - +then standard output will be used. +.It Fl q Ar device +Expose the audio device clock on this +.Xr sndio 7 +MIDI port and allow audio device properties to be controlled +through MIDI. +This includes per-stream volumes and the ability to +synchronously start, stop and relocate streams created in +MIDI Machine +Control (MMC) slave mode +.Pq Fl t . +.It Fl r Ar rate +Sample rate in Hertz of the stream. +The default is 44100Hz. +.It Fl s Ar name +Add +.Ar name +to the list of sub-devices to expose in server mode. +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 logical 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 streams are controlled by MIDI Machine Control (MMC) +messages. +If the mode is +.Va off +(the default), then streams are not affected by MMC messages. +If the mode is +.Va slave , +then streams 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 streams. +.It Fl U Ar unit +Unit number to use when running in server mode. +Each +.Nm +server instance has an unique unit number, +used in +.Xr sndio 7 +device names. +The default is 0. +.It Fl u +Normally +.Nm +tries to automatically determine the optimal parameters for the audio device; +if this option is specified, +it will instead use the parameters specified by the +.Fl Ccer +options. +.It Fl v Ar volume +Software volume attenuation of the playback stream. +The value must be between 1 and 127, +corresponding to \-42dB and \-0dB attenuation. +In server mode, clients inherit this parameter. +Reducing the volume in advance reduces a client's dynamic range, +but allows client volume to stay independent from the number +of clients as long as their number is small enough. +A good compromise is to use \-4dB attenuation (12 volume units) +for each additional client expected +(115 if 2 clients are expected, 103 for 3 clients, and so on). +.It Fl x Ar policy +Action when the output stream cannot accept +recorded data fast enough or the input stream +cannot provide data to play fast enough. +If the policy +is +.Dq ignore +(the default) then samples that cannot be written are discarded +and samples that cannot be read are replaced by silence. +If the policy is +.Dq sync +then recorded samples are discarded, +but the same amount of silence will be written +once the stream 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 stream is unblocked. +If the policy is +.Dq error +then the stream is closed permanently. +.Pp +If a stream is created with the +.Fl t +option, +the +.Dq ignore +action is disabled for any stream connected to it +to ensure proper synchronization. +.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 stream 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, 120 ticks per second at 48000Hz corresponds +to a 400 frame block size. +.El +.Pp +On the command line, +per-device parameters +.Pq Fl abz +must precede the device definition +.Pq Fl f , +and per-stream parameters +.Pq Fl Ccehjmrtvx +must precede the stream definition +.Pq Fl ios . +MIDI ports +.Pq Fl q +and streams definitions +.Pq Fl ios +must precede the definition of the device +.Pq Fl f +to which they are attached. +Global parameters +.Pq Fl dlnu +are position-independent. +.Pp +If no audio devices +.Pq Fl f +are specified, +settings are applied as if +the default device is specified as the last argument. +If no streams +.Pq Fl ios +are specified for a device, a default server sub-device is +created attached to it, meaning that +.Nm +behaves as an audio server. +The default +.Xr sndio 7 +device is +.Pa aucat:0 +.Pq also known as Pa aucat:0.default +.Pp +If +.Nm +is sent +.Dv SIGHUP , +.Dv SIGINT +or +.Dv SIGTERM , +it terminates recording to files. +.Pp +File formats are specified using the +.Fl h +option. +The following file formats are supported: +.Bl -tag -width s32lexxx -offset indent +.It raw +Headerless file. +This format is recommended since it has no limitations. +.It wav +Microsoft WAVE file format. +There are limitations inherent to the file format itself: +not all encodings are supported, +file sizes are limited to 2GB, +and the file must support the +.Xr lseek 2 +operation (e.g. pipes do not support it). +.It auto +Try to guess, depending on the file name. +.El +.Pp +Encodings are specified using the +.Fl e +option. +The following encodings are supported: +.Pp +.Bl -tag -width s32lexxx -offset indent -compact +.It s8 +signed 8-bit +.It u8 +unsigned 8-bit +.It s16le +signed 16-bit, little endian +.It u16le +unsigned 16-bit, little endian +.It s16be +signed 16-bit, big endian +.It u16be +unsigned 16-bit, big endian +.It s24le +signed 24-bit, stored in 4 bytes, little endian +.It u24le +unsigned 24-bit, stored in 4 bytes, little endian +.It s24be +signed 24-bit, stored in 4 bytes, big endian +.It u24be +unsigned 24-bit, stored in 4 bytes, big endian +.It s32le +signed 32-bit, little endian +.It u32le +unsigned 32-bit, little endian +.It s32be +signed 32-bit, big endian +.It u32be +unsigned 32-bit, big endian +.It s24le3 +signed 24-bit, packed in 3 bytes, little endian +.It u24le3 +unsigned 24-bit, packed in 3 bytes, big endian +.It s24be3 +signed 24-bit, packed in 3 bytes, little endian +.It u24be3 +unsigned 24-bit, packed in 3 bytes, big endian +.It s20le3 +signed 20-bit, packed in 3 bytes, little endian +.It u20le3 +unsigned 20-bit, packed in 3 bytes, big endian +.It s20be3 +signed 20-bit, packed in 3 bytes, little endian +.It u20be3 +unsigned 20-bit, packed in 3 bytes, big endian +.It s18le3 +signed 18-bit, packed in 3 bytes, little endian +.It u18le3 +unsigned 18-bit, packed in 3 bytes, big endian +.It s18be3 +signed 18-bit, packed in 3 bytes, little endian +.It u18be3 +unsigned 18-bit, packed in 3 bytes, big endian +.El +.Sh SERVER MODE +If at least one sub-device +.Pq Fl s +is exposed by +.Nm , +including the case when no stream options are given, +then +.Nm +can be used as a server +to overcome hardware limitations and allow applications +to run on fixed sample rate devices or on devices +supporting only unusual encodings. +.Pp +Certain applications, such as synthesis software, +require a low latency audio setup. +To reduce the probability of buffer underruns or overruns, especially +on busy machines, the server can be started by the super-user, in which +case it will run with higher priority. +Any user will still be able to connect to it, +but for privacy reasons only one user may have +connections to it at a given time. +.Sh MIDI CONTROL +.Nm +can expose the audio device clock on registered +MIDI ports +.Pq Fl q +and allows audio device properties to be controlled +through MIDI. +If running in server mode +.Nm +creates a MIDI port with the same name as the default audio +device to which MIDI programs can connect. +.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 +Streams created with the +.Fl t +option are controlled by the following MMC messages: +.Bl -tag -width relocateXXX -offset indent +.It relocate +Streams are relocated to the requested time postion +relative to the beginning of the stream, at which playback +and recording must start. +If the requested position is beyond the end of file, +the stream is temporarly disabled until a valid postion is requested. +This message is ignored by client streams (server mode). +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 (server mode) +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. +Files are stopped and rewound back to the starting position, +while client streams (server mode) 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 server clock using MTC, allowing non-audio +software or hardware to be synchronized to the audio stream. +The following sample rates +.Pq Fl r +and block sizes +.Pq Fl z +are recommended for maximum accuracy: +.Pp +.Bl -bullet -offset indent -compact +.It +44100Hz, 441 frames +.It +48000Hz, 400 frames +.It +48000Hz, 480 frames +.It +48000Hz, 500 frames +.El +.Pp +For instance, the following command will create two devices: +the default +.Va aucat:0 +and a MIDI-controlled +.Va aucat:0.mmc : +.Bd -literal -offset indent +$ aucat -l -r 48000 -z 400 -s default -t slave -s mmc +.Ed +.Pp +Streams connected to +.Va aucat:0 +behave normally, while streams connected to +.Va aucat: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. +.Pp +For instance, the following command will play a file on the +.Va aucat:0.mmc +audio device, and give full control to MIDI software or hardware +connected to the +.Va midithru:0 +MIDI device: +.Bd -literal -offset indent +$ aucat -t slave -q midithru:0 -i file.wav -f aucat:0.mmc +.Ed +.Pp +At this stage, +.Nm +will start, stop and relocate automatically following all user +actions in the MIDI sequencer. +Note that the sequencer must use +.Va aucat:0 +as the MTC source, i.e. the audio server, not the audio player. +.Sh ENVIRONMENT +.Bl -tag -width "AUDIODEVICE" -compact +.It Ev AUDIODEVICE +.Xr sndio 7 +audio device to use if the +.Fl f +option is not specified. +.El +.Sh EXAMPLES +The following will mix and play two stereo streams, +the first at 48kHz and the second at 44.1kHz: +.Bd -literal -offset indent +$ aucat -r 48000 -i file1.raw -r 44100 -i file2.raw +.Ed +.Pp +The following will record channels 2 and 3 into one stereo file and +channels 6 and 7 into another stereo file using a 96kHz sampling rate for +both: +.Bd -literal -offset indent +$ aucat -r 96000 -C 2:3 -o file1.raw -C 6:7 -o file2.raw +.Ed +.Pp +The following will split a stereo file into two mono files: +.Bd -literal -offset indent +$ aucat -n -i stereo.wav -C 0:0 -o left.wav -C 1:1 -o right.wav +.Ed +.Pp +The following will start +.Nm +in server mode using default parameters, but will create an +additional sub-device for output to channels 2:3 only (rear speakers +on most cards), exposing the +.Pa aucat:0 +and +.Pa aucat:0.rear +devices: +.Bd -literal -offset indent +$ aucat -l -s default -c 2:3 -s rear +.Ed +.Pp +The following will start +.Nm +in server mode creating the default sub-device with low volume and +an additional sub-device for high volume output, exposing the +.Pa aucat:0 +and +.Pa aucat:0.max +devices: +.Bd -literal -offset indent +$ aucat -l -v 65 -s default -v 127 -s max +.Ed +.Pp +The following will start +.Nm +in server mode 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 +$ aucat -l -r 48000 -b 480 -z 240 +.Ed +.Sh SEE ALSO +.Xr audioctl 1 , +.Xr cdio 1 , +.Xr mixerctl 1 , +.Xr audio 4 , +.Xr sndio 7 +.Sh BUGS +The +.Nm +utility assumes non-blocking I/O for input and output streams. +It will not work reliably on files that may block +(ordinary files block, pipes don't). +To avoid audio underruns/overruns or MIDI jitter caused by file I/O, +it's recommended to use two +.Nm +processes: a server handling audio and MIDI I/O and a client handling +disk I/O. +.Pp +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. +.Pp +If +.Fl a Ar off +is used in server mode, +.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 audio device, +creating a deadlock. +To avoid this, +.Fl a Ar off +is disabled for the default audio device, but nothing prevents the user +from shooting himself in the foot by creating a similar deadlock. diff --git a/aucat/aucat.c b/aucat/aucat.c new file mode 100644 index 0000000..37ef703 --- /dev/null +++ b/aucat/aucat.c @@ -0,0 +1,1100 @@ +/* $OpenBSD: aucat.c,v 1.103 2010/08/19 06:31:06 ratchov Exp $ */ +/* + * Copyright (c) 2008 Alexandre Ratchov + * + * 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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "abuf.h" +#include "aparams.h" +#include "aproc.h" +#include "conf.h" +#include "dev.h" +#include "listen.h" +#include "midi.h" +#include "opt.h" +#include "wav.h" +#ifdef DEBUG +#include "dbg.h" +#endif + +/* + * unprivileged user name + */ +#define SNDIO_USER "_sndio" + +/* + * priority when run as root + */ +#define SNDIO_PRIO (-20) + +#define PROG_AUCAT "aucat" +#define PROG_MIDICAT "midicat" + +#ifdef DEBUG +int debug_level = 0; +#endif +volatile int quit_flag = 0; + +/* + * 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; +} + +#ifdef DEBUG +/* + * Increase debug level on SIGUSR1. + */ +void +sigusr1(int s) +{ + if (debug_level < 4) + debug_level++; +} + +/* + * Decrease debug level on SIGUSR2. + */ +void +sigusr2(int s) +{ + if (debug_level > 0) + debug_level--; +} +#endif + +void +opt_ch(struct aparams *par) +{ + 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; + par->cmin = cmin; + par->cmax = 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_hdr(void) +{ + if (strcmp("auto", optarg) == 0) + return HDR_AUTO; + if (strcmp("raw", optarg) == 0) + return HDR_RAW; + if (strcmp("wav", optarg) == 0) + return HDR_WAV; + errx(1, "%s: bad header specification", optarg); +} + +int +opt_mmc(void) +{ + if (strcmp("off", optarg) == 0) + return 0; + if (strcmp("slave", optarg) == 0) + return 1; + errx(1, "%s: bad MMC mode", optarg); +} + +int +opt_onoff(void) +{ + if (strcmp("off", optarg) == 0) + return 0; + if (strcmp("on", optarg) == 0) + return 1; + errx(1, "%s: bad join/expand setting", optarg); +} + +int +opt_xrun(void) +{ + if (strcmp("ignore", optarg) == 0) + return XRUN_IGNORE; + if (strcmp("sync", optarg) == 0) + return XRUN_SYNC; + if (strcmp("error", optarg) == 0) + return XRUN_ERROR; + errx(1, "%s: bad underrun/overrun policy", optarg); +} + +unsigned +opt_mode(void) +{ + unsigned mode = 0; + char *p = optarg; + size_t len; + + for (p = optarg; *p != NULL; 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("duplex", p, len) == 0) { + /* XXX: backward compat, remove this */ + mode |= MODE_REC | MODE_PLAY; + } else + errx(1, "%s: bad mode", optarg); + p += len; + if (*p == '\0') + break; + } + if (mode == 0) + errx(1, "empty mode"); + return mode; +} + +/* + * stream configuration + */ +struct cfstr { + SLIST_ENTRY(cfstr) entry; + unsigned mode; /* bitmap of MODE_XXX */ + struct aparams ipar; /* input (read) parameters */ + struct aparams opar; /* output (write) parameters */ + unsigned vol; /* last requested volume */ + int hdr; /* header format */ + int xrun; /* overrun/underrun policy */ + int mmc; /* MMC mode */ + int join; /* join/expand enabled */ + char *path; /* static path (no need to copy it) */ +}; + +SLIST_HEAD(cfstrlist, cfstr); + +/* + * midi device (control stream) + */ +struct cfmid { + SLIST_ENTRY(cfmid) entry; + char *path; /* static path (no need to copy it) */ +}; + +SLIST_HEAD(cfmidlist, cfmid); + +/* + * audio device configuration + */ +struct cfdev { + SLIST_ENTRY(cfdev) entry; + struct cfstrlist ins; /* files to play */ + struct cfstrlist outs; /* files to record */ + struct cfstrlist opts; /* subdevices to expose */ + struct cfmidlist mids; /* midi ports to subscribe */ + struct aparams ipar; /* input (read) parameters */ + struct aparams opar; /* output (write) parameters */ + unsigned hold; /* open immediately */ + unsigned bufsz; /* par.bufsz for sio device */ + unsigned round; /* par.round for sio device */ + unsigned mode; /* bitmap of MODE_XXX */ + char *path; /* static path (no need to copy it) */ +}; + +SLIST_HEAD(cfdevlist, cfdev); + +void +cfdev_add(struct cfdevlist *list, struct cfdev *templ, char *path) +{ + struct cfdev *cd; + + cd = malloc(sizeof(struct cfdev)); + if (cd == NULL) { + perror("malloc"); + abort(); + } + *cd = *templ; + cd->path = path; + SLIST_INSERT_HEAD(list, cd, entry); + SLIST_INIT(&templ->ins); + SLIST_INIT(&templ->outs); + SLIST_INIT(&templ->opts); + SLIST_INIT(&templ->mids); +} + +void +cfstr_add(struct cfstrlist *list, struct cfstr *templ, char *path) +{ + size_t len; + struct cfstr *cs; + unsigned hdr; + + if (templ->hdr == HDR_AUTO) { + len = strlen(path); + if (len >= 4 && strcasecmp(path + len - 4, ".wav") == 0) + hdr = HDR_WAV; + else + hdr = HDR_RAW; + } else + hdr = templ->hdr; + cs = malloc(sizeof(struct cfstr)); + if (cs == NULL) { + perror("malloc"); + abort(); + } + *cs = *templ; + cs->path = path; + cs->hdr = hdr; + SLIST_INSERT_HEAD(list, cs, entry); +} + +void +cfmid_add(struct cfmidlist *list, char *path) +{ + struct cfmid *cm; + + cm = malloc(sizeof(struct cfmid)); + if (cm == NULL) { + perror("malloc"); + abort(); + } + cm->path = path; + SLIST_INSERT_HEAD(list, cm, entry); +} + +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"); +#ifdef DEBUG + sa.sa_handler = sigusr1; + if (sigaction(SIGUSR1, &sa, NULL) < 0) + err(1, "sigaction(usr1) failed"); + sa.sa_handler = sigusr2; + if (sigaction(SIGUSR2, &sa, NULL) < 0) + err(1, "sigaction(usr2) failed1n"); +#endif +} + +void +unsetsig(void) +{ + struct sigaction sa; + + sigfillset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sa.sa_handler = SIG_DFL; +#ifdef DEBUG + if (sigaction(SIGUSR2, &sa, NULL) < 0) + err(1, "unsetsig(usr2): sigaction failed"); + if (sigaction(SIGUSR1, &sa, NULL) < 0) + err(1, "unsetsig(usr1): sigaction failed"); +#endif + 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; + struct stat sb; + + if ((pw = getpwnam(SNDIO_USER)) == NULL) + err(1, "getpwnam"); + if (stat(pw->pw_dir, &sb) < 0) + err(1, "stat(\"%s\")", pw->pw_dir); + if (sb.st_uid != 0 || (sb.st_mode & 022) != 0) + errx(1, "%s has wrong permissions", pw->pw_dir); + 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"); +} + +void +aucat_usage(void) +{ + (void)fputs("usage: " PROG_AUCAT " [-dlnu] [-a flag] [-b nframes] " + "[-C min:max] [-c min:max] [-e enc]\n\t" + "[-f device] [-h fmt] [-i file] [-j flag] [-m mode]" + "[-o file] [-q device]\n\t" + "[-r rate] [-s name] [-t mode] [-U unit] " + "[-v volume] [-x policy]\n\t" + "[-z nframes]\n", + stderr); +} + +int +aucat_main(int argc, char **argv) +{ + struct cfdevlist cfdevs; + struct cfmid *cm; + struct cfstr *cs; + struct cfdev *cd; + struct listen *listen = NULL; + int c, u_flag, d_flag, l_flag, n_flag, unit; + char base[PATH_MAX], path[PATH_MAX]; + unsigned mode, rate; + const char *str; + int autostart; + struct dev *d, *dnext; + unsigned active; + unsigned nsock, nfile; + + /* + * global options defaults + */ + unit = -1; + u_flag = 0; + d_flag = 0; + l_flag = 0; + n_flag = 0; + SLIST_INIT(&cfdevs); + nfile = nsock = 0; + + /* + * default stream params + */ + cs = malloc(sizeof(struct cfstr)); + if (cs == NULL) { + perror("malloc"); + exit(1); + } + aparams_init(&cs->ipar, 0, 1, 44100); + aparams_init(&cs->opar, 0, 1, 44100); + cs->mmc = 0; + cs->hdr = HDR_AUTO; + cs->xrun = XRUN_IGNORE; + cs->vol = MIDI_MAXCTL; + cs->mode = MODE_PLAY | MODE_REC; + cs->join = 1; + + /* + * default device + */ + cd = malloc(sizeof(struct cfdev)); + if (cd == NULL) { + perror("malloc"); + exit(1); + } + aparams_init(&cd->ipar, 0, 1, 44100); + aparams_init(&cd->opar, 0, 1, 44100); + SLIST_INIT(&cd->ins); + SLIST_INIT(&cd->outs); + SLIST_INIT(&cd->opts); + SLIST_INIT(&cd->mids); + cd->path = NULL; + cd->bufsz = 0; + cd->round = 0; + cd->hold = 1; + + while ((c = getopt(argc, argv, "a:dnb:c:C:e:r:h:x:v:i:o:f:m:luq:s:U:t:j:z:")) != -1) { + switch (c) { + case 'd': +#ifdef DEBUG + if (d_flag) + debug_level++; +#endif + d_flag = 1; + break; + case 'n': + n_flag = 1; + break; + case 'u': + u_flag = 1; + break; + case 'U': + unit = strtonum(optarg, 0, MIDI_MAXCTL, &str); + if (str) + errx(1, "%s: unit number is %s", optarg, str); + break; + case 'm': + cs->mode = opt_mode(); + cd->mode = cs->mode; + break; + case 'h': + cs->hdr = opt_hdr(); + break; + case 'x': + cs->xrun = opt_xrun(); + break; + case 'j': + cs->join = opt_onoff(); + break; + case 't': + cs->mmc = opt_mmc(); + break; + case 'c': + opt_ch(&cs->ipar); + cd->opar.cmin = cs->ipar.cmin; + cd->opar.cmax = cs->ipar.cmax; + break; + case 'C': + opt_ch(&cs->opar); + cd->ipar.cmin = cs->opar.cmin; + cd->ipar.cmax = cs->opar.cmax; + break; + case 'e': + opt_enc(&cs->ipar); + aparams_copyenc(&cs->opar, &cs->ipar); + break; + case 'r': + rate = strtonum(optarg, RATE_MIN, RATE_MAX, &str); + if (str) + errx(1, "%s: rate is %s", optarg, str); + cs->opar.rate = cs->ipar.rate = rate; + cd->ipar.rate = cd->opar.rate = rate; + break; + case 'v': + cs->vol = strtonum(optarg, 0, MIDI_MAXCTL, &str); + if (str) + errx(1, "%s: volume is %s", optarg, str); + break; + case 'i': + cfstr_add(&cd->ins, cs, optarg); + nfile++; + break; + case 'o': + cfstr_add(&cd->outs, cs, optarg); + nfile++; + break; + case 's': + cfstr_add(&cd->opts, cs, optarg); + nsock++; + break; + case 'a': + cd->hold = opt_onoff(); + break; + case 'q': + cfmid_add(&cd->mids, optarg); + break; + case 'b': + cd->bufsz = strtonum(optarg, 1, RATE_MAX * 5, &str); + if (str) + errx(1, "%s: buffer size is %s", optarg, str); + break; + case 'z': + cd->round = strtonum(optarg, 1, SHRT_MAX, &str); + if (str) + errx(1, "%s: block size is %s", optarg, str); + break; + case 'f': + if (SLIST_EMPTY(&cd->opts) && + SLIST_EMPTY(&cd->ins) && + SLIST_EMPTY(&cd->outs)) { + cfstr_add(&cd->opts, cs, DEFAULT_OPT); + nsock++; + } + cfdev_add(&cfdevs, cd, optarg); + break; + case 'l': + l_flag = 1; + autostart = 0; + break; + default: + aucat_usage(); + exit(1); + } + } + argc -= optind; + argv += optind; + +#ifdef DEBUG + if (debug_level == 0) + debug_level = 1; +#endif + if (argc > 0) { + aucat_usage(); + exit(1); + } + + /* + * Check constraints specific to -n option + */ + if (n_flag) { + if (!SLIST_EMPTY(&cfdevs) || + !SLIST_EMPTY(&cd->mids) || + !SLIST_EMPTY(&cd->opts)) + errx(1, "-f, -s, and -q not allowed in loopback mode"); + if (SLIST_EMPTY(&cd->ins) || SLIST_EMPTY(&cd->outs)) + errx(1, "-i and -o are required in loopback mode"); + } + + /* + * If there's no device specified, do as if the default + * device is specified as last argument. + */ + if (SLIST_EMPTY(&cfdevs)) { + if (SLIST_EMPTY(&cd->opts) && + SLIST_EMPTY(&cd->ins) && + SLIST_EMPTY(&cd->outs)) { + cfstr_add(&cd->opts, cs, DEFAULT_OPT); + nsock++; + } + if (!cd->hold) + errx(1, "-a off not compatible with default device"); + cfdev_add(&cfdevs, cd, "default"); + } + if ((cs = SLIST_FIRST(&cd->opts)) || + (cs = SLIST_FIRST(&cd->ins)) || + (cs = SLIST_FIRST(&cd->outs))) + errx(1, "%s: no device to attach the stream to", cs->path); + + /* + * Check modes and calculate "best" device parameters. Iterate over all + * inputs and outputs and find the maximum sample rate and channel + * number. + */ + SLIST_FOREACH(cd, &cfdevs, entry) { + mode = 0; + SLIST_FOREACH(cs, &cd->ins, entry) { + if (cs->mode == 0) + errx(1, "%s: not in play mode", cs->path); + mode |= (cs->mode & MODE_PLAY); + if (!u_flag) + aparams_grow(&cd->opar, &cs->ipar); + } + SLIST_FOREACH(cs, &cd->outs, entry) { + if (cs->mode == 0) + errx(1, "%s: not in rec/mon mode", cs->path); + if ((cs->mode & MODE_REC) && (cs->mode & MODE_MON)) + errx(1, "%s: can't rec and mon", cs->path); + mode |= (cs->mode & MODE_RECMASK); + if (!u_flag) + aparams_grow(&cd->ipar, &cs->opar); + } + SLIST_FOREACH(cs, &cd->opts, entry) { + if ((cs->mode & MODE_REC) && (cs->mode & MODE_MON)) + errx(1, "%s: can't rec and mon", cs->path); + mode |= (cs->mode & (MODE_RECMASK | MODE_PLAY)); + if (!u_flag) { + aparams_grow(&cd->opar, &cs->ipar); + aparams_grow(&cd->ipar, &cs->opar); + } + } + if ((mode & MODE_MON) && !(mode & MODE_PLAY)) + errx(1, "no playback stream to monitor"); + if (n_flag && (mode & MODE_MON)) + errx(1, "-m mon not allowed in loopback mode"); + rate = (mode & MODE_REC) ? cd->ipar.rate : cd->opar.rate; + if (!cd->round) + cd->round = rate / 15; + if (!cd->bufsz) + cd->bufsz = rate / 15 * 4; + cd->mode = mode; + } + if (nsock > 0) { + getbasepath(base, sizeof(base)); + if (unit < 0) + unit = 0; + } + setsig(); + filelist_init(); + + /* + * Open devices + */ + while (!SLIST_EMPTY(&cfdevs)) { + cd = SLIST_FIRST(&cfdevs); + SLIST_REMOVE_HEAD(&cfdevs, entry); + + if (n_flag) { + d = dev_new_loop(&cd->ipar, &cd->opar, cd->bufsz); + } else { + d = dev_new_sio(cd->path, cd->mode, + &cd->ipar, &cd->opar, cd->bufsz, cd->round, + cd->hold); + } + if (d == NULL) + errx(1, "%s: can't open device", cd->path); + + /* + * register midi devices + */ + while (!SLIST_EMPTY(&cd->mids)) { + cm = SLIST_FIRST(&cd->mids); + SLIST_REMOVE_HEAD(&cd->mids, entry); + if (!dev_thruadd(d, cm->path, 1, 1)) + errx(1, "%s: can't open device", cm->path); + free(cm); + } + + /* + * register files + */ + autostart = 0; + while (!SLIST_EMPTY(&cd->ins)) { + cs = SLIST_FIRST(&cd->ins); + SLIST_REMOVE_HEAD(&cd->ins, entry); + if (!cs->mmc) + autostart = 1; + if (strcmp(cs->path, "-") == 0) + cs->path = NULL; + if (!wav_new_in(&wav_ops, d, cs->mode & MODE_PLAY, + cs->path, cs->hdr, &cs->ipar, cs->xrun, + cs->vol, cs->mmc, cs->join)) + exit(1); + free(cs); + } + while (!SLIST_EMPTY(&cd->outs)) { + cs = SLIST_FIRST(&cd->outs); + SLIST_REMOVE_HEAD(&cd->outs, entry); + if (!cs->mmc) + autostart = 1; + if (strcmp(cs->path, "-") == 0) + cs->path = NULL; + if (!wav_new_out(&wav_ops, d, cs->mode & MODE_RECMASK, + cs->path, cs->hdr, &cs->opar, cs->xrun, + cs->mmc, cs->join)) + exit(1); + free(cs); + } + while (!SLIST_EMPTY(&cd->opts)) { + cs = SLIST_FIRST(&cd->opts); + SLIST_REMOVE_HEAD(&cd->opts, entry); + opt_new(cs->path, d, &cs->opar, &cs->ipar, + MIDI_TO_ADATA(cs->vol), cs->mmc, + cs->join, cs->mode); + free(cs); + } + free(cd); + if (autostart) { + /* + * inject artificial mmc start + */ + ctl_start(d->midi); + } + } + if (nsock > 0) { + snprintf(path, sizeof(path), "%s/%s%u", base, + DEFAULT_SOFTAUDIO, unit); + listen = listen_new(&listen_ops, path); + if (listen == NULL) + exit(1); + } + if (geteuid() == 0) + privdrop(); + if (l_flag) { + debug_level = 0; + dbg_flush(); + if (daemon(0, 0) < 0) + err(1, "daemon"); + } + + /* + * Loop, start audio. + */ + for (;;) { + if (quit_flag) + break; + active = 0; + for (d = dev_list; d != NULL; d = dnext) { + dnext = d->next; + if (!dev_run(d)) + goto fatal; + if (d->pstate != DEV_CLOSED && !ctl_idle(d->midi)) + active = 1; + } + if (dev_list == NULL) + break; + if (nsock == 0 && !active) + break; + if (!file_poll()) + break; + } + fatal: + if (nsock > 0) + file_close(&listen->file); + /* + * give a chance to drain + */ + for (d = dev_list; d != NULL; d = d->next) + dev_drain(d); + while (file_poll()) + ; /* nothing */ + + while (dev_list) + dev_del(dev_list); + filelist_done(); + if (nsock > 0) { + if (rmdir(base) < 0 && errno != ENOTEMPTY && errno != EPERM) + warn("rmdir(\"%s\")", base); + } + unsetsig(); + return 0; +} + +void +midicat_usage(void) +{ + (void)fputs("usage: " PROG_MIDICAT " [-dl] " + "[-i file] [-o file] [-q port] [-s name] [-U unit]\n", + stderr); +} + +int +midicat_main(int argc, char **argv) +{ + struct cfdevlist cfdevs; + struct cfmid *cm; + struct cfstr *cs; + struct cfdev *cd; + struct listen *listen = NULL; + int c, d_flag, l_flag, unit, fd; + char base[PATH_MAX], path[PATH_MAX]; + struct file *stdx; + struct aproc *p; + struct abuf *buf; + const char *str; + struct dev *d, *dnext; + unsigned nsock; + + /* + * global options defaults + */ + unit = -1; + d_flag = 0; + l_flag = 0; + SLIST_INIT(&cfdevs); + nsock = 0; + + /* + * default stream params + */ + cs = malloc(sizeof(struct cfstr)); + if (cs == NULL) { + perror("malloc"); + exit(1); + } + cs->hdr = HDR_RAW; + + /* + * default device + */ + cd = malloc(sizeof(struct cfdev)); + if (cd == NULL) { + perror("malloc"); + exit(1); + } + SLIST_INIT(&cd->ins); + SLIST_INIT(&cd->outs); + SLIST_INIT(&cd->opts); + SLIST_INIT(&cd->mids); + cd->path = NULL; + + + while ((c = getopt(argc, argv, "di:o:ls:q:U:")) != -1) { + switch (c) { + case 'd': +#ifdef DEBUG + if (d_flag) + debug_level++; +#endif + d_flag = 1; + break; + case 'i': + cfstr_add(&cd->ins, cs, optarg); + break; + case 'o': + cfstr_add(&cd->outs, cs, optarg); + break; + case 'q': + cfmid_add(&cd->mids, optarg); + break; + case 's': + cfstr_add(&cd->opts, cs, optarg); + cfdev_add(&cfdevs, cd, optarg); + nsock++; + break; + case 'l': + l_flag = 1; + break; + case 'U': + unit = strtonum(optarg, 0, MIDI_MAXCTL, &str); + if (str) + errx(1, "%s: unit number is %s", optarg, str); + break; + default: + midicat_usage(); + exit(1); + } + } + argc -= optind; + argv += optind; + +#ifdef DEBUG + if (debug_level == 0) + debug_level = 1; +#endif + if (argc > 0) { + midicat_usage(); + exit(1); + } + + /* + * If there's no device specified (-s), then create one with + * reasonable defaults: + * + * - if there are no streams (-ioq) defined, assume server mode + * and expose the "defaut" option + * + * - if there are files (-io) but no ports (-q) to send/receive + * from, add the default sndio(7) MIDI port + */ + if (SLIST_EMPTY(&cfdevs)) { + if (SLIST_EMPTY(&cd->mids)) { + if (!SLIST_EMPTY(&cd->ins) || !SLIST_EMPTY(&cd->outs)) + cfmid_add(&cd->mids, "default"); + else { + cfstr_add(&cd->opts, cs, DEFAULT_OPT); + nsock++; + } + } + cfdev_add(&cfdevs, cd, "default"); + } + if (nsock > 0) { + getbasepath(base, sizeof(path)); + if (unit < 0) + unit = 0; + } + setsig(); + filelist_init(); + + while (!SLIST_EMPTY(&cfdevs)) { + cd = SLIST_FIRST(&cfdevs); + SLIST_REMOVE_HEAD(&cfdevs, entry); + + d = dev_new_thru(); + if (d == NULL) + errx(1, "%s: can't open device", cd->path); + if (!dev_ref(d)) + errx(1, "couldn't open midi thru box"); + if (SLIST_EMPTY(&cd->opts) && APROC_OK(d->midi)) + d->midi->flags |= APROC_QUIT; + + /* + * register midi ports + */ + while (!SLIST_EMPTY(&cd->mids)) { + cm = SLIST_FIRST(&cd->mids); + SLIST_REMOVE_HEAD(&cd->mids, entry); + if (!dev_thruadd(d, cm->path, 1, 1)) + errx(1, "%s: can't open device", cm->path); + free(cm); + } + + /* + * register files + */ + while (!SLIST_EMPTY(&cd->ins)) { + cs = SLIST_FIRST(&cd->ins); + SLIST_REMOVE_HEAD(&cd->ins, entry); + if (strcmp(cs->path, "-") == 0) { + fd = STDIN_FILENO; + if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0) + warn("stdin"); + } else { + fd = open(cs->path, O_RDONLY | O_NONBLOCK, 0666); + if (fd < 0) + err(1, "%s", cs->path); + } + stdx = (struct file *)pipe_new(&pipe_ops, fd, cs->path); + p = rfile_new(stdx); + buf = abuf_new(MIDI_BUFSZ, &aparams_none); + aproc_setout(p, buf); + dev_midiattach(d, buf, NULL); + free(cs); + } + while (!SLIST_EMPTY(&cd->outs)) { + cs = SLIST_FIRST(&cd->outs); + SLIST_REMOVE_HEAD(&cd->outs, entry); + if (strcmp(cs->path, "-") == 0) { + fd = STDOUT_FILENO; + if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0) + warn("stdout"); + } else { + fd = open(cs->path, + O_WRONLY | O_TRUNC | O_CREAT | O_NONBLOCK, 0666); + if (fd < 0) + err(1, "%s", cs->path); + } + stdx = (struct file *)pipe_new(&pipe_ops, fd, cs->path); + p = wfile_new(stdx); + buf = abuf_new(MIDI_BUFSZ, &aparams_none); + aproc_setin(p, buf); + dev_midiattach(d, NULL, buf); + free(cs); + } + while (!SLIST_EMPTY(&cd->opts)) { + cs = SLIST_FIRST(&cd->opts); + SLIST_REMOVE_HEAD(&cd->opts, entry); + opt_new(cs->path, d, NULL, NULL, 0, 0, 0, 0); + free(cs); + } + free(cd); + } + if (nsock > 0) { + snprintf(path, sizeof(path), "%s/%s%u", base, + DEFAULT_MIDITHRU, unit); + listen = listen_new(&listen_ops, path); + if (listen == NULL) + exit(1); + } + if (geteuid() == 0) + privdrop(); + if (l_flag) { + debug_level = 0; + dbg_flush(); + if (daemon(0, 0) < 0) + err(1, "daemon"); + } + + /* + * loop, start processing + */ + for (;;) { + if (quit_flag) + break; + for (d = dev_list; d != NULL; d = dnext) { + dnext = d->next; + if (!dev_run(d)) + goto fatal; + } + if (!file_poll()) + break; + } + fatal: + if (nsock > 0) + file_close(&listen->file); + /* + * give a chance to drain + */ + for (d = dev_list; d != NULL; d = d->next) + dev_drain(d); + while (file_poll()) + ; /* nothing */ + + while (dev_list) + dev_del(dev_list); + filelist_done(); + if (nsock > 0) { + if (rmdir(base) < 0 && errno != ENOTEMPTY && errno != EPERM) + warn("rmdir(\"%s\")", base); + } + unsetsig(); + return 0; +} + +int +main(int argc, char **argv) +{ + char *prog; + +#ifdef DEBUG + atexit(dbg_flush); +#endif + prog = strrchr(argv[0], '/'); + if (prog == NULL) + prog = argv[0]; + else + prog++; + if (strcmp(prog, PROG_AUCAT) == 0) { + return aucat_main(argc, argv); + } else if (strcmp(prog, PROG_MIDICAT) == 0) { + return midicat_main(argc, argv); + } else { + fprintf(stderr, "%s: can't determine program to run\n", prog); + } + return 1; +} diff --git a/aucat/conf.h b/aucat/conf.h new file mode 100644 index 0000000..1923cd0 --- /dev/null +++ b/aucat/conf.h @@ -0,0 +1,50 @@ +/* $OpenBSD: conf.h,v 1.15 2010/04/06 20:07:01 ratchov Exp $ */ +/* + * Copyright (c) 2008 Alexandre Ratchov + * + * 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 CONF_H +#define CONF_H + +#ifdef DEBUG +/* + * Debug trace 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 int debug_level; +#endif + +/* + * socket and option names + */ +#define DEFAULT_MIDITHRU "midithru" +#define DEFAULT_SOFTAUDIO "softaudio" +#define DEFAULT_OPT "default" + +/* + * 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 */ + +#endif /* !defined(CONF_H) */ diff --git a/aucat/dbg.c b/aucat/dbg.c new file mode 100644 index 0000000..10cbcd7 --- /dev/null +++ b/aucat/dbg.c @@ -0,0 +1,153 @@ +#ifdef DEBUG +/* + * Copyright (c) 2003-2007 Alexandre Ratchov + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/* + * dbg_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 +#include +#include +#include "dbg.h" + +/* + * size of the buffer where traces are stored + */ +#define DBG_BUFSZ 8192 + +/* + * store a character in the trace buffer + */ +#define DBG_PUTC(c) do { \ + if (dbg_used < DBG_BUFSZ) \ + dbg_buf[dbg_used++] = (c); \ +} while (0) + +char dbg_buf[DBG_BUFSZ]; /* buffer where traces are stored */ +unsigned dbg_used = 0; /* bytes used in the buffer */ +unsigned dbg_sync = 1; /* if true, flush after each '\n' */ + +/* + * write debug info buffer on stderr + */ +void +dbg_flush(void) +{ + if (dbg_used == 0) + return; + write(STDERR_FILENO, dbg_buf, dbg_used); + dbg_used = 0; +} + +/* + * store a string in the debug buffer + */ +void +dbg_puts(char *msg) +{ + char *p = msg; + int c; + + while ((c = *p++) != '\0') { + DBG_PUTC(c); + if (dbg_sync && c == '\n') + dbg_flush(); + } +} + +/* + * store a hex in the debug buffer + */ +void +dbg_putx(unsigned long num) +{ + char dig[sizeof(num) * 2], *p = dig, c; + unsigned 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; + DBG_PUTC(c); + } + } else + DBG_PUTC('0'); +} + +/* + * store a decimal in the debug buffer + */ +void +dbg_putu(unsigned long num) +{ + char dig[sizeof(num) * 3], *p = dig; + unsigned ndig; + + if (num != 0) { + for (ndig = 0; num != 0; ndig++) { + *p++ = num % 10; + num /= 10; + } + for (; ndig != 0; ndig--) + DBG_PUTC(*(--p) + '0'); + } else + DBG_PUTC('0'); +} + +/* + * store a signed integer in the trace buffer + */ +void +dbg_puti(long num) +{ + if (num < 0) { + DBG_PUTC('-'); + num = -num; + } + dbg_putu(num); +} + +/* + * abort program execution after a fatal error, we should + * put code here to backup user data + */ +void +dbg_panic(void) +{ + dbg_flush(); + abort(); +} +#endif diff --git a/aucat/dbg.h b/aucat/dbg.h new file mode 100644 index 0000000..119aa5d --- /dev/null +++ b/aucat/dbg.h @@ -0,0 +1,45 @@ +#ifdef DEBUG +/* + * Copyright (c) 2003-2007 Alexandre Ratchov + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef MIDISH_DBG_H +#define MIDISH_DBG_H + +void dbg_puts(char *); +void dbg_putx(unsigned long); +void dbg_putu(unsigned long); +void dbg_puti(long); +void dbg_panic(void); +void dbg_flush(void); + +extern unsigned dbg_sync; + +#endif /* MIDISH_DBG_H */ +#endif /* DEBUG */ diff --git a/aucat/dev.c b/aucat/dev.c new file mode 100644 index 0000000..e56a465 --- /dev/null +++ b/aucat/dev.c @@ -0,0 +1,1168 @@ +/* $OpenBSD: dev.c,v 1.63 2010/07/31 08:46:56 ratchov Exp $ */ +/* + * Copyright (c) 2008 Alexandre Ratchov + * + * 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. + */ +/* + * Device abstraction module + * + * This module exposes a ``enhanced device'' that uses aproc + * structures framework; it does conversions on the fly and can + * handle multiple streams. The enhanced device starts and stops + * automatically, when streams are attached, and provides + * primitives for MIDI control + * + * From the main loop, the device is used as follows: + * + * 1. create the device using dev_new_xxx() + * 2. call dev_run() in the event loop + * 3. destroy the device using dev_del() + * 4. continue running the event loop to drain + * + * The device is used as follows from aproc context: + * + * 1. open the device with dev_ref() + * 2. negociate parameters (mode, rate, ...) + * 3. create your stream (ie allocate and fill abufs) + * 4. attach your stream atomically: + * - first call dev_wakeup() to ensure device is not suspended + * - possibly fetch dynamic parameters (eg. dev_getpos()) + * - attach your buffers with dev_attach() + * 5. close your stream, ie abuf_eof() or abuf_hup() + * 6. close the device with dev_unref() + * + * The device has the following states: + * + * CLOSED sio_open() is not called, it's not ready and + * no streams can be attached; dev_ref() must + * be called to open the device + * + * INIT device is opened, processing chain is ready, but + * DMA is not started yet. Streams can attach, + * in which case device will automatically switch + * to the START state + * + * START at least one stream is attached, play buffers + * are primed (if necessary) DMA is ready and + * will start immeadiately (next cycle) + * + * RUN DMA is started. New streams can attach. If the + * device is idle (all streams are closed and + * finished draining), then the device + * automatically switches to INIT or CLOSED + */ +/* + * TODO: + * + * priming buffer is not ok, because it will insert silence and + * break synchronization to other programs. + * + * priming buffer in server mode is required, because f->bufsz may + * be smaller than the server buffer and may cause underrun in the + * dev_bufsz part of the buffer, in turn causing apps to break. It + * doesn't hurt because we care only in synchronization between + * clients. + * + * Priming is not required in non-server mode, because streams + * actually start when they are in the READY state, and their + * buffer is large enough to never cause underruns of dev_bufsz. + * + * Fix sock.c to allocate dev_bufsz, but to use only appbufsz -- + * or whatever -- but to avoid underruns in dev_bufsz. Then remove + * this ugly hack. + * + */ +#include +#include +#include + +#include "abuf.h" +#include "aproc.h" +#include "conf.h" +#include "dev.h" +#include "pipe.h" +#include "miofile.h" +#include "siofile.h" +#include "midi.h" +#include "opt.h" +#ifdef DEBUG +#include "dbg.h" +#endif + +int dev_open(struct dev *); +void dev_close(struct dev *); +void dev_start(struct dev *); +void dev_stop(struct dev *); +void dev_clear(struct dev *); + +struct dev *dev_list = NULL; + +/* + * Create a sndio device + */ +struct dev * +dev_new_sio(char *path, + unsigned mode, struct aparams *dipar, struct aparams *dopar, + unsigned bufsz, unsigned round, unsigned hold) +{ + struct dev *d; + + d = malloc(sizeof(struct dev)); + if (d == NULL) { + perror("malloc"); + exit(1); + } + d->path = path; + d->reqmode = mode; + if (mode & MODE_PLAY) + d->reqopar = *dopar; + if (mode & MODE_RECMASK) + d->reqipar = *dipar; + d->reqbufsz = bufsz; + d->reqround = round; + d->hold = hold; + d->pstate = DEV_CLOSED; + d->next = dev_list; + dev_list = d; + if (d->hold && !dev_open(d)) { + dev_del(d); + return NULL; + } + return d; +} + +/* + * Create a loopback synchronous device + */ +struct dev * +dev_new_loop(struct aparams *dipar, struct aparams *dopar, unsigned bufsz) +{ + struct aparams par; + unsigned cmin, cmax, rate; + struct dev *d; + + d = malloc(sizeof(struct dev)); + if (d == NULL) { + perror("malloc"); + exit(1); + } + cmin = (dipar->cmin < dopar->cmin) ? dipar->cmin : dopar->cmin; + cmax = (dipar->cmax > dopar->cmax) ? dipar->cmax : dopar->cmax; + rate = (dipar->rate > dopar->rate) ? dipar->rate : dopar->rate; + aparams_init(&par, cmin, cmax, rate); + d->reqipar = par; + d->reqopar = par; + d->rate = rate; + d->reqround = (bufsz + 1) / 2; + d->reqbufsz = d->reqround * 2; + d->reqmode = MODE_PLAY | MODE_REC | MODE_LOOP; + d->pstate = DEV_CLOSED; + d->hold = 0; + d->path = "loop"; + d->next = dev_list; + dev_list = d; + return d; +} + +/* + * Create a MIDI thru box device + */ +struct dev * +dev_new_thru(void) +{ + struct dev *d; + + d = malloc(sizeof(struct dev)); + if (d == NULL) { + perror("malloc"); + exit(1); + } + d->reqmode = 0; + d->pstate = DEV_CLOSED; + d->hold = 0; + d->path = "midithru"; + d->next = dev_list; + dev_list = d; + return d; +} + +/* + * 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) +{ + struct file *f; + struct aparams par; + struct aproc *conv; + struct abuf *buf; + unsigned siomode; + + d->mode = d->reqmode; + d->round = d->reqround; + d->bufsz = d->reqbufsz; + d->ipar = d->reqipar; + d->opar = d->reqopar; + d->rec = NULL; + d->play = NULL; + d->mon = NULL; + d->mix = NULL; + d->sub = NULL; + d->submon = NULL; + d->midi = NULL; + d->rate = 0; + + /* + * If needed, open the device (ie create dev_rec and dev_play) + */ + if ((d->mode & (MODE_PLAY | MODE_REC)) && !(d->mode & MODE_LOOP)) { + siomode = d->mode & (MODE_PLAY | MODE_REC); + f = (struct file *)siofile_new(&siofile_ops, + d->path, + &siomode, + &d->ipar, + &d->opar, + &d->bufsz, + &d->round); + if (f == NULL) { +#ifdef DEBUG + if (debug_level >= 1) { + dbg_puts(d->path); + dbg_puts(": failed to open audio device\n"); + } +#endif + return 0; + } + if (!(siomode & MODE_PLAY)) + d->mode &= ~(MODE_PLAY | MODE_MON); + if (!(siomode & MODE_REC)) + d->mode &= ~MODE_REC; + if ((d->mode & (MODE_PLAY | MODE_REC)) == 0) { +#ifdef DEBUG + if (debug_level >= 1) { + dbg_puts(d->path); + dbg_puts(": mode not supported by device\n"); + } +#endif + return 0; + } + d->rate = d->mode & MODE_REC ? d->ipar.rate : d->opar.rate; +#ifdef DEBUG + if (debug_level >= 2) { + if (d->mode & MODE_REC) { + dbg_puts(d->path); + dbg_puts(": recording "); + aparams_dbg(&d->ipar); + dbg_puts("\n"); + } + if (d->mode & MODE_PLAY) { + dbg_puts(d->path); + dbg_puts(": playing "); + aparams_dbg(&d->opar); + dbg_puts("\n"); + } + } +#endif + if (d->mode & MODE_REC) { + d->rec = rsio_new(f); + d->rec->refs++; + } + if (d->mode & MODE_PLAY) { + d->play = wsio_new(f); + d->play->refs++; + } + } + + /* + * Create the midi control end, or a simple thru box + * if there's no device + */ + d->midi = (d->mode == 0) ? thru_new("thru") : ctl_new("ctl", d); + d->midi->refs++; + + /* + * Create mixer, demuxer and monitor + */ + if (d->mode & MODE_PLAY) { + d->mix = mix_new("play", d->bufsz, d->round); + d->mix->refs++; + d->mix->u.mix.ctl = d->midi; + } + if (d->mode & MODE_REC) { + d->sub = sub_new("rec", d->bufsz, d->round); + d->sub->refs++; + /* + * If not playing, use the record end as clock source + */ + if (!(d->mode & MODE_PLAY)) + d->sub->u.sub.ctl = d->midi; + } + if (d->mode & MODE_LOOP) { + /* + * connect mixer out to demuxer in + */ + buf = abuf_new(d->bufsz, &d->opar); + aproc_setout(d->mix, buf); + aproc_setin(d->sub, buf); + + d->mix->flags |= APROC_QUIT; + d->sub->flags |= APROC_QUIT; + d->rate = d->opar.rate; + } + if (d->rec) { + aparams_init(&par, d->ipar.cmin, d->ipar.cmax, d->rate); + + /* + * Create device <-> demuxer buffer + */ + buf = abuf_new(d->bufsz, &d->ipar); + aproc_setout(d->rec, buf); + + /* + * Insert a converter, if needed. + */ + if (!aparams_eqenc(&d->ipar, &par)) { + conv = dec_new("rec", &d->ipar); + aproc_setin(conv, buf); + buf = abuf_new(d->round, &par); + aproc_setout(conv, buf); + } + d->ipar = par; + aproc_setin(d->sub, buf); + } + if (d->play) { + aparams_init(&par, d->opar.cmin, d->opar.cmax, d->rate); + + /* + * Create device <-> mixer buffer + */ + buf = abuf_new(d->bufsz, &d->opar); + aproc_setin(d->play, buf); + + /* + * Append a converter, if needed. + */ + if (!aparams_eqenc(&par, &d->opar)) { + conv = enc_new("play", &d->opar); + aproc_setout(conv, buf); + buf = abuf_new(d->round, &par); + aproc_setin(conv, buf); + } + d->opar = par; + aproc_setout(d->mix, buf); + } + if (d->mode & MODE_MON) { + d->mon = mon_new("mon", d->bufsz); + d->mon->refs++; + buf = abuf_new(d->bufsz, &d->opar); + aproc_setout(d->mon, buf); + + /* + * Append a "sub" to which clients will connect. + */ + d->submon = sub_new("mon", d->bufsz, d->round); + d->submon->refs++; + aproc_setin(d->submon, buf); + + /* + * Attach to the mixer + */ + d->mix->u.mix.mon = d->mon; + d->mon->refs++; + } +#ifdef DEBUG + if (debug_level >= 2) { + if (d->mode & (MODE_PLAY | MODE_RECMASK)) { + dbg_puts(d->path); + dbg_puts(": block size is "); + dbg_putu(d->round); + dbg_puts(" frames, using "); + dbg_putu(d->bufsz / d->round); + dbg_puts(" blocks\n"); + } + } +#endif + d->pstate = DEV_INIT; + return 1; +} + +/* + * Cleanly stop and drain everything and close the device + * once both play chain and record chain are gone. + */ +void +dev_close(struct dev *d) +{ + struct file *f; + + /* + * if the device is starting, ensure it actually starts + * so buffers are drained, else clear any buffers + */ + switch (d->pstate) { + case DEV_START: +#ifdef DEBUG + if (debug_level >= 3) + dbg_puts("draining device\n"); +#endif + dev_start(d); + break; + case DEV_INIT: +#ifdef DEBUG + if (debug_level >= 3) + dbg_puts("flushing device\n"); +#endif + dev_clear(d); + break; + } +#ifdef DEBUG + if (debug_level >= 2) + dbg_puts("closing device\n"); +#endif + + if (d->mix) { + /* + * Put the mixer in ``autoquit'' state and generate + * EOF on all inputs connected it. Once buffers are + * drained the mixer will terminate and shutdown the + * device. + * + * NOTE: since file_eof() can destroy the file and + * reorder the file_list, we have to restart the loop + * after each call to file_eof(). + */ + if (APROC_OK(d->mix)) + mix_quit(d->mix); + + /* + * XXX: handle this in mix_done() + */ + if (APROC_OK(d->mix->u.mix.mon)) { + d->mix->u.mix.mon->refs--; + aproc_del(d->mix->u.mix.mon); + d->mix->u.mix.mon = NULL; + } + restart_mix: + LIST_FOREACH(f, &file_list, entry) { + if (f->rproc != NULL && + aproc_depend(d->mix, f->rproc)) { + file_eof(f); + goto restart_mix; + } + } + } else if (d->sub) { + /* + * Same as above, but since there's no mixer, + * we generate EOF on the record-end of the + * device. + */ + restart_sub: + LIST_FOREACH(f, &file_list, entry) { + if (f->rproc != NULL && + aproc_depend(d->sub, f->rproc)) { + file_eof(f); + goto restart_sub; + } + } + } else if (d->submon) { + /* + * Same as above + */ + restart_submon: + LIST_FOREACH(f, &file_list, entry) { + if (f->rproc != NULL && + aproc_depend(d->submon, f->rproc)) { + file_eof(f); + goto restart_submon; + } + } + } + if (d->midi) { + d->midi->flags |= APROC_QUIT; + if (LIST_EMPTY(&d->midi->ins)) + aproc_del(d->midi); + restart_midi: + LIST_FOREACH(f, &file_list, entry) { + if (f->rproc && + aproc_depend(d->midi, f->rproc)) { + file_eof(f); + goto restart_midi; + } + } + } + if (d->mix) { + if (--d->mix->refs == 0 && (d->mix->flags & APROC_ZOMB)) + aproc_del(d->mix); + d->mix = NULL; + } + if (d->play) { + if (--d->play->refs == 0 && (d->play->flags & APROC_ZOMB)) + aproc_del(d->play); + d->play = NULL; + } + if (d->sub) { + if (--d->sub->refs == 0 && (d->sub->flags & APROC_ZOMB)) + aproc_del(d->sub); + d->sub = NULL; + } + if (d->rec) { + if (--d->rec->refs == 0 && (d->rec->flags & APROC_ZOMB)) + aproc_del(d->rec); + d->rec = NULL; + } + if (d->submon) { + if (--d->submon->refs == 0 && (d->submon->flags & APROC_ZOMB)) + aproc_del(d->submon); + d->submon = NULL; + } + if (d->mon) { + if (--d->mon->refs == 0 && (d->mon->flags & APROC_ZOMB)) + aproc_del(d->mon); + d->mon = NULL; + } + if (d->midi) { + if (--d->midi->refs == 0 && (d->midi->flags & APROC_ZOMB)) + aproc_del(d->midi); + d->midi = NULL; + } + d->pstate = DEV_CLOSED; +} + +/* + * Unless the device is already in process of closing, request it to close + */ +void +dev_drain(struct dev *d) +{ + if (d->pstate != DEV_CLOSED) + dev_close(d); +} + +/* + * Free the device + */ +void +dev_del(struct dev *d) +{ + struct dev **p; + + dev_drain(d); + for (p = &dev_list; *p != d; p = &(*p)->next) { +#ifdef DEBUG + if (*p == NULL) { + dbg_puts("device to delete not on the list\n"); + dbg_panic(); + } +#endif + } + *p = d->next; + free(d); +} + +/* + * Open a MIDI device and connect it to the thru box + */ +int +dev_thruadd(struct dev *d, char *name, int in, int out) +{ + struct file *f; + struct abuf *rbuf = NULL, *wbuf = NULL; + struct aproc *rproc, *wproc; + + if (!dev_ref(d)) + return 0; + f = (struct file *)miofile_new(&miofile_ops, name, in, out); + if (f == NULL) + return 0; + if (in) { + rproc = rfile_new(f); + rbuf = abuf_new(MIDI_BUFSZ, &aparams_none); + aproc_setout(rproc, rbuf); + } + if (out) { + wproc = wfile_new(f); + wbuf = abuf_new(MIDI_BUFSZ, &aparams_none); + aproc_setin(wproc, wbuf); + } + dev_midiattach(d, rbuf, wbuf); + return 1; +} + +/* + * Attach a bi-directional MIDI stream to the MIDI device + */ +void +dev_midiattach(struct dev *d, struct abuf *ibuf, struct abuf *obuf) +{ + if (ibuf) + aproc_setin(d->midi, ibuf); + if (obuf) { + aproc_setout(d->midi, obuf); + if (ibuf) { + ibuf->duplex = obuf; + obuf->duplex = ibuf; + } + } +} + +unsigned +dev_roundof(struct dev *d, unsigned newrate) +{ + return (d->round * newrate + d->rate / 2) / d->rate; +} + +/* + * Start the (paused) device. By default it's paused. + */ +void +dev_start(struct dev *d) +{ + struct file *f; + +#ifdef DEBUG + if (debug_level >= 2) + dbg_puts("starting device\n"); +#endif + d->pstate = DEV_RUN; + if (d->mode & MODE_LOOP) + return; + if (APROC_OK(d->mix)) + d->mix->flags |= APROC_DROP; + if (APROC_OK(d->sub)) + d->sub->flags |= APROC_DROP; + if (APROC_OK(d->submon)) + d->submon->flags |= APROC_DROP; + if (APROC_OK(d->play) && d->play->u.io.file) { + f = d->play->u.io.file; + f->ops->start(f); + } else if (APROC_OK(d->rec) && d->rec->u.io.file) { + f = d->rec->u.io.file; + f->ops->start(f); + } +} + +/* + * Pause the device. This may trigger context switches, + * so it shouldn't be called from aproc methods + */ +void +dev_stop(struct dev *d) +{ + struct file *f; + +#ifdef DEBUG + if (debug_level >= 2) + dbg_puts("device stopped\n"); +#endif + d->pstate = DEV_INIT; + if (d->mode & MODE_LOOP) + return; + if (APROC_OK(d->play) && d->play->u.io.file) { + f = d->play->u.io.file; + f->ops->stop(f); + } else if (APROC_OK(d->rec) && d->rec->u.io.file) { + f = d->rec->u.io.file; + f->ops->stop(f); + } + if (APROC_OK(d->mix)) + d->mix->flags &= ~APROC_DROP; + if (APROC_OK(d->sub)) + d->sub->flags &= ~APROC_DROP; + if (APROC_OK(d->submon)) + d->submon->flags &= ~APROC_DROP; +} + +int +dev_ref(struct dev *d) +{ +#ifdef DEBUG + if (debug_level >= 3) + dbg_puts("device requested\n"); +#endif + if (d->pstate == DEV_CLOSED && !dev_open(d)) { + if (d->hold) + dev_del(d); + return 0; + } + d->refcnt++; + return 1; +} + +void +dev_unref(struct dev *d) +{ +#ifdef DEBUG + if (debug_level >= 3) + dbg_puts("device released\n"); +#endif + d->refcnt--; + if (d->refcnt == 0 && d->pstate == DEV_INIT && !d->hold) + dev_close(d); +} + +/* + * There are actions (like start/stop/close ... ) that may trigger aproc + * operations, a thus cannot be started from aproc context. + * To avoid problems, aprocs only change the s!tate of the device, + * and actual operations are triggered from the main loop, + * outside the aproc code path. + * + * The following routine invokes pending actions, returns 0 + * on fatal error + */ +int +dev_run(struct dev *d) +{ + if (d->pstate == DEV_CLOSED) + return 1; + /* + * check if device isn't gone + */ + if (((d->mode & MODE_PLAY) && !APROC_OK(d->mix)) || + ((d->mode & MODE_REC) && !APROC_OK(d->sub)) || + ((d->mode & MODE_MON) && !APROC_OK(d->submon))) { +#ifdef DEBUG + if (debug_level >= 1) + dbg_puts("device disappeared\n"); +#endif + if (d->hold) { + dev_del(d); + return 0; + } + dev_close(d); + return 1; + } + switch (d->pstate) { + case DEV_INIT: + /* nothing */ + break; + case DEV_START: + dev_start(d); + /* PASSTHROUGH */ + case DEV_RUN: + /* + * if the device is not used, then stop it + */ + if ((!APROC_OK(d->mix) || + d->mix->u.mix.idle > 2 * d->bufsz) && + (!APROC_OK(d->sub) || + d->sub->u.sub.idle > 2 * d->bufsz) && + (!APROC_OK(d->submon) || + d->submon->u.sub.idle > 2 * d->bufsz) && + (!APROC_OK(d->midi) || + d->midi->u.ctl.tstate != CTL_RUN)) { +#ifdef DEBUG + if (debug_level >= 3) + dbg_puts("device idle, suspending\n"); +#endif + dev_stop(d); + if (d->refcnt == 0 && !d->hold) + dev_close(d); + else + dev_clear(d); + } + break; + } + return 1; +} + +/* + * If the device is paused, then resume it. + * This routine can be called from aproc context. + */ +void +dev_wakeup(struct dev *d) +{ + if (d->pstate == DEV_INIT) + d->pstate = DEV_START; +} + +/* + * Find the end points connected to the mix/sub. + */ +int +dev_getep(struct dev *d, + unsigned mode, struct abuf **sibuf, struct abuf **sobuf) +{ + struct abuf *ibuf, *obuf; + + if (mode & MODE_PLAY) { + if (!APROC_OK(d->mix)) + return 0; + ibuf = *sibuf; + for (;;) { + if (!ibuf || !ibuf->rproc) { +#ifdef DEBUG + if (debug_level >= 3) { + abuf_dbg(*sibuf); + dbg_puts(": not connected to device\n"); + } +#endif + return 0; + } + if (ibuf->rproc == d->mix) + break; + ibuf = LIST_FIRST(&ibuf->rproc->outs); + } + *sibuf = ibuf; + } + if (mode & MODE_REC) { + if (!APROC_OK(d->sub)) + return 0; + obuf = *sobuf; + for (;;) { + if (!obuf || !obuf->wproc) { +#ifdef DEBUG + if (debug_level >= 3) { + abuf_dbg(*sobuf); + dbg_puts(": not connected to device\n"); + } +#endif + return 0; + } + if (obuf->wproc == d->sub) + break; + obuf = LIST_FIRST(&obuf->wproc->ins); + } + *sobuf = obuf; + } + if (mode & MODE_MON) { + if (!APROC_OK(d->submon)) + return 0; + obuf = *sobuf; + for (;;) { + if (!obuf || !obuf->wproc) { +#ifdef DEBUG + if (debug_level >= 3) { + abuf_dbg(*sobuf); + dbg_puts(": not connected to device\n"); + } +#endif + return 0; + } + if (obuf->wproc == d->submon) + break; + obuf = LIST_FIRST(&obuf->wproc->ins); + } + *sobuf = obuf; + } + return 1; +} + +/* + * Sync play buffer to rec buffer (for instance when one of + * them underruns/overruns). + */ +void +dev_sync(struct dev *d, unsigned mode, struct abuf *ibuf, struct abuf *obuf) +{ + int delta, offs; + struct abuf *mbuf = NULL; + + if (!dev_getep(d, mode, &ibuf, &obuf)) + return; + /* + * Calculate delta, the number of frames the play chain is ahead + * of the record chain. It's necessary to schedule silences (or + * drops) in order to start playback and record in sync. + */ + offs = 0; + delta = 0; + if (APROC_OK(d->mix)) { + mbuf = LIST_FIRST(&d->mix->outs); + offs += mbuf->w.mix.todo; + delta += d->mix->u.mix.lat; + } + if (APROC_OK(d->sub)) + delta += d->sub->u.sub.lat; +#ifdef DEBUG + if (debug_level >= 3) { + dbg_puts("syncing device"); + if (APROC_OK(d->mix)) { + dbg_puts(", "); + aproc_dbg(d->mix); + dbg_puts(": todo = "); + dbg_putu(mbuf->w.mix.todo); + dbg_puts(": lat = "); + dbg_putu(d->mix->u.mix.lat); + } + if (APROC_OK(d->sub)) { + dbg_puts(", "); + aproc_dbg(d->sub); + dbg_puts(": lat = "); + dbg_putu(d->sub->u.sub.lat); + } + dbg_puts("\n"); + } +#endif + if (mode & MODE_PLAY) + mix_drop(ibuf, -offs); + if (mode & MODE_RECMASK) + sub_silence(obuf, -(offs + delta)); +} + +/* + * return the current latency (in frames), ie the latency that + * a stream would have if dev_attach() is called on it. + */ +int +dev_getpos(struct dev *d) +{ + struct abuf *mbuf = NULL; + + if (APROC_OK(d->mix)) { + mbuf = LIST_FIRST(&d->mix->outs); + return -(mbuf->w.mix.todo + d->mix->u.mix.lat); + } else + return 0; +} + +/* + * Attach the given input and output buffers to the mixer and the + * multiplexer respectively. The operation is done synchronously, so + * both buffers enter in sync. If buffers do not match play + * and rec. + */ +void +dev_attach(struct dev *d, char *name, unsigned mode, + struct abuf *ibuf, struct aparams *sipar, unsigned inch, + struct abuf *obuf, struct aparams *sopar, unsigned onch, + unsigned xrun, int vol) +{ + struct abuf *pbuf = NULL, *rbuf = NULL; + struct aparams ipar, opar; + struct aproc *conv; + unsigned round, nblk, nch; + +#ifdef DEBUG + if ((!APROC_OK(d->mix) && (mode & MODE_PLAY)) || + (!APROC_OK(d->sub) && (mode & MODE_REC)) || + (!APROC_OK(d->submon) && (mode & MODE_MON))) { + dbg_puts("mode beyond device mode, not attaching\n"); + return; + } +#endif + if (mode & MODE_PLAY) { + ipar = *sipar; + pbuf = LIST_FIRST(&d->mix->outs); + nblk = (d->bufsz / d->round + 3) / 4; + round = dev_roundof(d, ipar.rate); + nch = ipar.cmax - ipar.cmin + 1; + if (!aparams_eqenc(&ipar, &d->opar)) { + conv = dec_new(name, &ipar); + ipar.bps = d->opar.bps; + ipar.bits = d->opar.bits; + ipar.sig = d->opar.sig; + ipar.le = d->opar.le; + ipar.msb = d->opar.msb; + aproc_setin(conv, ibuf); + ibuf = abuf_new(nblk * round, &ipar); + aproc_setout(conv, ibuf); + } + if (inch > 0 && nch >= inch * 2) { + conv = join_new(name); + aproc_setin(conv, ibuf); + ipar.cmax = ipar.cmin + inch - 1; + ibuf = abuf_new(nblk * round, &ipar); + aproc_setout(conv, ibuf); + } + if (!aparams_eqrate(&ipar, &d->opar)) { + conv = resamp_new(name, round, d->round); + ipar.rate = d->opar.rate; + round = d->round; + aproc_setin(conv, ibuf); + ibuf = abuf_new(nblk * round, &ipar); + aproc_setout(conv, ibuf); + } + if (inch > 0 && nch * 2 <= inch) { + conv = join_new(name); + aproc_setin(conv, ibuf); + ipar.cmax = ipar.cmin + inch - 1; + ibuf = abuf_new(nblk * round, &ipar); + aproc_setout(conv, ibuf); + } + aproc_setin(d->mix, ibuf); + ibuf->r.mix.xrun = xrun; + ibuf->r.mix.maxweight = vol; + mix_setmaster(d->mix); + } + if (mode & MODE_REC) { + opar = *sopar; + rbuf = LIST_FIRST(&d->sub->ins); + round = dev_roundof(d, opar.rate); + nblk = (d->bufsz / d->round + 3) / 4; + nch = opar.cmax - opar.cmin + 1; + if (!aparams_eqenc(&opar, &d->ipar)) { + conv = enc_new(name, &opar); + opar.bps = d->ipar.bps; + opar.bits = d->ipar.bits; + opar.sig = d->ipar.sig; + opar.le = d->ipar.le; + opar.msb = d->ipar.msb; + aproc_setout(conv, obuf); + obuf = abuf_new(nblk * round, &opar); + aproc_setin(conv, obuf); + } + if (onch > 0 && nch >= onch * 2) { + conv = join_new(name); + aproc_setout(conv, obuf); + opar.cmax = opar.cmin + onch - 1; + obuf = abuf_new(nblk * round, &opar); + aproc_setin(conv, obuf); + } + if (!aparams_eqrate(&opar, &d->ipar)) { + conv = resamp_new(name, d->round, round); + opar.rate = d->ipar.rate; + round = d->round; + aproc_setout(conv, obuf); + obuf = abuf_new(nblk * round, &opar); + aproc_setin(conv, obuf); + } + if (onch > 0 && nch * 2 <= onch) { + conv = join_new(name); + aproc_setout(conv, obuf); + opar.cmax = opar.cmin + onch - 1; + obuf = abuf_new(nblk * round, &opar); + aproc_setin(conv, obuf); + } + aproc_setout(d->sub, obuf); + obuf->w.sub.xrun = xrun; + } + if (mode & MODE_MON) { + opar = *sopar; + rbuf = LIST_FIRST(&d->submon->ins); + round = dev_roundof(d, opar.rate); + nblk = (d->bufsz / d->round + 3) / 4; + nch = opar.cmax - opar.cmin + 1; + if (!aparams_eqenc(&opar, &d->opar)) { + conv = enc_new(name, &opar); + opar.bps = d->opar.bps; + opar.bits = d->opar.bits; + opar.sig = d->opar.sig; + opar.le = d->opar.le; + opar.msb = d->opar.msb; + aproc_setout(conv, obuf); + obuf = abuf_new(nblk * round, &opar); + aproc_setin(conv, obuf); + } + if (onch > 0 && nch >= onch * 2) { + conv = join_new(name); + aproc_setout(conv, obuf); + opar.cmax = opar.cmin + onch - 1; + obuf = abuf_new(nblk * round, &opar); + aproc_setin(conv, obuf); + } + if (!aparams_eqrate(&opar, &d->opar)) { + conv = resamp_new(name, d->round, round); + opar.rate = d->opar.rate; + round = d->round; + aproc_setout(conv, obuf); + obuf = abuf_new(nblk * round, &opar); + aproc_setin(conv, obuf); + } + if (onch > 0 && nch * 2 <= onch) { + conv = join_new(name); + aproc_setout(conv, obuf); + opar.cmax = opar.cmin + onch - 1; + obuf = abuf_new(nblk * round, &opar); + aproc_setin(conv, obuf); + } + aproc_setout(d->submon, obuf); + obuf->w.sub.xrun = xrun; + } + + /* + * Sync play to record. + */ + if ((mode & MODE_PLAY) && (mode & MODE_RECMASK)) { + ibuf->duplex = obuf; + obuf->duplex = ibuf; + } + dev_sync(d, mode, ibuf, obuf); +} + +/* + * Change the playback volume of the given stream. + */ +void +dev_setvol(struct dev *d, struct abuf *ibuf, int vol) +{ +#ifdef DEBUG + if (debug_level >= 3) { + abuf_dbg(ibuf); + dbg_puts(": setting volume to "); + dbg_putu(vol); + dbg_puts("\n"); + } +#endif + if (!dev_getep(d, MODE_PLAY, &ibuf, NULL)) { + return; + } + ibuf->r.mix.vol = vol; +} + +/* + * 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) +{ + struct abuf *buf; + + if (APROC_OK(d->mix)) { +#ifdef DEBUG + if (!LIST_EMPTY(&d->mix->ins)) { + dbg_puts("play end not idle, can't clear device\n"); + dbg_panic(); + } +#endif + buf = LIST_FIRST(&d->mix->outs); + while (buf) { + abuf_clear(buf); + buf = LIST_FIRST(&buf->rproc->outs); + } + mix_clear(d->mix); + } + if (APROC_OK(d->sub)) { +#ifdef DEBUG + if (!LIST_EMPTY(&d->sub->outs)) { + dbg_puts("record end not idle, can't clear device\n"); + dbg_panic(); + } +#endif + buf = LIST_FIRST(&d->sub->ins); + while (buf) { + abuf_clear(buf); + buf = LIST_FIRST(&buf->wproc->ins); + } + sub_clear(d->sub); + } + if (APROC_OK(d->submon)) { +#ifdef DEBUG + if (!LIST_EMPTY(&d->submon->outs)) { + dbg_puts("monitoring end not idle, can't clear device\n"); + dbg_panic(); + } +#endif + buf = LIST_FIRST(&d->submon->ins); + while (buf) { + abuf_clear(buf); + buf = LIST_FIRST(&buf->wproc->ins); + } + sub_clear(d->submon); + mon_clear(d->mon); + } +} diff --git a/aucat/dev.h b/aucat/dev.h new file mode 100644 index 0000000..58ad1bd --- /dev/null +++ b/aucat/dev.h @@ -0,0 +1,78 @@ +/* $OpenBSD: dev.h,v 1.27 2010/07/06 01:12:45 ratchov Exp $ */ +/* + * Copyright (c) 2008 Alexandre Ratchov + * + * 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 "aparams.h" + +struct aproc; +struct abuf; + +struct dev { + struct dev *next; + + /* + * desired parameters + */ + unsigned reqmode; /* mode */ + struct aparams reqipar, reqopar; /* parameters */ + unsigned reqbufsz; /* buffer size */ + unsigned reqround; /* block size */ + unsigned reqrate; /* sample rate */ + unsigned hold; /* hold the device open ? */ + unsigned refcnt; /* number of openers */ +#define DEV_CLOSED 0 /* closed */ +#define DEV_INIT 1 /* stopped */ +#define DEV_START 2 /* ready to start */ +#define DEV_RUN 3 /* started */ + unsigned pstate; /* on of DEV_xxx */ + char *path; /* sio path */ + + /* + * actual parameters and runtime state (i.e. once opened) + */ + unsigned mode; /* bitmap of MODE_xxx */ + unsigned bufsz, round, rate; + struct aparams ipar, opar; + struct aproc *mix, *sub, *submon; + struct aproc *rec, *play, *mon; + struct aproc *midi; +}; + +extern struct dev *dev_list; + +int dev_run(struct dev *); +int dev_ref(struct dev *); +void dev_unref(struct dev *); +void dev_del(struct dev *); +void dev_wakeup(struct dev *); +void dev_drain(struct dev *); +struct dev *dev_new_thru(void); +struct dev *dev_new_loop(struct aparams *, struct aparams *, unsigned); +struct dev *dev_new_sio(char *, unsigned, + struct aparams *, struct aparams *, unsigned, unsigned, unsigned); +int dev_thruadd(struct dev *, char *, int, int); +void dev_midiattach(struct dev *, struct abuf *, struct abuf *); +unsigned dev_roundof(struct dev *, unsigned); +int dev_getpos(struct dev *); +void dev_attach(struct dev *, char *, unsigned, + struct abuf *, struct aparams *, unsigned, + struct abuf *, struct aparams *, unsigned, + unsigned, int); +void dev_setvol(struct dev *, struct abuf *, int); + +#endif /* !define(DEV_H) */ diff --git a/aucat/file.c b/aucat/file.c new file mode 100644 index 0000000..e1eefa8 --- /dev/null +++ b/aucat/file.c @@ -0,0 +1,743 @@ +/* $OpenBSD: file.c,v 1.21 2010/07/10 12:32:45 ratchov Exp $ */ +/* + * Copyright (c) 2008 Alexandre Ratchov + * + * 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. If a read() or write() syscall return EAGAIN + * (operation will block), then the file is marked as "for polling", else + * the file is not polled again. + * + * the module also provides trivial timeout implementation, + * derived from: + * + * anoncvs@moule.caoua.org:/cvs + * + * midish/timo.c rev 1.16 + * midish/mdep.c rev 1.69 + * + * 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 + +#include +#include +#include +#include +#include +#include +#include + +#include "abuf.h" +#include "aproc.h" +#include "conf.h" +#include "file.h" +#ifdef DEBUG +#include "dbg.h" +#endif + +#define MAXFDS 100 +#define TIMER_USEC 10000 + +struct timespec file_ts; +struct filelist file_list; +struct timo *timo_queue; +unsigned timo_abstime; + +/* + * 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 delta) +{ + struct timo **i; + unsigned val; + int diff; + +#ifdef DEBUG + if (o->set) { + dbg_puts("timo_add: already set\n"); + dbg_panic(); + } + if (delta == 0) { + dbg_puts("timo_add: zero timeout is evil\n"); + dbg_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 (debug_level >= 4) + dbg_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 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) { + dbg_puts("timo_done: timo_queue not empty!\n"); + dbg_panic(); + } +#endif + timo_queue = (struct timo *)0xdeadbeef; +} + +#ifdef DEBUG +void +file_dbg(struct file *f) +{ + dbg_puts(f->ops->name); + dbg_puts("("); + dbg_puts(f->name); + dbg_puts("|"); + if (f->state & FILE_ROK) + dbg_puts("r"); + if (f->state & FILE_RINUSE) + dbg_puts("R"); + if (f->state & FILE_WOK) + dbg_puts("w"); + if (f->state & FILE_WINUSE) + dbg_puts("W"); + if (f->state & FILE_EOF) + dbg_puts("e"); + if (f->state & FILE_HUP) + dbg_puts("h"); + if (f->state & FILE_ZOMB) + dbg_puts("Z"); + dbg_puts(")"); +} +#endif + +struct file * +file_new(struct fileops *ops, char *name, unsigned nfds) +{ + struct file *f; + + LIST_FOREACH(f, &file_list, entry) + nfds += f->ops->nfds(f); + if (nfds > MAXFDS) { +#ifdef DEBUG + if (debug_level >= 1) { + dbg_puts(name); + dbg_puts(": too many polled files\n"); + } +#endif + return NULL; + } + f = malloc(ops->size); + if (f == NULL) + err(1, "file_new: %s", ops->name); + f->ops = ops; + f->name = name; + f->state = 0; + f->cycles = 0; + f->rproc = NULL; + f->wproc = NULL; + LIST_INSERT_HEAD(&file_list, f, entry); +#ifdef DEBUG + if (debug_level >= 3) { + file_dbg(f); + dbg_puts(": created\n"); + } +#endif + return f; +} + +void +file_del(struct file *f) +{ +#ifdef DEBUG + if (debug_level >= 3) { + file_dbg(f); + dbg_puts(": terminating...\n"); + } +#endif + if (f->state & (FILE_RINUSE | FILE_WINUSE)) { + f->state |= FILE_ZOMB; + } else { + LIST_REMOVE(f, entry); +#ifdef DEBUG + if (debug_level >= 3) { + file_dbg(f); + dbg_puts(": destroyed\n"); + } +#endif + f->ops->close(f); + free(f); + } +} + +int +file_poll(void) +{ + nfds_t nfds, n; + short events, revents; + struct pollfd pfds[MAXFDS]; + struct file *f, *fnext; + struct aproc *p; + struct timespec ts; + long delta_nsec; + + if (LIST_EMPTY(&file_list)) { +#ifdef DEBUG + if (debug_level >= 3) + dbg_puts("nothing to do...\n"); +#endif + return 0; + } + /* + * Fill the pfds[] array with files that are blocked on reading + * and/or writing, skipping those that are just waiting. + */ +#ifdef DEBUG + dbg_flush(); + if (debug_level >= 4) + dbg_puts("poll:"); +#endif + nfds = 0; + LIST_FOREACH(f, &file_list, entry) { + events = 0; + if (f->rproc && !(f->state & FILE_ROK)) + events |= POLLIN; + if (f->wproc && !(f->state & FILE_WOK)) + events |= POLLOUT; +#ifdef DEBUG + if (debug_level >= 4) { + dbg_puts(" "); + file_dbg(f); + } +#endif + n = f->ops->pollfd(f, pfds + nfds, events); + if (n == 0) { + f->pfd = NULL; + continue; + } + f->pfd = pfds + nfds; + nfds += n; + } +#ifdef DEBUG + if (debug_level >= 4) { + dbg_puts("\npfds[] ="); + for (n = 0; n < nfds; n++) { + dbg_puts(" "); + dbg_putx(pfds[n].events); + } + dbg_puts("\n"); + } +#endif + if (nfds > 0) { + if (poll(pfds, nfds, -1) < 0) { + if (errno == EINTR) + return 1; + err(1, "file_poll: poll failed"); + } + clock_gettime(CLOCK_MONOTONIC, &ts); + delta_nsec = 1000000000L * (ts.tv_sec - file_ts.tv_sec); + delta_nsec += ts.tv_nsec - file_ts.tv_nsec; + if (delta_nsec > 0) { + file_ts = ts; + timo_update(delta_nsec / 1000); + } + } + f = LIST_FIRST(&file_list); + while (f != LIST_END(&file_list)) { + if (f->pfd == NULL) { + f = LIST_NEXT(f, entry); + continue; + } + revents = f->ops->revents(f, f->pfd); +#ifdef DEBUG + if (revents) { + f->cycles++; + if (f->cycles > FILE_MAXCYCLES) { + file_dbg(f); + dbg_puts(": busy loop, disconnecting\n"); + revents = POLLHUP; + } + } +#endif + if (!(f->state & FILE_ZOMB) && (revents & POLLIN)) { + revents &= ~POLLIN; +#ifdef DEBUG + if (debug_level >= 4) { + file_dbg(f); + dbg_puts(": rok\n"); + } +#endif + f->state |= FILE_ROK; + f->state |= FILE_RINUSE; + for (;;) { + p = f->rproc; + if (!p) + break; +#ifdef DEBUG + if (debug_level >= 4) { + aproc_dbg(p); + dbg_puts(": in\n"); + } +#endif + if (!p->ops->in(p, NULL)) + break; + } + f->state &= ~FILE_RINUSE; + } + if (!(f->state & FILE_ZOMB) && (revents & POLLOUT)) { + revents &= ~POLLOUT; +#ifdef DEBUG + if (debug_level >= 4) { + file_dbg(f); + dbg_puts(": wok\n"); + } +#endif + f->state |= FILE_WOK; + f->state |= FILE_WINUSE; + for (;;) { + p = f->wproc; + if (!p) + break; +#ifdef DEBUG + if (debug_level >= 4) { + aproc_dbg(p); + dbg_puts(": out\n"); + } +#endif + if (!p->ops->out(p, NULL)) + break; + } + f->state &= ~FILE_WINUSE; + } + if (!(f->state & FILE_ZOMB) && (revents & POLLHUP)) { +#ifdef DEBUG + if (debug_level >= 3) { + file_dbg(f); + dbg_puts(": disconnected\n"); + } +#endif + f->state |= (FILE_EOF | FILE_HUP); + } + if (!(f->state & FILE_ZOMB) && (f->state & FILE_EOF)) { +#ifdef DEBUG + if (debug_level >= 3) { + file_dbg(f); + dbg_puts(": eof\n"); + } +#endif + p = f->rproc; + if (p) { + f->state |= FILE_RINUSE; +#ifdef DEBUG + if (debug_level >= 3) { + aproc_dbg(p); + dbg_puts(": eof\n"); + } +#endif + p->ops->eof(p, NULL); + f->state &= ~FILE_RINUSE; + } + f->state &= ~FILE_EOF; + } + if (!(f->state & FILE_ZOMB) && (f->state & FILE_HUP)) { +#ifdef DEBUG + if (debug_level >= 3) { + file_dbg(f); + dbg_puts(": hup\n"); + } +#endif + p = f->wproc; + if (p) { + f->state |= FILE_WINUSE; +#ifdef DEBUG + if (debug_level >= 3) { + aproc_dbg(p); + dbg_puts(": hup\n"); + } +#endif + p->ops->hup(p, NULL); + f->state &= ~FILE_WINUSE; + } + f->state &= ~FILE_HUP; + } + fnext = LIST_NEXT(f, entry); + if (f->state & FILE_ZOMB) + file_del(f); + f = fnext; + } + if (LIST_EMPTY(&file_list)) { +#ifdef DEBUG + if (debug_level >= 3) + dbg_puts("no files anymore...\n"); +#endif + return 0; + } + 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"); + LIST_INIT(&file_list); + 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); + } + timo_init(); +#ifdef DEBUG + dbg_sync = 0; +#endif +} + +void +filelist_done(void) +{ + struct itimerval it; +#ifdef DEBUG + struct file *f; + + if (!LIST_EMPTY(&file_list)) { + LIST_FOREACH(f, &file_list, entry) { + file_dbg(f); + dbg_puts(" not closed\n"); + } + dbg_panic(); + } + dbg_sync = 1; + dbg_flush(); +#endif + it.it_value.tv_sec = 0; + it.it_value.tv_usec = 0; + it.it_interval.tv_sec = 0; + it.it_interval.tv_usec = 0; + if (setitimer(ITIMER_REAL, &it, NULL) < 0) { + perror("setitimer"); + exit(1); + } + timo_done(); +} + +unsigned +file_read(struct file *f, unsigned char *data, unsigned count) +{ + unsigned n; +#ifdef DEBUG + struct timespec ts0, ts1; + long us; + + if (!(f->state & FILE_ROK)) { + file_dbg(f); + dbg_puts(": read: bad state\n"); + dbg_panic(); + } + clock_gettime(CLOCK_MONOTONIC, &ts0); +#endif + n = f->ops->read(f, data, count); +#ifdef DEBUG + if (n > 0) + f->cycles = 0; + clock_gettime(CLOCK_MONOTONIC, &ts1); + us = 1000000L * (ts1.tv_sec - ts0.tv_sec); + us += (ts1.tv_nsec - ts0.tv_nsec) / 1000; + if (debug_level >= 4 || (debug_level >= 2 && us >= 5000)) { + dbg_puts(f->name); + dbg_puts(": read "); + dbg_putu(n); + dbg_puts(" bytes in "); + dbg_putu(us); + dbg_puts("us\n"); + } +#endif + return n; +} + +unsigned +file_write(struct file *f, unsigned char *data, unsigned count) +{ + unsigned n; +#ifdef DEBUG + struct timespec ts0, ts1; + long us; + + if (!(f->state & FILE_WOK)) { + file_dbg(f); + dbg_puts(": write: bad state\n"); + dbg_panic(); + } + clock_gettime(CLOCK_MONOTONIC, &ts0); +#endif + n = f->ops->write(f, data, count); +#ifdef DEBUG + if (n > 0) + f->cycles = 0; + clock_gettime(CLOCK_MONOTONIC, &ts1); + us = 1000000L * (ts1.tv_sec - ts0.tv_sec); + us += (ts1.tv_nsec - ts0.tv_nsec) / 1000; + if (debug_level >= 4 || (debug_level >= 2 && us >= 5000)) { + dbg_puts(f->name); + dbg_puts(": wrote "); + dbg_putu(n); + dbg_puts(" bytes in "); + dbg_putu(us); + dbg_puts("us\n"); + } +#endif + return n; +} + +void +file_eof(struct file *f) +{ + struct aproc *p; + +#ifdef DEBUG + if (debug_level >= 3) { + file_dbg(f); + dbg_puts(": eof requested\n"); + } +#endif + if (!(f->state & (FILE_RINUSE | FILE_WINUSE))) { + p = f->rproc; + if (p) { + f->state |= FILE_RINUSE; +#ifdef DEBUG + if (debug_level >= 3) { + aproc_dbg(p); + dbg_puts(": eof\n"); + } +#endif + p->ops->eof(p, NULL); + f->state &= ~FILE_RINUSE; + } + if (f->state & FILE_ZOMB) + file_del(f); + } else { + f->state &= ~FILE_ROK; + f->state |= FILE_EOF; + } +} + +void +file_hup(struct file *f) +{ + struct aproc *p; + +#ifdef DEBUG + if (debug_level >= 3) { + file_dbg(f); + dbg_puts(": hup requested\n"); + } +#endif + if (!(f->state & (FILE_RINUSE | FILE_WINUSE))) { + p = f->wproc; + if (p) { + f->state |= FILE_WINUSE; +#ifdef DEBUG + if (debug_level >= 3) { + aproc_dbg(p); + dbg_puts(": hup\n"); + } +#endif + p->ops->hup(p, NULL); + f->state &= ~FILE_WINUSE; + } + if (f->state & FILE_ZOMB) + file_del(f); + } else { + f->state &= ~FILE_WOK; + f->state |= FILE_HUP; + } +} + +void +file_close(struct file *f) +{ + struct aproc *p; + +#ifdef DEBUG + if (debug_level >= 3) { + file_dbg(f); + dbg_puts(": closing\n"); + } +#endif + if (f->wproc == NULL && f->rproc == NULL) + f->state |= FILE_ZOMB; + if (!(f->state & (FILE_RINUSE | FILE_WINUSE))) { + p = f->rproc; + if (p) { + f->state |= FILE_RINUSE; +#ifdef DEBUG + if (debug_level >= 3) { + aproc_dbg(p); + dbg_puts(": eof\n"); + } +#endif + p->ops->eof(p, NULL); + f->state &= ~FILE_RINUSE; + } + p = f->wproc; + if (p) { + f->state |= FILE_WINUSE; +#ifdef DEBUG + if (debug_level >= 3) { + aproc_dbg(p); + dbg_puts(": hup\n"); + } +#endif + p->ops->hup(p, NULL); + f->state &= ~FILE_WINUSE; + } + if (f->state & FILE_ZOMB) + file_del(f); + } else { + f->state &= ~(FILE_ROK | FILE_WOK); + f->state |= (FILE_EOF | FILE_HUP); + } +} diff --git a/aucat/file.h b/aucat/file.h new file mode 100644 index 0000000..5431025 --- /dev/null +++ b/aucat/file.h @@ -0,0 +1,92 @@ +/* $OpenBSD: file.h,v 1.10 2010/07/06 20:06:35 ratchov Exp $ */ +/* + * Copyright (c) 2008 Alexandre Ratchov + * + * 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 +#include + +struct file; +struct aproc; +struct pollfd; + +struct timo { + struct timo *next; + unsigned val; /* time to wait before the callback */ + unsigned 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; + size_t size; + void (*close)(struct file *); + unsigned (*read)(struct file *, unsigned char *, unsigned); + unsigned (*write)(struct file *, unsigned char *, unsigned); + void (*start)(struct file *); + void (*stop)(struct file *); + int (*nfds)(struct file *); + int (*pollfd)(struct file *, struct pollfd *, int); + int (*revents)(struct file *, struct pollfd *); +}; + +struct file { + struct fileops *ops; + struct pollfd *pfd; /* arg to poll(2) syscall */ +#define FILE_ROK 0x1 /* file readable */ +#define FILE_WOK 0x2 /* file writable */ +#define FILE_EOF 0x4 /* eof on the read end */ +#define FILE_HUP 0x8 /* hang-up on the write end */ +#define FILE_ZOMB 0x10 /* closed, but struct not freed */ +#define FILE_RINUSE 0x20 /* inside rproc->ops->in() */ +#define FILE_WINUSE 0x40 /* inside wproc->ops->out() */ + unsigned state; /* one of above */ +#ifdef DEBUG +#define FILE_MAXCYCLES 20 + unsigned cycles; /* number of POLLIN/POLLOUT events */ +#endif + char *name; /* for debug purposes */ + struct aproc *rproc, *wproc; /* reader and/or writer */ + LIST_ENTRY(file) entry; +}; + +LIST_HEAD(filelist,file); + +extern struct filelist file_list; + +void timo_set(struct timo *, void (*)(void *), void *); +void timo_add(struct timo *, unsigned); +void timo_del(struct timo *); + +void filelist_init(void); +void filelist_done(void); +void filelist_unlisten(void); + +struct file *file_new(struct fileops *, char *, unsigned); +void file_del(struct file *); +void file_dbg(struct file *); + +void file_attach(struct file *, struct aproc *, struct aproc *); +unsigned file_read(struct file *, unsigned char *, unsigned); +unsigned file_write(struct file *, unsigned char *, unsigned); +int file_poll(void); +void file_eof(struct file *); +void file_hup(struct file *); +void file_close(struct file *); + +#endif /* !defined(FILE_H) */ diff --git a/aucat/headers.c b/aucat/headers.c new file mode 100644 index 0000000..37594e2 --- /dev/null +++ b/aucat/headers.c @@ -0,0 +1,291 @@ +/* $OpenBSD: headers.c,v 1.18 2010/06/05 16:54:19 ratchov Exp $ */ +/* + * Copyright (c) 2008 Alexandre Ratchov + * + * 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 + +#include +#include +#include +#include +#include +#include + +#include "aparams.h" +#include "conf.h" +#include "wav.h" + +/* + * Encoding IDs used in .wav headers. + */ +#define WAV_ENC_PCM 1 +#define WAV_ENC_ALAW 6 +#define WAV_ENC_ULAW 7 +#define WAV_ENC_EXT 0xfffe + +struct wavriff { + char magic[4]; + uint32_t size; + char type[4]; +} __packed; + +struct wavchunk { + char id[4]; + uint32_t size; +} __packed; + +struct wavfmt { + uint16_t fmt; + uint16_t nch; + uint32_t rate; + uint32_t byterate; + uint16_t blkalign; + uint16_t bits; +#define WAV_FMT_SIZE 16 +#define WAV_FMT_SIZE2 (16 + 2) +#define WAV_FMT_EXT_SIZE (16 + 24) + uint16_t extsize; + uint16_t valbits; + uint32_t chanmask; + uint16_t extfmt; + char guid[14]; +} __packed; + +char wav_id_riff[4] = { 'R', 'I', 'F', 'F' }; +char wav_id_wave[4] = { 'W', 'A', 'V', 'E' }; +char wav_id_data[4] = { 'd', 'a', 't', 'a' }; +char wav_id_fmt[4] = { 'f', 'm', 't', ' ' }; +char wav_guid[14] = { + 0x00, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x80, 0x00, + 0x00, 0xAA, 0x00, 0x38, + 0x9B, 0x71 +}; + +int +wav_readfmt(int fd, unsigned csize, struct aparams *par, short **map) +{ + struct wavfmt fmt; + unsigned nch, cmax, rate, bits, bps, enc; + + if (csize < WAV_FMT_SIZE) { + warnx("%u: bugus format chunk size", csize); + return 0; + } + if (csize > WAV_FMT_EXT_SIZE) + csize = WAV_FMT_EXT_SIZE; + if (read(fd, &fmt, csize) != csize) { + warn("riff_read: chunk"); + return 0; + } + enc = letoh16(fmt.fmt); + bits = letoh16(fmt.bits); + if (enc == WAV_ENC_EXT) { + if (csize != WAV_FMT_EXT_SIZE) { + warnx("missing extended format chunk in .wav file"); + return 0; + } + if (memcmp(fmt.guid, wav_guid, sizeof(wav_guid)) != 0) { + warnx("unknown format (GUID) in .wav file"); + return 0; + } + bps = (bits + 7) / 8; + bits = letoh16(fmt.valbits); + enc = letoh16(fmt.extfmt); + } else + bps = (bits + 7) / 8; + switch (enc) { + case WAV_ENC_PCM: + *map = NULL; + break; + case WAV_ENC_ALAW: + *map = wav_alawmap; + break; + case WAV_ENC_ULAW: + *map = wav_ulawmap; + break; + default: + errx(1, "%u: unsupported encoding in .wav file", enc); + } + nch = letoh16(fmt.nch); + if (nch == 0) { + warnx("zero number of channels"); + return 0; + } + cmax = par->cmin + nch - 1; + if (cmax >= NCHAN_MAX) { + warnx("%u:%u: bad range", par->cmin, cmax); + return 0; + } + rate = letoh32(fmt.rate); + if (rate < RATE_MIN || rate > RATE_MAX) { + warnx("%u: bad sample rate", rate); + return 0; + } + if (bits == 0 || bits > 32) { + warnx("%u: bad number of bits", bits); + return 0; + } + if (bits > bps * 8) { + warnx("%u: bits larger than bytes-per-sample", bps); + return 0; + } + if (enc == WAV_ENC_PCM) { + par->bps = bps; + par->bits = bits; + par->le = 1; + par->sig = (bits <= 8) ? 0 : 1; /* ask microsoft why... */ + } else { + if (bits != 8) { + warnx("%u: mulaw/alaw encoding not 8-bit", bits); + return 0; + } + par->bits = 8 * sizeof(short); + par->bps = sizeof(short); + par->le = NATIVE_LE; + par->sig = 1; + } + par->msb = 1; + par->cmax = cmax; + par->rate = rate; + return 1; +} + +int +wav_readhdr(int fd, struct aparams *par, off_t *startpos, off_t *datasz, short **map) +{ + struct wavriff riff; + struct wavchunk chunk; + unsigned csize, rsize, pos = 0; + int fmt_done = 0; + + if (lseek(fd, 0, SEEK_SET) < 0) { + warn("lseek: 0"); + return 0; + } + if (read(fd, &riff, sizeof(riff)) != sizeof(riff)) { + warn("wav_readhdr: header"); + return 0; + } + if (memcmp(&riff.magic, &wav_id_riff, 4) != 0 || + memcmp(&riff.type, &wav_id_wave, 4)) { + warnx("not a wave file"); + return 0; + } + rsize = letoh32(riff.size); + for (;;) { + if (pos + sizeof(struct wavchunk) > rsize) { + warnx("missing data chunk"); + return 0; + } + if (read(fd, &chunk, sizeof(chunk)) != sizeof(chunk)) { + warn("wav_readhdr: chunk"); + return 0; + } + csize = letoh32(chunk.size); + if (memcmp(chunk.id, wav_id_fmt, 4) == 0) { + if (!wav_readfmt(fd, csize, par, map)) + return 0; + fmt_done = 1; + } else if (memcmp(chunk.id, wav_id_data, 4) == 0) { + *startpos = pos + sizeof(riff) + sizeof(chunk); + *datasz = csize; + break; + } else { +#ifdef DEBUG + if (debug_level >= 2) + warnx("ignoring chuck <%.4s>\n", chunk.id); +#endif + } + + /* + * next chunk + */ + pos += sizeof(struct wavchunk) + csize; + if (lseek(fd, sizeof(riff) + pos, SEEK_SET) < 0) { + warn("lseek"); + return 0; + } + } + if (!fmt_done) { + warnx("missing format chunk"); + return 0; + } + return 1; +} + +/* + * Write header and seek to start position + */ +int +wav_writehdr(int fd, struct aparams *par, off_t *startpos, off_t datasz) +{ + unsigned nch = par->cmax - par->cmin + 1; + struct { + struct wavriff riff; + struct wavchunk fmt_hdr; + struct wavfmt fmt; + struct wavchunk data_hdr; + } hdr; + + /* + * Check that encoding is supported by .wav file format. + */ + if (par->bits > 8 && !par->le) { + warnx("samples must be little endian"); + return 0; + } + if (8 * par->bps - par->bits >= 8) { + warnx("padding must be less than 8 bits"); + return 0; + } + if ((par->bits <= 8 && par->sig) || (par->bits > 8 && !par->sig)) { + warnx("samples with more (less) than 8 bits must be signed " + "(unsigned)"); + return 0; + } + if (8 * par->bps != par->bits && !par->msb) { + warnx("samples must be MSB justified"); + return 0; + } + + memcpy(hdr.riff.magic, wav_id_riff, 4); + memcpy(hdr.riff.type, wav_id_wave, 4); + hdr.riff.size = htole32(datasz + sizeof(hdr) - sizeof(hdr.riff)); + + memcpy(hdr.fmt_hdr.id, wav_id_fmt, 4); + hdr.fmt_hdr.size = htole32(sizeof(hdr.fmt)); + hdr.fmt.fmt = htole16(1); + hdr.fmt.nch = htole16(nch); + hdr.fmt.rate = htole32(par->rate); + hdr.fmt.byterate = htole32(par->rate * par->bps * nch); + hdr.fmt.bits = htole16(par->bits); + hdr.fmt.blkalign = par->bps * nch; + + memcpy(hdr.data_hdr.id, wav_id_data, 4); + hdr.data_hdr.size = htole32(datasz); + + if (lseek(fd, 0, SEEK_SET) < 0) { + warn("wav_writehdr: lseek"); + return 0; + } + if (write(fd, &hdr, sizeof(hdr)) != sizeof(hdr)) { + warn("wav_writehdr: write"); + return 0; + } + *startpos = sizeof(hdr); + return 1; +} diff --git a/aucat/legacy.c b/aucat/legacy.c new file mode 100644 index 0000000..370a571 --- /dev/null +++ b/aucat/legacy.c @@ -0,0 +1,192 @@ +/* $OpenBSD: legacy.c,v 1.12 2010/04/06 20:07:01 ratchov Exp $ */ +/* + * Copyright (c) 1997 Kenneth Stailey. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Kenneth Stailey. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include +#include +#include +#include +#include + +#include "wav.h" + +/* + * Headerless data files. Played at /dev/audio's defaults. + */ +#define FMT_RAW 0 + +/* + * Sun/NeXT .au files. Header is skipped and /dev/audio is configured + * for monaural 8-bit ulaw @ 8kHz, the de facto format for .au files, + * as well as the historical default configuration for /dev/audio. + */ +#define FMT_AU 1 + +/* + * RIFF WAV files. Header is parsed for format details which are + * applied to /dev/audio. + */ +#define FMT_WAV 2 + + +int +legacy_play(char *dev, char *aufile) +{ + struct sio_hdl *hdl; + struct sio_par spar, par; + struct aparams apar; + ssize_t rd; + off_t datasz, dummy; + char buf[5120]; + size_t readsz; + int fd, fmt = FMT_RAW; + u_int32_t pos = 0, snd_fmt = 1, rate = 8000, chan = 1; + char magic[4]; + short *map; + + if ((fd = open(aufile, O_RDONLY)) < 0) { + warn("cannot open %s", aufile); + return(1); + } + + if (read(fd, magic, sizeof(magic)) != sizeof(magic)) { + /* + * read() error, or the file is smaller than sizeof(magic). + * treat as a raw file, like previous versions of aucat. + */ + } else if (!strncmp(magic, ".snd", 4)) { + fmt = FMT_AU; + if (read(fd, &pos, sizeof(pos)) == sizeof(pos)) + pos = ntohl(pos); + /* data size */ + if (lseek(fd, 4, SEEK_CUR) == -1) + warn("lseek hdr"); + if (read(fd, &snd_fmt, sizeof(snd_fmt)) == sizeof(snd_fmt)) + snd_fmt = ntohl(snd_fmt); + if (read(fd, &rate, sizeof(rate)) == sizeof(rate)) + rate = ntohl(rate); + if (read(fd, &chan, sizeof(chan)) == sizeof(chan)) + chan = ntohl(chan); + } else if (!strncmp(magic, "RIFF", 4) && + wav_readhdr(fd, &apar, &dummy, &datasz, &map)) { + fmt = FMT_WAV; + } + + /* + * Seek to start of audio data. wav_readhdr already took care + * of this for FMT_WAV. + */ + if (fmt == FMT_RAW || fmt == FMT_AU) + if (lseek(fd, (off_t)pos, SEEK_SET) == -1) + warn("lseek"); + + if ((hdl = sio_open(dev, SIO_PLAY, 0)) == NULL) { + warnx("can't get sndio handle"); + return(1); + } + + sio_initpar(&par); + switch(fmt) { + case FMT_WAV: + par.rate = apar.rate; + par.pchan = apar.cmax - apar.cmin + 1; + par.sig = apar.sig; + par.bits = apar.bits; + par.le = apar.le; + break; + case FMT_AU: + par.rate = rate; + par.pchan = chan; + par.sig = 1; + par.bits = 16; + par.le = SIO_LE_NATIVE; + map = wav_ulawmap; + if (snd_fmt == 27) + map = wav_alawmap; + break; + case FMT_RAW: + default: + break; + } + spar = par; + + if (!sio_setpar(hdl, &par) || !sio_getpar(hdl, &par)) { + warnx("can't set audio parameters"); + /* + * Only WAV could fail in previous aucat versions (unless + * the parameters returned by AUDIO_GETINFO would fail, + * which is unlikely). + */ + if (fmt == FMT_WAV) + return(1); + } + + /* + * Parameters may be silently modified. See audio(9)'s + * description of set_params. For compatability with previous + * aucat versions, continue running if something doesn't match. + */ + if (par.bits != spar.bits || + par.sig != par.sig || + par.le != spar.le || + par.pchan != spar.pchan || + /* + * Devices may return a very close rate, such as 44099 when + * 44100 was requested. The difference is inaudible. Allow + * 2% deviation as an example of how to cope. + */ + (par.rate > spar.rate * 1.02 || par.rate < spar.rate * 0.98)) { + warnx("format not supported"); + } + if (!sio_start(hdl)) { + warnx("could not start sndio"); + exit(1); + } + + readsz = sizeof(buf); + if (map) + readsz /= 2; + while ((rd = read(fd, buf, readsz)) > 0) { + if (map) { + wav_conv(buf, rd, map); + rd *= 2; + } + if (sio_write(hdl, buf, rd) != rd) + warnx("sio_write: short write"); + } + if (rd == -1) + warn("read"); + + sio_close(hdl); + close(fd); + + return(0); +} diff --git a/aucat/listen.c b/aucat/listen.c new file mode 100644 index 0000000..90be6b9 --- /dev/null +++ b/aucat/listen.c @@ -0,0 +1,143 @@ +/* $OpenBSD: listen.c,v 1.11 2009/09/27 11:51:20 ratchov Exp $ */ +/* + * Copyright (c) 2008 Alexandre Ratchov + * + * 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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "conf.h" +#include "listen.h" +#include "sock.h" + +struct fileops listen_ops = { + "listen", + sizeof(struct listen), + listen_close, + NULL, /* read */ + NULL, /* write */ + NULL, /* start */ + NULL, /* stop */ + listen_nfds, + listen_pollfd, + listen_revents +}; + +struct listen * +listen_new(struct fileops *ops, char *path) +{ + int sock, oldumask; + struct sockaddr_un sockname; + struct listen *f; + + sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock < 0) { + perror("socket"); + return NULL; + } + 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; + } + umask(oldumask); + if (listen(sock, 1) < 0) { + perror("listen"); + goto bad_close; + } + f = (struct listen *)file_new(ops, path, 1); + if (f == NULL) + goto bad_close; + f->path = strdup(path); + if (f->path == NULL) { + perror("strdup"); + exit(1); + } + f->fd = sock; + return f; + bad_close: + close(sock); + return NULL; +} + +int +listen_nfds(struct file *f) { + return 1; +} + +int +listen_pollfd(struct file *file, struct pollfd *pfd, int events) +{ + struct listen *f = (struct listen *)file; + + pfd->fd = f->fd; + pfd->events = POLLIN; + return 1; +} + +int +listen_revents(struct file *file, struct pollfd *pfd) +{ + struct listen *f = (struct listen *)file; + struct sockaddr caddr; + socklen_t caddrlen; + int sock; + + if (pfd->revents & POLLIN) { + caddrlen = sizeof(caddrlen); + sock = accept(f->fd, &caddr, &caddrlen); + if (sock < 0) { + perror("accept"); + return 0; + } + if (fcntl(sock, F_SETFL, O_NONBLOCK) < 0) { + perror("fcntl(sock, O_NONBLOCK)"); + close(sock); + return 0; + } + if (sock_new(&sock_ops, sock) == NULL) { + close(sock); + return 0; + } + } + return 0; +} + +void +listen_close(struct file *file) +{ + struct listen *f = (struct listen *)file; + + unlink(f->path); + free(f->path); + close(f->fd); +} diff --git a/aucat/listen.h b/aucat/listen.h new file mode 100644 index 0000000..8b156d6 --- /dev/null +++ b/aucat/listen.h @@ -0,0 +1,38 @@ +/* $OpenBSD: listen.h,v 1.5 2009/07/25 10:52:19 ratchov Exp $ */ +/* + * Copyright (c) 2008 Alexandre Ratchov + * + * 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 + +#include + +#include "aparams.h" +#include "file.h" + +struct listen { + struct file file; + char *path; + int fd; +}; + +struct listen *listen_new(struct fileops *, char *); +int listen_nfds(struct file *); +int listen_pollfd(struct file *, struct pollfd *, int); +int listen_revents(struct file *, struct pollfd *); +void listen_close(struct file *); +extern struct fileops listen_ops; + +#endif /* !defined(LISTEN_H) */ diff --git a/aucat/midi.c b/aucat/midi.c new file mode 100644 index 0000000..aa2b688 --- /dev/null +++ b/aucat/midi.c @@ -0,0 +1,1200 @@ +/* $OpenBSD: midi.c,v 1.28 2010/07/06 01:12:45 ratchov Exp $ */ +/* + * Copyright (c) 2008 Alexandre Ratchov + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +/* + * TODO + * + * use shadow variables (to save NRPNs, LSB of controller) + * in the midi merger + * + * make output and input identical when only one + * input is used (fix running status) + */ +#include +#include +#include + +#include "abuf.h" +#include "aproc.h" +#include "conf.h" +#include "dev.h" +#include "midi.h" +#ifdef DEBUG +#include "dbg.h" +#endif + +/* + * input data rate is XFER / TIMO (in bytes per microsecond), + * it must be slightly larger than the MIDI standard 3125 bytes/s + */ +#define MIDITHRU_XFER 340 +#define MIDITHRU_TIMO 100000 + +/* + * masks to extract command and channel of status byte + */ +#define MIDI_CMDMASK 0xf0 +#define MIDI_CHANMASK 0x0f + +/* + * MIDI status bytes of voice messages + */ +#define MIDI_NOFF 0x80 /* note off */ +#define MIDI_NON 0x90 /* note on */ +#define MIDI_KAT 0xa0 /* key after touch */ +#define MIDI_CTL 0xb0 /* controller */ +#define MIDI_PC 0xc0 /* program change */ +#define MIDI_CAT 0xd0 /* channel after touch */ +#define MIDI_BEND 0xe0 /* pitch bend */ +#define MIDI_ACK 0xfe /* active sensing message */ + +/* + * MIDI controller numbers + */ +#define MIDI_CTLVOL 7 /* volume */ +#define MIDI_CTLPAN 11 /* pan */ + +/* + * length of voice and common messages (status byte included) + */ +unsigned voice_len[] = { 3, 3, 3, 3, 2, 2, 3 }; +unsigned common_len[] = { 0, 2, 3, 2, 0, 0, 1, 1 }; + +/* + * send the message stored in of ibuf->r.midi.msg to obuf + */ +void +thru_flush(struct aproc *p, struct abuf *ibuf, struct abuf *obuf) +{ + unsigned ocount, itodo; + unsigned char *odata, *idata; + + itodo = ibuf->r.midi.used; + idata = ibuf->r.midi.msg; +#ifdef DEBUG + if (debug_level >= 4) { + aproc_dbg(p); + dbg_puts(": flushing "); + dbg_putu(itodo); + dbg_puts(" byte message\n"); + } +#endif + while (itodo > 0) { + if (!ABUF_WOK(obuf)) { +#ifdef DEBUG + if (debug_level >= 3) { + aproc_dbg(p); + dbg_puts(": overrun, discarding "); + dbg_putu(obuf->used); + dbg_puts(" bytes\n"); + } +#endif + abuf_rdiscard(obuf, obuf->used); + if (p->u.thru.owner == ibuf) + p->u.thru.owner = NULL; + return; + } + odata = abuf_wgetblk(obuf, &ocount, 0); + if (ocount > itodo) + ocount = itodo; + memcpy(odata, idata, ocount); + abuf_wcommit(obuf, ocount); + itodo -= ocount; + idata += ocount; + } + ibuf->r.midi.used = 0; + p->u.thru.owner = ibuf; +} + +/* + * send the real-time message (one byte) to obuf, similar to thrui_flush() + */ +void +thru_rt(struct aproc *p, struct abuf *ibuf, struct abuf *obuf, unsigned c) +{ + unsigned ocount; + unsigned char *odata; + +#ifdef DEBUG + if (debug_level >= 4) { + aproc_dbg(p); + dbg_puts(": "); + dbg_putx(c); + dbg_puts(": flushing realtime message\n"); + } +#endif + if (c == MIDI_ACK) + return; + if (!ABUF_WOK(obuf)) { +#ifdef DEBUG + if (debug_level >= 3) { + aproc_dbg(p); + dbg_puts(": overrun, discarding "); + dbg_putu(obuf->used); + dbg_puts(" bytes\n"); + } +#endif + abuf_rdiscard(obuf, obuf->used); + if (p->u.thru.owner == ibuf) + p->u.thru.owner = NULL; + } + odata = abuf_wgetblk(obuf, &ocount, 0); + odata[0] = c; + abuf_wcommit(obuf, 1); +} + +/* + * parse ibuf contents and store each message into obuf, + * use at most ``todo'' bytes (for throttling) + */ +void +thru_bcopy(struct aproc *p, struct abuf *ibuf, struct abuf *obuf, unsigned todo) +{ + unsigned char *idata; + unsigned c, icount, ioffs; + + idata = NULL; + icount = ioffs = 0; + for (;;) { + if (icount == 0) { + if (todo == 0) + break; + idata = abuf_rgetblk(ibuf, &icount, ioffs); + if (icount > todo) + icount = todo; + if (icount == 0) + break; + todo -= icount; + ioffs += icount; + } + c = *idata++; + icount--; + if (c < 0x80) { + if (ibuf->r.midi.idx == 0 && ibuf->r.midi.st) { + ibuf->r.midi.msg[ibuf->r.midi.used++] = ibuf->r.midi.st; + ibuf->r.midi.idx++; + } + ibuf->r.midi.msg[ibuf->r.midi.used++] = c; + ibuf->r.midi.idx++; + if (ibuf->r.midi.idx == ibuf->r.midi.len) { + thru_flush(p, ibuf, obuf); + if (ibuf->r.midi.st >= 0xf0) + ibuf->r.midi.st = 0; + ibuf->r.midi.idx = 0; + } + if (ibuf->r.midi.used == MIDI_MSGMAX) { + if (ibuf->r.midi.used == ibuf->r.midi.idx || + p->u.thru.owner == ibuf) + thru_flush(p, ibuf, obuf); + else + ibuf->r.midi.used = 0; + } + } else if (c < 0xf8) { + if (ibuf->r.midi.used == ibuf->r.midi.idx || + p->u.thru.owner == ibuf) { + thru_flush(p, ibuf, obuf); + } else + ibuf->r.midi.used = 0; + ibuf->r.midi.msg[0] = c; + ibuf->r.midi.used = 1; + ibuf->r.midi.len = (c >= 0xf0) ? + common_len[c & 7] : + voice_len[(c >> 4) & 7]; + if (ibuf->r.midi.len == 1) { + thru_flush(p, ibuf, obuf); + ibuf->r.midi.idx = 0; + ibuf->r.midi.st = 0; + ibuf->r.midi.len = 0; + } else { + ibuf->r.midi.st = c; + ibuf->r.midi.idx = 1; + } + } else { + thru_rt(p, ibuf, obuf, c); + } + } +} + +int +thru_in(struct aproc *p, struct abuf *ibuf) +{ + struct abuf *i, *inext; + unsigned todo; + + if (!ABUF_ROK(ibuf)) + return 0; + if (ibuf->tickets == 0) { +#ifdef DEBUG + if (debug_level >= 4) { + abuf_dbg(ibuf); + dbg_puts(": out of tickets, blocking\n"); + } +#endif + return 0; + } + todo = ibuf->used; + if (todo > ibuf->tickets) + todo = ibuf->tickets; + ibuf->tickets -= todo; + for (i = LIST_FIRST(&p->outs); i != NULL; i = inext) { + inext = LIST_NEXT(i, oent); + if (ibuf->duplex == i) + continue; + thru_bcopy(p, ibuf, i, todo); + (void)abuf_flush(i); + } + abuf_rdiscard(ibuf, todo); + return 1; +} + +int +thru_out(struct aproc *p, struct abuf *obuf) +{ + return 0; +} + +void +thru_eof(struct aproc *p, struct abuf *ibuf) +{ + if (!(p->flags & APROC_QUIT)) + return; + if (LIST_EMPTY(&p->ins)) + aproc_del(p); +} + +void +thru_hup(struct aproc *p, struct abuf *obuf) +{ +} + +void +thru_newin(struct aproc *p, struct abuf *ibuf) +{ + ibuf->r.midi.used = 0; + ibuf->r.midi.len = 0; + ibuf->r.midi.idx = 0; + ibuf->r.midi.st = 0; + ibuf->tickets = MIDITHRU_XFER; +} + +void +thru_done(struct aproc *p) +{ + timo_del(&p->u.thru.timo); +} + +struct aproc_ops thru_ops = { + "thru", + thru_in, + thru_out, + thru_eof, + thru_hup, + thru_newin, + NULL, /* newout */ + NULL, /* ipos */ + NULL, /* opos */ + thru_done +}; + +/* + * call-back invoked periodically to implement throttling at each invocation + * gain more ``tickets'' for processing. If one of the buffer was blocked by + * the throttelling mechanism, then run it + */ +void +thru_cb(void *addr) +{ + struct aproc *p = (struct aproc *)addr; + struct abuf *i, *inext; + unsigned tickets; + + timo_add(&p->u.thru.timo, MIDITHRU_TIMO); + + for (i = LIST_FIRST(&p->ins); i != NULL; i = inext) { + inext = LIST_NEXT(i, ient); + tickets = i->tickets; + i->tickets = MIDITHRU_XFER; + if (tickets == 0) + abuf_run(i); + } +} + +struct aproc * +thru_new(char *name) +{ + struct aproc *p; + + p = aproc_new(&thru_ops, name); + p->u.thru.owner = NULL; + timo_set(&p->u.thru.timo, thru_cb, p); + timo_add(&p->u.thru.timo, MIDITHRU_TIMO); + return p; +} + +#ifdef DEBUG +void +ctl_slotdbg(struct aproc *p, int slot) +{ + struct ctl_slot *s; + + if (slot < 0) { + dbg_puts("none"); + } else { + s = p->u.ctl.slot + slot; + dbg_puts(s->name); + dbg_putu(s->unit); + dbg_puts("("); + dbg_putu(s->vol); + dbg_puts(")/"); + switch (s->tstate) { + case CTL_OFF: + dbg_puts("off"); + break; + case CTL_RUN: + dbg_puts("run"); + break; + case CTL_START: + dbg_puts("sta"); + break; + case CTL_STOP: + dbg_puts("stp"); + break; + default: + dbg_puts("unk"); + break; + } + } +} +#endif + +/* + * broadcast a message to all output buffers on the behalf of ibuf. + * ie. don't sent back the message to the sender + */ +void +ctl_sendmsg(struct aproc *p, struct abuf *ibuf, unsigned char *msg, unsigned len) +{ + unsigned ocount, itodo; + unsigned char *odata, *idata; + struct abuf *i, *inext; + + for (i = LIST_FIRST(&p->outs); i != NULL; i = inext) { + inext = LIST_NEXT(i, oent); + if (i->duplex && i->duplex == ibuf) + continue; + itodo = len; + idata = msg; + while (itodo > 0) { + if (!ABUF_WOK(i)) { +#ifdef DEBUG + if (debug_level >= 3) { + abuf_dbg(i); + dbg_puts(": overrun, discarding "); + dbg_putu(i->used); + dbg_puts(" bytes\n"); + } +#endif + abuf_rdiscard(i, i->used); + } + odata = abuf_wgetblk(i, &ocount, 0); + if (ocount > itodo) + ocount = itodo; +#ifdef DEBUG + if (debug_level >= 4) { + abuf_dbg(i); + dbg_puts(": stored "); + dbg_putu(ocount); + dbg_puts(" bytes\n"); + } +#endif + memcpy(odata, idata, ocount); + abuf_wcommit(i, ocount); + itodo -= ocount; + idata += ocount; + } + (void)abuf_flush(i); + } +} + +/* + * send a quarter frame MTC message + */ +void +ctl_qfr(struct aproc *p) +{ + unsigned char buf[2]; + unsigned data; + + switch (p->u.ctl.qfr) { + case 0: + data = p->u.ctl.fr & 0xf; + break; + case 1: + data = p->u.ctl.fr >> 4; + break; + case 2: + data = p->u.ctl.sec & 0xf; + break; + case 3: + data = p->u.ctl.sec >> 4; + break; + case 4: + data = p->u.ctl.min & 0xf; + break; + case 5: + data = p->u.ctl.min >> 4; + break; + case 6: + data = p->u.ctl.hr & 0xf; + break; + case 7: + data = (p->u.ctl.hr >> 4) | (p->u.ctl.fps_id << 1); + /* + * tick messages are sent 2 frames ahead + */ + p->u.ctl.fr += 2; + if (p->u.ctl.fr < p->u.ctl.fps) + break; + p->u.ctl.fr -= p->u.ctl.fps; + p->u.ctl.sec++; + if (p->u.ctl.sec < 60) + break;; + p->u.ctl.sec = 0; + p->u.ctl.min++; + if (p->u.ctl.min < 60) + break; + p->u.ctl.min = 0; + p->u.ctl.hr++; + if (p->u.ctl.hr < 24) + break; + p->u.ctl.hr = 0; + break; + default: + /* NOTREACHED */ + data = 0; + } + buf[0] = 0xf1; + buf[1] = (p->u.ctl.qfr << 4) | data; + p->u.ctl.qfr++; + p->u.ctl.qfr &= 7; + ctl_sendmsg(p, NULL, buf, 2); +} + +/* + * send a full frame MTC message + */ +void +ctl_full(struct aproc *p) +{ + unsigned char buf[10]; + unsigned origin = p->u.ctl.origin; + unsigned fps = p->u.ctl.fps; + + p->u.ctl.hr = (origin / (3600 * MTC_SEC)) % 24; + p->u.ctl.min = (origin / (60 * MTC_SEC)) % 60; + p->u.ctl.sec = (origin / MTC_SEC) % 60; + p->u.ctl.fr = (origin / (MTC_SEC / fps)) % fps; + + buf[0] = 0xf0; + buf[1] = 0x7f; + buf[2] = 0x7f; + buf[3] = 0x01; + buf[4] = 0x01; + buf[5] = p->u.ctl.hr | (p->u.ctl.fps_id << 5); + buf[6] = p->u.ctl.min; + buf[7] = p->u.ctl.sec; + buf[8] = p->u.ctl.fr; + buf[9] = 0xf7; + p->u.ctl.qfr = 0; + ctl_sendmsg(p, NULL, buf, 10); +} + +/* + * find the best matching free slot index (ie midi channel). + * return -1, if there are no free slots anymore + */ +int +ctl_getidx(struct aproc *p, char *who) +{ + char *s; + struct ctl_slot *slot; + char name[CTL_NAMEMAX]; + unsigned i, unit, umap = 0; + unsigned ser, bestser, bestidx; + + /* + * create a ``valid'' control name (lowcase, remove [^a-z], trucate) + */ + for (i = 0, s = who; ; s++) { + if (i == CTL_NAMEMAX - 1 || *s == '\0') { + name[i] = '\0'; + break; + } else if (*s >= 'A' && *s <= 'Z') { + name[i++] = *s + 'a' - 'A'; + } else if (*s >= 'a' && *s <= 'z') + name[i++] = *s; + } + if (i == 0) + strlcpy(name, "noname", CTL_NAMEMAX); + + /* + * find the instance number of the control name + */ + for (i = 0, slot = p->u.ctl.slot; i < CTL_NSLOT; i++, slot++) { + if (slot->ops == NULL) + continue; + if (strcmp(slot->name, name) == 0) + umap |= (1 << i); + } + for (unit = 0; ; unit++) { + if (unit == CTL_NSLOT) + return -1; + if ((umap & (1 << unit)) == 0) + break; + } +#ifdef DEBUG + if (debug_level >= 3) { + aproc_dbg(p); + dbg_puts(": new control name is "); + dbg_puts(name); + dbg_putu(unit); + dbg_puts("\n"); + } +#endif + /* + * find a free controller slot with the same name/unit + */ + for (i = 0, slot = p->u.ctl.slot; i < CTL_NSLOT; i++, slot++) { + if (slot->ops == NULL && + strcmp(slot->name, name) == 0 && + slot->unit == unit) { +#ifdef DEBUG + if (debug_level >= 3) { + aproc_dbg(p); + dbg_puts(": found slot "); + dbg_putu(i); + dbg_puts("\n"); + } +#endif + return i; + } + } + + /* + * couldn't find a matching slot, pick oldest free slot + * and set its name/unit + */ + bestser = 0; + bestidx = CTL_NSLOT; + for (i = 0, slot = p->u.ctl.slot; i < CTL_NSLOT; i++, slot++) { + if (slot->ops != NULL) + continue; + ser = p->u.ctl.serial - slot->serial; + if (ser > bestser) { + bestser = ser; + bestidx = i; + } + } + if (bestidx == CTL_NSLOT) + return -1; + slot = p->u.ctl.slot + bestidx; + if (slot->name[0] != '\0') + slot->vol = MIDI_MAXCTL; + strlcpy(slot->name, name, CTL_NAMEMAX); + slot->serial = p->u.ctl.serial++; + slot->unit = unit; +#ifdef DEBUG + if (debug_level >= 3) { + aproc_dbg(p); + dbg_puts(": overwritten slot "); + dbg_putu(bestidx); + dbg_puts("\n"); + } +#endif + return bestidx; +} + +/* + * check that all clients controlled by MMC are ready to start, + * if so, start them all but the caller + */ +int +ctl_trystart(struct aproc *p, int caller) +{ + unsigned i; + struct ctl_slot *s; + + if (p->u.ctl.tstate != CTL_START) { +#ifdef DEBUG + if (debug_level >= 3) { + ctl_slotdbg(p, caller); + dbg_puts(": server not started, delayd\n"); + } +#endif + return 0; + } + for (i = 0, s = p->u.ctl.slot; i < CTL_NSLOT; i++, s++) { + if (!s->ops || i == caller) + continue; + if (s->tstate != CTL_OFF && s->tstate != CTL_START) { +#ifdef DEBUG + if (debug_level >= 3) { + ctl_slotdbg(p, i); + dbg_puts(": not ready, server delayed\n"); + } +#endif + return 0; + } + } + for (i = 0, s = p->u.ctl.slot; i < CTL_NSLOT; i++, s++) { + if (!s->ops || i == caller) + continue; + if (s->tstate == CTL_START) { +#ifdef DEBUG + if (debug_level >= 3) { + ctl_slotdbg(p, i); + dbg_puts(": started\n"); + } +#endif + s->tstate = CTL_RUN; + s->ops->start(s->arg); + } + } + if (caller >= 0) + p->u.ctl.slot[caller].tstate = CTL_RUN; + p->u.ctl.tstate = CTL_RUN; + p->u.ctl.delta = MTC_SEC * dev_getpos(p->u.ctl.dev); + if (p->u.ctl.dev->rate % (30 * 4 * p->u.ctl.dev->round) == 0) { + p->u.ctl.fps_id = MTC_FPS_30; + p->u.ctl.fps = 30; + } else if (p->u.ctl.dev->rate % (25 * 4 * p->u.ctl.dev->round) == 0) { + p->u.ctl.fps_id = MTC_FPS_25; + p->u.ctl.fps = 25; + } else { + p->u.ctl.fps_id = MTC_FPS_24; + p->u.ctl.fps = 24; + } +#ifdef DEBUG + if (debug_level >= 3) { + ctl_slotdbg(p, caller); + dbg_puts(": started server at "); + dbg_puti(p->u.ctl.delta); + dbg_puts(", "); + dbg_puti(p->u.ctl.fps); + dbg_puts(" mtc fps\n"); + } +#endif + dev_wakeup(p->u.ctl.dev); + ctl_full(p); + return 1; +} + +/* + * allocate a new slot and register the given call-backs + */ +int +ctl_slotnew(struct aproc *p, char *who, struct ctl_ops *ops, void *arg, int tr) +{ + int idx; + struct ctl_slot *s; + + if (!APROC_OK(p)) { +#ifdef DEBUG + if (debug_level >= 1) { + dbg_puts(who); + dbg_puts(": MIDI control not available\n"); + } +#endif + return -1; + } + idx = ctl_getidx(p, who); + if (idx < 0) + return -1; + + s = p->u.ctl.slot + idx; + s->ops = ops; + s->arg = arg; + s->tstate = tr ? CTL_STOP : CTL_OFF; + s->ops->vol(s->arg, s->vol); + ctl_slotvol(p, idx, s->vol); + return idx; +} + +/* + * release the given slot + */ +void +ctl_slotdel(struct aproc *p, int index) +{ + unsigned i; + struct ctl_slot *s; + + if (!APROC_OK(p)) + return; + p->u.ctl.slot[index].ops = NULL; + if (!(p->flags & APROC_QUIT)) + return; + for (i = 0, s = p->u.ctl.slot; i < CTL_NSLOT; i++, s++) { + if (s->ops) + return; + } + if (!LIST_EMPTY(&p->outs) || !LIST_EMPTY(&p->ins)) + aproc_del(p); +} + +/* + * called at every clock tick by the mixer, delta is positive, unless + * there's an overrun/underrun + */ +void +ctl_ontick(struct aproc *p, int delta) +{ + int qfrlen; + + /* + * don't send ticks before the start signal + */ + if (p->u.ctl.tstate != CTL_RUN) + return; + + p->u.ctl.delta += delta * MTC_SEC; + + /* + * don't send ticks during the count-down + */ + if (p->u.ctl.delta < 0) + return; + + qfrlen = p->u.ctl.dev->rate * (MTC_SEC / (4 * p->u.ctl.fps)); + while (p->u.ctl.delta >= qfrlen) { + ctl_qfr(p); + p->u.ctl.delta -= qfrlen; + } +} + +/* + * notifty the mixer that volume changed, called by whom allocad the slot using + * ctl_slotnew(). Note: it doesn't make sens to call this from within the + * call-back. + */ +void +ctl_slotvol(struct aproc *p, int slot, unsigned vol) +{ + unsigned char msg[3]; + + if (!APROC_OK(p)) + return; +#ifdef DEBUG + if (debug_level >= 3) { + ctl_slotdbg(p, slot); + dbg_puts(": changing volume to "); + dbg_putu(vol); + dbg_puts("\n"); + } +#endif + p->u.ctl.slot[slot].vol = vol; + msg[0] = MIDI_CTL | slot; + msg[1] = MIDI_CTLVOL; + msg[2] = vol; + ctl_sendmsg(p, NULL, msg, 3); +} + +/* + * notify the MMC layer that the stream is attempting + * to start. If other streams are not ready, 0 is returned meaning + * that the stream should wait. If other streams are ready, they + * are started, and the caller should start immediately. + */ +int +ctl_slotstart(struct aproc *p, int slot) +{ + struct ctl_slot *s = p->u.ctl.slot + slot; + + if (!APROC_OK(p)) + return 1; + if (s->tstate == CTL_OFF || p->u.ctl.tstate == CTL_OFF) + return 1; + + /* + * if the server already started (the client missed the + * start rendez-vous) or the server is stopped, then + * tag the client as ``wanting to start'' + */ + s->tstate = CTL_START; + return ctl_trystart(p, slot); +} + +/* + * notify the MMC layer that the stream no longer is trying to + * start (or that it just stopped), meaning that its ``start'' call-back + * shouldn't be called anymore + */ +void +ctl_slotstop(struct aproc *p, int slot) +{ + struct ctl_slot *s = p->u.ctl.slot + slot; + + if (!APROC_OK(p)) + return; + /* + * tag the stream as not trying to start, + * unless MMC is turned off + */ + if (s->tstate != CTL_OFF) + s->tstate = CTL_STOP; +} + +/* + * start all slots simultaneously + */ +void +ctl_start(struct aproc *p) +{ + if (!APROC_OK(p)) + return; + if (p->u.ctl.tstate == CTL_STOP) { + p->u.ctl.tstate = CTL_START; + (void)ctl_trystart(p, -1); +#ifdef DEBUG + } else { + if (debug_level >= 3) { + aproc_dbg(p); + dbg_puts(": ignoring mmc start\n"); + } +#endif + } +} + +/* + * stop all slots simultaneously + */ +void +ctl_stop(struct aproc *p) +{ + unsigned i; + struct ctl_slot *s; + + if (!APROC_OK(p)) + return; + switch (p->u.ctl.tstate) { + case CTL_START: + p->u.ctl.tstate = CTL_STOP; + return; + case CTL_RUN: + p->u.ctl.tstate = CTL_STOP; + break; + default: +#ifdef DEBUG + if (debug_level >= 3) { + aproc_dbg(p); + dbg_puts(": ignored mmc stop\n"); + } +#endif + return; + } + for (i = 0, s = p->u.ctl.slot; i < CTL_NSLOT; i++, s++) { + if (!s->ops) + continue; + if (s->tstate == CTL_RUN) { +#ifdef DEBUG + if (debug_level >= 3) { + ctl_slotdbg(p, i); + dbg_puts(": requested to stop\n"); + } +#endif + s->ops->stop(s->arg); + } + } +} + +/* + * relocate all slots simultaneously + */ +void +ctl_loc(struct aproc *p, unsigned origin) +{ + unsigned i, tstate; + struct ctl_slot *s; + + if (!APROC_OK(p)) + return; +#ifdef DEBUG + if (debug_level >= 2) { + dbg_puts("server relocated to "); + dbg_putu(origin); + dbg_puts("\n"); + } +#endif + tstate = p->u.ctl.tstate; + if (tstate == CTL_RUN) + ctl_stop(p); + p->u.ctl.origin = origin; + for (i = 0, s = p->u.ctl.slot; i < CTL_NSLOT; i++, s++) { + if (!s->ops) + continue; + s->ops->loc(s->arg, p->u.ctl.origin); + } + if (tstate == CTL_RUN) + ctl_start(p); +} + +/* + * check if there are controlled streams + */ +int +ctl_idle(struct aproc *p) +{ + unsigned i; + struct ctl_slot *s; + + if (!APROC_OK(p)) + return 1; + for (i = 0, s = p->u.ctl.slot; i < CTL_NSLOT; i++, s++) { + if (s->ops) + return 0; + } + return 1; +} + +/* + * handle a MIDI event received from ibuf + */ +void +ctl_ev(struct aproc *p, struct abuf *ibuf) +{ + unsigned chan; + struct ctl_slot *slot; + unsigned fps; +#ifdef DEBUG + unsigned i; + + if (debug_level >= 3) { + abuf_dbg(ibuf); + dbg_puts(": got event:"); + for (i = 0; i < ibuf->r.midi.idx; i++) { + dbg_puts(" "); + dbg_putx(ibuf->r.midi.msg[i]); + } + dbg_puts("\n"); + } +#endif + if ((ibuf->r.midi.msg[0] & MIDI_CMDMASK) == MIDI_CTL && + ibuf->r.midi.msg[1] == MIDI_CTLVOL) { + chan = ibuf->r.midi.msg[0] & MIDI_CHANMASK; + if (chan >= CTL_NSLOT) + return; + slot = p->u.ctl.slot + chan; + slot->vol = ibuf->r.midi.msg[2]; + if (slot->ops == NULL) + return; + slot->ops->vol(slot->arg, slot->vol); + ctl_sendmsg(p, ibuf, ibuf->r.midi.msg, ibuf->r.midi.len); + } + if (ibuf->r.midi.idx == 6 && + ibuf->r.midi.msg[0] == 0xf0 && + ibuf->r.midi.msg[1] == 0x7f && /* type is realtime */ + ibuf->r.midi.msg[3] == 0x06 && /* subtype is mmc */ + ibuf->r.midi.msg[5] == 0xf7) { /* subtype is mmc */ + switch (ibuf->r.midi.msg[4]) { + case 0x01: /* mmc stop */ +#ifdef DEBUG + if (debug_level >= 3) { + abuf_dbg(ibuf); + dbg_puts(": mmc stop\n"); + } +#endif + ctl_stop(p); + break; + case 0x02: /* mmc start */ +#ifdef DEBUG + if (debug_level >= 3) { + abuf_dbg(ibuf); + dbg_puts(": mmc start\n"); + } +#endif + ctl_start(p); + break; + } + } + if (ibuf->r.midi.idx == 13 && + ibuf->r.midi.msg[0] == 0xf0 && + ibuf->r.midi.msg[1] == 0x7f && + ibuf->r.midi.msg[3] == 0x06 && + ibuf->r.midi.msg[4] == 0x44 && + ibuf->r.midi.msg[5] == 0x06 && + ibuf->r.midi.msg[6] == 0x01 && + ibuf->r.midi.msg[12] == 0xf7) { + switch (ibuf->r.midi.msg[7] >> 5) { + case MTC_FPS_24: + fps = 24; + break; + case MTC_FPS_25: + fps = 25; + break; + case MTC_FPS_30: + fps = 30; + break; + default: + p->u.ctl.origin = 0; + return; + } + ctl_loc(p, + (ibuf->r.midi.msg[7] & 0x1f) * 3600 * MTC_SEC + + ibuf->r.midi.msg[8] * 60 * MTC_SEC + + ibuf->r.midi.msg[9] * MTC_SEC + + ibuf->r.midi.msg[10] * (MTC_SEC / fps) + + ibuf->r.midi.msg[11] * (MTC_SEC / 100 / fps)); + } +} + +int +ctl_in(struct aproc *p, struct abuf *ibuf) +{ + unsigned char *idata; + unsigned c, i, icount; + + if (!ABUF_ROK(ibuf)) + return 0; + idata = abuf_rgetblk(ibuf, &icount, 0); + for (i = 0; i < icount; i++) { + c = *idata++; + if (c >= 0xf8) { + /* clock events not used yet */ + } else if (c >= 0xf0) { + if (ibuf->r.midi.st == 0xf0 && c == 0xf7 && + ibuf->r.midi.idx < MIDI_MSGMAX) { + ibuf->r.midi.msg[ibuf->r.midi.idx++] = c; + ctl_ev(p, ibuf); + continue; + } + ibuf->r.midi.msg[0] = c; + ibuf->r.midi.len = common_len[c & 7]; + ibuf->r.midi.st = c; + ibuf->r.midi.idx = 1; + } else if (c >= 0x80) { + ibuf->r.midi.msg[0] = c; + ibuf->r.midi.len = voice_len[(c >> 4) & 7]; + ibuf->r.midi.st = c; + ibuf->r.midi.idx = 1; + } else if (ibuf->r.midi.st) { + if (ibuf->r.midi.idx == MIDI_MSGMAX) + continue; + if (ibuf->r.midi.idx == 0) + ibuf->r.midi.msg[ibuf->r.midi.idx++] = ibuf->r.midi.st; + ibuf->r.midi.msg[ibuf->r.midi.idx++] = c; + if (ibuf->r.midi.idx == ibuf->r.midi.len) { + ctl_ev(p, ibuf); + ibuf->r.midi.idx = 0; + } + } + } + abuf_rdiscard(ibuf, icount); + return 1; +} + +int +ctl_out(struct aproc *p, struct abuf *obuf) +{ + return 0; +} + +void +ctl_eof(struct aproc *p, struct abuf *ibuf) +{ + unsigned i; + struct ctl_slot *s; + + if (!(p->flags & APROC_QUIT)) + return; + for (i = 0, s = p->u.ctl.slot; i < CTL_NSLOT; i++, s++) { + if (s->ops != NULL) + s->ops->quit(s->arg); + } + if (!LIST_EMPTY(&p->outs) || !LIST_EMPTY(&p->ins)) + aproc_del(p); +} + +void +ctl_hup(struct aproc *p, struct abuf *obuf) +{ + unsigned i; + struct ctl_slot *s; + + if (!(p->flags & APROC_QUIT)) + return; + for (i = 0, s = p->u.ctl.slot; i < CTL_NSLOT; i++, s++) { + if (s->ops) + return; + } + if (!LIST_EMPTY(&p->outs) || !LIST_EMPTY(&p->ins)) + aproc_del(p); +} + +void +ctl_newin(struct aproc *p, struct abuf *ibuf) +{ + ibuf->r.midi.used = 0; + ibuf->r.midi.len = 0; + ibuf->r.midi.idx = 0; + ibuf->r.midi.st = 0; +} + +void +ctl_done(struct aproc *p) +{ + unsigned i; + struct ctl_slot *s; + + for (i = 0, s = p->u.ctl.slot; i < CTL_NSLOT; i++, s++) { + if (s->ops != NULL) + s->ops->quit(s->arg); + } +} + +struct aproc_ops ctl_ops = { + "ctl", + ctl_in, + ctl_out, + ctl_eof, + ctl_hup, + ctl_newin, + NULL, /* newout */ + NULL, /* ipos */ + NULL, /* opos */ + ctl_done +}; + +struct aproc * +ctl_new(char *name, struct dev *dev) +{ + struct aproc *p; + struct ctl_slot *s; + unsigned i; + + p = aproc_new(&ctl_ops, name); + p->u.ctl.dev = dev; + p->u.ctl.serial = 0; + p->u.ctl.tstate = CTL_STOP; + for (i = 0, s = p->u.ctl.slot; i < CTL_NSLOT; i++, s++) { + p->u.ctl.slot[i].unit = i; + p->u.ctl.slot[i].ops = NULL; + p->u.ctl.slot[i].vol = MIDI_MAXCTL; + p->u.ctl.slot[i].tstate = CTL_OFF; + p->u.ctl.slot[i].serial = p->u.ctl.serial++; + p->u.ctl.slot[i].name[0] = '\0'; + } + return p; +} diff --git a/aucat/midi.h b/aucat/midi.h new file mode 100644 index 0000000..d55fb42 --- /dev/null +++ b/aucat/midi.h @@ -0,0 +1,36 @@ +/* $OpenBSD: midi.h,v 1.9 2010/06/04 06:15:28 ratchov Exp $ */ +/* + * Copyright (c) 2008 Alexandre Ratchov + * + * 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 + +struct dev; + +struct aproc *thru_new(char *); +struct aproc *ctl_new(char *, struct dev *); + +int ctl_slotnew(struct aproc *, char *, struct ctl_ops *, void *, int); +void ctl_slotdel(struct aproc *, int); +void ctl_slotvol(struct aproc *, int, unsigned); +int ctl_slotstart(struct aproc *, int); +void ctl_slotstop(struct aproc *, int); +void ctl_ontick(struct aproc *, int); + +void ctl_stop(struct aproc *); +void ctl_start(struct aproc *); +int ctl_idle(struct aproc *); + +#endif /* !defined(MIDI_H) */ diff --git a/aucat/midicat.1 b/aucat/midicat.1 new file mode 100644 index 0000000..a4edb4c --- /dev/null +++ b/aucat/midicat.1 @@ -0,0 +1,166 @@ +.\" $OpenBSD: midicat.1,v 1.13 2010/07/06 10:45:01 jmc Exp $ +.\" +.\" Copyright (c) 2006 Alexandre Ratchov +.\" +.\" 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: July 6 2010 $ +.Dt MIDICAT 1 +.Os +.Sh NAME +.Nm midicat +.Nd MIDI server and manipulation tool +.Sh SYNOPSIS +.Nm midicat +.Op Fl dl +.Op Fl i Ar file +.Op Fl o Ar file +.Op Fl q Ar port +.Op Fl s Ar name +.Op Fl U Ar unit +.Sh DESCRIPTION +The +.Nm +utility can merge any number of MIDI inputs and broadcast the result +to any number of MIDI outputs, similarly to a hardware MIDI thru box. +MIDI streams are typically MIDI ports or plain files containing raw MIDI +data. +.Pp +The +.Nm +utility can also act as a MIDI server in which case MIDI streams +correspond to client connections. +The server capability +allows any MIDI-capable application to send MIDI messages to +MIDI hardware or to another application in a uniform way. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl d +Increase log verbosity. +.Nm +logs on stderr until it daemonizes. +.It Fl i Ar file +Read data to send from this file. +If the option argument is +.Sq - +then standard input will be used. +.It Fl l +Detach and become a daemon. +.It Fl o Ar file +Write received data into this file. +If the option argument is +.Sq - +then standard output will be used. +.It Fl q Ar port +Send and receive data from this +.Xr sndio 7 +MIDI port. +.It Fl s Ar name +Expose a MIDI thru box to which MIDI programs +can connect. +Preceding streams +.Pq Fl ioq +are subscribed to this thru box. +The given +.Ar name +corresponds to the +.Dq option +part of the +.Xr sndio 7 +device name string. +.It Fl U Ar unit +Unit number to use when running in server mode. +Each +.Nm +server instance has an unique unit number, +used in +.Xr sndio 7 +device names. +The default is 0. +.El +.Pp +If files +.Pq Fl io +are specified but no ports +.Pq Fl q +are specified, the default +.Xr sndio 7 +port is used. +If no streams +.Pq Fl ioq +are specified, server mode is assumed and a thru box is created +as if +.Fl s Ar default +was used as the last argument. +.Pp +Generally MIDI applications are real-time. +To reduce jitter, especially on busy machines, +the server can be started by the super-user, +in which case it will run with higher priority. +Any user will still be able to connect to it, +but for privacy reasons only one user may have connections to +it at a given time. +.Pp +If +.Nm +is sent +.Dv SIGHUP , +.Dv SIGINT +or +.Dv SIGTERM , +then processing terminates. +.Sh EXAMPLES +The following dumps MIDI data received from the default port: +.Bd -literal -offset indent +$ midicat -o - | hexdump -e '1/1 "%x"' +.Ed +.Pp +The following sends raw MIDI data to the +.Pa rmidi:5 +port: +.Bd -literal -offset indent +$ midicat -i sysexfile -q rmidi:5 +.Ed +.Pp +The following connects +.Pa rmidi:5 +and +.Pa rmidi:6 +ports: +.Bd -literal -offset indent +$ midicat -q rmidi:5 -q rmidi:6 +.Ed +.Pp +The following creates a MIDI thru box and daemonizes, +allowing MIDI programs to send data to each other instead of +using hardware MIDI ports: +.Bd -literal -offset indent +$ midicat -l +.Ed +.Pp +The following creates a MIDI thru box and subscribes the +.Pa rmidi:5 +port, allowing multiple MIDI programs to use the port +simultaneously: +.Bd -literal -offset indent +$ midicat -q rmidi:5 -s default +.Ed +.Sh SEE ALSO +.Xr aucat 1 , +.Xr midi 4 , +.Xr sndio 7 +.Sh BUGS +The ability to merge multiple inputs is provided to allow multiple +applications producing MIDI data to keep their connection open while +idling; it does not replace a fully featured MIDI merger. diff --git a/aucat/miofile.c b/aucat/miofile.c new file mode 100644 index 0000000..b457ec0 --- /dev/null +++ b/aucat/miofile.c @@ -0,0 +1,169 @@ +/* $OpenBSD: miofile.c,v 1.5 2010/06/04 06:15:28 ratchov Exp $ */ +/* + * Copyright (c) 2008 Alexandre Ratchov + * + * 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 +#include + +#include +#include +#include +#include +#include + +#include "conf.h" +#include "file.h" +#include "miofile.h" +#ifdef DEBUG +#include "dbg.h" +#endif + +struct miofile { + struct file file; + struct mio_hdl *hdl; +}; + +void miofile_close(struct file *); +unsigned miofile_read(struct file *, unsigned char *, unsigned); +unsigned miofile_write(struct file *, unsigned char *, unsigned); +void miofile_start(struct file *); +void miofile_stop(struct file *); +int miofile_nfds(struct file *); +int miofile_pollfd(struct file *, struct pollfd *, int); +int miofile_revents(struct file *, struct pollfd *); + +struct fileops miofile_ops = { + "mio", + sizeof(struct miofile), + miofile_close, + miofile_read, + miofile_write, + NULL, /* start */ + NULL, /* stop */ + miofile_nfds, + miofile_pollfd, + miofile_revents +}; + +/* + * open the device + */ +struct miofile * +miofile_new(struct fileops *ops, char *path, int input, int output) +{ + char *siopath; + struct mio_hdl *hdl; + struct miofile *f; + int mode = 0; + + siopath = (strcmp(path, "default") == 0) ? NULL : path; + if (input) + mode |= MIO_IN; + if (output) + mode |= MIO_OUT; + hdl = mio_open(siopath, mode, 1); + if (hdl == NULL) + return NULL; + f = (struct miofile *)file_new(ops, path, mio_nfds(hdl)); + if (f == NULL) + goto bad_close; + f->hdl = hdl; + return f; + bad_close: + mio_close(hdl); + return NULL; +} + +unsigned +miofile_read(struct file *file, unsigned char *data, unsigned count) +{ + struct miofile *f = (struct miofile *)file; + unsigned n; + + n = mio_read(f->hdl, data, count); + if (n == 0) { + f->file.state &= ~FILE_ROK; + if (mio_eof(f->hdl)) { +#ifdef DEBUG + dbg_puts(f->file.name); + dbg_puts(": failed to read from device\n"); +#endif + file_eof(&f->file); + } else { +#ifdef DEBUG + if (debug_level >= 4) { + file_dbg(&f->file); + dbg_puts(": reading blocked\n"); + } +#endif + } + return 0; + } + return n; + +} + +unsigned +miofile_write(struct file *file, unsigned char *data, unsigned count) +{ + struct miofile *f = (struct miofile *)file; + unsigned n; + + n = mio_write(f->hdl, data, count); + if (n == 0) { + f->file.state &= ~FILE_WOK; + if (mio_eof(f->hdl)) { +#ifdef DEBUG + dbg_puts(f->file.name); + dbg_puts(": failed to write on device\n"); +#endif + file_hup(&f->file); + } else { +#ifdef DEBUG + if (debug_level >= 4) { + file_dbg(&f->file); + dbg_puts(": writing blocked\n"); + } +#endif + } + return 0; + } + return n; +} + +int +miofile_nfds(struct file *file) +{ + return mio_nfds(((struct miofile *)file)->hdl); +} + +int +miofile_pollfd(struct file *file, struct pollfd *pfd, int events) +{ + return mio_pollfd(((struct miofile *)file)->hdl, pfd, events); +} + +int +miofile_revents(struct file *file, struct pollfd *pfd) +{ + return mio_revents(((struct miofile *)file)->hdl, pfd); +} + +void +miofile_close(struct file *file) +{ + return mio_close(((struct miofile *)file)->hdl); +} diff --git a/aucat/miofile.h b/aucat/miofile.h new file mode 100644 index 0000000..e1b2e73 --- /dev/null +++ b/aucat/miofile.h @@ -0,0 +1,28 @@ +/* $OpenBSD: miofile.h,v 1.1 2009/07/25 08:44:27 ratchov Exp $ */ +/* + * Copyright (c) 2008 Alexandre Ratchov + * + * 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 file; +struct fileops; +struct miofile; + +struct miofile *miofile_new(struct fileops *, char *, int, int); + +extern struct fileops miofile_ops; + +#endif /* !defined(MIOFILE_H) */ diff --git a/aucat/opt.c b/aucat/opt.c new file mode 100644 index 0000000..4e14b81 --- /dev/null +++ b/aucat/opt.c @@ -0,0 +1,131 @@ +/* $OpenBSD: opt.c,v 1.10 2010/07/06 01:12:45 ratchov Exp $ */ +/* + * Copyright (c) 2008 Alexandre Ratchov + * + * 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 +#include +#include + +#include "dev.h" +#include "conf.h" +#include "opt.h" +#ifdef DEBUG +#include "dbg.h" +#endif + +struct optlist opt_list = SLIST_HEAD_INITIALIZER(&opt_list); + +void +opt_new(char *name, struct dev *d, struct aparams *wpar, struct aparams *rpar, + int maxweight, int mmc, int join, unsigned mode) +{ + struct opt *o; + unsigned 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' && + c < '0' && c > '9' && + c != '_') { + fprintf(stderr, "%s: '%c' not allowed\n", name, c); + exit(1); + } + } + SLIST_FOREACH(o, &opt_list, entry) { + if (strcmp(name, o->name) == 0) { + fprintf(stderr, "%s: already defined\n", name); + exit(1); + } + } + o = malloc(sizeof(struct opt)); + if (o == NULL) { + perror("opt_new: malloc"); + exit(1); + } + memcpy(o->name, name, len + 1); + if (mode & MODE_RECMASK) + o->wpar = (mode & MODE_MON) ? *rpar : *wpar; + if (mode & MODE_PLAY) + o->rpar = *rpar; + o->maxweight = maxweight; + o->mmc = mmc; + o->join = join; + o->mode = mode; + o->dev = d; +#ifdef DEBUG + if (debug_level >= 2) { + dbg_puts(o->name); + dbg_puts("@"); + dbg_puts(o->dev->path); + dbg_puts(":"); + if (mode & MODE_REC) { + dbg_puts(" rec="); + dbg_putu(o->wpar.cmin); + dbg_puts(":"); + dbg_putu(o->wpar.cmax); + } + if (mode & MODE_PLAY) { + dbg_puts(" play="); + dbg_putu(o->rpar.cmin); + dbg_puts(":"); + dbg_putu(o->rpar.cmax); + dbg_puts(" vol="); + dbg_putu(o->maxweight); + } + if (mode & MODE_MON) { + dbg_puts(" mon="); + dbg_putu(o->wpar.cmin); + dbg_puts(":"); + dbg_putu(o->wpar.cmax); + } + if (o->mmc) + dbg_puts(" mmc"); + dbg_puts("\n"); + } +#endif + SLIST_INSERT_HEAD(&opt_list, o, entry); +} + +struct opt * +opt_byname(char *name) +{ + struct opt *o; + + SLIST_FOREACH(o, &opt_list, entry) { + if (strcmp(name, o->name) == 0) { +#ifdef DEBUG + if (debug_level >= 3) { + dbg_puts(o->name); + dbg_puts(": option found\n"); + } +#endif + return o; + } + } +#ifdef DEBUG + if (debug_level >= 3) { + dbg_puts(name); + dbg_puts(": option not found\n"); + } +#endif + return NULL; +} + diff --git a/aucat/opt.h b/aucat/opt.h new file mode 100644 index 0000000..79cb416 --- /dev/null +++ b/aucat/opt.h @@ -0,0 +1,53 @@ +/* $OpenBSD: opt.h,v 1.8 2010/06/04 06:15:28 ratchov Exp $ */ +/* + * Copyright (c) 2008 Alexandre Ratchov + * + * 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 + +#include +#include "aparams.h" + +struct dev; + +struct opt { + SLIST_ENTRY(opt) entry; +#define OPT_NAMEMAX 11 + char name[OPT_NAMEMAX + 1]; + int maxweight; /* max dynamic range for clients */ + struct aparams wpar; /* template for clients write params */ + struct aparams rpar; /* template for clients read params */ + int mmc; /* true if MMC control enabled */ + int join; /* true if join/expand enabled */ +#define MODE_PLAY 0x1 /* allowed to play */ +#define MODE_REC 0x2 /* allowed to rec */ +#define MODE_MIDIIN 0x4 /* allowed to read midi */ +#define MODE_MIDIOUT 0x8 /* allowed to write midi */ +#define MODE_MON 0x10 /* allowed to monitor */ +#define MODE_LOOP 0x20 /* deviceless mode */ +#define MODE_RECMASK (MODE_REC | MODE_MON) +#define MODE_AUDIOMASK (MODE_REC | MODE_MON | MODE_PLAY) +#define MODE_MIDIMASK (MODE_MIDIIN | MODE_MIDIOUT) + unsigned mode; /* bitmap of above */ + struct dev *dev; /* device to which we're attached */ +}; + +SLIST_HEAD(optlist,opt); + +void opt_new(char *, struct dev *, struct aparams *, struct aparams *, + int, int, int, unsigned); +struct opt *opt_byname(char *); + +#endif /* !defined(OPT_H) */ diff --git a/aucat/pipe.c b/aucat/pipe.c new file mode 100644 index 0000000..065f6f0 --- /dev/null +++ b/aucat/pipe.c @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2008 Alexandre Ratchov + * + * 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 +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "conf.h" +#include "pipe.h" +#ifdef DEBUG +#include "dbg.h" +#endif + +struct fileops pipe_ops = { + "pipe", + sizeof(struct pipe), + pipe_close, + pipe_read, + pipe_write, + NULL, /* start */ + NULL, /* stop */ + pipe_nfds, + pipe_pollfd, + pipe_revents +}; + +struct pipe * +pipe_new(struct fileops *ops, int fd, char *name) +{ + struct pipe *f; + + f = (struct pipe *)file_new(ops, name, 1); + if (f == NULL) + return NULL; + f->fd = fd; + return f; +} + +unsigned +pipe_read(struct file *file, unsigned char *data, unsigned count) +{ + struct pipe *f = (struct pipe *)file; + int n; + + while ((n = read(f->fd, data, count)) < 0) { + f->file.state &= ~FILE_ROK; + if (errno == EAGAIN) { +#ifdef DEBUG + if (debug_level >= 4) { + file_dbg(&f->file); + dbg_puts(": reading blocked\n"); + } +#endif + } else { + warn("%s", f->file.name); + file_eof(&f->file); + } + return 0; + } + if (n == 0) { + f->file.state &= ~FILE_ROK; /* XXX: already cleared in file_eof */ + file_eof(&f->file); + return 0; + } + return n; +} + + +unsigned +pipe_write(struct file *file, unsigned char *data, unsigned count) +{ + struct pipe *f = (struct pipe *)file; + int n; + + while ((n = write(f->fd, data, count)) < 0) { + f->file.state &= ~FILE_WOK; + if (errno == EAGAIN) { +#ifdef DEBUG + if (debug_level >= 4) { + file_dbg(&f->file); + dbg_puts(": writing blocked\n"); + } +#endif + } else { + if (errno != EPIPE) + warn("%s", f->file.name); + file_hup(&f->file); + } + return 0; + } + return n; +} + +int +pipe_nfds(struct file *file) { + return 1; +} + +int +pipe_pollfd(struct file *file, struct pollfd *pfd, int events) +{ + struct pipe *f = (struct pipe *)file; + + pfd->fd = f->fd; + pfd->events = events; + return (events != 0) ? 1 : 0; +} + +int +pipe_revents(struct file *f, struct pollfd *pfd) +{ + return pfd->revents; +} + +void +pipe_close(struct file *file) +{ + struct pipe *f = (struct pipe *)file; + + close(f->fd); +} + +off_t +pipe_endpos(struct file *file) +{ + struct pipe *f = (struct pipe *)file; + off_t pos; + + pos = lseek(f->fd, 0, SEEK_END); + if (pos < 0) { +#ifdef DEBUG + file_dbg(&f->file); + dbg_puts(": couldn't get file size\n"); +#endif + return 0; + } + return pos; +} + +int +pipe_seek(struct file *file, off_t pos) +{ + struct pipe *f = (struct pipe *)file; + off_t newpos; + + newpos = lseek(f->fd, pos, SEEK_SET); + if (newpos < 0) { +#ifdef DEBUG + file_dbg(&f->file); + dbg_puts(": couldn't seek\n"); +#endif + /* XXX: call eof() */ + return 0; + } + return 1; +} + +int +pipe_trunc(struct file *file, off_t pos) +{ + struct pipe *f = (struct pipe *)file; + + if (ftruncate(f->fd, pos) < 0) { +#ifdef DEBUG + file_dbg(&f->file); + dbg_puts(": couldn't truncate file\n"); +#endif + /* XXX: call hup() */ + return 0; + } + return 1; +} diff --git a/aucat/pipe.h b/aucat/pipe.h new file mode 100644 index 0000000..63ecb85 --- /dev/null +++ b/aucat/pipe.h @@ -0,0 +1,40 @@ +/* $OpenBSD: pipe.h,v 1.5 2010/04/06 20:07:01 ratchov Exp $ */ +/* + * Copyright (c) 2008 Alexandre Ratchov + * + * 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 PIPE_H +#define PIPE_H + +#include "file.h" + +struct pipe { + struct file file; + int fd; /* file descriptor */ +}; + +extern struct fileops pipe_ops; + +struct pipe *pipe_new(struct fileops *, int, char *); +void pipe_close(struct file *); +unsigned pipe_read(struct file *, unsigned char *, unsigned); +unsigned pipe_write(struct file *, unsigned char *, unsigned); +int pipe_nfds(struct file *); +int pipe_pollfd(struct file *, struct pollfd *, int); +int pipe_revents(struct file *, struct pollfd *); +int pipe_seek(struct file *, off_t); +int pipe_trunc(struct file *, off_t); +off_t pipe_endpos(struct file *); + +#endif /* !defined(PIPE_H) */ diff --git a/aucat/siofile.c b/aucat/siofile.c new file mode 100644 index 0000000..a371521 --- /dev/null +++ b/aucat/siofile.c @@ -0,0 +1,465 @@ +/* $OpenBSD: siofile.c,v 1.6 2010/06/04 06:15:28 ratchov Exp $ */ +/* + * Copyright (c) 2008 Alexandre Ratchov + * + * 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 +#include + +#include +#include +#include +#include +#include + +#include "aparams.h" +#include "aproc.h" +#include "abuf.h" +#include "conf.h" +#include "dev.h" +#include "file.h" +#include "siofile.h" +#ifdef DEBUG +#include "dbg.h" +#endif + +struct siofile { + struct file file; + struct sio_hdl *hdl; + unsigned wtickets, wbpf; + unsigned rtickets, rbpf; + unsigned bufsz; + int started; +}; + +void siofile_close(struct file *); +unsigned siofile_read(struct file *, unsigned char *, unsigned); +unsigned siofile_write(struct file *, unsigned char *, unsigned); +void siofile_start(struct file *); +void siofile_stop(struct file *); +int siofile_nfds(struct file *); +int siofile_pollfd(struct file *, struct pollfd *, int); +int siofile_revents(struct file *, struct pollfd *); + +struct fileops siofile_ops = { + "sio", + sizeof(struct siofile), + siofile_close, + siofile_read, + siofile_write, + siofile_start, + siofile_stop, + siofile_nfds, + siofile_pollfd, + siofile_revents +}; + +int wsio_out(struct aproc *, struct abuf *); +int rsio_in(struct aproc *, struct abuf *); + +struct aproc_ops rsio_ops = { + "rsio", + rsio_in, + rfile_out, + rfile_eof, + rfile_hup, + NULL, /* newin */ + NULL, /* newout */ + aproc_ipos, + aproc_opos, + rfile_done +}; + +struct aproc_ops wsio_ops = { + "wsio", + wfile_in, + wsio_out, + wfile_eof, + wfile_hup, + NULL, /* newin */ + NULL, /* newout */ + aproc_ipos, + aproc_opos, + wfile_done +}; + +struct aproc * +rsio_new(struct file *f) +{ + struct aproc *p; + + p = aproc_new(&rsio_ops, f->name); + p->u.io.file = f; + p->u.io.partial = 0; + f->rproc = p; + return p; +} + +struct aproc * +wsio_new(struct file *f) +{ + struct aproc *p; + + p = aproc_new(&wsio_ops, f->name); + p->u.io.file = f; + p->u.io.partial = 0; + f->wproc = p; + return p; +} + +int +wsio_out(struct aproc *p, struct abuf *obuf) +{ + struct siofile *f = (struct siofile *)p->u.io.file; + + if (f->wtickets == 0) { +#ifdef DEBUG + if (debug_level >= 4) { + file_dbg(&f->file); + dbg_puts(": no more write tickets\n"); + } +#endif + f->file.state &= ~FILE_WOK; + return 0; + } + return wfile_out(p, obuf); +} + +int +rsio_in(struct aproc *p, struct abuf *ibuf) +{ + struct siofile *f = (struct siofile *)p->u.io.file; + + if (f->rtickets == 0) { +#ifdef DEBUG + if (debug_level >= 4) { + file_dbg(&f->file); + dbg_puts(": no more read tickets\n"); + } +#endif + f->file.state &= ~FILE_ROK; + return 0; + } + return rfile_in(p, ibuf); +} + +void +siofile_cb(void *addr, int delta) +{ + struct siofile *f = (struct siofile *)addr; + struct aproc *p; + +#ifdef DEBUG + if (delta < 0 || delta > (60 * RATE_MAX)) { + file_dbg(&f->file); + dbg_puts(": "); + dbg_puti(delta); + dbg_puts(": bogus sndio delta"); + dbg_panic(); + } + if (debug_level >= 4) { + file_dbg(&f->file); + dbg_puts(": tick, delta = "); + dbg_puti(delta); + dbg_puts("\n"); + } +#endif + if (delta != 0) { + p = f->file.wproc; + if (p && p->ops->opos) + p->ops->opos(p, NULL, delta); + } + if (delta != 0) { + p = f->file.rproc; + if (p && p->ops->ipos) + p->ops->ipos(p, NULL, delta); + } + f->wtickets += delta * f->wbpf; + f->rtickets += delta * f->rbpf; +} + +/* + * Open the device. + */ +struct siofile * +siofile_new(struct fileops *ops, char *path, unsigned *rmode, + struct aparams *ipar, struct aparams *opar, + unsigned *bufsz, unsigned *round) +{ + char *siopath; + struct sio_par par; + struct sio_hdl *hdl; + struct siofile *f; + unsigned mode = *rmode; + + siopath = (strcmp(path, "default") == 0) ? NULL : path; + hdl = sio_open(siopath, mode, 1); + if (hdl == NULL) { + if (mode != (SIO_PLAY | SIO_REC)) + return NULL; + hdl = sio_open(siopath, SIO_PLAY, 1); + if (hdl != NULL) + mode = SIO_PLAY; + else { + hdl = sio_open(siopath, SIO_REC, 1); + if (hdl != NULL) + mode = SIO_REC; + else + return NULL; + } +#ifdef DEBUG + if (debug_level >= 1) { + dbg_puts("warning, device opened in "); + dbg_puts(mode == SIO_PLAY ? "play-only" : "rec-only"); + dbg_puts(" mode\n"); + } +#endif + } + + sio_initpar(&par); + if (mode & SIO_REC) { + par.bits = ipar->bits; + par.bps = ipar->bps; + par.sig = ipar->sig; + par.le = ipar->le; + par.msb = ipar->msb; + par.rate = ipar->rate; + par.rchan = ipar->cmax - ipar->cmin + 1; + } else { + par.bits = opar->bits; + par.bps = opar->bps; + par.sig = opar->sig; + par.le = opar->le; + par.msb = opar->msb; + par.rate = opar->rate; + } + if (mode & SIO_PLAY) + par.pchan = opar->cmax - opar->cmin + 1; + par.appbufsz = *bufsz; + par.round = *round; + if (!sio_setpar(hdl, &par)) + goto bad_close; + if (!sio_getpar(hdl, &par)) + goto bad_close; + if (mode & SIO_REC) { + ipar->bits = par.bits; + ipar->bps = par.bps; + ipar->sig = par.sig; + ipar->le = par.le; + ipar->msb = par.msb; + ipar->rate = par.rate; + ipar->cmax = ipar->cmin + par.rchan - 1; + } + if (mode & SIO_PLAY) { + opar->bits = par.bits; + opar->bps = par.bps; + opar->sig = par.sig; + opar->le = par.le; + opar->msb = par.msb; + opar->rate = par.rate; + opar->cmax = opar->cmin + par.pchan - 1; + } + *rmode = mode; + *bufsz = par.bufsz; + *round = par.round; + f = (struct siofile *)file_new(ops, path, sio_nfds(hdl)); + if (f == NULL) + goto bad_close; + f->hdl = hdl; + f->started = 0; + f->wtickets = 0; + f->rtickets = 0; + f->wbpf = par.pchan * par.bps; + f->rbpf = par.rchan * par.bps; + f->bufsz = par.bufsz; + sio_onmove(f->hdl, siofile_cb, f); + return f; + bad_close: + sio_close(hdl); + return NULL; +} + +void +siofile_start(struct file *file) +{ + struct siofile *f = (struct siofile *)file; + + if (!sio_start(f->hdl)) { +#ifdef DEBUG + dbg_puts(f->file.name); + dbg_puts(": failed to start device\n"); +#endif + file_close(file); + return; + } + f->started = 1; + f->wtickets = f->bufsz * f->wbpf; + f->rtickets = 0; +#ifdef DEBUG + if (debug_level >= 3) { + file_dbg(&f->file); + dbg_puts(": started\n"); + } +#endif +} + +void +siofile_stop(struct file *file) +{ + struct siofile *f = (struct siofile *)file; + + f->started = 0; + if (!sio_eof(f->hdl) && !sio_stop(f->hdl)) { +#ifdef DEBUG + dbg_puts(f->file.name); + dbg_puts(": failed to stop device\n"); +#endif + file_close(file); + return; + } +#ifdef DEBUG + if (debug_level >= 3) { + file_dbg(&f->file); + dbg_puts(": stopped\n"); + } +#endif +} + +unsigned +siofile_read(struct file *file, unsigned char *data, unsigned count) +{ + struct siofile *f = (struct siofile *)file; + unsigned n; + +#ifdef DEBUG + if (f->rtickets == 0) { + file_dbg(&f->file); + dbg_puts(": called with no read tickets\n"); + } +#endif + if (count > f->rtickets) + count = f->rtickets; + n = f->started ? sio_read(f->hdl, data, count) : 0; + if (n == 0) { + f->file.state &= ~FILE_ROK; + if (sio_eof(f->hdl)) { +#ifdef DEBUG + dbg_puts(f->file.name); + dbg_puts(": failed to read from device\n"); +#endif + file_eof(&f->file); + } else { +#ifdef DEBUG + if (debug_level >= 4) { + file_dbg(&f->file); + dbg_puts(": reading blocked\n"); + } +#endif + } + return 0; + } else { + f->rtickets -= n; + if (f->rtickets == 0) { + f->file.state &= ~FILE_ROK; +#ifdef DEBUG + if (debug_level >= 4) { + file_dbg(&f->file); + dbg_puts(": read tickets exhausted\n"); + } +#endif + } + } + return n; + +} + +unsigned +siofile_write(struct file *file, unsigned char *data, unsigned count) +{ + struct siofile *f = (struct siofile *)file; + unsigned n; + +#ifdef DEBUG + if (f->wtickets == 0) { + file_dbg(&f->file); + dbg_puts(": called with no write tickets\n"); + } +#endif + if (count > f->wtickets) + count = f->wtickets; + n = f->started ? sio_write(f->hdl, data, count) : 0; + if (n == 0) { + f->file.state &= ~FILE_WOK; + if (sio_eof(f->hdl)) { +#ifdef DEBUG + dbg_puts(f->file.name); + dbg_puts(": failed to write on device\n"); +#endif + file_hup(&f->file); + } else { +#ifdef DEBUG + if (debug_level >= 4) { + file_dbg(&f->file); + dbg_puts(": writing blocked\n"); + } +#endif + } + return 0; + } else { + f->wtickets -= n; + if (f->wtickets == 0) { + f->file.state &= ~FILE_WOK; +#ifdef DEBUG + if (debug_level >= 4) { + file_dbg(&f->file); + dbg_puts(": write tickets exhausted\n"); + } +#endif + } + } + return n; +} + +int +siofile_nfds(struct file *file) +{ + return sio_nfds(((struct siofile *)file)->hdl); +} + +int +siofile_pollfd(struct file *file, struct pollfd *pfd, int events) +{ + struct siofile *f = (struct siofile *)file; + + if (!f->started) + events &= ~(POLLIN | POLLOUT); + return sio_pollfd(((struct siofile *)file)->hdl, pfd, events); +} + +int +siofile_revents(struct file *file, struct pollfd *pfd) +{ + return sio_revents(((struct siofile *)file)->hdl, pfd); +} + +void +siofile_close(struct file *file) +{ + struct siofile *f = (struct siofile *)file; + + if (f->started) + siofile_stop(&f->file); + return sio_close(((struct siofile *)file)->hdl); +} diff --git a/aucat/siofile.h b/aucat/siofile.h new file mode 100644 index 0000000..c224288 --- /dev/null +++ b/aucat/siofile.h @@ -0,0 +1,32 @@ +/* $OpenBSD: siofile.h,v 1.5 2010/05/02 11:54:26 ratchov Exp $ */ +/* + * Copyright (c) 2008 Alexandre Ratchov + * + * 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 fileops; +struct siofile; +struct aparams; +struct aproc; + +struct siofile *siofile_new(struct fileops *, char *, unsigned *, + struct aparams *, struct aparams *, unsigned *, unsigned *); +struct aproc *rsio_new(struct file *f); +struct aproc *wsio_new(struct file *f); + +extern struct fileops siofile_ops; + +#endif /* !defined(SIOFILE_H) */ diff --git a/aucat/sock.c b/aucat/sock.c new file mode 100644 index 0000000..4eec95f --- /dev/null +++ b/aucat/sock.c @@ -0,0 +1,1745 @@ +/* $OpenBSD: sock.c,v 1.50 2010/06/05 16:00:52 ratchov Exp $ */ +/* + * Copyright (c) 2008 Alexandre Ratchov + * + * 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 +#include +#include +#include +#include +#include + +#include "abuf.h" +#include "aproc.h" +#include "conf.h" +#include "dev.h" +#include "midi.h" +#include "opt.h" +#include "sock.h" +#ifdef DEBUG +#include "dbg.h" +#endif + +void sock_attach(struct sock *, int); +int sock_read(struct sock *); +int sock_write(struct sock *); +int sock_execmsg(struct sock *); +void sock_reset(struct sock *); +void sock_close(struct file *); + +struct fileops sock_ops = { + "sock", + sizeof(struct sock), + sock_close, + pipe_read, + pipe_write, + NULL, /* start */ + NULL, /* stop */ + pipe_nfds, + pipe_pollfd, + pipe_revents +}; + +#ifdef DEBUG +void +sock_dbg(struct sock *f) +{ + static char *pstates[] = { "hel", "ini", "sta", "rdy", "run", "mid" }; + static char *rstates[] = { "rdat", "rmsg", "rret" }; + static char *wstates[] = { "widl", "wmsg", "wdat" }; + struct aproc *midi; + + midi = f->dev ? f->dev->midi : NULL; + if (f->slot >= 0 && APROC_OK(midi)) { + dbg_puts(midi->u.ctl.slot[f->slot].name); + dbg_putu(midi->u.ctl.slot[f->slot].unit); + } else + dbg_puts(f->pipe.file.name); + dbg_puts("/"); + dbg_puts(pstates[f->pstate]); + dbg_puts("|"); + dbg_puts(rstates[f->rstate]); + dbg_puts("|"); + dbg_puts(wstates[f->wstate]); +} +#endif + +void sock_setvol(void *, unsigned); +void sock_startreq(void *); +void sock_stopreq(void *); +void sock_quitreq(void *); +void sock_locreq(void *, unsigned); + +struct ctl_ops ctl_sockops = { + sock_setvol, + sock_startreq, + sock_stopreq, + sock_locreq, + sock_quitreq +}; + +unsigned sock_sesrefs = 0; /* connections to the session */ +uid_t sock_sesuid; /* owner of the session */ + +void +sock_close(struct file *arg) +{ + struct sock *f = (struct sock *)arg; + + sock_sesrefs--; + pipe_close(&f->pipe.file); + if (f->dev) { + dev_unref(f->dev); + f->dev = NULL; + } +} + +void +rsock_done(struct aproc *p) +{ + struct sock *f = (struct sock *)p->u.io.file; + + if (f == NULL) + return; + sock_reset(f); + f->pipe.file.rproc = NULL; + if (f->pipe.file.wproc) { + if (f->slot >= 0) + ctl_slotdel(f->dev->midi, f->slot); + aproc_del(f->pipe.file.wproc); + file_del(&f->pipe.file); + } + p->u.io.file = NULL; +} + +int +rsock_in(struct aproc *p, struct abuf *ibuf_dummy) +{ + struct sock *f = (struct sock *)p->u.io.file; + struct abuf *obuf; + + if (!sock_read(f)) + return 0; + obuf = LIST_FIRST(&p->outs); + if (obuf && f->pstate >= SOCK_RUN) { + if (!abuf_flush(obuf)) + return 0; + } + return 1; +} + +int +rsock_out(struct aproc *p, struct abuf *obuf) +{ + struct sock *f = (struct sock *)p->u.io.file; + + if (f->pipe.file.state & FILE_RINUSE) + return 0; + + /* + * When calling sock_read(), we may receive a ``STOP'' command, + * and detach ``obuf''. In this case, there's no more caller and + * we'll stop processing further messages, resulting in a deadlock. + * The solution is to iterate over sock_read() in order to + * consume all messages(). + */ + for (;;) { + if (!sock_read(f)) + return 0; + } + return 1; +} + +void +rsock_eof(struct aproc *p, struct abuf *ibuf_dummy) +{ + aproc_del(p); +} + +void +rsock_hup(struct aproc *p, struct abuf *ibuf) +{ + aproc_del(p); +} + +void +rsock_opos(struct aproc *p, struct abuf *obuf, int delta) +{ + struct sock *f = (struct sock *)p->u.io.file; + + if (f->mode & AMSG_RECMASK) + return; + + f->delta += delta; +#ifdef DEBUG + if (debug_level >= 4) { + aproc_dbg(p); + dbg_puts(": moved to delta = "); + dbg_puti(f->delta); + dbg_puts("\n"); + } +#endif + f->tickpending++; + for (;;) { + if (!sock_write(f)) + break; + } +} + +struct aproc_ops rsock_ops = { + "rsock", + rsock_in, + rsock_out, + rsock_eof, + rsock_hup, + NULL, /* newin */ + NULL, /* newout */ + NULL, /* ipos */ + rsock_opos, + rsock_done +}; + +void +wsock_done(struct aproc *p) +{ + struct sock *f = (struct sock *)p->u.io.file; + + if (f == NULL) + return; + sock_reset(f); + f->pipe.file.wproc = NULL; + if (f->pipe.file.rproc) { + if (f->slot >= 0) + ctl_slotdel(f->dev->midi, f->slot); + aproc_del(f->pipe.file.rproc); + file_del(&f->pipe.file); + } + p->u.io.file = NULL; +} + +int +wsock_in(struct aproc *p, struct abuf *ibuf) +{ + struct sock *f = (struct sock *)p->u.io.file; + + if (f->pipe.file.state & FILE_WINUSE) + return 0; + /* + * See remark in rsock_out(). + */ + for (;;) { + if (!sock_write(f)) + return 0; + } + return 1; +} + +int +wsock_out(struct aproc *p, struct abuf *obuf_dummy) +{ + struct abuf *ibuf = LIST_FIRST(&p->ins); + struct sock *f = (struct sock *)p->u.io.file; + + if (ibuf) { + if (!abuf_fill(ibuf)) + return 0; + } + if (!sock_write(f)) + return 0; + return 1; +} + +void +wsock_eof(struct aproc *p, struct abuf *obuf) +{ + aproc_del(p); +} + +void +wsock_hup(struct aproc *p, struct abuf *obuf_dummy) +{ + aproc_del(p); +} + +void +wsock_ipos(struct aproc *p, struct abuf *obuf, int delta) +{ + struct sock *f = (struct sock *)p->u.io.file; + + if (!(f->mode & AMSG_RECMASK)) + return; + + f->delta += delta; +#ifdef DEBUG + if (debug_level >= 4) { + aproc_dbg(p); + dbg_puts(": moved to delta = "); + dbg_puti(f->delta); + dbg_puts("\n"); + } +#endif + f->tickpending++; + for (;;) { + if (!sock_write(f)) + break; + } +} + +struct aproc_ops wsock_ops = { + "wsock", + wsock_in, + wsock_out, + wsock_eof, + wsock_hup, + NULL, /* newin */ + NULL, /* newout */ + wsock_ipos, + NULL, /* opos */ + wsock_done +}; + +/* + * Initialise socket in the SOCK_HELLO state with default + * parameters. + */ +struct sock * +sock_new(struct fileops *ops, int fd) +{ + struct aproc *rproc, *wproc; + struct sock *f; + uid_t uid, gid; + + /* + * ensure that all connections belong to the same user, + * for privacy reasons. + * + * XXX: is there a portable way of doing this ? + */ + if (getpeereid(fd, &uid, &gid) < 0) { + close(fd); + return NULL; + } + if (sock_sesrefs == 0) { + /* start a new session */ + sock_sesuid = uid; + } else if (uid != sock_sesuid) { + /* session owned by another user, drop connection */ + close(fd); + return NULL; + } + sock_sesrefs++; + + f = (struct sock *)pipe_new(ops, fd, "sock"); + if (f == NULL) { + close(fd); + return NULL; + } + f->pstate = SOCK_HELLO; + f->mode = 0; + f->opt = NULL; + f->dev = NULL; + f->xrun = AMSG_IGNORE; + f->delta = 0; + f->tickpending = 0; + f->startpos = 0; + f->startpending = 0; + f->vol = f->lastvol = MIDI_MAXCTL; + f->slot = -1; + + wproc = aproc_new(&wsock_ops, f->pipe.file.name); + wproc->u.io.file = &f->pipe.file; + wproc->u.io.partial = 0; + f->pipe.file.wproc = wproc; + f->wstate = SOCK_WIDLE; + f->wtodo = 0xdeadbeef; + + rproc = aproc_new(&rsock_ops, f->pipe.file.name); + rproc->u.io.file = &f->pipe.file; + rproc->u.io.partial = 0; + f->pipe.file.rproc = rproc; + f->rstate = SOCK_RMSG; + f->rtodo = sizeof(struct amsg); + return f; +} + +/* + * Free buffers. + */ +void +sock_freebuf(struct sock *f) +{ + struct abuf *rbuf, *wbuf; + + f->pstate = SOCK_INIT; +#ifdef DEBUG + if (debug_level >= 3) { + sock_dbg(f); + dbg_puts(": freeing buffers\n"); + } +#endif + wbuf = LIST_FIRST(&f->pipe.file.wproc->ins); + rbuf = LIST_FIRST(&f->pipe.file.rproc->outs); + if (rbuf || wbuf) + ctl_slotstop(f->dev->midi, f->slot); + if (rbuf) + abuf_eof(rbuf); + if (wbuf) + abuf_hup(wbuf); + f->tickpending = 0; + f->startpending = 0; +} + +/* + * Allocate buffers, so client can start filling write-end. + */ +void +sock_allocbuf(struct sock *f) +{ + struct abuf *rbuf = NULL, *wbuf = NULL; + unsigned bufsz; + + bufsz = f->bufsz + f->dev->bufsz / f->dev->round * f->round; + f->pstate = SOCK_START; + if (f->mode & AMSG_PLAY) { + rbuf = abuf_new(bufsz, &f->rpar); + aproc_setout(f->pipe.file.rproc, rbuf); + if (!ABUF_WOK(rbuf) || (f->pipe.file.state & FILE_EOF)) + f->pstate = SOCK_READY; + f->rmax = bufsz * aparams_bpf(&f->rpar); + } + if (f->mode & AMSG_RECMASK) { + wbuf = abuf_new(bufsz, &f->wpar); + aproc_setin(f->pipe.file.wproc, wbuf); + f->walign = f->round; + f->wmax = 0; + } + f->delta = 0; + f->startpos = 0; + f->tickpending = 0; + f->startpending = 0; +#ifdef DEBUG + if (debug_level >= 3) { + sock_dbg(f); + dbg_puts(": allocating "); + dbg_putu(f->bufsz); + dbg_puts("/"); + dbg_putu(bufsz); + dbg_puts(" fr buffers, rmax = "); + dbg_putu(f->rmax); + dbg_puts("\n"); + } +#endif + if (f->mode & AMSG_PLAY) { + f->pstate = SOCK_START; + } else { + f->pstate = SOCK_READY; + if (ctl_slotstart(f->dev->midi, f->slot)) + (void)sock_attach(f, 0); + } +} + +/* + * Set volume. Callback invoked when volume is modified externally + */ +void +sock_setvol(void *arg, unsigned vol) +{ + struct sock *f = (struct sock *)arg; + struct abuf *rbuf; + + f->vol = vol; + rbuf = LIST_FIRST(&f->pipe.file.rproc->outs); + if (!rbuf) { +#ifdef DEBUG + if (debug_level >= 3) { + sock_dbg(f); + dbg_puts(": no read buffer to set volume yet\n"); + } +#endif + return; + } + dev_setvol(f->dev, rbuf, MIDI_TO_ADATA(vol)); +} + +/* + * Attach the stream. Callback invoked when MMC start + */ +void +sock_startreq(void *arg) +{ + struct sock *f = (struct sock *)arg; + +#ifdef DEBUG + if (f->pstate != SOCK_READY) { + sock_dbg(f); + dbg_puts(": not in READY state\n"); + dbg_panic(); + } +#endif + (void)sock_attach(f, 0); +} + +/* + * Callback invoked by MMC stop + */ +void +sock_stopreq(void *arg) +{ +#ifdef DEBUG + struct sock *f = (struct sock *)arg; + + if (debug_level >= 3) { + sock_dbg(f); + dbg_puts(": ignored STOP signal\n"); + } +#endif +} + +/* + * Callback invoked by MMC relocate, ignored + */ +void +sock_locreq(void *arg, unsigned mmcpos) +{ +#ifdef DEBUG + struct sock *f = (struct sock *)arg; + + if (debug_level >= 3) { + sock_dbg(f); + dbg_puts(": ignored RELOCATE signal\n"); + } +#endif +} + +/* + * Callback invoked when slot is gone + */ +void +sock_quitreq(void *arg) +{ + struct sock *f = (struct sock *)arg; + +#ifdef DEBUG + if (debug_level >= 3) { + sock_dbg(f); + dbg_puts(": slot gone\n"); + } +#endif + file_close(&f->pipe.file); +} + +/* + * Attach play and/or record buffers to dev->mix and/or dev->sub. + */ +void +sock_attach(struct sock *f, int force) +{ + struct abuf *rbuf, *wbuf; + + rbuf = LIST_FIRST(&f->pipe.file.rproc->outs); + wbuf = LIST_FIRST(&f->pipe.file.wproc->ins); + + /* + * If in SOCK_START state, dont attach until + * the buffer isn't completely filled. + */ + if (!force && rbuf && ABUF_WOK(rbuf)) + return; + + /* + * start the device (dev_getpos() and dev_attach() must + * be called on a started device + */ + dev_wakeup(f->dev); + + /* + * get the current position, the origin is when + * the first sample is played/recorded + */ + f->startpos = dev_getpos(f->dev) * (int)f->round / (int)f->dev->round; + f->startpending = 1; + f->pstate = SOCK_RUN; +#ifdef DEBUG + if (debug_level >= 3) { + sock_dbg(f); + dbg_puts(": attaching at "); + dbg_puti(f->startpos); + dbg_puts("\n"); + } +#endif + /* + * We dont check whether the device is dying, + * because dev_xxx() functions are supposed to + * work (i.e., not to crash) + */ + dev_attach(f->dev, f->pipe.file.name, f->mode, + rbuf, &f->rpar, + f->opt->join ? f->opt->rpar.cmax - f->opt->rpar.cmin + 1 : 0, + wbuf, &f->wpar, + f->opt->join ? f->opt->wpar.cmax - f->opt->wpar.cmin + 1 : 0, + f->xrun, f->opt->maxweight); + if (f->mode & AMSG_PLAY) + dev_setvol(f->dev, rbuf, MIDI_TO_ADATA(f->vol)); + + /* + * Send the initial position, if needed. + */ + for (;;) { + if (!sock_write(f)) + break; + } +} + +void +sock_reset(struct sock *f) +{ + switch (f->pstate) { + case SOCK_START: + case SOCK_READY: + if (ctl_slotstart(f->dev->midi, f->slot)) { + (void)sock_attach(f, 1); + f->pstate = SOCK_RUN; + } + /* PASSTHROUGH */ + case SOCK_RUN: + sock_freebuf(f); + f->pstate = SOCK_INIT; + /* PASSTHROUGH */ + case SOCK_INIT: + /* nothing yet */ + break; + } +} + +/* + * Read a message from the file descriptor, return 1 if done, 0 + * otherwise. The message is stored in f->rmsg. + */ +int +sock_rmsg(struct sock *f) +{ + unsigned count; + unsigned char *data; + + while (f->rtodo > 0) { + if (!(f->pipe.file.state & FILE_ROK)) { +#ifdef DEBUG + if (debug_level >= 4) { + sock_dbg(f); + dbg_puts(": reading message blocked, "); + dbg_putu(f->rtodo); + dbg_puts(" bytes remaining\n"); + } +#endif + return 0; + } + data = (unsigned char *)&f->rmsg; + data += sizeof(struct amsg) - f->rtodo; + count = file_read(&f->pipe.file, data, f->rtodo); + if (count == 0) + return 0; + f->rtodo -= count; + } +#ifdef DEBUG + if (debug_level >= 4) { + sock_dbg(f); + dbg_puts(": read full message\n"); + } +#endif + return 1; +} + +/* + * Write a message to the file descriptor, return 1 if done, 0 + * otherwise. The "m" argument is f->rmsg or f->wmsg, and the "ptodo" + * points to the f->rtodo or f->wtodo respectively. + */ +int +sock_wmsg(struct sock *f, struct amsg *m, unsigned *ptodo) +{ + unsigned count; + unsigned char *data; + + while (*ptodo > 0) { + if (!(f->pipe.file.state & FILE_WOK)) { +#ifdef DEBUG + if (debug_level >= 4) { + sock_dbg(f); + dbg_puts(": writing message blocked, "); + dbg_putu(*ptodo); + dbg_puts(" bytes remaining\n"); + } +#endif + return 0; + } + data = (unsigned char *)m; + data += sizeof(struct amsg) - *ptodo; + count = file_write(&f->pipe.file, data, *ptodo); + if (count == 0) + return 0; + *ptodo -= count; + } +#ifdef DEBUG + if (debug_level >= 4) { + sock_dbg(f); + dbg_puts(": wrote full message\n"); + } +#endif + return 1; +} + +/* + * Read data chunk from the file descriptor, return 1 if at least one + * byte was read, 0 if the file blocked. + */ +int +sock_rdata(struct sock *f) +{ + struct aproc *p; + struct abuf *obuf; + unsigned n; + +#ifdef DEBUG + if (f->pstate != SOCK_MIDI && f->rtodo == 0) { + sock_dbg(f); + dbg_puts(": data block already read\n"); + dbg_panic(); + } +#endif + p = f->pipe.file.rproc; + obuf = LIST_FIRST(&p->outs); + if (obuf == NULL) + return 0; + if (!ABUF_WOK(obuf) || !(f->pipe.file.state & FILE_ROK)) + return 0; + if (f->pstate == SOCK_MIDI) { + if (!rfile_do(p, obuf->len, NULL)) + return 0; + } else { + if (!rfile_do(p, f->rtodo, &n)) + return 0; + f->rtodo -= n; + if (f->pstate == SOCK_START) { + if (!ABUF_WOK(obuf) || (f->pipe.file.state & FILE_EOF)) + f->pstate = SOCK_READY; + } + } + return 1; +} + +/* + * Write data chunk to the file descriptor, return 1 if at least one + * byte was written, 0 if the file blocked. + */ +int +sock_wdata(struct sock *f) +{ + struct aproc *p; + struct abuf *ibuf; + unsigned n; + +#ifdef DEBUG + if (f->pstate != SOCK_MIDI && f->wtodo == 0) { + sock_dbg(f); + dbg_puts(": attempted to write zero-sized data block\n"); + dbg_panic(); + } +#endif + if (!(f->pipe.file.state & FILE_WOK)) + return 0; + p = f->pipe.file.wproc; + ibuf = LIST_FIRST(&p->ins); +#ifdef DEBUG + if (f->pstate != SOCK_MIDI && ibuf == NULL) { + sock_dbg(f); + dbg_puts(": attempted to write on detached buffer\n"); + dbg_panic(); + } +#endif + if (ibuf == NULL) + return 0; + if (!ABUF_ROK(ibuf)) + return 0; + if (f->pstate == SOCK_MIDI) { + if (!wfile_do(p, ibuf->len, NULL)) + return 0; + } else { + if (!wfile_do(p, f->wtodo, &n)) + return 0; + f->wtodo -= n; + } + return 1; +} + +int +sock_setpar(struct sock *f) +{ + struct amsg_par *p = &f->rmsg.u.par; + unsigned min, max, rate; + + if (AMSG_ISSET(p->bits)) { + if (p->bits < BITS_MIN || p->bits > BITS_MAX) { +#ifdef DEBUG + if (debug_level >= 1) { + sock_dbg(f); + dbg_puts(": "); + dbg_putu(p->bits); + dbg_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 (debug_level >= 1) { + sock_dbg(f); + dbg_puts(": "); + dbg_putu(p->bps); + dbg_puts(": wrong bytes per sample\n"); + } +#endif + return 0; + } + } else + p->bps = APARAMS_BPS(p->bits); + f->rpar.bits = f->wpar.bits = p->bits; + f->rpar.bps = f->wpar.bps = p->bps; +#ifdef DEBUG + if (debug_level >= 3) { + sock_dbg(f); + dbg_puts(": using "); + dbg_putu(p->bits); + dbg_puts("bits, "); + dbg_putu(p->bps); + dbg_puts(" bytes per sample\n"); + } +#endif + } + if (AMSG_ISSET(p->sig)) + f->rpar.sig = f->wpar.sig = p->sig ? 1 : 0; + if (AMSG_ISSET(p->le)) + f->rpar.le = f->wpar.le = p->le ? 1 : 0; + if (AMSG_ISSET(p->msb)) + f->rpar.msb = f->wpar.msb = p->msb ? 1 : 0; + if (AMSG_ISSET(p->rchan) && (f->mode & AMSG_RECMASK)) { + if (p->rchan < 1) + p->rchan = 1; + if (p->rchan > NCHAN_MAX) + p->rchan = NCHAN_MAX; + f->wpar.cmin = f->opt->wpar.cmin; + f->wpar.cmax = f->opt->wpar.cmin + p->rchan - 1; + if (f->wpar.cmax > f->opt->wpar.cmax) + f->wpar.cmax = f->opt->wpar.cmax; +#ifdef DEBUG + if (debug_level >= 3) { + sock_dbg(f); + dbg_puts(": using recording channels "); + dbg_putu(f->wpar.cmin); + dbg_puts(".."); + dbg_putu(f->wpar.cmax); + dbg_puts("\n"); + } +#endif + } + if (AMSG_ISSET(p->pchan) && (f->mode & AMSG_PLAY)) { + if (p->pchan < 1) + p->pchan = 1; + if (p->pchan > NCHAN_MAX) + p->pchan = NCHAN_MAX; + f->rpar.cmin = f->opt->rpar.cmin; + f->rpar.cmax = f->opt->rpar.cmin + p->pchan - 1; + if (f->rpar.cmax > f->opt->rpar.cmax) + f->rpar.cmax = f->opt->rpar.cmax; +#ifdef DEBUG + if (debug_level >= 3) { + sock_dbg(f); + dbg_puts(": using playback channels "); + dbg_putu(f->rpar.cmin); + dbg_puts(".."); + dbg_putu(f->rpar.cmax); + dbg_puts("\n"); + } +#endif + } + if (AMSG_ISSET(p->rate)) { + if (p->rate < RATE_MIN) + p->rate = RATE_MIN; + if (p->rate > RATE_MAX) + p->rate = RATE_MAX; + f->round = dev_roundof(f->dev, p->rate); + f->rpar.rate = f->wpar.rate = p->rate; + if (!AMSG_ISSET(p->appbufsz)) { + p->appbufsz = f->dev->bufsz / f->dev->round * f->round; +#ifdef DEBUG + if (debug_level >= 3) { + sock_dbg(f); + dbg_puts(": using "); + dbg_putu(p->appbufsz); + dbg_puts(" fr app buffer size\n"); + } +#endif + } +#ifdef DEBUG + if (debug_level >= 3) { + sock_dbg(f); + dbg_puts(": using "); + dbg_putu(p->rate); + dbg_puts("Hz sample rate, "); + dbg_putu(f->round); + dbg_puts(" fr block size\n"); + } +#endif + } + if (AMSG_ISSET(p->xrun)) { + if (p->xrun != AMSG_IGNORE && + p->xrun != AMSG_SYNC && + p->xrun != AMSG_ERROR) { +#ifdef DEBUG + if (debug_level >= 1) { + sock_dbg(f); + dbg_puts(": "); + dbg_putx(p->xrun); + dbg_puts(": bad xrun policy\n"); + } +#endif + return 0; + } + f->xrun = p->xrun; + if (f->opt->mmc && f->xrun == AMSG_IGNORE) + f->xrun = AMSG_SYNC; +#ifdef DEBUG + if (debug_level >= 3) { + sock_dbg(f); + dbg_puts(": using 0x"); + dbg_putx(f->xrun); + dbg_puts(" xrun policy\n"); + } +#endif + } + if (AMSG_ISSET(p->appbufsz)) { + rate = (f->mode & AMSG_PLAY) ? f->rpar.rate : f->wpar.rate; + min = 1; + max = 1 + rate / f->dev->round; + min *= f->round; + max *= f->round; + p->appbufsz += f->round - 1; + p->appbufsz -= p->appbufsz % f->round; + if (p->appbufsz < min) + p->appbufsz = min; + if (p->appbufsz > max) + p->appbufsz = max; + f->bufsz = p->appbufsz; +#ifdef DEBUG + if (debug_level >= 3) { + sock_dbg(f); + dbg_puts(": using "); + dbg_putu(f->bufsz); + dbg_puts(" buffer size\n"); + } +#endif + } +#ifdef DEBUG + if (debug_level >= 2) { + if (APROC_OK(f->dev->midi)) { + dbg_puts(f->dev->midi->u.ctl.slot[f->slot].name); + dbg_putu(f->dev->midi->u.ctl.slot[f->slot].unit); + } else + dbg_puts(f->pipe.file.name); + dbg_puts(": buffer size = "); + dbg_putu(f->bufsz); + if (f->mode & AMSG_PLAY) { + dbg_puts(", play = "); + aparams_dbg(&f->rpar); + } + if (f->mode & AMSG_RECMASK) { + dbg_puts(", rec:"); + aparams_dbg(&f->wpar); + } + dbg_puts("\n"); + } +#endif + return 1; +} + +/* + * allocate buffers, so client can start filling write-end. + */ +void +sock_midiattach(struct sock *f, unsigned mode) +{ + struct abuf *rbuf = NULL, *wbuf = NULL; + + if (mode & AMSG_MIDIOUT) { + rbuf = abuf_new(MIDI_BUFSZ, &aparams_none); + aproc_setout(f->pipe.file.rproc, rbuf); + } + if (mode & AMSG_MIDIIN) { + wbuf = abuf_new(MIDI_BUFSZ, &aparams_none); + aproc_setin(f->pipe.file.wproc, wbuf); + } + dev_midiattach(f->dev, rbuf, wbuf); +} + +int +sock_hello(struct sock *f) +{ + struct amsg_hello *p = &f->rmsg.u.hello; + +#ifdef DEBUG + if (debug_level >= 3) { + sock_dbg(f); + dbg_puts(": hello from <"); + dbg_puts(p->who); + dbg_puts(">, proto = "); + dbg_putx(p->proto); + dbg_puts(", ver "); + dbg_putu(p->version); + dbg_puts("\n"); + } +#endif + if (p->version != AMSG_VERSION) { +#ifdef DEBUG + if (debug_level >= 1) { + sock_dbg(f); + dbg_puts(": "); + dbg_putu(p->version); + dbg_puts(": bad version\n"); + } +#endif + return 0; + } + f->opt = opt_byname(p->opt); + if (f->opt == NULL) + return 0; + if (!dev_ref(f->opt->dev)) + return 0; + f->dev = f->opt->dev; + + if (APROC_OK(f->dev->midi) && (p->proto & (AMSG_MIDIIN | AMSG_MIDIOUT))) { + if (p->proto & ~(AMSG_MIDIIN | AMSG_MIDIOUT)) { +#ifdef DEBUG + if (debug_level >= 1) { + sock_dbg(f); + dbg_puts(": "); + dbg_putx(p->proto); + dbg_puts(": bad hello protocol\n"); + } +#endif + return 0; + } + f->mode = p->proto; + f->pstate = SOCK_MIDI; + sock_midiattach(f, p->proto); + return 1; + } + if (f->opt->mode & MODE_RECMASK) + f->wpar = f->opt->wpar; + if (f->opt->mode & MODE_PLAY) + f->rpar = f->opt->rpar; + if (f->opt->mmc) + f->xrun = AMSG_SYNC; + f->bufsz = f->dev->bufsz; + f->round = f->dev->round; + if ((p->proto & ~(AMSG_PLAY | AMSG_REC)) != 0 || + (p->proto & (AMSG_PLAY | AMSG_REC)) == 0) { +#ifdef DEBUG + if (debug_level >= 1) { + sock_dbg(f); + dbg_puts(": "); + dbg_putx(p->proto); + dbg_puts(": unsupported hello protocol\n"); + } +#endif + return 0; + } + f->mode = 0; + if (p->proto & AMSG_PLAY) { + if (!APROC_OK(f->dev->mix) || !(f->opt->mode & MODE_PLAY)) { +#ifdef DEBUG + if (debug_level >= 1) { + sock_dbg(f); + dbg_puts(": playback not available\n"); + } +#endif + return 0; + } + f->mode |= AMSG_PLAY; + } + if (p->proto & AMSG_REC) { + if (!(APROC_OK(f->dev->sub) && (f->opt->mode & MODE_REC)) && + !(APROC_OK(f->dev->submon) && (f->opt->mode & MODE_MON))) { +#ifdef DEBUG + if (debug_level >= 1) { + sock_dbg(f); + dbg_puts(": recording not available\n"); + } +#endif + return 0; + } + f->mode |= (f->opt->mode & MODE_MON) ? AMSG_MON : AMSG_REC; + } + if (APROC_OK(f->dev->midi)) { + f->slot = ctl_slotnew(f->dev->midi, + p->who, &ctl_sockops, f, + f->opt->mmc); + if (f->slot < 0) { +#ifdef DEBUG + if (debug_level >= 1) { + sock_dbg(f); + dbg_puts(": out of mixer slots\n"); + } +#endif + return 0; + } + } + f->pstate = SOCK_INIT; + return 1; +} + +/* + * Execute message in f->rmsg and change the state accordingly; return 1 + * on success, and 0 on failure, in which case the socket is destroyed. + */ +int +sock_execmsg(struct sock *f) +{ + struct amsg *m = &f->rmsg; + struct abuf *obuf; + + switch (m->cmd) { + case AMSG_DATA: +#ifdef DEBUG + if (debug_level >= 4) { + sock_dbg(f); + dbg_puts(": DATA message\n"); + } +#endif + if (f->pstate != SOCK_RUN && f->pstate != SOCK_START && + f->pstate != SOCK_READY) { +#ifdef DEBUG + if (debug_level >= 1) { + sock_dbg(f); + dbg_puts(": DATA, bad state\n"); + } +#endif + aproc_del(f->pipe.file.rproc); + return 0; + } + if (!(f->mode & AMSG_PLAY)) { +#ifdef DEBUG + if (debug_level >= 1) { + sock_dbg(f); + dbg_puts(": DATA not allowed in record-only mode\n"); + } +#endif + aproc_del(f->pipe.file.rproc); + return 0; + } + obuf = LIST_FIRST(&f->pipe.file.rproc->outs); + if (f->pstate == SOCK_START && !ABUF_WOK(obuf)) { +#ifdef DEBUG + if (debug_level >= 1) { + sock_dbg(f); + dbg_puts(": DATA client violates flow control\n"); + } +#endif + aproc_del(f->pipe.file.rproc); + return 0; + } + if (m->u.data.size % obuf->bpf != 0) { +#ifdef DEBUG + if (debug_level >= 1) { + sock_dbg(f); + dbg_puts(": unaligned data chunk\n"); + } +#endif + aproc_del(f->pipe.file.rproc); + return 0; + } + f->rstate = SOCK_RDATA; + f->rtodo = m->u.data.size / obuf->bpf; +#ifdef DEBUG + if (f->rtodo > f->rmax && debug_level >= 2) { + sock_dbg(f); + dbg_puts(": received past current position, rtodo = "); + dbg_putu(f->rtodo); + dbg_puts(", rmax = "); + dbg_putu(f->rmax); + dbg_puts("\n"); + aproc_del(f->pipe.file.rproc); + return 0; + } +#endif + f->rmax -= f->rtodo; + if (f->rtodo == 0) { +#ifdef DEBUG + if (debug_level >= 1) { + sock_dbg(f); + dbg_puts(": zero-length data chunk\n"); + } +#endif + aproc_del(f->pipe.file.rproc); + return 0; + } + break; + case AMSG_START: +#ifdef DEBUG + if (debug_level >= 3) { + sock_dbg(f); + dbg_puts(": START message\n"); + } +#endif + if (f->pstate != SOCK_INIT) { +#ifdef DEBUG + if (debug_level >= 1) { + sock_dbg(f); + dbg_puts(": START, bad state\n"); + } +#endif + aproc_del(f->pipe.file.rproc); + return 0; + } + sock_allocbuf(f); + f->rstate = SOCK_RMSG; + f->rtodo = sizeof(struct amsg); + break; + case AMSG_STOP: +#ifdef DEBUG + if (debug_level >= 3) { + sock_dbg(f); + dbg_puts(": STOP message\n"); + } +#endif + if (f->pstate != SOCK_RUN && + f->pstate != SOCK_START && f->pstate != SOCK_READY) { +#ifdef DEBUG + if (debug_level >= 1) { + sock_dbg(f); + dbg_puts(": STOP, bad state\n"); + } +#endif + aproc_del(f->pipe.file.rproc); + return 0; + /* + * XXX: device could have desappeared at this point, + * see how this is fixed in wav.c + */ + } + if ((f->pstate == SOCK_START || f->pstate == SOCK_READY) && + ctl_slotstart(f->dev->midi, f->slot)) + (void)sock_attach(f, 1); + if (f->wstate != SOCK_WDATA || f->wtodo == 0) + sock_freebuf(f); + else + f->pstate = SOCK_STOP; + AMSG_INIT(m); + m->cmd = AMSG_ACK; + f->rstate = SOCK_RRET; + f->rtodo = sizeof(struct amsg); + break; + case AMSG_SETPAR: +#ifdef DEBUG + if (debug_level >= 3) { + sock_dbg(f); + dbg_puts(": SETPAR message\n"); + } +#endif + if (f->pstate != SOCK_INIT) { +#ifdef DEBUG + if (debug_level >= 1) { + sock_dbg(f); + dbg_puts(": SETPAR, bad state\n"); + } +#endif + aproc_del(f->pipe.file.rproc); + return 0; + } + if (!sock_setpar(f)) { + aproc_del(f->pipe.file.rproc); + return 0; + } + f->rtodo = sizeof(struct amsg); + f->rstate = SOCK_RMSG; + break; + case AMSG_GETPAR: +#ifdef DEBUG + if (debug_level >= 3) { + sock_dbg(f); + dbg_puts(": GETPAR message\n"); + } +#endif + if (f->pstate != SOCK_INIT) { +#ifdef DEBUG + if (debug_level >= 1) { + sock_dbg(f); + dbg_puts(": GETPAR, bad state\n"); + } +#endif + aproc_del(f->pipe.file.rproc); + return 0; + } + AMSG_INIT(m); + m->cmd = AMSG_GETPAR; + m->u.par.legacy_mode = f->mode; + if (f->mode & AMSG_PLAY) { + m->u.par.bits = f->rpar.bits; + m->u.par.bps = f->rpar.bps; + m->u.par.sig = f->rpar.sig; + m->u.par.le = f->rpar.le; + m->u.par.msb = f->rpar.msb; + m->u.par.rate = f->rpar.rate; + m->u.par.pchan = f->rpar.cmax - f->rpar.cmin + 1; + } + if (f->mode & AMSG_RECMASK) { + m->u.par.bits = f->wpar.bits; + m->u.par.bps = f->wpar.bps; + m->u.par.sig = f->wpar.sig; + m->u.par.le = f->wpar.le; + m->u.par.msb = f->wpar.msb; + m->u.par.rate = f->wpar.rate; + m->u.par.rchan = f->wpar.cmax - f->wpar.cmin + 1; + } + m->u.par.appbufsz = f->bufsz; + m->u.par.bufsz = + f->bufsz + (f->dev->bufsz / f->dev->round) * f->round; + m->u.par.round = f->round; + f->rstate = SOCK_RRET; + f->rtodo = sizeof(struct amsg); + break; + case AMSG_GETCAP: +#ifdef DEBUG + if (debug_level >= 3) { + sock_dbg(f); + dbg_puts(": GETCAP message\n"); + } +#endif + if (f->pstate != SOCK_INIT) { +#ifdef DEBUG + if (debug_level >= 1) { + sock_dbg(f); + dbg_puts(": GETCAP, bad state\n"); + } +#endif + aproc_del(f->pipe.file.rproc); + return 0; + } + AMSG_INIT(m); + m->cmd = AMSG_GETCAP; + m->u.cap.rate = f->dev->rate; + m->u.cap.pchan = (f->opt->mode & MODE_PLAY) ? + (f->opt->rpar.cmax - f->opt->rpar.cmin + 1) : 0; + m->u.cap.rchan = (f->opt->mode & (MODE_PLAY | MODE_REC)) ? + (f->opt->wpar.cmax - f->opt->wpar.cmin + 1) : 0; + m->u.cap.bits = sizeof(short) * 8; + m->u.cap.bps = sizeof(short); + f->rstate = SOCK_RRET; + f->rtodo = sizeof(struct amsg); + break; + case AMSG_SETVOL: +#ifdef DEBUG + if (debug_level >= 3) { + sock_dbg(f); + dbg_puts(": SETVOL message\n"); + } +#endif + if (f->pstate != SOCK_RUN && f->pstate != SOCK_START && + f->pstate != SOCK_INIT && f->pstate != SOCK_READY) { +#ifdef DEBUG + if (debug_level >= 1) { + sock_dbg(f); + dbg_puts(": SETVOL, bad state\n"); + } +#endif + aproc_del(f->pipe.file.rproc); + return 0; + } + if (m->u.vol.ctl > MIDI_MAXCTL) { +#ifdef DEBUG + if (debug_level >= 1) { + sock_dbg(f); + dbg_puts(": SETVOL, volume out of range\n"); + } +#endif + aproc_del(f->pipe.file.rproc); + return 0; + } + sock_setvol(f, m->u.vol.ctl); + if (f->slot >= 0) + ctl_slotvol(f->dev->midi, f->slot, m->u.vol.ctl); + f->rtodo = sizeof(struct amsg); + f->rstate = SOCK_RMSG; + break; + case AMSG_HELLO: +#ifdef DEBUG + if (debug_level >= 3) { + sock_dbg(f); + dbg_puts(": HELLO message\n"); + } +#endif + if (f->pstate != SOCK_HELLO) { +#ifdef DEBUG + if (debug_level >= 1) { + sock_dbg(f); + dbg_puts(": HELLO, bad state\n"); + } +#endif + aproc_del(f->pipe.file.rproc); + return 0; + } + if (!sock_hello(f)) { + aproc_del(f->pipe.file.rproc); + return 0; + } + AMSG_INIT(m); + m->cmd = AMSG_ACK; + f->rstate = SOCK_RRET; + f->rtodo = sizeof(struct amsg); + break; + case AMSG_BYE: +#ifdef DEBUG + if (debug_level >= 3) { + sock_dbg(f); + dbg_puts(": BYE message\n"); + } +#endif + if (f->pstate != SOCK_INIT) { +#ifdef DEBUG + if (debug_level >= 1) { + sock_dbg(f); + dbg_puts(": BYE, bad state\n"); + } +#endif + } + aproc_del(f->pipe.file.rproc); + return 0; + default: +#ifdef DEBUG + if (debug_level >= 1) { + sock_dbg(f); + dbg_puts(": unknown command in message\n"); + } +#endif + aproc_del(f->pipe.file.rproc); + return 0; + } + if (f->rstate == SOCK_RRET) { + if (f->wstate != SOCK_WIDLE || + !sock_wmsg(f, &f->rmsg, &f->rtodo)) + return 0; +#ifdef DEBUG + if (debug_level >= 3) { + sock_dbg(f); + dbg_puts(": RRET done\n"); + } +#endif + if (f->pstate == SOCK_MIDI && (f->mode & AMSG_MIDIOUT)) { + f->rstate = SOCK_RDATA; + f->rtodo = 0; + } else { + f->rstate = SOCK_RMSG; + f->rtodo = sizeof(struct amsg); + } + } + return 1; +} + +/* + * Create a new data/pos message. + */ +int +sock_buildmsg(struct sock *f) +{ + struct aproc *p; + struct abuf *ibuf; + unsigned size, max; + + if (f->pstate == SOCK_MIDI) { +#ifdef DEBUG + if (debug_level >= 3) { + sock_dbg(f); + dbg_puts(": switching to MIDI mode\n"); + } +#endif + f->wstate = SOCK_WDATA; + f->wtodo = 0; + return 1; + } + + /* + * Send initial position + */ + if (f->startpending) { +#ifdef DEBUG + if (debug_level >= 4) { + sock_dbg(f); + dbg_puts(": building POS message, pos = "); + dbg_puti(f->startpos); + dbg_puts("\n"); + } +#endif + AMSG_INIT(&f->wmsg); + f->wmsg.cmd = AMSG_POS; + f->wmsg.u.ts.delta = f->startpos; + f->rmax += f->startpos; + f->wtodo = sizeof(struct amsg); + f->wstate = SOCK_WMSG; + f->startpending = 0; + return 1; + } + + /* + * If pos changed, build a MOVE message. + */ + if (f->tickpending) { +#ifdef DEBUG + if (debug_level >= 4) { + sock_dbg(f); + dbg_puts(": building MOVE message, delta = "); + dbg_puti(f->delta); + dbg_puts("\n"); + } +#endif + f->wmax += f->delta; + f->rmax += f->delta; + AMSG_INIT(&f->wmsg); + f->wmsg.cmd = AMSG_MOVE; + f->wmsg.u.ts.delta = f->delta; + f->wtodo = sizeof(struct amsg); + f->wstate = SOCK_WMSG; + f->delta = 0; + f->tickpending = 0; + return 1; + } + + /* + * if volume changed build a SETVOL message + */ + if (f->pstate >= SOCK_START && f->vol != f->lastvol) { +#ifdef DEBUG + if (debug_level >= 4) { + sock_dbg(f); + dbg_puts(": building SETVOL message, vol = "); + dbg_puti(f->vol); + dbg_puts("\n"); + } +#endif + AMSG_INIT(&f->wmsg); + f->wmsg.cmd = AMSG_SETVOL; + f->wmsg.u.vol.ctl = f->vol; + f->wtodo = sizeof(struct amsg); + f->wstate = SOCK_WMSG; + f->lastvol = f->vol; + return 1; + } + + /* + * If data available, build a DATA message. + */ + p = f->pipe.file.wproc; + ibuf = LIST_FIRST(&p->ins); + if (ibuf && ABUF_ROK(ibuf)) { +#ifdef DEBUG + if (ibuf->used > f->wmax && debug_level >= 3) { + sock_dbg(f); + dbg_puts(": attempt to send past current position: used = "); + dbg_putu(ibuf->used); + dbg_puts(" wmax = "); + dbg_putu(f->wmax); + dbg_puts("\n"); + } +#endif + max = AMSG_DATAMAX / ibuf->bpf; + size = ibuf->used; + if (size > f->walign) + size = f->walign; + if (size > f->wmax) + size = f->wmax; + if (size > max) + size = max; + if (size == 0) + return 0; + f->walign -= size; + f->wmax -= size; + if (f->walign == 0) + f->walign = f->round; + AMSG_INIT(&f->wmsg); + f->wmsg.cmd = AMSG_DATA; + f->wmsg.u.data.size = size * ibuf->bpf; + f->wtodo = sizeof(struct amsg); + f->wstate = SOCK_WMSG; + return 1; + } +#ifdef DEBUG + if (debug_level >= 4) { + sock_dbg(f); + dbg_puts(": no messages to build anymore, idling...\n"); + } +#endif + f->wstate = SOCK_WIDLE; + return 0; +} + +/* + * Read from the socket file descriptor, fill input buffer and update + * the state. Return 1 if at least one message or 1 data byte was + * processed, 0 if something blocked. + */ +int +sock_read(struct sock *f) +{ +#ifdef DEBUG + if (debug_level >= 4) { + sock_dbg(f); + dbg_puts(": reading "); + dbg_putu(f->rtodo); + dbg_puts(" todo\n"); + } +#endif + switch (f->rstate) { + 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; + if (f->pstate != SOCK_MIDI && f->rtodo == 0) { + f->rstate = SOCK_RMSG; + f->rtodo = sizeof(struct amsg); + } + /* + * XXX: sock_attach() may not start if there's not enough + * samples queues, if so ctl_slotstart() will trigger + * other streams, but this one won't start. + */ + if (f->pstate == SOCK_READY && ctl_slotstart(f->dev->midi, f->slot)) + (void)sock_attach(f, 0); + break; + case SOCK_RRET: +#ifdef DEBUG + if (debug_level >= 4) { + sock_dbg(f); + dbg_puts(": blocked by pending RRET message\n"); + } +#endif + return 0; + } + return 1; +} + +/* + * Process messages to return. + */ +int +sock_return(struct sock *f) +{ + struct aproc *rp; + + while (f->rstate == SOCK_RRET) { + if (!sock_wmsg(f, &f->rmsg, &f->rtodo)) + return 0; +#ifdef DEBUG + if (debug_level >= 4) { + sock_dbg(f); + dbg_puts(": sent RRET message\n"); + } +#endif + if (f->pstate == SOCK_MIDI && (f->mode & AMSG_MIDIOUT)) { + f->rstate = SOCK_RDATA; + f->rtodo = 0; + } else { + f->rstate = SOCK_RMSG; + f->rtodo = sizeof(struct amsg); + } + if (f->pipe.file.state & FILE_RINUSE) + break; + f->pipe.file.state |= FILE_RINUSE; + for (;;) { + /* + * in() may trigger rsock_done and destroy the + * wsock. + */ + rp = f->pipe.file.rproc; + if (!rp || !rp->ops->in(rp, NULL)) + break; + } + f->pipe.file.state &= ~FILE_RINUSE; + if (f->pipe.file.wproc == NULL) + return 0; + } + return 1; +} + +/* + * Write messages and data on the socket file descriptor. Return 1 if + * at least one message or one data byte was processed, 0 if something + * blocked. + */ +int +sock_write(struct sock *f) +{ +#ifdef DEBUG + if (debug_level >= 4) { + sock_dbg(f); + dbg_puts(": writing "); + dbg_putu(f->wtodo); + dbg_puts(" todo\n"); + } +#endif + switch (f->wstate) { + case SOCK_WMSG: + if (!sock_wmsg(f, &f->wmsg, &f->wtodo)) + return 0; + if (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->wtodo = f->wmsg.u.data.size / + LIST_FIRST(&f->pipe.file.wproc->ins)->bpf; + /* PASSTHROUGH */ + case SOCK_WDATA: + if (!sock_wdata(f)) + return 0; + if (f->pstate == SOCK_MIDI || f->wtodo > 0) + break; + f->wstate = SOCK_WIDLE; + f->wtodo = 0xdeadbeef; + if (f->pstate == SOCK_STOP) + sock_freebuf(f); + /* PASSTHROUGH */ + case SOCK_WIDLE: + if (!sock_return(f)) + return 0; + if (!sock_buildmsg(f)) + return 0; + break; +#ifdef DEBUG + default: + sock_dbg(f); + dbg_puts(": bad writing end state\n"); + dbg_panic(); +#endif + } + return 1; +} diff --git a/aucat/sock.h b/aucat/sock.h new file mode 100644 index 0000000..190b4a8 --- /dev/null +++ b/aucat/sock.h @@ -0,0 +1,75 @@ +/* $OpenBSD: sock.h,v 1.17 2010/06/05 12:45:48 ratchov Exp $ */ +/* + * Copyright (c) 2008 Alexandre Ratchov + * + * 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" +#include "aparams.h" +#include "pipe.h" + +struct opt; + +struct sock { + struct pipe pipe; + /* + * Socket and protocol specific stuff, mainly used + * to decode/encode messages in the stream. + */ + struct amsg rmsg, wmsg; /* messages being sent/received */ + unsigned wmax; /* max frames we're allowed to write */ + unsigned rmax; /* max frames we're allowed to read */ + unsigned rtodo; /* input bytes not read yet */ + unsigned wtodo; /* output bytes not written yet */ +#define SOCK_RDATA 0 /* data chunk being read */ +#define SOCK_RMSG 1 /* amsg query being processed */ +#define SOCK_RRET 2 /* amsg reply being returned */ + unsigned 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 wstate; /* state of the write-end FSM */ +#define SOCK_HELLO 0 /* waiting for HELLO message */ +#define SOCK_INIT 1 /* parameter negotiation */ +#define SOCK_START 2 /* filling play buffers */ +#define SOCK_READY 3 /* play buffers full */ +#define SOCK_RUN 4 /* attached to the mix / sub */ +#define SOCK_STOP 5 /* draining rec buffers */ +#define SOCK_MIDI 6 /* raw byte stream (midi) */ + unsigned pstate; /* one of the above */ + unsigned mode; /* a set of AMSG_PLAY, AMSG_REC */ + struct aparams rpar; /* read (ie play) parameters */ + struct aparams wpar; /* write (ie rec) parameters */ + int delta; /* pos. change to send */ + int startpos; /* initial pos. to send */ + int tickpending; /* delta waiting to be transmitted */ + int startpending; /* initial delta waiting to be transmitted */ + unsigned walign; /* align data packets to this */ + unsigned bufsz; /* total buffer size */ + unsigned round; /* block size */ + unsigned xrun; /* one of AMSG_IGNORE, ... */ + int vol; /* requested volume */ + int lastvol; /* last volume */ + int slot; /* mixer ctl slot number */ + struct opt *opt; /* "subdevice" definition */ + struct dev *dev; /* actual hardware device */ + char who[12]; /* label, mostly for debugging */ +}; + +struct sock *sock_new(struct fileops *, int fd); +extern struct fileops sock_ops; + +#endif /* !defined(SOCK_H) */ diff --git a/aucat/wav.c b/aucat/wav.c new file mode 100644 index 0000000..cb28bb2 --- /dev/null +++ b/aucat/wav.c @@ -0,0 +1,930 @@ +/* + * Copyright (c) 2008 Alexandre Ratchov + * + * 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 +#include +#include +#include + +#include "abuf.h" +#include "aproc.h" +#include "conf.h" +#include "dev.h" +#include "midi.h" +#include "wav.h" +#include "opt.h" +#ifdef DEBUG +#include "dbg.h" +#endif + +short wav_ulawmap[256] = { + -32124, -31100, -30076, -29052, -28028, -27004, -25980, -24956, + -23932, -22908, -21884, -20860, -19836, -18812, -17788, -16764, + -15996, -15484, -14972, -14460, -13948, -13436, -12924, -12412, + -11900, -11388, -10876, -10364, -9852, -9340, -8828, -8316, + -7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140, + -5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092, + -3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004, + -2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980, + -1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436, + -1372, -1308, -1244, -1180, -1116, -1052, -988, -924, + -876, -844, -812, -780, -748, -716, -684, -652, + -620, -588, -556, -524, -492, -460, -428, -396, + -372, -356, -340, -324, -308, -292, -276, -260, + -244, -228, -212, -196, -180, -164, -148, -132, + -120, -112, -104, -96, -88, -80, -72, -64, + -56, -48, -40, -32, -24, -16, -8, 0, + 32124, 31100, 30076, 29052, 28028, 27004, 25980, 24956, + 23932, 22908, 21884, 20860, 19836, 18812, 17788, 16764, + 15996, 15484, 14972, 14460, 13948, 13436, 12924, 12412, + 11900, 11388, 10876, 10364, 9852, 9340, 8828, 8316, + 7932, 7676, 7420, 7164, 6908, 6652, 6396, 6140, + 5884, 5628, 5372, 5116, 4860, 4604, 4348, 4092, + 3900, 3772, 3644, 3516, 3388, 3260, 3132, 3004, + 2876, 2748, 2620, 2492, 2364, 2236, 2108, 1980, + 1884, 1820, 1756, 1692, 1628, 1564, 1500, 1436, + 1372, 1308, 1244, 1180, 1116, 1052, 988, 924, + 876, 844, 812, 780, 748, 716, 684, 652, + 620, 588, 556, 524, 492, 460, 428, 396, + 372, 356, 340, 324, 308, 292, 276, 260, + 244, 228, 212, 196, 180, 164, 148, 132, + 120, 112, 104, 96, 88, 80, 72, 64, + 56, 48, 40, 32, 24, 16, 8, 0 +}; + +short wav_alawmap[256] = { + -5504, -5248, -6016, -5760, -4480, -4224, -4992, -4736, + -7552, -7296, -8064, -7808, -6528, -6272, -7040, -6784, + -2752, -2624, -3008, -2880, -2240, -2112, -2496, -2368, + -3776, -3648, -4032, -3904, -3264, -3136, -3520, -3392, + -22016, -20992, -24064, -23040, -17920, -16896, -19968, -18944, + -30208, -29184, -32256, -31232, -26112, -25088, -28160, -27136, + -11008, -10496, -12032, -11520, -8960, -8448, -9984, -9472, + -15104, -14592, -16128, -15616, -13056, -12544, -14080, -13568, + -344, -328, -376, -360, -280, -264, -312, -296, + -472, -456, -504, -488, -408, -392, -440, -424, + -88, -72, -120, -104, -24, -8, -56, -40, + -216, -200, -248, -232, -152, -136, -184, -168, + -1376, -1312, -1504, -1440, -1120, -1056, -1248, -1184, + -1888, -1824, -2016, -1952, -1632, -1568, -1760, -1696, + -688, -656, -752, -720, -560, -528, -624, -592, + -944, -912, -1008, -976, -816, -784, -880, -848, + 5504, 5248, 6016, 5760, 4480, 4224, 4992, 4736, + 7552, 7296, 8064, 7808, 6528, 6272, 7040, 6784, + 2752, 2624, 3008, 2880, 2240, 2112, 2496, 2368, + 3776, 3648, 4032, 3904, 3264, 3136, 3520, 3392, + 22016, 20992, 24064, 23040, 17920, 16896, 19968, 18944, + 30208, 29184, 32256, 31232, 26112, 25088, 28160, 27136, + 11008, 10496, 12032, 11520, 8960, 8448, 9984, 9472, + 15104, 14592, 16128, 15616, 13056, 12544, 14080, 13568, + 344, 328, 376, 360, 280, 264, 312, 296, + 472, 456, 504, 488, 408, 392, 440, 424, + 88, 72, 120, 104, 24, 8, 56, 40, + 216, 200, 248, 232, 152, 136, 184, 168, + 1376, 1312, 1504, 1440, 1120, 1056, 1248, 1184, + 1888, 1824, 2016, 1952, 1632, 1568, 1760, 1696, + 688, 656, 752, 720, 560, 528, 624, 592, + 944, 912, 1008, 976, 816, 784, 880, 848 +}; + +/* + * Max data of a .wav file. The total file size must be smaller than + * 2^31, and we also have to leave some space for the headers (around 40 + * bytes). + */ +#define WAV_DATAMAX (0x7fff0000) + +struct fileops wav_ops = { + "wav", + sizeof(struct wav), + wav_close, + wav_read, + wav_write, + NULL, /* start */ + NULL, /* stop */ + pipe_nfds, + pipe_pollfd, + pipe_revents +}; + +int rwav_in(struct aproc *, struct abuf *); +int rwav_out(struct aproc *, struct abuf *); +void rwav_eof(struct aproc *, struct abuf *); +void rwav_hup(struct aproc *, struct abuf *); +void rwav_done(struct aproc *); +struct aproc *rwav_new(struct file *); + +int wwav_in(struct aproc *, struct abuf *); +int wwav_out(struct aproc *, struct abuf *); +void wwav_eof(struct aproc *, struct abuf *); +void wwav_hup(struct aproc *, struct abuf *); +void wwav_done(struct aproc *); +struct aproc *wwav_new(struct file *); + +void wav_setvol(void *, unsigned); +void wav_startreq(void *); +void wav_stopreq(void *); +void wav_locreq(void *, unsigned); +void wav_quitreq(void *); + +struct ctl_ops ctl_wavops = { + wav_setvol, + wav_startreq, + wav_stopreq, + wav_locreq, + wav_quitreq +}; + +struct aproc_ops rwav_ops = { + "rwav", + rwav_in, + rwav_out, + rfile_eof, + rfile_hup, + NULL, /* newin */ + NULL, /* newout */ + NULL, /* ipos */ + NULL, /* opos */ + rwav_done +}; + +struct aproc_ops wwav_ops = { + "wwav", + wwav_in, + wwav_out, + wfile_eof, + wfile_hup, + NULL, /* newin */ + NULL, /* newout */ + NULL, /* ipos */ + NULL, /* opos */ + wwav_done +}; + +#ifdef DEBUG +/* + * print the given wav structure + */ +void +wav_dbg(struct wav *f) +{ + static char *pstates[] = { "ini", "sta", "rdy", "run", "fai" }; + struct aproc *midi = f->dev ? f->dev->midi : NULL; + + dbg_puts("wav("); + if (f->slot >= 0 && APROC_OK(midi)) { + dbg_puts(midi->u.ctl.slot[f->slot].name); + dbg_putu(midi->u.ctl.slot[f->slot].unit); + } else + dbg_puts(f->pipe.file.name); + dbg_puts(")/"); + dbg_puts(pstates[f->pstate]); +} +#endif + +/* + * convert ``count'' samples using the given char->short map + */ +void +wav_conv(unsigned char *data, unsigned count, short *map) +{ + unsigned i; + unsigned char *iptr; + short *optr; + + iptr = data + count; + optr = (short *)data + count; + for (i = count; i > 0; i--) { + --optr; + --iptr; + *optr = map[*iptr]; + } +} + +/* + * read method of the file structure + */ +unsigned +wav_read(struct file *file, unsigned char *data, unsigned count) +{ + struct wav *f = (struct wav *)file; + unsigned n; + + if (f->map) + count /= sizeof(short); + if (f->rbytes >= 0 && count > f->rbytes) { + count = f->rbytes; /* file->rbytes fits in count */ + if (count == 0) { +#ifdef DEBUG + if (debug_level >= 3) { + wav_dbg(f); + dbg_puts(": read complete\n"); + } +#endif + if (!f->mmc) + file_eof(&f->pipe.file); + return 0; + } + } + n = pipe_read(file, data, count); + if (n == 0) + return 0; + if (f->rbytes >= 0) + f->rbytes -= n; + if (f->map) { + wav_conv(data, n, f->map); + n *= sizeof(short); + } + return n; +} + +/* + * write method of the file structure + */ +unsigned +wav_write(struct file *file, unsigned char *data, unsigned count) +{ + struct wav *f = (struct wav *)file; + unsigned n; + + if (f->wbytes >= 0 && count > f->wbytes) { + count = f->wbytes; /* wbytes fits in count */ + if (count == 0) { +#ifdef DEBUG + if (debug_level >= 3) { + wav_dbg(f); + dbg_puts(": write complete\n"); + } +#endif + file_hup(&f->pipe.file); + return 0; + } + } + n = pipe_write(file, data, count); + if (f->wbytes >= 0) + f->wbytes -= n; + f->endpos += n; + return n; +} + +/* + * close method of the file structure + */ +void +wav_close(struct file *file) +{ + struct wav *f = (struct wav *)file; + + if (f->mode & MODE_RECMASK) { + pipe_trunc(&f->pipe.file, f->endpos); + if (f->hdr == HDR_WAV) { + wav_writehdr(f->pipe.fd, + &f->hpar, + &f->startpos, + f->endpos - f->startpos); + } + } + pipe_close(file); + if (f->dev) { + dev_unref(f->dev); + f->dev = NULL; + } +} + +/* + * attach play (rec) abuf structure to the device and + * switch to the ``RUN'' state; the play abug must not be empty + */ +int +wav_attach(struct wav *f, int force) +{ + struct abuf *rbuf = NULL, *wbuf = NULL; + struct dev *d = f->dev; + + if (f->mode & MODE_PLAY) + rbuf = LIST_FIRST(&f->pipe.file.rproc->outs); + if (f->mode & MODE_RECMASK) + wbuf = LIST_FIRST(&f->pipe.file.wproc->ins); + f->pstate = WAV_RUN; +#ifdef DEBUG + if (debug_level >= 3) { + wav_dbg(f); + dbg_puts(": attaching\n"); + } +#endif + + /* + * start the device (dev_getpos() and dev_attach() must + * be called on a started device + */ + dev_wakeup(d); + + dev_attach(d, f->pipe.file.name, f->mode, + rbuf, &f->hpar, f->join ? d->opar.cmax - d->opar.cmin + 1 : 0, + wbuf, &f->hpar, f->join ? d->ipar.cmax - d->ipar.cmin + 1 : 0, + f->xrun, f->maxweight); + if (f->mode & MODE_PLAY) + dev_setvol(d, rbuf, MIDI_TO_ADATA(f->vol)); + return 1; +} + +/* + * allocate the play (rec) abuf structure; if this is a + * file to record, then attach it to the device + * + * XXX: buffer size should be larger than dev_bufsz, because + * in non-server mode we don't prime play buffers with + * silence + */ +void +wav_allocbuf(struct wav *f) +{ + struct abuf *buf; + struct dev *d = f->dev; + unsigned nfr; + + f->pstate = WAV_START; + if (f->mode & MODE_PLAY) { + nfr = 2 * d->bufsz * f->hpar.rate / d->rate; + buf = abuf_new(nfr, &f->hpar); + aproc_setout(f->pipe.file.rproc, buf); + abuf_fill(buf); + if (!ABUF_WOK(buf) || (f->pipe.file.state & FILE_EOF)) + f->pstate = WAV_READY; + } + if (f->mode & MODE_RECMASK) { + nfr = 2 * d->bufsz * f->hpar.rate / d->rate; + buf = abuf_new(nfr, &f->hpar); + aproc_setin(f->pipe.file.wproc, buf); + f->pstate = WAV_READY; + } +#ifdef DEBUG + if (debug_level >= 3) { + wav_dbg(f); + dbg_puts(": allocating buffers\n"); + } +#endif + if (f->pstate == WAV_READY && ctl_slotstart(d->midi, f->slot)) + (void)wav_attach(f, 0); +} + +/* + * free abuf structure and switch to the ``INIT'' state + */ +void +wav_freebuf(struct wav *f) +{ + struct abuf *rbuf = NULL, *wbuf = NULL; + + if (f->mode & MODE_PLAY) + rbuf = LIST_FIRST(&f->pipe.file.rproc->outs); + if (f->mode & MODE_RECMASK) + wbuf = LIST_FIRST(&f->pipe.file.wproc->ins); + f->pstate = WAV_INIT; +#ifdef DEBUG + if (debug_level >= 3) { + wav_dbg(f); + dbg_puts(": freeing buffers\n"); + } +#endif + if (rbuf || wbuf) + ctl_slotstop(f->dev->midi, f->slot); + if (rbuf) + abuf_eof(rbuf); + if (wbuf) + abuf_hup(wbuf); +} + +/* + * switch to the ``INIT'' state performing + * necessary actions to reach it + */ +void +wav_reset(struct wav *f) +{ + switch (f->pstate) { + case WAV_START: + case WAV_READY: + if (ctl_slotstart(f->dev->midi, f->slot)) + (void)wav_attach(f, 1); + /* PASSTHROUGH */ + case WAV_RUN: + wav_freebuf(f); + f->pstate = WAV_INIT; + /* PASSTHROUGH */ + case WAV_INIT: + case WAV_FAILED: + /* nothing yet */ + break; + } +} + +/* + * terminate the wav reader/writer + */ +void +wav_exit(struct wav *f) +{ + /* XXX: call file_close() ? */ + if (f->mode & MODE_PLAY) { + aproc_del(f->pipe.file.rproc); + } else if (f->mode & MODE_RECMASK) { + aproc_del(f->pipe.file.wproc); + } +} + +/* + * seek to f->mmcpos and prepare to start, close + * the file on error. + */ +int +wav_seekmmc(struct wav *f) +{ + /* + * don't go beyond the end-of-file, if so + * put it in INIT state so it dosn't start + */ + if (f->mmcpos > f->endpos) { + wav_reset(f); + f->pstate = WAV_FAILED; + /* + * don't make other stream wait for us + */ + if (f->slot >= 0) + ctl_slotstart(f->dev->midi, f->slot); + return 0; + } + if (!pipe_seek(&f->pipe.file, f->mmcpos)) { + wav_exit(f); + return 0; + } + if (f->hdr == HDR_WAV) + f->wbytes = WAV_DATAMAX - f->mmcpos; + f->rbytes = f->endpos - f->mmcpos; + wav_reset(f); + wav_allocbuf(f); + return 1; +} + +/* + * read samples from the file and possibly start it + */ +int +wav_rdata(struct wav *f) +{ + struct aproc *p; + struct abuf *obuf; + + p = f->pipe.file.rproc; + obuf = LIST_FIRST(&p->outs); + if (obuf == NULL) + return 0; + if (!ABUF_WOK(obuf) || !(f->pipe.file.state & FILE_ROK)) + return 0; + if (!rfile_do(p, obuf->len, NULL)) + return 0; + switch (f->pstate) { + case WAV_START: + if (!ABUF_WOK(obuf) || (f->pipe.file.state & FILE_EOF)) + f->pstate = WAV_READY; + /* PASSTHROUGH */ + case WAV_READY: + if (ctl_slotstart(f->dev->midi, f->slot)) + (void)wav_attach(f, 0); + break; +#ifdef DEBUG + case WAV_RUN: + break; + default: + wav_dbg(f); + dbg_puts(": bad state\n"); + dbg_panic(); +#endif + } + if (f->rbytes == 0 && f->mmc) { +#ifdef DEBUG + if (debug_level >= 3) { + wav_dbg(f); + dbg_puts(": trying to restart\n"); + } +#endif + if (!wav_seekmmc(f)) + return 0; + } + return 1; +} + +int +wav_wdata(struct wav *f) +{ + struct aproc *p; + struct abuf *ibuf; + + if (!(f->pipe.file.state & FILE_WOK)) + return 0; + p = f->pipe.file.wproc; + ibuf = LIST_FIRST(&p->ins); + if (ibuf == NULL) + return 0; + if (!ABUF_ROK(ibuf)) + return 0; + if (!wfile_do(p, ibuf->len, NULL)) + return 0; + return 1; +} + +/* + * callback to set the volume, invoked by the MIDI control code + */ +void +wav_setvol(void *arg, unsigned vol) +{ + struct wav *f = (struct wav *)arg; + struct abuf *rbuf; + + f->vol = vol; + if ((f->mode & MODE_PLAY) && f->pstate == WAV_RUN) { + rbuf = LIST_FIRST(&f->pipe.file.rproc->outs); + dev_setvol(f->dev, rbuf, MIDI_TO_ADATA(vol)); + } +} + +/* + * callback to start the stream, invoked by the MIDI control code + */ +void +wav_startreq(void *arg) +{ + struct wav *f = (struct wav *)arg; + + switch (f->pstate) { + case WAV_FAILED: +#ifdef DEBUG + if (debug_level >= 2) { + wav_dbg(f); + dbg_puts(": skipped (failed to seek)\n"); + } +#endif + return; + case WAV_READY: + if (f->mode & MODE_RECMASK) + f->endpos = f->startpos; + (void)wav_attach(f, 0); + break; +#ifdef DEBUG + default: + wav_dbg(f); + dbg_puts(": not in READY state\n"); + dbg_panic(); + break; +#endif + } +} + +/* + * callback to stop the stream, invoked by the MIDI control code + */ +void +wav_stopreq(void *arg) +{ + struct wav *f = (struct wav *)arg; + +#ifdef DEBUG + if (debug_level >= 2) { + wav_dbg(f); + dbg_puts(": stopping"); + if (f->pstate != WAV_FAILED && (f->mode & MODE_RECMASK)) { + dbg_puts(", "); + dbg_putu(f->endpos); + dbg_puts(" bytes recorded"); + } + dbg_puts("\n"); + } +#endif + if (!f->mmc) { + wav_exit(f); + return; + } + (void)wav_seekmmc(f); +} + +/* + * callback to relocate the stream, invoked by the MIDI control code + * on a stopped stream + */ +void +wav_locreq(void *arg, unsigned mmc) +{ + struct wav *f = (struct wav *)arg; + +#ifdef DEBUG + if (f->pstate == WAV_RUN) { + wav_dbg(f); + dbg_puts(": in RUN state\n"); + dbg_panic(); + } +#endif + f->mmcpos = f->startpos + + ((off_t)mmc * f->hpar.rate / MTC_SEC) * aparams_bpf(&f->hpar); + (void)wav_seekmmc(f); +} + +/* + * Callback invoked when slot is gone + */ +void +wav_quitreq(void *arg) +{ + struct wav *f = (struct wav *)arg; + +#ifdef DEBUG + if (debug_level >= 3) { + wav_dbg(f); + dbg_puts(": slot gone\n"); + } +#endif + if (f->pstate != WAV_RUN) + wav_exit(f); +} + +/* + * create a file reader in the ``INIT'' state + */ +struct wav * +wav_new_in(struct fileops *ops, + struct dev *dev, unsigned mode, char *name, unsigned hdr, + struct aparams *par, unsigned xrun, unsigned volctl, int tr, int join) +{ + int fd; + struct wav *f; + + if (name != NULL) { + fd = open(name, O_RDONLY | O_NONBLOCK, 0666); + if (fd < 0) { + perror(name); + return NULL; + } + } else { + name = "stdin"; + fd = STDIN_FILENO; + if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0) + perror(name); + } + f = (struct wav *)pipe_new(ops, fd, name); + if (f == NULL) { + close(fd); + return NULL; + } + if (!dev_ref(dev)) { + close(fd); + return NULL; + } + if (!(dev->mode & MODE_PLAY)) { +#ifdef DEBUG + dbg_puts(name); + dbg_puts(": device can't play\n"); +#endif + close(fd); + dev_unref(dev); + } + f->dev = dev; + if (hdr == HDR_WAV) { + if (!wav_readhdr(f->pipe.fd, par, &f->startpos, &f->rbytes, &f->map)) { + file_del((struct file *)f); + return NULL; + } + f->endpos = f->startpos + f->rbytes; + } else { + f->startpos = 0; + f->endpos = pipe_endpos(&f->pipe.file); + if (f->endpos > 0) { + if (!pipe_seek(&f->pipe.file, 0)) { + file_del((struct file *)f); + return NULL; + } + f->rbytes = f->endpos; + } else + f->rbytes = -1; + f->map = NULL; + } + f->mmc = tr; + f->join = join; + f->mode = mode; + f->hpar = *par; + f->hdr = 0; + f->xrun = xrun; + f->maxweight = MIDI_TO_ADATA(volctl); + f->slot = ctl_slotnew(f->dev->midi, "play", &ctl_wavops, f, 1); + rwav_new((struct file *)f); + wav_allocbuf(f); +#ifdef DEBUG + if (debug_level >= 2) { + dbg_puts(name); + dbg_puts(": playing "); + dbg_putu(f->startpos); + dbg_puts(".."); + dbg_putu(f->endpos); + dbg_puts(": playing "); + aparams_dbg(par); + if (f->mmc) + dbg_puts(", mmc"); + dbg_puts("\n"); + } +#endif + return f; +} + +/* + * create a file writer in the ``INIT'' state + */ +struct wav * +wav_new_out(struct fileops *ops, + struct dev *dev, unsigned mode, char *name, unsigned hdr, + struct aparams *par, unsigned xrun, int tr, int join) +{ + int fd; + struct wav *f; + + if (name == NULL) { + name = "stdout"; + fd = STDOUT_FILENO; + if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0) + perror(name); + } else { + fd = open(name, + O_WRONLY | O_TRUNC | O_CREAT | O_NONBLOCK, 0666); + if (fd < 0) { + perror(name); + return NULL; + } + } + f = (struct wav *)pipe_new(ops, fd, name); + if (f == NULL) { + close(fd); + return NULL; + } + if (!dev_ref(dev)) { + close(fd); + return NULL; + } + if (!(dev->mode & MODE_RECMASK)) { +#ifdef DEBUG + dbg_puts(name); + dbg_puts(": device can't record\n"); +#endif + close(fd); + dev_unref(dev); + } + f->dev = dev; + if (hdr == HDR_WAV) { + par->le = 1; + par->sig = (par->bits <= 8) ? 0 : 1; + par->bps = (par->bits + 7) / 8; + if (!wav_writehdr(f->pipe.fd, par, &f->startpos, 0)) { + file_del((struct file *)f); + return NULL; + } + f->wbytes = WAV_DATAMAX; + f->endpos = f->startpos; + } else { + f->wbytes = -1; + f->startpos = f->endpos = 0; + } + f->mmc = tr; + f->join = join; + f->mode = mode; + f->hpar = *par; + f->hdr = hdr; + f->xrun = xrun; + f->slot = ctl_slotnew(f->dev->midi, "rec", &ctl_wavops, f, 1); + wwav_new((struct file *)f); + wav_allocbuf(f); +#ifdef DEBUG + if (debug_level >= 2) { + dbg_puts(name); + dbg_puts(": recording "); + aparams_dbg(par); + dbg_puts("\n"); + } +#endif + return f; +} + +void +rwav_done(struct aproc *p) +{ + struct wav *f = (struct wav *)p->u.io.file; + + if (f->slot >= 0) + ctl_slotdel(f->dev->midi, f->slot); + f->slot = -1; + rfile_done(p); +} + +int +rwav_in(struct aproc *p, struct abuf *ibuf_dummy) +{ + struct wav *f = (struct wav *)p->u.io.file; + struct abuf *obuf; + + if (!wav_rdata(f)) + return 0; + obuf = LIST_FIRST(&p->outs); + if (obuf && f->pstate >= WAV_RUN) { + if (!abuf_flush(obuf)) + return 0; + } + return 1; +} + +int +rwav_out(struct aproc *p, struct abuf *obuf) +{ + struct wav *f = (struct wav *)p->u.io.file; + + if (f->pipe.file.state & FILE_RINUSE) + return 0; + for (;;) { + if (!wav_rdata(f)) + return 0; + } + return 1; +} + +struct aproc * +rwav_new(struct file *f) +{ + struct aproc *p; + + p = aproc_new(&rwav_ops, f->name); + p->u.io.file = f; + p->u.io.partial = 0;; + f->rproc = p; + return p; +} + +void +wwav_done(struct aproc *p) +{ + struct wav *f = (struct wav *)p->u.io.file; + + if (f->slot >= 0) + ctl_slotdel(f->dev->midi, f->slot); + f->slot = -1; + wfile_done(p); +} + +int +wwav_in(struct aproc *p, struct abuf *ibuf) +{ + struct wav *f = (struct wav *)p->u.io.file; + + if (f->pipe.file.state & FILE_WINUSE) + return 0; + for (;;) { + if (!wav_wdata(f)) + return 0; + } + return 1; +} + +int +wwav_out(struct aproc *p, struct abuf *obuf_dummy) +{ + struct abuf *ibuf = LIST_FIRST(&p->ins); + struct wav *f = (struct wav *)p->u.io.file; + + if (ibuf && f->pstate == WAV_RUN) { + if (!abuf_fill(ibuf)) + return 0; + } + if (!wav_wdata(f)) + return 0; + return 1; +} + +struct aproc * +wwav_new(struct file *f) +{ + struct aproc *p; + + p = aproc_new(&wwav_ops, f->name); + p->u.io.file = f; + p->u.io.partial = 0;; + f->wproc = p; + return p; +} + diff --git a/aucat/wav.h b/aucat/wav.h new file mode 100644 index 0000000..1734f2e --- /dev/null +++ b/aucat/wav.h @@ -0,0 +1,70 @@ +/* $OpenBSD: wav.h,v 1.11 2010/07/31 08:48:01 ratchov Exp $ */ +/* + * Copyright (c) 2008 Alexandre Ratchov + * + * 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 WAV_H +#define WAV_H + +#include + +#include "aparams.h" +#include "pipe.h" + +struct wav { + struct pipe pipe; +#define HDR_AUTO 0 /* guess by looking at the file name */ +#define HDR_RAW 1 /* no headers, ie openbsd native ;-) */ +#define HDR_WAV 2 /* microsoft riff wave */ + unsigned hdr; /* HDR_RAW or HDR_WAV */ + unsigned xrun; /* xrun policy */ + struct aparams hpar; /* parameters to write on the header */ + off_t rbytes; /* bytes to read, -1 if no limit */ + off_t wbytes; /* bytes to write, -1 if no limit */ + off_t startpos; /* beginning of the data chunk */ + off_t endpos; /* end of the data chunk */ + off_t mmcpos; /* play/rec start point set by MMC */ + short *map; /* mulaw/alaw -> s16 conversion table */ + int slot; /* mixer ctl slot number */ + int mmc; /* use MMC control */ + int join; /* join/expand channels */ + unsigned vol; /* current volume */ + unsigned maxweight; /* dynamic range when vol == 127 */ +#define WAV_INIT 0 /* not trying to do anything */ +#define WAV_START 1 /* buffer allocated */ +#define WAV_READY 2 /* buffer filled enough */ +#define WAV_RUN 3 /* buffer attached to device */ +#define WAV_FAILED 4 /* failed to seek */ + unsigned pstate; /* one of above */ + unsigned mode; /* bitmap of MODE_* */ + struct dev *dev; /* device playing or recording */ +}; + +extern struct fileops wav_ops; + +struct wav *wav_new_in(struct fileops *, struct dev *, + unsigned, char *, unsigned, struct aparams *, unsigned, unsigned, int, int); +struct wav *wav_new_out(struct fileops *, struct dev *, + unsigned, char *, unsigned, struct aparams *, unsigned, int, int); +unsigned wav_read(struct file *, unsigned char *, unsigned); +unsigned wav_write(struct file *, unsigned char *, unsigned); +void wav_close(struct file *); +int wav_readhdr(int, struct aparams *, off_t *, off_t *, short **); +int wav_writehdr(int, struct aparams *, off_t *, off_t); +void wav_conv(unsigned char *, unsigned, short *); + +extern short wav_ulawmap[256]; +extern short wav_alawmap[256]; + +#endif /* !defined(WAV_H) */ diff --git a/libsndio/aucat.c b/libsndio/aucat.c new file mode 100644 index 0000000..5147c1a --- /dev/null +++ b/libsndio/aucat.c @@ -0,0 +1,750 @@ +/* $OpenBSD: aucat.c,v 1.41 2010/06/05 16:00:52 ratchov Exp $ */ +/* + * Copyright (c) 2008 Alexandre Ratchov + * + * 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 +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "amsg.h" +#include "sndio_priv.h" + +struct aucat_hdl { + struct sio_hdl sio; + int fd; /* socket */ + struct amsg rmsg, wmsg; /* temporary messages */ + size_t wtodo, rtodo; /* bytes to complete the packet */ +#define STATE_IDLE 0 /* nothing to do */ +#define STATE_MSG 1 /* message being transferred */ +#define STATE_DATA 2 /* data being transferred */ + unsigned rstate, wstate; /* one of above */ + unsigned rbpf, wbpf; /* read and write bytes-per-frame */ + int maxwrite; /* latency constraint */ + int events; /* events the user requested */ + unsigned curvol, reqvol; /* current and requested volume */ + int delta; /* some of received deltas */ +}; + +static void aucat_close(struct sio_hdl *); +static int aucat_start(struct sio_hdl *); +static int aucat_stop(struct sio_hdl *); +static int aucat_setpar(struct sio_hdl *, struct sio_par *); +static int aucat_getpar(struct sio_hdl *, struct sio_par *); +static int aucat_getcap(struct sio_hdl *, struct sio_cap *); +static size_t aucat_read(struct sio_hdl *, void *, size_t); +static size_t aucat_write(struct sio_hdl *, const void *, size_t); +static int aucat_pollfd(struct sio_hdl *, struct pollfd *, int); +static int aucat_revents(struct sio_hdl *, struct pollfd *); +static int aucat_setvol(struct sio_hdl *, unsigned); +static void aucat_getvol(struct sio_hdl *); + +static struct sio_ops aucat_ops = { + aucat_close, + aucat_setpar, + aucat_getpar, + aucat_getcap, + aucat_write, + aucat_read, + aucat_start, + aucat_stop, + aucat_pollfd, + aucat_revents, + aucat_setvol, + aucat_getvol +}; + +/* + * read a message, return 0 if blocked + */ +static int +aucat_rmsg(struct aucat_hdl *hdl) +{ + ssize_t n; + unsigned char *data; + + while (hdl->rtodo > 0) { + data = (unsigned char *)&hdl->rmsg; + data += sizeof(struct amsg) - hdl->rtodo; + while ((n = read(hdl->fd, data, hdl->rtodo)) < 0) { + if (errno == EINTR) + continue; + if (errno != EAGAIN) { + hdl->sio.eof = 1; + DPERROR("aucat_rmsg: read"); + } + return 0; + } + if (n == 0) { + DPRINTF("aucat_rmsg: eof\n"); + hdl->sio.eof = 1; + return 0; + } + hdl->rtodo -= n; + } + return 1; +} + +/* + * write a message, return 0 if blocked + */ +static int +aucat_wmsg(struct aucat_hdl *hdl) +{ + ssize_t n; + unsigned char *data; + + while (hdl->wtodo > 0) { + data = (unsigned char *)&hdl->wmsg; + data += sizeof(struct amsg) - hdl->wtodo; + while ((n = write(hdl->fd, data, hdl->wtodo)) < 0) { + if (errno == EINTR) + continue; + if (errno != EAGAIN) { + hdl->sio.eof = 1; + DPERROR("aucat_wmsg: write"); + } + return 0; + } + hdl->wtodo -= n; + } + return 1; +} + +/* + * execute the next message, return 0 if blocked + */ +static int +aucat_runmsg(struct aucat_hdl *hdl) +{ + if (!aucat_rmsg(hdl)) + return 0; + switch (hdl->rmsg.cmd) { + case AMSG_DATA: + if (hdl->rmsg.u.data.size == 0 || + hdl->rmsg.u.data.size % hdl->rbpf) { + DPRINTF("aucat_runmsg: bad data message\n"); + hdl->sio.eof = 1; + return 0; + } + hdl->rstate = STATE_DATA; + hdl->rtodo = hdl->rmsg.u.data.size; + break; + case AMSG_POS: + hdl->maxwrite += hdl->rmsg.u.ts.delta * (int)hdl->wbpf; + DPRINTF("aucat: pos = %d, maxwrite = %d\n", + hdl->rmsg.u.ts.delta, hdl->maxwrite); + hdl->delta = hdl->rmsg.u.ts.delta; + hdl->rstate = STATE_MSG; + hdl->rtodo = sizeof(struct amsg); + break; + case AMSG_MOVE: + hdl->maxwrite += hdl->rmsg.u.ts.delta * hdl->wbpf; + hdl->delta += hdl->rmsg.u.ts.delta; + DPRINTF("aucat: move = %d, delta = %d, maxwrite = %d\n", + hdl->rmsg.u.ts.delta, hdl->delta, hdl->maxwrite); + if (hdl->delta >= 0) { + sio_onmove_cb(&hdl->sio, hdl->delta); + hdl->delta = 0; + } + hdl->rstate = STATE_MSG; + hdl->rtodo = sizeof(struct amsg); + break; + case AMSG_SETVOL: + hdl->curvol = hdl->reqvol = hdl->rmsg.u.vol.ctl; + sio_onvol_cb(&hdl->sio, hdl->curvol); + hdl->rstate = STATE_MSG; + hdl->rtodo = sizeof(struct amsg); + break; + case AMSG_GETPAR: + case AMSG_ACK: + hdl->rstate = STATE_IDLE; + hdl->rtodo = 0xdeadbeef; + break; + default: + DPRINTF("aucat_runmsg: unknown message\n"); + hdl->sio.eof = 1; + return 0; + } + return 1; +} + +struct sio_hdl * +sio_open_aucat(const char *str, unsigned mode, int nbio) +{ + extern char *__progname; + int s; + char unit[4], *sep, *opt; + struct aucat_hdl *hdl; + struct sockaddr_un ca; + socklen_t len = sizeof(struct sockaddr_un); + uid_t uid; + + sep = strchr(str, '.'); + if (sep == NULL) { + opt = "default"; + strlcpy(unit, str, sizeof(unit)); + } else { + opt = sep + 1; + if (sep - str >= sizeof(unit)) { + DPRINTF("sio_open_aucat: %s: too long\n", str); + return NULL; + } + strlcpy(unit, str, opt - str); + } + DPRINTF("sio_open_aucat: trying %s -> %s.%s\n", str, unit, opt); + uid = geteuid(); + if (strchr(str, '/') != NULL) + return NULL; + snprintf(ca.sun_path, sizeof(ca.sun_path), + "/tmp/aucat-%u/softaudio%s", uid, unit); + ca.sun_family = AF_UNIX; + + hdl = malloc(sizeof(struct aucat_hdl)); + if (hdl == NULL) + return NULL; + sio_create(&hdl->sio, &aucat_ops, mode, nbio); + + s = socket(AF_UNIX, SOCK_STREAM, 0); + if (s < 0) + goto bad_free; + while (connect(s, (struct sockaddr *)&ca, len) < 0) { + if (errno == EINTR) + continue; + DPERROR("sio_open_aucat: connect"); + /* try shared server */ + snprintf(ca.sun_path, sizeof(ca.sun_path), + "/tmp/aucat/softaudio%s", unit); + while (connect(s, (struct sockaddr *)&ca, len) < 0) { + if (errno == EINTR) + continue; + DPERROR("sio_open_aucat: connect"); + goto bad_connect; + } + break; + } + if (fcntl(s, F_SETFD, FD_CLOEXEC) < 0) { + DPERROR("FD_CLOEXEC"); + goto bad_connect; + } + hdl->fd = s; + hdl->rstate = STATE_IDLE; + hdl->rtodo = 0xdeadbeef; + hdl->wstate = STATE_IDLE; + hdl->wtodo = 0xdeadbeef; + hdl->curvol = SIO_MAXVOL; + hdl->reqvol = SIO_MAXVOL; + + /* + * say hello to server + */ + AMSG_INIT(&hdl->wmsg); + hdl->wmsg.cmd = AMSG_HELLO; + hdl->wmsg.u.hello.version = AMSG_VERSION; + hdl->wmsg.u.hello.proto = 0; + if (mode & SIO_PLAY) + hdl->wmsg.u.hello.proto |= AMSG_PLAY; + if (mode & SIO_REC) + hdl->wmsg.u.hello.proto |= AMSG_REC; + strlcpy(hdl->wmsg.u.hello.who, __progname, + sizeof(hdl->wmsg.u.hello.who)); + strlcpy(hdl->wmsg.u.hello.opt, opt, + sizeof(hdl->wmsg.u.hello.opt)); + hdl->wtodo = sizeof(struct amsg); + if (!aucat_wmsg(hdl)) + goto bad_connect; + hdl->rtodo = sizeof(struct amsg); + if (!aucat_rmsg(hdl)) { + DPRINTF("sio_open_aucat: mode refused\n"); + goto bad_connect; + } + if (hdl->rmsg.cmd != AMSG_ACK) { + DPRINTF("sio_open_aucat: protocol err\n"); + goto bad_connect; + } + return (struct sio_hdl *)hdl; + bad_connect: + while (close(s) < 0 && errno == EINTR) + ; /* retry */ + bad_free: + free(hdl); + return NULL; +} + +static void +aucat_close(struct sio_hdl *sh) +{ + struct aucat_hdl *hdl = (struct aucat_hdl *)sh; + char dummy[1]; + + if (!hdl->sio.eof && hdl->sio.started) + (void)aucat_stop(&hdl->sio); + if (!hdl->sio.eof) { + AMSG_INIT(&hdl->wmsg); + hdl->wmsg.cmd = AMSG_BYE; + hdl->wtodo = sizeof(struct amsg); + if (!aucat_wmsg(hdl)) + goto bad_close; + while (read(hdl->fd, dummy, 1) < 0 && errno == EINTR) + ; /* nothing */ + } + bad_close: + while (close(hdl->fd) < 0 && errno == EINTR) + ; /* nothing */ + free(hdl); +} + +static int +aucat_start(struct sio_hdl *sh) +{ + struct aucat_hdl *hdl = (struct aucat_hdl *)sh; + struct sio_par par; + + /* + * save bpf + */ + if (!sio_getpar(&hdl->sio, &par)) + return 0; + hdl->wbpf = par.bps * par.pchan; + hdl->rbpf = par.bps * par.rchan; + hdl->maxwrite = hdl->wbpf * par.bufsz; + hdl->delta = 0; + DPRINTF("aucat: start, maxwrite = %d\n", hdl->maxwrite); + + AMSG_INIT(&hdl->wmsg); + hdl->wmsg.cmd = AMSG_START; + hdl->wtodo = sizeof(struct amsg); + if (!aucat_wmsg(hdl)) + return 0; + hdl->rstate = STATE_MSG; + hdl->rtodo = sizeof(struct amsg); + if (fcntl(hdl->fd, F_SETFL, O_NONBLOCK) < 0) { + DPERROR("aucat_start: fcntl(0)"); + hdl->sio.eof = 1; + return 0; + } + return 1; +} + +static int +aucat_stop(struct sio_hdl *sh) +{ +#define ZERO_MAX 0x400 + static unsigned char zero[ZERO_MAX]; + struct aucat_hdl *hdl = (struct aucat_hdl *)sh; + unsigned n, count; + + if (fcntl(hdl->fd, F_SETFL, 0) < 0) { + DPERROR("aucat_stop: fcntl(0)"); + hdl->sio.eof = 1; + return 0; + } + + /* + * complete message or data block in progress + */ + if (hdl->wstate == STATE_MSG) { + if (!aucat_wmsg(hdl)) + return 0; + if (hdl->wmsg.cmd == AMSG_DATA) { + hdl->wstate = STATE_DATA; + hdl->wtodo = hdl->wmsg.u.data.size; + } else + hdl->wstate = STATE_IDLE; + } + if (hdl->wstate == STATE_DATA) { + hdl->maxwrite = hdl->wtodo; + while (hdl->wstate != STATE_IDLE) { + count = hdl->wtodo; + if (count > ZERO_MAX) + count = ZERO_MAX; + n = aucat_write(&hdl->sio, zero, count); + if (n == 0) + return 0; + } + } + + /* + * send stop message + */ + AMSG_INIT(&hdl->wmsg); + hdl->wmsg.cmd = AMSG_STOP; + hdl->wtodo = sizeof(struct amsg); + if (!aucat_wmsg(hdl)) + return 0; + if (hdl->rstate == STATE_IDLE) { + hdl->rstate = STATE_MSG; + hdl->rtodo = sizeof(struct amsg); + } + + /* + * wait for the STOP ACK + */ + while (hdl->rstate != STATE_IDLE) { + switch (hdl->rstate) { + case STATE_MSG: + if (!aucat_runmsg(hdl)) + return 0; + break; + case STATE_DATA: + if (!aucat_read(&hdl->sio, zero, ZERO_MAX)) + return 0; + break; + } + } + return 1; +} + +static int +aucat_setpar(struct sio_hdl *sh, struct sio_par *par) +{ + struct aucat_hdl *hdl = (struct aucat_hdl *)sh; + + AMSG_INIT(&hdl->wmsg); + hdl->wmsg.cmd = AMSG_SETPAR; + hdl->wmsg.u.par.bits = par->bits; + hdl->wmsg.u.par.bps = par->bps; + hdl->wmsg.u.par.sig = par->sig; + hdl->wmsg.u.par.le = par->le; + hdl->wmsg.u.par.msb = par->msb; + hdl->wmsg.u.par.rate = par->rate; + hdl->wmsg.u.par.appbufsz = par->appbufsz; + hdl->wmsg.u.par.xrun = par->xrun; + if (hdl->sio.mode & SIO_REC) + hdl->wmsg.u.par.rchan = par->rchan; + if (hdl->sio.mode & SIO_PLAY) + hdl->wmsg.u.par.pchan = par->pchan; + hdl->wtodo = sizeof(struct amsg); + if (!aucat_wmsg(hdl)) + return 0; + return 1; +} + +static int +aucat_getpar(struct sio_hdl *sh, struct sio_par *par) +{ + struct aucat_hdl *hdl = (struct aucat_hdl *)sh; + + AMSG_INIT(&hdl->wmsg); + hdl->wmsg.cmd = AMSG_GETPAR; + hdl->wtodo = sizeof(struct amsg); + if (!aucat_wmsg(hdl)) + return 0; + hdl->rtodo = sizeof(struct amsg); + if (!aucat_rmsg(hdl)) + return 0; + if (hdl->rmsg.cmd != AMSG_GETPAR) { + DPRINTF("aucat_getpar: protocol err\n"); + hdl->sio.eof = 1; + return 0; + } + par->bits = hdl->rmsg.u.par.bits; + par->bps = hdl->rmsg.u.par.bps; + par->sig = hdl->rmsg.u.par.sig; + par->le = hdl->rmsg.u.par.le; + par->msb = hdl->rmsg.u.par.msb; + par->rate = hdl->rmsg.u.par.rate; + par->bufsz = hdl->rmsg.u.par.bufsz; + par->appbufsz = hdl->rmsg.u.par.appbufsz; + par->xrun = hdl->rmsg.u.par.xrun; + par->round = hdl->rmsg.u.par.round; + if (hdl->sio.mode & SIO_PLAY) + par->pchan = hdl->rmsg.u.par.pchan; + if (hdl->sio.mode & SIO_REC) + par->rchan = hdl->rmsg.u.par.rchan; + return 1; +} + +static int +aucat_getcap(struct sio_hdl *sh, struct sio_cap *cap) +{ + struct aucat_hdl *hdl = (struct aucat_hdl *)sh; + unsigned i, bps, le, sig, chan, rindex, rmult; + static unsigned rates[] = { 8000, 11025, 12000 }; + + AMSG_INIT(&hdl->wmsg); + hdl->wmsg.cmd = AMSG_GETCAP; + hdl->wtodo = sizeof(struct amsg); + if (!aucat_wmsg(hdl)) + return 0; + hdl->rtodo = sizeof(struct amsg); + if (!aucat_rmsg(hdl)) + return 0; + if (hdl->rmsg.cmd != AMSG_GETCAP) { + DPRINTF("aucat_getcap: protocol err\n"); + hdl->sio.eof = 1; + return 0; + } + bps = 1; + sig = le = 0; + cap->confs[0].enc = 0; + for (i = 0; i < SIO_NENC; i++) { + if (bps > 4) + break; + cap->confs[0].enc |= 1 << i; + cap->enc[i].bits = bps == 4 ? 24 : bps * 8; + cap->enc[i].bps = bps; + cap->enc[i].sig = sig ^ 1; + cap->enc[i].le = bps > 1 ? le : SIO_LE_NATIVE; + cap->enc[i].msb = 1; + le++; + if (le > 1 || bps == 1) { + le = 0; + sig++; + } + if (sig > 1 || (le == 0 && bps > 1)) { + sig = 0; + bps++; + } + } + chan = 1; + cap->confs[0].rchan = 0; + for (i = 0; i < SIO_NCHAN; i++) { + if (chan > 16) + break; + cap->confs[0].rchan |= 1 << i; + cap->rchan[i] = chan; + if (chan >= 12) { + chan += 4; + } else if (chan >= 2) { + chan += 2; + } else + chan++; + } + chan = 1; + cap->confs[0].pchan = 0; + for (i = 0; i < SIO_NCHAN; i++) { + if (chan > 16) + break; + cap->confs[0].pchan |= 1 << i; + cap->pchan[i] = chan; + if (chan >= 12) { + chan += 4; + } else if (chan >= 2) { + chan += 2; + } else + chan++; + } + rindex = 0; + rmult = 1; + cap->confs[0].rate = 0; + for (i = 0; i < SIO_NRATE; i++) { + if (rmult >= 32) + break; + cap->rate[i] = rates[rindex] * rmult; + cap->confs[0].rate |= 1 << i; + rindex++; + if (rindex == sizeof(rates) / sizeof(unsigned)) { + rindex = 0; + rmult *= 2; + } + } + cap->nconf = 1; + return 1; +} + +static size_t +aucat_read(struct sio_hdl *sh, void *buf, size_t len) +{ + struct aucat_hdl *hdl = (struct aucat_hdl *)sh; + ssize_t n; + + while (hdl->rstate != STATE_DATA) { + switch (hdl->rstate) { + case STATE_MSG: + if (!aucat_runmsg(hdl)) + return 0; + break; + case STATE_IDLE: + DPRINTF("aucat_read: unexpected idle state\n"); + hdl->sio.eof = 1; + return 0; + } + } + if (len > hdl->rtodo) + len = hdl->rtodo; + while ((n = read(hdl->fd, buf, len)) < 0) { + if (errno == EINTR) + continue; + if (errno != EAGAIN) { + hdl->sio.eof = 1; + DPERROR("aucat_read: read"); + } + return 0; + } + if (n == 0) { + DPRINTF("aucat_read: eof\n"); + hdl->sio.eof = 1; + return 0; + } + hdl->rtodo -= n; + if (hdl->rtodo == 0) { + hdl->rstate = STATE_MSG; + hdl->rtodo = sizeof(struct amsg); + } + DPRINTF("aucat: read: n = %zd\n", n); + return n; +} + +static int +aucat_buildmsg(struct aucat_hdl *hdl, size_t len) +{ + unsigned sz; + + if (hdl->curvol != hdl->reqvol) { + hdl->wstate = STATE_MSG; + hdl->wtodo = sizeof(struct amsg); + hdl->wmsg.cmd = AMSG_SETVOL; + hdl->wmsg.u.vol.ctl = hdl->reqvol; + hdl->curvol = hdl->reqvol; + return 1; + } else if (len > 0 && hdl->maxwrite > 0) { + sz = len; + if (sz > AMSG_DATAMAX) + sz = AMSG_DATAMAX; + if (sz > hdl->maxwrite) + sz = hdl->maxwrite; + sz -= sz % hdl->wbpf; + if (sz == 0) + sz = hdl->wbpf; + hdl->wstate = STATE_MSG; + hdl->wtodo = sizeof(struct amsg); + hdl->wmsg.cmd = AMSG_DATA; + hdl->wmsg.u.data.size = sz; + return 1; + } + return 0; +} + +static size_t +aucat_write(struct sio_hdl *sh, const void *buf, size_t len) +{ + struct aucat_hdl *hdl = (struct aucat_hdl *)sh; + ssize_t n; + + while (hdl->wstate != STATE_DATA) { + switch (hdl->wstate) { + case STATE_IDLE: + if (!aucat_buildmsg(hdl, len)) + return 0; + /* PASSTHROUGH */ + case STATE_MSG: + if (!aucat_wmsg(hdl)) + return 0; + if (hdl->wmsg.cmd == AMSG_DATA) { + hdl->wstate = STATE_DATA; + hdl->wtodo = hdl->wmsg.u.data.size; + } else + hdl->wstate = STATE_IDLE; + break; + default: + DPRINTF("aucat_write: bad state\n"); + abort(); + } + } + if (hdl->maxwrite <= 0) + return 0; + if (len > hdl->maxwrite) + len = hdl->maxwrite; + if (len > hdl->wtodo) + len = hdl->wtodo; + if (len == 0) { + DPRINTF("aucat_write: len == 0\n"); + abort(); + } + while ((n = write(hdl->fd, buf, len)) < 0) { + if (errno == EINTR) + continue; + if (errno != EAGAIN) { + hdl->sio.eof = 1; + DPERROR("aucat_write: write"); + } + return 0; + } + hdl->maxwrite -= n; + DPRINTF("aucat: write: n = %zd, maxwrite = %d\n", n, hdl->maxwrite); + hdl->wtodo -= n; + if (hdl->wtodo == 0) { + hdl->wstate = STATE_IDLE; + hdl->wtodo = 0xdeadbeef; + } + return n; +} + +static int +aucat_pollfd(struct sio_hdl *sh, struct pollfd *pfd, int events) +{ + struct aucat_hdl *hdl = (struct aucat_hdl *)sh; + + hdl->events = events; + if (hdl->maxwrite <= 0) + events &= ~POLLOUT; + if (hdl->rstate == STATE_MSG) + events |= POLLIN; + pfd->fd = hdl->fd; + pfd->events = events; + DPRINTF("aucat: pollfd: %x -> %x\n", hdl->events, pfd->events); + return 1; +} + +static int +aucat_revents(struct sio_hdl *sh, struct pollfd *pfd) +{ + struct aucat_hdl *hdl = (struct aucat_hdl *)sh; + int revents = pfd->revents; + + if (revents & POLLIN) { + while (hdl->rstate == STATE_MSG) { + if (!aucat_runmsg(hdl)) { + revents &= ~POLLIN; + break; + } + } + } + if (revents & POLLOUT) { + if (hdl->maxwrite <= 0) + revents &= ~POLLOUT; + } + if (hdl->sio.eof) + return POLLHUP; + DPRINTF("aucat: revents: %x\n", revents & hdl->events); + return revents & (hdl->events | POLLHUP); +} + +static int +aucat_setvol(struct sio_hdl *sh, unsigned vol) +{ + struct aucat_hdl *hdl = (struct aucat_hdl *)sh; + + hdl->reqvol = vol; + return 1; +} + +static void +aucat_getvol(struct sio_hdl *sh) +{ + struct aucat_hdl *hdl = (struct aucat_hdl *)sh; + + sio_onvol_cb(&hdl->sio, hdl->reqvol); + return; +} diff --git a/libsndio/mio.c b/libsndio/mio.c new file mode 100644 index 0000000..d94f32c --- /dev/null +++ b/libsndio/mio.c @@ -0,0 +1,175 @@ +/* $OpenBSD: mio.c,v 1.8 2010/04/24 06:15:54 ratchov Exp $ */ +/* + * Copyright (c) 2008 Alexandre Ratchov + * + * 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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "mio_priv.h" + +#ifdef DEBUG +/* + * debug level, -1 means uninitialized + */ +int mio_debug = -1; +#endif + +struct mio_hdl * +mio_open(const char *str, unsigned mode, int nbio) +{ + static char prefix_midithru[] = "midithru"; + static char prefix_rmidi[] = "rmidi"; + static char prefix_aucat[] = "aucat"; + struct mio_hdl *hdl; + struct stat sb; + char *sep, buf[4]; + int len; +#ifdef DEBUG + char *dbg; + + if (mio_debug < 0) { + dbg = issetugid() ? NULL : getenv("MIO_DEBUG"); + if (!dbg || sscanf(dbg, "%u", &mio_debug) != 1) + mio_debug = 0; + } +#endif + if ((mode & (MIO_OUT | MIO_IN)) == 0) + return NULL; + if (str == NULL && !issetugid()) + str = getenv("MIDIDEVICE"); + if (str == NULL) { + hdl = mio_open_thru("0", mode, nbio); + if (hdl != NULL) + return hdl; + return mio_open_rmidi("0", mode, nbio); + } + sep = strchr(str, ':'); + if (sep == NULL) { + /* + * try legacy "/dev/rmidioxxx" device name + */ + if (stat(str, &sb) < 0 || !S_ISCHR(sb.st_mode)) { + DPRINTF("mio_open: %s: missing ':' separator\n", str); + return NULL; + } + snprintf(buf, sizeof(buf), "%u", minor(sb.st_rdev)); + return mio_open_rmidi(buf, mode, nbio); + } + + len = sep - str; + if (len == (sizeof(prefix_midithru) - 1) && + memcmp(str, prefix_midithru, len) == 0) + return mio_open_thru(sep + 1, mode, nbio); + if (len == (sizeof(prefix_aucat) - 1) && + memcmp(str, prefix_aucat, len) == 0) + return mio_open_aucat(sep + 1, mode, nbio); + if (len == (sizeof(prefix_rmidi) - 1) && + memcmp(str, prefix_rmidi, len) == 0) + return mio_open_rmidi(sep + 1, mode, nbio); + DPRINTF("mio_open: %s: unknown device type\n", str); + return NULL; +} + +void +mio_create(struct mio_hdl *hdl, struct mio_ops *ops, unsigned mode, int nbio) +{ + hdl->ops = ops; + hdl->mode = mode; + hdl->nbio = nbio; + hdl->eof = 0; +} + +void +mio_close(struct mio_hdl *hdl) +{ + hdl->ops->close(hdl); +} + +size_t +mio_read(struct mio_hdl *hdl, void *buf, size_t len) +{ + if (hdl->eof) { + DPRINTF("mio_read: eof\n"); + return 0; + } + if (!(hdl->mode & MIO_IN)) { + DPRINTF("mio_read: not input device\n"); + hdl->eof = 1; + return 0; + } + if (len == 0) { + DPRINTF("mio_read: zero length read ignored\n"); + return 0; + } + return hdl->ops->read(hdl, buf, len); +} + +size_t +mio_write(struct mio_hdl *hdl, const void *buf, size_t len) +{ + if (hdl->eof) { + DPRINTF("mio_write: eof\n"); + return 0; + } + if (!(hdl->mode & MIO_OUT)) { + DPRINTF("mio_write: not output device\n"); + hdl->eof = 1; + return 0; + } + if (len == 0) { + DPRINTF("mio_write: zero length write ignored\n"); + return 0; + } + return hdl->ops->write(hdl, buf, len); +} + +int +mio_nfds(struct mio_hdl *hdl) +{ + return 1; +} + +int +mio_pollfd(struct mio_hdl *hdl, struct pollfd *pfd, int events) +{ + if (hdl->eof) + return 0; + return hdl->ops->pollfd(hdl, pfd, events); +} + +int +mio_revents(struct mio_hdl *hdl, struct pollfd *pfd) +{ + if (hdl->eof) + return POLLHUP; + return hdl->ops->revents(hdl, pfd); +} + +int +mio_eof(struct mio_hdl *hdl) +{ + return hdl->eof; +} diff --git a/libsndio/mio_open.3 b/libsndio/mio_open.3 new file mode 100644 index 0000000..05a32f8 --- /dev/null +++ b/libsndio/mio_open.3 @@ -0,0 +1,253 @@ +.\" $OpenBSD: mio_open.3,v 1.3 2009/07/26 12:42:48 ratchov Exp $ +.\" +.\" Copyright (c) 2007 Alexandre Ratchov +.\" +.\" 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: July 26 2009 $ +.Dt MIO_OPEN 3 +.Os +.Sh NAME +.Nm mio_open , +.Nm mio_close , +.Nm mio_read , +.Nm mio_write , +.Nm mio_nfds , +.Nm mio_pollfd , +.Nm mio_revents , +.Nm mio_eof +.Nd interface to MIDI streams +.Sh SYNOPSIS +.Fd #include +.Ft "struct mio_hdl *" +.Fn "mio_open" "const char *name" "unsigned mode" "int nbio_flag" +.Ft "void" +.Fn "mio_close" "struct mio_hdl *hdl" +.Ft "size_t" +.Fn "mio_read" "struct mio_hdl *hdl" "void *addr" "size_t nbytes" +.Ft "size_t" +.Fn "mio_write" "struct mio_hdl *hdl" "const void *addr" "size_t nbytes" +.Ft "int" +.Fn "mio_nfds" "struct mio_hdl *hdl" +.Ft "int" +.Fn "mio_pollfd" "struct mio_hdl *hdl" "struct pollfd *pfd" "int events" +.Ft "int" +.Fn "mio_revents" "struct mio_hdl *hdl" "struct pollfd *pfd" +.Ft "int" +.Fn "mio_eof" "struct mio_hdl *hdl" +.Sh DESCRIPTION +The +.Nm sndio +library allows user processes to access +.Xr midi 4 +hardware and +.Xr midicat 1 +MIDI thru boxes in a uniform way. +.Ss Opening and closing an MIDI stream +First the application must call the +.Fn mio_open +function to obtain a handle representing the newly created stream; +later it will be passed as the +.Ar hdl +argument of most other functions. +The +.Fn mio_open +function tries to connect to the +.Xr midicat 1 +software MIDI thru box or to use the +.Xr midi 4 +hardware device. +The +.Ar name +parameter gives the device string discussed in +.Xr sndio 7 . +If the program is using a single device and is providing no device chooser, +it should be set to NULL to allow the user to select it using the +.Ev MIDIDEVICE +environment variable. +.Pp +The +.Ar mode +parameter gives the direction of the stream. +The following are supported: +.Bl -tag -width "MIO_OUT | MIO_IN" +.It MIO_OUT +The stream is output-only; data written to the stream will be sent +to the hardware or other programs. +.It MIO_IN +The stream is input-only; received data from the hardware or +other programs must be read from the stream. +.It MIO_IN | MIO_OUT +The stream sends and receives data. +This mode should be used rather than calling +.Fn mio_open +twice. +.El +.Pp +If the +.Ar nbio_flag +argument is true (i.e. non-zero), then the +.Fn mio_read +and +.Fn mio_write +functions (see below) will be non-blocking. +.Pp +The +.Fn mio_close +function closes the stream and frees all allocated resources +associated with the +.Nm libsndio +handle. +.Ss Sending and receiving data +When input mode is selected, the +.Fn mio_read +function must be called to retrieve received data; it must be called +often enough to ensure that internal buffers will not overrun. +It will store at most +.Ar nbytes +bytes at the +.Ar addr +location and return the number of bytes stored. +Unless the +.Ar nbio_flag +flag is set, it will block until data becomes available and +will return zero only on error. +.Pp +When output mode is selected, the +.Fn mio_write +function can be called to provide data to transmit. +Unless the +.Ar nbio_flag +is set, +.Fn mio_write +will block until the requested amount of data is written. +.Ss Non-blocking mode operation +If the +.Ar nbio_flag +is set on +.Fn mio_open , +then the +.Fn mio_read +and +.Fn mio_write +functions will never block; if no data is available, they will +return zero immediately. +.Pp +To avoid busy loops when non-blocking mode is used, the +.Xr poll 2 +system call can be used to check if data can be +read from or written to the stream. +The +.Fn mio_pollfd +function fills the array +.Ar pfd +of +.Va pollfd +structures, used by +.Xr poll 2 , +with +.Ar events ; +the latter is a bit-mask of +.Va POLLIN +and +.Va POLLOUT +constants; refer to +.Xr poll 2 +for more details. +.Fn mio_pollfd +returns the number of +.Va pollfd +structures filled. +The +.Fn mio_revents +function returns the bit-mask set by +.Xr poll 2 +in the +.Va pfd +array of +.Va pollfd +structures. +If +.Va POLLIN +is set, +.Fn mio_read +can be called without blocking. +If +.Va POLLOUT +is set, +.Fn mio_write +can be called without blocking. +POLLHUP may be set if an error occurs, even if +it is not selected with +.Fn mio_pollfd . +.Pp +The +.Fn mio_nfds +function returns the number of +.Va pollfd +structures the caller must preallocate in order to be sure +that +.Fn mio_pollfd +will never overrun. +.Ss Error handling +Errors related to the MIDI subsystem +(like hardware errors or dropped connections) and +programming errors (such as a call to +.Fn mio_read +on a play-only stream) are considered fatal. +Once an error occurs, all functions which take a +.Va mio_hdl +argument, except +.Fn mio_close +and +.Fn mio_eof , +stop working (i.e. always return 0). +.Pp +The +.Fn mio_eof +function can be used at any stage; +it returns 0 if there's no pending error, and a non-zero +value if there's an error. +.Sh RETURN VALUES +The +.Fn mio_open +function returns the newly created handle on success or NULL +on failure. +The +.Fn mio_pollfd +function returns 1 on success and 0 on failure. +The +.Fn mio_read +and +.Fn mio_write +functions return the number of bytes transferred. +.Sh ENVIRONMENT +.Bl -tag -width "MIO_DEBUGXXX" -compact +.It Ev MIO_DEBUG +The debug level: +may be a value between 0 and 2. +.El +.Sh FILES +.Bl -tag -width "/tmp/aucat-/midithru0" -compact +.It Pa /tmp/aucat-/midithru0 +Default path to +.Xr midicat 1 +socket to connect to. +.It Pa /dev/rmidiX +.Xr midi 4 +devices. +.El +.Sh SEE ALSO +.Xr midicat 1 , +.Xr midi 4 , +.Xr sndio 7 diff --git a/libsndio/mio_priv.h b/libsndio/mio_priv.h new file mode 100644 index 0000000..f5022dc --- /dev/null +++ b/libsndio/mio_priv.h @@ -0,0 +1,70 @@ +/* $OpenBSD: mio_priv.h,v 1.4 2009/08/21 16:48:03 ratchov Exp $ */ +/* + * Copyright (c) 2008 Alexandre Ratchov + * + * 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 MIO_PRIV_H +#define MIO_PRIV_H + +#include +#include "sndio.h" + +#ifdef DEBUG +#define DPRINTF(...) \ + do { \ + if (mio_debug > 0) \ + fprintf(stderr, __VA_ARGS__); \ + } while(0) +#define DPERROR(s) \ + do { \ + if (mio_debug > 0) \ + perror(s); \ + } while(0) +#else +#define DPRINTF(...) do {} while(0) +#define DPERROR(s) do {} while(0) +#endif + +/* + * private ``handle'' structure + */ +struct mio_hdl { + struct mio_ops *ops; + unsigned mode; /* MIO_IN | MIO_OUT */ + int nbio; /* true if non-blocking io */ + int eof; /* true if error occured */ +}; + +/* + * operations every device should support + */ +struct mio_ops { + void (*close)(struct mio_hdl *); + size_t (*write)(struct mio_hdl *, const void *, size_t); + size_t (*read)(struct mio_hdl *, void *, size_t); + int (*pollfd)(struct mio_hdl *, struct pollfd *, int); + int (*revents)(struct mio_hdl *, struct pollfd *); +}; + +struct mio_hdl *mio_open_rmidi(const char *, unsigned, int); +struct mio_hdl *mio_open_thru(const char *, unsigned, int); +struct mio_hdl *mio_open_aucat(const char *, unsigned, int); +void mio_create(struct mio_hdl *, struct mio_ops *, unsigned, int); +void mio_destroy(struct mio_hdl *); + +#ifdef DEBUG +extern int mio_debug; +#endif + +#endif /* !defined(MIO_PRIV_H) */ diff --git a/libsndio/mio_rmidi.c b/libsndio/mio_rmidi.c new file mode 100644 index 0000000..194863c --- /dev/null +++ b/libsndio/mio_rmidi.c @@ -0,0 +1,159 @@ +/* $OpenBSD: mio_rmidi.c,v 1.7 2010/07/21 23:00:16 ratchov Exp $ */ +/* + * Copyright (c) 2008 Alexandre Ratchov + * + * 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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mio_priv.h" + +#define RMIDI_PATH "/dev/rmidi0" + +struct rmidi_hdl { + struct mio_hdl mio; + int fd; +}; + +static void rmidi_close(struct mio_hdl *); +static size_t rmidi_read(struct mio_hdl *, void *, size_t); +static size_t rmidi_write(struct mio_hdl *, const void *, size_t); +static int rmidi_pollfd(struct mio_hdl *, struct pollfd *, int); +static int rmidi_revents(struct mio_hdl *, struct pollfd *); + +static struct mio_ops rmidi_ops = { + rmidi_close, + rmidi_write, + rmidi_read, + rmidi_pollfd, + rmidi_revents, +}; + +struct mio_hdl * +mio_open_rmidi(const char *str, unsigned mode, int nbio) +{ + int fd, flags; + struct rmidi_hdl *hdl; + char path[PATH_MAX]; + + hdl = malloc(sizeof(struct rmidi_hdl)); + if (hdl == NULL) + return NULL; + mio_create(&hdl->mio, &rmidi_ops, mode, nbio); + + snprintf(path, sizeof(path), "/dev/rmidi%s", str); + if (mode == (MIO_OUT | MIO_IN)) + flags = O_RDWR; + else + flags = (mode & MIO_OUT) ? O_WRONLY : O_RDONLY; + if (nbio) + flags |= O_NONBLOCK; + while ((fd = open(path, flags)) < 0) { + if (errno == EINTR) + continue; + DPERROR(path); + goto bad_free; + } + if (fcntl(fd, F_SETFD, FD_CLOEXEC) < 0) { + DPERROR("FD_CLOEXEC"); + goto bad_close; + } + hdl->fd = fd; + return (struct mio_hdl *)hdl; + bad_close: + while (close(hdl->fd) < 0 && errno == EINTR) + ; /* retry */ + bad_free: + free(hdl); + return NULL; +} + +static void +rmidi_close(struct mio_hdl *sh) +{ + struct rmidi_hdl *hdl = (struct rmidi_hdl *)sh; + int rc; + + do { + rc = close(hdl->fd); + } while (rc < 0 && errno == EINTR); + free(hdl); +} + +static size_t +rmidi_read(struct mio_hdl *sh, void *buf, size_t len) +{ + struct rmidi_hdl *hdl = (struct rmidi_hdl *)sh; + ssize_t n; + + while ((n = read(hdl->fd, buf, len)) < 0) { + if (errno == EINTR) + continue; + if (errno != EAGAIN) { + DPERROR("rmidi_read: read"); + hdl->mio.eof = 1; + } + return 0; + } + if (n == 0) { + DPRINTF("rmidi_read: eof\n"); + hdl->mio.eof = 1; + return 0; + } + return n; +} + +static size_t +rmidi_write(struct mio_hdl *sh, const void *buf, size_t len) +{ + struct rmidi_hdl *hdl = (struct rmidi_hdl *)sh; + ssize_t n; + + while ((n = write(hdl->fd, buf, len)) < 0) { + if (errno == EINTR) + continue; + if (errno != EAGAIN) { + DPERROR("rmidi_write: write"); + hdl->mio.eof = 1; + } + return 0; + } + return n; +} + +static int +rmidi_pollfd(struct mio_hdl *sh, struct pollfd *pfd, int events) +{ + struct rmidi_hdl *hdl = (struct rmidi_hdl *)sh; + + pfd->fd = hdl->fd; + pfd->events = events; + return 1; +} + +static int +rmidi_revents(struct mio_hdl *sh, struct pollfd *pfd) +{ + return pfd->revents; +} diff --git a/libsndio/mio_thru.c b/libsndio/mio_thru.c new file mode 100644 index 0000000..24c84ea --- /dev/null +++ b/libsndio/mio_thru.c @@ -0,0 +1,249 @@ +/* $OpenBSD: mio_thru.c,v 1.10 2010/07/21 23:00:16 ratchov Exp $ */ +/* + * Copyright (c) 2008 Alexandre Ratchov + * + * 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 +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "amsg.h" +#include "mio_priv.h" + +#define THRU_SOCKET "midithru" + +struct thru_hdl { + struct mio_hdl mio; + int fd; +}; + +static void thru_close(struct mio_hdl *); +static size_t thru_read(struct mio_hdl *, void *, size_t); +static size_t thru_write(struct mio_hdl *, const void *, size_t); +static int thru_pollfd(struct mio_hdl *, struct pollfd *, int); +static int thru_revents(struct mio_hdl *, struct pollfd *); + +static struct mio_ops thru_ops = { + thru_close, + thru_write, + thru_read, + thru_pollfd, + thru_revents, +}; + +struct mio_hdl * +thru_open(const char *str, char *sock, unsigned mode, int nbio) +{ + extern char *__progname; + char unit[4], *sep, *opt; + struct amsg msg; + int s, n, todo; + unsigned char *data; + struct thru_hdl *hdl; + struct sockaddr_un ca; + socklen_t len = sizeof(struct sockaddr_un); + uid_t uid; + + sep = strchr(str, '.'); + if (sep == NULL) { + opt = "default"; + strlcpy(unit, str, sizeof(unit)); + } else { + opt = sep + 1; + if (sep - str >= sizeof(unit)) { + DPRINTF("thru_open: %s: too long\n", str); + return NULL; + } + strlcpy(unit, str, opt - str); + } + DPRINTF("thru_open: trying %s -> %s.%s\n", str, unit, opt); + uid = geteuid(); + if (strchr(str, '/') != NULL) + return NULL; + snprintf(ca.sun_path, sizeof(ca.sun_path), + "/tmp/aucat-%u/%s%s", uid, sock, unit); + ca.sun_family = AF_UNIX; + + hdl = malloc(sizeof(struct thru_hdl)); + if (hdl == NULL) + return NULL; + mio_create(&hdl->mio, &thru_ops, mode, nbio); + + s = socket(AF_UNIX, SOCK_STREAM, 0); + if (s < 0) + goto bad_free; + while (connect(s, (struct sockaddr *)&ca, len) < 0) { + if (errno == EINTR) + continue; + DPERROR("thru_open: connect"); + /* try shared server */ + snprintf(ca.sun_path, sizeof(ca.sun_path), + "/tmp/aucat/%s%s", sock, unit); + while (connect(s, (struct sockaddr *)&ca, len) < 0) { + if (errno == EINTR) + continue; + DPERROR("thru_open: connect"); + goto bad_connect; + } + break; + } + if (fcntl(s, F_SETFD, FD_CLOEXEC) < 0) { + DPERROR("FD_CLOEXEC"); + goto bad_connect; + } + hdl->fd = s; + + /* + * say hello to server + */ + AMSG_INIT(&msg); + msg.cmd = AMSG_HELLO; + msg.u.hello.version = AMSG_VERSION; + msg.u.hello.proto = 0; + if (mode & MIO_IN) + msg.u.hello.proto |= AMSG_MIDIIN; + if (mode & MIO_OUT) + msg.u.hello.proto |= AMSG_MIDIOUT; + strlcpy(msg.u.hello.opt, opt, sizeof(msg.u.hello.opt)); + strlcpy(msg.u.hello.who, __progname, sizeof(msg.u.hello.who)); + n = write(s, &msg, sizeof(struct amsg)); + if (n < 0) { + DPERROR("thru_open"); + goto bad_connect; + } + if (n != sizeof(struct amsg)) { + DPRINTF("thru_open: short write\n"); + goto bad_connect; + } + todo = sizeof(struct amsg); + data = (unsigned char *)&msg; + while (todo > 0) { + n = read(s, data, todo); + if (n < 0) { + DPERROR("thru_open"); + goto bad_connect; + } + if (n == 0) { + DPRINTF("thru_open: eof\n"); + goto bad_connect; + } + todo -= n; + data += n; + } + if (msg.cmd != AMSG_ACK) { + DPRINTF("thru_open: proto error\n"); + goto bad_connect; + } + if (nbio && fcntl(hdl->fd, F_SETFL, O_NONBLOCK) < 0) { + DPERROR("thru_open: fcntl(NONBLOCK)"); + goto bad_connect; + } + return (struct mio_hdl *)hdl; + bad_connect: + while (close(s) < 0 && errno == EINTR) + ; /* retry */ + bad_free: + free(hdl); + return NULL; +} + +struct mio_hdl * +mio_open_thru(const char *str, unsigned mode, int nbio) +{ + return thru_open(str, "midithru", mode, nbio); +} + +struct mio_hdl * +mio_open_aucat(const char *str, unsigned mode, int nbio) +{ + return thru_open(str, "softaudio", mode, nbio); +} + +static void +thru_close(struct mio_hdl *sh) +{ + struct thru_hdl *hdl = (struct thru_hdl *)sh; + int rc; + + do { + rc = close(hdl->fd); + } while (rc < 0 && errno == EINTR); + free(hdl); +} + +static size_t +thru_read(struct mio_hdl *sh, void *buf, size_t len) +{ + struct thru_hdl *hdl = (struct thru_hdl *)sh; + ssize_t n; + + while ((n = read(hdl->fd, buf, len)) < 0) { + if (errno == EINTR) + continue; + if (errno != EAGAIN) { + DPERROR("thru_read: read"); + hdl->mio.eof = 1; + } + return 0; + } + if (n == 0) { + DPRINTF("thru_read: eof\n"); + hdl->mio.eof = 1; + return 0; + } + return n; +} + +static size_t +thru_write(struct mio_hdl *sh, const void *buf, size_t len) +{ + struct thru_hdl *hdl = (struct thru_hdl *)sh; + ssize_t n; + + while ((n = write(hdl->fd, buf, len)) < 0) { + if (errno == EINTR) + continue; + if (errno != EAGAIN) { + DPERROR("thru_write: write"); + hdl->mio.eof = 1; + } + return 0; + } + return n; +} + +static int +thru_pollfd(struct mio_hdl *sh, struct pollfd *pfd, int events) +{ + struct thru_hdl *hdl = (struct thru_hdl *)sh; + + pfd->fd = hdl->fd; + pfd->events = events; + return 1; +} + +static int +thru_revents(struct mio_hdl *sh, struct pollfd *pfd) +{ + return pfd->revents; +} diff --git a/libsndio/sio_open.3 b/libsndio/sio_open.3 new file mode 100644 index 0000000..91135d0 --- /dev/null +++ b/libsndio/sio_open.3 @@ -0,0 +1,774 @@ +.\" $OpenBSD: sio_open.3,v 1.24 2010/04/26 07:11:10 jakemsr Exp $ +.\" +.\" Copyright (c) 2007 Alexandre Ratchov +.\" +.\" 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: April 26 2010 $ +.Dt SIO_OPEN 3 +.Os +.Sh NAME +.Nm sio_open , +.Nm sio_close , +.Nm sio_setpar , +.Nm sio_getpar , +.Nm sio_getcap , +.Nm sio_start , +.Nm sio_stop , +.Nm sio_read , +.Nm sio_write , +.Nm sio_onmove , +.Nm sio_nfds , +.Nm sio_pollfd , +.Nm sio_revents , +.Nm sio_eof , +.Nm sio_setvol , +.Nm sio_onvol , +.Nm sio_initpar +.Nd interface to bidirectional audio streams +.Sh SYNOPSIS +.Fd #include +.Ft "struct sio_hdl *" +.Fn "sio_open" "const char *name" "unsigned mode" "int nbio_flag" +.Ft "void" +.Fn "sio_close" "struct sio_hdl *hdl" +.Ft "int" +.Fn "sio_setpar" "struct sio_hdl *hdl" "struct sio_par *par" +.Ft "int" +.Fn "sio_getpar" "struct sio_hdl *hdl" "struct sio_par *par" +.Ft "int" +.Fn "sio_getcap" "struct sio_hdl *hdl" "struct sio_cap *cap" +.Ft "int" +.Fn "sio_start" "struct sio_hdl *hdl" +.Ft "int" +.Fn "sio_stop" "struct sio_hdl *hdl" +.Ft "size_t" +.Fn "sio_read" "struct sio_hdl *hdl" "void *addr" "size_t nbytes" +.Ft "size_t" +.Fn "sio_write" "struct sio_hdl *hdl" "const void *addr" "size_t nbytes" +.Ft "void" +.Fn "sio_onmove" "struct sio_hdl *hdl" "void (*cb)(void *arg, int delta)" "void *arg" +.Ft "int" +.Fn "sio_nfds" "struct sio_hdl *hdl" +.Ft "int" +.Fn "sio_pollfd" "struct sio_hdl *hdl" "struct pollfd *pfd" "int events" +.Ft "int" +.Fn "sio_revents" "struct sio_hdl *hdl" "struct pollfd *pfd" +.Ft "int" +.Fn "sio_eof" "struct sio_hdl *hdl" +.Ft "int" +.Fn "sio_setvol" "struct sio_hdl *hdl" "unsigned vol" +.Ft "void" +.Fn "sio_onvol" "struct sio_hdl *hdl" "void (*cb)(void *arg, unsigned vol)" "void *arg" +.Ft "void" +.Fn "sio_initpar" "struct sio_par *par" +.\"Fd #define SIO_BPS(bits) +.\"Fd #define SIO_LE_NATIVE +.Sh DESCRIPTION +The +.Nm sndio +library allows user processes to access +.Xr audio 4 +hardware and the +.Xr aucat 1 +audio server in a uniform way. +It supports full-duplex operation, and when +used with the +.Xr aucat 1 +server it supports resampling and format +conversions on the fly. +.Ss Opening and closing an audio stream +First the application must call the +.Fn sio_open +function to obtain a handle representing the newly created stream; +later it will be passed as the +.Ar hdl +argument of most other functions. +The +.Fn sio_open +function first tries to connect to the +.Xr aucat 1 +audio server. +If that fails, it then tries to use the +.Xr audio 4 +hardware device. +The +.Ar name +parameter gives the device string discussed in +.Xr sndio 7 . +In most cases it should be set to NULL to allow +the user to select it using the +.Ev AUDIODEVICE +environment variable. +.Pp +The +.Ar mode +parameter gives the direction of the stream. +The following are supported: +.Bl -tag -width "SIO_PLAY | SIO_REC" +.It SIO_PLAY +The stream is play-only; data written to the stream will be played +by the hardware. +.It SIO_REC +The stream is record-only; recorded samples by the hardware +must be read from the stream. +.It SIO_PLAY | SIO_REC +The stream plays and records synchronously; this means that +the n-th recorded sample was physically sampled exactly when +the n-th played sample was actually played. +.El +.Pp +If the +.Ar nbio_flag +argument is true (i.e. non-zero), then the +.Fn sio_read +and +.Fn sio_write +functions (see below) will be non-blocking. +.Pp +The +.Fn sio_close +function closes the stream and frees all allocated resources +associated with the +.Nm libsndio +handle. +If the stream is not stopped it will be stopped first as if +.Fn sio_stop +is called. +.Ss Negotiating audio parameters +Audio streams always use linear interleaved encoding. +A frame consists of one sample for each channel in the stream. +For example, a 16-bit stereo stream has two samples per frame +and, typically, two bytes per sample (thus 4 bytes per frame). +.Pp +The set of parameters of the stream that can be controlled +is given by the following structure: +.Bd -literal +struct sio_par { + unsigned bits; /* bits per sample */ + unsigned bps; /* bytes per sample */ + unsigned sig; /* 1 = signed, 0 = unsigned */ + unsigned le; /* 1 = LE, 0 = BE byte order */ + unsigned msb; /* 1 = MSB, 0 = LSB aligned */ + unsigned rchan; /* number channels for recording */ + unsigned pchan; /* number channels for playback */ + unsigned rate; /* frames per second */ + unsigned appbufsz; /* minimum buffer size without xruns */ + unsigned bufsz; /* end-to-end buffer size (read-only) */ + unsigned round; /* optimal buffer size divisor */ +#define SIO_IGNORE 0 /* pause during xrun */ +#define SIO_SYNC 1 /* resync after xrun */ +#define SIO_ERROR 2 /* terminate on xrun */ + unsigned xrun; /* what to do on overrun/underrun */ +}; +.Ed +.Pp +The parameters are as follows: +.Bl -tag -width "appbufsz" +.It Va bits +Number of bits per sample: must be between 1 and 32. +.It Va bps +Bytes per samples; if specified, it must be large enough to hold all bits. +By default it's set to the smallest power of two large enough to hold +.Va bits . +.It Va sig +If set (i.e. non-zero) then the samples are signed, else unsigned. +.It Va le +If set, then the byte order is little endian, else big endian; +it's meaningful only if +.Va bps +\*(Gt 1. +.It Va msb +If set, then the +.Va bits +bits are aligned in the packet to the most significant bit +(i.e. lower bits are padded), +else to the least significant bit +(i.e. higher bits are padded); +it's meaningful only if +.Va bits +\*(Lt +.Va bps +* 8. +.It Va rchan +The number of recorded channels; meaningful only if +.Va SIO_REC +mode was selected. +.It Va pchan +The number of played channels; meaningful only if +.Va SIO_PLAY +mode was selected. +.It Va rate +The sampling frequency in Hz. +.It Va bufsz +The maximum number of frames that may be buffered. +This parameter takes into account any buffers, and +can be used for latency calculations. +It is read-only. +.It Va appbufsz +Size of the buffer in frames the application must maintain non empty +(on the play end) or non full (on the record end) by calling +.Fn sio_write +or +.Fn sio_read +fast enough to avoid overrun or underrun conditions. +The audio subsystem may use additional buffering, thus this +parameter cannot be used for latency calculations +.It Va round +Optimal number of frames that the application buffers +should be a multiple of, to get best performance. +Applications can use this parameter to round their block size. +.It Va xrun +The action when the client doesn't accept +recorded data or doesn't provide data to play fast enough; +it can be set to one of the +.Va SIO_IGNORE , +.Va SIO_SYNC +or +.Va SIO_ERROR +constants. +.El +.Pp +The following approach is recommended to negotiate parameters of the stream: +.Bl -bullet +.It +Initialize a +.Va sio_par +structure using +.Fn sio_initpar +and fill it with +the desired parameters. +If the application supports any value for a given parameter, +then the corresponding parameter should be left unset. +Then call +.Fn sio_setpar +to request the stream to use them. +.It +Call +.Fn sio_getpar +to retrieve the actual parameters of the stream +and check that they are usable. +If they are not, then fail or set up a conversion layer. +Sometimes the rate set can be slightly different to what was requested. +A difference of about 0.5% is not audible and should be ignored. +.El +.Pp +Parameters cannot be changed while the stream is in a waiting state; +if +.Fn sio_start +has been called, +.Fn sio_stop +must be called before parameters can be changed. +.Pp +If +.Nm libsndio +is used to connect to the +.Xr aucat 1 +server, a transparent emulation layer will +automatically be set up, and in this case any +parameters are supported. +.Pp +To ease filling the +.Va sio_par +structure, the +following macros can be used: +.Bl -tag -width "SIO_BPS(bits)" +.It "SIO_BPS(bits)" +Return the smallest value for +.Va bps +that is a power of two and that is large enough to +hold +.Va bits . +.It "SIO_LE_NATIVE" +Can be used to set the +.Va le +parameter when native byte order is required. +.El +.Ss Getting stream capabilities +There's no way to get an exhaustive list of all parameter +combinations the stream supports. +Applications that need to have a set of working +parameter combinations in advance can use the +.Fn sio_getcap +function. +.Pp +The +.Va sio_cap +structure contains the list of parameter configurations. +Each configuration contains multiple parameter sets. +The application must examine all configurations, and +choose its parameter set from +.Em one +of the configurations. +Parameters of different configurations +.Em are not +usable together. +.Bd -literal +struct sio_cap { + struct sio_enc { /* allowed encodings */ + unsigned bits; + unsigned bps; + unsigned sig; + unsigned le; + unsigned msb; + } enc[SIO_NENC]; + unsigned rchan[SIO_NCHAN]; /* allowed rchans */ + unsigned pchan[SIO_NCHAN]; /* allowed pchans */ + unsigned rate[SIO_NRATE]; /* allowed rates */ + unsigned nconf; /* num. of confs[] */ + struct sio_conf { + unsigned enc; /* bitmask of enc[] indexes */ + unsigned rchan; /* bitmask of rchan[] indexes */ + unsigned pchan; /* bitmask of pchan[] indexes */ + unsigned rate; /* bitmask of rate[] indexes */ + } confs[SIO_NCONF]; +}; +.Ed +.Pp +The parameters are as follows: +.Bl -tag -width "rchan[SIO_NCHAN]" +.It Va enc[SIO_NENC] +Array of supported encodings. +The tuple of +.Va bits , +.Va bps , +.Va sig , +.Va le +and +.Va msb +parameters are usable in the corresponding parameters +of the +.Va sio_par +structure. +.It Va rchan[SIO_NCHAN] +Array of supported channel numbers for recording usable +in the +.Va sio_par +structure. +.It Va pchan[SIO_NCHAN] +Array of supported channel numbers for playback usable +in the +.Va sio_par +structure. +.It Va rate[SIO_NRATE] +Array of supported sample rates usable +in the +.Va sio_par +structure. +.It Va nconf +Number of different configurations available, i.e. number +of filled elements of the +.Va confs[] +array. +.It Va confs[SIO_NCONF] +Array of available configurations. +Each configuration contains bitmasks indicating which +elements of the above parameter arrays are valid for the +given configuration. +For instance, if the second bit of +.Va rate +is set, in the +.Va sio_conf +structure, then the second element of the +.Va rate[SIO_NRATE] +array of the +.Va sio_cap +structure is valid for this configuration. +.El +.Ss Starting and stopping the stream +The +.Fn sio_start +function puts the stream in a waiting state: +the stream will wait for playback data to be provided +(using the +.Fn sio_write +function). +Once enough data is queued to ensure that play buffers +will not underrun, actual playback is started automatically. +If record mode only is selected, then recording starts +immediately. +In full-duplex mode, playback and recording will start +synchronously as soon as enough data to play is available. +.Pp +The +.Fn sio_stop +function stops playback and recording and puts the audio subsystem +in the same state as after +.Fn sio_open +is called. +Samples in the play buffers are not discarded, and will continue to +be played after +.Fn sio_stop +returns. +If samples to play are queued but playback hasn't started yet +then playback is forced immediately; the stream will actually stop +once the buffer is drained. +.Ss Playing and recording +When record mode is selected, the +.Fn sio_read +function must be called to retrieve recorded data; it must be called +often enough to ensure that internal buffers will not overrun. +It will store at most +.Ar nbytes +bytes at the +.Ar addr +location and return the number of bytes stored. +Unless the +.Ar nbio_flag +flag is set, it will block until data becomes available and +will return zero only on error. +.Pp +Similarly, when play mode is selected, the +.Fn sio_write +function must be called to provide data to play. +Unless the +.Ar nbio_flag +is set, +.Fn sio_write +will block until the requested amount of data is written. +.Ss Non-blocking mode operation +If the +.Ar nbio_flag +is set on +.Fn sio_open , +then the +.Fn sio_read +and +.Fn sio_write +functions will never block; if no data is available, they will +return zero immediately. +.Pp +Note that non-blocking mode must be used on bidirectional streams. +For instance, if recording is blocked in +.Fn sio_read +then, even if samples can be played, +.Fn sio_write +cannot be called and the play buffers may underrun. +.Pp +To avoid busy loops when non-blocking mode is used, the +.Xr poll 2 +system call can be used to check if data can be +read from or written to the stream. +The +.Fn sio_pollfd +function fills the array +.Ar pfd +of +.Va pollfd +structures, used by +.Xr poll 2 , +with +.Ar events ; +the latter is a bit-mask of +.Va POLLIN +and +.Va POLLOUT +constants; refer to +.Xr poll 2 +for more details. +.Fn sio_pollfd +returns the number of +.Va pollfd +structures filled. +The +.Fn sio_revents +function returns the bit-mask set by +.Xr poll 2 +in the +.Va pfd +array of +.Va pollfd +structures. +If +.Va POLLIN +is set, +.Fn sio_read +can be called without blocking. +If +.Va POLLOUT +is set, +.Fn sio_write +can be called without blocking. +POLLHUP may be set if an error occurs, even if +it is not selected with +.Fn sio_pollfd . +.Pp +The +.Fn sio_nfds +function returns the number of +.Va pollfd +structures the caller must preallocate in order to be sure +that +.Fn sio_pollfd +will never overrun. +.Ss Synchronizing non-audio events to the stream in real-time +In order to perform actions at precise positions of the stream, +such as displaying video in sync with the audio stream, +the application must be notified in real-time of the exact +position in the stream the hardware is processing. +.Pp +The +.Fn sio_onmove +function can be used to register the +.Va cb +callback function that will be called by the +.Nm sndio +library at regular time intervals to notify the application +the position in the stream changed. +The +.Va delta +argument contains the number of frames the hardware moved in the stream +since the last call of +.Va cb . +When the stream starts, the callback is invoked with a zero +.Va delta +argument. +The value of the +.Va arg +pointer is passed to the callback and can contain anything. +.Pp +If desired, the application can maintain the current position by +starting from zero (when +.Fn sio_start +is called) and adding to the current position +.Va delta +every time +.Fn cb +is called. +.Ss Measuring the latency and buffers usage +The playback latency is the delay it will take for the +frame just written to become audible, expressed in number of frames. +The exact playback +latency can be obtained by subtracting the current position +from the number of frames written. +Once playback is actually started (first sample audible) +the latency will never exceed the +.Va bufsz +parameter (see the sections above). +There's a phase during which +.Fn sio_write +only queues data; +once there's enough data, actual playback starts. +During this phase talking about latency is meaningless. +.Pp +In any cases, at most +.Va bufsz +frames are buffered. +This value takes into account all buffers, +including device, kernel and socket buffers. +The number of frames stored is equal to the number of frames +written minus the current position. +.Pp +The recording latency is obtained similarly, by subtracting +the number of frames read from the current position. +.Pp +It is strongly discouraged to use the latency and/or the buffer +usage for anything but monitoring. +Especially, note that +.Fn sio_write +might block even if there is buffer space left; +using the buffer usage to guess if +.Fn sio_write +would block is false and leads to unreliable programs \(en consider using +.Xr poll 2 +for this. +.Ss Handling buffer overruns and underruns +When the application cannot accept recorded data fast enough, +the record buffer (of size +.Va appbufsz ) +might overrun; in this case recorded data is lost. +Similarly if the application cannot provide data to play +fast enough, the play buffer underruns and silence is played +instead. +Depending on the +.Va xrun +parameter of the +.Va sio_par +structure, the audio subsystem will behave as follows: +.Bl -tag -width "SIO_IGNORE" +.It SIO_IGNORE +The stream is paused during overruns and underruns, +thus the current position (obtained through +.Va sio_onmove ) +stops being incremented. +Once the overrun and/or underrun condition is gone, the stream is unpaused; +play and record are always kept in sync. +With this mode, the application cannot notice +underruns and/or overruns and shouldn't care about them. +.Pp +This mode is the default. +It's suitable for applications, +like audio players and telephony, where time +is not important and overruns or underruns are not short. +.It SIO_SYNC +If the play buffer underruns, then silence is played, +but in order to reach the right position in time, +the same amount of written samples will be +discarded once the application is unblocked. +Similarly, if the record buffer overruns, then +samples are discarded, but the same amount of silence will be +returned later. +The current position (obtained through +.Va sio_onmove ) +is still incremented. +When the play buffer underruns the play latency might become negative; +when the record buffer overruns, the record latency might become +larger than +.Va bufsz . +.Pp +This mode is suitable for applications, like music production, +where time is important and where underruns or overruns are +short and rare. +.It SIO_ERROR +With this mode, on the first play buffer underrun or +record buffer overrun, the stream is terminated and +no other function than +.Fn sio_close +will succeed. +.Pp +This mode is mostly useful for testing; portable +applications shouldn't depend on it, since it's not available +on other systems. +.El +.Ss Controlling the volume +The +.Fn sio_setvol +function can be used to set playback attenuation. +The +.Va vol +parameter takes a value between 0 (maximum attenuation) +and +.Dv SIO_MAXVOL +(no attenuation). +It specifies the weight the audio subsystem will +give to this stream. +It is not meant to control hardware parameters like +speaker gain; the +.Xr mixerctl 1 +interface should be used for that purpose instead. +.Pp +An application can use the +.Fn sio_onvol +function to register a callback function that +will be called each time the volume is changed, +including when +.Fn sio_setvol +is used. +The callback is always invoked when +.Fn sio_onvol +is called in order to provide the initial volume. +An application can safely assume that once +.Fn sio_onvol +returns, the callback has already been invoked and thus +the current volume is available. +.Ss Error handling +Errors related to the audio subsystem +(like hardware errors, dropped connections) and +programming errors (e.g. call to +.Fn sio_read +on a play-only stream) are considered fatal. +Once an error occurs, all functions taking a +.Va sio_hdl +argument, except +.Fn sio_close +and +.Fn sio_eof , +stop working (i.e. always return 0). +.Pp +The +.Fn sio_eof +function can be used at any stage; +it returns 0 if there's no pending error, and a non-zero +value if there's an error. +.Sh RETURN VALUES +The +.Fn sio_open +function returns the newly created handle on success or NULL +on failure. +The +.Fn sio_setpar , +.Fn sio_getpar , +.Fn sio_getcap , +.Fn sio_start , +.Fn sio_stop , +.Fn sio_pollfd +and +.Fn sio_setvol +functions return 1 on success and 0 on failure. +The +.Fn sio_read +and +.Fn sio_write +functions return the number of bytes transferred. +.Sh ENVIRONMENT +.Bl -tag -width "AUDIODEVICEXXX" -compact +.It Ev AUDIODEVICE +Device to use if +.Fn sio_open +is called with a NULL +.Va name +argument. +.It Ev SIO_DEBUG +The debug level: +may be a value between 0 and 2. +.El +.Sh FILES +.Bl -tag -width "/tmp/aucat-/softaudio0" -compact +.It Pa /tmp/aucat-/softaudio0 +Default path to +.Xr aucat 1 +socket to connect to. +.It Pa /dev/audio +Default +.Xr audio 4 +device to use. +.El +.\".Sh EXAMPLES +.\".Bd -literal -offset indent +.\".Ed +.Sh SEE ALSO +.Xr aucat 1 , +.Xr audio 4 , +.Xr sndio 7 , +.Xr audio 9 +.Sh BUGS +The +.Xr audio 4 +driver cannot drain playback buffers in the background, thus if +.Nm libsndio +is used to directly access an +.Xr audio 4 +device, +the +.Fn sio_stop +function will stop playback immediately. +.Pp +The +.Xr aucat 1 +server doesn't implement flow control (for performance reasons). +If the application doesn't consume recorded data fast enough then +.Dq "control messages" +are delayed and consequently +.Va sio_onmove +callback or volume changes may be delayed. +.Pp +The +.Fn sio_open , +.Fn sio_setpar , +.Fn sio_getpar , +.Fn sio_getcap , +.Fn sio_start +and +.Fn sio_stop +functions may block for a very short period of time, thus they should +be avoided in code sections where blocking is not desirable. diff --git a/libsndio/sndio.7 b/libsndio/sndio.7 new file mode 100644 index 0000000..3a7ac25 --- /dev/null +++ b/libsndio/sndio.7 @@ -0,0 +1,180 @@ +.\" $OpenBSD: sndio.7,v 1.2 2009/08/21 16:48:03 ratchov Exp $ +.\" +.\" Copyright (c) 2007 Alexandre Ratchov +.\" +.\" 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: August 21 2009 $ +.Dt SNDIO 7 +.Os +.Sh NAME +.Nm sndio +.Nd interface to audio and MIDI +.Sh DESCRIPTION +The +.Nm sndio +audio and MIDI system provides access to audio and MIDI hardware and +to services provided by +.Xr aucat 1 +and +.Xr midicat 1 , +summarized below. +.Pp +Hardware +.Xr audio 4 +devices correspond to peripherals. +Only one application may use any device at a given time. +Generally a limited number of encodings, sample rates and channel numbers are +supported by the hardware, which may not meet the requirements of +audio programs. +.Pp +To overcome hardware limitations and to allow multiple applications +to share the hardware, +.Xr aucat 1 +can be used. +It exposes one or more software subdevices backed by the underlying hardware, +while doing all necessary conversions on the fly. +It can mix multiple streams or split the hardware into +multiple subdevices, to allow programs to use the hardware +concurently. +.Pp +Hardware MIDI ports correspond to serial connectors provided by the +.Xr midi 4 +driver. +They are typically used to access MIDI hardware (synthesizers, keyboards, +control surfaces, etc.), but they do not allow applications to exchange +information using the MIDI protocol. +.Pp +Software MIDI thru boxes allow one application to send MIDI data to other +applications connected to the thru box (for instance a software sequencer +can send events to multiple software synthesizers). +There's no hardware involved: thru boxes are created by +.Xr midicat 1 . +.Pp +Additionally, +.Xr aucat 1 +exposes a MIDI device used to control and monitor audio streams +in real time using MIDI. +.Sh DEVICE NAMES +From the user's perspective every audio interface, MIDI port, +.Xr aucat 1 +or +.Xr midicat 1 +service has a name of the form: +.Bd -literal -offset center +type:unit[.option] +.Ed +.Pp +This information is used by audio and MIDI applications to determine +how to access the audio or MIDI device or service. +.Bl -tag -width "option" +.It Pa type +The type of the audio or MIDI device. +Possible values for audio devices are +.Pa aucat +and +.Pa sun , +corresponding to +.Xr aucat 1 +sockets and hardware +.Xr audio 4 +devices. +Possible values for MIDI devices are +.Pa midithru , +.Pa rmidi , +and +.Pa aucat +corresponding to +.Xr midicat 1 +software MIDI thru boxes, hardware +.Xr midi 4 +ports and +.Xr aucat 1 +control through MIDI respectively. +.It Pa unit +For hardware audio or MIDI devices, this corresponds to +the character device minor number. +For audio or MIDI devices created with +.Xr aucat 1 +or +.Xr midicat 1 +it corresponds to the server +.Em unit +number, typically 0. +.It Pa option +Corresponds to the profile string registered using the +.Fl s +option of +.Xr aucat 1 . +Only meaningful for +.Pa aucat +device types. +.El +.Pp +For example: +.Pp +.Bl -tag -width "aucat:0.rear" -offset 3n -compact +.It Pa sun:0 +First hardware audio device. +.It Pa aucat:0 +Default audio device of the first +.Xr aucat 1 +audio server. +.It Pa aucat:0.rear +First +.Xr aucat 1 +server; +device registered with +.Fl s Fa rear . +.It Pa rmidi:5 +Hardware MIDI port number 5. +.It Pa midithru:0 +First software MIDI thru box created with +.Xr midicat 1 . +.It Pa aucat:0 +MIDI port controlling the first +.Xr aucat 1 +audio server. +.El +.Sh ENVIRONMENT +.Bl -tag -width "AUDIODEVICEXXX" -compact +.It Ev AUDIODEVICE +Audio device to use if the application provides +no device chooser. +.It Ev MIDIDEVICE +MIDI port to use if the application provides +no MIDI port chooser. +.El +.Pp +Environment variables are ignored by programs +with the set-user-ID or set-group-ID bits set. +.Sh FILES +.Bl -tag -width "/tmp/aucat-xxx/softaudioNXXX" -compact +.It Pa /dev/audioN +Audio devices. +.It Pa /dev/rmidiN +MIDI ports. +.It Pa /tmp/aucat-xxx/softaudioN +Audio devices provided by +.Xr aucat 1 . +.It Pa /tmp/aucat-xxx/midithruN +MIDI thru boxes provided by +.Xr midicat 1 . +.El +.Sh SEE ALSO +.Xr aucat 1 , +.Xr midicat 1 , +.Xr mio_open 3 , +.Xr sio_open 3 , +.Xr audio 4 , +.Xr midi 4 diff --git a/libsndio/sndio.c b/libsndio/sndio.c new file mode 100644 index 0000000..8f7e4b9 --- /dev/null +++ b/libsndio/sndio.c @@ -0,0 +1,599 @@ +/* $OpenBSD: sndio.c,v 1.25 2010/04/24 06:15:54 ratchov Exp $ */ +/* + * Copyright (c) 2008 Alexandre Ratchov + * + * 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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "sndio_priv.h" + +#define SIO_PAR_MAGIC 0x83b905a4 + +#ifdef DEBUG +/* + * debug level, -1 means uninitialized + */ +int sio_debug = -1; +#endif + +void +sio_initpar(struct sio_par *par) +{ + memset(par, 0xff, sizeof(struct sio_par)); + par->__magic = SIO_PAR_MAGIC; +} + +/* + * Generate a string corresponding to the encoding in par, + * return the length of the resulting string + */ +int +sio_enctostr(struct sio_par *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 != SIO_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 ... + * Return the number of bytes consumed + */ +int +sio_strtoenc(struct sio_par *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 < 1 || bits > 32) + return 0; + bps = SIO_BPS(bits); + le = SIO_LE_NATIVE; + msb = 1; + + /* + * 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 >= '1' && *p <= '4') { + bps = *p - '0'; + if (bps * 8 < bits) + return 0; + p++; + + /* + * get (optional) alignment + */ + 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; +} + +struct sio_hdl * +sio_open(const char *str, unsigned mode, int nbio) +{ + static char prefix_aucat[] = "aucat"; + static char prefix_sun[] = "sun"; + struct sio_hdl *hdl; + struct stat sb; + char *sep, buf[NAME_MAX]; + int len; +#ifdef DEBUG + char *dbg; + + if (sio_debug < 0) { + dbg = issetugid() ? NULL : getenv("SIO_DEBUG"); + if (!dbg || sscanf(dbg, "%u", &sio_debug) != 1) + sio_debug = 0; + } +#endif + if ((mode & (SIO_PLAY | SIO_REC)) == 0) + return NULL; + if (str == NULL && !issetugid()) + str = getenv("AUDIODEVICE"); + if (str == NULL) { + hdl = sio_open_aucat("0", mode, nbio); + if (hdl != NULL) + return hdl; + if (stat("/dev/audio", &sb) == 0 && S_ISCHR(sb.st_mode)) { + snprintf(buf, sizeof(buf), "%u", + minor(sb.st_rdev) & 0xf); + } else + strlcpy(buf, "0", sizeof(buf)); + return sio_open_sun(buf, mode, nbio); + } + sep = strchr(str, ':'); + if (sep == NULL) { + /* + * try legacy "/dev/audioxxx" or ``socket'' device name + */ + if (stat(str, &sb) < 0 || !S_ISCHR(sb.st_mode)) { + snprintf(buf, sizeof(buf), "0.%s", str); + return sio_open_aucat(buf, mode, nbio); + } + snprintf(buf, sizeof(buf), "%u", minor(sb.st_rdev) & 0xf); + return sio_open_sun(buf, mode, nbio); + } + len = sep - str; + if (len == (sizeof(prefix_aucat) - 1) && + memcmp(str, prefix_aucat, len) == 0) + return sio_open_aucat(sep + 1, mode, nbio); + if (len == (sizeof(prefix_sun) - 1) && + memcmp(str, prefix_sun, len) == 0) + return sio_open_sun(sep + 1, mode, nbio); + DPRINTF("sio_open: %s: unknown device type\n", str); + return NULL; +} + +void +sio_create(struct sio_hdl *hdl, struct sio_ops *ops, unsigned mode, int nbio) +{ + hdl->ops = ops; + hdl->mode = mode; + hdl->nbio = nbio; + hdl->started = 0; + hdl->eof = 0; + hdl->move_cb = NULL; + hdl->vol_cb = NULL; +} + +void +sio_close(struct sio_hdl *hdl) +{ + hdl->ops->close(hdl); +} + +int +sio_start(struct sio_hdl *hdl) +{ + if (hdl->eof) { + DPRINTF("sio_start: eof\n"); + return 0; + } + if (hdl->started) { + DPRINTF("sio_start: already started\n"); + hdl->eof = 1; + return 0; + } +#ifdef DEBUG + if (!sio_getpar(hdl, &hdl->par)) + return 0; + hdl->pollcnt = hdl->wcnt = hdl->rcnt = hdl->realpos = 0; + gettimeofday(&hdl->tv, NULL); +#endif + if (!hdl->ops->start(hdl)) + return 0; + hdl->started = 1; + return 1; +} + +int +sio_stop(struct sio_hdl *hdl) +{ + if (hdl->eof) { + DPRINTF("sio_stop: eof\n"); + return 0; + } + if (!hdl->started) { + DPRINTF("sio_stop: not started\n"); + hdl->eof = 1; + return 0; + } + if (!hdl->ops->stop(hdl)) + return 0; +#ifdef DEBUG + DPRINTF("libsndio: polls: %llu, written = %llu, read: %llu\n", + hdl->pollcnt, hdl->wcnt, hdl->rcnt); +#endif + hdl->started = 0; + return 1; +} + +int +sio_setpar(struct sio_hdl *hdl, struct sio_par *par) +{ + if (hdl->eof) { + DPRINTF("sio_setpar: eof\n"); + return 0; + } + if (par->__magic != SIO_PAR_MAGIC) { + DPRINTF("sio_setpar: use of uninitialized sio_par structure\n"); + hdl->eof = 1; + return 0; + } + if (hdl->started) { + DPRINTF("sio_setpar: already started\n"); + hdl->eof = 1; + return 0; + } + if (par->bufsz != ~0U) { + DPRINTF("sio_setpar: setting bufsz is deprecated\n"); + par->appbufsz = par->bufsz; + } + if (par->rate != ~0U && par->appbufsz == ~0U) + par->appbufsz = par->rate * 200 / 1000; + return hdl->ops->setpar(hdl, par); +} + +int +sio_getpar(struct sio_hdl *hdl, struct sio_par *par) +{ + if (hdl->eof) { + DPRINTF("sio_getpar: eof\n"); + return 0; + } + if (hdl->started) { + DPRINTF("sio_getpar: already started\n"); + hdl->eof = 1; + return 0; + } + if (!hdl->ops->getpar(hdl, par)) { + par->__magic = 0; + return 0; + } + par->__magic = 0; + return 1; +} + +int +sio_getcap(struct sio_hdl *hdl, struct sio_cap *cap) +{ + if (hdl->eof) { + DPRINTF("sio_getcap: eof\n"); + return 0; + } + if (hdl->started) { + DPRINTF("sio_getcap: already started\n"); + hdl->eof = 1; + return 0; + } + return hdl->ops->getcap(hdl, cap); +} + +static int +sio_psleep(struct sio_hdl *hdl, int event) +{ + struct pollfd pfd; + int revents; + + for (;;) { + sio_pollfd(hdl, &pfd, event); + while (poll(&pfd, 1, -1) < 0) { + if (errno == EINTR) + continue; + DPERROR("sio_psleep: poll"); + hdl->eof = 1; + return 0; + } + revents = sio_revents(hdl, &pfd); + if (revents & POLLHUP) { + DPRINTF("sio_psleep: hang-up\n"); + return 0; + } + if (revents & event) + break; + } + return 1; +} + +size_t +sio_read(struct sio_hdl *hdl, void *buf, size_t len) +{ + unsigned n; + char *data = buf; + size_t todo = len; + + if (hdl->eof) { + DPRINTF("sio_read: eof\n"); + return 0; + } + if (!hdl->started || !(hdl->mode & SIO_REC)) { + DPRINTF("sio_read: recording not started\n"); + hdl->eof = 1; + return 0; + } + if (todo == 0) { + DPRINTF("sio_read: zero length read ignored\n"); + return 0; + } + while (todo > 0) { + n = hdl->ops->read(hdl, data, todo); + if (n == 0) { + if (hdl->nbio || hdl->eof || todo < len) + break; + if (!sio_psleep(hdl, POLLIN)) + break; + continue; + } + data += n; + todo -= n; +#ifdef DEBUG + hdl->rcnt += n; +#endif + } + return len - todo; +} + +size_t +sio_write(struct sio_hdl *hdl, const void *buf, size_t len) +{ + unsigned n; + const unsigned char *data = buf; + size_t todo = len; +#ifdef DEBUG + struct timeval tv0, tv1, dtv; + unsigned us; + + if (sio_debug >= 2) + gettimeofday(&tv0, NULL); +#endif + + if (hdl->eof) { + DPRINTF("sio_write: eof\n"); + return 0; + } + if (!hdl->started || !(hdl->mode & SIO_PLAY)) { + DPRINTF("sio_write: playback not started\n"); + hdl->eof = 1; + return 0; + } + if (todo == 0) { + DPRINTF("sio_write: zero length write ignored\n"); + return 0; + } + while (todo > 0) { + n = hdl->ops->write(hdl, data, todo); + if (n == 0) { + if (hdl->nbio || hdl->eof) + break; + if (!sio_psleep(hdl, POLLOUT)) + break; + continue; + } + data += n; + todo -= n; +#ifdef DEBUG + hdl->wcnt += n; +#endif + } +#ifdef DEBUG + if (sio_debug >= 2) { + gettimeofday(&tv1, NULL); + timersub(&tv0, &hdl->tv, &dtv); + DPRINTF("%ld.%06ld: ", dtv.tv_sec, dtv.tv_usec); + + timersub(&tv1, &tv0, &dtv); + us = dtv.tv_sec * 1000000 + dtv.tv_usec; + DPRINTF( + "sio_write: wrote %d bytes of %d in %uus\n", + (int)(len - todo), (int)len, us); + } +#endif + return len - todo; +} + +int +sio_nfds(struct sio_hdl *hdl) +{ + /* + * In the future we might use larger values + */ + return 1; +} + +int +sio_pollfd(struct sio_hdl *hdl, struct pollfd *pfd, int events) +{ + if (hdl->eof) + return 0; + if (!hdl->started) + events = 0; + return hdl->ops->pollfd(hdl, pfd, events); +} + +int +sio_revents(struct sio_hdl *hdl, struct pollfd *pfd) +{ + int revents; +#ifdef DEBUG + struct timeval tv0, tv1, dtv; + unsigned us; + + if (sio_debug >= 2) + gettimeofday(&tv0, NULL); +#endif + if (hdl->eof) + return POLLHUP; +#ifdef DEBUG + hdl->pollcnt++; +#endif + revents = hdl->ops->revents(hdl, pfd); + if (!hdl->started) + return revents & POLLHUP; +#ifdef DEBUG + if (sio_debug >= 2) { + gettimeofday(&tv1, NULL); + timersub(&tv0, &hdl->tv, &dtv); + DPRINTF("%ld.%06ld: ", dtv.tv_sec, dtv.tv_usec); + + timersub(&tv1, &tv0, &dtv); + us = dtv.tv_sec * 1000000 + dtv.tv_usec; + DPRINTF("sio_revents: revents = 0x%x, complete in %uus\n", + revents, us); + } +#endif + return revents; +} + +int +sio_eof(struct sio_hdl *hdl) +{ + return hdl->eof; +} + +void +sio_onmove(struct sio_hdl *hdl, void (*cb)(void *, int), void *addr) +{ + if (hdl->started) { + DPRINTF("sio_onmove: already started\n"); + hdl->eof = 1; + return; + } + hdl->move_cb = cb; + hdl->move_addr = addr; +} + +void +sio_onmove_cb(struct sio_hdl *hdl, int delta) +{ +#ifdef DEBUG + struct timeval tv0, dtv; + long long playpos; + + if (sio_debug >= 3 && (hdl->mode & SIO_PLAY)) { + gettimeofday(&tv0, NULL); + timersub(&tv0, &hdl->tv, &dtv); + DPRINTF("%ld.%06ld: ", dtv.tv_sec, dtv.tv_usec); + hdl->realpos += delta; + playpos = hdl->wcnt / (hdl->par.bps * hdl->par.pchan); + DPRINTF("sio_onmove_cb: delta = %+7d, " + "plat = %+7lld, " + "realpos = %+7lld, " + "bufused = %+7lld\n", + delta, + playpos - hdl->realpos, + hdl->realpos, + hdl->realpos < 0 ? playpos : playpos - hdl->realpos); + } +#endif + if (hdl->move_cb) + hdl->move_cb(hdl->move_addr, delta); +} + +int +sio_setvol(struct sio_hdl *hdl, unsigned ctl) +{ + if (hdl->eof) + return 0; + if (!hdl->ops->setvol(hdl, ctl)) + return 0; + hdl->ops->getvol(hdl); + return 1; +} + +void +sio_onvol(struct sio_hdl *hdl, void (*cb)(void *, unsigned), void *addr) +{ + if (hdl->started) { + DPRINTF("sio_onvol: already started\n"); + hdl->eof = 1; + return; + } + hdl->vol_cb = cb; + hdl->vol_addr = addr; + hdl->ops->getvol(hdl); +} + +void +sio_onvol_cb(struct sio_hdl *hdl, unsigned ctl) +{ + if (hdl->vol_cb) + hdl->vol_cb(hdl->vol_addr, ctl); +} diff --git a/libsndio/sndio.h b/libsndio/sndio.h new file mode 100644 index 0000000..c3fcde7 --- /dev/null +++ b/libsndio/sndio.h @@ -0,0 +1,164 @@ +/* $OpenBSD: sndio.h,v 1.3 2009/07/25 11:27:14 ratchov Exp $ */ +/* + * Copyright (c) 2008 Alexandre Ratchov + * + * 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 SNDIO_H +#define SNDIO_H + +#include + +/* + * private ``handle'' structure + */ +struct sio_hdl; +struct mio_hdl; + +/* + * parameters of a full-duplex stream + */ +struct sio_par { + unsigned bits; /* bits per sample */ + unsigned bps; /* bytes per sample */ + unsigned sig; /* 1 = signed, 0 = unsigned */ + unsigned le; /* 1 = LE, 0 = BE byte order */ + unsigned msb; /* 1 = MSB, 0 = LSB aligned */ + unsigned rchan; /* number channels for recording direction */ + unsigned pchan; /* number channels for playback direction */ + unsigned rate; /* frames per second */ + unsigned bufsz; /* end-to-end buffer size */ +#define SIO_IGNORE 0 /* pause during xrun */ +#define SIO_SYNC 1 /* resync after xrun */ +#define SIO_ERROR 2 /* terminate on xrun */ + unsigned xrun; /* what to do on overruns/underruns */ + unsigned round; /* optimal bufsz divisor */ + unsigned appbufsz; /* minimum buffer size */ + int __pad[3]; /* for future use */ + int __magic; /* for internal/debug purposes only */ +}; + +/* + * capabilities of a stream + */ +struct sio_cap { +#define SIO_NENC 8 +#define SIO_NCHAN 8 +#define SIO_NRATE 16 +#define SIO_NCONF 4 + struct sio_enc { /* allowed sample encodings */ + unsigned bits; + unsigned bps; + unsigned sig; + unsigned le; + unsigned msb; + } enc[SIO_NENC]; + unsigned rchan[SIO_NCHAN]; /* allowed values for rchan */ + unsigned pchan[SIO_NCHAN]; /* allowed values for pchan */ + unsigned rate[SIO_NRATE]; /* allowed rates */ + int __pad[7]; /* for future use */ + unsigned nconf; /* number of elements in confs[] */ + struct sio_conf { + unsigned enc; /* mask of enc[] indexes */ + unsigned rchan; /* mask of chan[] indexes (rec) */ + unsigned pchan; /* mask of chan[] indexes (play) */ + unsigned rate; /* mask of rate[] indexes */ + } confs[SIO_NCONF]; +}; + +#define SIO_XSTRINGS { "ignore", "sync", "error" } + +/* + * mode bitmap + */ +#define SIO_PLAY 1 +#define SIO_REC 2 +#define MIO_OUT 4 +#define MIO_IN 8 + +/* + * maximum size of the encording string (the longest possible + * encoding is ``s24le3msb'') + */ +#define SIO_ENCMAX 10 + +/* + * default bytes per sample for the given bits per sample + */ +#define SIO_BPS(bits) (((bits) <= 8) ? 1 : (((bits) <= 16) ? 2 : 4)) + +/* + * default value of "sio_par->le" flag + */ +#if BYTE_ORDER == LITTLE_ENDIAN +#define SIO_LE_NATIVE 1 +#else +#define SIO_LE_NATIVE 0 +#endif + +/* + * default device for the sun audio(4) back-end + */ +#define SIO_SUN_PATH "/dev/audio" + +/* + * default socket name for the aucat(1) back-end + */ +#define SIO_AUCAT_PATH "default" + +/* + * maximum value of volume, eg. for sio_setvol() + */ +#define SIO_MAXVOL 127 + +#ifdef __cplusplus +extern "C" { +#endif + +struct pollfd; + +int sio_strtoenc(struct sio_par *, char *); +int sio_enctostr(struct sio_par *, char *); +void sio_initpar(struct sio_par *); + +struct sio_hdl *sio_open(const char *, unsigned, int); +void sio_close(struct sio_hdl *); +int sio_setpar(struct sio_hdl *, struct sio_par *); +int sio_getpar(struct sio_hdl *, struct sio_par *); +int sio_getcap(struct sio_hdl *, struct sio_cap *); +void sio_onmove(struct sio_hdl *, void (*)(void *, int), void *); +size_t sio_write(struct sio_hdl *, const void *, size_t); +size_t sio_read(struct sio_hdl *, void *, size_t); +int sio_start(struct sio_hdl *); +int sio_stop(struct sio_hdl *); +int sio_nfds(struct sio_hdl *); +int sio_pollfd(struct sio_hdl *, struct pollfd *, int); +int sio_revents(struct sio_hdl *, struct pollfd *); +int sio_eof(struct sio_hdl *); +int sio_setvol(struct sio_hdl *, unsigned); +void sio_onvol(struct sio_hdl *, void (*)(void *, unsigned), void *); + +struct mio_hdl *mio_open(const char *, unsigned, int); +void mio_close(struct mio_hdl *); +size_t mio_write(struct mio_hdl *, const void *, size_t); +size_t mio_read(struct mio_hdl *, void *, size_t); +int mio_nfds(struct mio_hdl *); +int mio_pollfd(struct mio_hdl *, struct pollfd *, int); +int mio_revents(struct mio_hdl *, struct pollfd *); +int mio_eof(struct mio_hdl *); + +#ifdef __cplusplus +} +#endif + +#endif /* !defined(SNDIO_H) */ diff --git a/libsndio/sndio_priv.h b/libsndio/sndio_priv.h new file mode 100644 index 0000000..4f1c6fa --- /dev/null +++ b/libsndio/sndio_priv.h @@ -0,0 +1,91 @@ +/* $OpenBSD: sndio_priv.h,v 1.8 2009/07/25 11:15:56 ratchov Exp $ */ +/* + * Copyright (c) 2008 Alexandre Ratchov + * + * 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 SNDIO_PRIV_H +#define SNDIO_PRIV_H + +#include +#include "sndio.h" + +#ifdef DEBUG +#define DPRINTF(...) \ + do { \ + if (sio_debug > 0) \ + fprintf(stderr, __VA_ARGS__); \ + } while(0) +#define DPERROR(s) \ + do { \ + if (sio_debug > 0) \ + perror(s); \ + } while(0) +#else +#define DPRINTF(...) do {} while(0) +#define DPERROR(s) do {} while(0) +#endif + +/* + * private ``handle'' structure + */ +struct sio_hdl { + struct sio_ops *ops; + void (*move_cb)(void *, int); /* call-back for realpos changes */ + void *move_addr; /* user priv. data for move_cb */ + void (*vol_cb)(void *, unsigned); /* call-back for volume changes */ + void *vol_addr; /* user priv. data for vol_cb */ + unsigned mode; /* SIO_PLAY | SIO_REC */ + int started; /* true if started */ + int nbio; /* true if non-blocking io */ + int eof; /* true if error occured */ +#ifdef DEBUG + unsigned long long pollcnt; /* times sio_revents was called */ + unsigned long long wcnt; /* bytes written with sio_write() */ + unsigned long long rcnt; /* bytes read with sio_read() */ + long long realpos; + struct timeval tv; + struct sio_par par; +#endif +}; + +/* + * operations every device should support + */ +struct sio_ops { + void (*close)(struct sio_hdl *); + int (*setpar)(struct sio_hdl *, struct sio_par *); + int (*getpar)(struct sio_hdl *, struct sio_par *); + int (*getcap)(struct sio_hdl *, struct sio_cap *); + size_t (*write)(struct sio_hdl *, const void *, size_t); + size_t (*read)(struct sio_hdl *, void *, size_t); + int (*start)(struct sio_hdl *); + int (*stop)(struct sio_hdl *); + int (*pollfd)(struct sio_hdl *, struct pollfd *, int); + int (*revents)(struct sio_hdl *, struct pollfd *); + int (*setvol)(struct sio_hdl *, unsigned); + void (*getvol)(struct sio_hdl *); +}; + +struct sio_hdl *sio_open_aucat(const char *, unsigned, int); +struct sio_hdl *sio_open_sun(const char *, unsigned, int); +void sio_create(struct sio_hdl *, struct sio_ops *, unsigned, int); +void sio_destroy(struct sio_hdl *); +void sio_onmove_cb(struct sio_hdl *, int); +void sio_onvol_cb(struct sio_hdl *, unsigned); + +#ifdef DEBUG +extern int sio_debug; +#endif + +#endif /* !defined(SNDIO_PRIV_H) */ diff --git a/libsndio/sun.c b/libsndio/sun.c new file mode 100644 index 0000000..946e493 --- /dev/null +++ b/libsndio/sun.c @@ -0,0 +1,969 @@ +/* $OpenBSD: sun.c,v 1.40 2010/08/06 06:52:17 ratchov Exp $ */ +/* + * Copyright (c) 2008 Alexandre Ratchov + * + * 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: + * + * remove filling code from sun_write() and create sun_fill() + * + * allow block size to be set + * + * call hdl->cb_pos() from sun_read() and sun_write(), or better: + * implement generic blocking sio_read() and sio_write() with poll(2) + * and use non-blocking sio_ops only + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sndio_priv.h" + +struct sun_hdl { + struct sio_hdl sio; + int fd; + int filling; + unsigned ibpf, obpf; /* bytes per frame */ + unsigned ibytes, obytes; /* bytes the hw transfered */ + unsigned ierr, oerr; /* frames the hw dropped */ + int offset; /* frames play is ahead of record */ + int idelta, odelta; /* position reported to client */ + int mix_fd, mix_index; /* /dev/mixerN stuff */ +}; + +static void sun_close(struct sio_hdl *); +static int sun_start(struct sio_hdl *); +static int sun_stop(struct sio_hdl *); +static int sun_setpar(struct sio_hdl *, struct sio_par *); +static int sun_getpar(struct sio_hdl *, struct sio_par *); +static int sun_getcap(struct sio_hdl *, struct sio_cap *); +static size_t sun_read(struct sio_hdl *, void *, size_t); +static size_t sun_write(struct sio_hdl *, const void *, size_t); +static int sun_pollfd(struct sio_hdl *, struct pollfd *, int); +static int sun_revents(struct sio_hdl *, struct pollfd *); +static int sun_setvol(struct sio_hdl *, unsigned); +static void sun_getvol(struct sio_hdl *); + +static struct sio_ops sun_ops = { + sun_close, + sun_setpar, + sun_getpar, + sun_getcap, + sun_write, + sun_read, + sun_start, + sun_stop, + sun_pollfd, + sun_revents, + sun_setvol, + sun_getvol +}; + +/* + * convert sun encoding to sio_par encoding + */ +static int +sun_infotoenc(struct sun_hdl *hdl, struct audio_prinfo *ai, struct sio_par *par) +{ + par->msb = ai->msb; + par->bits = ai->precision; + par->bps = ai->bps; + switch (ai->encoding) { + case AUDIO_ENCODING_SLINEAR_LE: + par->le = 1; + par->sig = 1; + break; + case AUDIO_ENCODING_SLINEAR_BE: + par->le = 0; + par->sig = 1; + break; + case AUDIO_ENCODING_ULINEAR_LE: + par->le = 1; + par->sig = 0; + break; + case AUDIO_ENCODING_ULINEAR_BE: + par->le = 0; + par->sig = 0; + break; + case AUDIO_ENCODING_SLINEAR: + par->le = SIO_LE_NATIVE; + par->sig = 1; + break; + case AUDIO_ENCODING_ULINEAR: + par->le = SIO_LE_NATIVE; + par->sig = 0; + break; + default: + DPRINTF("sun_infotoenc: unsupported encoding\n"); + hdl->sio.eof = 1; + return 0; + } + return 1; +} + +/* + * convert sio_par encoding to sun encoding + */ +static void +sun_enctoinfo(struct sun_hdl *hdl, unsigned *renc, struct sio_par *par) +{ + if (par->le == ~0U && par->sig == ~0U) { + *renc = ~0U; + } else if (par->le == ~0U || par->sig == ~0U) { + *renc = AUDIO_ENCODING_SLINEAR; + } else if (par->le && par->sig) { + *renc = AUDIO_ENCODING_SLINEAR_LE; + } else if (!par->le && par->sig) { + *renc = AUDIO_ENCODING_SLINEAR_BE; + } else if (par->le && !par->sig) { + *renc = AUDIO_ENCODING_ULINEAR_LE; + } else { + *renc = AUDIO_ENCODING_ULINEAR_BE; + } +} + +/* + * try to set the device to the given parameters and check that the + * device can use them; return 1 on success, 0 on failure or error + */ +static int +sun_tryinfo(struct sun_hdl *hdl, struct sio_enc *enc, + unsigned pchan, unsigned rchan, unsigned rate) +{ + struct audio_info aui; + struct audio_prinfo *pr; + + pr = (hdl->sio.mode & SIO_PLAY) ? &aui.play : &aui.record; + + AUDIO_INITINFO(&aui); + if (enc) { + if (enc->le && enc->sig) { + pr->encoding = AUDIO_ENCODING_SLINEAR_LE; + } else if (!enc->le && enc->sig) { + pr->encoding = AUDIO_ENCODING_SLINEAR_BE; + } else if (enc->le && !enc->sig) { + pr->encoding = AUDIO_ENCODING_ULINEAR_LE; + } else { + pr->encoding = AUDIO_ENCODING_ULINEAR_BE; + } + pr->precision = enc->bits; + } + if (rate) + pr->sample_rate = rate; + if ((hdl->sio.mode & (SIO_PLAY | SIO_REC)) == (SIO_PLAY | SIO_REC)) + aui.record = aui.play; + if (pchan && (hdl->sio.mode & SIO_PLAY)) + aui.play.channels = pchan; + if (rchan && (hdl->sio.mode & SIO_REC)) + aui.record.channels = rchan; + if (ioctl(hdl->fd, AUDIO_SETINFO, &aui) < 0) { + if (errno == EINVAL) + return 0; + DPERROR("sun_tryinfo: setinfo"); + hdl->sio.eof = 1; + return 0; + } + if (ioctl(hdl->fd, AUDIO_GETINFO, &aui) < 0) { + DPERROR("sun_tryinfo: getinfo"); + hdl->sio.eof = 1; + return 0; + } + if (pchan && aui.play.channels != pchan) + return 0; + if (rchan && aui.record.channels != rchan) + return 0; + if (rate) { + if ((hdl->sio.mode & SIO_PLAY) && + (aui.play.sample_rate != rate)) + return 0; + if ((hdl->sio.mode & SIO_REC) && + (aui.record.sample_rate != rate)) + return 0; + } + return 1; +} + +/* + * guess device capabilities + */ +static int +sun_getcap(struct sio_hdl *sh, struct sio_cap *cap) +{ +#define NCHANS (sizeof(chans) / sizeof(chans[0])) +#define NRATES (sizeof(rates) / sizeof(rates[0])) + static unsigned chans[] = { + 1, 2, 4, 6, 8, 10, 12 + }; + static unsigned rates[] = { + 8000, 11025, 12000, 16000, 22050, 24000, + 32000, 44100, 48000, 64000, 88200, 96000 + }; + struct sun_hdl *hdl = (struct sun_hdl *)sh; + struct sio_par savepar; + struct audio_encoding ae; + unsigned nenc = 0, nconf = 0; + unsigned enc_map = 0, rchan_map = 0, pchan_map = 0, rate_map; + unsigned i, j, conf; + + if (!sun_getpar(&hdl->sio, &savepar)) + return 0; + + /* + * fill encoding list + */ + for (ae.index = 0; nenc < SIO_NENC; ae.index++) { + if (ioctl(hdl->fd, AUDIO_GETENC, &ae) < 0) { + if (errno == EINVAL) + break; + DPERROR("sun_getcap: getenc"); + hdl->sio.eof = 1; + return 0; + } + if (ae.flags & AUDIO_ENCODINGFLAG_EMULATED) + continue; + if (ae.encoding == AUDIO_ENCODING_SLINEAR_LE) { + cap->enc[nenc].le = 1; + cap->enc[nenc].sig = 1; + } else if (ae.encoding == AUDIO_ENCODING_SLINEAR_BE) { + cap->enc[nenc].le = 0; + cap->enc[nenc].sig = 1; + } else if (ae.encoding == AUDIO_ENCODING_ULINEAR_LE) { + cap->enc[nenc].le = 1; + cap->enc[nenc].sig = 0; + } else if (ae.encoding == AUDIO_ENCODING_ULINEAR_BE) { + cap->enc[nenc].le = 0; + cap->enc[nenc].sig = 0; + } else if (ae.encoding == AUDIO_ENCODING_SLINEAR) { + cap->enc[nenc].le = SIO_LE_NATIVE; + cap->enc[nenc].sig = 1; + } else if (ae.encoding == AUDIO_ENCODING_ULINEAR) { + cap->enc[nenc].le = SIO_LE_NATIVE; + cap->enc[nenc].sig = 0; + } else { + /* unsipported encoding */ + continue; + } + cap->enc[nenc].bits = ae.precision; + cap->enc[nenc].bps = ae.bps; + cap->enc[nenc].msb = ae.msb; + enc_map |= (1 << nenc); + nenc++; + } + + /* + * fill channels + * + * for now we're lucky: all kernel devices assume that the + * number of channels and the encoding are independent so we can + * use the current encoding and try various channels. + */ + if (hdl->sio.mode & SIO_PLAY) { + memcpy(&cap->pchan, chans, NCHANS * sizeof(unsigned)); + for (i = 0; i < NCHANS; i++) { + if (sun_tryinfo(hdl, NULL, chans[i], 0, 0)) + pchan_map |= (1 << i); + } + } + if (hdl->sio.mode & SIO_REC) { + memcpy(&cap->rchan, chans, NCHANS * sizeof(unsigned)); + for (i = 0; i < NCHANS; i++) { + if (sun_tryinfo(hdl, NULL, 0, chans[i], 0)) + rchan_map |= (1 << i); + } + } + + /* + * fill rates + * + * rates are not independent from other parameters (eg. on + * uaudio devices), so certain rates may not be allowed with + * certain encodings. We have to check rates for all encodings + */ + memcpy(&cap->rate, rates, NRATES * sizeof(unsigned)); + for (j = 0; j < nenc; j++) { + rate_map = 0; + for (i = 0; i < NRATES; i++) { + if (sun_tryinfo(hdl, &cap->enc[j], 0, 0, rates[i])) + rate_map |= (1 << i); + } + for (conf = 0; conf < nconf; conf++) { + if (cap->confs[conf].rate == rate_map) { + cap->confs[conf].enc |= (1 << j); + break; + } + } + if (conf == nconf) { + if (nconf == SIO_NCONF) + break; + cap->confs[nconf].enc = (1 << j); + cap->confs[nconf].pchan = pchan_map; + cap->confs[nconf].rchan = rchan_map; + cap->confs[nconf].rate = rate_map; + nconf++; + } + } + cap->nconf = nconf; + if (!sun_setpar(&hdl->sio, &savepar)) + return 0; + return 1; +#undef NCHANS +#undef NRATES +} + +static void +sun_getvol(struct sio_hdl *sh) +{ + struct sun_hdl *hdl = (struct sun_hdl *)sh; + + sio_onvol_cb(&hdl->sio, SIO_MAXVOL); +} + +int +sun_setvol(struct sio_hdl *sh, unsigned vol) +{ + return 1; +} + +struct sio_hdl * +sio_open_sun(const char *str, unsigned mode, int nbio) +{ + int fd, flags, fullduplex; + struct audio_info aui; + struct sun_hdl *hdl; + struct sio_par par; + char path[PATH_MAX]; + + hdl = malloc(sizeof(struct sun_hdl)); + if (hdl == NULL) + return NULL; + sio_create(&hdl->sio, &sun_ops, mode, nbio); + + snprintf(path, sizeof(path), "/dev/audio%s", str); + if (mode == (SIO_PLAY | SIO_REC)) + flags = O_RDWR; + else + flags = (mode & SIO_PLAY) ? O_WRONLY : O_RDONLY; + + while ((fd = open(path, flags | O_NONBLOCK)) < 0) { + if (errno == EINTR) + continue; + DPERROR(path); + goto bad_free; + } + if (fcntl(fd, F_SETFD, FD_CLOEXEC) < 0) { + DPERROR("FD_CLOEXEC"); + goto bad_close; + } + + /* + * pause the device + */ + AUDIO_INITINFO(&aui); + if (mode & SIO_PLAY) + aui.play.pause = 1; + if (mode & SIO_REC) + aui.record.pause = 1; + if (ioctl(fd, AUDIO_SETINFO, &aui) < 0) { + DPERROR("sio_open_sun: setinfo"); + goto bad_close; + } + /* + * If both play and record are requested then + * set full duplex mode. + */ + if (mode == (SIO_PLAY | SIO_REC)) { + fullduplex = 1; + if (ioctl(fd, AUDIO_SETFD, &fullduplex) < 0) { + DPRINTF("sio_open_sun: %s: can't set full-duplex\n", path); + goto bad_close; + } + } + hdl->fd = fd; + + /* + * Default parameters may not be compatible with libsndio (eg. mulaw + * encodings, different playback and recording parameters, etc...), so + * set parameters to a random value. If the requested parameters are + * not supported by the device, then sio_setpar() will pick supported + * ones. + */ + sio_initpar(&par); + par.rate = 48000; + par.le = SIO_LE_NATIVE; + par.sig = 1; + par.bits = 16; + par.appbufsz = 1200; + if (!sio_setpar(&hdl->sio, &par)) + goto bad_close; + return (struct sio_hdl *)hdl; + bad_close: + while (close(fd) < 0 && errno == EINTR) + ; /* retry */ + bad_free: + free(hdl); + return NULL; +} + +static void +sun_close(struct sio_hdl *sh) +{ + struct sun_hdl *hdl = (struct sun_hdl *)sh; + + while (close(hdl->fd) < 0 && errno == EINTR) + ; /* retry */ + free(hdl); +} + +static int +sun_start(struct sio_hdl *sh) +{ + struct sio_par par; + struct sun_hdl *hdl = (struct sun_hdl *)sh; + struct audio_info aui; + + if (!sio_getpar(&hdl->sio, &par)) + return 0; + hdl->obpf = par.pchan * par.bps; + hdl->ibpf = par.rchan * par.bps; + hdl->ibytes = 0; + hdl->obytes = 0; + hdl->ierr = 0; + hdl->oerr = 0; + hdl->offset = 0; + hdl->idelta = 0; + hdl->odelta = 0; + + if (hdl->sio.mode & SIO_PLAY) { + /* + * keep the device paused and let sun_write() trigger the + * start later, to avoid buffer underruns + */ + hdl->filling = 1; + } else { + /* + * no play buffers to fill, start now! + */ + AUDIO_INITINFO(&aui); + if (hdl->sio.mode & SIO_REC) + aui.record.pause = 0; + if (ioctl(hdl->fd, AUDIO_SETINFO, &aui) < 0) { + DPERROR("sun_start: setinfo"); + hdl->sio.eof = 1; + return 0; + } + hdl->filling = 0; + sio_onmove_cb(&hdl->sio, 0); + } + return 1; +} + +static int +sun_stop(struct sio_hdl *sh) +{ + struct sun_hdl *hdl = (struct sun_hdl *)sh; + struct audio_info aui; + int mode; + + if (ioctl(hdl->fd, AUDIO_GETINFO, &aui) < 0) { + DPERROR("sun_stop: getinfo"); + hdl->sio.eof = 1; + return 0; + } + mode = aui.mode; + + /* + * there's no way to drain the device without blocking, so just + * stop it until the kernel driver get fixed + */ + AUDIO_INITINFO(&aui); + aui.mode = 0; + if (hdl->sio.mode & SIO_PLAY) + aui.play.pause = 1; + if (hdl->sio.mode & SIO_REC) + aui.record.pause = 1; + if (ioctl(hdl->fd, AUDIO_SETINFO, &aui) < 0) { + DPERROR("sun_stop: setinfo1"); + hdl->sio.eof = 1; + return 0; + } + AUDIO_INITINFO(&aui); + aui.mode = mode; + if (ioctl(hdl->fd, AUDIO_SETINFO, &aui) < 0) { + DPERROR("sun_stop: setinfo2"); + hdl->sio.eof = 1; + return 0; + } + return 1; +} + +static int +sun_setpar(struct sio_hdl *sh, struct sio_par *par) +{ +#define NRETRIES 8 + struct sun_hdl *hdl = (struct sun_hdl *)sh; + struct audio_info aui; + unsigned i, infr, ibpf, onfr, obpf; + unsigned bufsz, round; + unsigned rate, req_rate, prec, enc; + + /* + * try to set parameters until the device accepts + * a common encoding and rate for play and record + */ + rate = par->rate; + prec = par->bits; + sun_enctoinfo(hdl, &enc, par); + for (i = 0;; i++) { + if (i == NRETRIES) { + DPRINTF("sun_setpar: couldn't set parameters\n"); + hdl->sio.eof = 1; + return 0; + } + AUDIO_INITINFO(&aui); + if (hdl->sio.mode & SIO_PLAY) { + aui.play.sample_rate = rate; + aui.play.precision = prec; + aui.play.encoding = enc; + aui.play.channels = par->pchan; + } + if (hdl->sio.mode & SIO_REC) { + aui.record.sample_rate = rate; + aui.record.precision = prec; + aui.record.encoding = enc; + aui.record.channels = par->rchan; + } + DPRINTF("sun_setpar: %i: trying pars = %u/%u/%u\n", + i, rate, prec, enc); + if (ioctl(hdl->fd, AUDIO_SETINFO, &aui) < 0 && errno != EINVAL) { + DPERROR("sun_setpar: setinfo(pars)"); + hdl->sio.eof = 1; + return 0; + } + if (ioctl(hdl->fd, AUDIO_GETINFO, &aui) < 0) { + DPERROR("sun_setpar: getinfo(pars)"); + hdl->sio.eof = 1; + return 0; + } + enc = (hdl->sio.mode & SIO_REC) ? + aui.record.encoding : aui.play.encoding; + switch (enc) { + case AUDIO_ENCODING_SLINEAR_LE: + case AUDIO_ENCODING_SLINEAR_BE: + case AUDIO_ENCODING_ULINEAR_LE: + case AUDIO_ENCODING_ULINEAR_BE: + case AUDIO_ENCODING_SLINEAR: + case AUDIO_ENCODING_ULINEAR: + break; + default: + DPRINTF("sun_setpar: couldn't set linear encoding\n"); + hdl->sio.eof = 1; + return 0; + } + if (hdl->sio.mode != (SIO_REC | SIO_PLAY)) + break; + if (aui.play.sample_rate == aui.record.sample_rate && + aui.play.precision == aui.record.precision && + aui.play.encoding == aui.record.encoding) + break; + if (i < NRETRIES / 2) { + rate = aui.play.sample_rate; + prec = aui.play.precision; + enc = aui.play.encoding; + } else { + rate = aui.record.sample_rate; + prec = aui.record.precision; + enc = aui.record.encoding; + } + } + + /* + * If the rate that the hardware is using is different than + * the requested rate, scale buffer sizes so they will be the + * same time duration as what was requested. This just gets + * the rates to use for scaling, that actual scaling is done + * later. + */ + rate = (hdl->sio.mode & SIO_REC) ? aui.record.sample_rate : + aui.play.sample_rate; + req_rate = rate; + if (par->rate && par->rate != ~0U) + req_rate = par->rate; + + /* + * if block size and buffer size are not both set then + * set the blocksize to half the buffer size + */ + bufsz = par->appbufsz; + round = par->round; + if (bufsz != ~0U) { + bufsz = bufsz * rate / req_rate; + if (round == ~0U) + round = (bufsz + 1) / 2; + else + round = round * rate / req_rate; + } else if (round != ~0U) { + round = round * rate / req_rate; + bufsz = round * 2; + } else + return 1; + + /* + * get the play/record frame size in bytes + */ + if (ioctl(hdl->fd, AUDIO_GETINFO, &aui) < 0) { + DPERROR("sun_setpar: GETINFO"); + hdl->sio.eof = 1; + return 0; + } + ibpf = (hdl->sio.mode & SIO_REC) ? + aui.record.channels * aui.record.bps : 1; + obpf = (hdl->sio.mode & SIO_PLAY) ? + aui.play.channels * aui.play.bps : 1; + + DPRINTF("sun_setpar: bpf = (%u, %u)\n", ibpf, obpf); + + /* + * try to set parameters until the device accepts + * a common block size for play and record + */ + for (i = 0; i < NRETRIES; i++) { + AUDIO_INITINFO(&aui); + aui.hiwat = (bufsz + round - 1) / round; + aui.lowat = aui.hiwat; + if (hdl->sio.mode & SIO_REC) + aui.record.block_size = round * ibpf; + if (hdl->sio.mode & SIO_PLAY) + aui.play.block_size = round * obpf; + if (ioctl(hdl->fd, AUDIO_SETINFO, &aui) < 0) { + DPERROR("sun_setpar2: SETINFO"); + hdl->sio.eof = 1; + return 0; + } + if (ioctl(hdl->fd, AUDIO_GETINFO, &aui) < 0) { + DPERROR("sun_setpar2: GETINFO"); + hdl->sio.eof = 1; + return 0; + } + infr = aui.record.block_size / ibpf; + onfr = aui.play.block_size / obpf; + DPRINTF("sun_setpar: %i: trying round = %u -> (%u, %u)\n", + i, round, infr, onfr); + + /* + * if half-duplex or both block sizes match, we're done + */ + if (hdl->sio.mode != (SIO_REC | SIO_PLAY) || infr == onfr) { + DPRINTF("sun_setpar: blocksize ok\n"); + return 1; + } + + /* + * half of the retries, retry with the smaller value, + * then with the larger returned value + */ + if (i < NRETRIES / 2) + round = infr < onfr ? infr : onfr; + else + round = infr < onfr ? onfr : infr; + } + DPRINTF("sun_setpar: couldn't find a working blocksize\n"); + hdl->sio.eof = 1; + return 0; +#undef NRETRIES +} + +static int +sun_getpar(struct sio_hdl *sh, struct sio_par *par) +{ + struct sun_hdl *hdl = (struct sun_hdl *)sh; + struct audio_info aui; + + if (ioctl(hdl->fd, AUDIO_GETINFO, &aui) < 0) { + DPERROR("sun_getpar: getinfo"); + hdl->sio.eof = 1; + return 0; + } + if (hdl->sio.mode & SIO_PLAY) { + par->rate = aui.play.sample_rate; + if (!sun_infotoenc(hdl, &aui.play, par)) + return 0; + } else if (hdl->sio.mode & SIO_REC) { + par->rate = aui.record.sample_rate; + if (!sun_infotoenc(hdl, &aui.record, par)) + return 0; + } else + return 0; + par->pchan = (hdl->sio.mode & SIO_PLAY) ? + aui.play.channels : 0; + par->rchan = (hdl->sio.mode & SIO_REC) ? + aui.record.channels : 0; + par->round = (hdl->sio.mode & SIO_REC) ? + aui.record.block_size / (par->bps * par->rchan) : + aui.play.block_size / (par->bps * par->pchan); + par->appbufsz = aui.hiwat * par->round; + par->bufsz = par->appbufsz; + return 1; +} + +/* + * drop recorded samples to compensate xruns + */ +static int +sun_rdrop(struct sun_hdl *hdl) +{ +#define DROP_NMAX 0x1000 + static char dropbuf[DROP_NMAX]; + ssize_t n, todo; + + while (hdl->offset > 0) { + todo = hdl->offset * hdl->ibpf; + if (todo > DROP_NMAX) + todo = DROP_NMAX - DROP_NMAX % hdl->ibpf; + while ((n = read(hdl->fd, dropbuf, todo)) < 0) { + if (errno == EINTR) + continue; + if (errno != EAGAIN) { + DPERROR("sun_rdrop: read"); + hdl->sio.eof = 1; + } + return 0; + } + if (n == 0) { + DPRINTF("sun_rdrop: eof\n"); + hdl->sio.eof = 1; + return 0; + } + hdl->offset -= (int)n / (int)hdl->ibpf; + DPRINTF("sun_rdrop: dropped %ld/%ld bytes\n", n, todo); + } + return 1; +} + +static size_t +sun_read(struct sio_hdl *sh, void *buf, size_t len) +{ + struct sun_hdl *hdl = (struct sun_hdl *)sh; + ssize_t n; + + if (!sun_rdrop(hdl)) + return 0; + while ((n = read(hdl->fd, buf, len)) < 0) { + if (errno == EINTR) + continue; + if (errno != EAGAIN) { + DPERROR("sun_read: read"); + hdl->sio.eof = 1; + } + return 0; + } + if (n == 0) { + DPRINTF("sun_read: eof\n"); + hdl->sio.eof = 1; + return 0; + } + return n; +} + +static size_t +sun_autostart(struct sun_hdl *hdl) +{ + struct audio_info aui; + struct pollfd pfd; + + pfd.fd = hdl->fd; + pfd.events = POLLOUT; + while (poll(&pfd, 1, 0) < 0) { + if (errno == EINTR) + continue; + DPERROR("sun_autostart: poll"); + hdl->sio.eof = 1; + return 0; + } + if (!(pfd.revents & POLLOUT)) { + hdl->filling = 0; + AUDIO_INITINFO(&aui); + if (hdl->sio.mode & SIO_PLAY) + aui.play.pause = 0; + if (hdl->sio.mode & SIO_REC) + aui.record.pause = 0; + if (ioctl(hdl->fd, AUDIO_SETINFO, &aui) < 0) { + DPERROR("sun_autostart: setinfo"); + hdl->sio.eof = 1; + return 0; + } + sio_onmove_cb(&hdl->sio, 0); + } + return 1; +} + +/* + * insert silence to play to compensate xruns + */ +static int +sun_wsil(struct sun_hdl *hdl) +{ +#define ZERO_NMAX 0x1000 + static char zero[ZERO_NMAX]; + ssize_t n, todo; + + while (hdl->offset < 0) { + todo = (int)-hdl->offset * (int)hdl->obpf; + if (todo > ZERO_NMAX) + todo = ZERO_NMAX - ZERO_NMAX % hdl->obpf; + while ((n = write(hdl->fd, zero, todo)) < 0) { + if (errno == EINTR) + continue; + if (errno != EAGAIN) { + DPERROR("sun_wsil: write"); + hdl->sio.eof = 1; + return 0; + } + return 0; + } + hdl->offset += (int)n / (int)hdl->obpf; + DPRINTF("sun_wsil: inserted %ld/%ld bytes\n", n, todo); + } + return 1; +} + + +static size_t +sun_write(struct sio_hdl *sh, const void *buf, size_t len) +{ + struct sun_hdl *hdl = (struct sun_hdl *)sh; + const unsigned char *data = buf; + ssize_t n, todo; + + if (!sun_wsil(hdl)) + return 0; + todo = len; + while ((n = write(hdl->fd, data, todo)) < 0) { + if (errno == EINTR) + continue; + if (errno != EAGAIN) { + DPERROR("sun_write: write"); + hdl->sio.eof = 1; + } + return 0; + } + if (hdl->filling) { + if (!sun_autostart(hdl)) + return 0; + } + return n; +} + +static int +sun_pollfd(struct sio_hdl *sh, struct pollfd *pfd, int events) +{ + struct sun_hdl *hdl = (struct sun_hdl *)sh; + + pfd->fd = hdl->fd; + pfd->events = events; + return 1; +} + +int +sun_revents(struct sio_hdl *sh, struct pollfd *pfd) +{ + struct sun_hdl *hdl = (struct sun_hdl *)sh; + struct audio_offset ao; + int xrun, dmove, dierr = 0, doerr = 0, delta; + int revents = pfd->revents; + + if (hdl->sio.mode & SIO_PLAY) { + if (ioctl(hdl->fd, AUDIO_PERROR, &xrun) < 0) { + DPERROR("sun_revents: PERROR"); + hdl->sio.eof = 1; + return POLLHUP; + } + doerr = xrun - hdl->oerr; + hdl->oerr = xrun; + if (!(hdl->sio.mode & SIO_REC)) + dierr = doerr; + } + if (hdl->sio.mode & SIO_REC) { + if (ioctl(hdl->fd, AUDIO_RERROR, &xrun) < 0) { + DPERROR("sun_revents: RERROR"); + hdl->sio.eof = 1; + return POLLHUP; + } + dierr = xrun - hdl->ierr; + hdl->ierr = xrun; + if (!(hdl->sio.mode & SIO_PLAY)) + doerr = dierr; + } + hdl->offset += doerr - dierr; + dmove = dierr > doerr ? dierr : doerr; + hdl->idelta -= dmove; + hdl->odelta -= dmove; + + if ((revents & POLLOUT) && (hdl->sio.mode & SIO_PLAY)) { + if (ioctl(hdl->fd, AUDIO_GETOOFFS, &ao) < 0) { + DPERROR("sun_revents: GETOOFFS"); + hdl->sio.eof = 1; + return POLLHUP; + } + delta = (ao.samples - hdl->obytes) / hdl->obpf; + hdl->obytes = ao.samples; + hdl->odelta += delta; + if (!(hdl->sio.mode & SIO_REC)) + hdl->idelta += delta; + } + if ((revents & POLLIN) && (hdl->sio.mode & SIO_REC)) { + if (ioctl(hdl->fd, AUDIO_GETIOFFS, &ao) < 0) { + DPERROR("sun_revents: GETIOFFS"); + hdl->sio.eof = 1; + return POLLHUP; + } + delta = (ao.samples - hdl->ibytes) / hdl->ibpf; + hdl->ibytes = ao.samples; + hdl->idelta += delta; + if (!(hdl->sio.mode & SIO_PLAY)) + hdl->odelta += delta; + } + delta = (hdl->idelta > hdl->odelta) ? hdl->idelta : hdl->odelta; + if (delta > 0) { + sio_onmove_cb(&hdl->sio, delta); + hdl->idelta -= delta; + hdl->odelta -= delta; + } + + /* + * drop recorded samples or insert silence to play + * right now to adjust revents, and avoid busy loops + * programs + */ + if (hdl->sio.started) { + if (hdl->filling) + revents |= POLLOUT; + if ((hdl->sio.mode & SIO_PLAY) && !sun_wsil(hdl)) + revents &= ~POLLOUT; + if ((hdl->sio.mode & SIO_REC) && !sun_rdrop(hdl)) + revents &= ~POLLIN; + } + return revents; +}