ftp.nice.ch/pub/next/unix/music/NewMidiDrv.s.tar.gz#/NewMidiDriver/midifile.c

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, &current_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.