/*	MuFFy.c

	MIDI File Format Reader/Writer

	(c) 1989 Blue Ribbon Bakery
*/

#include <intuition/intuition.h>
#include <libraries/dos.h>
#include <exec/memory.h>
#include <string.h>
#include "bars.h"

/*	Image when MuFFy is selected: */

static UWORD muffyon[] = { 
/*------ plane # 0: --------*/

  0x0,   0x0,   0x0,   0x0,   0x0, 
  0x0,   0xff8,   0x3ff8,   0x0,   0x0, 
  0x0,   0xffff,   0xffff,   0x0,   0x0, 
  0x3,   0xc03f,   0xf807,   0xc000,   0x0, 
  0x7,   0x1f,   0xf001,   0xc000,   0x0, 
  0x0,   0xc,   0x6000,   0x7f,   0xe180, 
  0xe,   0x3,   0x8000,   0xe07f,   0xc300, 
  0xe,   0xf,   0xe000,   0xf07f,   0x8600, 
  0xf,   0x1f,   0xf001,   0xf07f,   0xc00, 
  0x1f,   0xc07f,   0xfc07,   0xf07e,   0x1800, 
  0x1f,   0xffff,   0xffff,   0xf07c,   0x3000, 
  0x1f,   0xffff,   0xffff,   0xf878,   0x6000, 
  0x1e,   0x7fff,   0xfffc,   0x7870,   0xc000, 
  0x3e,   0x7fc3,   0x83fc,   0x7861,   0x8000, 
  0x3e,   0x1e00,   0x70,   0x7c43,   0x0, 
  0x7e,   0xf00,   0x1e0,   0x7c06,   0x0, 
  0x1fe,   0x3c0,   0x780,   0x7f8c,   0x0, 
  0x1f8,   0x3f,   0xf800,   0x7f98,   0x0, 
  0x1f0,   0x0,   0x0,   0x3fb0,   0x0, 
  0x1e0,   0x0,   0x0,   0xfb0,   0x0, 
  0x7f,   0xffff,   0xffff,   0xffe0,   0x0,
 /*------ plane # 1: --------*/

  0x0,   0x0,   0x3ff,   0x8000,   0x0, 
  0x0,   0xff8,   0x3ff8,   0x0,   0x0, 
  0x0,   0xffff,   0xffff,   0x0,   0x0, 
  0x3,   0xc03f,   0xf807,   0xc000,   0x0, 
  0x7,   0x1f,   0xf001,   0xc07f,   0xff00, 
  0x0,   0xc,   0x6000,   0x0,   0x1f80, 
  0xe,   0x3,   0x8000,   0xe000,   0x3f00, 
  0xe,   0xf,   0xe000,   0xf000,   0x7e00, 
  0xf,   0x1f,   0xf001,   0xf000,   0xfc00, 
  0x1f,   0xc07f,   0xfc07,   0xf001,   0xf800, 
  0x1f,   0xf1ff,   0xff1f,   0xf003,   0xf000, 
  0x1f,   0xf1ff,   0xff1f,   0xf807,   0xe000, 
  0x1e,   0x7fff,   0xfffc,   0x780f,   0xc000, 
  0x3e,   0x7e3f,   0xfc7c,   0x781f,   0x8000, 
  0x3e,   0x1fff,   0xfff0,   0x7c3f,   0x0, 
  0x7e,   0xfff,   0xffe0,   0x7c7e,   0x0, 
  0x1fe,   0x3ff,   0xff80,   0x7ffc,   0x0, 
  0x1f8,   0x3f,   0xf800,   0x7ff8,   0x0, 
  0x1f0,   0x0,   0x0,   0x3ff0,   0x0, 
  0x1e0,   0x0,   0x0,   0xff0,   0x0, 
  0x7f,   0xffff,   0xffff,   0xffe0,   0x0,
 /*------ plane # 2: --------*/

  0x0,   0x0,   0x3ff,   0x8000,   0x0, 
  0x1ff,   0xffff,   0xffff,   0xff80,   0x0, 
  0x1ff,   0xffff,   0xffff,   0xff80,   0x0, 
  0x1ff,   0xc04f,   0xe407,   0xff80,   0x0, 
  0x1ff,   0x3,   0x8001,   0xff80,   0x0, 
  0x1c0,   0x0,   0x0,   0x780,   0x0, 
  0x1be,   0x0,   0x0,   0xfb80,   0x0, 
  0x17e,   0x0,   0x0,   0xfd80,   0x0, 
  0x1ff,   0x0,   0x1,   0xff80,   0x0, 
  0x1ff,   0x0,   0x1,   0xff80,   0x0, 
  0x1ff,   0x0,   0x1,   0xff80,   0x0, 
  0x1ff,   0x0,   0x1,   0xff80,   0x0, 
  0x1ff,   0x8000,   0x3,   0xff80,   0x0, 
  0x1ff,   0x803c,   0x7c03,   0xff80,   0x0, 
  0x1ff,   0xe1ef,   0xef8f,   0xff80,   0x0, 
  0x1ff,   0xf0f0,   0x1e1f,   0xff80,   0x0, 
  0x1ff,   0xfc3f,   0xf87f,   0xff80,   0x0, 
  0x1ff,   0xffc0,   0x7ff,   0xff80,   0x0, 
  0x1ff,   0xffff,   0xffff,   0xff80,   0x0, 
  0x1ff,   0xffff,   0xffff,   0xff80,   0x0, 
  0x0,   0x0,   0x0,   0x0,   0x0
};

static  struct  Image MuFFyonimage = {
	0,0,
	73 , 21 , 3 ,
	&muffyon[0],
	0x1f,0x00,
	NULL,
};
 
/*	The Image when MuFFy is not selected: */

static UWORD muffyoff[] = { 
/*------ plane # 0: --------*/

  0x0,   0x0,   0x0,   0x0,   0x0, 
  0x3ff,   0xffff,   0xffff,   0xff00,   0x0, 
  0x3ff,   0xffff,   0xffff,   0xffc0,   0x0, 
  0x3ff,   0xff8f,   0x8fff,   0xffc0,   0x0, 
  0x3ff,   0xf80f,   0x80ff,   0xffc0,   0x0, 
  0x3ff,   0xe000,   0x3f,   0xffc0,   0x0, 
  0x3ff,   0xc000,   0x1f,   0xffc0,   0x0, 
  0x3ff,   0x0,   0x7,   0xffc0,   0x0, 
  0x3ff,   0x0,   0x7,   0xffc0,   0x0, 
  0x3fe,   0x0,   0x3,   0xffc0,   0x0, 
  0x3fe,   0x1c00,   0x1c3,   0xffc0,   0x0, 
  0x3fe,   0x1c00,   0x1c3,   0xffc0,   0x0, 
  0x3ff,   0x0,   0x7,   0xffc0,   0x0, 
  0x3ff,   0x380,   0xe07,   0xffc0,   0x0, 
  0x3ff,   0xc380,   0xe1f,   0xffc0,   0x0, 
  0x3ff,   0xe007,   0x3f,   0xffc0,   0x0, 
  0x3ff,   0xf807,   0xff,   0xffc0,   0x0, 
  0x3ff,   0xff80,   0xfff,   0xffc0,   0x0, 
  0x3ff,   0xffff,   0xffff,   0xffc0,   0x0, 
  0x3ff,   0xffff,   0xffff,   0xffc0,   0x0, 
  0xff,   0xffff,   0xffff,   0xffc0,   0x0,
 /*------ plane # 1: --------*/

  0x0,   0x0,   0x7ff,   0x0,   0x0, 
  0x0,   0x0,   0x0,   0x0,   0x0, 
  0x0,   0x0,   0x0,   0xc0,   0x0, 
  0x0,   0x0,   0x0,   0xc0,   0x0, 
  0x0,   0x0,   0x0,   0xc0,   0x0, 
  0x0,   0x0,   0x0,   0xc0,   0x0, 
  0x0,   0x0,   0x0,   0xc0,   0x0, 
  0x0,   0x0,   0x0,   0xc0,   0x0, 
  0x0,   0x0,   0x0,   0xc0,   0x0, 
  0x0,   0x0,   0x0,   0xc0,   0x0, 
  0x0,   0x0,   0x0,   0xc0,   0x0, 
  0x0,   0x0,   0x0,   0xc0,   0x0, 
  0x0,   0x0,   0x0,   0xc0,   0x0, 
  0x0,   0x0,   0x0,   0xc0,   0x0, 
  0x0,   0x0,   0x0,   0xc0,   0x0, 
  0x0,   0x0,   0x0,   0xc0,   0x0, 
  0x0,   0x0,   0x0,   0xc0,   0x0, 
  0x0,   0x0,   0x0,   0xc0,   0x0, 
  0x0,   0x0,   0x0,   0xc0,   0x0, 
  0x0,   0x0,   0x0,   0xc0,   0x0, 
  0xff,   0xffff,   0xffff,   0xffc0,   0x0,
 /*------ plane # 2: --------*/

  0x0,   0x0,   0x7ff,   0x0,   0x0, 
  0x0,   0x0,   0x0,   0x0,   0x0, 
  0x0,   0x0,   0x0,   0x0,   0x0, 
  0x0,   0x0,   0x0,   0x0,   0x0, 
  0x0,   0x0,   0x0,   0x0,   0x0, 
  0x0,   0x0,   0x0,   0x0,   0x0, 
  0x0,   0x0,   0x0,   0x0,   0x0, 
  0x0,   0x0,   0x0,   0x0,   0x0, 
  0x0,   0x0,   0x0,   0x0,   0x0, 
  0x0,   0x0,   0x0,   0x0,   0x0, 
  0x0,   0x0,   0x0,   0x0,   0x0, 
  0x0,   0x0,   0x0,   0x0,   0x0, 
  0x0,   0x0,   0x0,   0x0,   0x0, 
  0x0,   0x0,   0x0,   0x0,   0x0, 
  0x0,   0x0,   0x0,   0x0,   0x0, 
  0x0,   0x0,   0x0,   0x0,   0x0, 
  0x0,   0x0,   0x0,   0x0,   0x0, 
  0x0,   0x0,   0x0,   0x0,   0x0, 
  0x0,   0x0,   0x0,   0x0,   0x0, 
  0x0,   0x0,   0x0,   0x0,   0x0, 
  0x0,   0x0,   0x0,   0x0,   0x0
};

struct  Image MuFFyoffimage = {
	1,0,
	73 , 21 , 3 ,
	&muffyoff[0],
	0x1f,0x00,
	NULL,
};

extern struct Functions *functions;

/*	MIDI File Format id's: */

#define ID_MTHD		0x4D546864
#define ID_MTRK		0x4D54726B

/*	Bars&Pipes global data and functions. */

extern struct Functions *functions;

static void loadunknown(file,size)

/*	Read unknown data from file, discard. */

long file,size;

{
    char buffer[100];
    for (;size >= 100;size-=100) {
	if (!(*functions->fastread)(file,buffer,100)) break;
    }
    if (size > 0) (*functions->fastread)(file,buffer,size);
}

writevarlen(file,value)

/*	Write a MIDI File Format variable length record. */

long file;
long value;

{
    long buffer;
    char out;
    buffer = value & 0x7F;
    while ((value >>= 7) > 0) {
	buffer <<= 8;
	buffer |= 0x80;
	buffer += (value & 0x7F);
    }
    for (;;) {
	out = buffer;
	if ((*functions->fastwrite)(file,&out,1) == -1) return(1);
	if (buffer & 0x80) buffer >>= 8;
	else break;
    }
    return(0);
}

calcvarlen(value)

/*	Calculate how many bytes it will take to write the data. */

long value;

{
    long len = 1;
    while ((value >>= 7) > 0) {
	len++;
    }
    return(len);
}

struct NoteEvent *createmiditrack(clip,channel)

/*	Create a list for saving a Track in the MIDI File Format. */

struct Clip *clip;
char channel;

{
    struct NoteEvent *event, *scan;
    struct NoteEvent *onlist = 0;
    struct TimeSigEvent *timesig, *newtimesig;
    struct StringEvent *lyric, *newlyric;
    struct KeyEvent *key, *newkey;
    for (scan = (struct NoteEvent *) clip->events.first;scan;scan = scan->next) {
	event = (struct NoteEvent *) (*functions->allocevent)();
	if (event) {
	    event->next = onlist;
	    onlist = event;
	    event->status = channel | scan->status;
	    event->type = EVENT_VOICE;
	    event->value = scan->value;
	    event->velocity = scan->velocity;
	    event->time = scan->time;
	    if (scan->status == MIDI_NOTEON) {
		event = (struct NoteEvent *) (*functions->allocevent)();
		if (event) {
		    event->next = onlist;
		    onlist = event;
		    event->status = channel | MIDI_NOTEON;
		    event->type = EVENT_VOICE;
		    event->value = scan->value;
		    event->velocity = 0;
		    event->time = scan->time + scan->duration;
		}
	    }
	}
    }
    timesig = (struct TimeSigEvent *) clip->timesig.first;
    for (;timesig;timesig = timesig->next) {
	newtimesig = (struct TimeSigEvent *) (*functions->allocevent)();
	if (newtimesig) {
	    memcpy((char *)newtimesig,(char *)timesig,sizeof(struct TimeSigEvent));
	    newtimesig->next = (struct TimeSigEvent *) onlist;
	    onlist = (struct NoteEvent *) newtimesig;
	}
    }
    lyric = (struct LyricEvent *) clip->lyrics.first;
    for (;lyric;lyric = lyric->next) {
	newlyric = (struct StringEvent *) (*functions->allocevent)();
	if (newlyric) {
	    memcpy((char *)newlyric,(char *)lyric,sizeof(struct StringEvent));
	    (*functions->dupestring)(&newlyric->string);
	    newlyric->next = (struct StringEvent *) onlist;
	    onlist = (struct NoteEvent *) newlyric;
	}
    }
    key = (struct KeyEvent *) clip->keys.first;
    for (;key;key = key->next) {
	newkey = (struct KeyEvent *) (*functions->allocevent)();
	if (newkey) {
	    memcpy((char *)newkey,(char *)key,sizeof(struct KeyEvent));
	    newkey->next = (struct KeyEvent *) onlist;
	    onlist = (struct NoteEvent *) newkey;
	}
    }
    onlist = (struct NoteEvent *) (*functions->sorteventlist)(onlist);
    return(onlist);
}

writebyte(file,c)

long file;
char c;

{
    return((*functions->fastwrite)(file,&c,1) == -1);
}

calcmidiclip(clip)

/*	Calculate the byte count to save a track. */

struct Clip *clip;

{
    struct Event *event;
    struct StringEvent *string;
    unsigned char laststatus = 0;
    long total = 0;
    long length;
    long lasttime = 0;
    struct Event *list = (struct Event *) createmiditrack(clip,0);
    event = list;
    if (clip->name) {
	length = clip->name->length - 2;
	total += calcvarlen(length);
	total += length;
	total += 3;
    }
    if (clip->notes) {
	length = clip->notes->length - 2;
	total += calcvarlen(length);
	total += length;
	total += 3;
    }
    for (;event;event = event->next) {
	total += calcvarlen(event->time - lasttime);
	lasttime = event->time;
	if (event->type == EVENT_TIMESIG) {
	    total += 7;
	    laststatus = 0;
	}
	else if (event->type == EVENT_LYRIC) {
	    laststatus = 0;
	    string = (struct StringEvent *) event;
	    length = string->string->length - 2;
	    total += calcvarlen(length);
	    total += length;
	    total += 2;
	}
	else if (event->type == EVENT_KEY) {
	    laststatus = 0;
	    total += 5;
	}
	else if (event->type == EVENT_VOICE) {
	    if (event->status != laststatus) {
		total++;
		laststatus = event->status;
	    }
	    total++;
	    switch(event->status & 0xF0) {
		case MIDI_NOTEON :
		case MIDI_NOTEOFF :
		case MIDI_PTOUCH :
		case MIDI_CCHANGE :
		case MIDI_PBEND :
		    total++;
	    }
	}
    }
    (*functions->freelist)(list);
    if (clip->end < lasttime) total++;
    else total += calcvarlen(clip->end - lasttime);
    return(total + 3);
}

sharpflatcount(key,minor)

/*	Convert a Key structure into total sharps or flats for
	key signature.
*/

struct KeyEvent *key;
char *minor;

{
    static char sharps[12] = { 0,7,2,-3,4,-1,6,1,-4,3,-2,5 };
    static char flats[12] = { 0,-5,2,-3,4,-1,-6,1,-4,3,-2,5 };
    short base, root;
    static unsigned short patterns[12] = 
    {	0xAB5, 0xD5A, 0x6AD, 0xB56, 
	0x5AB, 0xAD5, 0xD6A, 0x6B5, 
	0xB5A, 0x5AD, 0xAD6, 0x56B 
    };
    for (base=0;base<12;base++) {
	if (key->scale->pattern == patterns[base]) break;
    }
    *minor = (base == 9);
    if (base == 12) return(0);
    root = key->root;
    root = root - base;
    if (root < 0) root += 12;
    if (key->flats) key->pad = flats[root];
    else key->pad = sharps[root];
    return(key->pad);
}

savemidiclip(file,clip,channel)

/*	Save one track. */

long file;
struct Clip *clip;
char channel;

{
    struct Event *event;
    struct Event *list;
    struct TimeSigEvent *timesig;
    struct KeyEvent *key;
    long lasttime = 0;
    long id = ID_MTRK;
    short shift, mask;
    struct StringEvent *string;
    unsigned char laststatus = 0;
    char minor, flats;
    (*functions->fastwrite)(file,&id,4);
    id = calcmidiclip(clip);
    (*functions->fastwrite)(file,&id,4);
    list = (struct Event *) createmiditrack(clip,channel);
    event = list;
    if (clip->name) {
	writebyte(file,0);
	writebyte(file,0xFF);
	writebyte(file,3);
	writevarlen(file,clip->name->length-2);
	(*functions->fastwrite)(file,clip->name->string,clip->name->length-2);
    }
    if (clip->notes) {
	writebyte(file,0);
	writebyte(file,0xFF);
	writebyte(file,4);
	writevarlen(file,clip->notes->length-2);
	(*functions->fastwrite)(file,clip->notes->string,clip->notes->length-2);
    }
    for (;event;event = event->next) {
	if (writevarlen(file,event->time - lasttime)) {
	    (*functions->freelist)(list);
	    return(1);
	}
	lasttime = event->time;
	if (event->type == EVENT_LYRIC) {
	    laststatus = 0;
	    string = (struct StringEvent *) event;
	    writebyte(file,0xFF);
	    writebyte(file,0x05);
	    writevarlen(file,string->string->length-2);
	    (*functions->fastwrite)(file,string->string->string,string->string->length-2);
	}
	else if (event->type == EVENT_KEY) {
	    laststatus = 0;
	    key = (struct KeyEvent *) event;
	    flats = sharpflatcount(key,&minor);
	    writebyte(file,0xFF);
	    writebyte(file,0x59);
	    writebyte(file,2);
	    writebyte(file,flats);
	    writebyte(file,minor);
	}
	else if (event->type == EVENT_TIMESIG) {
	    laststatus = 0;
	    timesig = (struct TimeSigEvent *) event;
	    writebyte(file,0xFF);
	    writebyte(file,0x58);
	    writebyte(file,4);
	    writebyte(file,timesig->beatcount);
	    shift = 0;
	    mask = 1;
	    for (;(!(mask & timesig->beat));shift++) mask <<= 1;
	    writebyte(file,shift);
	    writebyte(file,timesig->clocks >> 3);
	    writebyte(file,8);
	}
	else if (event->type == EVENT_VOICE) {
	    if (event->status != laststatus) {
		laststatus = event->status;
		(*functions->fastwrite)(file,&laststatus,1);
	    }
	    (*functions->fastwrite)(file,&event->byte1,1);
	    switch(event->status & 0xF0) {
		case MIDI_NOTEON :
		case MIDI_NOTEOFF :
		case MIDI_PTOUCH :
		case MIDI_CCHANGE :
		case MIDI_PBEND :
		    (*functions->fastwrite)(file,&event->byte2,1);
	    }
	}
    }
    (*functions->freelist)(list);
    if (clip->end < lasttime) writevarlen(file,0);
    else writevarlen(file,clip->end - lasttime);
    writebyte(file,0xFF);
    writebyte(file,0x2F);
    writebyte(file,0x0);
    return(0);
}

savemidisong(file)

/*	Save the entire song. */

long file;

{
    struct Track *track;
    long id = ID_MTHD;
    if ((*functions->fastwrite)(file,&id,4) == -1) return(1);
    id = 6;
    (*functions->fastwrite)(file,&id,4);
    writebyte(file,0);
    writebyte(file,1);
    writebyte(file,0);
    writebyte(file,(*functions->List_Len)(functions->tracklist)+1);
    writebyte(file,0);
    writebyte(file,192);
    if (savemidiclip(file,&functions->masterclip,0)) return(1);
    for (track = functions->tracklist;track;track = track->next) {
	if (savemidiclip(file,&track->clip,track->channelout)) return(1);
    }
    return(0);
}

char readbyte(file)

long file;

{
    char c;
    (*functions->fastread)(file,&c,1);
    return(c);
}

readvarlen(file)

/*	Read a variable length number. */

long file;

{
    long value;
    char in;
    (*functions->fastread)(file,&in,1);
    value = in & 0x7F;
    if (in & 0x80) {
	do {
	    if (!(*functions->fastread)(file,&in,1)) return(-1);
	    value = (value << 7) + (in & 0x7F);
	} while (in & 0x80);
    }
    return(value);
}

static struct Chord *findkey(sharps,minor,root)

/*	Take the MIDI File Format key signature and find the right 
	B&P Key.
*/

char sharps, minor;
char *root;

{
    static char roots[14] = { 6,1,8,3,10,5,0,7,2,9,4,11,6,1 };
    struct Chord *scale;
    *root = roots[sharps + 6];
    if (minor) {
	*root += 9;
	if (*root > 11) *root -= 12;
    }
    scale = functions->scalelist;
    for (;scale;scale = scale->next) {
	if (minor) {
	    if (scale->pattern == 0x5AD) break;
	}
	else if (scale->pattern == 0xAB5) break;	
    }
    if (!scale) scale = functions->scalelist;
    return(scale);
}

void stripnoteoffs(track)

/*	Remove the Note Off Events and calculate the durations. */

struct Track *track;

{
    register struct NoteEvent *event = (struct NoteEvent *) track->record.first;
    register struct NoteEvent *up, *last;
    register char value;
    register unsigned short lastduration = 96;
    for (;event;event = event->next) {
	if ((event->status == MIDI_NOTEON) && !event->velocity)
	    event->status = MIDI_NOTEOFF;
    }
    event = (struct NoteEvent *) track->record.first;
    while (event) {
	if (event->status == MIDI_NOTEON) {
	    last = event;
	    up = event->next;
	    value = event->value;
	    while (up) {
		if ((up->value == value) && (up->status == MIDI_NOTEOFF)) break;
		last = up;
		up = up->next;
	    }
	    if (up) {
		lastduration = up->time - event->time;
		if (!lastduration) lastduration++;
		last->next = up->next;
		(*functions->freeevent)(up);
	    }
	    event->duration = lastduration;
	    event = event->next;
	}
	else if (event->status == MIDI_NOTEOFF) {
	    up = event;
	    event = event->next;
	    track->record.first = (struct Event *) (*functions->List_Remove)(
		(struct ListItem *) track->record.first,
		(struct ListItem *) up);
	    (*functions->freeevent)(up);
	}
	else event = event->next;
    }
}

void computetrackhighandlow(track)

/*	Comput upper and lower boundaries of the Track, so B&P will
	display it correctly.
*/

struct Track *track;

{
    struct Event *event = track->clip.events.first;
    short lownote = 72;
    short highnote = 72;
    char nonyet = 1;
    for (;event;event = event->next) {
	if (event->status == MIDI_NOTEON) {
	    if (nonyet) {
		lownote = event->byte1;
		highnote = event->byte1;
		nonyet = 0;
	    }
	    else if (event->byte1 < lownote) lownote = event->byte1;
	    else if (event->byte1 > highnote) highnote = event->byte1;
	}
    }
    track->clip.lownote = lownote;
    track->clip.highnote = highnote;
}

void cleanuptimesig(clip)

/*	Clean up errors in the Time Signature.  */

struct Clip *clip;

{
    register struct TimeSigEvent *ts, *next;
    register long clocks, measures;
    register long oldtype, newtype;
    if (!clip->timesig.first) return;
    clip->timesig.first = (struct TimeSigEvent *) 
	(*functions->sorteventlist)(clip->timesig.first);
    clip->timesig.point = clip->timesig.first;
    oldtype = 0;
    ts = clip->timesig.first;
    ts->time = 0;
    for (;ts;) {
	ts->type = EVENT_TIMESIG;
	clocks = ts->beat;
	if (!clocks) {
	    ts->beat = 4;
	    clocks = 4;
	}
	clocks = 768 / clocks;
	ts->clocks = clocks;
	next = ts->next;
	if (next) {
	    newtype = next->beat + (next->beatcount << 16);
	    oldtype = ts->beat + (ts->beatcount << 16);
	    if (newtype == oldtype) {
		ts->next = next->next;
		(*functions->freeevent)(next);
		continue;
	    }
	    clocks = clocks * ts->beatcount;
	    measures = next->time - ts->time;
	    measures += (clocks >> 1);
	    measures = measures / clocks;
	    if (!measures) {
		ts->next = next->next;
		(*functions->freeevent)(next);
		continue;
	    }
	    ts->measures = measures;
	    measures = measures * clocks;
	    next->time = measures + ts->time;
	}
	else ts->measures = -1;
	ts = next;
    }
}

void cleanupeventlist(clip,list,force)

/*	Remove duplicate events in the list.  If force is
	true, force all Events to measure boundaries.
*/

struct Clip *clip;
struct EventList *list;
char force;

{
    struct Event *event = list->first;
    struct Event *lose;
    unsigned long time;
    if (!event) return;
    if (force) {
	for (;event;event = event->next) {
	    time = (*functions->timetomeasure)(clip,event->time);
	    event->time = (*functions->measuretotime)(clip,time);
	}
    }
    list->first = (struct Event *) 
	(*functions->sorteventlist)(list->first);
    list->point = list->first;
    event = list->first;
    event->time = 0;
    for (;event;event = event->next) {
	lose = event->next;
	if (lose && (event->time == lose->time)) {
	    event->next = lose->next;
	    (*functions->freeevent)(lose);
	}
    }
}

void cleanupkey(clip)

struct Clip *clip;

{
    cleanupeventlist(clip,&clip->keys,1);
}

struct Track *readmiditrack(file,shift)

/*	Read a MIDI Track, convert to B&P. */

long file;
short shift;

{
    unsigned char c;
    struct NoteEvent *event;
    struct NoteEvent *onlists[16];
    struct Track *track, *toptrack;
    char channel;
    long varlen, delta;
    long length;
    long time = 0;
    long shiftedtime = 0;
    long bytesread;
    struct StringEvent *stringevent;
    struct TimeSigEvent *timesig;
    struct KeyEvent *keyevent;
    char name[100];
    char *text;
    unsigned char laststatus = 0;
    char sharps;
    char minor;
    (*functions->fastread)(file,&length,4);
    toptrack = (struct Track *) (*functions->createtrack)(1,0);
    if (!toptrack) return(0);
    for (channel = 0;channel < 16; channel++) onlists[channel] = 0;
    for (;length > 0;) {
	delta = readvarlen(file);
	if (delta == -1) break;
	length -= calcvarlen(delta);
	shiftedtime += delta;
	time = (shiftedtime * 192) / shift;
	c = readbyte(file);
	length--;
	if (c == 0xFF) {
	    c = readbyte(file);
	    length--;
	    varlen = readvarlen(file);
	    length -= calcvarlen(varlen);
	    length -= varlen;
	    bytesread = 0;
	    switch(c) {
		case 1 :
		case 3 :
		case 4 :
		    if (varlen) {
			text = (char *) (*functions->myalloc)(varlen+1,0);
			if (text) {
			    (*functions->fastread)(file,text,varlen);
			    text[varlen] = 0;
			    if (c == 3) (*functions->replacestring)
				(&toptrack->clip.name,text);
			    else (*functions->replacestring)
				(&toptrack->clip.notes,text);
			    (*functions->myfree)(text,varlen+1);
			    bytesread = varlen;
			}
		    }
		    break;
		case 5 :
		case 6 :
		case 7 :
		    if (varlen) {
			text = (char *) (*functions->myalloc)(varlen+1,0);
			if (text) {
			    (*functions->fastread)(file,text,varlen);
			    text[varlen] = 0;
			    stringevent = (struct StringEvent *) 
				(*functions->allocevent)();
			    stringevent->type = EVENT_LYRIC;
			    stringevent->string = (struct String *) 
				(*functions->allocstring)(text);
			    stringevent->time = time;
			    stringevent->next = (struct StringEvent *) 
				toptrack->clip.lyrics.first;
			    toptrack->clip.lyrics.first = 
				(struct Event *) stringevent;
			    (*functions->myfree)(text,varlen+1);
			    bytesread = varlen;
			}
		    }
		    break;
		case 0x58 :
		    timesig = (struct TimeSigEvent *) (*functions->allocevent)();
		    timesig->time = time;
		    timesig->type = EVENT_TIMESIG;
		    timesig->beatcount = readbyte(file);
		    timesig->beat = 1 << readbyte(file);
		    readbyte(file);
		    readbyte(file);
		    timesig->next = toptrack->clip.timesig.first;
		    toptrack->clip.timesig.first = timesig;
		    bytesread = 4;
		    break;
		case 0x59 :
		    keyevent = (struct KeyEvent *) (*functions->allocevent)();
		    keyevent->time = time;
		    keyevent->type = EVENT_KEY;
		    keyevent->next = (struct KeyEvent *) toptrack->clip.keys.first;
		    toptrack->clip.keys.first = (struct Event *) keyevent;
		    sharps = readbyte(file);
		    minor = readbyte(file);
		    keyevent->scale = findkey(sharps,minor,&keyevent->root);
		    keyevent->pad = 7;
		    keyevent->flats = (sharps < 0);
		    bytesread = 2;
		    break;
	    }
	    if (bytesread < varlen) loadunknown(file,varlen - bytesread);
	}
	else {
	    if ((c == 0xF0) || (c == 0xF7)) {
		varlen = readvarlen(file);
		length -= calcvarlen(varlen);
		loadunknown(file,varlen);
		laststatus = 0;
		continue;
	    }
	    if (c & 0x80) {
		laststatus = c;
		c = readbyte(file);
		length--;
	    }
	    event = (struct NoteEvent *) (*functions->allocevent)();
	    event->status = laststatus & 0xF0;
	    event->value = c;
	    event->time = time;
	    event->type = EVENT_VOICE;
	    event->next = onlists[laststatus & 0xF];
	    onlists[laststatus & 0xF] = event;
	    switch (laststatus & 0xF0) {
		case MIDI_NOTEON :
		case MIDI_NOTEOFF :
		case MIDI_PTOUCH :
		case MIDI_CCHANGE :
		case MIDI_PBEND :
		    event->velocity = readbyte(file);
		    length--;
		    break;
	    }
	}
    }
    track = 0;
    toptrack->clip.lyrics.first = (struct Event *)
	(*functions->sorteventlist)(toptrack->clip.lyrics.first);
    cleanuptimesig(&toptrack->clip);
    cleanupkey(&toptrack->clip);
    for (channel = 0;channel < 16; channel++) {
	if (onlists[channel]) {
	    if (!track) track = toptrack;
	    else {
		track = (struct Track *) (*functions->createtrack)(1,0);
		(*functions->Itoa)(channel+1,name,10);
		strcat(name," ");
		strcat(name,(char *)(*functions->stringtext)(track->clip.name));
		(*functions->replacestring)(&track->clip.name,name);
	    }
	    if (track) {
		track->record.first = (struct Event *)
		    (*functions->sorteventlist)(onlists[channel]);
		stripnoteoffs(track);
		track->clip.events.first = track->record.first;
		track->record.first = 0;
		computetrackhighandlow(track);
		track->channelout = channel;
		track->next = 0;
		track->clip.events.first = (struct Event *)
		    (*functions->sorteventlist)(track->clip.events.first);
		if (toptrack != track) 
		    toptrack = (struct Track *) 
			(*functions->List_Cat)(toptrack,track);
	    }	    
	}
    }
    return(toptrack);
}

void loadmidisong(file)

/*	Load a MIDI File, install in the environment. */

long file;

{
    struct Track *track, *toptrack;
    long id, length;
    short format, trackcount;
    short res;
    (*functions->fastread)(file,&id,4);
    if (id == ID_MTHD) {
	if (!(*functions->fastread)(file,&length,4)) return;
	(*functions->fastread)(file,&format,2);
	(*functions->fastread)(file,&trackcount,2);
	(*functions->fastread)(file,&res,2);
	if (length > 6) loadunknown(file,length-6);
	toptrack = 0;
	for (;trackcount;trackcount--) {
	    (*functions->fastread)(file,&id,4);
	    if (id == ID_MTRK) {
		track = readmiditrack(file,res);
		if (!track) break;
		toptrack = (struct Track *) (*functions->List_Cat)(toptrack,track);
	    }
	    else break;
	}
    }
    functions->tracklist = toptrack;
}

/*	Power Windows definition for MuFFy's Control Window: */

static SHORT midifileBorderVectors1[] = {
	0,0,
	135,0,
	135,12,
	0,12,
	0,0
};
static struct Border midifileBorder1 = {
	-1,-1,
	7,0,JAM1,
	5,
	midifileBorderVectors1,
	NULL
};

static struct IntuiText midifileIText1 = {
	6,0,JAM2,
	9,2,
	NULL,
	"Load MIDI File",
	NULL
};

static struct Gadget midifileGadget2 = {
	NULL,
	15,35,
	134,11,
	NULL,
	RELVERIFY+GADGIMMEDIATE,
	BOOLGADGET,
	(APTR)&midifileBorder1,
	NULL,
	&midifileIText1,
	NULL,
	NULL,
	2,
	NULL
};

static SHORT midifileBorderVectors2[] = {
	0,0,
	135,0,
	135,12,
	0,12,
	0,0
};
static struct Border midifileBorder2 = {
	-1,-1,
	7,0,JAM1,
	5,
	midifileBorderVectors2,
	NULL
};

static struct IntuiText midifileIText2 = {
	6,0,JAM2,
	9,2,
	NULL,
	"Save MIDI File",
	NULL
};

static struct Gadget midifileGadget1 = {
	&midifileGadget2,
	15,17,
	134,11,
	NULL,
	RELVERIFY+GADGIMMEDIATE,
	BOOLGADGET,
	(APTR)&midifileBorder2,
	NULL,
	&midifileIText2,
	NULL,
	NULL,
	1,
	NULL
};

#define midifileGadgetList1 midifileGadget1

static struct NewWindow newwindow = {
	238,45,
	164,54,
	5,1,
	GADGETUP+CLOSEWINDOW,
	WINDOWDRAG+WINDOWDEPTH+WINDOWCLOSE+ACTIVATE+NOCAREREFRESH,
	&midifileGadget1,
	NULL,
	"MuFFy",
	NULL,
	NULL,
	5,5,
	-1,-1,
	CUSTOMSCREEN
};

static struct Accessory MuFFy;

closewindow()

{
    struct IntuiMessage *message;
    if (MuFFy.window) {
	newwindow.LeftEdge = MuFFy.window->LeftEdge;
	newwindow.TopEdge = MuFFy.window->TopEdge;
	newwindow.Width = MuFFy.window->Width;
	newwindow.Height = MuFFy.window->Height;
	MuFFy.window->UserPort = 0;
	ModifyIDCMP(MuFFy.window,0);
	WaitTOF();
	WaitTOF();
	WaitTOF();
	Forbid();
	while (message = (struct IntuiMessage *) 
	    GetMsg(functions->window->UserPort)) ReplyMsg((struct Message *)message);
	(*functions->FlashyCloseWindow)(MuFFy.window);
	MuFFy.window = 0;
	Permit();
    }
    return(1);
}
 
struct Window *openwindow()

/*	Open MuFFy's Control Window & use the B&P main window Port.
*/

{
    struct Window *window;
    newwindow.Screen = functions->screen;
    newwindow.IDCMPFlags = 0;
    window = (struct Window *) 
	(*functions->FlashyOpenWindow)(&newwindow);
    if (window) {
	window->UserPort = functions->window->UserPort;
	ModifyIDCMP(window,functions->window->IDCMPFlags);
    }
    MuFFy.window = window;
    return(window);
}			

#define FILES_DELETE	1
#define FILES_OPEN	2
#define FILES_SAVE	4
#define FILES_TEST	8
#define FILES_TYPE	16
#define FILES_PATH	32

editwindow(message)

struct IntuiMessage *message;

{
    struct Window *window = MuFFy.window;
    struct Gadget *gadget;
    long file;
    long class;
    char filename[100];
    class = message->Class;
    gadget = (struct Gadget *) message->IAddress;
    ReplyMsg((struct Message *)message);
    if (class == CLOSEWINDOW) {
	closewindow(window);
    }
    else if (class == GADGETUP) {
	if (gadget->GadgetID == 2) {
	    (*functions->FileName)(filename,"Load MIDI File:","MID",
		functions->screen,FILES_OPEN|FILES_DELETE|FILES_TYPE,0,0);
	    if (filename[0]) {
		file = (*functions->fastopen)(filename,MODE_OLDFILE);
		if (file) {
		    if ((*functions->areyousure)("replace this song?")) {
			(*functions->openwait)();
			(*functions->clearenvironment)(functions,0);
			loadmidisong(file);
			(*functions->display)(0x1000000);
			(*functions->closewait)();
		    }
		    (*functions->fastclose)(file);
		}
	    }
	}
	else if (gadget->GadgetID == 1) {
	    (*functions->FileName)(filename,"Save MIDI File:","MID",
		functions->screen,FILES_SAVE|FILES_DELETE|FILES_TYPE,0,0);
	    if (filename[0]) {
		file = (*functions->fastopen)(filename,MODE_NEWFILE);
		if (file) {
		    (*functions->openwait)();
		    savemidisong(file);
		    (*functions->fastclose)(file);
		    (*functions->closewait)();
		}
	    }
	}
    }
    return(1);
}

struct Accessory *inittoolmaster()

{
    memset((char *)&MuFFy,0,sizeof(struct Accessory));
    MuFFy.image = &MuFFyoffimage;
    MuFFy.onimage = &MuFFyonimage;
    MuFFy.open = openwindow;
    MuFFy.close = closewindow;
    MuFFy.edit = editwindow;
    strcpy(MuFFy.name,"MuFFy");
    return(&MuFFy);
}
