/*
* Copyright (c) 2014, 2015, 2017 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.
*/
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 = true;
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 'R':
decode_uint(optarg, &round_beats);
if (round_beats == 0)
errx(1, "-R <round_beats> must be a positive integer");
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 {
stdout_mode = true;
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=%u\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);
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;
/*
* 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");
}
if (close(outfd) != 0)
warn("couldn't close output");
if (close(midifd) != 0)
warn("couldn't close midi device");
if (signo != 0)
(void)raise_default_signal(signo);
exit(0);
}
static void
rewrite_header(void)
{
/* can't do this here! */
if (stdout_mode)
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();
}