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

/*
 * Channels: [1...32]
 * Patterns: [1...65535]
 * Rows    : 64
 * Orders  : [1...999]
 * Samples : [1...63]
 * Instr.  : 0
 * Tempo   : [32...255], T frames per 2.5 seconds.
 * Speed   : [1...31], S frames per row.
 * Notes   : [C-0...C-2...B-5]
 * Pitch   : Uses Amiga periods, 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.
 * Sampling: 8363 Hz (frequency is turned into a relative tone).
 *
 * Limitations:
 *  Tag PEFX is ignored.
 */

#define NoteDelta (Note_Central-12*4)

static const uint8_t Tag_D_T_[] = "D.T.";
static const uint8_t Tag_VERS[] = "VERS";
static const uint8_t Tag_S_Q_[] = "S.Q.";
static const uint8_t Tag_PATN[] = "PATN";
static const uint8_t Tag_TRKN[] = "TRKN";
static const uint8_t Tag_SV19[] = "SV19";
static const uint8_t Tag_TEXT[] = "TEXT";
static const uint8_t Tag_PATT[] = "PATT";
static const uint8_t Tag_2_04[] = "2.04";
static const uint8_t Tag_2_06[] = "2.06";
static const uint8_t Tag_INST[] = "INST";
static const uint8_t Tag_DAPT[] = "DAPT";
static const uint8_t Tag_DAIT[] = "DAIT";
static const uint8_t Tag_PEFX[] = "PEFX";
static const uint8_t Tag_IENV[] = "IENV";
static const uint8_t Typestr_DT[] = "Digital Tracker";
static const char Invalid_Block[] = "Invalid block";
static const char Unexpected_Block[] = "Block not expected in that position";
static const char Unexpected_SampleNr[] = "Unexpected sample %d";
static const char Unexpected_InstrumentNr[] = "Unexpected instrument %d";

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

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

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

#ifndef USELOG
	IGNORE(pEnd);
#endif

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

		for (k = 0; k < pSong->Channels; k++)
		{
			// Read note
			Note = *pPos++;
			if (((Note & 0xf0) >= 0x20)
			&&  ((Note & 0xf0) <= 0x70)
			&&  ((Note & 0x0f) >= 0x01)
			&&  ((Note & 0x0f) <= 0x0c))
			{
				Note = ((Note & 0xf0) >> 4)*12 + (Note & 0x0f) - 1;
				Note += NoteDelta;
			}
			else
			{
				if (Note) SysLog_BadNote(pSong, FileLoad_GetPos(pSong) - (pEnd - pPos) - 1, k, Note);
				Note = 0;
			}

			// Read instrument
			Vol = *pPos++;
			Inst = *pPos++;
			Value = *pPos++;
			Cmd = Inst & 0xf;
			Inst = ((Vol & 0x3) << 4) + (Inst >> 4);
			Vol = Vol >> 2;

			err = Loaders_StartChannel(pSong, k, Note, Inst);
			if (err) return err;
			if (Vol) Pattern_AddEffect_NoteVolume(pSong, cmd_set, Vol << 2); // ((Vol -1)*0x42109)>>16 ?
//			if (Vol) SysLog_BadEffectValue(pSong, 256, Vol);
			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_DT206(SongHdr* pSong, SubSong* pSubSong, uint32_t i, Pattern* pPattern, const uint8_t* pGPos, const uint8_t* pGEnd)
{
	const _kernel_oserror* err = NULL;
	int             j, k;
	uint32_t        Note, Inst, Vol, Cmd, Value, Speed;
	const uint8_t*  pChStart[64];
	const uint8_t*  pChEnd[64];
	uint32_t        ChRow[64];

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

	pPattern->Rows /= pSubSong->Defaults.Speed;
	// Pattern is stored channels per channel
	for (k = 0; k < pSong->Channels; k++)
	{
		ChRow[k] = 0;

		if (pGEnd - pGPos >= 2)
		{
			uint32_t size = (*pGPos++) << 8;
			size += *pGPos++;
			if (size & 1) size++;
			pChStart[k] = pGPos;
			pChEnd[k] = pGPos + size;
			if (pChEnd[k] > pGEnd)
			{
				SysLog_FileCorrupted(SysLog_Medium, pSong, FileLoad_GetPos(pSong) - (pGEnd - pGPos) - 2, "Pattern channel %d, size %x truncated", k, size);
				pChEnd[k] = pGEnd;
			}
			pGPos = pChEnd[k];
		}
		else
		{
			pChStart[k] = pGEnd;
			pChEnd[k] = pGEnd;
			SysLog_FileCorrupted(SysLog_Medium, pSong, FileLoad_GetPos(pSong) - (pGEnd - pGPos), "Pattern channel %d not encoded", k);
		}
	}

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

		for (k = 0; k < pSong->Channels; k++)
		{
			const uint8_t* pPos = pChStart[k];
			const uint8_t* pEnd = pChEnd[k];
skip_0:
			if (((j*pSubSong->Defaults.Speed) >= ChRow[k]) && (pEnd - pPos) >= 6)
			{
				// Read note
				Note = *pPos++;
				if ((Note > 0) && (Note <= 96))
					Note += NoteDelta - 1;
				else if (Note == 0x80)
					Note = Note_Cut;
				else
				{
					if (Note) SysLog_BadNote(pSong, FileLoad_GetPos(pSong) - (pGEnd - pPos) - 1, k, Note);
					Note = 0;
				}

				// Read instrument
				Vol = *pPos++;
				Inst = *pPos++;
				Cmd = *pPos++;
				Value = *pPos++;
				Speed = *pPos++;
				if (Speed & 0x80) {
					Speed = ((Speed & 0x7f) << 8) + *pPos++;
				}

				pChStart[k] = pPos;
				ChRow[k] += Speed;

				// Bug in some file
				if (Speed == 0) goto skip_0;

				err = Loaders_StartChannel(pSong, k, Note, Inst);
				if (err) return err;
				if (Vol) Pattern_AddEffect_NoteVolume(pSong, cmd_set, (Vol > 64) ? 256 : Vol << 2); // ((Vol -1)*0x42109)>>16 ?
//				if (Vol) SysLog_BadEffectValue(pSong, 256, Vol);
				convert_effect_MOD(pSong, k, Cmd, Value, Note);
				if (ChRow[k] % pSubSong->Defaults.Speed)
				{
					SysLog_FileCorrupted(SysLog_Medium, pSong, FileLoad_GetPos(pSong) - (pGEnd - pPos), "Event on tick %d", ChRow[k] % pSubSong->Defaults.Speed);
				}


				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* 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_ReadReverseInt(pSong, pTagSize, 4);

	return err;
}

const _kernel_oserror* Loader_DT(SongHdr* pSong)
{
	const _kernel_oserror* err = NULL;
	SubSong*    pSubSong = Song_GetSubSong(pSong, 0);
	uint8_t*    pLTrack = NULL;
	int         i;
	Pattern*    pPattern;
	Sample*     pSmp;
	Instrument* pInst;
	uint32_t    Tag, TagSize;
	uint32_t    FileOffset;
	uint32_t    FileSize = FileLoad_GetSize(pSong);
	uint32_t    curPattern = 0;
	uint32_t    curSmp = 0;
	uint32_t    val;
	uint32_t    PatternTag = 0;
	uint32_t    Envelopes = 0;
	uint8_t*    pIEnvelopes = NULL;

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

	if (Tag != TAG(D_T_))
	{
		err = Loaders_NotThisType;
		goto loader_err;
	}

	if (TagSize < 22)
	{
		err = Loaders_Error(pSong, 4, Invalid_Block);
		goto loader_err;
	}

	err = FileLoad_SetPos(pSong, 14);
	if (err) goto loader_err;
	err = FileLoad_ReadReverseInt(pSong, &pSubSong->Defaults.Speed, 2);
	if (!err) err = FileLoad_ReadReverseInt(pSong, &val, 2);
	if (err) goto loader_err;
	if (val) pSubSong->Defaults.Tempo = val;
	err = FileLoad_ReadReverseInt(pSong, &val, 4); // dummy
	if (!err) FileLoad_ReadString(pSong, &pSong->pName, TagSize - 14, false);
	if (err) goto loader_err;

	pSong->Flags = 0;
	pSong->MinPitch = NoteDelta + 12 * 2;
	pSong->MaxPitch = NoteDelta + 12 * 8 - 1;

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

	// 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(VERS))
		{
			if (TagSize < 4)
			{
				err = Loaders_Error(pSong, FileOffset - 8, Invalid_Block);
				goto loader_err;
			}

			err = FileLoad_ReadReverseInt(pSong, &val, 4);
			if (err) goto loader_err;
			pSong->Version = val * 10;
		}
		else if (Tag == TAG(PEFX))
			; // Ignore
		else if (Tag == TAG(S_Q_))
		{
			if (pSubSong->SeqLen > 0)
			{
				err = Loaders_Error(pSong, FileOffset - 8, Unexpected_Block);
				goto loader_err;
			}

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

			err = FileLoad_ReadReverseInt(pSong, &pSubSong->SeqLen, 2);
			if (!err) err = FileLoad_ReadReverseInt(pSong, &pSubSong->RestartPos, 2);
			if (!err) err = FileLoad_ReadReverseInt(pSong, &val, 4); // dummy
			if (err) goto loader_err;

			if (!pSubSong->SeqLen
			|| (pSubSong->SeqLen > 999))
			{
				err = Loaders_InvalidSong(pSong, FileOffset, &pSubSong->SeqLen, pSubSong->SeqLen);
				goto loader_err;
			}

			if (TagSize < pSubSong->SeqLen + 8)
			{
				err = Loaders_Error(pSong, FileOffset - 8, Invalid_Block);
				goto loader_err;
			}

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

			// Read sequence
			for (i = 0; i < pSubSong->SeqLen; i++)
			{
				err = FileLoad_ReadByte(pSong, &val);
				pSubSong->pSeqs[i] = val;
				if (err) goto loader_err;
			}
		}
		else if (Tag == TAG(IENV))
		{
			if (TagSize < 2)
			{
				err = Loaders_Error(pSong, FileOffset - 8, Invalid_Block);
				goto loader_err;
			}

			err = FileLoad_ReadReverseInt(pSong, &Envelopes, 2);
			if (err) goto loader_err;

			// Truncate if necessary
			if (TagSize < 2 + Envelopes * 34)
			{
				SysLog_FileCorrupted(SysLog_Medium, pSong, FileOffset - 8, "IENV block too short by %d bytes", 2 + Envelopes * 34 - TagSize);
				Envelopes = (TagSize - 2) / 34;
			}

			if (Envelopes)
			{
				err = Loaders_Alloc(pSong, (void**) &pIEnvelopes, Envelopes * 34);
				if (err) goto loader_err;
				err = FileLoad_Read(pSong, pIEnvelopes, Envelopes * 34);
				if (err) goto loader_err;
			}
		}
		else if (Tag == TAG(PATN))
			; // Ignore pattern names
		else if (Tag == TAG(TRKN))
			; // Ignore channel names
		else if (Tag == TAG(SV19))
		{
			// Ignore layout info, but read default panning & volume
			uint32_t val;

			err = FileLoad_Skip(pSong, 6);
			if (err) goto loader_err;

			// Panning [0...90...180] !
			for (i = 0; i < 32; i++)
			{
				err = FileLoad_ReadReverseInt(pSong, &val, 2);
				if (err) goto loader_err;
				if (val > 180) val = 180;
				int sval = val;
				sval = ((sval - 90) * 128) / 90;
				pSubSong->Defaults.ChDef[i].Panning = sval + 128;
			}

			err = FileLoad_Skip(pSong, 146);
			if (err) goto loader_err;

			for (i = 0; i < 32; i++)
			{
				err = FileLoad_ReadReverseInt(pSong, &val, 2);
				if (err) goto loader_err;
				pSubSong->Defaults.ChDef[i].Volume = (val > 64) ? 256 : val << 2;;
			}
		}
		else if (Tag == TAG(TEXT))
		{
			uint32_t size, tabsize;

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

			err = FileLoad_ReadReverseInt(pSong, &val, 2); // type
			if (!err) err = FileLoad_ReadReverseInt(pSong, &size, 4);
			if (!err) err = FileLoad_ReadReverseInt(pSong, &tabsize, 2);
			if (!err) err = FileLoad_ReadReverseInt(pSong, &val, 4);
			if (err) goto loader_err;

			if (TagSize < (12 + size))
			{
				err = Loaders_Error(pSong, FileOffset - 20, Invalid_Block);
				goto loader_err;
			}

			pSong->CommentsLen = size;
			err = FileLoad_ReadString(pSong, &pSong->pComments, pSong->CommentsLen, false);
			if (err) goto loader_err;
		}
		else if (Tag == TAG(PATT))
		{
			if (pSong->Channels > 0)
			{
				err = Loaders_Error(pSong, FileOffset - 8, Unexpected_Block);
				goto loader_err;
			}

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

			err = FileLoad_ReadReverseInt(pSong, &pSong->Channels, 2);
			if (!err) err = FileLoad_ReadReverseInt(pSong, &pSong->Patterns, 2);
			if (!err) err = FileLoad_ReadInt(pSong, &PatternTag, 4); // No Reverse
			if (err) goto loader_err;

			if (!pSong->Channels
			|| (pSong->Channels > 32))
			{
				err = Loaders_InvalidSong(pSong, FileOffset, &pSong->Channels, pSong->Channels);
				goto loader_err;
			}

			if (!pSong->Patterns || (pSong->Patterns > song_max_patterns))
			{
				err = Loaders_InvalidSong(pSong, FileOffset + 2, &pSong->Patterns, pSong->Patterns);
				goto loader_err;
			}

			if (PatternTag)
			{
				if (PatternTag == TAG(2_06))
				{
					pSong->MinPitch = NoteDelta;
					pSong->MaxPitch = NoteDelta + 12 * 8 - 1;
					pSong->Flags |= Song_Flag_Linear;
				}
				else if (PatternTag != TAG(2_04))
				{
					err = Loaders_Error(pSong, FileOffset + 4, "Unsupported pattern encoding");
					goto loader_err;
				}
			}

			if (curPattern >= pSong->Patterns)
			{
				err = Loaders_Error(pSong, FileOffset - 8, Unexpected_Block);
				goto loader_err;
			}
		}
		else if (Tag == TAG(INST))
		{
			uint32_t storedSamples;
			uint32_t actSmp = 0;
			uint32_t actInst = 0;

			if (pSong->Samples > 0)
			{
				err = Loaders_Error(pSong, FileOffset - 8, Unexpected_Block);
				goto loader_err;
			}

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

			err = FileLoad_ReadReverseInt(pSong, &storedSamples, 2);
			if (err) goto loader_err;

			bool newSamples = ((storedSamples & 0x8000) != 0);
			storedSamples &= 0x7fff;
			if (!storedSamples
			|| (storedSamples > 63))
			{
				err = Loaders_InvalidSong(pSong, FileOffset, &pSong->Samples, storedSamples);
				goto loader_err;
			}

			// Read samples definitions

			for (i = 0, pSmp = pSong->pSamples; i < storedSamples; i++, actSmp++, pSmp++)
			{
				pSmp->Type = 0;

				if (newSamples)
				{
					// Sample index
					err = FileLoad_ReadReverseInt(pSong, &val, 2);
					if (err) goto loader_err;

					if ((val < actSmp) || (val >= song_max_samples))
					{
						err = Loaders_Error(pSong, -2, Unexpected_SampleNr, val);
						goto loader_err;
					}

					// Skip gap
					while (actSmp < val)
					{
						pSmp->Type = Smp_Type_Undefined;
						pSmp++;
						actSmp++;
					}
				}
				pSong->Samples = actSmp + 1;

				err = FileLoad_ReadReverseInt(pSong, &val, 4); // Ignore internal numbering
				if (!err) err = FileLoad_ReadReverseInt(pSong, &pSmp->Size, 4);
				if (!err) err = FileLoad_ReadByte(pSong, &val);
				if (err) goto loader_err;
				pSmp->FineTune = (val & 0xf) << 28;
				if (!err) err = FileLoad_ReadByte(pSong, &pSmp->DefaultVolume);
				if (!err) err = FileLoad_ReadReverseInt(pSong, &pSmp->LoopStart, 4);
				if (!err) err = FileLoad_ReadReverseInt(pSong, &pSmp->LoopEnd, 4);
				if (!err) err = FileLoad_ReadString(pSong, &pSmp->pName, 22, false);
				if (!err) err = FileLoad_ReadByte(pSong, &val);
				if (err) goto loader_err;
				if (val & 1) pSmp->Type |= Smp_Type_Stereo;
				err = FileLoad_ReadByte(pSong, &val);
				if (err) goto loader_err;
				if (val == 16)
					pSmp->Type |= Smp_Type_16bit | Smp_Type_BigEndian;
				else if (!val && !pSmp->Size)
					; // Ignore
				else if (val != 8)
				{
					err = Loaders_Error(pSong, -1, "Unsupported sample type");
					goto loader_err;
				}
				if (pSmp->DefaultVolume > 64) pSmp->DefaultVolume = 256;
				else pSmp->DefaultVolume <<= 2;
				if (pSmp->LoopEnd > 2) pSmp->Type |= Smp_Type_Loop;
				pSmp->LoopEnd += pSmp->LoopStart;

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

				if (newSamples)
				{
					err = FileLoad_ReadReverseInt(pSong, &val, 2);
					if (err) goto loader_err;
					if (val)
						pSmp->RelTone = (49 - val) << 8;
					else
						pSmp->RelTone = 0;
					err = FileLoad_ReadReverseInt(pSong, &val, 2); // Ignore
					if (err) goto loader_err;
				}
				else
				{
					err = FileLoad_ReadReverseInt(pSong, &val, 4); // Midi note
					if (err) goto loader_err;
					pSmp->RelTone = 0;
				}
				err = FileLoad_ReadReverseInt(pSong, &val, 4);
				if (err) goto loader_err;
				pSmp->Frequency = 8363;
				pSmp->RelTone += Convert_RelTone(pSmp->Frequency, val);
			}

			// Instruments ?
			if ((TagSize - (FileLoad_GetPos(pSong) - FileOffset)) >= (2 + 15 * storedSamples))
			{
				err = FileLoad_ReadReverseInt(pSong, &val, 2);
				if (err) goto loader_err;

				if (val == 0x0004)
				{
					uint32_t envNr;

					pSong->Flags |= Song_Flag_Instruments;
					// Read instruments definitions
					for (i = 0, pInst = pSong->pInstruments; i < storedSamples; i++, actInst++, pInst++)
					{
						// Instrument index
						err = FileLoad_ReadReverseInt(pSong, &val, 2);
						if (err) goto loader_err;

						if ((val < actInst) || (val >= song_max_instruments))
						{
							err = Loaders_Error(pSong, -1, Unexpected_InstrumentNr, val);
							goto loader_err;
						}

						// Skip gap
						while (actInst < val)
						{
							pInst++;
							actInst++;
						}
						pSong->Instruments = actInst + 1;

						err = Loaders_AllocMapTable(pSong, &pInst->pNotesMap);
						if (err) goto loader_err;

						for (int note = Note_Min; note <= Note_Max; note++)
						{
							NoteMap* pn = pInst->pNotesMap + note;
							pn->Volume = 255;
							pn->SampleNr = 1 + actInst;
							pn->Pitch = note << 8;
						}

						err = FileLoad_ReadByte(pSong, &val);
						if (err) goto loader_err;
						err = FileLoad_ReadByte(pSong, &envNr);
						if (err) goto loader_err;
						Envelope* pEnv = &pInst->VolumeEnvelope;
						if (envNr < Envelopes)
						{
							pEnv->Flags |= Envelope_Flag_On;
							pEnv->Points = pIEnvelopes[envNr * 34 + 1];
							err = Loaders_Alloc(pSong, (void**) &pEnv->pTable, 64);
							if (err) goto loader_err;
							for (int j = 2; j < 34; j += 2)
							{
								val = pIEnvelopes[envNr * 34 + j + 1] << 2;
								if (val > 256) val = 256;
								pEnv->pTable[(j>>1) - 1] = pIEnvelopes[envNr * 34 + j] + (val << 16);
							}

						}
						err = FileLoad_ReadByte(pSong, &val);
						if (err) goto loader_err;
						if (val != 0xff)
						{
							pEnv->Flags |= Envelope_Flag_Sustain;
							pEnv->SustainStart = val;
							pEnv->SustainEnd = val;
						}
						err = FileLoad_ReadReverseInt(pSong, &val, 2);
						if (err) goto loader_err;
						pInst->FadeoutVolume = val;
						err = FileLoad_ReadByte(pSong, &val);
						if (err) goto loader_err;
						pInst->Vibrato.Rate = val;
						err = FileLoad_ReadByte(pSong, &val);
						if (err) goto loader_err;
						pInst->Vibrato.Depth = val;
						err = FileLoad_Skip(pSong, 6);
						if (err) goto loader_err;
					}
				}
			}
		}
		else if (Tag == TAG(DAPT))
		{
			int size;

			err = FileLoad_ReadReverseInt(pSong, &val, 4);
			if (!err) err = FileLoad_ReadReverseInt(pSong, &val, 2);

			if ((val < curPattern) || (val > 255))
			{
				err = Loaders_Error(pSong, FileOffset - 8, Unexpected_Block);
				goto loader_err;
			}

			// May skip unused patterns
			for (;curPattern < val; curPattern++)
			{
				pPattern = &pSong->pPatterns[curPattern];
				pPattern->Rows = 64;

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

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

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

				err = Loaders_EndPattern(pSong, pPattern);
				if (err) goto loader_err;
			}

			pSong->Patterns = curPattern + 1;
			pPattern = &pSong->pPatterns[curPattern];

			if (!err) err = FileLoad_ReadReverseInt(pSong, &pPattern->Rows, 2);
			if (err) goto loader_err;

			if (!pPattern->Rows || (pPattern->Rows > song_max_rows))
			{
				err = Loaders_InvalidSong(pSong, -2, &pPattern->Rows, pPattern->Rows);
				goto loader_err;
			}

			size = 4*pPattern->Rows*pSong->Channels;
			if ((PatternTag != TAG(2_06))
			&&  (TagSize < 8 + size))
			{
				err = Loaders_Error(pSong, FileOffset - 8, Invalid_Block);
				goto loader_err;
			}

			size = TagSize - 8;
			err = Loaders_Alloc(pSong, (void**) &pLTrack, size);
			if (err) goto loader_err;
			err = FileLoad_Read(pSong, pLTrack, size);
			if (err) goto loader_err;
			if (!PatternTag)
				err = Pattern_MOD(pSong, curPattern, pPattern, (uint32_t*) pLTrack);
			else if (PatternTag == TAG(2_04))
				err = Pattern_DT(pSong, curPattern, pPattern, pLTrack, pLTrack + size);
			else
				err = Pattern_DT206(pSong, pSubSong, curPattern, pPattern, pLTrack + 4, pLTrack + size);
			if (err) goto loader_err;
			curPattern++;

			Loaders_Free(pSong, pLTrack);
			pLTrack = NULL;
		}
		else if (Tag == TAG(DAIT))
		{
			err = FileLoad_ReadReverseInt(pSong, &val, 2);
			if (err) goto loader_err;

			if ((curSmp > val) || (val >= pSong->Samples))
			{
				err = Loaders_Error(pSong, FileOffset - 8, Unexpected_SampleNr, val);
				goto loader_err;
			}

			curSmp = val;
			pSmp = &pSong->pSamples[curSmp];

			err = FileLoad_ReadSample(pSong, pSmp);
			if (err) goto loader_err;

			curSmp++;
		}
		else if (Tag == TAG(PATN))
			; // Ignore
		else
		{
			SysLog_FileCorrupted(SysLog_Medium, pSong, FileOffset - 8, Unexpected_Block);
		}

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

		FileLoad_SetPos(pSong, FileOffset + TagSize);
	}

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

	return err;
}
