/* phase2.c -- this module plays notes compiled and sorted in phase1 */

#include <stdio.h>
#include <ctype.h>
#include <malloc.h>
#include "cext.h"
#include "adagio.h"
#include "userio.h"
#include "cmdline.h"
#include "ma.h"
#include "pitch.h"
#include "midicode.h"
#include "midi.h"

#define MAXTIME 1000000

#define n_t_sw 2
private char *t_switches[n_t_sw] = { "-t", "-trace" };
#define nmsw 2
private char *msw[nmsw] = { "-i", "-init" };
private boolean initflag = false;

private long time = 0;	/* time clock */
private long lasttime = 0;	/* time clock */
private boolean readable = false;
private int program[num_voices];	/* current program */
private int user_scale = false; /* true if user-defined scale */
private int bend[num_voices];	/* current pitch bend on channel */
private pitch_table pit_tab[128];	/* scale definition */

/****************************************************************************
* Routines local to this module
****************************************************************************/
private	void	off_init();
private void	tuninginit();
private void	read_tuning();
private	boolean	note_offs();
private long	next_off();
private	void	off_schedule();
private void	f_note();
private void	f_touch();
private void	f_program();
private void	f_ctrl();
private void	f_bend();


/****************************************************************************
*				    phase2
* Inputs:
*	event_type root: Root of play list
* Effect: 
*	Plays the music
****************************************************************************/

void phase2(score)
    event_type score;
{	
    event_type event = score;	/* pointer to next note or control event */
    short done = false;
    long offtime = 0;		/* time  for next note off*/
    int i;			/* index counter to initialize channels */


    readable = (cl_nswitch(t_switches, n_t_sw) != NULL);
    initflag = (cl_nswitch(msw, nmsw) != NULL);

    off_init();
    tuninginit();  

    /* Initialize all midi channels with reasonable start values: */
    for (i = 1; i <= num_voices; i++) {
	program[i - 1] = 1;
	bend[i - 1] = 1 << 13;
	if (!initflag) continue;
	f_program(i, 1);
	f_bend(i, 1 << 13);
	f_touch(i, 0);
	f_ctrl(i, PORTARATE, 99);
	f_ctrl(i, PORTASWITCH, 0);
	f_ctrl(i, MODWHEEL, 0);
	f_ctrl(i, FOOT, 99);
    }

    while (event != NULL) { /* play it, Sam */

	time = event->ntime;
	note_offs(time);

	if (is_note(event)) { /* play a note */
		    /* check for correct program (preset) */
		    if (event->u.note.nprogram != program[event->nvoice]) {
			f_program(event->nvoice+1, event->u.note.nprogram);
			 program[event->nvoice] = event->u.note.nprogram;
		    }
		    /* if it is a note (not a rest) play it */
		    if (event->u.note.npitch != NO_PITCH) {
			f_note(event->nvoice+1, event->u.note.npitch,
				  event->u.note.nloud);
			off_schedule(event->ntime + event->u.note.ndur,
				     event->nvoice, event->u.note.npitch);
		    }
	} else {	/* send control info */
		    switch (vc_ctrl(event->nvoice)) {
			case 1: f_ctrl(vc_voice(event->nvoice) + 1,
					     PORTARATE,
					     event->u.ctrl.value);
				break;
			case 2: f_ctrl(vc_voice(event->nvoice) + 1,
					     PORTASWITCH,
					     event->u.ctrl.value);
				break;
			case 3: f_ctrl(vc_voice(event->nvoice) + 1,
					     MODWHEEL,
					     event->u.ctrl.value);
				break;
			case 4: f_touch(vc_voice(event->nvoice) + 1,
					   event->u.ctrl.value);
				break;
			case 5: f_ctrl(vc_voice(event->nvoice) + 1,
					     FOOT,
					     event->u.ctrl.value);
				break;
			case 6: f_bend(vc_voice(event->nvoice) + 1,
					  event->u.ctrl.value << 6);
				break;
			default: break;
		    }
	}

	event = event->next;
    } /* play it, Sam */

    note_offs(MAXTIME);
}


/* noteoff.c -- this module keeps track of pending note offs for adagio */

/*****************************************************************************
*	    Change Log
*  Date	    | Change
*-----------+-----------------------------------------------------------------
* 31-Dec-85 | Created changelog
* 31-Dec-85 | Add c:\ to include directives
*  1-Jan-86 | Declare malloc char * for lint consistency
* 21-Jan-86 | note_offs can now turn off more than one note per call
*****************************************************************************/


/* off_type is a structure containing note-off information */

typedef struct off_struct {
    long when;
    int voice;
    int pitch;
    struct off_struct *next;
} *off_type;

private off_type free_off;		/* free list of off_type structures */
private off_type off_events = NULL;	/* active list */
extern char * malloc();

/****************************************************************************
*	Routines declared in this module
****************************************************************************/

private off_type	off_alloc();
private void		off_free();

/****************************************************************************
*				note_offs
* Inputs:
*	long time: the current time
* Outputs:
*	return true if off list has more notes 
* Effect: turn off notes if it is time 
* Assumes:
* Implementation:
*	Find scheduled note off events in off_events, compare with time
****************************************************************************/

private boolean note_offs(mtime)
long mtime;
{
    off_type temp;
    while (off_events != NULL && (time=off_events->when) <= mtime) {
	f_note((off_events->voice) + 1, off_events->pitch, 0);
	temp = off_events;
	off_events = off_events->next;
	off_free(temp);
    }
    if (mtime < MAXTIME) time = mtime;
    return (off_events != NULL);
}

/****************************************************************************
*				off_alloc
* Outputs:
*	returns off_type: an allocated note off structure
* Effect:
*	allocates a structure using malloc
****************************************************************************/

private off_type off_alloc()
{
    return (off_type) malloc(sizeof(struct off_struct));
}

/****************************************************************************
*				off_free
* Inputs:
*	off_type off: a structure to deallocate
* Effect: 
*	returns off to freelist
****************************************************************************/

private void off_free(off)
    off_type off;
{
    off->next = free_off;
    free_off = off;
}

/****************************************************************************
*				off_init
* Effect: initialize this module
* Assumes:
*	only called once, otherwise storage is leaked
****************************************************************************/

private void off_init()
{
    int i;
    for (i = 0; i < 50; i++) off_free(off_alloc());
}

/****************************************************************************
*				off_schedule
* Inputs:
*	long offtime: time to turn note off
*	int voice: the midi channel
*	int pitch: the pitch
* Effect: 
*	schedules a note to be turned off
* Assumes:
*	note_offs will be called frequently to actually turn off notes
****************************************************************************/

private void off_schedule(offtime, voice, pitch)
    long offtime;
    int voice, pitch;
{
    off_type off, ptr, prv;
    /* allocate off */
    if ((off = free_off) == NULL) {
	off = off_alloc();
    } else free_off = off->next;

    if (off == NULL) {
	fprintf(stderr, "out of space for note off events");
	exit(1);
    }

    off->when = offtime;
    off->voice = voice;
    off->pitch = pitch;
    /* insert into list of off events */
    ptr = off_events;
    if (ptr == NULL || offtime <= ptr->when) {
	off->next = ptr;
	off_events = off;
    } else {
	while (ptr != NULL && offtime > ptr->when) {
	    prv = ptr;
	    ptr = ptr->next;
	}
	prv->next = off;
	off->next = ptr;
    }
/*
 *    printf("off_schedule(%ld, %d, %d): \n", offtime, voice, pitch);
 *    for (ptr = off_events; ptr != NULL; ptr = ptr->next) {
 *	printf("    %ld: %d, %d\n", ptr->when, ptr->voice, ptr->pitch);
 *    }
 */
}

WriteVarLen (value)
register long value;
{
	register long buffer;

	buffer = value & 0x7f;
	while ((value >>= 7) > 0)
	{
		buffer <<= 8;
		buffer |= 0x80;
		buffer += (value & 0x7f);
	}

	while (true)
	{
		putchar(buffer);
		if (buffer & 0x80)
			buffer >>= 8;
		else
			break;
	} 
}
/*
@@@@

Delta Time(decimal)  Event Code (hex)   Other Bytes (decimal)	
	Comment
	0	FF 58	04 04 02 24 08	4 bytes: 4/4 time, 24 MIDI 
clocks/click, 
				8 32nd notes/24 MIDI clocks
	0	FF 51	03 500000	3 bytes: 500,000 5sec per quarter-note
	0	C0	5	Ch. 1, Program Change 5
	0	C0	5	Ch. 1, Program Change 5
	0	C1	46	Ch. 2, Program Change 46
	0	C2	70	Ch. 3, Program Change 70
	0	92	48  96	Ch. 3 Note On C2, forte
	0	92	60  96	Ch. 3 Note On C3, forte
	96	91	67  64	Ch. 2 Note On G3, mezzo-forte
	96	90	76  32	Ch. 1 Note On E4, piano
	192	82	48  64	Ch. 3 Note Off C2, standard
	0	82	60  64	Ch. 3 Note Off C3, standard
	0	81	67  64	Ch. 2 Note Off G3, standard
	0	80	76  64	Ch. 1 Note Off E4, standard
	0	FF 2F	00	Track End

The entire format 0 MIDI file contents in hex follow.  First, the header 
chunk:

		4D 54 68 64 	MThd
		00 00 00 06	chunk length
		00 00 	format 0
		00 01	one track
		00 60 	96 per quarter-note

Then, the track chunk.  Its header, followed by the events (notice that 
running status is used in places):

		4D 54 72 6B	MTrk
		00 00 00 3B	chunk length (59)

	Delta-time	Event	Comments
	00	FF 58 04 04 02 18 08 	time signature
	00	FF 51 03 07 A1 20	tempo
	00	C0 05
	00	C1 2E
	00	C2 46
	00	92 30 60 
	00	3C 60	running status
	60	91 43 40
	60	90 4C 20
	81 40	82 30 40	two-byte delta-time
	00	3C 40	running status
	00	81 43 40
	00	80 4C 40
	00	FF 2F 00	end of track

@@@@
*/

deltatime()
{
	WriteVarLen(time - lasttime);
	lasttime = time;
}

/****************************************************************************
*				   f_note
* Inputs:
*	int channel: midi channel on which to send data
*	int pitch: midi pitch code
*	int velocity: velocity with which to sound it (0=> release)
* Effect: 
*	Prints a midi note-play request out
****************************************************************************/

private void f_note(channel, pitch, velocity)
    int channel, pitch, velocity;
{
    if (readable)
    printf("Time=%d  Note on, chan=%d pitch=%d vol=%d\n",
	    time, channel, pitch, velocity);
    else {
	deltatime();
	putchar(NOTEON + channel - 1);
	putchar(pitch);
	putchar(velocity);
    }

    if (user_scale) {
	/* check for correct pitch bend */
	if ((pit_tab[pitch+12].pbend != bend[MIDI_CHANNEL(channel)]) &&
	    (velocity != 0)) {
	    f_bend(channel, pit_tab[pitch+12].pbend);
	    bend[channel] = pit_tab[pitch+12].pbend;
	}
	pitch = pit_tab[pitch+12].ppitch;
    }
}

/****************************************************************************
*				   f_bend
* Inputs:
*	int channel: midi channel on which to send data
*	int value: pitch bend value
* Effect: 
*	Prints a midi pitch bend message
****************************************************************************/

private void f_bend(channel, value)
    int channel, value;
{
    if (readable)
	printf("Time=%d  Pitchbend, chan=%d value=%d\n",
		time, channel, value);
    else {
	deltatime();
	putchar(PITCHBEND + channel - 1);
/* are these bytes in right order? */
	putchar(value & 0x7f);
	putchar((value>>7) & 0x7f);
    }

	bend[MIDI_CHANNEL(channel)] = value;
}

/****************************************************************************
*				   f_ctrl
* Inputs:
*	int channel: midi channel on which to send data
*	int control: control number
*	int value: control value
* Effect: 
*	Prints a midi control change message
****************************************************************************/

private void f_ctrl(channel, control, value)
    int channel, control, value;
{
    if (readable)
	printf("Time=%d  Parameter, chan=%d ctrl=%d value=%d\n", 
		time, channel, control, value);
    else {
	deltatime();
	putchar(CONTROLLER + channel - 1);
	putchar(control);
	putchar(value);
    }
}

/****************************************************************************
*				 f_program
* Inputs:
*	int channel: Channel on which to send midi program change request
*	int program: Program number to send (decremented by 1 before
*			being sent as midi data)
* Effect: 
*	Prints a program change request out the channel
****************************************************************************/

private void f_program(channel, program)
    int channel;	/* midi channel */
    int program;	/* the program number */
{
    if (readable)
	printf("Time=%d  Program, chan=%d program=%d\n",
		time, channel, program);
    else {
	deltatime();
	putchar(PROGRAM + channel - 1);
	putchar(program - 1);
    }
}

/****************************************************************************
*				   f_touch
* Inputs:
*	int channel: midi channel on which to send data
*	int value: control value
* Effect: 
*	Prints a midi after touch message
****************************************************************************/

private void f_touch(channel, value)
    int channel, value;
{
    if (readable)
	printf("Time=%d  Channel pressure, chan=%d value=%d\n",
		time, channel, value);
    else {
	deltatime();
	putchar(CHANPRESSURE + channel - 1);
	putchar(value);
    }
}

/*****************************************************************
*			set_pitch_default
*****************************************************************/
private void set_pitch_default()
{
    int i;

    for (i = 0; i < 128; i++) {
	pit_tab[i].pbend = 8192;
	pit_tab[i].ppitch = i;
    }
}

/*****************************************************************
*			read_tuning
*****************************************************************/

private void read_tuning(filename)
    char *filename;
{
    int index, pit, lineno = 0;
    float bend;
    FILE *fpp;

    user_scale = true;
    set_pitch_default();
    fpp = fileopen(filename, "tun", "r", "Tuning definition file");
    while ((fscanf(fpp, "%d %d %f\n", &index, &pit, &bend) > 2) &&
	   (lineno < 128)) {
	lineno++;
	if (index >= -12 && index <= 115) {
	    pit_tab[index+12].pbend = (int)(8192 * bend/100 + 8192);
	    pit_tab[index+12].ppitch = pit;
	}
    }
}


/****************************************************************************
*				   tunginginit
* Effect: 
* Read tuning file
****************************************************************************/

private void tuninginit()
{
    int i;
    char *filename;

    filename = cl_option("-tune");
    if (filename != NULL) {
	    read_tuning(filename);
    }
/*
    if (user_scale) {
	for (i = 0; i < num_voices; i++) {
	    f_bend(i+1, 8192);
	    bend[i] = 8192;
	}
    }
*/

}
