mixer

Unnamed repository; edit this file 'description' to name the repository.
git clone git://git.margiolis.net/mixer.git
Log | Files | Refs | README

commit fc6d30ce359bab7a77363705f92b2c1702660a36
Author: Christos Margiolis <christos@margiolis.net>
Date:   Sun, 13 Jun 2021 20:37:29 +0300

initial commit

Diffstat:
A.gitignore | 3+++
AMakefile | 3+++
AREADME | 12++++++++++++
Amixer_lib/Makefile | 9+++++++++
Amixer_lib/mixer.3 | 39+++++++++++++++++++++++++++++++++++++++
Amixer_lib/mixer.c | 267+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amixer_lib/mixer.h | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amixer_prog/Makefile | 10++++++++++
Amixer_prog/mixer_prog.8 | 36++++++++++++++++++++++++++++++++++++
Amixer_prog/mixer_prog.c | 272+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
10 files changed, 731 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,3 @@ +*.o +*.depend +*.depend.* diff --git a/Makefile b/Makefile @@ -0,0 +1,3 @@ +SUBDIR= mixer_lib mixer_prog + +.include <bsd.subdir.mk> diff --git a/README b/README @@ -0,0 +1,12 @@ +mixer +===== +A new sound mixer library implementation for the FreeBSD, and a complete + rewrite of mixer(8) to use it and its new features. Some of its development +is part of Google Summer of Code 2021. + +Usage +----- + $ make + # make install clean + +Please report any bugs to <christos@freebsd.org>. diff --git a/mixer_lib/Makefile b/mixer_lib/Makefile @@ -0,0 +1,9 @@ +# $FreeBSD$ + +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 @@ -0,0 +1,39 @@ +.\"- +.\" 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. +.\" + +.Dd March 11, 2021 +.Dt mixer 3 +.Os +.Sh NAME +.Nm mixer +.Nd a mixer library +.Sh LIBRARY +Mixer (libmixer, -lmixer) +.Sh SYNOPSIS +.In mixer.h +.\" TODO +.Sh DESCRIPTION +.\" TODO +.Sh SEE ALSO +.Xr sound 4 +.Sh AUTHORS +.An Christos Margiolis Aq Mt christos@margiolis.net diff --git a/mixer_lib/mixer.c b/mixer_lib/mixer.c @@ -0,0 +1,267 @@ +/*- + * 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. + */ + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/sysctl.h> + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "mixer.h" + +#define BASEPATH "/dev/mixer" + +static int _mixer_close(struct mixer *m); + +static char *names[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_NAMES; + +/* + * Open a mixer device in `/dev/mixerN`, where N is the number of the mixer + * file. Each device maps to an actual pcmN audio card, so `/dev/mixer0` + * is the mixer device for pcm0, and so on. + * + * @arg: `name`: path to a mixer device. If it's NULL or "/dev/mixer", + * we open the default mixer (i.e `hw.snd.default_unit`). + */ +struct mixer * +mixer_open(const char *name) +{ + struct mixer *m = NULL; + struct mix_dev *dp; + int i, v; + + if ((m = calloc(0, 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; + goto fail; + } + /* `name` is "/dev/mixer" so, we'll use the default unit. */ + if (strncmp(name, BASEPATH, strlen(name)) == 0) + goto default_unit; + m->unit = strtol(name + strlen(BASEPATH), NULL, 10); + (void)strncpy(m->name, name, sizeof(m->name)); + } else { +default_unit: + if ((m->unit = mixer_get_default_unit()) < 0) + goto fail; + (void)snprintf(m->name, sizeof(m->name), "/dev/mixer%d", m->unit); + } + + if ((m->fd = open(m->name, O_RDWR, 0)) < 0) + goto fail; + + m->f_default = m->unit == mixer_get_default_unit(); + /* 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) + goto fail; + if (ioctl(m->fd, SNDCTL_CARDINFO, &m->ci) < 0) + goto fail; + if (ioctl(m->fd, SOUND_MIXER_READ_DEVMASK, &m->devmask) < 0) + goto fail; + if (ioctl(m->fd, SOUND_MIXER_READ_RECMASK, &m->recmask) < 0) + goto fail; + if (ioctl(m->fd, SOUND_MIXER_READ_RECSRC, &m->recsrc) < 0) + goto fail; + + TAILQ_INIT(&m->devs); + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if (!M_ISDEV(m, i) && !M_ISREC(m, i) && !M_ISRECSRC(m, i)) + continue; + if ((dp = malloc(sizeof(struct mix_dev))) == NULL) + goto fail; + if (ioctl(m->fd, MIXER_READ(i), &v) < 0) + goto fail; + dp->devno = i; + dp->lvol = v & 0x7f; + dp->rvol = (v >> 8) & 0x7f; + dp->lmute = 0; + dp->rmute = 0; + dp->f_pbk = !M_ISREC(m, i); + dp->f_rec = M_ISREC(m, i); + (void)strncpy(dp->name, names[i], sizeof(dp->name)); + TAILQ_INSERT_TAIL(&m->devs, dp, devs); + } + /* The default device is always "vol". */ + m->dev = TAILQ_FIRST(&m->devs); + + return m; +fail: + if (m != NULL) + (void)mixer_close(m); + + return NULL; +} + +int +mixer_close(struct mixer *m) +{ + return _mixer_close(m); +} + +/* + * Select a mixer device (e.g vol, pcm, mic) by name. The mixer structure + * keeps a list of all the devices the mixer has, but only one can be + * manipulated at a time -- this is what the `dev` field is for. Each + * time we want to manipulate a device, `dev` has to point to it first. + * + * @arg: `flags`: m->devmask / m->recmask / m->recsrc + */ +int +mixer_seldevbyname(struct mixer *m, const char *name, int flags) +{ + struct mix_dev *dp; + + TAILQ_FOREACH(dp, &m->devs, devs) { + if (M_ISSET(dp->devno, flags) + && !strncmp(dp->name, name, sizeof(dp->name))) { + m->dev = dp; + return 0; + } + } + errno = EINVAL; + + return -1; +} + +/* + * Change the mixer's left and right volume. The allowed volume values are + * between 0 and 100 and are stored as `lvol | rvol << 8`. + */ +int +mixer_chvol(struct mixer *m, int l, int r) +{ + if (l < 0) + l = 0; + else if (l > 100) + l = 100; + if (r < 0) + r = 0; + else if (r > 100) + r = 100; + m->dev->lvol = l; + m->dev->rvol = r; + l |= r << 8; + if (ioctl(m->fd, MIXER_WRITE(m->dev->devno), &l) < 0) + return -1; + + return 0; +} + +/* + * Modify the mixer's selected device flags. The `recsrc` flag tells + * us if a device is a recording source. + */ +int +mixer_modrecsrc(struct mixer *m, int opt) +{ + switch (opt) { + case M_ADDRECDEV: + m->recsrc |= (1 << m->dev->devno); + break; + case M_REMOVERECDEV: + m->recsrc &= ~(1 << m->dev->devno); + break; + case M_SETRECDEV: + m->recsrc = (1 << m->dev->devno); + break; + case M_TOGGLERECDEV: + m->recsrc ^= (1 << m->dev->devno); + break; + default: + errno = EINVAL; + return -1; + } + if (ioctl(m->fd, SOUND_MIXER_WRITE_RECSRC, &m->recsrc) < 0) + return -1; + if (ioctl(m->fd, SOUND_MIXER_READ_RECSRC, &m->recsrc) < 0) + return -1; + + return 0; +} + +/* + * Get default audio card's number. This is used to open the default mixer + * and set the mixer structure's `f_default` flag. + */ +int +mixer_get_default_unit(void) +{ + int unit; + size_t size; + + size = sizeof(int); + if (sysctlbyname("hw.snd.default_unit", &unit, &size, NULL, 0) < 0) + return -1; + + return unit; +} + +/* + * 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. + * + * @arg: `unit`: the audio card number (e.g pcm0, pcm1, ...). + */ +int +mixer_set_default_unit(struct mixer *m, int unit) +{ + size_t size; + + size = sizeof(int); + if (sysctlbyname("hw.snd.default_unit", NULL, 0, &unit, size) < 0) + return -1; + m->f_default = m->unit == unit; + + return 0; +} + +/* + * Free resources and close the mixer. + */ +static int +_mixer_close(struct mixer *m) +{ + struct mix_dev *dp; + int r; + + r = close(m->fd); + while (!TAILQ_EMPTY(&m->devs)) { + dp = TAILQ_FIRST(&m->devs); + TAILQ_REMOVE(&m->devs, dp, devs); + free(dp); + } + free(m); + + return r; +} diff --git a/mixer_lib/mixer.h b/mixer_lib/mixer.h @@ -0,0 +1,80 @@ +/*- + * 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. + */ + +#ifndef _MIXER_H_ +#define _MIXER_H_ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/queue.h> +#include <sys/soundcard.h> + +#include <limits.h> + +#define M_ADDRECDEV 0x01 +#define M_REMOVERECDEV 0x02 +#define M_SETRECDEV 0x04 +#define M_TOGGLERECDEV 0x08 + +#define M_ISSET(n, f) ((1 << (n)) & (f)) +#define M_ISDEV(m, n) M_ISSET(n, (m)->devmask) +#define M_ISREC(m, n) M_ISSET(n, (m)->recmask) +#define M_ISRECSRC(m, n) M_ISSET(n, (m)->recsrc) + +struct mix_dev { + char name[NAME_MAX]; + int devno; + int lvol; + int rvol; + int lmute; + int rmute; + //int rate; + //int samples; + int f_pbk; + int f_rec; + TAILQ_ENTRY(mix_dev) devs; +}; + +struct mixer { + TAILQ_HEAD(head, mix_dev) devs; + struct mix_dev *dev; + oss_mixerinfo mi; + oss_card_info ci; + char name[NAME_MAX]; + int fd; + int unit; + int devmask; + int recmask; + int recsrc; + int f_default; +}; + +struct mixer *mixer_open(const char *); +int mixer_close(struct mixer *); +int mixer_seldevbyname(struct mixer *, const char *, int); +int mixer_chvol(struct mixer *, int, int); +int mixer_modrecsrc(struct mixer *, int); +int mixer_get_default_unit(void); +int mixer_set_default_unit(struct mixer *, int); + +#endif /* _MIXER_H_ */ diff --git a/mixer_prog/Makefile b/mixer_prog/Makefile @@ -0,0 +1,10 @@ +# $FreeBSD$ + +PROG= mixer_prog +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_prog.8 b/mixer_prog/mixer_prog.8 @@ -0,0 +1,36 @@ +.\"- +.\" 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. +.\" + +.Dd June 07, 2021 +.Dt mixer 8 +.Os +.Sh NAME +.Nm mixer +.Nd manipulate soundcard mixer values +.Sh SYNOPSIS +.\" TODO: +.Sh DESCRIPTION +.\" TODO: +.Sh SEE ALSO +.Xr mixer 3 +.Sh AUTHORS +.An Christos Margiolis Aq Mt christos@margiolis.net diff --git a/mixer_prog/mixer_prog.c b/mixer_prog/mixer_prog.c @@ -0,0 +1,272 @@ +/*- + * 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. + */ + +#include <err.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <mixer.h> + +static void usage(struct mixer *) __dead2; +static void printrecsrc(struct mixer *, int); + +static void __dead2 +usage(struct mixer *m) +{ + struct mix_dev *dp; + int n; + + printf("usage: %1$s [-f device] [-s | -S] [dev [+|-][voll[:[+|-]volr]] ...\n" + " %1$s [-f device] [-s | -S] recsrc ...\n" + " %1$s [-f device] [-s | -S] {^|+|-|=}rec rdev ...\n", + getprogname()); + if (m->devmask) { + printf(" devices: "); + n = 0; + TAILQ_FOREACH(dp, &m->devs, devs) { + if (M_ISDEV(m, dp->devno)) { + if (n) + printf(", "); + printf("%s", dp->name); + n++; + } + } + } + if (m->recmask) { + printf("\n rec devices: "); + n = 0; + TAILQ_FOREACH(dp, &m->devs, devs) { + if (M_ISREC(m, dp->devno)) { + if (n) + printf(", "); + printf("%s", dp->name); + n++; + } + } + } + printf("\n"); + (void)mixer_close(m); + exit(1); +} + +static void +printrecsrc(struct mixer *m, int sflag) +{ + struct mix_dev *dp; + int n; + + if (!m->recmask) + return; + if (!sflag) + printf("Recording source: "); + n = 0; + TAILQ_FOREACH(dp, &m->devs, devs) { + if (M_ISRECSRC(m, dp->devno)) { + if (sflag) + printf("%srec ", n ? " +" : "="); + else if (n) + printf(", "); + printf("%s", dp->name); + n++; + } + } + if (!sflag) + printf("\n"); +} + +int +main(int argc, char *argv[]) +{ + struct mixer *m; + struct mix_dev *dp; + char lstr[8], rstr[8], *name = NULL; + int dusage = 0, drecsrc = 0, sflag = 0, Sflag = 0; + int l, r, lprev, rprev, lrel, rrel; + char ch, n, t, k, opt = 0; + + /* FIXME: problems with -rec */ + while ((ch = getopt(argc, argv, "f:sS")) != -1) { + switch (ch) { + case 'f': + name = optarg; + break; + case 's': + sflag = 1; + break; + case 'S': + Sflag = 1; + break; + case '?': + default: + dusage = 1; + break; + } + } + argc -= optind; + argv += optind; + + if (sflag && Sflag) + dusage = 1; + + if ((m = mixer_open(name)) == NULL) + err(1, "mixer_open: %s", name); + + if (!argc && !dusage) { + n = 0; + TAILQ_FOREACH(dp, &m->devs, devs) { + if (sflag || Sflag) { + printf("%s%s%c%d:%d", n ? " " : "", + dp->name, Sflag ? ':' : ' ', + dp->lvol, dp->rvol); + n++; + } else + printf("Mixer %-8s is currently set to %3d:%d\n", + dp->name, dp->lvol, dp->rvol); + } + if (n && m->recmask) + printf(" "); + printrecsrc(m, sflag || Sflag); + (void)mixer_close(m); + return 0; + } + + n = 0; + while (argc > 0 && !dusage) { + if (strcmp("recsrc", *argv) == 0) { + drecsrc = 1; + argc--; + argv++; + continue; + } else if (strcmp("rec", *argv + 1) == 0) { + if (**argv != '+' && **argv != '-' + && **argv != '=' && **argv != '^') { + warnx("unkown modifier: %c", **argv); + dusage = 1; + break; + } + if (argc <= 1) { + warnx("no recording device specified"); + dusage = 1; + break; + } + if (mixer_seldevbyname(m, argv[1], m->recmask) < 0) { + warnx("unkown recording revice: %s", argv[1]); + dusage = 1; + break; + } + + switch (**argv) { + case '+': + opt = M_ADDRECDEV; + break; + case '-': + opt = M_REMOVERECDEV; + break; + case '=': + opt = M_SETRECDEV; + break; + case '^': + opt = M_TOGGLERECDEV; + break; + } + if (mixer_modrecsrc(m, opt) < 0) { + warnx("cannot modify device"); + break; + } + drecsrc = 1; + argc -= 2; + argv += 2; + continue; + } + + if ((t = sscanf(*argv, "%d:%d", &l, &r)) > 0) + ; /* nothing */ + else if (mixer_seldevbyname(m, *argv, m->devmask) < 0) { + warnx("unkown device: %s", *argv); + dusage = 1; + break; + } + + lrel = rrel = 0; + if (argc > 1) { + k = sscanf(argv[1], "%7[^:]:%7s", lstr, rstr); + if (k == EOF) { + warnx("invalid value: %s", argv[1]); + dusage = 1; + break; + } + if (k > 0) { + if (*lstr == '+' || *lstr == '-') + lrel = rrel = 1; + l = strtol(lstr, NULL, 10); + } + if (k > 1) { + if (*rstr == '+' || *rstr == '-') + rrel = 1; + r = strtol(rstr, NULL, 10); + } + } + + switch (argc > 1 ? k : t) { + case 0: + printf("Mixer %-8s is currently set to %3d:%d\n", + m->dev->name, m->dev->lvol, m->dev->rvol); + argc--; + argv++; + continue; + case 1: + r = l; /* FALLTHROUGH */ + case 2: + /* + * Keep the previous volume so we don't have to clump + * it manually before we print it in case the user gives a + * value < 0 or > 100. + */ + lprev = m->dev->lvol; + rprev = m->dev->rvol; + if (lrel) + l += m->dev->lvol; + if (rrel) + r += m->dev->rvol; + if (mixer_chvol(m, l, r) < 0) { + warnx("cannot change volume"); + break; + } + if (!Sflag) + printf("Setting the mixer %s from %d:%d to %d:%d\n", + m->dev->name, lprev, rprev, + m->dev->lvol, m->dev->rvol); + argc -= 2; + argv += 2; + } + } + + if (dusage) + usage(m); + if (drecsrc) + printrecsrc(m, sflag || Sflag); + (void)mixer_close(m); + + return 0; +}