/*      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$

  Composer 669 module loader

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

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

#include <string.h>

#include "unimod_priv.h"

/*========== Module structure */

/* header */
typedef struct S69HEADER
  {
    UBYTE marker[2];
    CHAR message[108];
    UBYTE nos;
    UBYTE nop;
    UBYTE looporder;
    UBYTE orders[0x80];
    UBYTE tempos[0x80];
    UBYTE breaks[0x80];
  }
S69HEADER;

/* sample information */
typedef struct S69SAMPLE
  {
    CHAR filename[13];
    SLONG length;
    SLONG loopbeg;
    SLONG loopend;
  }
S69SAMPLE;

/* encoded note */
typedef struct S69NOTE
  {
    UBYTE a, b, c;
  }
S69NOTE;

/*========== Loader variables */

/* current pattern */
static S69NOTE *s69pat = NULL;
/* Module header */
static S69HEADER *mh = NULL;

/* file type identification */
static const CHAR *S69_Version[] =
{
  "Composer 669",
  "Extended 669"
};

/*========== Loader code */

static BOOL
S69_Test (void)
{
  UBYTE buf[0x80];

  if (!_mm_read_UBYTES (buf, 2, modreader))
    return 0;
  /* look for id */
  if (!memcmp (buf, "if", 2) || !memcmp (buf, "JN", 2))
    {
      int i;

      /* skip song message */
      _mm_fseek (modreader, 108, SEEK_CUR);
      /* sanity checks */
      if (_mm_read_UBYTE (modreader) > 64)
	return 0;
      if (_mm_read_UBYTE (modreader) > 128)
	return 0;
      if (_mm_read_UBYTE (modreader) > 127)
	return 0;
      /* check order table */
      if (!_mm_read_UBYTES (buf, 0x80, modreader))
	return 0;
      for (i = 0; i < 0x80; i++)
	if ((buf[i] >= 0x80) && (buf[i] != 0xff))
	  return 0;
      /* check tempos table */
      if (!_mm_read_UBYTES (buf, 0x80, modreader))
	return 0;
      for (i = 0; i < 0x80; i++)
	if ((!buf[i]) || (buf[i] > 32))
	  return 0;
      /* check pattern length table */
      if (!_mm_read_UBYTES (buf, 0x80, modreader))
	return 0;
      for (i = 0; i < 0x80; i++)
	if (buf[i] > 0x3f)
	  return 0;
    }
  else
    return 0;

  return 1;
}

static BOOL
S69_Init (void)
{
  if (!(s69pat = (S69NOTE *) _mm_malloc (64 * 8 * sizeof (S69NOTE))))
    return 0;
  if (!(mh = (S69HEADER *) _mm_malloc (sizeof (S69HEADER))))
    return 0;

  return 1;
}

static void
S69_Cleanup (void)
{
  _mm_free (s69pat);
  _mm_free (mh);
}

static BOOL 
S69_LoadPatterns (void)
{
  int track, row, channel;
  UBYTE note, inst, vol, effect, lastfx, lastval;
  S69NOTE *cur;
  int tracks = 0;

  if (!AllocPatterns ())
    return 0;
  if (!AllocTracks ())
    return 0;

  for (track = 0; track < of.numpat; track++)
    {
      /* set pattern break locations */
      of.pattrows[track] = mh->breaks[track] + 1;

      /* load the 669 pattern */
      cur = s69pat;
      for (row = 0; row < 64; row++)
	{
	  for (channel = 0; channel < 8; channel++, cur++)
	    {
	      cur->a = _mm_read_UBYTE (modreader);
	      cur->b = _mm_read_UBYTE (modreader);
	      cur->c = _mm_read_UBYTE (modreader);
	    }
	}

      if (_mm_eof (modreader))
	{
	  _mm_errno = MMERR_LOADING_PATTERN;
	  return 0;
	}

      /* translate the pattern */
      for (channel = 0; channel < 8; channel++)
	{
	  UniReset ();
	  /* set pattern tempo */
	  UniPTEffect (0xf, 78);
	  UniPTEffect (0xf, mh->tempos[track]);

	  lastfx = 0xff, lastval = 0;

	  for (row = 0; row <= mh->breaks[track]; row++)
	    {
	      int a, b, c;

	      /* fetch the encoded note */
	      a = s69pat[(row * 8) + channel].a;
	      b = s69pat[(row * 8) + channel].b;
	      c = s69pat[(row * 8) + channel].c;

	      /* decode it */
	      note = a >> 2;
	      inst = ((a & 0x3) << 4) | ((b & 0xf0) >> 4);
	      vol = b & 0xf;

	      if (a < 0xff)
		{
		  if (a < 0xfe)
		    {
		      UniInstrument (inst);
		      UniNote (note + 2 * OCTAVE);
		      lastfx = 0xff;	/* reset background effect memory */
		    }
		  UniPTEffect (0xc, vol << 2);
		}

	      if ((c != 0xff) || (lastfx != 0xff))
		{
		  if (c == 0xff)
		    c = lastfx, effect = lastval;
		  else
		    effect = c & 0xf;

		  switch (c >> 4)
		    {
		    case 0:	/* porta up */
		      UniPTEffect (0x1, effect);
		      lastfx = c, lastval = effect;
		      break;
		    case 1:	/* porta down */
		      UniPTEffect (0x2, effect);
		      lastfx = c, lastval = effect;
		      break;
		    case 2:	/* porta to note */
		      UniPTEffect (0x3, effect);
		      lastfx = c, lastval = effect;
		      break;
		    case 3:	/* frequency adjust */
		      /* DMP converts this effect to S3M FF1. Why not ? */
		      UniEffect (UNI_S3MEFFECTF, 0xf0 | effect);
		      break;
		    case 4:	/* vibrato */
		      UniPTEffect (0x4, effect);
		      lastfx = c, lastval = effect;
		      break;
		    case 5:	/* set speed */
		      if (effect)
			UniPTEffect (0xf, effect);
		      else if (mh->marker[0] != 0x69)
			{
#ifdef MIKMOD_DEBUG
			  fprintf (stderr, "\r669: unsupported super fast tempo at pat=%d row=%d chan=%d\n",
				   track, row, channel);
#endif
			}
		      break;
		    }
		}
	      UniNewline ();
	    }
	  if (!(of.tracks[tracks++] = UniDup ()))
	    return 0;
	}
    }

  return 1;
}

static BOOL 
S69_Load (BOOL curious)
{
  int i;
  SAMPLE *current;
  S69SAMPLE sample;

  /* module header */
  _mm_read_UBYTES (mh->marker, 2, modreader);
  _mm_read_UBYTES (mh->message, 108, modreader);
  mh->nos = _mm_read_UBYTE (modreader);
  mh->nop = _mm_read_UBYTE (modreader);
  mh->looporder = _mm_read_UBYTE (modreader);
  _mm_read_UBYTES (mh->orders, 0x80, modreader);
  for (i = 0; i < 0x80; i++)
    if ((mh->orders[i] >= 0x80) && (mh->orders[i] != 0xff))
      {
	_mm_errno = MMERR_NOT_A_MODULE;
	return 1;
      }
  _mm_read_UBYTES (mh->tempos, 0x80, modreader);
  for (i = 0; i < 0x80; i++)
    if ((!mh->tempos[i]) || (mh->tempos[i] > 32))
      {
	_mm_errno = MMERR_NOT_A_MODULE;
	return 1;
      }
  _mm_read_UBYTES (mh->breaks, 0x80, modreader);
  for (i = 0; i < 0x80; i++)
    if (mh->breaks[i] > 0x3f)
      {
	_mm_errno = MMERR_NOT_A_MODULE;
	return 1;
      }

  /* set module variables */
  of.initspeed = 4;
  of.inittempo = 78;
  of.songname = DupStr (mh->message, 36, 1);
  of.modtype = strdup (S69_Version[memcmp (mh->marker, "JN", 2) == 0]);
  of.numchn = 8;
  of.numpat = mh->nop;
  of.numins = of.numsmp = mh->nos;
  of.numtrk = of.numchn * of.numpat;
  of.flags = UF_XMPERIODS | UF_LINEAR;

  for (i = 35; (i >= 0) && (mh->message[i] == ' '); i--)
    mh->message[i] = 0;
  for (i = 36 + 35; (i >= 36 + 0) && (mh->message[i] == ' '); i--)
    mh->message[i] = 0;
  for (i = 72 + 35; (i >= 72 + 0) && (mh->message[i] == ' '); i--)
    mh->message[i] = 0;
  if ((mh->message[0]) || (mh->message[36]) || (mh->message[72]))
    if ((of.comment = (CHAR *) _mm_malloc (3 * (36 + 1) + 1)))
      {
	strncpy (of.comment, mh->message, 36);
	strcat (of.comment, "\r");
	if (mh->message[36])
	  strncat (of.comment, mh->message + 36, 36);
	strcat (of.comment, "\r");
	if (mh->message[72])
	  strncat (of.comment, mh->message + 72, 36);
	strcat (of.comment, "\r");
	of.comment[3 * (36 + 1)] = 0;
      }

  if (!AllocPositions (0x80))
    return 0;
  for (i = 0; i < 0x80; i++)
    {
      if (mh->orders[i] >= mh->nop)
	break;
      of.positions[i] = mh->orders[i];
    }
  of.numpos = i;
  of.reppos = mh->looporder < of.numpos ? mh->looporder : 0;

  if (!AllocSamples ())
    return 0;
  current = of.samples;

  for (i = 0; i < of.numins; i++)
    {
      /* sample information */
      _mm_read_UBYTES ((UBYTE *) sample.filename, 13, modreader);
      sample.length = _mm_read_I_SLONG (modreader);
      sample.loopbeg = _mm_read_I_SLONG (modreader);
      sample.loopend = _mm_read_I_SLONG (modreader);
      if (sample.loopend == 0xfffff)
	sample.loopend = 0;

      if ((sample.length < 0) || (sample.loopbeg < -1) || (sample.loopend < -1))
	{
	  _mm_errno = MMERR_LOADING_HEADER;
	  return 0;
	}

      current->samplename = DupStr (sample.filename, 13, 1);
      current->seekpos = 0;
      current->speed = 0;
      current->length = sample.length;
      current->loopstart = sample.loopbeg;
      current->loopend = (sample.loopend < sample.length) ? sample.loopend : sample.length;
      current->flags = (sample.loopbeg < sample.loopend) ? SF_LOOP : 0;
      current->volume = 64;

      current++;
    }

  if (!S69_LoadPatterns ())
    return 0;

  return 1;
}

static CHAR *
S69_LoadTitle (void)
{
  CHAR s[36];

  _mm_fseek (modreader, 2, SEEK_SET);
  if (!_mm_read_UBYTES (s, 36, modreader))
    return NULL;

  return (DupStr (s, 36, 1));
}

/*========== Loader information */

MLOADER load_669 =
{
  NULL,
  "669",
  "669 (Composer 669, Unis 669)",
  S69_Init,
  S69_Test,
  S69_Load,
  S69_Cleanup,
  S69_LoadTitle
};

/* ex:set ts=4: */
