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.