/* st_read.c 
	vi:ts=3 sw=3:
 */

/* $Id: st_read.c,v 4.31 1995/10/21 00:42:10 espie Exp espie $
 * $Log: st_read.c,v $
 * Revision 4.31  1995/10/21 00:42:10  espie
 * *** empty log message ***
 *
 * Revision 4.30  1995/09/16 15:34:03  espie
 * *** empty log message ***
 *
 * Revision 4.29  1995/09/05 19:21:28  espie
 * *** empty log message ***
 *
 * Revision 4.28  1995/09/03 13:40:17  espie
 * *** empty log message ***
 *
 * Revision 4.27  1995/09/02 22:20:57  espie
 * Free info->name all the time.
 *
 * Revision 4.26  1995/08/31 13:31:35  espie
 * Full duration.
 *
 * Revision 4.25  1995/08/27 18:43:08  espie
 * *** empty log message ***
 *
 * Revision 4.25  1995/08/14  13:26:42  espie
 * Fixed up spurious memory bug.
 *
 * Revision 4.24  1995/07/02 16:11:54  espie
 * *** empty log message ***
 *
 * Revision 4.23  1995/06/26 17:12:52  espie
 * *** empty log message ***
 *
 * Revision 4.22  1995/06/26 15:45:17  espie
 * Removed blocks, enter patterns.
 *
 * Revision 4.21  1995/06/26 10:11:17  espie
 * - sample numbers compression.
 * - sample reading compression (skip unused samples).
 * - one big chunk for all patterns.
 * (ready for virtua-player).
 *
 * Revision 4.20  1995/06/23 09:36:00  espie
 * Added pattern compression.
 *
 * Revision 4.19  1995/06/04  11:58:14  espie
 * Used higher level primitives to read song.
 *
 * Revision 4.18  1995/05/11  12:28:50  espie
 * Bug correction, case for effects.
 *
 * Revision 4.17  1995/03/03  14:24:28  espie
 * 15 colors.
 *
 * Revision 4.16  1995/03/01  15:24:51  espie
 * Stuff with scaling a bit cleaned up (though not perfect yet).
 *
 * Revision 4.15  1995/02/24  15:36:39  espie
 * Need to set all pblock events to 0 for correct clean up.
 *
 * Revision 4.14  1995/02/21  21:13:16  espie
 * Cleaned up source. Moved minor pieces of code around.
 *
 * Revision 4.13  1995/02/21  17:54:32  espie
 * Internal problem: buggy RCS. Fixed logs.
 *
 * Revision 4.11  1995/02/20  22:53:31  espie
 * Added missing parameters everywhere.
 *
 * Revision 4.10  1995/02/20  22:28:50  espie
 * 8 channels.
 *
 * Revision 4.9  1995/02/20  16:49:58  espie
 * Added song type.
 *
 * Revision 4.7  1995/02/06  14:50:47  espie
 * Changed sample_info.
 *
 * Revision 4.6  1995/02/01  17:14:54  espie
 * Scaled volume.
 *
 * Revision 4.0  1994/01/11  17:56:51  espie
 * Use new open.
 * Added lots of checks for malloc, plus count of bytes read.
 * Amiga support.
 * Added other commands (numerous).
 * Checks for finetune ?
 *
 * Revision 2.16  1992/11/17  17:06:25  espie
 * fix_xxx for better speed.
 * Added some sample information in the dump.
 * Added transpose feature.
 * Feature fix: length 1 sample should be empty.
 * Corrected repeat length problems concerning badly formed files,
 * added signature checking for new tracker files.
 * Corrected small problem with repeat being too short.
 * Coded error types. More amiga specific stuff.
 *
 * Revision 1.17  1991/11/17  16:30:48  espie
 * Rationnalized error recovery.
 * There was a bug: you could try to deallocate
 * stuff in no-noland. Also, strings never got
 * to be freed.
 * Centralized error control to error_song.
 * Added a new test on length, aborts most modules now.
 * Maybe should say it as well.
 * Added checkpoints for early return if file too short.
 * Added memory recovery and error control.
 * Suppressed ! warning for bad note.
 * Added note support.
 * Corrected length and rep_length/rep_offset
 * which are given in words and should be converted to
 * bytes.
 */

#include "defs.h"

#include <ctype.h>
#include <assert.h>

#include "extern.h"
#include "song.h"
#include "channel.h"

LOCAL int patsize;

#define NOT_YET (-1)

ID("$Id: st_read.c,v 4.31 1995/10/21 00:42:10 espie Exp espie $")

LOCAL unsigned char *buffer;		/* Buffer to read everything */
LOCAL int bufsize;

LOCAL void setup_buffer(size)
int size;
	{
	bufsize = size;
	if (!buffer)
		buffer = malloc(bufsize);
	else
		buffer = realloc(buffer, bufsize);
	}

/***
 ***	Low level st-file access routines 
 ***/

/* s = getstring(f, len):
 * gets a soundtracker string from file f.
 * I.e, it is a fixed length string terminated
 * by a 0 if too short. 
 */
LOCAL char *getstring(f, len)
struct exfile *f;
int len;
   {
   char *new;
        
	read_file(buffer, len, 1, f);
   buffer[len] = '\0';
   new = malloc(strlen(buffer)+1);
   if (!new) 
      return NULL;

   return strcpy(new, buffer);
   }

/* byteskip(f, len)
 * same as fseek, xcpt it works on stdin
 */
LOCAL void byteskip(f, len)
struct exfile *f;
int len;
   {
	while (len > bufsize)
		{
		read_file(buffer, bufsize, 1, f);
		len -= bufsize;
		}
	read_file(buffer, len, 1, f);
   }

/* v = getushort(f)
 * reads an unsigned short from f
 */
LOCAL int getushort(f)
struct exfile *f;
   {
   int i;

      /* order dependent !!! */
   i = getc_file(f) << 8;
   return i | getc_file(f);
   }


/* info = fill_sample_info(f):
 * allocate and fill sample info with the information at current position of
 * file f. Allocate memory for storing the sample, also.  fill_sample_info 
 * is guaranteed to give you an accurate snapshot of what sample should 
 * be like. In particular, length, rp_length, start, rp_start, fix_length, 
 * fix_rp_length will have the values you can expect if part of the sample 
 * is missing.
 */
LOCAL struct sample_info *fill_sample_info(f)
struct exfile *f;
   {
      /* New method: instead of allocating/freeing sample infos,
       * we allocate them on demand   */
	static struct sample_info *info = NULL;
	struct sample_info *result;

   if (!info)
      {
      info = malloc(sizeof(struct sample_info));
   	if (!info)
	   	{
		   error = OUT_OF_MEM;
   		return NULL;
	   	}
      }
	info->finetune = 0;
	info->name = NULL;
	info->length = 0;
	info->start = NULL;
	info->sample_size = 8;
	info->rp_start = NULL;
	info->fix_length = 0;
	info->fix_rp_length = 0;

   info->name = getstring(f, SAMPLENAME_MAXLENGTH);
   if (!info->name)
      {
      error = OUT_OF_MEM;
      return NULL;
      }
   info->length = getushort(f);
   info->finetune = getc_file(f);
   if (info->finetune > 15)
      info->finetune = 0;
   info->volume = getc_file(f);
   info->volume = MIN(info->volume, MAX_VOLUME);
   info->rp_offset = getushort(f);
   info->rp_length = getushort(f);

   /* the next check is for old modules for which
    * the sample data types are a bit confused, so
    * that what we were expecting to be #words is #bytes.
    */
      /* not sure I understand the -1 myself, though it's
       * necessary to play kawai-k1 correctly 
       */
   if (info->rp_length + info->rp_offset - 1 > info->length)
      info->rp_offset /= 2;
    
   if (info->rp_length + info->rp_offset > info->length)
      info->rp_length = info->length - info->rp_offset;

   info->length *= 2;
   info->rp_offset *= 2;
   info->rp_length *= 2;
      /* in all logic, a 2-sized sample could exist,
       * but this is not the case, and even so, some
       * trackers output empty instruments as being 2-sized.
       */
	if (info->rp_length <= 2)
		info->rp_length = 0;

   if (info->length <= 2)
      {
		if (info->name)
			free(info->name);
		return NULL;
      }
   if (info->length > MAX_SAMPLE_LENGTH)
      error = CORRUPT_FILE;
   info->fix_length = int_to_fix(info->length);
   info->fix_rp_length = int_to_fix(info->rp_length);
   result = info;
   info = NULL;
   return result;
   }

LOCAL void fill_song_info(info, f)
struct song_info *info;
struct exfile *f;
   {
   int i;
   int p;

   info->length = getc_file(f);
   getc_file(f);
   info->maxpat = -1;
   for (i = 0; i < NUMBER_PATTERNS; i++)
      {
      p = getc_file(f);
      if (p >= NUMBER_PATTERNS)
         p = 0;
      if (p > info->maxpat)
         info->maxpat = p;
      info->patnumber[i] = p;
      }
   info->maxpat++;
   if (info->maxpat == 0 || 
		info->length == 0 || info->length >= NUMBER_PATTERNS)
      error = CORRUPT_FILE;
   }

LOCAL int compress_patterns(info, used)
unsigned char used[];
struct song_info *info;
	{
	unsigned char real[NUMBER_PATTERNS];
	int i, j;

	for (i = 0; i < NUMBER_PATTERNS; i++)
		used[i] = 0;
	for (i = 0; i < info->length; i++)
		used[info->patnumber[i]] = 1;
	j = 0;
	for (i = 0; i < info->maxpat; i++)
		{
		real[i] = j;
		if (used[i])
			j++;
		}

	for (i = 0; i < NUMBER_PATTERNS; i++)
		info->patnumber[i] = real[info->patnumber[i]];

	return j;
	}

LOCAL void fill_event(e, p, current_instrument, song)
struct event *e;
unsigned char *p;
int *current_instrument;
struct song *song;
   {
	int pitch;

   e->sample_number = (p[0] & 0x10) | (p[2] >> 4);
	if (e->sample_number)
		*current_instrument = e->sample_number;
   e->effect = p[2] & 0xf;
   e->parameters = p[3];
   switch(e->effect)
      {
   case EFF_EXTENDED:
      e->effect = EXT_BASE + HI(e->parameters);
      e->parameters = LOW(e->parameters);
      break;
   case 0:
      e->effect = e->parameters ? EFF_ARPEGGIO : EFF_NONE;
      break;
   case EFF_SKIP:
      e->parameters = HI(e->parameters) * 10 + LOW(e->parameters);
      break;
	case EFF_SPEED:
		if (song->type == OLD_ST)
			e->effect = EFF_OLD_SPEED;
		break;
      }
   pitch = ( (p[0] & 15) << 8 ) | p[1];
   e->note = find_note(pitch);
   }

LOCAL struct event *fill_pattern(f, song, e)
struct exfile *f;
struct song *song;
struct event *e;
   {
   int i, j;
	int current_instrument;

	read_file(buffer, patsize, 1, f);

	for (j = 0; j < song->ntracks; j++)
		{
		current_instrument = NOT_YET;
		for (i = 0; i < song->info.plength; i++)
         fill_event(e+i, buffer+4*(i*song->ntracks+j), &current_instrument, 
				song);
		e += song->info.plength;
		}
	return e;
   }


/* fill_patterns:
 * read patterns from file f into song, keeping only patterns that are used
 */
LOCAL int fill_patterns(song, f, used)
struct song *song;
FILE *f;
char used[];
	{
	int i, j;
	struct event *e;
	int winned = 0;

	j = 0;
	e = song->info.data;
	printf("Patterns skipped:");
   for (i = 0; i < song->info.maxpat; i++)
      {
		if (used[i])
			{
			e = fill_pattern(f, song, e);
			if (error != NONE)
				return 0;
			}
		else
			{
			printf(" %d", i);
			byteskip(f, patsize);
			winned += patsize;
			}
      }
   printf("\n");
   return winned;
	}

LOCAL void read_sample(info, f)
struct sample_info *info;
struct exfile *f;
	{
		/* add one byte for resampling */
	info->start = (SAMPLE *)alloc_sample(info->length + 1);
	if (!info->start)
		{
		error = OUT_OF_MEM;
		return;
		}

	if (info->rp_length)
		info->rp_start = info->start + info->rp_offset;

	obtain_sample(info->start, info->length, f);
	}




/* new_song: allocate a new structure for a song.
 *  clear each and every field as appropriate.
 */
LOCAL struct song *new_song()
   {
   struct song *new;
	int i;

   new = (struct song *)malloc(sizeof(struct song));
   if (!new) 
      {
      error = OUT_OF_MEM;
      return NULL;
      }
   new->title = NULL;
   new->info.length = 0;
   new->info.maxpat = -1;
	new->info.patterns = NULL;
	new->info.data = NULL;
	for (i = 1; i < MAX_NUMBER_SAMPLES; i++)
		new->samples[i] = NULL;
   return new;
   }

LOCAL void free_sample_info(sample)
struct sample_info *sample;
	{
	if (sample)
		{
		if (sample->start)
			free_sample(sample->start);
		if (sample->name)
			free(sample->name);
		free(sample);
		}
	}

/* release_song(song): give back all memory occupied by song. Assume 
 * that each structure has been correctly allocated by a call to the
 * corresponding new_XXX function.
 */
void release_song(song)
struct song *song;
   {
   int i;

	mung_message("freeing song");
   if (!song)
      return;
      /* Since sample compression, structure is INVALID after song->ninstr */
   for (i = 1; i <= song->ninstr; i++)
		free_sample_info(song->samples[i]);
	if (song->info.patterns)
		free(song->info.patterns);
	if (song->info.data)
		free(song->info.data);
   if (song->title)
      free(song->title);
   free(song);
	mung_message("freeed song");
   }

/* error_song(song): what we should return if there was an error. 
 * Actually, is mostly useful for its side effects.
 */
LOCAL struct song *error_song(song)
struct song *song;
   {
   release_song(song);
   return NULL;
   }

/* bad_sig(f, song): read the signature on file f and returns TRUE if 
 * it is not a known sig. Set some parameters of song as a side effect
 */
LOCAL int bad_sig(f, song)
struct exfile *f;
struct song *song;
   {
   char a, b, c, d;

   a = getc_file(f);
   b = getc_file(f);
   c = getc_file(f);
   d = getc_file(f);
   if (a == 'M' && b == '.' && c == 'K' && d == '.')
      return FALSE;
   if (a == 'M' && b == '&' && c == 'K' && d == '!')
      return FALSE;
   if (a == 'F' && b == 'L' && c == 'T' && d == '4')
      return FALSE;
	if (a == '6' && b == 'C' && c == 'H' && d == 'N')
		{
		song->ntracks = 6;
		return FALSE;
		}
	if (a == '8' && b == 'C' && c == 'H' && d == 'N')
		{
		song->ntracks= 8;
		return FALSE;
		}
   return TRUE;
   }

LOCAL void adjust_volumes(s)
struct song *s;
	{
	int i, j;
	for (i = 1; i <= s->ninstr; i++)
		for (j = 0; j <= MAX_VOLUME; j++)		/* note <= not a bug */
			s->samples[i]->volume_lookup[j] = (s->ntracks == 6) ? (4 * j) / 3 : j;
	}

	
/* s = read_song(f, type): try to read a song s of type NEW/OLD/NEW_NOCHECK
 * from file f. Might fail, i.e., return NULL if file is not a mod file of 
 * the correct type.
 */
struct song *read_song(f, type)
struct exfile *f;
int type;
   {
   struct song *song;
   int i, j;
	unsigned char used[NUMBER_PATTERNS];
	unsigned char sample_used[MAX_NUMBER_SAMPLES],
					  map_sample[MAX_NUMBER_SAMPLES];
	int used_patterns;
	int winned;
	struct automaton a;

   error = NONE;

	mung_message("loading song");
	if (!buffer)
		setup_buffer(1024);

   song = new_song();
   if (!song)
      return error_song(song);

	song->ntracks = 4;
   if (type == NEW || type == NEW_NO_CHECK)
		{
		song->type = PROTRACKER;
      song->ninstr = PRO_NUMBER_SAMPLES;
		}
   else
		{
		song->type = OLD_ST;
      song->ninstr = ST_NUMBER_SAMPLES;
		}

	song->ntracks = NORMAL_NTRACKS;
	song->info.plength = NORMAL_PLENGTH;

   song->title = getstring(f, TITLE_MAXLENGTH);
   if (error != NONE)
      return error_song(song);

   for (i = 1; i <= song->ninstr; i++)
      {
		sample_used[i] = 0;
		song->samples[i] = fill_sample_info(f);
		if (error != NONE)
         return error_song(song);
      }

   fill_song_info(&song->info, f);

   if (error != NONE)
      return error_song(song);

   if (type == NEW && bad_sig(f, song))
      return error_song(song);

   if (type == NEW_NO_CHECK)
      byteskip(f, 4);
        
  	song->max_sample_width = 8;			/* temporary */

  	if (song->ntracks == 4)
  		song->side_width = song->max_sample_width + 6 + 1;
	else
		song->side_width = song->max_sample_width + 6 + 2;

	used_patterns = compress_patterns(&song->info, used);

	song->info.patterns = (struct pattern *)
		malloc(sizeof(struct pattern) * song->info.length);
	song->info.data = (struct event *)
		malloc(sizeof(struct event) * song->info.plength
			* song->ntracks * used_patterns);
   if (!song->info.patterns ||!song->info.data)
      {
      error = OUT_OF_MEM;
      return error_song(song);
      }
	for (i = 0; i < song->info.length; i++)
		{
		song->info.patterns[i].e = song->info.data 
			+ song->info.patnumber[i] * song->info.plength * song->ntracks;
		song->info.patterns[i].number = song->info.patnumber[i];
		}
	
	patsize = song->info.plength * song->ntracks * 4;

	setup_buffer(patsize);
	if (!buffer)
		{
		error = OUT_OF_MEM;
		return error_song(song);
		}

	winned = fill_patterns(song, f, used);
	if (error != NONE)
		return error_song(song);
	for (i = 0; i < song->info.plength * song->ntracks * used_patterns; i++)
			sample_used[song->info.data[i].sample_number] = 1;

	song->info.maxpat = used_patterns;
         /* future code... */
   song->samples_start = tell_file(f);

#if 0
   if (feof(f))
      for (i = 1; i <= song->ninstr; i++)
         find_sample(song->samples[i]);
   else
#endif
      /* read samples from the file if they are needed */
	for (i = 1; i <= song->ninstr; i++)
		if (song->samples[i])
			{
			if (sample_used[i]) 
				read_sample(song->samples[i], f);
			else
				byteskip(f, song->samples[i]->length);
			}
	
      /* remap samples around */
	j = 0;
	for (i = 0; i < MAX_NUMBER_SAMPLES; i++)
		map_sample[i] = 0;
   printf("Removing:");
	for (i = 1; i <= song->ninstr; i++)
		{
		if (sample_used[i])
			{
			if (song->samples[i])
			   {
			   map_sample[i] = ++j;
			   song->samples[j] = song->samples[i];
			   song->samples[j]->color = j%15+1;
			   }
			else
			   /* empty samples are remapped to the last sample.
			    * If there are empty samples, then the last sample is free,
             * after the remapping.
             */
			   map_sample[i] = LAST_SAMPLE;
         }
		else
			{
			printf(" %d", i);
			map_sample[i] = 0;
			winned += sizeof(struct sample_info);
			if (song->samples[i])
			   {
			   winned += song->samples[i]->length;
			   printf("!");
			   }
			free_sample_info(song->samples[i]);
			song->samples[i] = NULL;
			}
		}
   printf("\n");
      /* don't bother removing stuff after song->ninstr */
	song->ninstr = j;
      /* set the last sample to be the empty sample, if this can be of use */
	if (j < LAST_SAMPLE)
      song->samples[LAST_SAMPLE] = empty_sample();
   printf("Won %d bytes\n", winned);
	for (i = 0; i < song->info.plength * song->ntracks * used_patterns; i++)
			song->info.data[i].sample_number = 
				map_sample[song->info.data[i].sample_number];
 	adjust_volumes(song);

   init_automaton(&a, song, 0);
   if (error != NONE)
      return error_song(song);

	compute_duration(&a, song);
	mung_message("Song loaded");
   return song;
   }

