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

/*
 * Channels: [1...16]
 * Patterns: [1...1024]
 * Rows    : [1...65535]
 * Orders  : [1...65535]
 * Samples : [1...64]
 * Instr.  : [1...64]
 * Tempo   : See below.
 * Speed   : [1...32], S frames per row.
 * Notes   : [C-0...C-1...B-3] or [C-0...C-3...B-5]
 * 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 : [-16...15].
 * Sampling: 8363 Hz. Uses relative tones directly.
 *
 * MED tempo information is really confusing.
 * Primay tempo = TPL = Soundtracker speed ( = nr of ticks in a line or row).
 * Secondary tempo depends on working mode SDP or BPM.
 * In BPM mode secondary tempo is BPM where are LPB ticks per beat (I would have called it TPB! TPB = 4 in Soundtracker).
 * In SPD mode secondary tempo is directly in ticks.
 */

#define MED_FLAG_FILTERON   0x01
#define MED_FLAG_JUMPINGON  0x02
#define MED_FLAG_JUMP8TH    0x04
#define MED_FLAG_INSTRSATT  0x08
#define MED_FLAG_VOLHEX     0x10
#define MED_FLAG_STSLIDE    0x20
#define MED_FLAG_8CHANNEL   0x40
#define MED_FLAG_SLOWHQ     0x80
#define MED_FLAG2_BMASK     0x1F
#define MED_FLAG2_BPM       0x20
#define MED_FLAG2_MIX       0x80

#define NoteDelta (Note_Central-12*2)

static const uint8_t Tag_MED[] = "MED";

static const uint8_t Typestr_MED[] = "MED";

#define bigendian4(x) ((x[0] << 24) + (x[1] << 16) + (x[2] << 8) + x[3])
#define bigendian2(x) ((x[0] << 8) + x[1])

typedef struct
{
	uint8_t     Type[3];
	uint8_t     Vers;
} LHdr;

typedef struct
{
	uint8_t     Channel;
	uint8_t	    Note;
	uint8_t	    Inst;
	uint8_t	    Cmd;
	uint8_t	    Value;
} ChannelInfo;

static const uint8_t tempo_8ch[11] = {125, 179, 164, 152, 141, 131, 123, 116, 110, 104, 99};

static int Convert_MEDTempo(int value)
{
	if (value <= 10)
		return tempo_8ch[value] * 8;

	value = (value * 715909 + 11858) /* rounding*/ / ((4 * 474326) / (8 * 10));

	if (value > 0xfff) value = 0xfff; // cf. Tempo Cmd_Set command

	return value;
}

static void convert_effect(SongHdr* pSong, ChannelInfo* pChInfo, uint32_t effect, uint32_t value)
{
	uint32_t oeffect = effect;
	uint32_t ovalue = value;

	switch(effect)
	{
		case 0: // Pitch: Arpeggio, No memory
		{
			if (value)
			{
				uint32_t val;

				val = value >> 4;
				value &= 0xf;
				Pattern_AddEffect_Pitch(pSong, cmd_pitch_arpeggio, Effect_Arpeggio(value, val, cmd_arpeggio_type_NHL));
			}
		}
		break;
		case 1: // Pitch: Slide up, No memory
		{
			if (value) Pattern_AddEffect_Pitch(pSong, cmd_up | flag_cmd_slide_frame0N0, value << 2);
		}
		break;
		case 2: // Pitch: Slide down, No memory
		{
			if (value) Pattern_AddEffect_Pitch(pSong, cmd_down | flag_cmd_slide_frame0N0, value << 2);
		}
		break;
		case 0x03: // Old Vibrato, plays pitch, pitch + value
		{
			SysLog_SkipEffect(pSong, 0, oeffect, ovalue);
		}
		break;
		case 0xc: // Volume: Set, invalid values are ignored?
		{
			// Decimal value
			uint32_t ten = value >> 4;
			value &= 0xf;
			if ((value > 9) || (ten > 9))
				value = 100; // bad value
			else
				value += ten * 10;

			if (value <= 64)
				Pattern_AddEffect_NoteVolume(pSong, cmd_set, value << 2);
//				else
//					SysLog_BadEffectValue(pSong, 0, oeffect, ovalue);
		}
		break;
		case 0xd:
		{
			uint32_t cmd = flag_cmd_slide_frame0N0;
			uint32_t down = value & 0xf;
			uint32_t up = value >> 4;

			if (!up)
			{
				cmd += cmd_down;
				value = down;
			}
			else
			{
				cmd += cmd_up;
				value = up;
			}
			if (value) Pattern_AddEffect_NoteVolume(pSong, cmd, value << 2);
		}
		break;
		case 0xe: // Filter on/off
		break;
		case 0xf: // Tempo and more
		{
			// if 0 break to next pattern
			if (!value)
				Pattern_AddGlbEffect_Position(pSong, gcmd_pos_break, 0);
			else if (value <= 0xf0)
			{
				// Tempo
				Pattern_AddGlbEffect_Tempo(pSong, cmd_set, Convert_MEDTempo(value));
			}
			else
			{
				switch(value)
				{
					case 0xf1:
					{
						// play twice in row
						Pattern_AddEffect_RetrigCounter(pSong, cmd_retrig_count_type_MOD, 3);
						Pattern_AddEffect_Instrument(pSong, cmd_sample_note_retrig, 8);
					}
					break;
					case 0xf2:
					{
						// delay by half row
						Pattern_AddEffect_Instrument(pSong, cmd_sample_note_delay, 3);
					}
					break;
					case 0xf3:
					{
						// play 3 times in row
						Pattern_AddEffect_RetrigCounter(pSong, cmd_retrig_count_type_MOD, 2);
						Pattern_AddEffect_Instrument(pSong, cmd_sample_note_retrig, 8);
					}
					break;
					case 0xff:
					{
						// Stop playing note
						Pattern_AddEffect_Instrument(pSong, cmd_sample_note_action_cut, 0);
					}
					break;
					case 0xfe: // stop song
					break;
					default:
					{
						SysLog_BadEffect(pSong, 0, oeffect, ovalue);
					}
				}
			}
		}
		break;
		default:
		{
			// Unknown effect
			SysLog_BadEffect(pSong, 0, oeffect, ovalue);
		}
		break;
	}
}

static const _kernel_oserror* Pattern_MED0
	( SongHdr* pSong
	, uint32_t i
	, Pattern* pPattern
	, int channels
	, const uint8_t* pPos
	)
{
	const _kernel_oserror* err = NULL;
	int             j, k;
	ChannelInfo     Ch;
	uint32_t        Cmd, Value;

	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;

		for (k = 0; k < channels; k++)
		{
			Ch.Channel = k;
			Ch.Note = *pPos++;
			Cmd = *pPos++;
			Value = *pPos++;
			Ch.Inst = Cmd >> 4;
			if (Ch.Note & 0x80) Ch.Inst += 32;
			if (Ch.Note & 0x40) Ch.Inst += 64;
			Cmd &= 0xf;
			Ch.Note &= 0x3f;

			if (Ch.Note) Ch.Note += pSong->MinPitch - 1;

			err = Loaders_StartChannel(pSong, k, ((Cmd != 0x0f) || (Value != 0xfd)) ? Ch.Note : 0, Ch.Inst);
			if (err) return err;

			convert_effect(pSong, &Ch, Cmd, Value);
			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_MED1
	( SongHdr* pSong
	, uint32_t i
	, Pattern* pPattern
	, int channels
	, const uint8_t* pPos
	, const uint8_t* pPos2
	, int ecmds
	)
{
	const _kernel_oserror* err = NULL;
	int             j, k, c, step2;
	ChannelInfo     Ch;
	uint32_t        Cmd, Value;

	step2 = 2 * channels * pPattern->Rows;

	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;

		for (k = 0; k < channels; k++)
		{
			Ch.Channel = k;
			Ch.Note = *pPos++;
			Ch.Inst = *pPos++;
			Cmd = *pPos++;
			Value = *pPos++;

			if (Ch.Note)
			{
				if (Ch.Note <= 12*6)
				{
					Ch.Note += pSong->MinPitch - 1;
				}
				else
				{
					SysLog_BadNote(pSong, -1, channel, Ch.Note);
					Ch.Note = 0;
				}
			}

			err = Loaders_StartChannel(pSong, k, ((Cmd != 0x0f) || (Value != 0xfd)) ? Ch.Note : 0, Ch.Inst);
			if (err) return err;

			convert_effect(pSong, &Ch, Cmd, Value);

			for (c = 0; c < ecmds; c++)
			{
				Cmd = pPos2[0];
				Value = pPos2[1];
				convert_effect(pSong, &Ch, Cmd, Value);
				pPos2 += step2;
			}
			pPos2 += 2 - ecmds*step2;

			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_MED(SongHdr* pSong)
{
	const _kernel_oserror* err = NULL;
	SubSong*    pSubSong = Song_GetSubSong(0);
    LHdr*       pLHdr = NULL;
	Pattern*    pPattern;
	Sample*     pSmp;
	uint32_t    val, val2;
	int         i, j;
	ChannelDefault* pDef;
	uint32_t    SmpMask0, SmpMask1;
	int         transpose = 0;

	// First read all information required for memory allocation
	err = Loaders_Alloc(pSong, (void**) &pLHdr, sizeof(*pLHdr));
	if (!err) err = FileLoad_Read(pSong, &pLHdr, 4);
	||  Loaders_strncmp(&pLHdr->Type[0], Tag_MED, sizeof(pLHdr->Type)))
	{
		err = Loaders_NotThisType;
		goto loader_err;
	}

	// Note no info below v2, v3 loads sample from extrenal files
	if ((pLHdr->Vers < 3) || (pLHdr->Vers > 4))
	{
		err = Loaders_Error(pSong, 3, "Unsupported MED format");
		goto loader_err;
	}

	pSong->Version = pLHdr->Vers; // Interval for now
	pSong->pType = Typestr_MED;
	pSong->Flags = Song_Flag_InstNoNote_Swap;
	pSong->MinSpeed = 1;
	pSong->MaxSpeed = 32;
	pSong->TempoBase = Tempo_Base_20Hz;
	pSong->MinTempo = 332;   // BPM off, TEMPO 11
	pSong->MaxTempo = 15872; // BPM on LPB 32, TEMPO 240
	pSong->MinPitch = Note_Central - 12 * 3;
	pSong->MaxPitch = Note_Central + 12 * 3 - 1;
	pSong->Channels = 16;

	if (pLHdr->Vers <= 3)
	{
		pSong->Samples = 32;

		if (pLHdr->Vers <= 2)
		{
			pSong->Channels = 4;
			// Read names
			for (i = 0, pSmp = pSong->pSamples; i < pSong->Samples; i++, pSmp++)
			{
				err = FileLoad_ReadString(pSong, &pSmp->pName, 40, false);
				if (err) goto loader_err;
			}
		}
		else
		{
			// Read names
			for (i = 0, pSmp = pSong->pSamples; i < pSong->Samples; i++, pSmp++)
			{
				char buf[40];
				uint32_t pos = FileLoad_ReadPos(pSong);
				err = FileLoad_Read(pSong, buff, 40);
				if (err) goto loader_err;
				for (j = 0; j < 40; j++)
				{
					if (!buf[j]) break;
				}
				j++;
				if (j < 40) FileLoad_SetPos(pSong, pos + j);
				err = Loaders_String(pSong, &pSmp->pName, buf, 40, false);
				if (err) goto loader_err;
			}
		}

		// Read volumes
		if (pLHdr->Vers <= 2)
			SmpMask0 = 0xffffffff;
		else
		{
			err = FileLoad_ReadReverseInt(pSong, &SmpMask0, 4);
		}

		for (i = 0, pSmp = pSong->pSamples; i < pSong->Samples; i++, pSmp++, SmpMask0 <<= 1)
		{
			if (SmpMask0 & 0x80000000)
			{
				err = FileLoad_ReadByte(pSong, &val);
				if (err) goto loader_err;
			}
			else val = 0;

			if (val <= 64)
				pSmp->DefaultVolume = val << 2;
			else
				pSmp->DefaultVolume = 256;
		}

		// Read loop starts
		if (pLHdr->Vers <= 2)
			SmpMask0 = 0xffffffff;
		else
		{
			err = FileLoad_ReadReverseInt(pSong, &SmpMask0, 4);
		}

		for (i = 0, pSmp = pSong->pSamples; i < pSong->Samples; i++, pSmp++, SmpMask0 <<= 1)
		{
			if (SmpMask0 & 0x80000000)
			{
				err = FileLoad_ReadReverseInt(pSong, &val, 2);
				if (err) goto loader_err;
			}
			else val = 0;

			pSmp->LoopStart = val << 1;
		}

		// Read loop lengths
		if (pLHdr->Vers <= 2)
			SmpMask0 = 0xffffffff;
		else
		{
			err = FileLoad_ReadReverseInt(pSong, &SmpMask0, 4);
		}

		for (i = 0, pSmp = pSong->pSamples; i < pSong->Samples; i++, pSmp++, SmpMask0 <<= 1)
		{
			if (SmpMask0 & 0x80000000)
			{
				err = FileLoad_ReadReverseInt(pSong, &val, 2);
				if (err) goto loader_err;
			}
			else val = 0;

			pSmp->LoopEnd = pSmp->LoopStart + (val << 1);
			if (val > 1)
				pSmp->Type = Type_Smp_Loop;
			else
				pSmp->Type = 0;
		}
	}
	else
	{
		SmpMask0 = 0;
		SmpMask1 = 0;
		// 1 bit per byte following
		err = FileLoad_ReadByte(pSong, &val);
		if ((!err) && (val & 0x80)) {err = FileLoad_ReadByte(pSong, &val2); SmpMask0 |= val2 << 24;}
		if ((!err) && (val & 0x40)) {err = FileLoad_ReadByte(pSong, &val2); SmpMask0 |= val2 << 16;}
		if ((!err) && (val & 0x20)) {err = FileLoad_ReadByte(pSong, &val2); SmpMask0 |= val2 << 8;}
		if ((!err) && (val & 0x10)) {err = FileLoad_ReadByte(pSong, &val2); SmpMask0 |= val2;}
		if ((!err) && (val & 0x08)) {err = FileLoad_ReadByte(pSong, &val2); SmpMask1 |= val2 << 24;}
		if ((!err) && (val & 0x04)) {err = FileLoad_ReadByte(pSong, &val2); SmpMask1 |= val2 << 16;}
		if ((!err) && (val & 0x02)) {err = FileLoad_ReadByte(pSong, &val2); SmpMask1 |= val2 << 8;}
		if ((!err) && (val & 0x01)) {err = FileLoad_ReadByte(pSong, &val2); SmpMask1 |= val2;}
		if (err) goto loader_err;

		pSong->Samples = 0;
		for (i = 0, pSmp = pSong->pSamples; i < 64; i++, pSmp++, SmpMask0 <<= 1)
		{
			if (i == 32) SmpMask0 = SmpMask1;

			if (SmpMask0 & 0x80000000)
			{
				pSmp->Type = 0;
				pSong->Samples = i + 1;
				// read store flags, name size, name
				err = FileLoad_ReadByte(pSong, &val); val = ~val; // 1 means not present so invert flags
				if (!err) err = FileLoad_ReadByte(pSong, &val2);
				if (!err && val2)
					err = FileLoad_ReadString(pSong, &pSong->pName, val2, false);
				// read stored parts
				if (val & 0x9c)
				{
					SysLog_FileCorrupted(126, -(2 + val2), "Sample %d has unknown flags %x", i, val);
				}
				// read loop start
				if (!err && (val & 0x01))
				{
					err = FileLoad_ReadReverseInt(pSong, &val2, 2);
					pSong->LoopStart = val2 << 1;
				}
				// read loop size
				if (!err && (val & 0x02))
				{
					err = FileLoad_ReadReverseInt(pSong, &val2, 2);
					pSong->LoopEnd = pSong->LoopStart + (val2 << 1);
					if (val2) pSong->Type |= Sample_Type_Loop;
				}
				if (!err && (val & 0x04))
					err = FileLoad_ReadByte(pSong, &val2);
				if (!err && (val & 0x08))
					err = FileLoad_ReadByte(pSong, &val2);
				if (!err && (val & 0x10))
					err = FileLoad_ReadByte(pSong, &val2);
				// read volume
				if (!err && (val & 0x20))
				{
					err = FileLoad_ReadByte(pSong, &val2);
					pSmp->DefaultVolume = val2 << 2;
					if (pSmp->DefaultVolume > 256)
						pSmp->DefaultVolume = 256;
				}
				else pSmp->DefaultVolume = 256;
				// read transpose
				if (!err && (val & 0x40))
				{
					err = FileLoad_ReadByte(pSong, &val2);
					pSmp->RelTone = val2 << 24;
					pSmp->RelTone >>= 16; // Signed
				}
				if (err) goto loader_err;
			}
		}
	}

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

	if (pLHdr->Vers <= 2)
		pSubSong->SeqLen = 100;
	else
	{
		err = FileLoad_ReadReverseInt(pSong, &pSubSong->SeqLen, 2);
		if (err) goto loader_err;

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

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

	for (i = 0; i < pSubSong->SeqLen; i++)
	{
		err = FileLoad_ReadByte(pSong, &pSubSong->pSeqs[i]);
		if (err) goto loader_err;
	}

	// Sequence len is actually stored after sequence
	if (pLHdr->Vers <= 2)
	{
		err = FileLoad_ReadReverseInt(pSong, &pSubSong->SeqLen, 2);
		if (err) goto loader_err;
		if (!pSubSong->SeqLen || (pSubSong->SeqLen > 100))
		{
			err = Loaders_InvalidSong(pSong, -2, &pSubSong->SeqLen, pSubSong->SeqLen);
			goto loader_err;
		}
	}

	err = FileLoad_ReadReverseInt(pSong, &val, 2);
	if (err) goto loader_err;
	pSubSong->Defaults.Tempo = Convert_MEDTempo(&val);
	if (pLHdr->Vers <= 2)
	{
		err = FileLoad_ReadReverseInt(pSong, &val, 4); // flags + slide
		if (!err) err = FileLoad_ReadReverseInt(pSong, &val, 4); // jump mask
		if (!err) err = FileLoad_SkipBytes(pSong, 16); // RGB
	}
	else if (pLHdr->Vers == 3)
	{
		err = FileLoad_ReadByte(pSong, &transpose); // Transpose
		if (!err) err = FileLoad_ReadByte(pSong, &val); // Flags?
		if (!err) err = FileLoad_ReadReverseInt(pSong, &val, 2); // Sliding?
		if (!err) err = FileLoad_ReadReverseInt(pSong, &val, 4); // jump mask
		if (!err) err = FileLoad_SkipBytes(pSong, 16); // RGB
	}
	else
	{
		err = FileLoad_ReadByte(pSong, &transpose); // Transpose
		if (!err) err = FileLoad_ReadByte(pSong, &val); // ?
		if (!err) err = FileLoad_ReadByte(pSong, &val); // Flags?
		if (!err) err = FileLoad_ReadByte(pSong, &pSubSong->Defaults.Speed);
		if (!err) err = FileLoad_SkipBytes(pSong, 20);
		if (err) goto loader_err;

		for (i = 0, pDef = pSubSong->Defaults.ChDef; i < 16; i++, pDef++)
		{
			err = FileLoad_ReadByte(pSong, &val);
			if (err) goto loader_err;

			if (val <= 64)
				pDef->Volume = val << 2;
			else
				pDef->Volume = 256;
		}
		err = FileLoad_ReadByte(pSong, &val); // Master Volume
	}

	// convert transpose to signed
	transpose <<= 24;
	transpose >>= 16; // 1/256 semitone
	for (i = 0, pSmp = pSong->pSamples; i < pSong->Samples; i++, pSmp++)
		pSmp->RelTone += transpose;

	err = Loaders_Alloc(pSong, &pLPattern, 4*16*64);
	if (err) goto loader_err;

	for (i = 0, pPattern = pSong->pPatterns; i < pSong->Patterns; i++, pPattern++)
	{
		err = Loaders_StartPattern(pSong, i);
		if (err) goto loader_err;

		if (pLHdr->Vers <= 2)
		{
			pPattern->Rows = 64;

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

				for (k = 0; k < pSong->Channels; k++)
				{
					err = FileLoad_ReadReverseInt(pSong, &val, 2);
					if (err) goto loader_err;

					Note = scan_period_MOD(val);

					if (Note && ((Note < pSong->MinPitch) || (Note > pSong->MaxPitch)))
					{
						pSong->MinPitch = NoteDelta;
						pSong->MaxPitch = NoteDelta + 12 * 5 - 1;
					}

					err = FileLoad_ReadByte(pSong, &val);
					if (err) goto loader_err;
					Inst = val >> 4;
					Cmd = val & 0xf;

					err = FileLoad_ReadByte(pSong, &val);
					if (err) goto loader_err;

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

					convert_effect(pSong, k, Cmd, val, Note);

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

				err = Loaders_EndRow(pSong);
				if (err) goto loader_err;
			}
		}
		else if (pLHdr->Vers == 3)
		{
			err = FileLoad_ReadByte(pSong, &pPattern->Rows);
			if (err) goto loader_err;
		}
		else
		{
			uint32_t size, channels, packed_len, ctl;
			uint32_t lmask, fmask, cmask;
			uint32_t LineMask[4], FxMask[4], lmask, fmask, cmask;
			ChannelInfo[16];

			err = FileLoad_ReadByte(pSong, &size); // hdr size
			if (!err) err = FileLoad_ReadByte(pSong, &channels);
			if (!err) err = FileLoad_ReadByte(pSong, &pPattern->Rows);
			if (!err) err = FileLoad_ReadReverseInt(pSong, &packed_len, 2);
			if (err) goto loader_err;

			if (!channels || (channels > pSong->Channels))
			{
				err = Loaders_Error(pSong, -2, "Invalid Nr of channels (%d) in pattern %d", channels, i);
				goto loader_err;
			}
			val = 1 + (pPattern->Rows >> 6);
			err = FileLoad_ReadReverseInt(pSong, &ctl, val);
			if (err) goto loader_err;
			if (val < 4) ctl <<= 8 * (4 - val);

			pPattern->Rows++;

			if (ctl & 0x80000000) LineMask[0] = ~0;
			else if (ctl & 0x40000000) LineMask[0] = 0;
			else err = FileLoad_ReadReverseInt(pSong, &LineMask[0], 4);
			if (ctl & 0x20000000) FxMask[0] = ~0;
			else if (ctl & 0x10000000) FxMask[0] = 0;
			else err = FileLoad_ReadReverseInt(pSong, &FxMask[0], 4);
			if (ctl & 0x08000000) LineMask[1] = ~0;
			else if (ctl & 0x04000000) LineMask[1] = 0;
			else err = FileLoad_ReadReverseInt(pSong, &LineMask[1], 4);
			if (ctl & 0x02000000) FxMask[1] = ~0;
			else if (ctl & 0x01000000) FxMask[1] = 0;
			else err = FileLoad_ReadReverseInt(pSong, &FxMask[1], 4);
			if (ctl & 0x800000) LineMask[2] = ~0;
			else if (ctl & 0x400000) LineMask[2] = 0;
			else err = FileLoad_ReadReverseInt(pSong, &LineMask[2], 4);
			if (ctl & 0x200000) FxMask[2] = ~0;
			else if (ctl & 0x100000) FxMask[2] = 0;
			else err = FileLoad_ReadReverseInt(pSong, &FxMask[2], 4);
			if (ctl & 0x080000) LineMask[3] = ~0;
			else if (ctl & 0x040000) LineMask[3] = 0;
			else err = FileLoad_ReadReverseInt(pSong, &LineMask[3], 4);
			if (ctl & 0x020000) FxMask[3] = ~0;
			else if (ctl & 0x010000) FxMask[3] = 0;
			else err = FileLoad_ReadReverseInt(pSong, &FxMask[3], 4);
			if (ctl & 0x8000) LineMask[4] = ~0;
			else if (ctl & 0x4000) LineMask[4] = 0;
			else err = FileLoad_ReadReverseInt(pSong, &LineMask[4], 4);
			if (ctl & 0x2000) FxMask[4] = ~0;
			else if (ctl & 0x1000) FxMask[4] = 0;
			else err = FileLoad_ReadReverseInt(pSong, &FxMask[4], 4);
			if (ctl & 0x0800) LineMask[5] = ~0;
			else if (ctl & 0x0400) LineMask[5] = 0;
			else err = FileLoad_ReadReverseInt(pSong, &LineMask[5], 4);
			if (ctl & 0x0200) FxMask[5] = ~0;
			else if (ctl & 0x0100) FxMask[5] = 0;
			else err = FileLoad_ReadReverseInt(pSong, &FxMask[5], 4);
			if (ctl & 0x80) LineMask[6] = ~0;
			else if (ctl & 0x40) LineMask[6] = 0;
			else err = FileLoad_ReadReverseInt(pSong, &LineMask[6], 4);
			if (ctl & 0x20) FxMask[6] = ~0;
			else if (ctl & 0x10) FxMask[6] = 0;
			else err = FileLoad_ReadReverseInt(pSong, &FxMask[6], 4);
			if (ctl & 0x08) LineMask[7] = ~0;
			else if (ctl & 0x04) LineMask[7] = 0;
			else err = FileLoad_ReadReverseInt(pSong, &LineMask[7], 4);
			if (ctl & 0x02) FxMask[7] = ~0;
			else if (ctl & 0x01) FxMask[7] = 0;
			else err = FileLoad_ReadReverseInt(pSong, &FxMask[7], 4);
			if (err) goto loader_err;

			err = FileLoad_ReadByte(pSong, &val);
			if (err) goto loader_err;
			if (val != 0xff)
			{
				err = Loaders_Error(pSong, -1, "Pattern corrupted");
				goto loader_err;
			}

			for ( j = 0, pc = pLPattern
			    ; j < pPattern->Rows
			    ; j++, pc += 4*16, lmask <<= 1, fmask <<= 1)
			{
				err = Loaders_StartRow(pSong, j);
				if (err) goto loader_err;

				if ((j & 0x1f) == 0)
				{
					lmask = LineMask[j >> 5];
					fmask = FxMask[j >> 5];
				}
				if (lmask & 0x80000000)
				{
					err = FileLoad_ReadReverseInt(pSong, &cmask, 2);
					for (k = 0; k < channels; k++, chmask <<= 1)
					{
						if (chmask & 0x8000)
							read 12 bits
					}
					align o n byte
				}
				if (fmask & 0x80000000)
				{
					err = FileLoad_ReadReverseInt(pSong, &cmask, 2);
					for (k = 0; k < channels; k++, chmask <<= 1)
					{
						if (chmask & 0x8000)
							read 12 bits
					}
					align o n byte
				}

				for (k = 0; k < channels; k++)
				{
				}

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

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

	err = FileLoad_ReadReverseInt(pSong, &SmpMask0, 4);
	if (pLHdr->Version == 4)
		if (!err) err = FileLoad_ReadReverseInt(pSong, &SmpMask1, 4);
	else
		SmpMask1 = 0;
	if (err) goto loader_err;

	for(i = 0, pSmp = pSong->pSamples; i < pSong->Samples; i++, pSmp++)
	{
		err = FileLoad_ReadReverseInt(pSong, &pSmp->Size, 4);
		if (!err) err = FileLoad_ReadReverseInt(pSong, &val, 2);
		if (err) goto smp_err;

		if (val == 0xfffe)
		{
			SysLog_FileCorrupted(SysLog_High, pSong, -2, "Hybrid sample %d", i + 1);
			Loaders_String(pSong, &pSmp->pName, (uint8_t*) "[Hybrid sample]", 18, false);

			err = FileLoad_Skip(pSong, pSmp->Size);
			pSmp->Size = 0;
			if (err) goto smp_err;
		}
		else if (val == 0xffff)
		{
			SysLog_FileCorrupted(SysLog_High, pSong, -2, "Synthetic sample %d", i + 1);
			Loaders_String(pSong, &pSmp->pName, (uint8_t*) "[Synthetic sample]", 18, false);
			pSmp->Size = 0;
			//goto loader_err;
		}
		else
		{
			err = FileLoad_ReadSample(pSong, pSmp);
			if (err) goto loader_err;
		}
	}

smp_err:
	// Accept files truncated within the samples
	if (err)
		SysLog_FileCorrupted(SysLog_Medium, pSong, 0, "Sample %d: invalid data offset", 1 + (pSmp - pSong->pSamples));
	err = NULL;

loader_err:
	// Real version
	if (pLHdr->Vers <= 2)
		pSong->Version = 1.12;
	if (pLHdr->Vers == 3)
		pSong->Version = 200;
	else
		pSong->Version = 210;

	Loaders_Free(pSong, pLHdr);
	Loaders_Free(pSong, pLPattern);

	return err;
}
