commit 70b6e37e934710321226e5635613b54d8aa1fcc9
parent bf938ec7b4003e32c443088059f667c0829a85eb
Author: sin <sin@2f30.org>
Date: Wed, 20 Jul 2016 16:26:20 +0100
Split ncmixer into a daemon and a client
Diffstat:
M | Makefile | | | 16 | +++++++++++----- |
A | arg.h | | | 63 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | mixerd.c | | | 573 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
D | ncmixer.c | | | 608 | ------------------------------------------------------------------------------- |
A | ncmixerc.c | | | 435 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | patches/0001-output-plugins-Add-UNIX-domain-socket-output.patch | | | 326 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | proto.h | | | 44 | ++++++++++++++++++++++++++++++++++++++++++++ |
7 files changed, 1452 insertions(+), 613 deletions(-)
diff --git a/Makefile b/Makefile
@@ -2,11 +2,16 @@ VERSION = 0.1
CPPFLAGS = -DDEBUG
CFLAGS = -Wall
LDLIBS = -lsndio -lcurses -lm
-DISTFILES = ncmixer.c LICENSE Makefile README
-OBJ = ncmixer.o
-BIN = ncmixer
+DISTFILES = arg.h mixerd.c ncmixerc.c proto.h LICENSE Makefile README
+NCMIXERC_OBJ = ncmixerc.o
+NCMIXERC_BIN = ncmixerc
+MIXERD_OBJ = mixerd.o
+MIXERD_BIN = mixerd
-all: $(BIN)
+all: $(NCMIXERC_BIN) $(MIXERD_BIN)
+
+NCMIXERC_BIN: $(NCMIXERC_OBJ)
+MIXERD_BIN: $(MIXERD_OBJ)
dist:
mkdir -p ncmixer-$(VERSION)
@@ -16,6 +21,7 @@ dist:
rm -rf ncmixer-$(VERSION)
clean:
- rm -f $(OBJ) $(BIN) ncmixer-$(VERSION).tar.gz
+ rm -f $(NCMIXERC_OBJ) $(MIXERD_OBJ) $(NCMIXERC_BIN) $(MIXERD_BIN) \
+ ncmixer-$(VERSION).tar.gz
.PHONY: all dist clean
diff --git a/arg.h b/arg.h
@@ -0,0 +1,63 @@
+/*
+ * Copy me if you can.
+ * by 20h
+ */
+
+#ifndef ARG_H__
+#define ARG_H__
+
+extern char *argv0;
+
+/* use main(int argc, char *argv[]) */
+#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\
+ argv[0] && argv[0][1]\
+ && argv[0][0] == '-';\
+ argc--, argv++) {\
+ char argc_;\
+ char **argv_;\
+ int brk_;\
+ if (argv[0][1] == '-' && argv[0][2] == '\0') {\
+ argv++;\
+ argc--;\
+ break;\
+ }\
+ for (brk_ = 0, argv[0]++, argv_ = argv;\
+ argv[0][0] && !brk_;\
+ argv[0]++) {\
+ if (argv_ != argv)\
+ break;\
+ argc_ = argv[0][0];\
+ switch (argc_)
+
+/* Handles obsolete -NUM syntax */
+#define ARGNUM case '0':\
+ case '1':\
+ case '2':\
+ case '3':\
+ case '4':\
+ case '5':\
+ case '6':\
+ case '7':\
+ case '8':\
+ case '9'
+
+#define ARGEND }\
+ }
+
+#define ARGC() argc_
+
+#define ARGNUMF(base) (brk_ = 1, estrtol(argv[0], (base)))
+
+#define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\
+ ((x), abort(), (char *)0) :\
+ (brk_ = 1, (argv[0][1] != '\0')?\
+ (&argv[0][1]) :\
+ (argc--, argv++, argv[0])))
+
+#define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\
+ (char *)0 :\
+ (brk_ = 1, (argv[0][1] != '\0')?\
+ (&argv[0][1]) :\
+ (argc--, argv++, argv[0])))
+
+#endif
diff --git a/mixerd.c b/mixerd.c
@@ -0,0 +1,573 @@
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+
+#include <err.h>
+#include <errno.h>
+#include <math.h>
+#include <poll.h>
+#include <signal.h>
+#include <sndio.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "arg.h"
+#include "proto.h"
+
+#define LEN(x) (sizeof (x) / sizeof *(x))
+#undef MIN
+#define MIN(x, y) ((x) < (y) ? (x) : (y))
+#undef MAX
+#define MAX(x, y) ((x) > (y) ? (x) : (y))
+#define SA struct sockaddr
+#define CTL_SOCK_PATH "/tmp/mixerctl"
+#define CH0_SOCK_PATH "/tmp/mixer0"
+#define CH1_SOCK_PATH "/tmp/mixer1"
+#define MASTER_DEV "snd/0"
+#define MONITOR_DEV "rsnd/1"
+#define BITS 16
+#define BPS (BITS / 8)
+#define RATE 44100
+#define CHANS 2
+#define NSAMPLES 128
+#define BUFFER_TIME_MS 100
+#define TIMEO_MS 40
+
+char *argv0;
+int debug;
+
+/* mixer state */
+float xf_pos = 0.0f; /* values in [-1.0, 1.0] */
+
+/* forward decls */
+struct input;
+struct output;
+
+int mix_master(struct output *, struct input *, struct input *);
+int mix_monitor(struct output *, struct input *, struct input *);
+
+struct input {
+ int listenfd;
+ int clifd;
+ short buf[NSAMPLES];
+ short attenuated_buf[NSAMPLES];
+ int nsamples;
+ int mon_on;
+ float level;
+} inputs[] = {
+ { .listenfd = -1, .clifd = -1, .level = 1.0f },
+ { .listenfd = -1, .clifd = -1, .level = 1.0f }
+};
+
+struct output {
+ char *name;
+ struct sio_hdl *sio_hdl;
+ short buf[NSAMPLES];
+ int nsamples;
+ int (*mix)(struct output *, struct input *, struct input *);
+} outputs[] = {
+ { .name = MASTER_DEV, .mix = mix_master },
+ { .name = MONITOR_DEV, .mix = mix_monitor }
+};
+
+void
+log_msg(int prio, const char *msg, ...)
+{
+ va_list ap;
+
+ if (debug) {
+ va_start(ap, msg);
+ vfprintf(stderr, msg, ap);
+ fprintf(stderr, "\n");
+ va_end(ap);
+ } else {
+ va_start(ap, msg);
+ vsyslog(LOG_DAEMON | prio, msg, ap);
+ va_end(ap);
+ }
+}
+
+void
+log_err(const char *msg, ...)
+{
+ va_list ap;
+
+ if (debug) {
+ va_start(ap, msg);
+ fprintf(stderr, "rebound: ");
+ vfprintf(stderr, msg, ap);
+ fprintf(stderr, "\n");
+ va_end(ap);
+ } else {
+ va_start(ap, msg);
+ vsyslog(LOG_DAEMON | LOG_ERR, msg, ap);
+ va_end(ap);
+ }
+ exit(1);
+}
+
+struct sio_hdl *
+audio_open(char *devname)
+{
+ struct sio_hdl *sio_hdl;
+ struct sio_par par;
+
+ sio_hdl = sio_open(devname, SIO_PLAY, 0);
+ if (sio_hdl == NULL)
+ return NULL;
+
+ sio_initpar(&par);
+ par.bits = BITS;
+ par.rate = RATE;
+ par.pchan = CHANS;
+ par.sig = 1;
+ par.le = SIO_LE_NATIVE;
+ par.appbufsz = RATE * BUFFER_TIME_MS / 1000;
+
+ if (sio_setpar(sio_hdl, &par) == 0) {
+ log_msg(LOG_WARNING, "%s: failed to set params", devname);
+ goto err0;
+ }
+
+ if (sio_getpar(sio_hdl, &par) == 0) {
+ log_msg(LOG_WARNING, "%s: failed to get params", devname);
+ goto err0;
+ }
+
+ if (par.pchan != CHANS) {
+ log_msg(LOG_WARNING, "%s: failed to set number of channels",
+ devname);
+ goto err0;
+ }
+
+ if (par.sig != 1 ||
+ par.bits != BITS ||
+ par.le != SIO_LE_NATIVE) {
+ log_msg(LOG_WARNING, "%s: failed to set format", devname);
+ goto err0;
+ }
+
+ /* allow 0.5% tolerance for actual sample rate */
+ if (par.rate < RATE * 995 / 1000 ||
+ par.rate > RATE * 1005 / 1000) {
+ log_msg(LOG_WARNING, "%s: failed to set rate", devname);
+ goto err0;
+ }
+
+ if (sio_start(sio_hdl) == 0) {
+ log_msg(LOG_WARNING, "%s: failed to start audio device",
+ devname);
+ goto err0;
+ }
+
+ return sio_hdl;
+err0:
+ sio_close(sio_hdl);
+ return NULL;
+}
+
+int
+audio_play(struct sio_hdl *sio_hdl, void *buf, int len)
+{
+ int n;
+
+ n = sio_write(sio_hdl, buf, len);
+ if (n == 0 && sio_eof(sio_hdl) != 0)
+ return -1;
+ return n;
+}
+
+void
+audio_close(struct sio_hdl **sio_hdl)
+{
+ if (*sio_hdl != NULL)
+ sio_close(*sio_hdl);
+ *sio_hdl = NULL;
+}
+
+int
+consume(struct input *in)
+{
+ int n;
+
+ do {
+ n = read(in->clifd, in->buf, sizeof(in->buf));
+ } while (n == -1 && errno == EINTR);
+ if (n == 0 || n == -1)
+ return -1;
+ in->nsamples = n / BPS;
+ return in->nsamples;
+}
+
+void
+level(struct input *in)
+{
+ short *ch0 = (short *)in->buf;
+ int i;
+
+ for (i = 0; i < in->nsamples; i++) {
+ *ch0 *= in->level;
+ ch0++;
+ }
+}
+
+void
+attenuate(struct input *in, float factor)
+{
+ short *ch0 = (short *)in->buf;
+ short *out = (short *)in->attenuated_buf;
+ int i;
+
+ for (i = 0; i < in->nsamples; i++) {
+ *out = *ch0 * factor;
+ ch0++, out++;
+ }
+}
+
+int
+mix_master(struct output *out, struct input *in0, struct input *in1)
+{
+ short *ch0 = (short *)in0->attenuated_buf;
+ short *ch1 = (short *)in1->attenuated_buf;
+ short *buf = (short *)out->buf;
+ int i;
+
+ out->nsamples = MAX(in0->nsamples, in1->nsamples);
+ for (i = 0; i < out->nsamples; i++) {
+ *buf = *ch0 * 0.5 + *ch1 * 0.5;
+ ch0++, ch1++, buf++;
+ }
+ return out->nsamples;
+}
+
+int
+mix_monitor(struct output *out, struct input *in0, struct input *in1)
+{
+ short *ch0 = (short *)in0->buf;
+ short *ch1 = (short *)in1->buf;
+ short *buf = (short *)out->buf;
+ int i;
+
+ out->nsamples = MAX(in0->nsamples, in1->nsamples);
+ for (i = 0; i < out->nsamples; i++) {
+ *ch0 *= in0->mon_on;
+ *ch1 *= in1->mon_on;
+ *buf = *ch0 * 0.5 + *ch1 * 0.5;
+ ch0++, ch1++, buf++;
+ }
+ return out->nsamples;
+}
+
+int
+server_listen(char *name, int ctl)
+{
+ struct sockaddr_un sun;
+ socklen_t len;
+ int fd, type, oldumask;
+
+ if (ctl)
+ type = SOCK_SEQPACKET;
+ else
+ type = SOCK_STREAM;
+
+ fd = socket(AF_UNIX, type, 0);
+ if (fd == -1)
+ log_err("socket failed");
+ unlink(name);
+ memset(&sun, 0, sizeof(sun));
+ sun.sun_family = AF_UNIX;
+ snprintf(sun.sun_path, sizeof(sun.sun_path), "%s", name);
+ len = sizeof(sun);
+ oldumask = umask(0111);
+ if (bind(fd, (SA *)&sun, len) == -1)
+ log_err("bind failed");
+ umask(oldumask);
+ if (listen(fd, 5) == -1)
+ log_err("listen failed");
+ return fd;
+}
+
+int
+msg_handler(int fd)
+{
+ struct msg msg;
+ int n, tx_reply = 0;
+
+ do {
+ n = read(fd, &msg, sizeof(msg));
+ } while (n == -1 && errno == EINTR);
+ if (n == 0 || n == -1)
+ return -1;
+
+ switch (msg.type) {
+ case SET_XF:
+ if (msg.m_xf_pos < -1.0f ||
+ msg.m_xf_pos > 1.0f) {
+ log_msg(LOG_WARNING, "xf pos out of bounds: %f",
+ msg.m_xf_pos);
+ return 0;
+ }
+ xf_pos = msg.m_xf_pos;
+ break;
+ case GET_XF:
+ msg.m_xf_pos = xf_pos;
+ tx_reply = 1;
+ break;
+ case SET_MON:
+ if (msg.m_mon_index != 0 &&
+ msg.m_mon_index != 1) {
+ log_msg(LOG_WARNING, "invalid monitor index: %d",
+ msg.m_mon_index);
+ return 0;
+ }
+ inputs[msg.m_mon_index].mon_on = !inputs[msg.m_mon_index].mon_on;
+ break;
+ case GET_MON:
+ if (msg.m_mon_index != 0 &&
+ msg.m_mon_index != 1) {
+ log_msg(LOG_WARNING, "invalid monitor index: %d",
+ msg.m_mon_index);
+ return 0;
+ }
+ msg.m_mon_on = inputs[msg.m_mon_index].mon_on;
+ tx_reply = 1;
+ break;
+ case SET_VOL:
+ if (msg.m_vol_chan != 0 &&
+ msg.m_vol_chan != 1) {
+ log_msg(LOG_WARNING, "invalid input channel: %d",
+ msg.m_vol_chan);
+ return 0;
+ }
+ if (msg.m_vol_level < 0.0f ||
+ msg.m_vol_level > 1.0f) {
+ log_msg(LOG_WARNING, "invalid input channel volume: %f",
+ msg.m_vol_level);
+ return 0;
+ }
+ inputs[msg.m_vol_chan].level = msg.m_vol_level;
+ break;
+ case GET_VOL:
+ if (msg.m_vol_chan != 0 &&
+ msg.m_vol_chan != 1) {
+ log_msg(LOG_WARNING, "invalid input channel: %d",
+ msg.m_vol_chan);
+ return 0;
+ }
+ msg.m_vol_level = inputs[msg.m_vol_chan].level;
+ tx_reply = 1;
+ break;
+ case GET_IN:
+ if (msg.m_input_index != 0 &&
+ msg.m_input_index != 1) {
+ log_msg(LOG_WARNING, "invalid input index: %d",
+ msg.m_input_index);
+ return 0;
+ }
+ msg.m_input_on = inputs[msg.m_input_index].clifd != -1;
+ tx_reply = 1;
+ break;
+ case GET_OUT:
+ if (msg.m_output_index != 0 &&
+ msg.m_output_index != 1) {
+ log_msg(LOG_WARNING, "invalid output index: %d",
+ msg.m_output_index);
+ return 0;
+ }
+ msg.m_output_on = outputs[msg.m_output_index].sio_hdl != NULL;
+ tx_reply = 1;
+ break;
+ default:
+ log_msg(LOG_WARNING, "unknown message type: %d", msg.type);
+ break;
+ }
+
+ /* for GET_* requests, send a reply */
+ if (tx_reply) {
+ do {
+ n = write(fd, &msg, sizeof(msg));
+ } while (n == -1 && errno == EINTR);
+ if (n == -1)
+ return -1;
+ }
+ return 0;
+}
+
+/* do not re-order */
+#define CTL_LISTEN 0
+#define IN0_LISTEN 1
+#define IN1_LISTEN 2
+#define CTL_CLIENT 3
+#define IN0_CLIENT 4
+#define IN1_CLIENT 5
+#define NR_SOCKETS 6
+void
+loop(void)
+{
+ struct sockaddr_un sun;
+ socklen_t len;
+ struct pollfd pfd[NR_SOCKETS];
+ struct input *in;
+ struct output *out;
+ int i, ret, clifd, nready;
+
+ for (i = 0; i < LEN(pfd); i++) {
+ pfd[i].fd = -1;
+ pfd[i].events = 0;
+ }
+
+ inputs[0].listenfd = server_listen(CH0_SOCK_PATH, 0);
+ inputs[1].listenfd = server_listen(CH1_SOCK_PATH, 0);
+
+ pfd[CTL_LISTEN].fd = server_listen(CTL_SOCK_PATH, 1);
+ pfd[CTL_LISTEN].events = POLLIN;
+ pfd[IN0_LISTEN].fd = inputs[0].listenfd;
+ pfd[IN0_LISTEN].events = POLLIN;
+ pfd[IN1_LISTEN].fd = inputs[1].listenfd;
+ pfd[IN1_LISTEN].events = POLLIN;
+
+ for (;;) {
+ nready = poll(pfd, LEN(pfd), TIMEO_MS);
+ if (nready == -1) {
+ if (errno != EINTR)
+ log_err("poll failed");
+ continue;
+ }
+
+ for (i = 0; i < LEN(pfd); i++)
+ if (pfd[i].revents & POLLERR)
+ log_err("bad fd");
+
+ /* handle client request */
+ if (pfd[CTL_CLIENT].revents & (POLLIN | POLLHUP)) {
+ if (msg_handler(pfd[CTL_CLIENT].fd) == -1) {
+ close(pfd[CTL_CLIENT].fd);
+ pfd[CTL_CLIENT].fd = -1;
+ pfd[CTL_CLIENT].events = 0;
+ }
+ }
+
+ /* accept control socket connection */
+ if (pfd[CTL_LISTEN].revents & POLLIN) {
+ len = sizeof(sun);
+ do {
+ clifd = accept(pfd[CTL_LISTEN].fd,
+ (SA *)&sun, &len);
+ } while (clifd == -1 && errno == EINTR);
+ if (clifd == -1)
+ log_err("accept failed");
+ /* control socket already connected, reject this one */
+ if (pfd[CTL_CLIENT].fd != -1) {
+ close(clifd);
+ } else {
+ pfd[CTL_CLIENT].fd = clifd;
+ pfd[CTL_CLIENT].events = POLLIN;
+ }
+ }
+
+ /* accept input channel connections */
+ for (i = 0; i < LEN(inputs); i++) {
+ in = &inputs[i];
+ if (pfd[IN0_LISTEN + i].revents & POLLIN) {
+ len = sizeof(sun);
+ do {
+ clifd = accept(in->listenfd,
+ (SA *)&sun, &len);
+ } while (clifd == -1 && errno == EINTR);
+ if (clifd == -1)
+ log_err("accept failed");
+ /* input already connnected, reject this one */
+ if (in->clifd != -1) {
+ close(clifd);
+ continue;
+ }
+ in->clifd = clifd;
+ pfd[IN0_CLIENT + i].fd = clifd;
+ pfd[IN0_CLIENT + i].events = POLLIN;
+ }
+ }
+
+ /* reinit input channel buffers to avoid mixing stale data */
+ for (i = 0; i < LEN(inputs); i++) {
+ in = &inputs[i];
+ memset(in->buf, 0, sizeof(in->buf));
+ memset(in->attenuated_buf, 0,
+ sizeof(in->attenuated_buf));
+ in->nsamples = 0;
+ }
+
+ /* consume pcm data on input channels */
+ for (i = 0; i < LEN(inputs); i++) {
+ in = &inputs[i];
+ if (pfd[IN0_CLIENT + i].revents & (POLLIN | POLLHUP)) {
+ if (consume(in) == -1) {
+ close(in->clifd);
+ in->clifd = -1;
+ pfd[IN0_CLIENT + i].fd = -1;
+ pfd[IN0_CLIENT + i].events = 0;
+ } else {
+ level(in);
+ }
+ }
+ }
+
+ /* attenuate inputs based on cross-fader position */
+ if (xf_pos <= 0) {
+ attenuate(&inputs[0], 1.0f);
+ attenuate(&inputs[1], 1.0f - fabsf(xf_pos));
+ } else {
+ attenuate(&inputs[0], 1.0f - fabsf(xf_pos));
+ attenuate(&inputs[1], 1.0f);
+ }
+
+ /* play dat shit! */
+ for (i = 0; i < LEN(outputs); i++) {
+ out = &outputs[i];
+ /* open output device on demand */
+ if (out->sio_hdl == NULL) {
+ out->sio_hdl = audio_open(out->name);
+ if (out->sio_hdl == NULL)
+ continue;
+ }
+ out->mix(out, &inputs[0], &inputs[1]);
+ ret = audio_play(out->sio_hdl, out->buf,
+ out->nsamples * BPS);
+ if (ret == -1)
+ audio_close(&out->sio_hdl);
+ }
+ }
+}
+
+void
+usage(void)
+{
+ fprintf(stderr, "usage: mixerd [-d] [master-output] [monitor-output]\n");
+ exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+ ARGBEGIN {
+ case 'd':
+ debug = 1;
+ break;
+ default:
+ usage();
+ } ARGEND
+
+ if (argc > 0)
+ outputs[0].name = argv[0];
+ if (argc > 1)
+ outputs[1].name = argv[1];
+ if (!debug && daemon(0, 0) == -1)
+ err(1, "daemon");
+ signal(SIGPIPE, SIG_IGN);
+ loop();
+ return 0;
+}
diff --git a/ncmixer.c b/ncmixer.c
@@ -1,608 +0,0 @@
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <sys/stat.h>
-#include <sys/un.h>
-
-#include <curses.h>
-#include <err.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <math.h>
-#include <poll.h>
-#include <signal.h>
-#include <sndio.h>
-#include <stdarg.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-#define LEN(x) (sizeof (x) / sizeof *(x))
-#undef MIN
-#define MIN(x, y) ((x) < (y) ? (x) : (y))
-#undef MAX
-#define MAX(x, y) ((x) > (y) ? (x) : (y))
-#define ISODD(x) ((x) & 1)
-#define CONTROL(c) ((c) ^ 0x40)
-#define SA struct sockaddr
-#define CH0_NAME "/tmp/ncmixer0"
-#define CH1_NAME "/tmp/ncmixer1"
-#define MASTER_DEV "rsnd/0" /* bypass sndiod by default */
-#define MONITOR_DEV "rsnd/1"
-#define BITS 16
-#define BPS (BITS / 8)
-#define RATE 44100
-#define CHANS 2
-#define NSAMPLES 2048
-#define BUFFER_TIME_MS 1000
-
-#ifdef DEBUG
-#define DEBUG_FD 8
-#define DPRINTF(x...) dprintf(DEBUG_FD, x)
-#define DPRINTF_D(x) dprintf(DEBUG_FD, #x "=%d\n", x)
-#define DPRINTF_F(x) dprintf(DEBUG_FD, #x "=%0.2f\n", x)
-#else
-#define DPRINTF
-#define DPRINTF_D(x)
-#define DPRINTF_F(x)
-#endif /* DEBUG */
-
-/* mixer state */
-float xfpos = 0.; /* values in [-1.0, 1.0] */
-float step = 0.0625f;
-float level_step = 0.125f;
-
-/* forward decls */
-struct input;
-struct output;
-
-int mix_master(struct output *, struct input *, struct input *);
-int mix_monitor(struct output *, struct input *, struct input *);
-
-struct input {
- int listenfd;
- int clifd;
- short buf[NSAMPLES];
- short attenuated_buf[NSAMPLES];
- int nsamples;
- int monitor;
- float level;
-} inputs[] = {
- { .listenfd = -1, .clifd = -1, .level = 1.0f },
- { .listenfd = -1, .clifd = -1, .level = 1.0f }
-};
-
-struct output {
- char *name;
- struct sio_hdl *sio_hdl;
- short buf[NSAMPLES];
- int nsamples;
- int (*mix)(struct output *, struct input *, struct input *);
-} outputs[] = {
- { .name = MASTER_DEV, .mix = mix_master },
- { .name = MONITOR_DEV, .mix = mix_monitor }
-};
-
-void
-printerr(int ret, char *fmt, ...)
-{
- va_list ap;
-
- endwin();
- va_start(ap, fmt);
- verr(ret, fmt, ap);
- va_end(ap);
- exit(ret);
-}
-
-struct sio_hdl *
-audio_open(char *devname)
-{
- struct sio_hdl *sio_hdl;
- struct sio_par par;
-
- sio_hdl = sio_open(devname, SIO_PLAY, 0);
- if (sio_hdl == NULL) {
- DPRINTF("%s: failed to open audio device\n", devname);
- return NULL;
- }
-
- sio_initpar(&par);
- par.bits = BITS;
- par.rate = RATE;
- par.pchan = CHANS;
- par.sig = 1;
- par.le = SIO_LE_NATIVE;
- par.appbufsz = RATE * BUFFER_TIME_MS / 1000;
-
- if (sio_setpar(sio_hdl, &par) == 0) {
- DPRINTF("%s: failed to set params\n", devname);
- goto err0;
- }
-
- if (sio_getpar(sio_hdl, &par) == 0) {
- DPRINTF("%s: failed to get params\n", devname);
- goto err0;
- }
-
- if (par.pchan != CHANS) {
- DPRINTF("%s: failed to set number of channels\n", devname);
- goto err0;
- }
-
- if (par.sig != 1 ||
- par.bits != BITS ||
- par.le != SIO_LE_NATIVE) {
- DPRINTF("%s: failed to set format\n", devname);
- goto err0;
- }
-
- /* allow 0.5% tolerance for actual sample rate */
- if (par.rate < RATE * 995 / 1000 ||
- par.rate > RATE * 1005 / 1000) {
- DPRINTF("%s: failed to set rate\n", devname);
- goto err0;
- }
-
- if (sio_start(sio_hdl) == 0) {
- DPRINTF("%s: failed to start audio device\n", devname);
- goto err0;
- }
-
- return sio_hdl;
-err0:
- sio_close(sio_hdl);
- return NULL;
-}
-
-int
-audio_play(struct sio_hdl *sio_hdl, void *buf, int len)
-{
- int n;
-
- n = sio_write(sio_hdl, buf, len);
- if (n == 0 && sio_eof(sio_hdl) != 0)
- return -1;
- return n;
-}
-
-void
-audio_close(struct sio_hdl **sio_hdl)
-{
- if (*sio_hdl != NULL)
- sio_close(*sio_hdl);
- *sio_hdl = NULL;
-}
-
-int
-key_cb(int fd)
-{
- int c;
-
- c = getch();
- DPRINTF_D(c);
- switch (c) {
- case 'h':
- xfpos = MAX(xfpos - step, -1.0f);
- break;
- case 'l':
- xfpos = MIN(xfpos + step, 1.0f);
- break;
- case 'j':
- step = MAX(step / 2, 0.015625f);
- break;
- case 'k':
- step = MIN(step * 2, 0.25f);
- break;
- case CONTROL('H'):
- if (xfpos > 0.)
- xfpos = 0.;
- else
- xfpos = -1.;
- break;
- case CONTROL('L'):
- if (xfpos < 0.)
- xfpos = 0.;
- else
- xfpos = 1.;
- break;
- case '1':
- inputs[0].monitor = !inputs[0].monitor;
- break;
- case '2':
- inputs[1].monitor = !inputs[1].monitor;
- break;
- case 'q':
- inputs[0].level = MAX(inputs[0].level - level_step, 0.0f);
- break;
- case 'w':
- inputs[0].level = MIN(inputs[0].level + level_step, 1.0f);
- break;
- case 'o':
- inputs[1].level = MAX(inputs[1].level - level_step, 0.0f);
- break;
- case 'p':
- inputs[1].level = MIN(inputs[1].level + level_step, 1.0f);
- break;
- }
- return 0;
-}
-
-int
-consume(struct input *in)
-{
- int n;
-
- do {
- n = read(in->clifd, in->buf, sizeof(in->buf));
- } while (n == -1 && errno == EINTR);
- if (n == 0 || n == -1)
- return -1;
- in->nsamples = n / BPS;
- return in->nsamples;
-}
-
-void
-level(struct input *in)
-{
- short *ch0 = (short *)in->buf;
- int i;
-
- for (i = 0; i < in->nsamples; i++) {
- *ch0 *= in->level;
- ch0++;
- }
-}
-
-void
-attenuate(struct input *in, float factor)
-{
- short *ch0 = (short *)in->buf;
- short *out = (short *)in->attenuated_buf;
- int i;
-
- for (i = 0; i < in->nsamples; i++) {
- *out = *ch0 * factor;
- ch0++, out++;
- }
-}
-
-int
-mix_master(struct output *out, struct input *in0, struct input *in1)
-{
- short *ch0 = (short *)in0->attenuated_buf;
- short *ch1 = (short *)in1->attenuated_buf;
- short *buf = (short *)out->buf;
- int i;
-
- out->nsamples = MAX(in0->nsamples, in1->nsamples);
- for (i = 0; i < out->nsamples; i++) {
- *buf = *ch0 * 0.5 + *ch1 * 0.5;
- ch0++, ch1++, buf++;
- }
- return out->nsamples;
-}
-
-int
-mix_monitor(struct output *out, struct input *in0, struct input *in1)
-{
- short *ch0 = (short *)in0->buf;
- short *ch1 = (short *)in1->buf;
- short *buf = (short *)out->buf;
- int i;
-
- out->nsamples = MAX(in0->nsamples, in1->nsamples);
- for (i = 0; i < out->nsamples; i++) {
- *ch0 *= in0->monitor;
- *ch1 *= in1->monitor;
- *buf = *ch0 * 0.5 + *ch1 * 0.5;
- ch0++, ch1++, buf++;
- }
- return out->nsamples;
-}
-
-int
-server_listen(char *name)
-{
- struct sockaddr_un sun;
- socklen_t len;
- int fd, oldumask;
-
- fd = socket(AF_UNIX, SOCK_STREAM, 0);
- if (fd == -1)
- printerr(1, "socket");
- unlink(name);
- memset(&sun, 0, sizeof(sun));
- sun.sun_family = AF_UNIX;
- snprintf(sun.sun_path, sizeof(sun.sun_path), "%s", name);
- len = sizeof(sun);
- oldumask = umask(0111);
- if (bind(fd, (SA *)&sun, len) == -1)
- printerr(1, "bind");
- umask(oldumask);
- if (listen(fd, 5) == -1)
- printerr(1, "listen");
- return fd;
-}
-
-void
-draw_status(void)
-{
- int offs;
-
- offs = ISODD(COLS) ? 2 : 3;
- if (COLS < 2 + strlen("[CH0: ] [MON: ] [CH1: ] [MON: ]") + offs) {
- move(getcury(stdscr), 0);
- printw("\n");
- return;
- }
-
- move(getcury(stdscr), 2);
- printw("[CH0:%c] [MON:%c]",
- inputs[0].clifd != -1 ? '*' : ' ',
- inputs[0].monitor != 0 ? '*' : ' ');
- move(getcury(stdscr), COLS - strlen("[MON: ] [CH1: ]") - offs);
- printw("[CH1:%c] [MON:%c]",
- inputs[1].clifd != -1 ? '*' : ' ',
- inputs[1].monitor != 0 ? '*' : ' ');
- printw("\n");
-}
-
-void
-draw_levels(void)
-{
- int start, end, len, nsteps, i;
-
- start = 2;
- end = ISODD(COLS) ? COLS - 2 : COLS - 3;
- len = 1.0f / level_step;
-
- move(getcury(stdscr), start);
- nsteps = inputs[0].level / level_step;
- for (i = 0; i < nsteps; i++)
- printw("|");
- nsteps = inputs[1].level / level_step;
- move(getcury(stdscr), end - len);
- for (i = 0; i < nsteps; i++)
- printw("|");
- printw("\n");
-}
-
-void
-draw_xfader(void)
-{
- int start, end, len, center, pos, i;
-
- if (COLS < strlen(" -|x|- "))
- return;
-
- start = 2;
- end = ISODD(COLS) ? COLS - 2 : COLS - 3;
- len = end - start;
- center = start + (len / 2);
- if (xfpos <= 0.0f)
- pos = center - ceil((len / 2) * fabsf(xfpos));
- else
- pos = center + ceil((len / 2) * fabsf(xfpos));
-
- move(getcury(stdscr), center - strlen("[step: 0.000000") / 2);
- printw("step: %0.6f\n", step);
- move(getcury(stdscr), center - strlen("[pos: +0.000000") / 2);
- printw("pos: %+0.6f\n", xfpos);
- printw("\n");
-
- move(getcury(stdscr), start);
- for (i = 0; i < len; i++)
- printw("-");
- move(getcury(stdscr), center);
- printw("+");
- move(getcury(stdscr), pos - 1);
- if (pos == center)
- printw("|x|");
- else
- printw("|||");
- move(getcury(stdscr), end + 1);
- printw("\n");
-}
-
-void
-draw_outputs(void)
-{
- int start, end, len, center;
-
- start = 2;
- end = ISODD(COLS) ? COLS - 2 : COLS - 3;
- len = end - start;
- center = start + (len / 2);
-
- move(getcury(stdscr), center - strlen(" master: off") / 2);
- printw(" master: %s\n", outputs[0].sio_hdl ? "on" : "off");
- move(getcury(stdscr), center - strlen("monitor: off") / 2);
- printw("monitor: %s\n", outputs[1].sio_hdl ? "on" : "off");
-}
-
-void
-draw(void)
-{
- erase();
- draw_status();
- draw_xfader();
- draw_levels();
- draw_outputs();
- refresh();
-}
-
-void
-loop(void)
-{
- struct sockaddr_un sun;
- socklen_t len;
- struct pollfd pfd[16];
- struct input *in;
- struct output *out;
- int i, ret, clifd, nready;
-
- for (i = 0; i < LEN(pfd); i++) {
- pfd[i].fd = -1;
- pfd[i].events = 0;
- }
-
- inputs[0].listenfd = server_listen(CH0_NAME);
- inputs[1].listenfd = server_listen(CH1_NAME);
-
- pfd[0].fd = 0;
- pfd[0].events = POLLIN;
- pfd[inputs[0].listenfd].fd = inputs[0].listenfd;
- pfd[inputs[0].listenfd].events = POLLIN;
- pfd[inputs[1].listenfd].fd = inputs[1].listenfd;
- pfd[inputs[1].listenfd].events = POLLIN;
-
- for (;;) {
- draw();
-
- nready = poll(pfd, LEN(pfd), 40);
- if (nready == -1) {
- if (errno != EINTR)
- printerr(1, "poll");
- /* force refresh to update COLS on window resize */
- refresh();
- continue;
- }
-
- for (i = 0; i < LEN(pfd); i++)
- if (pfd[i].revents & POLLERR)
- printerr(1, "bad fd");
-
- /* handle key presses */
- if (pfd[0].revents & POLLIN)
- key_cb(pfd[0].fd);
-
- /* accept new connections on sockets */
- for (i = 0; i < LEN(inputs); i++) {
- in = &inputs[i];
- if (pfd[in->listenfd].revents & POLLIN) {
- len = sizeof(sun);
- clifd = accept(in->listenfd, (SA *)&sun, &len);
- if (clifd == -1) {
- if (errno != EINTR)
- printerr(1, "accept");
- continue;
- }
- /* input already connnected, reject this one */
- if (in->clifd != -1) {
- close(clifd);
- continue;
- }
- in->clifd = clifd;
- pfd[in->clifd].fd = clifd;
- pfd[in->clifd].events = POLLIN;
- }
- }
-
- /* read pcm data from sockets */
- for (i = 0; i < LEN(inputs); i++) {
- in = &inputs[i];
- memset(in->buf, 0, sizeof(in->buf));
- memset(in->attenuated_buf, 0,
- sizeof(in->attenuated_buf));
- in->nsamples = 0;
- if (in->clifd == -1)
- continue;
- if (pfd[in->clifd].revents & (POLLIN | POLLHUP)) {
- if (consume(in) == -1) {
- /* we lost the input, reset state */
- pfd[in->clifd].fd = -1;
- pfd[in->clifd].events = 0;
- close(in->clifd);
- in->clifd = -1;
- } else {
- level(in);
- }
- }
- }
-
- /* attenuate inputs based on cross-fader position */
- if (xfpos <= 0) {
- attenuate(&inputs[0], 1.0f);
- attenuate(&inputs[1], 1.0f - fabsf(xfpos));
- } else {
- attenuate(&inputs[0], 1.0f - fabsf(xfpos));
- attenuate(&inputs[1], 1.0f);
- }
-
- /* play dat shit! */
- for (i = 0; i < LEN(outputs); i++) {
- out = &outputs[i];
- /* open output device on demand */
- if (out->sio_hdl == NULL) {
- out->sio_hdl = audio_open(out->name);
- if (out->sio_hdl == NULL)
- continue;
- }
- out->mix(out, &inputs[0], &inputs[1]);
- ret = audio_play(out->sio_hdl, out->buf,
- out->nsamples * BPS);
- if (ret == -1)
- audio_close(&out->sio_hdl);
- }
- }
-}
-
-void
-curses_init(void)
-{
- char *term;
-
- if (initscr() == NULL) {
- term = getenv("TERM");
- if (term != NULL)
- errx(1, "error opening terminal: %s", term);
- else
- errx(1, "failed to initialize curses");
- }
- cbreak();
- noecho();
- nonl();
- intrflush(stdscr, FALSE);
- curs_set(FALSE);
-}
-
-void
-curses_exit(void)
-{
- endwin();
-}
-
-void
-nuke_fds(void)
-{
- int i;
-
- for (i = 3; i < getdtablesize(); i++) {
-#ifdef DEBUG
- if (i == DEBUG_FD)
- continue;
-#endif
- close(i);
- }
-}
-
-int
-main(int argc, char *argv[])
-{
- if (argc > 1)
- outputs[0].name = argv[1];
- if (argc > 2)
- outputs[1].name = argv[2];
- if (isatty(0) == 0 || isatty(1) == 0)
- errx(1, "stdin or stdout is not a tty");
-#ifndef DEBUG
- signal(SIGINT, SIG_IGN);
- signal(SIGQUIT, SIG_IGN);
-#endif
- nuke_fds();
- curses_init();
- loop();
- curses_exit();
- return 0;
-}
diff --git a/ncmixerc.c b/ncmixerc.c
@@ -0,0 +1,435 @@
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <curses.h>
+#include <err.h>
+#include <errno.h>
+#include <math.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "proto.h"
+
+#define LEN(x) (sizeof (x) / sizeof *(x))
+#undef MIN
+#define MIN(x, y) ((x) < (y) ? (x) : (y))
+#undef MAX
+#define SA struct sockaddr
+#define MAX(x, y) ((x) > (y) ? (x) : (y))
+#define ISODD(x) ((x) & 1)
+#define CONTROL(c) ((c) ^ 0x40)
+#define CTL_SOCK_PATH "/tmp/mixerctl"
+#define TIMEO_MS 40
+
+#ifdef DEBUG
+#define DEBUG_FD 8
+#define DPRINTF(x...) dprintf(DEBUG_FD, x)
+#define DPRINTF_D(x) dprintf(DEBUG_FD, #x "=%d\n", x)
+#define DPRINTF_F(x) dprintf(DEBUG_FD, #x "=%0.2f\n", x)
+#else
+#define DPRINTF
+#define DPRINTF_D(x)
+#define DPRINTF_F(x)
+#endif /* DEBUG */
+
+float xf_pos = 0.0f;
+float step = 0.0625f;
+float level_step = 0.125f;
+float ch0_level = 1.0f;
+float ch1_level = 1.0f;
+int mon0_on;
+int mon1_on;
+int in0_on;
+int in1_on;
+int out0_on;
+int out1_on;
+int sock;
+
+void
+curses_init(void)
+{
+ char *term;
+
+ if (initscr() == NULL) {
+ term = getenv("TERM");
+ if (term != NULL)
+ errx(1, "error opening terminal: %s", term);
+ else
+ errx(1, "failed to initialize curses");
+ }
+ cbreak();
+ noecho();
+ nonl();
+ intrflush(stdscr, FALSE);
+ curs_set(FALSE);
+ timeout(TIMEO_MS);
+}
+
+void
+curses_exit(void)
+{
+ endwin();
+}
+
+void
+tx_msg(struct msg *msg)
+{
+ int n;
+
+ do {
+ n = write(sock, msg, sizeof(*msg));
+ } while (n == -1 && errno == EINTR);
+ if (n == -1) {
+ curses_exit();
+ errx(1, "lost connection to daemon");
+ }
+}
+
+void
+rx_msg(struct msg *msg)
+{
+ int n;
+
+ do {
+ n = read(sock, msg, sizeof(*msg));
+ } while (n == -1 && errno == EINTR);
+ if (n == 0 || n == -1) {
+ curses_exit();
+ errx(1, "lost connection to daemon");
+ }
+}
+
+void
+get_xf_pos(float *xf_pos)
+{
+ struct msg msg;
+
+ msg.type = GET_XF;
+ tx_msg(&msg);
+ rx_msg(&msg);
+ *xf_pos = msg.m_xf_pos;
+}
+
+void
+set_xf_pos(float xf_pos)
+{
+ struct msg msg;
+
+ msg.type = SET_XF;
+ msg.m_xf_pos = xf_pos;
+ tx_msg(&msg);
+}
+
+void
+get_mon_on(int index, int *on)
+{
+ struct msg msg;
+
+ msg.type = GET_MON;
+ msg.m_mon_index = index;
+ tx_msg(&msg);
+ rx_msg(&msg);
+ *on = msg.m_mon_on;
+}
+
+void
+set_mon_on(int index, int on)
+{
+ struct msg msg;
+
+ msg.type = SET_MON;
+ msg.m_mon_index = index;
+ msg.m_mon_on = on;
+ tx_msg(&msg);
+}
+
+void
+get_vol_level(int chan, float *level)
+{
+ struct msg msg;
+
+ msg.type = GET_VOL;
+ msg.m_vol_chan = chan;
+ tx_msg(&msg);
+ rx_msg(&msg);
+ *level = msg.m_vol_level;
+}
+
+void
+set_vol_level(int chan, float level)
+{
+ struct msg msg;
+
+ msg.type = SET_VOL;
+ msg.m_vol_chan = chan;
+ msg.m_vol_level = level;
+ tx_msg(&msg);
+}
+
+void
+get_input_on(int index, int *on)
+{
+ struct msg msg;
+
+ msg.type = GET_IN;
+ msg.m_input_index = index;
+ tx_msg(&msg);
+ rx_msg(&msg);
+ *on = msg.m_input_on;
+}
+
+void
+get_output_on(int index, int *on)
+{
+ struct msg msg;
+
+ msg.type = GET_OUT;
+ msg.m_output_index = index;
+ tx_msg(&msg);
+ rx_msg(&msg);
+ *on = msg.m_output_on;
+}
+
+void
+kb_handler(void)
+{
+ int c;
+
+ c = getch();
+ DPRINTF_D(c);
+ switch (c) {
+ case 'h':
+ get_xf_pos(&xf_pos);
+ xf_pos = MAX(xf_pos - step, -1.0f);
+ set_xf_pos(xf_pos);
+ break;
+ case 'l':
+ get_xf_pos(&xf_pos);
+ xf_pos = MIN(xf_pos + step, 1.0f);
+ set_xf_pos(xf_pos);
+ break;
+ case 'j':
+ get_xf_pos(&xf_pos);
+ step = MAX(step / 2, 0.015625f);
+ set_xf_pos(xf_pos);
+ break;
+ case 'k':
+ get_xf_pos(&xf_pos);
+ step = MIN(step * 2, 0.25f);
+ set_xf_pos(xf_pos);
+ break;
+ case CONTROL('H'):
+ get_xf_pos(&xf_pos);
+ if (xf_pos > 0.)
+ xf_pos = 0.f;
+ else
+ xf_pos = -1.f;
+ set_xf_pos(xf_pos);
+ break;
+ case CONTROL('L'):
+ get_xf_pos(&xf_pos);
+ if (xf_pos < 0.)
+ xf_pos = 0.f;
+ else
+ xf_pos = 1.f;
+ set_xf_pos(xf_pos);
+ break;
+ case '1':
+ get_mon_on(0, &mon0_on);
+ mon0_on = !mon0_on;
+ set_mon_on(0, mon0_on);
+ break;
+ case '2':
+ get_mon_on(1, &mon1_on);
+ mon1_on = !mon1_on;
+ set_mon_on(1, mon1_on);
+ break;
+ case 'q':
+ get_vol_level(0, &ch0_level);
+ ch0_level = MAX(ch0_level - level_step, 0.0f);
+ set_vol_level(0, ch0_level);
+ break;
+ case 'w':
+ get_vol_level(0, &ch0_level);
+ ch0_level = MIN(ch0_level + level_step, 1.0f);
+ set_vol_level(0, ch0_level);
+ break;
+ case 'o':
+ get_vol_level(1, &ch1_level);
+ ch1_level = MAX(ch1_level - level_step, 0.0f);
+ set_vol_level(1, ch1_level);
+ break;
+ case 'p':
+ get_vol_level(1, &ch1_level);
+ ch1_level = MIN(ch1_level + level_step, 1.0f);
+ set_vol_level(1, ch1_level);
+ break;
+ case 'x':
+ curses_exit();
+ exit(0);
+ }
+}
+
+void
+draw_status(void)
+{
+ int offs;
+
+ offs = ISODD(COLS) ? 2 : 3;
+ if (COLS < 2 + strlen("[CH0: ] [MON: ] [CH1: ] [MON: ]") + offs) {
+ move(getcury(stdscr), 0);
+ printw("\n");
+ return;
+ }
+
+ get_input_on(0, &in0_on);
+ get_input_on(1, &in1_on);
+ get_mon_on(0, &mon0_on);
+ get_mon_on(1, &mon1_on);
+
+ move(getcury(stdscr), 2);
+ printw("[CH0:%c] [MON:%c]",
+ in0_on ? '*' : ' ',
+ mon0_on ? '*' : ' ');
+ move(getcury(stdscr), COLS - strlen("[MON: ] [CH1: ]") - offs);
+ printw("[CH1:%c] [MON:%c]",
+ in1_on ? '*' : ' ',
+ mon1_on ? '*' : ' ');
+ printw("\n");
+}
+
+void
+draw_levels(void)
+{
+ int start, end, len, nsteps, i;
+
+ start = 2;
+ end = ISODD(COLS) ? COLS - 2 : COLS - 3;
+ len = 1.0f / level_step;
+
+ get_vol_level(0, &ch0_level);
+ get_vol_level(1, &ch1_level);
+
+ move(getcury(stdscr), start);
+ nsteps = ch0_level / level_step;
+ for (i = 0; i < nsteps; i++)
+ printw("|");
+ nsteps = ch1_level / level_step;
+ move(getcury(stdscr), end - len);
+ for (i = 0; i < nsteps; i++)
+ printw("|");
+ printw("\n");
+}
+
+void
+draw_xfader(void)
+{
+ int start, end, len, center, pos, i;
+
+ if (COLS < strlen(" -|x|- "))
+ return;
+
+ get_xf_pos(&xf_pos);
+
+ start = 2;
+ end = ISODD(COLS) ? COLS - 2 : COLS - 3;
+ len = end - start;
+ center = start + (len / 2);
+ if (xf_pos <= 0.0f)
+ pos = center - ceil((len / 2) * fabsf(xf_pos));
+ else
+ pos = center + ceil((len / 2) * fabsf(xf_pos));
+
+ move(getcury(stdscr), center - strlen("[step: 0.000000") / 2);
+ printw("step: %0.6f\n", step);
+ move(getcury(stdscr), center - strlen("[pos: +0.000000") / 2);
+ printw("pos: %+0.6f\n", xf_pos);
+ printw("\n");
+
+ move(getcury(stdscr), start);
+ for (i = 0; i < len; i++)
+ printw("-");
+ move(getcury(stdscr), center);
+ printw("+");
+ move(getcury(stdscr), pos - 1);
+ if (pos == center)
+ printw("|x|");
+ else
+ printw("|||");
+ move(getcury(stdscr), end + 1);
+ printw("\n");
+}
+
+void
+draw_outputs(void)
+{
+ int start, end, len, center;
+
+ start = 2;
+ end = ISODD(COLS) ? COLS - 2 : COLS - 3;
+ len = end - start;
+ center = start + (len / 2);
+
+ get_output_on(0, &out0_on);
+ get_output_on(1, &out1_on);
+
+ move(getcury(stdscr), center - strlen(" master: off") / 2);
+ printw(" master: %s\n", out0_on ? "on" : "off");
+ move(getcury(stdscr), center - strlen("monitor: off") / 2);
+ printw("monitor: %s\n", out1_on ? "on" : "off");
+}
+
+void
+draw(void)
+{
+ erase();
+ draw_status();
+ draw_xfader();
+ draw_levels();
+ draw_outputs();
+ refresh();
+}
+
+void
+loop(void)
+{
+ for (;;) {
+ draw();
+ kb_handler();
+ }
+}
+
+int
+client_connect(char *name)
+{
+ struct sockaddr_un sun;
+ socklen_t len;
+ int fd;
+
+ fd = socket(AF_UNIX, SOCK_SEQPACKET, 0);
+ if (fd == -1)
+ err(1, "socket");
+ sun.sun_family = AF_UNIX;
+ strlcpy(sun.sun_path, name, sizeof(sun.sun_path));
+ len = sizeof(sun);
+ if (connect(fd, (SA *)&sun, len) == -1)
+ errx(1, "cannot connect to daemon");
+ return fd;
+}
+
+int
+main(void)
+{
+ if (isatty(0) == 0 || isatty(1) == 0)
+ errx(1, "stdin or stdout is not a tty");
+ signal(SIGPIPE, SIG_IGN);
+ sock = client_connect(CTL_SOCK_PATH);
+ curses_init();
+ loop();
+ curses_exit();
+ return 0;
+}
diff --git a/patches/0001-output-plugins-Add-UNIX-domain-socket-output.patch b/patches/0001-output-plugins-Add-UNIX-domain-socket-output.patch
@@ -0,0 +1,326 @@
+From 73246507075f772c40e717a741459d31b5a3a784 Mon Sep 17 00:00:00 2001
+From: Dimitris Papastamos <sin@2f30.org>
+Date: Sun, 12 Jun 2016 15:06:55 +0100
+Subject: [PATCH] output/plugins: Add UNIX domain socket output
+
+Signed-off-by: Dimitris Papastamos <sin@2f30.org>
+Tested-by: Lazaros Koromilas <lostd@2f30.org>
+---
+ Makefile.am | 6 +
+ configure.ac | 16 +++
+ src/output/Registry.cxx | 4 +
+ src/output/plugins/UnixOutputPlugin.cxx | 188 ++++++++++++++++++++++++++++++++
+ src/output/plugins/UnixOutputPlugin.hxx | 26 +++++
+ 5 files changed, 240 insertions(+)
+ create mode 100644 src/output/plugins/UnixOutputPlugin.cxx
+ create mode 100644 src/output/plugins/UnixOutputPlugin.hxx
+
+diff --git a/Makefile.am b/Makefile.am
+index 2cf8e05..bd94ae1 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -1409,6 +1409,12 @@ liboutput_plugins_a_SOURCES += \
+ src/output/plugins/SndioOutputPlugin.hxx
+ endif
+
++if HAVE_UNIX
++liboutput_plugins_a_SOURCES += \
++ src/output/plugins/UnixOutputPlugin.cxx \
++ src/output/plugins/UnixOutputPlugin.hxx
++endif
++
+ if HAVE_HAIKU
+ liboutput_plugins_a_SOURCES += \
+ src/output/plugins/HaikuOutputPlugin.cxx \
+diff --git a/configure.ac b/configure.ac
+index 51ab2e0..51f99a2 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -339,6 +339,11 @@ AC_ARG_ENABLE(sndio,
+ [enable support for sndio output plugin (default: auto)]),,
+ enable_sndio=auto)
+
++AC_ARG_ENABLE(unix,
++ AS_HELP_STRING([--disable-unix],
++ [disable support for UNIX domain socket output (default: enable)]),,
++ enable_unix=yes)
++
+ AC_ARG_ENABLE(haiku,
+ AS_HELP_STRING([--enable-haiku],
+ [enable the Haiku output plugin (default: auto)]),,
+@@ -1128,6 +1133,16 @@ fi
+
+ AM_CONDITIONAL(HAVE_SNDIO, test x$enable_sndio = xyes)
+
++dnl ----------------------------------- UNIX ----------------------------------
++if test x$enable_unix = xyes; then
++ AC_CHECK_HEADER(sys/un.h,
++ [enable_unix=yes],
++ [enable_unix=no;AC_MSG_WARN([sys/un.h not found -- disabling support for UNIX domain socket output])])
++fi
++
++MPD_DEFINE_CONDITIONAL(enable_unix, HAVE_UNIX,
++ [support for UNIX domain socket output])
++
+ dnl ----------------------------------- Haiku ---------------------------------
+ if test x$enable_haiku = xauto; then
+ AC_CHECK_HEADER(media/MediaDefs.h,
+@@ -1449,6 +1464,7 @@ printf '\nPlayback support:\n\t'
+ results(alsa,ALSA)
+ results(fifo,FIFO)
+ results(sndio,[SNDIO])
++results(unix,[UNIX domain socket])
+ results(recorder_output,[File Recorder])
+ results(haiku,[Haiku])
+ results(httpd_output,[HTTP Daemon])
+diff --git a/src/output/Registry.cxx b/src/output/Registry.cxx
+index 9d8d826..3961710 100644
+--- a/src/output/Registry.cxx
++++ b/src/output/Registry.cxx
+@@ -24,6 +24,7 @@
+ #include "plugins/AoOutputPlugin.hxx"
+ #include "plugins/FifoOutputPlugin.hxx"
+ #include "plugins/SndioOutputPlugin.hxx"
++#include "plugins/UnixOutputPlugin.hxx"
+ #include "plugins/httpd/HttpdOutputPlugin.hxx"
+ #include "plugins/HaikuOutputPlugin.hxx"
+ #include "plugins/JackOutputPlugin.hxx"
+@@ -56,6 +57,9 @@ const AudioOutputPlugin *const audio_output_plugins[] = {
+ #ifdef HAVE_SNDIO
+ &sndio_output_plugin,
+ #endif
++#ifdef HAVE_UNIX
++ &unix_output_plugin,
++#endif
+ #ifdef HAVE_HAIKU
+ &haiku_output_plugin,
+ #endif
+diff --git a/src/output/plugins/UnixOutputPlugin.cxx b/src/output/plugins/UnixOutputPlugin.cxx
+new file mode 100644
+index 0000000..2b85d64
+--- /dev/null
++++ b/src/output/plugins/UnixOutputPlugin.cxx
+@@ -0,0 +1,188 @@
++/*
++ * Copyright (C) 2016 The Music Player Daemon Project
++ * http://www.musicpd.org
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation; either version 2 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License along
++ * with this program; if not, write to the Free Software Foundation, Inc.,
++ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
++ */
++
++#include <sys/socket.h>
++#include <sys/stat.h>
++#include <sys/un.h>
++
++#include <errno.h>
++#include <string.h>
++#include <unistd.h>
++
++#include "config.h"
++#include "UnixOutputPlugin.hxx"
++#include "config/ConfigError.hxx"
++#include "../OutputAPI.hxx"
++#include "../Wrapper.hxx"
++#include "fs/AllocatedPath.hxx"
++#include "util/Error.hxx"
++#include "util/Domain.hxx"
++#include "Log.hxx"
++
++class UnixOutput {
++ friend struct AudioOutputWrapper<UnixOutput>;
++ AudioOutput base;
++ AllocatedPath path;
++ std::string path_utf8;
++ int sock;
++
++ bool Connect(Error &error);
++ void Disconnect();
++public:
++ UnixOutput()
++ :base(unix_output_plugin),
++ path(AllocatedPath::Null()), sock(-1) {}
++ ~UnixOutput() {
++ Disconnect();
++ }
++
++ bool Initialize(const ConfigBlock &block, Error &error) {
++ return base.Configure(block, error);
++ }
++
++ static UnixOutput *Create(const ConfigBlock &block, Error &error);
++
++ bool Open(AudioFormat &audio_format, Error &error);
++ void Close();
++ size_t Play(const void *chunk, size_t size, Error &error);
++};
++
++static constexpr Domain unix_output_domain("unix_output");
++
++bool
++UnixOutput::Connect(Error &error)
++{
++ struct sockaddr_un sun;
++ socklen_t len;
++ int ret;
++
++ sock = socket(AF_UNIX, SOCK_STREAM, 0);
++ if (sock == -1) {
++ error.FormatErrno("Could not create UNIX socket");
++ return false;
++ }
++
++ memset(&sun, 0, sizeof(sun));
++ sun.sun_family = AF_UNIX;
++ snprintf(sun.sun_path, sizeof(sun.sun_path), "%s", path_utf8.c_str());
++ len = sizeof(sun);
++ ret = connect(sock, (struct sockaddr *)&sun, len);
++ if (ret == -1) {
++ error.FormatErrno("Could not connect to UNIX socket \"%s\"",
++ path_utf8.c_str());
++ Disconnect();
++ return false;
++ }
++
++ return true;
++}
++
++void
++UnixOutput::Disconnect()
++{
++ if (sock >= 0) {
++ close(sock);
++ sock = -1;
++ }
++}
++
++UnixOutput *
++UnixOutput::Create(const ConfigBlock &block, Error &error)
++{
++ UnixOutput *ao = new UnixOutput();
++
++ ao->path = block.GetBlockPath("path", error);
++ if (ao->path.IsNull()) {
++ delete ao;
++
++ if (!error.IsDefined())
++ error.Set(config_domain,
++ "No \"path\" parameter specified");
++ return nullptr;
++ }
++
++ ao->path_utf8 = ao->path.ToUTF8();
++
++ if (!ao->Initialize(block, error)) {
++ delete ao;
++ return nullptr;
++ }
++
++ return ao;
++}
++
++bool
++UnixOutput::Open(gcc_unused AudioFormat &audio_format,
++ gcc_unused Error &error)
++{
++ if (sock == -1 && !Connect(error))
++ return false;
++ audio_format.sample_rate = 44100;
++ audio_format.format = SampleFormat::S16;
++ audio_format.channels = 2;
++ return true;
++}
++
++void
++UnixOutput::Close()
++{
++ Disconnect();
++}
++
++size_t
++UnixOutput::Play(const void *chunk, size_t size, Error &error)
++{
++ ssize_t bytes;
++
++ while (true) {
++ bytes = write(sock, chunk, size);
++ if (bytes > 0)
++ return (size_t)bytes;
++ if (bytes < 0) {
++ switch (errno) {
++ case EAGAIN:
++ case EINTR:
++ continue;
++ }
++ error.FormatErrno("Failed to write to UNIX socket%s",
++ path_utf8.c_str());
++ return 0;
++ }
++ }
++}
++
++typedef AudioOutputWrapper<UnixOutput> Wrapper;
++
++const struct AudioOutputPlugin unix_output_plugin = {
++ "unix",
++ nullptr,
++ &Wrapper::Init,
++ &Wrapper::Finish,
++ nullptr,
++ nullptr,
++ &Wrapper::Open,
++ &Wrapper::Close,
++ nullptr,
++ nullptr,
++ &Wrapper::Play,
++ nullptr,
++ nullptr,
++ nullptr,
++ nullptr,
++};
+diff --git a/src/output/plugins/UnixOutputPlugin.hxx b/src/output/plugins/UnixOutputPlugin.hxx
+new file mode 100644
+index 0000000..3fda10b
+--- /dev/null
++++ b/src/output/plugins/UnixOutputPlugin.hxx
+@@ -0,0 +1,26 @@
++/*
++ * Copyright (C) 2016 The Music Player Daemon Project
++ * http://www.musicpd.org
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation; either version 2 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License along
++ * with this program; if not, write to the Free Software Foundation, Inc.,
++ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
++ */
++
++#ifndef MPD_UNIX_OUTPUT_PLUGIN_HXX
++#define MPD_UNIX_OUTPUT_PLUGIN_HXX
++
++extern const struct AudioOutputPlugin unix_output_plugin;
++
++#endif
++
+--
+2.9.0
+
diff --git a/proto.h b/proto.h
@@ -0,0 +1,44 @@
+enum {
+ SET_XF,
+ GET_XF,
+ SET_MON,
+ GET_MON,
+ SET_VOL,
+ GET_VOL,
+ GET_IN,
+ GET_OUT
+};
+
+struct msg {
+ int type;
+ union {
+ struct {
+ float pos; /* in/out */
+ } xf;
+ struct {
+ int index; /* in */
+ int on; /* in/out */
+ } mon;
+ struct {
+ int chan; /* in */
+ float level; /* in/out */
+ } vol;
+ struct {
+ int index; /* in */
+ int on; /* out */
+ } input;
+ struct {
+ int index; /* in */
+ int on; /* out */
+ } output;
+ } u;
+};
+#define m_xf_pos u.xf.pos
+#define m_mon_index u.mon.index
+#define m_mon_on u.mon.on
+#define m_vol_chan u.vol.chan
+#define m_vol_level u.vol.level
+#define m_input_index u.input.index
+#define m_input_on u.input.on
+#define m_output_index u.output.index
+#define m_output_on u.output.on