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

/*
 * Channels: [1...32]
 * Patterns: [1...253]
 * 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 or x/4 units per frame.
 *           See note below on pitch limits.
 * Volume  : [0...63] , linear.
 * Panning : [0...255].
 * Sampling: 8363 Hz (frequency is turned into a relative tone).
 *
 * Note triggering & sample substitution:
 * -------------------------------------
 *
 * A new note nr with an instrument nr starts a note with the specified pitch and instrument.
 *
 * A new note nr without an instrument nr starts a note with the last used instrument nr.
 *
 * An instrument nr given without a note nr:
 *   - sets default volume from new sample.
 *   - no effect on volume/pitch vibrato position.
 *   - no effect on finetune?
 *
 * If sample is "undefined", it never affects playing (Note + Sample or Sample only).
 *
 * Note triggering does not apply when portamento effect is present.
 *
 * Retrig Qxy, y is delay, 00 is memory.
 *   Uses an increasing counter which triggers a new note and resets the counter when counter >= y
 *   and only if y > 0. The counter is not reset by a new row or a new note, but every time
 *   the previous row doesn't countain a Q effect. Really weird.
 *   For some reason retrigger is not processed if no note is playing.
 *
 * Effects:
 * -------
 *
 * ST3 period range seems to be from 33280 to 64, central note using 1712 (i.e Amiga period * 4),
 * The range is some semitones outside the [C-0..C-4..B-7] note range. Notes sliding below period 64
 * are cut.
 *
 * !!!! Effects D, E, F, I, J, K, L, Q, R with 0 use previous infobyte,
 * !!!! that is to say previous value set by ANY command.
 * !!!! Effects G, H, U with 0, use previously set value (i.e memory).
 * We try try to emulate this while loading by keeping for each channel the last non-0 value
 * and the corresponding effect. In the case the previous effect is the same as the current one
 * (that is to say in 99.9% of the cases) I keep value 0 to use memory and only forces
 * the past infobyte value in the remaining cases. Since there is sometime no previous infobyte
 * earlier in the pattern and would require the last infobyte in the previous played pattern
 * using memory will also be correct in most cases.
 *
 * Volume column is interpreted before effects column so effects may overwrite them.
 *
 * Curiously accepted volume values are in range [0-64], but values monitoring (F5 acreen)
 * shows a range [0-63]. This behaviour is confirmed in retrigs with volume multiplied by 1/2,
 * so it will differ slightly from our implementation.
 *
 * Volumes slides are DF0 and D0F are applied all slides, illegal values is treated as a D0y.
 *
 * Note cut SCx with x = 0 or x > speed does nothing. It is also not affected by a SEx.
 *
 * Fine vibrato U00 following a vibrato Hxy, will not continue vibrato at depth y but
 * switch to fine vibrato (i.e. depth y/4).
 * Implementation: we map both vibratos to a single command, so instead we continue
 * the same vibrato as before.
 *
 * Tremolo is twice finer as MOD one.
 *
 * Tremor Ixy. 00 uses previous non-0 infobyte. When the effect is present on a row, the counter
 *   decreases till a < 0 value is reached. At this point the switch between on and off occurs and
 *   the counter is reinitialised to x or y. The counter is never reset by other means
 *   (such as a new note or a new row with a tremor effect).
 *   As a consequence ontime duration x+1, offtime is y+1, changes of x or y due to a new tremor effect
 *   only take effect when the counter reaches -1 and is reinitialised.
 *   It seems that a seperate flag is set to force volume to 0 when it switches to the off counter
 *   and which is reset by a new note, because if I starts a new note in the middle of its off period,
 *   the new note plays for the remaing offtime + the ontime before being turned off.
 *   Also, if a Ixy sequence is stopped while note is off, the note remains off, meaning
 *   that this flag remains in effect.
 *   The very first tremor starts with the note off, which means that the initial counter value is 0.
 */

typedef struct
{
	uint8_t     Name[28];
	uint8_t     _1A;
	uint8_t     Type;
	uint8_t     Dummy[2];
	uint16_t    Orders;
	uint16_t    Samples;
	uint16_t    Patterns;
	uint16_t    Flags;
	uint8_t     Version[2];
	uint16_t    FileFormat;
	uint32_t    TAG;
	uint8_t     GlobalVolume;
	uint8_t     Speed;
	uint8_t     Tempo;
	uint8_t     MixingVolume;
	uint8_t     ClickRemoval;
	uint8_t     HasPanning;
	uint8_t     Dummy2[8];
	uint16_t    Special;
	uint8_t     ChannelsSettings[32];
} LHdr;

typedef struct
{
	uint8_t     Type;
	uint8_t     Filename[12];
	uint8_t     Dummy;
	uint16_t    Offset;
	uint32_t    Length;
	uint32_t    LoopStart;
	uint32_t    LoopEnd;
	uint8_t     Volume;
	uint8_t     Dummy2;
	uint8_t     Packing;
	uint8_t     Flags;
	uint32_t    Frequency;
	uint8_t     Dummy3[12];
	uint8_t     Name[28];
	uint32_t    TAG;
} LSmp;

#define S3M_Flags_SlideFrame0 0x40

#define NoteDelta (Note_Central-12*4)

static const uint8_t Tag_S3M[] = "SCRM";
static const uint8_t Typestr_S3M[] = "ScreamTracker";

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

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

static void convert_effect(SongHdr* pSong, LHdr* pLHdr, ChannelInfo* pChInfo, uint32_t effect, uint32_t value)
{
	uint32_t oeffect = effect;
#ifdef USELOG
	uint32_t ovalue = value;
#endif

	switch(effect)
	{
		case 0: // no cmd
		break;
		case 1: // A, Global: Set speed, No prev InfoByte
		{
			if (value) Pattern_AddGlbEffect_Frames(pSong, gcmd_frames_set, value);
		}
		break;
		case 2: // B, Global: Jump, No prev InfoByte
		{
			Pattern_AddGlbEffect_Position(pSong, gcmd_pos_jump, value);
		}
		break;
		case 3: // C, Global: Break, No prev InfoByte
		{
			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 4: // D, Volume: Slide, Prev InfoByte
		{
			if ((value == 0) && (pChInfo->LastEffect != oeffect))
			{
				value = pChInfo->LastValue;
				// SysLog_WatchEffect(pSong, 0, oeffect, ovalue);
			}

			if (value == 0)
			{
				// D00 repeat last slide, in case no previous infobyte in this pattern
				Pattern_AddEffect_NoteVolume(pSong, cmd_last_slide, 0);
			}
			else if (!(value & 0x0f))
			{
				// Dx0 slide up
				effect = cmd_upmem1 | flag_cmd_slide_frameN0;
				value &= 0xf0;
				if ((pLHdr->Flags & S3M_Flags_SlideFrame0)
				||  (value == 0xf0))
					effect |= flag_cmd_slide_frame0;
				Pattern_AddEffect_NoteVolume(pSong, effect, value >> 2);
			}
			else if (!(value & 0xf0))
			{
				// D0y slide down
				effect = cmd_downmem1 | flag_cmd_slide_frameN0;
				value &= 0x0f;
				if ((pLHdr->Flags & S3M_Flags_SlideFrame0)
				||  (value == 0x0f))
					effect |= flag_cmd_slide_frame0;
				Pattern_AddEffect_NoteVolume(pSong, effect, value << 2);
			}
			else if ((value & 0x0f) == 0x0f)
			{
				// DxF up
				effect = cmd_upmem1 | flag_cmd_slide_frame0;
				value &= 0xf0;
				Pattern_AddEffect_NoteVolume(pSong, effect, value >> 2);
			}
			else if ((value & 0xf0) == 0xf0)
			{
				// DFy down
				effect = cmd_downmem1 | flag_cmd_slide_frame0;
				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;
				if ((pLHdr->Flags & S3M_Flags_SlideFrame0)
				||  (value == 0x0f))
					effect |= flag_cmd_slide_frame0;
				Pattern_AddEffect_NoteVolume(pSong, effect, value << 2);
			}
		}
		break;
		case 5: // E, Pitch: Slide down, Prev InfoByte
		{
			effect = cmd_downmem1;

			if ((value == 0) && (pChInfo->LastEffect != oeffect))
			{
				value = pChInfo->LastValue;
				// SysLog_WatchEffect(pSong, 0, oeffect, ovalue);
			}

			if (value >= 0xf0)
			{
				// EFx fine down
				value = (value & 0x0f) << 2;
				// EF0 must force memory to 0, not read it
				if (!value) effect = cmd_pitch_memslide;
				effect |= flag_cmd_slide_frame0;
			}
			else if (value >= 0xe0)
			{
				// EEx extra fine down
				value &= 0x0f;
				// EE0 must force memory to 0, not read it
				if (!value) effect = cmd_pitch_memslide;
				effect |=flag_cmd_slide_frame0;
			}
			else if (value)
			{
				// Exx slide down
				value <<= 2;
				effect |= flag_cmd_slide_frameN0;
			}
				// E00 slide at last slide type and value

			Pattern_AddEffect_Pitch(pSong, effect, value);
		}
		break;
		case 6: // F, Pitch: Slide up, Prev InfoByte
		{
			effect = cmd_upmem1;

			if ((value == 0) && (pChInfo->LastEffect != oeffect))
			{
				value = pChInfo->LastValue;
				// SysLog_WatchEffect(pSong, 0, oeffect, ovalue);
			}

			if (value >= 0xf0)
			{
				// FFx fine up
				value = (value & 0x0f) << 2;
				// FF0 must force memory to 0, not read it
				if (!value) effect = cmd_pitch_memslide;
				effect |= flag_cmd_slide_frame0;
			}
			else if (value >= 0xe0)
			{
				// FEx extra fine up
				value &= 0x0f;
				// FE0 must force memory to 0, not read it
				if (!value) effect = cmd_pitch_memslide;
				effect |= flag_cmd_slide_frame0;
			}
			else if (value)
			{
				// Fxx slide up
				value <<= 2;
				effect |= flag_cmd_slide_frameN0;
			}
				// F00 slide at last slide type and value

			Pattern_AddEffect_Pitch(pSong, effect, value);
		}
		break;
		case 7: // G, Pitch: Portamento, Memory
		{
			Pattern_AddEffect_Pitch(pSong, cmd_portamento + flag_cmd_slide_frameN0, value << 2);
		}
		break;
		case 8: // H, 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 9: // I, Volume: Tremor, Prev InfoByte
		{
			uint32_t val;

			if ((value == 0) && (pChInfo->LastEffect != oeffect))
			{
				value = pChInfo->LastValue;
				// SysLog_WatchEffect(pSong, 0, oeffect, ovalue);
			}

			val = value >> 4;
			value &= 0xf;
			Pattern_AddEffect_NoteVolume(pSong, cmd_volume_tremor_S3M, Effect_Tremor(value, val));
		}
		break;
		case 0xa: // J, Pitch: Arpeggio, Prev InfoByte
		{
			uint32_t val;

			if ((value == 0) && (pChInfo->LastEffect != oeffect))
			{
				value = pChInfo->LastValue;
				// SysLog_WatchEffect(pSong, 0, oeffect, ovalue);
			}

			val = value >> 4;
			value &= 0xf;
			Pattern_AddEffect_Arpeggio(pSong, value, val, cmd_arpeggio_type_NHL);
		}
		break;
		case 0xb: // K, Pitch: Vibrato 0 - Volume: Slide, Prev InfoByte
		{
			convert_effect(pSong, pLHdr, pChInfo, 0x8, 0);
			convert_effect(pSong, pLHdr, pChInfo, 0x4, value);
		}
		break;
		case 0xc: // L, Pitch: Portamento 0 - Volume: Slide, Prev InfoByte
		{
			convert_effect(pSong, pLHdr, pChInfo, 0x7, 0);
			convert_effect(pSong, pLHdr, pChInfo, 0x4, value);
		}
		break;
		case 0xf: // O, Note: Set sample offset, No prev InfoByte
		{
			if (value)
			{
				// define offset
				// clear byte 0
				Pattern_AddEffect_Instrument(pSong, cmd_sample_set_offset, (0<<8) | 0);
				// set byte 1
				Pattern_AddEffect_Instrument(pSong, cmd_sample_set_offset, (1<<8) | 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 0x11: // Q, Note: Retrig note every y ticks with volume change type x, Prev InfoByte
		{
			uint32_t eff;

			if ((value == 0) && (pChInfo->LastEffect != oeffect))
			{
				value = pChInfo->LastValue;
				// SysLog_WatchEffect(pSong, 0, oeffect, ovalue);
			}

			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 0x12: // R, Volume: Vibrato (alias Tremolo), Prev InfoByte
		{
			uint32_t speed;

			if ((value == 0) && (pChInfo->LastEffect != oeffect))
			{
				value = pChInfo->LastValue;
				// SysLog_WatchEffect(pSong, 0, oeffect, ovalue);
			}

			speed = value >> 4;
			value &= 0xf;
			// Vibrate between [-2*y,+2*y]
			value <<= 3; // << 2 for range conversion and << 1 for depth (less than MOD)

			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 0x13: // S, +- protracker effects E, No prev InfoByte
		{
			effect = value >> 4;
			value &= 0xf;

			switch(effect)
			{
				case 0: // Set Filter, Ignore
				break;
				case 1: // S1, Pitch: Glissando control
				{
					if (value < 2)
						Pattern_AddEffect_Pitch(pSong, cmd_pitch_glissando, value);
					else
						SysLog_BadEffectValue(pSong, 0, oeffect, ovalue);
				}
				break;
				case 2: // S2, Note: Set fine tune, has no effect in ST 3.21
				{
					SysLog_BadEffect(pSong, 0, oeffect, ovalue);
				}
				break;
				case 3: // S3, Pitch: Vibrato type
				{
					switch(value)
					{
						case 1: value = vibrato_type_rampdown; break;
						case 2: value = vibrato_type_halfsquare; break;
						case 3: value = vibrato_type_random; break;
						default: value = vibrato_type_sin; break;
					}
					Pattern_AddEffect_Pitch(pSong, cmd_vibrato_type, value);
				}
				break;
				case 4: // S4, Volume: Vibrato type
				{
					switch(value)
					{
						case 1: value = vibrato_type_rampdown; break;
						case 2: value = vibrato_type_halfsquare; break;
						case 3: value = vibrato_type_random; break;
						default: value = vibrato_type_sin; break;
					}
					Pattern_AddEffect_NoteVolume(pSong, cmd_vibrato_type, value);
				}
				break;
				case 8: // S8, Panning: Set
				{
					value += value << 4;
					if (value >= 128) value++;
					Pattern_AddEffect_Panning(pSong, cmd_set, value);
				}
				break;
				case 0xa: // SA, Panning: Set (signed value)
				{
					value ^= 8; // Signed, cf StarShine - PM
					value += value << 4;
					if (value >= 128) value++;
					Pattern_AddEffect_Panning(pSong, cmd_set, value);
				}
				break;
				case 0xb: // SB, Global: Loop in column
				{
					Pattern_AddGlbEffect_Position(pSong, gcmd_pos_loop | value, pChInfo->Channel);
				}
				break;
				case 0xc: // SC, Note: Cut note after x ticks
				{
					if (value) // S3M quirk, no cut if x=0
						Pattern_AddEffect_NoteVolume(pSong, cmd_volume_cut, value);
				}
				break;
				case 0xd: // SD, Note: Delay note for x ticks
				{
					Pattern_AddEffect_Instrument(pSong, cmd_sample_note_delay, value);
				}
				break;
				case 0xe: // SE, Global: Delay row for x rows
				{
					Pattern_AddGlbEffect_Frames(pSong, gcmd_frames_repeat, value);
				}
				break;
				case 0xf: // SF, Ignore
				break;
				default:
				{
					SysLog_BadEffect(pSong, 0, oeffect, ovalue);
				}
			}
		}
		break;
		case 0x14: // T, Global: Set tempo, No prev InfoByte
		{
			if (value >= 0x20)
			{
				Pattern_AddGlbEffect_Tempo(pSong, cmd_set, value);
			}
			else
			{
				SysLog_BadEffectValue(pSong, 0, oeffect, ovalue);
			}
		}
		break;
		case 0x15: // U, 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 0x16: // V, Global Volume: Set, No prev InfoByte
		{
			if (value <= 64)
				Pattern_AddGlbEffect_Volume(pSong, cmd_set, value << 2);
			else
				SysLog_BadEffectValue(pSong, 0, oeffect, ovalue);
		}
		break;
		case 0x18: // X, Panning: Set, No prev InfoByte
		{
			if (value > 128) value = Panning_Surround;
			else value <<= 1;
			Pattern_AddEffect_Panning(pSong, cmd_set, value);
		}
		break;
		default:
		{
			SysLog_BadEffect(pSong, 0, oeffect, ovalue);
		}
	}
}

static const _kernel_oserror* Pattern_S3M
	( SongHdr* pSong
	, uint32_t i
	, LHdr* pLHdr
	, 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;
	uint32_t        Note, Inst;
	ChannelInfo     ChInfo[33]; // There is a single shared memory per channel
	ChannelInfo*    pChInfo;

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

	for (j = 0; j < 33; j++)
	{
		pChInfo = ChInfo + j;
		pChInfo->Channel = j;
		pChInfo->Note = 0;
		pChInfo->LastEffect = 0;
		pChInfo->LastValue = 0;
	}

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

		lastchannel = -1;
		while (pPos < pEnd)
		{
			// read channel nr and flags
			val = *pPos++;
			// next row mark?
			if (!val) break;
			// will we remain within pattern limits?
			pPos2 = pPos;
			if (val & 0x20) pPos2 += 2;
			if (val & 0x40) pPos2++;
			if (val & 0x80) pPos2 += 2;
			if (pPos2 > pEnd)
			{
				SysLog_FileCorrupted(SysLog_Medium, pSong, -1, "Pattern truncated");
				pPos = pEnd;
				break;
			}
			// valid channel ?
			channel = val & 0x1f;
			if ((channel <= lastchannel)
			||  (channel >= pSong->Channels))
			{
				if (channel <= lastchannel)
					SysLog_FileCorrupted
						( SysLog_High, pSong, - (pEnd - pPos + 1)
						, "Invalid channel %d order in pattern"
						, channel
						);
				else
					SysLog_FileCorrupted
						( SysLog_Low, pSong, - (pEnd - pPos + 1)
						, "Extra channel %d in pattern"
						, channel
						);
				pPos = pPos2;
				continue;
			}
			lastchannel = channel;

			// Read note & instrument
			if (val & 0x20)
			{
				Note = *pPos++;
				if (Note == 254) Note = Note_Cut;
				else if (((Note & 0xf0) <= 0x70)
				     &&  ((Note & 0x0f) <= 0x0b))
				{
					Note = ((Note & 0xf0) >> 4)*12 + (Note & 0x0f);
					Note += NoteDelta;
				}
				else
				{
					if (Note != 0xff)
						SysLog_BadNote(pSong, FileLoad_GetPos(pSong) - (pEnd - pPos), channel, Note);
					Note = 0;
				}
				Inst = *pPos++;
			}
			else
			{
				Note = 0;
				Inst = 0;
			}

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

			// Read Volume column
			if (val & 0x40)
			{
				if (*pPos <= 64)
					Pattern_AddEffect_NoteVolume(pSong, cmd_set, (*pPos) << 2);
				else
					SysLog_BadEffectValue(pSong, 0, 256, *pPos);
				pPos++;
			}

			// Read effect and value
			if (val & 0x80)
			{
				pChInfo = ChInfo + channel;
				pChInfo->Note = (Note < Note_CmdMin) ? Note : 0;
				// remember last non-zero value (and effect)
				if (pPos[1] != 0)
				{
					pChInfo->LastEffect = pPos[0];
					pChInfo->LastValue = pPos[1];
				}
				convert_effect(pSong, pLHdr, pChInfo, pPos[0], pPos[1]);
				pPos += 2;
			}

			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_S3M(SongHdr* pSong)
{
	const _kernel_oserror* err = NULL;
	SubSong*        pSubSong = Song_GetSubSong(pSong, 0);
	LHdr*           pLHdr = NULL;
	LSmp*           pLSmp = NULL;
	uint8_t*        pLTrack = NULL;
	uint16_t*       pOffsetTable = NULL;
	uint16_t*       pOffset;
	ChannelDefault* pDef;
	Pattern*        pPattern;
	Sample*         pSmp;
	uint32_t        Size;
	uint32_t        val;
	int             i;

	// Reject file to short to contain header
	if (FileLoad_GetSize(pSong) < sizeof(LHdr))
		return Loaders_NotThisType;

	// 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;

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

	// set version
	pSong->Version = (pLHdr->Version[1] & 0x0f) * 100
	               + ((pLHdr->Version[0] & 0xf0) >> 4) * 10
	               + (pLHdr->Version[0] & 0x0f);

	pSong->Flags = Song_Flag_CutOnHighPitch
	             | Song_Flag_MoveLoopAfterLoop
	             | Song_Flag_ResetsLoopsOnPatternChange;
	pSong->MinPitch = Note_Central - 52; // Somewhat below the ST3 C-0
	pSong->MaxPitch = Note_Central + 56; // Somewhat above the ST3 B-7
	// Unstored samples are initialised as undefined
	pSong->pSampleDef->Type = Smp_Type_Undefined;
	pSubSong->Defaults.TremorOn = true;

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

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

	pSubSong->SeqLen   = pLHdr->Orders;
	if (!pSubSong->SeqLen)
	{
		err = Loaders_InvalidSong(pSong, 32, &pSubSong->SeqLen, pSubSong->SeqLen);
		goto loader_err;
	}
	pSong->Patterns = pLHdr->Patterns;
	if (!pSong->Patterns || (pSong->Patterns > 253)) // cf. sequence
	{
		err = Loaders_InvalidSong(pSong, 26, &pSong->Patterns, pSong->Patterns);
		goto loader_err;
	}
	pSong->Samples = pLHdr->Samples;
	if (!pSong->Samples || (pSong->Samples > 255))
	{
		err = Loaders_InvalidSong(pSong, 34, &pSong->Samples, pSong->Samples);
		goto loader_err;
	}

	// add volume slide on 1st frame for old versions
	if (pSong->Version <= 300)
		pLHdr->Flags |= S3M_Flags_SlideFrame0;

	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;

	// read number of channels + left and right channels settings
	for (i = 0, pDef = pSubSong->Defaults.ChDef; i < 32; i++, pDef++)
	{
		val = pLHdr->ChannelsSettings[i];
		if (val == 0xff)
			pDef->Mute = 1;
		else
		{
			val &= 0xf;
			if (val <= 7) val = 64;
			else val = 192;
			pDef->Panning = val;
			pSong->Channels = i;
		}
	}
	pSong->Channels++;

	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;
		if (val >= 254) val -= 256;
		pSubSong->pSeqs[i] = val;
	}

	// read offset tables
	val = (pSong->Samples + pSong->Patterns) << 1;
	err = Loaders_Alloc(pSong, (void**) &pOffsetTable, val);
	if (err) goto loader_err;
	err = FileLoad_Read(pSong, pOffsetTable, val);
	if (err) goto loader_err;

	// read channels default panning
	if (pLHdr->HasPanning == 252)
	{
		err = FileLoad_Read(pSong, (void**) &pLHdr->ChannelsSettings[0], 32);
		if (err) goto loader_err;

		for (i = 0, pDef = pSubSong->Defaults.ChDef; i < 32; i++, pDef++)
		{
			val = pLHdr->ChannelsSettings[i];

			if (val & 0x20)
			{
				val &= 0xf;
				val += val << 4;
				if (val >= 128) val++;
				pDef->Panning = val;
			}
		}
	}

	pOffset = pOffsetTable;

	// read samples info
	for (i = 0, pSmp = pSong->pSamples; i < pSong->Samples; i++, pSmp++)
	{
		// move to sample header start
		err = FileLoad_SetPos(pSong, (*pOffset++) << 4);
		if (err) goto loader_err;

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

		// read filename
		err = Loaders_String(pSong, &pSmp->pFilename, &pLSmp->Filename[0], sizeof(pLSmp->Filename), false);
		if (err) goto loader_err;

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

		// check type and packing
		if ((pLSmp->Type == 1)
		&&  (pLSmp->Packing == 0))
		{
			// read sizes
			pSmp->Size         = pLSmp->Length;
			pSmp->LoopStart    = pLSmp->LoopStart;
			pSmp->LoopEnd      = pLSmp->LoopEnd;
			pSmp->Frequency    = 8363;
			pSmp->RelTone      = Convert_RelTone(pSmp->Frequency, pLSmp->Frequency);

			// read volume
			if (pLSmp->Volume > 64) pSmp->DefaultVolume = 256;
			else pSmp->DefaultVolume = pLSmp->Volume << 2;

			// read sample type
			pSmp->Type = Smp_Type_Unsigned;
			if (pLSmp->Flags & 1) pSmp->Type |= Smp_Type_Loop;
			if (pLSmp->Flags & 4) pSmp->Type |= Smp_Type_16bit;

			// read data
			err = FileLoad_SetPos(pSong, pLSmp->Offset << 4);
			if (err)
			{
				SysLog_FileCorrupted(SysLog_Medium, pSong, 0, "Sample %d: invalid data offset", 1 + (pSmp - pSong->pSamples));
				pSmp->Size = 0;
			}
			else
			{
				err = FileLoad_ReadSample(pSong, pSmp);
				if (err) goto loader_err;
			}
		}
		else
		{
			pSmp->Size = 0;
			pSmp->Type = Smp_Type_Undefined;
		}
	}

	// fill the patterns
	// pOffset still up to date
	for (i = 0, pPattern = pSong->pPatterns; i < pSong->Patterns; i++, pPattern++, pOffset++)
	{
		pPattern->Rows = 64;

		if (!*pOffset)
		{
			// empty pattern of 64 rows
			pPattern->Rows = 64;
			err = Loaders_EmptyPattern(pSong, pPattern);
			if (err) goto loader_err;
		}
		else
		{
			// Move to pattern description
			err = FileLoad_SetPos(pSong, (*pOffset) << 4);
			if (err) goto loader_err;
			// Read pattern length
			err = FileLoad_ReadInt(pSong, &Size, 2);
			if (err) goto loader_err;

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

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

loader_err:
	Loaders_Free(pSong, pLHdr);
	Loaders_Free(pSong, pLSmp);
	Loaders_Free(pSong, pLTrack);
	Loaders_Free(pSong, pOffsetTable);

	return err;
}
