
/*

   Name:
   MUNITRK.C

   Description:
   All routines dealing with the manipulation of UNITRK(tm) streams

   Portability:
   All systems - all compilers

 */
#include <malloc.h>
#include <string.h>
#include "mikmod.h"

#define BUFPAGE  128		/* smallest unibuffer size */
#define TRESHOLD 16

/* unibuffer is increased by BUFPAGE
   bytes when unipc reaches unimax-TRESHOLD */



/*
   Ok.. I'll try to explain the new internal module format.. so here it goes:


   The UNITRK(tm) 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 19 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.

 */



UWORD unioperands[256] =
{
    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_S3MEFFECTT */
    1,				/* UNI_XMEFFECTA */
    1				/* UNI_XMEFFECTP */
};


/***************************************************************************
>>>>>>>>>>> Next are the routines for reading a UNITRK stream: <<<<<<<<<<<<<
***************************************************************************/


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 = rowstart + (*(rowpc++) & 0x1f);
}


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


void UniSkipOpcode(UBYTE op)
{
    UWORD t = unioperands[op];
    while (t--)
	UniGetByte();
}


UBYTE *UniFindRow(UBYTE * t, UWORD row)
/*
   Finds the address of row number 'row' in the UniMod(tm) stream 't'

   returns NULL if the row can't be found.
 */
{
    UBYTE c, l;

    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;		/* havn't reached row yet.. update row */
	t += c & 0x1f;		/* point t to the next row */
    }

    return t;
}



/***************************************************************************
>>>>>>>>>>> Next are the routines for CREATING UNITRK streams: <<<<<<<<<<<<<
***************************************************************************/


static UBYTE *unibuf;		/* pointer to the temporary unitrk buffer */
static UWORD unimax;		/* maximum number of bytes to be written to this buffer */

static UWORD unipc;		/* index in the buffer where next opcode will be written */
static UWORD unitt;		/* holds index of the rep/len byte of a row */
static UWORD lastp;		/* holds index to the previous row (needed for compressing) */


void UniReset(void)
/*
   Resets index-pointers to create a new track.
 */
{
    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 */
}


void UniWrite(UBYTE data)
/*
   Appends one byte of data to the current row of a track.
 */
{
    /* write byte to current position and update */

    unibuf[unipc++] = data;

    /* Check if we've reached the end of the buffer */

    if (unipc > (unimax - TRESHOLD)) {

	UBYTE *newbuf;

	/* We've reached the end of the buffer, so expand
	   the buffer by BUFPAGE bytes */

	newbuf = (UBYTE *) realloc(unibuf, unimax + BUFPAGE);

	/* Check if realloc succeeded */

	if (newbuf != NULL) {
	    unibuf = newbuf;
	    unimax += BUFPAGE;
	} else {
	    /* realloc failed, so decrease unipc so we won't write beyond
	       the end of the buffer.. I don't report the out-of-memory
	       here; the UniDup() will fail anyway so that's where the
	       loader sees that something went wrong */

	    unipc--;
	}
    }
}


void UniInstrument(UBYTE ins)
/*
   Appends UNI_INSTRUMENT opcode to the unitrk stream.
 */
{
    UniWrite(UNI_INSTRUMENT);
    UniWrite(ins);
}


void UniNote(UBYTE note)
/*
   Appends UNI_NOTE opcode to the unitrk stream.
 */
{
    UniWrite(UNI_NOTE);
    UniWrite(note);
}


void UniPTEffect(UBYTE eff, UBYTE dat)
/*
   Appends UNI_PTEFFECTX opcode to the unitrk stream.
 */
{
    if (eff != 0 || dat != 0) {	/* don't write empty effect */
	UniWrite(UNI_PTEFFECT0 + eff);
	UniWrite(dat);
    }
}


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

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


void UniNewline(void)
/*
   Closes the current row of a unitrk stream (updates the rep/len byte)
   and sets pointers to start a new row.
 */
{
    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 {
	/* current and previous row aren't equal.. so just update the pointers */

	unibuf[unitt] = len;
	lastp = unitt;
	unitt = unipc;
	unipc++;
    }
}


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

    unibuf[unitt] = 0;

    if ((d = (UBYTE *) malloc(unipc)) == NULL) {
	myerr = ERROR_ALLOC_STRUCT;
	return NULL;
    }
    memcpy(d, unibuf, unipc);

    return d;
}


UWORD TrkLen(UBYTE * t)
/*
   Determines the length (in rows) of a unitrk stream 't'
 */
{
    UWORD len = 0;
    UBYTE c;

    while (c = *t & 0x1f) {
	len += c;
	t += c;
    }
    len++;

    return len;
}


BOOL UniInit(void)
{
    unimax = BUFPAGE;

    if (!(unibuf = (UBYTE *) malloc(unimax))) {
	myerr = ERROR_ALLOC_STRUCT;
	return 0;
    }
    return 1;
}


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