mirror of
https://github.com/alsa-project/alsa-utils
synced 2024-12-23 04:56:30 +01:00
arecordmidi enhancements by Pedro Lopez-Cabanillas
This commit is contained in:
parent
cfa7fe10eb
commit
58a11d86fd
2 changed files with 271 additions and 3 deletions
|
@ -1,4 +1,4 @@
|
||||||
.TH ARECORDMIDI 1 "22 Feb 2004"
|
.TH ARECORDMIDI 1 "6 Apr 2004"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
arecordmidi - record Standard MIDI Files
|
arecordmidi - record Standard MIDI Files
|
||||||
|
@ -58,5 +58,25 @@ The default value is 384 ticks/beat or 40 ticks/frame, respectively.
|
||||||
Specifies that the data for each MIDI channel should be written to a
|
Specifies that the data for each MIDI channel should be written to a
|
||||||
separate track in the MIDI file.
|
separate track in the MIDI file.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.I -d,--dump
|
||||||
|
Shows the events received as text on standard output.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.I -m,--metronome=client:port
|
||||||
|
Plays a metronome signal on the specified sequencer port.
|
||||||
|
|
||||||
|
Metronome sounds are played on channel 10, MIDI notes 33 & 34 (GM2/GS/XG
|
||||||
|
metronome standard notes), with velocity 100 and duration 1.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.I -i,--timesig=numerator:denominator
|
||||||
|
Sets the time signature for the MIDI file and metronome.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
.SH AUTHOR
|
.SH AUTHOR
|
||||||
Clemens Ladisch <clemens@ladisch.de>
|
Clemens Ladisch <clemens@ladisch.de>
|
||||||
|
|
|
@ -53,6 +53,13 @@ struct smf_track {
|
||||||
/* timing/sysex + 16 channels */
|
/* timing/sysex + 16 channels */
|
||||||
#define TRACKS_PER_PORT 17
|
#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 snd_seq_t *seq;
|
||||||
static int client;
|
static int client;
|
||||||
|
@ -68,6 +75,17 @@ static int channel_split;
|
||||||
static int num_tracks;
|
static int num_tracks;
|
||||||
static struct smf_track *tracks;
|
static struct smf_track *tracks;
|
||||||
static volatile sig_atomic_t stop = 0;
|
static volatile sig_atomic_t stop = 0;
|
||||||
|
static int dump = 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 */
|
/* prints an error message to stderr, and dies */
|
||||||
|
@ -142,6 +160,171 @@ static void parse_ports(const char *arg)
|
||||||
free(buf);
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Dump incoming events
|
||||||
|
*/
|
||||||
|
static void print_syx(unsigned int len, unsigned char *data)
|
||||||
|
{
|
||||||
|
unsigned int i;
|
||||||
|
|
||||||
|
for (i = 0; i < len; ++i) {
|
||||||
|
printf(" %02x", data[i]);
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_time(snd_seq_event_t *ev)
|
||||||
|
{
|
||||||
|
printf("%11d ", ev->time.tick);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_midi_event(snd_seq_event_t *ev)
|
||||||
|
{
|
||||||
|
switch (ev->type) {
|
||||||
|
case SND_SEQ_EVENT_NOTEON:
|
||||||
|
print_time(ev);
|
||||||
|
printf("Note on %2d %3d %3d\n",
|
||||||
|
ev->data.note.channel, ev->data.note.note, ev->data.note.velocity);
|
||||||
|
break;
|
||||||
|
case SND_SEQ_EVENT_NOTEOFF:
|
||||||
|
print_time(ev);
|
||||||
|
printf("Note off %2d %3d %3d\n",
|
||||||
|
ev->data.note.channel, ev->data.note.note, ev->data.note.velocity);
|
||||||
|
break;
|
||||||
|
case SND_SEQ_EVENT_KEYPRESS:
|
||||||
|
print_time(ev);
|
||||||
|
printf("Polyphonic aftertouch %2d %3d %3d\n",
|
||||||
|
ev->data.note.channel, ev->data.note.note, ev->data.note.velocity);
|
||||||
|
break;
|
||||||
|
case SND_SEQ_EVENT_CONTROLLER:
|
||||||
|
print_time(ev);
|
||||||
|
printf("Control change %2d %3d %3d\n",
|
||||||
|
ev->data.control.channel, ev->data.control.param, ev->data.control.value);
|
||||||
|
break;
|
||||||
|
case SND_SEQ_EVENT_PGMCHANGE:
|
||||||
|
print_time(ev);
|
||||||
|
printf("Program change %2d %3d\n",
|
||||||
|
ev->data.control.channel, ev->data.control.value);
|
||||||
|
break;
|
||||||
|
case SND_SEQ_EVENT_CHANPRESS:
|
||||||
|
print_time(ev);
|
||||||
|
printf("Channel aftertouch %2d %3d\n",
|
||||||
|
ev->data.control.channel, ev->data.control.value);
|
||||||
|
break;
|
||||||
|
case SND_SEQ_EVENT_PITCHBEND:
|
||||||
|
print_time(ev);
|
||||||
|
printf("Pitch bend %2d %6d\n",
|
||||||
|
ev->data.control.channel, ev->data.control.value);
|
||||||
|
break;
|
||||||
|
case SND_SEQ_EVENT_CONTROL14:
|
||||||
|
print_time(ev);
|
||||||
|
printf("Control change %2d %3d %5d\n",
|
||||||
|
ev->data.control.channel, ev->data.control.param, ev->data.control.value);
|
||||||
|
break;
|
||||||
|
case SND_SEQ_EVENT_NONREGPARAM:
|
||||||
|
print_time(ev);
|
||||||
|
printf("Non-reg. parameter %2d %5d %5d\n",
|
||||||
|
ev->data.control.channel, ev->data.control.param, ev->data.control.value);
|
||||||
|
break;
|
||||||
|
case SND_SEQ_EVENT_REGPARAM:
|
||||||
|
print_time(ev);
|
||||||
|
printf("Reg. parameter %2d %5d %5d\n",
|
||||||
|
ev->data.control.channel, ev->data.control.param, ev->data.control.value);
|
||||||
|
break;
|
||||||
|
case SND_SEQ_EVENT_SENSING:
|
||||||
|
print_time(ev);
|
||||||
|
printf("Active Sensing\n");
|
||||||
|
break;
|
||||||
|
case SND_SEQ_EVENT_SYSEX:
|
||||||
|
print_time(ev);
|
||||||
|
printf("System exclusive ");
|
||||||
|
print_syx(ev->data.ext.len, ev->data.ext.ptr);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
print_time(ev);
|
||||||
|
printf("Event type %d\n", ev->type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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)
|
static void init_tracks(void)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
@ -237,6 +420,20 @@ static void create_ports(void)
|
||||||
err = snd_seq_create_port(seq, pinfo);
|
err = snd_seq_create_port(seq, pinfo);
|
||||||
check_snd("create port", err);
|
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)
|
static void connect_ports(void)
|
||||||
|
@ -249,6 +446,14 @@ static void connect_ports(void)
|
||||||
fatal("Cannot connect from port %d:%d - %s",
|
fatal("Cannot connect from port %d:%d - %s",
|
||||||
ports[i].client, ports[i].port, snd_strerror(err));
|
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 */
|
/* records a byte to be written to the .mid file */
|
||||||
|
@ -327,6 +532,11 @@ static void record_event(const snd_seq_event_t *ev)
|
||||||
|
|
||||||
/* determine which track to record to */
|
/* determine which track to record to */
|
||||||
i = ev->dest.port;
|
i = ev->dest.port;
|
||||||
|
if (i == port_count) {
|
||||||
|
if (ev->type == SND_SEQ_EVENT_USR0)
|
||||||
|
metronome_pattern(ev->time.tick);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (channel_split) {
|
if (channel_split) {
|
||||||
i *= TRACKS_PER_PORT;
|
i *= TRACKS_PER_PORT;
|
||||||
if (snd_seq_ev_is_channel_type(ev))
|
if (snd_seq_ev_is_channel_type(ev))
|
||||||
|
@ -561,7 +771,10 @@ static void help(const char *argv0)
|
||||||
" -b,--bpm=beats tempo in beats per minute\n"
|
" -b,--bpm=beats tempo in beats per minute\n"
|
||||||
" -f,--fps=frames resolution in frames per second (SMPTE)\n"
|
" -f,--fps=frames resolution in frames per second (SMPTE)\n"
|
||||||
" -t,--ticks=ticks resolution in ticks per beat or frame\n"
|
" -t,--ticks=ticks resolution in ticks per beat or frame\n"
|
||||||
" -s,--split-channels create a track for each channel\n",
|
" -s,--split-channels create a track for each channel\n"
|
||||||
|
" -d,--dump dump events on standard output\n"
|
||||||
|
" -m,--metronome=client:port play a metronome signal\n"
|
||||||
|
" -i,--timesig=nn:dd time signature\n",
|
||||||
argv0);
|
argv0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -577,7 +790,7 @@ static void sighandler(int sig)
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
static char short_options[] = "hVlp:b:f:t:s";
|
static char short_options[] = "hVlp:b:f:t:sdm:i:";
|
||||||
static struct option long_options[] = {
|
static struct option long_options[] = {
|
||||||
{"help", 0, NULL, 'h'},
|
{"help", 0, NULL, 'h'},
|
||||||
{"version", 0, NULL, 'V'},
|
{"version", 0, NULL, 'V'},
|
||||||
|
@ -587,6 +800,9 @@ int main(int argc, char *argv[])
|
||||||
{"fps", 1, NULL, 'f'},
|
{"fps", 1, NULL, 'f'},
|
||||||
{"ticks", 1, NULL, 't'},
|
{"ticks", 1, NULL, 't'},
|
||||||
{"split-channels", 0, NULL, 's'},
|
{"split-channels", 0, NULL, 's'},
|
||||||
|
{"dump", 0, NULL, 'd'},
|
||||||
|
{"metronome", 1, NULL, 'm'},
|
||||||
|
{"timesig", 1, NULL, 'i'},
|
||||||
{ }
|
{ }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -634,6 +850,15 @@ int main(int argc, char *argv[])
|
||||||
case 's':
|
case 's':
|
||||||
channel_split = 1;
|
channel_split = 1;
|
||||||
break;
|
break;
|
||||||
|
case 'd':
|
||||||
|
dump = 1;
|
||||||
|
break;
|
||||||
|
case 'm':
|
||||||
|
init_metronome(optarg);
|
||||||
|
break;
|
||||||
|
case 'i':
|
||||||
|
time_signature(optarg);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
help(argv[0]);
|
help(argv[0]);
|
||||||
return 1;
|
return 1;
|
||||||
|
@ -678,7 +903,18 @@ int main(int argc, char *argv[])
|
||||||
add_byte(&tracks[0], usecs_per_quarter >> 16);
|
add_byte(&tracks[0], usecs_per_quarter >> 16);
|
||||||
add_byte(&tracks[0], usecs_per_quarter >> 8);
|
add_byte(&tracks[0], usecs_per_quarter >> 8);
|
||||||
add_byte(&tracks[0], usecs_per_quarter);
|
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 */
|
/* always write at least one track */
|
||||||
tracks[0].used = 1;
|
tracks[0].used = 1;
|
||||||
|
|
||||||
|
@ -692,6 +928,16 @@ int main(int argc, char *argv[])
|
||||||
|
|
||||||
err = snd_seq_nonblock(seq, 1);
|
err = snd_seq_nonblock(seq, 1);
|
||||||
check_snd("set nonblock mode", err);
|
check_snd("set nonblock mode", err);
|
||||||
|
|
||||||
|
if (dump) {
|
||||||
|
printf("Waiting for data. Press Ctrl+C to end\n");
|
||||||
|
printf("_______Tick Event_________________ Ch _Data__\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (use_metronome) {
|
||||||
|
metronome_set_program();
|
||||||
|
metronome_pattern(0);
|
||||||
|
}
|
||||||
|
|
||||||
signal(SIGINT, sighandler);
|
signal(SIGINT, sighandler);
|
||||||
signal(SIGTERM, sighandler);
|
signal(SIGTERM, sighandler);
|
||||||
|
@ -709,6 +955,8 @@ int main(int argc, char *argv[])
|
||||||
break;
|
break;
|
||||||
if (event)
|
if (event)
|
||||||
record_event(event);
|
record_event(event);
|
||||||
|
if (dump && event->dest.port < port_count)
|
||||||
|
print_midi_event(event);
|
||||||
} while (err > 0);
|
} while (err > 0);
|
||||||
if (stop)
|
if (stop)
|
||||||
break;
|
break;
|
||||||
|
|
Loading…
Reference in a new issue