mirror of
https://github.com/alsa-project/alsa-utils
synced 2024-11-10 00:25:43 +01:00
c95db638c0
Add a new option -u to specify the UMP MIDI1 or MIDI2 mode. As default (-u 0), the program reads the legacy MIDI 1.0 byte stream, while in UMP mode, it reads as UMP packets and send to the target. The UMP packet bytes are encoded in big endian. Signed-off-by: Takashi Iwai <tiwai@suse.de>
596 lines
13 KiB
C
596 lines
13 KiB
C
/*
|
|
* 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 ump_version;
|
|
|
|
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));
|
|
}
|
|
}
|
|
}
|
|
|
|
static 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;
|
|
case SND_SEQ_EVENT_QFRAME:
|
|
case SND_SEQ_EVENT_SONGSEL:
|
|
ev.type = type;
|
|
ev.data.control.channel = ch;
|
|
ev.data.control.value = data[1];
|
|
break;
|
|
case SND_SEQ_EVENT_SONGPOS:
|
|
ev.type = type;
|
|
ev.data.control.channel = ch;
|
|
ev.data.control.value = data[1] | (data[2] << 7);
|
|
break;
|
|
case SND_SEQ_EVENT_TUNE_REQUEST:
|
|
case SND_SEQ_EVENT_CLOCK:
|
|
case SND_SEQ_EVENT_START:
|
|
case SND_SEQ_EVENT_CONTINUE:
|
|
case SND_SEQ_EVENT_STOP:
|
|
case SND_SEQ_EVENT_SENSING:
|
|
case SND_SEQ_EVENT_RESET:
|
|
ev.type = type;
|
|
ev.data.control.channel = ch;
|
|
break;
|
|
default:
|
|
ev.type = SND_SEQ_EVENT_NONE;
|
|
}
|
|
}
|
|
|
|
snd_seq_event_output(seq, &ev);
|
|
snd_seq_drain_output(seq);
|
|
}
|
|
|
|
static int send_ump(const unsigned char *data)
|
|
{
|
|
static int ump_len = 0, offset = 0;
|
|
unsigned int ump[4];
|
|
snd_seq_ump_event_t ev;
|
|
|
|
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++;
|
|
if (offset < ump_len)
|
|
return 0;
|
|
|
|
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);
|
|
snd_seq_ev_set_ump_data(&ev, ump, ump_len * 4);
|
|
snd_seq_ump_event_output(seq, &ev);
|
|
snd_seq_drain_output(seq);
|
|
offset = 0;
|
|
return ump_len * 4;
|
|
}
|
|
|
|
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
|
|
int sent_data_c;
|
|
int k;
|
|
|
|
while ((c = getopt(argc, argv, "hi:Vvlp:s:u:")) != -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();
|
|
snd_seq_set_client_midi_version(seq, ump_version);
|
|
create_port();
|
|
|
|
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) {
|
|
sent_data_c += send_ump(send_data + k);
|
|
k += 4;
|
|
continue;
|
|
}
|
|
|
|
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 += 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 += 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 += 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 += 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 += 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 += 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 += 3;
|
|
} else {
|
|
switch (send_data[k]) {
|
|
case 0xF1:
|
|
if (msg_byte_in_range(send_data + k + 1, 1)) {
|
|
send_midi_msg(SND_SEQ_EVENT_QFRAME, send_data+k, 2);
|
|
sent_data_c += 2;
|
|
}
|
|
k += 2;
|
|
break;
|
|
case 0xF2:
|
|
if (msg_byte_in_range(send_data + k + 1, 2)) {
|
|
send_midi_msg(SND_SEQ_EVENT_SONGPOS, send_data+k, 3);
|
|
sent_data_c += 3;
|
|
}
|
|
k += 3;
|
|
break;
|
|
case 0xF3:
|
|
if (msg_byte_in_range(send_data + k + 1, 1)) {
|
|
send_midi_msg(SND_SEQ_EVENT_SONGSEL, send_data+k, 2);
|
|
sent_data_c += 2;
|
|
}
|
|
k += 2;
|
|
break;
|
|
case 0xF6:
|
|
send_midi_msg(SND_SEQ_EVENT_TUNE_REQUEST, send_data+k, 1);
|
|
sent_data_c++;
|
|
k++;
|
|
break;
|
|
case 0xF8:
|
|
send_midi_msg(SND_SEQ_EVENT_CLOCK, send_data+k, 1);
|
|
sent_data_c++;
|
|
k++;
|
|
break;
|
|
case 0xFA:
|
|
send_midi_msg(SND_SEQ_EVENT_START, send_data+k, 1);
|
|
sent_data_c++;
|
|
k++;
|
|
break;
|
|
case 0xFB:
|
|
send_midi_msg(SND_SEQ_EVENT_CONTINUE, send_data+k, 1);
|
|
sent_data_c++;
|
|
k++;
|
|
break;
|
|
case 0xFC:
|
|
send_midi_msg(SND_SEQ_EVENT_STOP, send_data+k, 1);
|
|
sent_data_c++;
|
|
k++;
|
|
break;
|
|
case 0xFE:
|
|
send_midi_msg(SND_SEQ_EVENT_SENSING, send_data+k, 1);
|
|
sent_data_c++;
|
|
k++;
|
|
break;
|
|
case 0xFF:
|
|
send_midi_msg(SND_SEQ_EVENT_RESET, send_data+k, 1);
|
|
sent_data_c++;
|
|
k++;
|
|
break;
|
|
default:
|
|
k++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (verbose)
|
|
printf("Sent : %u bytes\n", sent_data_c);
|
|
|
|
snd_seq_close(seq);
|
|
exit(EXIT_SUCCESS);
|
|
}
|