/*****************************************************************************

        GRAOUMF TRACKER 2
        Author: Laurent de Soras, 1996-2016

--- Legal stuff ---

This program is free software. It comes without any warranty, to
the extent permitted by applicable law. You can redistribute it
and/or modify it under the terms of the Do What The Fuck You Want
To Public License, Version 2, as published by Sam Hocevar. See
http://sam.zoy.org/wtfpl/COPYING for more details.

*Tab=3***********************************************************************/



#if defined (_MSC_VER)
	#pragma warning (1 : 4130 4223 4705 4706)
	#pragma warning (4 : 4355 4786 4800)
#endif



/*\\\ INCLUDE FILES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/

#include	"base.h"
#include	"gt_limit.h"
#include	"file.h"
#include	"inst.h"
#include	"log.h"
#include	"mod_mmd.h"
#include	"patt.h"
#include	"Player.h"
#include	"samp.h"
#include	"song.h"
#include	"WaveForm.h"

#include	<algorithm>
#include	<vector>

#include	<cassert>



/*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/



#define	MODMMD_ID_LEN				4
#define	MODMMD_MAX_SONG_LEN		256
#define	MODMMD_SCORE_TRANSPOSE	(3 * 12 - 1)

enum MODMMD_InstrType
{
	MODMMD_InstrType_HYBRID    = -2,
	MODMMD_InstrType_SYNTHETIC = -1,
	MODMMD_InstrType_SAMPLE    =  0, // an ordinary 1-octave sample
	MODMMD_InstrType_IFF5OCT,        // 5 octaves
	MODMMD_InstrType_IFF3OCT,        // 3 octaves
	// The following ones are recognized by OctaMED Pro only
	MODMMD_InstrType_IFF2OCT,        // 2 octaves
	MODMMD_InstrType_IFF4OCT,        // 4 octaves
	MODMMD_InstrType_IFF6OCT,        // 6 octaves
	MODMMD_InstrType_IFF7OCT         // 7 octaves
};

enum MODMMD_Flags
{
    MODMMD_Flags_FILTERON  = 0x01,  // the hardware audio filter is on
    MODMMD_Flags_JUMPINGON = 0x02,  // mouse pointer jumping on (not in OctaMED Pro)
    MODMMD_Flags_JUMP8TH   = 0x04,  // jump every 8th line (not in OctaMED Pro)
    MODMMD_Flags_INSTRSATT = 0x08,  // sng+samples indicator (not useful in MMD's)
    MODMMD_Flags_VOLHEX    = 0x10,  // volumes are HEX
    MODMMD_Flags_STSLIDE   = 0x20,  // use ST/NT/PT compatible sliding
    MODMMD_Flags_8CHANNEL  = 0x40   // this is OctaMED 5-8 channel song
};

enum MODMMD_Flags2
{
    MODMMD_Flags2_BMASK    = 0x1F,	// BPM beat length (in lines + 1). 0 = 1 line, $1F = 32 lines.
    MODMMD_Flags2_BPM      = 0x20   // BPM mode on
};



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

struct MODMMD_Song
{
	MODMMD_Sample  sample [63];      /* 63 * 8 bytes = 504 bytes */
	UWORD          numblocks;        /* offs: 504 */
	UWORD          songlen;          /* offs: 506 */
	UBYTE          playseq [MODMMD_MAX_SONG_LEN]; /* 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 */
}; /* length = 788 bytes */



struct MODMMD_BlockInfo
{
	SLWORD         hlmask;           // ULONG *
	SLWORD         blockname;	      // UBYTE *
	ULWORD         blocknamelen;
	ULWORD         reserved [6];
};

struct MODMMD_Block0
{
	UBYTE          numtracks;
	UBYTE          lines;
	UBYTE          data [1];
};

struct MODMMD_Block1
{
	UWORD          numtracks;
	UWORD          lines;
	SLWORD         info;             // MODMMD_BlockInfo *
	UBYTE          data [1];
};

union MODMMD_Block
{
	MODMMD_Block0  mmd0;
	MODMMD_Block1  mmd1;
};



struct MODMMD_SynthWF
{
	UWORD          length;           /* length in words */
	BYTE           wfdata [1];       /* the waveform */
};

struct MODMMD_SynthInstr
{
	ULWORD         length;           /* length of this struct */
	WORD           type;             /* -1 or -2 (offs: 4) */
	UBYTE          defaultdecay;
	UBYTE          reserved [3];
	UWORD          rep;
	UWORD          replen;
	UWORD          voltbllen;        /* offs: 14 */
	UWORD          wftbllen;         /* offs: 16 */
	UBYTE          volspeed;         /* offs: 18 */
	UBYTE          wfspeed;          /* offs: 19 */
	UWORD          wforms;           /* offs: 20 */
	UBYTE          voltbl [128];     /* offs: 22 */
	UBYTE          wftbl [128];      /* offs: 150 */
	SLWORD         wf [64];          /* offs: 278, SynthWF * */
};



struct MODMMD_InstrHdr
{
	ULWORD         length;
	SWORD          type;
	/* Followed by actual data */
	SBYTE          data [1];
};



struct MODMMD_InstrExt
{
	UBYTE          hold;
	UBYTE          decay;
	UBYTE          suppress_midi_off;
	SBYTE          finetune;
};

struct MODMMD_InstrInfo
{
	UBYTE          name [40];
};

struct MODMMD_NotationInfo
{
	UBYTE          n_of_sharps;
	UBYTE          flags;
	SWORD          trksel [5];
	UBYTE          trkshow [16];
	UBYTE          trkghost [16];
	SBYTE          notetr [63];
	UBYTE          pad;
};

struct MODMMD_DumpData
{
	UWORD          numdumps;
	UWORD          reserved [3];
};

struct MODMMD_Dump
{
	ULWORD         length;
	SLWORD         data;             // UBYTE *
	UWORD          ext_len;
	/* if ext_len >= 20: */
	UBYTE          name [20];
};

struct MODMMD_Exp
{
	SLWORD         nextmod;          // MODMMD_Header *
	SLWORD         exp_smp;          // MODMMD_InstrExt *
	UWORD          s_ext_entries;
	UWORD          s_ext_entrsz;
	SLWORD         annotxt;          // UBYTE *
	ULWORD         annolen;
	SLWORD         iinfo;            // MODMMD_InstrInfo *
	UWORD          i_ext_entries;
	UWORD          i_ext_entrsz;
	ULWORD         jumpmask;
	SLWORD         rgbtable;         // UWORD *
	UBYTE          channelsplit [4];
	SLWORD         n_info;           // MODMMD_NotationInfo *
	SLWORD         songname;         // UBYTE *
	ULWORD         songnamelen;
	SLWORD         dumps;            // MODMMD_DumpData *
	ULWORD         reserved2 [7];
};



struct MODMMD_Header
{
	ULWORD         id;
	ULWORD         modlen;
	SLWORD         song;             // MODMMD_Song *
	ULWORD         reserved0;
	SLWORD         blockarr;         // MODMMD_Block **
	ULWORD         reserved1;
	SLWORD         smplarr;          // MODMMD_InstrHdr **
	ULWORD         reserved2;
	SLWORD         expdata;          // MODMMD_Exp *
	ULWORD         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 */
};



void	MODMMD_read_instrument_spl (int instr, const MODMMD_InstrHdr &instrhdr, const MODMMD_Sample &sample, int &cur_spl);
void	MODMMD_read_instrument_multi (int instr, const MODMMD_InstrHdr &instrhdr, const MODMMD_Sample &sample, MODMMD_InstrType type, int &cur_spl);
double	MODMMD_conv_tempo_to_bpm (int deftempo, bool chn_8_flag, bool bpm_flag, int lines_per_beat);



template <class T>
static const T *	MODMMD_pointer (void *base_ptr, const SLWORD &offset_be)
{
	ptrdiff_t      ptr_int = reinterpret_cast <ptrdiff_t> (base_ptr);
	ptr_int += BASE_moto_lword (offset_be);
	const T *      ptr     = reinterpret_cast <const T *> (ptr_int);

	return (ptr);
}



/*\\\ FUNCTIONS \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/



/*==========================================================================*/
/*      Nom: MODMMD_save_module                                             */
/*      Description: Sauve un module en XM.                                 */
/*      Parametres en entree:                                               */
/*        - file_ptr: pointeur du fichier a sauver.                         */
/*      Retour: 0 si tout s'est bien passe.                                 */
/*==========================================================================*/

signed int	MODMMD_save_module (FILE *file_ptr)
{

	/*** A faire ***/

	return (0);
}



/*==========================================================================*/
/*      Nom: MODMMD_load_module                                             */
/*      Description: Charge un module OctaMED.                              */
/*      Parametres en entree:                                               */
/*        - file_ptr: pointeur du fichier a charger.                        */
/*      Retour: 0 si tout s'est bien passe.                                 */
/*==========================================================================*/

signed int	MODMMD_load_module (FILE *file_ptr, BYTE temp_info [MODS_TEMP_INFO_LEN])
{
	Player &			player = Player::use_instance ();

	// First, loads the module into memory
	if (FILE_fseek (file_ptr, 0, SEEK_END) != 0)
	{
		LOG_printf ("MODMMD_load_module: Error: couldn\'t read file (fseek).\n");
		return (-1);
	}
	const int64_t  file_len = FILE_ftell (file_ptr);	// Hopefully OctaMED files are below the 2-GB limit.
	if (file_len <= 0)
	{
		LOG_printf ("MODMMD_load_module: Error: couldn\'t read file (ftell).\n");
		return (-1);
	}
	if (FILE_fseek (file_ptr, 0, SEEK_SET) != 0)
	{
		LOG_printf ("MODMMD_load_module: Error: couldn\'t read file (fseek).\n");
		return (-1);
	}
	std::vector <BYTE>	filedata (static_cast <size_t> (file_len));
	assert (file_len > 0);
	UBYTE *        base_ptr = &filedata [0];
	if (fread (base_ptr, 1, size_t (file_len), file_ptr) != size_t (file_len))
	{
		LOG_printf ("MODMMD_load_module: Error: couldn\'t read file (fread).\n");
		return (-1);
	}

	// Header
	const MODMMD_Header *   head_ptr =
		reinterpret_cast <const MODMMD_Header *> (base_ptr);
	const MODMMD_Song *     song_ptr =
		MODMMD_pointer <MODMMD_Song> (base_ptr, head_ptr->song);
	const SLWORD *          blockarr_ptr =
		MODMMD_pointer <SLWORD> (base_ptr, head_ptr->blockarr);
	const SLWORD *          smplarr_ptr = 0;
	if (head_ptr->smplarr != 0)
	{
		smplarr_ptr = MODMMD_pointer <SLWORD> (base_ptr, head_ptr->smplarr);
	}
	const MODMMD_Exp *      expdata_ptr = 0;
	if (head_ptr->expdata != 0)
	{
		expdata_ptr = MODMMD_pointer <MODMMD_Exp> (base_ptr, head_ptr->expdata);
	}
	const char     mmd_type = char (BASE_moto_lword (head_ptr->id) & 0xFF);	// '0', '1'

	const int		nbr_blocks = BASE_moto_word (song_ptr->numblocks);	// Number of patterns
	const bool     vol_hex_flag = ((song_ptr->flags & MODMMD_Flags_VOLHEX  ) != 0);
	const bool     chn_8_flag   = ((song_ptr->flags & MODMMD_Flags_8CHANNEL) != 0);

	// Finds the total number of tracks and collects
	// the number of lines for each pattern
	int            nbr_tracks = 1;
	std::vector <int>	line_arr (nbr_blocks, 1);
	std::vector <int>	track_arr (nbr_blocks, 0);
	std::vector <const UBYTE *>	pdata_arr (nbr_blocks, static_cast <const UBYTE *> (0));
	for (int blk_cnt = 0; blk_cnt < nbr_blocks; ++blk_cnt)
	{
		if (blockarr_ptr [blk_cnt] != 0)
		{
			const MODMMD_Block *	block_ptr =
				MODMMD_pointer <MODMMD_Block> (base_ptr, blockarr_ptr [blk_cnt]);
			if (mmd_type == '0')
			{
				track_arr [blk_cnt] = block_ptr->mmd0.numtracks;
				line_arr [blk_cnt]  = block_ptr->mmd0.lines + 1;
				pdata_arr [blk_cnt] = block_ptr->mmd0.data;
			}
			else
			{
				track_arr [blk_cnt] = BASE_moto_word (block_ptr->mmd1.numtracks);
				line_arr [blk_cnt]  = BASE_moto_word (block_ptr->mmd1.lines) + 1;
				pdata_arr [blk_cnt] = block_ptr->mmd1.data;
			}
			nbr_tracks = std::max (nbr_tracks, track_arr [blk_cnt]);
		}
	}

	// Formats the patterns
	if (PAT_set_nbr_tracks (Pattern_TYPE_SPL, nbr_tracks))
	{
		LOG_printf (
			"MODMMD_load_module: Error: couldn't set number of tracks to %d.\n",
		   nbr_tracks
		);
		return (-1);
	}
	for (int blk_cnt = 0; blk_cnt < nbr_blocks; ++blk_cnt)
	{
		const int		nbr_lines = line_arr [blk_cnt];
		if (PAT_set_pattern_height (blk_cnt, nbr_lines))
		{
			LOG_printf (
				"MODMMD_load_module: Error: couldn't set number of lines of "
				"pattern # %d to %d.\n",
			   blk_cnt,
				nbr_lines
			);
			return (-1);
		}
	}

	// Tempo
	const int      speed = song_ptr->tempo2;
	if (speed > 0)
	{
		player.set_speed (speed);
	}

	const bool     bpm_flag = ((song_ptr->flags2 & MODMMD_Flags2_BPM) != 0);
	int            lines_per_beat = 4;
	if (bpm_flag && ! chn_8_flag)
	{
		lines_per_beat = (song_ptr->flags2 & MODMMD_Flags2_BMASK) + 1;
	}

	int            deftempo = BASE_moto_word (song_ptr->deftempo);
	if (deftempo > 0)
	{
		const double   tempo = MODMMD_conv_tempo_to_bpm (
			deftempo, chn_8_flag, bpm_flag, lines_per_beat
		);
		player.set_tempo (tempo);
	}

	// Instruments
	const int      nbr_instr = song_ptr->numsamples;
	int            cur_spl   = 1;
	for (int instr_cnt = 0; instr_cnt < nbr_instr; ++instr_cnt)
	{
		const int		gt_instr = instr_cnt + 1;
		if (smplarr_ptr [instr_cnt] != 0)
		{
			const MODMMD_InstrHdr * instrhdr_ptr =
				MODMMD_pointer <MODMMD_InstrHdr> (base_ptr, smplarr_ptr [instr_cnt]);
			const MODMMD_Sample &   sample = song_ptr->sample [instr_cnt];
			const MODMMD_InstrType  type =
				static_cast <MODMMD_InstrType> (static_cast <SWORD> (
					BASE_moto_word (instrhdr_ptr->type)
				));

			switch (type)
			{
			case MODMMD_InstrType_HYBRID:
				{
					// We only get the sampled part here
					const MODMMD_SynthInstr &  instr_synth =
						*reinterpret_cast <const MODMMD_SynthInstr *> (instrhdr_ptr);
					const MODMMD_Sample &   sample_synth =
						*reinterpret_cast <const MODMMD_Sample *> (&instr_synth.rep);
					instrhdr_ptr = MODMMD_pointer <MODMMD_InstrHdr> (
						(void *) instrhdr_ptr, instr_synth.wf [0]
					);
					const MODMMD_InstrType  type_synth =
						static_cast <MODMMD_InstrType> (BASE_moto_word (instrhdr_ptr->type)
					);
					if (type_synth == MODMMD_InstrType_SAMPLE)
					{
						MODMMD_read_instrument_spl (
							gt_instr, *instrhdr_ptr, sample_synth, cur_spl
						);
					}
					else
					{
						MODMMD_read_instrument_multi (
							gt_instr, *instrhdr_ptr, sample_synth, type_synth, cur_spl
						);
					}

					/*** To do ***/

				}
				break;

			case MODMMD_InstrType_SYNTHETIC:
				// Keep only the first waveform and use it as a regular sample
				{
					const MODMMD_SynthInstr &  instr_synth =
						*reinterpret_cast <const MODMMD_SynthInstr *> (instrhdr_ptr);
					const MODMMD_SynthWF *  wf_ptr = MODMMD_pointer <MODMMD_SynthWF> (
						(void *) instrhdr_ptr,
						instr_synth.wf [0]
					);
					const int      spl_len = BASE_moto_word (wf_ptr->length) * 2;
					const int      transp  = sample.strans - 24;
					SAMP_set_sample_resolution (cur_spl, 8);
					SAMP_set_sample_stereo (cur_spl, 1);
					SAMP_set_sample_freq (cur_spl, 8363);
					SAMP_set_sample_length (cur_spl, spl_len);
					SAMP_set_sample_loop (cur_spl, WaveForm_LOOP_TYPE_FWD, 0, spl_len);
					SAMP_set_sample_volume (cur_spl, 0x100);
					SAMP_set_sample_finetune (cur_spl, 0);
					SAMP_set_sample_balance (cur_spl, -1);
					memcpy (
						SAMP_get_sample_data_adr (cur_spl),
						wf_ptr->wfdata,
						spl_len
					);
					SAMP_make_buffers (cur_spl);
					for (int note = 0; note < GTK_NBRNOTES_MAXI; ++note)
					{
						INST_set_instr_sample (gt_instr, note, cur_spl);
						INST_set_instr_transp (gt_instr, note, transp);
					}
					++ cur_spl;
				}
				break;

			case MODMMD_InstrType_SAMPLE:
				MODMMD_read_instrument_spl (
					gt_instr, *instrhdr_ptr, sample, cur_spl
				);
				break;

			case MODMMD_InstrType_IFF5OCT:
			case MODMMD_InstrType_IFF3OCT:
			case MODMMD_InstrType_IFF2OCT:
			case MODMMD_InstrType_IFF4OCT:
			case MODMMD_InstrType_IFF6OCT:
			case MODMMD_InstrType_IFF7OCT:
				MODMMD_read_instrument_multi (
					gt_instr, *instrhdr_ptr, sample, type, cur_spl
				);
				break;

			default:
				assert (false);
				break;
			}
		}
	}

	// Pattern data
	for (int blk_cnt = 0; blk_cnt < nbr_blocks; ++blk_cnt)
	{
		if (blockarr_ptr [blk_cnt] != 0)
		{
			const int		nbr_tracks_pat = track_arr [blk_cnt];
			const int      nbr_lines      = line_arr [blk_cnt];
			const UBYTE *  data_ptr       = pdata_arr [blk_cnt];

			for (int line_cnt = 0; line_cnt < nbr_lines; ++line_cnt)
			{
				for (int trk_cnt = 0; trk_cnt < nbr_tracks_pat; ++trk_cnt)
				{
					const int      offset_note = line_cnt * nbr_tracks_pat + trk_cnt;

					// Reads data
					int            note  = 0;
					int            instr = 0;
					int            cmd   = 0;
					int            param = 0;
					if (mmd_type == '0')
					{
						const int      offset = offset_note * 3;
						note  = data_ptr [offset    ] & 0x3F;
						instr =
							  ((data_ptr [offset    ]) & 0xC0 >> 2)
							+ ( data_ptr [offset + 1]         >> 4);
						cmd   = data_ptr [offset + 1] & 0x0F;
						param = data_ptr [offset + 2];
					}
					else
					{
						const int      offset = offset_note * 4;
						note  = data_ptr [offset    ] & 0x7F;
						instr = data_ptr [offset + 1] & 0x3F;
						cmd   = data_ptr [offset + 2];
						param = data_ptr [offset + 3];
					}

					// Converts
					if (note > 0)
					{
						note += MODMMD_SCORE_TRANSPOSE;
					}

					int            cmd_gt = 0;
					int            par_gt = 0;
					int            volume = 0;

					switch (cmd)
					{
					case	0x03:
					case	0x07:
					case	0x0B:
						// These effects are unchanged
						cmd_gt = cmd;
						par_gt = param;
						break;
					case	0x00:
						// Arpeggio
						if (param != 0)
						{
							cmd_gt = 0x10;
							par_gt = param;
						}
						break;
					case	0x01:
						// Portamento up
						if (param != 0)
						{
							cmd_gt = 0x01;
							par_gt = param;
						}
						break;
					case	0x02:
						// Portamento down
						if (param != 0)
						{
							cmd_gt = 0x02;
							par_gt = param;
						}
						break;
					case	0x04:	
						cmd_gt = cmd;
						par_gt = (param & 0xF0) + std::min ((param & 0xF) << 1, 0xF);
						break;
					case	0x05:
						// Volume slide + portamento
						{
							const int		volslide = ((param >> 4) - (param & 0xF)) * 4;
							if (volslide < 0)
							{
								cmd_gt = 0x19;
							}
							else
							{
								cmd_gt = 0x18;
							}
							par_gt = std::abs (volslide);
						}
						break;
					case	0x06:
						// Volume slide + vibrato
						{
							const int		volslide = ((param >> 4) - (param & 0xF)) * 4;
							if (volslide < 0)
							{
								cmd_gt = 0x1D;
							}
							else
							{
								cmd_gt = 0x1C;
							}
							par_gt = std::abs (volslide);
						}
						break;
					case	0x09:
						// Set number of frames
						cmd_gt = 0xA8;
						par_gt = param;
						break;
					case	0x0A:
					case	0x0D:
						// Volume slide
						{
							const int		volslide = ((param >> 4) - (param & 0xF)) * 4;
							if (volslide < 0)
							{
								cmd_gt = 0x15;
							}
							else
							{
								cmd_gt = 0x14;
							}
							par_gt = std::abs (volslide);
						}
						break;
					case	0x0C:
						// Volume
// It seems that modules with hex volume flag disabled are
// still using hex volumes, not decimal. So the flag is actually
// ignored
#if 0
						if (vol_hex_flag)
#else
						if (true)
#endif
						{
							volume = param;
						}
						else
						{
							volume = (param >> 4) * 10 + (param & 0xF);
						}
						volume = std::min (volume, 0x40) + 0x10;
						break;
					case	0x0F:
						if (param == 0)
						{
							// Pattern break
							cmd_gt = 0x0D;
							par_gt = 0;
						}
						else if (param <= 240)
						{
							// Tempo
							cmd_gt = 0x0F;
							const double   bpm = MODMMD_conv_tempo_to_bpm (
								param,
								chn_8_flag,
								bpm_flag,
								lines_per_beat
							);
							par_gt = int (bpm + 0.5);
						}
						else
						{
							switch (param)
							{
							case	0xF1:
								// Plays two notes in a line
								/*** To do: change parameter depending on the actual speed ***/
								cmd_gt = 0x73;
								par_gt = 0x02;
								break;
							case	0xF2:
								// Delays the note from 1/2 line
								/*** To do: change parameter depending on the actual speed ***/
								cmd_gt = 0x09;
								par_gt = 0x03;
								break;
							case	0xF3:
								// Plays three notes in a line
								/*** To do: change parameter depending on the actual speed ***/
								cmd_gt = 0x72;
								par_gt = 0x03;
								break;
							case	0xFD:
								// Changes the pitch without playing the note
								cmd_gt = 0x03;
								par_gt = 0xFF;
								break;
							case	0xFF:
								// Stops the note
								cmd_gt = 0x0A;
								par_gt = 0x00;
								break;
							default:
								// Nothing
								break;
							}
						}
						break;
					case	0x11:
						// Fine portamento up
						cmd_gt = 0x11;
						par_gt = param;
						break;
					case	0x12:
						// Fine portamento down
						cmd_gt = 0x12;
						par_gt = param;
						break;
					case	0x15:
						// Set finetune
						{
							const int      sparam = static_cast <SBYTE> (param);
							if (sparam >= -8 && sparam <= 7)
							{
								cmd_gt = 0x08;
								par_gt = (sparam < 0) ? -sparam : (sparam << 4);
							}
						}
						break;
					case	0x18:
						// Not cut
						cmd_gt = 0x0A;
						par_gt = param;
						break;
					case	0x19:
						// Set sample start offset
						cmd_gt = 0x90;
						par_gt = param;
						break;
					case	0x1A:
						// Fine volume up
						cmd_gt = 0xA4;
						par_gt = param * 4;
						break;
					case	0x1B:
						// Fine volume down
						cmd_gt = 0xA5;
						par_gt = param * 4;
						break;
					case	0x1D:
						// Jump to next block
						cmd_gt = 0x0D;
						par_gt = param;
						break;
					case	0x1E:
						// Play line x times
						cmd_gt = 0xAA;
						par_gt = param;
						break;
					case	0x1F:
						// Combined note delay-retrigger
						{
							const int      delay  = param >> 4;
							const int      retrig = param & 0x0F;
							if (delay > 0)
							{
								cmd_gt = 0x0A;
								par_gt = delay;
							}
							else
							{
								cmd_gt = 0x70 + retrig;
								par_gt = 0;
							}
						}
						break;
					default:
						// Nothing
						break;
					}

					// Writes
					MODS_GT2_SPL_NOTE	*  note_ptr =
						PAT_get_spl_note_adr_pat (blk_cnt, line_cnt, trk_cnt);

					note_ptr->note    = note;
					note_ptr->instr   = instr;
					note_ptr->fxnum   = cmd_gt;
					note_ptr->fxparam = par_gt;
					note_ptr->volume  = volume;
				}
			}
		}
	}

	// Song data
	SONG_set_song_length (MODMMD_MAX_SONG_LEN);
	for (int pos = 0; pos < MODMMD_MAX_SONG_LEN; ++pos)
	{
		const int      pat = song_ptr->playseq [pos];
		SONG_set_pattern_number (pos, pat);
	}
	const int      song_len = BASE_moto_word (song_ptr->songlen);
	SONG_set_song_length (song_len);
	SONG_set_song_repeat (0);

	// Volumes
	for (int trk_cnt = 0; trk_cnt < nbr_tracks; ++trk_cnt)
	{
		const int      trk_vol = song_ptr->trkvol [trk_cnt] << 6;
		player.set_track_lin_volume (Pattern_TYPE_SPL, trk_cnt, false, trk_vol);

		const int      pan = 0x4000 + 0x4000 * ((trk_cnt + 1) & 2);
		player.set_track_panning (Pattern_TYPE_SPL, trk_cnt, false, pan);
	}
	const int      master_vol = (song_ptr->mastervol << 6);
	player.set_lin_master_volume (master_vol);

	// Expansion data
	if (expdata_ptr != 0)
	{
		// Instrument finetune
		const UBYTE *     instr_ext_ptr = 0;
		if (expdata_ptr->exp_smp != 0)
		{
			instr_ext_ptr = MODMMD_pointer <UBYTE> (base_ptr, expdata_ptr->exp_smp);
			int            nbr_instr_ext = BASE_moto_word (expdata_ptr->s_ext_entries);
			const int      instr_ext_sz  = BASE_moto_word (expdata_ptr->s_ext_entrsz);
			nbr_instr_ext = std::min (nbr_instr_ext, nbr_instr);
			for (int instr_cnt = 0; instr_cnt < nbr_instr_ext; ++ instr_cnt)
			{
				const int		gt_instr = instr_cnt + 1;
				const MODMMD_InstrExt & ext =
					*reinterpret_cast <const MODMMD_InstrExt *> (
						instr_ext_ptr + instr_cnt * instr_ext_sz
					);
				const int      finetune = ext.finetune;

				// Applies the finetune to all samples of the instrument
				std::set <int> sample_set;
				INST_find_samples_for_inst (gt_instr, sample_set);
				for (std::set <int>::const_iterator it = sample_set.begin ()
				;	it != sample_set.end ()
				;	++ it)
				{
					SAMP_set_sample_finetune (*it, finetune);
				}
			}
		}

		// Instrument names
		const UBYTE *     instr_info_ptr = 0;
		if (expdata_ptr->iinfo != 0)
		{
			instr_info_ptr = MODMMD_pointer <UBYTE> (base_ptr, expdata_ptr->iinfo);
			int            nbr_instr_inf = BASE_moto_word (expdata_ptr->i_ext_entries);
			const int      instr_inf_sz  = BASE_moto_word (expdata_ptr->i_ext_entrsz);
			nbr_instr_inf = std::min (nbr_instr_inf, nbr_instr);
			for (int instr_cnt = 0; instr_cnt < nbr_instr_inf; ++ instr_cnt)
			{
				const int		gt_instr = instr_cnt + 1;
				const MODMMD_InstrInfo &   inf =
					*reinterpret_cast <const MODMMD_InstrInfo *> (
						instr_info_ptr + instr_cnt * instr_inf_sz
					);
				const char *   instr_name_0 =
					reinterpret_cast <const char *> (inf.name);
				INST_set_instr_name (gt_instr, instr_name_0);

				// Set the name to all samples of the instrument
				std::set <int> sample_set;
				INST_find_samples_for_inst (gt_instr, sample_set);
				const size_t   nbr_spl  = sample_set.size ();
				if (nbr_spl == 1)
				{
					SAMP_set_sample_name (*sample_set.begin (), instr_name_0);
				}
				else
				{
					int           spl_index = 0;
					const int     nbr_digits = (nbr_spl < 10) ? 1 : 2;
					const int     name_len = int (strlen (instr_name_0));
					const int     trunc_len = std::min (name_len, Sample_NAME_LEN - 3 - nbr_digits);  // 3 = space + parenthesis + (digits) + parenthesis
					char          instr_name_0 [Sample_NAME_LEN + 128 + 1];
					BASE_copy_string (instr_name_0, instr_name_0, trunc_len, trunc_len);
					for (std::set <int>::const_iterator it = sample_set.begin ()
					;	it != sample_set.end ()
					;	++ it)
					{

						sprintf (instr_name_0 + trunc_len, " (%d)", spl_index);
						SAMP_set_sample_name (*it, instr_name_0);
						++ spl_index;
					}
				}
			}
		}

		// Songname
		if (expdata_ptr->songname != 0)
		{
			const char *   songname_0 =
				MODMMD_pointer <char> (base_ptr, expdata_ptr->songname);
			const int      songname_len = BASE_moto_lword (expdata_ptr->songnamelen);
			if (songname_len > 0)
			{
				SONG_set_song_name (songname_0);
			}
		}
	}

	return (0);
}



/*==========================================================================*/
/*      Nom: MODMMD_detect_format                                           */
/*      Description: Teste si un module est au format OctaMED.              */
/*      Parametres en entree:                                               */
/*        - header_ptr: les premiers octets du module.                      */
/*        - header_length: taille du header sus-cite.                       */
/*      Parametres en entree/sortie:                                        */
/*        - file_ptr: pointeur sur le fichier du module. Il n'y a pas       */
/*                    besoin de sauver la position.                         */
/*      Retour: true si le module est un XM.                                */
/*==========================================================================*/

bool	MODMMD_detect_format (FILE *file_ptr, const void *header_ptr, long header_length, BYTE temp_info [MODS_TEMP_INFO_LEN])
{
	char           id_0 [MODMMD_ID_LEN+1];

	memcpy (id_0, header_ptr, MODMMD_ID_LEN);
	id_0 [MODMMD_ID_LEN] = '\0';
	if (   strcmp (id_0, "MMD0") == 0
	    || strcmp (id_0, "MMD1") == 0)
	{
		return (true);
	}

	return (false);
}



/*\\\ PRIVATE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/



// instr = GT index (1-based)
void	MODMMD_read_instrument_spl (int instr, const MODMMD_InstrHdr &instrhdr, const MODMMD_Sample &sample, int &cur_spl)
{
	assert (instr >= 0);
	assert (&instrhdr != 0);
	assert (&sample != 0);

	const int      spl_base = cur_spl;
	const int      vol = sample.svol * 4;
	const int      transp = sample.strans;
	const SBYTE *  data_ptr = instrhdr.data;

	const int      repeat = BASE_moto_word (sample.rep)    << 1;
	const int      replen = BASE_moto_word (sample.replen) << 1;
	const int      spl_len = BASE_moto_lword (instrhdr.length);
	SAMP_set_sample_resolution (cur_spl, 8);
	SAMP_set_sample_stereo (cur_spl, 1);
	SAMP_set_sample_freq (cur_spl, 8363);
	SAMP_set_sample_length (cur_spl, spl_len);
	SAMP_set_sample_loop (
		cur_spl,
		(replen > 2) ? WaveForm_LOOP_TYPE_FWD : WaveForm_LOOP_TYPE_NONE,
		repeat,
		replen
	);
	SAMP_set_sample_volume (cur_spl, 0x100);
	SAMP_set_sample_finetune (cur_spl, 0);
	SAMP_set_sample_balance (cur_spl, -1);
	memcpy (SAMP_get_sample_data_adr (cur_spl), data_ptr, spl_len);
	SAMP_make_buffers (cur_spl);

	data_ptr += spl_len;
	++ cur_spl;

	for (int note = 0; note < GTK_NBRNOTES_MAXI; ++ note)
	{
		const int      oct = note / 12;
		const int      transp_lim =
			((std::min (std::max (oct, 3), 5)) - oct) * 12;
		int				transp_final = transp_lim + transp;

		INST_set_instr_sample (instr, note, spl_base);
		INST_set_instr_transp (instr, note, transp_final);
	}
	INST_set_instr_volume (instr, vol);
}



// instr = GT index (1-based)
void	MODMMD_read_instrument_multi (int instr, const MODMMD_InstrHdr &instrhdr, const MODMMD_Sample &sample, MODMMD_InstrType type, int &cur_spl)
{
	assert (instr >= 0);
	assert (&instrhdr != 0);
	assert (&sample != 0);
	assert (type > MODMMD_InstrType_SAMPLE);

	static const int  octave_table [] = { 1, 5, 3, 2, 4, 6, 7 };
	static const int  sample_table [6] [7] =	// 2-7
	{
		{ 1, 0, 0, 0, 0, 0, 0 },
		{ 2, 2, 2, 2, 1, 1, 0 },
		{ 3, 2, 2, 2, 1, 1, 0 },
		{ 4, 3, 2, 2, 1, 1, 0 },
		{ 5, 5, 4, 3, 2, 1, 0 },
		{ 6, 6, 5, 4, 3, 2, 1 }
	};
	static const int  transp_table [6] [7] =
	{
		{ 12,  0,  0,   0,   0,   0,   0 },
		{ 12, 12, 12,  12,   0,   0, -12 },
		{ 12,  0,  0,   0, -12, -12, -24 },
		{ 24, 12,  0,   0, -12, -24, -36 },
		{ 12, 12,  0, -12, -24, -36, -48 },
		{ 12, 12,  0, -12, -24, -36, -48 }
	};

	const int      spl_base = cur_spl;
	const int      nbr_oct = octave_table [type];
	const int      vol = sample.svol * 4;
	const int      transp = sample.strans - 12;
	const SBYTE *  data_ptr = instrhdr.data;
	const int      len_tot  = BASE_moto_lword (instrhdr.length);
	const int      len_base = len_tot / ((1 << nbr_oct) - 1);
	const int      repeat_base = BASE_moto_word (sample.rep)    << 1;
	const int      replen_base = BASE_moto_word (sample.replen) << 1;
	const int      loop_type =
		  (replen_base > 2)
		? WaveForm_LOOP_TYPE_FWD
		: WaveForm_LOOP_TYPE_NONE;
	for (int oct_cnt = 0; oct_cnt < nbr_oct; ++oct_cnt)
	{
		const int      repeat = repeat_base << oct_cnt;
		const int      replen = replen_base << oct_cnt;
		const int      spl_len = len_base << oct_cnt;
		SAMP_set_sample_resolution (cur_spl, 8);
		SAMP_set_sample_stereo (cur_spl, 1);
		SAMP_set_sample_freq (cur_spl, 8363);
		SAMP_set_sample_length (cur_spl, spl_len);
		SAMP_set_sample_loop (cur_spl, loop_type, repeat, replen);
		SAMP_set_sample_volume (cur_spl, 0x100);
		SAMP_set_sample_finetune (cur_spl, 0);
		SAMP_set_sample_balance (cur_spl, -1);
		memcpy (SAMP_get_sample_data_adr (cur_spl), data_ptr, spl_len);
		SAMP_make_buffers (cur_spl);

		data_ptr += spl_len;
		++ cur_spl;
	}

	for (int note = 0; note < GTK_NBRNOTES_MAXI; ++ note)
	{
		const int      oct       = note / 12;
		const int      oct_index = std::min (std::max (oct - 4, 0), 6);
		const int      spl       = sample_table [nbr_oct - 2] [oct_index];
		const int      transp_s  = transp_table [nbr_oct - 2] [oct_index];
		const int      transp_final = transp_s + transp;

		INST_set_instr_sample (instr, note, spl_base + spl);
		INST_set_instr_transp (instr, note, transp_final);
	}
	INST_set_instr_volume (instr, vol);
}



double	MODMMD_conv_tempo_to_bpm (int deftempo, bool chn_8_flag, bool bpm_flag, int lines_per_beat)
{
	assert (deftempo > 0);

	int            divisor = deftempo;
	if (chn_8_flag)
	{
		const int      dtclip = std::min (deftempo, 10);
		static const int  tempo_table [] =
		{
			47, 43, 40, 37, 35, 32, 30, 29, 27, 26
		};
		divisor = tempo_table [dtclip - 1];
	}

	int            ticks_timer_cia;
	if (! chn_8_flag && deftempo <= 10)
	{
		static const int  timer_table [] =
		{
			2417,4833,7250,9666,12083,14500,16916,19332,21436,24163
		};
		ticks_timer_cia = timer_table [deftempo - 1];
	}
	else
	{
		ticks_timer_cia = 470000 / divisor;
	}

	const double   ticks_per_s = 709379.0 / ticks_timer_cia;
	const int      ticks_per_beat = 6 * lines_per_beat;
	const double   beat_duration = ticks_per_beat / ticks_per_s;
	const double   tempo = 60 / beat_duration;

	return (tempo);
}



/*\\\ EOF \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/
