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

/*
 * Channels: [1...255]
 * Patterns: [1...65535]
 * Rows    : [1...65535]
 * Orders  : [1...65535]
 * Samples : [1...255]
 * 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-8]
 * 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 : [0...255].
 * Sampling: 8363 Hz (frequency is turned into a relative tone).
 *
 * Notes:
 * Digibooster Pro 3 seems a poor rewrite in C of Digibooster Pro 2 by another team.
 * We will use the ASM code of Pro 2 as reference.
 *
 * Some files contains notes 0x1f or notes above 0x80, which is illegal according to doc.
 * Checking the player source, 0x1f is a Key off and the period tables uses 8 octaves, not 7.
 * Implementation: Fine volume slides are applied in tick 1, while DigiBooster 3 do it in steps
 * of value/speed.
 * All effects like pitch up/down use a seperate memory.
 * Implementation: Effects in column A & B use different memories (not implemented).
 * This means that effect value 0 as usually no effect.
 * Real tempo is a multiplication factor, so effective tempo is (tempo * real tempo) / 125.
 */

#define NoteDelta (Note_Central-12*4)

static const uint8_t Tag_DBM0[] = "DBM0";
static const uint8_t Tag_NAME[] = "NAME";
static const uint8_t Tag_INFO[] = "INFO";
static const uint8_t Tag_SONG[] = "SONG";
static const uint8_t Tag_INST[] = "INST";
static const uint8_t Tag_PENV[] = "PENV";
static const uint8_t Tag_VENV[] = "VENV";
static const uint8_t Tag_DSPE[] = "DSPE";
static const uint8_t Tag_PATT[] = "PATT";
static const uint8_t Tag_SMPL[] = "SMPL";
static const uint8_t Typestr_DBM0[] = "DigiBooster Pro";
static const char Invalid_Block[] = "Invalid block";
static const char Unexpected_Block[] = "Block not expected in that position";

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

typedef struct
{
	uint8_t		Name[30];
	uint8_t		SampleNr[2];
	uint8_t		Volume[2];
	uint8_t		Frequency[4];
	uint8_t		LoopStart[4];
	uint8_t		LoopEnd[4];
	uint8_t		Panning[2];
	uint8_t		Flags[2];
} LSmp;

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

static void convert_effect(SongHdr* pSong, ChannelInfo* pChInfo, uint32_t effect, uint32_t value)
{
#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;

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

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

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

			Pattern_AddEffect_Pitch(pSong, effect, value);
		}
		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;
			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(pSong, pChInfo, 0x3, 0);
			convert_effect(pSong, pChInfo, 0xa, value);
		}
		break;
		case 6: // Pitch: Vibrato 0 - Volume: Slide, Memory
		{
			convert_effect(pSong, pChInfo, 0x4, 0);
			convert_effect(pSong, pChInfo, 0xa, 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 (pChInfo->Note) Pattern_AddEffect_Instrument(pSong, cmd_sample_set_offset, 0xf00);
		}
		break;
		case 0xa: // Volume: Slide (priority: slide down, up, down, rest is slide up), Memory
		{

			if (value == 0)
			{
				// A00 repeat last slide
				Pattern_AddEffect_NoteVolume(pSong, cmd_last_slide, 0);
			}
			else if (value < 0x10)
			{
				// A0y slide down
				effect = cmd_downmem1 | flag_cmd_slide_frameN0;
				value &= 0x0f;
				Pattern_AddEffect_NoteVolume(pSong, effect, value << 2);
			}
			else if ((value & 0x0f) == 0x0f)
			{
				// AxF up
				effect = cmd_upmem1 | flag_cmd_slide_frame0;
				value &= 0xf0;
				Pattern_AddEffect_NoteVolume(pSong, effect, value >> 2);
			}
			else if ((value & 0xf0) == 0xf0)
			{
				// AFy down
				effect = cmd_downmem1 | flag_cmd_slide_frame0;
				value &= 0x0f;
				Pattern_AddEffect_NoteVolume(pSong, effect, value << 2);
			}
			else
			{
				// Ax0 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);
		}
		case 0xc: // Volume: Set, invalid values are clipped to 64
		{
			if (value > 64) value = 64;
			Pattern_AddEffect_NoteVolume(pSong, cmd_set, value << 2);
		}
		break;
		case 0xd: // Global: Break, decimal
		{
			uint32_t ten = value >> 4;
			value &= 0xf;
			if ((value > 9) || (ten > 9))
			{
				SysLog_BadEffectValue(pSong, 0, oeffect, ovalue);
				value = 0; // Invalid value is treated as 0
			}
			else
			{
				value += ten * 10;
			}
			Pattern_AddGlbEffect_Position(pSong, gcmd_pos_break, value);
		}
		break;
		case 0xe: // Special effects
		{
			effect = value >> 4;
			value &= 0xf;

			switch (effect)
			{
				case 0: // e0 Set filter
				{
					// ignore
				}
				break;
				case 1: // e1 Pitch: Up, No memory
				{
					if (value) Pattern_AddEffect_Pitch(pSong, cmd_up+flag_cmd_slide_frame0, value << 2);
				}
				break;
				case 2: // e2 Pitch: Down, No memory
				{
					if (value) Pattern_AddEffect_Pitch(pSong, cmd_down+flag_cmd_slide_frame0, value << 2);
				}
				break;
				case 3: // e3 Sample: play backward
				{
					// only if a note is defined at the same time
					if (pChInfo->Note) Pattern_AddEffect_Instrument(pSong, cmd_sample_reverse_play, 0);
					else SysLog_SkipEffect(pSong, 0, oeffect, ovalue);
				}
				break;
				case 4: // e40 stop playing sample
				{
					if (!value) Pattern_AddEffect_Instrument(pSong, cmd_sample_note_action_cut, 0);
					else SysLog_BadEffectValue(pSong, 0, oeffect, ovalue);
				}
				break;
				case 5: // turn on/off channel
				{
					if (!value) Pattern_AddEffect_ChannelVolume(pSong, cmd_set, 0);
					else if (value == 1) Pattern_AddEffect_ChannelVolume(pSong, cmd_set, 256);
					else SysLog_BadEffectValue(pSong, 0, oeffect, ovalue);
				}
				break;
				case 6: //e6 Global: Loop in column
				{
					Pattern_AddGlbEffect_Position(pSong, gcmd_pos_loop | value, pChInfo->Channel);
				}
				break;
				case 7: // e7, Note: Set sample offset
				{
					// set offset byte 2
					Pattern_AddEffect_Instrument(pSong, cmd_sample_set_offset, 0x200 | 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 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
				{
					Pattern_AddEffect_RetrigCounter(pSong, cmd_retrig_count_type_MOD, value);
					Pattern_AddEffect_Instrument(pSong, cmd_sample_note_retrig, 8);
				}
				break;
				case 0xa: // ea Volume: Up, No memory
				{
					if (value) Pattern_AddEffect_NoteVolume(pSong, cmd_up+flag_cmd_slide_frame0, value << 2);
				}
				break;
				case 0xb: // eb Volume: Down, No memory
				{
					if (value) Pattern_AddEffect_NoteVolume(pSong, cmd_down+flag_cmd_slide_frame0, value << 2);
				}
				break;
				case 0xc: // ec Note: Cut note after x ticks
				{
					Pattern_AddEffect_NoteVolume(pSong, cmd_volume_cut, value);
				}
				break;
				case 0xd: // ed Note: Delay for x ticks
				{
					Pattern_AddEffect_Instrument(pSong, cmd_sample_note_delay, value);
				}
				break;
				case 0xe: // ee Global: Delay row for x rows
				{
					Pattern_AddGlbEffect_Frames(pSong, gcmd_frames_repeat, value);
				}
				break;
				default:
				{
					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: // Global Volume: Set, invalid values are clipped 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 0x18: // o, Sample: offset Slide (right has priority), Memory
		{
			SysLog_SkipEffect(pSong, 0, oeffect, ovalue);
		}
		break;
		case 0x19: // p, Panning: Slide (right has priority), range [0,256], 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 0x1c: // s, Tempo: real BPM
		{
			SysLog_SkipEffect(pSong, 0, oeffect, ovalue);
		}
		break;
		case 0x1f: // v, DSP
		case 0x20: // w, Echo
		case 0x21: // x, Echo
		case 0x22: // y, Echo
		case 0x23: // z, Echo
		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_ReadReverseInt(pSong, pTagSize, 4);

	return err;
}

static const _kernel_oserror* Pattern_DBM(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, effect, value;
	uint32_t        Note, Inst;
	ChannelInfo     ChInfo;
	ChannelInfo*    pChInfo = &ChInfo;

	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
			channel = *pPos++;
			// next row mark (channel == 0)?
			if (!channel) break;
			channel--;
			// read flags
			if (pPos >= pEnd)
			{
				SysLog_FileCorrupted(SysLog_Medium, pSong, -1, "Pattern truncated");
				break;
			}
			val = *pPos++;
			// will we remain within pattern limits?
			pPos2 = pPos;
			if (val & 0x01) pPos2++;
			if (val & 0x02) pPos2++;
			if (val & 0x04) pPos2++;
			if (val & 0x08) pPos2++;
			if (val & 0x10) pPos2++;
			if (val & 0x20) pPos2++;
			if (pPos2 > pEnd)
			{
				SysLog_FileCorrupted(SysLog_Medium, pSong, -1, "Pattern truncated");
				pPos = pEnd;
				break;
			}
			// valid channel ?
			if ((channel <= lastchannel)
			||  (channel >= pSong->Channels))
			{
				if (channel <= lastchannel)
					SysLog_FileCorrupted(SysLog_High, pSong, - (pEnd - pPos + 2), "Invalid channel %d order in pattern", channel);
				else
					SysLog_FileCorrupted(SysLog_Low, pSong, - (pEnd - pPos + 2), "Invalid channel %d in pattern", channel);
				pPos = pPos2;
				continue;
			}
			lastchannel = channel;

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

			// Read instrument
			if (val & 0x02)
				Inst = *pPos++;
			else
				Inst = 0;

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

			pChInfo->Note = (Note < Note_CmdMin) ?  Note : 0;
			pChInfo->Channel = channel;

			// Read first effect and value
			if (val & 0x04)
				effect = *pPos++;
			else
				effect = 0;
			if (val & 0x08)
				value = *pPos++;
			else
				value = 0;
			convert_effect(pSong, pChInfo, effect, value);

			// Read second effect and value
			if (val & 0x10)
				effect = *pPos++;
			else
				effect = 0;
			if (val & 0x20)
				value = *pPos++;
			else
				value = 0;
			convert_effect(pSong, pChInfo, 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_DBM(SongHdr* pSong)
{
	const _kernel_oserror* err = NULL;
	LSmp*       pLSmp = NULL;
	uint8_t*    pLTrack = NULL;
	uint32_t    val;
	int         i, j;
	Pattern*    pPattern;
	Sample*     pSmp;
	Instrument* pInst;
	Envelope*   pEnv;
	uint32_t    Tag, TagSize;
	uint32_t    FileOffset;
	uint32_t    FileSize = FileLoad_GetSize(pSong);
	uint32_t    samples;
	uint32_t    sections = 0;
	uint32_t    SmpNr[256];

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

	// is it a DBM0 file?
	err = FileLoad_SetPos(pSong, 0);
	if (err) goto loader_err;
	err = FileLoad_ReadInt(pSong, &Tag, 4);
	if (err) goto loader_err;

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

	err = FileLoad_ReadByte(pSong, &val);
	if (err) goto loader_err;
	pSong->Version = val * 100;
	err = FileLoad_ReadByte(pSong, &val);
	if (err) goto loader_err;
	pSong->Version += (val >> 4) * 10 + (val & 0xF);
	err = FileLoad_ReadReverseInt(pSong, &val, 2);
	if (err) goto loader_err;

	pSong->Flags = Song_Flag_Instruments;
	pSong->MinPitch = NoteDelta + 12;
	pSong->MaxPitch = NoteDelta + 12 * 9 - 1;

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

	// 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(NAME))
		{
			// Title
			err = FileLoad_ReadString(pSong, &pSong->pName, TagSize, false);
			if (err) goto loader_err;
		}
		else if (Tag == TAG(INFO))
		{
			if (pSong->Instruments)
			{
				err = Loaders_Error(pSong, FileOffset - 8, Unexpected_Block);
				goto loader_err;
			}

			if (TagSize != 0xA)
			{
				err = Loaders_Error(pSong, FileOffset - 4, Invalid_Block);
				goto loader_err;
			}
			// Nr of Instruments
			err = FileLoad_ReadReverseInt(pSong, &pSong->Instruments, 2);
			if (err) goto loader_err;
			if (!pSong->Instruments
			|| (pSong->Instruments > 255))
			{
				err = Loaders_InvalidSong(pSong, FileOffset, &pSong->Instruments, pSong->Instruments);
				goto loader_err;
			}
			pSong->Samples = pSong->Instruments;
			// Nr of samples data
			err = FileLoad_ReadReverseInt(pSong, &samples, 2);
			if (err) goto loader_err;
			if (!samples
			|| (samples > pSong->Instruments))
			{
				err = Loaders_InvalidSong(pSong, FileOffset + 2, &pSong->Samples, samples);
				goto loader_err;
			}
			// Nr of sections (sub-songs)
			err = FileLoad_ReadReverseInt(pSong, &sections, 2);
			if (err) goto loader_err;

			if (sections != 1)
				SysLog_Log(SysLog_Medium, "Multiple songs (%d)", sections);

			// Nr of patterns
			err = FileLoad_ReadReverseInt(pSong, &pSong->Patterns, 2);
			if (err) goto loader_err;
			if (!pSong->Patterns
			|| (pSong->Patterns > song_max_patterns))
			{
				err = Loaders_InvalidSong(pSong, FileOffset + 6, &pSong->Patterns, pSong->Patterns);
				goto loader_err;
			}
			// Nr of channels
			err = FileLoad_ReadReverseInt(pSong, &pSong->Channels, 2);
			if (err) goto loader_err;
			if (!pSong->Channels
			|| (pSong->Channels > song_max_channels))
			{
				err = Loaders_InvalidSong(pSong, FileOffset + 8, &pSong->Channels, pSong->Channels);
				goto loader_err;
			}
		}
		else if (Tag == TAG(SONG))
		{
			SubSong* pBaseSubSong = Song_GetSubSong(pSong, 0);
			SubSong* pSubSong;
			uint32_t size;

			if (pBaseSubSong->SeqLen || !sections)
			{
				err = Loaders_Error(pSong, FileOffset - 8, Unexpected_Block);
				goto loader_err;
			}
			size = TagSize;

			for (int s = sections; s; s--)
			{
				if (pBaseSubSong->SeqLen)
				{
					err = Song_CopySubSong(pSong, &pSubSong, pBaseSubSong);
					if (err) goto loader_err;
				}
				else pSubSong = pBaseSubSong;

				if (size < 46)
				{
					err = Loaders_Error(pSong, FileOffset - 8, Unexpected_Block);
					goto loader_err;
				}
				// Skip sub-song name
				err = FileLoad_Skip(pSong, 44);
				if (err) goto loader_err;
				// Nr of Orders
				err = FileLoad_ReadReverseInt(pSong, &pSubSong->SeqLen, 2);
				if (err) goto loader_err;
				size -= 46;
				if (pSubSong->SeqLen > size)
				{
					err = Loaders_InvalidSong(pSong, -2, &pBaseSubSong->SeqLen, pSubSong->SeqLen);
					goto loader_err;
				}
				err = Loaders_AllocSeq(pSong, pSubSong);
				if (err) goto loader_err;
				// Sequence
				for (int seq = 0; seq < pSubSong->SeqLen; seq++)
				{
					err = FileLoad_ReadReverseInt(pSong, &val, 2);
					pSubSong->pSeqs[seq] = val;
					if (err) goto loader_err;
				}
			}
		}
		else if (Tag == TAG(INST))
		{
			if (!pSong->Instruments)
			{
				err = Loaders_Error(pSong, FileOffset - 8, Unexpected_Block);
				goto loader_err;
			}

			if (TagSize != 50*pSong->Instruments)
			{
				err = Loaders_Error(pSong, FileOffset - 4, Invalid_Block);
				goto loader_err;
			}

			// Read Samples definitions
			for (i = 0, pSmp = pSong->pSamples, pInst = pSong->pInstruments
			    ; i < pSong->Samples
			    ; i++, pSmp++, pInst++
			    )
			{
				int frequency;

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

				NoteMap* pn;
				for (j = 0, pn = pInst->pNotesMap; j < Note_Max; j++, pn++)
				{
					pn->Volume = 255;
					pn->SampleNr = i + 1;
					pn->Pitch = j << 8;
				}

				err = FileLoad_Read(pSong, pLSmp, 50);
				if (err) goto loader_err;

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

				// Sample nr, real size not known yet
				SmpNr[i] = (pLSmp->SampleNr[0] << 8) + pLSmp->SampleNr[1];

				// Volume [0-64]
				pSmp->DefaultVolume = ((pLSmp->Volume[0] << 8) + pLSmp->Volume[1]) << 2;
				pSmp->Frequency = 8363;
				frequency = (pLSmp->Frequency[0] << 24) + (pLSmp->Frequency[1] << 16)
				          + (pLSmp->Frequency[2] << 8) + pLSmp->Frequency[3];
				pSmp->RelTone   = Convert_RelTone(pSmp->Frequency, frequency);
				pSmp->LoopStart = (pLSmp->LoopStart[0] << 24) + (pLSmp->LoopStart[1] << 16)
				                + (pLSmp->LoopStart[2] << 8) + pLSmp->LoopStart[3];
				pSmp->LoopEnd   = pSmp->LoopStart
				                + (pLSmp->LoopEnd[0] << 24) + (pLSmp->LoopEnd[1] << 16)
				                + (pLSmp->LoopEnd[2] << 8) + pLSmp->LoopEnd[3];
				pSmp->Panning   = ((pLSmp->Panning[0] << 8) + pLSmp->Panning[1]) + 128;
				pSmp->Type = 0;
				if (pLSmp->Flags[1] & 1)
					pSmp->Type |= Smp_Type_Loop;
				else if (pLSmp->Flags[1] & 2)
					pSmp->Type |= Smp_Type_Loop | Smp_Type_Loop_Bidi;
			}
		}
		else if (Tag == TAG(PATT))
		{
			if (!pSong->Patterns)
			{
				err = Loaders_Error(pSong, FileOffset - 8, Unexpected_Block);
				goto loader_err;
			}

			// Read Patterns
			for (i = 0, pPattern = pSong->pPatterns; i < pSong->Patterns; i++, pPattern++)
			{
				uint32_t size;

				// Read pattern rows
				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;
				}
				// Read data length
				err = FileLoad_ReadReverseInt(pSong, &size, 4);

				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_DBM(pSong, i, pPattern, pLTrack, pLTrack + size);
				if (err) goto loader_err;

				Loaders_Free(pSong, pLTrack);
				pLTrack = NULL;
			}
		}
		else if (Tag == TAG(SMPL))
		{
			if (!pSong->Instruments)
			{
				err = Loaders_Error(pSong, FileOffset - 8, Unexpected_Block);
				goto loader_err;
			}

			// Read Samples data
			for (i = 1; i <= samples; i++)
			{
				for (j = 0, pSmp = pSong->pSamples; j < pSong->Samples; j++, pSmp++)
				{
					// Sample data number was stored in sample size
					if (SmpNr[j] == i)
					{
						uint32_t flags;

						SmpNr[j] = 0;
						err = FileLoad_ReadReverseInt(pSong, &flags, 4);
						if (err) goto smp_err;
						err = FileLoad_ReadReverseInt(pSong, &pSmp->Size, 4);
						if (err) goto smp_err;

						if (flags == 2)
							pSmp->Type |= Smp_Type_16bit | Smp_Type_BigEndian;
						else if (flags != 1)
						{
							err = Loaders_Error(pSong, FileOffset - 8, Invalid_Block);
							goto smp_err;
						}

						err = FileLoad_ReadSample(pSong, pSmp);
						if (err) goto loader_err;
smp_err:
						// Accept files truncated within the samples
						if (err)
						{
							SysLog_FileCorrupted(SysLog_High, pSong, -8, "Sample data could not be read");
							pSmp->Size = 0;
							err = NULL;
						}

						// Duplicates ?
						for (j++; j < pSong->Samples; j++)
						{
							if (SmpNr[j] == i)
							{
								Sample* pSmp2 = &pSong->pSamples[j];
								int size = pSmp->Size;

								SmpNr[j] = 0;
								if (flags == 2)
								{
									pSmp2->Type |= Smp_Type_16bit | Smp_Type_BigEndian;
									size <<= 1;
								}
								err = Loaders_Alloc(pSong, &pSmp2->Ptr, size + 4);
								if(err) goto loader_err;
								memcpy(pSmp2->Ptr, pSmp->Ptr, size + 4);
							}
						}
					}
				}
			}

			for (j = 0; j < pSong->Samples; j++)
			{
				if ((SmpNr[j] > 0) && (SmpNr[j] < samples))
				{
					SysLog_FileCorrupted(SysLog_High, pSong, -8, "Sample %d data missing", j);
					pSmp = &pSong->pSamples[j];
					pSmp->Size = 0;
				}
			}
		}
		else if (Tag == TAG(VENV))
		{
			uint32_t count, pos;

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

			// Read number of envelopes
			err = FileLoad_ReadReverseInt(pSong, &count, 2);
			if (err) goto loader_err;

			if (TagSize < 2 + 136*count)
			{
				err = Loaders_Error(pSong, FileOffset - 4, Invalid_Block);
				goto loader_err;
			}

			for (i = 0; i < count; i++)
			{
				// Instrument nr
				err = FileLoad_ReadReverseInt(pSong, &val, 2);
				if (err) goto loader_err;

				if (!val || val > pSong->Instruments)
				{
					SysLog_FileCorrupted(SysLog_High, pSong, -2, "Invalid Instrument %d in envelopes", val);
					// Skip envelope
					FileLoad_SetPos(pSong, FileLoad_GetPos(pSong) + 134);
				}
				else
				{
					pInst = &pSong->pInstruments[val - 1];
					pEnv = &pInst->VolumeEnvelope;

					err = FileLoad_ReadByte(pSong, &val);
					if (err) goto loader_err;
					pEnv->Flags = 0;
					if (val & 1) pEnv->Flags |= Envelope_Flag_On;
					if (val & 2) pEnv->Flags |= Envelope_Flag_Sustain;
					if (val & 4) pEnv->Flags |= Envelope_Flag_Loop;
					// if (val & 8) second sustain not supported
					err = FileLoad_ReadByte(pSong, &val);
					if (err) goto loader_err;
					pEnv->Points = val + 1;
					if (pEnv->Points > 32) pEnv->Points = 32;
					err = FileLoad_ReadByte(pSong, &val);
					if (err) goto loader_err;
					pEnv->SustainStart = val;
					pEnv->SustainEnd = val;
					err = FileLoad_ReadByte(pSong, &val);
					if (err) goto loader_err;
					pEnv->LoopStart = val;
					err = FileLoad_ReadByte(pSong, &val);
					if (err) goto loader_err;
					pEnv->LoopEnd = val;
					err = FileLoad_ReadByte(pSong, &val); // second sustain
					if (err) goto loader_err;

					// Allocate table
					err = Loaders_Alloc(pSong, (void**) &pEnv->pTable, 4*32);
					if (err) goto loader_err;

					for (j = 0; j < 32; j++)
					{
						err = FileLoad_ReadReverseInt(pSong, &pos, 2);
						if (err) goto loader_err;
						err = FileLoad_ReadReverseInt(pSong, &val, 2);
						if (err) goto loader_err;

						if (val > 64) val = 256;
						else val <<= 2;
						pEnv->pTable[j] = pos + (val << 16);
					}
				}
			}
		}
		else if (Tag == TAG(PENV))
		{
			uint32_t count, pos;

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

			// Read number of envelopes
			err = FileLoad_ReadReverseInt(pSong, &count, 2);
			if (err) goto loader_err;

			if (TagSize < 2 + 136*count)
			{
				err = Loaders_Error(pSong, FileOffset - 4, Invalid_Block);
				goto loader_err;
			}

			for (i = 0; i < count; i++)
			{
				// Instrument nr
				err = FileLoad_ReadReverseInt(pSong, &val, 2);
				if (err) goto loader_err;

				if (!val || val > pSong->Instruments)
				{
					SysLog_FileCorrupted(SysLog_High, pSong, -2, "Invalid Instrument %d in envelopes", val);
					// Skip envelope
					FileLoad_SetPos(pSong, FileLoad_GetPos(pSong) + 134);
				}
				else
				{
					pInst = &pSong->pInstruments[val - 1];
					pEnv = &pInst->PanningEnvelope;

					err = FileLoad_ReadByte(pSong, &val);
					if (err) goto loader_err;
					pEnv->Flags = 0;
					if (val & 1) pEnv->Flags |= Envelope_Flag_On;
					if (val & 2) pEnv->Flags |= Envelope_Flag_Sustain;
					if (val & 4) pEnv->Flags |= Envelope_Flag_Loop;
					// if (val & 8) second sustain not supported
					err = FileLoad_ReadByte(pSong, &val);
					if (err) goto loader_err;
					pEnv->Points = val + 1;
					if (pEnv->Points > 32) pEnv->Points = 32;
					err = FileLoad_ReadByte(pSong, &val);
					if (err) goto loader_err;
					pEnv->SustainStart = val;
					pEnv->SustainEnd = val;
					err = FileLoad_ReadByte(pSong, &val);
					if (err) goto loader_err;
					pEnv->LoopStart = val;
					err = FileLoad_ReadByte(pSong, &val);
					if (err) goto loader_err;
					pEnv->LoopEnd = val;
					err = FileLoad_ReadByte(pSong, &val); // second sustain
					if (err) goto loader_err;

					// Allocate table
					err = Loaders_Alloc(pSong, (void**) &pEnv->pTable, 4*32);
					if (err) goto loader_err;

					for (j = 0; j < 32; j++)
					{
						err = FileLoad_ReadReverseInt(pSong, &pos, 2);
						if (err) goto loader_err;
						err = FileLoad_ReadReverseInt(pSong, &val, 2);
						if (err) goto loader_err;

						val += 128;
						if (val > 256) val = 128;
						pEnv->pTable[j] = pos + (val << 16);
					}
				}
			}
		}
		else if (Tag == TAG(DSPE))
		{
			// DSP Echo handling
		}
		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, pLTrack);
	Loaders_Free(pSong, pLSmp);

	return err;
}
