home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
NeXTSTEP 3.0
/
NeXTSTEP3.0.iso
/
NextDeveloper
/
Examples
/
SoundAndMusic
/
Drivers
/
MidiDriver
/
midifile.c
< prev
next >
Wrap
Text File
|
1992-02-14
|
18KB
|
632 lines
/* 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;
}