mirror of
https://github.com/alsa-project/alsa-utils
synced 2024-10-06 02:17:59 +02:00
Compare commits
58 commits
d2b75205b1
...
a02ab41e09
Author | SHA1 | Date | |
---|---|---|---|
|
a02ab41e09 | ||
|
fbde4f8c10 | ||
|
dbe0583874 | ||
|
3a8074ceb8 | ||
|
09a89f0139 | ||
|
5b511024e9 | ||
|
cbebb86a56 | ||
|
6e3fc0433e | ||
|
21e0adfa3b | ||
|
095b064af6 | ||
|
e26aa680aa | ||
|
0188f93f02 | ||
|
02b0c3af56 | ||
|
df736ad67a | ||
|
7de3cd3b8d | ||
|
580ea3c85e | ||
|
dec1ef064a | ||
|
df68ec3343 | ||
|
d480eac6f2 | ||
|
c95db638c0 | ||
|
5703d27773 | ||
|
39053b90d5 | ||
|
397c198955 | ||
|
946ea467cf | ||
|
c7b342db82 | ||
|
d26b66f881 | ||
|
cac4935ba2 | ||
|
4aae5a770f | ||
|
cdcfcddd95 | ||
|
176c94591c | ||
|
6a676f4a46 | ||
|
47931000fd | ||
|
33f6870f56 | ||
|
74daf3a93a | ||
|
274f1b9ebf | ||
|
f0df4b4cfe | ||
|
b1269eefdd | ||
|
99ce4b1d8a | ||
|
68491dd464 | ||
|
330741d523 | ||
|
2ee6c170a8 | ||
|
e609d66807 | ||
|
16533f81de | ||
|
1205dd5f6c | ||
|
7911d63370 | ||
|
b0f3126dbc | ||
|
2cdf5ebedb | ||
|
aa5e949403 | ||
|
ca82cf8ed6 | ||
|
7e7a53f0c9 | ||
|
be6356d847 | ||
|
506097ebb1 | ||
|
5db60d5e85 | ||
|
f417c1acfc | ||
|
cecc383aeb | ||
|
7547242d0f | ||
|
ba2bc072dc | ||
|
84d0a91f11 |
21 changed files with 2683 additions and 111 deletions
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
|
@ -109,7 +109,7 @@ jobs:
|
|||
cd alsa-utils-$(cat version)
|
||||
make install
|
||||
- name: Archive package
|
||||
uses: actions/upload-artifact@v1
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: alsa-utils-test-package
|
||||
path: artifacts/
|
||||
|
|
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -29,6 +29,8 @@ ABOUT-NLS
|
|||
*.o
|
||||
*~
|
||||
.deps
|
||||
.libs
|
||||
.dirstamp
|
||||
|
||||
alsactl/alsactl
|
||||
alsactl/alsactl_init.7
|
||||
|
@ -48,7 +50,10 @@ iecset/iecset
|
|||
seq/aconnect/aconnect
|
||||
seq/aplaymidi/aplaymidi
|
||||
seq/aplaymidi/arecordmidi
|
||||
seq/aplaymidi2/aplaymidi2
|
||||
seq/aplaymidi2/arecordmidi2
|
||||
seq/aseqdump/aseqdump
|
||||
seq/aseqsend/aseqsend
|
||||
seq/aseqnet/aseqnet
|
||||
speaker-test/speaker-test
|
||||
topology/alsatplg.1
|
||||
|
|
|
@ -79,6 +79,8 @@ static void card_free(struct card **card)
|
|||
{
|
||||
struct card *c = *card;
|
||||
|
||||
if (c == NULL)
|
||||
return;
|
||||
free_list(&c->blacklist);
|
||||
free_list(&c->whitelist);
|
||||
if (c->handle)
|
||||
|
|
|
@ -146,6 +146,8 @@ static int set_normalized_volume(snd_mixer_elem_t *elem,
|
|||
min_norm = pow(10, (min - max) / 6000.0);
|
||||
volume = volume * (1 - min_norm) + min_norm;
|
||||
}
|
||||
if (volume <= 0)
|
||||
volume = 1e-36;
|
||||
value = lrint_dir(6000.0 * log10(volume), dir) + max;
|
||||
return set_dB[ctl_dir](elem, channel, value, dir);
|
||||
}
|
||||
|
|
|
@ -1618,6 +1618,8 @@ static void do_pause(void)
|
|||
error(_("pause push error: %s"), snd_strerror(err));
|
||||
return;
|
||||
}
|
||||
fprintf(stderr, _("\r=== PAUSE === "));
|
||||
fflush(stderr);
|
||||
while (1) {
|
||||
b = wait_for_input();
|
||||
if (b == ' ' || b == '\r') {
|
||||
|
@ -1642,8 +1644,6 @@ static void check_stdin(void)
|
|||
while (read(fileno(stdin), &b, 1) == 1) {
|
||||
if (b == ' ' || b == '\r') {
|
||||
while (read(fileno(stdin), &b, 1) == 1);
|
||||
fprintf(stderr, _("\r=== PAUSE === "));
|
||||
fflush(stderr);
|
||||
do_pause();
|
||||
fprintf(stderr, " \r");
|
||||
fflush(stderr);
|
||||
|
|
19
configure.ac
19
configure.ac
|
@ -1,6 +1,6 @@
|
|||
dnl Process this file with autoconf to produce a configure script.
|
||||
AC_PREREQ(2.59)
|
||||
AC_INIT(alsa-utils, 1.2.11)
|
||||
AC_INIT(alsa-utils, 1.2.12)
|
||||
AC_CONFIG_SRCDIR([aplay/aplay.c])
|
||||
AC_PREFIX_DEFAULT(/usr)
|
||||
AM_INIT_AUTOMAKE([subdir-objects])
|
||||
|
@ -21,7 +21,7 @@ AC_PROG_SED
|
|||
AC_DISABLE_STATIC
|
||||
AM_PROG_LIBTOOL
|
||||
PKG_PROG_PKG_CONFIG
|
||||
AM_PATH_ALSA(1.2.5)
|
||||
AM_PATH_ALSA(1.2.13)
|
||||
if test "x$enable_alsatest" = "xyes"; then
|
||||
AC_CHECK_FUNC([snd_ctl_elem_add_enumerated],
|
||||
, [AC_ERROR([No user enum control support in alsa-lib])])
|
||||
|
@ -47,17 +47,6 @@ AC_CHECK_HEADERS([samplerate.h], [have_samplerate="yes"], [have_samplerate="no"]
|
|||
[#include <samplerate.h>])
|
||||
|
||||
AC_CHECK_LIB([asound], [snd_seq_client_info_get_card], [HAVE_SEQ_CLIENT_INFO_GET_CARD="yes"])
|
||||
if test "$HAVE_SEQ_CLIENT_INFO_GET_CARD" = "yes" ; then
|
||||
AC_DEFINE([HAVE_SEQ_CLIENT_INFO_GET_CARD], 1, [alsa-lib supports snd_seq_client_info_get_card])
|
||||
fi
|
||||
AC_CHECK_LIB([asound], [snd_seq_client_info_get_pid], [HAVE_SEQ_CLIENT_INFO_GET_PID="yes"])
|
||||
if test "$HAVE_SEQ_CLIENT_INFO_GET_PID" = "yes" ; then
|
||||
AC_DEFINE([HAVE_SEQ_CLIENT_INFO_GET_PID], 1, [alsa-lib supports snd_seq_client_info_get_pid])
|
||||
fi
|
||||
AC_CHECK_LIB([asound], [snd_seq_client_info_get_midi_version], [HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION="yes"])
|
||||
if test "$HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION" = "yes" -a "$have_rawmidi" = "yes"; then
|
||||
AC_DEFINE([HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION], 1, [alsa-lib supports snd_seq_client_info_get_midi_version])
|
||||
fi
|
||||
AC_CHECK_LIB([atopology], [snd_tplg_save], [have_topology="yes"], [have_topology="no"])
|
||||
|
||||
#
|
||||
|
@ -486,8 +475,8 @@ AC_OUTPUT(Makefile alsactl/Makefile alsactl/init/Makefile \
|
|||
bat/Makefile bat/tests/Makefile bat/tests/asound_state/Makefile \
|
||||
aplay/Makefile include/Makefile iecset/Makefile utils/Makefile \
|
||||
utils/alsa-utils.spec seq/Makefile seq/aconnect/Makefile \
|
||||
seq/aplaymidi/Makefile seq/aseqdump/Makefile seq/aseqnet/Makefile \
|
||||
speaker-test/Makefile speaker-test/samples/Makefile \
|
||||
seq/aplaymidi/Makefile seq/aplaymidi2/Makefile seq/aseqdump/Makefile seq/aseqnet/Makefile \
|
||||
seq/aseqsend/Makefile speaker-test/Makefile speaker-test/samples/Makefile \
|
||||
alsaloop/Makefile alsa-info/Makefile \
|
||||
axfer/Makefile axfer/test/Makefile \
|
||||
nhlt/Makefile)
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
#!/bin/bash
|
||||
|
||||
if test -d ../alsa-lib/utils && ! test -r `aclocal --print-ac-dir`/alsa.m4; then
|
||||
alsa_m4_flags="-I ../alsa-lib/utils"
|
||||
ACLOCAL_FLAGS="$ACLOCAL_FLAGS -I ../alsa-lib/utils"
|
||||
fi
|
||||
aclocal $alsa_m4_flags $ACLOCAL_FLAGS
|
||||
aclocal $ACLOCAL_FLAGS
|
||||
# save original files to avoid stupid modifications by gettextize
|
||||
cp Makefile.am Makefile.am.ok
|
||||
cp configure.ac configure.ac.ok
|
||||
|
|
|
@ -1 +1 @@
|
|||
SUBDIRS=aconnect aplaymidi aseqdump aseqnet
|
||||
SUBDIRS=aconnect aplaymidi aplaymidi2 aseqdump aseqnet aseqsend
|
||||
|
|
|
@ -29,12 +29,7 @@
|
|||
#include <alsa/asoundlib.h>
|
||||
#include "gettext.h"
|
||||
|
||||
#ifdef SND_SEQ_PORT_CAP_INACTIVE
|
||||
#define HANDLE_SHOW_ALL
|
||||
static int show_all;
|
||||
#else
|
||||
#define show_all 0
|
||||
#endif
|
||||
|
||||
static void error_handler(const char *file, int line, const char *function, int err, const char *fmt, ...)
|
||||
{
|
||||
|
@ -67,9 +62,7 @@ static void usage(void)
|
|||
printf(_(" aconnect -i|-o [-options]\n"));
|
||||
printf(_(" -i,--input list input (readable) ports\n"));
|
||||
printf(_(" -o,--output list output (writable) ports\n"));
|
||||
#ifdef HANDLE_SHOW_ALL
|
||||
printf(_(" -a,--all show inactive ports, too\n"));
|
||||
#endif
|
||||
printf(_(" -l,--list list current connections of each port\n"));
|
||||
printf(_(" * Remove all exported connections\n"));
|
||||
printf(_(" -x, --removeall\n"));
|
||||
|
@ -84,15 +77,11 @@ static void usage(void)
|
|||
|
||||
#define perm_ok(cap,bits) (((cap) & (bits)) == (bits))
|
||||
|
||||
#ifdef SND_SEQ_PORT_DIR_INPUT
|
||||
static int check_direction(snd_seq_port_info_t *pinfo, int bit)
|
||||
{
|
||||
int dir = snd_seq_port_info_get_direction(pinfo);
|
||||
return !dir || (dir & bit);
|
||||
}
|
||||
#else
|
||||
#define check_direction(x, y) 1
|
||||
#endif
|
||||
|
||||
static int check_permission(snd_seq_port_info_t *pinfo, int perm)
|
||||
{
|
||||
|
@ -174,20 +163,16 @@ static void do_search_port(snd_seq_t *seq, int perm, action_func_t do_action)
|
|||
/* reset query info */
|
||||
snd_seq_port_info_set_client(pinfo, snd_seq_client_info_get_client(cinfo));
|
||||
snd_seq_port_info_set_port(pinfo, -1);
|
||||
#ifdef HANDLE_SHOW_ALL
|
||||
if (show_all)
|
||||
snd_seq_port_info_set_capability(pinfo, SND_SEQ_PORT_CAP_INACTIVE);
|
||||
#endif
|
||||
count = 0;
|
||||
while (snd_seq_query_next_port(seq, pinfo) >= 0) {
|
||||
if (check_permission(pinfo, perm)) {
|
||||
do_action(seq, cinfo, pinfo, count);
|
||||
count++;
|
||||
}
|
||||
#ifdef HANDLE_SHOW_ALL
|
||||
if (show_all)
|
||||
snd_seq_port_info_set_capability(pinfo, SND_SEQ_PORT_CAP_INACTIVE);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -205,7 +190,6 @@ static void print_port(snd_seq_t *seq ATTRIBUTE_UNUSED,
|
|||
snd_seq_client_info_get_name(cinfo),
|
||||
(snd_seq_client_info_get_type(cinfo) == SND_SEQ_USER_CLIENT ?
|
||||
_("user") : _("kernel")));
|
||||
#ifdef HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION
|
||||
switch (snd_seq_client_info_get_midi_version(cinfo)) {
|
||||
case SND_SEQ_CLIENT_UMP_MIDI_1_0:
|
||||
printf(",UMP-MIDI1");
|
||||
|
@ -214,27 +198,21 @@ static void print_port(snd_seq_t *seq ATTRIBUTE_UNUSED,
|
|||
printf(",UMP-MIDI2");
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#ifdef HANDLE_SHOW_ALL
|
||||
if (snd_seq_port_info_get_capability(pinfo) & SND_SEQ_PORT_CAP_INACTIVE)
|
||||
printf(",INACTIVE");
|
||||
#endif
|
||||
#ifdef HAVE_SEQ_CLIENT_INFO_GET_CARD
|
||||
card = snd_seq_client_info_get_card(cinfo);
|
||||
#endif
|
||||
if (card != -1)
|
||||
printf(",card=%d", card);
|
||||
|
||||
#ifdef HAVE_SEQ_CLIENT_INFO_GET_PID
|
||||
pid = snd_seq_client_info_get_pid(cinfo);
|
||||
#endif
|
||||
if (pid != -1)
|
||||
printf(",pid=%d", pid);
|
||||
printf("]\n");
|
||||
}
|
||||
printf(" %3d '%-16s'\n",
|
||||
printf(" %3d '%-16s'",
|
||||
snd_seq_port_info_get_port(pinfo),
|
||||
snd_seq_port_info_get_name(pinfo));
|
||||
if (snd_seq_port_info_get_capability(pinfo) & SND_SEQ_PORT_CAP_INACTIVE)
|
||||
printf(" [INACTIVE]");
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
static void print_port_and_subs(snd_seq_t *seq, snd_seq_client_info_t *cinfo,
|
||||
|
@ -298,11 +276,7 @@ enum {
|
|||
SUBSCRIBE, UNSUBSCRIBE, LIST, REMOVE_ALL
|
||||
};
|
||||
|
||||
#ifdef HANDLE_SHOW_ALL
|
||||
#define ACONNECT_OPTS "dior:t:elxa"
|
||||
#else
|
||||
#define ACONNECT_OPTS "dior:t:elx"
|
||||
#endif
|
||||
|
||||
static const struct option long_option[] = {
|
||||
{"disconnect", 0, NULL, 'd'},
|
||||
|
@ -313,9 +287,7 @@ static const struct option long_option[] = {
|
|||
{"exclusive", 0, NULL, 'e'},
|
||||
{"list", 0, NULL, 'l'},
|
||||
{"removeall", 0, NULL, 'x'},
|
||||
#ifdef HANDLE_SHOW_ALL
|
||||
{"all", 0, NULL, 'a'},
|
||||
#endif
|
||||
{NULL, 0, NULL, 0},
|
||||
};
|
||||
|
||||
|
@ -369,12 +341,10 @@ int main(int argc, char **argv)
|
|||
case 'x':
|
||||
command = REMOVE_ALL;
|
||||
break;
|
||||
#ifdef HANDLE_SHOW_ALL
|
||||
case 'a':
|
||||
command = LIST;
|
||||
show_all = 1;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
usage();
|
||||
exit(1);
|
||||
|
|
|
@ -30,9 +30,7 @@
|
|||
#include <unistd.h>
|
||||
#include <alsa/asoundlib.h>
|
||||
#include "version.h"
|
||||
#ifdef HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION
|
||||
#include <alsa/ump_msg.h>
|
||||
#endif
|
||||
|
||||
/*
|
||||
* 31.25 kbaud, one start bit, eight data bits, two stop bits.
|
||||
|
@ -78,9 +76,7 @@ static int file_offset; /* current offset in input file */
|
|||
static int num_tracks;
|
||||
static struct track *tracks;
|
||||
static int smpte_timing;
|
||||
#ifdef HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION
|
||||
static int ump_mode;
|
||||
#endif
|
||||
|
||||
/* prints an error message to stderr */
|
||||
static void errormsg(const char *msg, ...)
|
||||
|
@ -685,7 +681,6 @@ static int fill_legacy_event(struct event* event, snd_seq_event_t *ev)
|
|||
return 0;
|
||||
}
|
||||
|
||||
#ifdef HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION
|
||||
static unsigned char to_ump_status(unsigned char ev_type)
|
||||
{
|
||||
switch (ev_type) {
|
||||
|
@ -762,13 +757,10 @@ static int fill_ump_event(struct event* event, snd_seq_ump_event_t *ump_ev,
|
|||
snd_seq_ev_set_ump_data(ump_ev, &ump, sizeof(ump));
|
||||
return 0;
|
||||
}
|
||||
#endif /* HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION */
|
||||
|
||||
static void play_midi(void)
|
||||
{
|
||||
#ifdef HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION
|
||||
snd_seq_ump_event_t ump_ev;
|
||||
#endif
|
||||
snd_seq_event_t ev;
|
||||
int i, max_tick, err;
|
||||
|
||||
|
@ -830,7 +822,7 @@ static void play_midi(void)
|
|||
if (err < 0)
|
||||
continue;
|
||||
}
|
||||
#ifdef HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION
|
||||
|
||||
if (ump_mode) {
|
||||
err = fill_ump_event(event, &ump_ev, &ev);
|
||||
if (err < 0)
|
||||
|
@ -839,7 +831,6 @@ static void play_midi(void)
|
|||
check_snd("output event", err);
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* this blocks when the output pool has been filled */
|
||||
err = snd_seq_event_output(seq, &ev);
|
||||
|
@ -957,9 +948,7 @@ static void usage(const char *argv0)
|
|||
"-V, --version print current version\n"
|
||||
"-l, --list list all possible output ports\n"
|
||||
"-p, --port=client:port,... set port(s) to play to\n"
|
||||
#ifdef HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION
|
||||
"-u, --ump=version UMP output (only version=1 is supported)\n"
|
||||
#endif
|
||||
"-d, --delay=seconds delay after song ends\n",
|
||||
argv0);
|
||||
}
|
||||
|
@ -969,12 +958,7 @@ static void version(void)
|
|||
puts("aplaymidi version " SND_UTIL_VERSION_STR);
|
||||
}
|
||||
|
||||
#ifdef HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION
|
||||
#define OPTIONS "hVlp:d:u:"
|
||||
#else
|
||||
#define OPTIONS "hVlp:d:"
|
||||
#endif
|
||||
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
|
@ -984,9 +968,7 @@ int main(int argc, char *argv[])
|
|||
{"version", 0, NULL, 'V'},
|
||||
{"list", 0, NULL, 'l'},
|
||||
{"port", 1, NULL, 'p'},
|
||||
#ifdef HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION
|
||||
{"ump", 1, NULL, 'u'},
|
||||
#endif
|
||||
{"delay", 1, NULL, 'd'},
|
||||
{0}
|
||||
};
|
||||
|
@ -1013,15 +995,11 @@ int main(int argc, char *argv[])
|
|||
case 'd':
|
||||
end_delay = atoi(optarg);
|
||||
break;
|
||||
#ifdef HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION
|
||||
case 'u':
|
||||
if (strcmp(optarg, "1")) {
|
||||
errormsg("Only MIDI 1.0 is supported");
|
||||
return 1;
|
||||
}
|
||||
ump_mode = 1;
|
||||
ump_mode = atoi(optarg);
|
||||
if (ump_mode < 0 || ump_mode > 1)
|
||||
fatal("Only MIDI 1.0 is supported");
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
usage(argv[0]);
|
||||
return 1;
|
||||
|
@ -1029,13 +1007,11 @@ int main(int argc, char *argv[])
|
|||
}
|
||||
|
||||
|
||||
#ifdef HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION
|
||||
if (ump_mode) {
|
||||
int err;
|
||||
err = snd_seq_set_client_midi_version(seq, SND_SEQ_CLIENT_UMP_MIDI_1_0);
|
||||
check_snd("set midi version", err);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (do_list) {
|
||||
list_ports();
|
||||
|
|
5
seq/aplaymidi2/Makefile.am
Normal file
5
seq/aplaymidi2/Makefile.am
Normal file
|
@ -0,0 +1,5 @@
|
|||
AM_CPPFLAGS = -I$(top_srcdir)/include
|
||||
EXTRA_DIST = aplaymidi2.1 arecordmidi2.1
|
||||
|
||||
bin_PROGRAMS = aplaymidi2 arecordmidi2
|
||||
man_MANS = aplaymidi2.1 arecordmidi2.1
|
84
seq/aplaymidi2/aplaymidi2.1
Normal file
84
seq/aplaymidi2/aplaymidi2.1
Normal file
|
@ -0,0 +1,84 @@
|
|||
.TH APLAYMIDI2 1 "4 July 2024"
|
||||
|
||||
.SH NAME
|
||||
aplaymidi2 \- play MIDI Clip Files
|
||||
|
||||
.SH SYNOPSIS
|
||||
.B aplaymidi2
|
||||
\-p client:port[,...] midi2file ...
|
||||
|
||||
.SH DESCRIPTION
|
||||
.B aplaymidi2
|
||||
is a command-line utility that plays the specified MIDI Clip file(s) to one
|
||||
or more ALSA sequencer ports.
|
||||
|
||||
.SH OPTIONS
|
||||
|
||||
.TP
|
||||
.I \-h, \-\-help
|
||||
Prints a list of options.
|
||||
|
||||
.TP
|
||||
.I \-V, \-\-version
|
||||
Prints the current version.
|
||||
|
||||
.TP
|
||||
.I \-p, \-\-port=client:port,...
|
||||
Sets the sequencer port(s) to which the events in the MIDI Clip file(s) are
|
||||
sent.
|
||||
|
||||
A client can be specified by its number, its name, or a prefix of its
|
||||
name. A port is specified by its number; for port 0 of a client, the
|
||||
":0" part of the port specification can be omitted.
|
||||
|
||||
Multiple ports can be specified to allow playback of MIDI Clip file(s) that
|
||||
contain events for multiple devices (ports) corresponding to the
|
||||
multiple UMP Groups.
|
||||
|
||||
For compatibility with
|
||||
.B pmidi(1),
|
||||
the port specification is taken from the
|
||||
.I ALSA_OUTPUT_PORTS
|
||||
environment variable if none is given on the command line.
|
||||
|
||||
.B aplaymidi2
|
||||
supports only basic UMP events: in addition to the standard MIDI1 and
|
||||
MIDI2 CVMs and 7bit SysEx, only the following are supported:
|
||||
DCTPQ, DC, Set Tempo, Start Clip, End Clip.
|
||||
Lyrics and other meta data in Flex Data are printed, too, unless
|
||||
\fI\-s\fP option is given.
|
||||
|
||||
The multiple output ports are useful when the given MIDI Clip file
|
||||
contains the UMP packets for multiple Groups.
|
||||
When the destination port is a UMP MIDI 2.0 port, the single
|
||||
connection should suffice, though, since a MIDI 2.0 port can process
|
||||
the inputs for multiple Groups. For other cases (e.g. connecting to a
|
||||
legacy MIDI port), you would need to specify the destination port per
|
||||
Group. If undefined, it's sent to the first destination port as
|
||||
default.
|
||||
|
||||
.TP
|
||||
.I \-d, \-\-delay=seconds
|
||||
Specifies how long to wait after the end of each MIDI Clip file,
|
||||
to allow the last notes to die away.
|
||||
Default is 2 seconds.
|
||||
|
||||
.TP
|
||||
.I \-s, \-\-silent
|
||||
Don't show message texts.
|
||||
|
||||
.TP
|
||||
.I \-a, \-\-passall
|
||||
Pass all UMP packets as is.
|
||||
|
||||
As default, \fBaplaymidi2\fP passes only MIDI1 and MIDI2 channel voice
|
||||
messages and process other UMP packets internally.
|
||||
With this option, it passes all UMP packets to the target.
|
||||
|
||||
.SH SEE ALSO
|
||||
pmidi(1)
|
||||
.br
|
||||
aplaymidi(1)
|
||||
|
||||
.SH AUTHOR
|
||||
Takashi Iwai <tiwai@suse.de>
|
581
seq/aplaymidi2/aplaymidi2.c
Normal file
581
seq/aplaymidi2/aplaymidi2.c
Normal file
|
@ -0,0 +1,581 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* aplaymidi2.c - simple player of a MIDI Clip File over ALSA sequencer
|
||||
*/
|
||||
|
||||
#include "aconfig.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <getopt.h>
|
||||
#include <unistd.h>
|
||||
#include <alsa/asoundlib.h>
|
||||
#include <alsa/ump_msg.h>
|
||||
#include "version.h"
|
||||
|
||||
static snd_seq_t *seq;
|
||||
static int client;
|
||||
static int port_count;
|
||||
static snd_seq_addr_t ports[16];
|
||||
static int queue;
|
||||
static int end_delay = 2;
|
||||
static int silent;
|
||||
static int passall;
|
||||
|
||||
static unsigned int _current_tempo = 50000000; /* default 120 bpm */
|
||||
static unsigned int tempo_base = 10;
|
||||
static unsigned int current_tick;
|
||||
|
||||
/* prints an error message to stderr */
|
||||
static void errormsg(const char *msg, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, msg);
|
||||
vfprintf(stderr, msg, ap);
|
||||
va_end(ap);
|
||||
fputc('\n', stderr);
|
||||
}
|
||||
|
||||
/* prints an error message to stderr, and dies */
|
||||
static void fatal(const char *msg, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, msg);
|
||||
vfprintf(stderr, msg, ap);
|
||||
va_end(ap);
|
||||
fputc('\n', stderr);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* memory allocation error handling */
|
||||
static void check_mem(void *p)
|
||||
{
|
||||
if (!p)
|
||||
fatal("Out of memory");
|
||||
}
|
||||
|
||||
/* error handling for ALSA functions */
|
||||
static void check_snd(const char *operation, int err)
|
||||
{
|
||||
if (err < 0)
|
||||
fatal("Cannot %s - %s", operation, snd_strerror(err));
|
||||
}
|
||||
|
||||
/* open and initialize the sequencer client */
|
||||
static void init_seq(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, 0);
|
||||
check_snd("open sequencer", err);
|
||||
|
||||
err = snd_seq_set_client_name(seq, "aplaymidi2");
|
||||
check_snd("set client name", err);
|
||||
|
||||
client = snd_seq_client_id(seq);
|
||||
check_snd("get client id", client);
|
||||
|
||||
err = snd_seq_set_client_midi_version(seq, SND_SEQ_CLIENT_UMP_MIDI_2_0);
|
||||
check_snd("set midi version", err);
|
||||
}
|
||||
|
||||
/* parses one or more port addresses from the string */
|
||||
static void parse_ports(const char *arg)
|
||||
{
|
||||
char *buf, *s, *port_name;
|
||||
int err;
|
||||
|
||||
/* make a copy of the string because we're going to modify it */
|
||||
buf = strdup(arg);
|
||||
check_mem(buf);
|
||||
|
||||
for (port_name = s = buf; s; port_name = s + 1) {
|
||||
/* Assume that ports are separated by commas. We don't use
|
||||
* spaces because those are valid in client names. */
|
||||
s = strchr(port_name, ',');
|
||||
if (s)
|
||||
*s = '\0';
|
||||
|
||||
++port_count;
|
||||
if (port_count > 16)
|
||||
fatal("Too many ports specified");
|
||||
|
||||
err = snd_seq_parse_address(seq, &ports[port_count - 1], port_name);
|
||||
if (err < 0)
|
||||
fatal("Invalid port %s - %s", port_name, snd_strerror(err));
|
||||
}
|
||||
|
||||
free(buf);
|
||||
}
|
||||
|
||||
/* create a source port to send from */
|
||||
static void create_source_port(void)
|
||||
{
|
||||
snd_seq_port_info_t *pinfo;
|
||||
int err;
|
||||
|
||||
snd_seq_port_info_alloca(&pinfo);
|
||||
|
||||
/* the first created port is 0 anyway, but let's make sure ... */
|
||||
snd_seq_port_info_set_port(pinfo, 0);
|
||||
snd_seq_port_info_set_port_specified(pinfo, 1);
|
||||
|
||||
snd_seq_port_info_set_name(pinfo, "aplaymidi2");
|
||||
|
||||
snd_seq_port_info_set_capability(pinfo, 0); /* sic */
|
||||
snd_seq_port_info_set_type(pinfo,
|
||||
SND_SEQ_PORT_TYPE_MIDI_GENERIC |
|
||||
SND_SEQ_PORT_TYPE_APPLICATION);
|
||||
|
||||
err = snd_seq_create_port(seq, pinfo);
|
||||
check_snd("create port", err);
|
||||
}
|
||||
|
||||
/* create a queue */
|
||||
static void create_queue(void)
|
||||
{
|
||||
if (!snd_seq_has_queue_tempo_base(seq))
|
||||
tempo_base = 1000;
|
||||
|
||||
queue = snd_seq_alloc_named_queue(seq, "aplaymidi2");
|
||||
check_snd("create queue", queue);
|
||||
}
|
||||
|
||||
/* connect to destination ports */
|
||||
static void connect_ports(void)
|
||||
{
|
||||
int i, err;
|
||||
|
||||
for (i = 0; i < port_count; ++i) {
|
||||
err = snd_seq_connect_to(seq, 0, ports[i].client, ports[i].port);
|
||||
if (err < 0)
|
||||
fatal("Cannot connect to port %d:%d - %s",
|
||||
ports[i].client, ports[i].port, snd_strerror(err));
|
||||
}
|
||||
}
|
||||
|
||||
/* read 32bit word and convert to native endian:
|
||||
* return 0 on success, -1 on error
|
||||
*/
|
||||
static int read_word(FILE *file, uint32_t *dest)
|
||||
{
|
||||
uint32_t v;
|
||||
|
||||
if (fread(&v, 4, 1, file) != 1)
|
||||
return -1;
|
||||
*dest = be32toh(v);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* read a UMP packet: return the number of packets, -1 on error */
|
||||
static int read_ump_packet(FILE *file, uint32_t *buf)
|
||||
{
|
||||
snd_ump_msg_hdr_t *h = (snd_ump_msg_hdr_t *)buf;
|
||||
|
||||
int i, num;
|
||||
|
||||
if (read_word(file, buf) < 0)
|
||||
return -1;
|
||||
num = snd_ump_packet_length(h->type);
|
||||
for (i = 1; i < num; i++) {
|
||||
if (read_word(file, buf + i) < 0)
|
||||
return -1;
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
/* read the file header and verify it's MIDI Clip File: return 0 on success */
|
||||
static int verify_file_header(FILE *file)
|
||||
{
|
||||
unsigned char buf[8];
|
||||
|
||||
if (fread(buf, 1, 8, file) != 8)
|
||||
return -1;
|
||||
if (memcmp(buf, "SMF2CLIP", 8))
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* return the current tempo, corrected to be sent to host */
|
||||
static int current_tempo(void)
|
||||
{
|
||||
if (tempo_base != 10)
|
||||
return _current_tempo / 100; /* down to us */
|
||||
return _current_tempo;
|
||||
}
|
||||
|
||||
/* send a timer event */
|
||||
static void send_timer_event(unsigned int type, unsigned int val)
|
||||
{
|
||||
snd_seq_ump_event_t ev = {
|
||||
.type = type,
|
||||
.flags = SND_SEQ_TIME_STAMP_TICK | SND_SEQ_EVENT_LENGTH_FIXED,
|
||||
};
|
||||
|
||||
ev.queue = queue;
|
||||
ev.source.port = 0;
|
||||
ev.time.tick = current_tick;
|
||||
|
||||
ev.dest.client = SND_SEQ_CLIENT_SYSTEM;
|
||||
ev.dest.port = SND_SEQ_PORT_SYSTEM_TIMER;
|
||||
ev.data.queue.queue = queue;
|
||||
ev.data.queue.param.value = val;
|
||||
|
||||
snd_seq_ump_event_output(seq, &ev);
|
||||
}
|
||||
|
||||
/* set DCTPQ */
|
||||
static void set_dctpq(unsigned int ppq)
|
||||
{
|
||||
snd_seq_queue_tempo_t *queue_tempo;
|
||||
|
||||
snd_seq_queue_tempo_alloca(&queue_tempo);
|
||||
snd_seq_queue_tempo_set_tempo(queue_tempo, current_tempo());
|
||||
snd_seq_queue_tempo_set_ppq(queue_tempo, ppq);
|
||||
snd_seq_queue_tempo_set_tempo_base(queue_tempo, tempo_base);
|
||||
|
||||
if (snd_seq_set_queue_tempo(seq, queue, queue_tempo) < 0)
|
||||
errormsg("Cannot set queue tempo (%d)", queue);
|
||||
}
|
||||
|
||||
/* set DC */
|
||||
static void set_dc(unsigned int ticks)
|
||||
{
|
||||
current_tick += ticks;
|
||||
}
|
||||
|
||||
/* set tempo event */
|
||||
static void set_tempo(unsigned int tempo)
|
||||
{
|
||||
_current_tempo = tempo;
|
||||
send_timer_event(SND_SEQ_EVENT_TEMPO, current_tempo());
|
||||
}
|
||||
|
||||
/* start clip */
|
||||
static void start_clip(void)
|
||||
{
|
||||
if (snd_seq_start_queue(seq, queue, NULL) < 0)
|
||||
errormsg("Cannot start queue (%d)", queue);
|
||||
}
|
||||
|
||||
/* end clip */
|
||||
static void end_clip(void)
|
||||
{
|
||||
send_timer_event(SND_SEQ_EVENT_STOP, 0);
|
||||
}
|
||||
|
||||
/* send a UMP packet */
|
||||
static void send_ump(const uint32_t *ump, int len)
|
||||
{
|
||||
snd_seq_ump_event_t ev = {
|
||||
.flags = SND_SEQ_TIME_STAMP_TICK | SND_SEQ_EVENT_LENGTH_FIXED |
|
||||
SND_SEQ_EVENT_UMP,
|
||||
};
|
||||
int group;
|
||||
|
||||
memcpy(ev.ump, ump, len * 4);
|
||||
|
||||
ev.queue = queue;
|
||||
ev.source.port = 0;
|
||||
ev.time.tick = current_tick;
|
||||
group = snd_ump_msg_group(ump);
|
||||
if (group >= port_count)
|
||||
ev.dest = ports[0];
|
||||
else
|
||||
ev.dest = ports[group];
|
||||
|
||||
snd_seq_ump_event_output(seq, &ev);
|
||||
}
|
||||
|
||||
struct flexdata_text_prefix {
|
||||
unsigned char status_bank;
|
||||
unsigned char status;
|
||||
const char *prefix;
|
||||
};
|
||||
|
||||
static struct flexdata_text_prefix text_prefix[] = {
|
||||
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA,
|
||||
.status = SND_UMP_FLEX_DATA_MSG_STATUS_PROJECT_NAME,
|
||||
.prefix = "Project" },
|
||||
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA,
|
||||
.status = SND_UMP_FLEX_DATA_MSG_STATUS_SONG_NAME,
|
||||
.prefix = "Song" },
|
||||
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA,
|
||||
.status = SND_UMP_FLEX_DATA_MSG_STATUS_MIDI_CLIP_NAME,
|
||||
.prefix = "MIDI Clip" },
|
||||
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA,
|
||||
.status = SND_UMP_FLEX_DATA_MSG_STATUS_COPYRIGHT_NOTICE,
|
||||
.prefix = "Copyright" },
|
||||
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA,
|
||||
.status = SND_UMP_FLEX_DATA_MSG_STATUS_COMPOSER_NAME,
|
||||
.prefix = "Composer" },
|
||||
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA,
|
||||
.status = SND_UMP_FLEX_DATA_MSG_STATUS_LYRICIST_NAME,
|
||||
.prefix = "Lyricist" },
|
||||
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA,
|
||||
.status = SND_UMP_FLEX_DATA_MSG_STATUS_ARRANGER_NAME,
|
||||
.prefix = "Arranger" },
|
||||
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA,
|
||||
.status = SND_UMP_FLEX_DATA_MSG_STATUS_PUBLISHER_NAME,
|
||||
.prefix = "Publisher" },
|
||||
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA,
|
||||
.status = SND_UMP_FLEX_DATA_MSG_STATUS_PRIMARY_PERFORMER,
|
||||
.prefix = "Performer" },
|
||||
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA,
|
||||
.status = SND_UMP_FLEX_DATA_MSG_STATUS_ACCOMPANY_PERFORMAER,
|
||||
.prefix = "Accompany Performer" },
|
||||
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA,
|
||||
.status = SND_UMP_FLEX_DATA_MSG_STATUS_RECORDING_DATE,
|
||||
.prefix = "Recording Date" },
|
||||
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA,
|
||||
.status = SND_UMP_FLEX_DATA_MSG_STATUS_RECORDING_LOCATION,
|
||||
.prefix = "Recording Location" },
|
||||
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_PERF_TEXT,
|
||||
.status = SND_UMP_FLEX_DATA_MSG_STATUS_LYRICS,
|
||||
.prefix = "Lyrics" },
|
||||
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_PERF_TEXT,
|
||||
.status = SND_UMP_FLEX_DATA_MSG_STATUS_LYRICS_LANGUAGE,
|
||||
.prefix = "Lyrics Language" },
|
||||
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_PERF_TEXT,
|
||||
.status = SND_UMP_FLEX_DATA_MSG_STATUS_RUBY,
|
||||
.prefix = "Ruby" },
|
||||
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_PERF_TEXT,
|
||||
.status = SND_UMP_FLEX_DATA_MSG_STATUS_RUBY_LANGUAGE,
|
||||
.prefix = "Ruby Language" },
|
||||
{}
|
||||
};
|
||||
|
||||
static void show_text(const uint32_t *ump)
|
||||
{
|
||||
static unsigned char textbuf[256];
|
||||
static int len;
|
||||
const snd_ump_msg_flex_data_t *fh =
|
||||
(const snd_ump_msg_flex_data_t *)ump;
|
||||
const char *prefix;
|
||||
int i;
|
||||
|
||||
if (fh->meta.format == SND_UMP_FLEX_DATA_MSG_FORMAT_SINGLE ||
|
||||
fh->meta.format == SND_UMP_FLEX_DATA_MSG_FORMAT_START)
|
||||
len = 0;
|
||||
|
||||
for (i = 0; i < 12 && len < (int)sizeof(textbuf); i++) {
|
||||
textbuf[len] = snd_ump_get_byte(ump, 4 + i);
|
||||
if (!textbuf[len])
|
||||
break;
|
||||
switch (textbuf[len]) {
|
||||
case 0x0a: /* end of paragraph */
|
||||
case 0x0d: /* end of line */
|
||||
textbuf[len] = '\n';
|
||||
break;
|
||||
}
|
||||
len++;
|
||||
}
|
||||
|
||||
if (fh->meta.format != SND_UMP_FLEX_DATA_MSG_FORMAT_SINGLE &&
|
||||
fh->meta.format != SND_UMP_FLEX_DATA_MSG_FORMAT_END)
|
||||
return;
|
||||
|
||||
if (len >= (int)sizeof(textbuf))
|
||||
len = sizeof(textbuf) - 1;
|
||||
textbuf[len] = 0;
|
||||
|
||||
prefix = NULL;
|
||||
for (i = 0; text_prefix[i].status_bank; i++) {
|
||||
if (text_prefix[i].status_bank == fh->meta.status_bank &&
|
||||
text_prefix[i].status == fh->meta.status) {
|
||||
prefix = text_prefix[i].prefix;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (prefix) {
|
||||
printf("%s: %s\n", prefix, textbuf);
|
||||
} else {
|
||||
printf("(%d:%d): %s\n", fh->meta.status_bank, fh->meta.status,
|
||||
textbuf);
|
||||
}
|
||||
|
||||
len = 0;
|
||||
}
|
||||
|
||||
/* play the given MIDI Clip File content */
|
||||
static void play_midi(FILE *file)
|
||||
{
|
||||
uint32_t ump[4];
|
||||
int len;
|
||||
|
||||
current_tick = 0;
|
||||
|
||||
while ((len = read_ump_packet(file, ump)) > 0) {
|
||||
const snd_ump_msg_hdr_t *h = (snd_ump_msg_hdr_t *)ump;
|
||||
|
||||
if (passall)
|
||||
send_ump(ump, len);
|
||||
|
||||
if (h->type == SND_UMP_MSG_TYPE_UTILITY) {
|
||||
const snd_ump_msg_utility_t *uh =
|
||||
(const snd_ump_msg_utility_t *)ump;
|
||||
switch (h->status) {
|
||||
case SND_UMP_UTILITY_MSG_STATUS_DCTPQ:
|
||||
set_dctpq(uh->dctpq.ticks);
|
||||
continue;
|
||||
case SND_UMP_UTILITY_MSG_STATUS_DC:
|
||||
set_dc(uh->dctpq.ticks);
|
||||
continue;
|
||||
}
|
||||
} else if (h->type == SND_UMP_MSG_TYPE_FLEX_DATA) {
|
||||
const snd_ump_msg_flex_data_t *fh =
|
||||
(const snd_ump_msg_flex_data_t *)ump;
|
||||
if (fh->meta.status_bank == SND_UMP_FLEX_DATA_MSG_BANK_SETUP &&
|
||||
fh->meta.status == SND_UMP_FLEX_DATA_MSG_STATUS_SET_TEMPO) {
|
||||
set_tempo(fh->set_tempo.tempo);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fh->meta.status_bank == SND_UMP_FLEX_DATA_MSG_BANK_METADATA ||
|
||||
fh->meta.status_bank == SND_UMP_FLEX_DATA_MSG_BANK_PERF_TEXT) {
|
||||
if (!silent)
|
||||
show_text(ump);
|
||||
continue;
|
||||
}
|
||||
} else if (h->type == SND_UMP_MSG_TYPE_STREAM) {
|
||||
const snd_ump_msg_stream_t *sh =
|
||||
(const snd_ump_msg_stream_t *)ump;
|
||||
switch (sh->gen.status) {
|
||||
case SND_UMP_STREAM_MSG_STATUS_START_CLIP:
|
||||
start_clip();
|
||||
continue;
|
||||
case SND_UMP_STREAM_MSG_STATUS_END_CLIP:
|
||||
end_clip();
|
||||
continue;
|
||||
}
|
||||
} else if (!passall &&
|
||||
(h->type == SND_UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE ||
|
||||
h->type == SND_UMP_MSG_TYPE_DATA ||
|
||||
h->type == SND_UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE)) {
|
||||
send_ump(ump, len);
|
||||
}
|
||||
}
|
||||
|
||||
snd_seq_drain_output(seq);
|
||||
snd_seq_sync_output_queue(seq);
|
||||
|
||||
/* give the last notes time to die away */
|
||||
if (end_delay > 0)
|
||||
sleep(end_delay);
|
||||
}
|
||||
|
||||
static void play_file(const char *file_name)
|
||||
{
|
||||
FILE *file;
|
||||
|
||||
if (!strcmp(file_name, "-"))
|
||||
file = stdin;
|
||||
else
|
||||
file = fopen(file_name, "rb");
|
||||
if (!file) {
|
||||
errormsg("Cannot open %s - %s", file_name, strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
if (verify_file_header(file) < 0) {
|
||||
errormsg("%s is not a MIDI Clip File", file_name);
|
||||
goto error;
|
||||
}
|
||||
|
||||
play_midi(file);
|
||||
|
||||
error:
|
||||
if (file != stdin)
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
static void usage(const char *argv0)
|
||||
{
|
||||
printf(
|
||||
"Usage: %s -p client:port[,...] [-d delay] midifile ...\n"
|
||||
"-h, --help this help\n"
|
||||
"-V, --version print current version\n"
|
||||
"-p, --port=client:port,... set port(s) to play to\n"
|
||||
"-d, --delay=seconds delay after song ends\n"
|
||||
"-s, --silent don't show texts\n"
|
||||
"-a, --passall pass all UMP packets as-is\n",
|
||||
argv0);
|
||||
}
|
||||
|
||||
static void version(void)
|
||||
{
|
||||
puts("aplaymidi2 version " SND_UTIL_VERSION_STR);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
static const struct option long_options[] = {
|
||||
{"help", 0, NULL, 'h'},
|
||||
{"version", 0, NULL, 'V'},
|
||||
{"port", 1, NULL, 'p'},
|
||||
{"delay", 1, NULL, 'd'},
|
||||
{"silent", 0, NULL, 's'},
|
||||
{"passall", 0, NULL, 'a'},
|
||||
{0}
|
||||
};
|
||||
int c;
|
||||
|
||||
init_seq();
|
||||
|
||||
while ((c = getopt_long(argc, argv, "hVp:d:sa",
|
||||
long_options, NULL)) != -1) {
|
||||
switch (c) {
|
||||
case 'h':
|
||||
usage(argv[0]);
|
||||
return 0;
|
||||
case 'V':
|
||||
version();
|
||||
return 0;
|
||||
case 'p':
|
||||
parse_ports(optarg);
|
||||
break;
|
||||
case 'd':
|
||||
end_delay = atoi(optarg);
|
||||
break;
|
||||
case 's':
|
||||
silent = 1;
|
||||
break;
|
||||
case 'a':
|
||||
passall = 1;
|
||||
break;
|
||||
default:
|
||||
usage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (port_count < 1) {
|
||||
/* use env var for compatibility with pmidi */
|
||||
const char *ports_str = getenv("ALSA_OUTPUT_PORTS");
|
||||
if (ports_str)
|
||||
parse_ports(ports_str);
|
||||
if (port_count < 1) {
|
||||
errormsg("Please specify at least one port with --port.");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if (optind >= argc) {
|
||||
errormsg("Please specify a file to play.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
create_source_port();
|
||||
create_queue();
|
||||
connect_ports();
|
||||
|
||||
for (; optind < argc; optind++)
|
||||
play_file(argv[optind]);
|
||||
|
||||
snd_seq_close(seq);
|
||||
return 0;
|
||||
}
|
110
seq/aplaymidi2/arecordmidi2.1
Normal file
110
seq/aplaymidi2/arecordmidi2.1
Normal file
|
@ -0,0 +1,110 @@
|
|||
.TH ARECORDMIDI2 1 "4 July 2024"
|
||||
|
||||
.SH NAME
|
||||
arecordmidi2 \- record a MIDI Clip file
|
||||
|
||||
.SH SYNOPSIS
|
||||
.B arecordmidi2
|
||||
[options] midi2file
|
||||
|
||||
.SH DESCRIPTION
|
||||
.B arecordmidi2
|
||||
is a command-line utility that records a MIDI Clip file from one or
|
||||
more ALSA sequencer ports.
|
||||
|
||||
To stop recording, press Ctrl+C.
|
||||
|
||||
When \fB\-\fP is passed to the MIDI Clip file argument,
|
||||
it's recorded to stdout. It implies \fI\-s\fP option, too.
|
||||
|
||||
.SH OPTIONS
|
||||
|
||||
.TP
|
||||
.I \-h,\-\-help
|
||||
Prints a list of options.
|
||||
|
||||
.TP
|
||||
.I \-V,\-\-version
|
||||
Prints the current version.
|
||||
|
||||
.TP
|
||||
.I \-p,\-\-port=client:port,...
|
||||
Sets the sequencer port(s) from which events are recorded.
|
||||
|
||||
A client can be specified by its number, its name, or a prefix of its
|
||||
name. A port is specified by its number; for port 0 of a client, the
|
||||
":0" part of the port specification can be omitted.
|
||||
|
||||
\fBarecordmidi2\fP creates a UMP Endpoint containing the same number
|
||||
of Function Blocks as specified by this option, each of which is
|
||||
connected to the specified port as a source.
|
||||
|
||||
When no source ports are specified with \fI\-p\fP option,
|
||||
\fBarecordmidi2\fP creates a UMP Endpoint with full 16 Function Blocks
|
||||
and records from those inputs. User can connect the sequencer ports
|
||||
freely via \fBaconnect\fP, for example. This mode can be used
|
||||
together with the interactive mode via \fI\-r\fP option.
|
||||
|
||||
.TP
|
||||
.I \-b,\-\-bpm=beats
|
||||
Sets the musical tempo of the MIDI file, in beats per minute.
|
||||
The default value is 120 BPM.
|
||||
|
||||
.TP
|
||||
.I \-t,\-\-ticks=ticks
|
||||
Sets the resolution of timestamps (ticks) in the MIDI file,
|
||||
in ticks per beat.
|
||||
The default value is 384 ticks/beat.
|
||||
|
||||
.TP
|
||||
.I \-i,\-\-timesig=numerator:denominator
|
||||
Sets the time signature for the MIDI file.
|
||||
|
||||
The time signature is specified as usual with two numbers, representing
|
||||
the numerator and denominator of the time signature as it would be
|
||||
notated. The denominator must be a power of two. Both numbers should be
|
||||
separated by a colon. The time signature is 4:4 by default.
|
||||
|
||||
.TP
|
||||
.I \-n,\-\-num-events=events
|
||||
Stops the recording after receiving the given number of events.
|
||||
|
||||
.TP
|
||||
.I \-u,\-\-ump=version
|
||||
Sets the UMP MIDI protocol version. Either 1 or 2 has to be given for
|
||||
MIDI 1.0 and MIDI 2.0 protocol, respectively.
|
||||
Default is 1.
|
||||
|
||||
.TP
|
||||
.I \-r,\-\-interactive
|
||||
Run in the interactive mode. \fBarecordmidi2\fP waits for a RETURN
|
||||
key input from the terminal to start the recording. After starting,
|
||||
the recording ends when another RETURN key is input from the
|
||||
terminal. The received events before the start of recording are
|
||||
discarded.
|
||||
|
||||
.TP
|
||||
.I \-s,\-\-silent
|
||||
Don't print messages to stdout.
|
||||
|
||||
.TP
|
||||
.I \-P,\-\-profile=file
|
||||
Read the UMP data from the given file and put them into the
|
||||
configuration section of the recorded output.
|
||||
The file must contain only valid UMP data encoded in big-endian.
|
||||
|
||||
.TP
|
||||
.I \-\-song=text, \-\-clip=text, \-\-copyright=text, \-\-composer=text, \
|
||||
\-\-lyricist=text, \-\-arranger=text, \-\-publisher=text, \
|
||||
\-\-performer=text \-\-accompany=text, \-\-date=text, \-\-location=text
|
||||
Put the given meta data text in the configuration section.
|
||||
|
||||
.SH SEE ALSO
|
||||
arecordmidi(1)
|
||||
.br
|
||||
aplaymidi2(1)
|
||||
|
||||
.SH AUTHOR
|
||||
Takashi Iwai <tiwai@suse.de>
|
||||
|
||||
|
717
seq/aplaymidi2/arecordmidi2.c
Normal file
717
seq/aplaymidi2/arecordmidi2.c
Normal file
|
@ -0,0 +1,717 @@
|
|||
/*
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
#include <getopt.h>
|
||||
#include <poll.h>
|
||||
#include <alsa/asoundlib.h>
|
||||
#include <alsa/ump_msg.h>
|
||||
#include "aconfig.h"
|
||||
#include "version.h"
|
||||
|
||||
static snd_seq_t *seq;
|
||||
static int client;
|
||||
static int port_count;
|
||||
static snd_seq_addr_t *ports;
|
||||
static int queue;
|
||||
static int midi_version = 1;
|
||||
static int beats = 120;
|
||||
static int ticks = 384;
|
||||
static int tempo_base = 10;
|
||||
static volatile sig_atomic_t stop;
|
||||
static int ts_num = 4; /* time signature: numerator */
|
||||
static int ts_div = 4; /* time signature: denominator */
|
||||
static int last_tick;
|
||||
static int silent;
|
||||
static const char *profile_ump_file;
|
||||
|
||||
#define MAX_METADATA 16
|
||||
static int metadata_num;
|
||||
static unsigned int metadata_types[MAX_METADATA];
|
||||
static const char *metadata_texts[MAX_METADATA];
|
||||
|
||||
/* Parse a decimal number from a command line argument. */
|
||||
static long arg_parse_decimal_num(const char *str, int *err)
|
||||
{
|
||||
long val;
|
||||
char *endptr;
|
||||
|
||||
errno = 0;
|
||||
val = strtol(str, &endptr, 0);
|
||||
if (errno > 0) {
|
||||
*err = -errno;
|
||||
return 0;
|
||||
}
|
||||
if (*endptr != '\0') {
|
||||
*err = -EINVAL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
/* prints an error message to stderr, and dies */
|
||||
static void fatal(const char *msg, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, msg);
|
||||
vfprintf(stderr, msg, ap);
|
||||
va_end(ap);
|
||||
fputc('\n', stderr);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* memory allocation error handling */
|
||||
static void check_mem(void *p)
|
||||
{
|
||||
if (!p)
|
||||
fatal("Out of memory");
|
||||
}
|
||||
|
||||
/* error handling for ALSA functions */
|
||||
static void check_snd(const char *operation, int err)
|
||||
{
|
||||
if (err < 0)
|
||||
fatal("Cannot %s - %s", operation, snd_strerror(err));
|
||||
}
|
||||
|
||||
/* open a sequencer client */
|
||||
static void init_seq(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
/* open sequencer */
|
||||
err = snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, 0);
|
||||
check_snd("open sequencer", err);
|
||||
|
||||
/* find out our client's id */
|
||||
client = snd_seq_client_id(seq);
|
||||
check_snd("get client id", client);
|
||||
}
|
||||
|
||||
/* set up UMP virtual client/port */
|
||||
static void create_ump_client(void)
|
||||
{
|
||||
snd_ump_endpoint_info_t *ep;
|
||||
snd_ump_block_info_t *blk;
|
||||
snd_seq_port_info_t *pinfo;
|
||||
int num_groups;
|
||||
int i, err;
|
||||
|
||||
/* in passive mode, create full 16 groups */
|
||||
if (port_count)
|
||||
num_groups = port_count;
|
||||
else
|
||||
num_groups = 16;
|
||||
|
||||
/* create a UMP Endpoint */
|
||||
snd_ump_endpoint_info_alloca(&ep);
|
||||
snd_ump_endpoint_info_set_name(ep, "arecordmidi2");
|
||||
if (midi_version == 1) {
|
||||
snd_ump_endpoint_info_set_protocol_caps(ep, SND_UMP_EP_INFO_PROTO_MIDI1);
|
||||
snd_ump_endpoint_info_set_protocol(ep, SND_UMP_EP_INFO_PROTO_MIDI1);
|
||||
} else {
|
||||
snd_ump_endpoint_info_set_protocol_caps(ep, SND_UMP_EP_INFO_PROTO_MIDI2);
|
||||
snd_ump_endpoint_info_set_protocol(ep, SND_UMP_EP_INFO_PROTO_MIDI2);
|
||||
}
|
||||
snd_ump_endpoint_info_set_num_blocks(ep, num_groups);
|
||||
|
||||
err = snd_seq_create_ump_endpoint(seq, ep, num_groups);
|
||||
check_snd("create UMP endpoint", err);
|
||||
|
||||
/* create UMP Function Blocks */
|
||||
snd_ump_block_info_alloca(&blk);
|
||||
for (i = 0; i < num_groups; i++) {
|
||||
char blkname[32];
|
||||
|
||||
sprintf(blkname, "Group %d", i + 1);
|
||||
snd_ump_block_info_set_name(blk, blkname);
|
||||
snd_ump_block_info_set_direction(blk, SND_UMP_DIR_INPUT);
|
||||
snd_ump_block_info_set_first_group(blk, i);
|
||||
snd_ump_block_info_set_num_groups(blk, 1);
|
||||
snd_ump_block_info_set_ui_hint(blk, SND_UMP_BLOCK_UI_HINT_RECEIVER);
|
||||
|
||||
err = snd_seq_create_ump_block(seq, i, blk);
|
||||
check_snd("create UMP block", err);
|
||||
}
|
||||
|
||||
/* toggle timestamping for all input ports */
|
||||
snd_seq_port_info_alloca(&pinfo);
|
||||
for (i = 0; i <= num_groups; i++) {
|
||||
err = snd_seq_get_port_info(seq, i, pinfo);
|
||||
check_snd("get port info", err);
|
||||
snd_seq_port_info_set_timestamping(pinfo, 1);
|
||||
snd_seq_port_info_set_timestamp_queue(pinfo, queue);
|
||||
snd_seq_set_port_info(seq, i, pinfo);
|
||||
check_snd("set port info", err);
|
||||
}
|
||||
}
|
||||
|
||||
/* parses one or more port addresses from the string */
|
||||
static void parse_ports(const char *arg)
|
||||
{
|
||||
char *buf, *s, *port_name;
|
||||
int err;
|
||||
|
||||
/* make a copy of the string because we're going to modify it */
|
||||
buf = strdup(arg);
|
||||
check_mem(buf);
|
||||
|
||||
for (port_name = s = buf; s; port_name = s + 1) {
|
||||
/* Assume that ports are separated by commas. We don't use
|
||||
* spaces because those are valid in client names.
|
||||
*/
|
||||
s = strchr(port_name, ',');
|
||||
if (s)
|
||||
*s = '\0';
|
||||
|
||||
++port_count;
|
||||
ports = realloc(ports, port_count * sizeof(snd_seq_addr_t));
|
||||
check_mem(ports);
|
||||
|
||||
err = snd_seq_parse_address(seq, &ports[port_count - 1], port_name);
|
||||
if (err < 0)
|
||||
fatal("Invalid port %s - %s", port_name, snd_strerror(err));
|
||||
}
|
||||
|
||||
free(buf);
|
||||
}
|
||||
|
||||
/* parses time signature specification */
|
||||
static void time_signature(const char *arg)
|
||||
{
|
||||
long x = 0;
|
||||
char *sep;
|
||||
|
||||
x = strtol(arg, &sep, 10);
|
||||
if (x < 1 || x > 64 || *sep != ':')
|
||||
fatal("Invalid time signature (%s)", arg);
|
||||
ts_num = x;
|
||||
x = strtol(++sep, NULL, 10);
|
||||
if (x < 1 || x > 64)
|
||||
fatal("Invalid time signature (%s)", arg);
|
||||
ts_div = x;
|
||||
}
|
||||
|
||||
/* create a queue, set up the default tempo */
|
||||
static void create_queue(void)
|
||||
{
|
||||
snd_seq_queue_tempo_t *tempo;
|
||||
|
||||
if (!snd_seq_has_queue_tempo_base(seq))
|
||||
tempo_base = 1000;
|
||||
|
||||
queue = snd_seq_alloc_named_queue(seq, "arecordmidi2");
|
||||
check_snd("create queue", queue);
|
||||
|
||||
snd_seq_queue_tempo_alloca(&tempo);
|
||||
if (tempo_base == 1000)
|
||||
snd_seq_queue_tempo_set_tempo(tempo, 60000000 / beats);
|
||||
else
|
||||
snd_seq_queue_tempo_set_tempo(tempo, (unsigned int)(6000000000ULL / beats));
|
||||
snd_seq_queue_tempo_set_ppq(tempo, ticks);
|
||||
snd_seq_queue_tempo_set_tempo_base(tempo, tempo_base);
|
||||
if (snd_seq_set_queue_tempo(seq, queue, tempo) < 0)
|
||||
fatal("Cannot set queue tempo (%d)", queue);
|
||||
}
|
||||
|
||||
/* connect to the input ports */
|
||||
static void connect_ports(void)
|
||||
{
|
||||
int i, err;
|
||||
|
||||
for (i = 0; i < port_count; ++i) {
|
||||
err = snd_seq_connect_from(seq, i + 1,
|
||||
ports[i].client, ports[i].port);
|
||||
check_snd("port connection", err);
|
||||
}
|
||||
}
|
||||
|
||||
/* write the given UMP packet */
|
||||
static void write_ump(FILE *file, const void *src)
|
||||
{
|
||||
const snd_ump_msg_hdr_t *h = src;
|
||||
const uint32_t *p = src;
|
||||
uint32_t v;
|
||||
int len;
|
||||
|
||||
len = snd_ump_packet_length(h->type);
|
||||
while (len-- > 0) {
|
||||
v = htobe32(*p++);
|
||||
fwrite(&v, 4, 1, file);
|
||||
}
|
||||
}
|
||||
|
||||
/* write a DC message */
|
||||
static void write_dcs(FILE *file, unsigned int t)
|
||||
{
|
||||
snd_ump_msg_dc_t d = {};
|
||||
|
||||
d.type = SND_UMP_MSG_TYPE_UTILITY;
|
||||
d.status = SND_UMP_UTILITY_MSG_STATUS_DC;
|
||||
d.ticks = t;
|
||||
write_ump(file, &d);
|
||||
}
|
||||
|
||||
/* write a DCTPQ message */
|
||||
static void write_dctpq(FILE *file)
|
||||
{
|
||||
snd_ump_msg_dctpq_t d = {};
|
||||
|
||||
d.type = SND_UMP_MSG_TYPE_UTILITY;
|
||||
d.status = SND_UMP_UTILITY_MSG_STATUS_DCTPQ;
|
||||
d.ticks = ticks;
|
||||
write_ump(file, &d);
|
||||
}
|
||||
|
||||
/* write a Start Clip message */
|
||||
static void write_start_clip(FILE *file)
|
||||
{
|
||||
snd_ump_msg_stream_gen_t d = {};
|
||||
|
||||
d.type = SND_UMP_MSG_TYPE_STREAM;
|
||||
d.status = SND_UMP_STREAM_MSG_STATUS_START_CLIP;
|
||||
write_ump(file, &d);
|
||||
}
|
||||
|
||||
/* write an End Clip message */
|
||||
static void write_end_clip(FILE *file)
|
||||
{
|
||||
snd_ump_msg_stream_gen_t d = {};
|
||||
|
||||
d.type = SND_UMP_MSG_TYPE_STREAM;
|
||||
d.status = SND_UMP_STREAM_MSG_STATUS_END_CLIP;
|
||||
write_ump(file, &d);
|
||||
}
|
||||
|
||||
/* write a Set Tempo message */
|
||||
static void write_tempo(FILE *file)
|
||||
{
|
||||
snd_ump_msg_set_tempo_t d = {};
|
||||
|
||||
d.type = SND_UMP_MSG_TYPE_FLEX_DATA;
|
||||
d.group = 0;
|
||||
d.format = SND_UMP_FLEX_DATA_MSG_FORMAT_SINGLE;
|
||||
d.addrs = SND_UMP_FLEX_DATA_MSG_ADDR_GROUP;
|
||||
d.status_bank = SND_UMP_FLEX_DATA_MSG_BANK_SETUP;
|
||||
d.status = SND_UMP_FLEX_DATA_MSG_STATUS_SET_TEMPO;
|
||||
d.tempo = (unsigned int)(6000000000ULL / beats);
|
||||
write_ump(file, &d);
|
||||
}
|
||||
|
||||
/* write a Set Time Signature message */
|
||||
static void write_time_sig(FILE *file)
|
||||
{
|
||||
snd_ump_msg_set_time_sig_t d = {};
|
||||
|
||||
d.type = SND_UMP_MSG_TYPE_FLEX_DATA;
|
||||
d.group = 0;
|
||||
d.format = SND_UMP_FLEX_DATA_MSG_FORMAT_SINGLE;
|
||||
d.addrs = SND_UMP_FLEX_DATA_MSG_ADDR_GROUP;
|
||||
d.status_bank = SND_UMP_FLEX_DATA_MSG_BANK_SETUP;
|
||||
d.status = SND_UMP_FLEX_DATA_MSG_STATUS_SET_TIME_SIGNATURE;
|
||||
d.numerator = ts_num;
|
||||
d.denominator = ts_div;
|
||||
d.num_notes = 8;
|
||||
write_ump(file, &d);
|
||||
}
|
||||
|
||||
/* record the delta time from the last event */
|
||||
static void delta_time(FILE *file, const snd_seq_ump_event_t *ev)
|
||||
{
|
||||
int diff = ev->time.tick - last_tick;
|
||||
|
||||
if (diff <= 0)
|
||||
return;
|
||||
write_dcs(file, diff);
|
||||
last_tick = ev->time.tick;
|
||||
}
|
||||
|
||||
static void record_event(FILE *file, const snd_seq_ump_event_t *ev)
|
||||
{
|
||||
/* ignore events without proper timestamps */
|
||||
if (ev->queue != queue || !snd_seq_ev_is_tick(ev) ||
|
||||
!snd_seq_ev_is_ump(ev))
|
||||
return;
|
||||
|
||||
delta_time(file, ev);
|
||||
write_ump(file, ev->ump);
|
||||
}
|
||||
|
||||
/* read a UMP raw (big-endian) packet, return the packet length in words */
|
||||
static int read_ump_raw(FILE *file, uint32_t *buf)
|
||||
{
|
||||
uint32_t v;
|
||||
int i, num;
|
||||
|
||||
if (fread(buf, 4, 1, file) != 1)
|
||||
return 0;
|
||||
v = be32toh(v);
|
||||
num = snd_ump_packet_length(snd_ump_msg_hdr_type(v));
|
||||
for (i = 1; i < num; i++) {
|
||||
if (fread(buf + i, 4, 1, file) != 1)
|
||||
return 0;
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
/* read the profile UMP data and write to the configuration */
|
||||
static void write_profiles(FILE *file)
|
||||
{
|
||||
FILE *fp;
|
||||
uint32_t ump[4];
|
||||
int len;
|
||||
|
||||
if (!profile_ump_file)
|
||||
return;
|
||||
|
||||
fp = fopen(profile_ump_file, "rb");
|
||||
if (!fp)
|
||||
fatal("cannot open the profile '%s'", profile_ump_file);
|
||||
|
||||
while (!feof(fp)) {
|
||||
len = read_ump_raw(fp, ump);
|
||||
if (!len)
|
||||
break;
|
||||
fwrite(ump, 4, len, file);
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
/* write Flex Data metadata text given by command lines */
|
||||
static void write_metadata(FILE *file, unsigned int type, const char *text)
|
||||
{
|
||||
int len = strlen(text), size;
|
||||
unsigned int format = SND_UMP_FLEX_DATA_MSG_FORMAT_START;
|
||||
|
||||
while (len > 0) {
|
||||
snd_ump_msg_flex_data_t d = {};
|
||||
|
||||
if (len <= 12) {
|
||||
if (format == SND_UMP_FLEX_DATA_MSG_FORMAT_CONTINUE)
|
||||
format = SND_UMP_FLEX_DATA_MSG_FORMAT_END;
|
||||
else
|
||||
format = SND_UMP_FLEX_DATA_MSG_FORMAT_SINGLE;
|
||||
size = len;
|
||||
} else {
|
||||
size = 12;
|
||||
}
|
||||
|
||||
d.meta.type = SND_UMP_MSG_TYPE_FLEX_DATA;
|
||||
d.meta.addrs = SND_UMP_FLEX_DATA_MSG_ADDR_GROUP;
|
||||
d.meta.status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA;
|
||||
d.meta.status = type;
|
||||
d.meta.format = format;
|
||||
|
||||
/* keep the data in big endian */
|
||||
d.raw[0] = htobe32(d.raw[0]);
|
||||
/* strings are copied as-is in big-endian */
|
||||
memcpy(d.meta.data, text, size);
|
||||
|
||||
fwrite(d.raw, 4, 4, file);
|
||||
len -= size;
|
||||
text += size;
|
||||
format = SND_UMP_FLEX_DATA_MSG_FORMAT_CONTINUE;
|
||||
}
|
||||
}
|
||||
|
||||
/* write MIDI Clip file header and the configuration packets */
|
||||
static void write_file_header(FILE *file)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* header id */
|
||||
fwrite("SMF2CLIP", 1, 8, file);
|
||||
|
||||
/* clip configuration header */
|
||||
write_profiles(file);
|
||||
|
||||
for (i = 0; i < metadata_num; i++)
|
||||
write_metadata(file, metadata_types[i], metadata_texts[i]);
|
||||
|
||||
/* first DCS */
|
||||
write_dcs(file, 0);
|
||||
write_dctpq(file);
|
||||
}
|
||||
|
||||
/* write start bar */
|
||||
static void start_bar(FILE *file)
|
||||
{
|
||||
int err;
|
||||
|
||||
/* start the queue */
|
||||
err = snd_seq_start_queue(seq, queue, NULL);
|
||||
check_snd("start queue", err);
|
||||
snd_seq_drain_output(seq);
|
||||
|
||||
write_start_clip(file);
|
||||
write_tempo(file);
|
||||
write_time_sig(file);
|
||||
}
|
||||
|
||||
static void help(const char *argv0)
|
||||
{
|
||||
fprintf(stderr, "Usage: %s [options] outputfile\n"
|
||||
"\nAvailable options:\n"
|
||||
" -h,--help this help\n"
|
||||
" -V,--version show version\n"
|
||||
" -p,--port=client:port,... source port(s)\n"
|
||||
" -b,--bpm=beats tempo in beats per minute\n"
|
||||
" -t,--ticks=ticks resolution in ticks per beat or frame\n"
|
||||
" -i,--timesig=nn:dd time signature\n"
|
||||
" -n,--num-events=events fixed number of events to record, then exit\n"
|
||||
" -u,--ump=version UMP MIDI version (1 or 2)\n"
|
||||
" -r,--interactive Interactive mode\n"
|
||||
" -s,--silent don't print messages\n"
|
||||
" -P,--profile=file configuration profile UMP\n"
|
||||
" --project=text put project name meta data text\n"
|
||||
" --song=text put song name meta data text\n"
|
||||
" --clip=text put MIDI clip name meta data text\n"
|
||||
" --copyright=text put copyright notice meta data text\n"
|
||||
" --composer=text put composer name meta data text\n"
|
||||
" --lyricist=text put lyricist name meta data text\n"
|
||||
" --arranger=text put arranger name meta data text\n"
|
||||
" --publisher=text put publisher name meta data text\n"
|
||||
" --publisher=text put publisher name meta data text\n"
|
||||
" --publisher=text put publisher name meta data text\n"
|
||||
" --performer=text put performer name meta data text\n"
|
||||
" --accompany=text put accompany performer name meta data text\n"
|
||||
" --date=text put recording date meta data text\n"
|
||||
" --location=text put recording location meta data text\n",
|
||||
argv0);
|
||||
}
|
||||
|
||||
static void version(void)
|
||||
{
|
||||
fputs("arecordmidi version " SND_UTIL_VERSION_STR "\n", stderr);
|
||||
}
|
||||
|
||||
static void sighandler(int sig ATTRIBUTE_UNUSED)
|
||||
{
|
||||
stop = 1;
|
||||
}
|
||||
|
||||
#define OPT_META_BIT 0x1000
|
||||
enum {
|
||||
OPT_META_PROJECT = 0x1001,
|
||||
OPT_META_SONG = 0x1002,
|
||||
OPT_META_CLIP = 0x1003,
|
||||
OPT_META_COPYRIGHT = 0x1004,
|
||||
OPT_META_COMPOSER = 0x1005,
|
||||
OPT_META_LYRICIST = 0x1006,
|
||||
OPT_META_ARRANGER = 0x1007,
|
||||
OPT_META_PUBLISHER = 0x1008,
|
||||
OPT_META_PERFORMER = 0x1009,
|
||||
OPT_META_ACCOMPANY = 0x100a,
|
||||
OPT_META_DATE = 0x100b,
|
||||
OPT_META_LOCATION = 0x100c,
|
||||
};
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
static const char short_options[] = "hVp:b:t:n:u:rsP:";
|
||||
static const struct option long_options[] = {
|
||||
{"help", 0, NULL, 'h'},
|
||||
{"version", 0, NULL, 'V'},
|
||||
{"port", 1, NULL, 'p'},
|
||||
{"bpm", 1, NULL, 'b'},
|
||||
{"ticks", 1, NULL, 't'},
|
||||
{"timesig", 1, NULL, 'i'},
|
||||
{"num-events", 1, NULL, 'n'},
|
||||
{"ump", 1, NULL, 'u'},
|
||||
{"interactive", 0, NULL, 'r'},
|
||||
{"silent", 0, NULL, 's'},
|
||||
{"profile", 1, NULL, 'P'},
|
||||
/* meta data texts */
|
||||
{"project", 1, NULL, OPT_META_PROJECT},
|
||||
{"song", 1, NULL, OPT_META_SONG},
|
||||
{"clip", 1, NULL, OPT_META_CLIP},
|
||||
{"copyright", 1, NULL, OPT_META_COPYRIGHT},
|
||||
{"composer", 1, NULL, OPT_META_COMPOSER},
|
||||
{"lyricist", 1, NULL, OPT_META_LYRICIST},
|
||||
{"arranger", 1, NULL, OPT_META_ARRANGER},
|
||||
{"publisher", 1, NULL, OPT_META_PUBLISHER},
|
||||
{"performer", 1, NULL, OPT_META_PERFORMER},
|
||||
{"accompany", 1, NULL, OPT_META_ACCOMPANY},
|
||||
{"date", 1, NULL, OPT_META_DATE},
|
||||
{"location", 1, NULL, OPT_META_LOCATION},
|
||||
{0}
|
||||
};
|
||||
|
||||
char *filename;
|
||||
FILE *file;
|
||||
struct pollfd *pfds;
|
||||
int npfds;
|
||||
int c, err;
|
||||
/* If |num_events| isn't specified, leave it at 0. */
|
||||
long num_events = 0;
|
||||
long events_received = 0;
|
||||
int start = 0;
|
||||
int interactive = 0;
|
||||
|
||||
init_seq();
|
||||
|
||||
while ((c = getopt_long(argc, argv, short_options,
|
||||
long_options, NULL)) != -1) {
|
||||
switch (c) {
|
||||
case 'h':
|
||||
help(argv[0]);
|
||||
return 0;
|
||||
case 'V':
|
||||
version();
|
||||
return 0;
|
||||
case 'p':
|
||||
parse_ports(optarg);
|
||||
break;
|
||||
case 'b':
|
||||
beats = atoi(optarg);
|
||||
if (beats < 4 || beats > 6000)
|
||||
fatal("Invalid tempo");
|
||||
break;
|
||||
case 't':
|
||||
ticks = atoi(optarg);
|
||||
if (ticks < 1 || ticks > 0x7fff)
|
||||
fatal("Invalid number of ticks");
|
||||
break;
|
||||
case 'i':
|
||||
time_signature(optarg);
|
||||
break;
|
||||
case 'n':
|
||||
err = 0;
|
||||
num_events = arg_parse_decimal_num(optarg, &err);
|
||||
if (err != 0) {
|
||||
fatal("Couldn't parse num_events argument: %s\n",
|
||||
strerror(-err));
|
||||
}
|
||||
if (num_events <= 0)
|
||||
fatal("num_events must be greater than 0");
|
||||
break;
|
||||
case 'u':
|
||||
midi_version = atoi(optarg);
|
||||
if (midi_version != 1 && midi_version != 2)
|
||||
fatal("Invalid MIDI version %d\n", midi_version);
|
||||
break;
|
||||
case 'r':
|
||||
interactive = 1;
|
||||
break;
|
||||
case 's':
|
||||
silent = 1;
|
||||
break;
|
||||
case 'P':
|
||||
profile_ump_file = optarg;
|
||||
break;
|
||||
default:
|
||||
if (c & OPT_META_BIT) {
|
||||
if (metadata_num >= MAX_METADATA)
|
||||
fatal("Too many metadata given");
|
||||
metadata_types[metadata_num] = c & 0x0f;
|
||||
metadata_texts[metadata_num] = optarg;
|
||||
metadata_num++;
|
||||
break;
|
||||
}
|
||||
help(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (optind >= argc) {
|
||||
fputs("Please specify a file to record to.\n", stderr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
create_queue();
|
||||
create_ump_client();
|
||||
if (port_count)
|
||||
connect_ports();
|
||||
|
||||
filename = argv[optind];
|
||||
|
||||
if (!strcmp(filename, "-")) {
|
||||
file = stdout;
|
||||
silent = 1; // imply silent mode
|
||||
} else {
|
||||
file = fopen(filename, "wb");
|
||||
if (!file)
|
||||
fatal("Cannot open %s - %s", filename, strerror(errno));
|
||||
}
|
||||
|
||||
write_file_header(file);
|
||||
if (interactive) {
|
||||
if (!silent) {
|
||||
printf("Press RETURN to start recording:");
|
||||
fflush(stdout);
|
||||
}
|
||||
} else {
|
||||
start_bar(file);
|
||||
start = 1;
|
||||
}
|
||||
|
||||
err = snd_seq_nonblock(seq, 1);
|
||||
check_snd("set nonblock mode", err);
|
||||
|
||||
signal(SIGINT, sighandler);
|
||||
signal(SIGTERM, sighandler);
|
||||
|
||||
npfds = snd_seq_poll_descriptors_count(seq, POLLIN);
|
||||
pfds = alloca(sizeof(*pfds) * (npfds + 1));
|
||||
for (;;) {
|
||||
snd_seq_poll_descriptors(seq, pfds, npfds, POLLIN);
|
||||
if (interactive) {
|
||||
pfds[npfds].fd = STDIN_FILENO;
|
||||
pfds[npfds].events = POLLIN | POLLERR | POLLNVAL;
|
||||
if (poll(pfds, npfds + 1, -1) < 0)
|
||||
break;
|
||||
if (pfds[npfds].revents & POLLIN) {
|
||||
while (!feof(stdin) && getchar() != '\n')
|
||||
;
|
||||
if (!start) {
|
||||
start_bar(file);
|
||||
start = 1;
|
||||
if (!silent) {
|
||||
printf("Press RETURN to stop recording:");
|
||||
fflush(stdout);
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
stop = 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (poll(pfds, npfds, -1) < 0)
|
||||
break;
|
||||
}
|
||||
|
||||
do {
|
||||
snd_seq_ump_event_t *event;
|
||||
|
||||
err = snd_seq_ump_event_input(seq, &event);
|
||||
if (err < 0)
|
||||
break;
|
||||
if (start && event) {
|
||||
record_event(file, event);
|
||||
events_received++;
|
||||
}
|
||||
} while (err > 0);
|
||||
if (stop)
|
||||
break;
|
||||
if (num_events && (events_received >= num_events))
|
||||
break;
|
||||
}
|
||||
|
||||
if (num_events && events_received < num_events) {
|
||||
if (!silent)
|
||||
fputs("Warning: Received signal before num_events\n", stdout);
|
||||
}
|
||||
|
||||
write_end_clip(file);
|
||||
if (file != stdout)
|
||||
fclose(file);
|
||||
snd_seq_close(seq);
|
||||
return 0;
|
||||
}
|
|
@ -29,9 +29,7 @@
|
|||
#include <poll.h>
|
||||
#include <alsa/asoundlib.h>
|
||||
#include "version.h"
|
||||
#ifdef HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION
|
||||
#include <alsa/ump_msg.h>
|
||||
#endif
|
||||
|
||||
enum {
|
||||
VIEW_RAW, VIEW_NORMALIZED, VIEW_PERCENT
|
||||
|
@ -41,11 +39,7 @@ static snd_seq_t *seq;
|
|||
static int port_count;
|
||||
static snd_seq_addr_t *ports;
|
||||
static volatile sig_atomic_t stop = 0;
|
||||
#ifdef HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION
|
||||
static int ump_version;
|
||||
#else
|
||||
#define ump_version 0
|
||||
#endif
|
||||
static int view_mode = VIEW_RAW;
|
||||
|
||||
/* prints an error message to stderr, and dies */
|
||||
|
@ -368,7 +362,6 @@ static void dump_event(const snd_seq_event_t *ev)
|
|||
}
|
||||
}
|
||||
|
||||
#ifdef HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION
|
||||
static int group_number(unsigned char c)
|
||||
{
|
||||
if (view_mode != VIEW_RAW)
|
||||
|
@ -442,7 +435,7 @@ static const char *midi2_velocity(unsigned int v)
|
|||
snprintf(tmp, sizeof(tmp), "%.2f",
|
||||
((double)v * 64.0) / 0x8000);
|
||||
else
|
||||
snprintf(tmp, sizeof(tmp), ".2%f",
|
||||
snprintf(tmp, sizeof(tmp), "%.2f",
|
||||
((double)(v - 0x8000) * 63.0) / 0x7fff + 64.0);
|
||||
return tmp;
|
||||
} else if (view_mode == VIEW_PERCENT) {
|
||||
|
@ -595,6 +588,427 @@ static void dump_ump_midi2_event(const unsigned int *ump)
|
|||
printf("\n");
|
||||
}
|
||||
|
||||
static void dump_ump_utility_event(const unsigned int *ump)
|
||||
{
|
||||
unsigned char status = snd_ump_msg_status(ump);
|
||||
unsigned int val = *ump & 0xfffff;
|
||||
|
||||
printf(" ");
|
||||
switch (status) {
|
||||
case SND_UMP_UTILITY_MSG_STATUS_NOOP:
|
||||
printf("Noop\n");
|
||||
break;
|
||||
case SND_UMP_UTILITY_MSG_STATUS_JR_CLOCK:
|
||||
printf("JR Clock value %d\n", val);
|
||||
break;
|
||||
case SND_UMP_UTILITY_MSG_STATUS_JR_TSTAMP:
|
||||
printf("JR Timestamp value %d\n", val);
|
||||
break;
|
||||
case SND_UMP_UTILITY_MSG_STATUS_DCTPQ:
|
||||
printf("DCTPQ value %d\n", val);
|
||||
break;
|
||||
case SND_UMP_UTILITY_MSG_STATUS_DC:
|
||||
printf("DC Ticks value %d\n", val);
|
||||
break;
|
||||
default:
|
||||
printf("UMP Utility event: status = %d, 0x%08x\n",
|
||||
status, *ump);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void dump_ump_system_event(const unsigned int *ump)
|
||||
{
|
||||
const snd_ump_msg_system_t *m = (const snd_ump_msg_system_t *)ump;
|
||||
|
||||
printf("Group %2d, ", group_number(m->group));
|
||||
switch (m->status) {
|
||||
case SND_UMP_MSG_MIDI_TIME_CODE:
|
||||
printf("MIDI Time Code value %d\n", m->parm1);
|
||||
break;
|
||||
case SND_UMP_MSG_SONG_POSITION:
|
||||
printf("Song position pointer value %d\n",
|
||||
((unsigned int)m->parm2 << 7) | m->parm1);
|
||||
break;
|
||||
case SND_UMP_MSG_SONG_SELECT:
|
||||
printf("Song select value %d\n", m->parm1);
|
||||
break;
|
||||
case SND_UMP_MSG_TUNE_REQUEST:
|
||||
printf("Tune request\n");
|
||||
break;
|
||||
case SND_UMP_MSG_TIMING_CLOCK:
|
||||
printf("Timing clock\n");
|
||||
break;
|
||||
case SND_UMP_MSG_START:
|
||||
printf("Start\n");
|
||||
break;
|
||||
case SND_UMP_MSG_CONTINUE:
|
||||
printf("Continue\n");
|
||||
break;
|
||||
case SND_UMP_MSG_STOP:
|
||||
printf("Stop\n");
|
||||
break;
|
||||
case SND_UMP_MSG_ACTIVE_SENSING:
|
||||
printf("Active sensing\n");
|
||||
break;
|
||||
case SND_UMP_MSG_RESET:
|
||||
printf("Reset\n");
|
||||
break;
|
||||
default:
|
||||
printf("UMP System event: status = %d, 0x%08x\n",
|
||||
m->status, *ump);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned char ump_sysex7_data(const unsigned int *ump,
|
||||
unsigned int offset)
|
||||
{
|
||||
return snd_ump_get_byte(ump, offset + 2);
|
||||
}
|
||||
|
||||
static void dump_ump_sysex_status(const char *prefix, unsigned int status)
|
||||
{
|
||||
printf("%s ", prefix);
|
||||
switch (status) {
|
||||
case SND_UMP_SYSEX_STATUS_SINGLE:
|
||||
printf("Single ");
|
||||
break;
|
||||
case SND_UMP_SYSEX_STATUS_START:
|
||||
printf("Start ");
|
||||
break;
|
||||
case SND_UMP_SYSEX_STATUS_CONTINUE:
|
||||
printf("Continue");
|
||||
break;
|
||||
case SND_UMP_SYSEX_STATUS_END:
|
||||
printf("End ");
|
||||
break;
|
||||
default:
|
||||
printf("(0x%04x)", status);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void dump_ump_sysex_event(const unsigned int *ump)
|
||||
{
|
||||
int i, length;
|
||||
|
||||
printf("Group %2d, ", group_number(snd_ump_msg_group(ump)));
|
||||
dump_ump_sysex_status("SysEx", snd_ump_sysex_msg_status(ump));
|
||||
length = snd_ump_sysex_msg_length(ump);
|
||||
printf(" length %d ", length);
|
||||
if (length > 6)
|
||||
length = 6;
|
||||
for (i = 0; i < length; i++)
|
||||
printf("%s%02x", i ? ":" : "", ump_sysex7_data(ump, i));
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
static unsigned char ump_sysex8_data(const unsigned int *ump,
|
||||
unsigned int offset)
|
||||
{
|
||||
return snd_ump_get_byte(ump, offset + 3);
|
||||
}
|
||||
|
||||
static void dump_ump_sysex8_event(const unsigned int *ump)
|
||||
{
|
||||
int i, length;
|
||||
|
||||
printf("Group %2d, ", group_number(snd_ump_msg_group(ump)));
|
||||
dump_ump_sysex_status("SysEx8", snd_ump_sysex_msg_status(ump));
|
||||
length = snd_ump_sysex_msg_length(ump);
|
||||
printf(" length %d ", length);
|
||||
printf(" stream %d ", (ump[0] >> 8) & 0xff);
|
||||
if (length > 13)
|
||||
length = 13;
|
||||
for (i = 0; i < length; i++)
|
||||
printf("%s%02x", i ? ":" : "", ump_sysex8_data(ump, i));
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
static void dump_ump_mixed_data_event(const unsigned int *ump)
|
||||
{
|
||||
const snd_ump_msg_mixed_data_t *m =
|
||||
(const snd_ump_msg_mixed_data_t *)ump;
|
||||
int i;
|
||||
|
||||
printf("Group %2d, ", group_number(snd_ump_msg_group(ump)));
|
||||
switch (snd_ump_sysex_msg_status(ump)) {
|
||||
case SND_UMP_MIXED_DATA_SET_STATUS_HEADER:
|
||||
printf("MDS Header id=0x%x, bytes=%d, chunk=%d/%d, manufacturer=0x%04x, device=0x%04x, sub_id=0x%04x 0x%04x\n",
|
||||
m->header.mds_id, m->header.bytes,
|
||||
m->header.chunk, m->header.chunks,
|
||||
m->header.manufacturer, m->header.device,
|
||||
m->header.sub_id_1, m->header.sub_id_2);
|
||||
break;
|
||||
case SND_UMP_MIXED_DATA_SET_STATUS_PAYLOAD:
|
||||
printf("MDS Payload id=0x%x, ", m->payload.mds_id);
|
||||
for (i = 0; i < 14; i++)
|
||||
printf("%s%02x", i ? ":" : "",
|
||||
snd_ump_get_byte(ump, i + 2));
|
||||
printf("\n");
|
||||
break;
|
||||
default:
|
||||
printf("Extended Data (status 0x%x)\n",
|
||||
snd_ump_sysex_msg_status(ump));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void dump_ump_extended_data_event(const unsigned int *ump)
|
||||
{
|
||||
unsigned char status = snd_ump_sysex_msg_status(ump);
|
||||
|
||||
if (status < 4)
|
||||
dump_ump_sysex8_event(ump);
|
||||
else
|
||||
dump_ump_mixed_data_event(ump);
|
||||
}
|
||||
|
||||
static void print_ump_string(const unsigned int *ump, unsigned int fmt,
|
||||
unsigned int offset, int maxlen)
|
||||
{
|
||||
static const char *fmtstr[4] = { "Single", "Start", "Cont", "End" };
|
||||
unsigned char buf[32];
|
||||
int i = 0;
|
||||
|
||||
do {
|
||||
buf[i] = snd_ump_get_byte(ump, offset);
|
||||
if (!buf[i])
|
||||
break;
|
||||
if (buf[i] < 0x20)
|
||||
buf[i] = '.';
|
||||
offset++;
|
||||
} while (++i < maxlen);
|
||||
buf[i] = 0;
|
||||
|
||||
printf("%6s: %s", fmtstr[fmt], buf);
|
||||
}
|
||||
|
||||
static void dump_ump_stream_event(const unsigned int *ump)
|
||||
{
|
||||
const snd_ump_msg_stream_t *s = (const snd_ump_msg_stream_t *)ump;
|
||||
|
||||
printf(" "); /* stream message is groupless */
|
||||
switch (s->gen.status) {
|
||||
case SND_UMP_STREAM_MSG_STATUS_EP_DISCOVERY:
|
||||
printf("EP Discovery ver=%d/%d, filter=0x%x\n",
|
||||
(ump[0] >> 8) & 0xff, ump[0] & 0xff, ump[1] & 0xff);
|
||||
break;
|
||||
case SND_UMP_STREAM_MSG_STATUS_EP_INFO:
|
||||
printf("EP Info ver=%d/%d, static=%d, fb#=%d, midi2=%d, midi1=%d, rxjr=%d, txjr=%d\n",
|
||||
(ump[0] >> 8) & 0xff, ump[0] & 0xff, (ump[1] >> 31),
|
||||
(ump[1] >> 24) & 0x7f,
|
||||
(ump[1] >> 9) & 1, (ump[1] >> 8) & 1,
|
||||
(ump[1] >> 1) & 1, ump[1] & 1);
|
||||
break;
|
||||
case SND_UMP_STREAM_MSG_STATUS_DEVICE_INFO:
|
||||
printf("Device Info sysid=%02x:%02x:%02x, family=%02x:%02x, model=%02x:%02x, rev=%02x:%02x:%02x:%02x\n",
|
||||
(ump[1] >> 16) & 0x7f, (ump[1] >> 8) & 0x7f, ump[1] & 0x7f,
|
||||
(ump[2] >> 16) & 0x7f, (ump[2] >> 24) & 0x7f,
|
||||
ump[2] & 0x7f, (ump[2] >> 8) & 0x7f,
|
||||
(ump[3] >> 24) & 0x7f, (ump[3] >> 16) & 0x7f,
|
||||
(ump[3] >> 8) & 0x7f, ump[3] & 0x7f);
|
||||
break;
|
||||
case SND_UMP_STREAM_MSG_STATUS_EP_NAME:
|
||||
printf("EP Name ");
|
||||
print_ump_string(ump, (ump[0] >> 26) & 3, 2, 14);
|
||||
printf("\n");
|
||||
break;
|
||||
case SND_UMP_STREAM_MSG_STATUS_PRODUCT_ID:
|
||||
printf("Product Id ");
|
||||
print_ump_string(ump, (ump[0] >> 26) & 3, 2, 14);
|
||||
printf("\n");
|
||||
break;
|
||||
case SND_UMP_STREAM_MSG_STATUS_STREAM_CFG_REQUEST:
|
||||
printf("Stream Cfg Req protocl=%d, rxjr=%d, txjr=%d\n",
|
||||
(ump[0] >> 8) & 0xff, (ump[0] >> 1) & 1, ump[0] & 1);
|
||||
break;
|
||||
case SND_UMP_STREAM_MSG_STATUS_STREAM_CFG:
|
||||
printf("Stream Cfg protocl=%d, rxjr=%d, txjr=%d\n",
|
||||
(ump[0] >> 8) & 0xff, (ump[0] >> 1) & 1, ump[0] & 1);
|
||||
break;
|
||||
case SND_UMP_STREAM_MSG_STATUS_FB_DISCOVERY:
|
||||
printf("FB Discovery fb#=%d, filter=0x%x\n",
|
||||
(ump[0] >> 8) & 0xff, ump[0] & 0xff);
|
||||
break;
|
||||
case SND_UMP_STREAM_MSG_STATUS_FB_INFO:
|
||||
printf("FB Info fb#=%d, active=%d, ui=%d, MIDI1=%d, dir=%d, group=%d-%d, MIDI-CI=%d, SysEx8=%d\n",
|
||||
(ump[0] >> 8) & 0x7f, (ump[0] >> 15) & 1,
|
||||
(ump[0] >> 4) & 3, (ump[0] >> 2) & 3, ump[0] & 3,
|
||||
(ump[1] >> 24) & 0xff, (ump[1] >> 16) & 0xff,
|
||||
(ump[1] >> 8) * 0xff, ump[1] & 0xff);
|
||||
break;
|
||||
case SND_UMP_STREAM_MSG_STATUS_FB_NAME:
|
||||
printf("Product Id ");
|
||||
printf("FB Name #%02d ", (ump[0] >> 8) & 0xff);
|
||||
print_ump_string(ump, (ump[0] >> 26) & 3, 3, 13);
|
||||
printf("\n");
|
||||
break;
|
||||
case SND_UMP_STREAM_MSG_STATUS_START_CLIP:
|
||||
printf("Start Clip\n");
|
||||
break;
|
||||
case SND_UMP_STREAM_MSG_STATUS_END_CLIP:
|
||||
printf("End Clip\n");
|
||||
break;
|
||||
default:
|
||||
printf("UMP Stream event: status = %d, 0x%08x:0x%08x:0x%08x:0x%08x\n",
|
||||
s->gen.status, ump[0], ump[1], ump[2], ump[3]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
struct flexdata_text_prefix {
|
||||
unsigned char status_bank;
|
||||
unsigned char status;
|
||||
const char *prefix;
|
||||
};
|
||||
|
||||
static struct flexdata_text_prefix text_prefix[] = {
|
||||
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA,
|
||||
.status = SND_UMP_FLEX_DATA_MSG_STATUS_PROJECT_NAME,
|
||||
.prefix = "Project" },
|
||||
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA,
|
||||
.status = SND_UMP_FLEX_DATA_MSG_STATUS_SONG_NAME,
|
||||
.prefix = "Song" },
|
||||
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA,
|
||||
.status = SND_UMP_FLEX_DATA_MSG_STATUS_MIDI_CLIP_NAME,
|
||||
.prefix = "MIDI Clip" },
|
||||
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA,
|
||||
.status = SND_UMP_FLEX_DATA_MSG_STATUS_COPYRIGHT_NOTICE,
|
||||
.prefix = "Copyright" },
|
||||
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA,
|
||||
.status = SND_UMP_FLEX_DATA_MSG_STATUS_COMPOSER_NAME,
|
||||
.prefix = "Composer" },
|
||||
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA,
|
||||
.status = SND_UMP_FLEX_DATA_MSG_STATUS_LYRICIST_NAME,
|
||||
.prefix = "Lyricist" },
|
||||
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA,
|
||||
.status = SND_UMP_FLEX_DATA_MSG_STATUS_ARRANGER_NAME,
|
||||
.prefix = "Arranger" },
|
||||
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA,
|
||||
.status = SND_UMP_FLEX_DATA_MSG_STATUS_PUBLISHER_NAME,
|
||||
.prefix = "Publisher" },
|
||||
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA,
|
||||
.status = SND_UMP_FLEX_DATA_MSG_STATUS_PRIMARY_PERFORMER,
|
||||
.prefix = "Performer" },
|
||||
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA,
|
||||
.status = SND_UMP_FLEX_DATA_MSG_STATUS_ACCOMPANY_PERFORMAER,
|
||||
.prefix = "Accompany Performer" },
|
||||
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA,
|
||||
.status = SND_UMP_FLEX_DATA_MSG_STATUS_RECORDING_DATE,
|
||||
.prefix = "Recording Date" },
|
||||
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_METADATA,
|
||||
.status = SND_UMP_FLEX_DATA_MSG_STATUS_RECORDING_LOCATION,
|
||||
.prefix = "Recording Location" },
|
||||
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_PERF_TEXT,
|
||||
.status = SND_UMP_FLEX_DATA_MSG_STATUS_LYRICS,
|
||||
.prefix = "Lyrics" },
|
||||
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_PERF_TEXT,
|
||||
.status = SND_UMP_FLEX_DATA_MSG_STATUS_LYRICS_LANGUAGE,
|
||||
.prefix = "Lyrics Language" },
|
||||
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_PERF_TEXT,
|
||||
.status = SND_UMP_FLEX_DATA_MSG_STATUS_RUBY,
|
||||
.prefix = "Ruby" },
|
||||
{ .status_bank = SND_UMP_FLEX_DATA_MSG_BANK_PERF_TEXT,
|
||||
.status = SND_UMP_FLEX_DATA_MSG_STATUS_RUBY_LANGUAGE,
|
||||
.prefix = "Ruby Language" },
|
||||
{}
|
||||
};
|
||||
|
||||
static const char *ump_meta_prefix(const snd_ump_msg_flex_data_t *fh)
|
||||
{
|
||||
static char buf[32];
|
||||
int i;
|
||||
|
||||
for (i = 0; text_prefix[i].status_bank; i++) {
|
||||
if (text_prefix[i].status_bank == fh->meta.status_bank &&
|
||||
text_prefix[i].status == fh->meta.status)
|
||||
return text_prefix[i].prefix;
|
||||
}
|
||||
|
||||
sprintf(buf, "(%d:%d)", fh->meta.status_bank, fh->meta.status);
|
||||
return buf;
|
||||
}
|
||||
|
||||
static void dump_ump_flex_data_event(const unsigned int *ump)
|
||||
{
|
||||
const snd_ump_msg_flex_data_t *fh =
|
||||
(const snd_ump_msg_flex_data_t *)ump;
|
||||
|
||||
printf("Group %2d, ", group_number(snd_ump_msg_group(ump)));
|
||||
|
||||
if (fh->meta.status_bank == SND_UMP_FLEX_DATA_MSG_BANK_SETUP &&
|
||||
fh->meta.status == SND_UMP_FLEX_DATA_MSG_STATUS_SET_TEMPO) {
|
||||
printf("UMP Set Tempo value %d\n", fh->set_tempo.tempo);
|
||||
return;
|
||||
}
|
||||
|
||||
if (fh->meta.status_bank == SND_UMP_FLEX_DATA_MSG_BANK_SETUP &&
|
||||
fh->meta.status == SND_UMP_FLEX_DATA_MSG_STATUS_SET_TIME_SIGNATURE) {
|
||||
printf("UMP Set Time Signature value %d / %d, num_notes %d\n",
|
||||
fh->set_time_sig.numerator, fh->set_time_sig.denominator,
|
||||
fh->set_time_sig.num_notes);
|
||||
return;
|
||||
}
|
||||
|
||||
if (fh->meta.status_bank == SND_UMP_FLEX_DATA_MSG_BANK_SETUP &&
|
||||
fh->meta.status == SND_UMP_FLEX_DATA_MSG_STATUS_SET_METRONOME) {
|
||||
printf("UMP Set Metronome clock %d, bar %d/%d/%d, sub %d/%d\n",
|
||||
fh->set_metronome.clocks_primary,
|
||||
fh->set_metronome.bar_accent_1,
|
||||
fh->set_metronome.bar_accent_2,
|
||||
fh->set_metronome.bar_accent_3,
|
||||
fh->set_metronome.subdivision_1,
|
||||
fh->set_metronome.subdivision_2);
|
||||
return;
|
||||
}
|
||||
|
||||
if (fh->meta.status_bank == SND_UMP_FLEX_DATA_MSG_BANK_SETUP &&
|
||||
fh->meta.status == SND_UMP_FLEX_DATA_MSG_STATUS_SET_KEY_SIGNATURE) {
|
||||
printf("UMP Set Key Signature sharps/flats %d, tonic %d\n",
|
||||
fh->set_key_sig.sharps_flats,
|
||||
fh->set_key_sig.tonic_note);
|
||||
return;
|
||||
}
|
||||
|
||||
if (fh->meta.status_bank == SND_UMP_FLEX_DATA_MSG_BANK_SETUP &&
|
||||
fh->meta.status == SND_UMP_FLEX_DATA_MSG_STATUS_SET_CHORD_NAME) {
|
||||
printf("UMP Set Chord Name tonic %d %d %d, alt1 %d/%d, alt2 %d/%d, alt3 %d/%d, alt4 %d/%d, bass %d %d %d, alt1 %d/%d alt2 %d/%d\n",
|
||||
fh->set_chord_name.tonic_sharp,
|
||||
fh->set_chord_name.chord_tonic,
|
||||
fh->set_chord_name.chord_type,
|
||||
fh->set_chord_name.alter1_type,
|
||||
fh->set_chord_name.alter1_degree,
|
||||
fh->set_chord_name.alter2_type,
|
||||
fh->set_chord_name.alter2_degree,
|
||||
fh->set_chord_name.alter3_type,
|
||||
fh->set_chord_name.alter3_degree,
|
||||
fh->set_chord_name.alter4_type,
|
||||
fh->set_chord_name.alter4_degree,
|
||||
fh->set_chord_name.bass_sharp,
|
||||
fh->set_chord_name.bass_note,
|
||||
fh->set_chord_name.bass_type,
|
||||
fh->set_chord_name.bass_alter1_type,
|
||||
fh->set_chord_name.bass_alter1_type,
|
||||
fh->set_chord_name.bass_alter2_degree,
|
||||
fh->set_chord_name.bass_alter2_degree);
|
||||
return;
|
||||
}
|
||||
|
||||
if (fh->meta.status_bank == SND_UMP_FLEX_DATA_MSG_BANK_METADATA ||
|
||||
fh->meta.status_bank == SND_UMP_FLEX_DATA_MSG_BANK_PERF_TEXT) {
|
||||
printf("Meta (%s) ", ump_meta_prefix(fh));
|
||||
print_ump_string(ump, fh->meta.format, 4, 12);
|
||||
printf("\n");
|
||||
return;
|
||||
}
|
||||
|
||||
printf("Flex Data: channel = %d, format = %d, addrs = %d, status_bank = %d, status = %d\n",
|
||||
fh->meta.channel, fh->meta.format, fh->meta.addrs,
|
||||
fh->meta.status_bank, fh->meta.status);
|
||||
}
|
||||
|
||||
static void dump_ump_event(const snd_seq_ump_event_t *ev)
|
||||
{
|
||||
if (!snd_seq_ev_is_ump(ev)) {
|
||||
|
@ -605,12 +1019,30 @@ static void dump_ump_event(const snd_seq_ump_event_t *ev)
|
|||
printf("%3d:%-3d ", ev->source.client, ev->source.port);
|
||||
|
||||
switch (snd_ump_msg_type(ev->ump)) {
|
||||
case SND_UMP_MSG_TYPE_UTILITY:
|
||||
dump_ump_utility_event(ev->ump);
|
||||
break;
|
||||
case SND_UMP_MSG_TYPE_SYSTEM:
|
||||
dump_ump_system_event(ev->ump);
|
||||
break;
|
||||
case SND_UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE:
|
||||
dump_ump_midi1_event(ev->ump);
|
||||
break;
|
||||
case SND_UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE:
|
||||
dump_ump_midi2_event(ev->ump);
|
||||
break;
|
||||
case SND_UMP_MSG_TYPE_DATA:
|
||||
dump_ump_sysex_event(ev->ump);
|
||||
break;
|
||||
case SND_UMP_MSG_TYPE_EXTENDED_DATA:
|
||||
dump_ump_extended_data_event(ev->ump);
|
||||
break;
|
||||
case SND_UMP_MSG_TYPE_FLEX_DATA:
|
||||
dump_ump_flex_data_event(ev->ump);
|
||||
break;
|
||||
case SND_UMP_MSG_TYPE_STREAM:
|
||||
dump_ump_stream_event(ev->ump);
|
||||
break;
|
||||
default:
|
||||
printf("UMP event: type = %d, group = %d, status = %d, 0x%08x\n",
|
||||
snd_ump_msg_type(ev->ump),
|
||||
|
@ -620,7 +1052,6 @@ static void dump_ump_event(const snd_seq_ump_event_t *ev)
|
|||
break;
|
||||
}
|
||||
}
|
||||
#endif /* HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION */
|
||||
|
||||
static void list_ports(void)
|
||||
{
|
||||
|
@ -663,10 +1094,8 @@ static void help(const char *argv0)
|
|||
" -N,--normalized-view show normalized values\n"
|
||||
" -P,--percent-view show percent values\n"
|
||||
" -R,--raw-view show raw values (default)\n"
|
||||
#ifdef HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION
|
||||
" -u,--ump=version set client MIDI version (0=legacy, 1= UMP MIDI 1.0, 2=UMP MIDI2.0)\n"
|
||||
" -r,--raw do not convert UMP and legacy events\n"
|
||||
#endif
|
||||
" -p,--port=client:port,... source port(s)\n",
|
||||
argv0);
|
||||
}
|
||||
|
@ -683,11 +1112,7 @@ static void sighandler(int sig ATTRIBUTE_UNUSED)
|
|||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
static const char short_options[] = "hVlp:NPR"
|
||||
#ifdef HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION
|
||||
"u:r"
|
||||
#endif
|
||||
;
|
||||
static const char short_options[] = "hVlp:NPRu:r";
|
||||
static const struct option long_options[] = {
|
||||
{"help", 0, NULL, 'h'},
|
||||
{"version", 0, NULL, 'V'},
|
||||
|
@ -696,10 +1121,8 @@ int main(int argc, char *argv[])
|
|||
{"normalized-view", 0, NULL, 'N'},
|
||||
{"percent-view", 0, NULL, 'P'},
|
||||
{"raw-view", 0, NULL, 'R'},
|
||||
#ifdef HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION
|
||||
{"ump", 1, NULL, 'u'},
|
||||
{"raw", 0, NULL, 'r'},
|
||||
#endif
|
||||
{0}
|
||||
};
|
||||
|
||||
|
@ -734,15 +1157,15 @@ int main(int argc, char *argv[])
|
|||
case 'N':
|
||||
view_mode = VIEW_NORMALIZED;
|
||||
break;
|
||||
#ifdef HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION
|
||||
case 'u':
|
||||
ump_version = atoi(optarg);
|
||||
if (ump_version < 0 || ump_version > 2)
|
||||
fatal("Invalid UMP version %d", ump_version);
|
||||
snd_seq_set_client_midi_version(seq, ump_version);
|
||||
break;
|
||||
case 'r':
|
||||
snd_seq_set_client_ump_conversion(seq, 0);
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
help(argv[0]);
|
||||
return 1;
|
||||
|
@ -784,7 +1207,6 @@ int main(int argc, char *argv[])
|
|||
break;
|
||||
for (;;) {
|
||||
snd_seq_event_t *event;
|
||||
#ifdef HAVE_SEQ_CLIENT_INFO_GET_MIDI_VERSION
|
||||
snd_seq_ump_event_t *ump_ev;
|
||||
if (ump_version > 0) {
|
||||
err = snd_seq_ump_event_input(seq, &ump_ev);
|
||||
|
@ -794,7 +1216,6 @@ int main(int argc, char *argv[])
|
|||
dump_ump_event(ump_ev);
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
|
||||
err = snd_seq_event_input(seq, &event);
|
||||
if (err < 0)
|
||||
|
|
5
seq/aseqsend/Makefile.am
Normal file
5
seq/aseqsend/Makefile.am
Normal file
|
@ -0,0 +1,5 @@
|
|||
AM_CPPFLAGS = -I$(top_srcdir)/include
|
||||
EXTRA_DIST = aseqsend.1
|
||||
|
||||
bin_PROGRAMS = aseqsend
|
||||
man_MANS = aseqsend.1
|
77
seq/aseqsend/aseqsend.1
Normal file
77
seq/aseqsend/aseqsend.1
Normal file
|
@ -0,0 +1,77 @@
|
|||
.TH ASEQSEND 1 "11 Mar 2024"
|
||||
|
||||
.SH NAME
|
||||
aseqsend \- send arbitrary messages to selected ALSA MIDI seqencer port
|
||||
|
||||
.SH SYNOPSIS
|
||||
\fBaseqsend\fP \-p client:port -s file-name
|
||||
.br
|
||||
\fBaseqsend\fP \-p client:port "hex encoded byte-string"
|
||||
|
||||
.SH DESCRIPTION
|
||||
\fBaseqsend\fP is a command-line utility which allows one to send
|
||||
SysEx (system exclusive) data to ALSA MIDI sequencer port.
|
||||
It can also send any other MIDI commands.
|
||||
Messages to be sent can be given in the last argument as hex encoded
|
||||
byte string or can be read from raw binary file.
|
||||
When sending several SysEx messages at once there is a delay of 1ms
|
||||
after each message as default and can be set to different value with
|
||||
option \-i.
|
||||
|
||||
A client can be specified by its number, its name, or a prefix of its
|
||||
name. A port is specified by its number; for port 0 of a client, the
|
||||
":0" part of the port specification can be omitted.
|
||||
|
||||
\fBaseqsend\fP can send UMP packets as MIDI 2.0 device by specifying
|
||||
via \-u option as well, while the default operation is the legacy MIDI
|
||||
1.0 byte stream.
|
||||
|
||||
.SH OPTIONS
|
||||
|
||||
.TP
|
||||
\fI\-h, \-\-help\fP
|
||||
Prints a list of options.
|
||||
|
||||
.TP
|
||||
\fI\-V, \-\-version\fP
|
||||
Prints the current version.
|
||||
|
||||
.TP
|
||||
\fI\-l, \-\-list\FP
|
||||
Prints a list of possible output ports.
|
||||
|
||||
.TP
|
||||
\fI\-v, \-\-verbose\fP
|
||||
Prints number of bytes actually sent
|
||||
|
||||
.TP
|
||||
\fI\-p, -\-port=client:port\fP
|
||||
Target port by number or name
|
||||
|
||||
.TP
|
||||
\fI\-s, \-\-file=filename\fP
|
||||
Send raw binary data from given file name
|
||||
|
||||
.TP
|
||||
\fI\-i, \-\-interval=msec\fP
|
||||
Interval between SysEx messages in milliseconds
|
||||
|
||||
.TP
|
||||
\fI\-u, \-\-ump=version\fP
|
||||
Specify the MIDI version. 0 for the legacy MIDI 1.0 (default),
|
||||
1 for UMP MIDI 1.0 protocol and 2 for UMP MIDI 2.0 protocol.
|
||||
|
||||
When UMP MIDI 1.0 or MIDI 2.0 protocol is specified, \fBaseqsend\fP
|
||||
reads the input as raw UMP packets, 4 each byte in big endian.
|
||||
|
||||
.SH EXAMPLES
|
||||
|
||||
\fBaseqsend -p 128:0 "F0 41 10 00 00 64 12 18 00 21 06 59 41 59 4E F7"\fP
|
||||
|
||||
\fBaseqsend -p 128:0 -s I7BulkDump.syx\fP
|
||||
|
||||
.SH SEE ALSO
|
||||
\fBaseqdump(1)\fP
|
||||
|
||||
.SH AUTHOR
|
||||
Miroslav Kovac <mixxoo@gmail.com>
|
458
seq/aseqsend/aseqsend.c
Normal file
458
seq/aseqsend/aseqsend.c
Normal file
|
@ -0,0 +1,458 @@
|
|||
/*
|
||||
* aseqsend.c - send arbitrary MIDI messages to selected ALSA MIDI seqencer port
|
||||
*
|
||||
* Copyright (c) 2005 Clemens Ladisch <clemens@ladisch.de>
|
||||
* Copyright (c) 2024 Miroslav Kovac <mixxoo@gmail.com>
|
||||
*
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include "aconfig.h"
|
||||
#include "version.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <getopt.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
#include <alsa/asoundlib.h>
|
||||
#include <alsa/ump_msg.h>
|
||||
|
||||
typedef unsigned char mbyte_t;
|
||||
|
||||
static snd_seq_t *seq;
|
||||
static char *port_name = NULL;
|
||||
static char *send_file_name = NULL;
|
||||
static char *send_hex;
|
||||
static mbyte_t *send_data;
|
||||
static snd_seq_addr_t addr;
|
||||
static int send_data_length;
|
||||
static int sent_data_c;
|
||||
static int ump_version;
|
||||
static int sysex_interval = 1000; //us
|
||||
static snd_midi_event_t *edev;
|
||||
|
||||
static void error(const char *format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, format);
|
||||
vfprintf(stderr, format, ap);
|
||||
va_end(ap);
|
||||
putc('\n', stderr);
|
||||
}
|
||||
|
||||
/* prints an error message to stderr, and dies */
|
||||
static void fatal(const char *msg, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, msg);
|
||||
vfprintf(stderr, msg, ap);
|
||||
va_end(ap);
|
||||
fputc('\n', stderr);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
static void usage(void)
|
||||
{
|
||||
printf(
|
||||
"\nUsage: aseqsend -p target-port -s file-name|\"hex encoded bytes\"\n\n"
|
||||
" -h,--help this help\n"
|
||||
" -V,--version print current version\n"
|
||||
" -v,--verbose verbose mode\n"
|
||||
" -l,--list list all sequencer ports\n"
|
||||
" -p,--port=c:p target port by number or name\n"
|
||||
" -s,--file=name send binary data from given file name\n"
|
||||
" -i,--interval=v interval between SysEx messages in miliseconds\n"
|
||||
" -u,--ump=version MIDI version: 0=legacy (default), 1=MIDI1, 2=MIDI2\n\n");
|
||||
}
|
||||
|
||||
static void version(void)
|
||||
{
|
||||
puts("aseqsend version " SND_UTIL_VERSION_STR);
|
||||
}
|
||||
|
||||
static void *my_malloc(size_t size)
|
||||
{
|
||||
void *p = malloc(size);
|
||||
if (!p) {
|
||||
fatal("out of memory");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
static int hex_value(char c)
|
||||
{
|
||||
if ('0' <= c && c <= '9')
|
||||
return c - '0';
|
||||
if ('A' <= c && c <= 'F')
|
||||
return c - 'A' + 10;
|
||||
if ('a' <= c && c <= 'f')
|
||||
return c - 'a' + 10;
|
||||
error("invalid character %c", c);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void parse_data(void)
|
||||
{
|
||||
const char *p;
|
||||
int i, value;
|
||||
|
||||
send_data = my_malloc(strlen(send_hex));
|
||||
i = 0;
|
||||
value = -1; /* value is >= 0 when the first hex digit of a byte has been read */
|
||||
for (p = send_hex; *p; ++p) {
|
||||
int digit;
|
||||
if (isspace((unsigned char)*p)) {
|
||||
if (value >= 0) {
|
||||
send_data[i++] = value;
|
||||
value = -1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
digit = hex_value(*p);
|
||||
if (digit < 0) {
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (value < 0) {
|
||||
value = digit;
|
||||
} else {
|
||||
send_data[i++] = (value << 4) | digit;
|
||||
value = -1;
|
||||
}
|
||||
}
|
||||
if (value >= 0)
|
||||
send_data[i++] = value;
|
||||
send_data_length = i;
|
||||
}
|
||||
|
||||
static void add_send_hex_data(const char *str)
|
||||
{
|
||||
int length;
|
||||
char *s;
|
||||
|
||||
length = (send_hex ? strlen(send_hex) + 1 : 0) + strlen(str) + 1;
|
||||
s = my_malloc(length);
|
||||
if (send_hex) {
|
||||
strcpy(s, send_hex);
|
||||
strcat(s, " ");
|
||||
} else {
|
||||
s[0] = '\0';
|
||||
}
|
||||
strcat(s, str);
|
||||
free(send_hex);
|
||||
send_hex = s;
|
||||
}
|
||||
|
||||
static void load_file(void)
|
||||
{
|
||||
int fd;
|
||||
off_t length;
|
||||
|
||||
fd = open(send_file_name, O_RDONLY);
|
||||
if (fd == -1) {
|
||||
error("cannot open %s - %s", send_file_name, strerror(errno));
|
||||
return;
|
||||
}
|
||||
length = lseek(fd, 0, SEEK_END);
|
||||
if (length == (off_t)-1) {
|
||||
error("cannot determine length of %s: %s", send_file_name, strerror(errno));
|
||||
goto _error;
|
||||
}
|
||||
send_data = my_malloc(length);
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
if (read(fd, send_data, length) != length) {
|
||||
error("cannot read from %s: %s", send_file_name, strerror(errno));
|
||||
goto _error;
|
||||
}
|
||||
if (length >= 4 && !memcmp(send_data, "MThd", 4)) {
|
||||
error("%s is a Standard MIDI File; use aplaymidi to send it", send_file_name);
|
||||
goto _error;
|
||||
}
|
||||
send_data_length = length;
|
||||
goto _exit;
|
||||
_error:
|
||||
free(send_data);
|
||||
send_data = NULL;
|
||||
_exit:
|
||||
close(fd);
|
||||
}
|
||||
|
||||
/* error handling for ALSA functions */
|
||||
static void check_snd(const char *operation, int err)
|
||||
{
|
||||
if (err < 0)
|
||||
fatal("Cannot %s - %s", operation, snd_strerror(err));
|
||||
}
|
||||
|
||||
static void init_seq(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
/* open sequencer */
|
||||
err = snd_seq_open(&seq, "default", SND_SEQ_OPEN_OUTPUT, 0);
|
||||
check_snd("open sequencer", err);
|
||||
|
||||
/* set our client's name */
|
||||
err = snd_seq_set_client_name(seq, "aseqsend");
|
||||
check_snd("set client name", err);
|
||||
|
||||
err = snd_seq_set_client_midi_version(seq, ump_version);
|
||||
check_snd("set client midi version", err);
|
||||
}
|
||||
|
||||
static void create_port(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = snd_seq_create_simple_port(seq, "aseqsend",
|
||||
SND_SEQ_PORT_CAP_READ,
|
||||
SND_SEQ_PORT_TYPE_MIDI_GENERIC |
|
||||
SND_SEQ_PORT_TYPE_APPLICATION);
|
||||
check_snd("create port", err);
|
||||
}
|
||||
|
||||
static void init_midi_event_encoder(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = snd_midi_event_new(256, &edev);
|
||||
check_snd("create midi event encoder", err);
|
||||
}
|
||||
|
||||
static void list_ports(void)
|
||||
{
|
||||
snd_seq_client_info_t *cinfo;
|
||||
snd_seq_port_info_t *pinfo;
|
||||
|
||||
snd_seq_client_info_alloca(&cinfo);
|
||||
snd_seq_port_info_alloca(&pinfo);
|
||||
|
||||
puts(" Port Client name Port name");
|
||||
|
||||
snd_seq_client_info_set_client(cinfo, -1);
|
||||
while (snd_seq_query_next_client(seq, cinfo) >= 0) {
|
||||
int client = snd_seq_client_info_get_client(cinfo);
|
||||
|
||||
snd_seq_port_info_set_client(pinfo, client);
|
||||
snd_seq_port_info_set_port(pinfo, -1);
|
||||
while (snd_seq_query_next_port(seq, pinfo) >= 0) {
|
||||
|
||||
if ((snd_seq_port_info_get_capability(pinfo)
|
||||
& SND_SEQ_PORT_CAP_WRITE)
|
||||
!= SND_SEQ_PORT_CAP_WRITE)
|
||||
continue;
|
||||
printf("%3d:%-3d %-32.32s %s\n",
|
||||
snd_seq_port_info_get_client(pinfo),
|
||||
snd_seq_port_info_get_port(pinfo),
|
||||
snd_seq_client_info_get_name(cinfo),
|
||||
snd_seq_port_info_get_name(pinfo));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* compose a UMP event, submit it, return the next data position */
|
||||
static int send_ump(int pos)
|
||||
{
|
||||
int ump_len = 0, offset = 0;
|
||||
unsigned int ump[4];
|
||||
snd_seq_ump_event_t ev;
|
||||
|
||||
snd_seq_ump_ev_clear(&ev);
|
||||
snd_seq_ev_set_source(&ev, 0);
|
||||
snd_seq_ev_set_dest(&ev, addr.client, addr.port);
|
||||
snd_seq_ev_set_direct(&ev);
|
||||
|
||||
do {
|
||||
const mbyte_t *data = send_data + pos;
|
||||
|
||||
if (pos >= send_data_length)
|
||||
return pos;
|
||||
ump[offset] = (data[0] << 24) | (data[1] << 16) |
|
||||
(data[2] << 8) | data[3];
|
||||
if (!offset)
|
||||
ump_len = snd_ump_packet_length(snd_ump_msg_type(ump));
|
||||
offset++;
|
||||
pos += 4;
|
||||
} while (offset < ump_len);
|
||||
|
||||
snd_seq_ev_set_ump_data(&ev, ump, ump_len * 4);
|
||||
snd_seq_ump_event_output(seq, &ev);
|
||||
snd_seq_drain_output(seq);
|
||||
|
||||
sent_data_c += ump_len * 4;
|
||||
return pos;
|
||||
}
|
||||
|
||||
/* compose an event, submit it, return the next data position */
|
||||
static int send_midi_bytes(int pos)
|
||||
{
|
||||
const mbyte_t *data = send_data + pos;
|
||||
snd_seq_event_t ev;
|
||||
int is_sysex = 0;
|
||||
int end;
|
||||
|
||||
snd_seq_ev_clear(&ev);
|
||||
snd_seq_ev_set_source(&ev, 0);
|
||||
snd_seq_ev_set_dest(&ev, addr.client, addr.port);
|
||||
snd_seq_ev_set_direct(&ev);
|
||||
|
||||
if (send_data[pos] == 0xf0) {
|
||||
is_sysex = 1;
|
||||
for (end = pos + 1; end < send_data_length; end++) {
|
||||
if (send_data[end] == 0xf7)
|
||||
break;
|
||||
}
|
||||
|
||||
if (end == send_data_length)
|
||||
fatal("SysEx is missing terminating byte (0xF7)");
|
||||
end++;
|
||||
snd_seq_ev_set_sysex(&ev, end - pos, send_data + pos);
|
||||
} else {
|
||||
end = pos;
|
||||
while (!snd_midi_event_encode_byte(edev, *data++, &ev)) {
|
||||
if (++end >= send_data_length)
|
||||
return end;
|
||||
}
|
||||
|
||||
end++;
|
||||
}
|
||||
|
||||
snd_seq_event_output(seq, &ev);
|
||||
snd_seq_drain_output(seq);
|
||||
if (is_sysex)
|
||||
usleep(sysex_interval);
|
||||
|
||||
sent_data_c += end - pos;
|
||||
return end;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
static const struct option long_options[] = {
|
||||
{"help", 0, NULL, 'h'},
|
||||
{"version", 0, NULL, 'V'},
|
||||
{"verbose", 0, NULL, 'v'},
|
||||
{"list", 0, NULL, 'l'},
|
||||
{"port", 1, NULL, 'p'},
|
||||
{"file", 1, NULL, 's'},
|
||||
{"interval", 1, NULL, 'i'},
|
||||
{"ump", 1, NULL, 'u'},
|
||||
{0}
|
||||
};
|
||||
char c = 0;
|
||||
char do_send_file = 0;
|
||||
char do_port_list = 0;
|
||||
char verbose = 0;
|
||||
int k;
|
||||
|
||||
while ((c = getopt_long(argc, argv, "hi:Vvlp:s:u:", long_options, NULL)) != -1) {
|
||||
switch (c) {
|
||||
case 'h':
|
||||
usage();
|
||||
return 0;
|
||||
case 'V':
|
||||
version();
|
||||
return 0;
|
||||
case 'v':
|
||||
verbose = 1;
|
||||
break;
|
||||
case 'l':
|
||||
do_port_list = 1;
|
||||
break;
|
||||
case 'p':
|
||||
port_name = optarg;
|
||||
break;
|
||||
case 's':
|
||||
send_file_name = optarg;
|
||||
do_send_file = 1;
|
||||
break;
|
||||
case 'i':
|
||||
sysex_interval = atoi(optarg) * 1000; //ms--->us
|
||||
break;
|
||||
case 'u':
|
||||
ump_version = atoi(optarg);
|
||||
break;
|
||||
default:
|
||||
error("Try 'aseqsend -h' for more information.");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
if (argc < 2) {
|
||||
usage();
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (do_port_list){
|
||||
init_seq();
|
||||
list_ports();
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
if (port_name == NULL)
|
||||
fatal("Output port must be specified!");
|
||||
|
||||
if (do_send_file) {
|
||||
load_file();
|
||||
} else {
|
||||
/* no file specified ---> send hex bytes from cmd arguments*/
|
||||
/* data for send can be specified as multiple arguments */
|
||||
for (; argv[optind]; ++optind) {
|
||||
add_send_hex_data(argv[optind]);
|
||||
}
|
||||
if (send_hex)
|
||||
parse_data();
|
||||
}
|
||||
|
||||
if (!send_data)
|
||||
exit(EXIT_SUCCESS);
|
||||
|
||||
if (ump_version && (send_data_length % 4) != 0)
|
||||
fatal("UMP data must be aligned to 4 bytes");
|
||||
|
||||
init_seq();
|
||||
create_port();
|
||||
if (!ump_version)
|
||||
init_midi_event_encoder();
|
||||
|
||||
if (snd_seq_parse_address(seq, &addr, port_name) < 0) {
|
||||
error("Unable to parse port name!");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
sent_data_c = 0; //counter of actually sent bytes
|
||||
|
||||
k = 0;
|
||||
while (k < send_data_length) {
|
||||
if (ump_version)
|
||||
k = send_ump(k);
|
||||
else
|
||||
k = send_midi_bytes(k);
|
||||
}
|
||||
|
||||
if (verbose)
|
||||
printf("Sent : %u bytes\n", sent_data_c);
|
||||
|
||||
snd_seq_close(seq);
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
|
@ -495,7 +495,7 @@ static int select_mode(struct intel_dmic_params *dmic, struct dmic_calc_configur
|
|||
g_cic = mcic * mcic * mcic * mcic * mcic;
|
||||
if (g_cic < 0) {
|
||||
/* Erroneous decimation factor and CIC gain */
|
||||
fprintf(stderr, "%s: erroneous decimation factor and CIC gain\n");
|
||||
fprintf(stderr, "%s: erroneous decimation factor and CIC gain\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
|
@ -520,7 +520,7 @@ static int select_mode(struct intel_dmic_params *dmic, struct dmic_calc_configur
|
|||
cfg->fir_a->length, gain_to_fir);
|
||||
if (ret < 0) {
|
||||
/* Invalid coefficient set found, should not happen. */
|
||||
fprintf(stderr, "%s: invalid coefficient set found\n");
|
||||
fprintf(stderr, "%s: invalid coefficient set found\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -697,13 +697,11 @@ static int pre_process_create_items(struct tplg_pre_processor *tplg_pp,
|
|||
snd_config_iterator_t i, next;
|
||||
snd_config_type_t type;
|
||||
const char *class_id;
|
||||
char *class_id_local;
|
||||
int attr_count = 0;
|
||||
int object_count = *object_count_offset;
|
||||
int ret;
|
||||
|
||||
snd_config_get_id(top, &class_id);
|
||||
class_id_local = strdup(class_id);
|
||||
|
||||
snd_config_for_each(i, next, cfg) {
|
||||
snd_config_iterator_t i2, next2;
|
||||
|
@ -719,7 +717,7 @@ static int pre_process_create_items(struct tplg_pre_processor *tplg_pp,
|
|||
if (snd_config_get_id(n, &attribute) < 0)
|
||||
continue;
|
||||
|
||||
ret = snd_config_make(&local_top, class_id_local, SND_CONFIG_TYPE_COMPOUND);
|
||||
ret = snd_config_make(&local_top, class_id, SND_CONFIG_TYPE_COMPOUND);
|
||||
|
||||
snd_config_for_each(i2, next2, n) {
|
||||
snd_config_t *n2, *new, *new_obj;
|
||||
|
@ -787,7 +785,7 @@ static int pre_process_create_items(struct tplg_pre_processor *tplg_pp,
|
|||
ret = pre_process_add_objects(tplg_pp, &object_count, top,
|
||||
local_top, new);
|
||||
if (ret < 0) {
|
||||
SNDERR("failed to add objects of type %s\n", class_id_local);
|
||||
SNDERR("failed to add objects of type %s\n", class_id);
|
||||
goto err;
|
||||
}
|
||||
|
||||
|
@ -921,6 +919,172 @@ static int pre_process_arrays(struct tplg_pre_processor *tplg_pp, snd_config_t *
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int pre_process_subtree_copy(struct tplg_pre_processor *tplg_pp, snd_config_t *curr,
|
||||
snd_config_t *top)
|
||||
{
|
||||
snd_config_iterator_t i, next;
|
||||
snd_config_t *subtrees;
|
||||
int ret;
|
||||
|
||||
ret = snd_config_search(curr, "SubTreeCopy", &subtrees);
|
||||
if (ret < 0)
|
||||
return 0;
|
||||
|
||||
snd_config_for_each(i, next, subtrees) {
|
||||
snd_config_t *n, *source_cfg, *target_cfg, *type_cfg;
|
||||
snd_config_t *source_tree, *target_tree, *tmp, *subtree_cfg;
|
||||
const char *id, *source_id;
|
||||
const char *type = NULL;
|
||||
const char *target_id = NULL;
|
||||
bool override = false;
|
||||
|
||||
n = snd_config_iterator_entry(i);
|
||||
ret = snd_config_get_id(n, &id);
|
||||
if (ret < 0) {
|
||||
SNDERR("Failed to get ID for subtree copy\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* get the type of copy ex: override/extend if set, by default override is false */
|
||||
ret = snd_config_search(n, "type", &type_cfg);
|
||||
if (ret >= 0) {
|
||||
ret = snd_config_get_string(type_cfg, &type);
|
||||
if (ret >= 0) {
|
||||
if (strcmp(type, "override") && strcmp(type, "extend")) {
|
||||
SNDERR("Invalid value for sub tree copy type %s\n", type);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!strcmp(type, "override"))
|
||||
override = true;
|
||||
}
|
||||
}
|
||||
|
||||
ret = snd_config_search(n, "source", &source_cfg);
|
||||
if (ret < 0) {
|
||||
SNDERR("failed to get source config for subtree %s\n", id);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* if the target node is not set, the subtree will be copied to current node */
|
||||
ret = snd_config_search(n, "target", &target_cfg);
|
||||
if (ret >= 0) {
|
||||
snd_config_t *temp_target;
|
||||
char *s;
|
||||
|
||||
ret = snd_config_get_string(target_cfg, &target_id);
|
||||
if (ret < 0) {
|
||||
SNDERR("Invalid target id for subtree %s\n", id);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* create a temporary node with target_id and merge with top to avoid
|
||||
* failure in case the target node ID doesn't exist already
|
||||
*/
|
||||
s = tplg_snprintf("%s {}", target_id);
|
||||
if (!s)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = snd_config_load_string(&temp_target, s, 0);
|
||||
free(s);
|
||||
if (ret < 0) {
|
||||
SNDERR("Cannot create temp node with target id %s\n", target_id);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = snd_config_merge(top, temp_target, false);
|
||||
if (ret < 0) {
|
||||
SNDERR("Cannot merge temp node with target id %s\n", target_id);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = snd_config_search(top, target_id, &target_tree);
|
||||
if (ret < 0) {
|
||||
SNDERR("failed to get target tree %s\n", target_id);
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
target_tree = curr;
|
||||
}
|
||||
|
||||
/* get the source tree node */
|
||||
ret = snd_config_get_string(source_cfg, &source_id);
|
||||
if (ret < 0) {
|
||||
SNDERR("Invalid source node id for subtree %s\n", id);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = snd_config_search(top, source_id, &source_tree);
|
||||
if (ret < 0) {
|
||||
SNDERR("failed to get source tree %s\n", source_id);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* get the subtree to be merged */
|
||||
ret = snd_config_search(n, "tree", &subtree_cfg);
|
||||
if (ret < 0)
|
||||
subtree_cfg = NULL;
|
||||
|
||||
/* make a temp copy of the source tree */
|
||||
ret = snd_config_copy(&tmp, source_tree);
|
||||
if (ret < 0) {
|
||||
SNDERR("failed to copy source tree for subtreecopy %s\n", id);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* merge the current block with the source tree */
|
||||
ret = snd_config_merge(tmp, subtree_cfg, override);
|
||||
if (ret < 0) {
|
||||
snd_config_delete(tmp);
|
||||
SNDERR("Failed to merge source tree w/ subtree %s\n", id);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* merge the merged block to the target tree */
|
||||
ret = snd_config_merge(target_tree, tmp, override);
|
||||
if (ret < 0) {
|
||||
snd_config_delete(tmp);
|
||||
SNDERR("Failed to merge subtree %s w/ target\n", id);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/* all subtree copies have been processed, remove the node */
|
||||
snd_config_delete(subtrees);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pre_process_subtree_copies(struct tplg_pre_processor *tplg_pp, snd_config_t *top,
|
||||
snd_config_t *curr)
|
||||
{
|
||||
snd_config_iterator_t i, next;
|
||||
int ret;
|
||||
|
||||
if (snd_config_get_type(curr) != SND_CONFIG_TYPE_COMPOUND)
|
||||
return 0;
|
||||
|
||||
/* process subtreecopies at this node */
|
||||
ret = pre_process_subtree_copy(tplg_pp, curr, top);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* process subtreecopies at all child nodes */
|
||||
snd_config_for_each(i, next, curr) {
|
||||
snd_config_t *n;
|
||||
|
||||
n = snd_config_iterator_entry(i);
|
||||
|
||||
/* process subtreecopies at this node */
|
||||
ret = pre_process_subtree_copies(tplg_pp, top, n);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* version < 1.2.6 */
|
||||
|
||||
int pre_process(struct tplg_pre_processor *tplg_pp, char *config, size_t config_size,
|
||||
|
@ -980,6 +1144,12 @@ int pre_process(struct tplg_pre_processor *tplg_pp, char *config, size_t config_
|
|||
fprintf(stderr, "Failed to process object arrays in input config\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
err = pre_process_subtree_copies(tplg_pp, tplg_pp->input_cfg, tplg_pp->input_cfg);
|
||||
if (err < 0) {
|
||||
SNDERR("Failed to process subtree copies in input config\n");
|
||||
goto err;
|
||||
}
|
||||
#endif
|
||||
|
||||
err = pre_process_config(tplg_pp, tplg_pp->input_cfg);
|
||||
|
|
Loading…
Reference in a new issue