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

/*
 * Channels: [1...16?]
 * Patterns: [1...256]
 * Rows    : [1...255?]
 * Orders  : [1...65535]
 * 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-7]
 * 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 or logarithmic.
 * Panning : [0...255].
 * Sampling: 8363 Hz (frequency is turned into a relative tone).
 *
 * Notes:
 * Effects have no memory except for portamento and vibrato.
 * This means that effect value 0 as usually no effect.
 */

#define NoteDelta (Note_Central-12*4)

static const uint8_t Tag_PSM[] = "PSM ";
static const uint8_t Tag_FILE[] = "FILE";
static const uint8_t Tag_TITL[] = "TITL";
//static const uint8_t Tag_SDFT[] = "SDFT";
static const uint8_t Tag_PBOD[] = "PBOD";
static const uint8_t Tag_SONG[] = "SONG";
//static const uint8_t Tag_DATE[] = "DATE";
static const uint8_t Tag_OPLH[] = "OPLH";
//static const uint8_t Tag_PATT[] = "PATT";
//static const uint8_t Tag_DSAM[] = "DSAM";
static const uint8_t Tag_DSMP[] = "DSMP";
static const uint8_t Typestr_PSM[] = "Protracker Studio";
static const char Invalid_Block[] = "Invalid block";

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

typedef struct
{
	uint32_t    Channel;
	uint32_t    Note;
	bool        NewFormat;
} ChannelInfo;

static void convert_effect(SongHdr* pSong, const ChannelInfo* pChInfo, uint32_t effect, uint32_t value)
{
#ifdef USELOG
	uint32_t oeffect = effect;
	uint32_t ovalue = value;
#endif

	switch(effect)
	{
		case 1: // Volume: Up, No memory
		{
			effect = cmd_up | flag_cmd_slide_frame0;
			Pattern_AddEffect_NoteVolume(pSong, effect, value << (pChInfo->NewFormat ? 2 : 1));
		}
		break;
		case 2: // Volume: Slide up, No memory
		{
			effect = cmd_up | flag_cmd_slide_frameN0;
			Pattern_AddEffect_NoteVolume(pSong, effect, value << (pChInfo->NewFormat ? 2 : 1));
		}
		break;
		case 3: // Volume: Down, No memory
		{
			effect = cmd_down | flag_cmd_slide_frame0;
			Pattern_AddEffect_NoteVolume(pSong, effect, value << (pChInfo->NewFormat ? 2 : 1));
		}
		break;
		case 4: // Volume: Slide down, No memory
		{
			effect = cmd_down | flag_cmd_slide_frameN0;
			Pattern_AddEffect_NoteVolume(pSong, effect, value << (pChInfo->NewFormat ? 2 : 1));
		}
		break;
		case 0xb: // Pitch: Up, No memory
		{
			effect = cmd_up | flag_cmd_slide_frame0;
			if (pChInfo->NewFormat) value <<= 2;
			Pattern_AddEffect_Pitch(pSong, effect, value);
		}
		break;
		case 0xc: // Pitch: Slide Up, No memory
		{
			effect = cmd_up | flag_cmd_slide_frameN0;
			if (pChInfo->NewFormat) value <<= 2;
			Pattern_AddEffect_Pitch(pSong, effect, value);
		}
		break;
		case 0xd: // Pitch: Down, No memory
		{
			effect = cmd_down | flag_cmd_slide_frame0;
			if (pChInfo->NewFormat) value <<= 2;
			Pattern_AddEffect_Pitch(pSong, effect, value);
		}
		break;
		case 0xe: // Pitch: Slide down, No memory
		{
			effect = cmd_down | flag_cmd_slide_frameN0;
			if (pChInfo->NewFormat) value <<= 2;
			Pattern_AddEffect_Pitch(pSong, effect, value);
		}
		break;
		case 0xf: // Pitch: Portamento, Memory
		{
			if (pChInfo->NewFormat) value <<= 2;
			Pattern_AddEffect_Pitch(pSong, cmd_portamento + flag_cmd_slide_frameN0, value);
		}
		break;
		case 0x10: // Pitch: Glissando control
		{
			if (value < 2)
				Pattern_AddEffect_Pitch(pSong, cmd_pitch_glissando, value);
			else
				SysLog_BadEffectValue(pSong, 0, oeffect, ovalue);
	        }
		break;
		case 0x11: // Pitch: Portamento, Volume: Slide up
		{
			convert_effect(pSong, pChInfo, 15, 0);
			convert_effect(pSong, pChInfo, 2, value);
		}
		break;
		case 0x12: // Pitch: Portamento, Volume: Slide down
		{
			convert_effect(pSong, pChInfo, 15, 0);
			convert_effect(pSong, pChInfo, 4, value);
		}
		break;
		case 0x15: // 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 0x16: // 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 0x17: // Pitch: Vibrato, Volume: Slide up
		{
			convert_effect(pSong, pChInfo, 0x15, 0);
			convert_effect(pSong, pChInfo, 2, value);
		}
		break;
		case 0x18: // Pitch: Vibrato, Volume: Slide down
		{
			convert_effect(pSong, pChInfo, 0x15, 0);
			convert_effect(pSong, pChInfo, 4, value);
		}
		break;
		case 0x1f: // 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 0x20: // 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 0x29: // Note: Set sample offset
		{
			if (value)
			{
				// define offset
				// clear byte 0
				Pattern_AddEffect_Instrument(pSong, cmd_sample_set_offset, (0<<8) | (value & 0xff));
				// set byte 1
				value >>= 8;
				Pattern_AddEffect_Instrument(pSong, cmd_sample_set_offset, (1<<8) | (value & 0xff));
				// set byte 2
				value >>= 8;
				Pattern_AddEffect_Instrument(pSong, cmd_sample_set_offset, (2<<8) | value);
			}

			// only if a note is defined at the same time
			if (pChInfo->Note) Pattern_AddEffect_Instrument(pSong, cmd_sample_set_offset, 0xf00);
		}
		break;
		case 0x2A: // 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 0x2B: // Note: Cut note after x ticks
		{
			Pattern_AddEffect_NoteVolume(pSong, cmd_volume_cut, value);
		}
		break;
		case 0x2C: // Note: Delay for x ticks
		{
			Pattern_AddEffect_Instrument(pSong, cmd_sample_note_delay, value);
		}
		break;
		case 0x34: // Global: Break
		{
			Pattern_AddGlbEffect_Position(pSong, gcmd_pos_break, value);
		}
		break;
		case 0x35: // Global: Loop in column
		{
			Pattern_AddGlbEffect_Position(pSong, gcmd_pos_loop | value, pChInfo->Channel);
		}
		break;
		case 0x36: // Global: Delay row for x rows
		{
			Pattern_AddGlbEffect_Frames(pSong, gcmd_frames_repeat, value);
		}
		break;
		case 0x3d: // Global: Set speed
		{
			if (value) Pattern_AddGlbEffect_Frames(pSong, gcmd_frames_set, value);
		}
		break;
		case 0x3e: // Global: Set tempo, No prev InfoByte
		{
			if (value >= 0x20)
			{
				Pattern_AddGlbEffect_Tempo(pSong, cmd_set, value);
			}
			else
			{
				SysLog_BadEffectValue(pSong, 0, oeffect, ovalue);
			}
		}
		break;
		case 0x47: // 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 0x48: // Note: Set fine tune in 1/8 semitone
		{
			int32_t svalue = value << 28;

			// only if a note is defined at the same time
			if (pChInfo->Note) Pattern_AddEffect_Instrument(pSong, cmd_sample_set_finetune, svalue >> 23);
		}
		break;
		case 0x49: // Panning: Set
		{
			value = value & 0xf;
			value += value << 4;
			if (value >= 128) value++;
			Pattern_AddEffect_Panning(pSong, cmd_set, value);
		}
		break;
		default:
		{
			SysLog_BadEffect(pSong, 0, oeffect, ovalue);
		}
	}
}

static const _kernel_oserror* Read_TagInfo(SongHdr* pSong, uint32_t* pTag, uint32_t* pTagSize)
{
	const _kernel_oserror* err = NULL;

	err = FileLoad_ReadInt(pSong, pTag, 4);
	if (err) return err;
	err = FileLoad_ReadInt(pSong, pTagSize, 4);

	return err;
}

static const _kernel_oserror* Pattern_PSM
	( SongHdr* pSong
	, bool isNewFormat
	, 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, Effect, Value;
	ChannelInfo     ChInfo;

	ChInfo.NewFormat = isNewFormat;

	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;

		if ((pPos + 2) <= pEnd)
		{
			const uint8_t* pRowEnd = pPos + (pPos[0] + (pPos[1] << 8));
			pPos += 2;
			if (pRowEnd > pEnd)
			{
				SysLog_FileCorrupted(SysLog_Medium, pSong, -1, "Pattern truncated");
				pRowEnd = pEnd;
			}

			lastchannel = -1;
			while (pPos < pRowEnd)
			{
				// read flags and channel
				val = *pPos++;
				channel = *pPos++;
				// will we remain within pattern limits?
				pPos2 = pPos;
				if (val & 0x80) pPos2++;
				if (val & 0x40) pPos2++;
				if (val & 0x20) pPos2++;
				if (val & 0x10)
				{
					pPos2+=2;
					if (pPos2 < pRowEnd)
					{
						switch(*pPos2)
						{
							case 0x29: pPos2 += 2;break;
							case 0x33: pPos2++;break;
						}
					}
				}
				if (pPos2 > pRowEnd)
				{
					SysLog_FileCorrupted(SysLog_Medium, pSong, -1, "Pattern row truncated");
					pPos = pEnd;
					break;
				}
				// valid channel ?
				if ((channel <= lastchannel)
				||  (channel >= pSong->Channels))
				{
					// Some files put speed in extra instance of last channel
					if ((val == 0x10)
					&&  ((channel + 1) == pSong->Channels)
					&   (pPos[0] == 0x3d))
					{
						if (pPos[1]) Pattern_AddGlbEffect_Frames(pSong, gcmd_frames_set, pPos[1]);
					}
					else if (channel <= lastchannel)
						SysLog_FileCorrupted
							( SysLog_High, pSong, - (pEnd - pPos + 1)
							, "Invalid channel %d order in pattern"
							, channel
							);
					else
						SysLog_FileCorrupted
							( SysLog_Low, pSong, - (pEnd - pPos + 1)
							, "Extra channel %d in pattern"
							, channel
							);
					pPos = pPos2;
					continue;
				}
				lastchannel = channel;

				// Read note
				if (val & 0x80)
				{
					Note = *pPos++;
					if (isNewFormat)
					{
						if (Note && (Note < 84))
							Note += NoteDelta + 23;
						else
						{
							SysLog_BadNote(pSong, FileLoad_GetPos(pSong) - (pEnd - pPos), channel, Note);
							Note = 0;
						}
					}
					else
					{
						if (((Note & 0xf0) <= 0x70)
						&&  ((Note & 0x0f) <= 0x0b))
						{
							Note = ((Note & 0xf0) >> 4)*12 + (Note & 0x0f);
							Note += NoteDelta;
						}
						else
						{
							SysLog_BadNote(pSong, FileLoad_GetPos(pSong) - (pEnd - pPos), channel, Note);
							Note = 0;
						}
					}
				}
				else Note = 0;

				if (val & 0x40)
					Inst = 1 + *pPos++;
				else
					Inst = 0;

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

				// Read Volume column
				if (val & 0x20)
				{
					Value = *pPos++;
					if (!Value)
						Pattern_AddEffect_NoteVolume(pSong, cmd_set, 0);
					else if (Value <= 127)
						Pattern_AddEffect_NoteVolume(pSong, cmd_set, 2 + (Value << 1));
					else
					{
						Pattern_AddEffect_NoteVolume(pSong, cmd_set, 256);
//						SysLog_BadEffectValue(pSong, 0, 256, Value);
					}
				}

				// Read effect and value
				if (val & 0x10)
				{
					ChInfo.Note = (Note < Note_CmdMin) ? Note : 0;
					Effect = pPos[0];
					Value = pPos[1];
					pPos += 2;
					switch(Effect)
					{
						case 0x29:
							Value += (*pPos++) << 8;
							Value += (*pPos++) << 16;
						break;
						case 0x33: Value += (*pPos++) << 8;break;
					}
					convert_effect(pSong, &ChInfo, Effect, 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* Loader_PSM(SongHdr* pSong)
{
	const _kernel_oserror* err = NULL;
	uint8_t*    pLTrack = NULL;
	uint32_t    val;
	Pattern*    pPattern;
	Sample*     pSmp;
	uint32_t    FileSize, Tag, TagSize;
	uint32_t    FileOffset;
	bool        isNewFormat = false;

	// is it a PSM file?
	err = FileLoad_SetPos(pSong, 0);
	if (err) goto loader_err;
	err = Read_TagInfo(pSong, &Tag, &FileSize);
	if (err) goto loader_err;

	if (Tag != TAG(PSM))
	{
		err = Loaders_NotThisType;
		goto loader_err;
	}
	FileSize += 8;
	err = FileLoad_ReadInt(pSong, &Tag, 4);
	if (err) goto loader_err;
	if (Tag != TAG(FILE))
	{
		err = Loaders_NotThisType;
		goto loader_err;
	}

	pSong->Flags = 0;
	pSong->MinPitch = Note_Central - 52; // Somewhat below the ST3 C-0
	pSong->MaxPitch = Note_Central + 56; // Somewhat above the ST3 B-7

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

	// First pass to extract SONG block, because it is stored after other blocks using its info.
	while (FileLoad_GetPos(pSong) < FileSize)
	{
		// Read TAG id & size
		err = Read_TagInfo(pSong, &Tag, &TagSize);
		if (err) goto loader_err;
		FileOffset = FileLoad_GetPos(pSong);

		if (Tag == TAG(SONG))
		{
			SubSong* pBaseSubSong = Song_GetSubSong(pSong, 0);
			SubSong* pSubSong;
			uint32_t SubOffset = FileOffset;
			uint32_t SubEnd = FileOffset + TagSize;
			uint32_t SubSize = TagSize;
			uint32_t SubTagSize;

			// May have multiple songs
			if (pBaseSubSong->SeqLen)
			{
				err = Song_CopySubSong(pSong, &pSubSong, pBaseSubSong);
				if (err) goto loader_err;
			}
			else pSubSong = pBaseSubSong;

			err = FileLoad_Skip(pSong, 10);
			if (!err) err = FileLoad_ReadByte(pSong, &pSong->Channels);
			if (!pSong->Channels || (pSong->Channels > 16))
			{
				err = Loaders_InvalidSong(pSong, -1, &pSong->Channels, pSong->Channels);
				goto loader_err;
			}

			// Read sub TAGS
			while (FileLoad_GetPos(pSong) < SubEnd)
			{
				// Read TAG id & size
				err = Read_TagInfo(pSong, &Tag, &SubTagSize);
				if (err) goto loader_err;
				SubOffset = FileLoad_GetPos(pSong);

				if (Tag == TAG(OPLH))
				{
					uint32_t count, size;
					char buf[5];

					if (SubTagSize < 2)
					{
						err = Loaders_Error(pSong, SubOffset - 8, Invalid_Block);
						goto loader_err;
					}
					err = FileLoad_ReadInt(pSong, &pSubSong->SeqLen, 2);
					if (!err) err = Loaders_AllocSeq(pSong, pSubSong);
					if (err) goto loader_err;
					count = pSubSong->SeqLen;
					pSubSong->SeqLen = 0;

					for (size = SubTagSize - 2; (count > 0) && (size > 0); count--)
					{
						err = FileLoad_ReadByte(pSong, &val);
						if (err) goto loader_err;

						if (!val) break;
						switch(val)
						{
							case 1:
							{
								if (size < 5)
								{
									err = Loaders_Error(pSong, -1, Invalid_Block);
									goto loader_err;
								}
								err = FileLoad_Read(pSong, buf, 4);
								if (err) goto loader_err;
								if (!strncmp(buf, "PATT", 4))
								{
									isNewFormat = true;
									if (size < 9)
									{
										err = Loaders_Error(pSong, -1, Invalid_Block);
										goto loader_err;
									}
									err = FileLoad_Read(pSong, buf, 4);
									buf[4] = 0;
									pSubSong->pSeqs[pSubSong->SeqLen++] = atoi(buf);
									size -= 9;
								}
								else
								{
									buf[4] = 0;
									pSubSong->pSeqs[pSubSong->SeqLen++] = atoi(buf + 1);
									size -= 5;
								}
							}
							break;
							case 4:
							{
								if (size < 3)
								{
									err = Loaders_Error(pSong, -1, Invalid_Block);
									goto loader_err;
								}
								err = FileLoad_ReadInt(pSong, &pSubSong->RestartPos, 2);
								if (err) goto loader_err;
								size -= 3;
							}
							break;
							case 7:
							{
								if (size < 2)
								{
									err = Loaders_Error(pSong, -1, Invalid_Block);
									goto loader_err;
								}
								err = FileLoad_ReadByte(pSong, &pSubSong->Defaults.Speed);
								if (err) goto loader_err;
								size -= 2;
							}
							break;
							case 8:
							{
								if (size < 2)
								{
									err = Loaders_Error(pSong, -1, Invalid_Block);
									goto loader_err;
								}
								err = FileLoad_ReadByte(pSong, &pSubSong->Defaults.Tempo);
								if (err) goto loader_err;
								size -= 2;
							}
							break;
							case 0xc:
							{
								err = FileLoad_Skip(pSong, 6);
								if (err) goto loader_err;
								size -= 7;
							}
							break;
							case 0xd:
							{
								uint32_t channel, panning, mode;
								if (size < 4)
								{
									err = Loaders_Error(pSong, -1, Invalid_Block);
									goto loader_err;
								}
								err = FileLoad_ReadByte(pSong, &channel);
								if (!err) err = FileLoad_ReadByte(pSong, &panning);
								if (!err) err = FileLoad_ReadByte(pSong, &mode);
								if (err) goto loader_err;
								if (channel < pSong->Channels)
								{
									if (mode == 2)
										panning = Panning_Surround;
									else if (mode == 4)
										panning = 128;
									else
										panning = panning ^0x80; // Signed
									pSubSong->Defaults.ChDef[channel].Panning = panning;
								}
								size -= 4;
							}
							break;
							case 0xe:
							{
								uint32_t channel, volume;
								if (size < 3)
								{
									err = Loaders_Error(pSong, -1, Invalid_Block);
									goto loader_err;
								}
								err = FileLoad_ReadByte(pSong, &channel);
								if (!err) err = FileLoad_ReadByte(pSong, &volume);
								if (err) goto loader_err;
								if (channel < pSong->Channels)
									pSubSong->Defaults.ChDef[channel].Volume = volume;
								size -= 3;
							}
							break;
							default:
							{
								err = Loaders_Error(pSong, -1, Invalid_Block);
								goto loader_err;
							}
						}
					}
				}

				// to prevent overflows
				if ((SubTagSize >= SubSize)
				||  (SubOffset + SubTagSize >= SubEnd))
					break;

				FileLoad_SetPos(pSong, SubOffset + SubTagSize);
			}
		}

		// to prevent overflows
		if ((TagSize >= FileSize)
		||  (FileOffset + TagSize >= FileSize))
			break;

		FileLoad_SetPos(pSong, FileOffset + TagSize);
	}

	FileLoad_SetPos(pSong, 0x0C);

	// Read TAGS.
	// Rule: assume that when tags require information from another tag, this tag is stored
	// earlier in the tag sequence, but don't assume any fixed tag order.
	// We will thus reject files which break this rule as too badly corrupted.
	// For missing tags which don't impact others (pattern, sequence),
	// we will let the global inegrity check catch these cases.
	while (FileLoad_GetPos(pSong) < FileSize)
	{
		// Read TAG id & size
		err = Read_TagInfo(pSong, &Tag, &TagSize);
		if (err) goto loader_err;
		FileOffset = FileLoad_GetPos(pSong);

		if (Tag == TAG(PBOD))
		{
			char buf[5];
			uint32_t patt_nr;

			err = FileLoad_ReadInt(pSong, &val, 4);
			if (err) goto loader_err;
			if (val != TagSize)
			{
				err = Loaders_Error(pSong, FileOffset - 8, "Invalid pattern");
				goto loader_err;
			}
			err = FileLoad_Read(pSong, buf, 4);
			if (err) goto loader_err;
			if (!strncmp(buf, "PATT", 4))
			{
				isNewFormat = true;
				err = FileLoad_Read(pSong, buf, 4);
				buf[4] = 0;
				patt_nr = atoi(buf);
			}
			else
			{
				buf[4] = 0;
				patt_nr = atoi(buf + 1);
			}
			if (patt_nr >= 256)
			{
				err = Loaders_Error(pSong, FileOffset - 8, "Invalid pattern number %d", patt_nr - 1);
				goto loader_err;
			}
			pPattern = pSong->pPatterns + patt_nr;
			if (pPattern->Ptr)
			{
				err = Loaders_Error(pSong, FileOffset - 8, "Duplicate pattern number %d", patt_nr - 1);
				goto loader_err;
			}
			if (pSong->Patterns <= patt_nr)
				pSong->Patterns = patt_nr + 1;
			if (!err) err = FileLoad_ReadInt(pSong, &pPattern->Rows, 2);

			uint32_t Size = TagSize + FileOffset - FileLoad_GetPos(pSong);
			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_PSM(pSong, isNewFormat, patt_nr, pPattern, pLTrack, pLTrack + Size);
			if (err) goto loader_err;

			Loaders_Free(pSong, pLTrack);
			pLTrack = NULL;
		}
		else if (Tag == TAG(DSMP))
		{
			uint32_t smp_nr;

			if (TagSize < 96)
			{
				err = Loaders_Error(pSong, FileOffset - 8, Invalid_Block);
				goto loader_err;
			}

			// Skip to sample nr
			err = FileLoad_Skip(pSong, (isNewFormat) ? 56 : 52);
			if (!err) err = FileLoad_ReadInt(pSong, &smp_nr, 2);
			if (smp_nr >= 256)
			{
				err = Loaders_Error(pSong, FileOffset - 8, "Invalid sample number %d", smp_nr);
				goto loader_err;
			}
			if (pSong->Samples <= smp_nr)
				pSong->Samples = smp_nr + 1;
			pSmp = pSong->pSamples + smp_nr;
			if (pSmp->pName)
			{
				err = Loaders_Error(pSong, FileOffset - 8, "Duplicate sample %d", smp_nr - 1);
				goto loader_err;
			}

			pSmp->Type |= Smp_Type_Delta;

			// Rollback to start of header
			err = FileLoad_SetPos(pSong, FileOffset);
			if (!err) err = FileLoad_ReadByte(pSong, &val);
			if (val & 0x80) pSmp->Type |= Smp_Type_Loop;
			if (isNewFormat)
			{
				if (!err) err = FileLoad_ReadString(pSong, &pSmp->pFilename, 8, false);
				if (!err) err = FileLoad_Skip(pSong, 8);
				if (!err) err = FileLoad_ReadString(pSong, &pSmp->pName, 33, false);
				if (!err) err = FileLoad_Skip(pSong, 8);
				if (!err) err = FileLoad_ReadInt(pSong, &pSmp->Size, 4);
				if (!err) err = FileLoad_ReadInt(pSong, &pSmp->LoopStart, 4);
				if (!err) err = FileLoad_ReadInt(pSong, &pSmp->LoopEnd, 4);
				if (err) goto loader_err;
				err = FileLoad_Skip(pSong, 2);
				if (!err) err = FileLoad_ReadByte(pSong, &val);
				if (err) goto loader_err;
				pSmp->Panning = val;
				err = FileLoad_ReadByte(pSong, &val);
				if (err) goto loader_err;
				if (val <= 128)
					val <<= 1;
				else
					val = 256;
				pSmp->DefaultVolume = val;
				err = FileLoad_Skip(pSong, 4);
				if (!err) err = FileLoad_ReadInt(pSong, &val, 2);
				if (!err) err = FileLoad_Skip(pSong, 16);
				pSmp->Frequency = 8363;
				pSmp->RelTone = Convert_RelTone(pSmp->Frequency, val);
			}
			else
			{
				if (!err) err = FileLoad_ReadString(pSong, &pSmp->pFilename, 8, false);
				if (!err) err = FileLoad_Skip(pSong, 4);
				if (!err) err = FileLoad_ReadString(pSong, &pSmp->pName, 33, false);
				if (!err) err = FileLoad_Skip(pSong, 8);
				if (!err) err = FileLoad_ReadInt(pSong, &pSmp->Size, 4);
				if (!err) err = FileLoad_ReadInt(pSong, &pSmp->LoopStart, 4);
				if (!err) err = FileLoad_ReadInt(pSong, &pSmp->LoopEnd, 4);
				if (err) goto loader_err;
				err = FileLoad_Skip(pSong, 1);
				if (!err) err = FileLoad_ReadByte(pSong, &val);
				if (err) goto loader_err;
				pSmp->Panning = val;
				if (!err) err = FileLoad_ReadByte(pSong, &val);
				if (err) goto loader_err;
				if (val < 128)
					val = 2 + (val << 1);
				else
					val = 256;
				pSmp->DefaultVolume = val;
				err = FileLoad_Skip(pSong, 4);
				if (!err) err = FileLoad_ReadInt(pSong, &val, 2);
				if (!err) err = FileLoad_Skip(pSong, 21);
				pSmp->Frequency = 8363;
				pSmp->RelTone = Convert_RelTone(pSmp->Frequency, val);
			}

			if (err) goto loader_err;
			if (TagSize < (96 + pSmp->Size))
			{
				SysLog_FileCorrupted
					( SysLog_High, pSong, FileOffset - 8
					, "DSMP size %x - 0x60 shorter than sample size %x"
					, TagSize, pSmp->Size
					);
				pSmp->Size = TagSize - 96;
			}
			if (pSmp->Size)
			{
				err = FileLoad_ReadSample(pSong, pSmp);
				if (err) goto loader_err;
			}
		}
		else if (Tag == TAG(TITL))
		{
			err = FileLoad_ReadString(pSong, &pSong->pName, TagSize, false);
			if (err) goto loader_err;
		}

		// to prevent overflows
		if ((TagSize >= FileSize)
		||  (FileOffset + TagSize >= FileSize))
			break;

		FileLoad_SetPos(pSong, FileOffset + TagSize);
	}

	if (isNewFormat)
		pSong->Version = 200;
	else
		pSong->Version = 100;

loader_err:
	Loaders_Free(pSong, pLTrack);

	return err;
}
