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

  DSIK internal format (DSM) module loader

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

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

#include <string.h>

#include "unimod_priv.h"

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

#define DSM_MAXCHAN (16)
#define DSM_MAXORDERS (128)

typedef struct DSMSONG
  {
    CHAR songname[28];
    UWORD version;
    UWORD flags;
    ULONG reserved2;
    UWORD numord;
    UWORD numsmp;
    UWORD numpat;
    UWORD numtrk;
    UBYTE globalvol;
    UBYTE mastervol;
    UBYTE speed;
    UBYTE bpm;
    UBYTE panpos[DSM_MAXCHAN];
    UBYTE orders[DSM_MAXORDERS];
  }
DSMSONG;

typedef struct DSMINST
  {
    CHAR filename[13];
    UWORD flags;
    UBYTE volume;
    ULONG length;
    ULONG loopstart;
    ULONG loopend;
    ULONG reserved1;
    UWORD c2spd;
    UWORD period;
    CHAR samplename[28];
  }
DSMINST;

typedef struct DSMNOTE
  {
    UBYTE note, ins, vol, cmd, inf;
  }
DSMNOTE;

#define DSM_SURROUND (0xa4)

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

static const CHAR *SONGID = "SONG";
static const CHAR *INSTID = "INST";
static const CHAR *PATTID = "PATT";

static UBYTE blockid[4];
static ULONG blockln;
static ULONG blocklp;
static DSMSONG *mh = NULL;
static DSMNOTE *dsmbuf = NULL;

static CHAR DSM_Version[] = "DSIK DSM-format";

static unsigned char DSMSIG[4 + 4] =
{'R', 'I', 'F', 'F', 'D', 'S', 'M', 'F'};

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

static BOOL
DSM_Test (void)
{
  UBYTE id[12];

  if (!_mm_read_UBYTES (id, 12, modreader))
    return 0;
  if (!memcmp (id, DSMSIG, 4) && !memcmp (id + 8, DSMSIG + 4, 4))
    return 1;

  return 0;
}

static BOOL
DSM_Init (void)
{
  if (!(dsmbuf = (DSMNOTE *) _mm_malloc (DSM_MAXCHAN * 64 * sizeof (DSMNOTE))))
    return 0;
  if (!(mh = (DSMSONG *) _mm_calloc (1, sizeof (DSMSONG))))
    return 0;
  return 1;
}

static void
DSM_Cleanup (void)
{
  _mm_free (dsmbuf);
  _mm_free (mh);
}

static BOOL
GetBlockHeader (void)
{
  /* make sure we're at the right position for reading the
     next riff block, no matter how many bytes read */
  _mm_fseek (modreader, blocklp + blockln, SEEK_SET);

  while (1)
    {
      _mm_read_UBYTES (blockid, 4, modreader);
      blockln = _mm_read_I_ULONG (modreader);
      if (_mm_eof (modreader))
	{
	  _mm_errno = MMERR_LOADING_HEADER;
	  return 0;
	}

      if (memcmp (blockid, SONGID, 4) && memcmp (blockid, INSTID, 4) &&
	  memcmp (blockid, PATTID, 4))
	{
#ifdef MIKMOD_DEBUG
	  fprintf (stderr, "\rDSM: Skipping unknown block type %4.4s\n", blockid);
#endif
	  _mm_fseek (modreader, blockln, SEEK_CUR);
	}
      else
	break;
    }

  blocklp = _mm_ftell (modreader);

  return 1;
}

static BOOL
DSM_ReadPattern (void)
{
  int flag, row = 0;
  SWORD length;
  DSMNOTE *n;

  /* clear pattern data */
  memset (dsmbuf, 255, DSM_MAXCHAN * 64 * sizeof (DSMNOTE));
  length = _mm_read_I_SWORD (modreader);

  while (row < 64)
    {
      flag = _mm_read_UBYTE (modreader);
      if ((_mm_eof (modreader)) || (--length < 0))
	{
	  _mm_errno = MMERR_LOADING_PATTERN;
	  return 0;
	}

      if (flag)
	{
	  n = &dsmbuf[((flag & 0xf) * 64) + row];
	  if (flag & 0x80)
	    n->note = _mm_read_UBYTE (modreader);
	  if (flag & 0x40)
	    n->ins = _mm_read_UBYTE (modreader);
	  if (flag & 0x20)
	    n->vol = _mm_read_UBYTE (modreader);
	  if (flag & 0x10)
	    {
	      n->cmd = _mm_read_UBYTE (modreader);
	      n->inf = _mm_read_UBYTE (modreader);
	    }
	}
      else
	row++;
    }

  return 1;
}

static UBYTE *
DSM_ConvertTrack (DSMNOTE * tr)
{
  int t;
  UBYTE note, ins, vol, cmd, inf;

  UniReset ();
  for (t = 0; t < 64; t++)
    {
      note = tr[t].note;
      ins = tr[t].ins;
      vol = tr[t].vol;
      cmd = tr[t].cmd;
      inf = tr[t].inf;

      if (ins != 0 && ins != 255)
	UniInstrument (ins - 1);
      if (note != 255)
	UniNote (note - 1);	/* normal note */
      if (vol < 65)
	UniPTEffect (0xc, vol);

      if (cmd != 255)
	{
	  if (cmd == 0x8)
	    {
	      if (inf == DSM_SURROUND)
		UniEffect (UNI_ITEFFECTS0, 0x91);
	      else if (inf <= 0x80)
		{
		  inf = (inf < 0x80) ? inf << 1 : 255;
		  UniPTEffect (cmd, inf);
		}
	    }
	  else if (cmd == 0xb)
	    {
	      if (inf <= 0x7f)
		UniPTEffect (cmd, inf);
	    }
	  else
	    {
	      /* Convert pattern jump from Dec to Hex */
	      if (cmd == 0xd)
		inf = (((inf & 0xf0) >> 4) * 10) + (inf & 0xf);
	      UniPTEffect (cmd, inf);
	    }
	}
      UniNewline ();
    }
  return UniDup ();
}

static BOOL
DSM_Load (BOOL curious)
{
  int t;
  DSMINST s;
  SAMPLE *q;
  int cursmp = 0, curpat = 0, track = 0;

  blocklp = 0;
  blockln = 12;

  if (!GetBlockHeader ())
    return 0;
  if (memcmp (blockid, SONGID, 4))
    {
      _mm_errno = MMERR_LOADING_HEADER;
      return 0;
    }

  _mm_read_UBYTES (mh->songname, 28, modreader);
  mh->version = _mm_read_I_UWORD (modreader);
  mh->flags = _mm_read_I_UWORD (modreader);
  mh->reserved2 = _mm_read_I_ULONG (modreader);
  mh->numord = _mm_read_I_UWORD (modreader);
  mh->numsmp = _mm_read_I_UWORD (modreader);
  mh->numpat = _mm_read_I_UWORD (modreader);
  mh->numtrk = _mm_read_I_UWORD (modreader);
  mh->globalvol = _mm_read_UBYTE (modreader);
  mh->mastervol = _mm_read_UBYTE (modreader);
  mh->speed = _mm_read_UBYTE (modreader);
  mh->bpm = _mm_read_UBYTE (modreader);
  _mm_read_UBYTES (mh->panpos, DSM_MAXCHAN, modreader);
  _mm_read_UBYTES (mh->orders, DSM_MAXORDERS, modreader);

  /* set module variables */
  of.initspeed = mh->speed;
  of.inittempo = mh->bpm;
  of.modtype = strdup (DSM_Version);
  of.numchn = mh->numtrk;
  of.numpat = mh->numpat;
  of.numtrk = of.numchn * of.numpat;
  of.songname = DupStr (mh->songname, 28, 1);	/* make a cstr of songname */
  of.reppos = 0;

  for (t = 0; t < DSM_MAXCHAN; t++)
    of.panning[t] = mh->panpos[t] == DSM_SURROUND ? PAN_SURROUND :
      mh->panpos[t] < 0x80 ? (mh->panpos[t] << 1) : 255;

  if (!AllocPositions (mh->numord))
    return 0;
  of.numpos = 0;
  for (t = 0; t < mh->numord; t++)
    {
      of.positions[of.numpos] = mh->orders[t];
      if (mh->orders[t] < 254)
	of.numpos++;
    }

  of.numins = of.numsmp = mh->numsmp;

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

  while (cursmp < of.numins || curpat < of.numpat)
    {
      if (!GetBlockHeader ())
	return 0;
      if (!memcmp (blockid, INSTID, 4) && cursmp < of.numins)
	{
	  q = &of.samples[cursmp];

	  /* try to read sample info */
	  _mm_read_UBYTES (s.filename, 13, modreader);
	  s.flags = _mm_read_I_UWORD (modreader);
	  s.volume = _mm_read_UBYTE (modreader);
	  s.length = _mm_read_I_ULONG (modreader);
	  s.loopstart = _mm_read_I_ULONG (modreader);
	  s.loopend = _mm_read_I_ULONG (modreader);
	  s.reserved1 = _mm_read_I_ULONG (modreader);
	  s.c2spd = _mm_read_I_UWORD (modreader);
	  s.period = _mm_read_I_UWORD (modreader);
	  _mm_read_UBYTES (s.samplename, 28, modreader);

	  q->samplename = DupStr (s.samplename, 28, 1);
	  q->seekpos = _mm_ftell (modreader);
	  q->speed = s.c2spd;
	  q->length = s.length;
	  q->loopstart = s.loopstart;
	  q->loopend = s.loopend;
	  q->volume = s.volume;

	  if (s.flags & 1)
	    q->flags |= SF_LOOP;
	  if (s.flags & 2)
	    q->flags |= SF_SIGNED;
	  /* (s.flags&4) means packed sample,
	     but did they really exist in dsm ? */
	  cursmp++;
	}
      else if (!memcmp (blockid, PATTID, 4) && curpat < of.numpat)
	{
	  DSM_ReadPattern ();
	  for (t = 0; t < of.numchn; t++)
	    if (!(of.tracks[track++] = DSM_ConvertTrack (&dsmbuf[t * 64])))
	      return 0;
	  curpat++;
	}
    }

  return 1;
}

static CHAR *
DSM_LoadTitle (void)
{
  CHAR s[28];

  _mm_fseek (modreader, 12, SEEK_SET);
  if (!_mm_read_UBYTES (s, 28, modreader))
    return NULL;

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

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

MLOADER load_dsm =
{
  NULL,
  "DSM",
  "DSM (DSIK internal format)",
  DSM_Init,
  DSM_Test,
  DSM_Load,
  DSM_Cleanup,
  DSM_LoadTitle
};


/* ex:set ts=4: */
