alsa-utils/seq/aplaymidi/arecordmidi.c
Diego E. 'Flameeyes' Pettenò 6232f1c96c Make some static tables and strings constants.
By doing this we move them from the .data section to .rodata setion,
or from .data.rel to .data.rel.ro.

The .rodata section is mapped directly from the on-disk file, which is
always a save, while .data.rel.ro is mapped directly when using
prelink, which is a save in a lot of cases.

Signed-off-by: Diego E. 'Flameeyes' Pettenò <flameeyes@gmail.com>
2008-11-21 13:10:02 +01:00

881 lines
23 KiB
C

/*
* arecordmidi.c - record standard MIDI files from sequencer ports
*
* Copyright (c) 2004-2005 Clemens Ladisch <clemens@ladisch.de>
*
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/* TODO: sequencer queue timer selection */
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <signal.h>
#include <getopt.h>
#include <sys/poll.h>
#include <alsa/asoundlib.h>
#include "aconfig.h"
#include "version.h"
#define BUFFER_SIZE 4088
/* linked list of buffers, stores data as in the .mid file */
struct buffer {
struct buffer *next;
unsigned char buf[BUFFER_SIZE];
};
struct smf_track {
int size; /* size of entire data */
int cur_buf_size; /* size of cur_buf */
struct buffer *cur_buf;
snd_seq_tick_time_t last_tick; /* end of track */
unsigned char last_command; /* used for running status */
int used; /* anything record on this track */
struct buffer first_buf; /* list head */
};
/* timing/sysex + 16 channels */
#define TRACKS_PER_PORT 17
/* metronome settings */
/* TODO: create options for this */
#define METRONOME_CHANNEL 9
#define METRONOME_STRONG_NOTE 34
#define METRONOME_WEAK_NOTE 33
#define METRONOME_VELOCITY 100
#define METRONOME_PROGRAM 0
static snd_seq_t *seq;
static int client;
static int port_count;
static snd_seq_addr_t *ports;
static int queue;
static int smpte_timing = 0;
static int beats = 120;
static int frames;
static int ticks = 0;
static FILE *file;
static int channel_split;
static int num_tracks;
static struct smf_track *tracks;
static volatile sig_atomic_t stop = 0;
static int use_metronome = 0;
static snd_seq_addr_t metronome_port;
static int metronome_weak_note = METRONOME_WEAK_NOTE;
static int metronome_strong_note = METRONOME_STRONG_NOTE;
static int metronome_velocity = METRONOME_VELOCITY;
static int metronome_program = METRONOME_PROGRAM;
static int metronome_channel = METRONOME_CHANNEL;
static int ts_num = 4; /* time signature: numerator */
static int ts_div = 4; /* time signature: denominator */
static int ts_dd = 2; /* time signature: denominator as a power of two */
/* 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));
}
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 our client's name */
err = snd_seq_set_client_name(seq, "arecordmidi");
check_snd("set client name", 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 the metronome port address */
static void init_metronome(const char *arg)
{
int err;
err = snd_seq_parse_address(seq, &metronome_port, arg);
if (err < 0)
fatal("Invalid port %s - %s", arg, snd_strerror(err));
use_metronome = 1;
}
/* 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;
for (ts_dd = 0; x > 1; x /= 2)
++ts_dd;
}
/*
* Metronome implementation
*/
static void metronome_note(unsigned char note, unsigned int tick)
{
snd_seq_event_t ev;
snd_seq_ev_clear(&ev);
snd_seq_ev_set_note(&ev, metronome_channel, note, metronome_velocity, 1);
snd_seq_ev_schedule_tick(&ev, queue, 0, tick);
snd_seq_ev_set_source(&ev, port_count);
snd_seq_ev_set_subs(&ev);
snd_seq_event_output(seq, &ev);
}
static void metronome_echo(unsigned int tick)
{
snd_seq_event_t ev;
snd_seq_ev_clear(&ev);
ev.type = SND_SEQ_EVENT_USR0;
snd_seq_ev_schedule_tick(&ev, queue, 0, tick);
snd_seq_ev_set_source(&ev, port_count);
snd_seq_ev_set_dest(&ev, client, port_count);
snd_seq_event_output(seq, &ev);
}
static void metronome_pattern(unsigned int tick)
{
int j, t, duration;
t = tick;
duration = ticks * 4 / ts_div;
for (j = 0; j < ts_num; j++) {
metronome_note(j ? metronome_weak_note : metronome_strong_note, t);
t += duration;
}
metronome_echo(t);
snd_seq_drain_output(seq);
}
static void metronome_set_program(void)
{
snd_seq_event_t ev;
snd_seq_ev_clear(&ev);
snd_seq_ev_set_pgmchange(&ev, metronome_channel, metronome_program);
snd_seq_ev_set_source(&ev, port_count);
snd_seq_ev_set_subs(&ev);
snd_seq_event_output(seq, &ev);
}
static void init_tracks(void)
{
int i;
/* MIDI RP-019 says we need at least one track per port */
num_tracks = port_count;
/* Allocate one track for each possible channel.
* Empty tracks won't be written to the file. */
if (channel_split)
num_tracks *= TRACKS_PER_PORT;
tracks = calloc(num_tracks, sizeof(struct smf_track));
check_mem(tracks);
for (i = 0; i < num_tracks; ++i)
tracks[i].cur_buf = &tracks[i].first_buf;
}
static void create_queue(void)
{
snd_seq_queue_tempo_t *tempo;
int err;
queue = snd_seq_alloc_named_queue(seq, "arecordmidi");
check_snd("create queue", queue);
snd_seq_queue_tempo_alloca(&tempo);
if (!smpte_timing) {
snd_seq_queue_tempo_set_tempo(tempo, 60000000 / beats);
snd_seq_queue_tempo_set_ppq(tempo, ticks);
} else {
/*
* ALSA doesn't know about the SMPTE time divisions, so
* we pretend to have a musical tempo with the equivalent
* number of ticks/s.
*/
switch (frames) {
case 24:
snd_seq_queue_tempo_set_tempo(tempo, 500000);
snd_seq_queue_tempo_set_ppq(tempo, 12 * ticks);
break;
case 25:
snd_seq_queue_tempo_set_tempo(tempo, 400000);
snd_seq_queue_tempo_set_ppq(tempo, 10 * ticks);
break;
case 29:
snd_seq_queue_tempo_set_tempo(tempo, 100000000);
snd_seq_queue_tempo_set_ppq(tempo, 2997 * ticks);
break;
case 30:
snd_seq_queue_tempo_set_tempo(tempo, 500000);
snd_seq_queue_tempo_set_ppq(tempo, 15 * ticks);
break;
default:
fatal("Invalid SMPTE frames %d", frames);
}
}
err = snd_seq_set_queue_tempo(seq, queue, tempo);
if (err < 0)
fatal("Cannot set queue tempo (%u/%i)",
snd_seq_queue_tempo_get_tempo(tempo),
snd_seq_queue_tempo_get_ppq(tempo));
}
static void create_ports(void)
{
snd_seq_port_info_t *pinfo;
int i, err;
char name[32];
snd_seq_port_info_alloca(&pinfo);
/* common information for all our ports */
snd_seq_port_info_set_capability(pinfo,
SND_SEQ_PORT_CAP_WRITE |
SND_SEQ_PORT_CAP_SUBS_WRITE);
snd_seq_port_info_set_type(pinfo,
SND_SEQ_PORT_TYPE_MIDI_GENERIC |
SND_SEQ_PORT_TYPE_APPLICATION);
snd_seq_port_info_set_midi_channels(pinfo, 16);
/* we want to know when the events got delivered to us */
snd_seq_port_info_set_timestamping(pinfo, 1);
snd_seq_port_info_set_timestamp_queue(pinfo, queue);
/* our port number is the same as our port index */
snd_seq_port_info_set_port_specified(pinfo, 1);
for (i = 0; i < port_count; ++i) {
snd_seq_port_info_set_port(pinfo, i);
sprintf(name, "arecordmidi port %i", i);
snd_seq_port_info_set_name(pinfo, name);
err = snd_seq_create_port(seq, pinfo);
check_snd("create port", err);
}
/* create an optional metronome port */
if (use_metronome) {
snd_seq_port_info_set_port(pinfo, port_count);
snd_seq_port_info_set_name(pinfo, "arecordmidi metronome");
snd_seq_port_info_set_capability(pinfo,
SND_SEQ_PORT_CAP_READ |
SND_SEQ_PORT_CAP_WRITE);
snd_seq_port_info_set_type(pinfo, SND_SEQ_PORT_TYPE_APPLICATION);
snd_seq_port_info_set_midi_channels(pinfo, 0);
snd_seq_port_info_set_timestamping(pinfo, 0);
err = snd_seq_create_port(seq, pinfo);
check_snd("create metronome port", err);
}
}
static void connect_ports(void)
{
int i, err;
for (i = 0; i < port_count; ++i) {
err = snd_seq_connect_from(seq, i, ports[i].client, ports[i].port);
if (err < 0)
fatal("Cannot connect from port %d:%d - %s",
ports[i].client, ports[i].port, snd_strerror(err));
}
/* subscribe the metronome port */
if (use_metronome) {
err = snd_seq_connect_to(seq, port_count, metronome_port.client, metronome_port.port);
if (err < 0)
fatal("Cannot connect to port %d:%d - %s",
metronome_port.client, metronome_port.port, snd_strerror(err));
}
}
/* records a byte to be written to the .mid file */
static void add_byte(struct smf_track *track, unsigned char byte)
{
/* make sure we have enough room in the current buffer */
if (track->cur_buf_size >= BUFFER_SIZE) {
track->cur_buf->next = calloc(1, sizeof(struct buffer));
if (!track->cur_buf->next)
fatal("out of memory");
track->cur_buf = track->cur_buf->next;
track->cur_buf_size = 0;
}
track->cur_buf->buf[track->cur_buf_size++] = byte;
track->size++;
}
/* record a variable-length quantity */
static void var_value(struct smf_track *track, int v)
{
if (v >= (1 << 28))
add_byte(track, 0x80 | ((v >> 28) & 0x03));
if (v >= (1 << 21))
add_byte(track, 0x80 | ((v >> 21) & 0x7f));
if (v >= (1 << 14))
add_byte(track, 0x80 | ((v >> 14) & 0x7f));
if (v >= (1 << 7))
add_byte(track, 0x80 | ((v >> 7) & 0x7f));
add_byte(track, v & 0x7f);
}
/* record the delta time from the last event */
static void delta_time(struct smf_track *track, const snd_seq_event_t *ev)
{
int diff = ev->time.tick - track->last_tick;
if (diff < 0)
diff = 0;
var_value(track, diff);
track->last_tick = ev->time.tick;
}
/* record a status byte (or not if we can use running status) */
static void command(struct smf_track *track, unsigned char cmd)
{
if (cmd != track->last_command)
add_byte(track, cmd);
track->last_command = cmd < 0xf0 ? cmd : 0;
}
/* put port numbers into all tracks */
static void record_port_numbers(void)
{
int i;
for (i = 0; i < num_tracks; ++i) {
var_value(&tracks[i], 0);
add_byte(&tracks[i], 0xff);
add_byte(&tracks[i], 0x21);
var_value(&tracks[i], 1);
if (channel_split)
add_byte(&tracks[i], i / TRACKS_PER_PORT);
else
add_byte(&tracks[i], i);
}
}
static void record_event(const snd_seq_event_t *ev)
{
unsigned int i;
struct smf_track *track;
/* ignore events without proper timestamps */
if (ev->queue != queue || !snd_seq_ev_is_tick(ev))
return;
/* determine which track to record to */
i = ev->dest.port;
if (i == port_count) {
if (ev->type == SND_SEQ_EVENT_USR0)
metronome_pattern(ev->time.tick);
return;
}
if (channel_split) {
i *= TRACKS_PER_PORT;
if (snd_seq_ev_is_channel_type(ev))
i += 1 + (ev->data.note.channel & 0xf);
}
if (i >= num_tracks)
return;
track = &tracks[i];
switch (ev->type) {
case SND_SEQ_EVENT_NOTEON:
delta_time(track, ev);
command(track, MIDI_CMD_NOTE_ON | (ev->data.note.channel & 0xf));
add_byte(track, ev->data.note.note & 0x7f);
add_byte(track, ev->data.note.velocity & 0x7f);
break;
case SND_SEQ_EVENT_NOTEOFF:
delta_time(track, ev);
command(track, MIDI_CMD_NOTE_OFF | (ev->data.note.channel & 0xf));
add_byte(track, ev->data.note.note & 0x7f);
add_byte(track, ev->data.note.velocity & 0x7f);
break;
case SND_SEQ_EVENT_KEYPRESS:
delta_time(track, ev);
command(track, MIDI_CMD_NOTE_PRESSURE | (ev->data.note.channel & 0xf));
add_byte(track, ev->data.note.note & 0x7f);
add_byte(track, ev->data.note.velocity & 0x7f);
break;
case SND_SEQ_EVENT_CONTROLLER:
delta_time(track, ev);
command(track, MIDI_CMD_CONTROL | (ev->data.control.channel & 0xf));
add_byte(track, ev->data.control.param & 0x7f);
add_byte(track, ev->data.control.value & 0x7f);
break;
case SND_SEQ_EVENT_PGMCHANGE:
delta_time(track, ev);
command(track, MIDI_CMD_PGM_CHANGE | (ev->data.control.channel & 0xf));
add_byte(track, ev->data.control.value & 0x7f);
break;
case SND_SEQ_EVENT_CHANPRESS:
delta_time(track, ev);
command(track, MIDI_CMD_CHANNEL_PRESSURE | (ev->data.control.channel & 0xf));
add_byte(track, ev->data.control.value & 0x7f);
break;
case SND_SEQ_EVENT_PITCHBEND:
delta_time(track, ev);
command(track, MIDI_CMD_BENDER | (ev->data.control.channel & 0xf));
add_byte(track, (ev->data.control.value + 8192) & 0x7f);
add_byte(track, ((ev->data.control.value + 8192) >> 7) & 0x7f);
break;
case SND_SEQ_EVENT_CONTROL14:
/* create two commands for MSB and LSB */
delta_time(track, ev);
command(track, MIDI_CMD_CONTROL | (ev->data.control.channel & 0xf));
add_byte(track, ev->data.control.param & 0x7f);
add_byte(track, (ev->data.control.value >> 7) & 0x7f);
if ((ev->data.control.param & 0x7f) < 0x20) {
delta_time(track, ev);
/* running status */
add_byte(track, (ev->data.control.param & 0x7f) + 0x20);
add_byte(track, ev->data.control.value & 0x7f);
}
break;
case SND_SEQ_EVENT_NONREGPARAM:
delta_time(track, ev);
command(track, MIDI_CMD_CONTROL | (ev->data.control.channel & 0xf));
add_byte(track, MIDI_CTL_NONREG_PARM_NUM_LSB);
add_byte(track, ev->data.control.param & 0x7f);
delta_time(track, ev);
add_byte(track, MIDI_CTL_NONREG_PARM_NUM_MSB);
add_byte(track, (ev->data.control.param >> 7) & 0x7f);
delta_time(track, ev);
add_byte(track, MIDI_CTL_MSB_DATA_ENTRY);
add_byte(track, (ev->data.control.value >> 7) & 0x7f);
delta_time(track, ev);
add_byte(track, MIDI_CTL_LSB_DATA_ENTRY);
add_byte(track, ev->data.control.value & 0x7f);
break;
case SND_SEQ_EVENT_REGPARAM:
delta_time(track, ev);
command(track, MIDI_CMD_CONTROL | (ev->data.control.channel & 0xf));
add_byte(track, MIDI_CTL_REGIST_PARM_NUM_LSB);
add_byte(track, ev->data.control.param & 0x7f);
delta_time(track, ev);
add_byte(track, MIDI_CTL_REGIST_PARM_NUM_MSB);
add_byte(track, (ev->data.control.param >> 7) & 0x7f);
delta_time(track, ev);
add_byte(track, MIDI_CTL_MSB_DATA_ENTRY);
add_byte(track, (ev->data.control.value >> 7) & 0x7f);
delta_time(track, ev);
add_byte(track, MIDI_CTL_LSB_DATA_ENTRY);
add_byte(track, ev->data.control.value & 0x7f);
break;
#if 0 /* ignore */
case SND_SEQ_EVENT_SONGPOS:
case SND_SEQ_EVENT_SONGSEL:
case SND_SEQ_EVENT_QFRAME:
case SND_SEQ_EVENT_START:
case SND_SEQ_EVENT_CONTINUE:
case SND_SEQ_EVENT_STOP:
case SND_SEQ_EVENT_TUNE_REQUEST:
case SND_SEQ_EVENT_RESET:
case SND_SEQ_EVENT_SENSING:
break;
#endif
case SND_SEQ_EVENT_SYSEX:
if (ev->data.ext.len == 0)
break;
delta_time(track, ev);
if (*(unsigned char*)ev->data.ext.ptr == 0xf0)
command(track, 0xf0), i = 1;
else
command(track, 0xf7), i = 0;
var_value(track, ev->data.ext.len - i);
for (; i < ev->data.ext.len; ++i)
add_byte(track, ((unsigned char*)ev->data.ext.ptr)[i]);
break;
default:
return;
}
track->used = 1;
}
static void finish_tracks(void)
{
snd_seq_queue_status_t *queue_status;
int tick, i, err;
snd_seq_queue_status_alloca(&queue_status);
err = snd_seq_get_queue_status(seq, queue, queue_status);
check_snd("get queue status", err);
tick = snd_seq_queue_status_get_tick_time(queue_status);
/* make length of first track the recording length */
var_value(&tracks[0], tick - tracks[0].last_tick);
add_byte(&tracks[0], 0xff);
add_byte(&tracks[0], 0x2f);
var_value(&tracks[0], 0);
/* finish other tracks */
for (i = 1; i < num_tracks; ++i) {
var_value(&tracks[i], 0);
add_byte(&tracks[i], 0xff);
add_byte(&tracks[i], 0x2f);
var_value(&tracks[i], 0);
}
}
static void write_file(void)
{
int used_tracks, time_division, i;
struct buffer *buf;
used_tracks = 0;
for (i = 0; i < num_tracks; ++i)
used_tracks += !!tracks[i].used;
/* header id and length */
fwrite("MThd\0\0\0\6", 1, 8, file);
/* type 0 or 1 */
fputc(0, file);
fputc(used_tracks > 1, file);
/* number of tracks */
fputc((used_tracks >> 8) & 0xff, file);
fputc(used_tracks & 0xff, file);
/* time division */
time_division = ticks;
if (smpte_timing)
time_division |= (0x100 - frames) << 8;
fputc(time_division >> 8, file);
fputc(time_division & 0xff, file);
for (i = 0; i < num_tracks; ++i) {
if (!tracks[i].used)
continue;
/* track id */
fwrite("MTrk", 1, 4, file);
/* data length */
fputc((tracks[i].size >> 24) & 0xff, file);
fputc((tracks[i].size >> 16) & 0xff, file);
fputc((tracks[i].size >> 8) & 0xff, file);
fputc(tracks[i].size & 0xff, file);
/* track contents */
for (buf = &tracks[i].first_buf; buf; buf = buf->next)
fwrite(buf->buf, 1, buf == tracks[i].cur_buf
? tracks[i].cur_buf_size : BUFFER_SIZE, file);
}
}
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);
if (client == SND_SEQ_CLIENT_SYSTEM)
continue; /* don't show system timer and announce ports */
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) {
/* port must understand MIDI messages */
if (!(snd_seq_port_info_get_type(pinfo)
& SND_SEQ_PORT_TYPE_MIDI_GENERIC))
continue;
/* we need both READ and SUBS_READ */
if ((snd_seq_port_info_get_capability(pinfo)
& (SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ))
!= (SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ))
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 help(const char *argv0)
{
fprintf(stderr, "Usage: %s [options] outputfile\n"
"\nAvailable options:\n"
" -h,--help this help\n"
" -V,--version show version\n"
" -l,--list list input ports\n"
" -p,--port=client:port,... source port(s)\n"
" -b,--bpm=beats tempo in beats per minute\n"
" -f,--fps=frames resolution in frames per second (SMPTE)\n"
" -t,--ticks=ticks resolution in ticks per beat or frame\n"
" -s,--split-channels create a track for each channel\n"
" -m,--metronome=client:port play a metronome signal\n"
" -i,--timesig=nn:dd time signature\n",
argv0);
}
static void version(void)
{
fputs("arecordmidi version " SND_UTIL_VERSION_STR "\n", stderr);
}
static void sighandler(int sig)
{
stop = 1;
}
int main(int argc, char *argv[])
{
static const char short_options[] = "hVlp:b:f:t:sdm:i:";
static const struct option long_options[] = {
{"help", 0, NULL, 'h'},
{"version", 0, NULL, 'V'},
{"list", 0, NULL, 'l'},
{"port", 1, NULL, 'p'},
{"bpm", 1, NULL, 'b'},
{"fps", 1, NULL, 'f'},
{"ticks", 1, NULL, 't'},
{"split-channels", 0, NULL, 's'},
{"dump", 0, NULL, 'd'},
{"metronome", 1, NULL, 'm'},
{"timesig", 1, NULL, 'i'},
{ }
};
char *filename = NULL;
int do_list = 0;
struct pollfd *pfds;
int npfds;
int c, err;
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 'l':
do_list = 1;
break;
case 'p':
parse_ports(optarg);
break;
case 'b':
beats = atoi(optarg);
if (beats < 4 || beats > 6000)
fatal("Invalid tempo");
smpte_timing = 0;
break;
case 'f':
frames = atoi(optarg);
if (frames != 24 && frames != 25 &&
frames != 29 && frames != 30)
fatal("Invalid number of frames/s");
smpte_timing = 1;
break;
case 't':
ticks = atoi(optarg);
if (ticks < 1 || ticks > 0x7fff)
fatal("Invalid number of ticks");
break;
case 's':
channel_split = 1;
break;
case 'd':
fputs("The --dump option isn't supported anymore, use aseqdump instead.\n", stderr);
break;
case 'm':
init_metronome(optarg);
break;
case 'i':
time_signature(optarg);
break;
default:
help(argv[0]);
return 1;
}
}
if (do_list) {
list_ports();
return 0;
}
if (port_count < 1) {
fputs("Pleast specify a source port with --port.\n", stderr);
return 1;
}
if (!ticks)
ticks = smpte_timing ? 40 : 384;
if (smpte_timing && ticks > 0xff)
ticks = 0xff;
if (optind >= argc) {
fputs("Please specify a file to record to.\n", stderr);
return 1;
}
filename = argv[optind];
init_tracks();
create_queue();
create_ports();
connect_ports();
if (port_count > 1)
record_port_numbers();
/* record tempo */
if (!smpte_timing) {
int usecs_per_quarter = 60000000 / beats;
var_value(&tracks[0], 0); /* delta time */
add_byte(&tracks[0], 0xff);
add_byte(&tracks[0], 0x51);
var_value(&tracks[0], 3);
add_byte(&tracks[0], usecs_per_quarter >> 16);
add_byte(&tracks[0], usecs_per_quarter >> 8);
add_byte(&tracks[0], usecs_per_quarter);
/* time signature */
var_value(&tracks[0], 0); /* delta time */
add_byte(&tracks[0], 0xff);
add_byte(&tracks[0], 0x58);
var_value(&tracks[0], 4);
add_byte(&tracks[0], ts_num);
add_byte(&tracks[0], ts_dd);
add_byte(&tracks[0], 24); /* MIDI clocks per metronome click */
add_byte(&tracks[0], 8); /* notated 32nd-notes per MIDI quarter note */
}
/* always write at least one track */
tracks[0].used = 1;
file = fopen(filename, "wb");
if (!file)
fatal("Cannot open %s - %s", filename, strerror(errno));
err = snd_seq_start_queue(seq, queue, NULL);
check_snd("start queue", err);
snd_seq_drain_output(seq);
err = snd_seq_nonblock(seq, 1);
check_snd("set nonblock mode", err);
if (use_metronome) {
metronome_set_program();
metronome_pattern(0);
}
signal(SIGINT, sighandler);
signal(SIGTERM, sighandler);
npfds = snd_seq_poll_descriptors_count(seq, POLLIN);
pfds = alloca(sizeof(*pfds) * npfds);
for (;;) {
snd_seq_poll_descriptors(seq, pfds, npfds, POLLIN);
if (poll(pfds, npfds, -1) < 0)
break;
do {
snd_seq_event_t *event;
err = snd_seq_event_input(seq, &event);
if (err < 0)
break;
if (event)
record_event(event);
} while (err > 0);
if (stop)
break;
}
finish_tracks();
write_file();
fclose(file);
snd_seq_close(seq);
return 0;
}