commit 7d6319d403d85cebe77bbc7c02cb2983c7ce9429
parent a247c24486bdb938591e98224dd92567dff93ef9
Author: Christos Margiolis <christos@margiolis.net>
Date: Tue, 28 Sep 2021 18:43:28 +0300
pushed to upstream
Diffstat:
11 files changed, 1123 insertions(+), 747 deletions(-)
diff --git a/LICENSE b/LICENSE
@@ -0,0 +1,20 @@
+MIT License
+
+(c) 2021-Present Christos Margiolis <christos@FreeBSD.org>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the “Software”), to deal in
+the Software without restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README b/README
@@ -2,8 +2,7 @@ mixer
=====
An OSS mixer library implementation, a complete rewrite of mixer(8)
-and sound(4) update for FreeBSD. Some of its development is part of
-Google Summer of Code 2021.
+and sound(4) update for FreeBSD.
Usage
-----
diff --git a/diff/mixer_kern.diff b/diff/mixer_kern.diff
@@ -1,3 +1,341 @@
+diff --git a/sys/dev/sound/pcm/channel.c b/sys/dev/sound/pcm/channel.c
+index ecf89df4c9a..234a0cb2435 100644
+--- a/sys/dev/sound/pcm/channel.c
++++ b/sys/dev/sound/pcm/channel.c
+@@ -1218,6 +1218,8 @@ chn_init(struct pcm_channel *c, void *devinfo, int dir, int direction)
+ c->volume[SND_VOL_C_MASTER][SND_CHN_T_VOL_0DB] = SND_VOL_0DB_MASTER;
+ c->volume[SND_VOL_C_PCM][SND_CHN_T_VOL_0DB] = chn_vol_0db_pcm;
+
++ memset(c->muted, 0, sizeof(c->muted));
++
+ chn_vpc_reset(c, SND_VOL_C_PCM, 1);
+
+ ret = ENODEV;
+@@ -1389,6 +1391,75 @@ chn_getvolume_matrix(struct pcm_channel *c, int vc, int vt)
+ return (c->volume[vc][vt]);
+ }
+
++int
++chn_setmute_multi(struct pcm_channel *c, int vc, int mute)
++{
++ int i, ret;
++
++ ret = 0;
++
++ for (i = 0; i < SND_CHN_T_MAX; i++) {
++ if ((1 << i) & SND_CHN_LEFT_MASK)
++ ret |= chn_setmute_matrix(c, vc, i, mute);
++ else if ((1 << i) & SND_CHN_RIGHT_MASK)
++ ret |= chn_setmute_matrix(c, vc, i, mute) << 8;
++ else
++ ret |= chn_setmute_matrix(c, vc, i, mute) << 16;
++ }
++ return (ret);
++}
++
++int
++chn_setmute_matrix(struct pcm_channel *c, int vc, int vt, int mute)
++{
++ int i;
++
++ KASSERT(c != NULL && vc >= SND_VOL_C_MASTER && vc < SND_VOL_C_MAX &&
++ (vc == SND_VOL_C_MASTER || (vc & 1)) &&
++ (vt == SND_CHN_T_VOL_0DB || (vt >= SND_CHN_T_BEGIN && vt <= SND_CHN_T_END)),
++ ("%s(): invalid mute matrix c=%p vc=%d vt=%d mute=%d",
++ __func__, c, vc, vt, mute));
++
++ CHN_LOCKASSERT(c);
++
++ mute = (mute != 0);
++
++ c->muted[vc][vt] = mute;
++
++ /*
++ * Do relative calculation here and store it into class + 1
++ * to ease the job of feeder_volume.
++ */
++ if (vc == SND_VOL_C_MASTER) {
++ for (vc = SND_VOL_C_BEGIN; vc <= SND_VOL_C_END;
++ vc += SND_VOL_C_STEP)
++ c->muted[SND_VOL_C_VAL(vc)][vt] = mute;
++ } else if (vc & 1) {
++ if (vt == SND_CHN_T_VOL_0DB) {
++ for (i = SND_CHN_T_BEGIN; i <= SND_CHN_T_END;
++ i += SND_CHN_T_STEP) {
++ c->muted[SND_VOL_C_VAL(vc)][i] = mute;
++ }
++ } else {
++ c->muted[SND_VOL_C_VAL(vc)][vt] = mute;
++ }
++ }
++ return (mute);
++}
++
++int
++chn_getmute_matrix(struct pcm_channel *c, int vc, int vt)
++{
++ KASSERT(c != NULL && vc >= SND_VOL_C_MASTER && vc < SND_VOL_C_MAX &&
++ (vt == SND_CHN_T_VOL_0DB ||
++ (vt >= SND_CHN_T_BEGIN && vt <= SND_CHN_T_END)),
++ ("%s(): invalid mute matrix c=%p vc=%d vt=%d",
++ __func__, c, vc, vt));
++ CHN_LOCKASSERT(c);
++
++ return (c->muted[vc][vt]);
++}
++
+ struct pcmchan_matrix *
+ chn_getmatrix(struct pcm_channel *c)
+ {
+diff --git a/sys/dev/sound/pcm/channel.h b/sys/dev/sound/pcm/channel.h
+index 969b8dba27d..45b517f22ce 100644
+--- a/sys/dev/sound/pcm/channel.h
++++ b/sys/dev/sound/pcm/channel.h
+@@ -166,7 +166,8 @@ struct pcm_channel {
+ struct pcmchan_matrix matrix;
+ struct pcmchan_matrix matrix_scratch;
+
+- int volume[SND_VOL_C_MAX][SND_CHN_T_VOL_MAX];
++ int16_t volume[SND_VOL_C_MAX][SND_CHN_T_VOL_MAX];
++ int8_t muted[SND_VOL_C_MAX][SND_CHN_T_VOL_MAX];
+
+ void *data1, *data2;
+ };
+@@ -271,6 +272,9 @@ int chn_setvolume_multi(struct pcm_channel *c, int vc, int left, int right,
+ int center);
+ int chn_setvolume_matrix(struct pcm_channel *c, int vc, int vt, int val);
+ int chn_getvolume_matrix(struct pcm_channel *c, int vc, int vt);
++int chn_setmute_multi(struct pcm_channel *c, int vc, int mute);
++int chn_setmute_matrix(struct pcm_channel *c, int vc, int vt, int mute);
++int chn_getmute_matrix(struct pcm_channel *c, int vc, int vt);
+ void chn_vpc_reset(struct pcm_channel *c, int vc, int force);
+ int chn_setparam(struct pcm_channel *c, uint32_t format, uint32_t speed);
+ int chn_setspeed(struct pcm_channel *c, uint32_t speed);
+@@ -307,6 +311,8 @@ int chn_syncdestroy(struct pcm_channel *c);
+ #define CHN_GETVOLUME(x, y, z) ((x)->volume[y][z])
+ #endif
+
++#define CHN_GETMUTE(x, y, z) ((x)->muted[y][z])
++
+ #ifdef OSSV4_EXPERIMENT
+ int chn_getpeaks(struct pcm_channel *c, int *lpeak, int *rpeak);
+ #endif
+diff --git a/sys/dev/sound/pcm/dsp.c b/sys/dev/sound/pcm/dsp.c
+index 9dfc47d60fb..0c5c8af341d 100644
+--- a/sys/dev/sound/pcm/dsp.c
++++ b/sys/dev/sound/pcm/dsp.c
+@@ -964,6 +964,7 @@ dsp_ioctl_channel(struct cdev *dev, struct pcm_channel *volch, u_long cmd,
+ struct snddev_info *d;
+ struct pcm_channel *rdch, *wrch;
+ int j, devtype, ret;
++ int left, right, center, mute;
+
+ d = dsp_get_info(dev);
+ if (!PCM_REGISTERED(d) || !(dsp_get_flags(dev) & SD_F_VPC))
+@@ -1002,67 +1003,95 @@ dsp_ioctl_channel(struct cdev *dev, struct pcm_channel *volch, u_long cmd,
+ }
+
+ /* Final validation */
+- if (volch != NULL) {
+- CHN_LOCK(volch);
+- if (!(volch->feederflags & (1 << FEEDER_VOLUME))) {
+- CHN_UNLOCK(volch);
+- return (-1);
+- }
+- if (volch->direction == PCMDIR_PLAY)
+- wrch = volch;
+- else
+- rdch = volch;
+- }
+-
+- ret = EINVAL;
++ if (volch == NULL)
++ return (EINVAL);
+
+- if (volch != NULL &&
+- ((j == SOUND_MIXER_PCM && volch->direction == PCMDIR_PLAY) ||
+- (j == SOUND_MIXER_RECLEV && volch->direction == PCMDIR_REC))) {
+- if ((cmd & ~0xff) == MIXER_WRITE(0)) {
+- int left, right, center;
++ CHN_LOCK(volch);
++ if (!(volch->feederflags & (1 << FEEDER_VOLUME))) {
++ CHN_UNLOCK(volch);
++ return (EINVAL);
++ }
+
++ switch (cmd & ~0xff) {
++ case MIXER_WRITE(0):
++ switch (j) {
++ case SOUND_MIXER_MUTE:
++ if (volch->direction == PCMDIR_REC) {
++ chn_setmute_multi(volch, SND_VOL_C_PCM, (*(int *)arg & SOUND_MASK_RECLEV) != 0);
++ } else {
++ chn_setmute_multi(volch, SND_VOL_C_PCM, (*(int *)arg & SOUND_MASK_PCM) != 0);
++ }
++ break;
++ case SOUND_MIXER_PCM:
++ if (volch->direction != PCMDIR_PLAY)
++ break;
+ left = *(int *)arg & 0x7f;
+ right = ((*(int *)arg) >> 8) & 0x7f;
+ center = (left + right) >> 1;
+- chn_setvolume_multi(volch, SND_VOL_C_PCM, left, right,
+- center);
+- } else if ((cmd & ~0xff) == MIXER_READ(0)) {
+- *(int *)arg = CHN_GETVOLUME(volch,
+- SND_VOL_C_PCM, SND_CHN_T_FL);
+- *(int *)arg |= CHN_GETVOLUME(volch,
+- SND_VOL_C_PCM, SND_CHN_T_FR) << 8;
++ chn_setvolume_multi(volch, SND_VOL_C_PCM,
++ left, right, center);
++ break;
++ case SOUND_MIXER_RECLEV:
++ if (volch->direction != PCMDIR_REC)
++ break;
++ left = *(int *)arg & 0x7f;
++ right = ((*(int *)arg) >> 8) & 0x7f;
++ center = (left + right) >> 1;
++ chn_setvolume_multi(volch, SND_VOL_C_PCM,
++ left, right, center);
++ break;
++ default:
++ /* ignore all other mixer writes */
++ break;
+ }
+- ret = 0;
+- } else if (rdch != NULL || wrch != NULL) {
++ break;
++
++ case MIXER_READ(0):
+ switch (j) {
++ case SOUND_MIXER_MUTE:
++ mute = CHN_GETMUTE(volch, SND_VOL_C_PCM, SND_CHN_T_FL) ||
++ CHN_GETMUTE(volch, SND_VOL_C_PCM, SND_CHN_T_FR);
++ if (volch->direction == PCMDIR_REC) {
++ *(int *)arg = mute << SOUND_MIXER_RECLEV;
++ } else {
++ *(int *)arg = mute << SOUND_MIXER_PCM;
++ }
++ break;
++ case SOUND_MIXER_PCM:
++ if (volch->direction != PCMDIR_PLAY)
++ break;
++ *(int *)arg = CHN_GETVOLUME(volch,
++ SND_VOL_C_PCM, SND_CHN_T_FL);
++ *(int *)arg |= CHN_GETVOLUME(volch,
++ SND_VOL_C_PCM, SND_CHN_T_FR) << 8;
++ break;
++ case SOUND_MIXER_RECLEV:
++ if (volch->direction != PCMDIR_REC)
++ break;
++ *(int *)arg = CHN_GETVOLUME(volch,
++ SND_VOL_C_PCM, SND_CHN_T_FL);
++ *(int *)arg |= CHN_GETVOLUME(volch,
++ SND_VOL_C_PCM, SND_CHN_T_FR) << 8;
++ break;
+ case SOUND_MIXER_DEVMASK:
+ case SOUND_MIXER_CAPS:
+ case SOUND_MIXER_STEREODEVS:
+- if ((cmd & ~0xff) == MIXER_READ(0)) {
+- *(int *)arg = 0;
+- if (rdch != NULL)
+- *(int *)arg |= SOUND_MASK_RECLEV;
+- if (wrch != NULL)
+- *(int *)arg |= SOUND_MASK_PCM;
+- }
+- ret = 0;
+- break;
+- case SOUND_MIXER_RECMASK:
+- case SOUND_MIXER_RECSRC:
+- if ((cmd & ~0xff) == MIXER_READ(0))
+- *(int *)arg = 0;
+- ret = 0;
++ if (volch->direction == PCMDIR_REC)
++ *(int *)arg = SOUND_MASK_RECLEV;
++ else
++ *(int *)arg = SOUND_MASK_PCM;
+ break;
+ default:
++ *(int *)arg = 0;
+ break;
+ }
+- }
+-
+- if (volch != NULL)
+- CHN_UNLOCK(volch);
++ break;
+
+- return (ret);
++ default:
++ break;
++ }
++ CHN_UNLOCK(volch);
++ return (0);
+ }
+
+ static int
+diff --git a/sys/dev/sound/pcm/feeder_volume.c b/sys/dev/sound/pcm/feeder_volume.c
+index 322d7f6b2c8..2312bd89c9d 100644
+--- a/sys/dev/sound/pcm/feeder_volume.c
++++ b/sys/dev/sound/pcm/feeder_volume.c
+@@ -237,10 +237,13 @@ static int
+ feed_volume_feed(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b,
+ uint32_t count, void *source)
+ {
++ int temp_vol[SND_CHN_T_VOL_MAX];
+ struct feed_volume_info *info;
+ uint32_t j, align;
+- int i, *vol, *matrix;
++ int i, *matrix;
+ uint8_t *dst;
++ const int16_t *vol;
++ const int8_t *muted;
+
+ /*
+ * Fetch filter data operation.
+@@ -251,6 +254,7 @@ feed_volume_feed(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b,
+ return (FEEDER_FEED(f->source, c, b, count, source));
+
+ vol = c->volume[SND_VOL_C_VAL(info->volume_class)];
++ muted = c->muted[SND_VOL_C_VAL(info->volume_class)];
+ matrix = info->matrix;
+
+ /*
+@@ -258,17 +262,22 @@ feed_volume_feed(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b,
+ */
+ j = 0;
+ i = info->channels;
+- do {
+- if (vol[matrix[--i]] != SND_VOL_FLAT) {
++ while (i--) {
++ if (vol[matrix[i]] != SND_VOL_FLAT ||
++ muted[matrix[i]] != 0) {
+ j = 1;
+ break;
+ }
+- } while (i != 0);
++ }
+
+ /* Nope, just bypass entirely. */
+ if (j == 0)
+ return (FEEDER_FEED(f->source, c, b, count, source));
+
++ /* Check if any controls are muted. */
++ for (j = 0; j != SND_CHN_T_VOL_MAX; j++)
++ temp_vol[j] = muted[j] ? 0 : vol[j];
++
+ dst = b;
+ align = info->bps * info->channels;
+
+@@ -281,7 +290,7 @@ feed_volume_feed(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b,
+ if (j == 0)
+ break;
+
+- info->apply(vol, matrix, info->channels, dst, j);
++ info->apply(temp_vol, matrix, info->channels, dst, j);
+
+ j *= align;
+ dst += j;
diff --git a/sys/dev/sound/pcm/mixer.c b/sys/dev/sound/pcm/mixer.c
index 92c5f3d613e..8275d1a2684 100644
--- a/sys/dev/sound/pcm/mixer.c
diff --git a/mixer_lib/Makefile b/mixer_lib/Makefile
@@ -4,6 +4,5 @@ LIB= mixer
SRCS= ${LIB}.c
INCS= ${LIB}.h
MAN= ${LIB}.3
-CFLAGS+= -pedantic -Wall -Wunused
.include <bsd.lib.mk>
diff --git a/mixer_lib/mixer.3 b/mixer_lib/mixer.3
@@ -22,7 +22,7 @@
.\" $FreeBSD$
.\"
-.Dd June 30, 2021
+.Dd September 22, 2021
.Dt mixer 3
.Os
.Sh NAME
@@ -533,7 +533,8 @@ TAILQ_FOREACH(dp, &m->devs, devs) {
.Xr mixer 8 ,
.Xr sound 4 ,
.Xr sysctl 3 ,
-.Xr queue 3 ,
+.Xr queue 3
+and
.Xr errno 2
.Sh AUTHORS
-.An Christos Margiolis Aq Mt christos@margiolis.net
+.An Christos Margiolis Aq Mt christos@FreeBSD.org
diff --git a/mixer_lib/mixer.c b/mixer_lib/mixer.c
@@ -28,6 +28,7 @@
#include <errno.h>
#include <fcntl.h>
+#include <libgen.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -35,7 +36,7 @@
#include "mixer.h"
-#define BASEPATH "/dev/mixer"
+#define BASEPATH "/dev/mixer"
static int _mixer_readvol(struct mixer *, struct mix_dev *);
@@ -57,7 +58,7 @@ _mixer_readvol(struct mixer *m, struct mix_dev *dev)
/*
* Open a mixer device in `/dev/mixerN`, where N is the number of the mixer.
- * Each device maps to an actual pcm audio card, so `/dev/mixer0` is the
+ * Each device maps to an actual pcm audio card, so `/dev/mixer0` is the
* mixer for pcm0, and so on.
*
* @param name path to mixer device. NULL or "/dev/mixer" for the
@@ -69,27 +70,25 @@ mixer_open(const char *name)
struct mixer *m = NULL;
struct mix_dev *dp;
const char *names[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_NAMES;
+ char *p = NULL;
int i;
if ((m = calloc(1, sizeof(struct mixer))) == NULL)
goto fail;
if (name != NULL) {
- /* `name` does not start with "/dev/mixer". */
- if (strncmp(name, BASEPATH, strlen(BASEPATH)) != 0) {
- errno = EINVAL;
+ /* XXX: should we remove `const` altogether? */
+ if ((p = strdup(basename((char *)name))) == NULL)
goto fail;
- }
- /* `name` is "/dev/mixer" so, we'll use the default unit. */
- if (strncmp(name, BASEPATH, strlen(name)) == 0)
+ if (strncmp(p, "mixer", 5) == 0 && p[5] == '\0')
goto dunit;
- m->unit = strtol(name + strlen(BASEPATH), NULL, 10);
+ (void)sscanf(name, "%*[^0123456789]%d", &m->unit);
(void)strlcpy(m->name, name, sizeof(m->name));
} else {
dunit:
if ((m->unit = mixer_get_dunit()) < 0)
goto fail;
- (void)snprintf(m->name, sizeof(m->name) - 1, "/dev/mixer%d", m->unit);
+ (void)snprintf(m->name, sizeof(m->name), "/dev/mixer%d", m->unit);
}
if ((m->fd = open(m->name, O_RDWR)) < 0)
@@ -101,9 +100,13 @@ dunit:
/* The unit number _must_ be set before the ioctl. */
m->mi.dev = m->unit;
m->ci.card = m->unit;
- if (ioctl(m->fd, SNDCTL_MIXERINFO, &m->mi) < 0 ||
- ioctl(m->fd, SNDCTL_CARDINFO, &m->ci) < 0 ||
- ioctl(m->fd, SOUND_MIXER_READ_DEVMASK, &m->devmask) < 0 ||
+ if (ioctl(m->fd, SNDCTL_MIXERINFO, &m->mi) < 0) {
+ memset(&m->mi, 0, sizeof(m->mi));
+ strlcpy(m->mi.name, m->name, sizeof(m->mi.name));
+ }
+ if (ioctl(m->fd, SNDCTL_CARDINFO, &m->ci) < 0)
+ memset(&m->ci, 0, sizeof(m->ci));
+ if(ioctl(m->fd, SOUND_MIXER_READ_DEVMASK, &m->devmask) < 0 ||
ioctl(m->fd, SOUND_MIXER_READ_MUTE, &m->mutemask) < 0 ||
ioctl(m->fd, SOUND_MIXER_READ_RECMASK, &m->recmask) < 0 ||
ioctl(m->fd, SOUND_MIXER_READ_RECSRC, &m->recsrc) < 0)
@@ -125,14 +128,15 @@ dunit:
TAILQ_INSERT_TAIL(&m->devs, dp, devs);
m->ndev++;
}
-
- /* The default device is always "vol". */
m->dev = TAILQ_FIRST(&m->devs);
return (m);
fail:
- if (m != NULL)
- (void)mixer_close(m);
+ if (p != NULL)
+ free(p);
+ /* XXX: do we need this? */
+ /*if (m != NULL)*/
+ /*(void)mixer_close(m);*/
return (NULL);
}
@@ -238,7 +242,7 @@ mixer_add_ctl(struct mix_dev *parent_dev, int id, const char *name,
}
TAILQ_INSERT_TAIL(&dp->ctls, ctl, ctls);
dp->nctl++;
-
+
return (0);
}
@@ -251,7 +255,7 @@ mixer_add_ctl_s(mix_ctl_t *ctl)
if (ctl == NULL)
return (-1);
- return (mixer_add_ctl(ctl->parent_dev, ctl->id, ctl->name,
+ return (mixer_add_ctl(ctl->parent_dev, ctl->id, ctl->name,
ctl->mod, ctl->print));
}
@@ -324,7 +328,7 @@ mixer_set_vol(struct mixer *m, mix_volume_t vol)
{
int v;
- if (vol.left < MIX_VOLMIN || vol.left > MIX_VOLMAX ||
+ if (vol.left < MIX_VOLMIN || vol.left > MIX_VOLMAX ||
vol.right < MIX_VOLMIN || vol.right > MIX_VOLMAX) {
errno = ERANGE;
return (-1);
@@ -432,7 +436,7 @@ mixer_get_dunit(void)
* Change the default audio card. This is normally _not_ a mixer feature, but
* it's useful to have, so the caller can avoid having to manually use
* the sysctl API.
- *
+ *
* @param unit the audio card number (e.g pcm0, pcm1, ...).
*/
int
@@ -460,10 +464,10 @@ mixer_get_mode(int unit)
size_t size;
unsigned int mode;
- (void)snprintf(buf, sizeof(buf) - 1, "dev.pcm.%d.mode", unit);
+ (void)snprintf(buf, sizeof(buf), "dev.pcm.%d.mode", unit);
size = sizeof(unsigned int);
if (sysctlbyname(buf, &mode, &size, NULL, 0) < 0)
- return (-1);
+ return (0);
return (mode);
}
@@ -477,7 +481,7 @@ mixer_get_nmixers(void)
struct mixer *m;
oss_sysinfo si;
- /*
+ /*
* Open a dummy mixer because we need the `fd` field for the
* `ioctl` to work.
*/
diff --git a/mixer_prog/Makefile b/mixer_prog/Makefile
@@ -1,10 +1,11 @@
# $FreeBSD$
-PROG= mixer_prog
+#.include <src.opts.mk>
+
+PROG= mixer
BINDIR= /usr/sbin
SRCS= ${PROG}.c
MAN= ${PROG}.8
-CFLAGS+= -pedantic -Wall -Wunused
LDFLAGS+= -lmixer
.include <bsd.prog.mk>
diff --git a/mixer_prog/mixer.8 b/mixer_prog/mixer.8
@@ -0,0 +1,245 @@
+.\"-
+.\" Copyright (c) 2021 Christos Margiolis <christos@FreeBSD.org>
+.\"
+.\" Permission is hereby granted, free of charge, to any person obtaining a copy
+.\" of this software and associated documentation files (the "Software"), to deal
+.\" in the Software without restriction, including without limitation the rights
+.\" to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+.\" copies of the Software, and to permit persons to whom the Software is
+.\" furnished to do so, subject to the following conditions:
+.\"
+.\" The above copyright notice and this permission notice shall be included in
+.\" all copies or substantial portions of the Software.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+.\" IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+.\" FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+.\" AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+.\" LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+.\" OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+.\" THE SOFTWARE.
+.\"
+.\" $FreeBSD$
+.\"
+
+.Dd September 22, 2021
+.Dt mixer 8
+.Os
+.Sh NAME
+.Nm mixer
+.Nd manipulate soundcard mixer controls
+.Sh SYNOPSIS
+.Nm
+.Op Fl f Ar device
+.Op Fl d Ar unit
+.Op Fl os
+.Op Ar dev Ns Op . Ns Ar control Ns Op = Ns Ar value
+.Ar ...
+.Nm
+.Op Fl d Ar unit
+.Op Fl os
+.Fl a
+.Sh DESCRIPTION
+The
+.Nm
+utility is used to set and display soundcard mixer device controls.
+.Pp
+The options are as follows:
+.Bl -tag -width "-f device"
+.It Fl a
+Print the values for all mixer devices available in the system (see FILES).
+.It Fl d Ar unit
+Change the default audio card to
+.Ar unit .
+The unit has to be an integer value. To see what unit values are available, look
+at the number each mixer device has by running
+.Nm .
+.It Fl f Ar device
+Open
+.Ar device
+as the mixer device (see FILES).
+.It Fl o
+Print mixer values in a format suitable for use inside scripts. The
+mixer's header (name, audio card name, ...) will not be printed.
+.It Fl s
+Print only the recording source(s) of the mixer device.
+.El
+.Pp
+The list of mixer devices that may be modified are:
+.Bd -ragged -offset indent
+vol, bass, treble, synth, pcm, speaker, line, mic, cd, mix,
+pcm2, rec, igain, ogain, line1, line2, line3, dig1, dig2, dig3,
+phin, phout, video, radio, and monitor.
+.Ed
+.Pp
+Not all mixer devices are available.
+.Pp
+Without any arguments,
+.Nm
+displays all information for each one of the mixer's supported devices to
+.Ar stdout .
+If the
+.Ar dev
+argument is specified,
+.Nm
+displays only the values for
+.Ar dev .
+More than one device may be specified.
+.Pp
+Commands use the following format:
+.Pp
+.Bl -column xxxxxxxxxxxxxxxxxxxxxxxx -offset indent
+.It Sy "Name Action"
+.It "dev Display all controls"
+.It "dev.control Display only the specified control"
+.It "dev.control=value Set control value"
+.El
+.Pp
+The available controls are as follows (replace
+.Ar dev
+with one of the available devices):
+.Bl -column xxxxxxxxxxxxxxxxxxxxxxxx -offset indent
+.It Sy "Name Value"
+.It "dev.volume [[+|-]lvol[:[+|-]rvol]]"
+.It "dev.mute {0|1|^}"
+.It "dev.recsrc {+|-|^|=}"
+.El
+.Pp
+The
+.Ar dev.volume
+control modifies a device's volume. The optional
+.Ar lvol
+and/or
+.Ar rvol
+values have to be specified. The values have to be normalized 32-bit floats,
+from 0.0 to 1.0 inclusivly. If no "." character is present, the value is treated
+like a percentage, for backwards compatibility.
+If the the left or right volume values are prefixed with
+.Cm +
+or
+.Cm - ,
+the value following will be used as a relative adjustment, modifying the
+current settings by the amount specified.
+.Pp
+The
+.Ar dev.mute
+control (un)mutes a device. The following values are available:
+.Bl -tag -width = -offset indent
+.It Cm 0
+unmutes
+.Ar dev .
+.It Cm 1
+mutes
+.Ar dev .
+.It Cm ^
+toggles the mute of
+.Ar dev .
+.El
+.Pp
+The
+.Ar dev.recsrc
+control modifies the recording sources of a mixer.
+.Nm
+marks devices which can be used as a recording source with
+.Ar rec .
+Recording sources are marked with
+.Ar src .
+To modify the recording source you can use one of the following modifiers
+on a
+.Ar rec
+device:
+.Bl -tag -width = -offset indent
+.It Cm ^
+toggles
+.Ar dev
+of possible recording devices
+.It Cm +
+adds
+.Ar dev
+to possible recording devices
+.It Cm -
+removes
+.Ar dev
+from possible recording devices
+.It Cm =
+sets the recording device to
+.Ar dev
+.El
+.Sh FILES
+.Bl -tag -width /dev/mixerN -compact
+.It Pa /dev/mixerN
+The mixer device, where
+.Ar N
+is the number of that device, for example
+.Ar /dev/mixer0 .
+PCM cards and mixers have a 1:1 relationship, which means that
+.Ar mixer0
+is the mixer for
+.Ar pcm0
+and so on. By default,
+.Nm
+prints both the audio card's number and the mixer associated with it
+in the form of
+.Ar pcmN:mixer .
+The
+.Ar /dev/mixer
+file, although it doesn't exist in the filesystem, points to the default
+mixer device and is the file
+.Nm
+opens when the
+.Fl f Ar device
+option has not been specified.
+.El
+.Sh EXAMPLES
+.Pp
+Change the volume for the
+.Ar vol
+device of the
+.Ar /dev/mixer0
+mixer device to 0.65:
+.Bl -tag -width Ds -offset indent
+.It $ mixer -f /dev/mixer0 vol.volume=0.65
+.El
+.Pp
+Increase the
+.Ar mic
+device's left volume by 0.10 and decrease the right
+volume by 0.05:
+.Bl -tag -width Ds -offset indent
+.It $ mixer mic.volume=+0.10:-0.05
+.El
+.Pp
+Toggle the mute for
+.Ar vol :
+.Bl -tag -width Ds -offset indent
+.It $ mixer vol.mute=^
+.El
+.Pp
+Set
+.Ar mic
+and toggle
+.Ar line
+recording sources:
+.Bl -tag -width Ds -offset indent
+.It $ mixer mic.recsrc=+ line.recsrc=^
+.El
+.Pp
+Dump
+.Ar /dev/mixer0
+information to a file and retrieve back later
+.Bl -tag -width Ds -offset indent
+.It $ mixer -f /dev/mixer0 -o > info
+.It ...
+.It $ mixer -f /dev/mixer0 `cat info`
+.El
+.Sh SEE ALSO
+.Xr mixer 3 ,
+.Xr sound 4 ,
+.Xr sysctl 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in FreeBSD 2.0.5 and was rewritten completely in
+FreeBSD 14.0.
+.Sh AUTHORS
+.An Christos Margiolis Aq Mt christos@FreeBSD.org
diff --git a/mixer_prog/mixer.c b/mixer_prog/mixer.c
@@ -0,0 +1,484 @@
+/*-
+ * Copyright (c) 2021 Christos Margiolis <christos@FreeBSD.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * $FreeBSD$
+ */
+
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <mixer.h>
+
+static void usage(void) __dead2;
+static void initctls(struct mixer *);
+static void printall(struct mixer *, int);
+static void printminfo(struct mixer *, int);
+static void printdev(struct mixer *, int);
+static void printrecsrc(struct mixer *, int); /* XXX: change name */
+/* Control handlers */
+static int mod_dunit(struct mix_dev *, void *);
+static int mod_volume(struct mix_dev *, void *);
+static int mod_mute(struct mix_dev *, void *);
+static int mod_recsrc(struct mix_dev *, void *);
+static int print_volume(struct mix_dev *, void *);
+static int print_mute(struct mix_dev *, void *);
+static int print_recsrc(struct mix_dev *, void *);
+
+static const mix_ctl_t ctl_dunit = {
+ .parent_dev = NULL,
+ .id = -1,
+ .name = "default_unit",
+ .mod = mod_dunit,
+ .print = NULL
+};
+
+int
+main(int argc, char *argv[])
+{
+ struct mixer *m;
+ mix_ctl_t *cp;
+ char *name = NULL, buf[NAME_MAX];
+ char *p, *bufp, *devstr, *ctlstr, *valstr = NULL;
+ int dunit, i, n, pall = 1;
+ int aflag = 0, dflag = 0, oflag = 0, sflag = 0;
+ int ch;
+
+ while ((ch = getopt(argc, argv, "ad:f:os")) != -1) {
+ switch (ch) {
+ case 'a':
+ aflag = 1;
+ break;
+ case 'd':
+ dunit = strtol(optarg, NULL, 10);
+ if (errno == EINVAL || errno == ERANGE)
+ err(1, "strtol");
+ dflag = 1;
+ break;
+ case 'f':
+ name = optarg;
+ break;
+ case 'o':
+ oflag = 1;
+ break;
+ case 's':
+ sflag = 1;
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* Print all mixers and exit. */
+ if (aflag) {
+ if ((n = mixer_get_nmixers()) < 0)
+ err(1, "mixer_get_nmixers");
+ for (i = 0; i < n; i++) {
+ (void)snprintf(buf, sizeof(buf), "/dev/mixer%d", i);
+ if ((m = mixer_open(buf)) == NULL)
+ err(1, "mixer_open: %s", buf);
+ initctls(m);
+ if (sflag)
+ printrecsrc(m, oflag);
+ else {
+ printall(m, oflag);
+ if (oflag)
+ printf("\n");
+ }
+ (void)mixer_close(m);
+ }
+ return (0);
+ }
+
+ if ((m = mixer_open(name)) == NULL)
+ err(1, "mixer_open: %s", name);
+
+ initctls(m);
+
+ if (dflag && ctl_dunit.mod(m->dev, &dunit) < 0)
+ goto parse;
+ if (sflag) {
+ printrecsrc(m, oflag);
+ (void)mixer_close(m);
+ return (0);
+ }
+
+parse:
+ while (argc > 0) {
+ if ((p = bufp = strdup(*argv)) == NULL)
+ err(1, "strdup(%s)", *argv);
+ /* Split the string into device, control and value. */
+ devstr = strsep(&p, ".");
+ if ((m->dev = mixer_get_dev_byname(m, devstr)) == NULL) {
+ warnx("%s: no such device", devstr);
+ goto next;
+ }
+ /* Input: `dev`. */
+ if (p == NULL) {
+ printdev(m, 1);
+ pall = 0;
+ goto next;
+ }
+ ctlstr = strsep(&p, "=");
+ if ((cp = mixer_get_ctl_byname(m->dev, ctlstr)) == NULL) {
+ warnx("%s.%s: no such control", devstr, ctlstr);
+ goto next;
+ }
+
+ /* Input: `dev.control`. */
+ if (p == NULL) {
+ (void)cp->print(cp->parent_dev, cp->name);
+ pall = 0;
+ goto next;
+ }
+ valstr = p;
+ /* Input: `dev.control=val`. */
+ cp->mod(cp->parent_dev, valstr);
+next:
+ free(p);
+ argc--;
+ argv++;
+ }
+
+ if (pall)
+ printall(m, oflag);
+ (void)mixer_close(m);
+
+ return (0);
+}
+
+static void __dead2
+usage(void)
+{
+ printf("usage: %1$s [-f device] [-d unit] [-os] [dev[.control[=value]]] ...\n"
+ " %1$s [-d unit] [-os] -a\n",
+ getprogname());
+ exit(1);
+}
+
+static void
+initctls(struct mixer *m)
+{
+ struct mix_dev *dp;
+ int rc = 0;
+
+#define C_VOL 0
+#define C_MUT 1
+#define C_SRC 2
+ TAILQ_FOREACH(dp, &m->devs, devs) {
+ rc += mixer_add_ctl(dp, C_VOL, "volume", mod_volume, print_volume);
+ rc += mixer_add_ctl(dp, C_MUT, "mute", mod_mute, print_mute);
+ rc += mixer_add_ctl(dp, C_SRC, "recsrc", mod_recsrc, print_recsrc);
+ }
+ if (rc) {
+ (void)mixer_close(m);
+ err(1, "cannot make controls");
+ }
+}
+
+static void
+printall(struct mixer *m, int oflag)
+{
+ struct mix_dev *dp;
+
+ printminfo(m, oflag);
+ TAILQ_FOREACH(dp, &m->devs, devs) {
+ m->dev = dp;
+ printdev(m, oflag);
+ }
+}
+
+static void
+printminfo(struct mixer *m, int oflag)
+{
+ int playrec = MIX_MODE_PLAY | MIX_MODE_REC;
+
+ if (oflag)
+ return;
+ printf("%s: <%s> %s", m->mi.name, m->ci.longname, m->ci.hw_info);
+ printf(" (");
+ if (m->mode & MIX_MODE_PLAY)
+ printf("play");
+ if ((m->mode & playrec) == playrec)
+ printf("/");
+ if (m->mode & MIX_MODE_REC)
+ printf("rec");
+ printf(")");
+ if (m->f_default)
+ printf(" (default)");
+ printf("\n");
+}
+
+static void
+printdev(struct mixer *m, int oflag)
+{
+ struct mix_dev *d = m->dev;
+ mix_ctl_t *cp;
+
+ if (!oflag) {
+ char buffer[32];
+ (void)snprintf(buffer, sizeof(buffer),
+ "%s.%s", d->name, "volume");
+
+ printf(" %-16s= %.2f:%.2f\t",
+ buffer, d->vol.left, d->vol.right);
+ if (!MIX_ISREC(m, d->devno))
+ printf(" pbk");
+ if (MIX_ISREC(m, d->devno))
+ printf(" rec");
+ if (MIX_ISRECSRC(m, d->devno))
+ printf(" src");
+ if (MIX_ISMUTE(m, d->devno))
+ printf(" mute");
+ printf("\n");
+ } else {
+ TAILQ_FOREACH(cp, &d->ctls, ctls) {
+ (void)cp->print(cp->parent_dev, cp->name);
+ }
+ }
+}
+
+static void
+printrecsrc(struct mixer *m, int oflag)
+{
+ struct mix_dev *dp;
+ int n = 0;
+
+ if (!m->recmask)
+ return;
+ if (!oflag)
+ printf("%s: ", m->mi.name);
+ TAILQ_FOREACH(dp, &m->devs, devs) {
+ if (MIX_ISRECSRC(m, dp->devno)) {
+ if (n++ && !oflag)
+ printf(", ");
+ printf("%s", dp->name);
+ if (oflag)
+ printf(".%s=+%s",
+ mixer_get_ctl(dp, C_SRC)->name,
+ n ? " " : "");
+ }
+ }
+ printf("\n");
+}
+
+static int
+mod_dunit(struct mix_dev *d, void *p)
+{
+ int dunit = *((int *)p);
+ int n;
+
+ if ((n = mixer_get_dunit()) < 0) {
+ warn("cannot get default unit");
+ return (-1);
+ }
+ if (mixer_set_dunit(d->parent_mixer, dunit) < 0) {
+ warn("cannot set default unit to: %d", dunit);
+ return (-1);
+ }
+ printf("%s: %d -> %d\n", ctl_dunit.name, n, dunit);
+
+ return (0);
+}
+
+static int
+mod_volume(struct mix_dev *d, void *p)
+{
+ struct mixer *m;
+ mix_ctl_t *cp;
+ mix_volume_t v;
+ const char *val;
+ char lstr[8], rstr[8];
+ float lprev, rprev, lrel, rrel;
+ int n;
+
+ m = d->parent_mixer;
+ cp = mixer_get_ctl(m->dev, C_VOL);
+ val = p;
+ n = sscanf(val, "%7[^:]:%7s", lstr, rstr);
+ if (n == EOF) {
+ warnx("invalid volume value: %s", val);
+ return (-1);
+ }
+ lrel = rrel = 0;
+ if (n > 0) {
+ if (*lstr == '+' || *lstr == '-')
+ lrel = rrel = 1;
+ v.left = strtof(lstr, NULL);
+
+ /* be backwards compatible */
+ if (strstr(lstr, ".") == NULL)
+ v.left /= 100.0f;
+ }
+ if (n > 1) {
+ if (*rstr == '+' || *rstr == '-')
+ rrel = 1;
+ v.right = strtof(rstr, NULL);
+
+ /* be backwards compatible */
+ if (strstr(rstr, ".") == NULL)
+ v.right /= 100.0f;
+ }
+ switch (n) {
+ case 1:
+ v.right = v.left; /* FALLTHROUGH */
+ case 2:
+ if (lrel)
+ v.left += m->dev->vol.left;
+ if (rrel)
+ v.right += m->dev->vol.right;
+
+ if (v.left < MIX_VOLMIN)
+ v.left = MIX_VOLMIN;
+ else if (v.left > MIX_VOLMAX)
+ v.left = MIX_VOLMAX;
+ if (v.right < MIX_VOLMIN)
+ v.right = MIX_VOLMIN;
+ else if (v.right > MIX_VOLMAX)
+ v.right = MIX_VOLMAX;
+
+ lprev = m->dev->vol.left;
+ rprev = m->dev->vol.right;
+ if (mixer_set_vol(m, v) < 0)
+ warn("%s.%s=%.2f:%.2f",
+ m->dev->name, cp->name, v.left, v.right);
+ else
+ printf("%s.%s: %.2f:%.2f -> %.2f:%.2f\n",
+ m->dev->name, cp->name, lprev, rprev, v.left, v.right);
+ }
+
+ return (0);
+}
+
+static int
+mod_mute(struct mix_dev *d, void *p)
+{
+ struct mixer *m;
+ mix_ctl_t *cp;
+ const char *val;
+ int n, opt = -1;
+
+ m = d->parent_mixer;
+ cp = mixer_get_ctl(m->dev, C_MUT);
+ val = p;
+ switch (*val) {
+ case '0':
+ opt = MIX_UNMUTE;
+ break;
+ case '1':
+ opt = MIX_MUTE;
+ break;
+ case '^':
+ opt = MIX_TOGGLEMUTE;
+ break;
+ default:
+ warnx("%c: no such modifier", *val);
+ return (-1);
+ }
+ n = MIX_ISMUTE(m, m->dev->devno);
+ if (mixer_set_mute(m, opt) < 0)
+ warn("%s.%s=%c", m->dev->name, cp->name, *val);
+ else
+ printf("%s.%s: %d -> %d\n",
+ m->dev->name, cp->name, n, MIX_ISMUTE(m, m->dev->devno));
+
+ return (0);
+}
+
+static int
+mod_recsrc(struct mix_dev *d, void *p)
+{
+ struct mixer *m;
+ mix_ctl_t *cp;
+ const char *val;
+ int n, opt = -1;
+
+ m = d->parent_mixer;
+ cp = mixer_get_ctl(m->dev, C_SRC);
+ val = p;
+ switch (*val) {
+ case '+':
+ opt = MIX_ADDRECSRC;
+ break;
+ case '-':
+ opt = MIX_REMOVERECSRC;
+ break;
+ case '=':
+ opt = MIX_SETRECSRC;
+ break;
+ case '^':
+ opt = MIX_TOGGLERECSRC;
+ break;
+ default:
+ warnx("%c: no such modifier", *val);
+ return (-1);
+ }
+ n = MIX_ISRECSRC(m, m->dev->devno);
+ if (mixer_mod_recsrc(m, opt) < 0)
+ warn("%s.%s=%c", m->dev->name, cp->name, *val);
+ else
+ printf("%s.%s: %d -> %d\n",
+ m->dev->name, cp->name, n, MIX_ISRECSRC(m, m->dev->devno));
+
+ return (0);
+}
+
+static int
+print_volume(struct mix_dev *d, void *p)
+{
+ struct mixer *m = d->parent_mixer;
+ const char *ctl_name = p;
+
+ printf("%s.%s=%.2f:%.2f\n",
+ m->dev->name, ctl_name, m->dev->vol.left, m->dev->vol.right);
+
+ return (0);
+}
+
+static int
+print_mute(struct mix_dev *d, void *p)
+{
+ struct mixer *m = d->parent_mixer;
+ const char *ctl_name = p;
+
+ printf("%s.%s=%d\n", m->dev->name, ctl_name, MIX_ISMUTE(m, m->dev->devno));
+
+ return (0);
+}
+
+static int
+print_recsrc(struct mix_dev *d, void *p)
+{
+ struct mixer *m = d->parent_mixer;
+ const char *ctl_name = p;
+
+ if (!MIX_ISRECSRC(m, m->dev->devno))
+ return (-1);
+ printf("%s.%s=+\n", m->dev->name, ctl_name);
+
+ return (0);
+}
diff --git a/mixer_prog/mixer_prog.8 b/mixer_prog/mixer_prog.8
@@ -1,243 +0,0 @@
-.\"-
-.\" Copyright (c) 2021 Christos Margiolis <christos@FreeBSD.org>
-.\"
-.\" Permission is hereby granted, free of charge, to any person obtaining a copy
-.\" of this software and associated documentation files (the "Software"), to deal
-.\" in the Software without restriction, including without limitation the rights
-.\" to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-.\" copies of the Software, and to permit persons to whom the Software is
-.\" furnished to do so, subject to the following conditions:
-.\"
-.\" The above copyright notice and this permission notice shall be included in
-.\" all copies or substantial portions of the Software.
-.\"
-.\" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-.\" IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-.\" FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-.\" AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-.\" LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-.\" OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-.\" THE SOFTWARE.
-.\"
-.\" $FreeBSD$
-.\"
-
-.Dd June 30, 2021
-.Dt mixer 8
-.Os
-.Sh NAME
-.Nm mixer
-.Nd manipulate soundcard mixer controls
-.Sh SYNOPSIS
-.Nm
-.Op Fl f Ar device
-.Op Fl d Ar unit
-.Op Fl os
-.Op Ar dev Ns Op . Ns Ar control Ns Op = Ns Ar value
-.Ar ...
-.Nm
-.Op Fl d Ar unit
-.Op Fl os
-.Fl a
-.Sh DESCRIPTION
-The
-.Nm
-utility is used to set and display soundcard mixer device controls.
-.Pp
-The options are as follows:
-.Bl -tag -width "-f device"
-.It Fl a
-Print the values for all mixer devices available in the system (see FILES).
-.It Fl d Ar unit
-Change the default audio card to
-.Ar unit .
-The unit has to be an integer value. To see what unit values are available, look
-at the number each mixer device has by running
-.Nm .
-.It Fl f Ar device
-Open
-.Ar device
-as the mixer device (see FILES).
-.It Fl o
-Print mixer values in a format suitable for use inside scripts. The
-mixer's header (name, audio card name, ...) will not be printed.
-.It Fl s
-Print only the recording source(s) of the mixer device.
-.El
-.Pp
-The list of mixer devices that may be modified are:
-.Bd -ragged -offset indent
-vol, bass, treble, synth, pcm, speaker, line, mic, cd, mix,
-pcm2, rec, igain, ogain, line1, line2, line3, dig1, dig2, dig3,
-phin, phout, video, radio, and monitor.
-.Ed
-.Pp
-Not all mixer devices are available.
-.Pp
-Without any arguments,
-.Nm
-displays all information for each one of the mixer's supported devices to
-.Ar stdout .
-If the
-.Ar dev
-argument is specified,
-.Nm
-displays only the values for
-.Ar dev .
-More than one device may be specified.
-.Pp
-Commands use the following format:
-.Pp
-.Bl -column xxxxxxxxxxxxxxxxxxxxxxxx -offset indent
-.It Sy "Name Action"
-.It "dev Display all controls"
-.It "dev.control Display only the specified control"
-.It "dev.control=value Set control value"
-.El
-.Pp
-The available controls are as follows (replace
-.Ar dev
-with one of the available devices):
-.Bl -column xxxxxxxxxxxxxxxxxxxxxxxx -offset indent
-.It Sy "Name Value"
-.It "dev.volume [[+|-]lvol[:[+|-]rvol]]"
-.It "dev.mute {0|1|^}"
-.It "dev.recsrc {+|-|^|=}"
-.El
-.Pp
-The
-.Ar dev.volume
-control modifies a device's volume. The optional
-.Ar lvol
-and/or
-.Ar rvol
-values have to be specified. The values have to be normalized 32-bit floats
-(0.0 to 1.0). If the the left or right volume values are prefixed with
-.Cm +
-or
-.Cm - ,
-the value following will be used as a relative adjustment, modifying the
-current settings by the amount specified.
-.Pp
-The
-.Ar dev.mute
-control (un)mutes a device. The following values are available:
-.Bl -tag -width = -offset indent
-.It Cm 0
-unmutes
-.Ar dev .
-.It Cm 1
-mutes
-.Ar dev .
-.It Cm ^
-toggles the mute of
-.Ar dev .
-.El
-.Pp
-The
-.Ar dev.recsrc
-control modifies the recording sources of a mixer.
-.Nm
-marks devices which can be used as a recording source with
-.Ar rec .
-Recording sources are marked with
-.Ar src .
-To modify the recording source you can use one of the following modifiers
-on a
-.Ar rec
-device:
-.Bl -tag -width = -offset indent
-.It Cm ^
-toggles
-.Ar dev
-of possible recording devices
-.It Cm +
-adds
-.Ar dev
-to possible recording devices
-.It Cm -
-removes
-.Ar dev
-from possible recording devices
-.It Cm =
-sets the recording device to
-.Ar dev
-.El
-.Sh FILES
-.Bl -tag -width /dev/mixerN -compact
-.It Pa /dev/mixerN
-The mixer device, where
-.Ar N
-is the number of that device, for example
-.Ar /dev/mixer0 .
-PCM cards and mixers have a 1:1 relationship, which means that
-.Ar mixer0
-is the mixer for
-.Ar pcm0
-and so on. By default,
-.Nm
-prints both the audio card's number and the mixer associated with it
-in the form of
-.Ar pcmN:mixer .
-The
-.Ar /dev/mixer
-file, although it doesn't exist in the filesystem, points to the default
-mixer device and is the file
-.Nm
-opens when the
-.Fl f Ar device
-option has not been specified.
-.El
-.Sh EXAMPLES
-.Pp
-Change the volume for the
-.Ar vol
-device of the
-.Ar /dev/mixer0
-mixer device to 0.65:
-.Bl -tag -width Ds -offset indent
-.It $ mixer -f /dev/mixer0 vol.volume=0.65
-.El
-.Pp
-Increase the
-.Ar mic
-device's left volume by 0.10 and decrease the right
-volume by 0.05:
-.Bl -tag -width Ds -offset indent
-.It $ mixer mic.volume=+0.10:-0.05
-.El
-.Pp
-Toggle the mute for
-.Ar vol :
-.Bl -tag -width Ds -offset indent
-.It $ mixer vol.mute=^
-.El
-.Pp
-Set
-.Ar mic
-and toggle
-.Ar line
-recording sources:
-.Bl -tag -width Ds -offset indent
-.It $ mixer mic.recsrc=+ line.recsrc=^
-.El
-.Pp
-Dump
-.Ar /dev/mixer0
-information to a file and retrieve back later
-.Bl -tag -width Ds -offset indent
-.It $ mixer -f /dev/mixer0 -o > info
-.It ...
-.It $ mixer -f /dev/mixer0 `cat info`
-.El
-.Sh SEE ALSO
-.Xr mixer 3 ,
-.Xr sound 4 ,
-.Xr sysctl 8
-.Sh HISTORY
-The
-.Nm
-utility first appeared in FreeBSD 2.0.5 and was rewritten completely in
-FreeBSD 12.2. \" FIXME: replace 12.2 with proper version.
-.Sh AUTHORS
-.An Christos Margiolis Aq Mt christos@margiolis.net
diff --git a/mixer_prog/mixer_prog.c b/mixer_prog/mixer_prog.c
@@ -1,472 +0,0 @@
-/*-
- * Copyright (c) 2021 Christos Margiolis <christos@FreeBSD.org>
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- * $FreeBSD$
- */
-
-#include <err.h>
-#include <errno.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-#include <mixer.h>
-
-static void usage(void) __dead2;
-static void initctls(struct mixer *);
-static void printall(struct mixer *, int);
-static void printminfo(struct mixer *, int);
-static void printdev(struct mixer *, int);
-static void printrecsrc(struct mixer *, int); /* XXX: change name */
-/* Control handlers */
-static int mod_dunit(struct mix_dev *, void *);
-static int mod_volume(struct mix_dev *, void *);
-static int mod_mute(struct mix_dev *, void *);
-static int mod_recsrc(struct mix_dev *, void *);
-static int print_volume(struct mix_dev *, void *);
-static int print_mute(struct mix_dev *, void *);
-static int print_recsrc(struct mix_dev *, void *);
-
-static const mix_ctl_t ctl_dunit = {
- .parent_dev = NULL,
- .id = -1,
- .name = "default_unit",
- .mod = mod_dunit,
- .print = NULL
-};
-
-int
-main(int argc, char *argv[])
-{
- struct mixer *m;
- mix_ctl_t *cp;
- char *name = NULL, buf[NAME_MAX];
- char *p, *bufp, *devstr, *ctlstr, *valstr = NULL;
- int dunit, i, n, pall = 1;
- int aflag = 0, dflag = 0, oflag = 0, sflag = 0;
- char ch;
-
- while ((ch = getopt(argc, argv, "ad:f:os")) != -1) {
- switch (ch) {
- case 'a':
- aflag = 1;
- break;
- case 'd':
- dunit = strtol(optarg, NULL, 10);
- if (errno == EINVAL || errno == ERANGE)
- err(1, "strtol");
- dflag = 1;
- break;
- case 'f':
- name = optarg;
- break;
- case 'o':
- oflag = 1;
- break;
- case 's':
- sflag = 1;
- break;
- case '?':
- default:
- usage();
- }
- }
- argc -= optind;
- argv += optind;
-
- /* Print all mixers and exit. */
- if (aflag) {
- if ((n = mixer_get_nmixers()) < 0)
- err(1, "mixer_get_nmixers");
- for (i = 0; i < n; i++) {
- (void)snprintf(buf, sizeof(buf), "/dev/mixer%d", i);
- if ((m = mixer_open(buf)) == NULL)
- err(1, "mixer_open: %s", buf);
- initctls(m);
- if (sflag)
- printrecsrc(m, oflag);
- else {
- printall(m, oflag);
- if (oflag)
- printf("\n");
- }
- (void)mixer_close(m);
- }
- return (0);
- }
-
- if ((m = mixer_open(name)) == NULL)
- err(1, "mixer_open: %s", name);
-
- initctls(m);
-
- if (dflag && ctl_dunit.mod(m->dev, &dunit) < 0)
- goto parse;
- if (sflag) {
- printrecsrc(m, oflag);
- (void)mixer_close(m);
- return (0);
- }
-
-parse:
- while (argc > 0) {
- if ((p = bufp = strdup(*argv)) == NULL)
- err(1, "strdup(%s)", *argv);
- /* Split the string into device, control and value. */
- devstr = strsep(&p, ".");
- if ((m->dev = mixer_get_dev_byname(m, devstr)) == NULL) {
- warnx("%s: no such device", devstr);
- goto next;
- }
- /* Input: `dev`. */
- if (p == NULL) {
- printdev(m, 1);
- pall = 0;
- goto next;
- }
- ctlstr = strsep(&p, "=");
- if ((cp = mixer_get_ctl_byname(m->dev, ctlstr)) == NULL) {
- warnx("%s.%s: no such control", devstr, ctlstr);
- goto next;
- }
-
- /* Input: `dev.control`. */
- if (p == NULL) {
- (void)cp->print(cp->parent_dev, cp->name);
- pall = 0;
- goto next;
- }
- valstr = p;
- /* Input: `dev.control=val`. */
- cp->mod(cp->parent_dev, valstr);
-next:
- free(p);
- argc--;
- argv++;
- }
-
- if (pall)
- printall(m, oflag);
- (void)mixer_close(m);
-
- return (0);
-}
-
-static void __dead2
-usage(void)
-{
- printf("usage: %1$s [-f device] [-d unit] [-os] [dev[.control[=value]]] ...\n"
- " %1$s [-d unit] [-os] -a\n",
- getprogname());
- exit(1);
-}
-
-static void
-initctls(struct mixer *m)
-{
- struct mix_dev *dp;
- int rc = 0;
-
-#define C_VOL 0
-#define C_MUT 1
-#define C_SRC 2
- TAILQ_FOREACH(dp, &m->devs, devs) {
- rc += mixer_add_ctl(dp, C_VOL, "volume", mod_volume, print_volume);
- rc += mixer_add_ctl(dp, C_MUT, "mute", mod_mute, print_mute);
- rc += mixer_add_ctl(dp, C_SRC, "recsrc", mod_recsrc, print_recsrc);
- }
- if (rc) {
- (void)mixer_close(m);
- err(1, "cannot make controls");
- }
-}
-
-static void
-printall(struct mixer *m, int oflag)
-{
- struct mix_dev *dp;
-
- printminfo(m, oflag);
- TAILQ_FOREACH(dp, &m->devs, devs) {
- m->dev = dp;
- printdev(m, oflag);
- }
-}
-
-static void
-printminfo(struct mixer *m, int oflag)
-{
- int playrec = MIX_MODE_PLAY | MIX_MODE_REC;
-
- if (oflag)
- return;
- printf("%s: <%s> %s", m->mi.name, m->ci.longname, m->ci.hw_info);
- printf(" (");
- if (m->mode & MIX_MODE_PLAY)
- printf("play");
- if ((m->mode & playrec) == playrec)
- printf("/");
- if (m->mode & MIX_MODE_REC)
- printf("rec");
- printf(")");
- if (m->f_default)
- printf(" (default)");
- printf("\n");
-}
-
-static void
-printdev(struct mixer *m, int oflag)
-{
- struct mix_dev *d = m->dev;
- mix_ctl_t *cp;
-
- if (!oflag) {
- printf(" %-11s= %.2f:%.2f\t",
- d->name, d->vol.left, d->vol.right);
- if (!MIX_ISREC(m, d->devno))
- printf(" pbk");
- if (MIX_ISREC(m, d->devno))
- printf(" rec");
- if (MIX_ISRECSRC(m, d->devno))
- printf(" src");
- if (MIX_ISMUTE(m, d->devno))
- printf(" mute");
- printf("\n");
- } else {
- TAILQ_FOREACH(cp, &d->ctls, ctls) {
- (void)cp->print(cp->parent_dev, cp->name);
- }
- }
-}
-
-static void
-printrecsrc(struct mixer *m, int oflag)
-{
- struct mix_dev *dp;
- int n = 0;
-
- if (!m->recmask)
- return;
- if (!oflag)
- printf("%s: ", m->mi.name);
- TAILQ_FOREACH(dp, &m->devs, devs) {
- if (MIX_ISRECSRC(m, dp->devno)) {
- if (n++ && !oflag)
- printf(", ");
- printf("%s", dp->name);
- if (oflag)
- printf(".%s=+%s",
- mixer_get_ctl(dp, C_SRC)->name,
- n ? " " : "");
- }
- }
- printf("\n");
-}
-
-static int
-mod_dunit(struct mix_dev *d, void *p)
-{
- int dunit = *((int *)p);
- int n;
-
- if ((n = mixer_get_dunit()) < 0) {
- warn("cannot get default unit");
- return (-1);
- }
- if (mixer_set_dunit(d->parent_mixer, dunit) < 0) {
- warn("cannot set default unit to: %d", dunit);
- return (-1);
- }
- printf("%s: %d -> %d\n", ctl_dunit.name, n, dunit);
-
- return (0);
-}
-
-static int
-mod_volume(struct mix_dev *d, void *p)
-{
- struct mixer *m;
- mix_ctl_t *cp;
- mix_volume_t v;
- const char *val;
- char lstr[8], rstr[8];
- float lprev, rprev, lrel, rrel;
- int n;
-
- m = d->parent_mixer;
- cp = mixer_get_ctl(m->dev, C_VOL);
- val = p;
- n = sscanf(val, "%7[^:]:%7s", lstr, rstr);
- if (n == EOF) {
- warnx("invalid volume value: %s", val);
- return (-1);
- }
- lrel = rrel = 0;
- if (n > 0) {
- if (*lstr == '+' || *lstr == '-')
- lrel = rrel = 1;
- v.left = strtof(lstr, NULL);
- }
- if (n > 1) {
- if (*rstr == '+' || *rstr == '-')
- rrel = 1;
- v.right = strtof(rstr, NULL);
- }
- switch (n) {
- case 1:
- v.right = v.left; /* FALLTHROUGH */
- case 2:
- if (lrel)
- v.left += m->dev->vol.left;
- if (rrel)
- v.right += m->dev->vol.right;
-
- if (v.left < MIX_VOLMIN)
- v.left = MIX_VOLMIN;
- else if (v.left > MIX_VOLMAX)
- v.left = MIX_VOLMAX;
- if (v.right < MIX_VOLMIN)
- v.right = MIX_VOLMIN;
- else if (v.right > MIX_VOLMAX)
- v.right = MIX_VOLMAX;
-
- lprev = m->dev->vol.left;
- rprev = m->dev->vol.right;
- if (mixer_set_vol(m, v) < 0)
- warn("%s.%s=%.2f:%.2f",
- m->dev->name, cp->name, v.left, v.right);
- else
- printf("%s.%s: %.2f:%.2f -> %.2f:%.2f\n",
- m->dev->name, cp->name, lprev, rprev, v.left, v.right);
- }
-
- return (0);
-}
-
-static int
-mod_mute(struct mix_dev *d, void *p)
-{
- struct mixer *m;
- mix_ctl_t *cp;
- const char *val;
- int n, opt = -1;
-
- m = d->parent_mixer;
- cp = mixer_get_ctl(m->dev, C_MUT);
- val = p;
- switch (*val) {
- case '0':
- opt = MIX_UNMUTE;
- break;
- case '1':
- opt = MIX_MUTE;
- break;
- case '^':
- opt = MIX_TOGGLEMUTE;
- break;
- default:
- warnx("%c: no such modifier", *val);
- return (-1);
- }
- n = MIX_ISMUTE(m, m->dev->devno);
- if (mixer_set_mute(m, opt) < 0)
- warn("%s.%s=%c", m->dev->name, cp->name, *val);
- else
- printf("%s.%s: %d -> %d\n",
- m->dev->name, cp->name, n, MIX_ISMUTE(m, m->dev->devno));
-
- return (0);
-}
-
-static int
-mod_recsrc(struct mix_dev *d, void *p)
-{
- struct mixer *m;
- mix_ctl_t *cp;
- const char *val;
- int n, opt = -1;
-
- m = d->parent_mixer;
- cp = mixer_get_ctl(m->dev, C_SRC);
- val = p;
- switch (*val) {
- case '+':
- opt = MIX_ADDRECSRC;
- break;
- case '-':
- opt = MIX_REMOVERECSRC;
- break;
- case '=':
- opt = MIX_SETRECSRC;
- break;
- case '^':
- opt = MIX_TOGGLERECSRC;
- break;
- default:
- warnx("%c: no such modifier", *val);
- return (-1);
- }
- n = MIX_ISRECSRC(m, m->dev->devno);
- if (mixer_mod_recsrc(m, opt) < 0)
- warn("%s.%s=%c", m->dev->name, cp->name, *val);
- else
- printf("%s.%s: %d -> %d\n",
- m->dev->name, cp->name, n, MIX_ISRECSRC(m, m->dev->devno));
-
- return (0);
-}
-
-static int
-print_volume(struct mix_dev *d, void *p)
-{
- struct mixer *m = d->parent_mixer;
- const char *ctl_name = p;
-
- printf("%s.%s=%.2f:%.2f\n",
- m->dev->name, ctl_name, m->dev->vol.left, m->dev->vol.right);
-
- return (0);
-}
-
-static int
-print_mute(struct mix_dev *d, void *p)
-{
- struct mixer *m = d->parent_mixer;
- const char *ctl_name = p;
-
- printf("%s.%s=%d\n", m->dev->name, ctl_name, MIX_ISMUTE(m, m->dev->devno));
-
- return (0);
-}
-
-static int
-print_recsrc(struct mix_dev *d, void *p)
-{
- struct mixer *m = d->parent_mixer;
- const char *ctl_name = p;
-
- if (!MIX_ISRECSRC(m, m->dev->devno))
- return (-1);
- printf("%s.%s=+\n", m->dev->name, ctl_name);
-
- return (0);
-}