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

/*
 * Channels: [1...32]
 * Patterns: [1...128]
 * Rows    : 64
 * Orders  : [1...256]
 * Samples : [1...255]
 * Instr.  : 0
 * Tempo   : [32...255], T frames per 2.5 seconds.
 * Speed   : [1...31], S frames per row.
 * Notes   : [C-0...C-4...B-9]
 * Pitch   : Amiga periods mode, base is 428 units, period is divided by 2 each octave up.
 *           Slides as x/1 or x/4 units per frame.
 * Volume  : [0...64] , Gamma curved.
 * Panning : [0...F].
 * Sampling: 8363 Hz (frequency is turned into a relative tone).
 *
 * Notes:
 * Tested with PolyPlayer.
 *
 * The player has not pitch limit checks and wraps around a little bit below C-0 and above B-9!!!
 *
 * 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 fintetune ???
 *
 * Unstored samples are treated as empty samples with default volume 0.
 *
 * Note triggering does not apply when portamento effect is present.
 *
 * Retrig E9y behaves as follow:
 *  E90 does nothing.
 *  E9y retrigs at ticks y-1, 2*y-1, 3*y-1, ...
 *  That is the same as XM E9z with z = y - 1.
 *
 * Retrig Hxy uses an increasing counter which is reset only when a new note is triggered.
 *   The counter increases at each tick of a row using an Hxy effect, and triggers a new note when
 *   the counter >= y. This means on a row that has a new note that H03 the new note is played
 *   at tick 0, and retrigs follows at tick 2, 5, 8, etc. Retrigs with y > speed will work provided
 *   that you put enough H commands in the row that follows (they can even be interspaced by
 *   other effects). This is the same as the XM Rxy command.
 *   In Hxy, x = 0 or y = 0 are independant memories.
 *
 * Effects:
 * -------
 *
 * Panning E80 seems to be treated as E8F by PolyPlayer, we assume its a bug.
 * Document states that it use a non-standard volume tables.
 *  Volumes given are GUS log one where volume is divided by 2
 *  every 4096 values. After converting the values back to linear ones,
 *  it looks like a gamma correction of ~0.875 is applied
 *  to give more importance to low values.
 * The Dumb player treats sample offset as byte offsets, not sample offsets.
 *
 * E9x seems to trigger notes even when no note/instrument is present, I have not implemented that behavior.
 *
 * Effects j-m and n don't sound right, I guess I have not the correct definition.
 */

typedef struct
{
	uint8_t     Name[28];
	uint8_t     _1A;
	uint8_t     Version[2]; // e.g. 0x0203
	uint8_t     Dummy;
	uint16_t    Orders;     // [0..256]
	uint16_t    Samples;    // [1..255]
	uint16_t    Patterns;   // [1..128]
	uint16_t    Channels;   // [1..32]
	uint16_t    Flags;
	uint16_t    Dummy2;
	uint32_t    TAG;
	uint8_t     Dummy3[16];
	uint8_t     Panning[32];
	uint8_t     Sequence[256];
	uint16_t    Segments[128];
} LHdr;

typedef struct
{
	uint8_t     Type;
	uint8_t     Filename[12];
	uint8_t     Volume;
	uint16_t    Frequency;    // in *2 Hz
	uint16_t    Dummy;
	uint16_t    Offset[2];
	uint16_t    Length[2];    // In bytes
	uint16_t    LoopStart[2]; // In bytes
	uint16_t    LoopEnd[2];   // In bytes
	uint8_t     Dummy2[14];
	uint8_t     Name[28];
	uint32_t    TAG;
} LSmp;

#define NoteDelta (Note_Central-12*4)

static const uint8_t Tag_PTM[] = "PTMF";
static const uint8_t Typestr_PTM[] = "PolyTracker";

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

// Approximated by 256*((x/256)^.875) = Gamma .875
static const uint8_t VolTable[] =
{ 0x00, 0x02, 0x04, 0x05, 0x07, 0x08, 0x0A, 0x0B, 0x0C, 0x0E, 0x0F, 0x10, 0x12, 0x13, 0x14, 0x15
, 0x17, 0x18, 0x19, 0x1A, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28
, 0x29, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A
, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B
, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5C
, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C
, 0x6D, 0x6E, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B
, 0x7C, 0x7D, 0x7E, 0x7F, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B
, 0x8C, 0x8D, 0x8D, 0x8E, 0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A
, 0x9B, 0x9C, 0x9D, 0x9E, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9
, 0xAA, 0xAB, 0xAC, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8
, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 0xC0, 0xC1, 0xC2, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6
, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCC, 0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5
, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDE, 0xDF, 0xE0, 0xE1, 0xE2, 0xE3
, 0xE4, 0xE5, 0xE6, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEE, 0xEF, 0xF0, 0xF1
, 0xF2, 0xF3, 0xF4, 0xF5, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFC, 0xFD, 0xFE, 0xFF
, 0xFF
};

static void convert_effect(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
		{
			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
		{
			effect = cmd_upmem1;

			if (value >= 0xf0)
			{
				// Fx fine up
				value = (value & 0x0f) << 2;
				// F0 must force memory to 0, not read it
				if (!value) effect = cmd_pitch_memslide;
				effect |= flag_cmd_slide_frame0;
			}
			else if (value >= 0xe0)
			{
				// Ex extra fine up
				value &= 0x0f;
				// E0 must force memory to 0, not read it
				if (!value) effect = cmd_pitch_memslide;
				effect |= flag_cmd_slide_frame0;
			}
			else if (value)
			{
				// xx slide up
				value <<= 2;
				effect |= flag_cmd_slide_frameN0;
			}
				// 00 slide at last slide type and value

			Pattern_AddEffect_Pitch(pSong, effect, value);
		}
		break;
		case 2: // Pitch - Slide down
		{
			effect = cmd_downmem1;

			if (value >= 0xf0)
			{
				// Fx fine down
				value = (value & 0x0f) << 2;
				// F0 must force memory to 0, not read it
				if (!value) effect = cmd_pitch_memslide;
				effect |= flag_cmd_slide_frame0;
			}
			else if (value >= 0xe0)
			{
				// Ex extra fine down
				value &= 0x0f;
				// E0 must force memory to 0, not read it
				if (!value) effect = cmd_pitch_memslide;
				effect |=flag_cmd_slide_frame0;
			}
			else if (value)
			{
				// xx slide down
				value <<= 2;
				effect |= flag_cmd_slide_frameN0;
			}
				// 00 slide at last slide type and value

			Pattern_AddEffect_Pitch(pSong, effect, value);
		}
		break;
		case 3: // Pitch: Portamento
		{
			Pattern_AddEffect_Pitch(pSong, cmd_portamento+flag_cmd_slide_frameN0, value << 2);
		}
		break;
		case 4: // Pitch: Vibrato
		{
			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
		{
			convert_effect(pSong, channel, 0x3, 0, note);
			convert_effect(pSong, channel, 0xa, value, note);
		}
		break;
		case 6: // Pitch: Vibrato 0 - Volume: Slide
		{
			convert_effect(pSong, channel, 0x4, 0, note);
			convert_effect(pSong, channel, 0xa, value, note);
		}
		break;
		case 7: // Volume: Vibrato (alias Tremolo)
		{
			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 9: // Note: Set sample offset (in bytes), no memory
		{
			// clear byte 0
			Pattern_AddEffect_Instrument(pSong, cmd_sample_set_offset, 0x000 | 0);
			// 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, 0xe00); // in bytes
		}
		break;
		case 0xa: // Volume: Slide (up takes precedence)
		{
			if (!value)
			{
				// 00 repeat last slide
				Pattern_AddEffect_NoteVolume(pSong, cmd_last_slide, 0);
			}
			else if (!(value & 0x0f))
			{
				// x0 slide up
				effect = cmd_upmem1 | flag_cmd_slide_frameN0;
				value &= 0xf0;
				Pattern_AddEffect_NoteVolume(pSong, effect, value >> 2);
			}
			else if (!(value & 0xf0))
			{
				// 0y slide down
				effect = cmd_downmem1 | flag_cmd_slide_frameN0;
				value &= 0x0f;
				Pattern_AddEffect_NoteVolume(pSong, effect, value << 2);
			}
			else if ((value & 0x0f) == 0x0f)
			{
				// xF up
				effect = cmd_upmem1 | flag_cmd_slide_frame0;
				value &= 0xf0;
				Pattern_AddEffect_NoteVolume(pSong, effect, value >> 2);
			}
			else if ((value & 0xf0) == 0xf0)
			{
				// Fy down
				effect = cmd_downmem1 | flag_cmd_slide_frame0;
				value &= 0x0f;
				Pattern_AddEffect_NoteVolume(pSong, effect, value << 2);
			}
			else
			{
				// xy behave as slide up
				effect = cmd_upmem1 | flag_cmd_slide_frameN0;
				value &= 0xf0;
				Pattern_AddEffect_NoteVolume(pSong, effect, value >> 2);
			}
		}
		break;
		case 0xb: // Global: Jump
		{
			Pattern_AddGlbEffect_Position(pSong, gcmd_pos_jump, value);
		}
		break;
		case 0xc: // Volume: Set [0, 64], values above are truncated
		{
			if (value > 64) value = 64;
			Pattern_AddEffect_NoteVolume(pSong, cmd_set, value << 2);
		}
		break;
		case 0xd: // Global: Break
		{
			uint32_t ten = value >> 4;
			value &= 0xf;
			if ((value > 9) || (ten > 9))
				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 1: // e1 Pitch: Up
				{
					if (value) Pattern_AddEffect_Pitch(pSong, cmd_up+flag_cmd_slide_frame0, value << 2);
				}
				break;
				case 2: // e2 Pitch: Down
				{
					if (value) Pattern_AddEffect_Pitch(pSong, cmd_down+flag_cmd_slide_frame0, value << 2);
				}
				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;
						case 3: value = vibrato_type_random; 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;
						case 3: value = vibrato_type_random; 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
				{
					value += (value << 4);
					if (value >= 128) value++;
					Pattern_AddEffect_Panning(pSong, cmd_set, value);
				}
				break;
				case 9: // e9 Note: Retrig note
				{
					if (value)
					{
						Pattern_AddEffect_RetrigCounter(pSong, cmd_retrig_count_type_XMOld, value);
						Pattern_AddEffect_Instrument(pSong, cmd_sample_note_retrig, 8);
					}
				}
				break;
				case 0xa: // ea Volume: Up
				{
					if (value) Pattern_AddEffect_NoteVolume(pSong, cmd_up+flag_cmd_slide_frame0, value << 2);
				}
				break;
				case 0xb: // eb Volume: Down
				{
					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 note for x ticks
				{
					if (value) Pattern_AddEffect_Instrument(pSong, cmd_sample_note_delay, value - 1);
				}
				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;
		case 0x10: // g, Global Volume: Set [0, 64], values above are truncated
		{
			if (value > 64) value = 64;
			Pattern_AddGlbEffect_Volume(pSong, cmd_set, value << 2);
		}
		break;
		case 0x11: // h, Note: Retrig note every y ticks with volume change type x, Memory
		{
			uint32_t eff;

			eff = value >> 4;
			value &= 0xf;

			Pattern_AddEffect_RetrigCounter(pSong, cmd_retrig_count_type_PTM, value);
			Pattern_AddEffect_Instrument(pSong, cmd_sample_note_retrig, eff);
		}
		break;
		case 0x12: // i, Pitch: Vibrato fine
		{
			uint32_t speed = value >> 4;
			value &= 0xf;
			// Vibrate between [-2*y,+2*y]
			value <<= 1; // << 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 0x13: // j, Pitch: Slide fine down by x every y ticks ?
		{
			uint32_t speed = value >> 4;
			value &= 0xf;
			Pattern_AddEffect_RetrigCounter(pSong, cmd_retrig_count_type_PTM, value);
			Pattern_AddEffect_Pitch(pSong, cmd_down, speed); // without 0/N0 flags = use counter
		}
		break;
		case 0x14: // k, Pitch: Slide fine up by x every y ticks ?
		{
			uint32_t speed = value >> 4;
			value &= 0xf;
			Pattern_AddEffect_RetrigCounter(pSong, cmd_retrig_count_type_PTM, value);
			Pattern_AddEffect_Pitch(pSong, cmd_up, speed); // without 0/N0 flags = use counter
		}
		break;
		case 0x15: // l, Pitch: Slide fine down by x every y ticks ? - Note: Retrig
		{
			uint32_t speed = value >> 4;
			value &= 0xf;
			Pattern_AddEffect_RetrigCounter(pSong, cmd_retrig_count_type_PTM, value);
			Pattern_AddEffect_Instrument(pSong, cmd_sample_note_retrig, 8);
			Pattern_AddEffect_Pitch(pSong, cmd_down, speed); // without 0/N0 flags = use counter
		}
		break;
		case 0x16: // m, Pitch: Slide fine up by x every y ticks ? - Note: Retrig
		{
			uint32_t speed = value >> 4;
			value &= 0xf;
			Pattern_AddEffect_RetrigCounter(pSong, cmd_retrig_count_type_PTM, value);
			Pattern_AddEffect_Instrument(pSong, cmd_sample_note_retrig, 8);
			Pattern_AddEffect_Pitch(pSong, cmd_up, speed); // without 0/N0 flags = use counter
		}
		break;
		case 0x17: // n, Note: Set revert sample offset in bytes (ie from end of sample)
		{
			// set offset byte 1
			Pattern_AddEffect_Instrument(pSong, cmd_sample_set_offset, 0x100 | value);
			// clear byte 0
			Pattern_AddEffect_Instrument(pSong, cmd_sample_set_offset, 0x000);

			// only if a note is defined at the same time
			if (note)
			{
				Pattern_AddEffect_Instrument(pSong, cmd_sample_reverse_play, 0);
				Pattern_AddEffect_Instrument(pSong, cmd_sample_set_offset, 0xe00); // in bytes
			}
		}
		break;
		default:
		{
			// Unknown effect
			SysLog_BadEffect(pSong, 0, oeffect, ovalue);
		}
	}
}

static const _kernel_oserror* Pattern_PTM(SongHdr* pSong, uint32_t i, Pattern* pPattern, const uint8_t* pPos, const uint8_t* pEnd)
{
	const _kernel_oserror*    err = NULL;
	uint32_t            val;
	int                 j;
	const uint8_t*      pPos2;
	int                 channel, lastchannel;
	uint32_t            Note, Inst, Cmd, Val;


	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;

		lastchannel = -1;
		while (pPos < pEnd)
		{
			// read channel nr and flags
			val = *pPos++;
			// next row mark?
			if (!val) break;
			// will we remain within pattern limits?
			pPos2 = pPos;
			if (val & 0x20) pPos2 += 2;
			if (val & 0x40) pPos2 += 2;
			if (val & 0x80) pPos2++;
			if (pPos2 > pEnd) break;
			// valid channel ?
			channel = val & 0x1f;
			if ((channel <= lastchannel)
			||  (channel >= pSong->Channels))
			{
				// ignore
				pPos = pPos2;
				continue;
			}
			lastchannel = channel;

			// Read note & instrument
			if (val & 0x20)
			{
				Note = *pPos++;
				if (Note == 254) Note = Note_CutVolume;
				else if (Note > (128-NoteDelta))
				{
					SysLog_BadNote(pSong, 0, channel, Note);
					Note = 0;
				}
				else if (Note) Note += NoteDelta - 1;
				Inst = *pPos++;
			}
			else
			{
				Note = 0;
				Inst = 0;
			}

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

			// Read efffect and value
			if (val & 0x40)
			{
				Cmd = pPos[0];
				Val = pPos[1];
				pPos += 2;
			}
			else
			{
				Cmd = 0;
				Val = 0;
			}

			// Read Volume column, truncate values above 64
			if (val & 0x80)
			{
				Pattern_AddEffect_NoteVolume(pSong, cmd_set, (*pPos <= 64) ? (*pPos) << 2 : 256);
				pPos++;
			}

			// Effects must be treated after volume column
			convert_effect(pSong, channel, Cmd, Val, (Note < Note_CmdMin) ? Note : 0);

			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_PTM(SongHdr* pSong)
{
	const _kernel_oserror* err = NULL;
	SubSong*        pSubSong = Song_GetSubSong(pSong, 0);
	LHdr*           pLHdr = NULL;
	LSmp*           pLSmp = NULL;
	uint8_t*        pLTrack = NULL;
	uint32_t        val;
	int             i, j;
	ChannelDefault* pDef;
	Pattern*        pPattern;
	Sample*         pSmp;
	uint8_t*        pc;
	int             Pos, EndPos = 0, Size;

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

	// Allocate memory
	err = Loaders_Alloc(pSong, (void**) &pLHdr, sizeof(LHdr));
	if (err) goto loader_err;
	err = Loaders_Alloc(pSong, (void**) &pLSmp, sizeof(LSmp));
	if (err) goto loader_err;

	// load header
	err = FileLoad_SetPos(pSong, 0);
	if (err) goto loader_err;
	err = FileLoad_Read(pSong, pLHdr, sizeof(LHdr));
	if (err)
	{
		// Header can be larger than some files of other type
		err = Loaders_NotThisType;
		goto loader_err;
	}

	// check if really PTM?

	if (pLHdr->TAG != TAG(PTM))
	{
		err = Loaders_NotThisType;
		goto loader_err;
	}

	// set version
	pSong->Version = (pLHdr->Version[1] & 0xf) * 100
	               + (pLHdr->Version[0] & 0xf);

	pSong->Flags = 0;
	pSong->MinPitch = NoteDelta;
	pSong->MaxPitch = NoteDelta + 12 * 10 - 1;
	pSong->VolumeTranslation = VolTable;

	// fill the info
	err = Loaders_String(pSong, &pSong->pName, &pLHdr->Name[0], sizeof(pLHdr->Name), false);
	if (err) goto loader_err;

	// fill file type ptr
	pSong->pType = Typestr_PTM;

	pSong->Patterns = pLHdr->Patterns;
	if (!pSong->Patterns || (pSong->Patterns > 128)) // cf. segments
	{
		err = Loaders_InvalidSong(pSong, 28, &pSong->Patterns, pSong->Patterns);
		goto loader_err;
	}
	pSubSong->SeqLen = pLHdr->Orders;
	if (!pSubSong->SeqLen || (pSubSong->SeqLen > 256))
	{
		err = Loaders_InvalidSong(pSong, 24, &pSubSong->SeqLen, pSubSong->SeqLen);
		goto loader_err;
	}
	pSong->Samples = pLHdr->Samples;
	if (!pSong->Samples  || (pLHdr->Samples > 255))
	{
		err = Loaders_InvalidSong(pSong, 26, &pSong->Samples, pSong->Samples);
		goto loader_err;
	}
	pSong->Channels = pLHdr->Channels;
	if (!pSong->Channels || (pSong->Channels > 32))
	{
		err = Loaders_InvalidSong(pSong, 30, &pSong->Channels, pSong->Channels);
		goto loader_err;
	}

	// set initial panning
	for (i = 0, pDef = pSubSong->Defaults.ChDef; i < pSong->Channels; i++, pDef++)
	{
		val = pLHdr->Panning[i] & 0xf;
		val += val << 4;
		if (val >= 128) val++;
		pDef->Panning = val;
	}

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

	// fill the pattern sequence
	for (i = 0; i < pSubSong->SeqLen; i++)
		pSubSong->pSeqs[i] = pLHdr->Sequence[i];

	// Start of samples
	Pos = FileLoad_GetPos(pSong);

	// read samples info
	for (i = 0, pSmp = pSong->pSamples; i < pSong->Samples; i++, pSmp++, Pos += sizeof(LSmp))
	{
		err = FileLoad_SetPos(pSong, Pos);
		if (err) goto loader_err;
		err = FileLoad_Read(pSong, pLSmp, sizeof(LSmp));
		if (err) goto loader_err;

		// filename
		err = Loaders_String(pSong, &pSmp->pFilename, &pLSmp->Filename[0], sizeof(pLSmp->Filename), false);
		if (err) goto loader_err;

		// read name
		err = Loaders_String(pSong, &pSmp->pName, &pLSmp->Name[0], sizeof(pLSmp->Name), false);
		if (err) goto loader_err;

		// read data offset
		val = (pLSmp->Offset[1] << 16) + pLSmp->Offset[0];
		// First offset is end of patterns
		if (i == 0)
			EndPos = val;

		// check type, don't support adlib or other types
		if ((pLSmp->Type & 3) != 1) continue;

		// read size
		pSmp->Size = (pLSmp->Length[1] << 16) + pLSmp->Length[0];

		// read loop begin
		pSmp->LoopStart = (pLSmp->LoopStart[1] << 16) + pLSmp->LoopStart[0];

		// read loop end
		pSmp->LoopEnd = (pLSmp->LoopEnd[1] << 16) + pLSmp->LoopEnd[0];

		// read volume
		if (pLSmp->Volume > 64) pSmp->DefaultVolume = 256;
		else pSmp->DefaultVolume = pLSmp->Volume << 2;

		// read freq
		pSmp->Frequency = 8363;
		pSmp->RelTone   = Convert_RelTone(pSmp->Frequency, pLSmp->Frequency);

		// read sample type
		pSmp->Type = 0;
		if (pLSmp->Type & 0x04)
			pSmp->Type |= Smp_Type_Loop;
		if (pLSmp->Type & 0x08)
			pSmp->Type |= Smp_Type_Loop_Bidi;
		if (pLSmp->Type & 0x10)
			pSmp->Type |= Smp_Type_16bit;

		if (pSmp->Type & Smp_Type_16bit)
		{
			pSmp->Size >>= 1;
			pSmp->LoopStart >>= 1;
			pSmp->LoopEnd >>= 1;
		}

		// read data
		err = FileLoad_SetPos(pSong, val);
		if (err)
		{
			SysLog_FileCorrupted(SysLog_Medium, pSong, 0, "Sample %d: invalid data offset", 1 + (pSmp - pSong->pSamples));
			pSmp->Size = 0;
		}
		else
		{
			err = FileLoad_ReadSample(pSong, pSmp);
			if (err) goto loader_err;

			// convert sample from diff to absolute
			// (byte wise even for 16-bit)
			j = pSmp->Size;
			if(pSmp->Type & Smp_Type_16bit)
				j <<= 1;

			for (val = 0, pc = pSmp->Ptr; j > 0; j--, pc++)
			{
				val += *pc;
				*pc = val;
			}
		}
	}

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

		// read pattern start/end offset
		Pos = pLHdr->Segments[i] << 4;
		err = FileLoad_SetPos(pSong, Pos);
		if (i < pSong->Patterns - 1)
			Size = (pLHdr->Segments[i+1] << 4) - Pos;
		else
			Size = EndPos - Pos;

		if (Size > 0)
		{
			err = Loaders_Alloc(pSong, (void**) &pLTrack, Size);
			if (err) goto loader_err;
			err = FileLoad_Read(pSong, pLTrack, Size);
			if (err) goto loader_err;
		}
		else Size = 0;

		err = Pattern_PTM(pSong, i, pPattern, pLTrack, pLTrack + Size);
		if (err) goto loader_err;

		Loaders_Free(pSong, pLTrack);
		pLTrack = NULL;
	}

loader_err:
	Loaders_Free(pSong, pLHdr);
	Loaders_Free(pSong, pLSmp);
	Loaders_Free(pSong, pLTrack);

	return err;
}
