/*************************************************
*    The PMW Music Typesetter - 3rd incarnation  *
*************************************************/

/* Copyright (c) Philip Hazel, 1991 - 2009 */

/* Written by Philip Hazel, starting November 1991 */
/* This file last modified: April 2009 */


/* This file contains code for writing a MIDI file */


#include "pmwhdr.h"


typedef struct midi_event {
  int time;
  short int seq;
  uschar data[8];
} midi_event;


/*************************************************
*             Local variables                    *
*************************************************/

static int      file_count = 0;
static midi_event *events = NULL;
static midi_event *next_event;
static int      next_event_seq;

static int      last_written_time;
static int      running_status;

static uschar   midi_channel[max_stave+1];
static uschar   midi_channel_volume[midi_maxchannel];
static FILE    *midi_file;
static uschar   midi_note[max_stave+1];


static int      play_bar;
static movtstr *play_movt;
static int      play_nextbar;
static BOOL     play_onebar_only = FALSE;
static int      play_staves[stave_bitvec_size] = { -1, -1 };
static int      play_tempo;
static int      play_volume = 127;

static int      repeat_bar;
static int      repeat_count;
static int      repeat_endbar;

static uschar   stavetie[max_stave+1];
static signed char midi_transpose[max_stave+1];
static uschar   stave_volume[max_stave+1];


/*************************************************
*      Comparison function for sorting events    *
*************************************************/

/* This function is passed to qsort(). Similar events at the same time should
preserve their order. To do this, we give each event a sequence number that is
compared if the times are equal. This function should never return zero in
practice.

Arguments:
  a          pointer to an event structure
  b          pointer to an event structure

Returns:     -1, 0, or +1
*/

static int
cf(const void *a, const void *b)
{
const midi_event *ma = (const midi_event *)a;
const midi_event *mb = (const midi_event *)b;
if (ma->time < mb->time) return -1;
if (ma->time > mb->time) return +1;
if (ma->seq < mb->seq) return -1;
if (ma->seq > mb->seq) return +1;
return 0;
}


/*************************************************
*          Find length of bar                    *
*************************************************/

/* Scan the staves selected for playing until one with some notes in it is
found. If there are none, return zero. If the bar contains only a centred rest,
carry on looking for another stave in case this bar is a nocheck whole-bar
rest, which might be of different length to the remaining staves' bars.

Arguments:   none; the required movement/bar are in play_movt and play_bar
Returns:     length of the bar, or zero
*/

static
int find_barlength(void)
{
int stave;
int yield = 0;

for (stave = 1; stave <= play_movt->laststave; stave++)
  {
  BOOL notjustrest = FALSE;
  int length = 0;
  int gracecount = 0;

  if (mac_teststave(play_staves, stave))
    {
    bstr *p = ((play_movt->stavetable)[stave])->barindex[play_bar];

    if (p != NULL)
      {
      int moff = 0;
      int type = p->type;

      while (type != b_End)
        {
        switch(type)
          {
          case b_Jump:
          p = (bstr *)(((b_Jumpstr *)p)->next);                    
          break;

          case b_reset:
          if (moff > length) length = moff;
          moff = 0;
          break;

          case b_note:
            {
            b_notestr *note = (b_notestr *)p;
            moff += note->length;
            if (note->length == 0) gracecount++; else gracecount = 0;
            if (note->spitch != 0 || (note->flags & nf_centre) == 0)
              notjustrest = TRUE;
            }
          break;
          }

        p = (bstr *)((uschar *)p + length_table[type]);
        type = p->type;
        }

      /* At bar end check for longest length in case there were resets */

      if (moff > length) length = moff;

      /* If there were grace notes at the end of the bar, increase its
      length by 1/10 second for each one. */

      length += (gracecount*len_crotchet*play_tempo)/(60*10);

      /* If we have found a bar with notes in it other than a whole bar
      rest, we are done. Otherwise carry on, but leave length so far in
      yield in case there are no staves with notes. */

      if (length > yield) yield = length;
      if (yield > 0 && notjustrest) break;
      }
    }
  }

return yield;
}




/*************************************************
*             Find second time bar               *
*************************************************/

/* Starting from the bar after play_bar in play_movt, look along the stave for
the second time bar.

Argument:     the stave to search
Returns:      the bar number, or 0 if there are no more bars
*/

static int
find_second_time(int stave)
{
int yield = play_bar + 1;

for (;;)
  {
  int type;
  bstr *p = ((play_movt->stavetable)[stave])->barindex[yield];

  if (p == NULL) return 0;

  type = p->type;

  while (type != b_End)
    {
    switch(type)
      {
      case b_Jump:
      p = (bstr *)(((b_Jumpstr *)p)->next);                      
      break;

      case b_nbar:
      return yield;
      }

    p = (bstr *)((uschar *)p + length_table[type]);
    type = p->type;
    }

  yield++;
  }
}




/*************************************************
*              Write 32-bit number               *
*************************************************/

/* Write the most significant byte first.

Argument:  the number
Returns:   nothing
*/

static void
write32(int n)
{
fputc((n>>24)&255, midi_file);
fputc((n>>16)&255, midi_file);
fputc((n>>8)&255, midi_file);
fputc(n&255, midi_file);
file_count += 4;
}


/*************************************************
*              Write 16-bit number               *
*************************************************/

/* Write the most significant byte first.

Argument:  the number
Returns:   nothing
*/

static void
write16(int n)
{
fputc((n>>8)&255, midi_file);
fputc(n&255, midi_file);
file_count += 2;
}


/*************************************************
*             Write variable length number       *
*************************************************/

/* The number is chopped up into 7-bit chunks, and then written with the most
significant chunk first. All but the last chunk have the top bit set. This
copes with numbers up to 28-bits long. That's all that MIDI needs.

Argument:  the number
Returns:   nothing
*/

static void
writevar(int n)
{
if (n < 0x80)
  {
  fputc(n, midi_file);
  file_count++;
  }

else if (n < 0x4000)
  {
  fputc(((n>>7)&127)|0x80, midi_file);
  fputc(n&127, midi_file);
  file_count += 2;
  }

else if (n < 0x200000)
  {
  fputc(((n>>14)&127)|0x80, midi_file);
  fputc(((n>>7)&127)|0x80, midi_file);
  fputc(n&127, midi_file);
  file_count += 3;
  }

else
  {
  fputc(((n>>21)&127)|0x80, midi_file);
  fputc(((n>>14)&127)|0x80, midi_file);
  fputc(((n>>7)&127)|0x80, midi_file);
  fputc(n&127, midi_file);
  file_count += 4;
  }
}


/*************************************************
*             Write one byte                     *
*************************************************/

static void
writebyte(int n)
{
fputc(n & 255, midi_file);
file_count++;
}



/*************************************************
*              Write one bar                     *
*************************************************/

/* The bar number is in play_bar.

Argument:   TRUE if this is the final bar to be written
Returns:    nothing
*/

static void
writebar(BOOL is_lastbar)
{
BOOL hadrepeat = FALSE;
BOOL oknbar = TRUE;
int stave;
int *p = play_movt->play_tempo_changes;
int this_barlength = find_barlength();
midi_event *eptr, *neptr;

DEBUG(("writebar %d\n", play_bar));

/* Find the tempo for this bar */

if (p != NULL && play_bar >= *p)
  {
  while (play_bar >= *p) p += 2;
  if (p[-1] != play_tempo)
    {
    int temp;
    play_tempo = p[-1];
    temp = 60000000/play_tempo;     /* Microseconds per crotchet */
    next_event->time = 0;
    next_event->seq = next_event_seq++;
    next_event->data[0] = 6;
    next_event->data[1] = 0xff;
    next_event->data[2] = 0x51;
    next_event->data[3] = 0x03;
    next_event->data[4] = temp >> 16;
    next_event->data[5] = temp >> 8;
    next_event->data[6] = temp;
    next_event++;
    }
  }

/* Now scan the staves. When [notes off] appears in the input, a control is 
placed at the start of each bar into which it continues, so we do not have to 
keep track between bars. */

for (stave = 1; stave <= play_movt->laststave; stave++)
  {
  if (mac_teststave(play_staves, stave))
    {
    bstr *p = ((play_movt->stavetable)[stave])->barindex[play_bar];

    if (p != NULL)
      {
      int midi_stave_status, midi_stave_pitch, midi_stave_velocity;
      int playtranspose = midi_transpose[stave];
      int moff = 0;
      int adjustlength = 0;
      int type = p->type;
      BOOL noteson = TRUE;   /* See above comment */

      /* Set up midi parameters */

      midi_stave_status = 0x90 + midi_channel[stave] - 1;
      midi_stave_pitch = midi_note[stave];
      midi_stave_velocity = ((play_volume * stave_volume[stave] *
        midi_channel_volume[midi_channel[stave]-1])/225);

      /* Scan the bar's data */

      while (type != b_End)
        {
        switch(type)
          {
          case b_Jump:
          p = (bstr *)(((b_Jumpstr *)p)->next);                      
          break;

          case b_reset:
          moff = 0;
          break;

          case b_rrepeat:
          if (play_repeats && moff == this_barlength && !play_onebar_only && !hadrepeat)
            {
            if (repeat_count == 1)
              {
              play_nextbar = repeat_bar;
              repeat_endbar = play_bar;
              repeat_count++;
              }
            else if (play_bar == repeat_endbar) repeat_count = 1;
            hadrepeat = TRUE;
            }
          break;

          case b_lrepeat:
          if (moff == 0) repeat_bar = play_bar;
          break;

          case b_nbar:
          if (moff == 0 && !play_onebar_only && oknbar)
            {
            b_nbarstr *b = (b_nbarstr *)p;
            if (b->n == 1 && repeat_count > 1)
              {
              int second = find_second_time(stave);
              if (second > 0)
                {
                play_bar = second;
                play_nextbar = play_bar + 1;
                repeat_bar = play_bar;
                repeat_count = 1;
                writebar(is_lastbar);
                }
              return;
              }
            else oknbar = FALSE;
            }
          break;

          case b_notes:
          noteson = ((b_notesstr *)p)->value;
          break;

          case b_playchange:
            {
            b_playchangestr *change = (b_playchangestr *)p;

            playtranspose += change->transpose;
            midi_transpose[stave] = playtranspose;

            /* If the relative volume parameter occurs with a change of
            channel, it is a channel volume change. Otherwise it is a
            stave volume change. */

            if (change->volume < 128 && change->channel == 128)
              {
              stave_volume[stave] = change->volume;
              midi_stave_velocity = ((play_volume * stave_volume[stave] *
                midi_channel_volume[midi_channel[stave]-1])/225);
              }

            /* Other changes */

            if (change->channel < 128)
              {
              midi_channel[stave] = change->channel;
              midi_stave_status = 0x90 + midi_channel[stave] - 1;
              if (change->volume < 128)
                midi_channel_volume[change->channel - 1] = change->volume;
              midi_stave_velocity = ((play_volume * stave_volume[stave] *
                midi_channel_volume[midi_channel[stave]-1])/225);
              }

            if (change->note < 128)
              midi_stave_pitch = midi_note[stave] = change->note;

            /* A voice change must be scheduled to occur in the correct
            sequence with the notes. */

            if (change->voice < 128)
              {
              next_event->time = moff;
              next_event->seq = next_event_seq++;
              next_event->data[0] = 2;
              next_event->data[1] = 0xC0 +  midi_channel[stave] - 1;
              next_event->data[2] = change->voice;
              next_event++;
              }
            }
          break;

          case b_note:
            {
            b_notestr *note = (b_notestr *)p;
            BOOL thisnotetied = FALSE;
            int length = note->length;
            int nstart = 0;
            int tiebarcount = 1;
            int pitchcount = 0;
            int pitchlist[20];
            int pitchlen[20];
            int pitchstart[20];
            
            oknbar = FALSE;

            if (length == 0)
              {
              length = (len_crotchet*play_tempo)/(60*10); /* 1/10 sec */
              adjustlength += length;
              }
            else
              {
              length -= adjustlength;
              adjustlength = 0;
              }

            /* nf_noplay is set when a note has already been played, because of
            a previous tie, which might have been in a previous bar. */

            if ((noteson || midi_for_notes_off) && 
                note->spitch != 0 && (note->flags & nf_noplay) == 0)
              {
              /* Get a list of pitches in a chord, and leave the general
              pointer p at the final note. */

              do
                {
                pitchlist[pitchcount] = note->truepitch;
                pitchlen[pitchcount] = length;
                pitchstart[pitchcount++] = nstart;
                p = (bstr *)note;
                mac_advancechord(note);
                }
              while (note->type == b_chord);

              /* If the note is followed by a tie, find the next note or
              chord on the stave. If any of its notes have the same pitch as
              any of those in the list, extend their playing times. If there
              are any new notes, add them to the list, with a later starting
              time. We have to do this because all the notes we are
              accumulating will be output at the end of this bar. Set the
              noplay flag in the next notes, to stop them playing again later.
              Continue for multiple ties. */

              nstart += length;
              while (note->type == b_tie)
                {
                int i, nlength;
                note = misc_nextnote(note, NULL);
                 
                if (note == NULL &&
                    play_bar + tiebarcount <= play_movt->barcount)
                  {
                  note = (b_notestr *)((play_movt->stavetable)[stave])->
                    barindex[play_bar + tiebarcount++];
                  if (note != NULL && note->type != b_note)
                    note = misc_nextnote(note, NULL);
                  }
                if (note == NULL) break;
                 
                nlength = note->length; 
                do
                  {
                  for (i = 0; i < pitchcount; i++)
                    {
                    if (pitchlist[i] == note->truepitch)
                      {
                      pitchlen[i] += note->length;
                      thisnotetied = TRUE;
                      note->flags |= nf_noplay;
                      break;
                      }
                    }
                  if (i >= pitchcount)
                    {
                    pitchlist[pitchcount] = note->truepitch;
                    pitchlen[pitchcount] = nlength;
                    note->flags |= nf_noplay;
                    pitchstart[pitchcount++] = nstart;
                    }

                  mac_advancechord(note);
                  }
                while (note->type == b_chord);
                nstart += nlength;
                }

              /* For each required pitch, set up the events to make a sound.
              The lengths and starting times may be different because of
              tied/non-tied notes in chords. */

              while (--pitchcount >= 0)
                {
                int pitch = pitchlist[pitchcount] + playtranspose;
                int start = moff + pitchstart[pitchcount];

                /* We have to schedule a note on and a note off event. Use note
                on with zero velocity for note off, as that means running
                status can be used. MIDI middle C is 60; PMW uses 48, so first
                adjust the pitch. */

                pitch = midi_stave_pitch? midi_stave_pitch : (pitch + 12);

                next_event->time = start;
                next_event->seq = next_event_seq++;
                next_event->data[0] = 3;
                next_event->data[1] = midi_stave_status;
                next_event->data[2] = pitch;
                next_event->data[3] = midi_stave_velocity;
                next_event++;

                next_event->time = start + pitchlen[pitchcount];
                next_event->seq = next_event_seq++;
                next_event->data[0] = 3;
                next_event->data[1] = midi_stave_status;
                next_event->data[2] = pitch;
                next_event->data[3] = 0;
                next_event++;
                }
              }

            stavetie[stave] = thisnotetied;
            moff += length;
            }
          break;
          }

        p = (bstr *)((uschar *)p + length_table[type]);
        type = p->type;
        }
      }
    }
  }


/* Sort and output the items we've created, along with any events left over
from the previous bar (ending tied notes). We relativize the times, and make
use of running status. Stop when we hit either the end, or an event that is
past the end of the bar, unless this is the last bar being played. */

qsort(events, next_event - events, sizeof(midi_event), cf);

for (eptr = events; eptr < next_event; eptr++)
  {
  if (!is_lastbar && eptr->time > this_barlength) break;

  writevar(mac_muldiv(eptr->time - last_written_time, 24, len_crotchet));
  last_written_time = eptr->time;

  if ((eptr->data[1] & 0xf0) == 0x90)
    {
    if (eptr->data[1] != running_status)
      {
      writebyte(eptr->data[1]);
      running_status = eptr->data[1];
      }
    writebyte(eptr->data[2]);
    writebyte(eptr->data[3]);
    }
  else
    {
    int i;
    running_status = 0;
    for (i = 1; i <= eptr->data[0]; i++) writebyte(eptr->data[i]);
    }
  }

/* If we haven't written all the items (some notes are tied over the barline),
shift down the remaining events, and re-relativize them. */

neptr = events;
next_event_seq = 0;
for (; eptr < next_event; eptr++, neptr++)
  {
  *neptr = *eptr;
  neptr->time -= this_barlength;
  next_event_seq = neptr->seq + 1;
  }
next_event = neptr;

/* Set time for start of next bar */

last_written_time -= this_barlength;
}



/*************************************************
*                  Write MIDI file               *
*************************************************/

/* This is the only external entry to this set of functions. The data is all in
memory and global variables.

Arguments:  none
Returns:    nothing
*/

void
midi_write(void)
{
int start_bar;
int end_bar;
int i, stave;
int temp;

midi_file = Ufopen(midi_filename, "w");

if (midi_file == NULL)
  {
  error_moan(4, midi_filename, strerror(errno));
  return;
  }

DEBUG(("midi_write()\n"));

/* Initialize things */

if (play_movt_number <= 0) play_movt_number = 1;

play_movt = movement[play_movt_number];
play_tempo = play_movt->play_tempo;

start_bar = (play_startbar <= 0)? 1 : play_startbar;
end_bar = (play_endbar <= 0)? play_movt->barcount : play_endbar;

play_onebar_only = (start_bar == end_bar);

play_volume = 127;     /* PRO TEM */


/* Stave selection is the main stave selection */

for (i = 0; i < stave_bitvec_size; i++)
  play_staves[i] = play_movt->staves[i] & main_staves[i];

/* Initialize the tie information */

for (stave = 1; stave <= play_movt->laststave; stave++)
  stavetie[stave] = FALSE;

/* Miscellaneous stuff */

last_written_time = 0;
running_status = 0;

/* Get store in which to hold a bar's events before sorting. For the
first bar, it is empty at the start. */

events = malloc(sizeof(midi_event) * 1000);
next_event = events;
next_event_seq = 0;

/* Set up the initial per-stave vectors */

memcpy(midi_channel, play_movt->midi_channel, sizeof(midi_channel));
memcpy(midi_channel_volume, play_movt->midi_volume, sizeof(midi_channel_volume));
memcpy(midi_note, play_movt->midi_note, sizeof(midi_note));

/* Write header chunk */

fprintf(midi_file, "MThd");
write32(6);                     /* length */
write16(0);                     /* format */
write16(1);                     /* number of tracks */
write16(24);                    /* ticks per crotchet (MIDI standard) */

/* Now write the track, leaving space for the length */

fprintf(midi_file, "MTrk");
write32(0);
file_count = 0;                 /* For computing the length */

/* Output any user-supplied initialization. The user's data is a plain MIDI
stream, without any time deltas. Ensure that each event is set to occur at the
beginning (time zero). */

if (curmovt->midi_start != NULL)
  {
  for (i = 1; i <= play_movt->midi_start[0]; i++)
    {
    if ((play_movt->midi_start[i] & 0x80) != 0) writebyte(0);
    writebyte(play_movt->midi_start[i]);
    }
  }

/* Default tempo - can change for specific bars */

writebyte(0);
writebyte(0xff);
writebyte(0x51);
writebyte(0x03);

temp = 60000000/play_tempo;     /* Microseconds per crotchet */
writebyte(temp>>16);
writebyte(temp>>8);
writebyte(temp);

/* Assign MIDI voices to MIDI channels if required. */

for (i = 1; i <= midi_maxchannel; i++)
  {
  if (play_movt->midi_voice[i-1] < 128)
    {
    writebyte(0);               /* delta time */
    writebyte(0xC0 + i - 1);
    writebyte(play_movt->midi_voice[i-1]);
    }
  }

/* Initialize the per-stave relative volume & transpose vectors */

memcpy(stave_volume, play_movt->play_volume, sizeof(stave_volume));
memcpy(midi_transpose, play_movt->playtranspose, sizeof(midi_transpose));

/* Unless starting at the beginning, scan through the playing changes
blocks that were set up by a heading directive, and update the data if the
change happened before this bar. The chain is for all staves, so we must always
scan to the end. */

if (start_bar > 1)
  {
  b_playchangestr *change = play_movt->play_changes;
  while (change != NULL)
    {
    if (change->barno < play_bar)
      {
      int stave = change->stave;

      /* If change volume occurs with midi channel change, it is a
      channel volume change, not a stave volume change. */

      if (change->volume < 128 && change->channel == 128)
        stave_volume[stave] = change->volume;

      if (change->transpose) midi_transpose[stave] += change->transpose;

      if (change->channel < 128)
        {
        midi_channel[stave] = change->channel;
        if (change->volume < 128)
          midi_channel_volume[change->channel-1] = change->volume;
        }
      if (change->note < 128) midi_note[stave] = change->note;

      if (change->voice < 128)
        {
        writebyte(0);   /* delta time */
        writebyte(0xC0 + midi_channel[stave] - 1);
        writebyte(change->voice);
        }
      }
    change = change->next;
    }
  }

/* Also, if not starting at the beginning, we must scan through the stave data 
for all preceding bars, in order to pick up any in-line MIDI changes. */

for (play_bar = 1; play_bar < start_bar; play_bar++)
  {
  for (stave = 1; stave <= play_movt->laststave; stave++)
    {
    bstr *p = ((play_movt->stavetable)[stave])->barindex[play_bar];
    if (p != NULL && mac_teststave(play_staves, stave))
      {
      int type = p->type;
      while (type != b_End)
        {
        switch(type)
          {
          case b_Jump:
          p = (bstr *)(((b_Jumpstr *)p)->next);                      
          break;
  
          case b_playchange:
            {
            b_playchangestr *change = (b_playchangestr *)p;
            midi_transpose[stave] += change->transpose;
  
            /* If the relative volume parameter occurs with a change of
            channel, it is a channel volume change. Otherwise it is a
            stave volume change. */
  
            if (change->volume < 128 && change->channel == 128)
              stave_volume[stave] = change->volume;
  
            /* Other changes */
  
            if (change->channel < 128)
              {
              midi_channel[stave] = change->channel;
              if (change->volume < 128)
                midi_channel_volume[change->channel - 1] = change->volume;
              }
  
            if (change->note < 128) midi_note[stave] = change->note;
  
            if (change->voice < 128)
              {
              writebyte(0);   /* delta time */
              writebyte(0xC0 + midi_channel[stave] - 1);
              writebyte(change->voice);
              }
            }
          break;
  
          default:
          break;
          }
  
        p = (bstr *)((uschar *)p + length_table[type]);
        type = p->type;
        }
      }
    }
  }  

/* Now write the bars */

repeat_bar = play_bar;
repeat_endbar = -1;
repeat_count = 1;

for (play_bar = start_bar; play_bar <= end_bar;)
  {
  play_nextbar = play_bar + 1;
  writebar(play_bar == end_bar);
  play_bar = play_nextbar;
  }

/* Mark the end of the track, and fill in its length before closing the file */

writebyte(0);
writebyte(0xff);
writebyte(0x2f);
writebyte(0);

fseek(midi_file, 18, SEEK_SET);
write32(file_count);

fclose(midi_file);
}

/* End of midi.c */
