The rest of merge from chap-midi branch. Version of midiplay that uses the

sequencer API definitions in sys/midiio.h (so there is a use example).
These produce the same (ABI) sequencer events as the OSS macros, but in
a more strongly-typed way; OSS API macros could be built on them and added
to the OSS compatibility header sys/soundcard.h but have not been, yet.
This commit is contained in:
chap 2006-06-30 22:19:32 +00:00
parent 1e34bd3ea3
commit 80f0cf5785
2 changed files with 366 additions and 139 deletions

View File

@ -1,4 +1,4 @@
.\" $NetBSD: midiplay.1,v 1.15 2006/06/17 02:18:48 reed Exp $
.\" $NetBSD: midiplay.1,v 1.16 2006/06/30 22:19:32 chap Exp $
.\" Copyright (c) 1998 The NetBSD Foundation, Inc.
.\" All rights reserved.
.\"
@ -73,6 +73,9 @@ specifies the number of the MIDI device used for output (as listed
by the
.Fl l
flag).
There is no way at present to have
.Nm
map playback to more than one device.
The default is device is given by environment variable
.Ev MIDIUNIT .
.It Fl f Ar file
@ -82,18 +85,27 @@ list the possible devices without playing anything.
.It Fl m
show MIDI file meta events (copyright, lyrics, etc).
.It Fl p Ar pgm
use only this program (range 1-128) for all channels.
force all channels to play with the single specified
program (or instrument patch, range 1-128). Program change events
in the file will be suppressed.
There is no way at present to have
.Nm
selectively map channels or instruments.
.It Fl q
specifies that the MIDI file should not be played, just parsed.
.It Fl t Ar tempo
specifies the tempo.
The default tempo is 100.
.It Fl t Ar tempo-adjust
specifies an adjustment (in percent) to the tempi recorded in the file.
The default of 100 plays as specified in the file, 50 halves every tempo,
and so on.
.It Fl v
be verbose.
If the flag is repeated the verbosity increases.
.It Fl x
play a small sample sound instead of the a file.
.El
.Pp
A file containing no tempo indication will be played as if it specified
150 beats per minute. You have been warned.
.Sh ENVIRONMENT
.Bl -tag -width MIDIUNIT
.It Ev MIDIUNIT
@ -115,5 +127,5 @@ command first appeared in
.Sh BUGS
It may take a long while before playing stops when
.Nm
is interrupted since the sequencer and MIDI buffers will still
be emptied.
is interrupted, as the data already buffered in the sequencer will contain
timing events.

View File

@ -1,4 +1,4 @@
/* $NetBSD: midiplay.c,v 1.22 2004/01/05 23:23:36 jmmv Exp $ */
/* $NetBSD: midiplay.c,v 1.23 2006/06/30 22:19:32 chap Exp $ */
/*
* Copyright (c) 1998, 2002 The NetBSD Foundation, Inc.
@ -38,7 +38,7 @@
#include <sys/cdefs.h>
#ifndef lint
__RCSID("$NetBSD: midiplay.c,v 1.22 2004/01/05 23:23:36 jmmv Exp $");
__RCSID("$NetBSD: midiplay.c,v 1.23 2006/06/30 22:19:32 chap Exp $");
#endif
@ -56,8 +56,9 @@ __RCSID("$NetBSD: midiplay.c,v 1.22 2004/01/05 23:23:36 jmmv Exp $");
#define DEVMUSIC "/dev/music"
struct track {
struct track *indirect; /* for fast swaps in heap code */
u_char *start, *end;
u_long curtime;
u_long delta;
u_char status;
};
@ -88,15 +89,24 @@ static int midi_lengths[] = { 2,2,2,2,1,1,2,0 };
#define MIDI_LENGTH(d) (midi_lengths[((d) >> 4) & 7])
void usage(void);
void send_event(seq_event_rec *);
void send_event(seq_event_t *);
void dometa(u_int, u_char *, u_int);
void midireset(void);
void send_sysex(u_char *, u_int);
u_long getvar(struct track *);
u_long getlen(struct track *);
void playfile(FILE *, char *);
void playdata(u_char *, u_int, char *);
int main(int argc, char **argv);
void Heapify(struct track *, int, int);
void BuildHeap(struct track *, int);
int ShrinkHeap(struct track *, int);
/*
* This sample plays at an apparent tempo of 120 bpm when the BASETEMPO is 150
* bpm, because the quavers are 5 divisions (4 on 1 off) rather than 4 total.
*/
#define P(c) 1,0x90,c,0x7f,4,0x80,c,0
#define PL(c) 1,0x90,c,0x7f,8,0x80,c,0
#define C 0x3c
@ -139,23 +149,26 @@ void
usage(void)
{
printf("usage: %s [-d unit] [-f file] [-l] [-m] [-p pgm] [-q] "
"[-t tempo] [-v] [-x] [file ...]\n",
"[-t %%tempo] [-v] [-x] [file ...]\n",
getprogname());
exit(1);
}
int showmeta = 0;
int verbose = 0;
#define BASETEMPO 400000
u_int tempo = BASETEMPO; /* microsec / quarter note */
#define BASETEMPO 400000 /* us/beat(=24 clks or qn) (150 bpm) */
u_int tempo_set = 0;
u_int tempo_abs = 0;
u_int ttempo = 100;
int unit = 0;
int play = 1;
int fd = -1;
int sameprogram = 0;
int insysex = 0;
int svsysex = 0; /* number of sysex bytes saved internally */
void
send_event(seq_event_rec *ev)
send_event(seq_event_t *ev)
{
/*
printf("%02x %02x %02x %02x %02x %02x %02x %02x\n",
@ -176,12 +189,31 @@ getvar(struct track *tp)
c = *tp->start++;
r = (r << 7) | (c & 0x7f);
} while ((c & 0x80) && tp->start < tp->end);
return (r);
return r;
}
u_long
getlen(struct track *tp)
{
u_long len;
len = getvar(tp);
if (tp->start + len > tp->end)
errx(1, "bogus item length exceeds remaining track size");
return len;
}
void
dometa(u_int meta, u_char *p, u_int len)
{
static char const * const keys[] = {
"Cb", "Gb", "Db", "Ab", "Eb", "Bb", "F",
"C",
"G", "D", "A", "E", "B", "F#", "C#",
"G#", "D#", "A#" /* for minors */
};
seq_event_t ev;
uint32_t usperbeat;
switch (meta) {
case META_TEXT:
case META_COPYRIGHT:
@ -197,23 +229,45 @@ dometa(u_int meta, u_char *p, u_int len)
}
break;
case META_SET_TEMPO:
tempo = GET24(p);
usperbeat = GET24(p);
ev = SEQ_MK_TIMING(TEMPO,
.bpm=(60000000. / usperbeat) * (ttempo / 100.) + 0.5);
if (showmeta)
printf("Tempo: %d us / quarter note\n", tempo);
printf("Tempo: %u us/'beat'(24 midiclks)"
" at %u%%; adjusted bpm = %u\n",
usperbeat, ttempo, ev.t_TEMPO.bpm);
if (tempo_abs)
warnx("tempo event ignored"
" in absolute-timed MIDI file");
else {
send_event(&ev);
if (!tempo_set) {
tempo_set = 1;
send_event(&SEQ_MK_TIMING(START));
}
}
break;
case META_TIMESIGN:
ev = SEQ_MK_TIMING(TIMESIG,
.numerator=p[0], .lg2denom=p[1],
.clks_per_click=p[2], .dsq_per_24clks=p[3]);
if (showmeta) {
int n = p[1];
int d = 1;
while (n-- > 0)
d *= 2;
printf("Time signature: %d/%d %d,%d\n",
p[0], d, p[2], p[3]);
printf("Time signature: %d/%d."
" Click every %d midiclk%s"
" (24 midiclks = %d 32nd note%s)\n",
ev.t_TIMESIG.numerator,
1 << ev.t_TIMESIG.lg2denom,
ev.t_TIMESIG.clks_per_click,
1 == ev.t_TIMESIG.clks_per_click ? "" : "s",
ev.t_TIMESIG.dsq_per_24clks,
1 == ev.t_TIMESIG.dsq_per_24clks ? "" : "s");
}
/* send_event(&ev); not implemented in sequencer */
break;
case META_KEY:
if (showmeta)
printf("Key: %d %s\n", (char)p[0],
printf("Key: %s %s\n",
keys[((char)p[0]) + p[1] ? 10 : 7],
p[1] ? "minor" : "major");
break;
default:
@ -225,31 +279,81 @@ void
midireset(void)
{
/* General MIDI reset sequence */
static u_char gm_reset[] = { 0x7e, 0x7f, 0x09, 0x01, 0xf7 };
send_sysex(gm_reset, sizeof gm_reset);
send_event(&SEQ_MK_SYSEX(unit,[0]=0x7e, 0x7f, 0x09, 0x01, 0xf7));
}
#define SYSEX_CHUNK 6
void
send_sysex(u_char *p, u_int l)
{
seq_event_rec event;
u_int n;
event.arr[0] = SEQ_SYSEX;
event.arr[1] = unit;
do {
n = SYSEX_CHUNK;
if (l < n) {
memset(&event.arr[2], 0xff, SYSEX_CHUNK);
n = l;
seq_event_t event;
static u_char bf[6];
if ( 0 == l ) {
warnx("zero-length system-exclusive event");
return;
}
/*
* This block is needed only to handle the possibility that a sysex
* message is broken into multiple events in a MIDI file that do not
* have length six; the /dev/music sequencer assumes a sysex message is
* finished with the first SYSEX event carrying fewer than six bytes,
* even if the last is not MIDI_SYSEX_END. So, we need to be careful
* not to send a short sysex event until we have seen the end byte.
* Instead, save some straggling bytes in bf, and send when we have a
* full six (or an end byte). Note bf/saved/insysex should be per-
* device, if we supported output to more than one device at a time.
*/
if ( svsysex > 0 ) {
if ( l > sizeof bf - svsysex ) {
memcpy(bf + svsysex, p, sizeof bf - svsysex);
l -= sizeof bf - svsysex;
p += sizeof bf - svsysex;
send_event(&SEQ_MK_SYSEX(unit,[0]=
bf[0],bf[1],bf[2],bf[3],bf[4],bf[5]));
svsysex = 0;
} else {
memcpy(bf + svsysex, p, l);
svsysex += l;
p += l;
if ( MIDI_SYSEX_END == bf[svsysex-1] ) {
event = SEQ_MK_SYSEX(unit);
memcpy(event.sysex.buffer, bf, svsysex);
send_event(&event);
svsysex = insysex = 0;
} else
insysex = 1;
return;
}
memcpy(&event.arr[2], p, n);
send_event(&event);
l -= n;
p += n;
} while (l > 0);
}
/*
* l > 0. May as well test now whether we will be left 'insysex'
* after processing this event.
*/
insysex = ( MIDI_SYSEX_END != p[l-1] );
/*
* If not for multi-event sysexes and chunk-size weirdness, this
* function could pretty much start here. :)
*/
while ( l >= SYSEX_CHUNK ) {
send_event(&SEQ_MK_SYSEX(unit,[0]=
p[0],p[1],p[2],p[3],p[4],p[5]));
p += SYSEX_CHUNK;
l -= SYSEX_CHUNK;
}
if ( l > 0 ) {
if ( insysex ) {
memcpy(bf, p, l);
svsysex = l;
} else { /* a <6 byte chunk is ok if it's REALLY the end */
event = SEQ_MK_SYSEX(unit);
memcpy(event.sysex.buffer, p, l);
send_event(&event);
}
}
}
void
@ -290,13 +394,11 @@ playfile(FILE *f, char *name)
void
playdata(u_char *buf, u_int tot, char *name)
{
int format, ntrks, divfmt, ticks, t, besttrk = 0;
int format, ntrks, divfmt, ticks, t;
u_int len, mlen, status, chan;
u_char *p, *end, byte, meta, *msg;
struct track *tracks;
u_long bestcur, now;
struct track *tp;
seq_event_rec event;
end = buf + tot;
if (verbose)
@ -361,13 +463,46 @@ playdata(u_char *buf, u_int tot, char *name)
divfmt = GET8(buf + MARK_LEN + SIZE_LEN + 4);
ticks = GET8(buf + MARK_LEN + SIZE_LEN + 5);
p = buf + MARK_LEN + SIZE_LEN + HEADER_LEN;
if ((divfmt & 0x80) == 0)
/*
* Set the timebase (or timebase and tempo, for absolute-timed files).
* PORTABILITY: some sequencers actually check the timebase against
* available timing sources and may adjust it accordingly (storing a
* new value in the ioctl arg) which would require us to compensate
* somehow. That possibility is ignored for now, as NetBSD's sequencer
* currently synthesizes all timebases, for better or worse, from the
* system clock.
*
* For a non-absolute file, if timebase is set to the file's divisions
* value, and tempo set in the obvious way, then the timing deltas in
* the MTrks require no scaling. A downside to this approach is that
* the sequencer API wants tempo in (integer) beats per minute, which
* limits how finely tempo can be specified. That might be got around
* in some cases by frobbing tempo and timebase more obscurely, but this
* player is meant to be simple and clear.
*/
if ((divfmt & 0x80) == 0) {
ticks |= divfmt << 8;
else
errx(1, "Absolute time codes not implemented yet");
if (ioctl(fd, SEQUENCER_TMR_TIMEBASE, &(int){ticks}) < 0)
err(1, "SEQUENCER_TMR_TIMEBASE");
} else {
tempo_abs = tempo_set = 1;
divfmt = -(int8_t)divfmt;
/*
* divfmt is frames per second; multiplying by 60 to set tempo
* in frames per minute could exceed sequencer's (arbitrary)
* tempo limits, so factor 60 as 12*5, set tempo in frames per
* 12 seconds, and account for the 5 in timebase.
*/
send_event(&SEQ_MK_TIMING(TEMPO,
.bpm=(12*divfmt) * (ttempo/100.) + 0.5));
if (ioctl(fd, SEQUENCER_TMR_TIMEBASE, &(int){5*ticks}) < 0)
err(1, "SEQUENCER_TMR_TIMEBASE");
}
if (verbose > 1)
printf("format=%d ntrks=%d divfmt=%x ticks=%d\n",
format, ntrks, divfmt, ticks);
printf(tempo_abs ?
"format=%d ntrks=%d abs fps=%u subdivs=%u\n" :
"format=%d ntrks=%d divisions=%u\n",
format, ntrks, tempo_abs ? divfmt : ticks, ticks);
if (format != 0 && format != 1) {
warnx("Cannot play MIDI file of type %d", format);
return;
@ -390,69 +525,58 @@ playdata(u_char *buf, u_int tot, char *name)
if (memcmp(p, MARK_TRACK, MARK_LEN) == 0) {
tracks[t].start = p + MARK_LEN + SIZE_LEN;
tracks[t].end = tracks[t].start + len;
tracks[t].curtime = getvar(&tracks[t]);
tracks[t].delta = getvar(&tracks[t]);
tracks[t].indirect = &tracks[t]; /* -> self for now */
t++;
}
p += MARK_LEN + SIZE_LEN + len;
}
/*
* Play MIDI events by selecting the track with the lowest
* curtime. Execute the event, update the curtime and repeat.
/*
* Force every channel to the same patch if requested by the user.
*/
if (sameprogram) {
for(t = 0; t < 16; t++) {
SEQ_MK_CHN_COMMON(&event, unit, MIDI_PGM_CHANGE, t,
sameprogram-1, 0, 0);
send_event(&event);
send_event(&SEQ_MK_CHN(PGM_CHANGE, .device=unit,
.channel=t, .program=sameprogram-1));
}
}
/*
* The ticks variable is the number of ticks that make up a quarter
* note and is used as a reference value for the delays between
/*
* Play MIDI events by selecting the track with the lowest
* delta. Execute the event, update the delta and repeat.
*
* The ticks variable is the number of ticks that make up a beat
* (beat: 24 MIDI clocks always, a quarter note by usual convention)
* and is used as a reference value for the delays between
* the MIDI events.
*/
now = 0;
BuildHeap(tracks, ntrks); /* tracks[0].indirect is always next */
for (;;) {
/* Locate lowest curtime */
bestcur = ~0;
for (t = 0; t < ntrks; t++) {
if (tracks[t].curtime < bestcur) {
bestcur = tracks[t].curtime;
besttrk = t;
}
}
if (bestcur == ~0)
break;
if (verbose > 1) {
printf("DELAY %4ld TRACK %2d ", bestcur-now, besttrk);
tp = tracks[0].indirect;
if ((verbose > 2 && tp->delta > 0) || verbose > 3) {
printf("DELAY %4ld TRACK %2d%s",
tp->delta, tp - tracks, verbose>3?" ":"\n");
fflush(stdout);
}
if (now < bestcur) {
union {
u_int32_t i;
u_int8_t b[4];
} u;
u_int32_t delta = bestcur - now;
delta = (int)((double)delta * tempo / (1000.0*ticks));
u.i = delta;
if (delta != 0) {
event.arr[0] = SEQ_TIMING;
event.arr[1] = TMR_WAIT_REL;
event.arr[4] = u.b[0];
event.arr[5] = u.b[1];
event.arr[6] = u.b[2];
event.arr[7] = u.b[3];
send_event(&event);
if (tp->delta > 0) {
if (!tempo_set) {
if (verbose || showmeta)
printf("No initial tempo;"
" defaulting:\n");
dometa(META_SET_TEMPO, (u_char[]){
BASETEMPO >> 16,
(BASETEMPO >> 8) & 0xff,
BASETEMPO & 0xff},
3);
}
send_event(&SEQ_MK_TIMING(WAIT_REL,
.divisions=tp->delta));
}
now = bestcur;
tp = &tracks[besttrk];
byte = *tp->start++;
if (byte == MIDI_META) {
meta = *tp->start++;
mlen = getvar(tp);
if (verbose > 1)
mlen = getlen(tp);
if (verbose > 3)
printf("META %02x (%d)\n", meta, mlen);
dometa(meta, tp->start, mlen);
tp->start += mlen;
@ -463,7 +587,7 @@ playdata(u_char *buf, u_int tot, char *name)
tp->start--;
mlen = MIDI_LENGTH(tp->status);
msg = tp->start;
if (verbose > 1) {
if (verbose > 3) {
if (mlen == 1)
printf("MIDI %02x (%d) %02x\n",
tp->status, mlen, msg[0]);
@ -471,43 +595,69 @@ playdata(u_char *buf, u_int tot, char *name)
printf("MIDI %02x (%d) %02x %02x\n",
tp->status, mlen, msg[0], msg[1]);
}
if (insysex && tp->status != MIDI_SYSEX_END) {
warnx("incomplete system exclusive message"
" aborted");
svsysex = insysex = 0;
}
status = MIDI_GET_STATUS(tp->status);
chan = MIDI_GET_CHAN(tp->status);
switch (status) {
case MIDI_NOTEOFF:
send_event(&SEQ_MK_CHN(NOTEOFF, .device=unit,
.channel=chan, .key=msg[0], .velocity=msg[1]));
break;
case MIDI_NOTEON:
send_event(&SEQ_MK_CHN(NOTEON, .device=unit,
.channel=chan, .key=msg[0], .velocity=msg[1]));
break;
case MIDI_KEY_PRESSURE:
SEQ_MK_CHN_VOICE(&event, unit, status, chan,
msg[0], msg[1]);
send_event(&event);
send_event(&SEQ_MK_CHN(KEY_PRESSURE,
.device=unit, .channel=chan,
.key=msg[0], .pressure=msg[1]));
break;
case MIDI_CTL_CHANGE:
SEQ_MK_CHN_COMMON(&event, unit, status, chan,
msg[0], 0, msg[1]);
send_event(&event);
send_event(&SEQ_MK_CHN(CTL_CHANGE,
.device=unit, .channel=chan,
.controller=msg[0], .value=msg[1]));
break;
case MIDI_PGM_CHANGE:
if (sameprogram)
break;
if (!sameprogram)
send_event(&SEQ_MK_CHN(PGM_CHANGE,
.device=unit, .channel=chan,
.program=msg[0]));
break;
case MIDI_CHN_PRESSURE:
SEQ_MK_CHN_COMMON(&event, unit, status, chan,
msg[0], 0, 0);
send_event(&event);
send_event(&SEQ_MK_CHN(CHN_PRESSURE,
.device=unit, .channel=chan, .pressure=msg[0]));
break;
case MIDI_PITCH_BEND:
SEQ_MK_CHN_COMMON(&event, unit, status, chan,
0, 0,
(msg[0] & 0x7f) |
((msg[1] & 0x7f) << 7));
send_event(&event);
send_event(&SEQ_MK_CHN(PITCH_BEND,
.device=unit, .channel=chan,
.value=(msg[0] & 0x7f) | ((msg[1] & 0x7f)<<7)));
break;
case MIDI_SYSTEM_PREFIX:
mlen = getvar(tp);
if (tp->status == MIDI_SYSEX_START)
mlen = getlen(tp);
if (tp->status == MIDI_SYSEX_START) {
send_sysex(tp->start, mlen);
else
/* Sorry, can't do this yet */;
break;
break;
} else if (tp->status == MIDI_SYSEX_END) {
/* SMF uses SYSEX_END as CONTINUATION/ESCAPE */
if (insysex) { /* CONTINUATION */
send_sysex(tp->start, mlen);
} else { /* ESCAPE */
for ( ; mlen > 0 ; -- mlen ) {
send_event(
&SEQ_MK_EVENT(putc,
SEQOLD_MIDIPUTC,
.device=unit,
.byte=*(tp->start++)
));
}
}
break;
}
/* Sorry, can't do this yet; FALLTHROUGH */
default:
if (verbose)
printf("MIDI event 0x%02x ignored\n",
@ -515,10 +665,13 @@ playdata(u_char *buf, u_int tot, char *name)
}
tp->start += mlen;
}
if (tp->start >= tp->end)
tp->curtime = ~0;
else
tp->curtime += getvar(tp);
if (tp->start >= tp->end) {
ntrks = ShrinkHeap(tracks, ntrks); /* track gone */
if (0 == ntrks)
break;
} else
tp->delta = getvar(tp);
Heapify(tracks, ntrks, 0);
}
if (ioctl(fd, SEQUENCER_SYNC, 0) < 0)
err(1, "SEQUENCER_SYNC");
@ -534,7 +687,6 @@ main(int argc, char **argv)
int listdevs = 0;
int example = 0;
int nmidi;
int t;
const char *file = DEVMUSIC;
const char *sunit;
struct synth_info info;
@ -599,24 +751,6 @@ main(int argc, char **argv)
exit(0);
}
/*
* The sequencer has two "knobs": the TIMEBASE and the TEMPO.
* The delay specified in TMR_WAIT_REL is specified in
* sequencer time units. The length of a unit is
* 60*1000000 / (TIMEBASE * TEMPO).
* Set it to 1ms/unit (adjusted by user tempo changes).
*/
t = 500 * ttempo / 100;
if (ioctl(fd, SEQUENCER_TMR_TIMEBASE, &t) < 0)
err(1, "SEQUENCER_TMR_TIMEBASE");
t = 120;
if (ioctl(fd, SEQUENCER_TMR_TEMPO, &t) < 0)
err(1, "SEQUENCER_TMR_TEMPO");
if (ioctl(fd, SEQUENCER_TMR_START, 0) < 0)
err(1, "SEQUENCER_TMR_START");
midireset();
output:
if (example)
while (example--)
@ -637,3 +771,84 @@ main(int argc, char **argv)
exit(0);
}
/*
* relative-time priority queue (min-heap). Properties:
* 1. The delta time at a node is relative to the node's parent's time.
* 2. When an event is dequeued from a track, the delta time of the new head
* event is relative to the time of the event just dequeued.
* Therefore:
* 3. After dequeueing the head event from the track at heap root, the next
* event's time is directly comparable to the root's children.
* These properties allow the heap to be maintained with delta times throughout.
* Insert is also implementable, but not needed: all the tracks are present
* at first; they just go away as they end.
*/
#define PARENT(i) ((i-1)>>1)
#define LEFT(i) ((i<<1)+1)
#define RIGHT(i) ((i+1)<<1)
#define DTIME(i) (t[i].indirect->delta)
#define SWAP(i,j) do { \
struct track *_t = t[i].indirect; \
t[i].indirect = t[j].indirect; \
t[j].indirect = _t; \
} while ( /*CONSTCOND*/ 0 )
void
Heapify(struct track *t, int ntrks, int node)
{
int lc, rc, mn;
lc = LEFT(node);
rc = RIGHT(node);
if (rc >= ntrks) { /* no right child */
if (lc >= ntrks) /* node is a leaf */
return;
if (DTIME(node) > DTIME(lc))
SWAP(node,lc);
DTIME(lc) -= DTIME(node);
return; /* no rc ==> lc is a leaf */
}
mn = lc;
if (DTIME(lc) > DTIME(rc))
mn = rc;
if (DTIME(node) <= DTIME(mn)) {
DTIME(rc) -= DTIME(node);
DTIME(lc) -= DTIME(node);
return;
}
SWAP(node,mn);
DTIME(rc) -= DTIME(node);
DTIME(lc) -= DTIME(node);
Heapify(t, ntrks, mn); /* gcc groks tail recursion */
}
void
BuildHeap(struct track *t, int ntrks)
{
int node;
for ( node = PARENT(ntrks-1); node --> 0; )
Heapify(t, ntrks, node);
}
/*
* Make the heap 1 item smaller by discarding the track at the root. Move the
* rightmost bottom-level leaf to the root and decrement ntrks. It remains to
* run Heapify, which the caller is expected to do. Returns the new ntrks.
*/
int
ShrinkHeap(struct track *t, int ntrks)
{
int ancest;
-- ntrks;
for ( ancest = PARENT(ntrks); ancest > 0; ancest = PARENT(ancest) )
DTIME(ntrks) += DTIME(ancest);
t[0].indirect = t[ntrks].indirect;
return ntrks;
}