
/* This file is part of the GMOD package */

/* load_s3m by Andrew J. Robinson */

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

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

struct list_node
  {
    struct list_node *prev, *next;
    unsigned int number;
    unsigned int location;
    char type;
  };

static unsigned char s3m_effect_tab[] =
{CMD_SET_TICKS, CMD_JUMP, CMD_BREAK, CMD_VOLSLIDE,	/* A B C D */
 CMD_SLIDEDOWN, CMD_SLIDEUP, CMD_SLIDETO, CMD_VIBRATO,	/* E F G H */
 CMD_TREMOR, CMD_ARPEG2, CMD_VIBRAANDVOL, CMD_PORTANDVOL,	/* I J K L */
 CMD_INFO, CMD_INFO, CMD_SETOFFSET, CMD_INFO,	/* M N O P */
 CMD_RETRIGVOL, CMD_TREMOLO, CMD_EXTENDED, CMD_SET_BPM,	/* Q R S T */
 CMD_INFO, CMD_GLOBAL_VOL, CMD_INFO, CMD_INFO,	/* U V W X */
 CMD_INFO, CMD_INFO};		/* Y Z */


void
insert_in_list (struct list_node *list, char type, int number,
		unsigned int location)
{
  struct list_node *current = list->next;
  struct list_node *node;

  node = (struct list_node *) malloc (sizeof (struct list_node));
  node->type = type;
  node->number = number;
  node->location = location;

  while ((current != list) && (current->location < node->location))
    current = current->next;

  node->next = current;
  node->prev = current->prev;
  (current->prev)->next = node;
  current->prev = node;
}

void
catchup (FILE * mod_fd, int wanted, int *file_pos)
{
  char input[1024];
  int bytes_to_read;

  while (wanted > *file_pos)
    {
      bytes_to_read = wanted - *file_pos;
      if (bytes_to_read > 1024)
	bytes_to_read = 1024;
      fread (input, bytes_to_read, 1, mod_fd);
      (*file_pos) += bytes_to_read;
    }
}

void
cvt_s3m_effect (unsigned char *effect, unsigned char *parm)
{
  if ((*effect > 0) && (*effect <= 26))
    *effect = s3m_effect_tab[*effect - 1];
  else
    *effect = CMD_INFO;
}

void
load_s3m_pattern (FILE * mod_fd, int pat_no, struct song_info *song_char,
		  unsigned char channel_map[], int *file_pos)
{
  int i;
  unsigned char has_note, has_volume, has_command;
  int read_bytes, voice;
  unsigned char channel, note, instrument, volume, command, info;
  unsigned char buffer[100];
  int index;

  fread (buffer, 2, 1, mod_fd);
  (*file_pos) += 2;

  buffer[3] = '\0';

  for (i = 0; i < song_char->nr_channels; i++)
    {
      voice_table[pat_no][i] = (pat_no * song_char->nr_channels) + i;
      pattern_table[voice_table[pat_no][i]] =
	(pattern *) calloc (1, sizeof (struct note_info) * 64);
    }

  for (i = 0; i < 64; i++)
    {
      while (1)
	{
	  has_note = 0;
	  has_volume = 0;
	  has_command = 0;
	  read_bytes = 0;
	  index = 0;

	  note = instrument = volume = command = info = 0;

	  fread (buffer, 1, 1, mod_fd);
	  (*file_pos) += 1;

	  if (buffer[0] == 0)
	    break;

	  channel = channel_map[buffer[0] & 31];

	  if (buffer[0] & 32)
	    {
	      has_note = 1;
	      read_bytes += 2;
	    }

	  if (buffer[0] & 64)
	    {
	      has_volume = 1;
	      read_bytes += 1;
	    }

	  if (buffer[0] & 128)
	    {
	      has_command = 1;
	      read_bytes += 2;
	    }

	  fread (buffer, read_bytes, 1, mod_fd);
	  (*file_pos) += read_bytes;

	  if (has_note)
	    {
	      if (buffer[0] == 255)	/* empty note */
		note = /* NOTE_LAST; */ 0;
	      else if (buffer[0] == 254)	/* stop sample */
		note = NOTE_STOP;
	      else
		{
		  note = ((buffer[0] >> 4) & 0x0f) * 12 + (buffer[0] & 0x0f) + 12;
		  if (note < song_char->lowest_note)
		    note = song_char->lowest_note;
		  else if (note > song_char->highest_note)
		    note = song_char->highest_note;
		}
	      instrument = buffer[1];
	      index += 2;
	    }

	  if (has_volume)
	    {
	      volume = buffer[index];
	      if (volume > 0)
		volume = volume * 4 - 1;
	      index += 1;
	    }

	  if (has_command)
	    {
	      command = buffer[index];
	      info = buffer[index + 1];
	      cvt_s3m_effect (&command, &info);
	    }

	  if (channel != 255)
	    {
	      voice = voice_table[pat_no][channel];

	      (*pattern_table[voice])[i].note = note;
	      (*pattern_table[voice])[i].sample = instrument;
	      if (has_volume)
		{
		  (*pattern_table[voice])[i].command[0] = CMD_VOLUME;
		  (*pattern_table[voice])[i].parm1[0] = volume;
		}
	      else
		{
		  (*pattern_table[voice])[i].command[0] = 0;
		  (*pattern_table[voice])[i].parm1[0] = 0;
		}

	      (*pattern_table[voice])[i].parm2[0] = 0;
	      (*pattern_table[voice])[i].command[1] = command;
	      (*pattern_table[voice])[i].parm1[1] = info;
	      (*pattern_table[voice])[i].parm2[1] = 0;
	    }
	}
    }
}

unsigned int
load_s3m_ins (FILE * mod_fd, unsigned char *sample_info, int *file_pos)
{
  unsigned int ret_val;

  fread (sample_info, 0x50, 1, mod_fd);
  (*file_pos) += 0x50;

  ret_val = INTEL_SHORT (sample_info + 0x0e) * 16;

  if (strncmp (sample_info + 0x4c, "SCRS", 4) != 0)
    ret_val = 0;

  return ret_val;
}

void
load_s3m_sample (FILE * mod_fd, int samp_no, unsigned short loop_flags,
		 unsigned char *sample_info, int *file_pos)
{
  unsigned char *buffer = sample_info;
  unsigned int length, loop_begin, loop_end, c2speed;
  unsigned char volume;
  struct patch_info *patch;
  char name[28];

  length = INTEL_LONG (&buffer[0x10]);
  if (length == 0)
    {
      sample_ok[samp_no] = 0;
      return;
    }
  loop_begin = INTEL_LONG (&buffer[0x14]);
  loop_end = INTEL_LONG (&buffer[0x18]);
  volume = buffer[0x1c];

  if (loop_end > 0)
    loop_end--;

  if (buffer[0x1f] & 0x01)
    loop_flags |= WAVE_LOOPING;

  if (buffer[0x1f] & 0x04)
    loop_flags |= WAVE_16_BITS;

  c2speed = INTEL_LONG (&buffer[0x20]);

  strcpy (name, &buffer[0x30]);
  remove_noprint (name);

  printf ("Sample %d: L%05d B%05d E%05d V%02d %s\n",
	  samp_no, length, loop_begin, loop_end, volume, name);

  patch = (struct patch_info *) malloc (sizeof (*patch) + length);

  patch->key = GUS_PATCH;
  patch->device_no = gus_dev;
  patch->instr_no = samp_no;
  patch->mode = loop_flags;
  patch->len = length;
  patch->loop_start = loop_begin;
  patch->loop_end = loop_end;
  patch->base_note = C2FREQ;	/* Middle C */
  patch->base_freq = c2speed;
  patch->low_note = 0;
  patch->high_note = 0x7fffffff;
  patch->volume = 0 /* changed from 120 by AJR */ ;
  patch->panning = 16;

  patch_load (mod_fd, patch, samp_no);

  if (volume > 0)
    sample_vol[samp_no] = (volume * 4) - 1;
  else
    sample_vol[samp_no] = 0;

  free (patch);
  (*file_pos) += length;
}

int
load_s3m_module (FILE * mod_fd, char *name, struct song_info *song_char,
		 struct options_info options, char *buffer, char *command)
{
  int i;

  unsigned char header[0x60];

  int nr_samples;
  int slen, npat, loadpat = 0;
  char mname[29];
  unsigned short flags;
  unsigned char *raw_data, *sample_info;
  unsigned int current_samp;
  struct list_node load_list, *current_list, *tmp_list;
  unsigned short loop_flags = 0;
  int file_position;
  unsigned char channel_map[32];
  int max_loaded = 0;

  load_list.next = load_list.prev = &load_list;

  fprintf (stderr, "Loading .S3M module: %s\n", name);

  memcpy (header, buffer, HDR_SIZE);

  if (fread (header + HDR_SIZE, 1, sizeof (header) - HDR_SIZE, mod_fd) !=
      sizeof (header) - HDR_SIZE)
    {
      fprintf (stderr, "Short header\n");
      return 0;
    }

  strncpy (mname, header, 28);
  mname[28] = '\0';
  remove_noprint (mname);
  fprintf (stderr, "\nModule: %s - ", mname);

  nr_samples = INTEL_SHORT (&header[0x22]);
  flags = INTEL_SHORT (&header[0x26]);
  song_char->nr_channels = 0;

  for (i = 0; i < 32; i++)
    channel_map[i] = 255;

  for (i = 0x40; i < (0x40 + 32); i++)
    if (header[i] <= 15)
      {
	channel_map[i - 0x40] = song_char->nr_channels;
	if (header[i] <= 7)
	  song_char->panning[song_char->nr_channels] = -127;
	else
	  song_char->panning[song_char->nr_channels] = 127;
	song_char->nr_channels++;
      }

  printf ("%d samples/%d channels\n", nr_samples, song_char->nr_channels);

  if (flags & 16)
    {				/* amiga limits */
      song_char->lowest_note = 48;
      song_char->highest_note = 84;
    }
  else
    {
      song_char->lowest_note = NOTE_BASE;
      song_char->highest_note = NOTE_BASE + NUM_PERIODS - 1;
    }

  song_char->global_vol = header[0x30];
  if (song_char->global_vol > 0)
    song_char->global_vol = (song_char->global_vol * 4) - 1;
  else
    song_char->global_vol = 255;
  song_char->play_speed = header[0x31];
  song_char->tempo = header[0x32];
  song_char->vol_type = VOL_LINEAR;
  song_char->slide_type = SLIDE_PERIOD_LIN;

  slen = INTEL_SHORT (&header[0x20]);
  npat = INTEL_SHORT (&header[0x24]);

  fprintf (stderr, "Song length %d, %d patterns.\n", slen, npat);

  song_char->nr_tracks = npat * song_char->nr_channels;
  song_char->songlength = slen;

  raw_data = (unsigned char *) malloc (slen);
  fread (raw_data, slen, 1, mod_fd);

  for (i = 0; i < slen; i++)
    {
      tune[i] = raw_data[i];

      if (tune[i] == 255)
	tune[i] = ORDER_STOP;
      else if (tune[i] == 254)
	tune[i] = ORDER_SKIP;
      else if (tune[i] > loadpat)
	loadpat = tune[i];
    }

  loadpat++;
  printf ("%d actual patterns\n", loadpat);

  free (raw_data);

  if (INTEL_SHORT (&header[0x2a]) == 2)
    loop_flags = WAVE_UNSIGNED;

  sample_info = (unsigned char *) malloc (nr_samples * 0x50);

  raw_data = (unsigned char *) malloc (nr_samples * 2);
  fread (raw_data, nr_samples, 2, mod_fd);

  for (i = 0; i < nr_samples; i++)
    if ((current_samp = INTEL_SHORT (&raw_data[i * 2]) * 16) > 0)
      insert_in_list (&load_list, 'I', i, current_samp);

  free (raw_data);

  raw_data = (unsigned char *) malloc (npat * 2);
  fread (raw_data, npat, 2, mod_fd);

  for (i = 0; i < loadpat; i++)
    if ((current_samp = INTEL_SHORT (&raw_data[i * 2]) * 16) > 0)
      insert_in_list (&load_list, 'P', i, current_samp);

  free (raw_data);

  file_position = 0x60 + slen + (nr_samples * 2) + (npat * 2);

  do
    {
      current_list = load_list.next;

      while (current_list != &load_list)
	{
	  if (current_list->type == 'P')
	    {
	      catchup (mod_fd, current_list->location, &file_position);
	      load_s3m_pattern (mod_fd, current_list->number,
				song_char, channel_map, &file_position);
	      if (options.compress)
		{
		  if (current_list->number > max_loaded)
		    max_loaded = current_list->number;
		  for (i = 0; i < song_char->nr_channels; i++)
		    voice_table[current_list->number][i] =
		      compress_voice ((max_loaded + 1) * song_char->nr_channels,
				      voice_table[current_list->number][i],
				      64, song_char->nr_channels - i);
		}
	    }
	  else if (current_list->type == 'I')
	    {
	      catchup (mod_fd, current_list->location, &file_position);
	      current_samp =
		load_s3m_ins (mod_fd, sample_info +
			      (current_list->number * 0x50), &file_position);
	      if (current_samp != 0)
		insert_in_list (&load_list, 'S', current_list->number,
				current_samp);
	    }
	  else
	    {
	      catchup (mod_fd, current_list->location, &file_position);
	      load_s3m_sample (mod_fd, current_list->number, loop_flags,
			       sample_info + (current_list->number * 0x50),
			       &file_position);
	    }

	  (current_list->prev)->next = current_list->next;
	  (current_list->next)->prev = current_list->prev;

	  tmp_list = current_list->next;
	  free (current_list);
	  current_list = tmp_list;
	}

      if (load_list.next != load_list.prev)
	{
	  printf ("Another pass is required to completely load this module.\n");
	  if (command == NULL)	/* not compressed */
	    fseek (mod_fd, 0, SEEK_SET);
	  else
	    {
	      pclose (mod_fd);
	      printf ("Command is %s\n", command);
	      mod_fd = popen (command, "rb");
	    }
	  file_position = 0;
	}
    }
  while (load_list.next != load_list.prev);

  free (sample_info);

  return 1;
}
