text: /* Extended Module Player
   * Copyright (C) 1996-2018 Claudio Matsuoka and Hipolito Carraro Jr
   *
   * Permission is hereby granted, free of charge, to any person obtaining a
   * copy of this software and associated documentation files (the "Software"),
   * to deal in the Software without restriction, including without limitation
   * the rights to use, copy, modify, merge, publish, distribute, sublicense,
   * and/or sell copies of the Software, and to permit persons to whom the
   * Software is furnished to do so, subject to the following conditions:
   *
   * The above copyright notice and this permission notice shall be included in
   * all copies or substantial portions of the Software.
   *
   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
   * THE SOFTWARE.
   */
  
  /*
   * OctaMED v1.00b: ftp://ftp.funet.fi/pub/amiga/fish/501-600/ff579
   */
  
  #include "med.h"
  #include "loader.h"
  #include "med_extras.h"
  
  static int mmd1_test(HIO_HANDLE *, char *, const int);
  static int mmd1_load (struct module_data *, HIO_HANDLE *, const int);
  
  const struct format_loader libxmp_loader_mmd1 = {
  	"MED 2.10/OctaMED",
  	mmd1_test,
  	mmd1_load
  };
  
  static int mmd1_test(HIO_HANDLE *f, char *t, const int start)
  {
  	char id[4];
  	uint32 offset, len;
  
  	if (hio_read(id, 1, 4, f) < 4)
  		return -1;
  
  	if (memcmp(id, "MMD0", 4) && memcmp(id, "MMD1", 4))
  		return -1;
  
  	hio_seek(f, 28, SEEK_CUR);
  	offset = hio_read32b(f);		/* expdata_offset */
  	
  	if (offset) {
  		hio_seek(f, start + offset + 44, SEEK_SET);
  		offset = hio_read32b(f);
  		len = hio_read32b(f);
  		hio_seek(f, start + offset, SEEK_SET);
  		libxmp_read_title(f, t, len);
  	} else {
  		libxmp_read_title(f, t, 0);
  	}
  
  	return 0;
  }
  
  /* Number of octaves in IFFOCT samples */
  static const int num_oct[6] = { 5, 3, 2, 4, 6, 7 };
  
  static int mmd1_load(struct module_data *m, HIO_HANDLE *f, const int start)
  {
  	struct xmp_module *mod = &m->mod;
  	int i, j, k;
  	struct MMD0 header;
  	struct MMD0song song;
  	struct MMD1Block block;
  	struct InstrHdr instr;
  	struct SynthInstr synth;
  	struct InstrExt exp_smp;
  	struct MMD0exp expdata;
  	struct xmp_event *event;
  	int ver = 0;
  	int smp_idx = 0;
  	uint8 e[4];
  	int song_offset;
  	int blockarr_offset;
  	int smplarr_offset;
  	int expdata_offset;
  	int expsmp_offset;
  	int songname_offset;
  	int iinfo_offset;
  	int annotxt_offset;
  	int pos;
  	int bpm_on, bpmlen, med_8ch, hexvol;
  
  	LOAD_INIT();
  
  	hio_read(&header.id, 4, 1, f);
  
  	ver = *((char *)&header.id + 3) - '1' + 1;
  
  	D_(D_WARN "load header");
  	header.modlen = hio_read32b(f);
  	song_offset = hio_read32b(f);
  	D_(D_INFO "song_offset = 0x%08x", song_offset);
  	hio_read16b(f);
  	hio_read16b(f);
  	blockarr_offset = hio_read32b(f);
  	D_(D_INFO "blockarr_offset = 0x%08x", blockarr_offset);
  	hio_read32b(f);
  	smplarr_offset = hio_read32b(f);
  	D_(D_INFO "smplarr_offset = 0x%08x", smplarr_offset);
  	hio_read32b(f);
  	expdata_offset = hio_read32b(f);
  	D_(D_INFO "expdata_offset = 0x%08x", expdata_offset);
  	hio_read32b(f);
  	header.pstate = hio_read16b(f);
  	header.pblock = hio_read16b(f);
  	header.pline = hio_read16b(f);
  	header.pseqnum = hio_read16b(f);
  	header.actplayline = hio_read16b(f);
  	header.counter = hio_read8(f);
  	header.extra_songs = hio_read8(f);
  
  	/*
  	 * song structure
  	 */
  	D_(D_WARN "load song");
  	if (hio_seek(f, start + song_offset, SEEK_SET) != 0)
  	  return -1;
  	for (i = 0; i < 63; i++) {
  		song.sample[i].rep = hio_read16b(f);
  		song.sample[i].replen = hio_read16b(f);
  		song.sample[i].midich = hio_read8(f);
  		song.sample[i].midipreset = hio_read8(f);
  		song.sample[i].svol = hio_read8(f);
  		song.sample[i].strans = hio_read8s(f);
  	}
  	song.numblocks = hio_read16b(f);
  	song.songlen = hio_read16b(f);
  
  	/* Sanity check */
  	if (song.numblocks > 255 || song.songlen > 256) {
  		return -1;
  	}
  
  	D_(D_INFO "song.songlen = %d", song.songlen);
  	for (i = 0; i < 256; i++)
  		song.playseq[i] = hio_read8(f);
  	song.deftempo = hio_read16b(f);
  	song.playtransp = hio_read8(f);
  	song.flags = hio_read8(f);
  	song.flags2 = hio_read8(f);
  	song.tempo2 = hio_read8(f);
  	for (i = 0; i < 16; i++)
  		song.trkvol[i] = hio_read8(f);
  	song.mastervol = hio_read8(f);
  	song.numsamples = hio_read8(f);
  
  	/* Sanity check */
  	if (song.numsamples > 63) {
  		return -1;
  	}
  
  	/*
  	 * convert header
  	 */
  	m->c4rate = C4_NTSC_RATE;
  	m->quirk |= song.flags & FLAG_STSLIDE ? 0 : QUIRK_VSALL | QUIRK_PBALL;
  	hexvol = song.flags & FLAG_VOLHEX;
  	med_8ch = song.flags & FLAG_8CHANNEL;
  	bpm_on = song.flags2 & FLAG2_BPM;
  	bpmlen = 1 + (song.flags2 & FLAG2_BMASK);
  	m->time_factor = MED_TIME_FACTOR;
  
  	mmd_set_bpm(m, med_8ch, song.deftempo, bpm_on, bpmlen);
  
          mod->spd = song.tempo2;
  	mod->pat = song.numblocks;
  	mod->ins = song.numsamples;
  	mod->len = song.songlen;
  	mod->rst = 0;
  	mod->chn = 0;
  	memcpy(mod->xxo, song.playseq, mod->len);
  	mod->name[0] = 0;
  
  	/*
  	 * Obtain number of samples from each instrument
  	 */
  	mod->smp = 0;
  	for (i = 0; i < mod->ins; i++) {
  		uint32 smpl_offset;
  		int16 type;
  		if (hio_seek(f, start + smplarr_offset + i * 4, SEEK_SET) != 0)
  		  return -1;
  		smpl_offset = hio_read32b(f);
  		if (smpl_offset == 0)
  			continue;
  		if (hio_seek(f, start + smpl_offset, SEEK_SET) != 0)
  		  return -1;
  		hio_read32b(f);				/* length */
  		type = hio_read16b(f);
  		if (type == -1) {			/* type is synth? */
  			int wforms;
  			hio_seek(f, 14, SEEK_CUR);
  			wforms = hio_read16b(f);
  
  			/* Sanity check */
  			if (wforms > 256)
  				return -1;
  
  			mod->smp += wforms;
  		} else if (type >= 1 && type <= 6) {
  			mod->smp += num_oct[type - 1];
  		} else {
  			mod->smp++;
  		}
  	}
  
  	/*
  	 * expdata
  	 */
  	D_(D_WARN "load expdata");
  	expdata.s_ext_entries = 0;
  	expdata.s_ext_entrsz = 0;
  	expdata.i_ext_entries = 0;
  	expdata.i_ext_entrsz = 0;
  	expsmp_offset = 0;
  	iinfo_offset = 0;
  	if (expdata_offset) {
  		if (hio_seek(f, start + expdata_offset, SEEK_SET) != 0)
  		  return -1;
  		hio_read32b(f);
  		expsmp_offset = hio_read32b(f);
  		D_(D_INFO "expsmp_offset = 0x%08x", expsmp_offset);
  		expdata.s_ext_entries = hio_read16b(f);
  		expdata.s_ext_entrsz = hio_read16b(f);
  		annotxt_offset = hio_read32b(f);
  		expdata.annolen = hio_read32b(f);
  		iinfo_offset = hio_read32b(f);
  		D_(D_INFO "iinfo_offset = 0x%08x", iinfo_offset);
  		expdata.i_ext_entries = hio_read16b(f);
  		expdata.i_ext_entrsz = hio_read16b(f);
  
  		/* Sanity check */
  		if (expsmp_offset < 0 ||
  		    annotxt_offset < 0 ||
                      expdata.annolen > 0x10000 ||
  		    iinfo_offset < 0) {
  			return -1;
  		}
  
  		hio_read32b(f);
  		hio_read32b(f);
  		hio_read32b(f);
  		hio_read32b(f);
  		songname_offset = hio_read32b(f);
  		expdata.songnamelen = hio_read32b(f);
  
  		hio_seek(f, start + songname_offset, SEEK_SET);
  		for (i = 0; i < expdata.songnamelen; i++) {
  			if (i >= XMP_NAME_SIZE)
  				break;
  			mod->name[i] = hio_read8(f);
  		}
  
  		/* Read annotation */
  		if (annotxt_offset != 0 && expdata.annolen != 0) {
  			m->comment = malloc(expdata.annolen + 1);
  			if (m->comment != NULL) {
  				hio_seek(f, start + annotxt_offset, SEEK_SET);
  				hio_read(m->comment, 1, expdata.annolen, f);
  				m->comment[expdata.annolen] = 0;
  			}
  		}
  	}
  
  	/*
  	 * Quickly scan patterns to check the number of channels
  	 */
  	D_(D_WARN "find number of channels");
  
  	for (i = 0; i < mod->pat; i++) {
  		int block_offset;
  
  		if (hio_seek(f, start + blockarr_offset + i * 4, SEEK_SET) != 0)
  		  return -1;
  		block_offset = hio_read32b(f);
  		D_(D_INFO "block %d block_offset = 0x%08x", i, block_offset);
  		if (block_offset == 0)
  			continue;
  		if (hio_seek(f, start + block_offset, SEEK_SET) != 0)
  		  return -1;
  
  		if (ver > 0) {
  			block.numtracks = hio_read16b(f);
  			/* block.lines = */ hio_read16b(f);
  		} else {
  			block.numtracks = hio_read8(f);
  			/* block.lines = */ hio_read8(f);
  		}
  
  		if (block.numtracks > mod->chn) {
  			mod->chn = block.numtracks;
  		}
  	}
  
  	/* Sanity check */
  	if (mod->chn > XMP_MAX_CHANNELS) {
  		return -1;
  	}
  
  	mod->trk = mod->pat * mod->chn;
  
  	libxmp_set_type(m, ver == 0 ? mod->chn > 4 ? "OctaMED 2.00 MMD0" :
  				"MED 2.10 MMD0" : "OctaMED 4.00 MMD1");
  	
  	MODULE_INFO();
  
  	D_(D_INFO "BPM mode: %s (length = %d)", bpm_on ? "on" : "off", bpmlen);
  	D_(D_INFO "Song transpose: %d", song.playtransp);
  	D_(D_INFO "Stored patterns: %d", mod->pat);
  
  	/*
  	 * Read and convert patterns
  	 */
  	D_(D_WARN "read patterns");
  	if (libxmp_init_pattern(mod) < 0)
  		return -1;
  
  	for (i = 0; i < mod->pat; i++) {
  		int block_offset;
  
  		if (hio_seek(f, start + blockarr_offset + i * 4, SEEK_SET) != 0)
  		  return -1;
  		block_offset = hio_read32b(f);
  		if (block_offset == 0)
  			continue;
  		if (hio_seek(f, start + block_offset, SEEK_SET) != 0)
  		  return -1;
  
  		if (ver > 0) {
  			block.numtracks = hio_read16b(f);
  			block.lines = hio_read16b(f);
  			hio_read32b(f);
  		} else {
  			block.numtracks = hio_read8(f);
  			block.lines = hio_read8(f);
  		}
  
  		if (libxmp_alloc_pattern_tracks(mod, i, block.lines + 1) < 0)
  			return -1;
  
  		if (ver > 0) {		/* MMD1 */
  			for (j = 0; j < mod->xxp[i]->rows; j++) {
  				for (k = 0; k < block.numtracks; k++) {
  					e[0] = hio_read8(f);
  					e[1] = hio_read8(f);
  					e[2] = hio_read8(f);
  					e[3] = hio_read8(f);
  
  					event = &EVENT(i, k, j);
  					event->note = e[0] & 0x7f;
  					if (event->note)
  						event->note +=
  						    12 + song.playtransp;
  
  					if (event->note >= XMP_MAX_KEYS)
  						event->note = 0;
  
  					event->ins = e[1] & 0x3f;
  
  					/* Decay */
  					if (event->ins && !event->note) {
  						event->f2t = FX_MED_HOLD;
  					}
  
  					event->fxt = e[2];
  					event->fxp = e[3];
  					mmd_xlat_fx(event, bpm_on, bpmlen,
  							med_8ch, hexvol);
  				}
  			}
  		} else {		/* MMD0 */
  			for (j = 0; j < mod->xxp[i]->rows; j++) {
  				for (k = 0; k < block.numtracks; k++) {
  					e[0] = hio_read8(f);
  					e[1] = hio_read8(f);
  					e[2] = hio_read8(f);
  
  					event = &EVENT(i, k, j);
  					event->note = e[0] & 0x3f;
  					if (event->note)
  						event->note += 12;
  
  					if (event->note >= XMP_MAX_KEYS)
  						event->note = 0;
  
  					event->ins =
  					    (e[1] >> 4) | ((e[0] & 0x80) >> 3)
  					    | ((e[0] & 0x40) >> 1);
  
  					/* Decay */
  					if (event->ins && !event->note) {
  						event->f2t = FX_MED_HOLD;
  					}
  
  					event->fxt = e[1] & 0x0f;
  					event->fxp = e[2];
  					mmd_xlat_fx(event, bpm_on, bpmlen,
  							med_8ch, hexvol);
  				}
  			}
  		}
  	}
  
  	if (libxmp_med_new_module_extras(m))
  		return -1;
  
  	/*
  	 * Read and convert instruments and samples
  	 */
  	D_(D_WARN "read instruments");
  	if (libxmp_init_instrument(m) < 0)
  		return -1;
  
  	D_(D_INFO "Instruments: %d", mod->ins);
  
  	for (smp_idx = i = 0; i < mod->ins; i++) {
  		int smpl_offset;
  
  		if (hio_seek(f, start + smplarr_offset + i * 4, SEEK_SET) != 0)
  			return -1;
  		smpl_offset = hio_read32b(f);
  
  		D_(D_INFO "sample %d smpl_offset = 0x%08x", i, smpl_offset);
  
  		if (smpl_offset == 0) {
  			continue;
  		}
  		if (hio_seek(f, start + smpl_offset, SEEK_SET) < 0) {
  			return -1;
  		}
  		instr.length = hio_read32b(f);
  		instr.type = hio_read16b(f);
  
  		if ((pos = hio_tell(f)) < 0) {
  			return -1;
  		}
  
  		if (expdata_offset && i < expdata.i_ext_entries) {
  			struct xmp_instrument *xxi = &mod->xxi[i];
  			int offset = iinfo_offset + i * expdata.i_ext_entrsz;
  			
  			if (offset < 0 || hio_seek(f, offset, SEEK_SET) < 0) {
  				return -1;
  			}
  			hio_read(&xxi->name, 40, 1, f);
  		}
  
  		D_(D_INFO "[%2x] %-40.40s %d", i, mod->xxi[i].name, instr.type);
  
  		exp_smp.finetune = 0;
  		if (expdata_offset && i < expdata.s_ext_entries) {
  			int offset = expsmp_offset + i * expdata.s_ext_entrsz;
  
  			if (offset < 0 || hio_seek(f, offset, SEEK_SET) < 0) {
  				return -1;
  			}
  			exp_smp.hold = hio_read8(f);
  			exp_smp.decay = hio_read8(f);
  			exp_smp.suppress_midi_off = hio_read8(f);
  			exp_smp.finetune = hio_read8(f);
  		}
  
  		hio_seek(f, pos, SEEK_SET);
  
  		if (instr.type == -2) {			/* Hybrid */
  			int ret = mmd_load_hybrid_instrument(f, m, i, smp_idx,
  				&synth, &exp_smp, &song.sample[i]);
  
  			smp_idx++;
  
  			if (ret < 0)
  				return -1;
  
  			if (mmd_alloc_tables(m, i, &synth) != 0)
  				return -1;
  
  			continue;
  		} else if (instr.type == -1) {			/* Synthetic */
  			int ret = mmd_load_synth_instrument(f, m, i, smp_idx,
  				&synth, &exp_smp, &song.sample[i]);
  
  			if (ret > 0)
  				continue;
  
  			if (ret < 0)
  				return -1;
  
  			smp_idx += synth.wforms;
  
  			if (mmd_alloc_tables(m, i, &synth) != 0)
  				return -1;
  
  			continue;
  		} else if (instr.type >= 1 && instr.type <= 6) { /* IFFOCT */
  			int ret;
  			const int oct = num_oct[instr.type - 1];
  
  			hio_seek(f, start + smpl_offset + 6, SEEK_SET);
  
  			ret = mmd_load_iffoct_instrument(f, m, i, smp_idx,
  				&instr, oct, &exp_smp, &song.sample[i]);
  
  			if (ret < 0)
  				return -1;
  
  			smp_idx += oct;
  
  			continue;
  		} else if (instr.type == 0) {			/* Sample */
  			int ret;
  
  			hio_seek(f, start + smpl_offset + 6, SEEK_SET);
  
  			ret = mmd_load_sampled_instrument(f, m, i, smp_idx,
  				&instr, &expdata, &exp_smp, &song.sample[i],
  				ver);
  
  			if (ret < 0)
  				return -1;
  
  			smp_idx++;
  
  			continue;
  		} else {
  			/* Invalid instrument type */
  			return -1;
  		}
  	}
  
  	for (i = 0; i < mod->chn; i++) {
  		mod->xxc[i].vol = song.trkvol[i];
  		mod->xxc[i].pan = DEFPAN((((i + 1) / 2) % 2) * 0xff);
  	}
  
  	m->read_event_type = READ_EVENT_MED;
  
  	return 0;
  }
  