This is midifile.c in view mode; [Download] [Up]
/*
* General purpose routines for parsing and writing standard Midifiles
* (level 0 and 1)
* written by Lee Boynton 5-89
*
* modified by dana c. massie 6-89
* modified by david jaffe 12-89 (bug fix in writing level-1 files)
* modified by brian willoughby 2-92 (support for multiple Tempo changes)
* & (bug fixes marked by 'bdw:')
* & (various informational comments)
*/
#import <stdio.h> // bdw: DEBUG
#import <strings.h>
#import "midifile.h"
#define DEFAULT_QUANTASIZE (1000)
#define DEFAULT_TEMPO (120.0) // bdw: was 96.0 (?)
static int quantasize = DEFAULT_QUANTASIZE; //size of quanta in microseconds
/*
* reading
*/
static double read_tempo = DEFAULT_TEMPO; // in beats per minute
static double read_time_scale;
static current_read_track = -1;
static current_read_time = 0;
static current_offset = 0; // bdw: added to support multiple tempo changes
static read_division = 0;
static int read_format = 0;
static int midievent_size[] = { 3, 3, 3, 3, 2, 2, 3, 0 }; // bdw
static int calc_midievent_size(int status)
// bdw: Calculate midiEvent size including the status byte
{
return midievent_size[7 & status >> 4];
}
static int readChunkType(NXStream *s, char *buf)
{
int count = NXRead(s, buf, 4);
buf[4] = '\0';
return (count == 4) ? 1 : 0;
}
static int readLong(NXStream *s, int *n)
{
int count = NXRead(s, n, 4);
return (count == 4) ? 1 : 0;
}
static int readShort(NXStream *s, short *n)
{
int count = NXRead(s, n, 2);
return (count == 2) ? 1 : 0;
}
static int readVariableQuantity(NXStream *s, int *n)
{
int m = 0;
unsigned char temp;
while (NXRead(s, &temp, 1) > 0)
{
if (128 & temp)
m = (m << 7) + (temp & 127);
else
{
*n = (m << 7) + (temp & 127);
return 1;
}
}
return 0;
}
static int readTrackHeader(NXStream *s, midievent_t *event)
// return 0 on EOS or if Track Chunk Type is wrong, return 1 otherwise
{
char typebuf[8];
int size;
if (!readChunkType(s, typebuf))
return 0;
if (strcmp(typebuf, "MTrk"))
return 0;
current_read_track++;
current_read_time = 0;
current_offset = 0; // bdw
if (!readLong(s, &size)) // NOTE: save size in global
return 0;
if (event)
{
event->metaevent = 1;
event->ndata = 3;
event->data[0] = MIDI_METAEVENT_TRACK_CHANGE;
event->data[1] = (current_read_track >> 8) & 0x7f;
event->data[2] = current_read_track & 0xff;
}
return 1;
}
#define SCALE_QUANTA(quanta) ((int)(0.5 + (read_time_scale * (double)quanta)))
static int readMetaevent(NXStream *s, midievent_t *event)
// return 0 when EOS is reached or if Track Chunk Type is wrong,
// return -1 if Set Tempo length is wrong or if unrecognized Meta Event,
// return 1 otherwise
{
unsigned char theByte;
if (!NXRead(s, &theByte, 1))
return 0;
if (theByte == 0x2F)
{ // end of track
NXRead(s, &theByte, 1);
return readTrackHeader(s, event); // NOTE: end of track not last?
}
else if (theByte == 0x51)
{ // tempo
int temp;
if (!readLong(s, &temp))
return 0;
if ((temp >> 24) == 3) // check the length. NOTE: allow >= 3 ?
{
double n = (double)(temp & 0x0ffffff);
read_tempo = 60000000.0 / n; // convert to BPM
// bdw: save current offset and reset current_read_time
current_offset += SCALE_QUANTA(current_read_time);
current_read_time = 0;
read_time_scale = n / (double)(read_division * quantasize);
event->metaevent = 1;
event->ndata = 3;
event->data[0] = MIDI_METAEVENT_TEMPO_CHANGE;
event->data[1] = ((int)read_tempo >> 8) & 0x7f;
event->data[2] = (int)read_tempo & 0x0ff;
return 1;
}
else // NOTE: file pointer is wrong at this point!
return -1;
}
else
{
int n;
readVariableQuantity(s, &n);
NXSeek(s, n, NX_FROMCURRENT);
}
return -1;
}
/*
* Exported routines
*/
int MIDIFileReadPreamble(NXStream *s, int *level, int *track_count)
{
char typebuf[8];
int size, err;
short fmt, tracks, div;
if (!readChunkType(s, typebuf))
return 0;
if (strcmp(typebuf, "MThd"))
return 0; // not a midifile
if (!readLong(s, &size))
return 0;
if (size != 6) // NOTE: could support >= 6
return 0; // bad header size
if (!readShort(s, &fmt))
return 0;
if (fmt < 0 || fmt > 1)
return 0; // must be level 0 or level 1
if (!readShort(s, &tracks))
return 0;
if (!readShort(s, &div))
return 0;
*track_count = fmt ? tracks - 1 : 1;
*level = read_format = fmt;
read_tempo = 120.0;
if (0x8000 & div)
return 0; // no SMPTE support
read_division = div;
current_read_track = -1;
// bdw: previously used incorrect constant of 60000000.0
read_time_scale = 500000.0 / (double)(read_division * quantasize);
return 1;
}
int MIDIFileReadEvent(NXStream *s, midievent_t *event)
// return 0 when EOS is reached, return 1 otherwise
{
// NOTE: global running_status for MIDIFileReadPreamble() & readTrackHeader()
static unsigned char running_status, the_byte;
static last_time_printed = 0;
int delta_time;
/*
* The following statement will only call readTrackHeader() if it has not
* been called at least once since MIDIFileReadPreamble()
*/
if (current_read_track < 0 && !readTrackHeader(s, 0))
return 0;
while (1)
{
int quanta_time;
if (!readVariableQuantity(s, &delta_time))
return 0;
current_read_time += delta_time;
quanta_time = current_offset + SCALE_QUANTA(current_read_time);
if (!NXRead(s, &the_byte, 1))
return 0;
event->metaevent = 0;
if (the_byte < 0x0f0)
{ // handle channel events
int data = 0;
if (the_byte & 128)
running_status = the_byte;
else
data = 1;
event->quanta = quanta_time;
event->ndata = midievent_size[7 & running_status >> 4];
event->data[0] = running_status;
if (event->ndata > 1)
{
if (data)
event->data[1] = the_byte;
else if (!NXRead(s, &event->data[1], 1))
return 0;
if (event->ndata > 2)
if (!NXRead(s, &event->data[2], 1))
return 0;
}
return 1;
}
else
{ // handle non-channel events
if (the_byte == 255)
{
int temp = readMetaevent(s, event);
if (temp >= 0)
{
if (temp)
event->quanta = quanta_time;
return temp;
}
// else if unrecognized Meta Event, then get the next event
}
else if ((0x0f0 == the_byte) || (0x0f7 == the_byte))
{ // bdw: handle System Exclusive Events
int n;
if (!readVariableQuantity(s, &n))
return 0;
NXSeek(s, n, NX_FROMCURRENT);
}
else // invalid event
return 0;
}
} // while
}
/*
* writing
*/
static double write_tempo = DEFAULT_TEMPO; // in beats per minute
static double write_time_scale;
static current_write_track = -1;
static int last_write_time = 0;
static write_division = 0;
static int write_format = 0;
static int current_write_count = 0;
static int writeByte(NXStream *s, unsigned char n)
{
int foo = NXWrite(s, &n, 1);
current_write_count += foo;
return foo;
}
static int writeShort(NXStream *s, short n)
{
int foo = NXWrite(s, &n, 2);
current_write_count += foo;
return (foo == 2) ? 1 : 0;
}
static int writeLong(NXStream *s, int n)
{
int foo = NXWrite(s, &n, 4);
current_write_count += foo;
return (foo == 4) ? 1 : 0;
}
static int writeChunkType(NXStream *s, char *buf)
{
int foo = NXWrite(s, buf, 4);
current_write_count += foo;
return (foo == 4)? 1 : 0;
}
static int writeText(NXStream *s, char *buf)
{
int size = strlen(buf);
int foo = NXWrite(s, buf, size);
current_write_count += foo;
return (foo == size)? 1 : 0;
}
static int writeVariableQuantity(NXStream *s, int n)
{
// if (n >= (1 << 28) && !writeByte(s, (((n>>28)&15)|128) ))
// return 0;
// bdw: the above case is never possible! See MMA Standard MIDI File spec.
if (n >= (1 << 21) && !writeByte(s, (((n>>21)&127)|128) ))
return 0;
if (n >= (1 << 14) && !writeByte(s, (((n>>14)&127)|128) ))
return 0;
if (n >= (1 << 7) && !writeByte(s, (((n>>7)&127)|128) ))
return 0;
return writeByte(s, (n&127));
}
int MIDIFileBeginWriting(NXStream *s, int level, char *sequenceName)
{
short lev = level, div = 1024, ntracks = 1;
if (!writeChunkType(s, "MThd"))
return 0;
if (!writeLong(s, 6))
return 0;
if (!writeShort(s, lev))
return 0;
if (!writeShort(s, ntracks))
return 0;
if (!writeShort(s, div))
return 0;
write_tempo = 120.0;
write_division = div;
current_write_track = -1;
// bdw: previously used incorrect constant of 60000000.0
// and the expression was inverted
write_time_scale = (double)(write_division * quantasize) / 500000.0;
return MIDIFileBeginWritingTrack(s, sequenceName);
}
int MIDIFileEndWriting(NXStream *s)
{
short ntracks = current_write_track + 1;
//NOTE: how is the first track of a legal format 0 file Ended?
if (!current_write_track)
{
int foo = MIDIFileEndWritingTrack(s);
if (!foo)
return 0;
}
NXSeek(s, 10, NX_FROMSTART);
if (NXWrite(s, &ntracks, 2) != 2)
return 0;
NXSeek(s, 0, NX_FROMEND); // NOTE: what if a larger file is overwritten?
return 1;
}
int MIDIFileBeginWritingTrack(NXStream *s, char *trackName)
{
if (!current_write_track)
MIDIFileEndWritingTrack(s);
if (!writeChunkType(s, "MTrk"))
return 0;
if (!writeLong(s, 0)) // save space in file for track length
return 0;
current_write_count = 0;
current_write_track++;
last_write_time = 0;
if (trackName)
{
int i = strlen(trackName);
if (i)
{
if (!writeByte(s, 0)) // delta time
return 0;
if (!writeByte(s, 0xff)) // Meta Event
return 0;
if (!writeByte(s, 0x03)) // Sequence/Track Name
return 0;
if (!writeVariableQuantity(s, i))
return 0;
// NOTE: the following could use writeText(),
// but the strlen() calculation would be duplicated
current_write_count += i; // daj Added this line
if (!NXWrite(s, trackName, i))
return 0;
}
}
return 1;
}
int MIDIFileEndWritingTrack(NXStream *s)
{
if (!writeLong(s, 0x00ff2f00)) // Meta Event: End of Track
return 0;
NXSeek(s, -(current_write_count + 4), NX_FROMCURRENT);
if (NXWrite(s, ¤t_write_count, 4) != 4)
return 0;
NXSeek(s, current_write_count, NX_FROMCURRENT);
return 1;
}
int MIDIFileWriteTempo(NXStream *s, int beats_per_minute)
{
int n;
write_tempo = beats_per_minute;
n = (int)(0.5 + (60000000.0 / write_tempo));
write_time_scale = (double)(write_division * quantasize) / (double)n;
n &= 0x00ffffff; // NOTE: check for lost resolution?
n |= 0x03000000; // length
if (!writeByte(s, 0))
return 0;
if (!writeShort(s, 0xff51)) // Meta Event: Set Tempo
return 0;
if (!writeLong(s, n))
return 0;
return 1;
}
int MIDIFileWriteEvent(NXStream *s, midievent_t *event)
{
int i, this_time = (int)(0.5 + (write_time_scale * event->quanta));
int delta_time = this_time - last_write_time;
last_write_time = this_time;
if (!writeVariableQuantity(s, delta_time))
return 0;
for (i = 0; i < event->ndata; i++)
if (!writeByte(s, event->data[i]))
return 0;
return 1;
}
/*
* Generic routines
*/
int MIDISetQuantaSize(int usec)
{
quantasize = usec;
if (read_division)
{
read_time_scale = 60000000.0
/ (double)(read_division * read_tempo * quantasize);
}
if (write_division)
{
// bdw: was previously inverted
write_time_scale = (double)(write_division * write_tempo * quantasize)
/ 60000000.0;
}
return usec;
}
int MIDIGetQuantaSize()
{
return quantasize;
}
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.