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

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>

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

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

#include "Sequencer.h"
#include "xm.h"

int catchup(FILE *, int, int *);

static unsigned char xmEffectTab[] =
{CMD_ARPEG, CMD_SLIDEUP, CMD_SLIDEDOWN, CMD_SLIDETO, /* 0 1 2 3 */
 CMD_VIBRATO, CMD_PORTANDVOL, CMD_VIBRAANDVOL, CMD_TREMOLO, /* 4 5 6 7 */
 CMD_SET_PAN, CMD_SETOFFSET, CMD_VOLSLIDE, CMD_JUMP, /* 8 9 A B */
 CMD_VOLUME, CMD_BREAK, CMD_EXTENDED, CMD_SPEED, /* C D E F */
 CMD_GLOBAL_VOL, CMD_GLOBALVOL_SLIDE, CMD_INFO, CMD_INFO, /* G H I J */
 CMD_KEYOFF, CMD_SETENV_POS, CMD_INFO, CMD_INFO, /* K L M N */
 CMD_INFO, CMD_PANSLIDE, CMD_INFO, CMD_RETRIGVOL, /* O P Q R */
 CMD_INFO, CMD_TREMOR, CMD_INFO, CMD_INFO, /* S T U V */
 CMD_INFO, CMD_XFINEPORTUP, CMD_INFO, CMD_INFO}; /* W X Y Z */

void
loadXmPattern (FILE * modFd, int patNo, struct songInfo *songChar,
	       int *filePos, unsigned char compress, u_short numBytes)
{
  int i, j, voice;
  int hasNote, hasInstr, hasVolume, hasCommand, hasInfo, index;
  u_char note, instrument, volume, command, info, volCommand;
  u_char buffer[100];

  for (i = 0; i < songChar->nrChannels; i++)
    {
      voiceTable[patNo][i] = (patNo * songChar->nrChannels) + i;
      patternTable[voiceTable[patNo][i]] =
	(struct noteInfo *) calloc (1, sizeof (struct noteInfo) * patternLen[patNo]);
    }

  if (numBytes > 0)
    {
      for (j = 0; j < patternLen[patNo]; j++)
	{
	  for (i = 0; i < songChar->nrChannels; i++)
	    {
	      hasNote = 0;
	      hasInstr = 0;
	      hasVolume = 0;
	      hasCommand = 0;
	      hasInfo = 0;
	      info = 0;
	      index = 0;

	      voice = voiceTable[patNo][i];
	      note = instrument = volume = command = info = 0;
	      
	      fread (buffer, 1, 1, modFd);
	      *filePos += 1;
	      
	      if ((buffer[0] & 0x80) > 0)
		{
		  if (buffer[0] & 0x01)
		    hasNote = 1;
		  
		  if (buffer[0] & 0x02)
		    hasInstr = 1;
		  
		  if (buffer[0] & 0x04)
		    hasVolume = 1;
		  
		  if (buffer[0] & 0x08)
		    hasCommand = 1;
		  
		  if (buffer[0] & 0x10)
		    hasInfo = 1;
		  
		  fread (buffer, 1, hasNote + hasInstr + hasVolume +
			 hasCommand + hasInfo, modFd);
		  
		  *filePos += (hasNote + hasInstr + hasVolume +
				hasCommand + hasInfo);
		}
	      else
		{
		  fread (buffer + 1, 1, 4, modFd);
		  *filePos += 4;
		  hasNote = hasInstr = hasVolume = hasCommand = hasInfo = 1;
		}
	      
	      if (hasNote)
		{
		  note = buffer[index++] + 11;
		  if (note >= 108)
		    note = NOTE_STOP;
		}

	      if (hasInstr)
		instrument = buffer[index++];
	      
	      if (hasVolume)
		{
		  volume = buffer[index++];
		  
		  switch (volume & 0xf0)
		    {
		    case 0x10: /* set volume */
		    case 0x20:
		    case 0x30:
		    case 0x40:
		    case 0x50:
		      if (volume <= 0x50)
			{
			  volume -= 0x10;
			  
			  if (volume > 0)
			    volume = volume * 4 - 1;
			  volCommand = CMD_VOLUME;
			}
		      else
			hasVolume = 0;
		      break;
		    case 0x60: /* volslide down */
		      volume &= 0x0f;
		      volCommand = CMD_VOLSLIDE;
		      break;
		    case 0x70: /* volslide up */
		      volume = (volume << 4) & 0xf0;
		      volCommand = CMD_VOLSLIDE;
		      break;
		    case 0x80: /* fine volside down */
		      volume &= 0x0f;
		      volCommand = CMD_FINEVOLDOWN;
		      break;
		    case 0x90: /* fine volside up */
		      volume &= 0x0f;
		      volCommand = CMD_FINEVOLUP;
		      break;
		    case 0xa0: /* vibrato speed */
		      volume &= 0x0f;
		      volCommand = CMD_VIBRASPEED;
		      break;
		    case 0xb0: /* vibrato */
		      volume &= 0x0f;
		      volCommand = CMD_VIBRATO;
		      break;
		    case 0xc0: /* set panning */
		      volume = (volume & 0x0f) * 17;
		      volCommand = CMD_SET_PAN;
		      break;
		    case 0xd0: // panning slide left
		      volume &= 0x0f;
		      volCommand = CMD_PANSLIDE;
		      break;
		    case 0xe0: // panning slide right
		      volume = (volume << 4) & 0xf0;
		      volCommand = CMD_PANSLIDE;
		      break;
		    case 0xf0: /* tone portamento */
		      volume = (volume & 0x0f) << 4;
		      volCommand = CMD_SLIDETO;
		      break;
		    default: /* unsupported */
		      hasVolume = 0;
		    }
		}

	      if (hasCommand)
		{
		  command = buffer[index++];
		  
		  if (command < 36)
		    command = xmEffectTab[command];
		  else
		    command = CMD_INFO;
		}

	      if (hasInfo)
		info = buffer[index++];

	      switch (command)
		{
		  //case CMD_SET_PAN:
		  //info = (info >> 4) & 0x0f;
		  //break;
		case CMD_SPEED:
		  command = CVT_MOD_SPEED (info);
		  break;
		case CMD_VOLUME:
		  if (info > 0)
		    info = (info * 4) - 1;
		  break;
		case CMD_BREAK:
		  info = ((info >> 4) & 0x0f) * 10 + (info & 0x0f);
		  break;
		case CMD_EXTENDED:
		  command = ((CMD_EXTENDED << 4) & 0xf0) + ((info >> 4) & 0x0f);
		  info &= 0x0f;
		  break;
		case CMD_KEYOFF: 
		  note = NOTE_STOP;
		  if (hasInfo)
		    command = CMD_INFO;
		  else
		    command = 0;
		  break;
		case CMD_XFINEPORTUP:
		  if ((info & 0xf0) == 0x20)
		    command = CMD_XFINEPORTDOWN;
		  info &= 0x0f;
		}

	      (patternTable[voice])[j].note = note;
	      (patternTable[voice])[j].sample = instrument;
	      
	      if (hasVolume)
		{
		  (patternTable[voice])[j].command[0] = volCommand;
		  (patternTable[voice])[j].parm1[0] = volume;
		}

	      (patternTable[voice])[j].command[1] = command;
	      (patternTable[voice])[j].parm1[1] = info;
	    }
	}
    }
  
  if (compress)
    {
      for (i = 0; i < songChar->nrChannels; i++)
	{
	  for (j = 0; (j < patNo) && (voiceTable[patNo][i] == (patNo * songChar->nrChannels + i)); j++)
	    if (patternLen[j] == patternLen[patNo])
	      voiceTable[patNo][i] =
		compressVoice ((j + 1) * songChar->nrChannels,
				voiceTable[patNo][i], patternLen[patNo], 1,
				j * songChar->nrChannels);
	  
	  if (voiceTable[patNo][i] == (patNo * songChar->nrChannels + i))
	    voiceTable[patNo][i] = compressVoice(voiceTable[patNo][i],
						    voiceTable[patNo][i],
						    patternLen[patNo], 1,
						    (patNo * songChar->nrChannels));
	}
    }
}

void
loadXmSamples(FILE ** modFd, int nrSamples, int *filePos, char *command)
{
  extern Sample *samples[];
  extern Sequencer *seq;
  int cutFactor = 1;
  int i;
  int oldPos = *filePos;

  for (i = 0; i < nrSamples; i++)
    samples[i] = new XM_sample;

  do {
    i = 0;

    while (i < nrSamples)
      {
	if ((samples[i]->load(*seq, *modFd, i, cutFactor, periodTable,
			      filePos) < 0) && (cutFactor < MAX_CUT))
	  {
	    cutFactor++;
	    i = nrSamples + 1;   // cause repeat of outer loop
	    
	    seq->resetSamples();

	    if (!command)
	      {
		fseek(*modFd, oldPos, SEEK_SET);
		*filePos = oldPos;
	      }
	    else
	      {
		pclose(*modFd);
		*modFd = popen(command, "rb");
		*filePos = 0;
		catchup(*modFd, oldPos, filePos);
	      }
	  }
	else
	  i++;
      }
  } while (i != nrSamples);
}

int
loadXmModule (FILE ** modFd, struct songInfo *songChar,
		struct optionsInfo options, unsigned char *buffer,
		char *command)
{
  unsigned char header[336];
  char mname[21];
  int i;
  unsigned int maxpat;
  long headerSize;
  int filePos = 336, oldPos;

  memcpy (header, buffer, HDR_SIZE);

  if (fread (header + HDR_SIZE, 1, sizeof (header) - HDR_SIZE, *modFd) !=
      sizeof (header) - HDR_SIZE)
    return 0;

  strncpy (mname, (char *)(header + 17), 20);
  mname[20] = '\0';
  removeNoprint (mname);

  strcpy (songChar->name, mname);
  songChar->nrChannels = INTEL_SHORT(header + 68);
  songChar->lowestNote = 11;
  songChar->highestNote = 107;
  songChar->nrSamples = INTEL_SHORT(header + 72);
  songChar->playSpeed = INTEL_SHORT(header + 76);
  songChar->tempo = INTEL_SHORT(header + 78);
  songChar->volType = VOL_LINEAR;
  songChar->volOnZero = MY_FALSE;
  songChar->clockSpeed = 60;
  songChar->type = MODULE_XM;
  sprintf(songChar->desc, "XM");

  if (INTEL_SHORT(header + 74) & 0x01)
    songChar->slideType = SLIDE_NOTE_LIN;
  else
    songChar->slideType = SLIDE_PERIOD_LIN;

  for (i = 0; i < songChar->nrChannels; i++)
    songChar->panning[i] = 128;

  maxpat = songChar->nrPatterns = INTEL_SHORT(header + 70);
  for (i = 0; i < 256; i++)
    {
      tune[i] = BYTE(header + 80 + i);

      if (tune[i] >= maxpat)
	{
	  maxpat = tune[i] + 1;
	  loadXmPattern(*modFd, tune[i], songChar, &filePos,
			  options.compress, 0);
	}
    }

  songChar->nrTracks = songChar->nrPatterns * songChar->nrChannels;
  songChar->songlength = INTEL_SHORT(header + 64);

  headerSize = INTEL_LONG(header + 60);

  catchup (*modFd, filePos + (headerSize - 276), &filePos);

  for (i = 0; i < songChar->nrPatterns; i++)
    {
      fread (header, 1, 9, *modFd);
      filePos += 9;
      headerSize = INTEL_LONG(header);
      catchup (*modFd, filePos + (headerSize - 9), &filePos);
      patternLen[i] = INTEL_SHORT(header + 5);
      oldPos = filePos;
      loadXmPattern (*modFd, i, songChar, &filePos, options.compress,
		       INTEL_SHORT(header + 7));
      catchup (*modFd, oldPos + INTEL_SHORT(header + 7), &filePos);
    }

  songChar->nrPatterns = maxpat;

  loadXmSamples(modFd, songChar->nrSamples, &filePos, command);

  return 1;
}
