From: Jan Stary Subject: Re: aucat: Add generic channel mapping in place of -j and -c options. To: Alexandre Ratchov Cc: tech@openbsd.org Date: Tue, 19 Mar 2024 16:24:47 +0100 On Mar 18 18:12:46, alex@caoua.org wrote: > On Mon, Mar 18, 2024 at 04:28:36PM +0100, Jan Stary wrote: > > > > I like the producer/consumer nomenclature, as it is the closest > > to how I think about a signal route - not in terms of files and devices. > > > > Here's a better diff: > - ranges in the producer/consumer order. > - the mapping is reset after every -i and -o. Thank you. I have a few notes on the manpage, but they are not necessarily -m related, so I will leave those till after this gets in. Jan > 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 14:54:52 -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 source channels into the given range of > +destination 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 2:3/0:1 -o file1.wav -m 6:7/0:1 -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 1:1/0:0 -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 14:54:52 -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 *rimin, int *rimax, int *romin, int *romax) > { > - char *next, *end; > - long cmin, cmax; > + char *s, *next; > + long imin, imax, omin, omax; > > errno = 0; > - cmin = strtol(s, &next, 10); > + s = str; > + imin = strtol(s, &next, 10); > + if (next == s || *next != ':') > + goto failed; > + s = next + 1; > + imax = strtol(s, &next, 10); > + if (next == s || *next != '/') > + goto failed; > + s = next + 1; > + omin = strtol(s, &next, 10); > if (next == s || *next != ':') > goto failed; > - cmax = strtol(++next, &end, 10); > - if (end == next || *end != '\0') > + s = next + 1; > + omax = strtol(s, &next, 10); > + if (next == s || *next != '\0') > goto failed; > - if (cmin < 0 || cmax < cmin || cmax >= NCHAN_MAX) > + if (imin < 0 || imax < imin || imax >= NCHAN_MAX) > goto failed; > - *rcmin = cmin; > - *rcmax = cmax; > + if (omin < 0 || omax < omin || omax >= NCHAN_MAX) > + goto failed; > + *rimin = imin; > + *rimax = imax; > + *romin = omin; > + *romax = omax; > 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, imin, imax, omin, omax, 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; > + imin = imax = omin = omax = -1; > par.bits = ADATA_BITS; > par.bps = APARAMS_BPS(par.bits); > par.le = ADATA_LE; > @@ -1409,14 +1480,14 @@ 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; > break; > case 'd': > @@ -1438,22 +1509,41 @@ main(int argc, char **argv) > return 1; > break; > case 'i': > + if (off > 0) { > + /* compat with legacy -c syntax */ > + omin = off; > + omax = off + nch - 1; > + } > if (!slot_new(optarg, SIO_PLAY, > - &par, hdr, cmin, cmax, rate, dup, vol, pos)) > + &par, hdr, imin, imax, omin, omax, > + nch, rate, dup, vol, pos)) > return 1; > mode |= SIO_PLAY; > + imin = imax = omin = omax = -1; > break; > case 'j': > + /* compat with legacy -j option */ > if (!opt_onoff(optarg, &dup)) > return 1; > break; > + case 'm': > + if (!opt_map(optarg, &imin, &imax, &omin, &omax)) > + return 1; > + break; > case 'n': > n_flag = 1; > break; > case 'o': > + if (off > 0) { > + /* compat with legacy -c syntax */ > + imin = off; > + imax = off + nch - 1; > + } > if (!slot_new(optarg, SIO_REC, > - &par, hdr, cmin, cmax, rate, dup, 0, pos)) > + &par, hdr, imin, imax, omin, omax, > + nch, rate, dup, 0, pos)) > return 1; > + imin = imax = omin = omax = -1; > mode |= SIO_REC; > break; > case 'p': > 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 14:54:53 -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 = "); > >