#include <string.h>
#include "Song.h"
#include "Effects.h"
#include "Instrument.h"
#include "Loaders.h"
#include "FSysLog.h"
//#include <stdio.h>

/*
 * Channels: [1...32]
 * Patterns: [1...256]
 * Rows    : [1...65535]
 * Orders  : [1...256]
 * Samples : [1...]
 * Instr.  : [1...255]
 * Tempo   : [32...255], T frames per 2.5 seconds.
 * Speed   : [1...31], S frames per row.
 * Notes   : [C-0...C-4...B-7]
 * Pitch   : Amiga periods mode, base is 428 units, period is divided by 2 each octave up.
 *           Slides as x/1 units per frame.
 *           Semitones mode, value is upped by 12 each octave up.
 *           Slides in x/16 units per row.
 * Volume  : [0...64] , linear.
 * Panning : [0...255].
 * Sampling: 8394 Hz. Uses relative tones directly.
 *
 * Bug in FT2.08: Panning envelope sustain is not removed by note off.
 *
 * 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/panning from OLD sample.
 *   - restores sustain and resets fadeout.
 *   - resets volume/pitch vibrato position.
 *   - resets envelopes positions.
 *   - does not affect finetune.
 *
 * Instrument mapping to no sample behave as empty sample with volume 0.
 *
 * Unstored samples are treated as empty samples with default volume 0.
 *
 * Note triggering does not apply when portamento effect is present.
 *
 * Note off in note column resets note information, so retrigs won't work anymore.
 *
 * Retrig E9y. The counter is set to y on tick 0 of the row, decremented at each tick
 *   and triggering a new note when then counter reaches 0. At that point the counter is reset to y.
 *   E90 will provoque a retrig at each tick of the row, E91 at ticks 1, 3, 5, etc. If y > speed
 *   then there will be no retrig.
 *
 * Retrig Rxy 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 Rxy effect, and triggers a new note when
 *   the counter >= y. This means on a row that has a new note and R03 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 R commands in the row that follows (they can even be interspaced by
 *   other effects).
 *   In Rxy, x = 0 or y = 0 are independant memories. x = 8 is supposed undefined but is the only way
 *   to stop the x volume effect set in a previous command.
 *
 * Effects:
 * -------
 *
 * Volume column is interpreted before effects column so effects may overwrite them.
 *
 * Volumes effects values above maximal values are clipped to maximal value.
 *
 * In all slides effects ?xy (? being A, H, P) 00 is memory, slide is up by x if x > 0
 * and slide down by y in the remaining cases. So if both x and y are set it behaves as x0.
 * For Pxy up means to the right and down to the left.
 *
 * Fine slides EAx and EBy have a seperate memory from Axy.
 * In volume column: volume slides -x, +x and fine slides vx, ^x have no memory.
 *                   panning slides <x and >x have no memory but <0 forces panning to full left.
 *
 * Pitch slides 1xx, 2xx, fine E1x, E2x, extra fine X1x, X2x use each its own memory!!!!!
 *
 * KeyOff, unsets "Sustain" state and sets "Fadeout" state, though if there is no volume envelope
 * volume is set directly to 0. When volume reaches 0, the note is not terminated, an "instrument swap"
 * can still reactivate it.
 *
 * Finetune does not overwrite sample finetune.
 *   Use 2 identical instruments, put +112 as finetune in second. Play the 2 instruments in parrallel.
 *   You have to put an E58 (Fine tune -7) effect along the second note to compensate pitch, not E50.
 *
 * Tremor Txy. 00 is memory. When the effect is present on a row, the counter decreases till
 *   a < 0 value is reached. At this point the switch between on and off occurs and
 *   the counter is reinitialised to x or y. The counter is reset by a new note.
 *   The counter is not updated on frame 0.
 *   As a consequence ontime duration x+1, offtime is y+1, changes of x or y due to a new tremor effect
 *   only take effect when the counter reaches -1 and is reinitialised.
 *   It seems that a seperate flag is set to force volume to 0 when it switches to the off counter
 *   and which is reset by a new note, because if a tremor sequence is stopped while note is off,
 *   the note remains off, meaning that this flag remains in effect.
 *
 * Speed F00, blocks the replay. We won't support it.
 */

typedef struct
{
	struct
	{
		uint8_t     TAG[17];
		uint8_t     Name[20];
		uint8_t     _1a;
		uint8_t     Tracker[20];
		uint8_t     Version[2];
	} tag;
	uint32_t    HdrSize;
	uint16_t    Orders;
	uint16_t    RestartPos;
	uint16_t    Channels;
	uint16_t    Patterns;
	uint16_t    Instruments;
	uint16_t    Flags;
	uint16_t    Speed;
	uint16_t    Tempo;
	uint8_t     Sequence[256];
} LHdr;

typedef struct
{
	uint32_t    HdrSize;
	uint8_t     Packing;
	uint8_t     Rows;
	uint8_t     DataSize[2];
} LPat102;
typedef struct
{
	uint32_t    HdrSize;
	uint8_t     Packing;
	uint8_t     Rows[2];
	uint8_t     DataSize[2];
} LPat103;

#define Size_LPat 9

typedef struct
{
	uint32_t    HdrSize;
	uint8_t     Name[22];
	uint8_t     Type[1];
	uint8_t     Samples[2];
} LInst;

#define Size_LInst 29

typedef struct
{
	uint32_t    SmpHdrSize;
	uint8_t     SampleNumbers[96];
	uint16_t    VolumeEnvelope[24];
	uint16_t    PanningEnvelope[24];
	uint8_t     VolumePoints;
	uint8_t     PanningPoints;
	uint8_t     VolumeSustain;
	uint8_t     VolumeLoopStart;
	uint8_t     VolumeLoopEnd;
	uint8_t     PanningSustain;
	uint8_t     PanningLoopStart;
	uint8_t     PanningLoopEnd;
	uint8_t     VolumeType;
	uint8_t     PanningType;
	uint8_t     VibratoType;
	uint8_t     VibratoSweep;
	uint8_t     VibratoDepth;
	uint8_t     VibratoRate;
	uint16_t    VolumeFadeout;
} LInst2;

typedef struct
{
	uint32_t    Length;
	uint32_t    LoopStart;
	uint32_t    LoopSize;
	uint8_t     Volume;
	int8_t      FineTune;
	uint8_t     Type;
	uint8_t     Panning;
	int8_t      RelTone;
	uint8_t     Reserved;
	uint8_t     Name[22];
} LSmp;

#define XM_Flags_Linear         0x01
#define XM_EnvType_On           0x01
#define XM_EnvType_Sustain      0x02
#define XM_EnvType_Loop         0x04
#define XM_SmpType_NoLoop       0x00
#define XM_SmpType_ForwardLoop  0x01
#define XM_SmpType_PingPong     0x02
#define XM_SmpType_LoopMask     0x03
#define XM_SmpType_16Bit        0x04

#define NoteDelta (Note_Central-12*4)

static const uint8_t TAG_XM[] = "Extended Module: ";
static const uint8_t TypeStr_XM[] = "FastTracker2";
static const uint8_t Tag_text[] = "text";

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

static const char* Error_Pattern_Header = "Invalid pattern header";
static const char* Error_Packed_Pattern = "FastTracker contains unknown pattern packing type";
static const char* Error_Pattern_Rows = "FastTracker contains patterns with more than 256 rows";
static const char* Error_Samples_Per_Instrument = "FastTracker contains more than 16 samples in an instrument";
static const char* Error_Too_Many_Samples = "FastTracker contains more than 255 samples";

void convert_effect_XM(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, Memory
		{
			effect = cmd_upmem1 | flag_cmd_slide_frameN0;

			Pattern_AddEffect_Pitch(pSong, effect, value << 2);
		}
		break;
		case 2: // Pitch: Slide down, Memory
		{
			effect = cmd_downmem2 | flag_cmd_slide_frameN0; // yes, another memory

			Pattern_AddEffect_Pitch(pSong, effect, 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 at speed x and depth y, 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, Memory
		{
			convert_effect_XM(pSong, channel, 0x3, 0, note);
			convert_effect_XM(pSong, channel, 0xa, value, note);
		}
		break;
		case 6: // Pitch: Vibrato 0 - Volume: Slide, Memory
		{
			convert_effect_XM(pSong, channel, 0x4, 0, note);
			convert_effect_XM(pSong, channel, 0xa, value, note);
		}
		break;
		case 7: // Volume: Vibrato (alias Tremolo), Memory
		{
			uint32_t speed = value >> 4;
			value &= 0xf;
			// Vibrate between [-2*y,+2*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 [0, 255]
		{
			if (value >= 128) value++;
			Pattern_AddEffect_Panning(pSong, cmd_set, value);
		}
		break;
		case 9: // Note: Set sample offset
		{
			if (value)
			{
				// define offset
				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, 0xf00);
		}
		break;
		case 0xa: // Volume: Slide (up has priority), Memory
		{
			if (value)
			{
				effect = flag_cmd_slide_frameN0;

				if (value & 0xf0)
				{
					// x0
					effect += cmd_upmem1;
					Pattern_AddEffect_NoteVolume(pSong, effect, (value & 0xf0) >> 2);
				}
				else
				{
					// 0y
					effect += cmd_downmem1;
					Pattern_AddEffect_NoteVolume(pSong, effect, value << 2);
				}
			}
			else
			{
				Pattern_AddEffect_NoteVolume(pSong, cmd_last_slide, 0);
			}
		}
		break;
		case 0xb: // Global: Jump
		{
			// Jumps beyond last order are ignored
			if (value < Song_GetSubSong(pSong, 0)->SeqLen)
				Pattern_AddGlbEffect_Position(pSong, gcmd_pos_jump, value);
			else
				SysLog_BadEffectValue(pSong, 0, oeffect, ovalue);
		}
		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
		{
			Pattern_AddGlbEffect_Position(pSong, gcmd_pos_break, value);
		}
		break;
		case 0xe: // Special effects
		{
			effect = value >> 4;
			value &= 0xf;
			switch(effect)
			{
				case 0: // e0 Amiga Filter
				{
					// Do nothing
				}
				break;
				case 1: // e1 Pitch: Up, Memory
				{
					effect = cmd_pitch_upmem3; // yes, another memory

					Pattern_AddEffect_Pitch(pSong, effect, value << 2);
				}
				break;
				case 2: // e2 Pitch: Down, Memory
				{
					effect = cmd_pitch_downmem4; // yes, another memory

					Pattern_AddEffect_Pitch(pSong, effect, 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 0: value = vibrato_type_sin; break;
						case 1: value = vibrato_type_ramp; break;
						// undefined 3 behaves as 2
						default: value = vibrato_type_square; break;
					}

					if (effect & 4)
						value |= vibrato_type_noretrig;

					Pattern_AddEffect_Pitch(pSong, cmd_vibrato_type, value);
				}
				break;
				case 5: //e5 Note: Set fine tune [0 = min ... 8 = no fine tune ... f = max]
				{
					int32_t svalue = value - 8;

					// only if a note is defined at the same time
					if (note) Pattern_AddEffect_Instrument(pSong, cmd_sample_set_finetune, svalue << 5);
				}
				break;
				case 6: //e6 Note: 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 0: value = vibrato_type_sin; break;
						case 1: value = vibrato_type_ramp; break;
						// undefined 3 behaves as 2
						default: value = vibrato_type_square; break;
					}

					if (effect & 4)
						value |= vibrato_type_noretrig;

					Pattern_AddEffect_NoteVolume(pSong, cmd_vibrato_type, value);
				}
				break;
				case 9: // e9 Note: Retrig note every x ticks
				{
					Pattern_AddEffect_RetrigCounter(pSong, cmd_retrig_count_type_XMOld, value + 1);
					Pattern_AddEffect_Instrument(pSong, cmd_sample_note_retrig, 8);
				}
				break;
				case 0xa: // ea Volume: Up, Memory seperate from Axy
				{
					effect = cmd_upmem2;

					if (value) effect |= flag_cmd_slide_frame0;

					Pattern_AddEffect_NoteVolume(pSong, effect, value << 2);
				}
				break;
				case 0xb: // eb Volume: Down, Memory seperate from Axy
				{
					effect = cmd_downmem2;

					if (value) effect |= flag_cmd_slide_frame0;

					Pattern_AddEffect_NoteVolume(pSong, effect, 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 after 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 (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 to 64
		{
			if (value > 64) value = 64;
			Pattern_AddGlbEffect_Volume(pSong, cmd_set, value << 2);
		}
		break;
		case 0x11: // h, Global Volume: Slide (up has priority), Memory
		{
			if (value)
			{
				effect = flag_cmd_slide_frameN0;

				if (value & 0xf0)
				{
					// x0
					effect += cmd_upmem1;
					Pattern_AddGlbEffect_Volume(pSong, effect, (value & 0xf0) >> 2);
				}
				else
				{
					// y0
					effect += cmd_downmem1;
					Pattern_AddGlbEffect_Volume(pSong, effect, value << 2);
				}
			}
			else
			{
				Pattern_AddGlbEffect_Volume(pSong, cmd_last_slide, 0);
			}
		}
		break;
		case 0x14: // k, Note: Key off
		{
			Pattern_AddEffect_Instrument(pSong, cmd_sample_note_action_off, value);
		}
		break;
		case 0x15: // l, Volume: Envelope position
		{
			Pattern_AddEffect_NoteVolume(pSong, cmd_volume_envelope_pos, value);
		}
		break;
		case 0x19: // p, Panning: Slide (right has priority), range [0,255], Memory
		{
			if (value)
			{
				effect = flag_cmd_slide_frameN0;

				if (value & 0xf0)
				{
					// x0
					effect += cmd_upmem1;
					Pattern_AddEffect_Panning(pSong, effect, (value & 0xf0) >> 4);
				}
				else
				{
					// 0y
					effect += cmd_downmem1;
					Pattern_AddEffect_Panning(pSong, effect, value);
				}
			}
			else
			{
				Pattern_AddEffect_Panning(pSong, cmd_last_slide, 0);
			}
		}
		break;
		case 0x1b: // r, Note: Retrig note every y ticks with volume change type x, Memory
		{
			uint32_t eff = 0;

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

			Pattern_AddEffect_RetrigCounter(pSong, cmd_retrig_count_type_XM, value);
			Pattern_AddEffect_Instrument(pSong, cmd_sample_note_retrig, eff);
		}
		break;
		case 0x1d: // t, Volume: Tremor on for x ticks, off for y ticks
		{
			uint32_t val;

			val = value >> 4;
			value &= 0xf;
			Pattern_AddEffect_NoteVolume(pSong, cmd_volume_tremor_XM, Effect_Tremor(value, val));
		}
		break;
		case 0x21: // x Special effects
		{
			effect = value >> 4;
			value &= 0xf;

			switch(effect)
			{
				case 1: // x1, Pitch: Up fine, Memory
				{
					effect = cmd_pitch_upmem5; // yes, another memory

					Pattern_AddEffect_Pitch(pSong, effect, value);
				}
				break;
				case 2: // x2, Pitch: Down fine, Memory
				{
					effect = cmd_pitch_downmem6; // yes, another memory

					Pattern_AddEffect_Pitch(pSong, effect, value);
				}
				break;
				default:
				{
					// Unknown effects
					SysLog_BadEffect(pSong, 0, oeffect, ovalue);
				}
			}
		}
		break;
		default:
		{
			// Unknown effects
			SysLog_BadEffect(pSong, 0, oeffect, ovalue);
		}
	}
}

static const _kernel_oserror* Pattern_XM
	( 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, k;
	const uint8_t*      pPos2;
	uint32_t            Info, Note, Inst, Cmd;

	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) && (pPos < pEnd); k++)
		{
			val = *pPos++;
			// packing mark ?
			if (val & 0x80)
			{
				// yes
				Note = 256;
				Info = val & 0x1f;
			}
			else
			{
				// no, is note and all 4 bytes follow
				Note = val;
				Info = 0x1e;
			}
			pPos2 = pPos;
			if (Info & 0x01) pPos2++;
			if (Info & 0x02) pPos2++;
			if (Info & 0x04) pPos2++;
			if (Info & 0x08) pPos2++;
			if (Info & 0x10) pPos2++;
			if (pPos2 > pEnd) break;
			// Read Note?
			if (Info & 0x01) Note = *pPos++;
			if (Note)
			{
				if (Note < 97)
					Note += NoteDelta - 1;
				else if (Note == 97)
					Note = Note_OffXM;
				else
				{
					if (Note < 256) SysLog_BadNote(pSong, 0, k, Note);
					Note = 0;
				}
			}
			if (Info & 0x02) Inst = *pPos++;
			else Inst = 0;
			Loaders_StartChannel(pSong, k, Note, Inst);
			// Treat volume column before effect
			if (Info & 0x04)
			{
				val = *pPos++;
				if (val < 0x10)
				{
					// [&0-0F] nothing
					if (val) SysLog_BadEffectValue(pSong, 0, 256, val);
				}
				else if (val <= 0x50)
				{
					// [&10-50] volume
					Pattern_AddEffect_NoteVolume(pSong
						, cmd_set
						, (val - 0x10) << 2);
				}
				else if (val < 0x60)
				{
					// [&51-5F] nothing
					SysLog_BadEffectValue(pSong, 0, 256, val);
				}
				else if (val < 0x70)
				{
					// [&60-6F] volume slide down, No memory
					val -= 0x60;

					if (val)
					{
						Pattern_AddEffect_NoteVolume(pSong
							, cmd_down | flag_cmd_slide_frameN0
							, val << 2);
					}
				}
				else if (val < 0x80)
				{
					// [&70-7F] volume slide up, No memory
					val -= 0x70;

					if (val)
					{
						Pattern_AddEffect_NoteVolume(pSong
							, cmd_up | flag_cmd_slide_frameN0
							, val << 2);
					}
				}
				else if (val < 0x90)
				{
					// [&80-&8F] fine volume down, No memory
					val -= 0x80;

					if (val)
					{
						Pattern_AddEffect_NoteVolume(pSong
							, cmd_down | flag_cmd_slide_frame0
							, val << 2);
					}
				}
				else if (val < 0xa0)
				{
					// [&80-&8F] fine volume up, No memory
					val -= 0x90;

					if (val)
					{
						Pattern_AddEffect_NoteVolume(pSong
							, cmd_up | flag_cmd_slide_frame0
							, val << 2);
					}
				}
				else if (val < 0xb0)
				{
					// [&A0-&AF] vibrato speed
					Pattern_AddEffect_Pitch(pSong
						, cmd_vibrato_speed
						, (val - 0xa0) << 2);
				}
				else if (val < 0xc0)
				{
					// [&B0-&BF] vibrato depth, but do not vibrate
					val -= 0xb0;
					// Vibrate between [-2*y,+2*y]
					val <<= 3; // << 2 for range conversion and << 1 for depth
					Pattern_AddEffect_Pitch(pSong
						, cmd_vibrato_depth | flag_cmd_vibrato_frameN0
						, val);
				}
				else if (val < 0xd0)
				{
					// [&C0-CF] panning
					val -= 0xc0;
					val += val << 4;
					if (val >= 128) val++;
					Pattern_AddEffect_Panning(pSong
						, cmd_set
						, val);
				}
				else if (val < 0xe0)
				{
					// [&D0-DF] panning slide left, No memory
					val -= 0xd0;

					if (val)
					{
						Pattern_AddEffect_Panning(pSong
							, cmd_down | flag_cmd_slide_frameN0
							, val);
					}
					else
					{
						// Bug or feature?
						Pattern_AddEffect_Panning(pSong, cmd_set, 0);
					}
				}
				else if (val < 0xf0)
				{
					// [&E0-EF] panning slide right, No memory
					val -= 0xe0;

					if (val)
					{
						Pattern_AddEffect_Panning(pSong
							, cmd_up | flag_cmd_slide_frameN0
							, val);
					}
				}
				else
				{
					// [&F0-FF] portamento at 4 times normal speed
					Pattern_AddEffect_Pitch(pSong
						, cmd_portamento + flag_cmd_slide_frameN0
						, (val - 0xf0) << 6);
				}
			}
			// Read command
			if (Info & 0x08) Cmd = *pPos++;
			else Cmd = 0;
			if (Info & 0x10) val = *pPos++;
			else val = 0;
			convert_effect_XM(pSong, k, 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_XM(SongHdr* pSong)
{
	const _kernel_oserror* err = NULL;
	SubSong*    pSubSong = Song_GetSubSong(pSong, 0);
	LHdr*       pLHdr = NULL;
	LInst*      pLInst = NULL;
	LInst2*     pLInst2 = NULL;
	LSmp*       pLSmp = NULL;
	LPat103*    pLPat = NULL;
	uint8_t*    pLTrack = NULL;
	Pattern*    pPattern;
	Sample*     pSmp = pSong->pSamples;
	Sample*     pFirstSmp = pSmp;
	Instrument* pInstr;
	uint32_t    val, Size;
	int         i, j;
	uint32_t    Offset;

	// First read all information required for memory allocation
	err = Loaders_Alloc(pSong, (void**) &pLHdr, sizeof(*pLHdr));
	if (err) goto loader_err;
	err = Loaders_Alloc(pSong, (void**) &pLInst, sizeof(*pLInst));
	if (err) goto loader_err;
	err = Loaders_Alloc(pSong, (void**) &pLInst2, sizeof(*pLInst2));
	if (err) goto loader_err;
	err = Loaders_Alloc(pSong, (void**) &pLSmp, sizeof(*pLSmp));
	if (err) goto loader_err;
	err = Loaders_Alloc(pSong, (void**) &pLPat, sizeof(*pLPat));
	if (err) goto loader_err;

	pSong->Flags = Song_Flag_Instruments
	             | Song_Flag_InstNoNote_Playing
	             | Song_Flag_XMFadeout
	             | Song_Flag_KeyoffLosesPrevNote;
	pSong->MinPitch = NoteDelta;
	pSong->MaxPitch = NoteDelta + 118 - 1; // cf reltone
	pSong->pSampleNoMap->Type = 0; // Non stored instrument as defined with volume 0

	//which type do we try to load?
	err = FileLoad_SetPos(pSong, 0);
	if (err) goto loader_err;
	err = FileLoad_Read(pSong, pLHdr, sizeof(*pLHdr));
	if (err) goto loader_err;

	// is XM ?
	if (Loaders_strncmp(pLHdr->tag.TAG, TAG_XM, sizeof(pLHdr->tag.TAG)))
	{
		err = Loaders_NotThisType;
		goto loader_err;
	}
	// Ensure header size, modified to cope with OpenMPT
	if (pLHdr->HdrSize < 0x14 + pSubSong->SeqLen) //sizeof(LHdr) - sizeof(pLHdr->tag)))
	{
		err = Loaders_Error(pSong, sizeof(pLHdr->tag), "Invalid song header");
		goto loader_err;
	}
	pSong->Version = pLHdr->tag.Version[0] + 100*pLHdr->tag.Version[1];

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

	// ... file type ptr
	pSong->pType = TypeStr_XM;

	pSubSong->SeqLen = pLHdr->Orders;
	if (!pSubSong->SeqLen || (pSubSong->SeqLen > 256))
	{
		err = Loaders_InvalidSong(pSong, 64, &pSubSong->SeqLen, pSubSong->SeqLen);
		goto loader_err;
	}
	pSubSong->RestartPos = pLHdr->RestartPos;
	pSong->Channels = pLHdr->Channels;
	if (!pSong->Channels || (pSong->Channels > 32))
	{
		err = Loaders_InvalidSong(pSong, 68, &pSong->Channels, pSong->Channels);
		goto loader_err;
	}
	pSong->Patterns = pLHdr->Patterns;
	if (!pSong->Patterns || (pSong->Patterns > 256)) // cf. sequence
	{
		err = Loaders_InvalidSong(pSong, 70, &pSong->Patterns, pSong->Patterns);
		goto loader_err;
	}
	pSong->Instruments = pLHdr->Instruments;
	if (!pSong->Instruments || (pSong->Instruments > 255))
	{
		err = Loaders_InvalidSong(pSong, 72, &pSong->Instruments, pSong->Instruments);
		goto loader_err;
	}
	if (pLHdr->Flags & XM_Flags_Linear)
		pSong->Flags |= Song_Flag_Linear;
	if (pLHdr->Speed) pSubSong->Defaults.Speed = pLHdr->Speed;
	if (pLHdr->Tempo) pSubSong->Defaults.Tempo = pLHdr->Tempo;

	// fill the sequence
	err = Loaders_AllocSeq(pSong, pSubSong);
	if (err) goto loader_err;

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

	Offset = pLHdr->HdrSize + sizeof(pLHdr->tag);

	if (pSong->Version >= 104)
	{
		// fill the patterns
		for (i = 0, pPattern = pSong->pPatterns; i < pSong->Patterns; i++, pPattern++)
		{
			err = FileLoad_SetPos(pSong, Offset);
			if (err) goto loader_err;

			// Read pattern header
			err = FileLoad_Read(pSong, pLPat, Size_LPat);
			if (err) goto loader_err;
			// Ensure header size
			if (pLPat->HdrSize < Size_LPat)
			{
				err = Loaders_Error(pSong, Offset, Error_Pattern_Header);
				goto loader_err;
			}

			// Packing unsupported
			if (pLPat->Packing)
			{
				err = Loaders_Error(pSong, Offset, Error_Packed_Pattern);
				goto loader_err;
			}
			// Rows in pattern
			pPattern->Rows = pLPat->Rows[0] + (pLPat->Rows[1] << 8);
			if (!pPattern->Rows || (pPattern->Rows > song_max_rows))
			{
				err = Loaders_Error(pSong, Offset, Error_Pattern_Rows);
				goto loader_err;
			}
			Size = pLPat->DataSize[0] + (pLPat->DataSize[1] << 8);

			Offset += pLPat->HdrSize;

			// Read pattern data
			err = FileLoad_SetPos(pSong, Offset);
			if (err) goto loader_err;

			if (Size)
			{
				err = Loaders_Alloc(pSong, (void**) &pLTrack, Size);
				if (err) goto loader_err;
				err = FileLoad_Read(pSong, pLTrack, Size);
				if (err) goto loader_err;
			}
			err = Pattern_XM(pSong, i, pPattern, pLTrack, pLTrack + Size);
			if (err) goto loader_err;

			Loaders_Free(pSong, pLTrack);
			pLTrack = NULL;

			Offset += Size;
		}

		// read instruments info
		for (i = 0, pInstr = pSong->pInstruments, pSmp = pSong->pSamples
			; i < pSong->Instruments
			; i++, pInstr++)
		{
			uint32_t Samples;
			uint32_t SmpMap[16];

			pFirstSmp = pSmp;

			err = FileLoad_SetPos(pSong, Offset);
			if (err) goto inst_err;

			// Read instrument header
			err = FileLoad_Read(pSong, pLInst, Size_LInst);
			if (err) goto inst_err;

			// Ensure header size
			if (pLInst->HdrSize < Size_LInst)
			{
				err = Loaders_Error(pSong, Offset, "Invalid instrument header");
				goto inst_err;
			}

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

			// read number of samples
			Samples = pLInst->Samples[0] + (pLInst->Samples[1] << 8);
			if (Samples > 16)
			{
				err = Loaders_Error(pSong, Offset, Error_Samples_Per_Instrument);
				goto inst_err;
			}

			// init note table
			memset(SmpMap, 0, sizeof(SmpMap));
			err = Loaders_AllocMapTable(pSong, &pInstr->pNotesMap);
			if (err) goto loader_err;

			if (Samples)
			{
				// read sub header
				err = FileLoad_Read(pSong, pLInst2, sizeof(*pLInst2));
				if (err) goto inst_err4;
				// Ensure header size
				if (pLInst2->SmpHdrSize < sizeof(LSmp))
				{
					err = Loaders_Error(pSong, Offset, "Invalid sample header");
					goto inst_err4;
				}

				// read volume envelope
				{
					Envelope* pEnv = &pInstr->VolumeEnvelope;

					if (pLInst2->VolumePoints > 12) pEnv->Points = 12;
					else pEnv->Points = pLInst2->VolumePoints;
					pEnv->Flags = 0;
					if (pLInst2->VolumeType & 1) pEnv->Flags |= Envelope_Flag_On;
					if (pLInst2->VolumeType & 2) pEnv->Flags |= Envelope_Flag_Sustain;
					if (pLInst2->VolumeType & 4) pEnv->Flags |= Envelope_Flag_Loop;
					pEnv->LoopStart = pLInst2->VolumeLoopStart;
					pEnv->LoopEnd = pLInst2->VolumeLoopEnd;
					pEnv->SustainStart =
					pEnv->SustainEnd = pLInst2->VolumeSustain;

					err = Loaders_Alloc(pSong, (void**) &pEnv->pTable, 48);
					if (err) goto loader_err;
					for (j = 0; j < 24; j += 2)
					{
						val = pLInst2->VolumeEnvelope[j+1] << 2;
						if (val > 256) val = 256;
						pEnv->pTable[j>>1] = pLInst2->VolumeEnvelope[j] + (val << 16);
					}
				}
				// read panning envelope
				// read volume envelope
				{
					Envelope* pEnv = &pInstr->PanningEnvelope;

					if (pLInst2->PanningPoints > 12) pEnv->Points = 12;
					else pEnv->Points = pLInst2->PanningPoints;
					pEnv->Flags = 0;
					if (pLInst2->PanningType & 1) pEnv->Flags |= Envelope_Flag_On;
					if (pLInst2->PanningType & 2) pEnv->Flags |= Envelope_Flag_Sustain;
					if (pLInst2->PanningType & 4) pEnv->Flags |= Envelope_Flag_Loop;
					pEnv->LoopStart = pLInst2->PanningLoopStart;
					pEnv->LoopEnd = pLInst2->PanningLoopEnd;
					pEnv->SustainStart =
					pEnv->SustainEnd = pLInst2->PanningSustain;

					err = Loaders_Alloc(pSong, (void**) &pEnv->pTable, 48);
					if (err) goto loader_err;
					for (j = 0; j < 24; j += 2)
					{
						val = pLInst2->PanningEnvelope[j+1] << 2;
						if (val > 256) val = 256;
						pEnv->pTable[j>>1] = pLInst2->PanningEnvelope[j] + (val << 16);
					}
				}
				// read vibrato type
				switch (pLInst2->VibratoType)
				{
					case 1: pInstr->Vibrato.Type = vibrato_type_square | vibrato_type_inverted; break;
					case 2: pInstr->Vibrato.Type = vibrato_type_ramp | vibrato_type_inverted; break;
					case 3: pInstr->Vibrato.Type = vibrato_type_ramp; break;
					default: pInstr->Vibrato.Type = vibrato_type_sin | vibrato_type_inverted;
				}
				// read vibrato sweep
				pInstr->Vibrato.Type |= vibrato_type_sweep;
				pInstr->Vibrato.Rate = pLInst2->VibratoSweep;
				// read vibrato depth (0 - F)
				pInstr->Vibrato.Depth = pLInst2->VibratoDepth << 2;
				// read vibrato rate
				pInstr->Vibrato.Speed = pLInst2->VibratoRate;
				// read volume fadeout
				pInstr->FadeoutVolume = pLInst2->VolumeFadeout << 1;
			}

			// Skip header
			Offset += pLInst->HdrSize;

			// read samples
			for (j = 0; j < Samples; j++)
			{
				err = FileLoad_SetPos(pSong, Offset);
				if (err) goto inst_err4;
				// Read sample header
				err = FileLoad_Read(pSong, pLSmp, sizeof(*pLSmp));
				if (err) goto inst_err4;

				Offset += pLInst2->SmpHdrSize;

				// Ignore empty samples
				if (pLSmp->Length)
				{
					SmpMap[j] = 1 + (pSmp - pSong->pSamples);
					if (SmpMap[j] > 256)
					{
						err = Loaders_Error(pSong, Offset, Error_Too_Many_Samples);
						goto loader_err;
					}

					pSmp->Size = pLSmp->Length;
					pSmp->Frequency = 8394;
					pSmp->LoopStart = pLSmp->LoopStart;
					pSmp->LoopEnd = pLSmp->LoopStart + pLSmp->LoopSize;

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

					// read fine tune
					pSmp->FineTune = pLSmp->FineTune << 24;

					pSmp->Type = Smp_Type_Set_Panning;
					if (pLSmp->Type & 0x10)
						pSmp->Type |= Smp_Type_16bit;
					if (pLSmp->Type & 0x3)
						pSmp->Type |= Smp_Type_Loop;
					if (pLSmp->Type & 0x2)
						pSmp->Type |= Smp_Type_Loop_Bidi;

					// 16bit sizes are corrected later

					// read panning
					pSmp->Panning = pLSmp->Panning;
					if (pSmp->Panning >= 128) pSmp->Panning++;

					// read reltone
					pSmp->RelTone = pLSmp->RelTone << 8;

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

					// Increase sample pointer
					pSmp++;
				}
				else
				{
					SmpMap[j] = 0;
				}
			}

			err = FileLoad_SetPos(pSong, Offset);
			if (err) goto inst_err4;

			// read the samples data
			for(Samples = 0; pFirstSmp < pSmp; pFirstSmp++, Samples++)
			{
				int k;

				// prepare next offset
				Offset += pFirstSmp->Size;

				// correct that only know for fucking files with 16-bit samples with odd byte size!!!
				if (pFirstSmp->Type & Smp_Type_16bit)
				{
					pFirstSmp->Size >>= 1;
					pFirstSmp->LoopStart >>= 1;
					pFirstSmp->LoopEnd >>= 1;
				}

				// read the sample
				err = FileLoad_ReadSample(pSong, pFirstSmp);
				if (err) goto loader_err;

				// convert sample from diff to absolute
				if (pFirstSmp->Type & Smp_Type_16bit)
				{
					int16_t* pc;

					val = 0;
					for (k = pFirstSmp->Size, pc = pFirstSmp->Ptr; k; k--, pc++)
					{
						val += *pc;
						*pc = val;
					}
				}
				else
				{
					signed char* pc;

					val = 0;
					for (k = pFirstSmp->Size, pc = pFirstSmp->Ptr; k; k--, pc++)
					{
						val += *pc;
						*pc = val;
					}
				}
			}

inst_err4:
			if (Samples)
			{
				NoteMap* pn;
				// read note table
				for (j = 0, pn = pInstr->pNotesMap + NoteDelta; j < 96; j++, pn++)
				{
					pn->Volume = 255;
					pn->Pitch = (NoteDelta + j) << 8;
					val = pLInst2->SampleNumbers[j];
					pn->SampleNr = (val < 16) ? SmpMap[val] : 0;
				}
			}
			if (err) goto inst_err;
		}
	}
	else
	{
		LPat102* pLPat2 = (LPat102*) pLPat;

		// read instruments info
		for (i = 0, pInstr = pSong->pInstruments, pSmp = pSong->pSamples
			; i < pSong->Instruments
			; i++, pInstr++)
		{
			uint32_t Samples;
			uint32_t SmpMap[16];

			pFirstSmp = pSmp;

			err = FileLoad_SetPos(pSong, Offset);
			if (err) goto loader_err;

			// Read instrument header
			err = FileLoad_Read(pSong, pLInst, Size_LInst);
			if (err) goto loader_err;

			// Ensure header size
			if (pLInst->HdrSize < Size_LInst)
			{
				err = Loaders_Error(pSong, Offset, "Invalid instrument header");
				goto loader_err;
			}

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

			// read number of samples
			Samples = pLInst->Samples[0] + (pLInst->Samples[1] << 8);
			if (Samples > 16)
			{
				err = Loaders_Error(pSong, Offset, Error_Samples_Per_Instrument);
				goto loader_err;
			}

			// init note table
			memset(SmpMap, 0, sizeof(SmpMap));
			err = Loaders_AllocMapTable(pSong, &pInstr->pNotesMap);
			if (err) goto loader_err;

			if (Samples)
			{
				// read sub header
				err = FileLoad_Read(pSong, pLInst2, sizeof(*pLInst2));
				if (err) goto inst_err2;
				// Ensure header size
				if (pLInst2->SmpHdrSize < sizeof(LSmp))
				{
					err = Loaders_Error(pSong, Offset, "Invalid sample header");
					goto loader_err;
				}

				// read volume envelope
				{
					Envelope* pEnv = &pInstr->VolumeEnvelope;

					// read nr volume points
					if (pLInst2->VolumePoints > 12) pEnv->Points = 12;
					else pEnv->Points = pLInst2->VolumePoints;
					pEnv->Flags = 0;
					if (pLInst2->VolumeType & 1) pEnv->Flags |= Envelope_Flag_On;
					if (pLInst2->VolumeType & 2) pEnv->Flags |= Envelope_Flag_Sustain;
					if (pLInst2->VolumeType & 4) pEnv->Flags |= Envelope_Flag_Loop;
					pEnv->LoopStart = pLInst2->VolumeLoopStart;
					pEnv->LoopEnd = pLInst2->VolumeLoopEnd;
					pEnv->SustainStart =
					pEnv->SustainEnd = pLInst2->VolumeSustain;

					err = Loaders_Alloc(pSong, (void**) &pEnv->pTable, 48);
					if (err) goto loader_err;
					for (j = 0; j < 24; j += 2)
					{
						val = pLInst2->VolumeEnvelope[j+1] << 2;
						if (val > 256) val = 256;
						pEnv->pTable[j>>1] = pLInst2->VolumeEnvelope[j] + (val << 16);
					}
				}
				// read panning envelope
				{
					Envelope* pEnv = &pInstr->PanningEnvelope;

					// read nr panning points
					if (pLInst2->PanningPoints > 12) pEnv->Points = 12;
					else pEnv->Points = pLInst2->PanningPoints;
					pEnv->Flags = 0;
					if (pLInst2->PanningType & 1) pEnv->Flags |= Envelope_Flag_On;
					if (pLInst2->PanningType & 2) pEnv->Flags |= Envelope_Flag_Sustain;
					if (pLInst2->PanningType & 4) pEnv->Flags |= Envelope_Flag_Loop;
					pEnv->LoopStart = pLInst2->PanningLoopStart;
					pEnv->LoopEnd = pLInst2->PanningLoopEnd;
					pEnv->SustainStart =
					pEnv->SustainEnd = pLInst2->PanningSustain;

					err = Loaders_Alloc(pSong, (void**) &pEnv->pTable, 48);
					if (err) goto loader_err;
					for (j = 0; j < 24; j += 2)
					{
						val = pLInst2->PanningEnvelope[j+1] << 2;
						if (val > 256) val = 256;
						pEnv->pTable[j>>1] = pLInst2->PanningEnvelope[j] + (val << 16);
					}
				}

				// read vibrato type
				switch (pLInst2->VibratoType)
				{
					case 1: pInstr->Vibrato.Type = vibrato_type_square | vibrato_type_inverted; break;
					case 2: pInstr->Vibrato.Type = vibrato_type_ramp | vibrato_type_inverted; break;
					case 3: pInstr->Vibrato.Type = vibrato_type_ramp; break;
					default: pInstr->Vibrato.Type = vibrato_type_sin | vibrato_type_inverted;
				}
				// read vibrato sweep
				pInstr->Vibrato.Type |= vibrato_type_sweep;
				pInstr->Vibrato.Rate = pLInst2->VibratoSweep;
				// read vibrato depth (0 - F)
				pInstr->Vibrato.Depth = pLInst2->VibratoDepth << 2;
				// read vibrato rate
				pInstr->Vibrato.Speed = pLInst2->VibratoRate;
				// read volume fadeout
				pInstr->FadeoutVolume = pLInst2->VolumeFadeout << 1;
			}

			// Skip header
			Offset += pLInst->HdrSize;

			// read samples
			for (j = 0; j < Samples; j++)
			{
				err = FileLoad_SetPos(pSong, Offset);
				if (err) goto inst_err2;
				// Read sample header
				err = FileLoad_Read(pSong, pLSmp, sizeof(*pLSmp));
				if (err) goto inst_err2;

				Offset += pLInst2->SmpHdrSize;

				// Ignore empty samples
				if (pLSmp->Length)
				{
					SmpMap[j] = 1 + (pSmp - pSong->pSamples);
					if (SmpMap[j] > 256)
					{
						err = Loaders_Error(pSong, Offset, Error_Too_Many_Samples);
						goto loader_err;
					}

					pSmp->Size = pLSmp->Length;
					pSmp->Frequency = 8394;
					pSmp->LoopStart = pLSmp->LoopStart;
					pSmp->LoopEnd = pLSmp->LoopStart + pLSmp->LoopSize;

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

					// read fine tune
					pSmp->FineTune = pLSmp->FineTune << 24;

					pSmp->Type = Smp_Type_Set_Panning;
					if (pLSmp->Type & 0x10)
						pSmp->Type |= Smp_Type_16bit;
					if (pLSmp->Type & 0x3)
						pSmp->Type |= Smp_Type_Loop;
					if (pLSmp->Type & 0x2)
						pSmp->Type |= Smp_Type_Loop_Bidi;

					// 16bit sizes are corrected later

					// read panning
					pSmp->Panning = pLSmp->Panning;
					if (pSmp->Panning >= 128) pSmp->Panning++;

					// read reltone
					pSmp->RelTone = pLSmp->RelTone << 8;

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

					// Increase sample pointer
					pSmp++;
				}
				else
				{
					SmpMap[j] = 0;
				}
			}

inst_err2:
			if (Samples)
			{
				NoteMap* pn;
				// read note table
				for (j = 0, pn = pInstr->pNotesMap + NoteDelta; j < 96; j++, pn++)
				{
					pn->Volume = 255;
					pn->Pitch = (NoteDelta + j) << 8;
					val = pLInst2->SampleNumbers[j];
					pn->SampleNr = (val < 16) ? SmpMap[val] : 0;
				}
			}
			if (err) goto loader_err; // not inst_err as we cannot read the patterns
		}

		// fill the patterns
		for (i = 0, pPattern = pSong->pPatterns; i < pSong->Patterns; i++, pPattern++)
		{
			err = FileLoad_SetPos(pSong, Offset);
			if (err) goto loader_err;

			if (pSong->Version == 103)
			{
				// Read pattern header
				err = FileLoad_Read(pSong, pLPat, Size_LPat);
				if (err) goto loader_err;
				// Ensure header size
				if (pLPat->HdrSize < Size_LPat)
				{
					err = Loaders_Error(pSong, Offset, Error_Pattern_Header);
					goto loader_err;
				}

				// Packing unsupported
				if (pLPat->Packing)
				{
					err = Loaders_Error(pSong, Offset, Error_Packed_Pattern);
					goto loader_err;
				}
				// Rows in pattern
				pPattern->Rows = pLPat->Rows[0] + (pLPat->Rows[1] << 8);
				if (!pPattern->Rows || (pPattern->Rows > song_max_rows))
				{
					err = Loaders_Error(pSong, Offset, Error_Pattern_Rows);
					goto loader_err;
				}
				Size = pLPat->DataSize[0] + (pLPat->DataSize[1] << 8);

				Offset += pLPat->HdrSize;
			}
			else
			{
				err = FileLoad_Read(pSong, pLPat2, sizeof(*pLPat2));
				if (err) goto loader_err;
				// Ensure header size
				if (pLPat2->HdrSize < sizeof(*pLPat2))
				{
					err = Loaders_Error(pSong, Offset, Error_Pattern_Header);
					goto loader_err;
				}

				// Packing unsupported
				if (pLPat2->Packing)
				{
					err = Loaders_Error(pSong, Offset, Error_Packed_Pattern);
					goto loader_err;
				}
				// Rows in pattern
				pPattern->Rows = pLPat2->Rows + 1;
				Size = pLPat2->DataSize[0] + (pLPat2->DataSize[1] << 8);

				Offset += pLPat2->HdrSize;
			}

			// Read pattern data
			err = FileLoad_SetPos(pSong, Offset);
			if (err) goto loader_err;

			if (Size)
			{
				err = Loaders_Alloc(pSong, (void**) &pLTrack, Size);
				if (err) goto loader_err;
				err = FileLoad_Read(pSong, pLTrack, Size);
				if (err) goto loader_err;
			}
			err = Pattern_XM(pSong, i, pPattern, pLTrack, pLTrack + Size);
			if (err) goto loader_err;

			Loaders_Free(pSong, pLTrack);
			pLTrack = NULL;

			Offset += Size;
		}

		err = FileLoad_SetPos(pSong, Offset);
		if (err) goto inst_err;

		// read the samples data
		for(pFirstSmp = pSong->pSamples; pFirstSmp < pSmp; pFirstSmp++)
		{
			int k;

			// prepare next offset
			Offset += pFirstSmp->Size;

			// correct that only know for fucking files with 16-bit samples with odd byte size!!!
			if (pFirstSmp->Type & Smp_Type_16bit)
			{
				pFirstSmp->Size >>= 1;
				pFirstSmp->LoopStart >>= 1;
				pFirstSmp->LoopEnd >>= 1;
			}

			// read the sample
			err = FileLoad_ReadSample(pSong, pFirstSmp);
			if (err) goto loader_err;

			// convert sample from diff to absolute
			if (pFirstSmp->Type & Smp_Type_16bit)
			{
				int16_t* pc;

				val = 0;
				for (k = pFirstSmp->Size, pc = pFirstSmp->Ptr; k; k--, pc++)
				{
					val += *pc;
					*pc = val;
				}
			}
			else
			{
				signed char* pc;

				val = 0;
				for (k = pFirstSmp->Size, pc = pFirstSmp->Ptr; k; k--, pc++)
				{
					val += *pc;
					*pc = val;
				}
			}
		}
	}

inst_err:
	if (err)
	{
		SysLog_FileCorrupted(SysLog_Medium, pSong, 0, "XM instrument %d corrupted", 1 + (pInstr - pSong->pInstruments));

		// Last samples may be bad, keep them as correct to avoid possible memory leeks
		// and just reset samples length
		for (; pFirstSmp < pSmp; pFirstSmp++)
			pFirstSmp->Size = 0;

		err = NULL;
	}

	// Read comments
	if (!FileLoad_ReadInt(pSong, &val, 4)
	&&  (val == TAG(text)))
	{
		if (!FileLoad_ReadInt(pSong, &val, 4))
		{
			pSong->CommentsLen = val;
			FileLoad_ReadString(pSong, &pSong->pComments, pSong->CommentsLen, false);
		}
	}

loader_err:
	// store nr of samples
	// do it here to avoid memory leeks in case of error
	pSong->Samples = pSmp - pSong->pSamples;

	Loaders_Free(pSong, pLHdr);
	Loaders_Free(pSong, pLInst);
	Loaders_Free(pSong, pLInst2);
	Loaders_Free(pSong, pLSmp);
	Loaders_Free(pSong, pLPat);
	Loaders_Free(pSong, pLTrack);

	return err;
}

