/* seqwrite -- write a seq in Adagio format to a file */
/***************************************************************************
*       Change Log
*  Date | Change
*-----------+--------------------------------------------------------------
* 11-Mar-94 | Created Change Log
* 11-Mar-94 | PLu : Added private to function defs.
****************************************************************************/

#include "switches.h"
#include "stdio.h"
#include "cext.h"
#include "midifns.h"
#include "timebase.h"
#include "seq.h"
#include "seqwrite.h"
#include "userio.h"

private boolean next_event_time();
private void write_event();
private void write_velocity(FILE *f, int velocity);
private void write_voice();
private void write_rest(FILE *f, event_type ev, boolean abs_flag);
private void write_time();

private boolean clock_started;
private long clock_half_tick;
private int new_tempo;
/* non-zero indicates a pending tempo change... */
private long tempo_change_at;

private int macro_count;
private int call_count;
private int deframp_count;
private int seti_count;

private int last_velocity;
private last_voice = seq_dflt_voice;


/* next_event_time -- get the time of the next event */
/*
 * NOTE: clock events are ignored (unless this is the first clock event)
 */
private boolean next_event_time(event, next_time)
  event_type event;
  time_type *next_time;
{
    while (event) {
	if (vc_ctrl(event->nvoice) == ESC_CTRL &&
	    event->value == CLOCK_VALUE && 
	    clock_started) {
	    /* this is a clock event, ignore it: */
	    event = event->next;
	} else {
	    *next_time = event->ntime;
	    return true;
	}
    }
    return false;
}


/* seq_write -- write a seq to a file */
/*
 * NOTE: if abs_flag, then write using absolute times ('T'), otherwise use
 *  relative timing ('N').  Also, only selected channels are written.
 */
void seq_write(seq_type seq, FILE *f, boolean abs_flag)
{
    event_type ev = seq_events(seq);

    last_velocity = seq_dflt_loud;
    last_voice = seq_dflt_voice;

    fprintf(f, "!MSEC\n");
    clock_started = false;
    tempo_change_at = 0;

    macro_count = 0;
    call_count = 0;
    deframp_count = 0;
    seti_count = 0;

    while (ev) {
	write_event(seq, ev, f, abs_flag);
	ev = ev->next;
    }
    if (macro_count) gprintf(TRANS, "%d macros ignored.\n", macro_count);
    if (call_count) gprintf(TRANS, "%d calls ignored.\n", call_count);
    if (deframp_count) gprintf(TRANS, "%d deframps ignored.\n", deframp_count);
    if (seti_count) gprintf(TRANS, "%d setis ignored.\n", seti_count);
}


private char ctrl_letter[] = "?KMOXYZ";

/* write_event -- write a single event to a file */
/**/
private void write_event(seq, event, f, abs_flag)
  seq_type seq;
  event_type event;
  FILE *f;
  boolean abs_flag;
{
    int voice = vc_voice(event->nvoice);

    /* process all current (and earlier) events */
    if (is_note(event)) {       /*** a note or rest ***/
	/* if this note is not a rest, write it */
	if (event->value != NO_PITCH &&
		  (seq_channel_mask(seq) &
		   (1 << (voice - 1)))) {
	    write_pitch(f, event->value);
	    fprintf(f, " U%ld ", event->u.note.ndur >> 8);
	    write_velocity(f, (int) (event->u.note.ndur & 0xFF));
	    write_voice(f, voice);
	    write_time(f, event, abs_flag);
	}
    } else {            /*** a control command ***/
	switch (vc_ctrl(event->nvoice)) {
	  case PSWITCH_CTRL:
	  case MODWHEEL_CTRL:
	  case TOUCH_CTRL:
	  case VOLUME_CTRL:
	  case BEND_CTRL:
	    fprintf(f, "%c%d ", ctrl_letter[vc_ctrl(event->nvoice)],
				    event->value);
	    write_voice(f, voice);
	    write_time(f, event, abs_flag);
	    break;
	  case PROGRAM_CTRL:
	    fprintf(f, "Z%d ", event->value + 1);
	    write_voice(f, voice);
	    write_time(f, event, abs_flag);
	    break;
	  case ESC_CTRL:
	    switch (event->value) {
	      case CALL_VALUE:
		call_count++;
		write_rest(f, event, abs_flag);
		/* !call's are not written because there isn't enough
		 * information.  A future version of seq should store
		 * the hash table entry instead of the address of the
		 * routine (then we could get string name of the call)
		 * and the number of arguments.
		 */
		break;
	      case CLOCK_VALUE:
		/* design for !clock: for absolute (absflag) files, put the
		   clocks (if any) at the end where they can be edited out
		   easily.  For relative files, keep the clocks in-line.
		   On each clock, write a !tempo command inferred by the 
		   clock and followed by the !clock.  Because the clock
		   event comes before the actual tempo change, the chnage
		   must be delayed by a half tick except for the first one.
		 */
		if (abs_flag) break;
		new_tempo = (2500L << 16) / event->u.clock.ticksize;
		if (clock_started) {
		    tempo_change_at = event->ntime + clock_half_tick;
		} else {
		    fprintf(f, "!tempo %d\n", new_tempo);
		    fprintf(f, "!clock\n");
		    clock_started = true;
		}
		clock_half_tick = (event->u.clock.ticksize) >> 17;
		break;
	      case MACCTRL_VALUE:
		fprintf(f, "~%d(%d) ", event->u.macctrl.ctrl_number,
				       event->u.macctrl.value);
		write_voice(f, voice);
		write_time(f, event, abs_flag);
		break;
	      case MACRO_VALUE: 
		macro_count++;
		write_rest(f, event, abs_flag);
		/* macros are not written because there isn't enough
		 * information.  A future version of seq should store
		 * the number of arguments in the event, or better yet,
		 * in the definition.  Send complaints to RBD!
		 */
		break;
	      case CTRLRAMP_VALUE:
		fprintf(f, "!ramp ~%d(%d) ~%d(%d) U%ld U%ld ",
			event->u.ramp.ctrl, event->u.ramp.u.ctrl.from_value,
			event->u.ramp.ctrl, event->u.ramp.u.ctrl.to_value,
			event->u.ramp.step, event->u.ramp.dur);
		write_voice(f, voice);
		write_time(f, event, abs_flag);
		break;
	      case DEFRAMP_VALUE:
		deframp_count++;
		write_rest(f, event, abs_flag);
		/* See MACRO_VALUE above for why this isn't implemented. */
		break;
	      case SETI_VALUE:
		seti_count++;
		write_rest(f, event, abs_flag);
		/* !seti and !setv are not implemented -- a future version
		 * of seq should save more information so we can reconstruct
		 * the Adagio source command.
		 */
		break;
	      default:
		gprintf(TRANS, "unexpected ESC_CTRL value\n");
		break;
	    }
	    break;
	  default:
	    gprintf(TRANS, "unexpected seq data\n");
	    break;
	}
    }
}


/* write_rest -- write a rest (in place of an event) */
/**/
private void write_rest(FILE *f, event_type ev, boolean abs_flag)
{
    fprintf(f, "R ");
    write_time(f, ev, abs_flag);
}


/* write_time -- write the final field on the line with N or T command */
/**/
private void write_time(f, event, abs_flag)
  FILE *f;
  event_type event;
  boolean abs_flag;
{
    time_type next_time;

    if (abs_flag) {
	fprintf(f, "T%ld\n", event->ntime);
	return;
    }

    if (next_event_time(event->next, &next_time)) {
	if (tempo_change_at && (next_time >= tempo_change_at)) {
	    fprintf(f, "N%ld\n!TEMPO %d\n!CLOCK\nR U%ld\n", 
			tempo_change_at - event->ntime,
			new_tempo, next_time - tempo_change_at);
	    tempo_change_at = 0;
	} else {
	    fprintf(f, "N%ld\n", next_time - event->ntime);
	}
    } else {
	fprintf(f, "\n");
    }
}


/* write_velocity -- write the velocity field */
/**/
private void write_velocity(FILE *f, int velocity)
{
    if (last_velocity != velocity) {
	last_velocity = velocity;
	fprintf(f, "L%d ", velocity);
    }
}


/* write_voice -- write the voice field */
/**/
private void write_voice(f, voice)
  FILE *f;
  int voice;
{
    if (last_voice != voice) {
	last_voice = voice;
	fprintf(f, "V%d ", voice);
    }
}
