/* record.c -- keyboard to adagio recorder
 *
 * the interface consists of three routines:
 *	    rec_init()		-- initialization
 *	int rec_poll(long time) -- called during recording, returns true if
 *					recording space is exhausted
 *	    rec_final()		-- called to finish up
 */

/*****************************************************************************
*	    Change Log
*  Date	    | Change
*-----------+-----------------------------------------------------------------
* 27-Feb-86 | Created changelog
*	    | Use pedal information when computing durations (code taken
*	    |  from transcribe.c)
* 23-Mar-86 | Determine size of transcription when rec_init is called.
* 21-May-86 | Major rewrite to use continuous controls (code taken 
*	    |  from transcribe.c)
*****************************************************************************/

#include "cext.h"
#include "stdio.h"
#include "mpu.h"
#include "userio.h"
#include "midicode.h"
#include "record.h"
		
extern long space;	/* how much space is left? */

int debug_rec = false;	/* verbose debug flag for this module */
int max_notes = -1;	/* -1 is flag that space must be allocated */

/****************************************************************
* data structure notes: the midi stream is stored as an array 
* of 4-byte records, each of which is either a time or midi
* data.	 Midi data always begins with a control byte (high
* order bit set), and it is assumed times are positive (high
* order bit clear), so the two are easy to distinguish
* IF THE COMPILER PUTS THESE BITS IN THE SAME PLACE.  It looks
* like the high order byte of the time lines up with the last
* byte of a 4 byte array, so we will always set the high order
* bit of the last array byte when the first 3 bytes are filled
* with MIDI data.  This is refered to as the "tag" bit.
* WARNING: Lattice C longs are UNSIGNED, therefore always
* positive.  Test the high order bit with a mask.
****************************************************************/

#define MIDI_CMD_BIT		0x80
#define HIGH_BIT		0x80000000
#define istime(note) (!(((note)->when) & HIGH_BIT))

#define ndsw 2
private char *dsw[ndsw] = { "-d", "-debug" };

typedef union note_struct {
	byte n[4];
	long when;
} *note_type, note_node;

private note_type event_buff;	/* pointer to allocated buffer */
private FILE *fp;
private char file_name[100];
private note_type next;	/* pointer to next entry in buffer */
private note_type last;	/* pointer to last entry in buffer */
private int pile_ups;	/* inner loop iteration count */
private int max_pile_ups;	/* maximum of pile_ups */

/****************************************************************************
*	Routines local to this module
****************************************************************************/
private void	bend_filter();
private void	byteorder();
private void	ctrl_filter();
private int	event_bend();
private void	filter();
private long	getdur();
private long	getnext();
private char	map_ctrl();
private void	output();
private void	put_pitch();

/****************************************************************************
*				bend_filter
* Inputs:
*	note_type note: the current note
*	note_type last: the last recorded event
*	long now: the current time
* Effect:
*	remove pitch bend events in same 0.01 sec time slot
* Implementation:
*	If the current event is a pitch bend that bends again
*	in the same time slot, make it a no-op by replacing it with
*	the time.
****************************************************************************/

private void bend_filter(note, last, now)
    note_type note;	/* current note */
    note_type last;	/* the last recorded event */
    long now;		/* the current time */
{
    /* first see if there is another bend in this time
     * slot.
     */
    note_type note2 = note + 1;
    while (note2 < last) {
	if (istime(note2) && (note2->when > now)) {
	    break; /* new time slot */
	} else if (note->n[0] == note2->n[0]) {
	    note->when = now;
	    return; /* found another bend */
	}
	note2++;
    }
}

/****************************************************************************
*				byteorder
* Effect: 
*	check out assumptions about byte order and placement
****************************************************************************/

private void byteorder()
{
    if ((sizeof(event_buff[0]) != 4) ||
	(sizeof(event_buff[0].when) != 4) ||
	(sizeof(event_buff[0].n[0]) != 1)) {
	fprintf(stderr, "implementation error: size problem\n");
	exit(1);
    }
    event_buff[0].n[0] = 0x12;
    event_buff[0].n[1] = 0x34;
    event_buff[0].n[2] = 0x56;
    event_buff[0].n[3] = 0x78;
    if ((event_buff[0].when != 0x78563412) &&
	(event_buff[0].when != 0x12345678)) {
	fprintf(stderr, "implementation error: layout problem\n");
	exit(1);
    }
}

/****************************************************************************
*				ctrl_filter
* Inputs:
*	note_type note: the current note
*	note_type last: the last recorded event
*	long now: the current time
* Effect:
*	remove ctrl change events in same 0.01 sec time slot
* Implementation:
*	If the current event is a control change that changes again
*	in the same time slot, make it a no-op by replacing it with
*	the time.
****************************************************************************/

private void ctrl_filter(note, last, now)
    note_type note;	/* the current note */
    note_type last;	/* the last recorded event */
    long now;		/* the current time */
{
    /* see if there is another control change in this time
     * slot.
     */
    note_type note2 = note+1;
    while (note2 < last) {
	if (istime(note2) && (note2->when > now)) {
	    break;	/* new time slot */
	} else if ((note->n[0] == note2->n[0]) &&
		   (note->n[1] == note2->n[1])) {
	    note->when = now;
	    return; /* found another change */
	}
	note2++;
    }
}

/****************************************************************************
*				event_bend
* Inputs:
*	note_type note: pointer to a pitch bend event
* Outputs:
*	returns int: an 8 bit pitch bend number
****************************************************************************/

private int event_bend(note)
    note_type note;
{
    return (int) (((note->n[1]) >> 6) + ((note->n[2]) << 1)); 
}

/****************************************************************************
*				filter
* Inputs:
*	note_type last: the last note recorded
* Effect: allow only one control change per time slot (0.01 sec)
* Implementation:
*	call ctrl_filter and bend_filter to overwrite control changes with
*	noop data (the current time is used as a noop)
****************************************************************************/

private void filter(last)
    note_type last;
{
    note_type note;	/* loop control variable */
    long now;		/* last time seen */
    int command;	/* command pointed to by note */
    int chan;		/* channel pointed to by note */

    for (note = event_buff; note <= last; note++) {
	if (istime(note)) {
	    now = note->when;
	} else {
	    command = note->n[0] & MIDI_CODE_MASK;
	    chan = note->n[0] & MIDI_CHN_MASK;

	    if (command == MIDI_CTRL &&
		note->n[1] == SUSTAIN) {
		/* do nothing */;
	    } else if (command == MIDI_CTRL) {
		ctrl_filter(note, last, now);
	    } else if (command == MIDI_TOUCH) {
		bend_filter(note, last, now);	/* bend and touch use the */
	    } else if (command == MIDI_BEND) {	/*  same filter routines  */
		bend_filter(note, last, now);
	    }
	}
    }
}


/****************************************************************************
*				getdur
* Inputs:
*	int i: index of the note
*	note_type last: pointer to the last event recorded
*	int ped: true if pedal is down at event i
*	long now: the time at event i
* Outputs:
*	returns long: the duration of note i
* Assumes:
*	assumes i is a note
* Implementation:
*	This is tricky because of pedal messages.  The note is kept on by
*	either the key or the pedal.  Keep 2 flags, key and ped.  Key is
*	turned off when a key is released, ped goes off and on with pedal.
*	Note ends when (1) both key and ped are false, (2) key is
*	pressed (this event will also start another note).
****************************************************************************/

private long getdur(i, last, ped, now)
    int i;
    note_type last;
    int ped;
    long now;
{
    int key = true;	/* flag that says if note is on */
    long start = now;
    int chan = event_buff[i].n[0] & MIDI_CHN_MASK;
    int pitch = event_buff[i].n[1];
    note_type note = &(event_buff[i+1]);
    int noteon; /* true if a noteon message received on chan */
    int keyon;	/* true if noteon message had non-zero velocity */

    /* search from the next event (i+1) to the end of the buffer:
     */
    for (; note < last; note++) {
	if (istime(note)) {
	    now = note->when;
	} else {
	    noteon = keyon = false;
	    if ((note->n[0] & MIDI_CHN_MASK) == chan) {
		noteon = ((note->n[0] & MIDI_CODE_MASK) == MIDI_ON_NOTE) &&
		     (note->n[1] == pitch);
		keyon = noteon && (note->n[2] != 0);
		if ((noteon && (note->n[2] == 0)) ||
		    (((note->n[0] & MIDI_CODE_MASK) == MIDI_OFF_NOTE) &&
		     (note->n[1] == pitch))) key = false;
		if (((note->n[0] & MIDI_CODE_MASK) == MIDI_CTRL) &&
		    note->n[1] == SUSTAIN && note->n[2] == 127) ped = true;
		if (((note->n[0] & MIDI_CODE_MASK) == MIDI_CTRL) &&
		    note->n[1] == SUSTAIN && note->n[2] == 0) ped = false;

		if ((!key && !ped) || keyon)
		    return now - start;
	    }
	}
    }
    return last->when - start;
}

/****************************************************************************
*				getnext
* Inputs:
*	int i: the index of the current note
*	note_type last: pointer to last valid data
*	long now: the current time
* Outputs:
*	returns long: the time of the next note, program, or control change
*		(returns time of last event if nothing else is found)
****************************************************************************/

private long getnext(i, last, now)
    int i;	/* the index of the current note */
    note_type last;	/* pointer to last valid data */
    long now;	/* the current time */
{
    i++;	/* advance to next item */
    for (; event_buff + i < last; i++) {
	note_type note = &(event_buff[i]);
	int cmd = note->n[0] & MIDI_CODE_MASK;

	if (istime(note)) {
	    now = note->when;
	} else if (((cmd == MIDI_ON_NOTE) &&
		    (note->n[2] != 0)) /* note on */ ||
		   (cmd == MIDI_CH_PROGRAM) /* program change */ ||
		   ((cmd == MIDI_CTRL) &&
		    (note->n[1] != SUSTAIN) /* control change */ ) ||
		   (cmd == MIDI_TOUCH) ||
		   (cmd == MIDI_BEND)) {
	    return now;
	}
    }
    return last->when;
}

/****************************************************************************
*				map_ctrl
* Inputs:
*	int control: a midi control number
* Outputs:
*	returns char: an adagio control change command letter, NULL if
*		control change is not one of PORTARATE, PORTASWITCH,
*		MODWHEEL, FOOT
****************************************************************************/

private char map_ctrl(control)
    int control;
{
    switch (control) {
	case PORTARATE:		return 'J';
	case PORTASWITCH:	return 'K';
	case MODWHEEL:		return 'M';
	case FOOT:		return 'X';
	default:		return NULL;
    }
    return NULL;	/* make Lattice C type cheker happy */
}

/****************************************************************************
*				output
* Inputs:
*	FILE *fp: an opened file pointer
*	note_type last: the last data in the buffer
*	boolean absflag: set to true if first line of the adagio score should
*		include the absolute time
* Effect: 
*	write adagio file using data in event_buff
* Implementation:
*	NOTE: put all program changes in rests
*	use N(ext) notation for all timing
*	output no more than one continuous parameter change per
*	clock tick for each continuous change parameter
****************************************************************************/

private void output(fp, last, absflag)
    FILE *fp;
    note_type last;
    boolean absflag;
{
    int i;			/* loop counter */
    int command;		/* the current command */
    int chan;			/* the midi channel of the current event */
    int lastchan = 0;		/* the default adagio channel (1) */
    int ped = false;		/* flag maintains state of pedal */
    int how_many = last - event_buff;
    long now;		/* the time of the next event */
    boolean bad_ctrl_flag = false;	/* set to true if unknown ctrl read */

    if (fp == NULL) {
	fprintf(stderr, "internal error: output called with NULL file.\n");
	exit(1);
    }

    if (debug_rec)
	printf("hint: if file is not being closed, decrease MAXSPACE\n");

    /* set the initial absolute time, all other times are relative */
    if (absflag)
	fprintf(fp, "t%ld ", event_buff[0].when);

    for (i = 0; i < how_many; i++) {
	if (debug_rec) {
	    printf("ev %d: %x %x %x (%ld)\n", i, event_buff[i].n[0],
		event_buff[i].n[1], event_buff[i].n[2], event_buff[i].when);
	}

	if (istime(event_buff+i)) {
	    now = event_buff[i].when;
	    if (debug_rec) printf("i = %d, now = %ld\n", i, now);
	} else {
	    command = event_buff[i].n[0] & MIDI_CODE_MASK;
	    chan = event_buff[i].n[0] & MIDI_CHN_MASK;

	    if (command == MIDI_ON_NOTE && event_buff[i].n[2] != 0) {
		put_pitch(fp, event_buff[i].n[1] - 12);
		fprintf(fp, " u%ld l%d n%ld", getdur(i, last, ped, now),
			event_buff[i].n[2], getnext(i, last, now) - now);
		if (lastchan != chan) {
		    fprintf(fp, " v%d\n", chan + 1);
		    lastchan = chan;
		} else fprintf(fp, "\n");
	    } else if (command == MIDI_CH_PROGRAM) {
		fprintf(fp, "r z%d n%ld", event_buff[i].n[1] + 1,
			getnext(i, last, now) - now);
		if (lastchan != chan) {
		    fprintf(fp, " v%d\n", chan + 1);
		    lastchan = chan;
		} else fprintf(fp, "\n");
	    } else if (command == MIDI_CTRL &&
		       event_buff[i].n[1] == SUSTAIN) {
		ped = (event_buff[i].n[2] != 0);
	    } else if (command == MIDI_CTRL) {
		char c = map_ctrl(event_buff[i].n[1]);
		if (c != NULL) {
		    fprintf(fp, "%c%d n%d\n", c,
			event_buff[i].n[2], getnext(i, last, now) - now);
		} else bad_ctrl_flag = true;
	    } else if (command == MIDI_TOUCH) {
		fprintf(fp, "O%d n%d\n", event_buff[i].n[1],
			getnext(i, last, now) - now);
	    } else if (command == MIDI_BEND) {
		fprintf(fp, "Y%d n%d\n", event_bend(&event_buff[i]),
			getnext(i, last, now) - now);
	    } else if (command != MIDI_ON_NOTE) {
		fprintf(stderr, "Command 0x%x ignored\n", command);
	    }
	}
    }
    if (bad_ctrl_flag)
	fprintf(stderr,
	       "Some unrecognized control changes were omitted from file.\n");
}

/****************************************************************************
*				put_pitch
* Inputs:
*	FILE *fp: an open file
*	int p: a pitch number
* Effect: write out the pitch name for a given number
****************************************************************************/

private void put_pitch(fp, p)
    FILE *fp;
    int p;
{
    static char *ptos[] = {"c", "cs", "d", "ef", "e", "f", "fs", "g",
			   "gs", "a", "bf", "b"};
    fprintf(fp, "%s%d", ptos[p % 12], p / 12);
}

/**********************************************************************
*			rec_final
* Inputs:
*	boolean absflag: output absolute time of first note if true
* Effect:
*	Write recorded data to a file
**********************************************************************/

void rec_final(absflag)
    boolean absflag;
{
    next->when = gettime();
    last = next;
    if (debug_rec) printf("max_pile_up = %d, ", max_pile_up);
    printf("%d times and events recorded.\n", last - event_buff);
    filter(last);
    output(fp, last, absflag);
    fclose(fp);
}

/****************************************************************************
*				rec_init
* Inputs:
*	char *file:  pointer to file name from command line (if any)
*	boolean bender: true if pitch bend should be enabled
* Outputs:
*	returns true if initialization succeeds
* Effect:
*	prepares module to record midi input
****************************************************************************/

boolean rec_init(file, bender)
    char *file;
    boolean bender;
{
    char *getmem();	/* memory allocation */

    debug_rec = (cl_nswitch(dsw, ndsw) != NULL);

    byteorder();
    pile_ups = 0;
    max_pile_ups = 0;

    fp = fileopen(file, "gio", "w", "Name of output file");
    if (max_notes == -1) {	/* allocate space 1st time rec_init called */
	max_notes = space/sizeof(note_node);
	event_buff = (note_type) getmem(sizeof(note_node) * max_notes);
	if (event_buff == NULL) {
	    fprintf(stderr, "Internal error allocating record space.");
	    musicterm();
	    exit(1);
	}
	printf("Space for %d events has been allocated.\n", max_notes);
    }
    next = event_buff;
    last = event_buff + max_notes - 2;

    while (getkey(false) != -1) ;	/* flush old midi events */
    midi_cont(bender);
    return max_notes > 10;
}

/****************************************************************************
*				rec_poll
* Inputs:
*	long time: the current time
* Outputs:
*	returns true if there is no more memory
* Effect: reads and stores any input
* Assumes: rec_poll must be called frequently to get accurate results
* Implementation:
*	time stamps and midi events share the same buffer of 4-byte events
*	save time at most once per call to rec_poll
*	save time only if midi data is present
****************************************************************************/

boolean rec_poll(time)
    long time;
{
    next->when = time;	/* this will overwrite an earlier time unless data */
			/* was recorded */
    if (getbuf(false, (next+1)->n)) {	/* buffer nonempty? */
	next++;
	next->n[3] = MIDI_CMD_BIT;	/* set tag bit */
	pile_ups = 1;
	while (getbuf(false, (++next)->n)) {
	    next->n[3] = MIDI_CMD_BIT;	/* set tag bit */
	    pile_ups++;
	    if (next >= last) {
		break;
	    }
	}
    }
    if (pile_ups > max_pile_up) max_pile_up = pile_ups;
    if (next >= last) {
	fprintf(stderr, "No more memory.\n");
	return true;
    } /* else */ return false;
}
