/*	MikMod sound library
	(c) 1998, 1999, 2000, 2001, 2002 Miodrag Vallat and others - see file
	AUTHORS for complete list.

	This library is free software; you can redistribute it and/or modify
	it under the terms of the GNU Library General Public License as
	published by the Free Software Foundation; either version 2 of
	the License, or (at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU Library General Public License for more details.

	You should have received a copy of the GNU Library General Public
	License along with this library; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
	02111-1307, USA.
*/

/*==============================================================================

  $Id: load_med.c,v 1.1.1.1 2004/06/01 12:16:17 raph Exp $

  Amiga MED module loader

==============================================================================*/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#include <stdio.h>
#ifdef HAVE_MEMORY_H
#include <memory.h>
#endif
#include <string.h>

#include "mikmod_internals.h"

#ifdef SUNOS
extern int fprintf(FILE *, const char *, ...);
#endif

/*========== Module information */

typedef struct MEDHEADER {
	ULONG id;
	ULONG modlen;
	ULONG MEDSONGP;				/* struct MEDSONG *song; */
	UWORD psecnum;				/* for the player routine, MMD2 only */
	UWORD pseq;					/*  "   "   "   " */
	ULONG MEDBlockPP;			/* struct MEDBlock **blockarr; */
	ULONG reserved1;
	ULONG MEDINSTHEADERPP;		/* struct MEDINSTHEADER **smplarr; */
	ULONG reserved2;
	ULONG MEDEXPP;				/* struct MEDEXP *expdata; */
	ULONG reserved3;
	UWORD pstate;				/* some data for the player routine */
	UWORD pblock;
	UWORD pline;
	UWORD pseqnum;
	SWORD actplayline;
	UBYTE counter;
	UBYTE extra_songs;			/* number of songs - 1 */
} MEDHEADER;

typedef struct MEDSAMPLE {
	UWORD rep, replen;			/* offs: 0(s), 2(s) */
	UBYTE midich;				/* offs: 4(s) */
	UBYTE midipreset;			/* offs: 5(s) */
	UBYTE svol;					/* offs: 6(s) */
	SBYTE strans;				/* offs: 7(s) */
} MEDSAMPLE;

typedef struct MEDSONG {
	MEDSAMPLE sample[63];		/* 63 * 8 bytes = 504 bytes */
	UWORD numblocks;			/* offs: 504 */
	UWORD songlen;				/* offs: 506 */
	UBYTE playseq[256];			/* offs: 508 */
	UWORD deftempo;				/* offs: 764 */
	SBYTE playtransp;			/* offs: 766 */
	UBYTE flags;				/* offs: 767 */
	UBYTE flags2;				/* offs: 768 */
	UBYTE tempo2;				/* offs: 769 */
	UBYTE trkvol[16];			/* offs: 770 */
	UBYTE mastervol;			/* offs: 786 */
	UBYTE numsamples;			/* offs: 787 */
} MEDSONG;

typedef struct MEDEXP {
	ULONG nextmod;				/* pointer to next module */
	ULONG exp_smp;				/* pointer to MEDINSTEXT array */
	UWORD s_ext_entries;
	UWORD s_ext_entrsz;
	ULONG annotxt;				/* pointer to annotation text */
	ULONG annolen;
	ULONG iinfo;				/* pointer to MEDINSTINFO array */
	UWORD i_ext_entries;
	UWORD i_ext_entrsz;
	ULONG jumpmask;
	ULONG rgbtable;
	ULONG channelsplit;
	ULONG n_info;
	ULONG songname;				/* pointer to songname */
	ULONG songnamelen;
	ULONG dumps;
	ULONG reserved2[7];
} MEDEXP;

typedef struct MMD0NOTE {
	UBYTE a, b, c;
} MMD0NOTE;

typedef struct MMD1NOTE {
	UBYTE a, b, c, d;
} MMD1NOTE;

typedef struct MEDINSTHEADER {
	ULONG length;
	SWORD type;
	/* Followed by actual data */
} MEDINSTHEADER;

typedef struct MEDINSTEXT {
	UBYTE hold;
	UBYTE decay;
	UBYTE suppress_midi_off;
	SBYTE finetune;
} MEDINSTEXT;

typedef struct MEDINSTINFO {
	UBYTE name[40];
} MEDINSTINFO;

/*========== Loader variables */

#define MMD0_string 0x4D4D4430
#define MMD1_string 0x4D4D4431

static MEDHEADER *mh = NULL;
static MEDSONG *ms = NULL;
static MEDEXP *me = NULL;
static ULONG *ba = NULL;
static MMD0NOTE *mmd0pat = NULL;
static MMD1NOTE *mmd1pat = NULL;

static BOOL decimalvolumes;
static BOOL bpmtempos;

#define d0note(row,col) mmd0pat[((row)*(UWORD)of.numchn)+(col)]
#define d1note(row,col) mmd1pat[((row)*(UWORD)of.numchn)+(col)]

static CHAR MED_Version[] = "OctaMED (MMDx)";

/*========== Loader code */

static BOOL MED_Test(void)
{
	UBYTE id[4];

	if (!_mm_read_UBYTES(id, 4, modreader))
		return 0;
	if ((!memcmp(id, "MMD0", 4)) || (!memcmp(id, "MMD1", 4)))
		return 1;
	return 0;
}

static BOOL MED_Init(void)
{
	if (!(me = (MEDEXP *)_mm_malloc(sizeof(MEDEXP))))
		return 0;
	if (!(mh = (MEDHEADER *)_mm_malloc(sizeof(MEDHEADER))))
		return 0;
	if (!(ms = (MEDSONG *)_mm_malloc(sizeof(MEDSONG))))
		return 0;
	return 1;
}

static void MED_Cleanup(void)
{
	_mm_free(me);
	_mm_free(mh);
	_mm_free(ms);
	_mm_free(ba);
	_mm_free(mmd0pat);
	_mm_free(mmd1pat);
}

static void EffectCvt(UBYTE eff, UBYTE dat)
{
	switch (eff) {
		/* 0x0 0x1 0x2 0x3 0x4 PT effects */
	  case 0x5:				/* PT vibrato with speed/depth nibbles swapped */
		UniPTEffect(0x4, (dat >> 4) | ((dat & 0xf) << 4));
		break;
		/* 0x6 0x7 not used */
	  case 0x6:
	  case 0x7:
		break;
	  case 0x8:				/* midi hold/decay */
		break;
	  case 0x9:
		if (bpmtempos) {
			if (!dat)
				dat = of.initspeed;
			UniEffect(UNI_S3MEFFECTA, dat);
		} else {
			if (dat <= 0x20) {
				if (!dat)
					dat = of.initspeed;
				else
					dat /= 4;
				UniPTEffect(0xf, dat);
			} else
				UniEffect(UNI_MEDSPEED, ((UWORD)dat * 125) / (33 * 4));
		}
		break;
		/* 0xa 0xb PT effects */
	  case 0xc:
		if (decimalvolumes)
			dat = (dat >> 4) * 10 + (dat & 0xf);
		UniPTEffect(0xc, dat);
		break;
	  case 0xd:				/* same as PT volslide */
		UniPTEffect(0xa, dat);
		break;
	  case 0xe:				/* synth jmp - midi */
		break;
	  case 0xf:
		switch (dat) {
		  case 0:				/* patternbreak */
			UniPTEffect(0xd, 0);
			break;
		  case 0xf1:			/* play note twice */
			UniWriteByte(UNI_MEDEFFECTF1);
			break;
		  case 0xf2:			/* delay note */
			UniWriteByte(UNI_MEDEFFECTF2);
			break;
		  case 0xf3:			/* play note three times */
			UniWriteByte(UNI_MEDEFFECTF3);
			break;
		  case 0xfe:			/* stop playing */
			UniPTEffect(0xb, of.numpat);
			break;
		  case 0xff:			/* note cut */
			UniPTEffect(0xc, 0);
			break;
		  default:
			if (dat <= 10)
				UniPTEffect(0xf, dat);
			else if (dat <= 240) {
				if (bpmtempos)
					UniPTEffect(0xf, (dat < 32) ? 32 : dat);
				else
					UniEffect(UNI_MEDSPEED, ((UWORD)dat * 125) / 33);
			}
		}
		break;
	  default:					/* all normal PT effects are handled here */
		UniPTEffect(eff, dat);
		break;
	}
}

static UBYTE *MED_Convert1(int count, int col)
{
	int t;
	UBYTE inst, note, eff, dat;
	MMD1NOTE *n;

	UniReset();
	for (t = 0; t < count; t++) {
		n = &d1note(t, col);

		note = n->a & 0x7f;
		inst = n->b & 0x3f;
		eff = n->c & 0xf;
		dat = n->d;

		if (inst)
			UniInstrument(inst - 1);
		if (note)
			UniNote(note + 3 * OCTAVE - 1);
		EffectCvt(eff, dat);
		UniNewline();
	}
	return UniDup();
}

static UBYTE *MED_Convert0(int count, int col)
{
	int t;
	UBYTE a, b, inst, note, eff, dat;
	MMD0NOTE *n;

	UniReset();
	for (t = 0; t < count; t++) {
		n = &d0note(t, col);
		a = n->a;
		b = n->b;

		note = a & 0x3f;
		a >>= 6;
		a = ((a & 1) << 1) | (a >> 1);
		inst = (b >> 4) | (a << 4);
		eff = b & 0xf;
		dat = n->c;

		if (inst)
			UniInstrument(inst - 1);
		if (note)
			UniNote(note + 3 * OCTAVE - 1);
		EffectCvt(eff, dat);
		UniNewline();
	}
	return UniDup();
}

static BOOL LoadMEDPatterns(void)
{
	int t, row, col;
	UWORD numtracks, numlines, maxlines = 0, track = 0;
	MMD0NOTE *mmdp;

	/* first, scan patterns to see how many channels are used */
	for (t = 0; t < of.numpat; t++) {
		_mm_fseek(modreader, ba[t], SEEK_SET);
		numtracks = _mm_read_UBYTE(modreader);
		numlines = _mm_read_UBYTE(modreader);

		if (numtracks > of.numchn)
			of.numchn = numtracks;
		if (numlines > maxlines)
			maxlines = numlines;
	}

	of.numtrk = of.numpat * of.numchn;
	if (!AllocTracks())
		return 0;
	if (!AllocPatterns())
		return 0;

	if (!(mmd0pat = (MMD0NOTE *)_mm_calloc(of.numchn * (maxlines + 1), sizeof(MMD0NOTE))))
		return 0;

	/* second read: read and convert patterns */
	for (t = 0; t < of.numpat; t++) {
		_mm_fseek(modreader, ba[t], SEEK_SET);
		numtracks = _mm_read_UBYTE(modreader);
		numlines = _mm_read_UBYTE(modreader);

		of.pattrows[t] = ++numlines;
		memset(mmdp = mmd0pat, 0, of.numchn * maxlines * sizeof(MMD0NOTE));
		for (row = numlines; row; row--) {
			for (col = numtracks; col; col--, mmdp++) {
				mmdp->a = _mm_read_UBYTE(modreader);
				mmdp->b = _mm_read_UBYTE(modreader);
				mmdp->c = _mm_read_UBYTE(modreader);
			}
		}

		for (col = 0; col < of.numchn; col++)
			of.tracks[track++] = MED_Convert0(numlines, col);
	}
	return 1;
}

static BOOL LoadMMD1Patterns(void)
{
	int t, row, col;
	UWORD numtracks, numlines, maxlines = 0, track = 0;
	MMD1NOTE *mmdp;

	/* first, scan patterns to see how many channels are used */
	for (t = 0; t < of.numpat; t++) {
		_mm_fseek(modreader, ba[t], SEEK_SET);
		numtracks = _mm_read_M_UWORD(modreader);
		numlines = _mm_read_M_UWORD(modreader);
		if (numtracks > of.numchn)
			of.numchn = numtracks;
		if (numlines > maxlines)
			maxlines = numlines;
	}

	of.numtrk = of.numpat * of.numchn;
	if (!AllocTracks())
		return 0;
	if (!AllocPatterns())
		return 0;

	if (!(mmd1pat = (MMD1NOTE *)_mm_calloc(of.numchn * (maxlines + 1), sizeof(MMD1NOTE))))
		return 0;

	/* second read: really read and convert patterns */
	for (t = 0; t < of.numpat; t++) {
		_mm_fseek(modreader, ba[t], SEEK_SET);
		numtracks = _mm_read_M_UWORD(modreader);
		numlines = _mm_read_M_UWORD(modreader);

		_mm_fseek(modreader, sizeof(ULONG), SEEK_CUR);
		of.pattrows[t] = ++numlines;
		memset(mmdp = mmd1pat, 0, of.numchn * maxlines * sizeof(MMD1NOTE));

		for (row = numlines; row; row--) {
			for (col = numtracks; col; col--, mmdp++) {
				mmdp->a = _mm_read_UBYTE(modreader);
				mmdp->b = _mm_read_UBYTE(modreader);
				mmdp->c = _mm_read_UBYTE(modreader);
				mmdp->d = _mm_read_UBYTE(modreader);
			}
		}

		for (col = 0; col < of.numchn; col++)
			of.tracks[track++] = MED_Convert1(numlines, col);
	}
	return 1;
}

static BOOL MED_Load(BOOL curious)
{
	int t;
	ULONG sa[64];
	MEDINSTHEADER s;
	SAMPLE *q;
	MEDSAMPLE *mss;

	/* try to read module header */
	mh->id = _mm_read_M_ULONG(modreader);
	mh->modlen = _mm_read_M_ULONG(modreader);
	mh->MEDSONGP = _mm_read_M_ULONG(modreader);
	mh->psecnum = _mm_read_M_UWORD(modreader);
	mh->pseq = _mm_read_M_UWORD(modreader);
	mh->MEDBlockPP = _mm_read_M_ULONG(modreader);
	mh->reserved1 = _mm_read_M_ULONG(modreader);
	mh->MEDINSTHEADERPP = _mm_read_M_ULONG(modreader);
	mh->reserved2 = _mm_read_M_ULONG(modreader);
	mh->MEDEXPP = _mm_read_M_ULONG(modreader);
	mh->reserved3 = _mm_read_M_ULONG(modreader);
	mh->pstate = _mm_read_M_UWORD(modreader);
	mh->pblock = _mm_read_M_UWORD(modreader);
	mh->pline = _mm_read_M_UWORD(modreader);
	mh->pseqnum = _mm_read_M_UWORD(modreader);
	mh->actplayline = _mm_read_M_SWORD(modreader);
	mh->counter = _mm_read_UBYTE(modreader);
	mh->extra_songs = _mm_read_UBYTE(modreader);

	/* Seek to MEDSONG struct */
	_mm_fseek(modreader, mh->MEDSONGP, SEEK_SET);

	/* Load the MED Song Header */
	mss = ms->sample;			/* load the sample data first */
	for (t = 63; t; t--, mss++) {
		mss->rep = _mm_read_M_UWORD(modreader);
		mss->replen = _mm_read_M_UWORD(modreader);
		mss->midich = _mm_read_UBYTE(modreader);
		mss->midipreset = _mm_read_UBYTE(modreader);
		mss->svol = _mm_read_UBYTE(modreader);
		mss->strans = _mm_read_SBYTE(modreader);
	}

	ms->numblocks = _mm_read_M_UWORD(modreader);
	ms->songlen = _mm_read_M_UWORD(modreader);
	_mm_read_UBYTES(ms->playseq, 256, modreader);
	ms->deftempo = _mm_read_M_UWORD(modreader);
	ms->playtransp = _mm_read_SBYTE(modreader);
	ms->flags = _mm_read_UBYTE(modreader);
	ms->flags2 = _mm_read_UBYTE(modreader);
	ms->tempo2 = _mm_read_UBYTE(modreader);
	_mm_read_UBYTES(ms->trkvol, 16, modreader);
	ms->mastervol = _mm_read_UBYTE(modreader);
	ms->numsamples = _mm_read_UBYTE(modreader);

	/* check for a bad header */
	if (_mm_eof(modreader)) {
		_mm_errno = MMERR_LOADING_HEADER;
		return 0;
	}

	/* load extension structure */
	if (mh->MEDEXPP) {
		_mm_fseek(modreader, mh->MEDEXPP, SEEK_SET);
		me->nextmod = _mm_read_M_ULONG(modreader);
		me->exp_smp = _mm_read_M_ULONG(modreader);
		me->s_ext_entries = _mm_read_M_UWORD(modreader);
		me->s_ext_entrsz = _mm_read_M_UWORD(modreader);
		me->annotxt = _mm_read_M_ULONG(modreader);
		me->annolen = _mm_read_M_ULONG(modreader);
		me->iinfo = _mm_read_M_ULONG(modreader);
		me->i_ext_entries = _mm_read_M_UWORD(modreader);
		me->i_ext_entrsz = _mm_read_M_UWORD(modreader);
		me->jumpmask = _mm_read_M_ULONG(modreader);
		me->rgbtable = _mm_read_M_ULONG(modreader);
		me->channelsplit = _mm_read_M_ULONG(modreader);
		me->n_info = _mm_read_M_ULONG(modreader);
		me->songname = _mm_read_M_ULONG(modreader);
		me->songnamelen = _mm_read_M_ULONG(modreader);
		me->dumps = _mm_read_M_ULONG(modreader);
	}

	/* seek to and read the samplepointer array */
	_mm_fseek(modreader, mh->MEDINSTHEADERPP, SEEK_SET);
	if (!_mm_read_M_ULONGS(sa, ms->numsamples, modreader)) {
		_mm_errno = MMERR_LOADING_HEADER;
		return 0;
	}

	/* alloc and read the blockpointer array */
	if (!(ba = (ULONG *)_mm_calloc(ms->numblocks, sizeof(ULONG))))
		return 0;
	_mm_fseek(modreader, mh->MEDBlockPP, SEEK_SET);
	if (!_mm_read_M_ULONGS(ba, ms->numblocks, modreader)) {
		_mm_errno = MMERR_LOADING_HEADER;
		return 0;
	}

	/* copy song positions */
	if (!AllocPositions(ms->songlen))
		return 0;
	for (t = 0; t < ms->songlen; t++)
		of.positions[t] = ms->playseq[t];

	decimalvolumes = (ms->flags & 0x10) ? 0 : 1;
	bpmtempos = (ms->flags2 & 0x20) ? 1 : 0;

	if (bpmtempos) {
		int bpmlen = (ms->flags2 & 0x1f) + 1;
		of.initspeed = ms->tempo2;
		of.inittempo = ms->deftempo * bpmlen / 4;

		if (bpmlen != 4) {
			/* Let's do some math : compute GCD of BPM beat length and speed */
			int a, b;

			a = bpmlen;
			b = ms->tempo2;

			if (a > b) {
				t = b;
				b = a;
				a = t;
			}
			while ((a != b) && (a)) {
				t = a;
				a = b - a;
				b = t;
				if (a > b) {
					t = b;
					b = a;
					a = t;
				}
			}

			of.initspeed /= b;
			of.inittempo = ms->deftempo * bpmlen / (4 * b);
		}
	} else {
		of.initspeed = ms->tempo2;
		of.inittempo = ms->deftempo ? ((UWORD)ms->deftempo * 125) / 33 : 128;
		if ((ms->deftempo <= 10) && (ms->deftempo))
			of.inittempo = (of.inittempo * 33) / 6;
		of.flags |= UF_HIGHBPM;
	}
	MED_Version[12] = mh->id;
	of.modtype = _mm_strdup(MED_Version);
	of.numchn = 0;				/* will be counted later */
	of.numpat = ms->numblocks;
	of.numpos = ms->songlen;
	of.numins = ms->numsamples;
	of.numsmp = of.numins;
	of.reppos = 0;
	if ((mh->MEDEXPP) && (me->songname) && (me->songnamelen)) {
		char *name;

		_mm_fseek(modreader, me->songname, SEEK_SET);
		name = (char *) _mm_malloc(me->songnamelen);
		_mm_read_UBYTES(name, me->songnamelen, modreader);
		of.songname = DupStr(name, me->songnamelen, 1);
		free(name);
	} else
		of.songname = DupStr(NULL, 0, 0);
	if ((mh->MEDEXPP) && (me->annotxt) && (me->annolen)) {
		_mm_fseek(modreader, me->annotxt, SEEK_SET);
		ReadComment(me->annolen);
	}

	if (!AllocSamples())
		return 0;
	q = of.samples;
	for (t = 0; t < of.numins; t++) {
		q->flags = SF_SIGNED;
		q->volume = 64;
		if (sa[t]) {
			_mm_fseek(modreader, sa[t], SEEK_SET);
			s.length = _mm_read_M_ULONG(modreader);
			s.type = _mm_read_M_SWORD(modreader);

			if (s.type) {
#ifdef MIKMOD_DEBUG
				fprintf(stderr, "\rNon-sample instruments not supported in MED loader yet\n");
#endif
				if (!curious) {
					_mm_errno = MMERR_MED_SYNTHSAMPLES;
					return 0;
				}
				s.length = 0;
			}

			if (_mm_eof(modreader)) {
				_mm_errno = MMERR_LOADING_SAMPLEINFO;
				return 0;
			}

			q->length = s.length;
			q->seekpos = _mm_ftell(modreader);
			q->loopstart = ms->sample[t].rep << 1;
			q->loopend = q->loopstart + (ms->sample[t].replen << 1);

			if (ms->sample[t].replen > 1)
				q->flags |= SF_LOOP;

			/* don't load sample if length>='MMD0'...
			   such kluges make libmikmod's code unique !!! */
			if (q->length >= MMD0_string)
				q->length = 0;
		} else
			q->length = 0;

		if ((mh->MEDEXPP) && (me->exp_smp) &&
			(t < me->s_ext_entries) && (me->s_ext_entrsz >= 4)) {
			MEDINSTEXT ie;

			_mm_fseek(modreader, me->exp_smp + t * me->s_ext_entrsz,
					  SEEK_SET);
			ie.hold = _mm_read_UBYTE(modreader);
			ie.decay = _mm_read_UBYTE(modreader);
			ie.suppress_midi_off = _mm_read_UBYTE(modreader);
			ie.finetune = _mm_read_SBYTE(modreader);

			q->speed = finetune[ie.finetune & 0xf];
		} else
			q->speed = 8363;

		if ((mh->MEDEXPP) && (me->iinfo) &&
			(t < me->i_ext_entries) && (me->i_ext_entrsz >= 40)) {
			MEDINSTINFO ii;

			_mm_fseek(modreader, me->iinfo + t * me->i_ext_entrsz, SEEK_SET);
			_mm_read_UBYTES(ii.name, 40, modreader);
			q->samplename = DupStr((char*)ii.name, 40, 1);
		} else
			q->samplename = NULL;

		q++;
	}

	if (mh->id == MMD0_string) {
		if (!LoadMEDPatterns()) {
			_mm_errno = MMERR_LOADING_PATTERN;
			return 0;
		}
	} else if (mh->id == MMD1_string) {
		if (!LoadMMD1Patterns()) {
			_mm_errno = MMERR_LOADING_PATTERN;
			return 0;
		}
	} else {
		_mm_errno = MMERR_NOT_A_MODULE;
		return 0;
	}
	return 1;
}

static CHAR *MED_LoadTitle(void)
{
	ULONG posit, namelen;
	CHAR *name, *retvalue = NULL;

	_mm_fseek(modreader, 0x20, SEEK_SET);
	posit = _mm_read_M_ULONG(modreader);

	if (posit) {
		_mm_fseek(modreader, posit + 0x2C, SEEK_SET);
		posit = _mm_read_M_ULONG(modreader);
		namelen = _mm_read_M_ULONG(modreader);

		_mm_fseek(modreader, posit, SEEK_SET);
		name = (CHAR*) _mm_malloc(namelen);
		_mm_read_UBYTES(name, namelen, modreader);
		retvalue = DupStr(name, namelen, 1);
		free(name);
	}

	return retvalue;
}

/*========== Loader information */

MIKMODAPI MLOADER load_med = {
	NULL,
	"MED",
	"MED (OctaMED)",
	MED_Init,
	MED_Test,
	MED_Load,
	MED_Cleanup,
	MED_LoadTitle
};

/* ex:set ts=4: */
