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

  Ultratracker (ULT) module loader

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

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

#include <string.h>

#include "unimod_priv.h"

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

/* header */
typedef struct ULTHEADER
  {
    CHAR id[16];
    CHAR songtitle[32];
    UBYTE reserved;
  }
ULTHEADER;

/* sample information */
typedef struct ULTSAMPLE
  {
    CHAR samplename[32];
    CHAR dosname[12];
    SLONG loopstart;
    SLONG loopend;
    SLONG sizestart;
    SLONG sizeend;
    UBYTE volume;
    UBYTE flags;
    UWORD speed;
    SWORD finetune;
  }
ULTSAMPLE;

typedef struct ULTEVENT
  {
    UBYTE note, sample, eff, dat1, dat2;
  }
ULTEVENT;

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

#define ULTS_16BITS     4
#define ULTS_LOOP       8
#define ULTS_REVERSE    16

#define ULT_VERSION_LEN 18
static CHAR ULT_Version[ULT_VERSION_LEN] = "Ultra Tracker v1.x";

static ULTEVENT ev;

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

static BOOL
ULT_Test (void)
{
  CHAR id[16];

  if (!_mm_read_string (id, 15, modreader))
    return 0;
  if (strncmp (id, "MAS_UTrack_V00", 14))
    return 0;
  if ((id[14] < '1') || (id[14] > '4'))
    return 0;
  return 1;
}

static BOOL
ULT_Init (void)
{
  return 1;
}

static void
ULT_Cleanup (void)
{
}

static UBYTE 
ReadUltEvent (ULTEVENT * event)
{
  UBYTE flag, rep = 1;

  flag = _mm_read_UBYTE (modreader);
  if (flag == 0xfc)
    {
      rep = _mm_read_UBYTE (modreader);
      event->note = _mm_read_UBYTE (modreader);
    }
  else
    event->note = flag;

  event->sample = _mm_read_UBYTE (modreader);
  event->eff = _mm_read_UBYTE (modreader);
  event->dat1 = _mm_read_UBYTE (modreader);
  event->dat2 = _mm_read_UBYTE (modreader);

  return rep;
}

static BOOL
ULT_Load (BOOL curious)
{
  int t, u, tracks = 0;
  SAMPLE *q;
  ULTSAMPLE s;
  ULTHEADER mh;
  UBYTE nos, noc, nop;

  /* try to read module header */
  _mm_read_string (mh.id, 15, modreader);
  _mm_read_string (mh.songtitle, 32, modreader);
  mh.reserved = _mm_read_UBYTE (modreader);

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

  ULT_Version[ULT_VERSION_LEN - 1] = '3' + (mh.id[14] - '1');
  of.modtype = DupStr (ULT_Version, ULT_VERSION_LEN, 1);
  of.initspeed = 6;
  of.inittempo = 125;
  of.reppos = 0;

  /* read songtext */
  if ((mh.id[14] > '1') && (mh.reserved))
    if (!ReadLinedComment (mh.reserved, 32))
      return 0;

  nos = _mm_read_UBYTE (modreader);
  if (_mm_eof (modreader))
    {
      _mm_errno = MMERR_LOADING_HEADER;
      return 0;
    }

  of.songname = DupStr (mh.songtitle, 32, 1);
  of.numins = of.numsmp = nos;

  if (!AllocSamples ())
    return 0;
  q = of.samples;
  for (t = 0; t < nos; t++)
    {
      /* try to read sample info */
      _mm_read_string (s.samplename, 32, modreader);
      _mm_read_string (s.dosname, 12, modreader);
      s.loopstart = _mm_read_I_ULONG (modreader);
      s.loopend = _mm_read_I_ULONG (modreader);
      s.sizestart = _mm_read_I_ULONG (modreader);
      s.sizeend = _mm_read_I_ULONG (modreader);
      s.volume = _mm_read_UBYTE (modreader);
      s.flags = _mm_read_UBYTE (modreader);
      s.speed = (mh.id[14] >= '4') ? _mm_read_I_UWORD (modreader) : 8363;
      s.finetune = _mm_read_I_SWORD (modreader);

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

      q->samplename = DupStr (s.samplename, 32, 1);
      /* The correct formula for the coefficient would be
         pow(2,(double)s.finetume/OCTAVE/32768), but to avoid floating point
         here, we'll use a first order approximation here.
         1/567290 == Ln(2)/OCTAVE/32768 */
      q->speed = s.speed + s.speed * (((SLONG) s.speed * (SLONG) s.finetune) / 567290);
      q->length = s.sizeend - s.sizestart;
      q->volume = s.volume >> 2;
      q->loopstart = s.loopstart;
      q->loopend = s.loopend;
      q->flags = SF_SIGNED;
      if (s.flags & ULTS_LOOP)
	q->flags |= SF_LOOP;
      if (s.flags & ULTS_16BITS)
	{
	  s.sizeend += (s.sizeend - s.sizestart);
	  s.sizestart <<= 1;
	  q->flags |= SF_16BITS;
	  q->loopstart >>= 1;
	  q->loopend >>= 1;
	}
      q++;
    }

  if (!AllocPositions (256))
    return 0;
  for (t = 0; t < 256; t++)
    of.positions[t] = _mm_read_UBYTE (modreader);
  for (t = 0; t < 256; t++)
    if (of.positions[t] == 255)
      break;
  of.numpos = t;

  noc = _mm_read_UBYTE (modreader);
  nop = _mm_read_UBYTE (modreader);

  of.numchn = ++noc;
  of.numpat = ++nop;
  of.numtrk = of.numchn * of.numpat;
  if (!AllocTracks ())
    return 0;
  if (!AllocPatterns ())
    return 0;
  for (u = 0; u < of.numchn; u++)
    for (t = 0; t < of.numpat; t++)
      of.patterns[(t * of.numchn) + u] = tracks++;

  /* SA37775, CVE-2009-3996 */
  if (of.numchn>=UF_MAXCHAN)
    of.numchn=UF_MAXCHAN - 1;

  /* read pan position table for v1.5 and higher */
  if (mh.id[14] >= '3')
    for (t = 0; t < of.numchn; t++)
      of.panning[t] = _mm_read_UBYTE (modreader) << 4;

  for (t = 0; t < of.numtrk; t++)
    {
      int rep, row = 0;

      UniReset ();
      while (row < 64)
	{
	  rep = ReadUltEvent (&ev);

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

	  while (rep--)
	    {
	      UBYTE eff;
	      int offset;

	      if (ev.sample)
		UniInstrument (ev.sample - 1);
	      if (ev.note)
		UniNote (ev.note + 2 * OCTAVE - 1);

	      /* first effect - various fixes by Alexander Kerkhove and
	         Thomas Neumann */
	      eff = ev.eff >> 4;
	      switch (eff)
		{
		case 0x3:	/* tone portamento */
		  UniEffect (UNI_ITEFFECTG, ev.dat2);
		  break;
		case 0x5:
		  break;
		case 0x9:	/* sample offset */
		  offset = (ev.dat2 << 8) | ((ev.eff & 0xf) == 9 ? ev.dat1 : 0);
		  UniEffect (UNI_ULTEFFECT9, offset);
		  break;
		case 0xb:	/* panning */
		  UniPTEffect (8, ev.dat2 * 0xf);
		  break;
		case 0xc:	/* volume */
		  UniPTEffect (eff, ev.dat2 >> 2);
		  break;
		default:
		  UniPTEffect (eff, ev.dat2);
		  break;
		}

	      /* second effect */
	      eff = ev.eff & 0xf;
	      switch (eff)
		{
		case 0x3:	/* tone portamento */
		  UniEffect (UNI_ITEFFECTG, ev.dat1);
		  break;
		case 0x5:
		  break;
		case 0x9:	/* sample offset */
		  if ((ev.eff >> 4) != 9)
		    UniEffect (UNI_ULTEFFECT9, ((UWORD) ev.dat1) << 8);
		  break;
		case 0xb:	/* panning */
		  UniPTEffect (8, ev.dat1 * 0xf);
		  break;
		case 0xc:	/* volume */
		  UniPTEffect (eff, ev.dat1 >> 2);
		  break;
		default:
		  UniPTEffect (eff, ev.dat1);
		  break;
		}

	      UniNewline ();
	      row++;
	    }
	}
      if (!(of.tracks[t] = UniDup ()))
	return 0;
    }
  return 1;
}

static CHAR *
ULT_LoadTitle (void)
{
  CHAR s[32];

  _mm_fseek (modreader, 15, SEEK_SET);
  if (!_mm_read_UBYTES (s, 32, modreader))
    return NULL;

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

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

MLOADER load_ult =
{
  NULL,
  "ULT",
  "ULT (UltraTracker)",
  ULT_Init,
  ULT_Test,
  ULT_Load,
  ULT_Cleanup,
  ULT_LoadTitle
};


/* ex:set ts=4: */
