/*
 *	Filter for midi data. Make behaviour of sustain more like a piano
 *	and allocate voices in a better way.
 */

#include <stdio.h>
#include <osbind.h>
#include "midi.h"

/* forward declaration of static functions */
static vinit();
static turnoff();

#define keywait() while (!kbddatap())

main()
{
	/* set autowrap on console */
	printf("\033v");
	fflush(stdout);

	vinit();
	initmidi();
	midiparse();
	switch (Bconin(CON)) {
	case 'd':
		diagnose();
	}
	keywait();
	exit(0);
}

/*
 *	We now need:
 *	active()		active sensing
 *	keyon(note, velocity)	key on/off
 *	control(ctrlno, code)	control change
 *	program(progno)		program change
 *	to do what you want when those commands are received.
 */

active()
{
	midiout(ACTIVE);	/* I'm still here too! */
}

program(progno)
{
	/* not interested in this. */
}

/*
 *	Functions to deal with various sorts of command byte.
 *
 *	Terms:
 *	key:	corresponds to a physical key on the keyboard
 *	pitch:	one of the 88 notes.
 *	voice:	one of the 16 sound generators
 */
#define NPITCHES 128
#define NVOICES 10

static int sustained = 0;	/* is the sustain pedal down? */

/*
 *	Now the hairy bit. voice allocation and management
 *
 *	Data structures:
 *	
 *	We have 16 structures, one for each voice, recording what pitch
 *	each voice is playing (if any).
 *
 *	Each voice structure is linked into either
 *	the freelist if it is silent or
 *	the usedlist if the voice is sounding.
 *
 *	The freelist is a singly linked list strung between freelist, the
 *	pointer to the head of the list, and freetail.
 *	the v_flink field is used for the links.
 *	If there are no free voices, freelist == NULL.
 *	Items are put on the tail and added to the head so that voices are
 *	used in rotation and the device can decay the voice if it wants to.
 *
 *	The usedlist is a doubly linked list using the v_flink and v_blink
 *	fields, between usedlist and usedtail. Usedlist == NULL if there are
 *	no active voices.
 *	The elements of the usedlist are kept in order of age, according to
 *	when the note was pressed. The oldest is at the head.
 */

typedef struct voice {
	struct voice *v_flink;		/* next element in free or used list */
	struct voice *v_blink;		/* previous element in used list */
	int v_pitch;			/* the pitch when it is sounding. */
	struct voice *v_sounding;	/* other voices on the same pitch */
} voice_t;

static voice_t *freelist = NULL, *freetail;	/* head and tail of free voice list */
static voice_t *usedlist = NULL, *usedtail;	/* head of list of active voices */

/*
 *	For each pitch, we need a linked list to tell you which voices
 *	are sounding this note, for when you receive a key off message
 *	and to work out whether there are 2 voices sounding the same pitch.
 *	Status remembers whether the key is fingered and whether it is under
 *	the influence of the key hold foot switch,
 */
typedef struct pitch {
	voice_t *p_sounding;
	voice_t *p_tail;	/* tail of sounding list */
	char p_status;
} pitch_t;

static pitch_t pitch[NPITCHES];

#define FINGERED 1
#define KEYHELD 2

static
vinit()
{
	/* an array out of which to build the free list */
	static voice_t voice[NVOICES];
	register voice_t *vp;
	
	/* build a free list out of them */
	for (vp = voice; vp < &voice[NVOICES-1]; vp++) {
		vp->v_flink = vp+1;
	}
	voice[NVOICES-1].v_flink = NULL;
	freelist = &voice[0];
	freetail = &voice[NVOICES-1];
}

/*
 *	Put the indicated voice structure onto the tail of the free list
 */
static
addfree(vp)
voice_t *vp;
{
	vp->v_flink = NULL;
	if (freelist == NULL) {
		freelist = freetail = vp;
		return;
	}
	freetail->v_flink = vp;
	freetail = vp;
	return;
}

/*
 *	Add a voice to the tail of the used list
 */
static
addused(vp)
voice_t *vp;
{
	if (usedlist == NULL) {
		usedlist = usedtail = vp;
		vp->v_flink = NULL;
		vp->v_blink = NULL;
	} else {
		vp->v_flink = NULL;
		vp->v_blink = usedtail;
		usedtail->v_flink = vp;
		usedtail = vp;
	}
}
/*
 *	Remove vp from the used list
 */
static
remused(vp)
voice_t *vp;
{
	if (vp->v_blink == NULL) usedlist = vp->v_flink; /* was head */
	else vp->v_blink->v_flink = vp->v_flink;

	if (vp->v_flink == NULL) usedtail = vp->v_blink; /* was tail */
	else vp->v_flink->v_blink = vp->v_blink;
}

/*
 *	Oh dear, I was trying to put this off. Right.
 *
 *	Voice allocation strategy:
 *
 *	1st choice: a voice which is silent (on the free list)
 *	--all voices are sounding (pressed and/or sustained)--
 *	2nd choice: the oldest unfingered voice whose pitch is also being
 *		sounded by another voice.
 *	--all voices are busy on different pitches--
 *	3rd choice: the oldest unfingered voice (a la Yamaha).
 *	--all voices are busy and fingered (wow!)--
 *	4th choice: hum. have to think about this one. Oldest voice?
 *		Newest voice? second newest voice (for held accomp with melody)
 */

voice_t *
valloc()
{
	register voice_t *vp;

	/* 1st choice: one from the free list */
	if (freelist) {
		vp = freelist;
		freelist = vp->v_flink;	/* faster than freelist->v_link */
		return(vp);
	}

	/* 2nd choice: oldest voice whose pitch is duplicated */
	for (vp = usedlist; vp != NULL; vp = vp->v_flink) {
		/*
		 * A voice is duplicated if there are two voices on the list
		 * dangling down from the sounding array entry for its pitch.
		 * Because the usedlist and the sounding lists are kept in
		 * oldest first order, it will be at the head of its sounding
		 * list.
		 * Here is some code to ckeck if you don't believe it!
		if (pitch[vp->v_pitch].p_sounding != vp) {
			fprintf(stderr, "Boom!\n");
			diagnose();
			keywait();
			exit(1);
		}		
		 */
		if (vp->v_sounding != NULL) {
			/* found one! */
found:			midi3out(KEYON, vp->v_pitch, 0);
			/*
			 * Detach from sounding list.
			 * It must be the first element, remember?
			 */
			pitch[vp->v_pitch].p_sounding = vp->v_sounding;
			remused(vp);
			/* no point in putting back on free list */
			return(vp);
		}
	}

	/* 3rd choice - oldest unfingered voice */
	/* thinks: maintain a list of unfingered voices? */
	for (vp = usedlist; vp != NULL; vp++) {
		if (!(pitch[vp->v_pitch].p_status & FINGERED)) {
			/* found one. */
			goto found;
		}
	}

	/* unfinished */
	puts("No free voices.");
	diagnose();
	keywait();
	exit(1);
}

keyon(keyno, velocity)
{
	register voice_t *vp;
	register pitch_t *pp;

	if (velocity) {
		/* a new key */
		vp = valloc();
		/* sound note asap */
		midi3out(KEYON, keyno, velocity);
		
		/* set up data structures accordingly */
		vp->v_pitch = keyno;
		/* tack it onto the end of the used list */
		addused(vp);
		/* and the appropriate sounding list,
		 * at the tail to preserve order */
		vp->v_sounding = NULL;
		pp = &pitch[keyno];
		if (pp->p_sounding == NULL) {
			pp->p_tail = pp->p_sounding = vp;
		} else {
			pp->p_tail->v_sounding = vp;
			pp->p_tail = vp;
		}
		pp->p_status |= FINGERED;
	} else {
		/* key off */
		/*
		 * As we are doing the sustaining ourselves,
		 * we may not actually turn it off.
		 */

		pp = &pitch[keyno];
		pp->p_status &= ~FINGERED;
		if (!sustained && (pp->p_status & KEYHELD) == 0) {
			/* the key is not held in any way */
			turnoff(keyno);
		}
	}
}

/*
 *	turn a key off by natural causes
 */
static
turnoff(keyno)
{
	register pitch_t *pp = &pitch[keyno];
	register voice_t *vp;
	/*
	 * Trace down the sounding list of the pitch,
	 * deleting as you go.
	 */
	for (vp = pp->p_sounding; vp != NULL; vp = vp->v_sounding) {
		/* turn the note off */
		midi3out(KEYON, keyno, 0);
		remused(vp); addfree(vp);
	}
	pp->p_sounding = NULL;	/* ready for rebuilding */
	/* pp->p_status must == 0 */
}	

control(par1, par2)
{
	register pitch_t *pp;
	register voice_t *vp;
	register int *ip;

	switch (par1) {
	case 1:	/* tremolo depth */
	case 4:	/* tremolo speed */
	case 67:/* soft pedal */
	case 92:/* tremolo */
	case 93:/* chorus */
		break;
	case SUSTAIN:	/* sustain */
		sustained = par2;
		if (!sustained) {
			/* look for notes to turn off */
			for (pp = pitch; pp < &pitch[NPITCHES]; pp++) {
				if (pp->p_sounding && pp->p_status == 0) {
					/* not held in any way */
					turnoff(pp - pitch);
				}
			}
		}
		break;
	case KEYHOLD:	/* key hold */
		switch (par2) {
			/* remember which hels are held for quick turn off */
			static int keysheld[NPITCHES];
			static int nkeysheld = 0;

		default: /* footswitch pressed; hold all fingered keys */
			for (vp = usedlist; vp != NULL; vp=vp->v_flink) {
				pp = &pitch[vp->v_pitch];
				/* == ensures all entries in keysheld unique */
				if (pp->p_status == FINGERED) {
					pp->p_status = FINGERED|KEYHELD;
					keysheld[nkeysheld++] = pp-pitch;
				}
			}
			break;
		case 0:	/* footswitch released; clear all keyhold bits */
			for (ip=&keysheld[nkeysheld-1]; ip>=keysheld; ip--) {
				pp = &pitch[*ip];
				if (pp->p_status == (KEYHELD | FINGERED)) {
					pp->p_status = FINGERED;
				} else {
					pp->p_status = 0;
					turnoff(*ip);
				}
			}
			nkeysheld = 0;
			break;
	}
		break;
	default:
		fprintf(stderr, "Bad par1 to control %02x %02x %02x ??\n",
			CONTROL, par1, par2);
	}
}

diagnose()
{
	register voice_t *vp;
	register int i;

	for (vp = freelist, i=0; vp != NULL; vp=vp->v_flink, i++);
	printf("FREELIST: %d items.\n", i);
	printf("USEDLIST:");
	for (vp = usedlist, i=0; vp != NULL; vp=vp->v_flink, i++) {
		printf(" %02x", vp->v_pitch);
		if (vp->v_flink == NULL && vp != usedtail) {
			puts(" NULL not at tailptr");
			break;
		}
		if (vp == usedtail && vp->v_flink != NULL) {
			fputs(" (tailptr)", stdout);
		}
	}
	printf(" (%d items)\n", i);
	
	puts("PITCH:");
	for (i=0; i<NPITCHES; i++) {
		if (pitch[i].p_status & FINGERED) {
			if (pitch[i].p_sounding == NULL) {
				printf("%d fingered but no sounding\n", i);
			}
		}
		if ((vp = pitch[i].p_sounding) != NULL) {
			printf("%02x", i);
			for ( ; vp != NULL; vp = vp->v_sounding) {
				printf(" %02x", vp->v_pitch);
			}
			putchar('\n');
		}
	}
}
