From: Alexandre Ratchov 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 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 = ");