#ifndef PATCH_PATH
#define PATCH_PATH "/dos/ultrasnd/midi"
#endif

#include <stdio.h>
#include <sys/ultrasound.h>
#include <sys/soundcard.h>
#include <sys/ioctl.h>
#include <sys/soundcard.h>
#include <strings.h>
#include <unistd.h>
#include <fcntl.h>
#include "gmidi.h"

static int instrmap[256] = {-1, 0};
static int initialized = 0;

struct pat_header
  {
    char            magic[12];
    char            version[10];
    char            description[60];
    unsigned char   instruments;
    char            voices;
    char            channels;
    unsigned short  nr_waveforms;
    unsigned short  master_volume;
    unsigned long   data_size;
  };

struct sample_header
  {
    char            name[7];
    unsigned char   fractions;
    long            len;
    long            loop_start;
    long            loop_end;
    unsigned short  base_freq;
    long            low_note;
    long            high_note;
    long            base_note;
    short           detune;
    unsigned char   panning;

    unsigned char   envelope_rate[6];
    unsigned char   envelope_offset[6];

    unsigned char   tremolo_sweep;
    unsigned char   tremolo_rate;
    unsigned char   tremolo_depth;

    unsigned char   vibrato_sweep;
    unsigned char   vibrato_rate;
    unsigned char   vibrato_depth;

    char            modes;

    short           scale_frequency;
    unsigned short  scale_factor;
  };

SEQ_USE_EXTBUF();

int             seqfd;

int
get_dint(unsigned char *p)
{
	int i;
	unsigned int v=0;

	for (i=0;i<4;i++)
        {
	    v |= (p[i] << (i*8));
 	}
	return (int)v;
}

unsigned short
get_word(unsigned char *p)
{
	int i;
	unsigned short v=0;

	for (i=0;i<2;i++)
	    v |= (*p++ << (i*8));
	return (short)v;
}

short
get_int(unsigned char *p)
{
   return (short)get_word(p);
}

int
gusinit(int seqfd, int dev)
{

  	initialized=1;

	if (ioctl (seqfd, SNDCTL_SEQ_RESETSAMPLES, &dev) == -1)
	{
		fprintf(stderr, "OSSlib: Can't reset GM programs. Device %d\n",
				dev);
		perror("GUS init");
		exit(-1);
	}

	return 0;
}

int
gusload (int seqfd, int type, int dev, int pgm)
{
  int             i, n, patfd, print_only = 0;
  struct synth_info info;
  struct pat_header header;
  struct sample_header sample;
  unsigned char buf[256];
  char            name[256];
  long            offset;
  struct patch_info *patch;

  if (!initialized)
     gusinit(seqfd, dev);

  if (pgm<0 || pgm>255)
     return 0;

  if (instrmap[pgm] == pgm)
     return 0;

  instrmap[pgm] = pgm;

  if (patch_names[pgm][0] == 0) /* Not defined */
     return 0;

  sprintf (name, PATCH_PATH "/%s.pat", patch_names[pgm]);
/* fprintf(stderr, "Loading instrument %d from %s\n", pgm, name); */

  if ((patfd = open (name, O_RDONLY, 0)) == -1)
    {
      perror (name);
      return 0;
    }

  if (read (patfd, buf, 0xef) != 0xef)
    {
      fprintf (stderr, "%s: Short file\n", name);
      exit (-1);
    }

  memcpy ((char *) &header, buf, sizeof (header));

  if (strncmp (header.magic, "GF1PATCH110", 12))
    {
      fprintf (stderr, "%s: Not a patch file\n", name);
      exit (-1);
    }

  if (strncmp (header.version, "ID#000002", 10))
    {
      fprintf (stderr, "%s: Incompatible patch file version\n", name);
      exit (-1);
    }

  header.nr_waveforms = get_word(&buf[85]);
  header.master_volume = get_word(&buf[87]);

  offset = 0xef;

  for (i = 0; i < header.nr_waveforms; i++)
    {
      if (lseek (patfd, offset, 0) == -1)
	{
	  perror (name);
	  exit (-1);
	}

      if (read (patfd, &buf, sizeof (sample)) != sizeof (sample))
	{
	  fprintf (stderr, "%s: Short file\n", name);
	  exit (-1);
	}

      memcpy ((char *) &sample, buf, sizeof (sample));

      sample.fractions = (char)buf[7];
      sample.len = get_dint(&buf[8]);
      sample.loop_start = get_dint(&buf[12]);
      sample.loop_end = get_dint(&buf[16]);
      sample.base_freq = get_word(&buf[20]);
      sample.low_note = get_dint(&buf[22]);
      sample.high_note = get_dint(&buf[26]);
      sample.base_note = get_dint(&buf[30]);
      sample.detune = get_int(&buf[34]);
      sample.panning = (unsigned char) buf[36];

      memcpy (sample.envelope_rate, &buf[37], 6);
      memcpy (sample.envelope_offset, &buf[43], 6);

      sample.tremolo_sweep = (unsigned char) buf[49];
      sample.tremolo_rate = (unsigned char) buf[50];
      sample.tremolo_depth = (unsigned char) buf[51];

      sample.vibrato_sweep = (unsigned char) buf[52];
      sample.vibrato_rate = (unsigned char) buf[53];
      sample.vibrato_depth = (unsigned char) buf[54];
      sample.modes = (unsigned char) buf[55];
      sample.scale_frequency = get_int(&buf[56]);
      sample.scale_factor = get_word(&buf[58]);

      offset = offset + 96;
      patch = (struct patch_info *) malloc (sizeof (*patch) + sample.len);
      if (patch == NULL)
	 {
		fprintf(stderr, "Failed to allocate %d bytes of memory\n",
				sizeof (*patch) + sample.len);
		exit(0);
	 }

      patch->key = GUS_PATCH;
      patch->device_no = dev;
      patch->instr_no = pgm;
      patch->mode = sample.modes | WAVE_TREMOLO |
	WAVE_VIBRATO | WAVE_SCALE;
      patch->len = sample.len;
      patch->loop_start = sample.loop_start;
      patch->loop_end = sample.loop_end;
      patch->base_note = sample.base_note;
      patch->high_note = sample.high_note;
      patch->low_note = sample.low_note;
      patch->base_freq = sample.base_freq;
      patch->detuning = sample.detune;
      patch->panning = (sample.panning - 7) * 16;

      memcpy (patch->env_rate, sample.envelope_rate, 6);
      memcpy (patch->env_offset, sample.envelope_offset, 6);

      patch->tremolo_sweep = sample.tremolo_sweep;
      patch->tremolo_rate = sample.tremolo_rate;
      patch->tremolo_depth = sample.tremolo_depth;

      patch->vibrato_sweep = sample.vibrato_sweep;
      patch->vibrato_rate = sample.vibrato_rate;
      patch->vibrato_depth = sample.vibrato_depth;

      patch->scale_frequency = sample.scale_frequency;
      patch->scale_factor = sample.scale_factor;

      patch->volume = header.master_volume;

      if (lseek (patfd, offset, 0) == -1)
	{
	  perror (name);
	  exit (-1);
	}

	  if (read (patfd, patch->data, sample.len) != sample.len)
	    {
	      fprintf (stderr, "%s: Short file\n", name);
	      exit (-1);
	    }

	  SEQ_WRPATCH (patch, sizeof (*patch) + sample.len);

      offset = offset + sample.len;
    }
    
    close(patfd);
    free(patch);
    return 0;
}
