mirror of
https://github.com/alsa-project/alsa-utils
synced 2024-11-09 17:15:43 +01:00
aseqsend: initial version
aseqsend is a command-line utility which allows one to send SysEx (system exclusive) data to ALSA MIDI seqencer port. It can also send any other MIDI commands. Closes: https://github.com/alsa-project/alsa-utils/pull/257 Signed-off-by: Miroslav Kovac <mixxoo@gmail.com> Signed-off-by: Jaroslav Kysela <perex@perex.cz>
This commit is contained in:
parent
ba2bc072dc
commit
7547242d0f
6 changed files with 545 additions and 2 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -49,6 +49,7 @@ seq/aconnect/aconnect
|
|||
seq/aplaymidi/aplaymidi
|
||||
seq/aplaymidi/arecordmidi
|
||||
seq/aseqdump/aseqdump
|
||||
seq/aseqsend/aseqsend
|
||||
seq/aseqnet/aseqnet
|
||||
speaker-test/speaker-test
|
||||
topology/alsatplg.1
|
||||
|
|
|
@ -487,7 +487,7 @@ AC_OUTPUT(Makefile alsactl/Makefile alsactl/init/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/aseqsend/Makefile speaker-test/Makefile speaker-test/samples/Makefile \
|
||||
alsaloop/Makefile alsa-info/Makefile \
|
||||
axfer/Makefile axfer/test/Makefile \
|
||||
nhlt/Makefile)
|
||||
|
|
|
@ -1 +1 @@
|
|||
SUBDIRS=aconnect aplaymidi aseqdump aseqnet
|
||||
SUBDIRS=aconnect aplaymidi aseqdump aseqnet aseqsend
|
||||
|
|
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
|
59
seq/aseqsend/aseqsend.1
Normal file
59
seq/aseqsend/aseqsend.1
Normal file
|
@ -0,0 +1,59 @@
|
|||
.TH ASEQSEND 1 "11 Mar 2024"
|
||||
|
||||
.SH NAME
|
||||
.B aseqsend
|
||||
\- send arbitrary messages to selected ALSA MIDI seqencer port
|
||||
|
||||
.SH SYNOPSIS
|
||||
aseqsend \-p client:port -s file-name|"hex encoded byte-string"
|
||||
|
||||
.SH DESCRIPTION
|
||||
.B aseqsend
|
||||
is a command-line utility which allows one to send SysEx (system exclusive) data to ALSA MIDI seqencer port.
|
||||
It can also send any other MIDI commands.
|
||||
Messages to be send 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 deafult and can be set to different value with option \-i.
|
||||
|
||||
.SH OPTIONS
|
||||
|
||||
.TP
|
||||
\-h
|
||||
Prints a list of options.
|
||||
|
||||
.TP
|
||||
\-V
|
||||
Prints the current version.
|
||||
|
||||
.TP
|
||||
\-l
|
||||
Prints a list of possible output ports.
|
||||
|
||||
.TP
|
||||
\-v
|
||||
Prints number of bytes actually sent
|
||||
|
||||
.TP
|
||||
\-p
|
||||
Target port by number or name
|
||||
|
||||
.TP
|
||||
\-s
|
||||
Send raw binary data from given file name
|
||||
|
||||
.TP
|
||||
\-i
|
||||
Interval between SysEx messages in miliseconds
|
||||
|
||||
|
||||
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.
|
||||
|
||||
.SH EXAMPLES
|
||||
|
||||
aseqsend -p 128:0 "F0 41 10 00 00 64 12 18 00 21 06 59 41 59 4E F7"
|
||||
|
||||
aseqsend -p 128:0 -s I7BulkDump.syx
|
||||
|
||||
.SH AUTHOR
|
||||
Miroslav Kovac <mixxoo@gmail.com>
|
478
seq/aseqsend/aseqsend.c
Normal file
478
seq/aseqsend/aseqsend.c
Normal file
|
@ -0,0 +1,478 @@
|
|||
/*
|
||||
* 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>
|
||||
|
||||
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 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 this help\n"
|
||||
" -V print current version\n"
|
||||
" -v verbose\n"
|
||||
" -l list all sequencer ports\n"
|
||||
" -p target port by number or name\n"
|
||||
" -s send binary data from given file name\n"
|
||||
" -i interval between SysEx messages in miliseconds\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);
|
||||
}
|
||||
|
||||
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 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void send_midi_msg(snd_seq_event_type_t type, mbyte_t *data, int len)
|
||||
{
|
||||
snd_seq_event_t ev;
|
||||
|
||||
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 (type == SND_SEQ_EVENT_SYSEX) {
|
||||
|
||||
snd_seq_ev_set_sysex(&ev,len,data);
|
||||
|
||||
} else {
|
||||
|
||||
mbyte_t ch = data[0] & 0xF;
|
||||
|
||||
switch (type) {
|
||||
case SND_SEQ_EVENT_NOTEON:
|
||||
snd_seq_ev_set_noteon(&ev,ch,data[1],data[2]);
|
||||
break;
|
||||
case SND_SEQ_EVENT_NOTEOFF:
|
||||
snd_seq_ev_set_noteoff(&ev,ch,data[1],data[2]);
|
||||
break;
|
||||
case SND_SEQ_EVENT_KEYPRESS:
|
||||
snd_seq_ev_set_keypress(&ev,ch,data[1],data[2]);
|
||||
break;
|
||||
case SND_SEQ_EVENT_CONTROLLER:
|
||||
snd_seq_ev_set_controller(&ev,ch,data[1],data[2]);
|
||||
break;
|
||||
case SND_SEQ_EVENT_PITCHBEND:
|
||||
snd_seq_ev_set_pitchbend(&ev,ch,(data[1]<<7|data[2])-8192);
|
||||
break;
|
||||
case SND_SEQ_EVENT_PGMCHANGE:
|
||||
snd_seq_ev_set_pgmchange(&ev,ch,data[1]);
|
||||
break;
|
||||
case SND_SEQ_EVENT_CHANPRESS:
|
||||
snd_seq_ev_set_chanpress(&ev,ch,data[1]);
|
||||
break;
|
||||
default:
|
||||
ev.type = SND_SEQ_EVENT_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
snd_seq_event_output(seq, &ev);
|
||||
snd_seq_drain_output(seq);
|
||||
|
||||
}
|
||||
|
||||
static int msg_byte_in_range(mbyte_t *data, mbyte_t len)
|
||||
{
|
||||
for (int i=0;i<len;i++) {
|
||||
if (data[i] > 0x7F) {
|
||||
error("msg byte value out of range 0-127");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
char c = 0;
|
||||
char do_send_file = 0;
|
||||
char do_port_list = 0;
|
||||
char verbose = 0;
|
||||
int sysex_interval = 1000; //us
|
||||
|
||||
while ((c = getopt(argc, argv, "hi:Vvlp:s:")) != -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;
|
||||
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) {
|
||||
|
||||
init_seq();
|
||||
create_port();
|
||||
|
||||
if (snd_seq_parse_address(seq,&addr,port_name) == 0) {
|
||||
|
||||
int sent_data_c = 0;//counter of actually sent bytes
|
||||
|
||||
int k = 0;
|
||||
|
||||
while (k < send_data_length) {
|
||||
|
||||
if (send_data[k] == 0xF0) {
|
||||
|
||||
int c1 = k;
|
||||
while (c1 < send_data_length)
|
||||
{
|
||||
if (send_data[c1] == 0xF7) break;
|
||||
c1++;
|
||||
}
|
||||
|
||||
if (c1 == send_data_length)
|
||||
fatal("SysEx is missing terminating byte (0xF7)");
|
||||
|
||||
int sl = c1-k+1;
|
||||
sent_data_c += sl;
|
||||
|
||||
send_midi_msg(SND_SEQ_EVENT_SYSEX, send_data+k,sl);
|
||||
|
||||
usleep(sysex_interval);
|
||||
|
||||
k = c1+1;
|
||||
|
||||
} else {
|
||||
|
||||
mbyte_t tp = send_data[k] >> 4;
|
||||
|
||||
if (tp == 0x8) {
|
||||
if (msg_byte_in_range(send_data + k + 1, 2)) {
|
||||
send_midi_msg(SND_SEQ_EVENT_NOTEOFF, send_data+k,3);
|
||||
sent_data_c += 3;
|
||||
}
|
||||
k = k+3;
|
||||
} else if (tp == 0x9) {
|
||||
if (msg_byte_in_range(send_data + k + 1, 2)) {
|
||||
send_midi_msg(SND_SEQ_EVENT_NOTEON, send_data+k,3);
|
||||
sent_data_c += 3;
|
||||
}
|
||||
k = k+3;
|
||||
} else if (tp == 0xA) {
|
||||
if (msg_byte_in_range(send_data + k + 1, 2)) {
|
||||
send_midi_msg(SND_SEQ_EVENT_KEYPRESS, send_data+k,3);
|
||||
sent_data_c += 3;
|
||||
}
|
||||
k = k+3;
|
||||
} else if (tp == 0xB) {
|
||||
if (msg_byte_in_range(send_data + k + 1, 2)) {
|
||||
send_midi_msg(SND_SEQ_EVENT_CONTROLLER, send_data+k,3);
|
||||
sent_data_c += 3;
|
||||
}
|
||||
k = k+3;
|
||||
} else if (tp == 0xC) {
|
||||
if (msg_byte_in_range(send_data + k + 1, 1)) {
|
||||
send_midi_msg(SND_SEQ_EVENT_PGMCHANGE, send_data+k,2);
|
||||
sent_data_c += 2;
|
||||
}
|
||||
k = k+2;
|
||||
} else if (tp == 0xD) {
|
||||
if (msg_byte_in_range(send_data + k + 1, 1)) {
|
||||
send_midi_msg(SND_SEQ_EVENT_CHANPRESS, send_data+k,2);
|
||||
sent_data_c += 2;
|
||||
}
|
||||
k = k+2;
|
||||
} else if (tp == 0xE) {
|
||||
if (msg_byte_in_range(send_data + k + 1, 2)) {
|
||||
send_midi_msg(SND_SEQ_EVENT_PITCHBEND, send_data+k,3);
|
||||
sent_data_c += 3;
|
||||
}
|
||||
k = k+3;
|
||||
} else k++;
|
||||
}
|
||||
}
|
||||
|
||||
if (verbose)
|
||||
printf("Sent : %u bytes\n",sent_data_c);
|
||||
|
||||
} else {
|
||||
|
||||
error("Unable to parse port name!");
|
||||
exit(EXIT_FAILURE);
|
||||
|
||||
}
|
||||
snd_seq_close(seq);
|
||||
}
|
||||
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
Loading…
Reference in a new issue