/* record.c -- keyboard to adagio recorder
 * Copyright 1989 Carnegie Mellon University
 *
 * the interface consists of three routines:
 *      rec_init()      -- initialization
 *      rec_event(byte *data)   -- called to insert (record) midi data, 
 *                              -- returns false if no more space
 *      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)
* 1-Aug-87  | F.H. Changed rec_init() to new memory handling.
* 17-Oct-88 | JCD : portable version.
* 31-Jan-90 | GWL : cleaned up for LATTICE
* 30-Jun-90 | RBD : further changes
* 2-April-91 | JDW :further changes
*****************************************************************************/

#include "switches.h"
#include "stdio.h"
#include "cext.h"
#include "midifns.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 */

long max_notes = -1L;    /* -1 is flag that space must be allocated */

time_type previous_time;

/****************************************************************
* 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))

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

private note_type event_buff;    /* pointer to allocated buffer */
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_up;    /* maximum of pile_ups */
private boolean fixed_octave;	/* used to avoid many error messages */

/****************************************************************************
*    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();

/****************************************************************************
*               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()
{
    note_node test_event;
    if ((sizeof(test_event) != 4) ||
	(sizeof(test_event.when) != 4) ||
	(sizeof(test_event.n[0]) != 1)) {
	gprintf(ERROR, "implementation error: size problem\n");
	EXIT(1);
    }
    test_event.n[0] = 0x12;
    test_event.n[1] = 0x34;
    test_event.n[2] = 0x56;
    test_event.n[3] = 0x78;
    if ((test_event.when != 0x78563412) &&
	(test_event.when != 0x12345678)) {
	gprintf(ERROR, "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 */

    for (note = event_buff; note <= last; note++) {
	if (istime(note)) {
	    now = note->when;
	} 
	else {
	    command = note->n[0] & MIDI_CODE_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, EOS if
*       control change is not one of PORTARATE, PORTASWITCH,
*       MODWHEEL, FOOT
****************************************************************************/

private char map_ctrl(control)
int control;
{
    switch (control) {
/* 'J' is no longer code for PORTARATE
      case PORTARATE:
	return 'J'; */
      case PORTASWITCH:
	return 'K';
      case MODWHEEL:
	return 'M';
      case VOLUME:
	return 'X';
      default:
	return EOS;
    }
#ifdef LATTICE322
    return EOS;    /* make Lattice C type checker happy */
#endif
}

/****************************************************************************
*               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 voice;			/* the midi channel of the current event */
    int last_velocity = -1;	/* used to filter repeated Lnn attributes */
    int last_voice = 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 */

    if (fp == NULL) {
	gprintf(ERROR, "internal error: output called with NULL file.\n");
	EXIT(1);
    }

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


    fprintf(fp, "!MSEC\n");     /* times will be in milliseconds */
    /* set the initial absolute time, all other times are relative */

    if (absflag) {
	now = event_buff[0].when;
	if (now < 0) {
	    fprintf(fp, "* First event took place at Adagio time %d,\n", now);
	    fprintf(fp, "*  but Adagio cannot represent negative times,\n");
	    fprintf(fp, "*  so this entire score will be %d ms late\n", -now);
	    gprintf(TRANS, "First event took place at Adagio time %d!\n", now);
	    gprintf(TRANS, "All events times will be %d ms late\n", -now);
	    now = 0L;
	}
	fprintf(fp, "T%ld ", now);
    }

    for (i = 0; i < how_many; i++) {
	if (debug_rec) {
	    gprintf(GDEBUG,"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) gprintf(GDEBUG,"i = %d, now = %ld\n", i, now);
	} else {
	    boolean needs_voice = true;
	    command = event_buff[i].n[0] & MIDI_CODE_MASK;
	    voice = event_buff[i].n[0] & MIDI_CHN_MASK;

	    if (command == MIDI_ON_NOTE && event_buff[i].n[2] != 0) {
		int velocity =	event_buff[i].n[2];
		write_pitch(fp, event_buff[i].n[1]);
		fprintf(fp, " U%ld", getdur(i, last, ped, now));
		if (last_velocity != velocity) {
		    fprintf(fp, " L%d", velocity);
		    last_velocity = velocity;
		}
	    } else if (command == MIDI_CH_PROGRAM) {
		fprintf(fp, "Z%d", event_buff[i].n[1] + 1);
	    } else if (command == MIDI_CTRL &&
		event_buff[i].n[1] == SUSTAIN) {
		ped = (event_buff[i].n[2] != 0);
		needs_voice = false;
	    } else if (command == MIDI_CTRL) {
		char c = map_ctrl(event_buff[i].n[1]);
		if (c != EOS) fprintf(fp, "%c%d", c, event_buff[i].n[2]);
		else fprintf(fp, "~%d(%d)", event_buff[i].n[1], event_buff[i].n[2]);
	    } else if (command == MIDI_TOUCH) {
		fprintf(fp, "O%d", event_buff[i].n[1]);
	    } else if (command == MIDI_BEND) {
		fprintf(fp, "Y%d", event_bend(&event_buff[i]));
	    } else if (command == MIDI_ON_NOTE || command == MIDI_OFF_NOTE) {
		needs_voice = false; /* ignore note-offs */
	    } else {
		gprintf(ERROR, "Command 0x%x ignored\n", command);
		needs_voice = false;
	    }
	    if (needs_voice) {
		if (last_voice != voice) {
		    fprintf(fp, " V%d", voice + 1);
		    last_voice = voice;
		}
		fprintf(fp, " N%d", getnext(i, last, now) - now);
		fprintf(fp, "\n");
	    }
	}
    }
}


/****************************************************************************
*               write_pitch
* Inputs:
*    FILE *fp: an open file
*    int p: a pitch number
* Effect: write out the pitch name for a given number
****************************************************************************/

void write_pitch(FILE *fp, int p)
{
    static char *ptos[] = {
	"C", "CS", "D", "EF", "E", "F", "FS", "G",
	"GS", "A", "BF", "B"	};
    /* avoid negative numbers: adagio can't express lowest octave: */
    while (p < 12) {
	if (!fixed_octave) {
	    gprintf(ERROR, "%s%s%s",
		    "A low note was transposed up an octave\n",
		    "(Adagio cannot express the lowest MIDI octave).\n",
		    "This message will appear only once.\n");
	    fixed_octave = true;
	}
	p += 12;
    }
    fprintf(fp, "%s%d", ptos[p % 12], (p / 12) - 1);
}

/**********************************************************************
*           rec_final
* Inputs:
*    boolean absflag: output absolute time of first note if true
* Effect:
*    Write recorded data to a file
**********************************************************************/

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

/* ENOUGH_ROOM says if we have room for 10000 events + 10000 timestamps =
 * 20000 note_struct's, then that's "enough room" for recording a sequence.
 * If more ram is available, it won't be used.  If less is available, we'll
 * use as much as we can get, minus "SPACE_FOR_PLAY", which leaves a little
 * bit of spare ram in case Moxc or stdio need to allocate some space.
 *      For DOS, we limit recording space to 64K.
 */
#ifdef DOS
#define ENOUGH_ROOM 64000L
#else
#define ENOUGH_ROOM (20000L * sizeof(union note_struct))
#endif


boolean rec_init(boolean bender)
{
    size_t biggestChunk, spaceForRecord;

    debug_rec = cl_switch("debug");
    byteorder();
    pile_ups = 0;
    max_pile_up = 0;
    previous_time = -1L; /* this will force putting in initial timestamp */
    fixed_octave = false;

    if (max_notes == -1) {    /* allocate space 1st time rec_init called */
	biggestChunk = AVAILMEM;
	if (biggestChunk <= SPACE_FOR_PLAY) {
	    /* not enough memory; give up */
	    return(false);
	} 
	else {
	    spaceForRecord =
		min((biggestChunk - SPACE_FOR_PLAY), ENOUGH_ROOM);
	    /* leave SPACE_FOR_PLAY contiguous bytes of memory */
	}
	max_notes = spaceForRecord / sizeof(note_node);
	/*    gprintf(GDEBUG,"max_notes = %d\n", max_notes);*/
	event_buff = (note_type) MALLOC(spaceForRecord);
	if (event_buff == NULL) {
	    /* should never happen */
	    gprintf(FATAL, "Implementation error (record.c): getting memory.");
	    return false;
	}
    }
    next = event_buff;
    last = event_buff + max_notes - 2; /* it is critical that last not point
					* to the very last storage loc */
    midi_cont(bender);
    return((boolean)(max_notes > 10));
    /* it would be silly to record with only room enough for 10 notes! */
}


/****************************************************************************
*               rec_event
* Inputs:
*    long time: the current time
*    long data: midi data to record
* Outputs:
*    returns false if there is no more memory
* Effect: reads and stores any input
* 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 it changes
****************************************************************************/

boolean rec_event(long *data, time_type time)
{
    /* can't allow negative time because sign bit distinguishes 
     * data from time: */
    if (time < 0) time = 0;

    if (previous_time != time) {
	next++->when = previous_time = time;
	if (next >= last) goto overflow;
    }

    next->when = *data;
    next++->n[3] = MIDI_CMD_BIT;        /* set tag bit */
    if (next >= last) goto overflow;
    return true;

overflow:
    next = last; /* last doesn't really point to last storage */
    gprintf(ERROR, "No more memory.\n");
    return false;
}
