xref: /netbsd-src/usr.bin/midiplay/midiplay.c (revision 1d6b489c6ecbe44de5cd5944470e3e2e40e4879d)
1*1d6b489cSrillig /*	$NetBSD: midiplay.c,v 1.34 2021/11/27 22:16:41 rillig Exp $	*/
2d6f31170Saugustss 
3d6f31170Saugustss /*
470567d1cSaugustss  * Copyright (c) 1998, 2002 The NetBSD Foundation, Inc.
5d6f31170Saugustss  * All rights reserved.
6d6f31170Saugustss  *
79726cfd1Saugustss  * This code is derived from software contributed to The NetBSD Foundation
80f5a0c15Ssalo  * by Lennart Augustsson (augustss@NetBSD.org).
9d6f31170Saugustss  *
10d6f31170Saugustss  * Redistribution and use in source and binary forms, with or without
11d6f31170Saugustss  * modification, are permitted provided that the following conditions
12d6f31170Saugustss  * are met:
13d6f31170Saugustss  * 1. Redistributions of source code must retain the above copyright
14d6f31170Saugustss  *    notice, this list of conditions and the following disclaimer.
15d6f31170Saugustss  * 2. Redistributions in binary form must reproduce the above copyright
16d6f31170Saugustss  *    notice, this list of conditions and the following disclaimer in the
17d6f31170Saugustss  *    documentation and/or other materials provided with the distribution.
18d6f31170Saugustss  *
19d6f31170Saugustss  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20d6f31170Saugustss  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21d6f31170Saugustss  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22d6f31170Saugustss  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23d6f31170Saugustss  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24d6f31170Saugustss  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25d6f31170Saugustss  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26d6f31170Saugustss  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27d6f31170Saugustss  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28d6f31170Saugustss  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29d6f31170Saugustss  * POSSIBILITY OF SUCH DAMAGE.
30d6f31170Saugustss  */
31a8d6388eSagc #include <sys/cdefs.h>
32a8d6388eSagc 
33a8d6388eSagc #ifndef lint
34*1d6b489cSrillig __RCSID("$NetBSD: midiplay.c,v 1.34 2021/11/27 22:16:41 rillig Exp $");
35a8d6388eSagc #endif
36a8d6388eSagc 
37d6f31170Saugustss 
38d6f31170Saugustss #include <stdio.h>
39d6f31170Saugustss #include <stdlib.h>
40d6f31170Saugustss #include <fcntl.h>
41d6f31170Saugustss #include <err.h>
4226719ba3Sjmcneill #include <errno.h>
4326719ba3Sjmcneill #include <limits.h>
44d6f31170Saugustss #include <unistd.h>
45d6f31170Saugustss #include <string.h>
46d6f31170Saugustss #include <sys/types.h>
47d6f31170Saugustss #include <sys/stat.h>
48d6f31170Saugustss #include <sys/ioctl.h>
49d6f31170Saugustss #include <sys/midiio.h>
50d6f31170Saugustss 
51d6f31170Saugustss #define DEVMUSIC "/dev/music"
52d6f31170Saugustss 
53d6f31170Saugustss struct track {
5480f0cf57Schap 	struct track *indirect; /* for fast swaps in heap code */
55d6f31170Saugustss 	u_char *start, *end;
5680f0cf57Schap 	u_long delta;
57d6f31170Saugustss 	u_char status;
58d6f31170Saugustss };
59d6f31170Saugustss 
60d6f31170Saugustss #define MIDI_META 0xff
61d6f31170Saugustss 
62d6f31170Saugustss #define META_SEQNO	0x00
63d6f31170Saugustss #define META_TEXT	0x01
64d6f31170Saugustss #define META_COPYRIGHT	0x02
65d6f31170Saugustss #define META_TRACK	0x03
66d6f31170Saugustss #define META_INSTRUMENT	0x04
67d6f31170Saugustss #define META_LYRIC	0x05
68d6f31170Saugustss #define META_MARKER	0x06
69d6f31170Saugustss #define META_CUE	0x07
70d6f31170Saugustss #define META_CHPREFIX	0x20
71d6f31170Saugustss #define META_EOT	0x2f
72d6f31170Saugustss #define META_SET_TEMPO	0x51
73d6f31170Saugustss #define META_KEY	0x59
74d6f31170Saugustss #define META_SMPTE	0x54
75d6f31170Saugustss #define META_TIMESIGN	0x58
76d6f31170Saugustss 
77d0e87f5fSchristos static const char *metanames[] = {
78d6f31170Saugustss 	"", "Text", "Copyright", "Track", "Instrument",
79d6f31170Saugustss 	"Lyric", "Marker", "Cue",
80d6f31170Saugustss };
81d6f31170Saugustss 
82d6f31170Saugustss static int midi_lengths[] = { 2, 2, 2, 2, 1, 1, 2, 0 };
83d6f31170Saugustss /* Number of bytes in a MIDI command */
84d6f31170Saugustss #define MIDI_LENGTH(d) (midi_lengths[((d) >> 4) & 7])
85d6f31170Saugustss 
86d0e87f5fSchristos #define SEQ_MK_SYSEX0(_dev,...) \
87d0e87f5fSchristos SEQ_MK_EVENT(sysex, 0x94, .device=(_dev), .buffer={__VA_ARGS__})
88d6f31170Saugustss 
89d0e87f5fSchristos 
90d0e87f5fSchristos static void usage(void);
91d0e87f5fSchristos static void send_event(seq_event_t *);
92d0e87f5fSchristos static void dometa(u_int, u_char *, u_int);
93d0e87f5fSchristos #if 0
94d0e87f5fSchristos static void midireset(void);
95d0e87f5fSchristos #endif
96d0e87f5fSchristos static void send_sysex(u_char *, u_int);
97d0e87f5fSchristos static u_long getvar(struct track *);
98d0e87f5fSchristos static u_long getlen(struct track *);
99d0e87f5fSchristos static void playfile(FILE *, const char *);
100d0e87f5fSchristos static void playdata(u_char *, u_int, const char *);
101d0e87f5fSchristos 
102d0e87f5fSchristos static void Heapify(struct track *, int, int);
103d0e87f5fSchristos static void BuildHeap(struct track *, int);
104d0e87f5fSchristos static int ShrinkHeap(struct track *, int);
10580f0cf57Schap 
10680f0cf57Schap /*
10780f0cf57Schap  * This sample plays at an apparent tempo of 120 bpm when the BASETEMPO is 150
10880f0cf57Schap  * bpm, because the quavers are 5 divisions (4 on 1 off) rather than 4 total.
10980f0cf57Schap  */
110d6f31170Saugustss #define P(c) 1, 0x90, c, 0x7f, 4, 0x80, c, 0
111d6f31170Saugustss #define PL(c) 1, 0x90, c, 0x7f, 8, 0x80, c, 0
112d6f31170Saugustss #define C 0x3c
113d6f31170Saugustss #define D 0x3e
114d6f31170Saugustss #define E 0x40
115d6f31170Saugustss #define F 0x41
116d6f31170Saugustss 
117d0e87f5fSchristos static u_char sample[] = {
118d6f31170Saugustss 	'M', 'T', 'h', 'd',  0, 0, 0, 6,  0, 1,  0, 1,  0, 8,
119d6f31170Saugustss 	'M', 'T', 'r', 'k',  0, 0, 0, 4+13*8,
120d6f31170Saugustss 	P(C), P(C), P(C), P(E), P(D), P(D), P(D),
121d6f31170Saugustss 	P(F), P(E), P(E), P(D), P(D), PL(C),
122d6f31170Saugustss 	0, 0xff, 0x2f, 0
123d6f31170Saugustss };
124d6f31170Saugustss #undef P
125d6f31170Saugustss #undef PL
126d6f31170Saugustss #undef C
127d6f31170Saugustss #undef D
128d6f31170Saugustss #undef E
129d6f31170Saugustss #undef F
130d6f31170Saugustss 
131f2057d54Smrg static u_char silence_sample[] = {
132f2057d54Smrg 	'M', 'T', 'h', 'd',  0, 0, 0, 6,  0, 1,  0, 1,  0, 8,
133f2057d54Smrg 	'M', 'T', 'r', 'k',  0, 0, 0, 8,
134f2057d54Smrg 	0, 0xb0, 0x78, 0x00,
135f2057d54Smrg 	0, 0xff, 0x2f, 0
136f2057d54Smrg };
137f2057d54Smrg 
138d6f31170Saugustss #define MARK_HEADER "MThd"
139d6f31170Saugustss #define MARK_TRACK "MTrk"
140d6f31170Saugustss #define MARK_LEN 4
141d6f31170Saugustss 
14234592076Saugustss #define	RMID_SIG "RIFF"
14334592076Saugustss #define	RMID_MIDI_ID "RMID"
14434592076Saugustss #define	RMID_DATA_ID "data"
14534592076Saugustss 
146d6f31170Saugustss #define SIZE_LEN 4
147d6f31170Saugustss #define HEADER_LEN 6
148d6f31170Saugustss 
149d6f31170Saugustss #define GET8(p) ((p)[0])
150d6f31170Saugustss #define GET16(p) (((p)[0] << 8) | (p)[1])
151d6f31170Saugustss #define GET24(p) (((p)[0] << 16) | ((p)[1] << 8) | (p)[2])
152d6f31170Saugustss #define GET32(p) (((p)[0] << 24) | ((p)[1] << 16) | ((p)[2] << 8) | (p)[3])
15334592076Saugustss #define GET32_LE(p) (((p)[3] << 24) | ((p)[2] << 16) | ((p)[1] << 8) | (p)[0])
154d6f31170Saugustss 
155d0e87f5fSchristos static void __attribute__((__noreturn__))
usage(void)15670567d1cSaugustss usage(void)
157d6f31170Saugustss {
158f3452549Swiz 	fprintf(stderr, "usage: %s [-lmqsvx] [-d devno] [-f file] "
159f3452549Swiz 		"[-p pgm] [-t tempo] [file ...]\n",
160a8ec668dScgd 		getprogname());
161d6f31170Saugustss 	exit(1);
162d6f31170Saugustss }
163d6f31170Saugustss 
164d0e87f5fSchristos static int showmeta = 0;
165d0e87f5fSchristos static int verbose = 0;
16680f0cf57Schap #define BASETEMPO 400000		/* us/beat(=24 clks or qn) (150 bpm) */
167d0e87f5fSchristos static u_int tempo_set = 0;
168d0e87f5fSchristos static u_int tempo_abs = 0;
169d0e87f5fSchristos static u_int ttempo = 100;
170d0e87f5fSchristos static int unit = 0;
171d0e87f5fSchristos static int play = 1;
172d0e87f5fSchristos static int fd = -1;
173d0e87f5fSchristos static int sameprogram = 0;
174d0e87f5fSchristos static int insysex = 0;
175d0e87f5fSchristos static int svsysex = 0; /* number of sysex bytes saved internally */
176d6f31170Saugustss 
177d0e87f5fSchristos static void
send_event(seq_event_t * ev)17880f0cf57Schap send_event(seq_event_t *ev)
179d6f31170Saugustss {
180f99fc37dSaugustss 	/*
181f99fc37dSaugustss 	printf("%02x %02x %02x %02x %02x %02x %02x %02x\n",
182f99fc37dSaugustss 	       ev->arr[0], ev->arr[1], ev->arr[2], ev->arr[3],
183f99fc37dSaugustss 	       ev->arr[4], ev->arr[5], ev->arr[6], ev->arr[7]);
184f99fc37dSaugustss 	*/
185f99fc37dSaugustss 	if (play)
186d6f31170Saugustss 		write(fd, ev, sizeof *ev);
187d6f31170Saugustss }
188d6f31170Saugustss 
189d0e87f5fSchristos static u_long
getvar(struct track * tp)19070567d1cSaugustss getvar(struct track *tp)
191d6f31170Saugustss {
192d6f31170Saugustss 	u_long r, c;
193d6f31170Saugustss 
194d6f31170Saugustss 	r = 0;
195d6f31170Saugustss 	do {
196d6f31170Saugustss 		c = *tp->start++;
197d6f31170Saugustss 		r = (r << 7) | (c & 0x7f);
198d6f31170Saugustss 	} while ((c & 0x80) && tp->start < tp->end);
19980f0cf57Schap 	return r;
20080f0cf57Schap }
20180f0cf57Schap 
202d0e87f5fSchristos static u_long
getlen(struct track * tp)20380f0cf57Schap getlen(struct track *tp)
20480f0cf57Schap {
20580f0cf57Schap 	u_long len;
20680f0cf57Schap 	len = getvar(tp);
20780f0cf57Schap 	if (tp->start + len > tp->end)
20880f0cf57Schap 		errx(1, "bogus item length exceeds remaining track size");
20980f0cf57Schap 	return len;
210d6f31170Saugustss }
211d6f31170Saugustss 
212d0e87f5fSchristos static void
dometa(u_int meta,u_char * p,u_int len)21370567d1cSaugustss dometa(u_int meta, u_char *p, u_int len)
214d6f31170Saugustss {
21580f0cf57Schap 	static char const * const keys[] = {
21680f0cf57Schap 	        "Cb", "Gb", "Db", "Ab", "Eb", "Bb", "F",
21780f0cf57Schap 		"C",
21880f0cf57Schap 		"G", "D", "A", "E", "B", "F#", "C#",
21980f0cf57Schap 		"G#", "D#", "A#" /* for minors */
22080f0cf57Schap 	};
22180f0cf57Schap 	seq_event_t ev;
22280f0cf57Schap 	uint32_t usperbeat;
22380f0cf57Schap 
224d6f31170Saugustss 	switch (meta) {
225d6f31170Saugustss 	case META_TEXT:
226d6f31170Saugustss 	case META_COPYRIGHT:
227d6f31170Saugustss 	case META_TRACK:
228d6f31170Saugustss 	case META_INSTRUMENT:
229d6f31170Saugustss 	case META_LYRIC:
230d6f31170Saugustss 	case META_MARKER:
231d6f31170Saugustss 	case META_CUE:
232d6f31170Saugustss 		if (showmeta) {
233d6f31170Saugustss 			printf("%s: ", metanames[meta]);
234d6f31170Saugustss 			fwrite(p, len, 1, stdout);
235d6f31170Saugustss 			printf("\n");
236d6f31170Saugustss 		}
237d6f31170Saugustss 		break;
238d6f31170Saugustss 	case META_SET_TEMPO:
23980f0cf57Schap 		usperbeat = GET24(p);
24080f0cf57Schap 		ev = SEQ_MK_TIMING(TEMPO,
24180f0cf57Schap 		    .bpm=(60000000. / usperbeat) * (ttempo / 100.) + 0.5);
242d6f31170Saugustss 		if (showmeta)
24380f0cf57Schap 			printf("Tempo: %u us/'beat'(24 midiclks)"
24480f0cf57Schap 			       " at %u%%; adjusted bpm = %u\n",
24580f0cf57Schap 			       usperbeat, ttempo, ev.t_TEMPO.bpm);
24680f0cf57Schap 		if (tempo_abs)
24780f0cf57Schap 			warnx("tempo event ignored"
24880f0cf57Schap 			      " in absolute-timed MIDI file");
24980f0cf57Schap 		else {
25080f0cf57Schap 			send_event(&ev);
25180f0cf57Schap 			if (!tempo_set) {
25280f0cf57Schap 				tempo_set = 1;
25380f0cf57Schap 				send_event(&SEQ_MK_TIMING(START));
25480f0cf57Schap 			}
25580f0cf57Schap 		}
256d6f31170Saugustss 		break;
257d6f31170Saugustss 	case META_TIMESIGN:
25880f0cf57Schap 		ev = SEQ_MK_TIMING(TIMESIG,
25980f0cf57Schap 		    .numerator=p[0],      .lg2denom=p[1],
26080f0cf57Schap 		    .clks_per_click=p[2], .dsq_per_24clks=p[3]);
261d6f31170Saugustss 		if (showmeta) {
26280f0cf57Schap 			printf("Time signature: %d/%d."
26380f0cf57Schap 			       " Click every %d midiclk%s"
26480f0cf57Schap 			       " (24 midiclks = %d 32nd note%s)\n",
26580f0cf57Schap 			       ev.t_TIMESIG.numerator,
26680f0cf57Schap 			       1 << ev.t_TIMESIG.lg2denom,
26780f0cf57Schap 			       ev.t_TIMESIG.clks_per_click,
26880f0cf57Schap 			       1 == ev.t_TIMESIG.clks_per_click ? "" : "s",
26980f0cf57Schap 			       ev.t_TIMESIG.dsq_per_24clks,
27080f0cf57Schap 			       1 == ev.t_TIMESIG.dsq_per_24clks ? "" : "s");
271d6f31170Saugustss 		}
27280f0cf57Schap 		/* send_event(&ev); not implemented in sequencer */
273d6f31170Saugustss 		break;
274d6f31170Saugustss 	case META_KEY:
275d6f31170Saugustss 		if (showmeta)
27680f0cf57Schap 			printf("Key: %s %s\n",
27780f0cf57Schap 			       keys[((char)p[0]) + p[1] ? 10 : 7],
278d6f31170Saugustss 			       p[1] ? "minor" : "major");
279d6f31170Saugustss 		break;
280d6f31170Saugustss 	default:
281d6f31170Saugustss 		break;
282d6f31170Saugustss 	}
283d6f31170Saugustss }
284d6f31170Saugustss 
285d0e87f5fSchristos #if 0
286d0e87f5fSchristos static void
28770567d1cSaugustss midireset(void)
288d6f31170Saugustss {
289d6f31170Saugustss 	/* General MIDI reset sequence */
290d0e87f5fSchristos 	send_event(&SEQ_MK_SYSEX0(unit, 0x7e, 0x7f, 0x09, 0x01, 0xf7, 0xff));
291de37abf1Saugustss }
292d0e87f5fSchristos #endif
293de37abf1Saugustss 
294de37abf1Saugustss #define SYSEX_CHUNK 6
295d0e87f5fSchristos static void
send_sysex(u_char * p,u_int l)29670567d1cSaugustss send_sysex(u_char *p, u_int l)
297de37abf1Saugustss {
29880f0cf57Schap 	seq_event_t event;
29980f0cf57Schap 	static u_char bf[6];
300d6f31170Saugustss 
30180f0cf57Schap 	if (0 == l) {
30280f0cf57Schap 		warnx("zero-length system-exclusive event");
30380f0cf57Schap 		return;
304de37abf1Saugustss 	}
30580f0cf57Schap 
30680f0cf57Schap 	/*
30780f0cf57Schap 	 * This block is needed only to handle the possibility that a sysex
30880f0cf57Schap 	 * message is broken into multiple events in a MIDI file that do not
30980f0cf57Schap 	 * have length six; the /dev/music sequencer assumes a sysex message is
31080f0cf57Schap 	 * finished with the first SYSEX event carrying fewer than six bytes,
31180f0cf57Schap 	 * even if the last is not MIDI_SYSEX_END. So, we need to be careful
31280f0cf57Schap 	 * not to send a short sysex event until we have seen the end byte.
31380f0cf57Schap 	 * Instead, save some straggling bytes in bf, and send when we have a
31480f0cf57Schap 	 * full six (or an end byte). Note bf/saved/insysex should be per-
31580f0cf57Schap 	 * device, if we supported output to more than one device at a time.
31680f0cf57Schap 	 */
31780f0cf57Schap 	if (svsysex > 0) {
31880f0cf57Schap 		if (l > sizeof bf - svsysex) {
31980f0cf57Schap 			memcpy(bf + svsysex, p, sizeof bf - svsysex);
32080f0cf57Schap 			l -= sizeof bf - svsysex;
32180f0cf57Schap 			p += sizeof bf - svsysex;
322d0e87f5fSchristos 			send_event(&SEQ_MK_SYSEX0(unit,
32380f0cf57Schap 			    bf[0], bf[1], bf[2], bf[3], bf[4], bf[5]));
32480f0cf57Schap 			svsysex = 0;
32580f0cf57Schap 		} else {
32680f0cf57Schap 			memcpy(bf + svsysex, p, l);
32780f0cf57Schap 			svsysex += l;
32880f0cf57Schap 			p += l;
32980f0cf57Schap 			if (MIDI_SYSEX_END == bf[svsysex-1]) {
33080f0cf57Schap 				event = SEQ_MK_SYSEX(unit);
33180f0cf57Schap 				memcpy(event.sysex.buffer, bf, svsysex);
332d6f31170Saugustss 				send_event(&event);
33380f0cf57Schap 				svsysex = insysex = 0;
33480f0cf57Schap 			} else
33580f0cf57Schap 				insysex = 1;
33680f0cf57Schap 			return;
33780f0cf57Schap 		}
33880f0cf57Schap 	}
33980f0cf57Schap 
34080f0cf57Schap 	/*
34180f0cf57Schap 	 * l > 0. May as well test now whether we will be left 'insysex'
34280f0cf57Schap 	 * after processing this event.
34380f0cf57Schap 	 */
34480f0cf57Schap 	insysex = (MIDI_SYSEX_END != p[l-1]);
34580f0cf57Schap 
34680f0cf57Schap 	/*
34780f0cf57Schap 	 * If not for multi-event sysexes and chunk-size weirdness, this
34880f0cf57Schap 	 * function could pretty much start here. :)
34980f0cf57Schap 	 */
35080f0cf57Schap 	while (l >= SYSEX_CHUNK) {
351d0e87f5fSchristos 		send_event(&SEQ_MK_SYSEX0(unit, p[0], p[1], p[2], p[3], p[4], p[5]));
35280f0cf57Schap 		p += SYSEX_CHUNK;
35380f0cf57Schap 		l -= SYSEX_CHUNK;
35480f0cf57Schap 	}
35580f0cf57Schap 	if (l > 0) {
35680f0cf57Schap 		if (insysex) {
35780f0cf57Schap 			memcpy(bf, p, l);
35880f0cf57Schap 			svsysex = l;
35980f0cf57Schap 		} else { /* a <6 byte chunk is ok if it's REALLY the end */
36080f0cf57Schap 			event = SEQ_MK_SYSEX(unit);
36180f0cf57Schap 			memcpy(event.sysex.buffer, p, l);
36280f0cf57Schap 			send_event(&event);
36380f0cf57Schap 		}
36480f0cf57Schap 	}
365d6f31170Saugustss }
366d6f31170Saugustss 
367d0e87f5fSchristos static void
playfile(FILE * f,const char * name)3689e3117efSlukem playfile(FILE *f, const char *name)
369d6f31170Saugustss {
37050847da5Sitojun 	u_char *buf, *nbuf;
371d6f31170Saugustss 	u_int tot, n, size, nread;
372d6f31170Saugustss 
373d6f31170Saugustss 	/*
374d6f31170Saugustss 	 * We need to read the whole file into memory for easy processing.
375d6f31170Saugustss 	 * Using mmap() would be nice, but some file systems do not support
376d6f31170Saugustss 	 * it, nor does reading from e.g. a pipe.  The latter also precludes
377d6f31170Saugustss 	 * finding out the file size without reading it.
378d6f31170Saugustss 	 */
379d6f31170Saugustss 	size = 1000;
380d6f31170Saugustss 	buf = malloc(size);
381d6f31170Saugustss 	if (buf == 0)
382f51456c2Sitojun 		errx(1, "malloc() failed");
383d6f31170Saugustss 	nread = size;
384d6f31170Saugustss 	tot = 0;
385d6f31170Saugustss 	for (;;) {
386d6f31170Saugustss 		n = fread(buf + tot, 1, nread, f);
387d6f31170Saugustss 		tot += n;
388d6f31170Saugustss 		if (n < nread)
389d6f31170Saugustss 			break;
390d6f31170Saugustss 		/* There must be more to read. */
391d6f31170Saugustss 		nread = size;
39250847da5Sitojun 		nbuf = realloc(buf, size * 2);
39350847da5Sitojun 		if (nbuf == NULL)
394f51456c2Sitojun 			errx(1, "realloc() failed");
39550847da5Sitojun 		buf = nbuf;
39650847da5Sitojun 		size *= 2;
397d6f31170Saugustss 	}
398d6f31170Saugustss 	playdata(buf, tot, name);
399d6f31170Saugustss 	free(buf);
400d6f31170Saugustss }
401d6f31170Saugustss 
402d0e87f5fSchristos static void
playdata(u_char * buf,u_int tot,const char * name)4039e3117efSlukem playdata(u_char *buf, u_int tot, const char *name)
404d6f31170Saugustss {
40580f0cf57Schap 	int format, ntrks, divfmt, ticks, t;
406d6f31170Saugustss 	u_int len, mlen, status, chan;
407d6f31170Saugustss 	u_char *p, *end, byte, meta, *msg;
40826719ba3Sjmcneill 	struct synth_info info;
409d6f31170Saugustss 	struct track *tracks;
410d6f31170Saugustss 	struct track *tp;
411d6f31170Saugustss 
41226719ba3Sjmcneill 	/* verify that the requested midi unit exists */
41326719ba3Sjmcneill 	info.device = unit;
41499baa91eSmrg 	if (play && ioctl(fd, SEQUENCER_INFO, &info) < 0)
41526719ba3Sjmcneill 		err(1, "ioctl(SEQUENCER_INFO) failed");
41626719ba3Sjmcneill 
417d6f31170Saugustss 	end = buf + tot;
418f2057d54Smrg 	if (verbose) {
419f2057d54Smrg 		printf("Playing %s (%d bytes)", name, tot);
420f2057d54Smrg 		if (play)
421f2057d54Smrg 			printf(" on %s (unit %d)...", info.name, info.device);
422f2057d54Smrg 		puts("\n");
423f2057d54Smrg 	}
424d6f31170Saugustss 
42534592076Saugustss 	if (tot < MARK_LEN + 4) {
42634592076Saugustss 		warnx("Not a MIDI file, too short");
42734592076Saugustss 		return;
42834592076Saugustss 	}
42934592076Saugustss 
43034592076Saugustss 	if (memcmp(buf, RMID_SIG, MARK_LEN) == 0) {
43134592076Saugustss 		u_char *eod;
43234592076Saugustss 		/* Detected a RMID file, let's just check if it's
43334592076Saugustss 		 * a MIDI file */
4349e3117efSlukem 		if ((u_int)GET32_LE(buf + MARK_LEN) != tot - 8) {
43534592076Saugustss 			warnx("Not a RMID file, bad header");
43634592076Saugustss 			return;
43734592076Saugustss 		}
43834592076Saugustss 
43934592076Saugustss 		buf += MARK_LEN + 4;
44034592076Saugustss 		if (memcmp(buf, RMID_MIDI_ID, MARK_LEN) != 0) {
44134592076Saugustss 			warnx("Not a RMID file, bad ID");
44234592076Saugustss 			return;
44334592076Saugustss 		}
44434592076Saugustss 
44534592076Saugustss 		/* Now look for the 'data' chunk, which contains
44634592076Saugustss 		 * MIDI data */
44734592076Saugustss 		buf += MARK_LEN;
44834592076Saugustss 
44934592076Saugustss 		/* Test against end-8 since we must have at least 8 bytes
45034592076Saugustss 		 * left to read */
45134592076Saugustss 		while(buf < end-8 && memcmp(buf, RMID_DATA_ID, MARK_LEN))
45234592076Saugustss 			buf += GET32_LE(buf+4) + 8; /* MARK_LEN + 4 */
45334592076Saugustss 
45434592076Saugustss 		if (buf >= end-8) {
45534592076Saugustss 			warnx("Not a valid RMID file, no data chunk");
45634592076Saugustss 			return;
45734592076Saugustss 		}
45834592076Saugustss 
45934592076Saugustss 		buf += MARK_LEN; /* "data" */
46034592076Saugustss 		eod = buf + 4 + GET32_LE(buf);
46134592076Saugustss 		if (eod >= end) {
46234592076Saugustss 			warnx("Not a valid RMID file, bad data chunk size");
46334592076Saugustss 			return;
46434592076Saugustss 		}
46534592076Saugustss 
46634592076Saugustss 		end = eod;
46734592076Saugustss 		buf += 4;
46834592076Saugustss 	}
46934592076Saugustss 
470d6f31170Saugustss 	if (memcmp(buf, MARK_HEADER, MARK_LEN) != 0) {
471f51456c2Sitojun 		warnx("Not a MIDI file, missing header");
472d6f31170Saugustss 		return;
473d6f31170Saugustss 	}
47434592076Saugustss 
475d6f31170Saugustss 	if (GET32(buf + MARK_LEN) != HEADER_LEN) {
476f51456c2Sitojun 		warnx("Not a MIDI file, bad header");
477d6f31170Saugustss 		return;
478d6f31170Saugustss 	}
479d6f31170Saugustss 	format = GET16(buf + MARK_LEN + SIZE_LEN);
480d6f31170Saugustss 	ntrks = GET16(buf + MARK_LEN + SIZE_LEN + 2);
481d6f31170Saugustss 	divfmt = GET8(buf + MARK_LEN + SIZE_LEN + 4);
482d6f31170Saugustss 	ticks = GET8(buf + MARK_LEN + SIZE_LEN + 5);
483d6f31170Saugustss 	p = buf + MARK_LEN + SIZE_LEN + HEADER_LEN;
48480f0cf57Schap 	/*
48580f0cf57Schap 	 * Set the timebase (or timebase and tempo, for absolute-timed files).
48680f0cf57Schap 	 * PORTABILITY: some sequencers actually check the timebase against
48780f0cf57Schap 	 * available timing sources and may adjust it accordingly (storing a
48880f0cf57Schap 	 * new value in the ioctl arg) which would require us to compensate
48980f0cf57Schap 	 * somehow. That possibility is ignored for now, as NetBSD's sequencer
49080f0cf57Schap 	 * currently synthesizes all timebases, for better or worse, from the
49180f0cf57Schap 	 * system clock.
49280f0cf57Schap 	 *
49380f0cf57Schap 	 * For a non-absolute file, if timebase is set to the file's divisions
49480f0cf57Schap 	 * value, and tempo set in the obvious way, then the timing deltas in
49580f0cf57Schap 	 * the MTrks require no scaling. A downside to this approach is that
49680f0cf57Schap 	 * the sequencer API wants tempo in (integer) beats per minute, which
49780f0cf57Schap 	 * limits how finely tempo can be specified. That might be got around
49880f0cf57Schap 	 * in some cases by frobbing tempo and timebase more obscurely, but this
49980f0cf57Schap 	 * player is meant to be simple and clear.
50080f0cf57Schap 	 */
50199baa91eSmrg 	if (!play)
50299baa91eSmrg 		/* do nothing */;
50399baa91eSmrg 	else if ((divfmt & 0x80) == 0) {
504d6f31170Saugustss 		ticks |= divfmt << 8;
50580f0cf57Schap 		if (ioctl(fd, SEQUENCER_TMR_TIMEBASE, &(int){ticks}) < 0)
50680f0cf57Schap 			err(1, "SEQUENCER_TMR_TIMEBASE");
50780f0cf57Schap 	} else {
50880f0cf57Schap 		tempo_abs = tempo_set = 1;
50980f0cf57Schap 		divfmt = -(int8_t)divfmt;
51080f0cf57Schap 		/*
51180f0cf57Schap 		 * divfmt is frames per second; multiplying by 60 to set tempo
51280f0cf57Schap 		 * in frames per minute could exceed sequencer's (arbitrary)
51380f0cf57Schap 		 * tempo limits, so factor 60 as 12*5, set tempo in frames per
51480f0cf57Schap 		 * 12 seconds, and account for the 5 in timebase.
51580f0cf57Schap 		 */
51680f0cf57Schap 		send_event(&SEQ_MK_TIMING(TEMPO,
51780f0cf57Schap 		    .bpm=(12*divfmt) * (ttempo/100.) + 0.5));
51880f0cf57Schap 		if (ioctl(fd, SEQUENCER_TMR_TIMEBASE, &(int){5*ticks}) < 0)
51980f0cf57Schap 			err(1, "SEQUENCER_TMR_TIMEBASE");
52080f0cf57Schap 	}
521d6f31170Saugustss 	if (verbose > 1)
52280f0cf57Schap 		printf(tempo_abs ?
52380f0cf57Schap 		       "format=%d ntrks=%d abs fps=%u subdivs=%u\n" :
52480f0cf57Schap 		       "format=%d ntrks=%d divisions=%u\n",
52580f0cf57Schap 		       format, ntrks, tempo_abs ? divfmt : ticks, ticks);
526d6f31170Saugustss 	if (format != 0 && format != 1) {
527f51456c2Sitojun 		warnx("Cannot play MIDI file of type %d", format);
528d6f31170Saugustss 		return;
529d6f31170Saugustss 	}
530d6f31170Saugustss 	if (ntrks == 0)
531d6f31170Saugustss 		return;
532d6f31170Saugustss 	tracks = malloc(ntrks * sizeof(struct track));
533d6f31170Saugustss 	if (tracks == NULL)
534f51456c2Sitojun 		errx(1, "malloc() tracks failed");
535d6f31170Saugustss 	for (t = 0; t < ntrks;) {
536d6f31170Saugustss 		if (p >= end - MARK_LEN - SIZE_LEN) {
537f51456c2Sitojun 			warnx("Cannot find track %d", t);
538de37abf1Saugustss 			goto ret;
539d6f31170Saugustss 		}
540d6f31170Saugustss 		len = GET32(p + MARK_LEN);
541d6f31170Saugustss 		if (len > 1000000) { /* a safe guard */
542f51456c2Sitojun 			warnx("Crazy track length");
543de37abf1Saugustss 			goto ret;
544d6f31170Saugustss 		}
545d6f31170Saugustss 		if (memcmp(p, MARK_TRACK, MARK_LEN) == 0) {
546d6f31170Saugustss 			tracks[t].start = p + MARK_LEN + SIZE_LEN;
547d6f31170Saugustss 			tracks[t].end = tracks[t].start + len;
54880f0cf57Schap 			tracks[t].delta = getvar(&tracks[t]);
54980f0cf57Schap 			tracks[t].indirect = &tracks[t]; /* -> self for now */
550d6f31170Saugustss 			t++;
551d6f31170Saugustss 		}
552d6f31170Saugustss 		p += MARK_LEN + SIZE_LEN + len;
553d6f31170Saugustss 	}
554d6f31170Saugustss 
555d6f31170Saugustss 	/*
55680f0cf57Schap 	 * Force every channel to the same patch if requested by the user.
557d6f31170Saugustss 	 */
5586751cbc7Saugustss 	if (sameprogram) {
5596751cbc7Saugustss 		for(t = 0; t < 16; t++) {
56080f0cf57Schap 			send_event(&SEQ_MK_CHN(PGM_CHANGE, .device=unit,
56180f0cf57Schap 			    .channel=t, .program=sameprogram-1));
5626751cbc7Saugustss 		}
5636751cbc7Saugustss 	}
564d6f31170Saugustss 	/*
56580f0cf57Schap 	 * Play MIDI events by selecting the track with the lowest
56680f0cf57Schap 	 * delta.  Execute the event, update the delta and repeat.
56780f0cf57Schap 	 *
56880f0cf57Schap 	 * The ticks variable is the number of ticks that make up a beat
56980f0cf57Schap 	 * (beat: 24 MIDI clocks always, a quarter note by usual convention)
57080f0cf57Schap 	 * and is used as a reference value for the delays between
571d6f31170Saugustss 	 * the MIDI events.
572d6f31170Saugustss 	 */
57380f0cf57Schap 	BuildHeap(tracks, ntrks); /* tracks[0].indirect is always next */
574d6f31170Saugustss 	for (;;) {
57580f0cf57Schap 		tp = tracks[0].indirect;
57680f0cf57Schap 		if ((verbose > 2 && tp->delta > 0)  ||  verbose > 3) {
577cb27e766She 			printf("DELAY %4ld TRACK %2td%s",
578cb27e766She 			       tp->delta, tp - tracks, verbose>3?" ":"\n");
579d6f31170Saugustss 			fflush(stdout);
580d6f31170Saugustss 		}
58180f0cf57Schap 		if (tp->delta > 0) {
58280f0cf57Schap 			if (!tempo_set) {
58380f0cf57Schap 				if (verbose || showmeta)
58480f0cf57Schap 					printf("No initial tempo;"
58580f0cf57Schap 					       " defaulting:\n");
58680f0cf57Schap 				dometa(META_SET_TEMPO, (u_char[]){
58780f0cf57Schap 				    BASETEMPO >> 16,
58880f0cf57Schap 				    (BASETEMPO >> 8) & 0xff,
58980f0cf57Schap 				    BASETEMPO & 0xff},
59080f0cf57Schap 				    3);
591d6f31170Saugustss 			}
59280f0cf57Schap 			send_event(&SEQ_MK_TIMING(WAIT_REL,
59380f0cf57Schap 			    .divisions=tp->delta));
594d6f31170Saugustss 		}
595d6f31170Saugustss 		byte = *tp->start++;
596d6f31170Saugustss 		if (byte == MIDI_META) {
597d6f31170Saugustss 			meta = *tp->start++;
59880f0cf57Schap 			mlen = getlen(tp);
59980f0cf57Schap 			if (verbose > 3)
600d6f31170Saugustss 				printf("META %02x (%d)\n", meta, mlen);
601d6f31170Saugustss 			dometa(meta, tp->start, mlen);
602d6f31170Saugustss 			tp->start += mlen;
603d6f31170Saugustss 		} else {
604d6f31170Saugustss 			if (MIDI_IS_STATUS(byte))
605d6f31170Saugustss 				tp->status = byte;
606c0c080efSaugustss 			else
607c0c080efSaugustss 				tp->start--;
608d6f31170Saugustss 			mlen = MIDI_LENGTH(tp->status);
609f99fc37dSaugustss 			msg = tp->start;
61080f0cf57Schap 			if (verbose > 3) {
611f99fc37dSaugustss 			    if (mlen == 1)
612f99fc37dSaugustss 				printf("MIDI %02x (%d) %02x\n",
613f99fc37dSaugustss 				       tp->status, mlen, msg[0]);
614f99fc37dSaugustss 			    else
615f99fc37dSaugustss 				printf("MIDI %02x (%d) %02x %02x\n",
616f99fc37dSaugustss 				       tp->status, mlen, msg[0], msg[1]);
617f99fc37dSaugustss 			}
61880f0cf57Schap 			if (insysex && tp->status != MIDI_SYSEX_END) {
61980f0cf57Schap 				warnx("incomplete system exclusive message"
62080f0cf57Schap 				      " aborted");
62180f0cf57Schap 				svsysex = insysex = 0;
62280f0cf57Schap 			}
623d6f31170Saugustss 			status = MIDI_GET_STATUS(tp->status);
624d6f31170Saugustss 			chan = MIDI_GET_CHAN(tp->status);
625d6f31170Saugustss 			switch (status) {
626d6f31170Saugustss 			case MIDI_NOTEOFF:
62780f0cf57Schap 				send_event(&SEQ_MK_CHN(NOTEOFF, .device=unit,
62880f0cf57Schap 				.channel=chan, .key=msg[0], .velocity=msg[1]));
62980f0cf57Schap 				break;
630d6f31170Saugustss 			case MIDI_NOTEON:
63180f0cf57Schap 				send_event(&SEQ_MK_CHN(NOTEON, .device=unit,
63280f0cf57Schap 				.channel=chan, .key=msg[0], .velocity=msg[1]));
63380f0cf57Schap 				break;
634d6f31170Saugustss 			case MIDI_KEY_PRESSURE:
63580f0cf57Schap 				send_event(&SEQ_MK_CHN(KEY_PRESSURE,
63680f0cf57Schap 				.device=unit, .channel=chan,
63780f0cf57Schap 				.key=msg[0], .pressure=msg[1]));
638d6f31170Saugustss 				break;
639d6f31170Saugustss 			case MIDI_CTL_CHANGE:
64080f0cf57Schap 				send_event(&SEQ_MK_CHN(CTL_CHANGE,
64180f0cf57Schap 				.device=unit, .channel=chan,
64280f0cf57Schap 				.controller=msg[0], .value=msg[1]));
643d6f31170Saugustss 				break;
644d6f31170Saugustss 			case MIDI_PGM_CHANGE:
64580f0cf57Schap 				if (!sameprogram)
64680f0cf57Schap 					send_event(&SEQ_MK_CHN(PGM_CHANGE,
64780f0cf57Schap 					.device=unit, .channel=chan,
64880f0cf57Schap 					.program=msg[0]));
649da048cb7Saugustss 				break;
650d6f31170Saugustss 			case MIDI_CHN_PRESSURE:
65180f0cf57Schap 				send_event(&SEQ_MK_CHN(CHN_PRESSURE,
65280f0cf57Schap 				.device=unit, .channel=chan, .pressure=msg[0]));
653d6f31170Saugustss 				break;
654d6f31170Saugustss 			case MIDI_PITCH_BEND:
65580f0cf57Schap 				send_event(&SEQ_MK_CHN(PITCH_BEND,
65680f0cf57Schap 				.device=unit, .channel=chan,
65780f0cf57Schap 				.value=(msg[0] & 0x7f) | ((msg[1] & 0x7f)<<7)));
658d6f31170Saugustss 				break;
659de37abf1Saugustss 			case MIDI_SYSTEM_PREFIX:
66080f0cf57Schap 				mlen = getlen(tp);
66180f0cf57Schap 				if (tp->status == MIDI_SYSEX_START) {
662de37abf1Saugustss 					send_sysex(tp->start, mlen);
663de37abf1Saugustss 					break;
66480f0cf57Schap 				} else if (tp->status == MIDI_SYSEX_END) {
66580f0cf57Schap 				/* SMF uses SYSEX_END as CONTINUATION/ESCAPE */
66680f0cf57Schap 					if (insysex) { /* CONTINUATION */
66780f0cf57Schap 						send_sysex(tp->start, mlen);
66880f0cf57Schap 					} else { /* ESCAPE */
66980f0cf57Schap 						for (; mlen > 0 ; -- mlen) {
67080f0cf57Schap 							send_event(
67180f0cf57Schap 							    &SEQ_MK_EVENT(putc,
67280f0cf57Schap 							    SEQOLD_MIDIPUTC,
67380f0cf57Schap 							    .device=unit,
67480f0cf57Schap 							    .byte=*(tp->start++)
67580f0cf57Schap 							   ));
67680f0cf57Schap 						}
67780f0cf57Schap 					}
67880f0cf57Schap 					break;
67980f0cf57Schap 				}
6802235c7e9Smrg 				/* Sorry, can't do this yet */
6812235c7e9Smrg 				/* FALLTHROUGH */
682f99fc37dSaugustss 			default:
683f99fc37dSaugustss 				if (verbose)
684f99fc37dSaugustss 					printf("MIDI event 0x%02x ignored\n",
685f99fc37dSaugustss 					       tp->status);
686d6f31170Saugustss 			}
687d6f31170Saugustss 			tp->start += mlen;
688d6f31170Saugustss 		}
68980f0cf57Schap 		if (tp->start >= tp->end) {
69080f0cf57Schap 			ntrks = ShrinkHeap(tracks, ntrks); /* track gone */
69180f0cf57Schap 			if (0 == ntrks)
69280f0cf57Schap 				break;
69380f0cf57Schap 		} else
69480f0cf57Schap 			tp->delta = getvar(tp);
69580f0cf57Schap 		Heapify(tracks, ntrks, 0);
696d6f31170Saugustss 	}
69799baa91eSmrg 	if (play && ioctl(fd, SEQUENCER_SYNC, 0) < 0)
698de37abf1Saugustss 		err(1, "SEQUENCER_SYNC");
699d6f31170Saugustss 
700de37abf1Saugustss  ret:
701d6f31170Saugustss 	free(tracks);
702d6f31170Saugustss }
703d6f31170Saugustss 
70426719ba3Sjmcneill static int
parse_unit(const char * sunit)70526719ba3Sjmcneill parse_unit(const char *sunit)
70626719ba3Sjmcneill {
70726719ba3Sjmcneill 	const char *osunit = sunit;
70826719ba3Sjmcneill 	long n;
70926719ba3Sjmcneill 	char *ep;
71026719ba3Sjmcneill 
71126719ba3Sjmcneill 	if (strncmp(sunit, "midi", strlen("midi")) == 0)
71226719ba3Sjmcneill 		sunit += strlen("midi");
71326719ba3Sjmcneill 
71426719ba3Sjmcneill 	errno = 0;
71526719ba3Sjmcneill 	n = strtol(sunit, &ep, 10);
71626719ba3Sjmcneill 	if (n < 0 || n > INT_MAX || *ep != '\0' ||
71726719ba3Sjmcneill 	    (errno == ERANGE &&
71826719ba3Sjmcneill 	    (n == LONG_MAX || n == LONG_MIN)))
71926719ba3Sjmcneill 		errx(1, "bad midi unit -- %s", osunit);
72026719ba3Sjmcneill 
72126719ba3Sjmcneill 	return (int)n;
72226719ba3Sjmcneill }
72326719ba3Sjmcneill 
724d6f31170Saugustss int
main(int argc,char ** argv)72570567d1cSaugustss main(int argc, char **argv)
726d6f31170Saugustss {
727d6f31170Saugustss 	int ch;
728d6f31170Saugustss 	int listdevs = 0;
729d6f31170Saugustss 	int example = 0;
730f2057d54Smrg 	int silence = 0;
731d6f31170Saugustss 	int nmidi;
73270567d1cSaugustss 	const char *file = DEVMUSIC;
73370567d1cSaugustss 	const char *sunit;
734d6f31170Saugustss 	struct synth_info info;
735d6f31170Saugustss 	FILE *f;
736d6f31170Saugustss 
73770567d1cSaugustss 	if ((sunit = getenv("MIDIUNIT")))
73826719ba3Sjmcneill 		unit = parse_unit(sunit);
73970567d1cSaugustss 
740f2057d54Smrg 	while ((ch = getopt(argc, argv, "?d:f:lmp:qst:vx")) != -1) {
741d6f31170Saugustss 		switch(ch) {
742d6f31170Saugustss 		case 'd':
74326719ba3Sjmcneill 			unit = parse_unit(optarg);
744d6f31170Saugustss 			break;
745d6f31170Saugustss 		case 'f':
746d6f31170Saugustss 			file = optarg;
747d6f31170Saugustss 			break;
748d6f31170Saugustss 		case 'l':
749d6f31170Saugustss 			listdevs++;
750d6f31170Saugustss 			break;
751d6f31170Saugustss 		case 'm':
752d6f31170Saugustss 			showmeta++;
753d6f31170Saugustss 			break;
7546751cbc7Saugustss 		case 'p':
7556751cbc7Saugustss 			sameprogram = atoi(optarg);
7566751cbc7Saugustss 			break;
757f99fc37dSaugustss 		case 'q':
758f99fc37dSaugustss 			play = 0;
759f99fc37dSaugustss 			break;
760f2057d54Smrg 		case 's':
761f2057d54Smrg 			silence++;
762f2057d54Smrg 			break;
763d6f31170Saugustss 		case 't':
764c22b7affSaugustss 			ttempo = atoi(optarg);
765d6f31170Saugustss 			break;
766d6f31170Saugustss 		case 'v':
767d6f31170Saugustss 			verbose++;
768d6f31170Saugustss 			break;
769d6f31170Saugustss 		case 'x':
770d6f31170Saugustss 			example++;
771d6f31170Saugustss 			break;
772d6f31170Saugustss 		case '?':
773d6f31170Saugustss 		default:
774d6f31170Saugustss 			usage();
775d6f31170Saugustss 		}
776d6f31170Saugustss 	}
777d6f31170Saugustss 	argc -= optind;
778d6f31170Saugustss 	argv += optind;
779d6f31170Saugustss 
7804385e5bfSaugustss 	if (!play)
7814385e5bfSaugustss 		goto output;
7824385e5bfSaugustss 
783d6f31170Saugustss 	fd = open(file, O_WRONLY);
784d6f31170Saugustss 	if (fd < 0)
785d6f31170Saugustss 		err(1, "%s", file);
786d6f31170Saugustss 	if (ioctl(fd, SEQUENCER_NRMIDIS, &nmidi) < 0)
787d6f31170Saugustss 		err(1, "ioctl(SEQUENCER_NRMIDIS) failed, ");
788d6f31170Saugustss 	if (nmidi == 0)
789f51456c2Sitojun 		errx(1, "Sorry, no MIDI devices available");
790d6f31170Saugustss 	if (listdevs) {
791d6f31170Saugustss 		for (info.device = 0; info.device < nmidi; info.device++) {
792d6f31170Saugustss 			if (ioctl(fd, SEQUENCER_INFO, &info) < 0)
793d6f31170Saugustss 				err(1, "ioctl(SEQUENCER_INFO) failed, ");
794d6f31170Saugustss 			printf("%d: %s\n", info.device, info.name);
795d6f31170Saugustss 		}
796d6f31170Saugustss 		exit(0);
797d6f31170Saugustss 	}
798551f9b0dSaugustss 
7994385e5bfSaugustss  output:
800d6f31170Saugustss 	if (example)
801551f9b0dSaugustss 		while (example--)
80215bc968eSaugustss 			playdata(sample, sizeof sample, "<Gubben Noa>");
803f2057d54Smrg 	else if (silence)
804f2057d54Smrg 		while (silence--)
805f2057d54Smrg 			playdata(silence_sample, sizeof silence_sample,
806f2057d54Smrg 				 "<Silence>");
807d6f31170Saugustss 	else if (argc == 0)
808d6f31170Saugustss 		playfile(stdin, "<stdin>");
809d6f31170Saugustss 	else
810d6f31170Saugustss 		while (argc--) {
811d6f31170Saugustss 			f = fopen(*argv, "r");
812d6f31170Saugustss 			if (f == NULL)
813d6f31170Saugustss 				err(1, "%s", *argv);
814d6f31170Saugustss 			else {
815d6f31170Saugustss 				playfile(f, *argv);
816d6f31170Saugustss 				fclose(f);
817d6f31170Saugustss 			}
818d6f31170Saugustss 			argv++;
819d6f31170Saugustss 		}
820d6f31170Saugustss 
821d6f31170Saugustss 	exit(0);
822d6f31170Saugustss }
82380f0cf57Schap 
82480f0cf57Schap /*
82580f0cf57Schap  * relative-time priority queue (min-heap). Properties:
82680f0cf57Schap  * 1. The delta time at a node is relative to the node's parent's time.
82780f0cf57Schap  * 2. When an event is dequeued from a track, the delta time of the new head
82880f0cf57Schap  *    event is relative to the time of the event just dequeued.
82980f0cf57Schap  * Therefore:
83080f0cf57Schap  * 3. After dequeueing the head event from the track at heap root, the next
83180f0cf57Schap  *    event's time is directly comparable to the root's children.
83280f0cf57Schap  * These properties allow the heap to be maintained with delta times throughout.
83380f0cf57Schap  * Insert is also implementable, but not needed: all the tracks are present
83480f0cf57Schap  * at first; they just go away as they end.
83580f0cf57Schap  */
83680f0cf57Schap 
83780f0cf57Schap #define PARENT(i) ((i - 1) >> 1)
83880f0cf57Schap #define LEFT(i)   ((i << 1) + 1)
83980f0cf57Schap #define RIGHT(i)  ((i + 1) << 1)
84080f0cf57Schap #define DTIME(i)  (t[i].indirect->delta)
84180f0cf57Schap #define SWAP(i, j) do { \
84280f0cf57Schap     struct track *_t = t[i].indirect; \
84380f0cf57Schap     t[i].indirect = t[j].indirect; \
84480f0cf57Schap     t[j].indirect = _t; \
845*1d6b489cSrillig } while (0)
84680f0cf57Schap 
847d0e87f5fSchristos static void
Heapify(struct track * t,int ntrks,int node)84880f0cf57Schap Heapify(struct track *t, int ntrks, int node)
84980f0cf57Schap {
85080f0cf57Schap 	int lc, rc, mn;
85180f0cf57Schap 
85280f0cf57Schap 	lc = LEFT(node);
85380f0cf57Schap 	rc = RIGHT(node);
85480f0cf57Schap 
85580f0cf57Schap 	if (rc >= ntrks) {			/* no right child */
85680f0cf57Schap 		if (lc >= ntrks)		/* node is a leaf */
85780f0cf57Schap 			return;
85880f0cf57Schap 		if (DTIME(node) > DTIME(lc))
85980f0cf57Schap 			SWAP(node, lc);
86080f0cf57Schap 		DTIME(lc) -= DTIME(node);
86180f0cf57Schap 		return;				/* no rc ==> lc is a leaf */
86280f0cf57Schap 	}
86380f0cf57Schap 
86480f0cf57Schap 	mn = lc;
86580f0cf57Schap 	if (DTIME(lc) > DTIME(rc))
86680f0cf57Schap 		mn = rc;
86780f0cf57Schap 	if (DTIME(node) <= DTIME(mn)) {
86880f0cf57Schap 		DTIME(rc) -= DTIME(node);
86980f0cf57Schap 		DTIME(lc) -= DTIME(node);
87080f0cf57Schap 		return;
87180f0cf57Schap 	}
87280f0cf57Schap 
87380f0cf57Schap 	SWAP(node, mn);
87480f0cf57Schap 	DTIME(rc) -= DTIME(node);
87580f0cf57Schap 	DTIME(lc) -= DTIME(node);
87680f0cf57Schap 	Heapify(t, ntrks, mn); /* gcc groks tail recursion */
87780f0cf57Schap }
87880f0cf57Schap 
879d0e87f5fSchristos static void
BuildHeap(struct track * t,int ntrks)88080f0cf57Schap BuildHeap(struct track *t, int ntrks)
88180f0cf57Schap {
88280f0cf57Schap 	int node;
88380f0cf57Schap 
88480f0cf57Schap 	for (node = PARENT(ntrks - 1); node --> 0;)
88580f0cf57Schap 		Heapify(t, ntrks, node);
88680f0cf57Schap }
88780f0cf57Schap 
88880f0cf57Schap /*
88980f0cf57Schap  * Make the heap 1 item smaller by discarding the track at the root. Move the
89080f0cf57Schap  * rightmost bottom-level leaf to the root and decrement ntrks. It remains to
89180f0cf57Schap  * run Heapify, which the caller is expected to do. Returns the new ntrks.
89280f0cf57Schap  */
893d0e87f5fSchristos static int
ShrinkHeap(struct track * t,int ntrks)89480f0cf57Schap ShrinkHeap(struct track *t, int ntrks)
89580f0cf57Schap {
89680f0cf57Schap 	int ancest;
89780f0cf57Schap 
89880f0cf57Schap 	--ntrks;
89980f0cf57Schap 	for (ancest = PARENT(ntrks); ancest > 0; ancest = PARENT(ancest))
90080f0cf57Schap 		DTIME(ntrks) += DTIME(ancest);
90180f0cf57Schap 	t[0].indirect = t[ntrks].indirect;
90280f0cf57Schap 	return ntrks;
90380f0cf57Schap }
904