Module Name:    src
Committed By:   nia
Date:           Fri May  7 16:29:24 UTC 2021

Added Files:
        src/usr.bin/aiomixer: Makefile aiomixer.1 app.h draw.c draw.h main.c
            parse.c parse.h

Log Message:
import aiomixer from git into usr.bin.

aiomixer is a graphical (curses-based) mixer for NetBSD audio.


To generate a diff of this commit:
cvs rdiff -u -r0 -r1.1 src/usr.bin/aiomixer/Makefile \
    src/usr.bin/aiomixer/aiomixer.1 src/usr.bin/aiomixer/app.h \
    src/usr.bin/aiomixer/draw.c src/usr.bin/aiomixer/draw.h \
    src/usr.bin/aiomixer/main.c src/usr.bin/aiomixer/parse.c \
    src/usr.bin/aiomixer/parse.h

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Added files:

Index: src/usr.bin/aiomixer/Makefile
diff -u /dev/null src/usr.bin/aiomixer/Makefile:1.1
--- /dev/null	Fri May  7 16:29:24 2021
+++ src/usr.bin/aiomixer/Makefile	Fri May  7 16:29:24 2021
@@ -0,0 +1,11 @@
+# $NetBSD: Makefile,v 1.1 2021/05/07 16:29:24 nia Exp $
+
+PROG=	aiomixer
+SRCS+=	main.c draw.c parse.c
+
+LDADD+=	-lcurses
+DPADD+=	$(LIBCURSES)
+
+WARNS= 6
+
+.include <bsd.prog.mk>
Index: src/usr.bin/aiomixer/aiomixer.1
diff -u /dev/null src/usr.bin/aiomixer/aiomixer.1:1.1
--- /dev/null	Fri May  7 16:29:24 2021
+++ src/usr.bin/aiomixer/aiomixer.1	Fri May  7 16:29:24 2021
@@ -0,0 +1,90 @@
+.\"     $NetBSD: aiomixer.1,v 1.1 2021/05/07 16:29:24 nia Exp $
+.\"
+.\" Copyright (c) 2021 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to The NetBSD Foundation
+.\" by Nia Alarie.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+.\" PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+.\" POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd April 4, 2019
+.Dt AIOMIXER 1
+.Os
+.Sh NAME
+.Nm aiomixer
+.Nd graphical mixer for
+.Nx
+audio
+.Sh SYNOPSIS
+.Nm aiomixer
+.Op Fl d Ar device
+.Op Fl u
+.Sh DESCRIPTION
+.Nm
+is a graphical frontend for
+.Xr mixer 4
+devices that runs in your terminal.
+.Pp
+.Nm
+is primarily controlled using the cursor keys, e.g. to select a
+control, or change a control's value.
+Movement similar to
+.Xr vi 1
+is also supported.
+The current class can be changed with the number keys.
+The Q or Escape key can be used to return to the previous pane or exit
+.Nm .
+.Pp
+By default, volume levels for individual channels cannot be changed
+separately.
+The U key can be pressed to toggle changing the volume of individual
+channels.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl d Ar device
+Used to specify an alternative mixer device.
+.It Fl u
+Used to unlock channels on start up.
+.El
+.Sh FILES
+.Bl -tag -width /dev/mixer[0-3] -compact
+.It Pa /dev/mixer
+.It Pa /dev/mixer[0-3]
+.El
+.Sh SEE ALSO
+.Xr mixerctl 1 ,
+.Xr mixer 4
+.Sh HISTORY
+The first version (only available in pkgsrc) didn't support
+.Dv AUDIO_MIXER_SET
+properly, among other bugs.
+.Sh AUTHORS
+.Nm
+was written by 
+.An Nia Alarie
+.Aq n...@netbsd.org .
+.Sh BUGS
+.Nm aiomixer
+shows whatever mixer nodes the audio device's driver returns.
+Some drivers do not provide the most user-friendly control names.
Index: src/usr.bin/aiomixer/app.h
diff -u /dev/null src/usr.bin/aiomixer/app.h:1.1
--- /dev/null	Fri May  7 16:29:24 2021
+++ src/usr.bin/aiomixer/app.h	Fri May  7 16:29:24 2021
@@ -0,0 +1,82 @@
+/* $NetBSD: app.h,v 1.1 2021/05/07 16:29:24 nia Exp $ */
+/*-
+ * Copyright (c) 2021 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Nia Alarie.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef APP_H
+#define APP_H
+#include <sys/audioio.h>
+#include <stdbool.h>
+
+enum aiomixer_state {
+	STATE_DEVICE_SELECT,
+	STATE_CLASS_SELECT,
+	STATE_CONTROL_SELECT
+};
+
+struct aiomixer_control {
+	struct mixer_devinfo info;
+	/* currently selected index for sets, channel for sliders */
+	int setindex;
+	int widget_y;
+	int height;
+	WINDOW *widgetpad;
+};
+
+struct aiomixer_class {
+	char name[MAX_AUDIO_DEV_LEN];
+	struct aiomixer_control controls[128];
+	unsigned int numcontrols;
+	WINDOW *widgetpad;
+	int index;
+	int height;
+};
+
+struct aiomixer {
+	int fd;
+	enum aiomixer_state state;
+	struct audio_device mixerdev;
+	struct aiomixer_class classes[128];
+	unsigned int numclasses;
+	unsigned int curclass;
+	unsigned int curcontrol;
+	bool channels_unlocked;
+	int class_scroll_y;
+	int last_max_x;
+	bool widgets_resized;
+	WINDOW *header;
+	WINDOW *classbar;
+};
+
+#define COLOR_CONTROL_SELECTED	1
+#define COLOR_LEVELS		2
+#define COLOR_SET_SELECTED	3
+#define COLOR_ENUM_OFF		4
+#define COLOR_ENUM_ON		5
+#define COLOR_ENUM_MISC		6
+
+#endif
Index: src/usr.bin/aiomixer/draw.c
diff -u /dev/null src/usr.bin/aiomixer/draw.c:1.1
--- /dev/null	Fri May  7 16:29:24 2021
+++ src/usr.bin/aiomixer/draw.c	Fri May  7 16:29:24 2021
@@ -0,0 +1,416 @@
+/* $NetBSD: draw.c,v 1.1 2021/05/07 16:29:24 nia Exp $ */
+/*-
+ * Copyright (c) 2021 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Nia Alarie.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+#include <sys/audioio.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <curses.h>
+#include <err.h>
+#include <stdlib.h>
+#include "draw.h"
+
+static void bold_on(WINDOW *);
+static void bold_off(WINDOW *);
+static int get_enum_color(const char *);
+static void draw_enum(struct aiomixer_control *, int, bool);
+static void draw_set(struct aiomixer_control *, int);
+static void draw_levels(struct aiomixer_control *,
+    const struct mixer_level *, bool, bool);
+
+static void
+bold_on(WINDOW *w)
+{
+	/*
+	 * Some (XXX: which?) legacy terminals do not support a Bold
+	 * attribute.  In this case, we fall back to standout.
+	 */
+	if (termattrs() & A_BOLD)
+		wattron(w, A_BOLD);
+	else
+		wattron(w, A_STANDOUT);
+}
+
+static void
+bold_off(WINDOW *w)
+{
+	chtype attrs = getattrs(w);
+
+	if (attrs & A_BOLD)
+		wattroff(w, A_BOLD);
+	if (attrs & A_STANDOUT)
+		wattroff(w, A_STANDOUT);
+}
+
+void
+draw_mixer_select(unsigned int num_mixers, unsigned int selected_mixer)
+{
+	struct audio_device dev;
+	char mixer_path[16];
+	unsigned int i;
+	int fd;
+
+	mvprintw(0, 0, "Select a mixer device:\n");
+
+	for (i = 0; i < num_mixers; ++i) {
+		(void)snprintf(mixer_path, sizeof(mixer_path),
+		    "/dev/mixer%d", i);
+		fd = open(mixer_path, O_RDWR);
+		if (fd == -1)
+			break;
+		if (ioctl(fd, AUDIO_GETDEV, &dev) < 0) {
+			close(fd);
+			break;
+		}
+		close(fd);
+		if (selected_mixer == i) {
+			bold_on(stdscr);
+			addstr("[*] ");
+		} else {
+			addstr("[ ] ");
+		}
+		printw("%s: %s %s %s\n", mixer_path,
+		    dev.name, dev.version, dev.config);
+		if (selected_mixer == i)
+			bold_off(stdscr);
+	}
+}
+
+void
+draw_control(struct aiomixer *aio,
+    struct aiomixer_control *control, bool selected)
+{
+	struct mixer_ctrl value;
+
+	value.dev = control->info.index;
+	value.type = control->info.type;
+	if (value.type == AUDIO_MIXER_VALUE)
+		value.un.value.num_channels = control->info.un.v.num_channels;
+
+	if (ioctl(aio->fd, AUDIO_MIXER_READ, &value) < 0)
+		err(EXIT_FAILURE, "failed to read from mixer device");
+
+	wclear(control->widgetpad);
+	if (selected) {
+		bold_on(control->widgetpad);
+		if (has_colors()) {
+			wattron(control->widgetpad,
+			    COLOR_PAIR(COLOR_CONTROL_SELECTED));
+		}
+		waddch(control->widgetpad, '*');
+		if (has_colors()) {
+			wattroff(control->widgetpad,
+			    COLOR_PAIR(COLOR_CONTROL_SELECTED));
+		}
+	}
+	wprintw(control->widgetpad, "%s\n", control->info.label.name);
+	if (selected)
+		bold_off(control->widgetpad);
+
+	switch (value.type) {
+	case AUDIO_MIXER_ENUM:
+		draw_enum(control, value.un.ord, selected);
+		break;
+	case AUDIO_MIXER_SET:
+		draw_set(control, value.un.mask);
+		break;
+	case AUDIO_MIXER_VALUE:
+		draw_levels(control, &value.un.value,
+		    aio->channels_unlocked, selected);
+		break;
+	}
+}
+
+void
+draw_screen(struct aiomixer *aio)
+{
+	int i, max_y;
+
+	/* Clear any leftovers if the screen changed. */
+	if (aio->widgets_resized) {
+		aio->widgets_resized = false;
+		max_y = getmaxy(stdscr);
+		for (i = 3; i < max_y; ++i) {
+			mvprintw(i, 0, "\n");
+		}
+	}
+
+	wnoutrefresh(stdscr);
+	wnoutrefresh(aio->header);
+	wnoutrefresh(aio->classbar);
+	pnoutrefresh(aio->classes[aio->curclass].widgetpad,
+	    aio->class_scroll_y, 0,
+	    3, 0,
+	    1 + aio->classes[aio->curclass].height, getmaxx(stdscr));
+	doupdate();
+}
+
+static int
+get_enum_color(const char *name)
+{
+	if (strcmp(name, AudioNon) == 0) {
+		return COLOR_ENUM_ON;
+	}
+	if (strcmp(name, AudioNoff) == 0) {
+		return COLOR_ENUM_OFF;
+	}
+
+	return COLOR_ENUM_MISC;
+}
+
+static void
+draw_enum(struct aiomixer_control *control, int ord, bool selected)
+{
+	struct audio_mixer_enum *e;
+	int color = COLOR_ENUM_MISC;
+	int i;
+
+	for (i = 0; i < control->info.un.e.num_mem; ++i) {
+		e = &control->info.un.e;
+		if (ord == e->member[i].ord && selected)
+			bold_on(control->widgetpad);
+		waddch(control->widgetpad, '[');
+		if (ord == e->member[i].ord) {
+			if (has_colors()) {
+				color = get_enum_color(e->member[i].label.name);
+				wattron(control->widgetpad,
+					COLOR_PAIR(color));
+			} else {
+				waddch(control->widgetpad, '*');
+			}
+		}
+		wprintw(control->widgetpad, "%s", e->member[i].label.name);
+		if (ord == control->info.un.e.member[i].ord) {
+			if (has_colors()) {
+				wattroff(control->widgetpad,
+					COLOR_PAIR(color));
+			}
+		}
+		waddch(control->widgetpad, ']');
+		if (ord == e->member[i].ord && selected)
+			bold_off(control->widgetpad);
+		if (i != (e->num_mem - 1))
+			waddstr(control->widgetpad, ", ");
+	}
+	waddch(control->widgetpad, '\n');
+}
+
+static void
+draw_set(struct aiomixer_control *control, int mask)
+{
+	int i;
+
+	for (i = 0; i < control->info.un.s.num_mem; ++i) {
+		waddch(control->widgetpad, '[');
+		if (mask & control->info.un.s.member[i].mask) {
+			if (has_colors()) {
+				wattron(control->widgetpad,
+					COLOR_PAIR(COLOR_SET_SELECTED));
+			}
+			waddch(control->widgetpad, '*');
+			if (has_colors()) {
+				wattroff(control->widgetpad,
+					COLOR_PAIR(COLOR_SET_SELECTED));
+			}
+		} else {
+			waddch(control->widgetpad, ' ');
+		}
+		waddstr(control->widgetpad, "] ");
+		if (control->setindex == i) {
+			bold_on(control->widgetpad);
+			waddch(control->widgetpad, '*');
+		}
+		wprintw(control->widgetpad, "%s",
+		    control->info.un.s.member[i].label.name);
+		if (control->setindex == i)
+			bold_off(control->widgetpad);
+		if (i != (control->info.un.s.num_mem - 1))
+			waddstr(control->widgetpad, ", ");
+	}
+}
+
+static void
+draw_levels(struct aiomixer_control *control,
+    const struct mixer_level *levels, bool channels_unlocked, bool selected)
+{
+	int i;
+	int j, nchars;
+
+	for (i = 0; i < control->info.un.v.num_channels; ++i) {
+		if ((selected && !channels_unlocked) ||
+		    (control->setindex == i && channels_unlocked)) {
+			bold_on(control->widgetpad);
+		}
+		wprintw(control->widgetpad, "[%3u/%3u ",
+		    levels->level[i], AUDIO_MAX_GAIN);
+		if (has_colors()) {
+			wattron(control->widgetpad,
+				COLOR_PAIR(COLOR_LEVELS));
+		}
+		nchars = (levels->level[i] *
+		    (getmaxx(control->widgetpad) - 11)) / AUDIO_MAX_GAIN;
+		for (j = 0; j < nchars; ++j)
+			waddch(control->widgetpad, '*');
+		if (has_colors()) {
+			wattroff(control->widgetpad,
+				COLOR_PAIR(COLOR_LEVELS));
+		}
+		nchars = getmaxx(control->widgetpad) - 11 - nchars;
+		for (j = 0; j < nchars; ++j)
+			waddch(control->widgetpad, ' ');
+		wprintw(control->widgetpad, "]\n");
+		if ((selected && !channels_unlocked) ||
+		    (control->setindex == i && channels_unlocked)) {
+			bold_off(control->widgetpad);
+		}
+	}
+}
+
+void
+draw_classbar(struct aiomixer *aio)
+{
+	unsigned int i;
+
+	wmove(aio->classbar, 0, 0);
+
+	for (i = 0; i < aio->numclasses; ++i) {
+		if (aio->curclass == i)
+			bold_on(aio->classbar);
+		wprintw(aio->classbar, "[%u:", i + 1);
+		if (aio->curclass == i) {
+			if (has_colors()) {
+				wattron(aio->classbar,
+					COLOR_PAIR(COLOR_CONTROL_SELECTED));
+			}
+			waddch(aio->classbar, '*');
+			if (has_colors()) {
+				wattroff(aio->classbar,
+					COLOR_PAIR(COLOR_CONTROL_SELECTED));
+			}
+		}
+		waddstr(aio->classbar, aio->classes[i].name);
+		if (aio->curclass == i)
+			bold_off(aio->classbar);
+		waddstr(aio->classbar, "] ");
+	}
+
+	wprintw(aio->classbar, "\n\n");
+}
+
+void
+draw_header(struct aiomixer *aio)
+{
+	mvwaddstr(aio->header, 0,
+	    getmaxx(aio->header) - (int)sizeof("NetBSD audio mixer") + 1,
+	    "NetBSD audio mixer");
+
+	if (aio->mixerdev.version[0] != '\0') {
+		wprintw(aio->header, "%s %s",
+		    aio->mixerdev.name, aio->mixerdev.version);
+	} else {
+		wprintw(aio->header, "%s", aio->mixerdev.name);
+	}
+}
+
+void
+create_widgets(struct aiomixer *aio)
+{
+	size_t i, j;
+	struct aiomixer_class *class;
+	struct aiomixer_control *control;
+
+	aio->header = newwin(1, getmaxx(stdscr), 0, 0);
+	if (aio->header == NULL)
+		errx(EXIT_FAILURE, "failed to create window");
+
+	aio->classbar = newwin(2, getmaxx(stdscr), 1, 0);
+	if (aio->classbar == NULL)
+		errx(EXIT_FAILURE, "failed to create window");
+
+	for (i = 0; i < aio->numclasses; ++i) {
+		class = &aio->classes[i];
+		class->height = 0;
+		class->widgetpad = newpad(4 * __arraycount(class->controls),
+		    getmaxx(stdscr));
+		if (class->widgetpad == NULL)
+			errx(EXIT_FAILURE, "failed to create curses pad");
+		for (j = 0; j < class->numcontrols; ++j) {
+			control = &class->controls[j];
+			switch (control->info.type) {
+			case AUDIO_MIXER_VALUE:
+				control->height = 2 +
+				    control->info.un.v.num_channels;
+				break;
+			case AUDIO_MIXER_ENUM:
+			case AUDIO_MIXER_SET:
+				control->height = 3;
+				break;
+			}
+			control->widgetpad = subpad(class->widgetpad,
+			    control->height, getmaxx(stdscr),
+			    class->height, 0);
+			if (control->widgetpad == NULL)
+				errx(EXIT_FAILURE, "failed to create curses pad");
+			control->widget_y = class->height;
+			class->height += control->height;
+		}
+		wresize(class->widgetpad, class->height, getmaxx(stdscr));
+	}
+
+	aio->last_max_x = getmaxx(stdscr);
+}
+
+void
+resize_widgets(struct aiomixer *aio)
+{
+	size_t i, j;
+	struct aiomixer_class *class;
+	struct aiomixer_control *control;
+	int max_x;
+
+	max_x = getmaxx(stdscr);
+
+	if (aio->last_max_x != max_x) {
+		aio->last_max_x = max_x;
+		wresize(aio->header, 1, max_x);
+		wresize(aio->classbar, 2, max_x);
+
+		for (i = 0; i < aio->numclasses; ++i) {
+			class = &aio->classes[i];
+			wresize(class->widgetpad, class->height, max_x);
+			for (j = 0; j < class->numcontrols; ++j) {
+				control = &class->controls[j];
+				wresize(control->widgetpad,
+				    control->height, max_x);
+			}
+		}
+	}
+
+	aio->widgets_resized = true;
+}
Index: src/usr.bin/aiomixer/draw.h
diff -u /dev/null src/usr.bin/aiomixer/draw.h:1.1
--- /dev/null	Fri May  7 16:29:24 2021
+++ src/usr.bin/aiomixer/draw.h	Fri May  7 16:29:24 2021
@@ -0,0 +1,51 @@
+/* $NetBSD: draw.h,v 1.1 2021/05/07 16:29:24 nia Exp $ */
+/*-
+ * Copyright (c) 2021 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Nia Alarie.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef DRAW_H
+#define DRAW_H
+
+#include <sys/audioio.h>
+#include <stdbool.h>
+#include "app.h"
+
+void draw_mixer_select(unsigned int, unsigned int);
+
+void create_widgets(struct aiomixer *);
+
+void resize_widgets(struct aiomixer *);
+
+void draw_control(struct aiomixer *, struct aiomixer_control *, bool);
+
+void draw_screen(struct aiomixer *);
+
+void draw_classbar(struct aiomixer *);
+
+void draw_header(struct aiomixer *);
+
+#endif
Index: src/usr.bin/aiomixer/main.c
diff -u /dev/null src/usr.bin/aiomixer/main.c:1.1
--- /dev/null	Fri May  7 16:29:24 2021
+++ src/usr.bin/aiomixer/main.c	Fri May  7 16:29:24 2021
@@ -0,0 +1,572 @@
+/* $NetBSD: main.c,v 1.1 2021/05/07 16:29:24 nia Exp $ */
+/*-
+ * Copyright (c) 2021 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Nia Alarie.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+#include <sys/audioio.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <signal.h>
+#include <paths.h>
+#include <curses.h>
+#include <stdlib.h>
+#include <err.h>
+#include "app.h"
+#include "draw.h"
+#include "parse.h"
+
+static void process_device_select(struct aiomixer *, unsigned int);
+static void open_device(struct aiomixer *, const char *);
+static void __dead usage(void);
+static int adjust_level(int, int);
+static int select_class(struct aiomixer *, unsigned int);
+static int select_control(struct aiomixer *, unsigned int);
+static void slide_control(struct aiomixer *, struct aiomixer_control *, bool);
+static int toggle_set(struct aiomixer *);
+static void step_up(struct aiomixer *);
+static void step_down(struct aiomixer *);
+static int read_key(struct aiomixer *, int);
+
+static void __dead
+usage(void)
+{
+	fputs("aiomixer [-u] [-d device]\n", stderr);
+	exit(1);
+}
+
+static int
+select_class(struct aiomixer *aio, unsigned int n)
+{
+	struct aiomixer_class *class;
+	unsigned i;
+
+	if (n >= aio->numclasses)
+		return -1;
+
+	class = &aio->classes[n];
+	aio->widgets_resized = true;
+	aio->class_scroll_y = 0;
+	aio->curcontrol = 0;
+	aio->curclass = n;
+	for (i = 0; i < class->numcontrols; ++i) {
+		class->controls[i].setindex = -1;
+		draw_control(aio, &class->controls[i], false);
+	}
+	draw_classbar(aio);
+	return 0;
+}
+
+static int
+select_control(struct aiomixer *aio, unsigned int n)
+{
+	struct aiomixer_class *class;
+	struct aiomixer_control *lastcontrol;
+	struct aiomixer_control *control;
+
+	class = &aio->classes[aio->curclass];
+
+	if (n >= class->numcontrols)
+		return -1;
+
+	lastcontrol = &class->controls[aio->curcontrol];
+	lastcontrol->setindex = -1;
+	draw_control(aio, lastcontrol, false);
+
+	control = &class->controls[n];
+	aio->curcontrol = n;
+	control->setindex = 0;
+	draw_control(aio, control, true);
+
+	if (aio->class_scroll_y > control->widget_y) {
+		aio->class_scroll_y = control->widget_y;
+		aio->widgets_resized = true;
+	}
+
+	if ((control->widget_y + control->height) >
+	    ((getmaxy(stdscr) - 4) + aio->class_scroll_y)) {
+		aio->class_scroll_y = control->widget_y;
+		aio->widgets_resized = true;
+	}
+	return 0;
+}
+
+static int
+adjust_level(int level, int delta)
+{
+	if (level > (AUDIO_MAX_GAIN - delta))
+		return AUDIO_MAX_GAIN;
+
+	if (delta < 0 && level < (AUDIO_MIN_GAIN + (-delta)))
+		return AUDIO_MIN_GAIN;
+
+	return level + delta;
+}
+
+static void
+slide_control(struct aiomixer *aio,
+    struct aiomixer_control *control, bool right)
+{
+	struct mixer_devinfo *info = &control->info;
+	struct mixer_ctrl value;
+	unsigned char *level;
+	int i, delta;
+	int cur_index = 0;
+
+	if (info->type != AUDIO_MIXER_SET) {
+		value.dev = info->index;
+		value.type = info->type;
+		if (info->type == AUDIO_MIXER_VALUE)
+			value.un.value.num_channels = info->un.v.num_channels;
+
+		if (ioctl(aio->fd, AUDIO_MIXER_READ, &value) < 0)
+			err(EXIT_FAILURE, "failed to read mixer control");
+	}
+
+	switch (info->type) {
+	case AUDIO_MIXER_VALUE:
+		delta = right ? info->un.v.delta : -info->un.v.delta;
+		/*
+		 * work around strange problem where the level can be
+		 * increased but not decreased, seen with uaudio(4)
+		 */
+		if (delta < 16)
+			delta *= 2;
+		if (aio->channels_unlocked) {
+			level = &value.un.value.level[control->setindex];
+			*level = (unsigned char)adjust_level(*level, delta);
+		} else {
+			for (i = 0; i < value.un.value.num_channels; ++i) {
+				level = &value.un.value.level[i];
+				*level = (unsigned char)adjust_level(*level, delta);
+			}
+		}
+		break;
+	case AUDIO_MIXER_ENUM:
+		for (i = 0; i < info->un.e.num_mem; ++i) {
+			if (info->un.e.member[i].ord == value.un.ord) {
+				cur_index = i;
+				break;
+			}
+		}
+		if (right) {
+			value.un.ord = cur_index < (info->un.e.num_mem - 1) ?
+			    info->un.e.member[cur_index + 1].ord :
+			    info->un.e.member[0].ord;
+		} else {
+			value.un.ord = cur_index > 0 ?
+			    info->un.e.member[cur_index - 1].ord :
+			    info->un.e.member[control->info.un.e.num_mem - 1].ord;
+		}
+		break;
+	case AUDIO_MIXER_SET:
+		if (right) {
+			control->setindex =
+			    control->setindex < (info->un.s.num_mem - 1) ?
+				control->setindex + 1 : 0;
+		} else {
+			control->setindex = control->setindex > 0 ?
+			    control->setindex - 1 :
+				control->info.un.s.num_mem - 1;
+		}
+		break;
+	}
+
+	if (info->type != AUDIO_MIXER_SET) {
+		if (ioctl(aio->fd, AUDIO_MIXER_WRITE, &value) < 0)
+			err(EXIT_FAILURE, "failed to adjust mixer control");
+	}
+
+	draw_control(aio, control, true);
+}
+
+static int
+toggle_set(struct aiomixer *aio)
+{
+	struct mixer_ctrl ctrl;
+	struct aiomixer_class *class = &aio->classes[aio->curclass];
+	struct aiomixer_control *control = &class->controls[aio->curcontrol];
+
+	ctrl.dev = control->info.index;
+	ctrl.type = control->info.type;
+
+	if (control->info.type != AUDIO_MIXER_SET)
+		return -1;
+
+	if (ioctl(aio->fd, AUDIO_MIXER_READ, &ctrl) < 0)
+		err(EXIT_FAILURE, "failed to read mixer control");
+
+	ctrl.un.mask ^= control->info.un.s.member[control->setindex].mask;
+
+	if (ioctl(aio->fd, AUDIO_MIXER_WRITE, &ctrl) < 0)
+		err(EXIT_FAILURE, "failed to read mixer control");
+
+	draw_control(aio, control, true);
+	return 0;
+}
+
+static void
+step_up(struct aiomixer *aio)
+{
+	struct aiomixer_class *class;
+	struct aiomixer_control *control;
+
+	class = &aio->classes[aio->curclass];
+	control = &class->controls[aio->curcontrol];
+
+	if (aio->channels_unlocked &&
+	    control->info.type == AUDIO_MIXER_VALUE &&
+	    control->setindex > 0) {
+		control->setindex--;
+		draw_control(aio, control, true);
+		return;
+	}
+	select_control(aio, aio->curcontrol - 1);
+}
+
+static void
+step_down(struct aiomixer *aio)
+{
+	struct aiomixer_class *class;
+	struct aiomixer_control *control;
+
+	class = &aio->classes[aio->curclass];
+	control = &class->controls[aio->curcontrol];
+
+	if (aio->channels_unlocked &&
+	    control->info.type == AUDIO_MIXER_VALUE &&
+	    control->setindex < (control->info.un.v.num_channels - 1)) {
+		control->setindex++;
+		draw_control(aio, control, true);
+		return;
+	}
+
+	select_control(aio, (aio->curcontrol + 1) % class->numcontrols);
+}
+
+static int
+read_key(struct aiomixer *aio, int ch)
+{
+	struct aiomixer_class *class;
+	struct aiomixer_control *control;
+	size_t i;
+
+	switch (ch) {
+	case KEY_RESIZE:
+		class = &aio->classes[aio->curclass];
+		resize_widgets(aio);
+		draw_header(aio);
+		draw_classbar(aio);
+		for (i = 0; i < class->numcontrols; ++i) {
+			draw_control(aio,
+			    &class->controls[i],
+			    aio->state == STATE_CONTROL_SELECT ?
+				(aio->curcontrol == i) : false);
+		}
+		break;
+	case KEY_LEFT:
+	case 'h':
+		if (aio->state == STATE_CLASS_SELECT) {
+			select_class(aio, aio->curclass > 0 ?
+			    aio->curclass - 1 : aio->numclasses - 1);
+		} else if (aio->state == STATE_CONTROL_SELECT) {
+			class = &aio->classes[aio->curclass];
+			slide_control(aio,
+			    &class->controls[aio->curcontrol], false);
+		}
+		break;
+	case KEY_RIGHT:
+	case 'l':
+		if (aio->state == STATE_CLASS_SELECT) {
+			select_class(aio,
+			    (aio->curclass + 1) % aio->numclasses);
+		} else if (aio->state == STATE_CONTROL_SELECT) {
+			class = &aio->classes[aio->curclass];
+			slide_control(aio,
+			    &class->controls[aio->curcontrol], true);
+		}
+		break;
+	case KEY_UP:
+	case 'k':
+		if (aio->state == STATE_CONTROL_SELECT) {
+			if (aio->curcontrol == 0) {
+				class = &aio->classes[aio->curclass];
+				control = &class->controls[aio->curcontrol];
+				control->setindex = -1;
+				aio->state = STATE_CLASS_SELECT;
+				draw_control(aio, control, false);
+			} else {
+				step_up(aio);
+			}
+		}
+		break;
+	case KEY_DOWN:
+	case 'j':
+		if (aio->state == STATE_CLASS_SELECT) {
+			class = &aio->classes[aio->curclass];
+			if (class->numcontrols > 0) {
+				aio->state = STATE_CONTROL_SELECT;
+				select_control(aio, 0);
+			}
+		} else if (aio->state == STATE_CONTROL_SELECT) {
+			step_down(aio);
+		}
+		break;
+	case '\n':
+	case ' ':
+		if (aio->state == STATE_CONTROL_SELECT)
+			toggle_set(aio);
+		break;
+	case '1':
+		select_class(aio, 0);
+		break;
+	case '2':
+		select_class(aio, 1);
+		break;
+	case '3':
+		select_class(aio, 2);
+		break;
+	case '4':
+		select_class(aio, 3);
+		break;
+	case '5':
+		select_class(aio, 4);
+		break;
+	case '6':
+		select_class(aio, 5);
+		break;
+	case '7':
+		select_class(aio, 6);
+		break;
+	case '8':
+		select_class(aio, 7);
+		break;
+	case '9':
+		select_class(aio, 8);
+		break;
+	case 'q':
+	case '\e':
+		if (aio->state == STATE_CONTROL_SELECT) {
+			class = &aio->classes[aio->curclass];
+			control = &class->controls[aio->curcontrol];
+			aio->state = STATE_CLASS_SELECT;
+			draw_control(aio, control, false);
+			break;
+		}
+		return 1;
+	case 'u':
+		aio->channels_unlocked = !aio->channels_unlocked;
+		if (aio->state == STATE_CONTROL_SELECT) {
+			class = &aio->classes[aio->curclass];
+			control = &class->controls[aio->curcontrol];
+			if (control->info.type == AUDIO_MIXER_VALUE)
+				draw_control(aio, control, true);
+		}
+		break;
+	}
+
+	draw_screen(aio);
+	return 0;
+}
+
+static void
+process_device_select(struct aiomixer *aio, unsigned int num_devices)
+{
+	unsigned int selected_device = 0;
+	char device_path[16];
+	int ch;
+
+	draw_mixer_select(num_devices, selected_device);
+
+	while ((ch = getch()) != ERR) {
+		switch (ch) {
+		case '\n':
+			(void)snprintf(device_path, sizeof(device_path),
+			    "/dev/mixer%d", selected_device);
+			open_device(aio, device_path);
+			return;
+		case KEY_UP:
+		case 'k':
+			if (selected_device > 0)
+				selected_device--;
+			else
+				selected_device = (num_devices - 1);
+			break;
+		case KEY_DOWN:
+		case 'j':
+			if (selected_device < (num_devices - 1))
+				selected_device++;
+			else
+				selected_device = 0;
+			break;
+		case '1':
+			selected_device = 0;
+			break;
+		case '2':
+			selected_device = 1;
+			break;
+		case '3':
+			selected_device = 2;
+			break;
+		case '4':
+			selected_device = 3;
+			break;
+		case '5':
+			selected_device = 4;
+			break;
+		case '6':
+			selected_device = 5;
+			break;
+		case '7':
+			selected_device = 6;
+			break;
+		case '8':
+			selected_device = 7;
+			break;
+		case '9':
+			selected_device = 8;
+			break;
+		}
+		draw_mixer_select(num_devices, selected_device);
+	}
+}
+
+static void
+open_device(struct aiomixer *aio, const char *device)
+{
+	int ch;
+
+	if ((aio->fd = open(device, O_RDWR)) < 0)
+		err(EXIT_FAILURE, "couldn't open mixer device");
+
+	if (ioctl(aio->fd, AUDIO_GETDEV, &aio->mixerdev) < 0)
+		err(EXIT_FAILURE, "AUDIO_GETDEV failed");
+
+	aio->state = STATE_CLASS_SELECT;
+
+	aiomixer_parse(aio);
+
+	create_widgets(aio);
+
+	draw_header(aio);
+	select_class(aio, 0);
+	draw_screen(aio);
+
+	while ((ch = getch()) != ERR) {
+		if (read_key(aio, ch) != 0)
+			break;
+	}
+}
+
+static void
+on_signal(int dummy)
+{
+	endwin();
+	exit(0);
+}
+
+int
+main(int argc, char **argv)
+{
+	const char *mixer_device = NULL;
+	extern char *optarg;
+	extern int optind;
+	struct aiomixer *aio;
+	char mixer_path[32];
+	unsigned int mixer_count = 0;
+	int i, fd;
+	int ch;
+
+	if ((aio = malloc(sizeof(struct aiomixer))) == NULL) {
+		err(EXIT_FAILURE, "malloc failed");
+	}
+
+	while ((ch = getopt(argc, argv, "d:u")) != -1) {
+		switch (ch) {
+		case 'd':
+			mixer_device = optarg;
+			break;
+		case 'u':
+			aio->channels_unlocked = true;
+			break;
+		default:
+			usage();
+			break;
+		}
+	}
+
+	argc -= optind;
+	argv += optind;
+
+	if (initscr() == NULL)
+		err(EXIT_FAILURE, "can't initialize curses");
+
+	(void)signal(SIGHUP, on_signal);
+	(void)signal(SIGINT, on_signal);
+	(void)signal(SIGTERM, on_signal);
+
+	curs_set(0);
+	keypad(stdscr, TRUE);
+	cbreak();
+	noecho();
+
+	if (has_colors()) {
+		start_color();
+		init_pair(COLOR_CONTROL_SELECTED, COLOR_BLUE, COLOR_BLACK);
+		init_pair(COLOR_LEVELS, COLOR_GREEN, COLOR_BLACK);
+		init_pair(COLOR_SET_SELECTED, COLOR_BLACK, COLOR_GREEN);
+		init_pair(COLOR_ENUM_ON, COLOR_WHITE, COLOR_RED);
+		init_pair(COLOR_ENUM_OFF, COLOR_WHITE, COLOR_BLUE);
+		init_pair(COLOR_ENUM_MISC, COLOR_BLACK, COLOR_YELLOW);
+	}
+
+	if (mixer_device != NULL) {
+		open_device(aio, mixer_device);
+	} else {
+		for (i = 0; i < 16; ++i) {
+			(void)snprintf(mixer_path, sizeof(mixer_path),
+			    "/dev/mixer%d", i);
+			fd = open(mixer_path, O_RDWR);
+			if (fd == -1)
+				break;
+			close(fd);
+			mixer_count++;
+		}
+
+		if (mixer_count > 1) {
+			process_device_select(aio, mixer_count);
+		} else {
+			open_device(aio, _PATH_MIXER);
+		} 
+	}
+
+	endwin();
+	close(aio->fd);
+	free(aio);
+
+	return 0;
+}
Index: src/usr.bin/aiomixer/parse.c
diff -u /dev/null src/usr.bin/aiomixer/parse.c:1.1
--- /dev/null	Fri May  7 16:29:24 2021
+++ src/usr.bin/aiomixer/parse.c	Fri May  7 16:29:24 2021
@@ -0,0 +1,115 @@
+/* $NetBSD: parse.c,v 1.1 2021/05/07 16:29:24 nia Exp $ */
+/*-
+ * Copyright (c) 2021 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Nia Alarie.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+#include <sys/audioio.h>
+#include <sys/ioctl.h>
+#include <curses.h>
+#include <stdlib.h>
+#include "app.h"
+#include "parse.h"
+
+static struct aiomixer_class *get_class(struct aiomixer *, int);
+static int compare_control(const void *, const void *);
+
+static struct aiomixer_class *
+get_class(struct aiomixer *aio, int class)
+{
+	size_t i;
+
+	for (i = 0; i < aio->numclasses; ++i) {
+		if (aio->classes[i].index == class) {
+			return &aio->classes[i];
+		}
+	}
+	return NULL;
+}
+
+static int
+compare_control(const void *pa, const void *pb)
+{
+	const struct aiomixer_control *a = (const struct aiomixer_control *)pa;
+	const struct aiomixer_control *b = (const struct aiomixer_control *)pb;
+
+	if (a->info.prev != AUDIO_MIXER_LAST &&
+	    b->info.prev != AUDIO_MIXER_LAST) {
+		if (b->info.prev == a->info.index)
+			return -1;
+		if (a->info.prev == b->info.index)
+			return 1;
+	}
+	return strcmp(a->info.label.name, b->info.label.name);
+}
+
+int
+aiomixer_parse(struct aiomixer *aio)
+{
+	size_t i;
+	struct mixer_devinfo info;
+	struct aiomixer_class *class;
+	struct aiomixer_control *control;
+
+	for (info.index = 0;
+	    ioctl(aio->fd, AUDIO_MIXER_DEVINFO, &info) != -1; ++info.index) {
+		if (info.type != AUDIO_MIXER_CLASS)
+			continue;
+		if (aio->numclasses >= __arraycount(aio->classes))
+			break;
+		class = &aio->classes[aio->numclasses++];
+		memcpy(class->name, info.label.name, MAX_AUDIO_DEV_LEN);
+		class->index = info.index;
+		class->numcontrols = 0;
+	}
+	for (info.index = 0;
+	    ioctl(aio->fd, AUDIO_MIXER_DEVINFO, &info) != -1; ++info.index) {
+		if (info.type == AUDIO_MIXER_CLASS)
+			continue;
+		if (info.type == AUDIO_MIXER_VALUE) {
+			/* XXX workaround for hdaudio(4) bugs */
+			if (info.un.v.delta > AUDIO_MAX_GAIN)
+				continue;
+			if (info.un.v.num_channels == 0)
+				continue;
+		}
+		class = get_class(aio, info.mixer_class);
+		if (class == NULL)
+			break;
+		if (class->numcontrols >= __arraycount(class->controls))
+			break;
+		control = &class->controls[class->numcontrols++];
+		control->info = info;
+	}
+	for (i = 0; i < aio->numclasses; ++i) {
+		class = &aio->classes[i];
+		qsort(class->controls, class->numcontrols,
+		    sizeof(struct aiomixer_control),
+		    compare_control);
+	}
+	return 0;
+}
+
Index: src/usr.bin/aiomixer/parse.h
diff -u /dev/null src/usr.bin/aiomixer/parse.h:1.1
--- /dev/null	Fri May  7 16:29:24 2021
+++ src/usr.bin/aiomixer/parse.h	Fri May  7 16:29:24 2021
@@ -0,0 +1,37 @@
+/* $NetBSD: parse.h,v 1.1 2021/05/07 16:29:24 nia Exp $ */
+/*-
+ * Copyright (c) 2021 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Nia Alarie.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef PARSE_H
+#define PARSE_H
+
+#include "app.h"
+
+int aiomixer_parse(struct aiomixer *);
+
+#endif

Reply via email to