Audio control API, part 2: add new sndioctl(1) utility

classic Classic list List threaded Threaded
7 messages Options
Reply | Threaded
Open this post in threaded view
|

Audio control API, part 2: add new sndioctl(1) utility

Alexandre Ratchov-2
Here's a new sndioctl utility similar to mixerctl(1) but using the new
sndio API. Example:

$ sndioctl                                                    
output.level=127
app/aucat0.level=127
app/firefox0.level=127
app/firefox1.level=12
app/midisyn0.level=127
app/mpv0.level=127
app/prog5.level=127
app/prog6.level=127
app/prog7.level=127
hw/input.level=62
hw/input.mute=0
hw/output.level=63
hw/output.mute=0

Configuration parameters that are not exposed by sndiod will be
handled by audioctl(1), including the /etc/mixerctl.conf file at
system startup.

Originally the program was designed to handle modern many-channel
devices by presenting many-channel knobs on a single line; this
feature isn't used yet as the corresponding kernel bits are missing.

Index: usr.bin/Makefile
===================================================================
RCS file: /cvs/src/usr.bin/Makefile,v
retrieving revision 1.161
diff -u -p -u -p -r1.161 Makefile
--- usr.bin/Makefile 9 Aug 2019 06:18:25 -0000 1.161
+++ usr.bin/Makefile 9 Feb 2020 11:05:02 -0000
@@ -22,7 +22,7 @@ SUBDIR= apply arch at aucat audioctl awk
  pr printenv printf quota radioctl rcs rdist rdistd \
  readlink renice rev rpcgen rpcinfo rs rsync rup rusers rwall \
  sdiff script sed sendbug shar showmount signify skey \
- skeyaudit skeyinfo skeyinit sndiod snmp \
+ skeyaudit skeyinfo skeyinit sndioctl sndiod snmp \
  sort spell split ssh stat su systat \
  tail talk tcpbench tee telnet tftp tic time \
  tmux top touch tput tr true tset tsort tty usbhidaction usbhidctl \
Index: usr.bin/sndioctl/Makefile
===================================================================
RCS file: usr.bin/sndioctl/Makefile
diff -N usr.bin/sndioctl/Makefile
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ usr.bin/sndioctl/Makefile 9 Feb 2020 11:05:02 -0000
@@ -0,0 +1,5 @@
+# $OpenBSD$
+
+PROG= sndioctl
+LDADD+= -lsndio
+.include <bsd.prog.mk>
Index: usr.bin/sndioctl/sndioctl.1
===================================================================
RCS file: usr.bin/sndioctl/sndioctl.1
diff -N usr.bin/sndioctl/sndioctl.1
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ usr.bin/sndioctl/sndioctl.1 9 Feb 2020 11:05:02 -0000
@@ -0,0 +1,148 @@
+.\" $OpenBSD$
+.\"
+.\" Copyright (c) 2007 Alexandre Ratchov <[hidden email]>
+.\"
+.\" 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 8 2011 $
+.Dt SNDIOCTL 1
+.Os
+.Sh NAME
+.Nm sndioctl
+.Nd control audio parameters
+.Sh SYNOPSIS
+.Nm
+.Bk -words
+.Op Fl iv
+.Op Fl f Ar device
+.Op Ar command ...
+.Ek
+.Nm
+.Bk -words
+.Fl d
+.Ek
+.Sh DESCRIPTION
+The
+.Nm
+utility can display or change parameters of
+.Xr sndio 7
+audio devices.
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl d
+Dump the raw list of available parameters and exit.
+Useful as a debug tool.
+.It Fl f Ar device
+Use this
+.Xr sndio 7
+audio device.
+.It Fl m
+Monitor and display audio parameters changes.
+.It Fl i
+Display characteristics of requested parameters
+instead of their values.
+.It Fl v
+Enable verbose mode, a.k.a. multi-channel mode.
+By default parameters affecting different channels
+of the same stream are disguised as a single mono
+parameter to hide details that are not essential.
+.El
+.Pp
+If no commands are specified all valid parameters are displayed on
+.Em stdout .
+Unless
+.Fl d ,
+.Fl m ,
+or
+.Fl i
+are used, displayed parameters are valid commands.
+The set of available controls depends on the control device.
+.Pp
+Commands use the following two formats to display and set
+parameters respectively:
+.Pp
+.Dl group/stream[channel].function
+.Dl group/stream[channel].function=value
+.Pp
+On the left-hand side are specified the optional parameter group,
+the affected stream name, and the optional channel number.
+Examples of left-hand side terms:
+.Pp
+.Dl output.level
+.Dl hw/spkr[6].mute
+.Pp
+There are 4 parameter types: switches, numbers, selectors, and vectors.
+.Pp
+Numbers are specified in decimal and follow the same semantics
+as MIDI controllers.
+Values are in the 0..127 range and 64 is the neutral state (if applicable).
+Two-state controls (switches) take either 0 or 1 as value,
+typically corresponding to the
+.Em off
+and
+.Em on
+states respectively.
+.Pp
+If a decimal is prefixed by the plus (minus) sign then
+the given value is added to (subtracted from) the
+current value of the control.
+If
+.Qq \&!
+is used instead of a number, then the switch is toggled.
+Examples:
+.Pp
+.Dl hw/spkr.level=85
+.Dl hw/spkr.level=+10
+.Dl hw/spkr.mute=0
+.Dl hw/spkr.mute=!
+.Pp
+Selector values are substreams; they are specified
+as the stream name followed by an optional channel
+numer.
+If no channel number is specified, the same
+number as the stream specified on the left-hand side is used.
+For instance the following are equivalent:
+.Pp
+.Dl hw/record[1].source=mic
+.Dl hw/record[1].source=mic1
+.Pp
+Vectors are arrays of numbers.
+Values are specified as comma-separated components.
+Each component is a substream, followed by
+a colon, followed by a number.
+If the colon and the number are omitted, then 127 is
+assumed.
+If a component is missing, then 0 is assumed.
+Example:
+.Pp
+.Dl hw/monitor.mix=play:120,linein:85
+.Dl hw/record.source=mic,linein
+.Pp
+Numbers are specified as discussed above.
+Note that a vector of switches is equivalent to
+a list.
+.Sh EXAMPLES
+The following will set all
+.Ar level
+parameters that control the
+.Ar spkr
+stream to zero.
+.Pp
+.Dl $ sndioctl hw/spkr.level=0
+.Pp
+The following commands are equivalent:
+.Pp
+.Dl $ sndioctl hw/record[0].source=mic0 hw/record[1].source=mic1
+.Dl $ sndioctl hw/record.source=mic
+.Sh SEE ALSO
+.Xr sioctl_open 3
Index: usr.bin/sndioctl/sndioctl.c
===================================================================
RCS file: usr.bin/sndioctl/sndioctl.c
diff -N usr.bin/sndioctl/sndioctl.c
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ usr.bin/sndioctl/sndioctl.c 9 Feb 2020 11:05:02 -0000
@@ -0,0 +1,930 @@
+/* $OpenBSD$ */
+/*
+ * Copyright (c) 2007-2011 Alexandre Ratchov <[hidden email]>
+ *
+ * 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 <errno.h>
+#include <poll.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sndio.h>
+
+struct info {
+ struct info *next;
+ struct sioctl_desc desc;
+ unsigned ctladdr;
+#define MODE_IGNORE 0 /* ignore this value */
+#define MODE_PRINT 1 /* print-only, don't change value */
+#define MODE_SET 2 /* set to newval value */
+#define MODE_ADD 3 /* increase current value by newval */
+#define MODE_SUB 4 /* decrease current value by newval */
+#define MODE_TOGGLE 5 /* toggle current value */
+ unsigned mode;
+ int curval, newval;
+};
+
+int cmpdesc(struct sioctl_desc *, struct sioctl_desc *);
+int isdiag(struct info *);
+struct info *vecent(struct info *, char *, int);
+struct info *nextfunc(struct info *);
+struct info *nextpar(struct info *);
+struct info *firstent(struct info *, char *);
+struct info *nextent(struct info *, int);
+int matchpar(struct info *, char *, int);
+int matchent(struct info *, char *, int);
+int ismono(struct info *);
+void print_node(struct sioctl_node *, int);
+void print_desc(struct info *, int);
+void print_val(struct info *, int);
+void print_par(struct info *, int, char *);
+int parse_name(char **, char *);
+int parse_dec(char **, int *);
+int parse_node(char **, char *, int *);
+int parse_modeval(char **, int *, int *);
+void dump(void);
+int cmd(char *);
+void commit(void);
+void list(void);
+void ondesc(void *, struct sioctl_desc *, int);
+void onctl(void *, unsigned, unsigned);
+
+struct sioctl_hdl *hdl;
+struct info *infolist;
+int i_flag = 0, v_flag = 0, m_flag = 0;
+
+static inline int
+isname_first(int c)
+{
+ return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
+}
+
+static inline int
+isname_next(int c)
+{
+ return isname_first(c) || (c >= '0' && c <= '9') || (c == '_');
+}
+
+/*
+ * compare two sioctl_desc structures, used to sort infolist
+ */
+int
+cmpdesc(struct sioctl_desc *d1, struct sioctl_desc *d2)
+{
+ int res;
+
+ res = strcmp(d1->group, d2->group);
+ if (res != 0)
+ return res;
+ res = strcmp(d1->node0.name, d2->node0.name);
+ if (res != 0)
+ return res;
+ res = d1->type - d2->type;
+ if (res != 0)
+ return res;
+ res = strcmp(d1->func, d2->func);
+ if (res != 0)
+ return res;
+ res = d1->node0.unit - d2->node0.unit;
+ if (d1->type == SIOCTL_VEC ||
+    d1->type == SIOCTL_LIST) {
+ if (res != 0)
+ return res;
+ res = strcmp(d1->node1.name, d2->node1.name);
+ if (res != 0)
+ return res;
+ res = d1->node1.unit - d2->node1.unit;
+ }
+ return res;
+}
+
+/*
+ * return true of the vector entry is diagonal
+ */
+int
+isdiag(struct info *e)
+{
+ if (e->desc.node0.unit < 0 || e->desc.node1.unit < 0)
+ return 1;
+ return e->desc.node1.unit == e->desc.node0.unit;
+}
+
+/*
+ * find the selector or vector entry with the given name and channels
+ */
+struct info *
+vecent(struct info *i, char *vstr, int vunit)
+{
+ while (i != NULL) {
+ if ((strcmp(i->desc.node1.name, vstr) == 0) &&
+    (vunit < 0 || i->desc.node1.unit == vunit))
+ break;
+ i = i->next;
+ }
+ return i;
+}
+
+/*
+ * skip all parameters with the same group, name, and func
+ */
+struct info *
+nextfunc(struct info *i)
+{
+ char *str, *group, *func;
+
+ group = i->desc.group;
+ func = i->desc.func;
+ str = i->desc.node0.name;
+ for (i = i->next; i != NULL; i = i->next) {
+ if (strcmp(i->desc.group, group) != 0 ||
+    strcmp(i->desc.node0.name, str) != 0 ||
+    strcmp(i->desc.func, func) != 0)
+ return i;
+ }
+ return NULL;
+}
+
+/*
+ * find the next parameter with the same group, name, func
+ */
+struct info *
+nextpar(struct info *i)
+{
+ char *str, *group, *func;
+ int unit;
+
+ group = i->desc.group;
+ func = i->desc.func;
+ str = i->desc.node0.name;
+ unit = i->desc.node0.unit;
+ for (i = i->next; i != NULL; i = i->next) {
+ if (strcmp(i->desc.group, group) != 0 ||
+    strcmp(i->desc.node0.name, str) != 0 ||
+    strcmp(i->desc.func, func) != 0)
+ break;
+ /* XXX: need to check for -1 ? */
+ if (i->desc.node0.unit != unit)
+ return i;
+ }
+ return NULL;
+}
+
+/*
+ * return the first vector entry with the given name
+ */
+struct info *
+firstent(struct info *g, char *vstr)
+{
+ char *astr, *group, *func;
+ struct info *i;
+
+ group = g->desc.group;
+ astr = g->desc.node0.name;
+ func = g->desc.func;
+ for (i = g; i != NULL; i = i->next) {
+ if (strcmp(i->desc.group, group) != 0 ||
+    strcmp(i->desc.node0.name, astr) != 0 ||
+    strcmp(i->desc.func, func) != 0)
+ break;
+ if (!isdiag(i))
+ continue;
+ if (strcmp(i->desc.node1.name, vstr) == 0)
+ return i;
+ }
+ return NULL;
+}
+
+/*
+ * find the next entry of the given vector, if the mono flag
+ * is set then the whole group is searched and off-diagonal entries are
+ * skipped
+ */
+struct info *
+nextent(struct info *i, int mono)
+{
+ char *str, *group, *func;
+ int unit;
+
+ group = i->desc.group;
+ func = i->desc.func;
+ str = i->desc.node0.name;
+ unit = i->desc.node0.unit;
+ for (i = i->next; i != NULL; i = i->next) {
+ if (strcmp(i->desc.group, group) != 0 ||
+    strcmp(i->desc.node0.name, str) != 0 ||
+    strcmp(i->desc.func, func) != 0)
+ return NULL;
+ if (mono)
+ return i;
+ if (i->desc.node0.unit == unit)
+ return i;
+ }
+ return NULL;
+}
+
+/*
+ * return true if parameter matches the given name and channel
+ */
+int
+matchpar(struct info *i, char *astr, int aunit)
+{
+ if (strcmp(i->desc.node0.name, astr) != 0)
+ return 0;
+ if (aunit < 0)
+ return 1;
+ else if (i->desc.node0.unit < 0) {
+ fprintf(stderr, "unit used for parameter with no unit\n");
+ exit(1);
+ }
+ return i->desc.node0.unit == aunit;
+}
+
+/*
+ * return true if selector or vector entry matches the given name and
+ * channel range
+ */
+int
+matchent(struct info *i, char *vstr, int vunit)
+{
+ if (strcmp(i->desc.node1.name, vstr) != 0)
+ return 0;
+ if (vunit < 0)
+ return 1;
+ else if (i->desc.node1.unit < 0) {
+ fprintf(stderr, "unit used for parameter with no unit\n");
+ exit(1);
+ }
+ return i->desc.node1.unit == vunit;
+}
+
+/*
+ * return true if the given group can be represented as a signle mono
+ * parameter
+ */
+int
+ismono(struct info *g)
+{
+ struct info *p1, *p2;
+ struct info *e1, *e2;
+
+ p1 = g;
+ switch (g->desc.type) {
+ case SIOCTL_NUM:
+ case SIOCTL_SW:
+ for (p2 = g; p2 != NULL; p2 = nextpar(p2)) {
+ if (p2->curval != p1->curval)
+ return 0;
+ }
+ break;
+ case SIOCTL_VEC:
+ case SIOCTL_LIST:
+ for (p2 = g; p2 != NULL; p2 = nextpar(p2)) {
+ for (e2 = p2; e2 != NULL; e2 = nextent(e2, 0)) {
+ if (!isdiag(e2)) {
+ if (e2->curval != 0)
+ return 0;
+ } else {
+ e1 = vecent(p1,
+    e2->desc.node1.name,
+    p1->desc.node0.unit);
+ if (e1 == NULL)
+ continue;
+ if (e1->curval != e2->curval)
+ return 0;
+ }
+ }
+ }
+ break;
+ }
+ return 1;
+}
+
+/*
+ * print a sub-stream, eg. "spkr[4]"
+ */
+void
+print_node(struct sioctl_node *c, int mono)
+{
+ printf("%s", c->name);
+ if (!mono && c->unit >= 0)
+ printf("[%d]", c->unit);
+}
+
+/*
+ * print info about the parameter
+ */
+void
+print_desc(struct info *p, int mono)
+{
+ struct info *e;
+ int more;
+
+ switch (p->desc.type) {
+ case SIOCTL_NUM:
+ case SIOCTL_SW:
+ printf("*");
+ break;
+ case SIOCTL_VEC:
+ case SIOCTL_LIST:
+ more = 0;
+ for (e = p; e != NULL; e = nextent(e, mono)) {
+ if (mono) {
+ if (!isdiag(e))
+ continue;
+ if (e != firstent(p, e->desc.node1.name))
+ continue;
+ }
+ if (more)
+ printf(",");
+ print_node(&e->desc.node1, mono);
+ printf(":*");
+ more = 1;
+ }
+ }
+}
+
+/*
+ * print parameter value
+ */
+void
+print_val(struct info *p, int mono)
+{
+ struct info *e;
+ int more;
+
+ switch (p->desc.type) {
+ case SIOCTL_NUM:
+ case SIOCTL_SW:
+ printf("%u", p->curval);
+ break;
+ case SIOCTL_VEC:
+ case SIOCTL_LIST:
+ more = 0;
+ for (e = p; e != NULL; e = nextent(e, mono)) {
+ if (mono) {
+ if (!isdiag(e))
+ continue;
+ if (e != firstent(p, e->desc.node1.name))
+ continue;
+ }
+ if (more)
+ printf(",");
+ print_node(&e->desc.node1, mono);
+ printf(":%u", e->curval);
+ more = 1;
+ }
+ }
+}
+
+/*
+ * print ``<parameter>=<value>'' string (including '\n')
+ */
+void
+print_par(struct info *p, int mono, char *comment)
+{
+ if (p->desc.group[0] != 0) {
+ printf("%s", p->desc.group);
+ printf("/");
+ }
+ print_node(&p->desc.node0, mono);
+ printf(".%s=", p->desc.func);
+ if (i_flag)
+ print_desc(p, mono);
+ else
+ print_val(p, mono);
+ if (comment)
+ printf(" # %s", comment);
+ printf("\n");
+}
+
+/*
+ * parse a stream name or parameter name
+ */
+int
+parse_name(char **line, char *name)
+{
+ char *p = *line;
+ unsigned len = 0;
+
+ if (!isname_first(*p)) {
+ fprintf(stderr, "letter expected near '%s'\n", p);
+ return 0;
+ }
+ while (isname_next(*p)) {
+ if (len >= SIOCTL_NAMEMAX - 1) {
+ name[SIOCTL_NAMEMAX - 1] = '\0';
+ fprintf(stderr, "%s...: too long\n", name);
+ return 0;
+ }
+ name[len++] = *p;
+ p++;
+ }
+ name[len] = '\0';
+ *line = p;
+ return 1;
+}
+
+/*
+ * parse a decimal number
+ */
+int
+parse_dec(char **line, int *num)
+{
+#define MAXQ (SIOCTL_VALMAX / 10)
+#define MAXR (SIOCTL_VALMAX % 10)
+ char *p = *line;
+ unsigned int dig, val;
+
+ val = 0;
+ for (;;) {
+ dig = *p - '0';
+ if (dig >= 10)
+ break;
+ if (val > MAXQ || (val == MAXQ && dig > MAXR)) {
+ fprintf(stderr, "integer overflow\n");
+ return 0;
+ }
+ val = val * 10 + dig;
+ p++;
+ }
+ if (p == *line) {
+ fprintf(stderr, "number expected near '%s'\n", p);
+ return 0;
+ }
+ *num = val;
+ *line = p;
+ return 1;
+}
+
+/*
+ * parse a sub-stream, eg. "spkr[7]"
+ */
+int
+parse_node(char **line, char *str, int *unit)
+{
+ char *p = *line;
+
+ if (!parse_name(&p, str))
+ return 0;
+ if (*p != '[') {
+ *unit = -1;
+ *line = p;
+ return 1;
+ }
+ p++;
+ if (!parse_dec(&p, unit))
+ return 0;
+ if (*p != ']') {
+ fprintf(stderr, "']' expected near '%s'\n", p);
+ return 0;
+ }
+ p++;
+ *line = p;
+ return 1;
+}
+
+/*
+ * parse a decimal prefixed by the optional mode
+ */
+int
+parse_modeval(char **line, int *rmode, int *rval)
+{
+ char *p = *line;
+ unsigned mode;
+
+ switch (*p) {
+ case '+':
+ mode = MODE_ADD;
+ p++;
+ break;
+ case '-':
+ mode = MODE_SUB;
+ p++;
+ break;
+ case '!':
+ mode = MODE_TOGGLE;
+ p++;
+ break;
+ default:
+ mode = MODE_SET;
+ }
+ if (mode != MODE_TOGGLE) {
+ if (!parse_dec(&p, rval))
+ return 0;
+ }
+ *line = p;
+ *rmode = mode;
+ return 1;
+}
+
+/*
+ * dump the whole controls list, useful for debugging
+ */
+void
+dump(void)
+{
+ struct info *i;
+
+ for (i = infolist; i != NULL; i = i->next) {
+ printf("%03u:", i->ctladdr);
+ print_node(&i->desc.node0, 0);
+ printf(".%s", i->desc.func);
+ printf("=");
+ switch (i->desc.type) {
+ case SIOCTL_NUM:
+ case SIOCTL_SW:
+ printf("* (%u)", i->curval);
+ break;
+ case SIOCTL_VEC:
+ case SIOCTL_LIST:
+ print_node(&i->desc.node1, 0);
+ printf(":* (%u)", i->curval);
+ }
+ printf("\n");
+ }
+}
+
+/*
+ * parse and execute a command ``<parameter>[=<value>]''
+ */
+int
+cmd(char *line)
+{
+ char *pos = line;
+ struct info *i, *e, *g;
+ char group[SIOCTL_NAMEMAX];
+ char func[SIOCTL_NAMEMAX];
+ char astr[SIOCTL_NAMEMAX], vstr[SIOCTL_NAMEMAX];
+ int aunit, vunit;
+ unsigned npar = 0, nent = 0;
+ int val, comma, mode;
+
+ if (!parse_name(&pos, group))
+ return 0;
+ if (*pos == '/')
+ pos++;
+ else {
+ /* this was node string, go backwards and assume no group */
+ pos = line;
+ group[0] = '\0';
+ }
+ if (!parse_node(&pos, astr, &aunit))
+ return 0;
+ if (*pos != '.') {
+ fprintf(stderr, "'.' expected near '%s'\n", pos);
+ return 0;
+ }
+ pos++;
+ if (!parse_name(&pos, func))
+ return 0;
+ for (g = infolist;; g = g->next) {
+ if (g == NULL) {
+ fprintf(stderr, "%s.%s: no such group\n", astr, func);
+ return 0;
+ }
+ if (strcmp(g->desc.group, group) == 0 &&
+    strcmp(g->desc.func, func) == 0 &&
+    strcmp(g->desc.node0.name, astr) == 0)
+ break;
+ }
+ g->mode = MODE_PRINT;
+ if (*pos != '=') {
+ if (*pos != '\0') {
+ fprintf(stderr, "junk at end of command\n");
+ return 0;
+ }
+ return 1;
+ }
+ pos++;
+ if (i_flag) {
+ printf("can't set values in info mode\n");
+ return 0;
+ }
+ npar = 0;
+ switch (g->desc.type) {
+ case SIOCTL_NUM:
+ case SIOCTL_SW:
+ if (!parse_modeval(&pos, &mode, &val))
+ return 0;
+ for (i = g; i != NULL; i = nextpar(i)) {
+ if (!matchpar(i, astr, aunit))
+ continue;
+ i->mode = mode;
+ i->newval = val;
+ npar++;
+ }
+ break;
+ case SIOCTL_VEC:
+ case SIOCTL_LIST:
+ for (i = g; i != NULL; i = nextpar(i)) {
+ if (!matchpar(i, astr, aunit))
+ continue;
+ for (e = i; e != NULL; e = nextent(e, 0)) {
+ e->newval = 0;
+ e->mode = MODE_SET;
+ }
+ npar++;
+ }
+ comma = 0;
+ for (;;) {
+ if (*pos == '\0')
+ break;
+ if (comma) {
+ if (*pos != ',')
+ break;
+ pos++;
+ }
+ if (!parse_node(&pos, vstr, &vunit))
+ return 0;
+ if (*pos == ':') {
+ pos++;
+ if (!parse_modeval(&pos, &mode, &val))
+ return 0;
+ } else {
+ val = SIOCTL_VALMAX;
+ mode = MODE_SET;
+ }
+ nent = 0;
+ for (i = g; i != NULL; i = nextpar(i)) {
+ if (!matchpar(i, astr, aunit))
+ continue;
+ for (e = i; e != NULL; e = nextent(e, 0)) {
+ if (matchent(e, vstr, vunit)) {
+ e->newval = val;
+ e->mode = mode;
+ nent++;
+ }
+ }
+ }
+ if (nent == 0) {
+ /* XXX: use print_node()-like routine */
+ fprintf(stderr, "%s[%d]: invalid value\n", vstr, vunit);
+ print_par(g, 0, NULL);
+ exit(1);
+ }
+ comma = 1;
+ }
+ }
+ if (npar == 0) {
+ fprintf(stderr, "%s: invalid parameter\n", line);
+ exit(1);
+ }
+ if (*pos != '\0') {
+ printf("%s: junk at end of command\n", pos);
+ exit(1);
+ }
+ return 1;
+}
+
+/*
+ * write the controls with the ``set'' flag on the device
+ */
+void
+commit(void)
+{
+ struct info *i;
+ int val;
+
+ for (i = infolist; i != NULL; i = i->next) {
+ val = 0xdeadbeef;
+ switch (i->mode) {
+ case MODE_IGNORE:
+ case MODE_PRINT:
+ continue;
+ case MODE_SET:
+ val = i->newval;
+ break;
+ case MODE_ADD:
+ val = i->curval + i->newval;
+ if (val > SIOCTL_VALMAX)
+ val = SIOCTL_VALMAX;
+ break;
+ case MODE_SUB:
+ val = i->curval - i->newval;
+ if (val < 0)
+ val = 0;
+ break;
+ case MODE_TOGGLE:
+ val = (i->curval >= SIOCTL_VALMAX / 2) ?
+    0 : SIOCTL_VALMAX;
+ }
+ switch (i->desc.type) {
+ case SIOCTL_NUM:
+ case SIOCTL_SW:
+ sioctl_setval(hdl, i->ctladdr, val);
+ break;
+ case SIOCTL_VEC:
+ case SIOCTL_LIST:
+ sioctl_setval(hdl, i->ctladdr, val);
+ }
+ i->curval = val;
+ }
+}
+
+/*
+ * print all parameters
+ */
+void
+list(void)
+{
+ struct info *p, *g;
+
+ for (g = infolist; g != NULL; g = nextfunc(g)) {
+ if (g->mode == MODE_IGNORE)
+ continue;
+ if (i_flag) {
+ if (v_flag) {
+ for (p = g; p != NULL; p = nextpar(p))
+ print_par(p, 0, NULL);
+ } else
+ print_par(g, 1, NULL);
+ } else {
+ if (v_flag || !ismono(g)) {
+ for (p = g; p != NULL; p = nextpar(p))
+ print_par(p, 0, NULL);
+ } else
+ print_par(g, 1, NULL);
+ }
+ }
+}
+
+/*
+ * register a new knob/button, called from the poll() loop.  this may be
+ * called when label string changes, in which case we update the
+ * existing label widged rather than inserting a new one.
+ */
+void
+ondesc(void *arg, struct sioctl_desc *d, int curval)
+{
+ struct info *i, **pi;
+ int cmp;
+
+ if (d == NULL)
+ return;
+
+ /*
+ * delete control
+ */
+ for (pi = &infolist; (i = *pi) != NULL; pi = &i->next) {
+ if (d->addr == i->desc.addr) {
+ if (m_flag)
+ print_par(i, 0, "deleted");
+ *pi = i->next;
+ free(i);
+ break;
+ }
+ }
+
+ if (d->type == SIOCTL_NONE)
+ return;
+
+ /*
+ * find the right position to insert the new widget
+ */
+ for (pi = &infolist; (i = *pi) != NULL; pi = &i->next) {
+ cmp = cmpdesc(d, &i->desc);
+ if (cmp == 0) {
+ fprintf(stderr, "fatal: duplicate control:\n");
+ print_par(i, 0, "duplicate");
+ exit(1);
+ }
+ if (cmp < 0)
+ break;
+ }
+ i = malloc(sizeof(struct info));
+ if (i == NULL) {
+ perror("malloc");
+ exit(1);
+ }
+ i->desc = *d;
+ i->ctladdr = d->addr;
+ i->curval = i->newval = curval;
+ i->mode = MODE_IGNORE;
+ i->next = *pi;
+ *pi = i;
+ if (m_flag)
+ print_par(i, 0, "added");
+}
+
+/*
+ * update a knob/button state, called from the poll() loop
+ */
+void
+onctl(void *arg, unsigned addr, unsigned val)
+{
+ struct info *i;
+
+ for (i = infolist; i != NULL; i = i->next) {
+ if (i->ctladdr != addr)
+ continue;
+ i->curval = val;
+ if (m_flag)
+ print_par(i, 0, "changed");
+ }
+}
+
+int
+main(int argc, char **argv)
+{
+ char *devname = SIOCTL_DEVANY;
+ int i, c, d_flag = 0;
+ struct info *g;
+ struct pollfd *pfds;
+ int nfds, revents;
+
+ while ((c = getopt(argc, argv, "df:imv")) != -1) {
+ switch (c) {
+ case 'd':
+ d_flag = 1;
+ break;
+ case 'f':
+ devname = optarg;
+ break;
+ case 'i':
+ i_flag = 1;
+ break;
+ case 'm':
+ m_flag = 1;
+ break;
+ case 'v':
+ v_flag++;
+ break;
+ default:
+ fprintf(stderr, "usage: sndioctl "
+    "[-dimnv] [-f device] [command ...]\n");
+ exit(1);
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ hdl = sioctl_open(devname, SIOCTL_READ | SIOCTL_WRITE, 0);
+ if (hdl == NULL) {
+ fprintf(stderr, "%s: can't open control device\n", devname);
+ exit(1);
+ }
+ if (!sioctl_ondesc(hdl, ondesc, NULL)) {
+ fprintf(stderr, "%s: can't get device description\n", devname);
+ exit(1);
+ }
+ sioctl_onval(hdl, onctl, NULL);
+
+ if (d_flag) {
+ if (argc > 0) {
+ fprintf(stderr,
+    "commands are not allowed with -d option\n");
+ exit(1);
+ }
+ dump();
+ } else {
+ if (argc == 0) {
+ for (g = infolist; g != NULL; g = nextfunc(g))
+ g->mode = MODE_PRINT;
+ } else {
+ for (i = 0; i < argc; i++) {
+ if (!cmd(argv[i]))
+ return 1;
+ }
+ }
+ commit();
+ list();
+ }
+ if (m_flag) {
+ pfds = malloc(sizeof(struct pollfd) * sioctl_nfds(hdl));
+ if (pfds == NULL) {
+ perror("malloc");
+ exit(1);
+ }
+ for (;;) {
+ nfds = sioctl_pollfd(hdl, pfds, POLLIN);
+ if (nfds == 0)
+ break;
+ while (poll(pfds, nfds, -1) < 0) {
+ if (errno != EINTR) {
+ perror("poll");
+ exit(1);
+ }
+ }
+ revents = sioctl_revents(hdl, pfds);
+ if (revents & POLLHUP) {
+ fprintf(stderr, "disconnected\n");
+ break;
+ }
+ }
+ free(pfds);
+ }
+ sioctl_close(hdl);
+ return 0;
+}

Reply | Threaded
Open this post in threaded view
|

Re: Audio control API, part 2: add new sndioctl(1) utility

Landry Breuil-5
On Sun, Feb 09, 2020 at 01:14:47PM +0100, Alexandre Ratchov wrote:

> Here's a new sndioctl utility similar to mixerctl(1) but using the new
> sndio API. Example:
>
> $ sndioctl                                                    
> output.level=127
> app/aucat0.level=127
> app/firefox0.level=127
> app/firefox1.level=12
> app/midisyn0.level=127
> app/mpv0.level=127
> app/prog5.level=127
> app/prog6.level=127
> app/prog7.level=127
> hw/input.level=62
> hw/input.mute=0
> hw/output.level=63
> hw/output.mute=0

i suppose that replaces audio/aucatctl port ? audio/cmixer relies on the
latter but i'll have no issue migrating to it if sndioctl gets added.

Reply | Threaded
Open this post in threaded view
|

Re: Audio control API, part 2: add new sndioctl(1) utility

Alexandre Ratchov-2
On Sun, Feb 09, 2020 at 07:20:15PM +0100, Landry Breuil wrote:

> On Sun, Feb 09, 2020 at 01:14:47PM +0100, Alexandre Ratchov wrote:
> > Here's a new sndioctl utility similar to mixerctl(1) but using the new
> > sndio API. Example:
> >
> > $ sndioctl                                                    
> > output.level=127
> > app/aucat0.level=127
> > app/firefox0.level=127
> > app/firefox1.level=12
> > app/midisyn0.level=127
> > app/mpv0.level=127
> > app/prog5.level=127
> > app/prog6.level=127
> > app/prog7.level=127
> > hw/input.level=62
> > hw/input.mute=0
> > hw/output.level=63
> > hw/output.mute=0
>
> i suppose that replaces audio/aucatctl port ?

Yes. But aucatctl will continue to work, it's based on the MIDI
interface that will stay for now. The advantage of sndioctl is that it
exposes selected hardware controls, while aucatctl exposes sndiod
internal knobs only.

> audio/cmixer relies on the
> latter but i'll have no issue migrating to it if sndioctl gets added.
>

The -m option might be useful to cmixer to get updates of the knobs
state without the need to restart sndioctl periodically.

Reply | Threaded
Open this post in threaded view
|

Re: Audio control API, part 2: add new sndioctl(1) utility

Raf Czlonka-2
In reply to this post by Alexandre Ratchov-2
On Sun, Feb 09, 2020 at 12:14:47PM GMT, Alexandre Ratchov wrote:

> Here's a new sndioctl utility similar to mixerctl(1) but using the new
> sndio API. Example:
>
> $ sndioctl                                                    
> output.level=127
> app/aucat0.level=127
> app/firefox0.level=127
> app/firefox1.level=12
> app/midisyn0.level=127
> app/mpv0.level=127
> app/prog5.level=127
> app/prog6.level=127
> app/prog7.level=127
> hw/input.level=62
> hw/input.mute=0
> hw/output.level=63
> hw/output.mute=0
>

Hi Alexandre,

Just a quick question.

Is there a good reason to have the above using "slash" ('/') as the
first separator instead of the, more familiar, "dot" ('.') known
from sysctl(8)'s MIB (Management Information Base) style names or
even the "pseudo" MIB know from mixerctl(1)?

Regards,

Raf

> Configuration parameters that are not exposed by sndiod will be
> handled by audioctl(1), including the /etc/mixerctl.conf file at
> system startup.
>
> Originally the program was designed to handle modern many-channel
> devices by presenting many-channel knobs on a single line; this
> feature isn't used yet as the corresponding kernel bits are missing.
>
> Index: usr.bin/Makefile
> ===================================================================
> RCS file: /cvs/src/usr.bin/Makefile,v
> retrieving revision 1.161
> diff -u -p -u -p -r1.161 Makefile
> --- usr.bin/Makefile 9 Aug 2019 06:18:25 -0000 1.161
> +++ usr.bin/Makefile 9 Feb 2020 11:05:02 -0000
> @@ -22,7 +22,7 @@ SUBDIR= apply arch at aucat audioctl awk
>   pr printenv printf quota radioctl rcs rdist rdistd \
>   readlink renice rev rpcgen rpcinfo rs rsync rup rusers rwall \
>   sdiff script sed sendbug shar showmount signify skey \
> - skeyaudit skeyinfo skeyinit sndiod snmp \
> + skeyaudit skeyinfo skeyinit sndioctl sndiod snmp \
>   sort spell split ssh stat su systat \
>   tail talk tcpbench tee telnet tftp tic time \
>   tmux top touch tput tr true tset tsort tty usbhidaction usbhidctl \
> Index: usr.bin/sndioctl/Makefile
> ===================================================================
> RCS file: usr.bin/sndioctl/Makefile
> diff -N usr.bin/sndioctl/Makefile
> --- /dev/null 1 Jan 1970 00:00:00 -0000
> +++ usr.bin/sndioctl/Makefile 9 Feb 2020 11:05:02 -0000
> @@ -0,0 +1,5 @@
> +# $OpenBSD$
> +
> +PROG= sndioctl
> +LDADD+= -lsndio
> +.include <bsd.prog.mk>
> Index: usr.bin/sndioctl/sndioctl.1
> ===================================================================
> RCS file: usr.bin/sndioctl/sndioctl.1
> diff -N usr.bin/sndioctl/sndioctl.1
> --- /dev/null 1 Jan 1970 00:00:00 -0000
> +++ usr.bin/sndioctl/sndioctl.1 9 Feb 2020 11:05:02 -0000
> @@ -0,0 +1,148 @@
> +.\" $OpenBSD$
> +.\"
> +.\" Copyright (c) 2007 Alexandre Ratchov <[hidden email]>
> +.\"
> +.\" 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 8 2011 $
> +.Dt SNDIOCTL 1
> +.Os
> +.Sh NAME
> +.Nm sndioctl
> +.Nd control audio parameters
> +.Sh SYNOPSIS
> +.Nm
> +.Bk -words
> +.Op Fl iv
> +.Op Fl f Ar device
> +.Op Ar command ...
> +.Ek
> +.Nm
> +.Bk -words
> +.Fl d
> +.Ek
> +.Sh DESCRIPTION
> +The
> +.Nm
> +utility can display or change parameters of
> +.Xr sndio 7
> +audio devices.
> +The options are as follows:
> +.Bl -tag -width Ds
> +.It Fl d
> +Dump the raw list of available parameters and exit.
> +Useful as a debug tool.
> +.It Fl f Ar device
> +Use this
> +.Xr sndio 7
> +audio device.
> +.It Fl m
> +Monitor and display audio parameters changes.
> +.It Fl i
> +Display characteristics of requested parameters
> +instead of their values.
> +.It Fl v
> +Enable verbose mode, a.k.a. multi-channel mode.
> +By default parameters affecting different channels
> +of the same stream are disguised as a single mono
> +parameter to hide details that are not essential.
> +.El
> +.Pp
> +If no commands are specified all valid parameters are displayed on
> +.Em stdout .
> +Unless
> +.Fl d ,
> +.Fl m ,
> +or
> +.Fl i
> +are used, displayed parameters are valid commands.
> +The set of available controls depends on the control device.
> +.Pp
> +Commands use the following two formats to display and set
> +parameters respectively:
> +.Pp
> +.Dl group/stream[channel].function
> +.Dl group/stream[channel].function=value
> +.Pp
> +On the left-hand side are specified the optional parameter group,
> +the affected stream name, and the optional channel number.
> +Examples of left-hand side terms:
> +.Pp
> +.Dl output.level
> +.Dl hw/spkr[6].mute
> +.Pp
> +There are 4 parameter types: switches, numbers, selectors, and vectors.
> +.Pp
> +Numbers are specified in decimal and follow the same semantics
> +as MIDI controllers.
> +Values are in the 0..127 range and 64 is the neutral state (if applicable).
> +Two-state controls (switches) take either 0 or 1 as value,
> +typically corresponding to the
> +.Em off
> +and
> +.Em on
> +states respectively.
> +.Pp
> +If a decimal is prefixed by the plus (minus) sign then
> +the given value is added to (subtracted from) the
> +current value of the control.
> +If
> +.Qq \&!
> +is used instead of a number, then the switch is toggled.
> +Examples:
> +.Pp
> +.Dl hw/spkr.level=85
> +.Dl hw/spkr.level=+10
> +.Dl hw/spkr.mute=0
> +.Dl hw/spkr.mute=!
> +.Pp
> +Selector values are substreams; they are specified
> +as the stream name followed by an optional channel
> +numer.
> +If no channel number is specified, the same
> +number as the stream specified on the left-hand side is used.
> +For instance the following are equivalent:
> +.Pp
> +.Dl hw/record[1].source=mic
> +.Dl hw/record[1].source=mic1
> +.Pp
> +Vectors are arrays of numbers.
> +Values are specified as comma-separated components.
> +Each component is a substream, followed by
> +a colon, followed by a number.
> +If the colon and the number are omitted, then 127 is
> +assumed.
> +If a component is missing, then 0 is assumed.
> +Example:
> +.Pp
> +.Dl hw/monitor.mix=play:120,linein:85
> +.Dl hw/record.source=mic,linein
> +.Pp
> +Numbers are specified as discussed above.
> +Note that a vector of switches is equivalent to
> +a list.
> +.Sh EXAMPLES
> +The following will set all
> +.Ar level
> +parameters that control the
> +.Ar spkr
> +stream to zero.
> +.Pp
> +.Dl $ sndioctl hw/spkr.level=0
> +.Pp
> +The following commands are equivalent:
> +.Pp
> +.Dl $ sndioctl hw/record[0].source=mic0 hw/record[1].source=mic1
> +.Dl $ sndioctl hw/record.source=mic
> +.Sh SEE ALSO
> +.Xr sioctl_open 3
> Index: usr.bin/sndioctl/sndioctl.c
> ===================================================================
> RCS file: usr.bin/sndioctl/sndioctl.c
> diff -N usr.bin/sndioctl/sndioctl.c
> --- /dev/null 1 Jan 1970 00:00:00 -0000
> +++ usr.bin/sndioctl/sndioctl.c 9 Feb 2020 11:05:02 -0000
> @@ -0,0 +1,930 @@
> +/* $OpenBSD$ */
> +/*
> + * Copyright (c) 2007-2011 Alexandre Ratchov <[hidden email]>
> + *
> + * 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 <errno.h>
> +#include <poll.h>
> +#include <stdlib.h>
> +#include <stdio.h>
> +#include <string.h>
> +#include <unistd.h>
> +#include <sndio.h>
> +
> +struct info {
> + struct info *next;
> + struct sioctl_desc desc;
> + unsigned ctladdr;
> +#define MODE_IGNORE 0 /* ignore this value */
> +#define MODE_PRINT 1 /* print-only, don't change value */
> +#define MODE_SET 2 /* set to newval value */
> +#define MODE_ADD 3 /* increase current value by newval */
> +#define MODE_SUB 4 /* decrease current value by newval */
> +#define MODE_TOGGLE 5 /* toggle current value */
> + unsigned mode;
> + int curval, newval;
> +};
> +
> +int cmpdesc(struct sioctl_desc *, struct sioctl_desc *);
> +int isdiag(struct info *);
> +struct info *vecent(struct info *, char *, int);
> +struct info *nextfunc(struct info *);
> +struct info *nextpar(struct info *);
> +struct info *firstent(struct info *, char *);
> +struct info *nextent(struct info *, int);
> +int matchpar(struct info *, char *, int);
> +int matchent(struct info *, char *, int);
> +int ismono(struct info *);
> +void print_node(struct sioctl_node *, int);
> +void print_desc(struct info *, int);
> +void print_val(struct info *, int);
> +void print_par(struct info *, int, char *);
> +int parse_name(char **, char *);
> +int parse_dec(char **, int *);
> +int parse_node(char **, char *, int *);
> +int parse_modeval(char **, int *, int *);
> +void dump(void);
> +int cmd(char *);
> +void commit(void);
> +void list(void);
> +void ondesc(void *, struct sioctl_desc *, int);
> +void onctl(void *, unsigned, unsigned);
> +
> +struct sioctl_hdl *hdl;
> +struct info *infolist;
> +int i_flag = 0, v_flag = 0, m_flag = 0;
> +
> +static inline int
> +isname_first(int c)
> +{
> + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
> +}
> +
> +static inline int
> +isname_next(int c)
> +{
> + return isname_first(c) || (c >= '0' && c <= '9') || (c == '_');
> +}
> +
> +/*
> + * compare two sioctl_desc structures, used to sort infolist
> + */
> +int
> +cmpdesc(struct sioctl_desc *d1, struct sioctl_desc *d2)
> +{
> + int res;
> +
> + res = strcmp(d1->group, d2->group);
> + if (res != 0)
> + return res;
> + res = strcmp(d1->node0.name, d2->node0.name);
> + if (res != 0)
> + return res;
> + res = d1->type - d2->type;
> + if (res != 0)
> + return res;
> + res = strcmp(d1->func, d2->func);
> + if (res != 0)
> + return res;
> + res = d1->node0.unit - d2->node0.unit;
> + if (d1->type == SIOCTL_VEC ||
> +    d1->type == SIOCTL_LIST) {
> + if (res != 0)
> + return res;
> + res = strcmp(d1->node1.name, d2->node1.name);
> + if (res != 0)
> + return res;
> + res = d1->node1.unit - d2->node1.unit;
> + }
> + return res;
> +}
> +
> +/*
> + * return true of the vector entry is diagonal
> + */
> +int
> +isdiag(struct info *e)
> +{
> + if (e->desc.node0.unit < 0 || e->desc.node1.unit < 0)
> + return 1;
> + return e->desc.node1.unit == e->desc.node0.unit;
> +}
> +
> +/*
> + * find the selector or vector entry with the given name and channels
> + */
> +struct info *
> +vecent(struct info *i, char *vstr, int vunit)
> +{
> + while (i != NULL) {
> + if ((strcmp(i->desc.node1.name, vstr) == 0) &&
> +    (vunit < 0 || i->desc.node1.unit == vunit))
> + break;
> + i = i->next;
> + }
> + return i;
> +}
> +
> +/*
> + * skip all parameters with the same group, name, and func
> + */
> +struct info *
> +nextfunc(struct info *i)
> +{
> + char *str, *group, *func;
> +
> + group = i->desc.group;
> + func = i->desc.func;
> + str = i->desc.node0.name;
> + for (i = i->next; i != NULL; i = i->next) {
> + if (strcmp(i->desc.group, group) != 0 ||
> +    strcmp(i->desc.node0.name, str) != 0 ||
> +    strcmp(i->desc.func, func) != 0)
> + return i;
> + }
> + return NULL;
> +}
> +
> +/*
> + * find the next parameter with the same group, name, func
> + */
> +struct info *
> +nextpar(struct info *i)
> +{
> + char *str, *group, *func;
> + int unit;
> +
> + group = i->desc.group;
> + func = i->desc.func;
> + str = i->desc.node0.name;
> + unit = i->desc.node0.unit;
> + for (i = i->next; i != NULL; i = i->next) {
> + if (strcmp(i->desc.group, group) != 0 ||
> +    strcmp(i->desc.node0.name, str) != 0 ||
> +    strcmp(i->desc.func, func) != 0)
> + break;
> + /* XXX: need to check for -1 ? */
> + if (i->desc.node0.unit != unit)
> + return i;
> + }
> + return NULL;
> +}
> +
> +/*
> + * return the first vector entry with the given name
> + */
> +struct info *
> +firstent(struct info *g, char *vstr)
> +{
> + char *astr, *group, *func;
> + struct info *i;
> +
> + group = g->desc.group;
> + astr = g->desc.node0.name;
> + func = g->desc.func;
> + for (i = g; i != NULL; i = i->next) {
> + if (strcmp(i->desc.group, group) != 0 ||
> +    strcmp(i->desc.node0.name, astr) != 0 ||
> +    strcmp(i->desc.func, func) != 0)
> + break;
> + if (!isdiag(i))
> + continue;
> + if (strcmp(i->desc.node1.name, vstr) == 0)
> + return i;
> + }
> + return NULL;
> +}
> +
> +/*
> + * find the next entry of the given vector, if the mono flag
> + * is set then the whole group is searched and off-diagonal entries are
> + * skipped
> + */
> +struct info *
> +nextent(struct info *i, int mono)
> +{
> + char *str, *group, *func;
> + int unit;
> +
> + group = i->desc.group;
> + func = i->desc.func;
> + str = i->desc.node0.name;
> + unit = i->desc.node0.unit;
> + for (i = i->next; i != NULL; i = i->next) {
> + if (strcmp(i->desc.group, group) != 0 ||
> +    strcmp(i->desc.node0.name, str) != 0 ||
> +    strcmp(i->desc.func, func) != 0)
> + return NULL;
> + if (mono)
> + return i;
> + if (i->desc.node0.unit == unit)
> + return i;
> + }
> + return NULL;
> +}
> +
> +/*
> + * return true if parameter matches the given name and channel
> + */
> +int
> +matchpar(struct info *i, char *astr, int aunit)
> +{
> + if (strcmp(i->desc.node0.name, astr) != 0)
> + return 0;
> + if (aunit < 0)
> + return 1;
> + else if (i->desc.node0.unit < 0) {
> + fprintf(stderr, "unit used for parameter with no unit\n");
> + exit(1);
> + }
> + return i->desc.node0.unit == aunit;
> +}
> +
> +/*
> + * return true if selector or vector entry matches the given name and
> + * channel range
> + */
> +int
> +matchent(struct info *i, char *vstr, int vunit)
> +{
> + if (strcmp(i->desc.node1.name, vstr) != 0)
> + return 0;
> + if (vunit < 0)
> + return 1;
> + else if (i->desc.node1.unit < 0) {
> + fprintf(stderr, "unit used for parameter with no unit\n");
> + exit(1);
> + }
> + return i->desc.node1.unit == vunit;
> +}
> +
> +/*
> + * return true if the given group can be represented as a signle mono
> + * parameter
> + */
> +int
> +ismono(struct info *g)
> +{
> + struct info *p1, *p2;
> + struct info *e1, *e2;
> +
> + p1 = g;
> + switch (g->desc.type) {
> + case SIOCTL_NUM:
> + case SIOCTL_SW:
> + for (p2 = g; p2 != NULL; p2 = nextpar(p2)) {
> + if (p2->curval != p1->curval)
> + return 0;
> + }
> + break;
> + case SIOCTL_VEC:
> + case SIOCTL_LIST:
> + for (p2 = g; p2 != NULL; p2 = nextpar(p2)) {
> + for (e2 = p2; e2 != NULL; e2 = nextent(e2, 0)) {
> + if (!isdiag(e2)) {
> + if (e2->curval != 0)
> + return 0;
> + } else {
> + e1 = vecent(p1,
> +    e2->desc.node1.name,
> +    p1->desc.node0.unit);
> + if (e1 == NULL)
> + continue;
> + if (e1->curval != e2->curval)
> + return 0;
> + }
> + }
> + }
> + break;
> + }
> + return 1;
> +}
> +
> +/*
> + * print a sub-stream, eg. "spkr[4]"
> + */
> +void
> +print_node(struct sioctl_node *c, int mono)
> +{
> + printf("%s", c->name);
> + if (!mono && c->unit >= 0)
> + printf("[%d]", c->unit);
> +}
> +
> +/*
> + * print info about the parameter
> + */
> +void
> +print_desc(struct info *p, int mono)
> +{
> + struct info *e;
> + int more;
> +
> + switch (p->desc.type) {
> + case SIOCTL_NUM:
> + case SIOCTL_SW:
> + printf("*");
> + break;
> + case SIOCTL_VEC:
> + case SIOCTL_LIST:
> + more = 0;
> + for (e = p; e != NULL; e = nextent(e, mono)) {
> + if (mono) {
> + if (!isdiag(e))
> + continue;
> + if (e != firstent(p, e->desc.node1.name))
> + continue;
> + }
> + if (more)
> + printf(",");
> + print_node(&e->desc.node1, mono);
> + printf(":*");
> + more = 1;
> + }
> + }
> +}
> +
> +/*
> + * print parameter value
> + */
> +void
> +print_val(struct info *p, int mono)
> +{
> + struct info *e;
> + int more;
> +
> + switch (p->desc.type) {
> + case SIOCTL_NUM:
> + case SIOCTL_SW:
> + printf("%u", p->curval);
> + break;
> + case SIOCTL_VEC:
> + case SIOCTL_LIST:
> + more = 0;
> + for (e = p; e != NULL; e = nextent(e, mono)) {
> + if (mono) {
> + if (!isdiag(e))
> + continue;
> + if (e != firstent(p, e->desc.node1.name))
> + continue;
> + }
> + if (more)
> + printf(",");
> + print_node(&e->desc.node1, mono);
> + printf(":%u", e->curval);
> + more = 1;
> + }
> + }
> +}
> +
> +/*
> + * print ``<parameter>=<value>'' string (including '\n')
> + */
> +void
> +print_par(struct info *p, int mono, char *comment)
> +{
> + if (p->desc.group[0] != 0) {
> + printf("%s", p->desc.group);
> + printf("/");
> + }
> + print_node(&p->desc.node0, mono);
> + printf(".%s=", p->desc.func);
> + if (i_flag)
> + print_desc(p, mono);
> + else
> + print_val(p, mono);
> + if (comment)
> + printf(" # %s", comment);
> + printf("\n");
> +}
> +
> +/*
> + * parse a stream name or parameter name
> + */
> +int
> +parse_name(char **line, char *name)
> +{
> + char *p = *line;
> + unsigned len = 0;
> +
> + if (!isname_first(*p)) {
> + fprintf(stderr, "letter expected near '%s'\n", p);
> + return 0;
> + }
> + while (isname_next(*p)) {
> + if (len >= SIOCTL_NAMEMAX - 1) {
> + name[SIOCTL_NAMEMAX - 1] = '\0';
> + fprintf(stderr, "%s...: too long\n", name);
> + return 0;
> + }
> + name[len++] = *p;
> + p++;
> + }
> + name[len] = '\0';
> + *line = p;
> + return 1;
> +}
> +
> +/*
> + * parse a decimal number
> + */
> +int
> +parse_dec(char **line, int *num)
> +{
> +#define MAXQ (SIOCTL_VALMAX / 10)
> +#define MAXR (SIOCTL_VALMAX % 10)
> + char *p = *line;
> + unsigned int dig, val;
> +
> + val = 0;
> + for (;;) {
> + dig = *p - '0';
> + if (dig >= 10)
> + break;
> + if (val > MAXQ || (val == MAXQ && dig > MAXR)) {
> + fprintf(stderr, "integer overflow\n");
> + return 0;
> + }
> + val = val * 10 + dig;
> + p++;
> + }
> + if (p == *line) {
> + fprintf(stderr, "number expected near '%s'\n", p);
> + return 0;
> + }
> + *num = val;
> + *line = p;
> + return 1;
> +}
> +
> +/*
> + * parse a sub-stream, eg. "spkr[7]"
> + */
> +int
> +parse_node(char **line, char *str, int *unit)
> +{
> + char *p = *line;
> +
> + if (!parse_name(&p, str))
> + return 0;
> + if (*p != '[') {
> + *unit = -1;
> + *line = p;
> + return 1;
> + }
> + p++;
> + if (!parse_dec(&p, unit))
> + return 0;
> + if (*p != ']') {
> + fprintf(stderr, "']' expected near '%s'\n", p);
> + return 0;
> + }
> + p++;
> + *line = p;
> + return 1;
> +}
> +
> +/*
> + * parse a decimal prefixed by the optional mode
> + */
> +int
> +parse_modeval(char **line, int *rmode, int *rval)
> +{
> + char *p = *line;
> + unsigned mode;
> +
> + switch (*p) {
> + case '+':
> + mode = MODE_ADD;
> + p++;
> + break;
> + case '-':
> + mode = MODE_SUB;
> + p++;
> + break;
> + case '!':
> + mode = MODE_TOGGLE;
> + p++;
> + break;
> + default:
> + mode = MODE_SET;
> + }
> + if (mode != MODE_TOGGLE) {
> + if (!parse_dec(&p, rval))
> + return 0;
> + }
> + *line = p;
> + *rmode = mode;
> + return 1;
> +}
> +
> +/*
> + * dump the whole controls list, useful for debugging
> + */
> +void
> +dump(void)
> +{
> + struct info *i;
> +
> + for (i = infolist; i != NULL; i = i->next) {
> + printf("%03u:", i->ctladdr);
> + print_node(&i->desc.node0, 0);
> + printf(".%s", i->desc.func);
> + printf("=");
> + switch (i->desc.type) {
> + case SIOCTL_NUM:
> + case SIOCTL_SW:
> + printf("* (%u)", i->curval);
> + break;
> + case SIOCTL_VEC:
> + case SIOCTL_LIST:
> + print_node(&i->desc.node1, 0);
> + printf(":* (%u)", i->curval);
> + }
> + printf("\n");
> + }
> +}
> +
> +/*
> + * parse and execute a command ``<parameter>[=<value>]''
> + */
> +int
> +cmd(char *line)
> +{
> + char *pos = line;
> + struct info *i, *e, *g;
> + char group[SIOCTL_NAMEMAX];
> + char func[SIOCTL_NAMEMAX];
> + char astr[SIOCTL_NAMEMAX], vstr[SIOCTL_NAMEMAX];
> + int aunit, vunit;
> + unsigned npar = 0, nent = 0;
> + int val, comma, mode;
> +
> + if (!parse_name(&pos, group))
> + return 0;
> + if (*pos == '/')
> + pos++;
> + else {
> + /* this was node string, go backwards and assume no group */
> + pos = line;
> + group[0] = '\0';
> + }
> + if (!parse_node(&pos, astr, &aunit))
> + return 0;
> + if (*pos != '.') {
> + fprintf(stderr, "'.' expected near '%s'\n", pos);
> + return 0;
> + }
> + pos++;
> + if (!parse_name(&pos, func))
> + return 0;
> + for (g = infolist;; g = g->next) {
> + if (g == NULL) {
> + fprintf(stderr, "%s.%s: no such group\n", astr, func);
> + return 0;
> + }
> + if (strcmp(g->desc.group, group) == 0 &&
> +    strcmp(g->desc.func, func) == 0 &&
> +    strcmp(g->desc.node0.name, astr) == 0)
> + break;
> + }
> + g->mode = MODE_PRINT;
> + if (*pos != '=') {
> + if (*pos != '\0') {
> + fprintf(stderr, "junk at end of command\n");
> + return 0;
> + }
> + return 1;
> + }
> + pos++;
> + if (i_flag) {
> + printf("can't set values in info mode\n");
> + return 0;
> + }
> + npar = 0;
> + switch (g->desc.type) {
> + case SIOCTL_NUM:
> + case SIOCTL_SW:
> + if (!parse_modeval(&pos, &mode, &val))
> + return 0;
> + for (i = g; i != NULL; i = nextpar(i)) {
> + if (!matchpar(i, astr, aunit))
> + continue;
> + i->mode = mode;
> + i->newval = val;
> + npar++;
> + }
> + break;
> + case SIOCTL_VEC:
> + case SIOCTL_LIST:
> + for (i = g; i != NULL; i = nextpar(i)) {
> + if (!matchpar(i, astr, aunit))
> + continue;
> + for (e = i; e != NULL; e = nextent(e, 0)) {
> + e->newval = 0;
> + e->mode = MODE_SET;
> + }
> + npar++;
> + }
> + comma = 0;
> + for (;;) {
> + if (*pos == '\0')
> + break;
> + if (comma) {
> + if (*pos != ',')
> + break;
> + pos++;
> + }
> + if (!parse_node(&pos, vstr, &vunit))
> + return 0;
> + if (*pos == ':') {
> + pos++;
> + if (!parse_modeval(&pos, &mode, &val))
> + return 0;
> + } else {
> + val = SIOCTL_VALMAX;
> + mode = MODE_SET;
> + }
> + nent = 0;
> + for (i = g; i != NULL; i = nextpar(i)) {
> + if (!matchpar(i, astr, aunit))
> + continue;
> + for (e = i; e != NULL; e = nextent(e, 0)) {
> + if (matchent(e, vstr, vunit)) {
> + e->newval = val;
> + e->mode = mode;
> + nent++;
> + }
> + }
> + }
> + if (nent == 0) {
> + /* XXX: use print_node()-like routine */
> + fprintf(stderr, "%s[%d]: invalid value\n", vstr, vunit);
> + print_par(g, 0, NULL);
> + exit(1);
> + }
> + comma = 1;
> + }
> + }
> + if (npar == 0) {
> + fprintf(stderr, "%s: invalid parameter\n", line);
> + exit(1);
> + }
> + if (*pos != '\0') {
> + printf("%s: junk at end of command\n", pos);
> + exit(1);
> + }
> + return 1;
> +}
> +
> +/*
> + * write the controls with the ``set'' flag on the device
> + */
> +void
> +commit(void)
> +{
> + struct info *i;
> + int val;
> +
> + for (i = infolist; i != NULL; i = i->next) {
> + val = 0xdeadbeef;
> + switch (i->mode) {
> + case MODE_IGNORE:
> + case MODE_PRINT:
> + continue;
> + case MODE_SET:
> + val = i->newval;
> + break;
> + case MODE_ADD:
> + val = i->curval + i->newval;
> + if (val > SIOCTL_VALMAX)
> + val = SIOCTL_VALMAX;
> + break;
> + case MODE_SUB:
> + val = i->curval - i->newval;
> + if (val < 0)
> + val = 0;
> + break;
> + case MODE_TOGGLE:
> + val = (i->curval >= SIOCTL_VALMAX / 2) ?
> +    0 : SIOCTL_VALMAX;
> + }
> + switch (i->desc.type) {
> + case SIOCTL_NUM:
> + case SIOCTL_SW:
> + sioctl_setval(hdl, i->ctladdr, val);
> + break;
> + case SIOCTL_VEC:
> + case SIOCTL_LIST:
> + sioctl_setval(hdl, i->ctladdr, val);
> + }
> + i->curval = val;
> + }
> +}
> +
> +/*
> + * print all parameters
> + */
> +void
> +list(void)
> +{
> + struct info *p, *g;
> +
> + for (g = infolist; g != NULL; g = nextfunc(g)) {
> + if (g->mode == MODE_IGNORE)
> + continue;
> + if (i_flag) {
> + if (v_flag) {
> + for (p = g; p != NULL; p = nextpar(p))
> + print_par(p, 0, NULL);
> + } else
> + print_par(g, 1, NULL);
> + } else {
> + if (v_flag || !ismono(g)) {
> + for (p = g; p != NULL; p = nextpar(p))
> + print_par(p, 0, NULL);
> + } else
> + print_par(g, 1, NULL);
> + }
> + }
> +}
> +
> +/*
> + * register a new knob/button, called from the poll() loop.  this may be
> + * called when label string changes, in which case we update the
> + * existing label widged rather than inserting a new one.
> + */
> +void
> +ondesc(void *arg, struct sioctl_desc *d, int curval)
> +{
> + struct info *i, **pi;
> + int cmp;
> +
> + if (d == NULL)
> + return;
> +
> + /*
> + * delete control
> + */
> + for (pi = &infolist; (i = *pi) != NULL; pi = &i->next) {
> + if (d->addr == i->desc.addr) {
> + if (m_flag)
> + print_par(i, 0, "deleted");
> + *pi = i->next;
> + free(i);
> + break;
> + }
> + }
> +
> + if (d->type == SIOCTL_NONE)
> + return;
> +
> + /*
> + * find the right position to insert the new widget
> + */
> + for (pi = &infolist; (i = *pi) != NULL; pi = &i->next) {
> + cmp = cmpdesc(d, &i->desc);
> + if (cmp == 0) {
> + fprintf(stderr, "fatal: duplicate control:\n");
> + print_par(i, 0, "duplicate");
> + exit(1);
> + }
> + if (cmp < 0)
> + break;
> + }
> + i = malloc(sizeof(struct info));
> + if (i == NULL) {
> + perror("malloc");
> + exit(1);
> + }
> + i->desc = *d;
> + i->ctladdr = d->addr;
> + i->curval = i->newval = curval;
> + i->mode = MODE_IGNORE;
> + i->next = *pi;
> + *pi = i;
> + if (m_flag)
> + print_par(i, 0, "added");
> +}
> +
> +/*
> + * update a knob/button state, called from the poll() loop
> + */
> +void
> +onctl(void *arg, unsigned addr, unsigned val)
> +{
> + struct info *i;
> +
> + for (i = infolist; i != NULL; i = i->next) {
> + if (i->ctladdr != addr)
> + continue;
> + i->curval = val;
> + if (m_flag)
> + print_par(i, 0, "changed");
> + }
> +}
> +
> +int
> +main(int argc, char **argv)
> +{
> + char *devname = SIOCTL_DEVANY;
> + int i, c, d_flag = 0;
> + struct info *g;
> + struct pollfd *pfds;
> + int nfds, revents;
> +
> + while ((c = getopt(argc, argv, "df:imv")) != -1) {
> + switch (c) {
> + case 'd':
> + d_flag = 1;
> + break;
> + case 'f':
> + devname = optarg;
> + break;
> + case 'i':
> + i_flag = 1;
> + break;
> + case 'm':
> + m_flag = 1;
> + break;
> + case 'v':
> + v_flag++;
> + break;
> + default:
> + fprintf(stderr, "usage: sndioctl "
> +    "[-dimnv] [-f device] [command ...]\n");
> + exit(1);
> + }
> + }
> + argc -= optind;
> + argv += optind;
> +
> + hdl = sioctl_open(devname, SIOCTL_READ | SIOCTL_WRITE, 0);
> + if (hdl == NULL) {
> + fprintf(stderr, "%s: can't open control device\n", devname);
> + exit(1);
> + }
> + if (!sioctl_ondesc(hdl, ondesc, NULL)) {
> + fprintf(stderr, "%s: can't get device description\n", devname);
> + exit(1);
> + }
> + sioctl_onval(hdl, onctl, NULL);
> +
> + if (d_flag) {
> + if (argc > 0) {
> + fprintf(stderr,
> +    "commands are not allowed with -d option\n");
> + exit(1);
> + }
> + dump();
> + } else {
> + if (argc == 0) {
> + for (g = infolist; g != NULL; g = nextfunc(g))
> + g->mode = MODE_PRINT;
> + } else {
> + for (i = 0; i < argc; i++) {
> + if (!cmd(argv[i]))
> + return 1;
> + }
> + }
> + commit();
> + list();
> + }
> + if (m_flag) {
> + pfds = malloc(sizeof(struct pollfd) * sioctl_nfds(hdl));
> + if (pfds == NULL) {
> + perror("malloc");
> + exit(1);
> + }
> + for (;;) {
> + nfds = sioctl_pollfd(hdl, pfds, POLLIN);
> + if (nfds == 0)
> + break;
> + while (poll(pfds, nfds, -1) < 0) {
> + if (errno != EINTR) {
> + perror("poll");
> + exit(1);
> + }
> + }
> + revents = sioctl_revents(hdl, pfds);
> + if (revents & POLLHUP) {
> + fprintf(stderr, "disconnected\n");
> + break;
> + }
> + }
> + free(pfds);
> + }
> + sioctl_close(hdl);
> + return 0;
> +}
>

Reply | Threaded
Open this post in threaded view
|

Re: Audio control API, part 2: add new sndioctl(1) utility

Alexandre Ratchov-2
On Mon, Feb 10, 2020 at 09:59:09AM +0000, Raf Czlonka wrote:

> On Sun, Feb 09, 2020 at 12:14:47PM GMT, Alexandre Ratchov wrote:
> > Here's a new sndioctl utility similar to mixerctl(1) but using the new
> > sndio API. Example:
> >
> > $ sndioctl                                                    
> > output.level=127
> > app/aucat0.level=127
> > app/firefox0.level=127
> > app/firefox1.level=12
> > app/midisyn0.level=127
> > app/mpv0.level=127
> > app/prog5.level=127
> > app/prog6.level=127
> > app/prog7.level=127
> > hw/input.level=62
> > hw/input.mute=0
> > hw/output.level=63
> > hw/output.mute=0
> >
>
> Hi Alexandre,
>
> Just a quick question.
>
> Is there a good reason to have the above using "slash" ('/') as the
> first separator instead of the, more familiar, "dot" ('.') known
> from sysctl(8)'s MIB (Management Information Base) style names or
> even the "pseudo" MIB know from mixerctl(1)?

Hi,

I don't know if the following qualifies as a "good reason". The first
part (the group) is a prefix of the control identifier. The identifier
itself has a strict "<stream>[channel].<function>" format. The prefix
is not always present, examples:

        output.level <- sndiod volume knob
        hw/output.level <- underlying hardware volume knob

I tried to avoid the group part, but as mixers may be nested it seems
necessary to avoid name clashes.

In the sndioctl syntax, we could replace '/' by '.' but this looks
confusing as the syntax doesn't map directly to the underlying
model. But maybe we should hide such developer-centric details and
just use only dots to make this look as a MIB.

Another option I've considered is to drop the group concept in the API
and simply prefix the stream name to make it unique; in turn we obtain
a flat control list. It's uglier and seems to complicate GUIs
task. For instance the group part could be used to represent controls
of different groups in different sections or to filter-out certain
groups).

Reply | Threaded
Open this post in threaded view
|

Re: Audio control API, part 2: add new sndioctl(1) utility

Raf Czlonka-2
On Mon, Feb 10, 2020 at 09:14:57PM GMT, Alexandre Ratchov wrote:

> On Mon, Feb 10, 2020 at 09:59:09AM +0000, Raf Czlonka wrote:
> > On Sun, Feb 09, 2020 at 12:14:47PM GMT, Alexandre Ratchov wrote:
> > > Here's a new sndioctl utility similar to mixerctl(1) but using the new
> > > sndio API. Example:
> > >
> > > $ sndioctl                                                    
> > > output.level=127
> > > app/aucat0.level=127
> > > app/firefox0.level=127
> > > app/firefox1.level=12
> > > app/midisyn0.level=127
> > > app/mpv0.level=127
> > > app/prog5.level=127
> > > app/prog6.level=127
> > > app/prog7.level=127
> > > hw/input.level=62
> > > hw/input.mute=0
> > > hw/output.level=63
> > > hw/output.mute=0
> > >
> >
> > Hi Alexandre,
> >
> > Just a quick question.
> >
> > Is there a good reason to have the above using "slash" ('/') as the
> > first separator instead of the, more familiar, "dot" ('.') known
> > from sysctl(8)'s MIB (Management Information Base) style names or
> > even the "pseudo" MIB know from mixerctl(1)?
>
> Hi,
>
> I don't know if the following qualifies as a "good reason". The first
> part (the group) is a prefix of the control identifier. The identifier
> itself has a strict "<stream>[channel].<function>" format. The prefix
> is not always present, examples:
>
> output.level <- sndiod volume knob
> hw/output.level <- underlying hardware volume knob
>
> I tried to avoid the group part, but as mixers may be nested it seems
> necessary to avoid name clashes.
>
> In the sndioctl syntax, we could replace '/' by '.' but this looks
> confusing as the syntax doesn't map directly to the underlying
> model. But maybe we should hide such developer-centric details and
> just use only dots to make this look as a MIB.
>
> Another option I've considered is to drop the group concept in the API
> and simply prefix the stream name to make it unique; in turn we obtain
> a flat control list. It's uglier and seems to complicate GUIs
> task. For instance the group part could be used to represent controls
> of different groups in different sections or to filter-out certain
> groups).

Hi Alexandre,

I honestly can't tell which one of these would be "better" - best
if others chime in.

I was thinking only from a "uniform" interface angle, i.e. if (some
of) these are to be set from the command line, and its similarity
to MIB-like mixerctl(1) variables, I suspect it might cause some
"muscle memory"-related issues ;^)

If no one else shares it, then I rest my case.

Either way, thanks for the explanation.

Regards,

Raf

Reply | Threaded
Open this post in threaded view
|

Re: Audio control API, part 1: libsndio, sndiod bits

Raf Czlonka-2
In reply to this post by Alexandre Ratchov-2
On Sun, Feb 09, 2020 at 12:13:02PM GMT, Alexandre Ratchov wrote:

> +++ lib/libsndio/sioctl_aucat.c 8 Feb 2020 14:49:37 -0000
> [...]
> + * Copyright (c) 2010-2011 Alexandre Ratchov <[hidden email]>
>
> [...]
>
> +++ lib/libsndio/sioctl_open.3 8 Feb 2020 14:49:37 -0000
> [...]
> +.\" Copyright (c) 2011 Alexandre Ratchov <[hidden email]>
>
> [...]
>
> +++ lib/libsndio/sioctl_priv.h 8 Feb 2020 14:49:38 -0000
> [...]
> + * Copyright (c) 2008 Alexandre Ratchov <[hidden email]>
>
> [...]
>
> +++ lib/libsndio/sioctl_sun.c 8 Feb 2020 14:49:38 -0000
> [...]
> + * Copyright (c) 2010-2011 Alexandre Ratchov <[hidden email]>
>
> [...]
>
> +++ lib/libsndio/sioctl.c 8 Feb 2020 14:49:37 -0000
> [...]
> + * Copyright (c) 2008 Alexandre Ratchov <[hidden email]>
>
> [...]
>
> +++ usr.bin/sndioctl/sndioctl.1 9 Feb 2020 11:05:02 -0000
> [...]
> +.\" Copyright (c) 2007 Alexandre Ratchov <[hidden email]>
>
> [...]
>
> +++ usr.bin/sndioctl/sndioctl.c 9 Feb 2020 11:05:02 -0000
> [...]
> + * Copyright (c) 2007-2011 Alexandre Ratchov <[hidden email]>
>
> [...]
>
> +++ usr.bin/sndiod/dev_sioctl.c 8 Feb 2020 14:49:38 -0000
> [...]
> + * Copyright (c) 2014 Alexandre Ratchov <[hidden email]>
>
> [...]
>
> +++ usr.bin/sndiod/dev_sioctl.h 8 Feb 2020 14:49:38 -0000
> [...]
> + * Copyright (c) 2014 Alexandre Ratchov <[hidden email]>
>
> [...]
>

Hi Alexandre,

Shouldn't all of these dates be adjusted?

Regards,

Raf