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

/*
 * Channels: [1...32]
 * Patterns: [1...256]
 * Rows    : [1...65535]
 * Orders  : [1...256]
 * Samples : [1...]
 * Instr.  : [1...255]
 * Tempo   : [32...255], T frames per 2.5 seconds.
 * Speed   : [1...31?], S frames per row.
 * Notes   : [C-0...C-4...B-9]
 * 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/16 units per row.
 * Volume  : [0...64] , linear.
 * Panning : [0...255].
 * Sampling: 8363 Hz (frequency is turned into a relative tone).
 */

typedef struct
{
	uint8_t     Name[32];
	uint16_t    Orders;
	uint16_t    Patterns;
	uint16_t    Instruments;
	uint16_t    Flags;
	uint8_t     Dummy[8];
	uint8_t     Speed;
	uint8_t     Tempo;
	uint8_t     GlobalVolume;
	uint8_t     MixingVolume;
	uint8_t     Dummy2[8];
	uint32_t    TAG;
	struct
	{
		uint8_t Name[12];
		uint8_t Chorus;
		uint8_t Reverb;
		uint8_t Panning;
		uint8_t Status;
	} Channels[32];
	uint8_t     Seq[256];
} LHdr;

typedef struct
{
	uint8_t     Name[32];
	uint8_t     Map[120];
	uint8_t     Dummy[8];
	uint8_t     VolEnvValues[64];
	uint8_t     PanEnvValues[64];
	uint8_t     PitchEnvValues[64];
	uint8_t     VolEnvPoints;
	uint8_t     VolEnvSustain;
	uint8_t     VolEnvLoopStart;
	uint8_t     VolEnvLoopEnd;
	uint8_t     VolEnvFlags;
	uint8_t     VolEnvDummy[3];
	uint8_t     PanEnvPoints;
	uint8_t     PanEnvSustain;
	uint8_t     PanEnvLoopStart;
	uint8_t     PanEnvLoopEnd;
	uint8_t     PanEnvFlags;
	uint8_t     PanEnvDummy[3];
	uint8_t     PitchEnvPoints;
	uint8_t     PitchEnvSustain;
	uint8_t     PitchEnvLoopStart;
	uint8_t     PitchEnvLoopEnd;
	uint8_t     PitchEnvFlags;
	uint8_t     PitchEnvDummy[3];
	uint16_t    Fadeout;
	uint16_t    Samples;
	uint32_t    TAG;
} LInst;

typedef struct
{
	uint8_t     Filename[13];
	uint8_t     Dummy[3];
	uint32_t    Length;
	uint32_t    LoopStart;
	uint32_t    LoopEnd;
	uint32_t    Frequency;
	uint8_t     Volume;
	uint8_t     Panning;
	uint8_t     Dummy2[14];
	uint8_t     Flags;
	uint8_t     Dummy3[11];
	uint32_t    TAG;
} LSmp;

typedef struct
{
	int32_t     Channel;
	uint32_t    Note;
} ChannelConfig;

#define NoteDelta (Note_Central-12*4)

static const uint8_t Tag_IM10[] = "IM10";
static const uint8_t Tag_II10[] = "II10";
static const uint8_t Tag_IS10[] = "IS10";

static const uint8_t Typestr_IMF[] = "Imago Orpheus";
#define TAG(x) (*(const uint32_t*) Tag_##x)

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

	switch(effect)
	{
		case 0: // no cmd
		break;
		case 1: // Global: Set speed
		{
			if (value)
				Pattern_AddGlbEffect_Frames(pSong, gcmd_frames_set, value);
			else
				SysLog_BadEffectValue(pSong, 0, oeffect, ovalue);
		}
		break;
		case 2: // Global: Tempo, Set
		{
			if (value >= 0x20)
				Pattern_AddGlbEffect_Tempo(pSong, cmd_set, value);
			else
				SysLog_BadEffectValue(pSong, 0, oeffect, ovalue);
		}
		break;
		case 3: // Pitch: Portamento, Memory
		{
			Pattern_AddEffect_Pitch(pSong, cmd_portamento+flag_cmd_slide_frameN0, value << 2);
		}
		break;
		case 4: // Pitch: Portamento 0 - Volume: Slide, Memory
		{
			convert_effect(pSong, pChConfig, 0x3, 0);
			convert_effect(pSong, pChConfig, 0xd, value);
		}
		break;
		case 5: // Pitch: Vibrato at speed x and depth y, 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 6: // Pitch: Vibrato 0 - Volume: Slide, Memory
		{
			convert_effect(pSong, pChConfig, 0x5, 0);
			convert_effect(pSong, pChConfig, 0xd, value);
		}
		break;
		case 7: // Pitch: Vibrato, fine, at speed x and depth y, Memory
		{
			uint32_t 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 8: // Volume: Vibrato (alias Tremolo), Memory
		{
			uint32_t speed = value >> 4;
			value &= 0xf;
			// Vibrate between [-4*y,+4*y]
			value <<= 4; // << 2 for range conversion and << 2 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 9: // Pitch: Arpeggio, Memory
		{
			uint32_t val;

			val = value >> 4;
			value &= 0xf;
			Pattern_AddEffect_Arpeggio(pSong, value, val, cmd_arpeggio_type_NHL);
		}
		break;
		case 0xA: // Panning: Set [0, 255]
		{
			if (value >= 128) value++;
			Pattern_AddEffect_Panning(pSong, cmd_set, value);
		}
		break;
		case 0xB: // Panning: Slide (right has priority), range [0,255], Memory
		{
			if (value)
			{
				effect = flag_cmd_slide_frameN0;

				if (value & 0xf0)
				{
					// x0
					effect += cmd_upmem1;
					Pattern_AddEffect_Panning(pSong, effect, (value & 0xf0) >> 4);
				}
				else
				{
					// 0y
					effect += cmd_downmem1;
					Pattern_AddEffect_Panning(pSong, effect, value);
				}
			}
			else
			{
				Pattern_AddEffect_Panning(pSong, cmd_last_slide, 0);
			}
		}
		break;
		case 0xC: // Volume: Set [0, 64], values above are truncated to 64
		{
			if (value > 64) value = 64;
			Pattern_AddEffect_NoteVolume(pSong, cmd_set, value << 2);
		}
		break;
		case 0xD: // Volume: Slide (up has priority), Memory
		{
			if (value)
			{
				effect = flag_cmd_slide_frameN0;

				if (value & 0xf0)
				{
					// x0
					effect += cmd_upmem1;
					Pattern_AddEffect_NoteVolume(pSong, effect, (value & 0xf0) >> 2);
				}
				else
				{
					// 0y
					effect += cmd_downmem1;
					Pattern_AddEffect_NoteVolume(pSong, effect, value << 2);
				}
			}
			else
			{
				Pattern_AddEffect_NoteVolume(pSong, cmd_last_slide, 0);
			}
		}
		break;
		case 0xE: // Volume: Slide, fine, (up has priority), Memory
		{
			if (value)
			{
				effect = flag_cmd_slide_frame0;

				if (value & 0xf0)
				{
					// x0
					effect += cmd_upmem1;
					Pattern_AddEffect_NoteVolume(pSong, effect, (value & 0xf0) >> 2);
				}
				else
				{
					// 0y
					effect += cmd_downmem1;
					Pattern_AddEffect_NoteVolume(pSong, effect, value << 2);
				}
			}
			else
			{
				Pattern_AddEffect_NoteVolume(pSong, cmd_last_slide, 0);
			}
		}
		break;
		case 0xF: // Note: Set fine tune in 1/8 semitone
		{
			int32_t svalue = value << 28;

			// only if a note is defined at the same time
			if (pChConfig->Note) Pattern_AddEffect_Instrument(pSong, cmd_sample_set_finetune, svalue >> 23);
		}
		break;
		case 0x10: // Pitch: Semitone up, Memory
		{
			if (value > 63) value = 63;
			Pattern_AddEffect_Pitch(pSong, cmd_pitch_semitoneup, (flag_cmd_slide_frameN0 << 4) | value);
		}
		break;
		case 0x11: // Pitch: Semitone down, Memory
		{

			if (value > 63) value = 63;
			Pattern_AddEffect_Pitch(pSong, effect, (flag_cmd_slide_frameN0 << 4) | value);
		}
		break;
		case 0x12: // Pitch: Slide up, Memory
		{
			effect = cmd_upmem1 | flag_cmd_slide_frameN0;

			Pattern_AddEffect_Pitch(pSong, effect, value << 2);
		}
		break;
		case 0x13: // Pitch: Slide down, Memory
		{
			effect = cmd_downmem1 | flag_cmd_slide_frameN0;

			Pattern_AddEffect_Pitch(pSong, effect, value << 2);
		}
		break;
		case 0x14: // Pitch: Slide up, fine, Memory
		{
			effect = cmd_upmem1 | flag_cmd_slide_frame0;

			Pattern_AddEffect_Pitch(pSong, effect, value << 2);
		}
		break;
		case 0x15: // Pitch: Slide down, fine, Memory
		{
			effect = cmd_downmem1 | flag_cmd_slide_frame0;

			Pattern_AddEffect_Pitch(pSong, effect, value << 2);
		}
		break;
		case 0x16: // Filter, Cutoff
		{
			Pattern_AddEffect_Pitch(pSong, cmd_filter_setcutoff, value);
			if (value < 0x7f) pSong->Flags |= Song_Flag_Filters;
		}
		break;
		case 0x17: // Filter, Slide + Resonance
		{
			Pattern_AddEffect_Pitch(pSong, cmd_filter_setresonance, value);
			if (value) pSong->Flags |= Song_Flag_Filters;
		}
		break;
		case 0x18: // Note: Set sample offset
		{
			if (value)
			{
				// define offset
				Pattern_AddEffect_Instrument(pSong, cmd_sample_set_offset, 0x000 | 0);
				// set offset byte 1
				Pattern_AddEffect_Instrument(pSong, cmd_sample_set_offset, 0x100 | value);
			}

			// only if a note is defined at the same time
			if (pChConfig->Note) Pattern_AddEffect_Instrument(pSong, cmd_sample_set_offset, 0xf00);
		}
		break;
		case 0x19: // Note: Set sample offset, fine
		{
			if (value)
			{
				// set offset byte 0
				Pattern_AddEffect_Instrument(pSong, cmd_sample_set_offset, 0x000 | value);
			}

			// only if a note is defined at the same time
			if (pChConfig->Note) Pattern_AddEffect_Instrument(pSong, cmd_sample_set_offset, 0xf00);
		}
		break;
		case 0x1A: // Note: Key off
		{
			Pattern_AddEffect_Instrument(pSong, cmd_sample_note_action_off, value);
		}
		break;
		case 0x1B: // Note: Retrig note every y ticks with volume change type x, Memory
		{
			uint32_t eff = 0;

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

			Pattern_AddEffect_RetrigCounter(pSong, cmd_retrig_count_type_XM, value);
			Pattern_AddEffect_Instrument(pSong, cmd_sample_note_retrig, eff);
		}
		break;
		case 0x1C: // Volume: Tremor on for x ticks, off for y ticks
		{
			uint32_t val;

			val = value >> 4;
			value &= 0xf;
			Pattern_AddEffect_NoteVolume(pSong, cmd_volume_tremor_XM, Effect_Tremor(value, val));
		}
		break;
		case 0x1D: // Global: Jump
		{
			Pattern_AddGlbEffect_Position(pSong, gcmd_pos_jump, value);
		}
		break;
		case 0x1E: // Global: Break
		{
			Pattern_AddGlbEffect_Position(pSong, gcmd_pos_break, value);
		}
		break;
		case 0x1F: // Global Volume: Set
		{
			if (value <= 64)
				Pattern_AddGlbEffect_Volume(pSong, cmd_set, value << 2);
			else
				SysLog_BadEffectValue(pSong, 0, oeffect, ovalue);
		}
		break;
		case 0x20: // GlobalVolume: Slide (up has priority), Memory
		{
			if (value)
			{
				effect = flag_cmd_slide_frameN0;

				if (value & 0xf0)
				{
					// x0
					effect += cmd_upmem1;
					Pattern_AddGlbEffect_Volume(pSong, effect, (value & 0xf0) >> 2);
				}
				else
				{
					// 0y
					effect += cmd_downmem1;
					Pattern_AddGlbEffect_Volume(pSong, effect, value << 2);
				}
			}
			else
			{
				Pattern_AddGlbEffect_Volume(pSong, cmd_last_slide, 0);
			}
		}
		break;
		case 0x21: // +- protracker effects E
		{
			effect = value >> 4;
			value &= 0xf;

			switch(effect)
			{
				case 1: // X1, Set filter
				{
					SysLog_SkipEffect(pSong, 0, oeffect, ovalue);
				}
				break;
				case 3: // X3 Pitch: Glissando control
				{
					if (value < 2)
						Pattern_AddEffect_Pitch(pSong, cmd_pitch_glissando, value);
					else
					SysLog_BadEffectValue(pSong, 0, oeffect, ovalue);
			        }
				break;
				case 5: // X5 Pitch: Vibrato type
				{
					effect = value;

					switch(effect & 3)
					{
						case 1: value = vibrato_type_ramp; break;
						case 2: value = vibrato_type_square; break;
						default: value = vibrato_type_sin;
					}

					if (effect & 4)
						value |= vibrato_type_noretrig;

					Pattern_AddEffect_Pitch(pSong, cmd_vibrato_type, value);
				}
				break;
				case 8: // X8 Volume: Vibrato type
				{
					effect = value;

					switch(effect & 3)
					{
						case 1: value = vibrato_type_ramp; break;
						case 2: value = vibrato_type_square; break;
						default: value = vibrato_type_sin;
					}

					if (effect & 4)
						value |= vibrato_type_noretrig;

					Pattern_AddEffect_NoteVolume(pSong, cmd_vibrato_type, value);
				}
				break;
				case 0xa: // XA Volume: Up
				{
					if (value) Pattern_AddEffect_NoteVolume(pSong, cmd_up | flag_cmd_slide_frame0, value << 2);
				}
				break;
				case 0xb: // XB Volume: Down, Memory seperate from Axy
				{
					if (value) Pattern_AddEffect_NoteVolume(pSong, cmd_down | flag_cmd_slide_frame0, value << 2);
				}
				break;
				case 0xc: // XC Note: Cut note after x ticks
				{
					Pattern_AddEffect_NoteVolume(pSong, cmd_volume_cut, value);
				}
				break;
				case 0xd: // XD Note: Delay note after x ticks
				{
					Pattern_AddEffect_Instrument(pSong, cmd_sample_note_delay, value);
				}
				break;
				case 0xe: // XE Ignore envelope
				{
					Pattern_AddEffect_NoteVolume(pSong, cmd_volume_envelope, 0);
					Pattern_AddEffect_Panning(pSong, cmd_panning_envelope, 0);
					Pattern_AddEffect_Pitch(pSong, cmd_pitch_envelope, 0);
					if (value) SysLog_BadEffect(pSong, 0, oeffect, ovalue);
				}
				break;
				case 0xf: // XF Invert loop
				{
					SysLog_SkipEffect(pSong, 0, oeffect, ovalue);
				}
				break;
				default:
				{
					SysLog_BadEffect(pSong, 0, oeffect, ovalue);
				}
			}
		}
		break;
		case 0x22: // Chorus
		case 0x23: // Reverb
		{
			SysLog_SkipEffect(pSong, 0, oeffect, ovalue);
		}
		break;
		default:
		{
			SysLog_BadEffect(pSong, 0, oeffect, ovalue);
		}
	}
}

static const _kernel_oserror* Pattern_IMF(SongHdr* pSong, uint32_t i, Pattern* pPattern, const uint8_t* pPos, const uint8_t* pEnd)
{
	const _kernel_oserror* err = NULL;
	int             j;
	const uint8_t*  pPos2;
	int             lastchannel;
	uint32_t        Note, Inst, Info, Effect, Value;
	ChannelConfig   ChConfig;

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

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

		lastchannel = -1;

		while (pPos < pEnd)
		{
			// read channel nr and flag
			Info = *pPos++;
			// next row mark?
			if (!Info) break;
			ChConfig.Channel = Info & 0x1f;

			// will we remain within pattern limits while reading channel info?
			if (Info & 0xE0)
			{
				if (pPos >= pEnd)
				{
					SysLog_FileCorrupted(SysLog_Medium, pSong, - 1, "Pattern truncated");
					break;
				}
				pPos2 = pPos;
				if (Info & 0x20) pPos2 += 2;
				if (Info & 0x40) pPos2 += 2;
				if (Info & 0x80) pPos2 += 2;
				if (pPos2 > pEnd)
				{
					SysLog_FileCorrupted(SysLog_Medium, pSong, -1, "Pattern truncated");
					pPos = pEnd;
					break;
				}
			}
			else
			{
				Info = 0;
				pPos2 = pPos;
			}

			if ((ChConfig.Channel <= lastchannel)
			||  (ChConfig.Channel >= pSong->Channels))
			{
				if (ChConfig.Channel <= lastchannel)
					SysLog_FileCorrupted
						( SysLog_High, pSong, - (pEnd - pPos + 1)
						, "Invalid channel %d order in pattern"
						, ChConfig.Channel
						);
				else
					SysLog_FileCorrupted
						( SysLog_Low, pSong, - (pEnd - pPos + 1)
						, "Invalid channel %d in pattern"
						, ChConfig.Channel
						);
				pPos = pPos2;
			}
			else
			{
				lastchannel = ChConfig.Channel;

				// Read channel values
				if (Info & 0x20)
				{
					Note = *pPos++;
					if (Note == 160) Note = Note_OffXM;
					else if (((Note & 0xf0) <= 0x90)
					     &&  ((Note & 0x0f) <= 0x0b))
					{
						Note = ((Note & 0xf0) >> 4)*12 + (Note & 0x0f);
						Note += NoteDelta;
					}
					else
					{
						if (Note != 0xff) SysLog_BadNote(pSong, FileLoad_GetPos(pSong) - (pEnd - pPos), ChConfig.Channel, Note);
						Note = 0;
					}
					Inst = *pPos++;
				}
				else
				{
					Note = 0;
					Inst = 0;
				}

				ChConfig.Note = (Note < Note_CmdMin) ? Note : 0;
				err = Loaders_StartChannel(pSong, ChConfig.Channel, Note, Inst);
				if (err) return err;

				if (Info & 0x40)
				{
					Effect = *pPos++;
					Value = *pPos++;
					convert_effect(pSong, &ChConfig, Effect, Value);
				}
				if (Info & 0x80)
				{
					Effect = *pPos++;
					Value = *pPos++;
					convert_effect(pSong, &ChConfig, Effect, Value);
				}

				err = Loaders_EndChannel(pSong);
				if (err) return err;
			}
		}

		err = Loaders_EndRow(pSong);
		if (err) return err;
	}

	return Loaders_EndPattern(pSong, pPattern);
}

const _kernel_oserror* Loader_IMF(SongHdr* pSong)
{
	const _kernel_oserror* err = NULL;
	SubSong*        pSubSong = Song_GetSubSong(pSong, 0);
	LHdr*           pLHdr = NULL;
	LInst*          pLInst = NULL;
	LSmp*           pLSmp = NULL;
	uint8_t*        pLTrack = NULL;
	Pattern*        pPattern;
	Sample*         pSmp;
	Instrument*     pInstr;
	ChannelDefault* pDef;
	uint8_t*        pc;
	uint32_t        val, Size;
	int             i, j;

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

	// First read all information required for memory allocation
	err = Loaders_Alloc(pSong, (void**) &pLHdr, sizeof(*pLHdr));
	if (err) goto loader_err;
	err = Loaders_Alloc(pSong, (void**) &pLInst, sizeof(*pLInst));
	if (err) goto loader_err;
	err = Loaders_Alloc(pSong, (void**) &pLSmp, sizeof(*pLSmp));
	if (err) goto loader_err;

	pSong->Flags = 0;
	pSong->MinPitch = NoteDelta;
	pSong->MaxPitch = NoteDelta + 120;

	//which type do we try to load?
	err = FileLoad_SetPos(pSong, 0);
	if (err) goto loader_err;
	err = FileLoad_Read(pSong, pLHdr, sizeof(*pLHdr));
	if (err) goto loader_err;

	// is IMF ?
	if (pLHdr->TAG != TAG(IM10))
	{
		err = Loaders_NotThisType;
		goto loader_err;
	}
	pSong->Version = 0;

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

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

	pSubSong->SeqLen = pLHdr->Orders;
	if (!pSubSong->SeqLen || (pSubSong->SeqLen > 256))
	{
		err = Loaders_InvalidSong(pSong, 32, &pSubSong->SeqLen, pSubSong->SeqLen);
		goto loader_err;
	}
	pSong->Instruments = pLHdr->Instruments;
	if (pSong->Instruments > 255)
	{
		err = Loaders_InvalidSong(pSong, 34, &pSong->Instruments, pSong->Instruments);
		goto loader_err;
	}
	pSong->Samples = 0;
	pSong->Patterns = pLHdr->Patterns;
	if (!pSong->Patterns || (pSong->Patterns > 256)) // cf. orders
	{
		err = Loaders_InvalidSong(pSong, 38, &pSong->Patterns, pSong->Patterns);
		goto loader_err;
	}
	if (pLHdr->Flags & 1) pSong->Flags |= Song_Flag_Linear;

	if (pLHdr->GlobalVolume > 64)
		 pSubSong->Defaults.Volume = 256;
	else pSubSong->Defaults.Volume = pLHdr->GlobalVolume << 2;
	if (pLHdr->Speed) pSubSong->Defaults.Speed = pLHdr->Speed;
	if (pLHdr->Tempo) pSubSong->Defaults.Tempo = pLHdr->Tempo;

	// read number of channels + channel panning settings
	pSong->Channels  = 0;

	for (i = 0, pDef = pSubSong->Defaults.ChDef; i < 32; i++, pDef++)
	{
		val = pLHdr->Channels[i].Status;
		if (val == 1)
			pDef->Mute = 1;
		if (val < 2)
			pSong->Channels  = i + 1;

		pDef->Panning = pLHdr->Channels[i].Panning;
		if (pDef->Panning >= 128)
			pDef->Panning++;
	}

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

	for (i = 0; i < pSubSong->SeqLen; i++)
	{
		val = pLHdr->Seq[i];
		if (val == 255) val -= 257; // To be igonred
		pSubSong->pSeqs[i] = val;
	}

	// fill the patterns
	for (i = 0, pPattern = pSong->pPatterns; i < pSong->Patterns; i++, pPattern++)
	{
		err = FileLoad_ReadInt(pSong, &Size, 2);
		if (err) goto loader_err;
		// read pattern length
		err = FileLoad_ReadInt(pSong, &pPattern->Rows, 2);
		if (err) goto loader_err;

		if (!pPattern->Rows || (pPattern->Rows > song_max_rows))
		{
			err = Loaders_InvalidSong(pSong, -2, &pPattern->Rows, pPattern->Rows);
			goto loader_err;
		}

		Size -= 4;
		if (Size)
		{
			err = Loaders_Alloc(pSong, (void**) &pLTrack, Size);
			if (err) goto loader_err;
			err = FileLoad_Read(pSong, pLTrack, Size);
			if (err) goto loader_err;
		}
		err = Pattern_IMF(pSong, i, pPattern, pLTrack, pLTrack + Size);
		if (err) goto loader_err;

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

	// read instruments and samples info
	for (i = 0, pInstr = pSong->pInstruments, pSmp = pSong->pSamples; i < pSong->Instruments; i++, pInstr++)
	{
		err = FileLoad_Read(pSong, pLInst, sizeof(*pLInst));
		if (err) goto loader_err;

		// check if is really instrument header
		if (pLInst->TAG != TAG(II10))
		{
			err = Loaders_Error(pSong, -4, "Expected II10 tag");
			goto loader_err;
		}

		// read name
		err = Loaders_String(pSong, &pInstr->pName, &pLInst->Name[0], sizeof(pLInst->Name), true);
		if (err) goto loader_err;

		// read note table
		err = Loaders_AllocMapTable(pSong, &pInstr->pNotesMap);
		if (err) goto loader_err;
		NoteMap* pn;
		for (j = 0, pn = pInstr->pNotesMap + NoteDelta; j < 120; j++, pn++)
		{
			pn->Volume = 255;
			pn->SampleNr = pLInst->Map[j] + pSong->Samples + 1;
			pn->Pitch = (NoteDelta + j) << 8;
		}
		// read volume fadeout
		pInstr->FadeoutVolume = pLInst->Fadeout << 1;
		// Volume Envelope
		{
			Envelope* pEnv = &pInstr->VolumeEnvelope;

			val = pLInst->VolEnvFlags;
			pEnv->Flags = 0;
			if (val & 1) pEnv->Flags |= Envelope_Flag_On;
			if (val & 2) pEnv->Flags |= Envelope_Flag_Sustain;
			if (val & 4) pEnv->Flags |= Envelope_Flag_Loop;
			val = pLInst->VolEnvPoints;
			if (val > 16) val = 0;
			pEnv->Points = val;
			if (pEnv->Points)
			{
				pEnv->LoopStart = pLInst->VolEnvLoopStart;
				pEnv->LoopEnd   = pLInst->VolEnvLoopEnd;
				pEnv->SustainStart = pLInst->VolEnvSustain;
				pEnv->SustainEnd   = pLInst->VolEnvSustain;
				// read envelope points
				err = Loaders_Alloc(pSong, (void**) &pEnv->pTable, pEnv->Points << 2);
				if (err) goto loader_err;
				for (j = 0, pc = pLInst->VolEnvValues; j < pEnv->Points; j++, pc += 4)
				{
					uint32_t val = pc[2] << 2;

					if (val > 256) val = 256;
					pEnv->pTable[j] = pc[0] + (pc[1] << 8) + (val << 16);
				}
			}
		}

		// Panning Envelope
		{
			Envelope* pEnv = &pInstr->PanningEnvelope;

			val = pLInst->PanEnvFlags;
			pEnv->Flags = 0;
			if (val & 1) pEnv->Flags |= Envelope_Flag_On;
			if (val & 2) pEnv->Flags |= Envelope_Flag_Sustain;
			if (val & 4) pEnv->Flags |= Envelope_Flag_Loop;
			val = pLInst->PanEnvPoints;
			if (val > 16) val = 0;
			pEnv->Points = val;
			if (pEnv->Points)
			{
				pEnv->LoopStart = pLInst->PanEnvLoopStart;
				pEnv->LoopEnd   = pLInst->PanEnvLoopEnd;
				pEnv->SustainStart = pLInst->PanEnvSustain;
				pEnv->SustainEnd   = pLInst->PanEnvSustain;
				// read envelope points
				err = Loaders_Alloc(pSong, (void**) &pEnv->pTable, pEnv->Points << 2);
				if (err) goto loader_err;
				for (j = 0, pc = pLInst->PanEnvValues; j < pEnv->Points; j++, pc += 4)
				{
					int val = pc[2];
					pEnv->pTable[j] = pc[0] + (pc[1] << 8) + (val << 16);
				}
			}
		}

		// Pitch Envelope
		{
			Envelope* pEnv = &pInstr->PitchEnvelope;

			val = pLInst->PitchEnvFlags;
			pEnv->Flags = 0;
			if (val & 1) pEnv->Flags |= Envelope_Flag_On;
			if (val & 2) pEnv->Flags |= Envelope_Flag_Sustain;
			if (val & 4) pEnv->Flags |= Envelope_Flag_Loop;
			val = pLInst->PitchEnvPoints;
			if (val > 16) val = 0;
			pEnv->Points = val;
			if (pEnv->Points)
			{
				pEnv->LoopStart = pLInst->PitchEnvLoopStart;
				pEnv->LoopEnd   = pLInst->PitchEnvLoopEnd;
				pEnv->SustainStart = pLInst->PitchEnvSustain;
				pEnv->SustainEnd   = pLInst->PitchEnvSustain;
				// read envelope points
				err = Loaders_Alloc(pSong, (void**) &pEnv->pTable, pEnv->Points << 2);
				if (err) goto loader_err;
				for (j = 0, pc = pLInst->PitchEnvValues; j < pEnv->Points; j++, pc += 4)
				{
					int val = pc[2] << 24;

					pEnv->pTable[j] = pc[1] + (pc[0] << 8) + (val >> 1);
				}
			}
		}

		// read samples info
		for (j = 0; j < pLInst->Samples; j++, pSong->Samples++, pSmp++)
		{
			err = FileLoad_Read(pSong, pLSmp, sizeof(*pLSmp));
			if (err) goto loader_err;

			// check if is really sample header, disabled to cope with some corrupted files
			if (pLSmp->TAG != TAG(IS10))
			{
				err = Loaders_Error(pSong, -4, "Expected IS10 tag");
				goto loader_err;
			}

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

			// 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
			val = pLSmp->Volume << 2;
			if (val <= 256) pSmp->DefaultVolume = val;

			pSmp->Type = 0;

			// read panning
			pSmp->Panning = pLSmp->Panning;
			if (pSmp->Panning >= 128)
				pSmp->Panning++;
/*
			// read vibrato
			pSmp->Vibrato.Speed = pLSmp->VibratoSpeed;
			pSmp->Vibrato.Depth = pLSmp->VibratoDepth;
			pSmp->Vibrato.Rate = pLSmp->VibratoRate;
			switch(pLSmp->VibratoType)
			{
				case 1: pSmp->Vibrato.Type = vibrato_type_ramp; break;
				case 2: pSmp->Vibrato.Type = vibrato_type_square; break;
				case 3: pSmp->Vibrato.Type = vibrato_type_random; break;
				default: pSmp->Vibrato.Type = vibrato_type_sin;
			}
*/
			// read sample type
			val = pLSmp->Flags;
			if (val & 0x01) pSmp->Type |= Smp_Type_Loop;
			if (val & 0x02) pSmp->Type |= Smp_Type_Loop_Bidi;
			if (val & 0x04)
			{
				pSmp->Type |= Smp_Type_16bit;
				pSmp->Size >>= 1;
				pSmp->LoopStart >>= 1;
				pSmp->LoopEnd >>= 1;
			}
			if (val & 0x08) pSmp->Type |= Smp_Type_Set_Panning;

			// read the sample data
			err = FileLoad_ReadSample(pSong, pSmp);
			if (err) goto loader_err;
		}
	}

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

	return err;
}
