Blob Blame History Raw
mfread,mfwrite \- read and write a standard MIDI file
\fC#include "mfread.h"

mfread ()

int (*Mf_getc) ();
int (*Mf_putc) ();
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;
.sp 1
mfwrite(int format, int ntracks, int division, FILE *fp)
.sp 1
int (*Mf_writetrack)(int track);
int (*Mf_writetempotrack)();

void mf_write_midi_event(delta, type, chan, data, size)
unsigned long delta;
unsigned int type,chan,size;
char *data;

void mf_write_meta_event(delta, type, data, size)
unsigned long delta;
unsigned int type,chan,size;
char *data;

void mf_write_tempo(tempo)
unsigned long tempo;

unsigned long mf_sec2ticks(float seconds, int division, int tempo)
float seconds;
int division;
unsigned int tempo;

float mf_ticks2sec(ticks, division, tempo)
unsigned long ticks;
int division;
unsigned int tempo;

The \fCmfread\fR function reads and interprets 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.

The \fCmfwrite\fR function writes a standard MIDI file making
use of user-defined functions that access the program's
data structure.  To use it you need to define your own Mf_writetrack
routine and then make use of the write_* family of routines to
write out the MIDI data.  The \fCmfwrite\fR routine takes
care of the file format and writing the file and track chunk headers. 

A single call to \fCmfread\fR will read an entire MIDI file.
The interface to \fCmfread\fR is a set of external variables
named \fCMf_*\fR, most of which are function pointers to be called
from within \fCmfread\fR during the process of parsing the MIDI file.
Before calling \fCmfread\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 \fCmfread\fR
that could serve as a 'syntax checker' for MIDI files:

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

	/* use standard input */

	Mf_getc = mygetc;
.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 -
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

.in +1i
0x01		Text Event
0x02		Copyright
0x03		Sequence/Track Name
0x04		Instrument
0x05		Lyric
0x06		Marker
0x07		Cue Point
0x08-0x0F	Reserved but Undefined
.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.

Miscellaneous \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
The following is a \fCstrings\fR-like program for MIDI files:

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


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

char *msg;
	char *p;
	char *ep = msg + leng;

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

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

	Mf_getc = mygetc;
	Mf_text = mytext;


.ft R
.in -1i
A single call to \fCmfwrite\fR will write an entire MIDI file.  Before
calling \fCmfwrite\fR, you must assign values to function pointers
\fCMf_writetrack\fR and \fCMf_putc\fR.  The first is a routine to
access your MIDI data structure, which can make use of other library
routines to write the actual MIDI data.  The routine
\fCMf_writetrack\fR will be passed a single parameter which is the
number of the track to be written.  The pointer \fCMf_putc\fR should be
set to point to a routine that accepts a character as input, writes that
character to a file, and returns the value that was written.  In the
case of a format 1 file, a routine has to be written to write a tempo
map, and assigned to the function pointer \fCMf_writetempotrack\fR.
This is because format 1 files assume the first track written is a
tempo track.

\fCmf_write_midi_event\fR and \fCmf_write_meta_event\fR are routines
that should be called from your \fCMf_writetrack\fR routine to write
out MIDI events.  The delta time param is the number of ticks since the
last event.  The int "type" is the type of MIDI message. The int "chan"
is the MIDI channel, which can be between 1 and 16.  The char pointer
"data" points to an array containing the data bytes, if any exist. The
int "size" is the number of data bytes.

\fCmf_sec2ticks\fR and \fCmf_ticks2sec\fR are utility routines
to help you convert between the MIDI file parameter of ticks
and the more standard seconds. The int "division" is the same
division parameter from the file header, and tempo is expressed
in microseconds per MIDI quarter-note, or "24ths of a microsecond
per MIDI clock". The division has two meanings, depending on
whether bit 15 is set or not.  If bit 15 of division is zero,
bits 14 through 0 represent the number of delta-time "ticks"
which make up a quarter note.  If bit 15 of division is a one,
delta-times in a file correspond to subdivisions of a second
similar to SMPTE and MIDI time code. In this format bits
14 through 8 contain one of four values \-24, \-25, \-29, or \-30,
corresponding to the four standard SMPTE and MIDI time code
frame per second formats, where \-29 represents 30 drop frame.
The second byte consisting of bits 7 through 0 corresponds
the the resolution within a frame.  Refer the Standard MIDI Files 
1.0 spec for more details.

The following is a simple program to demonstrate writing MIDI files.
The track would consist of a series of quarter notes from lowest to
highest in pitch at constant velocity, each separated by a quarter-note
.in +1i
.ft C
#include <stdio.h>
#include <ctype.h>
#include "midifile.h"

FILE *fp;
myputc(c) { return(putc(c,fp));}

int mywritetrack(track)
int track;
    int i;
    char data[2];

    /* 120 beats/per/second */

    for(i = 1 ; i < 128; i++){
       data[0] = i; /* note number */
       data[1] = 64; /* velocity */

} /* end of write_track() */

char **argv;
    if((fp = fopen(argv[1],"w")) == 0L)

    Mf_putc = myputc;
    Mf_writetrack = mywritetrack;

    /* write a single track */
.ft R
.in -1i
Tim Thompson (att!twitch!glimmer!tjt)
Michael Czeiszperger (