Index | Thread | Search

From:
Alexandre Ratchov <alex@caoua.org>
Subject:
aucat: Add generic channel mapping in place of -j and -c options.
To:
tech@openbsd.org
Date:
Mon, 18 Mar 2024 12:07:11 +0100

Download raw body.

Thread
This diff allows any file channel range to be played to any device
channel range (and similarly for recording). IMHO, channel mapping
also simplifies the program interface, as it removes the need for
hacks based on the -c and -j options.

Example:

	aucat -m 2:3/4:5 -i file.wav

plays channels 2:3 of file.wav on device channels 4:5. The -c option
can't take ranges anymore (conflicts with -m), so it becomes simply
the (optional) number of channels for new and/or headerless files.

If the two ranges given to -m are not of the same size, source
channels are joined or expanded. This makes the -j option useless (and
conflicting with -m), so we drop it.

Compatibility with existing scripts using aucat is maintained: if the
new -m option is not used, the old-style -c and -j options still work
as expected, no urgency to remove them from the code.

If you're using aucat, especially hackery involving -j and -c, please
test.

OK?

Index: aucat.1
===================================================================
RCS file: /cvs/src/usr.bin/aucat/aucat.1,v
diff -u -p -r1.119 aucat.1
--- aucat.1	10 Jan 2023 20:48:34 -0000	1.119
+++ aucat.1	18 Mar 2024 10:50:40 -0000
@@ -24,13 +24,13 @@
 .Nm aucat
 .Op Fl dn
 .Op Fl b Ar size
-.Op Fl c Ar min : Ns Ar max
+.Op Fl c Ar channels
 .Op Fl e Ar enc
 .Op Fl f Ar device
 .Op Fl g Ar position
 .Op Fl h Ar fmt
 .Op Fl i Ar file
-.Op Fl j Ar flag
+.Op Fl m Ar min : Ns Ar max Ns / Ns Ar min : Ns Ar max
 .Op Fl o Ar file
 .Op Fl p Ar position
 .Op Fl q Ar port
@@ -78,11 +78,9 @@ The options are as follows:
 .It Fl b Ar size
 The buffer size of the audio device in frames.
 Default is 7680.
-.It Fl c Ar min : Ns Ar max
-The range of audio file channel numbers.
-The default is
-.Cm 0:1 ,
-i.e. stereo.
+.It Fl c Ar channels
+The audio file channels count.
+The default is 2, i.e. stereo.
 .It Fl d
 Increase log verbosity.
 .It Fl e Ar enc
@@ -146,21 +144,9 @@ Play this audio file.
 If the option argument is
 .Sq -
 then standard input will be used.
-.It Fl j Ar flag
-Control whether source channels are joined or expanded if
-they don't match the destination number of channels.
-If the flag is
-.Cm off ,
-then each source channel is routed to a single destination channel,
-possibly discarding channels.
-If the flag is
-.Cm on ,
-then a single source may be sent to multiple destinations
-and multiple sources may be mixed into a single destination.
-For instance, this feature could be used to convert
-a stereo file into a mono file mixing left and right channels together.
-The default is
-.Cm off .
+.It Fl m Ar min : Ns Ar max Ns / Ns Ar min : Ns Ar max
+Map the given range of file channels into the given range of
+device channels.
 .It Fl n
 Off-line mode.
 Read input files and store the result in the output files,
@@ -198,7 +184,7 @@ The default is 127, i.e. no attenuation.
 .Pp
 On the command line,
 per-file parameters
-.Pq Fl cehjrv
+.Pq Fl cehmrv
 must precede the file definition
 .Pq Fl io .
 .Pp
@@ -282,13 +268,13 @@ Record channels 2 and 3 into one stereo 
 channels 6 and 7 into another stereo file using a 44.1kHz sampling
 rate for both:
 .Bd -literal -offset indent
-$ aucat -r 44100 -c 2:3 -o file1.wav -c 6:7 -o file2.wav
+$ aucat -r 44100 -m 0:1/2:3 -o file1.wav -m 0:1/6:7 -o file2.wav
 .Ed
 .Pp
 Split a stereo file into two mono files:
 .Bd -literal -offset indent
-$ aucat -n -i stereo.wav -c 0:0 -o left.wav \e
-	-c 1:1 -o right.wav
+$ aucat -n -i stereo.wav -c 1 -m 0:0/0:0 -o left.wav \e
+	-m 0:0/1:1 -o right.wav
 .Ed
 .Sh SEE ALSO
 .Xr cdio 1 ,
Index: aucat.c
===================================================================
RCS file: /cvs/src/usr.bin/aucat/aucat.c,v
diff -u -p -r1.179 aucat.c
--- aucat.c	1 Feb 2024 05:28:54 -0000	1.179
+++ aucat.c	18 Mar 2024 10:50:40 -0000
@@ -75,14 +75,14 @@ struct slot {
 	int volctl;			/* volume in the 0..127 range */
 	struct abuf buf;		/* file i/o buffer */
 	int bpf;			/* bytes per frame */
-	int cmin, cmax;			/* file channel range */
+	int imin, imax, omin, omax;	/* channel mapping ranges */
 	struct cmap cmap;		/* channel mapper state */
 	struct resamp resamp;		/* resampler state */
 	struct conv conv;		/* format encoder state */
 	int join;			/* channel join factor */
 	int expand;			/* channel expand factor */
 	void *resampbuf, *convbuf;	/* conversion tmp buffers */
-	int dup;			/* mono-to-stereo and alike */
+	int dup;			/* compat with legacy -j option */
 	int round;			/* slot-side block size */
 	int mode;			/* MODE_{PLAY,REC} */
 #define SLOT_CFG	0		/* buffers not allocated yet */
@@ -136,9 +136,9 @@ const unsigned int voice_len[] = { 3, 3,
 const unsigned int common_len[] = { 0, 2, 3, 2, 0, 0, 1, 1 };
 
 char usagestr[] = "usage: aucat [-dn] [-b size] "
-    "[-c min:max] [-e enc] [-f device] [-g position]\n\t"
-    "[-h fmt] [-i file] [-j flag] [-o file] [-p position] [-q port]\n\t"
-    "[-r rate] [-v volume]\n";
+    "[-c channels] [-e enc] [-f device] [-g position]\n\t"
+    "[-h fmt] [-i file] [-m min:max/min:max] [-o file] [-p position]\n\t"
+    "[-q port] [-r rate] [-v volume]\n";
 
 static void *
 allocbuf(int nfr, int nch)
@@ -218,19 +218,22 @@ slot_fill(struct slot *s)
 
 static int
 slot_new(char *path, int mode, struct aparams *par, int hdr,
-    int cmin, int cmax, int rate, int dup, int vol, long long pos)
+    int imin, int imax, int omin, int omax, int nch,
+    int rate, int dup, int vol, long long pos)
 {
 	struct slot *s, **ps;
 
 	s = xmalloc(sizeof(struct slot));
 	if (!afile_open(&s->afile, path, hdr,
 		mode == SIO_PLAY ? AFILE_FREAD : AFILE_FWRITE,
-		par, rate, cmax - cmin + 1)) {
+		par, rate, nch)) {
 		xfree(s);
 		return 0;
 	}
-	s->cmin = cmin;
-	s->cmax = cmin + s->afile.nch - 1;
+	s->imin = (imin != -1) ? imin : 0;
+	s->imax = (imax != -1) ? imax : s->imin + s->afile.nch - 1;
+	s->omin = (omin != -1) ? omin : 0;
+	s->omax = (omax != -1) ? omax : s->omin + s->afile.nch - 1;
 	s->dup = dup;
 	s->vol = MIDI_TO_ADATA(vol);
 	s->mode = mode;
@@ -240,11 +243,17 @@ slot_new(char *path, int mode, struct ap
 		slot_log(s);
 		log_puts(": ");
 		log_puts(s->mode == SIO_PLAY ? "play" : "rec");
-		log_puts(", chan ");
-		log_putu(s->cmin);
-		log_puts(":");
-		log_putu(s->cmax);
 		log_puts(", ");
+		log_putu(s->afile.nch);
+		log_puts("ch (");
+		log_putu(s->imin);
+		log_puts(":");
+		log_putu(s->imax);
+		log_puts("/");
+		log_putu(s->omin);
+		log_puts(":");
+		log_putu(s->omax);
+		log_puts("), ");
 		log_putu(s->afile.rate);
 		log_puts("Hz, ");
 		switch (s->afile.fmt) {
@@ -283,7 +292,7 @@ slot_new(char *path, int mode, struct ap
 static void
 slot_init(struct slot *s)
 {
-	unsigned int slot_nch, bufsz;
+	unsigned int inch, onch, bufsz;
 
 #ifdef DEBUG
 	if (s->pstate != SLOT_CFG) {
@@ -292,7 +301,7 @@ slot_init(struct slot *s)
 		panic();
 	}
 #endif
-	s->bpf = s->afile.par.bps * (s->cmax - s->cmin + 1);
+	s->bpf = s->afile.par.bps * s->afile.nch;
 	s->round = ((long long)dev_round * s->afile.rate +
 	    dev_rate - 1) / dev_rate;
 
@@ -310,54 +319,50 @@ slot_init(struct slot *s)
 	}
 #endif
 
-	slot_nch = s->cmax - s->cmin + 1;
 	s->convbuf = NULL;
 	s->resampbuf = NULL;
 	s->join = 1;
 	s->expand = 1;
+	inch = s->imax - s->imin + 1;
+	onch = s->omax - s->omin + 1;
+	if (s->dup) {
+		/* compat with legacy -j option */
+		if (s->mode == SIO_PLAY)
+			onch = dev_pchan;
+		else
+			inch = dev_rchan;
+	}
+	if (onch > inch)
+		s->expand = onch / inch;
+	else if (onch < inch)
+		s->join = inch / onch;
 	if (s->mode & SIO_PLAY) {
-		if (s->dup) {
-			if (dev_pchan > slot_nch)
-				s->expand = dev_pchan / slot_nch;
-			else if (dev_pchan < slot_nch)
-				s->join = slot_nch / dev_pchan;
-		}
 		cmap_init(&s->cmap,
-		    s->cmin, s->cmax,
-		    s->cmin, s->cmax,
-		    0, dev_pchan - 1,
-		    0, dev_pchan - 1);
+		    0, s->afile.nch - 1, s->imin, s->imax,
+		    0, dev_pchan - 1, s->omin, s->omax);
 		if (s->afile.fmt != AFILE_FMT_PCM ||
 		    !aparams_native(&s->afile.par)) {
-			dec_init(&s->conv, &s->afile.par, slot_nch);
-			s->convbuf = allocbuf(s->round, slot_nch);
+			dec_init(&s->conv, &s->afile.par, s->afile.nch);
+			s->convbuf = allocbuf(s->round, s->afile.nch);
 		}
 		if (s->afile.rate != dev_rate) {
 			resamp_init(&s->resamp, s->afile.rate, dev_rate,
-			    slot_nch);
-			s->resampbuf = allocbuf(dev_round, slot_nch);
+			    s->afile.nch);
+			s->resampbuf = allocbuf(dev_round, s->afile.nch);
 		}
 	}
 	if (s->mode & SIO_REC) {
-		if (s->dup) {
-			if (dev_rchan > slot_nch)
-				s->join = dev_rchan / slot_nch;
-			else if (dev_rchan < slot_nch)
-				s->expand = slot_nch / dev_rchan;
-		}
 		cmap_init(&s->cmap,
-		    0, dev_rchan - 1,
-		    0, dev_rchan - 1,
-		    s->cmin, s->cmax,
-		    s->cmin, s->cmax);
+		    0, dev_rchan - 1, s->imin, s->imax,
+		    0, s->afile.nch - 1, s->omin, s->omax);
 		if (s->afile.rate != dev_rate) {
 			resamp_init(&s->resamp, dev_rate, s->afile.rate,
-			    slot_nch);
-			s->resampbuf = allocbuf(dev_round, slot_nch);
+			    s->afile.nch);
+			s->resampbuf = allocbuf(dev_round, s->afile.nch);
 		}
 		if (!aparams_native(&s->afile.par)) {
-			enc_init(&s->conv, &s->afile.par, slot_nch);
-			s->convbuf = allocbuf(s->round, slot_nch);
+			enc_init(&s->conv, &s->afile.par, s->afile.nch);
+			s->convbuf = allocbuf(s->round, s->afile.nch);
 		}
 
 		/*
@@ -368,13 +373,13 @@ slot_init(struct slot *s)
 	         */
 		if (s->resampbuf) {
 			memset(s->resampbuf, 0,
-			    dev_round * slot_nch * sizeof(adata_t));
+			    dev_round * s->afile.nch * sizeof(adata_t));
 		} else if (s->convbuf) {
 			memset(s->convbuf, 0,
-			    s->round * slot_nch * sizeof(adata_t));
+			    s->round * s->afile.nch * sizeof(adata_t));
 		} else {
 			memset(s->buf.data, 0,
-			    bufsz * slot_nch * sizeof(adata_t));
+			    bufsz * s->afile.nch * sizeof(adata_t));
 		}
 	}
 	s->pstate = SLOT_INIT;
@@ -487,7 +492,7 @@ slot_getcnt(struct slot *s, int *icnt, i
 static void
 play_filt_resamp(struct slot *s, void *res_in, void *out, int icnt, int ocnt)
 {
-	int i, offs, vol, nch;
+	int i, offs, vol, inch, onch;
 	void *in;
 
 	if (s->resampbuf) {
@@ -496,18 +501,24 @@ play_filt_resamp(struct slot *s, void *r
 	} else
 		in = res_in;
 
-	nch = s->cmap.nch;
+	inch = s->imax - s->imin + 1;
+	onch = s->omax - s->omin + 1;
 	vol = s->vol / s->join; /* XXX */
 	cmap_add(&s->cmap, in, out, vol, ocnt);
 
 	offs = 0;
 	for (i = s->join - 1; i > 0; i--) {
-		offs += nch;
+		offs += onch;
+		if (offs + s->cmap.nch > s->afile.nch)
+			break;
 		cmap_add(&s->cmap, (adata_t *)in + offs, out, vol, ocnt);
 	}
+
 	offs = 0;
 	for (i = s->expand - 1; i > 0; i--) {
-		offs += nch;
+		offs += inch;
+		if (offs + s->cmap.nch > dev_pchan)
+			break;
 		cmap_add(&s->cmap, in, (adata_t *)out + offs, vol, ocnt);
 	}
 }
@@ -581,23 +592,28 @@ slot_mix_badd(struct slot *s, adata_t *o
 static void
 rec_filt_resamp(struct slot *s, void *in, void *res_out, int icnt, int ocnt)
 {
-	int i, vol, offs, nch;
+	int i, vol, offs, inch, onch;
 	void *out = res_out;
 
 	out = (s->resampbuf) ? s->resampbuf : res_out;
 
-	nch = s->cmap.nch;
+	inch = s->imax - s->imin + 1;
+	onch = s->omax - s->omin + 1;
 	vol = ADATA_UNIT / s->join;
 	cmap_copy(&s->cmap, in, out, vol, icnt);
 
 	offs = 0;
 	for (i = s->join - 1; i > 0; i--) {
-		offs += nch;
+		offs += onch;
+		if (offs + s->cmap.nch > dev_rchan)
+			break;
 		cmap_add(&s->cmap, (adata_t *)in + offs, out, vol, icnt);
 	}
 	offs = 0;
 	for (i = s->expand - 1; i > 0; i--) {
-		offs += nch;
+		offs += inch;
+		if (offs + s->cmap.nch > s->afile.nch)
+			break;
 		cmap_copy(&s->cmap, in, (adata_t *)out + offs, vol, icnt);
 	}
 	if (s->resampbuf)
@@ -683,12 +699,12 @@ dev_open(char *dev, int mode, int bufsz,
 		if (s->afile.rate > rate)
 			rate = s->afile.rate;
 		if (s->mode == SIO_PLAY) {
-			if (s->cmax > pmax)
-				pmax = s->cmax;
+			if (s->omax > pmax)
+				pmax = s->omax;
 		}
 		if (s->mode == SIO_REC) {
-			if (s->cmax > rmax)
-				rmax = s->cmax;
+			if (s->imax > rmax)
+				rmax = s->imax;
 		}
 	}
 	sio_initpar(&par);
@@ -1078,8 +1094,10 @@ offline(void)
 	for (s = slot_list; s != NULL; s = s->next) {
 		if (s->afile.rate > rate)
 			rate = s->afile.rate;
-		if (s->cmax > cmax)
-			cmax = s->cmax;
+		if (s->imax > cmax)
+			cmax = s->imax;
+		if (s->omax > cmax)
+			cmax = s->omax;
 	}
 	dev_sh = NULL;
 	dev_name = "offline";
@@ -1323,26 +1341,78 @@ opt_hdr(char *s, int *hdr)
 }
 
 static int
-opt_ch(char *s, int *rcmin, int *rcmax)
+opt_map(char *str, int *rfmin, int *rfmax, int *rdmin, int *rdmax)
 {
-	char *next, *end;
-	long cmin, cmax;
+	char *s, *next;
+	long fmin, fmax, dmin, dmax;
 
 	errno = 0;
-	cmin = strtol(s, &next, 10);
+	s = str;
+	fmin = strtol(s, &next, 10);
+	if (next == s || *next != ':')
+		goto failed;
+	s = next + 1;
+	fmax = strtol(s, &next, 10);
+	if (next == s || *next != '/')
+		goto failed;
+	s = next + 1;
+	dmin = strtol(s, &next, 10);
 	if (next == s || *next != ':')
 		goto failed;
-	cmax = strtol(++next, &end, 10);
-	if (end == next || *end != '\0')
+	s = next + 1;
+	dmax = strtol(s, &next, 10);
+	if (next == s || *next != '\0')
 		goto failed;
-	if (cmin < 0 || cmax < cmin || cmax >= NCHAN_MAX)
+	if (fmin < 0 || fmax < fmin || fmax >= NCHAN_MAX)
 		goto failed;
-	*rcmin = cmin;
-	*rcmax = cmax;
+	if (dmin < 0 || dmax < dmin || dmax >= NCHAN_MAX)
+		goto failed;
+	*rfmin = fmin;
+	*rfmax = fmax;
+	*rdmin = dmin;
+	*rdmax = dmax;
 	return 1;
 failed:
-	log_puts(s);
-	log_puts(": channel range expected\n");
+	log_puts(str);
+	log_puts(": channel mapping expected\n");
+	return 0;
+}
+
+static int
+opt_nch(char *str, int *rnch, int *roff)
+{
+	char *s, *next;
+	long nch, off, cmin, cmax;
+
+	errno = 0;
+	s = str;
+	nch = strtol(s, &next, 10);
+	if (next == s)
+		goto failed;
+	if (*next == ':') {
+		/* compat with legacy -c syntax */
+		s = next + 1;
+		cmin = nch;
+		cmax = strtol(s, &next, 10);
+		if (next == s)
+			goto failed;
+		if (cmin < 0 || cmax < cmin || cmax >= NCHAN_MAX)
+			goto failed;
+		nch = cmax - cmin + 1;
+		off = cmin;
+	} else {
+		off = 0;
+		if (nch < 0 || nch >= NCHAN_MAX)
+			goto failed;
+	}
+	if (*next != '\0')
+		goto failed;
+	*rnch = nch;
+	*roff = off;
+	return 1;
+failed:
+	log_puts(str);
+	log_puts(": channel count expected\n");
 	return 0;
 }
 
@@ -1381,7 +1451,7 @@ opt_pos(char *s, long long *pos)
 int
 main(int argc, char **argv)
 {
-	int dup, cmin, cmax, rate, vol, bufsz, hdr, mode;
+	int dup, fmin, fmax, dmin, dmax, nch, off, rate, vol, bufsz, hdr, mode;
 	char *port, *dev;
 	struct aparams par;
 	int n_flag, c;
@@ -1393,9 +1463,10 @@ main(int argc, char **argv)
 	vol = 127;
 	dup = 0;
 	bufsz = 0;
+	nch = 2;
+	off = 0;
 	rate = DEFAULT_RATE;
-	cmin = 0;
-	cmax = 1;
+	fmin = fmax = dmin = dmax = -1;
 	par.bits = ADATA_BITS;
 	par.bps = APARAMS_BPS(par.bits);
 	par.le = ADATA_LE;
@@ -1409,15 +1480,20 @@ main(int argc, char **argv)
 	pos = 0;
 
 	while ((c = getopt(argc, argv,
-		"b:c:de:f:g:h:i:j:no:p:q:r:t:v:")) != -1) {
+		"b:c:de:f:g:h:i:j:m:no:p:q:r:t:v:")) != -1) {
 		switch (c) {
 		case 'b':
 			if (!opt_num(optarg, 1, RATE_MAX, &bufsz))
 				return 1;
 			break;
 		case 'c':
-			if (!opt_ch(optarg, &cmin, &cmax))
+			if (!opt_nch(optarg, &nch, &off))
 				return 1;
+			if (off > 0) {
+				/* compat with legacy -c syntax */
+				dmin = off;
+				dmax = off + nch - 1;
+			}
 			break;
 		case 'd':
 			log_level++;
@@ -1439,20 +1515,27 @@ main(int argc, char **argv)
 			break;
 		case 'i':
 			if (!slot_new(optarg, SIO_PLAY,
-				&par, hdr, cmin, cmax, rate, dup, vol, pos))
+				&par, hdr, fmin, fmax, dmin, dmax,
+				nch, rate, dup, vol, pos))
 				return 1;
 			mode |= SIO_PLAY;
 			break;
 		case 'j':
+			/* compat with legacy -j option */
 			if (!opt_onoff(optarg, &dup))
 				return 1;
 			break;
+		case 'm':
+			if (!opt_map(optarg, &fmin, &fmax, &dmin, &dmax))
+				return 1;
+			break;
 		case 'n':
 			n_flag = 1;
 			break;
 		case 'o':
 			if (!slot_new(optarg, SIO_REC,
-				&par, hdr, cmin, cmax, rate, dup, 0, pos))
+				&par, hdr, dmin, dmax, fmin, fmax,
+				nch, rate, dup, 0, pos))
 				return 1;
 			mode |= SIO_REC;
 			break;
Index: dsp.c
===================================================================
RCS file: /cvs/src/usr.bin/aucat/dsp.c,v
diff -u -p -r1.18 dsp.c
--- dsp.c	26 Dec 2022 19:16:00 -0000	1.18
+++ dsp.c	18 Mar 2024 10:50:40 -0000
@@ -992,33 +992,35 @@ cmap_init(struct cmap *p,
     int imin, int imax, int isubmin, int isubmax,
     int omin, int omax, int osubmin, int osubmax)
 {
-	int cmin, cmax;
+	int inch, onch, nch;
 
-	cmin = -NCHAN_MAX;
-	if (osubmin > cmin)
-		cmin = osubmin;
-	if (omin > cmin)
-		cmin = omin;
-	if (isubmin > cmin)
-		cmin = isubmin;
-	if (imin > cmin)
-		cmin = imin;
+	/*
+	 * Ignore channels outside of the available sets
+	 */
+	if (isubmin < imin)
+		isubmin = imin;
+	if (isubmax > imax)
+		isubmax = imax;
+	if (osubmin < omin)
+		osubmin = omin;
+	if (osubmax > omax)
+		osubmax = omax;
 
-	cmax = NCHAN_MAX;
-	if (osubmax < cmax)
-		cmax = osubmax;
-	if (omax < cmax)
-		cmax = omax;
-	if (isubmax < cmax)
-		cmax = isubmax;
-	if (imax < cmax)
-		cmax = imax;
+	/*
+	 * Shrink the input or the output subset to make both subsets of
+	 * the same size
+	 */
+	inch = isubmax - isubmin + 1;
+	onch = osubmax - osubmin + 1;
+	nch = (inch < onch) ? inch : onch;
+	isubmax = isubmin + nch - 1;
+	osubmax = osubmin + nch - 1;
 
-	p->ostart = cmin - omin;
-	p->onext = omax - cmax;
-	p->istart = cmin - imin;
-	p->inext = imax - cmax;
-	p->nch = cmax - cmin + 1;
+	p->ostart = osubmin - omin;
+	p->onext = omax - osubmax;
+	p->istart = isubmin - imin;
+	p->inext = imax - isubmax;
+	p->nch = nch;
 #ifdef DEBUG
 	if (log_level >= 3) {
 		log_puts("cmap: nch = ");