/* trans2.c -- replacement for phase2.c for use in record program */

/*****************************************************************************
*	    Change Log
*  Date	    | Change
*-----------+-----------------------------------------------------------------
* 31-Dec-85 | Created changelog
* 31-Dec-85 | Add c:\ to include directives
* 31-Dec-85 | Changed to use new CBREAK as well as kbhit
*  1-Jan-86 | Added stop_reason
*  1-Jan-86 | <rgd/jmn> Require musicfns.h before adagio.h
*	    | stop_time is now long
*	    | Turn off all notes in moreclean
*	    | changed control logic in play
*  1-Jan-86 | Added miditrace
*  1-Jan-86 | Added command scanner at play request level
* 18-Jan-86 | Gave priority to note offs.
*	    | Created stop_check routine.
* 21-Jan-86 | Added mpu_error_check to main loop
*  3-Feb-86 | Changed help message slightly
*  7-Feb-86 | Adapted from phase2 of adagio system to do overdubs
* 21-May-86 | Pulled over lots of code from latest phase2
*  7-Aug-86 | Initialize bend, touch, porta, mod, and foot controls
*****************************************************************************/
/*    This module plays notes compiled and sorted in phase1 and
 *    records from the keyboard
 */

#include "cext.h"
#include "stdio.h"
#include "adagio.h"
#include "mpu.h"
#include "record.h"
#include "userio.h"
#include "trans2.h"

#define stop_code ' '
char stop_explanation[] = "Space bar";

extern int musictrace;
extern int miditrace;
extern int CBREAK;
extern char outfile[];	/* output file from command line */

#define STOP_CC 1
#define STOP_CBRK 2
#define STOP_KEY 3
#define STOP_SPACE 4

private long offset = 100;	/* time offset from clock to performance */

private int program[num_voices];	/* current program */

/****************************************************************************
* routines declared in this module
****************************************************************************/
private void	init_stuff();
private void	moreclean();
private void	play();
private void	play_help();
private void	print_status();
private boolean stop_check();

/****************************************************************************
*				  init_stuff
* Effect: 
*	one-time initializations to prepare for playing and recording
****************************************************************************/

private void init_stuff()
{
    off_init();
}

/****************************************************************************
*				moreclean
* Inputs:
*	event_type score: the score that was in progress
*	int stop_reason: the reason the score was stopped
* Effect: 
*	prints a message to announce stopped state
*	tells what was playing at the stop time
* Implementation:
*	What was playing is determined by reading the time and searching
*	for notes that are on at that time.  If nothing is found on a
*	given channel, it would be nice to print the last note played
*	on the channel provided it was played within say 1 sec of the
*	stop time.  This is unimplemented, but would help locate short
*	notes.
****************************************************************************/

private void moreclean(score, stop_reason)
   event_type score;
   int stop_reason;
{
    long stop_time;
    event_type n;

    while (note_offs(0x7fffffff)) ;	/* turn off all notes */

    printf ("\n	     * * STOPPED * *\n");

    switch(stop_reason)
       { /* reason for stopping */
	case STOP_CC:	printf("Control-C seen\n");
			break;
	case STOP_CBRK: printf("Control-break seen\n");
			break;
	case STOP_KEY:	printf("%s hit\n",stop_explanation);
			break;
	case STOP_SPACE:printf("Out of space for recording\n");
			break;
       } /* reason for stopping */

    stop_time = gettime() - offset;
    if (stop_time > 0)
	printf ("Stopped at time = %ld (hundreths of a second). \n\n",
		stop_time);

    for (n = score; n != NULL; n = n->next) {
	if (is_note(n) &&
	    (n->ntime <= stop_time) &&
	    (n->ntime + n->u.note.ndur) >= stop_time) {
	    printf("Voice: %d was playing line: %d\n",
		n->nvoice + 1, n->nline);
	}
    }
}

/****************************************************************************
*				    phase2
* Inputs:
*	event_type root: Root of play list
* Effect: 
*	Plays the music
****************************************************************************/

void phase2(root)
   event_type root;
   {
    char resp;
    short done = false;
    int truth = true;

    init_stuff();

    while (!done) 
       { /* ask user */
	truth = true;
	printf ("\nType <RETURN> to record, q<RETURN> to quit, ?<RETURN> for help:");
	while(true)
	   { /* read input */
	    resp = getchar();
	    switch(tolower(resp))
	       { /* decode */
		case ' ':
			continue;	/* ignore spaces */
		case '?': 
			play_help();
			readln(stdin);
			break;
		case '-':
			truth = !truth;
			continue;	/* read next char */
		case 't':
			musictrace = truth;
			readln(stdin);
			break;
		case 'm':
			miditrace = truth;
			readln(stdin);
			break;
		case 'q':
			done = true;
			readln(stdin);
			break;
		case 's':
			print_status();
			readln(stdin);
			break;
		case '\n':
			CBREAK = 0;
			play(root);
			break;
	       } /* decode */
	    break;
	   } /* read input */
       } /* ask user */
}

/****************************************************************************
*				     play
* Inputs:
*	event_type score: Score to play
* Effect: 
*	Plays the score
****************************************************************************/

private void play(score)
    event_type score;
{	
    event_type event = score;	/* save first pointer for moreclean */
    short done = false;
    int stop_reason;
    long time = 0;
    int i;

    musicinit();
    /* initialize midi recording */
    if (!rec_init(outfile, askbool("Pitch bend, etc. on", false))) {	
	printf("No space for recording, use a smaller adagio file\n");
	musicterm();
	exit(1);
    }

    printf("Type %s to stop.\n",stop_explanation);

    /* Initialize all midi channels with reasonable start values: */
    for (i = 1; i <= num_voices; i++) {
	midi_program(i, 1);
	program[i - 1] = 1;
	midi_bend(i, 0);
	midi_touch(i, 0);
	midi_ctrl(i, PORTARATE, 99);
	midi_ctrl(i, PORTASWITCH, 0);
	midi_ctrl(i, MODWHEEL, 0);
	midi_ctrl(i, FOOT, 99);
    }

    timereset();
    l_restuntil(offset);

    while (!done) { /* play it, Sam */
	time = gettime() - offset; /* delay everything by offset */
	if (rec_poll(time)) {	/* record anything that's there */
	    done = true;
	    stop_reason = STOP_SPACE;
	}
	if (CBREAK || kbhit()) {
	    done |= stop_check(&stop_reason);
	}
	note_offs(time);
	if (done) { /* clean up */
	    moreclean(score, stop_reason);
	    rec_final(true);	/* write out recorded data */
	} else if (event != NULL) {	/* something to play */
	    if (time >= event->ntime) {
		if (is_note(event)) { /* play a note */
		    /* check for correct program (preset) */
		    if (event->u.note.nprogram != program[event->nvoice]) {
			midi_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) {
			midi_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: midi_ctrl(vc_voice(event->nvoice) + 1,
					     PORTARATE,
					     event->u.ctrl.value);
				break;
			case 2: midi_ctrl(vc_voice(event->nvoice) + 1,
					     PORTASWITCH,
					     event->u.ctrl.value);
				break;
			case 3: midi_ctrl(vc_voice(event->nvoice) + 1,
					     MODWHEEL,
					     event->u.ctrl.value);
				break;
			case 4: midi_touch(vc_voice(event->nvoice) + 1,
					   event->u.ctrl.value);
				break;
			case 5: midi_ctrl(vc_voice(event->nvoice) + 1,
					     FOOT,
					     event->u.ctrl.value);
				break;
			case 6: midi_bend(vc_voice(event->nvoice) + 1,
					  event->u.ctrl.value << 6);
				break;
			default: break;
		    }
		}
		event = event->next;
	    }
	} else mpu_error_check();
    } /* play it, Sam */

/* Debugging:
 *  i = gettic();
 *  printf("Max Latency is %d\n", i);
 */
    musicterm();
}

/****************************************************************************
*				   play_help
* Effect: 
*	Lists help for play option
****************************************************************************/

private void play_help()
{
    fprintf(stderr," <return>	   Play music\n");
    fprintf(stderr," q<return>	   Quit Adagio (Exit)\n\n");
    fprintf(stderr," m<return>	   Turn on MIDI byte trace\n");
    fprintf(stderr,"-m<return>	   Turn off MIDI byte trace\n");
    fprintf(stderr," s<return>	   Report state\n");
    fprintf(stderr," t<return>	   Turn on music operation trace\n");
    fprintf(stderr,"-t<return>	   Turn off music operation trace\n");
    fprintf(stderr," ?<return>	   This message\n");
}

/****************************************************************************
*				  print_status
* Effect: 
*	Informative output about state
****************************************************************************/

private void print_status()
    {
     fprintf(stderr,"MIDI trace (m option) %s\n",(miditrace ? "on" : "off"));
     fprintf(stderr,"Music trace (t option) %s\n",(musictrace ? "on" : "off"));
    }

/****************************************************************************
*				     stop_check
* Outputs:
*	*reason is set to reason for stop
*	true is returned iff play should stop
* Effect: 
*	Checks for break character or stop code from kbd
****************************************************************************/
private boolean stop_check(reason)
    int *reason;
{
    boolean done = false;
    switch(CBREAK) { /* stop reason */
	case 0: /* no stop code, try keyboard */
	    done = (getch() == stop_code);
	    if (done) *reason = STOP_KEY;
	    break;
	case 1: /* ctrl-break */
	    done = true;
	    if (kbhit()) getch();
	    *reason = STOP_CBRK;
	    break;
	case 2: /* ctrl-C */
	    done = true;
	    *reason = STOP_CC;
	    if (kbhit()) getch();
	    break;
    } /* stop reason */
    return done;
}
