#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...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.
 * Panning : [0...255].
 * Sampling: 8363 Hz (frequency is turned into a relative tone).
 *
 * Notes:
 * Based on S3M2MTRX rather than the loose doc since this is the only origin of such files.
 *
 * Since there exists only converted S3M files, we will fiddle the effects to reflect S3M.
 */

typedef struct
{
	uint32_t    MTRX;
	uint32_t    FileLength;
	uint32_t    CTRL;
	uint16_t    Patterns;
	uint8_t     Channels;
	uint8_t     Compression;
	uint16_t    Orders;
	uint16_t    Samples;
	uint8_t     Tempo;
	uint8_t     Speed;
	uint8_t     GlobalVolume;
	uint8_t     Compatibility;
	uint32_t    Stereo[];
} LHdr;

typedef struct
{
	uint32_t    TAG;
	uint16_t    CompressedSize;
	uint16_t    RealSize;
} LChunk;

typedef struct
{
	uint32_t    TAG;
	uint16_t    Patterns;
	uint8_t     Channels;
	uint8_t     Compression;
	uint32_t    CompressedSize;
	uint32_t    RealSize;
} LPat;

typedef struct
{
	uint32_t    TAG;
	uint8_t     Name[32];
	uint32_t    LoopStart;
	uint32_t    LoopEnd;
	uint32_t    Volume;
	uint16_t    Frequency;
	uint16_t    Note; // C-4 is 428<<2
	uint8_t     Format; // bit 0: 0 log, 1 lin; bit 1: 0 signed, 1 unsigned
	uint8_t     Dummy;
	uint8_t     Depth;
	uint8_t     Compression;
	uint32_t    CompressedSize;
	uint32_t    RealSize;
} LSmp;

static const uint8_t Tag_MTRX[] = "MTRX";
static const uint8_t Tag_CTRL[] = "CTRL";
static const uint8_t Tag_NAME[] = "NAME";
static const uint8_t Tag_AUTH[] = "AUTH";
static const uint8_t Tag_SEQC[] = "SEQC";
static const uint8_t Tag_PATT[] = "PATT";
static const uint8_t Tag_SAMP[] = "SAMP";
//static const uint8_t Tag_ETRK[] = "ETRK";

#define NoteDelta  (Note_Central-12*4)

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

static const uint8_t Typestr_MTRX[] = "MatrixTRK";

static const char* Invalid_Compression_Method = "MatrixTRK contains unknown compression method";
static const char* Invalid_Sample_Depth = "MatrixTRK contains sample which are not in 8-bit in 16-bit depth";
static const char* Invalid_Squash = "Squash decompression error";

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

	switch(effect)
	{
		case 0x00: // Specials
		{
			effect = value >> 4;
			value = value & 0xf;

			switch (effect)
			{
				case 0x0: // No effect
				break;
				case 0x1: // Note: Set fine tune in 1/8 semitone
				{
					int32_t svalue = value << 28;

					// only if a note is defined at the same time
					if (note) Pattern_AddEffect_Instrument(pSong, cmd_sample_set_finetune, svalue >> 23);
				}
				break;
				case 0x2: // Pitch: Up, Memory (But S3M has none)
				{
					// cf S3M FFx
					if (value)
						effect = cmd_upmem1;
					else
						effect = cmd_pitch_memslide;
					effect |= flag_cmd_slide_frame0;

					Pattern_AddEffect_Pitch(pSong, effect, value << 2);
				}
				break;
				case 0x3: // Pitch: Up fine, Memory (But S3M has none)
				{
					// cf S3M FEx
					if (value)
						effect = cmd_upmem1;
					else
						effect = cmd_pitch_memslide;
					effect |= flag_cmd_slide_frame0;

					Pattern_AddEffect_Pitch(pSong, effect, value);
				}
				break;
				case 0x4: // Pitch: Down, Memory (But S3M has none)
				{
					// cf S3M EFx
					if (value)
						effect = cmd_downmem1;
					else
						effect = cmd_pitch_memslide;
					effect |= flag_cmd_slide_frame0;

					Pattern_AddEffect_Pitch(pSong, effect, value << 2);
				}
				break;
				case 0x5: // Pitch: Down fine, Memory (But S3M has none)
				{
					// cf S3M EEx
					if (value)
						effect = cmd_downmem1;
					else
						effect = cmd_pitch_memslide;
					effect |= flag_cmd_slide_frame0;

					Pattern_AddEffect_Pitch(pSong, effect, value);
				}
				break;
				case 0x6: // Pitch: Glissando control
				{
					if (value < 2)
						Pattern_AddEffect_Pitch(pSong, cmd_pitch_glissando, value);
					else
						SysLog_BadEffectValue(pSong, 0, oeffect, ovalue);
				}
				break;
				case 0x7: // Pitch: Vibrato type
				{
					effect = value;

					switch(effect & 3)
					{
						case 1: value = vibrato_type_ramp; break;
						case 2: value = vibrato_type_square; break;
						case 3: 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 0xa: // Volume: Vibrato type, S3M2MTRX
				{
					effect = value;

					switch(effect & 3)
					{
						case 1: value = vibrato_type_ramp; break;
						case 2: value = vibrato_type_square; break;
						case 3: 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 0xb: // Volume: Up, Memory, S3M2MTRX
				{
					effect = cmd_upmem1;

					if (value) effect |= flag_cmd_slide_frame0;

					Pattern_AddEffect_NoteVolume(pSong, effect, value << 2);
				}
				break;
				case 0xc: // Volume: Down, Memory, S3M2MTRX
				{
					effect = cmd_down | flag_cmd_slide_frame0;
					Pattern_AddEffect_NoteVolume(pSong, effect, value << 2);
				}
				break;
				case 0xd: // Panning: Set
				{
					value += value << 4;
					if (value >= 128) value++;
					Pattern_AddEffect_Panning(pSong, cmd_set, value);
				}
				break;
				default:
				{
					SysLog_BadEffect(pSong, 0, oeffect, ovalue);
				}
			}
		}
		break;
		case 0x02: // Pitch: Slide up, Memory
		{
			// cf S3M Fxx slide up
			effect = cmd_upmem1;

			if (value) effect |= flag_cmd_slide_frameN0;

			Pattern_AddEffect_Pitch(pSong, effect, value << 2);
		}
		break;
		case 0x03: // Pitch: Slide down, Memory
		{
			// cf S3M Exx slide down
			effect = cmd_downmem1;

			if (value) effect |= flag_cmd_slide_frameN0;

			Pattern_AddEffect_Pitch(pSong, effect, value << 2);
		}
		break;
		case 0x04: // Pitch: Arpeggio, Memory
		{
			uint32_t val;

			val = value >> 4;
			value &= 0xf;
			Pattern_AddEffect_Arpeggio(pSong, value, val, cmd_arpeggio_type_NHL);
		}
		break;
		case 0x05: // Pitch: Portamento, Memory
		{
			effect = cmd_portamento;

			if (value) effect |= flag_cmd_slide_frameN0;

			Pattern_AddEffect_Pitch(pSong, effect, value << 2);
		}
		break;
		case 0x06: // Pitch: Portamento 0 - Volume: Slide
		{
			convert_effect(pSong, channel, 0x05, 0, note);
			convert_effect(pSong, channel, 0x12, value, note);
		}
		break;
		case 0x07: // 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 0x08: // Pitch: Vibrato fine, Memory
		{
			uint32_t speed;

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

			// Vibrate between [-2*y,+2*y]
			value <<= 1; // << 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 0x09: // Pitch: Vibrato 0 - Volume: Slide, Memory
		{
			convert_effect(pSong, channel, 0x07, 0, note);
			convert_effect(pSong, channel, 0x12, value, note);
		}
		break;
		case 0x10: // Global Volume: Set [0, 64], values above are ignored
		{
			if (value <= 64)
				Pattern_AddGlbEffect_Volume(pSong, cmd_set, value << 2);
			else
				SysLog_BadEffectValue(pSong, 0, oeffect, ovalue);
		}
		break;
		case 0x11: // Global Volume: Slide
		{
			// signed value
			if (value & 0x80)
				Pattern_AddGlbEffect_Volume(pSong, cmd_down | flag_cmd_slide_frameN0, 0x100 - value);
			else if (value)
				Pattern_AddGlbEffect_Volume(pSong, cmd_up | flag_cmd_slide_frameN0, value);
		}
		break;
		case 0x12: // Volume: Slide, Memory
		{
			// See S3M Dxy effect
			if (value == 0)
			{
				// D00 repeat last volume slide
				Pattern_AddEffect_NoteVolume(pSong, cmd_last_slide, 0);
			}
			else if (!(value & 0x0f))
			{
				// Dx0 slide up
				effect = cmd_upmem1 | flag_cmd_slide_frameN0;
				value &= 0xf0;
				Pattern_AddEffect_NoteVolume(pSong, effect, value >> 2);
			}
			else if (!(value & 0xf0))
			{
				// D0y slide down
				effect = cmd_downmem1 | flag_cmd_slide_frameN0;
				value &= 0x0f;
				Pattern_AddEffect_NoteVolume(pSong, effect, value << 2);
			}
			else
			{
				// Bad values behave as D0y slide down
				effect = cmd_downmem1 | flag_cmd_slide_frameN0;
				value &= 0x0f;
				Pattern_AddEffect_NoteVolume(pSong, effect, value << 2);
			}
		}
		break;
		case 0x13: // Volume: Vibrato (alias Tremolo)
		{
			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_NoteVolume(pSong, cmd_vibrato_speed, speed << 2);
			Pattern_AddEffect_NoteVolume(pSong, cmd_vibrato_depth | flag_cmd_vibrato_frameN0, value);
		}
		break;
		case 0x18: // Global: Set speed, 0 is ignored
		{
			if (value) Pattern_AddGlbEffect_Frames(pSong, gcmd_frames_set, value);
		}
		break;
		case 0x19: // Global: Set tempo, 0 is ignored
		{
			if (value >= 0x20)
			{
				Pattern_AddGlbEffect_Tempo(pSong, cmd_set, value);
			}
			else
			{
				SysLog_BadEffectValue(pSong, 0, oeffect, ovalue);
			}
		}
		break;
		case 0x1a: // Note: Delay for x ticks
		{
			Pattern_AddEffect_Instrument(pSong, cmd_sample_note_delay, value);
		}
		break;
		case 0x1b: // Note: Cut after x ticks
		{
			if (value) // S3M quirk, no cut if x=0
				Pattern_AddEffect_NoteVolume(pSong, cmd_volume_cut, value);
		}
		break;
		case 0x1c: // Note: Retrig note every y ticks with volume change type x, Memory
		{
			uint32_t eff;

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

			// Memory only if xy = 00
			if (value && !eff) eff = 8;
			if (!value && eff) value = 0xff; // should be infinite
			Pattern_AddEffect_RetrigCounter(pSong, cmd_retrig_count_type_S3M, value);
			Pattern_AddEffect_Instrument(pSong, cmd_sample_note_retrig, eff);
		}
		break;
		case 0x1d: // Volume: Tremor, on time x, off time y, Memory
		{
			uint32_t val;

			val = value >> 4;
			value &= 0xf;
			Pattern_AddEffect_NoteVolume(pSong, cmd_volume_tremor_S3M, Effect_Tremor(value, val));
		}
		break;
		case 0x20: // Global: Loop in column
		{
			Pattern_AddGlbEffect_Position(pSong, gcmd_pos_loop | value, channel);
		}
		break;
		case 0x21: // Global: Delay for x rows
		{
			Pattern_AddGlbEffect_Frames(pSong, gcmd_frames_repeat, value);
		}
		break;
		case 0x22: // Global: Break, S3M2MTRX
		{
			uint32_t ten = value >> 4;
			value &= 0xf;
			if ((value > 9) || (ten > 9))
				SysLog_BadEffectValue(pSong, 0, oeffect, ovalue);
			else
			{
				value += ten * 10;
				Pattern_AddGlbEffect_Position(pSong, gcmd_pos_break, value);
			}
		}
		break;
		case 0x30: // Note: Set sample offset
		{
			if (value)
			{
				// define offset
				// clear byte 0
				Pattern_AddEffect_Instrument(pSong, cmd_sample_set_offset, 0x000 | 0);
				// set byte 1
				Pattern_AddEffect_Instrument(pSong, cmd_sample_set_offset, 0x100 | value);
			}

			// apply only if a note is defined at the same time
			if (note) Pattern_AddEffect_Instrument(pSong, cmd_sample_set_offset, 0xf00);
		}
		break;
		case 0x40: // Global: Jump
		{
			Pattern_AddGlbEffect_Position(pSong, gcmd_pos_jump, value);
		}
		break;
		default:
		{
			SysLog_BadEffect(pSong, 0, effect, value);
		}
	}
}

static const _kernel_oserror* Pattern_MTRX(SongHdr* pSong, uint32_t i, Pattern* pPattern, uint8_t* pPos)
{
	const _kernel_oserror* err = NULL;
	uint32_t row;
	uint32_t channel;
	uint32_t Note, Inst;

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

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

		for (channel = 0; channel < pSong->Channels; channel++)
		{
			// Read note & instrument
			Note = pPos[0];
			if (Note == 254) Note = Note_Cut;
			else if (Note <= 0x5f) // Range [0, 5f]
			{
				Note += NoteDelta;
			}
			else
			{
				if (Note != 0xff) SysLog_BadNote(pSong, 0, channel, Note);
				Note = 0;
			}
			Inst = pPos[1];

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

			// Read Volume column [0, 64], values above are ignored
			if (pPos[2] <= 64)
				Pattern_AddEffect_NoteVolume(pSong, cmd_set, (pPos[2]) << 2);
			else if (pPos[2] != 0xff) // normal way to signal empty volume column
				SysLog_BadEffectValue(pSong, 0, 256, pPos[2]);

			// Read effect and value
			convert_effect(pSong, channel, pPos[3], pPos[4]
			             , (Note < Note_CmdMin) ? Note : 0);
			pPos += 5;

			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* Loader_ReadData(SongHdr* pSong, void** pp, uint32_t size, int mode, uint32_t csize)
{
	const _kernel_oserror* err = NULL;
	unsigned int pos = FileLoad_GetPos(pSong);

	if (!csize) csize = size;
	if (!size) size = csize;
	if (csize & 3)
	{
		csize &= ~3;
		csize += 4;
	}

	err = Loaders_Alloc(pSong, pp, size + 4);
	if (err) return err;

	switch(mode)
	{
		case 0:
			err = FileLoad_Read(pSong, *pp, csize);
		break;
		case 1: // Squash
		{
			unsigned int dummy;

			FileLoad_ReadByte(pSong, &dummy); // 0x1f
			if (dummy != 0x1f) return Loaders_Error(pSong, FileLoad_GetPos(pSong), Invalid_Squash);
			FileLoad_ReadByte(pSong, &dummy); // 0x9d
			if (dummy != 0x9d) return Loaders_Error(pSong, FileLoad_GetPos(pSong), Invalid_Squash);
			FileLoad_ReadByte(pSong, &dummy); // 0x8c

			err = lzwd_decode(pSong, *pp, size, dummy & 0x1f, (dummy & 0x80) ? 257 : 256);
		}
		break;
//		case 2: // ADPCM
		default:
			err = Loaders_Error(pSong, FileLoad_GetPos(pSong), Invalid_Compression_Method);
	}

	if (!err)
		FileLoad_SetPos(pSong, pos + csize);

	return err;
}

static const _kernel_oserror* Loader_ReadChunk(SongHdr* pSong, uint32_t tag, uint32_t mode, void** pp, uint32_t* pSize)
{
	const _kernel_oserror*    err = NULL;
	LChunk                    Chunk;

	err = FileLoad_Read(pSong, &Chunk, sizeof(Chunk));
	if (err) return err;

	if (Chunk.TAG != tag)
		return Loaders_Error(pSong, -(int32_t) sizeof(Chunk), "Tag not found");

	*pSize = Chunk.RealSize ? Chunk.RealSize : Chunk.CompressedSize;

	return Loader_ReadData(pSong, pp, Chunk.RealSize, mode, Chunk.CompressedSize);
}

static const _kernel_oserror* Loader_ReadPatt(SongHdr* pSong, LPat* pLPat, void* pp)
{
	const _kernel_oserror*    err = NULL;

	err = FileLoad_Read(pSong, pLPat, sizeof(*pLPat));
	if (err) return err;

	if (!pLPat->RealSize) pLPat->RealSize = pLPat->CompressedSize;
	if ((pLPat->TAG != TAG(PATT))
	||  (pLPat->Patterns != pSong->Patterns)
	||  (pLPat->Channels != pSong->Channels)
	||  (pLPat->RealSize != 5*64*pLPat->Patterns*pLPat->Channels))
		return Loaders_Error(pSong, -(int32_t) sizeof(*pLPat), "Invalid pattern");

	return Loader_ReadData(pSong, pp, pLPat->RealSize, pLPat->Compression, pLPat->CompressedSize);
}

static const _kernel_oserror* Loader_ReadSmp(SongHdr* pSong, Sample* pSmp, LSmp* pLSmp)
{
	const _kernel_oserror*    err = NULL;
	const uint8_t* SmpLogToLin = Table_ArcSampleLogToLin();

	err = FileLoad_Read(pSong, pLSmp, sizeof(*pLSmp));
	if (err) return err;

	if (pLSmp->TAG != TAG(SAMP))
		return Loaders_Error(pSong, -(int32_t) sizeof(*pLSmp), "Invalid sample");

	pSmp->Size = pLSmp->RealSize ? pLSmp->RealSize : pLSmp->CompressedSize;
	pSmp->LoopStart = pLSmp->LoopStart;
	pSmp->LoopEnd = pLSmp->LoopEnd;
	pSmp->DefaultVolume = pLSmp->Volume << 2;
	pSmp->Frequency = 8363;
	pSmp->RelTone = Convert_RelTone(pSmp->Frequency, pLSmp->Frequency);
	pSmp->Type = 0;

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

	if ((pLSmp->Depth != 8) && (pLSmp->Depth != 16))
		return Loaders_Error(pSong, -(int32_t) sizeof(*pLSmp), Invalid_Sample_Depth);

	err = Loader_ReadData(pSong, &pSmp->Ptr, pSmp->Size, pLSmp->Compression, pLSmp->CompressedSize);
	if (err) return err;

	if (!(pLSmp->Format & 1))
	{
		uint8_t* pc;
		uint8_t* pcend;

		// Convert from Log to Lin
		for (pc = pSmp->Ptr, pcend = pc + pSmp->Size; pc < pcend; pc++)
		{
			*pc = SmpLogToLin[*pc];
		}
	}
	if (pLSmp->Format & 2)
		pSmp->Type |= Smp_Type_Unsigned;
	if ((pLSmp->LoopStart || pLSmp->LoopEnd) && (pLSmp->LoopStart < pLSmp->LoopEnd))
		pSmp->Type |= Smp_Type_Loop;

	return NULL;
}

const _kernel_oserror* Loader_MTX(SongHdr* pSong)
{
	const _kernel_oserror* err = NULL;
	SubSong*        pSubSong = Song_GetSubSong(pSong, 0);
	LHdr*           pLHdr = NULL;
	LPat*           pLPat = NULL;
	LSmp*           pLSmp = NULL;
	uint8_t*        pData = NULL;
	ChannelDefault* pDef;
	Pattern*        pPattern;
	Sample*         pSmp;
	uint32_t        Size;
	uint32_t        val;
	int             i;

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

	// 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 Matrix Module?
	if ((pLHdr->MTRX != TAG(MTRX))
	||  (pLHdr->CTRL != TAG(CTRL)))
	{
		err = Loaders_NotThisType;
		goto loader_err;
	}

	// Set version
	pSong->Version = 100;
	pSong->Flags = 0;
	pSong->MinPitch = NoteDelta;
	pSong->MaxPitch = NoteDelta + 12 * 8 - 1;

	// Fill file type ptr
	pSong->pType = Typestr_MTRX;

	pSubSong->SeqLen = pLHdr->Orders;
	if (!pSubSong->SeqLen)
	{
		err = Loaders_InvalidSong(pSong, 16, &pSubSong->SeqLen, pSubSong->SeqLen);
		goto loader_err;
	}
	pSong->Patterns = pLHdr->Patterns;
	if (!pSong->Patterns || (pSong->Patterns > song_max_patterns))
	{
		err = Loaders_InvalidSong(pSong, 12, &pSong->Patterns, pSong->Patterns);
		goto loader_err;
	}
	pSong->Samples  = pLHdr->Samples;
	if (!pSong->Samples || (pSong->Samples > 255))
	{
		err = Loaders_InvalidSong(pSong, 17, &pSong->Samples, pSong->Samples);
		goto loader_err;
	}

	if (pLHdr->GlobalVolume <= 64)
		pSubSong->Defaults.Volume = pLHdr->GlobalVolume << 2;
	else
		pSubSong->Defaults.Volume = 256;
	if (pLHdr->Speed) pSubSong->Defaults.Speed = pLHdr->Speed;
	if (pLHdr->Tempo) pSubSong->Defaults.Tempo = pLHdr->Tempo;
	pSong->Channels = pLHdr->Channels;
	if (!pSong->Channels || (pSong->Channels > song_max_channels))
	{
		err = Loaders_InvalidSong(pSong, 14, &pSong->Channels, pSong->Channels);
		goto loader_err;
	}

	// Read number of channels + left and right channels settings
	for (i = 0, pDef = pSubSong->Defaults.ChDef; i < pSong->Channels; i++, pDef++)
	{
		int32_t val;
		err = FileLoad_ReadInt(pSong, (uint32_t*) &val, 4);
		if (err) goto loader_err;
		val += 128;
		if (val < 0) val = 0;
		else if (val > 256) val = 256;
		pDef->Panning = val;
	}

	// Load name
	err = Loader_ReadChunk(pSong, TAG(NAME), pLHdr->Compression, (void**) &pData, &Size);
	if (err) goto loader_err;

	err = Loaders_String(pSong, &pSong->pName, pData, Size, false);
	if (err) goto loader_err;

	Loaders_Free(pSong, pData);
	pData = NULL;

	// Load author
	err = Loader_ReadChunk(pSong, TAG(AUTH), pLHdr->Compression, (void**) &pData, &Size);
	if (err) goto loader_err;

	Loaders_Free(pSong, pData);
	pData = NULL;

	// Load sequence
	err = Loader_ReadChunk(pSong, TAG(SEQC), pLHdr->Compression, (void**) &pData, &Size);
	if (err) goto loader_err;

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

	// Fill the pattern sequence
	for (i = 0; i < pSubSong->SeqLen; i++)
	{
		val = pData[i];
		if (err) goto loader_err;
		if (val >= 254) val -= 256;
		pSubSong->pSeqs[i] = val;
	}

	Loaders_Free(pSong, pData);
	pData = NULL;

	// Fill the patterns
	err = Loader_ReadPatt(pSong, pLPat, &pData);
	if (err) goto loader_err;

	for (i = 0, pPattern = pSong->pPatterns; i < pSong->Patterns; i++, pPattern++)
	{
		pPattern->Rows = 64;

		err = Pattern_MTRX(pSong, i, pPattern, pData+i*5*pPattern->Rows*pSong->Channels);
		if (err) goto loader_err;
	}

	Loaders_Free(pSong, pData);
	pData = NULL;

	// read samples info
	for (i = 0, pSmp = pSong->pSamples; i < pSong->Samples; i++, pSmp++)
	{
		err = Loader_ReadSmp(pSong, pSmp, pLSmp);
		if (err) goto loader_err;
	}

loader_err:
	Loaders_Free(pSong, pLHdr);
	Loaders_Free(pSong, pLSmp);
	Loaders_Free(pSong, pData);

	return err;
}
