#include <conio.h>
#include <dos.h>
#include <direct.h>
#include <stdio.h>
#include <io.h>
#include <fcntl.h>
#include <string.h>
#include <malloc.h>
#include <stdlib.h>
#include <math.h>
#include <limits.h>
#include <signal.h>

#include "iw.h"
#include "patch.h"
#include "iwapi.h"

#if defined(__WATCOMC__)
#define LOADDS __loadds
#define FAR __far
#define HUGE
#define farmalloc malloc
#define farfree free
#define INPORTB inp
#define OUTPORTB outp
#define DISABLE _disable
#define ENABLE _enable
#elif defined(__BORLANDC__)
#define LOADDS
#define FAR far
#define HUGE huge
#define OUTPORTB outportb
#define INPORTB inportb
#define DISABLE disable
#define ENABLE enable
#endif

static volatile int not_done_playing = 1;      /* midi song playing */
int force8bit = 0;

#define PATCHBUFFSIZE 8192
char patchbuff[PATCHBUFFSIZE];	/* dma buffer for stereo seperation */

/****************************************************************************/
/* MIDI *********************************************************************/
/****************************************************************************/
int MidiApiError = 0;
int midi_synth_volume=100;		/* master synth volume */
char midi_on = 0;                  /* midi library in use */
char midi_paused = 0;              /* song paused */
enum midi_mode {
	COLLECT_PATCHES,           /* midi parser is gathering patch stats */
	MIDI_PLAY                  /* midi parser is playing notes */
} midi_mode;

enum play_state {
	MPS_BEGIN,		   /* track being initialized */
	MPS_PLAYING,		   /* track playing */
	MPS_DONE		   /* track done playing */
};

struct track {
    unsigned char HUGE *data;	/* beginning of midi track - after length */
    unsigned char HUGE *playp;	/* pointer to current pos during playback */
    unsigned long position;	/* number of bytes in track already played */
    unsigned long length;	/* total length of track */
    unsigned long play_time;	/* time for next event in track */
    unsigned char status;	/* running status for track */
    enum play_state play_state; /* current playing status for track */
    struct track *next_track;	/* pointer to next track */
};

/* definitions for MIDI file parsing code and playback code */
struct MIDILIB {
    short (*Mf_getc)(void);
    void (*Mf_error)(int);
    void (*Mf_header)(int, int, int);
    void (*Mp_reset_tick_counter)(void);
    void (*Mp_set_tempo)(unsigned long, short);
    void (*Mp_init_timers)(void);
    void (*Mp_cleanup_timers)(void);
    void (*Mp_reset_midi)(void);
    void (*Mp_note_on)(int, int, int);
    void (*Mp_note_off)(int, int, int);
    void (*Mp_pressure)(int, int, int);
    void (*Mp_parameter)(int, int, int);
    void (*Mp_pitch_bend)(int, int, int);
    void (*Mp_program)(int, int);
    void (*Mp_chanpressure)(int, int, int);
    void (*Mp_all_notes_off)(int);
    int (*Mp_init_hardware)(struct MIDILIB *);
    void (*Mp_cleanup)(void);
    struct track *Mp_tracks;
    short tracknum;
    long Mf_toberead;
    unsigned long Mp_next_event_time;
};

static struct MIDILIB *gml=0L;	/* pointer to midi functions and data */

struct header_type {
    short format;
    short ntrks;
    union {
	short quarter_note;
	struct {
	    char format;
	    char resolution;
	} smpte;
    } division;
} header;

static int channel_to_program[16];     /* keep track of program changes during
				       patch gathering */
static int channel_bank[16];		/* keep track of bank changes during
				       patch gathering */

/* MIDI status commands most significant bit is 1 */
#define note_off		0x80
#define note_on			0x90
#define poly_aftertouch		0xa0
#define control_change		0xb0
#define program_chng		0xc0
#define channel_aftertouch      0xd0
#define pitch_wheel		0xe0
#define system_exclusive	0xf0
#define sysex_continue		0xf7
#define delay_packet		(1111)

/* Standard MIDI Files meta event definitions */
#define	MT_meta_event		0xff
#define	MT_sequence_number	0x00
#define	MT_text_event		0x01
#define MT_copyright_notice	0x02
#define MT_sequence_name	0x03
#define MT_instrument_name	0x04
#define MT_lyric		0x05
#define MT_marker		0x06
#define	MT_cue_point		0x07
#define MT_channel_prefix	0x20
#define	MT_end_of_track		0x2f
#define	MT_set_tempo		0x51
#define	MT_smpte_offset		0x54
#define	MT_time_signature	0x58
#define	MT_key_signature	0x59
#define	MT_sequencer_specific	0x74

static struct MIDILIB *bg_library=NULL;
static void midi_do_something(struct MIDILIB *ml, unsigned long now_time);
static unsigned char midi_getc(struct track *track);
static long midi_getvarinum(struct track *track);
static void midi_get_next_time(struct MIDILIB *ml, struct track *track);
static FILE *fp_midi=NULL;

#if defined(__WATCOMC__) && defined(__FLAT__)
#define USER_STACK_SIZE 1024
#else
#define USER_STACK_SIZE 1024
#endif
static unsigned char user_stack[USER_STACK_SIZE];
static unsigned char FAR *user_stack_p = user_stack + USER_STACK_SIZE;
static volatile unsigned int user_ss;
static volatile unsigned int user_sp;
static volatile unsigned char stack_in_use=0;

/****************************************************************************/
/* TIMER ********************************************************************/
/****************************************************************************/
#define TIMER 0x08
static unsigned long tempo = 5000L; /* just a random tempo. 5.0ms tick */
static unsigned long us_timer = 0L;             /* microsecond timer */
static unsigned long timer = 0L;                /* tempo timer */
static unsigned long second_timer = 0L;		/* second timer */
static unsigned long sus_timer = 0L;		/* microsecond timer */
static volatile unsigned ticks=0;        /* clock ticks between calls to BIOS */
/* interrupt clock stuff */
static void (interrupt FAR *orig_clock)(void) = 0L;
static void interrupt clock_handler(void);
static int RFAR clock1(void);
static int use_iw_timers = 1;
static unsigned long clock_interval;
#define CLOCK_DIVISOR 8192U
#define ORIG_CLOCK_DIVISOR 0U
#define CLOCK_RATIO 8

/****************************************************************************/
/* OS INTERFACE FUNCTIONS ***************************************************/
/****************************************************************************/

static void iw_parameter(int channel, int controller, int value)
{
    if (controller == 0) {
	channel_bank[channel] = value;
    }
    if (midi_mode != COLLECT_PATCHES) {
	iw_midi_parameter(channel, controller, value);
    }
}

static void iw_change_program(int channel, int prog)
{
    struct iwu_fast_patch *fpi;
    struct patch_lib RFAR *pl=0;

    if (channel == 9) {
	channel_bank[channel] = prog;
    }
    channel_to_program[channel] = prog;
    if (midi_mode != COLLECT_PATCHES) {
	fpi = iwu_find_patch(prog, channel_bank[channel], &pl);
	if (fpi && fpi->flags & IWU_PATCH_LOADED) {
	    iw_midi_change_program(channel, fpi->p);
	} else {
	    iw_midi_change_program(channel, 0);
	}
    }
}

static void iw_note_on(int chan, int note, int velocity)
{
    struct iwu_fast_patch *fpi;
    struct patch_lib RFAR *pl=0;

    if (midi_mode == COLLECT_PATCHES) {
	if (chan == 9) {
	    iwu_mark_patch(note+128, channel_bank[chan]);
	} else {
	    iwu_mark_patch(channel_to_program[chan], channel_bank[chan]);
	}
    } else {
	if (chan == 9) {
	    fpi = iwu_find_patch(note+128, channel_bank[chan], &pl);
	    if (fpi && fpi->flags & IWU_PATCH_LOADED) {
		iw_midi_note_on(chan, note, velocity, fpi->p, 1);
	    }
	} else {
	    iw_midi_note_on(chan, note, velocity, 0L, 1);
	}
    }
}

#if defined(__BORLANDC__)
#pragma warn -par
#endif
static void iw_note_off(int chan, int note, int velocity)
{
	iw_midi_note_off(chan, note);
}
#if defined(__BORLANDC__)
#pragma warn .par
#endif

static void reset_midi(void)
{
    int i;

    for (i=0; i < 16; i++) {
	iw_midi_all_notes_off(i);
	iw_midi_parameter(i, 0x78, 0); /* all sounds off */
	iw_midi_parameter(i, 0x79, 0); /* reset all controllers */
	iw_midi_parameter(i, 100, 0); /* RPN MSB 0 */
	iw_midi_parameter(i, 101, 0); /* RPN LSB 0 */
	iw_midi_parameter(i, 6, 2); /* Pitch Bend Sensitivity MSB */
	iw_midi_parameter(i, 38, 0); /* Pitch Bend Sensitivity LSB */
    }
}

/****************************************************************************/
/* CLOCK FUNCTIONS **********************************************************/
/****************************************************************************/
static void midi_init_timers(void)
{
    if (use_iw_timers) {
	clock_interval = 10560L;
	iw_enable_timer1(clock1, 124);
    } else {
	clock_interval = 6857L; /* microseconds */
	DISABLE();
	orig_clock = (void(interrupt FAR *)(void))_dos_getvect(TIMER);
	_dos_setvect(TIMER, clock_handler);
	OUTPORTB(0x43, 0x36);
	OUTPORTB(0x40, CLOCK_DIVISOR & 0xff);
	OUTPORTB(0x40, (CLOCK_DIVISOR & 0xff00) >> 8);
	ENABLE();
    }
}

static void midi_cleanup_timers(void)
{
    if (use_iw_timers) {
	iw_disable_timer1();
    } else {
	DISABLE();
	OUTPORTB(0x43, 0x36);
	OUTPORTB(0x40, ORIG_CLOCK_DIVISOR & 0xff);
	OUTPORTB(0x40, (ORIG_CLOCK_DIVISOR & 0xff00) >> 8);
	if (orig_clock) _dos_setvect(TIMER, orig_clock);
	orig_clock = NULL;
	ENABLE();
    }
}

static void reset_timers(void)
{
    us_timer = 0;
    timer = 0;
}

static int RFAR clock1(void)
{
    unsigned short t;

    if (bg_library) {
	us_timer += clock_interval;
	if (us_timer > tempo) {
	    t = (unsigned short)(us_timer / tempo);
	    us_timer -= (t * tempo);
	    timer += t;
	}
	midi_do_something(bg_library, timer);
    }
    return(0);
}

#if defined(__WATCOMC__) && defined(__FLAT__)
#pragma aux NewStack = \
	"mov    word ptr user_ss, ss"		\
	"mov	user_sp, esp"			\
	"lss	esp, user_stack_p"		

#pragma aux OldStack = \
	"mov	ax, word ptr user_ss"		\
	"mov	ss, ax"				\
	"mov	esp, user_sp"			\
	modify [EAX]
#endif

static void interrupt clock_handler(void)
{
	/* no local variables allowed in this routine because
	** stacks are switched */
    if (!stack_in_use) {
#if defined(__BORLANDC__)
	user_ss = _SS;
	user_sp = _SP;
	_SS = FP_SEG( user_stack_p );
	_SP = FP_OFF( user_stack_p );
#elif defined(_MSC_VER)
	asm mov user_ss, ss
	asm mov user_sp, sp
	asm les di, user_stack_p
	asm mov ax, es
	asm mov ss, ax
	asm mov sp, di
#elif defined(__WATCOMC__) && defined(__FLAT__)
	NewStack();
#endif
	stack_in_use = 1;
	clock1();
	DISABLE();
	stack_in_use = 0;
#if defined(__BORLANDC__) || defined(_MSC_VER)
	asm mov ss, user_ss
	asm mov sp, user_sp
#elif defined(__WATCOMC__) && defined(__FLAT__)
	OldStack();
#endif
    }
    if (orig_clock && ++ticks == CLOCK_RATIO) {
	ticks = 0;
#ifdef __WATCOMC__
	_chain_intr(orig_clock);
#else
	(*orig_clock)();
#endif
    } else {
	OUTPORTB(0x20, 0x20);
    }
    return;
}

static void midi_set_tempo(unsigned long us, short divisor)
{
	/*
	** the tempo and divisor values are described in the Standard Midi
	** File specification 1.0
	*/
    int smpte_format, smpte_resolution;

    if (divisor > 0) {
	tempo = us / divisor;
    } else {
	smpte_format = (-divisor >> 8) & 0x7f;
	smpte_resolution = divisor & 0x7f;
	tempo = 1000000 / (smpte_format * smpte_resolution);
    }
    if (tempo <= 0) tempo = 1;
}

/****************************************************************************/
/* MIDI BACKGROUND FUNCTIONS ************************************************/
/****************************************************************************/
static void chanmessage( struct MIDILIB *ml, unsigned char c, unsigned char c1, unsigned char c2)
{
    int chan;

    chan = c & 0x0f;
    switch (c & 0xf0) {
	case note_on:
	    (*ml->Mp_note_on)(chan, c1, c2);
	    break;
	case note_off:
	    if (midi_mode == MIDI_PLAY) {
		(*ml->Mp_note_off)(chan, c1, c2);
	    }
	    break;
	case poly_aftertouch:
	    if (midi_mode == MIDI_PLAY && ml->Mp_pressure) {
		(*ml->Mp_pressure)(chan, c1, c2);
	    }
	    break;
	case control_change:
	    (*ml->Mp_parameter)(chan, c1, c2);
	    break;
	case pitch_wheel:
	    if (midi_mode == MIDI_PLAY) {
		(*ml->Mp_pitch_bend)(chan, c1, c2);
	    }
	    break;
	case program_chng:
	    (*ml->Mp_program)(chan, c1);
	    break;
	case channel_aftertouch:
	    if (midi_mode == MIDI_PLAY && ml->Mp_chanpressure) {
		(*ml->Mp_chanpressure)(chan, c1, c2);
	    }
	    break;
    }
}

/* This array is indexed by the high half of a status byte.  It's */
/* value is either the number of bytes needed (1 or 2) for a channel */
/* message, or 0 (meaning it's not  a channel message). */
static int chantype[] = {
    0, 0, 0, 0, 0, 0, 0, 0,		/* 0x00 through 0x70 */
    2, 2, 2, 2, 1, 1, 2, 0		/* 0x80 through 0xf0 */
};

static void midi_do_something(struct MIDILIB *ml, unsigned long now_time)
{
    short not_done = 0, len, needed, type, i;
    unsigned long us;
    register struct track *track;
    unsigned char c, c1, c2, check_next_time;

    if (now_time < ml->Mp_next_event_time) return;
    ml->Mp_next_event_time = ~0LU;
    for (track=ml->Mp_tracks; track; track = track->next_track) {
	check_next_time = 1;
	if (track->play_state == MPS_PLAYING) {
	    while (track->play_time <= now_time) {
		check_next_time = 0;
		if (*track->playp & 0x80) {
		    c = track->status = midi_getc(track);
		} else {
		    c = track->status;
		}
		needed = chantype[ (c>>4) & 0xf ];
		if ( needed ) {	/* is it a channel message? */
		    c1 = midi_getc(track);
		    c2 = (unsigned char)((needed>1) ? (midi_getc(track)) : ('\0'));
		    chanmessage( ml, c, c1, c2);
		} else {
		    switch ( c ) {
			case MT_meta_event:
			    type = midi_getc(track);
			    len = (short)midi_getvarinum(track);
			    switch (type) {
				case MT_set_tempo:
				    len -= 3;
/* Microsoft C6 changes a + b + c to c + b + a, so, rewrite for C6 */
#if _MSC_VER == 600
				    us = ((long)midi_getc(track)<<16);
				    us += ((long)midi_getc(track)<<8);
				    us += (long)midi_getc(track);
#else
				    us = ((long)midi_getc(track)<<16) + ((long)midi_getc(track)<<8) + (long)midi_getc(track);
#endif
				    for ( ; len > 0; len--) {
					midi_getc(track);
				    }
				    (*ml->Mp_set_tempo)(us, header.division.quarter_note);
				    break;
				default:
				    for (i=0; i < len; i++) {
					midi_getc(track);
				    }
			    }
			    break;
			case system_exclusive:
			case sysex_continue:
			    len = (short)midi_getvarinum(track);
			    for ( i=0; i < len; i++) {
				midi_getc(track);
			    }
			    break;
			default: /* whoops */
			    track->play_state = MPS_DONE;
			    break;
		    }
		}
		midi_get_next_time(ml, track);
		if (track->play_state != MPS_PLAYING) break;
	    }
	}
	if (track->play_state == MPS_PLAYING) {
	    if (check_next_time) {
		if (track->play_time < ml->Mp_next_event_time) {
		    ml->Mp_next_event_time = track->play_time;
		}
	    }
	    not_done = 1;
	}
    }
    if (not_done_playing) not_done_playing = not_done;
}

/****************************************************************************/
/* MIDI FOREGROUND FUNCTIONS ************************************************/
/****************************************************************************/
static int midi_play(struct MIDILIB *ml)
{
    struct track *track;
    unsigned long next_event_time;
    int iret;

    midi_mode = MIDI_PLAY;
    not_done_playing = 1;
	/* initialize track playback info */
    next_event_time = ~0LU;
    for (track=ml->Mp_tracks; track; track = track->next_track) {
	track->playp = track->data;
	track->position = 0L;
	track->status = 0;
	track->play_state = MPS_PLAYING;
	track->play_time = midi_getvarinum(track);
	if (track->play_time < next_event_time) {
	    next_event_time = track->play_time;
	}
    }
    ml->Mp_next_event_time = next_event_time;
    if (ml->Mp_reset_tick_counter) (*ml->Mp_reset_tick_counter)();
    if (ml->Mp_init_hardware) {
	iret = (*ml->Mp_init_hardware)(ml);
	if (iret != 0) {
	    fprintf(stderr, "Couldn't initialize hardware\n");
	    if (ml->Mp_cleanup) (*ml->Mp_cleanup)();
	    return(1);
	}
    }
    if (ml->Mp_reset_midi) (*ml->Mp_reset_midi)();
    bg_library = ml;
    if (ml->Mp_init_timers) {
	(*ml->Mp_init_timers)();
    }
    return(0);
}

static void midi_pause(void)
{
    int i;

    if (bg_library && bg_library->Mp_cleanup_timers) {
	(*bg_library->Mp_cleanup_timers)();
	if (bg_library->Mp_all_notes_off) {
	    for (i=0; i < 16; i++) {
		(*bg_library->Mp_all_notes_off)(i);
	    }
	}
    }
    bg_library = NULL;
}

static void midi_unpause(struct MIDILIB *ml)
{
    bg_library = ml;
    if (ml && ml->Mp_init_timers) {
	(*ml->Mp_init_timers)();
    }
}

static void midi_stop(struct MIDILIB *ml)
{
    if (midi_on) {
	if (ml->Mp_cleanup_timers) {
	    (*ml->Mp_cleanup_timers)();
	}
	bg_library = NULL;
	if (ml->Mp_cleanup) (*ml->Mp_cleanup)();
	midi_on = 0;
    }
}

static unsigned char midi_getc(struct track *track)
{
    if (track->position < track->length) {
	track->position++;
	return(*track->playp++);
    } else {
	track->play_state = MPS_DONE;
	return(0);
    }
}

static long midi_getvarinum(struct track *track)
{
    long value=0L;
    int c;

    do {
	c = midi_getc(track);
	value = (value << 7) + (c & 0x7f);
    } while (c & 0x80);
    return (value);
}

static void midi_get_next_time(struct MIDILIB *ml, struct track *track)
{
    if (track->play_state == MPS_PLAYING) {
	track->play_time += midi_getvarinum(track);
	if (track->play_time < ml->Mp_next_event_time) {
	    ml->Mp_next_event_time = track->play_time;
	}
    }
}

static char sequence_name[80];
static int names_found;

static void midi_check(struct MIDILIB *ml)
{
    /* This array is indexed by the high half of a status byte.  It's */
    /* value is either the number of bytes needed (1 or 2) for a channel */
    /* message, or 0 (meaning it's not  a channel message). */
    static int chantype[] = {
	0, 0, 0, 0, 0, 0, 0, 0,		/* 0x00 through 0x70 */
	2, 2, 2, 2, 1, 1, 2, 0		/* 0x80 through 0xf0 */
    };
    struct track *track;
    int not_done = 1, needed, len, i;
    unsigned char c, c1, c2, type;
    unsigned long next_event_time;
    struct iwu_fast_patch *fpi;
    struct patch_lib RFAR *pl=0;

    names_found = 0;
    iwu_unload_all_patches();
    iwu_clear_marked_patches();
    midi_mode = COLLECT_PATCHES;
    for (i=0; i < 16; i++) {
	channel_to_program[i] = 0;
	channel_bank[i] = 0;
    }
    /* initialize track playback info */
    for (track=ml->Mp_tracks; track; track = track->next_track) {
	track->playp = track->data;
	track->position = 0L;
	track->status = 0;
	track->play_state = MPS_PLAYING;
	track->play_time = midi_getvarinum(track);
    }
    /* search through all tracks and create list of instruments */
    while (not_done) {
	/* find minimum time for next event */
	not_done = 0;
	next_event_time = ~0LU;
	for (track=ml->Mp_tracks; track; track = track->next_track) {
	    if (track->play_state == MPS_PLAYING) {
		not_done = 1;
		if (track->play_time < next_event_time) {
		    next_event_time = track->play_time;
		}
	    }
	}
	/* if we were playing, we'd wait for next_event_time */
	/* wait_for() ... */
	for (track=ml->Mp_tracks; track; track = track->next_track) {
	    while (track->play_state == MPS_PLAYING &&
		   track->play_time <= next_event_time) {
		c = *track->playp;
		if (c & 0x80) {
		    c = track->status = midi_getc(track);
		} else {
		    c = track->status;
		}
		needed = chantype[ (c>>4) & 0xf ];
		if ( needed ) {	/* is it a channel message? */
		    c1 = midi_getc(track);
		    c2 = (unsigned char)((needed>1) ? (midi_getc(track)) : ('\0'));
		    chanmessage( ml, c, c1, c2 );
		} else {
		    switch ( c ) {
			case MT_meta_event:
			    type = midi_getc(track);
			    len = (short)midi_getvarinum(track);
			    switch (type) {
				case MT_sequence_name:
				    for ( i=0; i < len; i++) {
					if (i < 79 && names_found == 0) {
					    sequence_name[i] = midi_getc(track);
					} else {
					    midi_getc(track);
					}
				    }
				    for ( i=0; i < len; i++) {
					if (sequence_name[i] < 32) break;
				    }
				    if (i && i == len) {
					if (names_found == 0) sequence_name[i] = 0;
					names_found++;
				    }
				    break;
				default:
				    for ( i=0; i < len; i++) {
					midi_getc(track);
				    }
				    break;
			    }
			    break;
			case system_exclusive:
			case sysex_continue:
			    len = (short)midi_getvarinum(track);
			    for ( i=0; i < len; i++) {
				midi_getc(track);
			    }
			    break;
			default: /* whoops */
			    track->play_state = MPS_DONE;
			    break;
		    }
		}
		midi_get_next_time(ml, track);
	    }
	}
    }
    iwu_load_marked_patches(patchbuff, PATCHBUFFSIZE, 0, force8bit);
    /* initally assign all channels to piano patch */
    for (i=0; i < 16; i++) {
	fpi = iwu_find_patch(0, 0, &pl);
	if (fpi && fpi->flags & IWU_PATCH_LOADED) {
	    iw_midi_change_program(i, fpi->p);
	}
    }
}

/* private stuff */
static long to32bit(int c1,int c2,int c3,int c4);
static to16bit(int c1,int c2);
static long read32bit(struct MIDILIB *ml);
static read16bit(struct MIDILIB *ml);
static void mftrack(struct MIDILIB *ml, struct track *track);

static void mferror(int error)
{
	MidiApiError = error;
}

static void mfcleanup(struct MIDILIB *ml)
{
    struct track *track;

    if (ml) {
	while (ml->Mp_tracks) {
	    track = ml->Mp_tracks;
	    ml->Mp_tracks = ml->Mp_tracks->next_track;
	    if (track->data) farfree((void RFAR *)track->data);
	    free(track);
	}
    }
}

static void mfclose(struct MIDILIB **ml)
{
    if (*ml) {
	mfcleanup(*ml);
	free(*ml);
	*ml = 0;
    }
}

static struct MIDILIB *
mfinit(void (*errorfunc)(int))
{
	/* The midi library communicates with the application's software by
	** calling functions.  The midilibrary allocates memory for a structure
	** that the application fills in with all of the functions it can
	** fufill.  This library is operating system and machine independant.
	*/
    struct MIDILIB *ml;

    ml = (struct MIDILIB *)malloc(sizeof(struct MIDILIB));
    if (ml == NULL) {
	if (errorfunc) {
	    (void)(*errorfunc)(MIDI_API_NO_MEMORY);
	    return(0);
	}
    }
    ml->Mf_error = errorfunc;
    ml->Mf_getc = NULL;	  /* get a character (from a midi file) */
    ml->Mf_header = NULL; /* tell application about header info */
    ml->Mf_toberead = 0L;	  /* number of bytes not read yet */
    ml->Mp_tracks = NULL;
    return(ml);
}

/* read through the "MThd" or "MTrk" header string */
static readmt(struct MIDILIB *ml, char *s)
{
    int n = 0;
    char *p = s;
    int c;

    while ( n++<4 && (c=(*ml->Mf_getc)()) != EOF ) {
	if ( c != *p++ ) {
	    char buff[32];
	    if (c == 26) return(EOF);
	    _fstrcpy(buff,"Expecting ");
	    _fstrcat(buff,s);
	    _fstrcat(buff,", corrupt midi file?");
	    mferror(MIDI_API_CORRUPT_FILE);
	    return(EOF);
	}
    }
    return(c);
}

/* read a single character and abort on EOF */
static egetc(struct MIDILIB *ml)
{
    int c = (*ml->Mf_getc)();

    if ( c == EOF ) {
    	(void)mferror(MIDI_API_CORRUPT_FILE);
    }
    ml->Mf_toberead--;
    return(c);
}

/* read a header chunk */
static readheader(struct MIDILIB *ml)
{
    int format, ntrks, division;

    if ( readmt(ml, "MThd") == EOF || MidiApiError) {
	return(0);
    }

    ml->Mf_toberead = read32bit(ml);
    format = read16bit(ml);
    ntrks = read16bit(ml);
    division = read16bit(ml);
    if (MidiApiError) return(0);

    if ( ml->Mf_header ) (*ml->Mf_header)(format,ntrks,division);

	/* flush any extra stuff, in case the length of header is not 6 */
    while ( ml->Mf_toberead > 0 ) (void) egetc(ml);
    MidiApiError = 0;
    return(ntrks);
}

/* read a track chunk */
static readtrack(struct MIDILIB *ml)
{
    struct track *track;
    char HUGE *cp;

    if ( readmt(ml, "MTrk") == EOF ) return(0);

    ml->Mf_toberead = read32bit(ml);
    track = malloc(sizeof(struct track));
    if (track == NULL) {
	(void)mferror(MIDI_API_NO_MEMORY);
	return(0);
    }
    track->length = ml->Mf_toberead;
#if _MSC_VER == 600
    track->data = (void HUGE *)halloc(ml->Mf_toberead, 1);
#elif _MSC_VER > 600 
    track->data = (void HUGE *)_halloc(ml->Mf_toberead, 1);
#else
    track->data = (void HUGE *)farmalloc(ml->Mf_toberead);
#endif
    if (track->data == (void HUGE *)NULL) {
	(void)mferror(MIDI_API_NO_MEMORY);
	return(0);
    }
    cp = track->data;
    while (ml->Mf_toberead > 0) {
	*cp++ = (unsigned char)egetc(ml);
    }
    track->playp = track->data;
    track->play_time = 0L;
    track->play_state = MPS_BEGIN;
    track->next_track = NULL;
    mftrack(ml, track);
    return(1);
}

static void mfread(struct MIDILIB *ml)
{
    int ntrks;

    ml->tracknum=0;
    if ( ml->Mf_getc == NULL )
	(void)mferror(MIDI_API_DUMB_PROGRAMMER);

    ntrks = readheader(ml);
    while ( readtrack(ml) && --ntrks) ;
}

static long to32bit(int c1,int c2,int c3,int c4)
{
    long value = 0L;

    value = (c1 & 0xff);
    value = (value<<8) + (c2 & 0xff);
    value = (value<<8) + (c3 & 0xff);
    value = (value<<8) + (c4 & 0xff);
    return (value);
}

static to16bit(int c1,int c2)
{
    return ((c1 & 0xff ) << 8) + (c2 & 0xff);
}

static long read32bit(struct MIDILIB *ml)
{
    int c1, c2, c3, c4;

    c1 = egetc(ml);
    c2 = egetc(ml);
    c3 = egetc(ml);
    c4 = egetc(ml);
    return to32bit(c1,c2,c3,c4);
}

static read16bit(struct MIDILIB *ml)
{
    int c1, c2;
    c1 = egetc(ml);
    c2 = egetc(ml);
    return to16bit(c1,c2);
}

static void mftrack(struct MIDILIB *ml, struct track *track)
{
    struct track *tp;

    if (ml->Mp_tracks) {
	for (tp=ml->Mp_tracks; tp->next_track; tp = tp->next_track) ;
	tp->next_track = track;
    } else {
	ml->Mp_tracks = track;
    }
}

/* callback routine for the midi library to record header information */
static void midi_header(int format,int ntrks,int division)
{
    header.format = format;
    header.ntrks = ntrks;
    header.division.quarter_note = division;
}

static void myerror(int error)
{
    MidiApiError = error;
}

/* callback routine to read a character from the midi file */
static short mygetc(void)
{
    return(fgetc(fp_midi));
}

/*
** This routine initalizes the midifile and midiplay libraries and
** sets up all of the callback functions
*/
static void init_funcs(struct MIDILIB *ml)
{
    ml->Mf_getc = mygetc;
    ml->Mf_header = midi_header;
    ml->Mp_set_tempo = midi_set_tempo;
    ml->Mp_reset_tick_counter = reset_timers;
    ml->Mp_init_timers = midi_init_timers;
    ml->Mp_cleanup_timers = midi_cleanup_timers;
    ml->Mp_reset_midi = reset_midi;
    ml->Mp_note_on = iw_note_on;
    ml->Mp_note_off = iw_note_off;
    ml->Mp_pressure = NULL;
    ml->Mp_parameter = iw_parameter;
    ml->Mp_pitch_bend = iw_midi_pitch_bend;
    ml->Mp_program = iw_change_program;
    ml->Mp_chanpressure = NULL;
    ml->Mp_all_notes_off = iw_midi_all_notes_off;
    ml->Mp_init_hardware = NULL;
    ml->Mp_cleanup = reset_midi;
}
int InitMidi(void)
{
    int ret;

    memset(channel_to_program, 0, sizeof(int)*16);
    memset(channel_bank, 0, sizeof(int)*16);
    iw_midi_synth_volume(0, midi_synth_volume); /* range = 0 to 127 */
    ret = iwu_get_patch_config();
    if (ret) return(ret);
    return(MIDI_API_OK);
}
void DeInitMidi(void)
{
    StopMidiFile();
    iwu_free_patch_config();
}
int PlayMidiFile(char *fname)
{
    if (fname == 0) return(MIDI_API_FILE_NOT_FOUND);
    if (midi_on) {
	midi_stop(gml);
	mfclose(&gml);
    }
    fp_midi = fopen(fname, "rb");
    if (fp_midi != NULL) {
	MidiApiError = 0;
	gml = mfinit(myerror);
	if (MidiApiError) return(MidiApiError);
	init_funcs(gml);
	mfread(gml);
	if (MidiApiError) return(MidiApiError);
	fclose(fp_midi);
	midi_check(gml);	/* load patches */
	if (MidiApiError) return(MidiApiError);
	if (names_found == 1) {
//	    printf("playing %s (%s)\n", sequence_name, fname);
	} else {
//	    printf("playing %s\n", fname);
	}
	if (midi_play(gml)) return(MidiApiError);
	midi_on = 1;
    } else {
        return(MIDI_API_FILE_NOT_FOUND);
    }
    second_timer = 0L;
    return(0);
}
void StopMidiFile(void)
{
    if (gml) {
        midi_stop(gml);
	mfclose(&gml);
    }
}
void PauseMidiFile(void)
{
    midi_pause();
}
void UnPauseMidiFile(void)
{
    if (gml) {
	midi_unpause(gml);
    }
}
int PollMidi(void)
{
    return(not_done_playing);
}
