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

/*
 * Channels: [1...16]
 * Patterns: [1...]
 * Rows    : [1...255]
 * Orders  : [1...4096]
 * Samples : [1...63]
 * Instr.  : 0
 * Tempo   : [1...255], T frames per 1 seconds.
 * Speed   : [1...255], S frames per row.
 * Notes   : [C-0...C-1...B-2]
 * 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/32 units per row.
 * Volume  : [0...64] in linear mode or [0...127] in logarithmic mode.
 * Panning : [1...7].
 * Sampling: Frequency derived from a period and a note.
 *
 * Notes:
 * In the destop player module, pitch range is either 113-826 (Amiga period range)
 * or 1152-32 (Linear mode in 1/32 semitone).
 *
 * Tempo base is 1Hz, we will use the 20Hz one internally.
 *
 * Example loader in doc uses period = &1DC030C and Note = 13, for MOD loader,
 * so we will use this as reference for converting sample period & note into a sample frequency.
 */

typedef struct
{
	uint32_t    TAG;
	uint8_t     Name[64];
	uint8_t     Author[64];
	uint32_t    Flags;
	uint32_t    Channels;
	uint32_t    Orders;
	uint8_t     Stereo[8];
	uint32_t    Speed;
	uint32_t    RestartPos;
	uint32_t    Patterns;
	uint32_t    Samples;
} LHdr;

typedef struct
{
	uint8_t     Note;
	uint8_t     Volume;
	uint8_t     Dummy[2];
	uint32_t    Period;
	uint32_t    SustainStart;
	uint32_t    SustainLength;
	uint32_t    LoopStart;
	uint32_t    LoopLength;
	uint32_t    Size;
	uint8_t     Name[32];
	int32_t     Offset;
} LSmp;

#define NoteDelta   (Note_Central-12)

static const uint8_t Tag_DskT[] = "DskT";
static const uint8_t Tag_EskT[] = "EskT";
static const uint8_t Typestr_DskT[] = "Desktop Tracker";
static const uint8_t Panning[8] = {128, 2, 44, 86, 128, 170, 212, 254};

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

static void convert_effect
	( SongHdr* pSong
	, const LHdr* pLHdr
	, uint32_t channel
	, uint32_t effect
	, uint32_t value
	)
{
	uint32_t ovalue = value;

	switch(effect)
	{
		case 0: // 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 1: // Pitch: Slide up
		{
			if (pLHdr->Flags & 2) // Amiga or linear
				value <<= 2;
			else
				value <<= 1;
			Pattern_AddEffect_Pitch(pSong,  cmd_upmem1+flag_cmd_slide_frame0N0, value);
		}
		break;
		case 2: // Pitch: Slide down
		{
			if (pLHdr->Flags & 2) // Amiga or linear
				value <<= 2;
			else
				value <<= 1;
			Pattern_AddEffect_Pitch(pSong, cmd_downmem1+flag_cmd_slide_frame0N0, value);
		}
		break;
		case 3: // Pitch: Portamento
		{
			if (pLHdr->Flags & 2) // Amiga or linear
				value <<= 2;
			else
				value <<= 1;
			Pattern_AddEffect_Pitch(pSong, cmd_portamento+flag_cmd_slide_frame0N0, value);
		}
		break;
		case 4: // Pitch: Vibrato, 00 is memory, 0y speed = 0, x0 amplitude= 0 !!!!
		{
			uint32_t speed = value >> 4;
			value &= 0xf;
			value <<= 1; // Vibrate between [-2*y, 2*y]
			if (value) value -= 1;
			if (pLHdr->Flags & 2) // Amiga or linear, range conversion
				value <<= 2;
			else
				value <<= 1;
			if (ovalue) Pattern_AddEffect_Pitch(pSong, cmd_vibrato_speed, speed << 2);
			Pattern_AddEffect_Pitch(pSong, cmd_vibrato_depth | flag_cmd_vibrato_frame0N0, value);
		}
		break;
		case 5: // delayed note on another channel xy x = channel, y = delay
		{
			uint32_t ch = value >> 4;
			value &= 0xf;
			// Can't put back note on correct channel but we can set the delay
			if (value) Pattern_AddEffect_Instrument(pSong, cmd_sample_note_delay, value);
			if (ch != channel) SysLog_SkipEffect(pSong, 0, effect, ovalue);
		}
		break;
		case 6: // play end of note, offset by 256*xx or by xxxxx (4 effects on row)
		{
			Pattern_AddEffect_Instrument(pSong, cmd_sample_set_offset, 0x200);
			Pattern_AddEffect_Instrument(pSong, cmd_sample_set_offset, 0x100 | value);
			Pattern_AddEffect_Instrument(pSong, cmd_sample_set_offset, 0x000);
			Pattern_AddEffect_Instrument(pSong, cmd_sample_set_offset, 0xF00);
			Pattern_AddEffect_Instrument(pSong, cmd_sample_loop_off, 0);

			SysLog_SkipEffect(pSong, 0, effect, ovalue);
		}
		break;
		case 7: // Volume: Vibrato (alias Tremolo), 00 is memory, 0y speed = 0, x0 amplitude= 0 !!!!
		{
			uint32_t speed = value >> 4;
			value &= 0xf;
			value <<= 2; // vibrate between [-4*y, 4*y]
			if (pLHdr->Flags & 1) // linear or log, range conversion
				value <<= 2;
			else
				value <<= 1;
			if (ovalue) Pattern_AddEffect_NoteVolume(pSong, cmd_vibrato_speed, speed << 2);
			Pattern_AddEffect_NoteVolume(pSong, cmd_vibrato_depth | flag_cmd_vibrato_frame0N0, value);
		}
		break;
		case 8: // phasor effect #2, sample pos += xx
		{
			if (value) SysLog_SkipEffect(pSong, 0, effect, ovalue);
		}
		break;
		case 9: // phasor effect #1, sample pos += xx*256
		{
			if (value) SysLog_SkipEffect(pSong, 0, effect, ovalue);
		}
		break;
		case 0xa: // Volume: Slide, signed, Memory
		{
			if (value == 0)
			{
				Pattern_AddEffect_NoteVolume(pSong, cmd_last_slide, flag_cmd_slide_frame0N0 << 4);
			}
			else
			{
				effect = flag_cmd_slide_frame0N0;
				if (value < 0x80)
					effect += cmd_upmem1;
				else
				{
					effect += cmd_downmem1;
					value = 0x100 - value;
				}
				if (pLHdr->Flags & 1) // linear or log
					value <<= 2;
				else
					value <<= 1;
				Pattern_AddEffect_NoteVolume(pSong, effect, value);
			}
		}
		break;
		case 0xb: // Global: Jump
		{
			Pattern_AddGlbEffect_Position(pSong, gcmd_pos_jump, value);
		}
		break;
		case 0xc: // Volume: Set
		{
			if (pLHdr->Flags & 1) // linear or log
			{
				if (value <= 64)
					Pattern_AddEffect_NoteVolume(pSong, cmd_set, value << 2);
				else
					SysLog_BadEffectValue(pSong, 0, effect, ovalue);
			}
			else
			{
				if (value <= 127)
				{
					Pattern_AddEffect_NoteVolume(pSong, cmd_set, value << 1);
				}
				else
					SysLog_BadEffectValue(pSong, 0, effect, ovalue);
			}
		}
		break;
		case 0xd: // Panning: Set
		{
			if ((value >= 1) && (value <= 7))
			{
				Pattern_AddEffect_Panning(pSong, cmd_set, Panning[value]);
			}
		}
		break;
		case 0xe: // stereo slide (with rebound) xy, x = delay, y = delta (signed?)
		{
			SysLog_SkipEffect(pSong, 0, effect, ovalue);
		}
		break;
		case 0xf: // Global: Set speed
		{
			if (value)
				Pattern_AddGlbEffect_Frames(pSong, gcmd_frames_set, value);
			else
				SysLog_BadEffectValue(pSong, 0, effect, ovalue);
		}
		break;
		case 0x10: // set arpegio speed xx, 1 is default
		{
			SysLog_SkipEffect(pSong, 0, effect, ovalue);
		}
		break;
		case 0x11: // Pitch: Slide fine, signed, No memory
		{
			if (value == 0)
			{
				Pattern_AddEffect_Pitch(pSong, cmd_last_slide, flag_cmd_slide_frame0 << 4);
			}
			else
			{
				effect = flag_cmd_slide_frame0;
				if (value < 0x80)
					effect += cmd_upmem1;
				else
				{
					effect += cmd_downmem1;
					value = 0x100 - value;
				}
				if (pLHdr->Flags & 2) // Amiga or linear
					value <<= 2;
				else
					value <<= 1;
				Pattern_AddEffect_Pitch(pSong, effect, value);
			}
		}
		break;
		case 0x12: // clear repeat after xx frames
		{
			Pattern_AddEffect_Instrument(pSong, cmd_sample_loop_off, 0);

			SysLog_SkipEffect(pSong, 0, effect, ovalue);
		}
		break;
		case 0x14: // Pitch: Vibrato type
		{
			switch(value)
			{
				case 1: value = vibrato_type_ramp; break;
				case 2: value = vibrato_type_square; break;
				case 3: value = vibrato_type_saw; break;
				default: value = vibrato_type_sin; break;
			}
			if (value)
				Pattern_AddEffect_Pitch(pSong, cmd_vibrato_type, value);
			else
				SysLog_BadEffectValue(pSong, 0, effect, ovalue);
		}
		break;
		case 0x16: // Global: Loop in column
		{
			Pattern_AddGlbEffect_Position(pSong, gcmd_pos_loop | value, channel);
		}
		break;
		case 0x17: // Volume: Vibrato type
		{
			switch(value)
			{
				case 1: value = vibrato_type_ramp; break;
				case 2: value = vibrato_type_square; break;
				case 3: value = vibrato_type_saw; break;
				default: value = vibrato_type_sin; break;
			}
			if (value)
				Pattern_AddEffect_NoteVolume(pSong, cmd_vibrato_type, value);
			else
				SysLog_BadEffectValue(pSong, 0, effect, ovalue);
		}
		break;
		case 0x18: // Global: Set tempo
		{
			if (value)
			{
				value *= 4;
				Pattern_AddGlbEffect_Tempo(pSong, cmd_set, value);
			}
			else
				SysLog_BadEffectValue(pSong, 0, effect, ovalue);
		}
		break;
		case 0x19: // Note: Retrig note
		{
			Pattern_AddEffect_RetrigCounter(pSong, cmd_retrig_count_type_MOD, value);
			Pattern_AddEffect_Instrument(pSong, cmd_sample_note_retrig, 8);
		}
		break;
		case 0x1a: // Volume: Slide fine, signed, memory
		{
			if (value == 0)
			{
				Pattern_AddEffect_NoteVolume(pSong, cmd_last_slide, flag_cmd_slide_frame0 << 4);
			}
			else
			{
				effect = flag_cmd_slide_frame0;
				if (value < 0x80)
					effect += cmd_upmem1;
				else
				{
					effect += cmd_downmem1;
					value = 0x100 - value;
				}
				if (pLHdr->Flags & 1) // linear or log
					value <<= 2;
				else
					value <<= 1;
				Pattern_AddEffect_NoteVolume(pSong, effect, value);
			}
		}
		break;
		case 0x1b: // hold sustain for x ticks
		{
			SysLog_SkipEffect(pSong, 0, effect, ovalue);
		}
		break;
		case 0x1c: // Note: Cut note after x ticks
		{
			Pattern_AddEffect_NoteVolume(pSong, cmd_volume_cut, value);
		}
		break;
		case 0x1d: // Note: Delay note for x ticks
		{
			Pattern_AddEffect_Instrument(pSong, cmd_sample_note_delay, value);
		}
		break;
		case 0x1e: // Global: Delay row for x rows
		{
			Pattern_AddGlbEffect_Frames(pSong, gcmd_frames_repeat, value);
		}
		break;
		case 0x1f: // call linked code
		{
			Pattern_AddEffect_Instrument(pSong, cmd_upcall, value);
		}
		break;
		default:
		{
			// Unknown effect
			SysLog_BadEffect(pSong, 0, effect, value);
		}
	}
}

static const _kernel_oserror* Align(SongHdr* pSong)
{
	int pos = FileLoad_GetPos(pSong);
	const _kernel_oserror* err = NULL;
	uint32_t dummy;

	while (pos & 3)
	{
		err = FileLoad_ReadByte(pSong, &dummy);
		if (err) return err;
		pos++;
	}

	return err;
}

static const _kernel_oserror* lzwr_decompress(SongHdr* pSong, uint8_t* buffer, uint32_t len, uint32_t srclen)
{
	const _kernel_oserror* err = NULL;
	uint8_t *to, *toend, *src;
	uint32_t control = 1;
	const uint8_t *from, *fromend;

	// Reserve storage
	err = Loaders_Alloc(pSong, (void**) &src, srclen);
	if (err) return err;
	err = FileLoad_Read(pSong, src, srclen);
	if (err) return err;

	to = buffer;
	toend = to + len;
	from = src;
	fromend = src + srclen;

	while ((to < toend) && (from < fromend))
	{
		if (control == 1)
		{
			control = from[0] + (from[1] << 8);
			control += (1 << 16);
			from += 2;
		}
		if (control & 1)
		{
			int len = 1 + (from[0] & 0x0F);
			uint8_t* to1 = to - (((from[0] & 0xF0) << 4) + from[1]);
			from += 2;

			if ((to1 < buffer)
			||  (to + len > toend))
			{
				err = Loaders_Error(pSong, - srclen + (from - src), "Decompression error");
				goto lzwr_error;
			}

			while (len--)
				*to++ = *to1++;
		}
		else
			*to++ = *from++;

		control >>= 1;
	}

	while (to < toend)
		*to++ = 0;

lzwr_error:
	Loaders_Free(pSong, src);

	return err;

}

static const _kernel_oserror* ReadBlock
( SongHdr* pSong
, void** pptr
, void** pendptr
, int32_t Offset
, int32_t NextOffset
, int32_t dstsize)
{
	const _kernel_oserror* err = NULL;
	int32_t offset = (Offset >= 0) ? Offset : -Offset;
	int32_t noffset = (NextOffset >= 0) ? NextOffset : -NextOffset;
	int32_t srcsize = noffset - offset;
	int32_t pos = FileLoad_GetPos(pSong);

	if ((offset > pos)
	||  (!noffset && (offset != pos)))
		FileLoad_SetPos(pSong, offset);

	if (offset != FileLoad_GetPos(pSong))
	{
		err = Loaders_Error(pSong, FileLoad_GetPos(pSong), "Bad file offset %x", offset);
		return err;
	}

	if (Offset >= 0)
	{
		if (dstsize)
		{
			if ((noffset)
			&&  ((srcsize > dstsize) || (srcsize + 4 <= dstsize)))
			{
				err = Loaders_Error(pSong, FileLoad_GetPos(pSong), "Bad block size");
				return err;
			}
			srcsize = dstsize;
		}

		if (srcsize <= 0)
		{
			err = Loaders_Error(pSong, FileLoad_GetPos(pSong), "Invalid block size");
			return err;
		}

		err = Loaders_Alloc(pSong, pptr, srcsize + 4);
		if (err) return err;

		err = FileLoad_Read(pSong, *pptr, srcsize);
		if (err) return err;

		*pendptr = ((uint8_t*)*pptr) + srcsize;
	}
	else
	{
		uint32_t srclen, dstlen, val;

		err = FileLoad_ReadInt(pSong, &dstlen, 4);
		if (err) return err;
		if (dstsize && (dstsize != dstlen))
		{
			err = Loaders_Error(pSong, -4, "Bad uncompressed size");
			return err;
		}

		err = FileLoad_ReadInt(pSong, &srclen, 4);
		if (err) return err;
		srclen -= 4;
		if ((noffset)
		&&  ((srclen + 12 > srcsize) || (srclen + 16 <= srcsize)))
		{
			err = Loaders_Error(pSong, -4, "Bad compressed size");
			return err;
		}

		err = FileLoad_ReadInt(pSong, &val, 4);
		if (err) return err;
		val &= 0xFF;
		if (val > 1)
		{
			err = Loaders_Error(pSong, -4, "Bad compression flag");
			return err;
		}

		err = Loaders_Alloc(pSong, pptr, dstlen + 4);
		if (err) return err;

		if (val)
		{
			err = FileLoad_Read(pSong, *pptr, dstlen);
			if (err) return err;
		}
		else
		{
			err = lzwr_decompress(pSong, *pptr, dstlen, srclen);
			if (err) return err;
		}

		*pendptr = ((uint8_t*)*pptr) + dstlen;
	}

	return NULL;
}

const _kernel_oserror* Loader_DskT(SongHdr* pSong)
{
	const _kernel_oserror* err = NULL;
	SubSong*        pSubSong = Song_GetSubSong(pSong, 0);
	LHdr*           pLHdr = NULL;
	LSmp*           pLSmp = NULL;
	int32_t*        pPatOffsets = NULL;
	uint8_t*        pPatSizes = NULL;
	LSmp*           pLSmpTable = NULL;
	uint8_t*        pBlock = NULL;
	uint8_t*        pEndBlock;
	ChannelDefault* pDef;
	Pattern*        pPattern;
	Sample*         pSmp;
	uint32_t        val;
	int             i, j, k;
	const uint8_t*  SmpLogToLin = Table_ArcSampleLogToLin();

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

	// load header
	err = FileLoad_SetPos(pSong, 0);
	if (err) goto loader_err;
	err = FileLoad_Read(pSong, pLHdr, sizeof(LHdr));
	if (err) goto loader_err;

	// check if really DstT Module?
	if ((pLHdr->TAG != TAG(DskT))
	&&  (pLHdr->TAG != TAG(EskT)))
	{
		err = Loaders_NotThisType;
		goto loader_err;
	}

	// some sanity checks
	if (!pLHdr->Channels || (pLHdr->Channels > 16))
	{
		err = Loaders_InvalidSong
				( pSong, ((char*) &pLHdr->Channels) - ((char*) pLHdr)
				, &pSong->Channels, pLHdr->Channels
				);
		goto loader_err;
	}
	if (!pLHdr->Orders || (pLHdr->Orders > 4096))
	{
		err = Loaders_InvalidSong
				( pSong, ((char*) &pLHdr->Orders) - ((char*) pLHdr)
				, &pSubSong->SeqLen, pLHdr->Orders
				);
		goto loader_err;
	}
	if (!pLHdr->Patterns || (pLHdr->Patterns > song_max_patterns))
	{
		err = Loaders_InvalidSong
				( pSong, ((char*) &pLHdr->Patterns) - ((char*) pLHdr)
				, &pSong->Patterns, pLHdr->Patterns);
		goto loader_err;
	}
	if (!pLHdr->Samples || (pLHdr->Samples > 63))
	{
		err = Loaders_InvalidSong
				( pSong, ((char*) &pLHdr->Samples) - ((char*) pLHdr)
				, &pSong->Samples, pLHdr->Samples);
		goto loader_err;
	}

	// set version
	pSong->Version = 100;
	pSong->Flags = 0;
	pSong->MinPitch = NoteDelta;
	pSong->MaxPitch = NoteDelta + 12 * 3 - 1;
	pSong->MaxSpeed = 255;
	pSong->TempoBase = Tempo_Base_4Hz;
	pSong->MinTempo = 1*4;
	pSong->MaxTempo = 255*4;
	pSubSong->Defaults.Tempo = 50*4;
	if ((pLHdr->Flags & 1) == 0) // log volume
		pSong->VolumeTranslation = Table_ArcVolumeLogToLin();

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

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

	pSubSong->SeqLen = pLHdr->Orders;
	pSong->Patterns = pLHdr->Patterns;
	pSong->Samples  = pLHdr->Samples;
	pSong->Channels = pLHdr->Channels;
	pSubSong->RestartPos = pLHdr->RestartPos;

	if (pLHdr->Speed) pSubSong->Defaults.Speed = pLHdr->Speed;
	if (!(pLHdr->Flags & 2)) pSong->Flags |= Song_Flag_Linear;

	// read channels settings
	for (i = 0, pDef = pSubSong->Defaults.ChDef; i < pSong->Channels; i++, pDef++)
	{
		pDef->Panning = Panning[pLHdr->Stereo[i] & 7];
	}

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

	// fill the pattern sequence
	for (i = 0; i < pSubSong->SeqLen; i++)
	{
		err = FileLoad_ReadByte(pSong, &val);
		if (err) goto loader_err;
		pSubSong->pSeqs[i] = val;
	}
	// Align
	err = Align(pSong);
	if (err) goto loader_err;

	// read offset tables
	val = 4 * pSong->Patterns;
	err = Loaders_Alloc(pSong, (void**) &pPatOffsets, val + 4);
	if (err) goto loader_err;
	err = FileLoad_Read(pSong, pPatOffsets, val);
	if (err) goto loader_err;
	pPatOffsets[pSong->Patterns] = 0;
	// read patterns lengths + align
	val = pSong->Patterns;
	err = Loaders_Alloc(pSong, (void**) &pPatSizes, val);
	if (err) goto loader_err;
	err = FileLoad_Read(pSong, pPatSizes, val);
	if (err) goto loader_err;
	// Align
	err = Align(pSong);
	if (err) goto loader_err;

	// Read samples info
	val = pSong->Samples * sizeof(*pLSmp);
	err = Loaders_Alloc(pSong, (void**) &pLSmpTable, val);
	if (err) goto loader_err;
	err = FileLoad_Read(pSong, pLSmpTable, val);
	if (err) goto loader_err;

	// decode samples info
	for ( i = 0, pSmp = pSong->pSamples, pLSmp = pLSmpTable
		; i < pSong->Samples
		; i++, pSmp++, pLSmp++
		)
	{
		// read name
		err = Loaders_String(pSong, &pSmp->pName, &pLSmp->Name[0], sizeof(pLSmp->Name), false);
		if (err) goto loader_err;

		// read sizes
		if (pLSmp->Offset)
		{
			if (!pPatOffsets[pSong->Patterns])
				pPatOffsets[pSong->Patterns] = pLSmp->Offset;

			pSmp->Size = pLSmp->Size;
		}
		else pSmp->Size = 0;
		pSmp->LoopStart    = pLSmp->LoopStart;
		pSmp->LoopEnd      = pLSmp->LoopStart + pLSmp->LoopLength;
		pSmp->SustainStart = pLSmp->SustainStart;
		pSmp->SustainEnd   = pLSmp->SustainStart + pLSmp->SustainLength;
		pSmp->Frequency    = (0x1dc3*8363) / (pLSmp->Period >> 8);
		pSmp->Frequency    = (pSmp->Frequency << 8) / (Convert_ToneToFrac((Note_Central - 13 + pLSmp->Note) << 8) >> 8);

		// read volume
		if (pLHdr->Flags & 1) // linear or log
		{
			if (pLSmp->Volume > 64) pSmp->DefaultVolume = 256;
			else pSmp->DefaultVolume = pLSmp->Volume << 2;
		}
		else
		{
			if (pLSmp->Volume <= 127)
				pSmp->DefaultVolume = pLSmp->Volume << 1;
			else
				pSmp->DefaultVolume = 256;
		}

		// read sample type
		pSmp->Type = 0;
		if (pLSmp->LoopLength) pSmp->Type |= Smp_Type_Loop;
		if (pLSmp->SustainLength) pSmp->Type |= Smp_Type_Sustain;
	}

	// Comments?
	val = (*pPatOffsets >= 0) ? *pPatOffsets : -*pPatOffsets;
	val -= FileLoad_GetPos(pSong);
	if (val > 0)
	{
		uint32_t val2;

		if (val < 4)
		{
			err = Loaders_Error(pSong, FileLoad_GetPos(pSong), "Bad comment block");
			goto loader_err;
		}

		err = FileLoad_ReadInt(pSong, &val2, 4);
		if (err) goto loader_err;
		if (val2 != 0xfff)
		{
			err = Loaders_Error(pSong, FileLoad_GetPos(pSong), "Bad comment block");
			goto loader_err;
		}

		val -= 4;
		err = FileLoad_ReadString(pSong, &pSong->pComments, val, false);
		if (err) goto loader_err;
		pSong->CommentsLen = val;
	}

	// fill the patterns
	// pOffset still up to date
	for (i = 0, pPattern = pSong->pPatterns; i < pSong->Patterns; i++, pPattern++)
	{
		uint32_t Note, Inst;
		const uint32_t *pval, *pvalend;

		pPattern->Rows = pPatSizes[i];
		err = ReadBlock
				( pSong
				, (void**) &pBlock
				, (void**) &pEndBlock
				, pPatOffsets[i]
				, pPatOffsets[i+1]
				, 0
				);
		if (err) goto loader_err;

		pval = (const uint32_t*) pBlock;
		pvalend = (const uint32_t*) pEndBlock;

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

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

			for (k = 0; k < pSong->Channels; k++)
			{
				if ((pval >= pvalend) // 1 effect
				||  ((((pval[0] >> 17) & 0x7f) != 0) && ((pval + 1) >= pvalend))) // 4 effects
				{
					SysLog_FileCorrupted
						( SysLog_High, pSong, FileLoad_GetPos(pSong)
						, "%03d:%03d:%02d: End of pattern block"
						, i, j, k
						);
					break;
				}

				Note = ((pval[0] >> 6) & 0x3f);
				if (Note) Note += NoteDelta - 1;
				Inst = pval[0] & 0x3f;

				err = Loaders_StartChannel(pSong, k, Note, Inst);
				if (err) goto loader_err;

				if (((pval[0] >> 17) & 0x7f) == 0)
				{
					convert_effect(pSong, pLHdr, k, (pval[0] >> 12) & 0x1f, (pval[0] >> 24));
					pval++;
				}
				else // 4 effects
				{
					convert_effect(pSong, pLHdr, k, (pval[0] >> 12) & 0x1f, pval[1] & 0xff);
					convert_effect(pSong, pLHdr, k, (pval[0] >> 17) & 0x1f, (pval[1] >> 8) & 0xff);
					convert_effect(pSong, pLHdr, k, (pval[0] >> 22) & 0x1f, (pval[1] >> 16) & 0xff);
					convert_effect(pSong, pLHdr, k, (pval[0] >> 27) & 0x1f, (pval[1] >> 24) & 0xff);
					pval += 2;
				}

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

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

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

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

	// read samples data
	for ( i = 0, pSmp = pSong->pSamples, pLSmp = pLSmpTable
		; i < pSong->Samples
		; i++, pSmp++, pLSmp++
		)
	{
		uint8_t* pc;

		if (pLSmp->Offset && pSmp->Size)
		{
			err = ReadBlock
					( pSong
					, &pSmp->Ptr
					, (void**) &pEndBlock
					, pLSmp->Offset
					, 0
					, pSmp->Size
					);
			if (err) goto loader_err;
		}
		else pSmp->Size = 0;

		// Convert from Log to Lin
		for (j = 0, pc = pSmp->Ptr; j < pSmp->Size; j++, pc++)
		{
			*pc = SmpLogToLin[*pc];
		}
	}

loader_err:
	Loaders_Free(pSong, pLHdr);
	Loaders_Free(pSong, pLSmpTable);
	Loaders_Free(pSong, pPatOffsets);
	Loaders_Free(pSong, pPatSizes);
	Loaders_Free(pSong, pBlock);

	return err;
}
