From 58a11d86fd4dbba5b82a5b4561298ea0f16e2353 Mon Sep 17 00:00:00 2001 From: Clemens Ladisch Date: Tue, 6 Apr 2004 06:22:19 +0000 Subject: arecordmidi enhancements by Pedro Lopez-Cabanillas --- seq/aplaymidi/arecordmidi.1 | 22 +++- seq/aplaymidi/arecordmidi.c | 252 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 271 insertions(+), 3 deletions(-) (limited to 'seq') diff --git a/seq/aplaymidi/arecordmidi.1 b/seq/aplaymidi/arecordmidi.1 index 0e94a09..d1914a0 100644 --- a/seq/aplaymidi/arecordmidi.1 +++ b/seq/aplaymidi/arecordmidi.1 @@ -1,4 +1,4 @@ -.TH ARECORDMIDI 1 "22 Feb 2004" +.TH ARECORDMIDI 1 "6 Apr 2004" .SH NAME 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 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 Clemens Ladisch diff --git a/seq/aplaymidi/arecordmidi.c b/seq/aplaymidi/arecordmidi.c index 4f07d05..506a45f 100644 --- a/seq/aplaymidi/arecordmidi.c +++ b/seq/aplaymidi/arecordmidi.c @@ -53,6 +53,13 @@ struct smf_track { /* 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; @@ -68,6 +75,17 @@ static int channel_split; static int num_tracks; static struct smf_track *tracks; 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 */ @@ -142,6 +160,171 @@ static void parse_ports(const char *arg) 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) { int i; @@ -237,6 +420,20 @@ static void create_ports(void) 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) @@ -249,6 +446,14 @@ static void connect_ports(void) 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 */ @@ -327,6 +532,11 @@ static void record_event(const snd_seq_event_t *ev) /* 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)) @@ -561,7 +771,10 @@ static void help(const char *argv0) " -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", + " -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); } @@ -577,7 +790,7 @@ static void sighandler(int sig) 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[] = { {"help", 0, NULL, 'h'}, {"version", 0, NULL, 'V'}, @@ -587,6 +800,9 @@ int main(int argc, char *argv[]) {"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'}, { } }; @@ -634,6 +850,15 @@ int main(int argc, char *argv[]) case 's': channel_split = 1; break; + case 'd': + dump = 1; + break; + case 'm': + init_metronome(optarg); + break; + case 'i': + time_signature(optarg); + break; default: help(argv[0]); 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 >> 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; @@ -692,6 +928,16 @@ int main(int argc, char *argv[]) err = snd_seq_nonblock(seq, 1); 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(SIGTERM, sighandler); @@ -709,6 +955,8 @@ int main(int argc, char *argv[]) break; if (event) record_event(event); + if (dump && event->dest.port < port_count) + print_midi_event(event); } while (err > 0); if (stop) break; -- cgit v1.2.1