/* load.c 

/* loads a soundtracker binary file
 * into memory, using a more convenient format
 */

/* $Author: Espie $
 * $Date: 91/05/16 15:05:24 $
 * $Revision: 1.34 $
 * $Log:	load.c,v $
 * Revision 1.34  91/05/16  15:05:24  Espie
 * *** empty log message ***
 * 
 * Revision 1.33  91/05/12  19:56:39  Espie
 * Shortened event structure.
 * 
 * Revision 1.32  91/05/12  16:00:39  Espie
 * switched back to a char *.
 * 
 * Revision 1.31  91/05/07  12:13:36  Espie
 * *** empty log message ***
 * 
 * Revision 1.30  91/05/06  23:39:31  Espie
 * Now tries to load a WBArg.
 * Changes for accomodating new finetune and external find_note.
 * 
 * Revision 1.29  91/05/06  15:15:06  Espie
 * Some more error checking, still not enough probably.
 * 
 * Revision 1.28  91/05/05  19:06:41  Espie
 * *** empty log message ***
 * 
 * Revision 1.27  91/05/05  03:59:37  Espie
 * Corrected other problems. Added some measure of error recovery
 * 
 * 
 * Revision 1.26  91/05/04  23:22:22  Espie
 * Corrected a very small bug in the handling of dummy samples.
 * 
 * Revision 1.25  91/05/02  23:31:14  Espie
 * Now uses standard amigados filehandles... Might come in handy.
 * 
 * Revision 1.24  91/05/02  11:20:48  Espie
 * Can now unload() empty songs...
 * 
 * Revision 1.23  91/05/02  01:30:57  Espie
 * Correction of some small bugs, added a dummy sample for the player.
 * 
 * Revision 1.22  91/04/30  16:52:09  Espie
 * Suppressed every IO.
 * Added channel tracks, should handle finetuned instruments.
 * 
 * Revision 1.21  91/04/30  01:48:58  Espie
 * Corrected a BIG and stupid bug, there was two separate cleanups where
 * there should have been one, so that cleaning up an old module only happened
 * on exit of the program...
 * 
 * Revision 1.20  91/04/30  00:35:15  Espie
 * Stable version III.
 * 
 * Revision 1.19  91/04/30  00:23:34  Espie
 * New error checking, does still exit if not enough memory.
 * 
 * Revision 1.18  91/04/29  23:53:56  Espie
 * Added unload_song(). Need a decent error recovery strategy right now.
 * 
 * Revision 1.17  91/04/29  02:22:06  Espie
 * checkabort() added for user interruption,
 * since there are no longer critical sections.
 * 
 * Revision 1.16  91/04/28  22:53:37  Espie
 * More leeway in the period computation.
 * 
 * Revision 1.15  91/04/27  20:49:00  Espie
 * Small bug in find_note (order was reversed)
 * 
 * Revision 1.14  91/04/27  16:45:56  Espie
 * Rounding errors for note period.
 * 
 * Revision 1.13  91/04/24  15:27:19  Espie
 * Minor changes ???
 * 
 * Revision 1.12  91/04/23  21:30:32  Espie
 * Revised so that one song can be automatically cleaned out.
 * 
 * Revision 1.11  91/04/21  20:06:07  Espie
 * The loader now precomputes notes.
 * It does not yet precompiles the effects (next step ?).
 * 
 * Revision 1.10  91/04/21  12:11:43  Espie
 * Stable version, known as bunch II.
 * Also features ``right'' log description.
 * 
 * Revision 1.9  91/04/21  11:14:34
 * Bug in st files: repeat start is sometimes double what it should be.
 *
 * Revision 1.8  91/04/20  18:14:24
 *
 * Revision 1.7  91/04/19  13:22:20
 *
 * Revision 1.6  91/04/19  02:20:07
 * loader without error checking for last sample.
 * added dummy sample for simplified check.
 *
 * Revision 1.5  91/04/18  02:25:41
 * bunch I.
 *
 * Revision 1.4  91/04/17  18:50:42
 * This is now a working module. Only the block translation is not yet debugged.
 *
 * Revision 1.3  91/04/14  22:02:32
 * Playing version. This one knows how to play sample!
 *
 * Revision 1.2  91/04/14  18:06:52
 * This version is able to load most soundtrackers files without apparent mistakes.
 * I still have got to figure out how the audio hardware works.
 * So this doesn't play any tune.
 * Apparently no bug as long as I don't try anything with the hardware.
 *
 * Revision 1.1  91/04/11  18:39:23
 * Initial revision.
 * Not yet debugged or anything.
 * No interface for other modules.
 *
 */
  
#include <exec/types.h>
#include <exec/memory.h>
#include <string.h>
#include <stddef.h>
#include <dos/dos.h>
#include <workbench/startup.h>
#include <proto/dos.h>
#include <proto/exec.h>
#include <custom/cleanup.h>
#include "song.h"
#include "proto.h"
#include "periods.h"

#include <proto/exec.h>
#include <stdlib.h>

/* definitions for the binary st file format 
 */

#define MAX_BLOCKS 128
#define SAMPLE_NAME 22
#define SONG_NAME 20
#define OLD_SAMPLES 15
#define NEW_SAMPLES 31

struct binary_sample_info
	{
		char sample_name[SAMPLE_NAME];
		UWORD length;
		UBYTE finetune;
		UBYTE volume;
		UWORD rp_start;
		UWORD rp_length;
	};
	
struct binary_song_info
	{
		UBYTE length;
		UBYTE thing;
		UBYTE block_number[MAX_BLOCKS];
	};

struct binary_event
	{
		UBYTE detail[4];
	};
	
struct interleaved
	{
		struct binary_event track[NUMBER_TRACKS];
	};
			
struct binary_block
	{
		struct interleaved data[BLOCK_LENGTH];
	};

	
/* a song is laid out like this:
	char song_name[SONG_NAME];
	struct sample_info samples[OLD_SAMPLES or NEW_SAMPLES];
	struct song_info song;
	(char sig[4] == "M.K." for new soundtrackers, with NEW_SAMPLES instruments)
	struct block blocks[number of blocks];
	UWORD sample0[length sample0];
	UWORD sample1[length sample1];
		...
	UWORD samplen[length samplen];	 where n=OLD_SAMPLES or NEW_SAMPLES.
 */
 
struct noisetracker_header
	{
		char song_name[SONG_NAME];
		struct binary_sample_info sample_info[NEW_SAMPLES];
		struct binary_song_info song_info;
		ULONG sig;
	};
	
struct old_st_header
	{
		char song_name[SONG_NAME];
		struct binary_sample_info sample_info[OLD_SAMPLES];
		struct binary_song_info song_info;
			/* no sig */
	};
	
struct other_stuff
	{
		ULONG sig;
	};

#define MK_SIG(a, b, c, d) ((a<<24) | (b<< 16) | (c<<8) | d) 	

union header
	{
		struct noisetracker_header nt;
		struct old_st_header st;
		struct other_stuff other;
	} header;
	



struct sample_info dummy =
	{
		"dummy sample",
		0, 0, 0, 0, 0, 0, 0
	};

int last_error;
		
	
/* cstring(buffer, maxlength)
 * converts a soundtracker string into a decent cstring
 */
 
char *cstring(CLEAN clear, char buffer[], int maxlength)
	{
	char *st;
	int i;
		for (i = 0; i < maxlength; i++)
			if (buffer[i] == 0)
				break;
		st = malloc(i+1);
		if (st)
			ToCleanL(clear, free, st);
		else
			return NULL;
		st[i] = 0;
		return strncpy(st, buffer, i);
	}
	


struct sample_info *extract_sample_info(CLEAN clear, struct binary_sample_info *i)
	{
	struct sample_info *new;
		check_abort();
		if (i->length <= 1)
			return &dummy;
		new = malloc(sizeof(struct sample_info));
		if (new)
			ToCleanL(clear, free, new);
		else
			return NULL;
		new->length = i->length;
		new->finetune = normalize_finetune(i->finetune);
		new->volume = i->volume;
		if (i->rp_start + i->rp_length - 1 > i->length)
			{
				i->rp_start >>=1;
			}
		new->rp_offset = i->rp_start;
		new->rp_length = i->rp_length;
		new->name = cstring(clear, i->sample_name, SAMPLE_NAME);
		if (new->finetune < 0)
			{
				last_error = NOT_A_MOD;
				return NULL;
			}
		else
			return new;
	}
	
int find_max(UBYTE block_number[], int max_index)
	{
	int current, i;
		for (current = -1, i = 0; i < max_index; i++)
			if (block_number[i] > current)
				current = block_number[i];
			/* a negative return indicates a problem */
		if (current > 127)
			return (-1);
		return current;
	}

struct block **map_blocks(CLEAN clear, UBYTE block_number[], int length, struct block array[])
	{
	int i;
	struct block **pters;
		pters = malloc(length * sizeof(struct block *));
		if (pters)
			ToCleanL(clear, free, pters);
		else
			return NULL;
		for (i = 0; i < length; i++)
			pters[i] = array+block_number[i];
		return pters;
	}

struct block *allocate_blocks(CLEAN clear, int number)
	{
	struct block *new;
		new = malloc(number * sizeof(struct block));
		if (new)
			ToCleanL(clear, free, new);
		else
			return NULL;
		return new;
	}
		
struct song_info *extract_song_info(CLEAN clear, struct binary_song_info *b)
	{
	struct song_info *new;
		new = malloc(sizeof(struct song_info));
		if (new)
			ToCleanL(clear, free, new);
		else
			return NULL;
		new->length = b->length;
		new->thing = b->thing;
		new->total = find_max(b->block_number, 128)+1;
		if (new->total < 0)
			return NULL;
		new->physical = allocate_blocks(clear, new->total);
		if (!new->physical)
			return NULL;
		new->pblocks = map_blocks(clear, b->block_number, new->length, 
			new->physical);
		if (!new->pblocks)
			return NULL;
		return new;
	}


/* we will leave that alone for the time being */
	
struct sample_info *instr_channel[NUMBER_TRACKS];
struct sample_info **sample_array;
int current_max;

void parse_event(struct binary_event *b, struct event *e, int tn)
	{
	UWORD period;
		e->sample_number = (b->detail[0]&~15) | (b->detail[2]>>4);
		e->sample_number &= 31;
		if (e->sample_number)
			{
				instr_channel[tn] = sample_array[e->sample_number];
				if (e->sample_number > current_max)
					current_max = e->sample_number;
			}
		period = ((b->detail[0]&15)<<8) | b->detail[1];
		e->note = find_note(period, instr_channel[tn]->finetune);
		if (e->note > FINE_PERIOD)
			last_error = NOTE_PROBLEM;
		e->effect = b->detail[2]&15;
		e->parameters = b->detail[3];
	}
	
void parse_block(struct binary_block *b, struct block *d)
	{
	int i, j;
		for (i = 0; i < BLOCK_LENGTH; i++)
			for (j = 0; j < NUMBER_TRACKS; j++)
				parse_event(&b->data[i].track[j], &d->e[j][i], j);
	}
				
int read_blocks(CLEAN clear, BPTR f, int total, struct block *first)
	{
	struct binary_block b;
	int i;
		current_max = 0;
		for (i = 0; i < NUMBER_TRACKS; i++)
			instr_channel[i] = sample_array[0];
		for (i = 0; i < total; i++)
			{
				if (Read(f, &b, sizeof(b)) != sizeof(b))
					return 0;
				check_abort();
				parse_block(&b, first+i);
			}
		return current_max;
	}

BOOL allocate_samples(CLEAN clear, struct song *s, int max_sn)
	{
	int i;
	int length;
	UWORD *p, *empty;		
			/* so we don't have to multiply everything by 2
			 */
					 
			/* compute the total length
			 */
		for (length = 1, i = 1 ; i <= max_sn; i++)
			{
				if (s->samples[i] != &dummy)
					length += s->samples[i]->length;
			}
				
			/* allocate corresponding chip memory 
			 */
		p = AllocMem(2 * length, MEMF_CHIP|MEMF_CLEAR);
		if (p)
			ToClean2L(clear, FreeMem, p, 2*length);
		else
			{
				last_error = OUT_OF_CHIP;
				return NULL;
			}
		empty = p++;
		dummy.start = empty;
		dummy.length = 1;
		dummy.rp_start = empty;
		dummy.rp_length = 1;
			/* compute addresses information
			 */
		for (i = 1; i <= max_sn; i++)
			if (s->samples[i] != &dummy)
				{
					s->samples[i]->start = p;
					if (s->samples[i]->rp_length == 1)
						s->samples[i]->rp_start = empty;
					else
						s->samples[i]->rp_start = p + s->samples[i]->rp_offset;
					p+= s->samples[i]->length;
				}
	}

BOOL read_sample(CLEAN clear, BPTR f, struct binary_sample_info *b, 
	struct sample_info *s)
	{
		check_abort();
		if (Read(f, s->start, b->length*2) != b->length*2 
			&& b->length > 1)
				{
					last_error = MISSING_SAMPLE;
					return TRUE;
				}
		else
			return TRUE;
	}


struct song *read_song(BPTR f)
	{
	struct song *s;
	int i, sn, max_sn;
	CLEAN clear;
		last_error = OUT_OF_MEMORY;
		clear = AllocClean(NIL);
		if (!clear)
			mayPanic("Couldn't allocate cleanup");
		s = malloc(sizeof(struct song));
		if (s)
			{
				s->clear = clear;
				ToCleanL(clear, free, s);
			}
		else
			{
				last_error = OUT_OF_MEMORY;
				return NULL;
			}
		if (Read(f, &header, sizeof(union header)) != sizeof(union header))
			{
				last_error = NOT_A_MOD;
				return unload_song(s);
			}
				/* fast recognize other formats */
		if (header.other.sig == MK_SIG('M','M','D','0')
			|| header.other.sig == MK_SIG('S','M','O','D')
			|| header.other.sig == MK_SIG('F','C','1','4'))
				{
					last_error = UNSUPPORTED;
					return unload_song(s);
				}
		if (header.nt.sig == MK_SIG('M','.','K','.'))
			{
				sn = NEW_SAMPLES;
				s->info = extract_song_info(clear, &header.nt.song_info);
			}
		else
			{
				sn = OLD_SAMPLES;
				s->info = extract_song_info(clear, &header.st.song_info);
				Seek(f, sizeof(struct old_st_header), OFFSET_BEGINNING);
			}
		if (!s->info)
			return unload_song(s);

		s->title = cstring(clear, header.nt.song_name, SONG_NAME);
		if (!s->title)
			return unload_song(s);

				/* so that we don't allocate memory for dummy samples */
		dummy.length = 0;
		for (i = 0; i < NUMBER_SAMPLES; i++)
			s->samples[i] = &dummy;
		for (i = 0; i < sn; i++)
			{
				s->samples[i+1] = extract_sample_info(clear, 
					&header.nt.sample_info[i]);
				if (!s->samples[i+1])
					return unload_song(s);
			}
		sample_array = s->samples;
		max_sn = read_blocks(clear, f, s->info->total, s->info->physical);
		if (!max_sn)
			return unload_song(s);
		if (!allocate_samples(clear, s, max_sn))
			return unload_song(s);
		for (i = 0; i < max_sn; i++)
			if (!read_sample(clear, f, &header.nt.sample_info[i], s->samples[i+1]))
				return unload_song(s);
		return s;
	}

int load_error(void)
	{
		return last_error;
	}
	
struct song *unload_song(struct song *s)
	{
		if (s)
			CleanUp(s->clear);
		return NULL;
	}

struct song *load_song(char *arg)
	{
	BPTR f;
	CLEAN closefile;
	struct song *s;
	static char message[200];
		closefile = AllocClean(NIL);
		if (!arg)
			return NULL;
			/* check the precise length !!! */
		sprintf(message, "Loading %s...", arg);
		temporary_title(message);
		ToClean0L(closefile, restore_title);
		f = Open(arg, MODE_OLDFILE);
		if (f)
			{
				ToCleanL(closefile, Close, f);
				s = read_song(f);
			}
		else
			s = NULL;
		CleanUp(closefile);
		return s;
	}
