Module Name: src Committed By: martin Date: Mon Jan 12 10:22:22 UTC 2015
Modified Files: src/distrib/sets/lists/base [netbsd-7]: mi src/distrib/sets/lists/debug [netbsd-7]: mi src/distrib/sets/lists/man [netbsd-7]: mi src/usr.bin [netbsd-7]: Makefile Added Files: src/usr.bin/midirecord [netbsd-7]: Makefile midirecord.1 midirecord.c Log Message: Pull up following revision(s) (requested by mrg in ticket #409): usr.bin/midirecord/Makefile: revision 1.1 usr.bin/midirecord/midirecord.1: revision 1.1-1.3 usr.bin/midirecord/midirecord.c: revision 1.1-1.6 usr.bin/Makefile: revision 1.219 distrib/sets/lists/base/mi: revision 1.1093 distrib/sets/lists/man/mi: revision 1.1492 distrib/sets/lists/debug/mi: revision 1.98 Add midirecord. To generate a diff of this commit: cvs rdiff -u -r1.1087.2.1 -r1.1087.2.2 src/distrib/sets/lists/base/mi cvs rdiff -u -r1.81.2.1 -r1.81.2.2 src/distrib/sets/lists/debug/mi cvs rdiff -u -r1.1485.2.4 -r1.1485.2.5 src/distrib/sets/lists/man/mi cvs rdiff -u -r1.218 -r1.218.2.1 src/usr.bin/Makefile cvs rdiff -u -r0 -r1.1.2.2 src/usr.bin/midirecord/Makefile cvs rdiff -u -r0 -r1.3.2.2 src/usr.bin/midirecord/midirecord.1 cvs rdiff -u -r0 -r1.6.2.2 src/usr.bin/midirecord/midirecord.c Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/distrib/sets/lists/base/mi diff -u src/distrib/sets/lists/base/mi:1.1087.2.1 src/distrib/sets/lists/base/mi:1.1087.2.2 --- src/distrib/sets/lists/base/mi:1.1087.2.1 Tue Nov 18 18:32:29 2014 +++ src/distrib/sets/lists/base/mi Mon Jan 12 10:22:22 2015 @@ -1,4 +1,4 @@ -# $NetBSD: mi,v 1.1087.2.1 2014/11/18 18:32:29 snj Exp $ +# $NetBSD: mi,v 1.1087.2.2 2015/01/12 10:22:22 martin Exp $ # # Note: Don't delete entries from here - mark them as "obsolete" instead, # unless otherwise stated below. @@ -593,6 +593,7 @@ ./usr/bin/merge base-util-bin ./usr/bin/mesg base-util-bin ./usr/bin/midiplay base-audio-bin +./usr/bin/midirecord base-audio-bin ./usr/bin/mixerctl base-audio-bin ./usr/bin/mk_cmds base-obsolete obsolete ./usr/bin/mkcsmapper base-util-bin Index: src/distrib/sets/lists/debug/mi diff -u src/distrib/sets/lists/debug/mi:1.81.2.1 src/distrib/sets/lists/debug/mi:1.81.2.2 --- src/distrib/sets/lists/debug/mi:1.81.2.1 Fri Aug 15 12:26:56 2014 +++ src/distrib/sets/lists/debug/mi Mon Jan 12 10:22:22 2015 @@ -1,4 +1,4 @@ -# $NetBSD: mi,v 1.81.2.1 2014/08/15 12:26:56 martin Exp $ +# $NetBSD: mi,v 1.81.2.2 2015/01/12 10:22:22 martin Exp $ ./etc/mtree/set.debug comp-sys-root ./usr/lib/i18n/libBIG5_g.a comp-c-debuglib debuglib @@ -616,6 +616,7 @@ ./usr/libdata/debug/usr/bin/merge.debug comp-util-debug debug ./usr/libdata/debug/usr/bin/mesg.debug comp-util-debug debug ./usr/libdata/debug/usr/bin/midiplay.debug comp-audio-debug debug +./usr/libdata/debug/usr/bin/midirecord.debug comp-audio-debug debug ./usr/libdata/debug/usr/bin/mixerctl.debug comp-audio-debug debug ./usr/libdata/debug/usr/bin/mk_cmds.debug comp-obsolete obsolete ./usr/libdata/debug/usr/bin/mkcsmapper.debug comp-util-debug debug Index: src/distrib/sets/lists/man/mi diff -u src/distrib/sets/lists/man/mi:1.1485.2.4 src/distrib/sets/lists/man/mi:1.1485.2.5 --- src/distrib/sets/lists/man/mi:1.1485.2.4 Fri Dec 12 16:38:48 2014 +++ src/distrib/sets/lists/man/mi Mon Jan 12 10:22:22 2015 @@ -1,4 +1,4 @@ -# $NetBSD: mi,v 1.1485.2.4 2014/12/12 16:38:48 martin Exp $ +# $NetBSD: mi,v 1.1485.2.5 2015/01/12 10:22:22 martin Exp $ # # Note: don't delete entries from here - mark them as "obsolete" instead. # @@ -319,6 +319,7 @@ ./usr/share/man/cat1/merge.0 man-util-catman .cat ./usr/share/man/cat1/mesg.0 man-util-catman .cat ./usr/share/man/cat1/midiplay.0 man-audio-catman .cat +./usr/share/man/cat1/midirecord.0 man-audio-catman .cat ./usr/share/man/cat1/mixerctl.0 man-audio-catman .cat ./usr/share/man/cat1/mkdep.0 man-c-catman .cat ./usr/share/man/cat1/mkdir.0 man-util-catman .cat @@ -3436,6 +3437,7 @@ ./usr/share/man/html1/merge.html man-util-htmlman html ./usr/share/man/html1/mesg.html man-util-htmlman html ./usr/share/man/html1/midiplay.html man-audio-htmlman html +./usr/share/man/html1/midirecord.html man-audio-htmlman html ./usr/share/man/html1/mixerctl.html man-audio-htmlman html ./usr/share/man/html1/mkdep.html man-c-htmlman html ./usr/share/man/html1/mkdir.html man-util-htmlman html @@ -6192,6 +6194,7 @@ ./usr/share/man/man1/merge.1 man-util-man .man ./usr/share/man/man1/mesg.1 man-util-man .man ./usr/share/man/man1/midiplay.1 man-audio-man .man +./usr/share/man/man1/midirecord.1 man-audio-man .man ./usr/share/man/man1/mixerctl.1 man-audio-man .man ./usr/share/man/man1/mkdep.1 man-c-man .man ./usr/share/man/man1/mkdir.1 man-util-man .man Index: src/usr.bin/Makefile diff -u src/usr.bin/Makefile:1.218 src/usr.bin/Makefile:1.218.2.1 --- src/usr.bin/Makefile:1.218 Fri Aug 1 14:01:30 2014 +++ src/usr.bin/Makefile Mon Jan 12 10:22:22 2015 @@ -1,4 +1,4 @@ -# $NetBSD: Makefile,v 1.218 2014/08/01 14:01:30 christos Exp $ +# $NetBSD: Makefile,v 1.218.2.1 2015/01/12 10:22:22 martin Exp $ # from: @(#)Makefile 8.3 (Berkeley) 1/7/94 .include <bsd.own.mk> @@ -16,9 +16,9 @@ SUBDIR= apply asa at audio audiocfg \ head hexdump iconv id indent infocmp innetgr ipcrm ipcs join jot \ kdump ktrace ktruss lam last lastcomm ldd leave \ locale locate lock logger login logname look lorder m4 \ - machine mail make man menuc mesg midiplay mixerctl mkcsmapper \ - mkdep mkesdb mkfifo mklocale mkstr mktemp mkubootimage moduli \ - msgc msgs \ + machine mail make man menuc mesg midiplay midirecord mixerctl \ + mkcsmapper mkdep mkesdb mkfifo mklocale mkstr mktemp mkubootimage \ + moduli msgc msgs \ nbperf netgroup netstat newgrp newsyslog nfsstat nice nl nohup \ pagesize passwd paste patch pathchk pkill pmap pmc pr \ printenv printf progress pwhash qsubst quota radioctl rdist \ Added files: Index: src/usr.bin/midirecord/Makefile diff -u /dev/null src/usr.bin/midirecord/Makefile:1.1.2.2 --- /dev/null Mon Jan 12 10:22:22 2015 +++ src/usr.bin/midirecord/Makefile Mon Jan 12 10:22:22 2015 @@ -0,0 +1,15 @@ +# $NetBSD: Makefile,v 1.1.2.2 2015/01/12 10:22:22 martin Exp $ + +PROG= midirecord + +.include <bsd.own.mk> + +LIBAUDIO != cd ${.CURDIR}/../audio/common && ${PRINTOBJDIR} +CPPFLAGS+=-I${.CURDIR}/../audio/common +DPADD+= ${LIBAUDIO}/libaudio.a +LDADD+= -L${LIBAUDIO} -laudio + +DPADD+= ${LIBUTIL} +LDADD+= -lutil + +.include <bsd.prog.mk> Index: src/usr.bin/midirecord/midirecord.1 diff -u /dev/null src/usr.bin/midirecord/midirecord.1:1.3.2.2 --- /dev/null Mon Jan 12 10:22:22 2015 +++ src/usr.bin/midirecord/midirecord.1 Mon Jan 12 10:22:22 2015 @@ -0,0 +1,125 @@ +.\" $NetBSD: midirecord.1,v 1.3.2.2 2015/01/12 10:22:22 martin Exp $ +.\" +.\" Copyright (c) 1998, 1999, 2001, 2002, 2010 Matthew R. Green +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +.\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +.\" BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +.\" LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +.\" AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +.\" OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd December 30, 2014 +.Dt MIDIRECORD 1 +.Os +.Sh NAME +.Nm midirecord +.Nd record midi files +.Sh SYNOPSIS +.Nm +.Op Fl aDfhoqV +.Op Fl B Ar buffersize +.Op Fl c Ar channels +.Op Fl d Ar devices +.Op Fl f Ar sequencerdev +.Op Fl n Ar notesperbeat +.Op Fl r Ar raw_output +.Op Fl T Ar tempo +.Op Fl t Ar time +.Ar file +.Sh DESCRIPTION +The +.Nm +program converts the sequencer events read on +.Ar sequencerdev +to the named MIDI SMF output. +If the file name is \-, output will go to the standard output. +By default, timing for events begins with the first event. +The +.Fl o +flag may be used to start timing events at the process start up. +.Sh OPTIONS +The following options are available: +.Bl -tag -width XnXnotesperbeatX +.It Fl a +Append to the specified file, rather than overwriting. +.It Fl B Ar buffersize +Set the sequencer device read buffer size to +.Ar buffersize . +The default value is 32768 bytes. +.It Fl c Ar channels +Sets the filter list of channels to +.Ar channels , +which is a comma separated list of channels to filter in. +.It Fl D +Enable debug log. +.It Fl d Ar devices +Sets the filter list of devices to +.Ar devices , +which is a comma separated list of devices to filter in. +.It Fl f Ar sequencerdev +Sets the sequencer device to use to +.Ar sequencerdev . +The default is +.Pa /dev/music . +.It Fl h +Print a help message. +.It Fl n Ar notesperbeat +Sets the MIDI notes (clocks) per beat to +.Ar notesperbeat . +.It Fl o +Start the relative timer at process start up instead of at +the first event. +.It Fl q +Be quiet. +.It Fl r Ar raw_output +Create the raw output of the sequencer device in +.Ar raw_output . +.It Fl T Ar tempo +Set the tempo for the recording to +.Ar tempo . +.It Fl t Ar time +Sets the maximum amount of time to record. +Format is [hh:]mm:ss[.dddddd]. +.It Fl V +Be verbose. +.El +.Sh ENVIRONMENT +.Bl -tag -width MIDIDEVICE +.It Ev MIDIDEVICE +the midi device to be used. +.El +.Sh SEE ALSO +.Xr midiplay 1 , +.Xr midi 4 , +.Xr sequencer 4 +.Sh HISTORY +The +.Nm +program was first seen in +.Nx 8 . +.Sh AUTHORS +The +.Nm +program was written by +.An Matthew R. Green Aq Mt m...@eterna.com.au . +.Sh BUGS +SYSEX, LOCAL and FULLSIZE messages are not currently handled, but the +.Nx +.Xr sequencer 4 +device does not generate them. Index: src/usr.bin/midirecord/midirecord.c diff -u /dev/null src/usr.bin/midirecord/midirecord.c:1.6.2.2 --- /dev/null Mon Jan 12 10:22:22 2015 +++ src/usr.bin/midirecord/midirecord.c Mon Jan 12 10:22:22 2015 @@ -0,0 +1,802 @@ +/* $NetBSD: midirecord.c,v 1.6.2.2 2015/01/12 10:22:22 martin Exp $ */ + +/* + * Copyright (c) 2014 Matthew R. Green + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * midirecord(1), similar to audiorecord(1). + */ + +#include <sys/cdefs.h> + +#ifndef lint +__RCSID("$NetBSD: midirecord.c,v 1.6.2.2 2015/01/12 10:22:22 martin Exp $"); +#endif + +#include <sys/param.h> +#include <sys/midiio.h> +#include <sys/ioctl.h> +#include <sys/time.h> +#include <sys/uio.h> + +#include <err.h> +#include <errno.h> +#include <ctype.h> +#include <fcntl.h> +#include <paths.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <util.h> +#include <assert.h> +#include <stdbool.h> + +#include "libaudio.h" + +static const char *midi_device; +static unsigned *filt_devnos = NULL; +static unsigned *filt_chans = NULL; +static unsigned num_filt_devnos, num_filt_chans; +static char *raw_output; +static int midifd; +static int aflag, qflag, oflag; +static bool debug = false; +int verbose; +static int outfd, rawfd = -1; +static ssize_t data_size; +static struct timeval record_time; +static struct timeval start_time; +static int tempo = 120; +static unsigned notes_per_beat = 24; +static bool ignore_timer_fail = false; + +static void debug_log(const char *, size_t, const char *, ...) + __printflike(3, 4); +static size_t midi_event_local_to_output(seq_event_t, u_char *, size_t); +static size_t midi_event_timer_wait_abs_to_output(seq_event_t, u_char *, + size_t); +static size_t midi_event_timer_to_output(seq_event_t, u_char *, size_t); +static size_t midi_event_chn_common_to_output(seq_event_t, u_char *, size_t); +static size_t midi_event_chn_voice_to_output(seq_event_t, u_char *, size_t); +static size_t midi_event_sysex_to_output(seq_event_t, u_char *, size_t); +static size_t midi_event_fullsize_to_output(seq_event_t, u_char *, size_t); +static size_t midi_event_to_output(seq_event_t, u_char *, size_t); +static int timeleft(struct timeval *, struct timeval *); +static bool filter_array(unsigned, unsigned *, size_t); +static bool filter_dev(unsigned); +static bool filter_chan(unsigned); +static bool filter_devchan(unsigned, unsigned); +static void parse_ints(const char *, unsigned **, unsigned *, const char *); +static void cleanup(int) __dead; +static void rewrite_header(void); +static void write_midi_header(void); +static void write_midi_trailer(void); +static void usage(void) __dead; + +#define PATH_DEV_MUSIC "/dev/music" + +int +main(int argc, char *argv[]) +{ + u_char *buffer; + size_t bufsize = 0; + int ch, no_time_limit = 1; + + while ((ch = getopt(argc, argv, "aB:c:Dd:f:hn:oqr:t:T:V")) != -1) { + switch (ch) { + case 'a': + aflag++; + break; + case 'B': + bufsize = strsuftoll("read buffer size", optarg, + 1, UINT_MAX); + break; + case 'c': + parse_ints(optarg, &filt_chans, &num_filt_chans, + "channels"); + break; + case 'D': + debug++; + break; + case 'd': + parse_ints(optarg, &filt_devnos, &num_filt_devnos, + "devices"); + break; + case 'f': + midi_device = optarg; + ignore_timer_fail = true; + break; + case 'n': + decode_uint(optarg, ¬es_per_beat); + break; + case 'o': + oflag++; /* time stamp starts at proc start */ + break; + case 'q': + qflag++; + break; + case 'r': + raw_output = optarg; + break; + case 't': + no_time_limit = 0; + decode_time(optarg, &record_time); + break; + case 'T': + decode_int(optarg, &tempo); + break; + case 'V': + verbose++; + break; + /* case 'h': */ + default: + usage(); + /* NOTREACHED */ + } + } + argc -= optind; + argv += optind; + + if (argc != 1) + usage(); + + /* + * work out the buffer size to use, and allocate it. don't allow it + * to be too small. + */ + if (bufsize < 32) + bufsize = 32 * 1024; + buffer = malloc(bufsize); + if (buffer == NULL) + err(1, "couldn't malloc buffer of %d size", (int)bufsize); + + /* + * open the music device + */ + if (midi_device == NULL && (midi_device = getenv("MIDIDEVICE")) == NULL) + midi_device = PATH_DEV_MUSIC; + midifd = open(midi_device, O_RDONLY); + if (midifd < 0) + err(1, "failed to open %s", midi_device); + + /* open the output file */ + if (argv[0][0] != '-' || argv[0][1] != '\0') { + int mode = O_CREAT|(aflag ? O_APPEND : O_TRUNC)|O_WRONLY; + + outfd = open(*argv, mode, 0666); + if (outfd < 0) + err(1, "could not open %s", *argv); + } else + outfd = STDOUT_FILENO; + + /* open the raw output file */ + if (raw_output) { + int mode = O_CREAT|(aflag ? O_APPEND : O_TRUNC)|O_WRONLY; + + rawfd = open(raw_output, mode, 0666); + if (rawfd < 0) + err(1, "could not open %s", raw_output); + } + + /* start the midi timer */ + if (ioctl(midifd, SEQUENCER_TMR_START, NULL) < 0) { + if (ignore_timer_fail) + warn("failed to start midi timer"); + else + err(1, "failed to start midi timer"); + } + + /* set the timebase */ + if (ioctl(midifd, SEQUENCER_TMR_TIMEBASE, ¬es_per_beat) < 0) { + if (ignore_timer_fail) + warn("SEQUENCER_TMR_TIMEBASE: notes_per_beat %d", + notes_per_beat); + else + err(1, "SEQUENCER_TMR_TIMEBASE: notes_per_beat %d", + notes_per_beat); + } + + /* set the tempo */ + if (ioctl(midifd, SEQUENCER_TMR_TEMPO, &tempo) < 0) { + if (ignore_timer_fail) + warn("SEQUENCER_TMR_TIMEBASE: tempo %d", tempo); + else + err(1, "SEQUENCER_TMR_TIMEBASE: tempo %d", tempo); + } + + signal(SIGINT, cleanup); + + data_size = 0; + + if (verbose) + fprintf(stderr, "tempo=%d notes_per_beat=%d\n", + tempo, notes_per_beat); + + if (!no_time_limit && verbose) + fprintf(stderr, "recording for %lu seconds, %lu microseconds\n", + (u_long)record_time.tv_sec, (u_long)record_time.tv_usec); + + /* Create a midi header. */ + write_midi_header(); + + (void)gettimeofday(&start_time, NULL); + while (no_time_limit || timeleft(&start_time, &record_time)) { + seq_event_t e; + size_t wrsize; + size_t rdsize; + + rdsize = (size_t)read(midifd, &e, sizeof e); + if (rdsize == 0) + break; + + if (rdsize != sizeof e) + err(1, "read failed"); + + if (rawfd != -1 && write(rawfd, &e, sizeof e) != sizeof e) + err(1, "write to raw file failed"); + + /* convert 'e' into something useful for output */ + wrsize = midi_event_to_output(e, buffer, bufsize); + + if (wrsize) { + if ((size_t)write(outfd, buffer, wrsize) != wrsize) + err(1, "write failed"); + data_size += wrsize; + } + } + cleanup(0); +} + +static void +debug_log(const char *file, size_t line, const char *fmt, ...) +{ + va_list ap; + + if (!debug) + return; + fprintf(stderr, "%s:%zd: ", file, line); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, "\n"); +} + +#define LOG(fmt...) \ + debug_log(__func__, __LINE__, fmt) + + +/* + * handle a SEQ_LOCAL event. NetBSD /dev/music doesn't generate these. + */ +static size_t +midi_event_local_to_output(seq_event_t e, u_char *buffer, size_t bufsize) +{ + size_t size = 0; + + LOG("UNHANDLED SEQ_COCAL"); + + return size; +} + +/* + * convert a midi absolute time event to a variable length delay + */ +static size_t +midi_event_timer_wait_abs_to_output( + seq_event_t e, + u_char *buffer, + size_t bufsize) +{ + static unsigned prev_div; + unsigned cur_div; + unsigned val = 0, xdiv; + int vallen = 0, i; + + if (prev_div == 0 && !oflag) + prev_div = e.t_WAIT_ABS.divisions; + cur_div = e.t_WAIT_ABS.divisions; + + xdiv = cur_div - prev_div; + if (xdiv) { + while (xdiv) { + uint32_t extra = val ? 0x80 : 0; + + val <<= 8; + val |= (xdiv & 0x7f) | extra; + xdiv >>= 7; + vallen++; + } + } else + vallen = 1; + + for (i = 0; i < vallen; i++) { + buffer[i] = val & 0xff; + val >>= 8; + } + for (; i < 4; i++) + buffer[i] = 0; + LOG("TMR_WAIT_ABS: new div %x (cur %x prev %x): bufval (len=%u): " + "%02x:%02x:%02x:%02x", + cur_div - prev_div, cur_div, prev_div, + vallen, buffer[0], buffer[1], buffer[2], buffer[3]); + + prev_div = cur_div; + + return vallen; +} + +/* + * handle a SEQ_TIMING event. + */ +static size_t +midi_event_timer_to_output(seq_event_t e, u_char *buffer, size_t bufsize) +{ + size_t size = 0; + + LOG("SEQ_TIMING"); + switch (e.timing.op) { + case TMR_WAIT_REL: + /* NetBSD /dev/music doesn't generate these. */ + LOG("UNHANDLED TMR_WAIT_REL: divisions: %x", e.t_WAIT_REL.divisions); + break; + + case TMR_WAIT_ABS: + size = midi_event_timer_wait_abs_to_output(e, buffer, bufsize); + break; + + case TMR_STOP: + case TMR_START: + case TMR_CONTINUE: + case TMR_TEMPO: + case TMR_ECHO: + case TMR_CLOCK: + case TMR_SPP: + case TMR_TIMESIG: + /* NetBSD /dev/music doesn't generate these. */ + LOG("UNHANDLED timer op: %x", e.timing.op); + break; + + default: + LOG("unknown timer op: %x", e.timing.op); + break; + } + + return size; +} + +/* + * handle a SEQ_CHN_COMMON event. + */ +static size_t +midi_event_chn_common_to_output(seq_event_t e, u_char *buffer, size_t bufsize) +{ + size_t size = 0; + + assert(e.common.channel < 16); + LOG("SEQ_CHN_COMMON"); + + if (filter_devchan(e.common.device, e.common.channel)) + return 0; + + switch (e.common.op) { + case MIDI_CTL_CHANGE: + buffer[0] = MIDI_CTL_CHANGE | e.c_CTL_CHANGE.channel; + buffer[1] = e.c_CTL_CHANGE.controller; + buffer[2] = e.c_CTL_CHANGE.value; + LOG("MIDI_CTL_CHANGE: channel %x ctrl %x val %x", + e.c_CTL_CHANGE.channel, e.c_CTL_CHANGE.controller, + e.c_CTL_CHANGE.value); + size = 3; + break; + + case MIDI_PGM_CHANGE: + buffer[0] = MIDI_PGM_CHANGE | e.c_PGM_CHANGE.channel; + buffer[1] = e.c_PGM_CHANGE.program; + LOG("MIDI_PGM_CHANGE: channel %x program %x", + e.c_PGM_CHANGE.channel, e.c_PGM_CHANGE.program); + size = 2; + break; + + case MIDI_CHN_PRESSURE: + buffer[0] = MIDI_CHN_PRESSURE | e.c_CHN_PRESSURE.channel; + buffer[1] = e.c_CHN_PRESSURE.pressure; + LOG("MIDI_CHN_PRESSURE: channel %x pressure %x", + e.c_CHN_PRESSURE.channel, e.c_CHN_PRESSURE.pressure); + size = 2; + break; + + case MIDI_PITCH_BEND: + buffer[0] = MIDI_PITCH_BEND | e.c_PITCH_BEND.channel; + /* 14 bits split over 2 data bytes, lsb first */ + buffer[1] = e.c_PITCH_BEND.value & 0x7f; + buffer[2] = (e.c_PITCH_BEND.value >> 7) & 0x7f; + LOG("MIDI_PITCH_BEND: channel %x val %x", + e.c_PITCH_BEND.channel, e.c_PITCH_BEND.value); + size = 3; + break; + + default: + LOG("unknown common op: %x", e.voice.op); + break; + } + + return size; +} + +/* + * handle a SEQ_CHN_VOICE event. + */ +static size_t +midi_event_chn_voice_to_output(seq_event_t e, u_char *buffer, size_t bufsize) +{ + size_t size = 0; + + assert(e.common.channel < 16); + LOG("SEQ_CHN_VOICE"); + + if (filter_devchan(e.voice.device, e.voice.channel)) + return 0; + + switch (e.voice.op) { + case MIDI_NOTEOFF: + buffer[0] = MIDI_NOTEOFF | e.c_NOTEOFF.channel; + buffer[1] = e.c_NOTEOFF.key; + buffer[2] = e.c_NOTEOFF.velocity; + + LOG("MIDI_NOTEOFF: channel %x key %x velocity %x", + e.c_NOTEOFF.channel, e.c_NOTEOFF.key, e.c_NOTEOFF.velocity); + size = 3; + break; + + case MIDI_NOTEON: + buffer[0] = MIDI_NOTEON | e.c_NOTEON.channel; + buffer[1] = e.c_NOTEON.key; + buffer[2] = e.c_NOTEON.velocity; + + LOG("MIDI_NOTEON: channel %x key %x velocity %x", + e.c_NOTEON.channel, e.c_NOTEON.key, e.c_NOTEON.velocity); + size = 3; + break; + + case MIDI_KEY_PRESSURE: + buffer[0] = MIDI_KEY_PRESSURE | e.c_KEY_PRESSURE.channel; + buffer[1] = e.c_KEY_PRESSURE.key; + buffer[2] = e.c_KEY_PRESSURE.pressure; + + LOG("MIDI_KEY_PRESSURE: channel %x key %x pressure %x", + e.c_KEY_PRESSURE.channel, e.c_KEY_PRESSURE.key, + e.c_KEY_PRESSURE.pressure); + size = 3; + break; + + default: + LOG("unknown voice op: %x", e.voice.op); + break; + } + + return size; +} + +/* + * handle a SEQ_SYSEX event. NetBSD /dev/music doesn't generate these. + */ +static size_t +midi_event_sysex_to_output(seq_event_t e, u_char *buffer, size_t bufsize) +{ + size_t size = 0; + + LOG("UNHANDLED SEQ_SYSEX"); + + return size; +} + +/* + * handle a SEQ_FULLSIZE event. NetBSD /dev/music doesn't generate these. + */ +static size_t +midi_event_fullsize_to_output(seq_event_t e, u_char *buffer, size_t bufsize) +{ + size_t size = 0; + + LOG("UNHANDLED SEQ_FULLSIZE"); + + return size; +} + +/* + * main handler for MIDI events. + */ +static size_t +midi_event_to_output(seq_event_t e, u_char *buffer, size_t bufsize) +{ + size_t size = 0; + + /* XXX so far we only process 4 byte returns */ + assert(bufsize >= 4); + + LOG("event: %02x:%02x:%02x:%02x %02x:%02x:%02x:%02x", e.tag, + e.unknown.byte[0], e.unknown.byte[1], + e.unknown.byte[2], e.unknown.byte[3], + e.unknown.byte[4], e.unknown.byte[5], + e.unknown.byte[6]); + + switch (e.tag) { + case SEQ_LOCAL: + size = midi_event_local_to_output(e, buffer, bufsize); + break; + + case SEQ_TIMING: + size = midi_event_timer_to_output(e, buffer, bufsize); + break; + + case SEQ_CHN_COMMON: + size = midi_event_chn_common_to_output(e, buffer, bufsize); + break; + + case SEQ_CHN_VOICE: + size = midi_event_chn_voice_to_output(e, buffer, bufsize); + break; + + case SEQ_SYSEX: + size = midi_event_sysex_to_output(e, buffer, bufsize); + break; + + case SEQ_FULLSIZE: + size = midi_event_fullsize_to_output(e, buffer, bufsize); + break; + + default: + errx(1, "don't understand midi tag %x", e.tag); + } + + return size; +} + +static bool +filter_array(unsigned val, unsigned *array, size_t arraylen) +{ + + if (array == NULL) + return false; + + for (; arraylen; arraylen--) + if (array[arraylen - 1] == val) + return false; + + return true; +} + +static bool +filter_dev(unsigned device) +{ + + if (filter_array(device, filt_devnos, num_filt_devnos)) + return true; + + return false; +} + +static bool +filter_chan(unsigned channel) +{ + + if (filter_array(channel, filt_chans, num_filt_chans)) + return true; + + return false; +} + +static bool +filter_devchan(unsigned device, unsigned channel) +{ + + if (filter_dev(device) || filter_chan(channel)) + return true; + + return false; +} + +static int +timeleft(struct timeval *start_tvp, struct timeval *record_tvp) +{ + struct timeval now, diff; + + (void)gettimeofday(&now, NULL); + timersub(&now, start_tvp, &diff); + timersub(record_tvp, &diff, &now); + + return (now.tv_sec > 0 || (now.tv_sec == 0 && now.tv_usec > 0)); +} + +static void +parse_ints(const char *str, unsigned **arrayp, unsigned *sizep, const char *msg) +{ + unsigned count = 1, u, longest = 0, c = 0; + unsigned *ip; + const char *s, *os; + char *num_buf; + + /* + * count all the comma separated values, and figre out + * the longest one. + */ + for (s = str; *s; s++) { + c++; + if (*s == ',') { + count++; + if (c > longest) + longest = c; + c = 0; + } + } + *sizep = count; + + num_buf = malloc(longest + 1); + ip = malloc(sizeof(*ip) * count); + if (!ip || !num_buf) + errx(1, "malloc failed"); + + for (count = 0, s = os = str, u = 0; *s; s++) { + if (*s == ',') { + num_buf[u] = '\0'; + decode_uint(num_buf, &ip[count++]); + os = s + 1; + u = 0; + } else + num_buf[u++] = *s; + } + num_buf[u] = '\0'; + decode_uint(num_buf, &ip[count++]); + *arrayp = ip; + + if (verbose) { + fprintf(stderr, "Filtering %s in:", msg); + for (size_t i = 0; i < *sizep; i++) + fprintf(stderr, " %u", ip[i]); + fprintf(stderr, "\n"); + } + + free(num_buf); +} + +static void +cleanup(int signo) +{ + + write_midi_trailer(); + rewrite_header(); + + if (ioctl(midifd, SEQUENCER_TMR_STOP, NULL) < 0) { + if (ignore_timer_fail) + warn("failed to stop midi timer"); + else + err(1, "failed to stop midi timer"); + } + + close(outfd); + close(midifd); + if (signo != 0) + (void)raise_default_signal(signo); + + exit(0); +} + +static void +rewrite_header(void) +{ + + /* can't do this here! */ + if (outfd == STDOUT_FILENO) + return; + + if (lseek(outfd, (off_t)0, SEEK_SET) == (off_t)-1) + err(1, "could not seek to start of file for header rewrite"); + write_midi_header(); +} + +#define BYTE1(x) ((x) & 0xff) +#define BYTE2(x) (((x) >> 8) & 0xff) +#define BYTE3(x) (((x) >> 16) & 0xff) +#define BYTE4(x) (((x) >> 24) & 0xff) + +static void +write_midi_header(void) +{ + unsigned char header[] = { + 'M', 'T', 'h', 'd', + 0, 0, 0, 6, + 0, 1, + 0, 0, /* ntracks */ + 0, 0, /* notes per beat */ + }; + /* XXX only spport one track so far */ + unsigned ntracks = 1; + unsigned char track[] = { + 'M', 'T', 'r', 'k', + 0, 0, 0, 0, + }; + unsigned char bpm[] = { + 0, 0xff, 0x51, 0x3, + 0, 0, 0, /* inverse tempo */ + }; + unsigned total_size = data_size + sizeof header + sizeof track + sizeof bpm; + + header[10] = BYTE2(ntracks); + header[11] = BYTE1(ntracks); + header[12] = BYTE2(notes_per_beat); + header[13] = BYTE1(notes_per_beat); + + track[4] = BYTE4(total_size); + track[5] = BYTE3(total_size); + track[6] = BYTE2(total_size); + track[7] = BYTE1(total_size); + +#define TEMPO_INV(x) (60000000UL / (x)) + bpm[4] = BYTE3(TEMPO_INV(tempo)); + bpm[5] = BYTE2(TEMPO_INV(tempo)); + bpm[6] = BYTE1(TEMPO_INV(tempo)); + + if (write(outfd, header, sizeof header) != sizeof header) + err(1, "write of header failed"); + if (write(outfd, track, sizeof track) != sizeof track) + err(1, "write of track header failed"); + if (write(outfd, bpm, sizeof bpm) != sizeof bpm) + err(1, "write of bpm header failed"); + + LOG("wrote header: ntracks=%u notes_per_beat=%u tempo=%d total_size=%u", + ntracks, notes_per_beat, tempo, total_size); +} + +static void +write_midi_trailer(void) +{ + unsigned char trailer[] = { + 0, 0xff, 0x2f, 0, + }; + + if (write(outfd, trailer, sizeof trailer) != sizeof trailer) + err(1, "write of trailer failed"); +} + +static void +usage(void) +{ + + fprintf(stderr, "Usage: %s [-aDfhqV] [options] {outfile|-}\n", + getprogname()); + fprintf(stderr, "Options:\n" + "\t-B buffer size\n" + "\t-c channels\n" + "\t-d devices\n" + "\t-f sequencerdev\n" + "\t-n notesperbeat\n" + "\t-r raw_output\n" + "\t-T tempo\n" + "\t-t recording time\n"); + exit(EXIT_FAILURE); +}