Lines are logged using a single function call, which makes the sources easier to read and reduces the binary size. Debug strings that would be too difficult to produce with snprintf(3) are simplified.
1537 lines
33 KiB
C
1537 lines
33 KiB
C
/*
|
|
* Copyright (c) 2008-2014 Alexandre Ratchov <alex@caoua.org>
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
#include <err.h>
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
#include <poll.h>
|
|
#include <signal.h>
|
|
#include <sndio.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include "abuf.h"
|
|
#include "afile.h"
|
|
#include "dsp.h"
|
|
#include "sysex.h"
|
|
#include "utils.h"
|
|
|
|
/*
|
|
* masks to extract command and channel of status byte
|
|
*/
|
|
#define MIDI_CMDMASK 0xf0
|
|
#define MIDI_CHANMASK 0x0f
|
|
|
|
/*
|
|
* MIDI status bytes of voice messages
|
|
*/
|
|
#define MIDI_NOFF 0x80 /* note off */
|
|
#define MIDI_NON 0x90 /* note on */
|
|
#define MIDI_KAT 0xa0 /* key after touch */
|
|
#define MIDI_CTL 0xb0 /* controller */
|
|
#define MIDI_PC 0xc0 /* program change */
|
|
#define MIDI_CAT 0xd0 /* channel after touch */
|
|
#define MIDI_BEND 0xe0 /* pitch bend */
|
|
#define MIDI_ACK 0xfe /* active sensing message */
|
|
|
|
/*
|
|
* MIDI controller numbers
|
|
*/
|
|
#define MIDI_CTL_VOL 7
|
|
|
|
/*
|
|
* Max coarse value
|
|
*/
|
|
#define MIDI_MAXCTL 127
|
|
|
|
/*
|
|
* MIDI status bytes for sysex
|
|
*/
|
|
#define MIDI_SX_START 0xf0
|
|
#define MIDI_SX_STOP 0xf7
|
|
|
|
/*
|
|
* audio device defaults
|
|
*/
|
|
#define DEFAULT_RATE 48000
|
|
#define DEFAULT_BUFSZ_MS 200
|
|
|
|
struct slot {
|
|
struct slot *next; /* next on the play/rec list */
|
|
int vol; /* dynamic range */
|
|
int volctl; /* volume in the 0..127 range */
|
|
struct abuf buf; /* file i/o buffer */
|
|
int bpf; /* bytes per frame */
|
|
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; /* compat with legacy -j option */
|
|
int round; /* slot-side block size */
|
|
int mode; /* MODE_{PLAY,REC} */
|
|
#define SLOT_CFG 0 /* buffers not allocated yet */
|
|
#define SLOT_INIT 1 /* not trying to do anything */
|
|
#define SLOT_RUN 2 /* playing/recording */
|
|
#define SLOT_STOP 3 /* draining (play only) */
|
|
int pstate; /* one of above */
|
|
long long skip; /* frames to skip at the beginning */
|
|
long long pos; /* start position (at device rate) */
|
|
struct afile afile; /* file desc & friends */
|
|
};
|
|
|
|
/*
|
|
* device properties
|
|
*/
|
|
unsigned int dev_mode; /* bitmap of SIO_{PLAY,REC} */
|
|
unsigned int dev_bufsz; /* device buffer size */
|
|
unsigned int dev_round; /* device block size */
|
|
int dev_rate; /* device sample rate (Hz) */
|
|
unsigned int dev_pchan, dev_rchan; /* play & rec channels count */
|
|
adata_t *dev_pbuf, *dev_rbuf; /* play & rec buffers */
|
|
struct aparams dev_par; /* device sample format */
|
|
struct conv dev_enc, dev_dec; /* format conversions */
|
|
unsigned char *dev_encbuf, *dev_decbuf; /* buf for format conversions */
|
|
long long dev_pos; /* last MMC position in frames */
|
|
#define DEV_STOP 0 /* stopped */
|
|
#define DEV_START 1 /* started */
|
|
unsigned int dev_pstate; /* one of above */
|
|
char *dev_name; /* device sndio(7) name */
|
|
char *dev_port; /* control port sndio(7) name */
|
|
struct sio_hdl *dev_sh; /* device handle */
|
|
struct mio_hdl *dev_mh; /* MIDI control port handle */
|
|
unsigned int dev_volctl = MIDI_MAXCTL; /* master volume */
|
|
|
|
/*
|
|
* MIDI parser state
|
|
*/
|
|
#define MIDI_MSGMAX 32 /* max size of MIDI msg */
|
|
unsigned char dev_msg[MIDI_MSGMAX]; /* parsed input message */
|
|
unsigned int dev_mst; /* input MIDI running status */
|
|
unsigned int dev_mused; /* bytes used in ``msg'' */
|
|
unsigned int dev_midx; /* current ``msg'' size */
|
|
unsigned int dev_mlen; /* expected ``msg'' length */
|
|
unsigned int dev_prime; /* blocks to write to start */
|
|
|
|
unsigned int log_level = 1;
|
|
volatile sig_atomic_t quit_flag = 0;
|
|
struct slot *slot_list = NULL;
|
|
|
|
/*
|
|
* length of voice and common MIDI messages (status byte included)
|
|
*/
|
|
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 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, int bps)
|
|
{
|
|
size_t fsize;
|
|
void *ptr;
|
|
|
|
if (nch < 0 || nch > NCHAN_MAX || bps < 0 || bps > 4) {
|
|
logx(1, "allocbuf: bogus channels or bytes per sample count\n");
|
|
panic();
|
|
}
|
|
fsize = nch * bps;
|
|
|
|
ptr = reallocarray(NULL, nfr, fsize);
|
|
if (ptr == NULL)
|
|
err(1, "reallocarray");
|
|
return ptr;
|
|
}
|
|
|
|
static size_t
|
|
chans_fmt(char *buf, size_t size, int mode, int pmin, int pmax, int rmin, int rmax)
|
|
{
|
|
const char *sep = "";
|
|
char *end = buf + size;
|
|
char *p = buf;
|
|
|
|
if (mode & SIO_PLAY) {
|
|
p += snprintf(p, p < end ? end - p : 0, "play %d:%d", pmin, pmax);
|
|
sep = ", ";
|
|
}
|
|
if (mode & SIO_REC) {
|
|
p += snprintf(p, p < end ? end - p : 0, "%srec %d:%d", sep, rmin, rmax);
|
|
}
|
|
|
|
return p - buf;
|
|
}
|
|
|
|
static size_t
|
|
slot_fmt(char *buf, size_t size, struct slot *s)
|
|
{
|
|
char enc[ENCMAX];
|
|
char *end = buf + size;
|
|
char *p = buf;
|
|
|
|
switch (s->afile.fmt) {
|
|
case AFILE_FMT_PCM:
|
|
aparams_enctostr(&s->afile.par, enc);
|
|
break;
|
|
case AFILE_FMT_ULAW:
|
|
strlcpy(enc, "ulaw", sizeof(enc));
|
|
break;
|
|
case AFILE_FMT_ALAW:
|
|
strlcpy(enc, "alaw", sizeof(enc));
|
|
break;
|
|
case AFILE_FMT_FLOAT:
|
|
strlcpy(enc, "f32le", sizeof(enc));
|
|
break;
|
|
default:
|
|
enc[0] = 0;
|
|
}
|
|
|
|
p += snprintf(p, p < end ? end - p : 0,
|
|
"%s, %uch (%u:%u/%u:%u), %uHz, %s",
|
|
s->mode == SIO_PLAY ? "play" : "rec",
|
|
s->afile.nch, s->imin, s->imax, s->omin, s->omax,
|
|
s->afile.rate, enc);
|
|
|
|
if (s->mode == SIO_PLAY) {
|
|
if (s->afile.endpos >= 0) {
|
|
p += snprintf(p, p < end ? end - p : 0,
|
|
", bytes %lld..%lld",
|
|
s->afile.startpos,
|
|
s->afile.endpos);
|
|
}
|
|
p += snprintf(p, p < end ? end - p : 0, ", vol %d", s->vol);
|
|
}
|
|
return p - buf;
|
|
}
|
|
|
|
static void
|
|
slot_flush(struct slot *s)
|
|
{
|
|
int count, n;
|
|
unsigned char *data;
|
|
|
|
for (;;) {
|
|
data = abuf_rgetblk(&s->buf, &count);
|
|
if (count == 0)
|
|
break;
|
|
n = afile_write(&s->afile, data, count);
|
|
if (n == 0) {
|
|
logx(1, "%s: can't write, disabled", s->afile.path);
|
|
s->pstate = SLOT_INIT;
|
|
return;
|
|
}
|
|
abuf_rdiscard(&s->buf, n);
|
|
}
|
|
}
|
|
|
|
static void
|
|
slot_fill(struct slot *s)
|
|
{
|
|
int count, n;
|
|
unsigned char *data;
|
|
|
|
for (;;) {
|
|
data = abuf_wgetblk(&s->buf, &count);
|
|
if (count == 0)
|
|
break;
|
|
n = afile_read(&s->afile, data, count);
|
|
if (n == 0) {
|
|
#ifdef DEBUG
|
|
logx(3, "%s: eof reached, stopping", s->afile.path);
|
|
#endif
|
|
s->pstate = SLOT_STOP;
|
|
break;
|
|
}
|
|
abuf_wcommit(&s->buf, n);
|
|
}
|
|
}
|
|
|
|
static int
|
|
slot_new(char *path, int mode, struct aparams *par, int hdr,
|
|
int imin, int imax, int omin, int omax, int nch,
|
|
int rate, int dup, int vol, long long pos)
|
|
{
|
|
struct slot *s, **ps;
|
|
char str[64];
|
|
|
|
s = xmalloc(sizeof(struct slot));
|
|
if (!afile_open(&s->afile, path, hdr,
|
|
mode == SIO_PLAY ? AFILE_FREAD : AFILE_FWRITE,
|
|
par, rate, nch)) {
|
|
xfree(s);
|
|
return 0;
|
|
}
|
|
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;
|
|
s->pstate = SLOT_CFG;
|
|
s->pos = pos;
|
|
|
|
logx(2, "%s: %s", s->afile.path, (slot_fmt(str, sizeof(str), s), str));
|
|
|
|
for (ps = &slot_list; *ps != NULL; ps = &(*ps)->next)
|
|
;
|
|
s->next = NULL;
|
|
*ps = s;
|
|
return 1;
|
|
}
|
|
|
|
static void
|
|
slot_init(struct slot *s)
|
|
{
|
|
unsigned int inch, onch, bufsz;
|
|
|
|
#ifdef DEBUG
|
|
if (s->pstate != SLOT_CFG) {
|
|
logx(1, "%s: slot_init: wrong state", s->afile.path);
|
|
panic();
|
|
}
|
|
#endif
|
|
s->bpf = s->afile.par.bps * s->afile.nch;
|
|
s->round = ((long long)dev_round * s->afile.rate +
|
|
dev_rate - 1) / dev_rate;
|
|
|
|
bufsz = s->round * (dev_bufsz / dev_round);
|
|
bufsz -= bufsz % s->round;
|
|
if (bufsz == 0)
|
|
bufsz = s->round;
|
|
abuf_init(&s->buf, bufsz * s->bpf);
|
|
#ifdef DEBUG
|
|
logx(3, "%s: allocated %u frame buffer", s->afile.path, bufsz);
|
|
#endif
|
|
|
|
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) {
|
|
cmap_init(&s->cmap,
|
|
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, s->afile.nch);
|
|
s->convbuf = allocbuf(s->round, s->afile.nch, sizeof(adata_t));
|
|
}
|
|
if (s->afile.rate != dev_rate) {
|
|
resamp_init(&s->resamp, s->afile.rate, dev_rate,
|
|
s->afile.nch);
|
|
s->resampbuf = allocbuf(dev_round, s->afile.nch, sizeof(adata_t));
|
|
}
|
|
}
|
|
if (s->mode & SIO_REC) {
|
|
cmap_init(&s->cmap,
|
|
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,
|
|
s->afile.nch);
|
|
s->resampbuf = allocbuf(dev_round, s->afile.nch, sizeof(adata_t));
|
|
}
|
|
if (!aparams_native(&s->afile.par)) {
|
|
enc_init(&s->conv, &s->afile.par, s->afile.nch);
|
|
s->convbuf = allocbuf(s->round, s->afile.nch, sizeof(adata_t));
|
|
}
|
|
|
|
/*
|
|
* cmap_copy() doesn't write samples in all channels,
|
|
* for instance when mono->stereo conversion is
|
|
* disabled. So we have to prefill cmap_copy() output
|
|
* with silence.
|
|
*/
|
|
if (s->resampbuf) {
|
|
memset(s->resampbuf, 0,
|
|
dev_round * s->afile.nch * sizeof(adata_t));
|
|
} else if (s->convbuf) {
|
|
memset(s->convbuf, 0,
|
|
s->round * s->afile.nch * sizeof(adata_t));
|
|
} else {
|
|
memset(s->buf.data, 0,
|
|
bufsz * s->afile.nch * sizeof(adata_t));
|
|
}
|
|
}
|
|
s->pstate = SLOT_INIT;
|
|
#ifdef DEBUG
|
|
logx(3, "%s: chain initialized", s->afile.path);
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
slot_start(struct slot *s, long long pos)
|
|
{
|
|
#ifdef DEBUG
|
|
if (s->pstate != SLOT_INIT) {
|
|
logx(1, "%s: slot_start: wrong state", s->afile.path);
|
|
panic();
|
|
}
|
|
#endif
|
|
pos -= s->pos;
|
|
if (pos < 0) {
|
|
s->skip = -pos;
|
|
pos = 0;
|
|
} else
|
|
s->skip = 0;
|
|
|
|
/*
|
|
* convert pos to slot sample rate
|
|
*
|
|
* At this stage, we could adjust s->resamp.diff to get
|
|
* sub-frame accuracy.
|
|
*/
|
|
pos = pos * s->afile.rate / dev_rate;
|
|
|
|
if (!afile_seek(&s->afile, pos * s->bpf)) {
|
|
s->pstate = SLOT_INIT;
|
|
return;
|
|
}
|
|
s->pstate = SLOT_RUN;
|
|
if (s->mode & SIO_PLAY)
|
|
slot_fill(s);
|
|
#ifdef DEBUG
|
|
logx(2, "%s: started", s->afile.path);
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
slot_stop(struct slot *s)
|
|
{
|
|
if (s->pstate == SLOT_INIT)
|
|
return;
|
|
if (s->mode & SIO_REC)
|
|
slot_flush(s);
|
|
if (s->mode & SIO_PLAY)
|
|
s->buf.used = s->buf.start = 0;
|
|
s->pstate = SLOT_INIT;
|
|
#ifdef DEBUG
|
|
logx(2, "%s: stopped", s->afile.path);
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
slot_del(struct slot *s)
|
|
{
|
|
struct slot **ps;
|
|
|
|
if (s->pstate != SLOT_CFG) {
|
|
slot_stop(s);
|
|
afile_close(&s->afile);
|
|
#ifdef DEBUG
|
|
logx(3, "%s: closed", s->afile.path);
|
|
#endif
|
|
abuf_done(&s->buf);
|
|
if (s->resampbuf)
|
|
xfree(s->resampbuf);
|
|
if (s->convbuf)
|
|
xfree(s->convbuf);
|
|
}
|
|
for (ps = &slot_list; *ps != s; ps = &(*ps)->next)
|
|
; /* nothing */
|
|
*ps = s->next;
|
|
xfree(s);
|
|
}
|
|
|
|
static void
|
|
slot_getcnt(struct slot *s, int *icnt, int *ocnt)
|
|
{
|
|
int cnt;
|
|
|
|
if (s->resampbuf)
|
|
resamp_getcnt(&s->resamp, icnt, ocnt);
|
|
else {
|
|
cnt = (*icnt < *ocnt) ? *icnt : *ocnt;
|
|
*icnt = cnt;
|
|
*ocnt = cnt;
|
|
}
|
|
}
|
|
|
|
static void
|
|
play_filt_resamp(struct slot *s, void *res_in, void *out, int icnt, int ocnt)
|
|
{
|
|
int i, offs, vol, inch, onch;
|
|
void *in;
|
|
|
|
if (s->resampbuf) {
|
|
resamp_do(&s->resamp, res_in, s->resampbuf, icnt, ocnt);
|
|
in = s->resampbuf;
|
|
} else
|
|
in = res_in;
|
|
|
|
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 += 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 += inch;
|
|
if (offs + s->cmap.nch > dev_pchan)
|
|
break;
|
|
cmap_add(&s->cmap, in, (adata_t *)out + offs, vol, ocnt);
|
|
}
|
|
}
|
|
|
|
static void
|
|
play_filt_dec(struct slot *s, void *in, void *out, int icnt, int ocnt)
|
|
{
|
|
void *tmp;
|
|
|
|
tmp = s->convbuf;
|
|
if (tmp) {
|
|
switch (s->afile.fmt) {
|
|
case AFILE_FMT_PCM:
|
|
dec_do(&s->conv, in, tmp, icnt);
|
|
break;
|
|
case AFILE_FMT_ULAW:
|
|
dec_do_ulaw(&s->conv, in, tmp, icnt, 0);
|
|
break;
|
|
case AFILE_FMT_ALAW:
|
|
dec_do_ulaw(&s->conv, in, tmp, icnt, 1);
|
|
break;
|
|
case AFILE_FMT_FLOAT:
|
|
dec_do_float(&s->conv, in, tmp, icnt);
|
|
break;
|
|
}
|
|
} else
|
|
tmp = in;
|
|
play_filt_resamp(s, tmp, out, icnt, ocnt);
|
|
}
|
|
|
|
/*
|
|
* Mix as many as possible frames (but not more than a block) from the
|
|
* slot buffer to the given location. Return the number of frames mixed
|
|
* in the output buffer
|
|
*/
|
|
static int
|
|
slot_mix_badd(struct slot *s, adata_t *odata)
|
|
{
|
|
adata_t *idata;
|
|
int len, icnt, ocnt, otodo, odone;
|
|
|
|
odone = 0;
|
|
otodo = dev_round;
|
|
if (s->skip > 0) {
|
|
ocnt = otodo;
|
|
if (ocnt > s->skip)
|
|
ocnt = s->skip;
|
|
s->skip -= ocnt;
|
|
odata += dev_pchan * ocnt;
|
|
otodo -= ocnt;
|
|
odone += ocnt;
|
|
}
|
|
while (otodo > 0) {
|
|
idata = (adata_t *)abuf_rgetblk(&s->buf, &len);
|
|
icnt = len / s->bpf;
|
|
if (icnt > s->round)
|
|
icnt = s->round;
|
|
ocnt = otodo;
|
|
slot_getcnt(s, &icnt, &ocnt);
|
|
if (icnt == 0)
|
|
break;
|
|
play_filt_dec(s, idata, odata, icnt, ocnt);
|
|
abuf_rdiscard(&s->buf, icnt * s->bpf);
|
|
otodo -= ocnt;
|
|
odone += ocnt;
|
|
odata += ocnt * dev_pchan;
|
|
}
|
|
return odone;
|
|
}
|
|
|
|
static void
|
|
rec_filt_resamp(struct slot *s, void *in, void *res_out, int icnt, int ocnt)
|
|
{
|
|
int i, vol, offs, inch, onch;
|
|
void *out = res_out;
|
|
|
|
out = (s->resampbuf) ? s->resampbuf : res_out;
|
|
|
|
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 += 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 += inch;
|
|
if (offs + s->cmap.nch > s->afile.nch)
|
|
break;
|
|
cmap_copy(&s->cmap, in, (adata_t *)out + offs, vol, icnt);
|
|
}
|
|
if (s->resampbuf)
|
|
resamp_do(&s->resamp, s->resampbuf, res_out, icnt, ocnt);
|
|
else
|
|
ocnt = icnt;
|
|
}
|
|
|
|
static void
|
|
rec_filt_enc(struct slot *s, void *in, void *out, int icnt, int ocnt)
|
|
{
|
|
void *tmp;
|
|
|
|
tmp = s->convbuf;
|
|
rec_filt_resamp(s, in, tmp ? tmp : out, icnt, ocnt);
|
|
if (tmp)
|
|
enc_do(&s->conv, tmp, out, ocnt);
|
|
}
|
|
|
|
/*
|
|
* Copy "todo" frames from the given buffer to the slot buffer,
|
|
* but not more than a block.
|
|
*/
|
|
static void
|
|
slot_sub_bcopy(struct slot *s, adata_t *idata, int itodo)
|
|
{
|
|
adata_t *odata;
|
|
int len, icnt, ocnt;
|
|
|
|
if (s->skip > 0) {
|
|
icnt = itodo;
|
|
if (icnt > s->skip)
|
|
icnt = s->skip;
|
|
s->skip -= icnt;
|
|
idata += dev_rchan * icnt;
|
|
itodo -= icnt;
|
|
}
|
|
|
|
while (itodo > 0) {
|
|
odata = (adata_t *)abuf_wgetblk(&s->buf, &len);
|
|
ocnt = len / s->bpf;
|
|
if (ocnt > s->round)
|
|
ocnt = s->round;
|
|
icnt = itodo;
|
|
slot_getcnt(s, &icnt, &ocnt);
|
|
if (ocnt == 0)
|
|
break;
|
|
rec_filt_enc(s, idata, odata, icnt, ocnt);
|
|
abuf_wcommit(&s->buf, ocnt * s->bpf);
|
|
itodo -= icnt;
|
|
idata += icnt * dev_rchan;
|
|
}
|
|
}
|
|
|
|
static int
|
|
dev_open(char *dev, int mode, int bufsz, char *port)
|
|
{
|
|
int rate, pmax, rmax;
|
|
struct sio_par par;
|
|
char enc_str[ENCMAX], chans_str[64];
|
|
struct slot *s;
|
|
|
|
if (port) {
|
|
dev_port = port;
|
|
dev_mh = mio_open(dev_port, MIO_IN, 0);
|
|
if (dev_mh == NULL) {
|
|
logx(1, "%s: couldn't open midi port", port);
|
|
return 0;
|
|
}
|
|
} else
|
|
dev_mh = NULL;
|
|
|
|
dev_name = dev;
|
|
dev_sh = sio_open(dev, mode, 0);
|
|
if (dev_sh == NULL) {
|
|
logx(1, "%s: couldn't open audio device", dev_name);
|
|
return 0;
|
|
}
|
|
|
|
rate = pmax = rmax = 0;
|
|
for (s = slot_list; s != NULL; s = s->next) {
|
|
if (s->afile.rate > rate)
|
|
rate = s->afile.rate;
|
|
if (s->mode == SIO_PLAY) {
|
|
if (s->omax > pmax)
|
|
pmax = s->omax;
|
|
}
|
|
if (s->mode == SIO_REC) {
|
|
if (s->imax > rmax)
|
|
rmax = s->imax;
|
|
}
|
|
}
|
|
sio_initpar(&par);
|
|
par.bits = ADATA_BITS;
|
|
par.bps = sizeof(adata_t);
|
|
par.msb = 0;
|
|
par.le = SIO_LE_NATIVE;
|
|
par.rate = rate;
|
|
if (mode & SIO_PLAY)
|
|
par.pchan = pmax + 1;
|
|
if (mode & SIO_REC)
|
|
par.rchan = rmax + 1;
|
|
par.appbufsz = bufsz > 0 ? bufsz : rate * DEFAULT_BUFSZ_MS / 1000;
|
|
if (!sio_setpar(dev_sh, &par) || !sio_getpar(dev_sh, &par)) {
|
|
logx(1, "%s: couldn't set audio params", dev_name);
|
|
return 0;
|
|
}
|
|
dev_par.bits = par.bits;
|
|
dev_par.bps = par.bps;
|
|
dev_par.sig = par.sig;
|
|
dev_par.le = par.le;
|
|
dev_par.msb = par.msb;
|
|
dev_mode = mode;
|
|
dev_rate = par.rate;
|
|
dev_bufsz = par.bufsz;
|
|
dev_round = par.round;
|
|
if (mode & SIO_PLAY) {
|
|
dev_pchan = par.pchan;
|
|
dev_pbuf = allocbuf(dev_round, dev_pchan, sizeof(adata_t));
|
|
}
|
|
if (mode & SIO_REC) {
|
|
dev_rchan = par.rchan;
|
|
dev_rbuf = allocbuf(dev_round, dev_rchan, sizeof(adata_t));
|
|
}
|
|
if (!aparams_native(&dev_par)) {
|
|
if (mode & SIO_PLAY) {
|
|
dev_encbuf = allocbuf(dev_round, dev_pchan, dev_par.bps);
|
|
enc_init(&dev_enc, &dev_par, dev_pchan);
|
|
}
|
|
if (mode & SIO_REC) {
|
|
dev_decbuf = allocbuf(dev_round, dev_rchan, dev_par.bps);
|
|
dec_init(&dev_dec, &dev_par, dev_rchan);
|
|
}
|
|
}
|
|
dev_pstate = DEV_STOP;
|
|
logx(2, "%s: %uHz, %s, %s, %u blocks of %u frames", dev_name, dev_rate,
|
|
(aparams_enctostr(&dev_par, enc_str), enc_str),
|
|
(chans_fmt(chans_str, sizeof(chans_str),
|
|
dev_mode, 0, dev_pchan - 1, 0, dev_rchan - 1), chans_str),
|
|
dev_bufsz / dev_round, dev_round);
|
|
return 1;
|
|
}
|
|
|
|
static void
|
|
dev_close(void)
|
|
{
|
|
sio_close(dev_sh);
|
|
if (dev_mh)
|
|
mio_close(dev_mh);
|
|
if (dev_mode & SIO_PLAY)
|
|
xfree(dev_pbuf);
|
|
if (dev_mode & SIO_REC)
|
|
xfree(dev_rbuf);
|
|
}
|
|
|
|
static void
|
|
dev_master(int val)
|
|
{
|
|
struct slot *s;
|
|
int mastervol, slotvol;
|
|
|
|
mastervol = MIDI_TO_ADATA(dev_volctl);
|
|
for (s = slot_list; s != NULL; s = s->next) {
|
|
slotvol = MIDI_TO_ADATA(val);
|
|
s->vol = ADATA_MUL(mastervol, slotvol);
|
|
}
|
|
#ifdef DEBUG
|
|
logx(3, "master volume set to %u", val);
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
dev_slotvol(int midich, int val)
|
|
{
|
|
struct slot *s;
|
|
int mastervol, slotvol;
|
|
|
|
for (s = slot_list; s != NULL; s = s->next) {
|
|
if (midich == 0) {
|
|
mastervol = MIDI_TO_ADATA(dev_volctl);
|
|
slotvol = MIDI_TO_ADATA(val);
|
|
s->vol = ADATA_MUL(mastervol, slotvol);
|
|
#ifdef DEBUG
|
|
logx(3, "%s: volume set to %u", s->afile.path, val);
|
|
#endif
|
|
break;
|
|
}
|
|
midich--;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* start all slots simultaneously
|
|
*/
|
|
static void
|
|
dev_mmcstart(void)
|
|
{
|
|
struct slot *s;
|
|
|
|
if (dev_pstate == DEV_STOP) {
|
|
dev_pstate = DEV_START;
|
|
for (s = slot_list; s != NULL; s = s->next)
|
|
slot_start(s, dev_pos);
|
|
dev_prime = (dev_mode & SIO_PLAY) ? dev_bufsz / dev_round : 0;
|
|
sio_start(dev_sh);
|
|
logx(2, "started");
|
|
} else {
|
|
#ifdef DEBUG
|
|
logx(3, "ignoring mmc start");
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/*
|
|
* stop all slots simultaneously
|
|
*/
|
|
static void
|
|
dev_mmcstop(void)
|
|
{
|
|
struct slot *s;
|
|
|
|
if (dev_pstate == DEV_START) {
|
|
dev_pstate = DEV_STOP;
|
|
for (s = slot_list; s != NULL; s = s->next)
|
|
slot_stop(s);
|
|
sio_stop(dev_sh);
|
|
logx(2, "stopped");
|
|
} else {
|
|
#ifdef DEBUG
|
|
logx(3, "ignored mmc stop\n");
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/*
|
|
* relocate all slots simultaneously
|
|
*/
|
|
static void
|
|
dev_mmcloc(int hr, int min, int sec, int fr, int cent, int fps)
|
|
{
|
|
long long pos;
|
|
|
|
pos = (long long)dev_rate * hr * 3600 +
|
|
(long long)dev_rate * min * 60 +
|
|
(long long)dev_rate * sec +
|
|
(long long)dev_rate * fr / fps +
|
|
(long long)dev_rate * cent / (100 * fps);
|
|
if (dev_pos == pos)
|
|
return;
|
|
dev_pos = pos;
|
|
logx(2, "relocated to %u:%u:%u.%u.%u at %u fps", hr, min, sec, fr, cent, fps);
|
|
if (dev_pstate == DEV_START) {
|
|
dev_mmcstop();
|
|
dev_mmcstart();
|
|
}
|
|
}
|
|
|
|
static void
|
|
dev_imsg(unsigned char *msg, unsigned int len)
|
|
{
|
|
struct sysex *x;
|
|
unsigned int fps, chan;
|
|
|
|
if ((msg[0] & MIDI_CMDMASK) == MIDI_CTL && msg[1] == MIDI_CTL_VOL) {
|
|
chan = msg[0] & MIDI_CHANMASK;
|
|
dev_slotvol(chan, msg[2]);
|
|
return;
|
|
}
|
|
x = (struct sysex *)msg;
|
|
if (x->start != SYSEX_START)
|
|
return;
|
|
if (len < SYSEX_SIZE(empty))
|
|
return;
|
|
if (x->type != SYSEX_TYPE_RT)
|
|
return;
|
|
if (x->id0 == SYSEX_CONTROL && x->id1 == SYSEX_MASTER) {
|
|
if (len == SYSEX_SIZE(master))
|
|
dev_master(x->u.master.coarse);
|
|
return;
|
|
}
|
|
if (x->id0 != SYSEX_MMC)
|
|
return;
|
|
switch (x->id1) {
|
|
case SYSEX_MMC_STOP:
|
|
if (len != SYSEX_SIZE(stop))
|
|
return;
|
|
dev_mmcstop();
|
|
break;
|
|
case SYSEX_MMC_START:
|
|
if (len != SYSEX_SIZE(start))
|
|
return;
|
|
dev_mmcstart();
|
|
break;
|
|
case SYSEX_MMC_LOC:
|
|
if (len != SYSEX_SIZE(loc) ||
|
|
x->u.loc.len != SYSEX_MMC_LOC_LEN ||
|
|
x->u.loc.cmd != SYSEX_MMC_LOC_CMD)
|
|
return;
|
|
switch (x->u.loc.hr >> 5) {
|
|
case MTC_FPS_24:
|
|
fps = 24;
|
|
break;
|
|
case MTC_FPS_25:
|
|
fps = 25;
|
|
break;
|
|
case MTC_FPS_30:
|
|
fps = 30;
|
|
break;
|
|
default:
|
|
dev_mmcstop();
|
|
return;
|
|
}
|
|
dev_mmcloc(x->u.loc.hr & 0x1f,
|
|
x->u.loc.min,
|
|
x->u.loc.sec,
|
|
x->u.loc.fr,
|
|
x->u.loc.cent,
|
|
fps);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* parse the given data chunk and call imsg() for each message
|
|
*/
|
|
static void
|
|
midi_in(unsigned char *idata, int icount)
|
|
{
|
|
int i;
|
|
unsigned char c;
|
|
|
|
for (i = 0; i < icount; i++) {
|
|
c = *idata++;
|
|
if (c >= 0xf8) {
|
|
/* we don't use real-time events */
|
|
} else if (c == SYSEX_END) {
|
|
if (dev_mst == SYSEX_START) {
|
|
dev_msg[dev_midx++] = c;
|
|
dev_imsg(dev_msg, dev_midx);
|
|
}
|
|
dev_mst = 0;
|
|
dev_midx = 0;
|
|
} else if (c >= 0xf0) {
|
|
dev_msg[0] = c;
|
|
dev_mlen = common_len[c & 7];
|
|
dev_mst = c;
|
|
dev_midx = 1;
|
|
} else if (c >= 0x80) {
|
|
dev_msg[0] = c;
|
|
dev_mlen = voice_len[(c >> 4) & 7];
|
|
dev_mst = c;
|
|
dev_midx = 1;
|
|
} else if (dev_mst) {
|
|
if (dev_midx == 0 && dev_mst != SYSEX_START)
|
|
dev_msg[dev_midx++] = dev_mst;
|
|
dev_msg[dev_midx++] = c;
|
|
if (dev_midx == dev_mlen) {
|
|
dev_imsg(dev_msg, dev_midx);
|
|
if (dev_mst >= 0xf0)
|
|
dev_mst = 0;
|
|
dev_midx = 0;
|
|
} else if (dev_midx == MIDI_MSGMAX) {
|
|
/* sysex too long */
|
|
dev_mst = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static int
|
|
slot_list_mix(unsigned int round, unsigned int pchan, adata_t *pbuf)
|
|
{
|
|
unsigned int done, n;
|
|
struct slot *s;
|
|
|
|
memset(pbuf, 0, pchan * round * sizeof(adata_t));
|
|
done = 0;
|
|
for (s = slot_list; s != NULL; s = s->next) {
|
|
if (s->pstate == SLOT_INIT || !(s->mode & SIO_PLAY))
|
|
continue;
|
|
if (s->pstate == SLOT_STOP && s->buf.used < s->bpf) {
|
|
#ifdef DEBUG
|
|
logx(3, "%s: drained, done", s->afile.path);
|
|
#endif
|
|
slot_stop(s);
|
|
continue;
|
|
}
|
|
n = slot_mix_badd(s, dev_pbuf);
|
|
if (n > done)
|
|
done = n;
|
|
}
|
|
return done;
|
|
}
|
|
|
|
static int
|
|
slot_list_copy(unsigned int count, unsigned int rchan, adata_t *rbuf)
|
|
{
|
|
unsigned int done;
|
|
struct slot *s;
|
|
|
|
done = 0;
|
|
for (s = slot_list; s != NULL; s = s->next) {
|
|
if (s->pstate == SLOT_INIT || !(s->mode & SIO_REC))
|
|
continue;
|
|
slot_sub_bcopy(s, rbuf, count);
|
|
done = count;
|
|
}
|
|
return done;
|
|
}
|
|
|
|
static void
|
|
slot_list_iodo(void)
|
|
{
|
|
struct slot *s;
|
|
|
|
for (s = slot_list; s != NULL; s = s->next) {
|
|
if (s->pstate != SLOT_RUN)
|
|
continue;
|
|
if ((s->mode & SIO_PLAY) &&
|
|
(s->buf.used < s->round * s->bpf))
|
|
slot_fill(s);
|
|
if ((s->mode & SIO_REC) &&
|
|
(s->buf.len - s->buf.used < s->round * s->bpf))
|
|
slot_flush(s);
|
|
}
|
|
}
|
|
|
|
static int
|
|
offline(void)
|
|
{
|
|
unsigned int todo;
|
|
int rate, cmax;
|
|
struct slot *s;
|
|
|
|
if (pledge("stdio", NULL) == -1)
|
|
err(1, "pledge");
|
|
|
|
rate = cmax = 0;
|
|
for (s = slot_list; s != NULL; s = s->next) {
|
|
if (s->afile.rate > rate)
|
|
rate = s->afile.rate;
|
|
if (s->imax > cmax)
|
|
cmax = s->imax;
|
|
if (s->omax > cmax)
|
|
cmax = s->omax;
|
|
}
|
|
dev_sh = NULL;
|
|
dev_name = "offline";
|
|
dev_mode = SIO_PLAY | SIO_REC;
|
|
dev_rate = rate;
|
|
dev_bufsz = rate;
|
|
dev_round = rate;
|
|
dev_pchan = dev_rchan = cmax + 1;
|
|
dev_pbuf = dev_rbuf = allocbuf(dev_round, dev_pchan, sizeof(adata_t));
|
|
dev_pstate = DEV_STOP;
|
|
for (s = slot_list; s != NULL; s = s->next)
|
|
slot_init(s);
|
|
for (s = slot_list; s != NULL; s = s->next)
|
|
slot_start(s, 0);
|
|
for (;;) {
|
|
todo = slot_list_mix(dev_round, dev_pchan, dev_pbuf);
|
|
if (todo == 0)
|
|
break;
|
|
slot_list_copy(todo, dev_pchan, dev_pbuf);
|
|
slot_list_iodo();
|
|
}
|
|
xfree(dev_pbuf);
|
|
while (slot_list)
|
|
slot_del(slot_list);
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
playrec_cycle(void)
|
|
{
|
|
unsigned int n, todo;
|
|
unsigned char *p;
|
|
int pcnt, rcnt;
|
|
|
|
#ifdef DEBUG
|
|
logx(4, "%s: cycle, prime = %u", dev_name, dev_prime);
|
|
#endif
|
|
pcnt = rcnt = 0;
|
|
if (dev_mode & SIO_REC) {
|
|
if (dev_prime > 0)
|
|
dev_prime--;
|
|
else {
|
|
todo = dev_round * dev_rchan * dev_par.bps;
|
|
p = dev_decbuf ? dev_decbuf : (unsigned char *)dev_rbuf;
|
|
while (todo > 0) {
|
|
n = sio_read(dev_sh, p, todo);
|
|
if (n == 0) {
|
|
logx(1, "%s: failed to read", dev_name);
|
|
return 0;
|
|
}
|
|
p += n;
|
|
todo -= n;
|
|
}
|
|
rcnt = slot_list_copy(dev_round, dev_rchan, dev_rbuf);
|
|
if (dev_decbuf) {
|
|
dec_do(&dev_dec,
|
|
dev_decbuf, (unsigned char *)dev_rbuf,
|
|
dev_round);
|
|
}
|
|
}
|
|
}
|
|
if (dev_mode & SIO_PLAY) {
|
|
pcnt = slot_list_mix(dev_round, dev_pchan, dev_pbuf);
|
|
todo = dev_par.bps * dev_pchan * dev_round;
|
|
if (dev_encbuf) {
|
|
enc_do(&dev_enc,
|
|
(unsigned char *)dev_pbuf, dev_encbuf,
|
|
dev_round);
|
|
p = dev_encbuf;
|
|
} else
|
|
p = (unsigned char *)dev_pbuf;
|
|
n = sio_write(dev_sh, p, todo);
|
|
if (n == 0) {
|
|
logx(1, "%s: failed to write to device", dev_name);
|
|
return 0;
|
|
}
|
|
}
|
|
slot_list_iodo();
|
|
return pcnt > 0 || rcnt > 0;
|
|
}
|
|
|
|
static void
|
|
sigint(int s)
|
|
{
|
|
if (quit_flag)
|
|
_exit(1);
|
|
quit_flag = 1;
|
|
}
|
|
|
|
static int
|
|
playrec(char *dev, int mode, int bufsz, char *port)
|
|
{
|
|
#define MIDIBUFSZ 0x100
|
|
unsigned char mbuf[MIDIBUFSZ];
|
|
struct sigaction sa;
|
|
struct pollfd *pfds;
|
|
struct slot *s;
|
|
int n, ns, nm, ev;
|
|
|
|
if (!dev_open(dev, mode, bufsz, port))
|
|
return 0;
|
|
if (pledge("stdio audio", NULL) == -1)
|
|
err(1, "pledge");
|
|
|
|
n = sio_nfds(dev_sh);
|
|
if (dev_mh)
|
|
n += mio_nfds(dev_mh);
|
|
pfds = reallocarray(NULL, n, sizeof(struct pollfd));
|
|
if (pfds == NULL)
|
|
err(1, "malloc");
|
|
|
|
for (s = slot_list; s != NULL; s = s->next)
|
|
slot_init(s);
|
|
if (dev_mh == NULL)
|
|
dev_mmcstart();
|
|
else
|
|
logx(2, "ready, waiting for mmc messages");
|
|
|
|
quit_flag = 0;
|
|
sigfillset(&sa.sa_mask);
|
|
sa.sa_flags = SA_RESTART;
|
|
sa.sa_handler = sigint;
|
|
sigaction(SIGINT, &sa, NULL);
|
|
sigaction(SIGTERM, &sa, NULL);
|
|
sigaction(SIGHUP, &sa, NULL);
|
|
while (!quit_flag) {
|
|
if (dev_pstate == DEV_START) {
|
|
ev = 0;
|
|
if (mode & SIO_PLAY)
|
|
ev |= POLLOUT;
|
|
if (mode & SIO_REC)
|
|
ev |= POLLIN;
|
|
ns = sio_pollfd(dev_sh, pfds, ev);
|
|
} else
|
|
ns = 0;
|
|
if (dev_mh)
|
|
nm = mio_pollfd(dev_mh, pfds + ns, POLLIN);
|
|
else
|
|
nm = 0;
|
|
if (poll(pfds, ns + nm, -1) == -1) {
|
|
if (errno == EINTR)
|
|
continue;
|
|
logx(1, "poll failed");
|
|
panic();
|
|
}
|
|
if (dev_pstate == DEV_START) {
|
|
ev = sio_revents(dev_sh, pfds);
|
|
if (ev & POLLHUP) {
|
|
logx(1, "%s: audio device gone, stopping", dev);
|
|
break;
|
|
}
|
|
if (ev & (POLLIN | POLLOUT)) {
|
|
if (!playrec_cycle() && dev_mh == NULL)
|
|
break;
|
|
}
|
|
}
|
|
if (dev_mh) {
|
|
ev = mio_revents(dev_mh, pfds + ns);
|
|
if (ev & POLLHUP) {
|
|
logx(1, "%s: midi port gone, stopping", dev_port);
|
|
break;
|
|
}
|
|
if (ev & POLLIN) {
|
|
n = mio_read(dev_mh, mbuf, MIDIBUFSZ);
|
|
midi_in(mbuf, n);
|
|
}
|
|
}
|
|
}
|
|
sigfillset(&sa.sa_mask);
|
|
sa.sa_flags = SA_RESTART;
|
|
sa.sa_handler = SIG_DFL;
|
|
sigaction(SIGINT, &sa, NULL);
|
|
sigaction(SIGTERM, &sa, NULL);
|
|
sigaction(SIGHUP, &sa, NULL);
|
|
|
|
if (dev_pstate == DEV_START)
|
|
dev_mmcstop();
|
|
xfree(pfds);
|
|
dev_close();
|
|
while (slot_list)
|
|
slot_del(slot_list);
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
opt_onoff(char *s, int *flag)
|
|
{
|
|
if (strcmp("off", s) == 0) {
|
|
*flag = 0;
|
|
return 1;
|
|
}
|
|
if (strcmp("on", s) == 0) {
|
|
*flag = 1;
|
|
return 1;
|
|
}
|
|
logx(1, "%s: on/off expected", s);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
opt_enc(char *s, struct aparams *par)
|
|
{
|
|
int len;
|
|
|
|
len = aparams_strtoenc(par, s);
|
|
if (len == 0 || s[len] != '\0') {
|
|
logx(1, "%s: bad encoding", s);
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
opt_hdr(char *s, int *hdr)
|
|
{
|
|
if (strcmp("auto", s) == 0) {
|
|
*hdr = AFILE_HDR_AUTO;
|
|
return 1;
|
|
}
|
|
if (strcmp("raw", s) == 0) {
|
|
*hdr = AFILE_HDR_RAW;
|
|
return 1;
|
|
}
|
|
if (strcmp("wav", s) == 0) {
|
|
*hdr = AFILE_HDR_WAV;
|
|
return 1;
|
|
}
|
|
if (strcmp("aiff", s) == 0) {
|
|
*hdr = AFILE_HDR_AIFF;
|
|
return 1;
|
|
}
|
|
if (strcmp("au", s) == 0) {
|
|
*hdr = AFILE_HDR_AU;
|
|
return 1;
|
|
}
|
|
logx(1, "%s: bad header type", s);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
opt_map(char *str, int *rimin, int *rimax, int *romin, int *romax)
|
|
{
|
|
char *s, *next;
|
|
long imin, imax, omin, omax;
|
|
|
|
errno = 0;
|
|
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;
|
|
s = next + 1;
|
|
omax = strtol(s, &next, 10);
|
|
if (next == s || *next != '\0')
|
|
goto failed;
|
|
if (imin < 0 || imax < imin || imax >= NCHAN_MAX)
|
|
goto failed;
|
|
if (omin < 0 || omax < omin || omax >= NCHAN_MAX)
|
|
goto failed;
|
|
*rimin = imin;
|
|
*rimax = imax;
|
|
*romin = omin;
|
|
*romax = omax;
|
|
return 1;
|
|
failed:
|
|
logx(1, "%s: channel mapping expected", str);
|
|
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:
|
|
logx(1, "%s: channel count expected", str);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
opt_num(char *s, int min, int max, int *num)
|
|
{
|
|
const char *errstr;
|
|
|
|
*num = strtonum(s, min, max, &errstr);
|
|
if (errstr) {
|
|
logx(1, "%s: expected integer between %d and %d", s, min, max);
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
opt_pos(char *s, long long *pos)
|
|
{
|
|
const char *errstr;
|
|
|
|
*pos = strtonum(s, 0, LLONG_MAX, &errstr);
|
|
if (errstr) {
|
|
logx(1, "%s: positive number of samples expected", s);
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
int dup, imin, imax, omin, omax, nch, off, rate, vol, bufsz, hdr, mode;
|
|
char *port, *dev;
|
|
struct aparams par;
|
|
int n_flag, c;
|
|
long long pos;
|
|
|
|
if (pledge("stdio rpath wpath cpath inet unix dns audio", NULL) == -1)
|
|
err(1, "pledge");
|
|
|
|
vol = 127;
|
|
dup = 0;
|
|
bufsz = 0;
|
|
nch = 2;
|
|
off = 0;
|
|
rate = DEFAULT_RATE;
|
|
imin = imax = omin = omax = -1;
|
|
par.bits = ADATA_BITS;
|
|
par.bps = APARAMS_BPS(par.bits);
|
|
par.le = ADATA_LE;
|
|
par.sig = 1;
|
|
par.msb = 1;
|
|
hdr = AFILE_HDR_AUTO;
|
|
n_flag = 0;
|
|
port = NULL;
|
|
dev = NULL;
|
|
mode = 0;
|
|
pos = 0;
|
|
|
|
while ((c = getopt(argc, argv,
|
|
"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_nch(optarg, &nch, &off))
|
|
return 1;
|
|
break;
|
|
case 'd':
|
|
log_level++;
|
|
break;
|
|
case 'e':
|
|
if (!opt_enc(optarg, &par))
|
|
return 1;
|
|
break;
|
|
case 'f':
|
|
dev = optarg;
|
|
break;
|
|
case 'g':
|
|
if (!opt_pos(optarg, &dev_pos))
|
|
return 1;
|
|
break;
|
|
case 'h':
|
|
if (!opt_hdr(optarg, &hdr))
|
|
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, 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, imin, imax, omin, omax,
|
|
nch, rate, dup, 0, pos))
|
|
return 1;
|
|
imin = imax = omin = omax = -1;
|
|
mode |= SIO_REC;
|
|
break;
|
|
case 'p':
|
|
if (!opt_pos(optarg, &pos))
|
|
return 1;
|
|
break;
|
|
case 'q':
|
|
port = optarg;
|
|
break;
|
|
case 'r':
|
|
if (!opt_num(optarg, RATE_MIN, RATE_MAX, &rate))
|
|
return 1;
|
|
break;
|
|
case 'v':
|
|
if (!opt_num(optarg, 0, MIDI_MAXCTL, &vol))
|
|
return 1;
|
|
break;
|
|
default:
|
|
goto bad_usage;
|
|
}
|
|
}
|
|
argc -= optind;
|
|
argv += optind;
|
|
if (argc != 0) {
|
|
bad_usage:
|
|
logx(1, "%s", usagestr);
|
|
return 1;
|
|
}
|
|
if (n_flag) {
|
|
if (dev != NULL || port != NULL) {
|
|
logx(1, "-f and -q make no sense in off-line mode");
|
|
return 1;
|
|
}
|
|
if (mode != (SIO_PLAY | SIO_REC)) {
|
|
logx(1, "both -i and -o required");
|
|
return 1;
|
|
}
|
|
if (!offline())
|
|
return 1;
|
|
} else {
|
|
if (dev == NULL)
|
|
dev = SIO_DEVANY;
|
|
if (mode == 0) {
|
|
logx(1, "at least -i or -o required");
|
|
return 1;
|
|
}
|
|
if (!playrec(dev, mode, bufsz, port))
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|