alsa-utils/seq/aseqsend/aseqsend.c
Takashi Iwai ca82cf8ed6 aseqsend: Support realtime / system messages
The realtime / system messages (0xFx) wasn't handled properly.
Add the code to support those messages.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
2024-06-27 14:06:25 +02:00

556 lines
12 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>
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));
}
}
}
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 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:")) != -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)
exit(EXIT_SUCCESS);
init_seq();
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 (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);
}