File:  [NeXTSTEP 3.3 examples] / Examples / SoundAndMusic / MidiDriver / midifile.c
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs
Tue Apr 24 17:48:25 2018 UTC (8 years, 1 month ago) by root
Branches: NeXT, MAIN
CVS tags: NeXTSTEP33, HEAD
Sample Programs from NeXSTEP 3.3

/* 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;
    rtn->format = 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 >> 8;
	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 exclusive 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->timeScale = 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, unsigned 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_FREE(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,int 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 MIDIFILEWriteSysExcl(void *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;
}




unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.