/* readsong.c
 *
 * Routines to read MS song and convert to TRAKs
 */

#include "smus.h"
#include "music.h"

#define MAXNEST 16      /* Maximum nesting for repeats */

typedef struct {        /* Structure of a RepeatStack frame */
   char *SongPtr;
   int  NumReps;
} StackFrame;

static StackFrame RepStack[MAXNEST];
static int StackPtr;    /* Indexes next empty frame on stack */

/* Number of notes in this frame for each color */
static int NoteCount[MAXCOLORS+1];

static LONG TimeNow;  /* Current time at start of frame in 144ths */

/*
 * Handle an end-of-repeat command...
 *  If repeats left to go, return ptr to start of repeat block
 *   else return NULL
 */
char *FinishRep(CurSong)
   char *CurSong;
{
   int Last = StackPtr - 1;

   if ((RepStack[Last].NumReps)-- > 1) /* if have more repeats to go */
      return(RepStack[Last].SongPtr);  /* return saved song pointer */
   else
   {
      StackPtr--;                      /* one less item in repeat stack */
      return(NULL);
   }
} /* Finish Rep */



/*
 * Handle a Begin Repeat:
 *   Save repeat count and current MS song position on repeat stack
 */
BeginRep(CurSong,Reps)
   char *CurSong;    /* ptr to next Cmd after BEGREP */
   char Reps;         /* # of times to repeat */
{
   RepStack[StackPtr].SongPtr = CurSong;     /* save song pointer */
   RepStack[StackPtr].NumReps = Reps;        /* save number of repeats */
   StackPtr++;       /* point to empty space on top of stack */
} /* Begin Rep */



/*
 * ChordedNote: see if this note is chorded
 *  Return TRUE if this note is a member of a chord but is not the
 *  most important note of the chord (the last in the frame)
 */
BOOL
ChordedNote(NoteColor)
   char NoteColor;
{
      /* if not last note of this color in this frame */
   if (NoteCount[NoteColor] > 1)
   {
      NoteCount[NoteColor] -= 1;    /* one less note of this color */
      return(TRUE);
   }
   else
      return(FALSE);
} /* ChordedNote */


/*
 * Print a note
 */
PutNote(curnote)
   MCmd *curnote;
{
   int color,duration,freq;
   char bits;

   color = curnote->colbyt & 0x0F;
   duration = DurTab[curnote->durbyt & 0x1F];   /* dur in 144ths */
   freq = curnote->freq & 0x7F;

   if (!(curnote->colbyt & 0x10))
      printf("  N:");
   else
      printf("  R:");
   printf("%x  C:%x  D:%x  ",
          freq,color,duration);
   bits = curnote->durbyt & ACCMASK;
   if (bits & 0xC0)      /* if is accidental */
   {
      if (bits & 0x80)   /* if is # or b, NOT natural */
      {
         if (bits & 0x40)
            printf("Acc: b");       /* flat = 11 */
         else
            printf("Acc: #");       /* sharp = 10 */
      }
      else printf("Acc: Nat");      /* natural = 01 */
   }
} /* PutNote */

/*
 *  Output rests on given track to make up given duration
 */
Rest(RestColor,RestTime)
   int   RestColor;
   int   RestTime;
{
   int   RestNo;
   int   RestInd;
   SEvent RestEvent;

   while (RestTime > 0)
   {
         /* First, try to find an exact match for this rest length */
      RestInd = 0;
      for (RestNo = 3 ; RestNo < MS_WHOLE_NOTE; RestNo++)
         if (DurTab[RestNo] == RestTime)
            RestInd = RestNo;

      if (RestInd == 0)             /* if not exact match */
      {                             /* find first of 1,1/2,1/4,... to fit */
         RestNo = MAX_REST;         /* max rest is whole note */
         while ((RestTab[RestNo] > RestTime) && (RestNo > 0))
            RestNo--;
         RestInd = RestDurInd[RestNo];
      }
      RestTime -= DurTab[RestInd];
      RestEvent.sID = SID_Rest;
      RestEvent.data = IFFDurTab[RestInd];

      Traks[RestColor].CurrTime +=        /* in 144ths of a whole note */
         DurTab[RestInd];
      if (Debug)
      {
         printf("  <R: %d,",DurTab[RestInd]);
         printf("T: %ld>",Traks[RestColor].CurrTime);
      }
      AddEvent(RestColor,&RestEvent);
   } /* while */
} /* Rest */

/*
 *  Advance track by given MS Duration table index
 *   Possibly add rests if track lagging behind current time
 */
TrackTime(ThisTrack,Event,DurInd,FrameTime)
   int   ThisTrack;                       /* Track to advance */
   SEvent *Event;                         /* Note causing track to adv */
   int   DurInd;                          /* MS index into DurTab */
   int   FrameTime;
{

      /* FrameTime holds time at the start of this frame */
   if (Traks[ThisTrack].CurrTime < FrameTime)  /* if this track lagging */
      Rest(ThisTrack,FrameTime - Traks[ThisTrack].CurrTime);
          /* catch up with rests */

   if (!(Event->data & (1<<7)))           /* if not a chorded note */
   {                                      /* then advance time by note dur */
      Traks[ThisTrack].CurrTime +=        /* in 144ths of a whole note */
         DurTab[DurInd];
      Traks[ThisTrack].TimeToGo = DurTab[DurInd];  /* set track note time */
      if (Debug)
         printf(" => %ld",Traks[ThisTrack].CurrTime);
   }
} /* TrackTime */


#define  NO_TRACK 0        /* returned by ChooseTrack to discard note */

/*
 * Choose a track for this note,based on color
 *   Returns track number to play on, or NO_TRACK if none available
 */
int
ChooseTrack(ThisColor,ExpandNote)
   int   ThisColor;
   BOOL  ExpandNote;         /* true if not IFFChords and note chorded */
{
   int i;
   int TargTrak;
   BOOL Steal = FALSE;

   TargTrak = ThisColor;
         /* Steal a note if this note is on an 'extra' track or
            if we are expanding this chorded note or
            if our track is already playing a note from earlier frame */
   if (Traks[ThisColor].TrakData == NULL)
      Steal = TRUE;
   if (ExpandNote)
      Steal = TRUE;
   if (Traks[ThisColor].TimeToGo > 0)      /* time left to go on note */
      Steal = TRUE;
   if (Steal)
   {
      TargTrak = NO_TRACK;

      for (i = MAXCOLORS; ((i > 0) && (TargTrak == NO_TRACK)); i--)
         if ((Traks[i].TrakData != NULL) &&  /* not null track and */
             (Traks[i].TimeToGo == 0) &&     /* not playing note and */
             (i != ThisColor) &&             /* not native track and */
             (NoteCount[i] == 0))         /* no notes same color in frame */
            TargTrak = i;
   }
   return(TargTrak);
} /* ChooseTrack */


/*
 * Translate a MS note to an SMUS note
 *   and add it to the appropriate SMUS track
 */
Cmd2Note(curnote,FrameTime)
   MCmd *curnote;
   int   FrameTime;
{
   int   freq;             /* MS frequency value of MS note */
   int   ThisTrack;        /* SMUS track to add to */
   SEvent ThisEvent;       /* SMUS Event being built */
   char acstat;            /* MS Accidental and Accented bits */
   BOOL  Expand = FALSE;   /* True when expanding a chorded note */
   int   ThisColor;        /* Color of this note */
   BOOL  Thief;            /* True if this note stole a track */

   freq = curnote->freq & 0x7F;                    /* extract frequency */

   ThisEvent.data = IFFDurTab[curnote->durbyt & 0x1F];   /* set duration */

   if (!(curnote->colbyt & 0x10))      /* if not a note its a rest */
   {
      if ((curnote->colbyt & STARTTIE) && /* if tied to next note */
          (!IFFChords) && (MaxTracks == 0)) /* and not stealing tracks */
      {
         ThisEvent.data |= (1<<6);     /* set SMUS TieOut bit */
         if (Debug)
            printf("   Tie Out");
      }
      if (ChordedNote( (curnote->colbyt & 0x0F) ))    /* if chorded */
      {
         ThisEvent.data |= (1<<7);     /* set SMUS Chord bit */
         Expand = !IFFChords;
         if (Debug)
         {
            if (Expand)
               printf("   Expand");
            else
               printf("   Chorded");
         }
      }
      acstat = curnote->durbyt & 0xC0;
      if (acstat & 0xC0)      /* if is accidental */
      {
         ThisEvent.sID = freq;   /* maybe Natural */
         if (acstat & 0x80)   /* if is # or b, NOT natural */
         {
           if (acstat & 0x40)
               ThisEvent.sID--;              /* flat = 11 */
            else                             /* #$#$ reversed 8/16 */
               ThisEvent.sID++;              /* sharp = 10 */
         }
      } /* if accidental */
      else /* NOT accidental */
         ThisEvent.sID = keytab[freq]; /* voila! Just add water... */
      ThisEvent.sID &= 0x7F;
   } /* if note */
   else /* its a rest! */
      ThisEvent.sID = SID_Rest;

   if (ThisEvent.sID < SID_Rest)    /* ignore rests; they only advance */
   {                                /* TimeNow and are not translated */
      ThisColor = curnote->colbyt & 0x0F;
      ThisTrack = ChooseTrack(ThisColor,Expand);
      if ((ThisTrack > 0) && (ThisTrack != ThisColor))   /* if bumped */
      {
         ThisEvent.data &= 0x7F;                         /* no chord bit */
         NumNotes[ThisTrack]++;              /* one more note on this trak */
         /* Set-Instrument to desired one */
         Thief = TRUE;              /* this note stole a track */
      }
      else
         Thief = FALSE;             /* this note did not steal a track */

      if (ThisTrack != NO_TRACK)
         TrackTime(ThisTrack,&ThisEvent,curnote->durbyt &0x1F,FrameTime);

      if (Debug)
      {
         PutNote(curnote);
         if (Thief)
            printf("  {%x} ",ThisTrack);
         if (ThisTrack == NO_TRACK)
            printf("  {lost} ");
         printf("\n");                       /* done printing this note */
      }

      if ((ThisEvent.sID != SID_Rest) && (ThisTrack != NO_TRACK))
         AddEvent(ThisTrack,&ThisEvent);     /* add to SMUS track */
      /* if (Thief) then Set-Instrument back */
   } /* if not rest */
} /* Cmd2Note */
    

/*
 * NextCmd:  reads one command from a MS song
 *  and return pointer to next byte.
 */
char *
NextCmd(cursong,IgnoreReps)
   char *cursong;
   BOOL IgnoreReps;
{
   char  *mysong;
   UBYTE cmd;

   if (*cursong == FRAMEMARK)       /* if mark, return 1st byte in frame */
      return(++cursong);
   if (!(*cursong & 0x80 )) {       /* if not a command */
      cursong += 3;                 /* skip ahead 3 bytes */
      return(cursong);
   } else {                         /* else is a command */
      cmd = ((UBYTE) *cursong) & 0xFF;    /* extract command byte */
      switch (cmd) {
         case SONGEND:
            cursong = NULL;         /* return NULL at song end */
            break;
         case BEGREP:
            cursong++;              /* move past this command */
            BeginRep(cursong+1,*cursong);
            cursong++;              /* skip over repeat count */
            break;
         case ENDREP:
            cursong++;              /* move past this command */
            if (IgnoreReps == FALSE) { /* maybe ignoring repeats? */
               mysong = FinishRep(cursong);
               if (mysong != NULL)
                  cursong = mysong;
            } /* if not ignoring reps */
            break;
         default:
            cursong++;
            break;
      } /* switch */
   } /* else */
   return(cursong);
} /* NextCmd */

/*
 * Cmd2Event: translate event pointed to by cursong to IFF SEvent,
 *  put in track, and print it.
 */  
Cmd2Event(cursong,FrameTime)
   char *cursong;
   int   FrameTime;
{
   MCmd MNote;
   UBYTE  cmd;

   if (!(*cursong & 0x80 )) {       /* if not a command */
      MNote.colbyt = *cursong++;    /* then set up Note structure */
      MNote.durbyt = *cursong++;
      MNote.freq = *cursong++;
      Cmd2Note(&MNote,FrameTime);             /* and translate to SMUS */
   } else {
      cmd = (UBYTE) *cursong++;     /* extract command byte */
      cmd &= 0xFF;
       switch (cmd) {
         case  MEASURE:
               break;
         case  ENDREP:
               break;
         case  BEGREP:
               break;
         case  SONGEND:
               break;
         default:
               if (Debug)
                  printf("  Burp! %x\n",cmd);
               break;
      } /* switch */
   } /* else */
} /* Cmd2Event */

/*
 * Figure how many notes of each color in this frame,
 *  given ptr to first Cmd in frame
 */
FrameChords(SongP)
   char  *SongP;           /* local copy of song ptr steps thru one frame */
{
   int i;

   for (i = 0; i <= MAXCOLORS; i++)
      NoteCount[i] = 0;
   do /* Do a frame at a time */
   {
         /* if Cmd is not a command and not a rest then its a note */
      if ( (!(*SongP & 0x80)) && (!(*SongP & 0x10)) ) {
         NoteCount[*SongP & 0x0F]++;   /* one more note in this color */
      }
      SongP = NextCmd(SongP,TRUE);          /* go on to next Command */
   } while ((*SongP & 0xFF) != FRAMEMARK);
} /* FrameChords */


/*
 *  Decrease time left to play on all notes being played
 */
LessTime(FrameTime)
   int   FrameTime;
{
   int   TrakNo;
   int   NewTime;


   for (TrakNo = 1; TrakNo <= MAXCOLORS; TrakNo++)
   {
      if (Traks[TrakNo].TimeToGo > 0)
      {
         NewTime = Traks[TrakNo].TimeToGo - FrameTime;
         if (NewTime < 0)
            Traks[TrakNo].TimeToGo = 0;
         else
            Traks[TrakNo].TimeToGo = NewTime;
      } /* if time left to go */
   }
} /* LessTime */


/*
 * Read an entire MS song, a command at a time
 */
ReadSong(songst)
   char *songst;
{
   char *SongP = songst;
   char *FrameP;
   int   FrameTime;
   int   FrameNo = 0;
   int   PrevFrameTime = 0;

   TimeNow = 0;         /* start current time at 0 */

   do {                                   /* repeat 'till end of song */
      FrameP = NULL;
      while ((*SongP & 0xFF) == FRAMEMARK) {       /* skip frame marks */
         FrameP = SongP++;
         if (Debug)
            printf("Frame %5d Time: %ld\n",FrameNo++,TimeNow);
         FrameTime = TimeNow;
      } /* while frame marker */
      if (FrameP != NULL)              /* If not starting a frame */
      {
         FrameChords(++FrameP);        /* find chorded notes in frame */
         if (!(*FrameP & 0x80))        /* if min dur of frame not cmd */
         {
               /* Advance current time by min dur event for this frame */
            TimeNow += DurTab[(FrameP[1] & 0x1F)];
               /* Decrease time left on notes being played */
            LessTime(PrevFrameTime);
            PrevFrameTime = DurTab[(FrameP[1] & 0x1F)];
                /* LessTime(DurTab[(FrameP[1] & 0x1F)]); */
         } /* if */
      } /* if */
      Cmd2Event(SongP,FrameTime);
      SongP = NextCmd(SongP,FALSE);
   } while (SongP != NULL);
} /* Read Song */

