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

#include <stdlib.h>
#include <string.h>

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

#include "Sample.h"
#include "Sequencer.h"

int
Sequencer::patchLoad(FILE * modFd, int sampleNum, Sample &samp,
		     int &bytesRead, int cutFactor) const
{
  int xBytesRead;
  int len;
  unsigned short loopFlags;
  int loopStart;
  int loopEnd;
  int i, rc;
  struct patch_info tpatch;
  struct patch_info *patch;

  memset(&tpatch, 0, sizeof(patch_info));
  samp.getPatchInfo(tpatch);
  patch = (struct patch_info *)malloc(sizeof(struct patch_info) +
				      tpatch.len + 2);

  memcpy(patch, &tpatch, sizeof(struct patch_info));
  patch->key = GUS_PATCH;
  patch->low_note = 0;
  patch->high_note = 0x7fffffff;
  patch->volume = 0;
  patch->panning = -112;
  patch->device_no = gusDev_;
  patch->instr_no = sampleNum;

  len = patch->len;

  /* fix loopEnd if necessary */

  if (patch->loop_end > len)
    patch->loop_end = len;

  loopStart = patch->loop_start;
  loopEnd = patch->loop_end;

  if (loopStart >= loopEnd)
    patch->mode &= ~WAVE_LOOPING;

  loopFlags = patch->mode;

  if ((bytesRead = fread(patch->data, 1, len, modFd)) != len)
    {
      if (((loopFlags & WAVE_16_BITS) && (bytesRead > 1)) ||
	  (!(loopFlags & WAVE_16_BITS) && (bytesRead > 0)))
	{
	  /* Warning: Short sample */

	  xBytesRead = bytesRead;

	  if ((loopFlags & WAVE_16_BITS) && ((bytesRead % 2) != 0))
	    xBytesRead--;
	  
	  i = xBytesRead - (len - bytesRead);

	  if (i < 0)
	    i = 0;

	  if ((loopFlags & WAVE_16_BITS) && ((i % 2) != 0))
	    i--;

	  memcpy(patch->data + xBytesRead, patch->data + i, len - xBytesRead);
	}
      else
	{
	  free(patch);
	  return -ENODATA;
	}
    }

  /* "decode" sample if necessary */
  samp.decode(patch->data);

  /* convert 16 bits to 8 if necessary */

  if ((loopFlags & WAVE_16_BITS) && ((len > 256*1024) || (cutFactor > 1)))
    {
      for (i = 0; i < len - 1; i+= 2)
	patch->data[i / 2] = patch->data[i + 1];
      
      len = (patch->len /= 2);
      loopStart = (patch->loop_start /= 2);
      loopEnd = (patch->loop_end /= 2);
      loopFlags &= ~WAVE_16_BITS;
      patch->base_freq /= 2;
    }

  /* extend looped samples, since length might equal loop end */

  if (loopFlags & WAVE_LOOPING)
    {
      if (loopFlags & WAVE_16_BITS)
	{
	  if (len >= 2)
	    {
	      patch->data[len] = patch->data[len - 2];
	      patch->data[len + 1] = patch->data[len - 1];
	      len += 2;
	      patch->len += 2;
	    }
	}
      else if (len >= 1)
	{
	  patch->data[len] = patch->data[len - 1];
	  len++;
	  (patch->len)++;
	}
    }

  /* Shorten samples to fit in memory.  Do not shorten a looped sample if */
  /* the start and end are not evenly divisible by cutFactor; otherwise, */
  /* the sample may go out of tune. */

  if ((cutFactor == 2) && (loopFlags & WAVE_LOOPING) && (loopStart % 2) &&
      (loopEnd % 2))
    {
      loopStart = (patch->loop_start -= 1);
      loopEnd = (patch->loop_end -= 1);
      i = 2;
    }
  else
    i = 1;

  if ((cutFactor > 1) && !((loopFlags & WAVE_LOOPING) &&
			   ((loopStart % cutFactor) ||
			    (loopEnd % cutFactor))))
    {
      samp.cutFactor(i * cutFactor);

      for (i = 0; i < len / cutFactor; i++)
	patch->data[i] = patch->data[i * cutFactor];
      
      len = (patch->len /= cutFactor);
      loopStart = (patch->loop_start /= cutFactor);
      loopEnd = (patch->loop_end /= cutFactor);
      patch->base_freq /= cutFactor;
    }
  else
    samp.cutFactor(i);

  /* try to remove loop clicking */

  if ((loopFlags & WAVE_LOOPING) && (loopEnd >= 2) &&
      !(loopFlags & WAVE_16_BITS))
    {
      patch->data[loopEnd] = patch->data[loopStart];

      if (loopFlags & WAVE_UNSIGNED)
	patch->data[loopEnd - 1] =
	  ((unsigned char)(patch->data[loopEnd - 2]) +
	   (unsigned char)(patch->data[loopEnd])) / 2;
      else
	patch->data[loopEnd - 1] =
	  ((signed char)(patch->data[loopEnd - 2]) +
	   (signed char)(patch->data[loopEnd])) / 2;
    }

  if (memory() < len)
    rc = -ENOSPC;
  else
    {
#define write ::write
#define seqfd seqfd_
      rc = SEQ_WRPATCH2(patch, sizeof(struct patch_info) + len);
#undef write
    }

  free(patch);
  return rc;
}
