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