home *** CD-ROM | disk | FTP | other *** search
- /* Based on original version written by Lee Boynton, revised by David Jaffe.
- */
-
- #import <appkit/nextstd.h>
- #import <mididriver/midi_spec.h>
- #import "midifile.h"
-
- /* Some metaevents */
- #define SEQUENCENUMBER 0
- #define TRACKCHANGE 0x2f
- #define TEMPOCHANGE 0x51
- #define SMPTEOFFSET 0x54
- #define TIMESIG 0x58
- #define KEYSIG 0x59
-
- #define DEFAULTTEMPO (120.0)
- #define DEFAULTDIVISION 1024
-
- /*
- * reading
- */
-
- typedef struct _MIDIFILEInStruct { /* Private structure for this module. */
- double tempo; /* in quarter notes per minute */
- double timeScale;/* timeScale * currentTime gives time in seconds */
- int currentTrack;/* Current track number */
- int currentTime; /* Current time in quanta. */
- int division; /* # of delta-time ticks per quarter. (See spec) */
- short format; /* Level 0, 1 or 2 */
- int quantaSize; /* In micro-seconds. */
- unsigned char runningStatus; /* Current MIDI running status */
- NXStream *s; /* midifile stream */
- int curBufSize; /* Size of data buffer */
- MIDIFILEReadStruct *m; /* Info for current event */
- } MIDIFILEInStruct;
-
- void *MIDIFILEBeginReading(NXStream *s,MIDIFILEReadStruct *m)
- {
- MIDIFILEInStruct *rtn;
- NX_MALLOC(rtn,MIDIFILEInStruct,1);
- rtn->tempo = DEFAULTTEMPO; /* in beats per minute */
- rtn->currentTrack = -1; /* We call the first or "tempo track"
- "track 0". Therefore, we start counting at -1
- here. */
- rtn->currentTime = 0;
- rtn->division = 0;
- rtBS"ormat = 0;
- rtn->quantaSize = MIDIFILE_DEFAULTQUANTASIZE; /* size in microseconds */
- rtn->s = s;
- /* Malloc enough for SMPTEoffset metaevent. Realloc longer fields later */
- rtn->curBufSize = 6;
- rtn->m = m;
- NX_MALLOC(rtn->m->data,unsigned char,rtn->curBufSize);
- /* Values are always returned indirectly in these fields. */
- return rtn;
- }
-
- #define IP ((MIDIFILEInStruct *)p)
-
- void *MIDIFILEEndReading(void *p)
- {
- NX_FREE(IP->m->data);
- NX_FREE(IP);
- return NULL;
- }
-
- enum {unrecognized = -1,endOfStream = 0,ok = 1,undefined,
- /* Multi-packet sys excl: */
- firstISysExcl,middleISysExcl,endISysExcl,
- /* Single-packet sys excl */
- sysExcl};
-
- static int readChunkType(NXStream *s,char *buf)
- {
- int count = NXRead(s,buf,4);
- buf[4] = '\0';
- return (count == 4)? ok : 0;
- }
-
- static int readLong(NXStream *s, int *n)
- {
- int count = NXRead(s,n,4);
- return (count == 4)? ok : 0;
- }
-
- static int readBytes(NXStream *s, unsigned char *bytes,int n)
- {
- int count = NXRead(s,bytes,n);
- return (count == n) ? ok : 0;
- }
-
- static int readShort(NXStream *s, short *n)
- {
- int count = NXRead(s,n,2);
- return (count == 2)? ok : 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 ok;
- }
- }
- return endOfStream;
- }
-
- static int readTrackHeader(MIDIFILEInStruct *p)
- {
- char typebuf[8];
- int size;
- if (!readChunkType(p->s,typebuf))
- return endOfStream;
- if (strcmp(typebuf,"MTrk"))
- return endOfStream;
- p->currentTrack++;
- p->currentTime = 0;
- if (!readLong(p->s,&size))
- return endOfStream;
- return ok;
- }
-
- static void checkRealloc(MIDIFILEInStruct *p,int newSize)
- {
- if (p->curBufSize < newSize)
- NX_REALLOC(p->m->data,unsigned char,newSize);
- p->curBufSize = newSize;
- }
-
- static int readMetaevent(MIDIFILEInStruct *p)
- {
- unsigned char theByte;
- int temp;
- int len;
- if (!(NXRead(p->s,&theByte,1) && readVariableQuantity(p->s,&len)))
- return endOfStream;
- if (theByte == SEQUENCENUMBER) {
- short val;
- if (!readShort(p->s,&val))
- return endOfStream;
- p->m->data[0] = MIDIFILE_sequenceNumber;
- p->m->data[1] = val >BS#
- p->m->data[2] = val;
- len -= 2;
- p->m->nData = 3;
- }
- else if (theByte >= 1 && theByte <= 0x0f) { /* Text meta events */
- p->m->data[0] = theByte;
- p->m->nData = len + 1; /* nData doesn't include the \0 */
- checkRealloc(p,p->m->nData + 1);
- if (!readBytes(p->s,&(p->m->data[1]),p->m->nData - 1))
- return endOfStream;
- p->m->data[p->m->nData] = '\0';
- return ok;
- }
- else if (theByte == TRACKCHANGE) { /* end of track */
- temp = readTrackHeader(p);
- if (temp == endOfStream)
- return endOfStream;
- /* trackChange doesn't have any args but we pass up the track number,
- so no len -= needed.
- */
- p->m->nData = 3;
- p->m->data[0] = MIDIFILE_trackChange;
- p->m->data[1] = (p->currentTrack >> 8);
- p->m->data[2] = p->currentTrack;
- }
- else if (theByte == TEMPOCHANGE) { /* tempo */
- double n;
- int i;
- if (!readBytes(p->s,&(p->m->data[1]),3)) /* 24 bits */
- return endOfStream;
- i = (p->m->data[1] << 16) | (p->m->data[2] << 8) | p->m->data[3];
- n = (double)i;
- /* tempo in file is in micro-seconds per quarter note */
- p->tempo = 60000000.0 / n + 0.5;
- i = p->tempo;
- /* division is the number of delta time "ticks" that make up a
- quarter note. Quanta size is in micro seconds. */
- p->timeScale = n / (double)(p->division * p->quantaSize);
- p->m->data[0] = MIDIFILE_tempoChange;
- /* It's a 3 byte quantity but we store it in 4 bytes.*/
- p->m->data[1] = 0;
- p->m->data[2] = (i >> 16);
- p->m->data[3] = (i >> 8);
- p->m->data[4] = i;
- p->m->nData = 5;
- len -= 3;
- }
- else if (theByte == SMPTEOFFSET) {
- p->m->data[0] = MIDIFILE_smpteOffset;
- if (!readBytes(p->s,&(p->m->data[1]),5))
- return endOfStream;
- p->m->nData = 6;
- len -= 5;
- }
- else if (theByte == TIMESIG) {
- if (!readBytes(p->s,&p->m->data[1],4))
- return endOfStream;
- p->m->data[0] = MIDIFILE_timeSig;
- p->m->nData = 5;
- len -= 4;
- }
- else if (theByte == KEYSIG) {
- if (!readBytes(p->s,&p->m->data[1],2))
- return endOfStream;
- p->m->data[0] = MIDIFILE_keySig;
- p->m->nData = 3;
- len -= 2;
- }
- else { /* Skip unrecognized meta events */
- if (!readVariableQuantity(p->s,&temp))
- return endOfStream;
- NXSeek(p->s,temp,NX_FROMCURRENT);
- return unrecognized;
- }
- NXSeek(p->s,len,NX_FROMCURRENT); /* Skip any extra length in field. */
- return ok;
- }
-
- /* We do not support multi-packet system excBS$ve messages with different
- timings. When such a beast occurs, it is concatenated into a single
- event and the time stamp is that of the last piece of the event. */
-
- static int readSysExclEvent(MIDIFILEInStruct *p,int oldState)
- {
- int len;
- unsigned char *ptr;
- if (!readVariableQuantity(p->s,&len))
- return endOfStream;
- if (oldState == undefined) {
- checkRealloc(p,len + 1); /* len doesn't include data[0] */
- p->m->data[0] = MIDI_SYSEXCL;
- p->m->nData = len + 1;
- ptr = &(p->m->data[1]);
- } else { /* firstISysExcl or middleISysExcl */
- checkRealloc(p,len + p->m->nData);
- ptr = &(p->m->data[p->m->nData]);
- p->m->nData += len;
- }
- if (readBytes(p->s,ptr,len) == endOfStream)
- return endOfStream;
- return ((p->m->data[p->m->nData - 1] == MIDI_EOX) ?
- ((oldState == undefined) ? sysExcl : endISysExcl) :
- ((oldState == undefined) ? firstISysExcl : middleISysExcl));
- }
-
- static int readEscapeEvent(MIDIFILEInStruct *p)
- {
- if (!readVariableQuantity(p->s,&(p->m->nData)))
- return endOfStream;
- checkRealloc(p,p->m->nData);
- return readBytes(p->s,p->m->data, p->m->nData);
- }
-
- #define SCALEQUANTA(_p,quanta) \
- ((int)(0.5 + (((MIDIFILEInStruct *)_p)->timeScale * (double)quanta)))
-
- /*
- * Exported routines
- */
-
- int MIDIFILEReadPreamble(void *p,int *level,int *trackCount)
- {
- char typebuf[8];
- int size;
- short fmt, tracks, div;
-
- if ((!readChunkType(IP->s,typebuf)) ||
- (strcmp(typebuf,"MThd")) || /* not a MIDIFILE */
- (!readLong(IP->s,&size)) ||
- (size < 6) || /* bad header */
- (!readShort(IP->s,&fmt)) ||
- (fmt < 0 || fmt > 2) || /* must be level 0, 1 or 2 */
- (!readShort(IP->s,&tracks)) ||
- (!readShort(IP->s,&div)))
- return endOfStream;
- size -= 6;
- if (size)
- NXSeek(IP->s,size,NX_FROMCURRENT); /* Skip any extra length in field. */
- *trackCount = fmt ? tracks-1 : 1;
- *level = IP->format = fmt;
- if (div < 0) { /* Time code encoding? */
- /* For now, we undo the effect of the time code. We may want to
- eventually pass the time code up? */
- short SMPTEformat,ticksPerFrame;
- ticksPerFrame = div & 0xff;
- SMPTEformat = -(div >> 8);
- /* SMPTEformat is one of 24, 25, 29, or 30. It's stored negative */
- div = ticksPerFrame * SMPTEformat;
- }
- IP->division = div;
- IP->currentTrack = -1;
- IP->BS%Scale = 60000000.0 / (double)(IP->division * IP->quantaSize);
- return ok;
- }
-
- int MIDIFILEReadEvent(register void *p)
- /* return endOfStream when EOS is reached, return 1 otherwise.
- Data should be an array of length 3. */
- {
- int deltaTime,quantaTime,state = undefined;
- unsigned char theByte;
- if (IP->currentTrack < 0 && !readTrackHeader(p))
- return endOfStream;
- for (;;) {
- if (!readVariableQuantity(IP->s,&deltaTime))
- return endOfStream;
- IP->currentTime += deltaTime;
- quantaTime = SCALEQUANTA(p,IP->currentTime);
- if (!NXRead(IP->s,&theByte,1))
- return endOfStream;
- if (theByte == 0xff) {
- state = readMetaevent(p);
- IP->m->metaEventFlag = YES;
- if (state != unrecognized) {
- IP->m->quanta = quantaTime;
- return state;
- }
- } else if ((theByte == MIDI_SYSEXCL) || (state != undefined)) {
- /* System exclusive */
- state = readSysExclEvent(p,state);
- IP->m->metaEventFlag = NO;
- switch (state) {
- case firstISysExcl:
- IP->m->quanta = quantaTime;
- break;
- case middleISysExcl:
- IP->m->quanta += quantaTime;
- break;
- case endISysExcl:
- IP->m->quanta += quantaTime;
- return ok;
- case endOfStream:
- case sysExcl:
- IP->m->quanta = quantaTime;
- return ok;
- default:
- break;
- }
- } else if (theByte == 0xf7) { /* Special "escape" code */
- IP->m->quanta = quantaTime;
- return readEscapeEvent(p);
- } else { /* Normal MIDI */
- BOOL newRunningStatus = (theByte & MIDI_STATUSBIT);
- if (newRunningStatus)
- IP->runningStatus = theByte;
- IP->m->metaEventFlag = 0;
- IP->m->quanta = quantaTime;
- IP->m->nData = MIDI_EVENTSIZE(IP->runningStatus);
- IP->m->data[0] = IP->runningStatus;
- if (IP->m->nData > 1) {
- if (newRunningStatus) {
- if (!NXRead(IP->s,&(IP->m->data[1]),1))
- return endOfStream;
- }
- else IP->m->data[1] = theByte;
- if (IP->m->nData > 2)
- if (!NXRead(IP->s,&(IP->m->data[2]),1))
- return endOfStream;
- }
- return ok;
- }
- }
- }
-
-
- /*
- * writing
- */
-
- typedef struct _MIDIFILEOutStruct {
- double tempo;
- double timeScale;
- int currentTrack;
- int division;
- int currentCount;
- int lastTime;
- NXStream *s;
- int quantaSize;
- } MIDIFILEOutStruct;
-
- #define OP ((MIDIFILEOutStruct *)p)
-
- static int writeBytes(MIDIFILEOutStruct *p, uBS&ned char *bytes,int count)
- {
- int bytesWritten;
- bytesWritten = NXWrite(p->s,bytes,count);
- OP->currentCount += count;
- if (bytesWritten != count)
- return endOfStream;
- else return ok;
- }
-
- static int writeByte(MIDIFILEOutStruct *p, unsigned char n)
- {
- int bytesWritten = NXWrite(p->s,&n,1);
- p->currentCount += bytesWritten;
- return bytesWritten;
- }
-
- static int writeShort(MIDIFILEOutStruct *p, short n)
- {
- int bytesWritten = NXWrite(p->s,&n,2);
- p->currentCount += bytesWritten;
- return (bytesWritten == 2) ? ok : endOfStream;
- }
-
- static int writeLong(MIDIFILEOutStruct *p, int n)
- {
- int bytesWritten = NXWrite(p->s,&n,4);
- p->currentCount += bytesWritten;
- return (bytesWritten == 4) ? ok : endOfStream;
- }
-
- static int writeChunkType(MIDIFILEOutStruct *p, char *buf)
- {
- int bytesWritten = NXWrite(p->s,buf,4);
- p->currentCount += bytesWritten;
- return (bytesWritten == 4) ? ok : endOfStream;
- }
-
- static int writeVariableQuantity(MIDIFILEOutStruct *p, int n)
- {
- if ((n >= (1 << 28) && !writeByte(p,(((n>>28)&15)|128) )) ||
- (n >= (1 << 21) && !writeByte(p,(((n>>21)&127)|128) )) ||
- (n >= (1 << 14) && !writeByte(p,(((n>>14)&127)|128) )) ||
- (n >= (1 << 7) && !writeByte(p,(((n>>7)&127)|128) )))
- return endOfStream;
- return writeByte(p,(n&127));
- }
-
- void *MIDIFILEBeginWriting(NXStream *s, int level, char *sequenceName)
- {
- short lev = level, div = DEFAULTDIVISION, ntracks = 1;
- MIDIFILEOutStruct *p;
- NX_MALLOC(p,MIDIFILEOutStruct,1);
- OP->tempo = DEFAULTTEMPO; /* in beats per minute */
- OP->quantaSize = MIDIFILE_DEFAULTQUANTASIZE; /* size in microseconds */
- OP->lastTime = 0;
- OP->s = s;
- if ((!writeChunkType(p,"MThd")) || (!writeLong(p,6)) ||
- !writeShort(p,lev) || !writeShort(p,ntracks) || !writeShort(p,div))
- return endOfStream;
- OP->division = div;
- OP->currentTrack = -1;
- OP->timeScale = 60000000.0 / (double)(OP->division * OP->quantaSize);
- OP->currentCount = 0;
- if (MIDIFILEBeginWritingTrack(p,sequenceName))
- return p;
- else {
- NX_FREE(p);
- return NULL;
- }
- }
-
- int MIDIFILEEndWriting(void *p)
- {
- short ntracks = OP->currentTrack+1; /* +1 for "tempo track" */
- if (OP->currentCount) { /* Did we forget to finish before? */
- int err = MIDIFILEEndWritingTrack(p,0);
- if (err == endOfStream) {
- NX_BS'(p);
- return endOfStream;
- }
- }
- NXSeek(OP->s,10,NX_FROMSTART);
- if (NXWrite(OP->s,&ntracks,2) != 2) {
- NX_FREE(p);
- return endOfStream;
- }
- NXSeek(OP->s,0,NX_FROMEND);
- NX_FREE(p);
- return ok;
- }
-
- int MIDIFILEBeginWritingTrack(void *p, char *trackName)
- {
- if (OP->currentCount) /* Did we forget to finish before? */
- MIDIFILEEndWritingTrack(p,0);
- if (!writeChunkType(p,"MTrk"))
- return endOfStream;
- if (!writeLong(p,0)) /* This will be the length of the track. */
- return endOfStream;
- OP->lastTime = 0;
- OP->currentTrack++;
- OP->currentCount = 0; /* Set this after the "MTrk" and dummy length are
- written */
- if (trackName) {
- int i = strlen(trackName);
- if (i) {
- if (!writeByte(p,0) || !writeByte(p,0xff) || !writeByte(p,0x03) ||
- !writeVariableQuantity(p,i) ||
- !writeBytes(p,(unsigned char *)trackName,i))
- return endOfStream;
- }
- }
- return ok;
- }
-
- static int writeTime(MIDIFILEOutStruct *p, int quanta)
- {
- int thisTime = (int)(0.5 + (OP->timeScale * quanta));
- int deltaTime = thisTime - OP->lastTime;
- OP->lastTime = thisTime;
- if (!writeVariableQuantity(p,deltaTime))
- return endOfStream;
- return ok;
- }
-
- #define METAEVENT(_x) (0xff00 | _x)
-
- int MIDIFILEEndWritingTrack(void *p,int quanta)
- {
- if (!writeTime(p,quanta) || !writeShort(p,METAEVENT(TRACKCHANGE)) ||
- !writeByte(p,0))
- return endOfStream;
- /* Seek back to the track length field for this track. */
- NXSeek(OP->s,-(OP->currentCount+4),NX_FROMCURRENT);
- /* +4 because we don't include the "MTrk" specification and length
- in the count */
- if (NXWrite(OP->s,&OP->currentCount,4) != 4)
- return endOfStream;
- /* Seek to end again. */
- NXSeek(OP->s,OP->currentCount,NX_FROMCURRENT);
- OP->currentCount = 0; /* Signals other functions that we've just finished
- a track. */
- return ok;
- }
-
-
- int MIDIFILEWriteSig(void *p,int quanta,short metaevent,unsigned data)
- {
- BOOL keySig = (metaevent == MIDIFILE_keySig);
- unsigned char byteCount = (keySig) ? 2 : 4;
- metaevent = METAEVENT(((keySig) ? KEYSIG : TIMESIG));
- if (!writeTime(p,quanta) || !writeShort(p,metaevent) ||
- !writeByte(p,byteCount))
- return endOfStream;
- return (keySig) ? writeShort(p,data) : writeLong(p,data);
- }
-
- int MIDIFILEWriteText(void *p,BS(quanta,short metaevent,char *text)
- {
- int i;
- metaevent = METAEVENT(metaevent);
- if (!text)
- return ok;
- i = strlen(text);
- if (!writeTime(p,quanta) || !writeShort(p,metaevent) ||
- !writeVariableQuantity(p,i) || !writeBytes(p,(unsigned char *)text,i))
- return endOfStream;
- return ok;
- }
-
- int MIDIFILEWriteSMPTEoffset(void *p,unsigned char hr,unsigned char min,
- unsigned char sec,unsigned char ff,
- unsigned char fr)
- {
- if (!writeByte(p,0) || /* Delta-time is always 0 for SMPTE offset */
- !writeShort(p,METAEVENT(SMPTEOFFSET)) ||
- !writeByte(p,5) || !writeByte(p,hr) || !writeByte(p,min) ||
- !writeByte(p,sec) || !writeByte(p,fr) || !writeByte(p,ff))
- return endOfStream;
- return ok;
- }
-
- int MIDIFILEWriteSequenceNumber(void *p,int data)
- {
- if (!writeByte(p,0) || /* Delta time is 0 */
- !writeShort(p,METAEVENT(SEQUENCENUMBER)) ||
- !writeByte(p,2) || !writeShort(p,data))
- return endOfStream;
- return ok;
- }
-
- int MIDIFILEWriteTempo(void *p,int quanta, int beatsPerMinute)
- {
- int n;
- OP->tempo = beatsPerMinute;
- n = (int)(0.5 + (60000000.0 / OP->tempo));
- OP->timeScale = (double)(OP->division * OP->quantaSize) / (double)n;
- n &= 0x00ffffff;
- n |= 0x03000000;
- if (!writeTime(p,quanta) || !writeShort(p,METAEVENT(TEMPOCHANGE)) ||
- !writeLong(p,n))
- return endOfStream;
- return ok;
- }
-
- int MIDIFILEWriteEvent(register void *p,int quanta,int nData,
- unsigned char *bytes)
- {
- if (!writeTime(p,quanta))
- return endOfStream;
- if (nData && MIDI_TYPE_SYSTEM(bytes[0]))
- if (!writeByte(p,MIDI_EOX) || /* Escape byte */
- !writeVariableQuantity(p,nData)) /* Length of message */
- return endOfStream;
- if (!writeBytes(p,bytes,nData))
- return endOfStream;
- return ok;
- }
-
- int MIDIFILEWriteSysExBSoid *p,int quanta,int nData,unsigned char *bytes)
- /* Assumes there's a MIDI_SYSEXCL at start and there is a
- * MIDI_EOX at end. */
- {
- if (!writeTime(p,quanta) || !writeByte(p,MIDI_SYSEXCL) ||
- !writeVariableQuantity(p,nData-1) || !writeBytes(p,bytes+1,nData-1))
- return endOfStream;
- return ok;
- }
-
- int MIDIFILESetReadQuantaSize(void *p,int usec)
- {
- IP->quantaSize = usec;
- if (IP->division) {
- double n = 60000000.0 / IP->tempo;
- IP->timeScale = n / (double)(IP->division * IP->quantaSize);
- }
- return usec;
- }
-
-
-
-