/*      MikMod sound library
   (c) 1998, 1999 Miodrag Vallat and others - see file AUTHORS for
   complete list.

   This library is free software; you can redistribute it and/or modify
   it under the terms of the GNU Library General Public License as
   published by the Free Software Foundation; either version 2 of
   the License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Library General Public License for more details.

   You should have received a copy of the GNU Library General Public
   License along with this library; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
   02111-1307, USA.
 */

/*==============================================================================

  $Id$

  All routines dealing with the manipulation of UNITRK streams

==============================================================================*/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "unimod_priv.h"

#include <string.h>

/* Unibuffer chunk size */
#define BUFPAGE  128

UWORD unioperands[UNI_LAST] =
{
  0,				/* not used */
  1,				/* UNI_NOTE */
  1,				/* UNI_INSTRUMENT */
  1,				/* UNI_PTEFFECT0 */
  1,				/* UNI_PTEFFECT1 */
  1,				/* UNI_PTEFFECT2 */
  1,				/* UNI_PTEFFECT3 */
  1,				/* UNI_PTEFFECT4 */
  1,				/* UNI_PTEFFECT5 */
  1,				/* UNI_PTEFFECT6 */
  1,				/* UNI_PTEFFECT7 */
  1,				/* UNI_PTEFFECT8 */
  1,				/* UNI_PTEFFECT9 */
  1,				/* UNI_PTEFFECTA */
  1,				/* UNI_PTEFFECTB */
  1,				/* UNI_PTEFFECTC */
  1,				/* UNI_PTEFFECTD */
  1,				/* UNI_PTEFFECTE */
  1,				/* UNI_PTEFFECTF */
  1,				/* UNI_S3MEFFECTA */
  1,				/* UNI_S3MEFFECTD */
  1,				/* UNI_S3MEFFECTE */
  1,				/* UNI_S3MEFFECTF */
  1,				/* UNI_S3MEFFECTI */
  1,				/* UNI_S3MEFFECTQ */
  1,				/* UNI_S3MEFFECTR */
  1,				/* UNI_S3MEFFECTT */
  1,				/* UNI_S3MEFFECTU */
  0,				/* UNI_KEYOFF */
  1,				/* UNI_KEYFADE */
  2,				/* UNI_VOLEFFECTS */
  1,				/* UNI_XMEFFECT4 */
  1,				/* UNI_XMEFFECTA */
  1,				/* UNI_XMEFFECTE1 */
  1,				/* UNI_XMEFFECTE2 */
  1,				/* UNI_XMEFFECTEA */
  1,				/* UNI_XMEFFECTEB */
  1,				/* UNI_XMEFFECTG */
  1,				/* UNI_XMEFFECTH */
  1,				/* UNI_XMEFFECTL */
  1,				/* UNI_XMEFFECTP */
  1,				/* UNI_XMEFFECTX1 */
  1,				/* UNI_XMEFFECTX2 */
  1,				/* UNI_ITEFFECTG */
  1,				/* UNI_ITEFFECTH */
  1,				/* UNI_ITEFFECTI */
  1,				/* UNI_ITEFFECTM */
  1,				/* UNI_ITEFFECTN */
  1,				/* UNI_ITEFFECTP */
  1,				/* UNI_ITEFFECTT */
  1,				/* UNI_ITEFFECTU */
  1,				/* UNI_ITEFFECTW */
  1,				/* UNI_ITEFFECTY */
  2,				/* UNI_ITEFFECTZ */
  1,				/* UNI_ITEFFECTS0 */
  2,				/* UNI_ULTEFFECT9 */
  2,				/* UNI_MEDSPEED */
  0,				/* UNI_MEDEFFECTF1 */
  0,				/* UNI_MEDEFFECTF2 */
  0				/* UNI_MEDEFFECTF3 */
};

/* Sparse description of the internal module format
   ------------------------------------------------

   A UNITRK stream is an array of bytes representing a single track of a pattern.
   It's made up of 'repeat/length' bytes, opcodes and operands (sort of a assembly
   language):

   rrrlllll
   [REP/LEN][OPCODE][OPERAND][OPCODE][OPERAND] [REP/LEN][OPCODE][OPERAND]..
   ^                                         ^ ^
   |-------ROWS 0 - 0+REP of a track---------| |-------ROWS xx - xx+REP of a track...

   The rep/len byte contains the number of bytes in the current row, _including_
   the length byte itself (So the LENGTH byte of row 0 in the previous example
   would have a value of 5). This makes it easy to search through a stream for a
   particular row. A track is concluded by a 0-value length byte.

   The upper 3 bits of the rep/len byte contain the number of times -1 this row
   is repeated for this track. (so a value of 7 means this row is repeated 8 times)

   Opcodes can range from 1 to 255 but currently only opcodes 1 to 52 are being
   used. Each opcode can have a different number of operands. You can find the
   number of operands to a particular opcode by using the opcode as an index into
   the 'unioperands' table.

 */

/*========== Reading routines */

static UBYTE *rowstart;		/* startadress of a row */
static UBYTE *rowend;		/* endaddress of a row (exclusive) */
static UBYTE *rowpc;		/* current unimod(tm) programcounter */


void 
UniSetRow (UBYTE * t)
{
  rowstart = t;
  rowpc = rowstart;
  rowend = t ? rowstart + (*(rowpc++) & 0x1f) : t;
}

UBYTE 
UniGetByte (void)
{
  return (rowpc < rowend) ? *(rowpc++) : 0;
}

UWORD 
UniGetWord (void)
{
  return ((UWORD) UniGetByte () << 8) | UniGetByte ();
}

void 
UniSkipOpcode (UBYTE op)
{
  if (op < UNI_LAST)
    {
      UWORD t = unioperands[op];

      while (t--)
	UniGetByte ();
    }
}

/* Finds the address of row number 'row' in the UniMod(tm) stream 't' returns
   NULL if the row can't be found. */
UBYTE *
UniFindRow (UBYTE * t, UWORD row)
{
  UBYTE c, l;

  if (t)
    while (1)
      {
	c = *t;			/* get rep/len byte */
	if (!c)
	  return NULL;		/* zero ? -> end of track.. */
	l = (c >> 5) + 1;	/* extract repeat value */
	if (l > row)
	  break;		/* reached wanted row? -> return pointer */
	row -= l;		/* haven't reached row yet.. update row */
	t += c & 0x1f;		/* point t to the next row */
      }
  return t;
}

/*========== Writing routines */

static UBYTE *unibuf;		/* pointer to the temporary unitrk buffer */
static UWORD unimax;		/* buffer size */

static UWORD unipc;		/* buffer cursor */
static UWORD unitt;		/* current row index */
static UWORD lastp;		/* previous row index */

/* Resets index-pointers to create a new track. */
void 
UniReset (void)
{
  unitt = 0;			/* reset index to rep/len byte */
  unipc = 1;			/* first opcode will be written to index 1 */
  lastp = 0;			/* no previous row yet */
  unibuf[0] = 0;		/* clear rep/len byte */
}

/* Expands the buffer */
static BOOL 
UniExpand (int wanted)
{
  if ((unipc + wanted) >= unimax)
    {
      UBYTE *newbuf;

      /* Expand the buffer by BUFPAGE bytes */
      newbuf = (UBYTE *) realloc (unibuf, (unimax + BUFPAGE) * sizeof (UBYTE));

      /* Check if realloc succeeded */
      if (newbuf)
	{
	  unibuf = newbuf;
	  unimax += BUFPAGE;
	  return 1;
	}
      else
	return 0;
    }
  return 1;
}

/* Appends one byte of data to the current row of a track. */
void 
UniWriteByte (UBYTE data)
{
  if (UniExpand (1))
    /* write byte to current position and update */
    unibuf[unipc++] = data;
}

void 
UniWriteWord (UWORD data)
{
  if (UniExpand (2))
    {
      unibuf[unipc++] = data >> 8;
      unibuf[unipc++] = data & 0xff;
    }
}

static BOOL 
MyCmp (UBYTE * a, UBYTE * b, UWORD l)
{
  UWORD t;

  for (t = 0; t < l; t++)
    if (*(a++) != *(b++))
      return 0;
  return 1;
}

/* Closes the current row of a unitrk stream (updates the rep/len byte) and sets
   pointers to start a new row. */
void 
UniNewline (void)
{
  UWORD n, l, len;

  n = (unibuf[lastp] >> 5) + 1;	/* repeat of previous row */
  l = (unibuf[lastp] & 0x1f);	/* length of previous row */

  len = unipc - unitt;		/* length of current row */

  /* Now, check if the previous and the current row are identical.. when they
     are, just increase the repeat field of the previous row */
  if (n < 8 && len == l && MyCmp (&unibuf[lastp + 1], &unibuf[unitt + 1], len - 1))
    {
      unibuf[lastp] += 0x20;
      unipc = unitt + 1;
    }
  else
    {
      if (UniExpand (unitt - unipc))
	{
	  /* current and previous row aren't equal... update the pointers */
	  unibuf[unitt] = len;
	  lastp = unitt;
	  unitt = unipc++;
	}
    }
}

/* Terminates the current unitrk stream and returns a pointer to a copy of the
   stream. */
UBYTE *
UniDup (void)
{
  UBYTE *d;

  if (!UniExpand (unitt - unipc))
    return NULL;
  unibuf[unitt] = 0;

  if (!(d = (UBYTE *) _mm_malloc (unipc)))
    return NULL;
  memcpy (d, unibuf, unipc);

  return d;
}

BOOL 
UniInit (void)
{
  unimax = BUFPAGE;

  if (!(unibuf = (UBYTE *) _mm_malloc (unimax * sizeof (UBYTE))))
    return 0;
  return 1;
}

void 
UniCleanup (void)
{
  if (unibuf)
    free (unibuf);
  unibuf = NULL;
}

/* ex:set ts=4: */
