mirror of https://github.com/ericonr/sndio.git
299 lines
6.3 KiB
C
299 lines
6.3 KiB
C
/*
|
|
* Copyright (c) 2010-2011 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 <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sndio.h>
|
|
#include <unistd.h>
|
|
#include "sysex.h"
|
|
#include "bsd-compat.h"
|
|
|
|
#define MIDI_CMDMASK 0xf0 /* command mask */
|
|
#define MIDI_CHANMASK 0x0f /* channel mask */
|
|
#define MIDI_CTL 0xb0 /* controller command */
|
|
#define MIDI_CTLVOL 7 /* volume */
|
|
#define MIDI_NCHAN 16 /* max channels */
|
|
#define MSGMAX 0x100 /* buffer size */
|
|
|
|
void setvol(int, int);
|
|
void setmaster(int);
|
|
void onsysex(unsigned char *, int);
|
|
void oncommon(unsigned char *, int);
|
|
void oninput(unsigned char *, int);
|
|
void usage(void);
|
|
|
|
int verbose = 0;
|
|
int mst, midx, mlen, mready; /* midi parser state */
|
|
unsigned char mmsg[MSGMAX]; /* resulting midi message */
|
|
struct mio_hdl *hdl; /* handle to sndiod MIDI port */
|
|
|
|
struct ctl {
|
|
char name[SYSEX_NAMELEN]; /* stream name */
|
|
unsigned vol; /* current volume */
|
|
} ctls[MIDI_NCHAN];
|
|
int master = -1;
|
|
|
|
unsigned char dumpreq[] = {
|
|
SYSEX_START,
|
|
SYSEX_TYPE_EDU,
|
|
0,
|
|
SYSEX_AUCAT,
|
|
SYSEX_AUCAT_DUMPREQ,
|
|
SYSEX_END
|
|
};
|
|
|
|
void
|
|
setvol(int cn, int vol)
|
|
{
|
|
#define VOLMSGLEN 3
|
|
char msg[VOLMSGLEN];
|
|
|
|
msg[0] = MIDI_CTL | cn;
|
|
msg[1] = MIDI_CTLVOL;
|
|
msg[2] = vol;
|
|
if (mio_write(hdl, msg, VOLMSGLEN) != VOLMSGLEN) {
|
|
fprintf(stderr, "couldn't write message\n");
|
|
exit(1);
|
|
}
|
|
printf("%s -> %u\n", ctls[cn].name, vol);
|
|
}
|
|
|
|
void
|
|
setmaster(int vol)
|
|
{
|
|
struct sysex msg;
|
|
|
|
msg.start = SYSEX_START;
|
|
msg.type = SYSEX_TYPE_RT;
|
|
msg.id0 = SYSEX_CONTROL;
|
|
msg.id1 = SYSEX_MASTER;
|
|
msg.u.master.fine = 0;
|
|
msg.u.master.coarse = vol;
|
|
msg.u.master.end = SYSEX_END;
|
|
if (mio_write(hdl, &msg, SYSEX_SIZE(master)) == 0) {
|
|
fprintf(stderr, "couldn't write message\n");
|
|
exit(1);
|
|
}
|
|
printf("master -> %u\n", vol);
|
|
}
|
|
|
|
void
|
|
onsysex(unsigned char *buf, int len)
|
|
{
|
|
int cn, i;
|
|
struct sysex *x = (struct sysex *)buf;
|
|
|
|
if (verbose) {
|
|
fprintf(stderr, "sysex: ");
|
|
for (i = 0; i < len; i++)
|
|
fprintf(stderr, " %02x", buf[i]);
|
|
fprintf(stderr, ", len = %u/%zu\n", len, SYSEX_SIZE(slotdesc));
|
|
}
|
|
|
|
if (len < SYSEX_SIZE(empty))
|
|
return;
|
|
if (x->type == SYSEX_TYPE_RT &&
|
|
x->id0 == SYSEX_CONTROL &&
|
|
x->id1 == SYSEX_MASTER) {
|
|
if (len == SYSEX_SIZE(master))
|
|
master = x->u.master.coarse;
|
|
return;
|
|
}
|
|
if (x->type != SYSEX_TYPE_EDU ||
|
|
x->id0 != SYSEX_AUCAT)
|
|
return;
|
|
switch(x->id1) {
|
|
case SYSEX_AUCAT_SLOTDESC:
|
|
cn = x->u.slotdesc.chan;
|
|
if (cn >= MIDI_NCHAN) {
|
|
fprintf(stderr, "%u: invalid channel\n", cn);
|
|
exit(1);
|
|
}
|
|
if (memchr(x->u.slotdesc.name, '\0', SYSEX_NAMELEN) == NULL) {
|
|
fprintf(stderr, "%u: invalid channel name\n", cn);
|
|
exit(1);
|
|
}
|
|
memcpy(ctls[cn].name, x->u.slotdesc.name, SYSEX_NAMELEN);
|
|
ctls[cn].vol = 0;
|
|
break;
|
|
case SYSEX_AUCAT_DUMPEND:
|
|
mready = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
oncommon(unsigned char *buf, int len)
|
|
{
|
|
int cn, vol;
|
|
|
|
if ((buf[0] & MIDI_CMDMASK) != MIDI_CTL)
|
|
return;
|
|
if (buf[1] != MIDI_CTLVOL)
|
|
return;
|
|
cn = buf[0] & MIDI_CHANMASK;
|
|
vol = buf[2];
|
|
ctls[cn].vol = vol;
|
|
}
|
|
|
|
void
|
|
oninput(unsigned char *buf, int len)
|
|
{
|
|
static int voice_len[] = { 3, 3, 3, 3, 2, 2, 3 };
|
|
static int common_len[] = { 0, 2, 3, 2, 0, 0, 1, 1 };
|
|
int c;
|
|
|
|
for (; len > 0; len--) {
|
|
c = *buf;
|
|
buf++;
|
|
|
|
if (c >= 0xf8) {
|
|
/* clock events not used yet */
|
|
} else if (c >= 0xf0) {
|
|
if (mst == SYSEX_START &&
|
|
c == SYSEX_END &&
|
|
midx < MSGMAX) {
|
|
mmsg[midx++] = c;
|
|
|
|
onsysex(mmsg, midx);
|
|
continue;
|
|
}
|
|
mmsg[0] = c;
|
|
mlen = common_len[c & 7];
|
|
mst = c;
|
|
midx = 1;
|
|
} else if (c >= 0x80) {
|
|
mmsg[0] = c;
|
|
mlen = voice_len[(c >> 4) & 7];
|
|
mst = c;
|
|
midx = 1;
|
|
} else if (mst) {
|
|
if (midx == MSGMAX)
|
|
continue;
|
|
if (midx == 0)
|
|
mmsg[midx++] = mst;
|
|
mmsg[midx++] = c;
|
|
if (midx == mlen) {
|
|
oncommon(mmsg, midx);
|
|
midx = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
usage(void)
|
|
{
|
|
fprintf(stderr, "usage: sndioctl [-v] [-f port] [expr ...]\n");
|
|
exit(1);
|
|
}
|
|
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
char *dev = NULL;
|
|
unsigned char buf[MSGMAX];
|
|
char *lhs, *rhs;
|
|
int c, cn, vol, size;
|
|
|
|
while ((c = getopt(argc, argv, "f:v")) != -1) {
|
|
switch (c) {
|
|
case 'f':
|
|
dev = optarg;
|
|
break;
|
|
case 'v':
|
|
verbose++;
|
|
break;
|
|
default:
|
|
usage();
|
|
}
|
|
}
|
|
argc -= optind;
|
|
argv += optind;
|
|
|
|
if (dev == NULL)
|
|
dev = getenv("AUDIODEVICE");
|
|
if (dev == NULL)
|
|
dev = "snd/0";
|
|
|
|
hdl = mio_open(dev, MIO_OUT | MIO_IN, 0);
|
|
if (hdl == NULL) {
|
|
fprintf(stderr, "%s: couldn't open MIDI device\n", dev);
|
|
exit(1);
|
|
}
|
|
mio_write(hdl, dumpreq, sizeof(dumpreq));
|
|
while (!mready) {
|
|
size = mio_read(hdl, buf, MSGMAX);
|
|
if (size == 0) {
|
|
fprintf(stderr, "%s: read failed\n", dev);
|
|
exit(1);
|
|
}
|
|
oninput(buf, size);
|
|
}
|
|
if (argc == 0) {
|
|
for (cn = 0; cn < MIDI_NCHAN; cn++) {
|
|
if (*ctls[cn].name != '\0') {
|
|
printf("%s=%u\n",
|
|
ctls[cn].name,
|
|
ctls[cn].vol);
|
|
}
|
|
}
|
|
if (master >= 0)
|
|
printf("master=%u\n", master);
|
|
return 0;
|
|
}
|
|
for (; argc > 0; argc--, argv++) {
|
|
lhs = *argv;
|
|
rhs = strchr(*argv, '=');
|
|
if (rhs) {
|
|
*rhs++ = '\0';
|
|
if (sscanf(rhs, "%u", &vol) != 1) {
|
|
fprintf(stderr, "%s: not a number\n", lhs);
|
|
return 1;
|
|
}
|
|
if (vol > 127) {
|
|
fprintf(stderr, "%u: not in 0..127\n", vol);
|
|
return 1;
|
|
}
|
|
}
|
|
if (strlen(lhs) == 0) {
|
|
fprintf(stderr, "stream name expected\n");
|
|
return 1;
|
|
}
|
|
if (master >= 0 && strcmp(lhs, "master") == 0) {
|
|
if (rhs)
|
|
setmaster(vol);
|
|
else
|
|
printf("master=%u\n", master);
|
|
continue;
|
|
}
|
|
for (cn = 0; ; cn++) {
|
|
if (cn == MIDI_NCHAN) {
|
|
fprintf(stderr, "%s: no such stream\n", lhs);
|
|
return 1;
|
|
}
|
|
if (strcmp(lhs, ctls[cn].name) == 0)
|
|
break;
|
|
}
|
|
if (rhs)
|
|
setvol(cn, vol);
|
|
else
|
|
printf("%s=%u\n", ctls[cn].name, ctls[cn].vol);
|
|
}
|
|
mio_close(hdl);
|
|
return 0;
|
|
}
|