From e670b5392ab1e1a2b6a7860b341406bb397fa51e Mon Sep 17 00:00:00 2001 From: ratchov Date: Wed, 20 Mar 2024 08:42:11 +0000 Subject: [PATCH] aucat: Add generic channel mapping in place of -j and -c options. The argument to the -m option specifies the source and destination channel ranges to be mapped. Compatibility is maintained: if -m is not used, the -c and -j options still work. Help and suggestions from Jan Stary , thanks. --- usr.bin/aucat/aucat.1 | 42 +++---- usr.bin/aucat/aucat.c | 248 ++++++++++++++++++++++++++++-------------- usr.bin/aucat/dsp.c | 58 +++++----- 3 files changed, 213 insertions(+), 135 deletions(-) diff --git a/usr.bin/aucat/aucat.1 b/usr.bin/aucat/aucat.1 index 76efef00d39..544c3d8ae0d 100644 --- a/usr.bin/aucat/aucat.1 +++ b/usr.bin/aucat/aucat.1 @@ -1,4 +1,4 @@ -.\" $OpenBSD: aucat.1,v 1.119 2023/01/10 20:48:34 ratchov Exp $ +.\" $OpenBSD: aucat.1,v 1.120 2024/03/20 08:42:11 ratchov Exp $ .\" .\" Copyright (c) 2006 Alexandre Ratchov .\" @@ -14,7 +14,7 @@ .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" -.Dd $Mdocdate: January 10 2023 $ +.Dd $Mdocdate: March 20 2024 $ .Dt AUCAT 1 .Os .Sh NAME @@ -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 file and 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 , diff --git a/usr.bin/aucat/aucat.c b/usr.bin/aucat/aucat.c index d90ad729d31..bd43d7fb6a8 100644 --- a/usr.bin/aucat/aucat.c +++ b/usr.bin/aucat/aucat.c @@ -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, 3, 3, 2, 2, 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 aparams *par, int hdr, 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 aparams *par, int hdr, 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, int *ocnt) 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 *res_in, void *out, int icnt, int ocnt) } 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 *odata) 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, char *port) 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': diff --git a/usr.bin/aucat/dsp.c b/usr.bin/aucat/dsp.c index 0f4d771014d..d15fe728923 100644 --- a/usr.bin/aucat/dsp.c +++ b/usr.bin/aucat/dsp.c @@ -1,4 +1,4 @@ -/* $OpenBSD: dsp.c,v 1.18 2022/12/26 19:16:00 jmc Exp $ */ +/* $OpenBSD: dsp.c,v 1.19 2024/03/20 08:42:11 ratchov Exp $ */ /* * Copyright (c) 2008-2012 Alexandre Ratchov * @@ -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; - - cmin = -NCHAN_MAX; - if (osubmin > cmin) - cmin = osubmin; - if (omin > cmin) - cmin = omin; - if (isubmin > cmin) - cmin = isubmin; - if (imin > cmin) - cmin = imin; - - cmax = NCHAN_MAX; - if (osubmax < cmax) - cmax = osubmax; - if (omax < cmax) - cmax = omax; - if (isubmax < cmax) - cmax = isubmax; - if (imax < cmax) - cmax = imax; - - p->ostart = cmin - omin; - p->onext = omax - cmax; - p->istart = cmin - imin; - p->inext = imax - cmax; - p->nch = cmax - cmin + 1; + int inch, onch, nch; + + /* + * 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; + + /* + * 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 = 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 = "); -- 2.20.1