// -*-C++-*-
// This file is part of the gmod package
// Copyright (C) 1997 by Andrew J. Robinson

#include <sys/types.h>

#ifdef USE_LOCAL
#include "soundcard.h"
#else
#include <sys/soundcard.h>
#endif

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/fcntl.h>

#include "commands.h"
#include "defines.h"
#include "structs.h"
#include "globals.h"
#include "protos.h"

#include "mtm.h"

#define HEADER_SIZE	66
#define SAMPLE_SIZE	37
#define TRACK_SIZE	192
#define PATTERN_SIZE	64
#define NOTES_PER_TRACK	64
#define NR_CHANNELS	32


typedef struct
{
  char magic[3];		/* must be "MTM" */
  u_char version;		/* upper 4 bits - major, lower minor */
  char name[20];		/* null-term song name string */
  u_short nrSavedTracks;	/* number of tracks saved */
  u_char lastSavedPattern;	/* last pattern number saved */
  u_char lastPlayedPattern;	/* last pattern to play (songlength-1) */
  u_short commentLen;		/* length of extra comment field */
  u_char nrSamples;		/* number of samples saved (NOS) */
  u_char attribute;		/* currently defined as 0 */
  u_char bpt;			/* beats per track */
  u_char nrPlayedTracks;	/* number of tracks toplay back */
  u_char panPositions[32];	/* pan positions for each voice (0-15) */
}

mtmHeader;

typedef struct
{
  u_char pitch;
  u_char instrument;
  u_char effect;
  u_char argument;
} mtmNote;

typedef struct
{
  mtmNote notes[NOTES_PER_TRACK];
} mtmTrack;

typedef struct
{
  u_short tracks[NR_CHANNELS];
} mtmPattern;

void
dumpMtmHeader (mtmHeader * h, struct songInfo *songChar)
{
  strncpy (songChar->name, h->name, 20);
  songChar->name[20] = '\0';
  sprintf (songChar->desc, "MultiTracker v%d.%d", h->version >> 4,
	   h->version & 0x0f);
}

#define PITCH(p)	((*(u_char *)(p))>>2)
#define INSTRUMENT(p)	(((*(u_char *)(p)<<4) | (*((u_char *)(p)+1)>>4)) & 077)
#define EFFECT(p)	(*((u_char *)(p)+1) & 0xf)
#define ARGUMENT(p)	(*((u_char *)(p)+2))

static mtmTrack
getTrack (char *buf)
{
  mtmTrack t;
  int i;

  for (i = 0; i < NOTES_PER_TRACK; i++, buf += 3)
    {
      t.notes[i].pitch = PITCH (buf);
      t.notes[i].instrument = INSTRUMENT (buf);
      t.notes[i].effect = EFFECT (buf);
      t.notes[i].argument = ARGUMENT (buf);
    }

  return (t);
}

static mtmPattern
getPattern (char *buf)
{
  mtmPattern p;
  int i;

  for (i = 0; i < NR_CHANNELS; i++, buf += 2)
    p.tracks[i] = INTEL_SHORT (buf);

  return (p);
}


int
loadMtmModule(FILE * modFd, struct songInfo *songChar,
		unsigned char *buffer)
{
  extern Sample *samples[];
  extern Sequencer *seq;

  mtmHeader header;
  int totalSampleSize, i;
  char *rawData;
  unsigned char *sampleData;
  mtmTrack *tracks;
  mtmPattern *patterns;
  u_char order[128];
  int tracki, notei, pati;
  int voice;
  int params, effect, sample, note;

  songChar->lowestNote = 36;
  songChar->highestNote = 98;
  songChar->volType = VOL_LINEAR;
  songChar->volOnZero = MY_FALSE;
  songChar->slideType = SLIDE_PERIOD_LIN;
  songChar->clockSpeed = 60;

  /* read in header */
  rawData = (char *) malloc (HEADER_SIZE);
  memcpy (rawData, buffer, HDR_SIZE);
  fread (rawData + HDR_SIZE, 1, HEADER_SIZE - HDR_SIZE, modFd);

  memcpy (&header.magic, rawData, 3);
  header.version = BYTE (rawData + 3);
  memcpy (header.name, rawData + 4, 20);
  header.nrSavedTracks = INTEL_SHORT (rawData + 24);
  header.lastSavedPattern = BYTE (rawData + 26);
  header.lastPlayedPattern = BYTE (rawData + 27);
  header.commentLen = INTEL_SHORT (rawData + 28);
  header.nrSamples = BYTE (rawData + 30);
  header.attribute = BYTE (rawData + 31);
  header.bpt = BYTE (rawData + 32);
  header.nrPlayedTracks = BYTE (rawData + 33);
  memcpy (header.panPositions, rawData + 34, 32);

  songChar->nrSamples = header.nrSamples;
  free (rawData);
  dumpMtmHeader (&header, songChar);

  /* ======== get sample data =============================== */
  totalSampleSize = SAMPLE_SIZE * header.nrSamples;
  sampleData = (unsigned char *)malloc(totalSampleSize);
  fread (sampleData, 1, totalSampleSize, modFd);

  fread (order, 1, 128, modFd);

  /* =========== read tracks ============== */
  rawData = (char *) malloc (TRACK_SIZE);
  tracks = (mtmTrack *) malloc (sizeof (mtmTrack));

  patternTable[0] = (struct noteInfo *)
    calloc (1, sizeof (struct noteInfo) * NOTES_PER_TRACK);

  for (i = 0; i < header.nrSavedTracks; i++)
    {
      fread (rawData, 1, TRACK_SIZE, modFd);

      tracks[0] = getTrack (rawData);

      patternTable[i + 1] = (struct noteInfo *)
	malloc (sizeof (struct noteInfo) * NOTES_PER_TRACK);

      for (notei = 0; notei < NOTES_PER_TRACK; notei++)
	{
	  params = tracks[0].notes[notei].argument;
	  effect = tracks[0].notes[notei].effect;
	  sample = tracks[0].notes[notei].instrument;
	  note = tracks[0].notes[notei].pitch;

	  if (effect == CMD_EXTENDED)
	    {
	      effect = ((CMD_EXTENDED << 4) & 0xf0) +
		((params >> 4) & 0x0f);
	      params &= 0x0f;
	    }

	  if (note)		/* note->period */
	    {
	      note = note + 3 * 12;	/* shift up 3 octaves */
	    }

	  switch (effect)
	    {
	    case CMD_SPEED:
	      effect = CVT_MOD_SPEED (params);
	      break;
	    case CMD_VOLUME:
	      if (params > 0)
		params = (params * 4) - 1;
	      break;
	    case CMD_BREAK:
	      params = ((params >> 4) & 0x0f) * 10 + (params & 0x0f);
	      break;
	    case CMD_SET_PAN:
	      params *= 17;
	      break;
	    }

	  (patternTable[i + 1])[notei].note = note;
	  (patternTable[i + 1])[notei].sample = sample;
	  (patternTable[i + 1])[notei].command[0] = effect;
	  (patternTable[i + 1])[notei].parm1[0] = params;
	  (patternTable[i + 1])[notei].parm2[0] = 0;
	  (patternTable[i + 1])[notei].command[1] = 0;
	  (patternTable[i + 1])[notei].parm1[1] = 0;
	  (patternTable[i + 1])[notei].parm2[1] = 0;
	}
    }
  free (rawData);

  /* =========== read patterns ============== */
  rawData = (char *) malloc (PATTERN_SIZE * (header.lastSavedPattern + 1));
  patterns = (mtmPattern *) malloc (sizeof (mtmPattern) *
				     (header.lastSavedPattern + 1));

  fread (rawData, 1, PATTERN_SIZE * (header.lastSavedPattern + 1), modFd);
  for (i = 0; i <= header.lastSavedPattern; i++)
    patterns[i] = getPattern (rawData + i * PATTERN_SIZE);

  free (rawData);

  for (i = 0; i <= header.lastSavedPattern; i++)
    for (voice = 0; voice < NR_CHANNELS; voice++)
      voiceTable[i][voice] = patterns[i].tracks[voice];


  songChar->comment = (char *) realloc (songChar->comment, header.commentLen + 1);
  bzero (songChar->comment, header.commentLen + 1);
  fread (songChar->comment, 1, header.commentLen, modFd);
  songChar->commentLineLen = 0;

  for (i = 0; i < header.nrSamples; i++)
    {
      samples[i] = new MTM_sample;
      samples[i]->load(*seq, modFd, i, 1, sampleData + i * SAMPLE_SIZE);
    }

  songChar->nrTracks = header.nrSavedTracks + 1;
  songChar->songlength = header.lastPlayedPattern + 1;
  songChar->nrChannels = header.nrPlayedTracks;
  songChar->playSpeed = 6;
  songChar->tempo = 125;
  songChar->nrPatterns = header.lastSavedPattern + 1;

  /* copy pattern orders into the tune area */
  for (i = 0; i <= header.lastPlayedPattern; i++)
    {
      tune[i] = order[i];
    }

  /* determine maximum channel actually used */

  songChar->nrChannels = 0;

  for (tracki = 0; tracki < header.nrPlayedTracks; tracki++)
    for (pati = 0; pati < (header.lastSavedPattern + 1); pati++)
      if ((tracki > songChar->nrChannels) && (patterns[pati].tracks[tracki]))
	songChar->nrChannels = tracki;

  songChar->nrChannels++;

  /* set proper panning */

  for (i = 0; i < songChar->nrChannels; i++)
    songChar->panning[i] = (header.panPositions[i] & 0x0f) * 17;

  /* all done */
  free(sampleData);
  free (tracks);
  free (patterns);

  return (1);
}
