
From gos.ukc.ac.uk!harrier.ukc.ac.uk!ukc!mcvax!uunet!lll-winken!ames!ncar!boulder!sunybcs!rutgers!bellcore!faline!thumper!ulysses!att!twitch!glimmer!tjt Wed Mar  8 12:05:31 GMT 1989
Article 1798 of rec.music.synth:
Path: gos.ukc.ac.uk!harrier.ukc.ac.uk!ukc!mcvax!uunet!lll-winken!ames!ncar!boulder!sunybcs!rutgers!bellcore!faline!thumper!ulysses!att!twitch!glimmer!tjt
>From: tjt@glimmer.UUCP (Tim Thompson)
Newsgroups: rec.music.synth
Subject: Re: MIDI file format, C src?
Summary: here's some code
Keywords: MIDI, C
Message-ID: <171@glimmer.UUCP>
Date: 6 Mar 89 01:42:38 GMT
References: <1859@amadeus.LA.TEK.COM>
Organization: a UNIX PC in Howell, NJ
Lines: 1422

In article <1859@amadeus.LA.TEK.COM>, jrb@amadeus.LA.TEK.COM (Jim Binkley) writes:
> Someone recently asked about the MIDI file format.
> Does anyone have any C src that they would be
> willing to share that will deal with MIDI file format?

Enclosed is my library for reading Standard MIDI Files.

                               ...Tim Thompson...att!twitch!glimmer!tjt...

#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create the files:
#	README
#	Makefile
#	example1.uu
#	example2.uu
#	example3.uu
#	example4.uu
#	example5.uu
#	mfstrings.c
#	mftext.c
#	midi.h
#	midifile.3
#	midifile.c
#	midifile.h
# This archive created: Sun Mar  5 20:21:45 1989
export PATH; PATH=/bin:$PATH
echo shar: extracting "'README'" '(510 characters)'
if test -f 'README'
then
	echo shar: will not over-write existing file "'README'"
else
cat << \SHAR_EOF > 'README'
This directory contains a parser for Standard MIDI Files.  "make mftext"
will compile a program that gives a verbose textual listing of a MIDI
file.  "make midifile.man" will produce a formatted manual page.
Example MIDI files are named "example*.uu".  They're uuencoded;
"make uutomid" will uudecode them all.  The first two examples
(example1 and example2) are the examples given in the 0.06 version
of the standard MIDI file specification.

                      ...Tim Thompson...att!twitch!glimmer!tjt...
SHAR_EOF
if test 510 -ne "`wc -c < 'README'`"
then
	echo shar: error transmitting "'README'" '(should have been 510 characters)'
fi
fi # end of overwriting check
echo shar: extracting "'Makefile'" '(811 characters)'
if test -f 'Makefile'
then
	echo shar: will not over-write existing file "'Makefile'"
else
cat << \SHAR_EOF > 'Makefile'
CFLAGS = -O -DNOVOID

all : mftext mfstrings midifile.man

mftext : midifile.o mftext.o
	cc midifile.o mftext.o -o mftext

mfstrings : midifile.o mfstrings.o
	cc midifile.o mfstrings.o -o mfstrings

midifile.man : midifile.3
	nroff -man -Tlp midifile.3 | col -b > midifile.man

clean :
	rm -f mftext mfstrings *.o midifile.man

lint :
	lint midifile.c mftext.c

midtouu :
	for i in example*.mid ; \
	do \
		echo $$i ; \
		uuencode $$i $$i > `basename $$i .mid`.uu ; \
		if [ $$? -eq 0 ] ; then  rm -f $$i ; fi ; \
	done

uutomid :
	for i in example*.uu ; \
	do \
		echo $$i ; \
		uudecode $$i ; \
		if [ $$? -eq 0 ] ; then  rm -f $$i ; fi ; \
	done

test : mftext
	for i in example*.mid ; \
	do \
		echo $$i ; \
		mftext $$i > /dev/null ; \
		if [ $$? -ne 0 ] ; then echo "Mftext of $$i failed!" ; fi ; \
	done
SHAR_EOF
if test 811 -ne "`wc -c < 'Makefile'`"
then
	echo shar: error transmitting "'Makefile'" '(should have been 811 characters)'
fi
fi # end of overwriting check
echo shar: extracting "'example1.uu'" '(141 characters)'
if test -f 'example1.uu'
then
	echo shar: will not over-write existing file "'example1.uu'"
else
cat << \SHAR_EOF > 'example1.uu'
begin 664 example1.mid
M351H9`````8````!`&!-5')K````.P#_6`0$`A@(`/]1`P>A(`#`!0#!+@#"
D1@"2,&``/&!@D4-`8)!,((%`@C!``#Q``(%#0`"`3$``_R\`
`
end
SHAR_EOF
if test 141 -ne "`wc -c < 'example1.uu'`"
then
	echo shar: error transmitting "'example1.uu'" '(should have been 141 characters)'
fi
fi # end of overwriting check
echo shar: extracting "'example2.uu'" '(195 characters)'
if test -f 'example2.uu'
then
	echo shar: will not over-write existing file "'example2.uu'"
else
cat << \SHAR_EOF > 'example2.uu'
begin 664 example2.mid
M351H9`````8``0`$`&!-5')K````%`#_6`0$`A@(`/]1`P>A((,`_R\`351R
M:P```!``P`6!0)!,((%`3```_R\`351R:P````\`P2Y@D4-`@B!#``#_+P!-
<5')K````%0#"1@"2,&``/&"#`#```#P``/\O``\`
`
end
SHAR_EOF
if test 195 -ne "`wc -c < 'example2.uu'`"
then
	echo shar: error transmitting "'example2.uu'" '(should have been 195 characters)'
fi
fi # end of overwriting check
echo shar: extracting "'example3.uu'" '(191 characters)'
if test -f 'example3.uu'
then
	echo shar: will not over-write existing file "'example3.uu'"
else
cat << \SHAR_EOF > 'example3.uu'
begin 664 example3.mid
M351H9`````8````!`!A-5')K````7P#_`29$('-C86QE("`@("`@("`@("`@
M("`@("`@("`@("`@("`@("`@(`"0/D`,/@`,0$`,0``,0D`,0@`,0T`,0P`,
;14`,10`,1T`,1P`,24`,20`,2D`,2@`,_R\`
`
end
SHAR_EOF
if test 191 -ne "`wc -c < 'example3.uu'`"
then
	echo shar: error transmitting "'example3.uu'" '(should have been 191 characters)'
fi
fi # end of overwriting check
echo shar: extracting "'example4.uu'" '(2519 characters)'
if test -f 'example4.uu'
then
	echo shar: will not over-write existing file "'example4.uu'"
else
cat << \SHAR_EOF > 'example4.uu'
begin 664 example4.mid
M351H9`````8````!`#!-5')K```&]P#_`28Y-G!P<3$L,B`@("`@("`@("`@
M("`@("`@("`@("`@("`@("`@(`"0*U``/E@`.U``05`$D4]T%)`^```[``!!
M``613F`$3P`/D"L``9%/7`!.`!I19`!/`!!1``%/:A5/`$^0*T0`.E@`/5``
M0&@"D4]T%I`Z```]``!```613EP"3P`/3V@`3@`"D"L`&)%19`!/`!-/:`51
M`!)/`$U37`&0*T0`/E``.TX`05`0D5,``E10!I`^```[``!!``614UL#5``,
M4F0"4P`"D"L`#9%2`"%15`*0*T0`.E0`/5``0$T.D5)4`E$`")`Z```]``!`
M``214@`"45H/4%0#D"L``9%1``E0`")/6`20*T@`/EH`.U``05H#D4\`%9`^
M```[``!!``:10U0*0P`#148%D"L``9%#9`%%``A"=`1#`!5"``%#7!%$6P!#
M`!E$``)%9!%&4`)%`!!&``M'9!!'``*0)$0"D4AH+I`\1@`W0`!`3``D``*1
M2``6D#P``#<``$``!9%(6P=(``M(9`&0*T8*D4@`)4A<`9`\2``W.P!`4``K
M``N12``-D#P``#<``$``&"1(`Y%(7"V0/$T`-T0`0$\`)``!D4@`%Y`\```W
M``!``!@K1@&11UP51P`:D#Q0`#=(`$!0`"L``I%(7!:0/```-P``0``$D4@`
M`DE;#4D``DIH`Y`F2#`W2``[3@`U2``^*P`F`!@W```[```U```^`!@K2":1
M2@`*D#Y.`#=.`#5&`#M(`"L`&#X``#<``#4``#L`&"9`&I%#7`Q%:`1#``:0
M/DT`-T@`-3X`.T<`)@`!D44``D-D"T)H!$,`!I`^```W```U```[``Z10@`"
M0V8(D"M,#9%-9`!#`!E*9`%-``F0/DX`-T8`.TT`-3T`*P`(D4=<`TH`#9`^
M```W```[```U``611P`#0UP0D"],`)%#``)*="Z0/E``-4@`-TP`.T\`+P`#
MD4H`%9`^```U```W```[``612F@)2@`(2F0"D"M$#Y%*`"!*:@&0/E``-T@`
M-44`.T@`*P`1D4H`!Y`^```W```U```[`!@F4`&12F@OD#Y,`#=(`#M-`#5`
M`"8``I%)8`)*`!20/@``-P``.P``-0`%D4D``4IH$I`K2`.12UP"2@`72P`"
M4V@2D#Y0`#5&`#=/`#M0`"L`#I%27`)3``B0/@``-0``-P``.P`%D5%8`%(`
M#5!;!%$``I`D1PF13V0!4``FD#Q,`#=0`$!4`"0`&#P``#<``$``%Y%/``&0
M*TP?D4AD#4QF`4@``Y`\3``W0`!`4``K``B13``$1V0+2UL!D#P``#<``$``
M`Y%'``M+``-*7`-&9`20)$TP/%@`-UH`0%@`)``5D48``Y`\```W``!```B1
M2@`0D"9$&"8`&"A/`#Q8`#=0`$!:"I%+=`Z0*```/```-P``0```D4L`"4QJ
M#Y`I3`"13``"370ND#E0`#Q(`$%-`"D``Y%-`!60.0``/```00`"D4QD#DP`
M!DUH`I`D3"2130`,D#E/`#Q(`$%.`"0`&)%/7`"0.0``/```00`1D4\`!$U;
M`Y`I4#`Y3``\3@!!3``I`!@Y```\``!!``:130`2D"1',#E(`#Q0`$%4`"0`
M`Y%/:!)/``.0.0``/```00`%D4U<$$T``Y`P3`&13%POD#=,`#Q4`$!4`#``
M!Y%,`!&0-P``/```0``$D4M;#4L`!TQH`)`K1!"13``+2UP.2P`"2F0%D#Q0
M`#=``$!0`"L`#I%*``J0/```-P``0``#D4E4#4D`!DAD`I`P4#`\4``W0`!`
M2``P``:12``2D#P``#<``$``&"](&"\`&)%'6`"0+4@`/%@`-T@`0$@0D4A4
M`$<`")`M```\```W``!```2125P%2``,2F@#D"M$`)%)`!%*`!^0/D@`-40`
M-T<`.T``*P`!D4-'$454`$,`!I`^```U```W```[``*11EL&10`)1VH&1@`!
MD"90")%'`"8^9`*0/DX`-40`-T0`.T0`)@`+D3]<`CX`"Y`^```U```W```[
M``210'0`/P`-0``'070`D"M(&I%!``)%9!)%``)(9`"0/D\`-U``-4T`.TX`
M*P`8/@``-P``-0``.P`!D4@``TQJ$$P`!)`F3`.12G062@`,3UL+D#L[`#X^
M`#5``#=``"8`"I%37`1/``J0.P``/@``-P`'D5,`!E9T!Y`U``0D3P`\7@61
M5%P`5@`3D#P`$9%4``>0)``8+4P`.5@8*U@`-UP`+0`4.0`$*P``-P`8*%X`
M-%@8*```-``8)%\`,&@8,``8)``>D5M3!UU8!%L`!%]<!%T`!&!T`U\`"6``
%@T__+P``
`
end
SHAR_EOF
if test 2519 -ne "`wc -c < 'example4.uu'`"
then
	echo shar: error transmitting "'example4.uu'" '(should have been 2519 characters)'
fi
fi # end of overwriting check
echo shar: extracting "'example5.uu'" '(1431 characters)'
if test -f 'example5.uu'
then
	echo shar: will not over-write existing file "'example5.uu'"
else
cat << \SHAR_EOF > 'example5.uu'
begin 664 example5.mid
M351H9`````8````!`#!-5')K```#X@#_`294<F%S:"$A`"`@("`@("`@("`@
M("`@("`@("`@("`@("`@("`@(`"0/D``L`0K``0N`00S`@0W`00Z`00^`01!
M`01$`=`"`@4!L`1(`=```+`$30"0/@`!L`13`01:`01A`01G`=`#`;`$;`'0
M$@"P!'`!!'0!T#``L`1W`01Z`=!3`+`$?P&00$`!T'4"?P2P`5,"`5`"`4P!
M!'<`D$```;`$<`$!20`$:@$$9`$$7P$!1P$$6@$$50$$4`$$3`$!00`$1P$$
M0@&00D``L`0\`0$^``0W`00Q`0$Z``0K`00E`@$U``0>`009`0$Q``03`>!\
M/@"P!`\!X'L]`+`$"@'@>#P`L`0$`0$L``0``)!"``'@=SL`=#H!L`$H`>!S
M.0%P.`!L-@%K-0"P`2,!X&@T`6<S`&,Q`6`P`+`!'`'@7"X!6"P`5RL!5"H`
ML`$7`9!#0`#@4RD!4"@!3R<`L`$3`00#`00(`00,`000`006`0$8``0<`>!3
M*0%7*P"P!"(!`1T`!"@!X%LM`+`$+P"00P`!X&`P`+`$-P$!)`#@9#(`L`0^
M`>!H-`"P!$0!X&PV`+`!+``$2@'@<#@`L`11`>!T.@"P!%8!X'@\`+`!,P`$
M70'@?#X`L`1C`>``0`"P!&H!`3H!!&\!D$5``+`!/@`$<@$$=`$!0@`$=P$$
M>@$$?0$!10`$?P(!2`(!3`(!40&010`!L`%4`@%8`@%<`P%A`>`$0@$(1`$,
M1@&01T``L`%J``1_`>`02``42@"P!'@!!'(!`7``X!M-`+`$;`+@'T\`L`%T
M``1H`01E`>`C40"P!&0!X"=3`+`$8@$!>``$70'@*U4`L`16`01.`>`O5P"P
M!$8`D$<``;`!?``$/@$$.`$$-`$$,`'@+%8`L`0M`>`K50"P`7D!X"=3`+`$
M*0'@(U$`'T\`L`0E`>`;30"P`6X`!"$!X!1*`+`$'`'@$$@`"T4`L`08`9!)
M0`#@!T,`L`%@``03`>``0`"P!`X!!`H!`5``!`8!!`(!`44`!``"`3P"`38#
M`3(`D$D``K`!+P$$`@$!*P`$!@$$"P'@?#X!>ST`L`$E``00`>!W.P!S.0"P
M!!8!X&\W`+`$'0'@:S4`L`$9``0D`>!D,@!C,0"P!"L!D$I``.!<+@"P!#(!
MX%LM`+`!#0`$.@'@5RL`L`1"`>!3*0!0*`"P!$<!X$\G`+`!`@`$2@+@2R4!
ML`$``.!()`602@`"X$PF`5`H`;`!"0#@5RL!8#`!:S4!<#@`L`$A`>![/0``
:0`*P`3,`T"@"L`$_`=```K`!1P(!2FS_+P`!
`
end
SHAR_EOF
if test 1431 -ne "`wc -c < 'example5.uu'`"
then
	echo shar: error transmitting "'example5.uu'" '(should have been 1431 characters)'
fi
fi # end of overwriting check
echo shar: extracting "'mfstrings.c'" '(544 characters)'
if test -f 'mfstrings.c'
then
	echo shar: will not over-write existing file "'mfstrings.c'"
else
cat << \SHAR_EOF > 'mfstrings.c'
/* This program, when compiled with the midifile(3) library, will */
/* print only the text messages in a standard MIDI file. */

#include <stdio.h>
#include <ctype.h>
#include "midifile.h"

FILE *F;

mygetc() { return(getc(F)); }

mytext(type,leng,msg)
char *msg;
{
	char *p;
	char *ep = msg + leng;

	for ( p=msg; p<ep ; p++ )
		putchar( isprint(*p) ? *p : '?' );
	putchar('\n');
}

main(argc,argv)
char **argv;
{
	if ( argc > 1 )
		F = fopen(argv[1],"r");
	else
		F = stdin;

	Mf_getc = mygetc;
	Mf_text = mytext;

	midifile();

	exit(0);
}
SHAR_EOF
if test 544 -ne "`wc -c < 'mfstrings.c'`"
then
	echo shar: error transmitting "'mfstrings.c'" '(should have been 544 characters)'
fi
fi # end of overwriting check
echo shar: extracting "'mftext.c'" '(4324 characters)'
if test -f 'mftext.c'
then
	echo shar: will not over-write existing file "'mftext.c'"
else
cat << \SHAR_EOF > 'mftext.c'
/*
 * mftext
 * 
 * Convert a MIDI file to verbose text.
 */

#include <stdio.h>
#include <ctype.h>
#include "midifile.h"

static FILE *F;

filegetc()
{
	return(getc(F));
}

main(argc,argv)
char **argv;
{
	FILE *efopen();

	if ( argc > 1 )
		F = efopen(argv[1],"r");
	else
		F = stdin;

	initfuncs();
	Mf_getc = filegetc;
	midifile();
	fclose(F);
	exit(0);
}

FILE *
efopen(name,mode)
char *name;
char *mode;
{
	FILE *f;
	extern int errno;
	extern char *sys_errlist[];
	extern int sys_nerr;
	char *errmess;

	if ( (f=fopen(name,mode)) == NULL ) {
		(void) fprintf(stderr,"*** ERROR *** Cannot open '%s'!\n",name);
		if ( errno <= sys_nerr )
			errmess = sys_errlist[errno];
		else
			errmess = "Unknown error!";
		(void) fprintf(stderr,"************* Reason: %s\n",errmess);
		exit(1);
	}
	return(f);
}

error(s)
char *s;
{
	fprintf(stderr,"Error: %s\n",s);
}

txt_header(format,ntrks,division)
{
	printf("Header format=%d ntrks=%d division=%d\n",format,ntrks,division);
}

txt_trackstart()
{
	printf("Track start\n");
}

txt_trackend()
{
	printf("Track end\n");
}

txt_noteon(chan,pitch,vol)
{
	prtime();
	printf("Note on, chan=%d pitch=%d vol=%d\n",chan+1,pitch,vol);
}

txt_noteoff(chan,pitch,vol)
{
	prtime();
	printf("Note off, chan=%d pitch=%d vol=%d\n",chan+1,pitch,vol);
}

txt_pressure(chan,pitch,press)
{
	prtime();
	printf("Pressure, chan=%d pitch=%d press=%d\n",chan+1,pitch,press);
}

txt_parameter(chan,control,value)
{
	prtime();
	printf("Parameter, chan=%d c1=%d c2=%d\n",chan+1,control,value);
}

txt_pitchbend(chan,msb,lsb)
{
	prtime();
	printf("Pitchbend, chan=%d msb=%d lsb=%d\n",chan+1,msb,lsb);
}

txt_program(chan,program)
{
	prtime();
	printf("Program, chan=%d program=%d\n",chan+1,program);
}

txt_chanpressure(chan,press)
{
	prtime();
	printf("Channel pressure, chan=%d pressure=%d\n",chan+1,press);
}

txt_sysex(leng,mess)
char *mess;
{
	prtime();
	printf("Sysex, leng=%d\n",leng);
}

txt_metamisc(type,leng,mess)
char *mess;
{
	prtime();
	printf("Meta event, unrecognized, type=0x%02x leng=%d\n",type,leng);
}

txt_metaspecial(type,leng,mess)
char *mess;
{
	prtime();
	printf("Meta event, sequencer-specific, type=0x%02x leng=%d\n",type,leng);
}

txt_metatext(type,leng,mess)
char *mess;
{
	static char *ttype[] = {
		NULL,
		"Text Event",		/* type=0x01 */
		"Copyright Notice",	/* type=0x02 */
		"Sequence/Track Name",
		"Instrument Name",	/* ...       */
		"Lyric",
		"Marker",
		"Cue Point",		/* type=0x07 */
		"Unrecognized"
	};
	int unrecognized = (sizeof(ttype)/sizeof(char *)) - 1;
	register int n, c;
	register char *p = mess;

	if ( type < 1 || type > unrecognized )
		type = unrecognized;
	prtime();
	printf("Meta Text, type=0x%02x (%s)  leng=%d\n",type,ttype[type],leng);
	printf("     Text = <");
	for ( n=0; n<leng; n++ ) {
		c = *p++;
		printf( (isprint(c)||isspace(c)) ? "%c" : "\\0x%02x" , c);
	}
	printf(">\n");
}

txt_metaseq(num)
{
	prtime();
	printf("Meta event, sequence number = %d\n",num);
}

txt_metaeot()
{
	prtime();
	printf("Meta event, end of track\n");
}

txt_keysig(sf,mi)
{
	prtime();
	printf("Key signature, sharp/flats=%d  minor=%d\n",sf,mi);
}

txt_tempo(tempo)
long tempo;
{
	prtime();
	printf("Tempo, microseconds-per-MIDI-quarter-note=%d\n",tempo);
}

txt_timesig(nn,dd,cc,bb)
{
	int denom = 1;
	while ( dd-- > 0 )
		denom *= 2;
	prtime();
	printf("Time signature=%d/%d  MIDI-clocks/click=%d  32nd-notes/24-MIDI-clocks=%d\n",
		nn,denom,cc,bb);
}

txt_smpte(hr,mn,se,fr,ff)
{
	prtime();
	printf("SMPTE, hour=%d minute=%d second=%d frame=%d fract-frame=%d\n",
		hr,mn,se,fr,ff);
}

txt_arbitrary(leng,mess)
char *mess;
{
	prtime();
	printf("Arbitrary bytes, leng=%d\n",leng);
}

prtime()
{
	printf("Time=%ld  ",Mf_currtime);
}

initfuncs()
{
	Mf_error = error;
	Mf_header =  txt_header;
	Mf_starttrack =  txt_trackstart;
	Mf_endtrack =  txt_trackend;
	Mf_on =  txt_noteon;
	Mf_off =  txt_noteoff;
	Mf_pressure =  txt_pressure;
	Mf_controller =  txt_parameter;
	Mf_pitchbend =  txt_pitchbend;
	Mf_program =  txt_program;
	Mf_chanpressure =  txt_chanpressure;
	Mf_sysex =  txt_sysex;
	Mf_metamisc =  txt_metamisc;
	Mf_seqnum =  txt_metaseq;
	Mf_eot =  txt_metaeot;
	Mf_timesig =  txt_timesig;
	Mf_smpte =  txt_smpte;
	Mf_tempo =  txt_tempo;
	Mf_keysig =  txt_keysig;
	Mf_sqspecific =  txt_metaspecial;
	Mf_text =  txt_metatext;
	Mf_arbitrary =  txt_arbitrary;
}
SHAR_EOF
if test 4324 -ne "`wc -c < 'mftext.c'`"
then
	echo shar: error transmitting "'mftext.c'" '(should have been 4324 characters)'
fi
fi # end of overwriting check
echo shar: extracting "'midi.h'" '(545 characters)'
if test -f 'midi.h'
then
	echo shar: will not over-write existing file "'midi.h'"
else
cat << \SHAR_EOF > 'midi.h'
#define NOTEOFF 0x80
#define NOTEON 0x90
#define PRESSURE 0xa0
#define CONTROLLER 0xb0
#define PITCHBEND 0xe0
#define PROGRAM 0xc0
#define CHANPRESSURE 0xd0

/* These are the strings used in keynote to identify Standard MIDI File */
/* meta text messages. */

#define METATEXT		"Text Event"
#define METACOPYRIGHT		"Copyright Notice"
#define METASEQUENCE		"Sequence/Track Name"
#define METAINSTRUMENT		"Instrument Name"
#define METALYRIC		"Lyric"
#define METAMARKER		"Marker"
#define METACUE			"Cue Point"
#define METAUNRECOGNIZED	"Unrecognized"
SHAR_EOF
if test 545 -ne "`wc -c < 'midi.h'`"
then
	echo shar: error transmitting "'midi.h'" '(should have been 545 characters)'
fi
fi # end of overwriting check
echo shar: extracting "'midifile.3'" '(6215 characters)'
if test -f 'midifile.3'
then
	echo shar: will not over-write existing file "'midifile.3'"
else
cat << \SHAR_EOF > 'midifile.3'
.TH MIDIFILE 3
.SH NAME
midifile - read a standard MIDI file
.SH SYNOPSIS
\fC#include "midifile.h"

midifile ()

.nf
int (*Mf_getc) ();
int (*Mf_error) (char *msg);
int (*Mf_header) (int format, int ntrks, int division);
int (*Mf_trackstart) ();
int (*Mf_trackend) ();
int (*Mf_noteon) (int chan, int pitch, int vol);
int (*Mf_noteoff) (int chan, int pitch, int vol);
int (*Mf_pressure) (int chan, int pitch, int pressure);
int (*Mf_parameter) (int chan, int control, int value);
int (*Mf_pitchbend) (int chan, int msb, int lsb);
int (*Mf_program) (int chan, int program);
int (*Mf_chanpressure) (int chan, int pressure);
int (*Mf_sysex) (int leng, char *msg);
int (*Mf_metamisc) (int type, int leng, int msg);
int (*Mf_seqspecific) (int type, int leng, int msg);
int (*Mf_seqnum) (int num);
int (*Mf_text) (int type, int leng, int msg);
int (*Mf_eot) ();
int (*Mf_timesig) (int numer, int denom, int clocks, int qnotes);
int (*Mf_smpte) (int hour, int min, int sec, int frame, int fract);
int (*Mf_tempo) (int microsecs);
int (*Mf_keysig) (int sharpflat, int minor);
int (*Mf_arbitrary) (int leng, int msg);

int Mf_nomerge;
long Mf_currtime;
.fi

.SH DESCRIPTION
The \fCmidifile\fR function reads and inteprets a standard MIDI file.
To use it you need to understand the general form of a
MIDI file and the type of information it contains, but you don't
need to know much, if anything, about the detailed format of the file
and the mechanics of reading it reliably and portably.

A single call to \fCmidifile\fR will read an entire MIDI file.
The interface to \fCmidifile\fR is a set of external variables
named \fCMf_*\fR, most of which are function pointers to be called
from within \fCmidifile\fR during the process of parsing the MIDI file.
Before calling \fCmidifile\fR, the only
requirement is that you assign a value
to \fCMf_getc\fR - a pointer to a function that will return
characters from the MIDI file, using -1 to indicate EOF.
All the rest of the function
pointers are initialized to NULL, and the default action for each
is to do nothing.  The following is a complete program using \fCmidifile\fR
that could serve as a 'syntax checker' for MIDI files:

.in +1i
.ft C
.nf
#include <stdio.h>
#include "midifile.h"

mygetc()
{
	/* use standard input */
	return(getchar());
}

main()
{
	Mf_getc = mygetc;
	midifile();
	exit(0);
}
.fi
.ft R
.in -1i

This takes advantage of the default action when an error is detected, which
is to exit silently with a return code of 1.  An error function of your
own can be used by giving a value to \fCMf_error\fR; the function will be
called with the error message as an argument.
The other \fCMf_* variables can similarly be used to call arbitrary
functions while parsing the MIDI file.  The descriptions below
of the information passed to these functions is sparse; refer to
the MIDI file standard for the complete descriptions.

\fCMf_header\fR is the first function to be called, and its arguments
contain information from the MIDI file's header; the format (0,1, or 2),
the number of tracks, and the division of a quarter-note that defines
the times units.
\fCMf_trackstart\fR and
\fCMf_trackend\fR are called at the beginning and end of each track.

Once inside a track, each separate message causes a function to be called.
For example, each note-on message causes \fCMf_noteon\fR to be called
with the channel, pitch, and volume as arguments.  The time at which
the message occurred is stored in \fCMf_currtime\fR - one of the few
external variables that isn't a function pointer.  The other channel messages
are handled in a similar and obvious fashion -
\fCMf_noteoff\fR,
\fCMf_pressure\fR,
\fCMf_parameter\fR,
\fCMf_pitchbend\fR,
\fCMf_program\fR,
and \fCMf_chanpressure\fR.  See the declarations above for the arguments
that are passed to each.

System exclusive messages are handled by calling \fCMf_sysex\fR, passing
as arguments the message length and a pointer to a static buffer containing
the entire message.
The buffer is expanded when necessary; memory availability is the only limit
to its size.  Normally, 'continued' system exclusives are automatically
merged, and \fCMf_sysex\fR is only called once.  It you want to disable this
you can set \fCMf_nomerge\fR to 1, causing \fCMf_sysex\fR to be called
once for each part of the message.

\fCMf_seqnum\fR is called by the \fImeta\fR message that provides
a sequence number,
which if present must appear at the beginning of a track.
The tempo \fImeta\fR message causes \fCMf_tempo\fR to be called; its
argument is the number of microseconds per MIDI quarter-note (24 MIDI clocks).
The end-of-track \fImeta\fR message causes \fCMf_eot\fR to be called.
The key signature \fImeta\fR message causes \fCMf_keysig\fR to be called;
the first argument conveys the number of sharps or flats, the second
argument is 1 if the key is minor.

The \fCMf_timesig\fR and \fCMf_smpte\fR functions are called when the
corresponding \fImeta\fR messages are seen.  See the MIDI file standard
for a description of their arguments.

The \fItext\fR messages in the MIDI file standard are of the following
types:

.in +1i
.nf
0x01		Text Event
0x02		Copyright
0x03		Sequence/Track Name
0x04		Instrument
0x05		Lyric
0x06		Marker
0x07		Cue Point
0x08-0x0F	Reserverd but Undefined
.fi
.in -1i

\fCMf_text\fR is called for each of these; the arguments are
the type number, the message length, and a pointer to the message buffer.

Misceallaneous \fImeta\fR messages are handled by \fCMf_metamisc\fR,
sequencer-specific messages are handled by \fCMf_seqspecific\fR, and
arbitrary "escape" messages (started with 0xF7) are handled by
\fCMf_arbitrary\fR.
.SH EXAMPLE
The following is a \fCstrings\fR-like program for MIDI files:

.in +1i
.ft C
.nf
#include <stdio.h>
#include <ctype.h>
#include "midifile.h"

FILE *F;

mygetc() { return(getc(F)); }

mytext(type,leng,msg)
char *msg;
{
	char *p;
	char *ep = msg + leng;

	for ( p=msg; p<ep ; p++ )
		putchar( isprint(*p) ? *p : '?' );
	putchar('\n');
}

main(argc,argv)
char **argv;
{
	if ( argc > 1 )
		F = fopen(argv[1],"r");
	else
		F = stdin;

	Mf_getc = mygetc;
	Mf_text = mytext;

	midifile();

	exit(0);
}
.fi
.ft R
.in -1i

.SH AUTHOR
Tim Thompson, att!twitch!glimmer!tjt
SHAR_EOF
if test 6215 -ne "`wc -c < 'midifile.3'`"
then
	echo shar: error transmitting "'midifile.3'" '(should have been 6215 characters)'
fi
fi # end of overwriting check
echo shar: extracting "'midifile.c'" '(10990 characters)'
if test -f 'midifile.c'
then
	echo shar: will not over-write existing file "'midifile.c'"
else
cat << \SHAR_EOF > 'midifile.c'
/*
 * Read a Standard MIDI File.  Externally-assigned function pointers are
 * called upon recognizing things in the file.  See midifile(3).
 */

#include "midi.h"
#include "midifile.h"

#define EOF (-1)

#ifdef PROTOTYPES
#define NOARGS void
#else
#define NOARGS
#endif

#ifdef NOVOID
#define VOID int
#else
#define VOID void
#endif

/* public stuff */

/* Functions to be called while processing the MIDI file. */
VOID (*Mf_starttrack)(NOARGS) = 0;
VOID (*Mf_endtrack)(NOARGS) = 0;
int (*Mf_getc)(NOARGS) = 0;
VOID (*Mf_eot)(NOARGS) = 0;
#ifdef PROTOTYPES
VOID (*Mf_error)(char *) = 0;
VOID (*Mf_header)(int,int,int) = 0;
VOID (*Mf_on)(int,int,int) = 0;
VOID (*Mf_off)(int,int,int) = 0;
VOID (*Mf_pressure)(int,int,int) = 0;
VOID (*Mf_controller)(int,int,int) = 0;
VOID (*Mf_pitchbend)(int,int,int) = 0;
VOID (*Mf_program)(int,int) = 0;
VOID (*Mf_chanpressure)(int,int) = 0;
VOID (*Mf_sysex)(int,char*) = 0;
VOID (*Mf_arbitrary)(int,char*) = 0;
VOID (*Mf_metamisc)(int,int,char*) = 0;
VOID (*Mf_seqnum)(int) = 0;
VOID (*Mf_smpte)(int,int,int,int,int) = 0;
VOID (*Mf_timesig)(int,int,int,int) = 0;
VOID (*Mf_tempo)(int) = 0;
VOID (*Mf_keysig)(int,int) = 0;
VOID (*Mf_sqspecific)(int,char*) = 0;
VOID (*Mf_text)(int,int,char*) = 0;
#else
VOID (*Mf_error)() = 0;
VOID (*Mf_header)() = 0;
VOID (*Mf_on)() = 0;
VOID (*Mf_off)() = 0;
VOID (*Mf_pressure)() = 0;
VOID (*Mf_controller)() = 0;
VOID (*Mf_pitchbend)() = 0;
VOID (*Mf_program)() = 0;
VOID (*Mf_chanpressure)() = 0;
VOID (*Mf_sysex)() = 0;
VOID (*Mf_arbitrary)() = 0;
VOID (*Mf_metamisc)() = 0;
VOID (*Mf_seqnum)() = 0;
VOID (*Mf_smpte)() = 0;
VOID (*Mf_tempo)() = 0;
VOID (*Mf_timesig)() = 0;
VOID (*Mf_keysig)() = 0;
VOID (*Mf_sqspecific)() = 0;
VOID (*Mf_text)() = 0;
#endif

int Mf_nomerge = 0;		/* 1 => continue'ed system exclusives are */
				/* not collapsed. */
long Mf_currtime = 0L;		/* current time in delta-time units */
int Mf_skipinit = 0;		/* 1 if initial garbage should be skipped */

/* private stuff */

static long Mf_toberead = 0L;

static long readvarinum(NOARGS);
static long read32bit(NOARGS);
static int read16bit(NOARGS);
static VOID msgenlarge(NOARGS);
static char *msg(NOARGS);
static int readheader(NOARGS);
static VOID readtrack(NOARGS);
static VOID sysex(NOARGS), msginit(NOARGS);
static int egetc(NOARGS);
static int msgleng(NOARGS);

#ifdef PROTOTYPES
static int readmt(char*,int);
static long to32bit(int,int,int,int);
static int to16bit(int,int);
static VOID mferror(char *);
static VOID badbyte(int);
static VOID metaevent(int);
static VOID msgadd(int);
static VOID chanmessage(int,int,int);
#else
static long to32bit();
static int to16bit();
static VOID mferror();
static VOID badbyte();
static VOID metaevent();
static VOID msgadd();
static VOID chanmessage();
#endif

VOID
midifile()	 	/* The only non-static function in this file. */
{
	int ntrks;

	if ( Mf_getc == 0 )
		mferror("mf.h() called without setting Mf_getc"); 

	ntrks = readheader();
	if ( ntrks <= 0 )
		mferror("No tracks!");
	while ( ntrks-- > 0 )
		readtrack();
}

static int
readmt(s,skip)		/* read through the "MThd" or "MTrk" header string */
char *s;
int skip;		/* if 1, we attempt to skip initial garbage. */
{
	int nread = 0;
	char b[4];
	char buff[32];
	int c;
	char *errmsg = "expecting ";

    retry:
	while ( nread<4 ) {
		c = (*Mf_getc)();
		if ( c == EOF ) {
			errmsg = "EOF while expecting ";
			goto err;
		}
		b[nread++] = c;
	}
	/* See if we found the 4 characters we're looking for */
	if ( s[0]==b[0] && s[1]==b[1] && s[2]==b[2] && s[3]==b[3] )
		return(0);
	if ( skip ) {
		/* If we are supposed to skip initial garbage, */
		/* try again with the next character. */
		b[0]=b[1];
		b[1]=b[2];
		b[2]=b[3];
		nread = 3;
		goto retry;
	}
    err:
	(VOID) strcpy(buff,errmsg);
	(VOID) strcat(buff,s);
	mferror(buff);
	return(0);
}

static int
egetc()			/* read a single character and abort on EOF */
{
	int c = (*Mf_getc)();

	if ( c == EOF )
		mferror("premature EOF");
	Mf_toberead--;
	return(c);
}

static int
readheader()		/* read a header chunk */
{
	int format, ntrks, division;

	if ( readmt("MThd",Mf_skipinit) == EOF )
		return(0);

	Mf_toberead = read32bit();
	format = read16bit();
	ntrks = read16bit();
	division = read16bit();

	if ( Mf_header )
		(*Mf_header)(format,ntrks,division);

	/* flush any extra stuff, in case the length of header is not 6 */
	while ( Mf_toberead > 0 )
		(VOID) egetc();
	return(ntrks);
}

static VOID
readtrack()		 /* read a track chunk */
{
	/* This array is indexed by the high half of a status byte.  It's */
	/* value is either the number of bytes needed (1 or 2) for a channel */
	/* message, or 0 (meaning it's not  a channel message). */
	static int chantype[] = {
		0, 0, 0, 0, 0, 0, 0, 0,		/* 0x00 through 0x70 */
		2, 2, 2, 2, 1, 1, 2, 0		/* 0x80 through 0xf0 */
	};
	long lookfor, lng;
	int c, c1, type;
	int sysexcontinue = 0;	/* 1 if last message was an unfinished sysex */
	int running = 0;	/* 1 when running status used */
	int status = 0;		/* (possibly running) status byte */
	int needed;

	if ( readmt("MTrk",0) == EOF )
		return;

	Mf_toberead = read32bit();
	Mf_currtime = 0;

	if ( Mf_starttrack )
		(*Mf_starttrack)();

	while ( Mf_toberead > 0 ) {

		Mf_currtime += readvarinum();	/* delta time */

		c = egetc();

		if ( sysexcontinue && c != 0xf7 )
			mferror("didn't find expected continuation of a sysex");

		if ( (c & 0x80) == 0 ) {	 /* running status? */
			if ( status == 0 )
				mferror("unexpected running status");
			running = 1;
		}
		else {
			status = c;
			running = 0;
		}

		needed = chantype[ (status>>4) & 0xf ];

		if ( needed ) {		/* ie. is it a channel message? */

			if ( running )
				c1 = c;
			else
				c1 = egetc();
			chanmessage( status, c1, (needed>1) ? egetc() : 0 );
			continue;;
		}

		switch ( c ) {

		case 0xff:			/* meta event */

			type = egetc();
			/* watch out - Don't combine the next 2 statements */
			lng = readvarinum();
			lookfor = Mf_toberead - lng;
			msginit();

			while ( Mf_toberead > lookfor )
				msgadd(egetc());

			metaevent(type);
			break;

		case 0xf0:		/* start of system exclusive */

			/* watch out - Don't combine the next 2 statements */
			lng = readvarinum();
			lookfor = Mf_toberead - lng;
			msginit();
			msgadd(0xf0);

			while ( Mf_toberead > lookfor )
				msgadd(c=egetc());

			if ( c==0xf7 || Mf_nomerge==0 )
				sysex();
			else
				sysexcontinue = 1;  /* merge into next msg */
			break;

		case 0xf7:	/* sysex continuation or arbitrary stuff */

			/* watch out - Don't combine the next 2 statements */
			lng = readvarinum();
			lookfor = Mf_toberead - lng;

			if ( ! sysexcontinue )
				msginit();

			while ( Mf_toberead > lookfor )
				msgadd(c=egetc());

			if ( ! sysexcontinue ) {
				if ( Mf_arbitrary )
					(*Mf_arbitrary)(msgleng(),msg());
			}
			else if ( c == 0xf7 ) {
				sysex();
				sysexcontinue = 0;
			}
			break;
		default:
			badbyte(c);
			break;
		}
	}
	if ( Mf_endtrack )
		(*Mf_endtrack)();
	return;
}

static VOID
badbyte(c)
int c;
{
	char buff[32];

	(VOID) sprintf(buff,"unexpected byte: 0x%02x",c);
	mferror(buff);
}

static VOID
metaevent(type)
{
	int leng = msgleng();
	char *m = msg();

	switch  ( type ) {
	case 0x00:
		if ( Mf_seqnum )
			(*Mf_seqnum)(to16bit(m[0],m[1]));
		break;
	case 0x01:	/* Text event */
	case 0x02:	/* Copyright notice */
	case 0x03:	/* Sequence/Track name */
	case 0x04:	/* Instrument name */
	case 0x05:	/* Lyric */
	case 0x06:	/* Marker */
	case 0x07:	/* Cue point */
	case 0x08:
	case 0x09:
	case 0x0a:
	case 0x0b:
	case 0x0c:
	case 0x0d:
	case 0x0e:
	case 0x0f:
		/* These are all text events */
		if ( Mf_text )
			(*Mf_text)(type,leng,m);
		break;
	case 0x2f:	/* End of Track */
		if ( Mf_eot )
			(*Mf_eot)();
		break;
	case 0x51:	/* Set tempo */
		if ( Mf_tempo )
			(*Mf_tempo)(to32bit(0,m[0],m[1],m[2]));
		break;
	case 0x54:
		if ( Mf_smpte )
			(*Mf_smpte)(m[0],m[1],m[2],m[3],m[4]);
		break;
	case 0x58:
		if ( Mf_timesig )
			(*Mf_timesig)(m[0],m[1],m[2],m[3]);
		break;
	case 0x59:
		if ( Mf_keysig )
			(*Mf_keysig)(m[0],m[1]);
		break;
	case 0x7f:
		if ( Mf_sqspecific )
			(*Mf_sqspecific)(leng,m);
		break;
	default:
		if ( Mf_metamisc )
			(*Mf_metamisc)(type,leng,m);
	}
}

static VOID
sysex()
{
	if ( Mf_sysex )
		(*Mf_sysex)(msgleng(),msg());
}

static VOID
chanmessage(status,c1,c2)
int status;
int c1, c2;
{
	int chan = status & 0xf;

	switch ( status & 0xf0 ) {
	case NOTEOFF:
		if ( Mf_off )
			(*Mf_off)(chan,c1,c2);
		break;
	case NOTEON:
		if ( Mf_on )
			(*Mf_on)(chan,c1,c2);
		break;
	case PRESSURE:
		if ( Mf_pressure )
			(*Mf_pressure)(chan,c1,c2);
		break;
	case CONTROLLER:
		if ( Mf_controller )
			(*Mf_controller)(chan,c1,c2);
		break;
	case PITCHBEND:
		if ( Mf_pitchbend )
			(*Mf_pitchbend)(chan,c1,c2);
		break;
	case PROGRAM:
		if ( Mf_program )
			(*Mf_program)(chan,c1);
		break;
	case CHANPRESSURE:
		if ( Mf_chanpressure )
			(*Mf_chanpressure)(chan,c1);
		break;
	}
}

/* readvarinum - read a varying-length number, and return the */
/* number of characters it took. */

static long
readvarinum()
{
	long value;
	int c;

	c = egetc();
	value = c;
	if ( c & 0x80 ) {
		value &= 0x7f;
		do {
			c = egetc();
			value = (value << 7) + (c & 0x7f);
		} while (c & 0x80);
	}
	return (value);
}

static long
to32bit(c1,c2,c3,c4)
{
	long value = 0L;

	value = (c1 & 0xff);
	value = (value<<8) + (c2 & 0xff);
	value = (value<<8) + (c3 & 0xff);
	value = (value<<8) + (c4 & 0xff);
	return (value);
}

static
to16bit(c1,c2)
int c1, c2;
{
	return ((c1 & 0xff ) << 8) + (c2 & 0xff);
}

static long
read32bit()
{
	int c1, c2, c3, c4;

	c1 = egetc();
	c2 = egetc();
	c3 = egetc();
	c4 = egetc();
	return to32bit(c1,c2,c3,c4);
}

static int
read16bit()
{
	int c1, c2;
	c1 = egetc();
	c2 = egetc();
	return to16bit(c1,c2);
}

static VOID
mferror(s)
char *s;
{
	if ( Mf_error )
		(*Mf_error)(s);
	exit(1);
}

/* The code below allows collection of a system exclusive message of */
/* arbitrary length.  The Msgbuff is expanded as necessary.  The only */
/* visible data/routines are msginit(), msgadd(), msg(), msgleng(). */

#define MSGINCREMENT 128
static char *Msgbuff = 0;	/* message buffer */
static int Msgsize = 0;		/* Size of currently allocated Msg */
static int Msgindex = 0;	/* index of next available location in Msg */

static VOID
msginit()
{
	Msgindex = 0;
}

static char *
msg()
{
	return(Msgbuff);
}

static int
msgleng()
{
	return(Msgindex);
}

static VOID
msgadd(c)
int c;
{
	/* If necessary, allocate larger message buffer. */
	if ( Msgindex >= Msgsize )
		msgenlarge();
	Msgbuff[Msgindex++] = c;
}

static VOID
msgenlarge()
{
	char *newmess;
	char *oldmess = Msgbuff;
	int oldleng = Msgsize;
	char *malloc();

	Msgsize += MSGINCREMENT;
	newmess = malloc( (unsigned)(sizeof(char)*Msgsize) );

	/* copy old message into larger new one */
	if ( oldmess != 0 ) {
		register char *p = newmess;
		register char *q = oldmess;
		register char *endq = &oldmess[oldleng];

		for ( ; q!=endq ; p++,q++ )
			*p = *q;
		free(oldmess);
	}
	Msgbuff = newmess;
}
SHAR_EOF
if test 10990 -ne "`wc -c < 'midifile.c'`"
then
	echo shar: error transmitting "'midifile.c'" '(should have been 10990 characters)'
fi
fi # end of overwriting check
echo shar: extracting "'midifile.h'" '(707 characters)'
if test -f 'midifile.h'
then
	echo shar: will not over-write existing file "'midifile.h'"
else
cat << \SHAR_EOF > 'midifile.h'
extern int (*Mf_getc)();
extern int (*Mf_header)();
extern int (*Mf_starttrack)();
extern int (*Mf_endtrack)();
extern int (*Mf_on)();
extern int (*Mf_off)();
extern int (*Mf_pressure)();
extern int (*Mf_controller)();
extern int (*Mf_pitchbend)();
extern int (*Mf_program)();
extern int (*Mf_chanpressure)();
extern int (*Mf_sysex)();
extern int (*Mf_metamisc)();
extern int (*Mf_sqspecific)();
extern int (*Mf_seqnum)();
extern int (*Mf_text)();
extern int (*Mf_eot)();
extern int (*Mf_timesig)();
extern int (*Mf_smpte)();
extern int (*Mf_tempo)();
extern int (*Mf_keysig)();
extern int (*Mf_arbitrary)();
extern int (*Mf_error)();
extern long Mf_currtime;
extern int Mf_nomerge;
extern int Mf_skipinit;
SHAR_EOF
if test 707 -ne "`wc -c < 'midifile.h'`"
then
	echo shar: error transmitting "'midifile.h'" '(should have been 707 characters)'
fi
fi # end of overwriting check
#	End of shell archive
exit 0


