#include "Song.h"
#include "Effects.h"
#include "Instrument.h"
#include "Loaders.h"
#include "FSysLog.h"

/*
 * Channels: 4, 6, 8 or [1...32]
 * Patterns: [1...255]
 * Rows    : 64
 * Orders  : [1...128]
 * Samples : 15, 31, 63
 * Instr.  : 0
 * Tempo   : [32...255], T frames per 2.5 seconds.
 * Speed   : [1...31], S frames per row.
 * Notes   : [C-0...C-1...B-2] or [C-0...C-2...B-4]
 * Pitch   : Amiga periods mode, base is 428 units, period is divided by 2 each octave up.
 *           Slides as x/1 units per frame.
 * Volume  : [0...64] , linear.
 * Panning : None or [0...F].
 * Sampling: 8287 (PAL) or 8363 Hz (NTSC).
 *
 * Sampling on Amiga depends on PAL/NTSC, so 2 sample rates are possible. Which one to choose?
 * When playing samples that were split into several samples to work around the sample size
 * limit, if we use NTSC for a PAL encoded song small gaps are audible between each fragment
 * so we will use PAL except for FastTracker which is a PC tracker that mimicked Amiga NTSC.
 *
 * The initial format used 15 instruments, but other trackers switched to 31 and added special tags
 * somewhere in the file to mark this. Most documentation is for the 31 instruments variant
 * an fails to note differences for 15 instruments format:
 * - effect F is always speed (VBlank MODE), tempo is defined globally where 31 instruments
 *   variant stores restart position.
 * - sample offset is expressed in words not bytes.
 *
 * Compatibility flags:
 * -------------------
 * The very first tracker "Ultimate SoundTracker", used a different set of effects.
 * We cannot detect this so we need to be told to use that mode, thought we will ignore
 * that flag we are sure it is not possible (like using the 31 instruments format).
 *
 * Songs converted from 15 to 31 instruments may also suffer from the difference of behaviour
 * of effect F with values >= 32 so there is also a VBlank compatibility flag.
 *
 * Many trackers used this format and behaved differently in corner cases.
 * The most noticable is sample swapping (see below) so we have compatibility flag
 * to disbable sample swapping.
 *
 * Note triggering & sample substitution:
 * -------------------------------------
 *
 * A new note nr with an instrument nr starts a note with the specified pitch and instrument.
 *
 * A new note nr without an instrument nr starts a note with the last used instrument nr.
 *
 * An instrument nr given without a note nr:
 *   - sets default volume from new sample.
 *   - resets volume/pitch vibrato position ???
 *   - resets finetune from new sample ???
 *   Standard behaviour:
 *   - while a note is playing, substitutes the sample used by the note without reseting
 *     any parameter (position, loop, ...).
 *   Non-standard behaviour (compatibility settings):
 *   - do not swap samples.
 *
 * Unstored samples are treated as empty samples with default volume 0.
 *
 * Note triggering does not apply when portamento effect is present.
 *
 * Retrig E9x, retrig occurs at tick 0, x + 1, 2*x + 1, ... of current row.
 *   x = 0 is memory.
 *
 * Effects:
 * -------
 *
 * Effects have no memory except for portamento, vibrato and tremolo.
 * This means that effect value 0 as usually no effect.
 *
 * Volume slide Axy, x has priority so A0y is slide down by y and the rest is slide up by x.
 */

#define NoteDelta (Note_Central-12*2)

static const int MOD_Period_Table[12*6] =
{1712,1616,1525,1440,1357,1281,1209,1141,1077,1017, 961, 907
, 856, 808, 762, 720, 678, 640, 604, 570, 538, 508, 480, 453
, 428, 404, 381, 360, 339, 320, 302, 285, 269, 254, 240, 226
, 214, 202, 190, 180, 170, 160, 151, 143, 135, 127, 120, 113
, 107, 101,  95,  90,  85,  80,  75,  71,  67,  63,  60,  56
,  53,  50,  47,  45,  42,  40,  37,  35,  33,  31,  30,  20
};

static const uint8_t Tag_CD61[] = "CD61";
static const uint8_t Tag_CD81[] = "CD81";
static const uint8_t Tag_CHN[]  = "\0CHN";
static const uint8_t Tag_CH[]   = "\0\0CH";
static const uint8_t Tag_FA[]   = "FA\0\0";
static const uint8_t Tag_FLT[]  = "FLT\0";
static const uint8_t Tag_IT10[] = "IT10";
static const uint8_t Tag_MK[]   = "M.K.";
static const uint8_t Tag_MK_[]  = "M&K!";
static const uint8_t Tag_M_K_[] = "M!K!";
static const uint8_t Tag_MTN[]  = "MTN\0";
static const uint8_t Tag_NT[]   = "N.T.";
static const uint8_t Tag_NSMS[] = "NSMS";
static const uint8_t Tag_OKTA[] = "OKTA";
static const uint8_t Tag_TDZ[]  = "TDZ\0";
static const uint8_t Tag_TXP[]  = "TXP\0";

static const uint8_t Typestr_STK[] = "Ultimate SoundTracker";
static const uint8_t Typestr_15inst[] = "SoundTracker";
static const uint8_t Typestr_CDx1[] = "Octalyser";
static const uint8_t Typestr_CHN[] = "FastTracker";
static const uint8_t Typestr_FA[] = "DigitalTracker";
static const uint8_t Typestr_FLT[] = "StarTrekker";
static const uint8_t Typestr_IT10[] = "IceTracker";
static const uint8_t Typestr_MK[] = "ProTracker";
static const uint8_t Typestr_MTN[] = "Soundtracker 2.6";
static const uint8_t Typestr_NSMS[] = "SoundTracker31";
static const uint8_t Typestr_OKTA[] = "Oktalyzer";
static const uint8_t Typestr_TDZ[] = "TakeTracker";
static const uint8_t Typestr_TXP[] = "The Xperience";

#define TAG(x) (*(const uint32_t*) Tag_##x)

// Ultimate Soundtracker, AKA the very first tracker (by Karsten Obarsky)
static int convert_effect_STK(SongHdr* pSong, uint32_t effect, uint32_t value)
{
	switch(effect)
	{
		case 0:
		{
			if (value) return 0; // Not an STK
		}
		break;
		case 1: // Pitch: Arpeggio, No memory
		{
			if (value)
			{
				uint32_t val;

				val = value >> 4;
				value &= 0xf;
				Pattern_AddEffect_Arpeggio(pSong, value, val, cmd_arpeggio_type_NHL);
			}
		}
		break;
		case 2: // Pitch: Slide up/down, No memory
		{
			if (value >> 4) Pattern_AddEffect_Pitch(pSong, cmd_down+flag_cmd_slide_frameN0, (value & 0xf0) >> 2);
			else if (value) Pattern_AddEffect_Pitch(pSong, cmd_up+flag_cmd_slide_frameN0, value << 2);
		}
		break;
		default:
			return 0; // Is not STK
	}

	return 1;
}

void convert_effect_MOD(SongHdr* pSong, uint32_t channel, uint32_t effect, uint32_t value, uint32_t note)
{
#ifdef USELOG
	uint32_t oeffect = effect;
	uint32_t ovalue = value;
#endif

	switch(effect)
	{
		case 0: // Pitch: Arpeggio, No memory
		{
			if (value)
			{
				uint32_t val;

				val = value >> 4;
				value &= 0xf;
				Pattern_AddEffect_Arpeggio(pSong, value, val, cmd_arpeggio_type_NHL);
			}
		}
		break;
		case 1: // Pitch: Slide up, No memory
		{
			if (value) Pattern_AddEffect_Pitch(pSong, cmd_up+flag_cmd_slide_frameN0, value << 2);
		}
		break;
		case 2: // Pitch: Slide down, No memory
		{
			if (value) Pattern_AddEffect_Pitch(pSong, cmd_down+flag_cmd_slide_frameN0, value << 2);
		}
		break;
		case 3: // Pitch: Portamento, Memory
		{
			Pattern_AddEffect_Pitch(pSong, cmd_portamento+flag_cmd_slide_frameN0, value << 2);
		}
		break;
		case 4: // Pitch: Vibrato, Memory
		{
			uint32_t speed = value >> 4;
			value &= 0xf;
			// Vibrate between [-2*y,+2*y]
			value <<= 3; // <<2 for range conversion and <<1 for depth
			if (speed) Pattern_AddEffect_Pitch(pSong, cmd_vibrato_speed, speed << 2);
			Pattern_AddEffect_Pitch(pSong, cmd_vibrato_depth | flag_cmd_vibrato_frameN0, value);
		}
		break;
		case 5: // Pitch: Portamento 0 - Volume: Slide, No memory
		{
			convert_effect_MOD(pSong, channel, 0x3, 0, note);
			convert_effect_MOD(pSong, channel, 0xa, value, note);
		}
		break;
		case 6: // Pitch: Vibrato 0 - Volume: Slide, No memory
		{
			convert_effect_MOD(pSong, channel, 0x4, 0, note);
			convert_effect_MOD(pSong, channel, 0xa, value, note);
		}
		break;
		case 7: // Volume: Vibrato (alias Tremolo), Memory
		{
			uint32_t speed = value >> 4;
			value &= 0xf;
			// Vibrate between [-4*y,+4*y]
			value <<= 4; // << 2 for range conversion and << 2 for depth
			if (speed) Pattern_AddEffect_NoteVolume(pSong, cmd_vibrato_speed, speed << 2);
			Pattern_AddEffect_NoteVolume(pSong, cmd_vibrato_depth | flag_cmd_vibrato_frameN0, value);
		}
		break;
		case 8: // Panning: Set
		{
			if ((pSong->pType == Typestr_15inst)
			||  (pSong->pType == Typestr_MK))
			{
				// Unknown effect
				SysLog_BadEffect(pSong, 0, oeffect, ovalue);
			}
			else
			{
				if (value >= 128) value++;
				Pattern_AddEffect_Panning(pSong, cmd_set, value);
			}
		}
		break;
		case 9: // Note: Set sample offset
		{
			if (value)
			{
				// define offset
				// set offset byte 0
				Pattern_AddEffect_Instrument(pSong, cmd_sample_set_offset, 0x000);
				// set offset byte 1
				Pattern_AddEffect_Instrument(pSong, cmd_sample_set_offset, 0x100 | value);
			}

			// only if a note is defined at the same time
			if (note) Pattern_AddEffect_Instrument(pSong, cmd_sample_set_offset, 0xf00);
		}
		break;
		case 0xa: // Volume: Slide (up has priority), No memory
		{
			uint32_t cmd = flag_cmd_slide_frameN0;
			uint32_t down = value & 0xf;
			uint32_t up = value >> 4;

			if (!up)
			{
				cmd += cmd_down;
				value = down;
			}
			else
			{
				cmd += cmd_up;
				value = up;
			}
			if (value) Pattern_AddEffect_NoteVolume(pSong, cmd, value << 2);
		}
		break;
		case 0xb: // Global: Jump
		{
			Pattern_AddGlbEffect_Position(pSong, gcmd_pos_jump, value);
		}
		break;
		case 0xc: // Volume: Set, invalid values are rounded to max
		{
			if (value <= 64)
				Pattern_AddEffect_NoteVolume(pSong, cmd_set, value << 2);
			else
				Pattern_AddEffect_NoteVolume(pSong, cmd_set, 256);
		}
		break;
		case 0xd: // Global: Break
		{
			uint32_t ten = value >> 4;
			value &= 0xf;
			if ((value > 9) || (ten > 8))
				SysLog_BadEffectValue(pSong, 0, oeffect, ovalue);
			else
			{
				value += ten * 10;
				Pattern_AddGlbEffect_Position(pSong, gcmd_pos_break, value);
			}
		}
		break;
		case 0xe: // Special effects
		{
			effect = value >> 4;
			value &= 0xf;
			switch(effect)
			{
				case 0: // e0 Set filter
				{
					// ignore
				}
				break;
				case 1: // e1 Pitch: Up, No memory
				{
					if (value) Pattern_AddEffect_Pitch(pSong, cmd_up+flag_cmd_slide_frame0, value << 2);
				}
				break;
				case 2: // e2 Pitch: Down, No memory
				{
					if (value) Pattern_AddEffect_Pitch(pSong, cmd_down+flag_cmd_slide_frame0, value << 2);
				}
				break;
				case 3: // e3 Pitch: Glissando control
				{
					if (value < 2)
						Pattern_AddEffect_Pitch(pSong, cmd_pitch_glissando, value);
					else
						SysLog_BadEffectValue(pSong, 0, oeffect, ovalue);
			        }
				break;
				case 4: // e4 Pitch: Vibrato type
				{
					effect = value;

					switch(effect & 3)
					{
						case 1: value = vibrato_type_ramp; break;
						case 2: value = vibrato_type_square; break;
						default: value = vibrato_type_sin; break;
					}

					if (effect & 4)
						value |= vibrato_type_noretrig;

					Pattern_AddEffect_Pitch(pSong, cmd_vibrato_type, value);
				}
				break;
				case 5: //e5 Note: Set fine tune in 1/8 semitone
				{
					int32_t svalue = value << 28;

					// only if a note is defined at the same time
					if (note) Pattern_AddEffect_Instrument(pSong, cmd_sample_set_finetune, svalue >> 23);
				}
				break;
				case 6: //e6 Global: Loop in column
				{
					Pattern_AddGlbEffect_Position(pSong, gcmd_pos_loop | value, channel);
				}
				break;
				case 7: //e7 Volume: Vibrato type
				{
					effect = value;

					switch(effect & 3)
					{
						case 1: value = vibrato_type_ramp; break;
						case 2: value = vibrato_type_square; break;
						default: value = vibrato_type_sin; break;
					}

					if (effect & 4)
						value |= vibrato_type_noretrig;

					Pattern_AddEffect_NoteVolume(pSong, cmd_vibrato_type, value);
				}
				break;
				case 8: // e8 Panning: Set
				{
					convert_effect_MOD(pSong, channel, 8, value + (value << 4), note);
				}
				break;
				case 9: // e9 Note: Retrig note, Memory
				{
					Pattern_AddEffect_RetrigCounter(pSong, cmd_retrig_count_type_MOD, value);
					Pattern_AddEffect_Instrument(pSong, cmd_sample_note_retrig, 8);
				}
				break;
				case 0xa: // ea Volume: Up, No memory
				{
					if (value) Pattern_AddEffect_NoteVolume(pSong, cmd_up+flag_cmd_slide_frame0, value << 2);
				}
				break;
				case 0xb: // eb Volume: Down, No memory
				{
					if (value) Pattern_AddEffect_NoteVolume(pSong, cmd_down+flag_cmd_slide_frame0, value << 2);
				}
				break;
				case 0xc: // ec Note: Cut note after x ticks
				{
					Pattern_AddEffect_NoteVolume(pSong, cmd_volume_cut, value);
				}
				break;
				case 0xd: // ed Note: Delay for x ticks
				{
					Pattern_AddEffect_Instrument(pSong, cmd_sample_note_delay, value);
				}
				break;
				case 0xe: // ee Global: Delay row for x rows
				{
					Pattern_AddGlbEffect_Frames(pSong, gcmd_frames_repeat, value);
				}
				break;
				default:
				{
					// Unknown effect
					SysLog_BadEffect(pSong, 0, oeffect, ovalue);
				}
			}
		}
		break;
		case 0xf: // Global: Set speed/tempo
		{
			// if 0 stop (restart) song, test removed cf multiple F on same row
			if (value)
			{
				if (value <= pSong->MaxSpeed)
				{
					// effect_f_test_speed
					Pattern_AddGlbEffect_Frames(pSong, gcmd_frames_set, value);
				}
				else
				{
					// effect_f_tempo
					Pattern_AddGlbEffect_Tempo(pSong, cmd_set, value);
				}
			}
		}
		break;
	}
}

//-----------------------------------------------------------------
// scan_period; converts an amiga period into a symphony note
//
// On input;
// - R0; amiga period
//
// On output;
// - R0; symphony note
//-----------------------------------------------------------------

uint32_t scan_period_MOD(uint32_t period)
{
	uint32_t i;

	if (!period) return 0;

	for (i = 0; i < 5*12; i++)
	{
		if (period >= MOD_Period_Table[i]) return i + NoteDelta;
	}

	return i + NoteDelta - 1;
}

static const _kernel_oserror* Pattern_STK
	( SongHdr* pSong
	, uint32_t i
	, Pattern* pPattern
	, const uint32_t* pPos
	, int* p_is_stk
	)
{
	const _kernel_oserror* err = NULL;
	int             j, k;
	uint32_t        Note, Inst, Cmd, Value;

	err = Loaders_StartPattern(pSong, i);
	if (err) return err;

	for (j = 0; j < pPattern->Rows; j++)
	{
		err = Loaders_StartRow(pSong, j);
		if (err) return err;

		for (k = 0; k < pSong->Channels; k++)
		{
			Value = *pPos++;
			Note = scan_period_MOD(((Value & 0xf) << 8) + ((Value & 0x0000ff00) >> 8));

			if (Note && ((Note < pSong->MinPitch) || (Note > pSong->MaxPitch)))
				*p_is_stk = 0;

			Inst = (Value & 0x000000f0) + ((Value & 0x00f00000) >> 20);
			Cmd = (Value & 0x000f0000) >> 16;
			Value = Value >> 24;

			err = Loaders_StartChannel(pSong, k, Note, Inst);
			if (err) return err;

			*p_is_stk &= convert_effect_STK(pSong, Cmd, Value);
			err = Loaders_EndChannel(pSong);
			if (err) return err;
		}

		err = Loaders_EndRow(pSong);
		if (err) return err;
	}

	return Loaders_EndPattern(pSong, pPattern);
}

const _kernel_oserror* Pattern_MOD(SongHdr* pSong, uint32_t i, Pattern* pPattern, const uint32_t* pPos)
{
	const _kernel_oserror* err = NULL;
	int             j, k;
	uint32_t        Note, Inst, Cmd, Value;

	err = Loaders_StartPattern(pSong, i);
	if (err) return err;

	for (j = 0; j < pPattern->Rows; j++)
	{
		err = Loaders_StartRow(pSong, j);
		if (err) return err;

		for (k = 0; k < pSong->Channels; k++)
		{
			Value = *pPos++;
			Note = scan_period_MOD(((Value & 0xf) << 8) + ((Value & 0x0000ff00) >> 8));

			if (Note && ((Note < pSong->MinPitch) || (Note > pSong->MaxPitch)))
			{
				pSong->MinPitch = NoteDelta;
				pSong->MaxPitch = NoteDelta + 12 * 5 - 1;
			}

			Inst = (Value & 0x000000f0) + ((Value & 0x00f00000) >> 20);
			Cmd = (Value & 0x000f0000) >> 16;
			Value = Value >> 24;

			err = Loaders_StartChannel(pSong, k, Note, Inst);
			if (err) return err;

			convert_effect_MOD(pSong, k, Cmd, Value, Note);
			err = Loaders_EndChannel(pSong);
			if (err) return err;
		}

		err = Loaders_EndRow(pSong);
		if (err) return err;
	}

	return Loaders_EndPattern(pSong, pPattern);
}

static const _kernel_oserror* Pattern_FLT8(SongHdr* pSong, uint32_t i, Pattern* pPattern, const uint32_t* pPos)
{
	const _kernel_oserror* err = NULL;
	int             j, k;
	uint32_t        Note, Inst, Cmd, Value;
	const uint32_t* pPos2 = pPos + 4*64;

	err = Loaders_StartPattern(pSong, i);
	if (err) return err;

	for (j = 0; j < pPattern->Rows; j++)
	{
		err = Loaders_StartRow(pSong, j);
		if (err) return err;

		for (k = 0; k < 4; k++)
		{
			Value = *pPos++;
			Note = scan_period_MOD(((Value & 0xf) << 8) + ((Value & 0x0000ff00) >> 8));

			if (Note && ((Note < pSong->MinPitch) || (Note > pSong->MaxPitch)))
			{
				pSong->MinPitch = NoteDelta;
				pSong->MaxPitch = NoteDelta + 12 * 5 - 1;
			}

			Inst = (Value & 0x000000f0) + ((Value & 0x00f00000) >> 20);
			Cmd = (Value & 0x000f0000) >> 16;
			Value = Value >> 24;

			err = Loaders_StartChannel(pSong, k, Note, Inst);
			if (err) return err;
			convert_effect_MOD(pSong, k, Cmd, Value, Note);
			err = Loaders_EndChannel(pSong);
			if (err) return err;
		}

		for (k = 4; k < 8; k++)
		{
			Value = *pPos2++;
			Note = scan_period_MOD(((Value & 0xf) << 8) + ((Value & 0x0000ff00) >> 8));

			if (Note && ((Note < pSong->MinPitch) || (Note > pSong->MaxPitch)))
			{
				pSong->MinPitch = NoteDelta;
				pSong->MaxPitch = NoteDelta + 12 * 5 - 1;
			}

			Inst = (Value & 0x000000f0) + ((Value & 0x00f00000) >> 20);
			Cmd = (Value & 0x000f0000) >> 16;
			Value = Value >> 24;

			err = Loaders_StartChannel(pSong, k, Note, Inst);
			if (err) return err;
			convert_effect_MOD(pSong, k, Cmd, Value, Note);
			err = Loaders_EndChannel(pSong);
			if (err) return err;
		}

		err = Loaders_EndRow(pSong);
		if (err) return err;
	}

	return Loaders_EndPattern(pSong, pPattern);
}

const _kernel_oserror* Loader_MOD(SongHdr* pSong)
{
	const _kernel_oserror* err = NULL;
	SubSong*    pSubSong = Song_GetSubSong(pSong, 0);
	int         Type;
	int         FileOffset = 20; // skip song name and first sample name
	int         PattOffset = 0x43c; // Offset to first pattern
	Sample*     pSmp;
	uint32_t    Samples;
	uint32_t    val, val2;
	int         i, count, count15;
	uint32_t*   pLTrack = NULL;
	uint8_t*    pSeqBuf = NULL;
	Pattern*    pPattern;
	int         is_ultimate = 0;
	int         onechannelpatterns = 0;

	// Reject file to short to contain header
	if (FileLoad_GetSize(pSong) < PattOffset)
		return Loaders_NotThisType;

	// First read all information required for memory allocation

	pSong->Flags = Song_Flag_InstNoNote_Swap;
	pSong->MinPitch = NoteDelta + 12 * 1;
	pSong->MaxPitch = NoteDelta + 12 * 4 - 1;

	pSong->Samples = 31;

	//which MOD type do we try to load?
	err = FileLoad_SetPos(pSong, PattOffset - 4);
	if (err) return err;
	err = FileLoad_Read(pSong, &Type, 4);
	if (err) return err;

	// is CHN ?
	if ((Type & 0xffffff00) == TAG(CHN))
	{
		int val = (Type & 0xff) - 0x30;
		if ((val <= 0) || (val > 9))
			return Loaders_NotThisType;
		pSong->Channels = val;
		pSong->pType = Typestr_CHN;
	}
	// is CH ?
	else if ((Type & 0xffff0000) == TAG(CH))
	{
		int val = (Type & 0xff) - 0x30;
		int val2 = ((Type >> 8) & 0xff) - 0x30;
		if ((val  < 0) || (val  > 9)
		||  (val2 < 0) || (val2 > 9))
			return Loaders_NotThisType;
		pSong->Channels = val * 10 + val2;
		if (!pSong->Channels
		||  (pSong->Channels > 32))
			return Loaders_NotThisType;
		pSong->pType = Typestr_CHN;
	}
	else if ((Type & 0x0000ffff) == TAG(FA))
	{
		int val = ((Type >> 16) & 0xff) - 0x30;
		int val2 = (Type >> 24) - 0x30;
		if ((val  < 0) || (val  > 9)
		||  (val2 < 0) || (val2 > 9))
			return Loaders_NotThisType;
		pSong->Channels = val * 10 + val2;
		if (!pSong->Channels
		||  (pSong->Channels > 32))
			return Loaders_NotThisType;
		pSong->pType = Typestr_FA;
	}
	else if ((Type & 0x00ffffff) == TAG(FLT))
	{
		int val = (Type >> 24) - 0x30;
		if ((val < 1) || (val > 9))
			return Loaders_NotThisType;
		pSong->Channels = val;
		pSong->pType = Typestr_FLT;
	}
	else if ((Type & 0x00ffffff) == TAG(TDZ))
	{
		int val = (Type >> 24) - 0x30;
		if ((val < 1) || (val > 9))
			return Loaders_NotThisType;
		pSong->Channels = val;
		pSong->pType = Typestr_TDZ;
	}
	else if (Type == TAG(CD81))
	{
		pSong->Channels = 8;
		pSong->pType = Typestr_CDx1;
	}
	else if (Type == TAG(CD61))
	{
		pSong->Channels = 6;
		pSong->pType = Typestr_CDx1;
	}
	else if (Type == TAG(OKTA))
	{
		pSong->Channels = 8;
		pSong->pType = Typestr_OKTA;
	}
	else if (Type == TAG(NSMS))
	{
		pSong->Channels = 4;
		pSong->pType = Typestr_NSMS;
	}
	else if ((Type == TAG(MK))
		 ||  (Type == TAG(MK_))
		 ||  (Type == TAG(M_K_))
		 ||  (Type == TAG(NT)))
	{
		pSong->Channels = 4;
		pSong->pType = Typestr_MK;
	}
	else
	{
		Type = 0;
		PattOffset = 0x7e0;
		if (FileLoad_GetSize(pSong) >= PattOffset)
		{
			err = FileLoad_SetPos(pSong, PattOffset - 4);
			if (err) return err;
			err = FileLoad_Read(pSong, &Type, 4);
			if (err) return err;
		}

		if ((Type & 0x00ffffff) == TAG(TXP))
		{
			int val = (Type >> 24) - 0x30;
			if ((val < 1) || (val > 9))
				return Loaders_NotThisType;
			pSong->Channels = val;
			pSong->pType = Typestr_TXP;
			pSong->Samples = 62;
			pSong->MaxSpeed = 255; // VBlank mode
		}
        else
        {
			Type = 0;
			PattOffset = 0x5bc;
			if (FileLoad_GetSize(pSong) >= PattOffset)
			{
				err = FileLoad_SetPos(pSong, PattOffset - 4);
				if (err) return err;
				err = FileLoad_Read(pSong, &Type, 4);
				if (err) return err;
			}

			if (Type == TAG(MTN))
			{
				pSong->Samples = 31;
				pSong->Channels = 4;
				pSong->pType = Typestr_MTN;
				onechannelpatterns = 1;
			}
			else if (Type == TAG(IT10))
			{
				pSong->Samples = 31;
				pSong->Channels = 4;
				pSong->pType = Typestr_IT10;
				onechannelpatterns = 1;
			}
    	    else
        	{
				pSong->Samples = 15;
				pSong->Channels = 4;
				pSong->pType = Typestr_15inst;
				pSong->MaxSpeed = 255; // VBlank mode
				PattOffset = 0x258;
				is_ultimate = 1; // Is possibly an old Ultimate Soundtracker
			}
		}
	}

	// Compatibility
	if (pSong->Compatibility & song_compat_mod_vblank)
		pSong->MaxSpeed = 255; // VBlank mode
	if (pSong->Compatibility & song_compat_mod_no_instr_swap)
		pSong->Flags &= ~Song_Flag_InstNoNote_Swap;
	if (!(pSong->Compatibility & song_compat_mod_ultimate))
		is_ultimate = 0;

	// fill the info
	err = FileLoad_SetPos(pSong, 0);
	if (err) return err;
	err = FileLoad_ReadString(pSong, &pSong->pName, 20, false);
	if (err) return err;

	//------------------
	// read samples info
	count = 0;
	for(Samples = pSong->Samples, pSmp = pSong->pSamples, FileOffset = 20
		; Samples > 0; Samples--, pSmp++, FileOffset += 30)
	{
		err = FileLoad_SetPos(pSong, FileOffset);
		if (err) return err;

		pSmp->Type = 0;
		if (pSong->pType != Typestr_CHN) pSmp->Frequency = 8287;

		// read sample name
		err = FileLoad_ReadString(pSong, &pSmp->pName, 22, false);
		if (err) return err;

		// read size/2
		err = FileLoad_ReadReverseInt(pSong, &val, 2); // 22
		if (err) return err;
		pSmp->Size = val << 1;
		if (pSmp->Size > 9999)
			is_ultimate = 0;
		// Try to filter out non-MOD
		if ((pSong->pType == Typestr_15inst) && (pSmp->Size > 32*1024))
			return Loaders_NotThisType;

		// read fine tune
		err = FileLoad_ReadByte(pSong, &val); // 24
		if (err) return err;
		pSmp->FineTune = (val & 0xf) << 28;

		// read volume (max 64)
		err = FileLoad_ReadByte(pSong, &val); // 25
		if (err) return err;
		val = val << 2;
//		if (val > 256) val = 256;
		if (val > 256) return Loaders_NotThisType;
		pSmp->DefaultVolume = val;

		// read repeat offset
		err = FileLoad_ReadReverseInt(pSong, &val, 2); // 26
		if (err) return err;
		// except on old one, it is actually offset/2
		if (pSong->pType != Typestr_15inst)
			val <<= 1;

		// read repeat len/2
		err = FileLoad_ReadReverseInt(pSong, &val2, 2); // 28
		if (err) return err;
		val2 <<= 1;

		// special case
		if (val2 == 2)
		{
			if (val)
				val2 = pSmp->Size - val;
			else
				val2 = 0;
		}
		// due to a problem some trackers have a loop start non divided by 2
		if (val + val2 > pSmp->Size)
			val >>= 1;

		if (val2)
		{
			val2 += val;
			pSmp->Type |= Smp_Type_Loop;
		}
		pSmp->LoopStart = val;
		pSmp->LoopEnd = val2;

		count += pSmp->Size;
	}

	// Move behind last sample info
	err = FileLoad_SetPos(pSong, FileOffset);
	if (err) goto loader_err;

	// Read sequence length
	err = FileLoad_ReadInt(pSong, &pSubSong->SeqLen, 1);
	// Perform the only possible rejection test
	if (!pSubSong->SeqLen
	||  (pSubSong->SeqLen > 128))
		return Loaders_NotThisType;

	// read restart position
	err = FileLoad_ReadInt(pSong, &val, 1);

	if (onechannelpatterns)
	{
		int             j, k;
		uint32_t        Note, Inst, Cmd, Value;

		// Nr of 1 channelr patterns
		onechannelpatterns = val;
		pSong->Patterns = pSubSong->SeqLen;

		err = Loaders_AllocSeq(pSong, pSubSong);
		if (err) goto loader_err;

		for (i = 0; i < pSubSong->SeqLen; i++)
			pSubSong->pSeqs[i] = i;

		// read sequence buffer
		err = Loaders_Alloc(pSong, (void**) &pSeqBuf, 128*pSong->Channels);
		if (err) goto loader_err;
		err = FileLoad_Read(pSong, pSeqBuf, 128*pSong->Channels);
		if (err) goto loader_err;

		// check sequence numbers
		for (i = 0; i < pSubSong->SeqLen*pSong->Channels; i++)
		{
			if (pSeqBuf[i] >= onechannelpatterns)
			{
				err = Loaders_Error(pSong, FileLoad_GetPos(pSong), "Bad order value");
				goto loader_err;
			}
		}

		// prepare patterns buffer
		err = Loaders_Alloc(pSong, (void**) &pLTrack, 4*64*onechannelpatterns);
		if (err) goto loader_err;
		err = FileLoad_Read(pSong, pLTrack, 4*64*onechannelpatterns);
		if (err) goto loader_err;

		//---------------
		// fill the patterns

		for (i = 0, pPattern = pSong->pPatterns; i < pSong->Patterns; i++, pPattern++)
		{
			uint8_t* seq = pSeqBuf + i*pSong->Channels;

			pPattern->Rows = 64;

			err = Loaders_StartPattern(pSong, i);
			if (err) goto loader_err;

			for (j = 0; j < pPattern->Rows; j++)
			{
				err = Loaders_StartRow(pSong, j);
				if (err) goto loader_err;

				for (k = 0; k < pSong->Channels; k++)
				{
					Value = pLTrack[pPattern->Rows*seq[k] + j];
					Note = scan_period_MOD(((Value & 0xf) << 8) + ((Value & 0x0000ff00) >> 8));

					if (Note && ((Note < pSong->MinPitch) || (Note > pSong->MaxPitch)))
					{
						pSong->MinPitch = NoteDelta;
						pSong->MaxPitch = NoteDelta + 12 * 5 - 1;
					}

					Inst = (Value & 0x000000f0) + ((Value & 0x00f00000) >> 20);
					Cmd = (Value & 0x000f0000) >> 16;
					Value = Value >> 24;

					err = Loaders_StartChannel(pSong, k, Note, Inst);
					if (err) return err;

					convert_effect_MOD(pSong, k, Cmd, Value, Note);
					err = Loaders_EndChannel(pSong);
					if (err) return err;
				}

				err = Loaders_EndRow(pSong);
				if (err) goto loader_err;
			}

			err = Loaders_EndPattern(pSong, pPattern);
			if (err) goto loader_err;
		}
	}
	else
	{
		if (pSong->pType == Typestr_15inst)
		{
			// Tempo
			if (val > 0)
				pSubSong->Defaults.Tempo = val;
		}
		else
		{
			// Restart position
			if (val < pSubSong->SeqLen)
				pSubSong->RestartPos = val;
		}

		// get a minimal number of patterns
		count = FileLoad_GetSize(pSong) - FileLoad_GetPos(pSong) - count;
		if (count > 0) pSong->Patterns = count / (4*64*pSong->Channels);
		else pSong->Patterns = 0;

		//---------
		// loop on sequence to get the greatest pattern number
		// Note that FLT8 uses 2 consecutive 4-channels patterns
		// instead of a single 8-channels pattern
		err = Loaders_AllocSeq(pSong, pSubSong);
		if (err) goto loader_err;

		count = 0;
		for (i = 0; i < pSubSong->SeqLen; i++)
		{
			err = FileLoad_ReadByte(pSong, &val);
			if (err) goto loader_err;
			if ((pSong->pType == Typestr_FLT) && (pSong->Channels == 8))
				pSubSong->pSeqs[i] = val >> 1;
			else
				pSubSong->pSeqs[i] = val;
			if (count <= pSubSong->pSeqs[i]) count = pSubSong->pSeqs[i] + 1;
		}
		count15 = count;

		// loop on the unused part of the sequence
		// to get the greatest pattern number
		for (; i < 128; i++)
		{
			err = FileLoad_ReadByte(pSong, &val);
			if (err) goto loader_err;
			if ((pSong->pType == Typestr_FLT) && (pSong->Channels == 8))
				val >>= 1;
			if (count <= val) count = val + 1;
		}

		// found the very highest pattern
		// there may be no more patterns in file
		if (pSong->Patterns > count)
			SysLog_FileCorrupted(SysLog_High, pSong, FileLoad_GetPos(pSong), "More patterns than orders");
		// File not large enough to contain all info? Possible causes:
		// 1) Truncated files, some sample info is missing.
		// 2) Tracker variant which does not store patterns of unused part of sequence.
		//    Since tracker cannot be identified, be very cautious, limit selection of this choice
		//    to strict condiftions.
		if ((pSong->Patterns < count)
		&&  (pSong->Patterns == count15)
		&&  (pSong->pType == Typestr_15inst))
			pSong->Patterns = count15;
		else
			pSong->Patterns = count;

		//---------------
		// fill the patterns
		err = Loaders_Alloc(pSong, (void**) &pLTrack, 4*64*pSong->Channels);
		if (err) goto loader_err;

		// is_ultimate may be reset within the following block
		if (is_ultimate)
		{
			err = FileLoad_SetPos(pSong, PattOffset);
			if (err) goto loader_err;

			for (i = 0, pPattern = pSong->pPatterns; is_ultimate && (i < pSong->Patterns); i++, pPattern++)
			{
				pPattern->Rows = 64;

				err = FileLoad_Read(pSong, pLTrack, 4*64*pSong->Channels);
				if (err) goto loader_err;

				err = Pattern_STK(pSong, i, pPattern, pLTrack, &is_ultimate);
				if (err) goto loader_err;
			}
		}

		if (is_ultimate)
			pSong->pType = Typestr_STK;
		else
		{
			err = FileLoad_SetPos(pSong, PattOffset);
			if (err) goto loader_err;

			for (i = 0, pPattern = pSong->pPatterns; i < pSong->Patterns; i++, pPattern++)
			{
				pPattern->Rows = 64;

				err = FileLoad_Read(pSong, pLTrack, 4*64*pSong->Channels);
				if (err) goto loader_err;

				if ((pSong->pType == Typestr_FLT) && (pSong->Channels == 8))
					err = Pattern_FLT8(pSong, i, pPattern, pLTrack);
				else
					err = Pattern_MOD(pSong, i, pPattern, pLTrack);
				if (err) goto loader_err;
			}
		}
	}

	//---------------
	// fill the samples
	for (pSmp = pSong->pSamples, i = 0; i < pSong->Samples; i++, pSmp++)
	{
		err = FileLoad_ReadSample(pSong, pSmp);
		if (err) goto loader_err;

		if (pSong->pType == Typestr_TXP)
		{
			uint8_t* pc;
			uint8_t* pcend;
			const uint8_t* SmpLogToLin = Table_ArcSampleLogToLin();

			// Convert from Log to Lin
			for (pc = pSmp->Ptr, pcend = pc + pSmp->Size; pc < pcend; pc++)
			{
				*pc = SmpLogToLin[*pc];
			}
		}
	}

loader_err:
	Loaders_Free(pSong, pLTrack);
	Loaders_Free(pSong, pSeqBuf);

	return err;
}
