#include "Song.h"
#include "Effects.h"
#include "Instrument.h"
#include "Loaders.h"
#include "FSysLog.h"
/*
 * Channels: [1...8]
 * Patterns: [1...65535] single channel patterns
 * Rows    : 64
 * Orders  : [1...65535]
 * Samples : 63
 * Instr.  : 0
 * Tempo   : [8...4095], T frames per 20 seconds.
 * Speed   : [1...31], S frames per row.
 * Notes   : [C-0...C-1...B-2]
 * Pitch   : Uses Amiga periods, 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...7].
 * Sampling: 8363 Hz.
 *
 * Notes:
 * Effects have no memory except for portamento and vibrato.
 * This means that effect value 0 as usually no effect.
 *
 * Sequence position is n channels wide and patterns are 1 channel wide.
 */

static const uint8_t Tag_BASSTRAK[] = {0x02,0x01,0x13,0x13,0x14,0x12,0x01,0x0B};
#define TAG_BASSTRAK ((uint32_t*) Tag_BASSTRAK)
static const uint8_t Typestr_DSym[] = "Digital Symphony";

typedef struct
{
	uint32_t    Tag[2];
	uint8_t     Version;
	uint8_t     Channels;
	uint16_t    Orders;
	uint16_t    Patterns;
	uint8_t     Comments[3];
} LHdr;

typedef struct
{
	uint8_t     LoopStart[3];
	uint8_t     LoopSize[3];
	uint8_t     Volume;
	uint8_t     FineTune;
} LSmp;

#define NoteDelta   (Note_Central-12*1)

typedef struct
{
	uint32_t    Allowed[2];
	uint32_t    Channel;
	uint32_t    Note;
} EffectParams;

static void convert_effect_DSym(SongHdr* pSong, const EffectParams* pParams, uint32_t effect, uint32_t value)
{
#ifdef USELOG
	uint32_t oeffect = effect;
	uint32_t ovalue = value;
#endif
	uint32_t xvalue = value >> 8;

	// Not all effects may be allowed
	if (effect < 0x20)
	{
		if (!(pParams->Allowed[0] & (1u<<effect))) return;
	}
	else
	{
		if (!(pParams->Allowed[1] & (1<<(effect-0x20)))) return;
	}

	switch(effect)
	{
		case 0x00: // Pitch: Arpeggio, No memory - Volume: Slide up, No memory
		{
			if (xvalue)
				Pattern_AddEffect_NoteVolume(pSong, cmd_up + flag_cmd_slide_frameN0, xvalue << 2);

			value &= 0xff;

			if (value)
			{
				uint32_t val;

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

				Pattern_AddEffect_Arpeggio(pSong, value, val, cmd_arpeggio_type_NHL);
			}
		}
		break;
		case 0x01: // Pitch: Slide up, No memory - Volume: Slide up, No memory
		{
			if (xvalue)
				Pattern_AddEffect_NoteVolume(pSong, cmd_up + flag_cmd_slide_frameN0, xvalue << 2);

			value &= 0xff;

			if (value)
				Pattern_AddEffect_Pitch(pSong, cmd_up+flag_cmd_slide_frameN0, value << 2);
		}
		break;
		case 0x02: // Pitch: Slide down, No memory - Volume: Slide up, No memory
		{
			if (xvalue)
				Pattern_AddEffect_NoteVolume(pSong, cmd_up + flag_cmd_slide_frameN0, xvalue << 2);

			value &= 0xff;

			if (value)
				Pattern_AddEffect_Pitch(pSong, cmd_down+flag_cmd_slide_frameN0, value << 2);
		}
		break;
		case 0x03: // Pitch: Portamento
		{
			if (xvalue)
				SysLog_BadEffectValue(pSong, 0, oeffect, xvalue << 8);

			value &= 0xff;

			Pattern_AddEffect_Pitch(pSong, cmd_portamento+flag_cmd_slide_frameN0, value << 2);
		}
		break;
		case 0x04: // Pitch: Vibrato
		{
			if (xvalue)
				SysLog_BadEffectValue(pSong, 0, oeffect, xvalue << 8);

			value &= 0xff;
			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 0x05: // Pitch: Portamento - Volume: Slide, No memory
		{
			if (xvalue)
				SysLog_BadEffectValue(pSong, 0, oeffect, xvalue << 8);

			value &= 0xff;

			convert_effect_DSym(pSong, pParams, 0x03, 0);
			convert_effect_DSym(pSong, pParams, 0x0a, value);
		}
		break;
		case 0x06: // Pitch: Vibrato - Volume: Slide, No memory
		{
			if (xvalue)
				SysLog_BadEffectValue(pSong, 0, oeffect, xvalue << 8);

			value &= 0xff;

			convert_effect_DSym(pSong, pParams, 0x04, 0);
			convert_effect_DSym(pSong, pParams, 0x0a, value);
		}
		break;
		case 0x07: // Volume: Vibrato (alias Tremolo)
		{
			if (xvalue)
				SysLog_BadEffectValue(pSong, 0, oeffect, xvalue << 8);

			value &= 0xff;
			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 0x09: // Note: Set sample offset
		{
			if (value)
			{
				// define offset
				// set offset byte 0
				Pattern_AddEffect_Instrument(pSong, cmd_sample_set_offset, 0x000 | (value << 7) & 0x80);
				// set offset byte 1
				Pattern_AddEffect_Instrument(pSong, cmd_sample_set_offset, 0x100 | (value >> 1) & 0xff);
				// set offset byte 2
				Pattern_AddEffect_Instrument(pSong, cmd_sample_set_offset, 0x200 | (value >> 9) & 0xff);
			}

			// apply only if a note is defined at the same time
			if (pParams->Note) Pattern_AddEffect_Instrument(pSong, cmd_sample_set_offset, 0xf00);
		}
		break;
		case 0x0a: // Volume: Slide (up has priority), No memory - Pitch: Up, No memory
		{
			if (xvalue)
				Pattern_AddEffect_Pitch(pSong, cmd_up+flag_cmd_slide_frame0, xvalue << 2);

			value &= 0xff;

			uint32_t cmd = flag_cmd_slide_frameN0;
			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 0x0b: // Global: Jump
		{
			if (xvalue)
				SysLog_BadEffectValue(pSong, 0, oeffect, xvalue << 8);

			value &= 0xff;

			Pattern_AddGlbEffect_Position(pSong, gcmd_pos_jump, value);
		}
		break;
		case 0x0c: // Volume: Set
		{
			if (xvalue)
				SysLog_BadEffectValue(pSong, 0, oeffect, xvalue << 8);

			value &= 0xff;
			if (value > 64)
				value = 64; // DSym doc say value is rounded, not ignore

			Pattern_AddEffect_NoteVolume(pSong, cmd_set, value << 2);
		}
		break;
		case 0x0d: // Global: Break
		{
			if (xvalue)
				SysLog_BadEffectValue(pSong, 0, oeffect, xvalue << 8);

			value &= 0xff;
			if (value >= 64) value = 0;

			Pattern_AddGlbEffect_Position(pSong, gcmd_pos_break, value);
		}
		break;
		case 0x0f: // Global: Set speed
		{
			if (value)
				Pattern_AddGlbEffect_Frames(pSong, gcmd_frames_set, value);
		}
		break;
		case 0x10: // set amiga filter
		{
			// Ignored
		}
		break;
		case 0x11: // Pitch: Up, No memory - Volume: Up, No memory
		{
			if (xvalue)
				Pattern_AddEffect_NoteVolume(pSong, cmd_up+flag_cmd_slide_frame0, xvalue << 2);

			value &= 0xff;

			if (value)
				Pattern_AddEffect_Pitch(pSong, cmd_up+flag_cmd_slide_frame0, value << 2);
		}
		break;
		case 0x12: // Pitch: Down, No memory - Volume: Up, No memory
		{
			if (xvalue)
				Pattern_AddEffect_NoteVolume(pSong, cmd_up+flag_cmd_slide_frame0, xvalue << 2);

			value &= 0xff;

			if (value)
				Pattern_AddEffect_Pitch(pSong, cmd_down+flag_cmd_slide_frame0, value << 2);
		}
		break;
		case 0x13: // Pitch: Glissando control
		{
			if (value < 2)
				Pattern_AddEffect_Pitch(pSong, cmd_pitch_glissando, value);
			else
				SysLog_BadEffectValue(pSong, 0, oeffect, ovalue);
        }
		break;
		case 0x14: // 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; break;
			}

			if (effect & 4)
				value |= vibrato_type_noretrig;

			Pattern_AddEffect_Pitch(pSong, cmd_vibrato_type, value);
		}
		break;
		case 0x15: // Note: Set fine tune in 1/8 semitone
		{
			int32_t svalue = value << 28;

			// only if a note is defined at the same time
			if (pParams->Note) Pattern_AddEffect_Instrument(pSong, cmd_sample_set_finetune, svalue >> 23);
		}
		break;
		case 0x16: // Global: Loop in column
		{
			if (value <= 0xf)
				Pattern_AddGlbEffect_Position(pSong, gcmd_pos_loop | value, pParams->Channel);
			else
				SysLog_BadEffectValue(pSong, 0, oeffect, ovalue);
		}
		break;
		case 0x17: // 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; break;
			}

			if (effect & 4)
				value |= vibrato_type_noretrig;

			Pattern_AddEffect_NoteVolume(pSong, cmd_vibrato_type, value);
		}
		break;
		case 0x19: // Note: Retrig note
		{
			Pattern_AddEffect_RetrigCounter(pSong, cmd_retrig_count_type_MOD, value);
			Pattern_AddEffect_Instrument(pSong, cmd_sample_note_retrig, 8);
		}
		break;
		case 0x1a: // Pitch: Up, No memory - Volume: Down, No memory
		{
			if (xvalue)
				Pattern_AddEffect_NoteVolume(pSong, cmd_down+flag_cmd_slide_frame0, xvalue << 2);

			value &= 0xff;

			if (value)
				Pattern_AddEffect_Pitch(pSong, cmd_up+flag_cmd_slide_frame0, value << 2);
		}
		break;
		case 0x1b: // Pitch: Down, No memory - Volume: Down, No memory
		{
			if (xvalue)
				Pattern_AddEffect_NoteVolume(pSong, cmd_down+flag_cmd_slide_frame0, xvalue << 2);

			value &= 0xff;

			if (value)
				Pattern_AddEffect_Pitch(pSong, cmd_down+flag_cmd_slide_frame0, value << 2);
		}
		break;
		case 0x1c: // Note: Cut note after x ticks
		{
			Pattern_AddEffect_NoteVolume(pSong, cmd_volume_cut, value);
		}
		break;
		case 0x1d: // Note: Delay note for x ticks
		{
			Pattern_AddEffect_Instrument(pSong, cmd_sample_note_delay, value);
		}
		break;
		case 0x1e: // Global: Delay row for x rows
		{
			Pattern_AddGlbEffect_Frames(pSong, gcmd_frames_repeat, value);
		}
		break;
		case 0x1f: // invert loop
		{
			SysLog_SkipEffect(pSong, 0, oeffect, ovalue);
		}
		break;
		case 0x20: // Pitch: Arpeggio, No memory - Volume: Slide down, No memory
		{
			if (xvalue)
				Pattern_AddEffect_NoteVolume(pSong, cmd_down + flag_cmd_slide_frameN0, xvalue << 2);

			value &= 0xff;

			if (value)
			{
				uint32_t val;

				val = value >> 4;
				value &= 0xf;
				Pattern_AddEffect_Arpeggio(pSong, value, val, cmd_arpeggio_type_NHL);
			}
		}
		break;
		case 0x21: // Pitch: Slide up, No memory - Volume: Slide down, No memory
		{
			if (xvalue)
				Pattern_AddEffect_NoteVolume(pSong, cmd_down + flag_cmd_slide_frameN0, xvalue << 2);

			value &= 0xff;

			if (value)
				Pattern_AddEffect_Pitch(pSong, cmd_up+flag_cmd_slide_frameN0, value << 2);
		}
		break;
		case 0x22: // Pitch: Slide down, No memory - Volume: Slide down, No memory
		{
			if (xvalue)
				Pattern_AddEffect_NoteVolume(pSong, cmd_down + flag_cmd_slide_frameN0, xvalue << 2);

			value &= 0xff;

			if (value)
				Pattern_AddEffect_Pitch(pSong, cmd_down+flag_cmd_slide_frameN0, value << 2);
		}
		break;
		case 0x2a: // Volume: Slide (up has priority), No memory - Pitch: down, No memory
		{
			xvalue = value >> 8;
			value &= 0xff;

			if (xvalue)
				Pattern_AddEffect_Pitch(pSong, cmd_down+flag_cmd_slide_frame0, xvalue << 2);

			uint32_t cmd = flag_cmd_slide_frameN0;
			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 0x2b: // Global: Line jump
		{
			if (xvalue)
				SysLog_BadEffectValue(pSong, 0, oeffect, xvalue << 8);

			value &= 0xff;
			if (value >= 64) value = 0;
			Pattern_AddGlbEffect_Position(pSong, gcmd_pos_linejump, value);
		}
		break;
		case 0x2f: // Global: Set tempo
		{
			if (value)
				Pattern_AddGlbEffect_Tempo(pSong, cmd_set, value);
			else
				SysLog_BadEffectValue(pSong, 0, oeffect, ovalue);
		}
		break;
		case 0x30: // Panning: Set
		{
			if (value & 7)
			{
				static const uint16_t pan[8] = {0, 0, 42, 85, 128, 171, 214, 256};
				value &= 7;
				Pattern_AddEffect_Panning(pSong, cmd_set, pan[value]);
			}
			else
			{
				value >>= 4;
				value ^= 0x80; // convert to unsigned
				if (value)
					Pattern_AddEffect_Panning(pSong, cmd_set, value);
			}
		}
		break;
		case 0x31: // upcall
		{
			Pattern_AddEffect_Instrument(pSong, cmd_upcall, value);
		}
		break;
		case 0x32: // Note: Unset Sample Repeat
		{
			Pattern_AddEffect_Instrument(pSong, cmd_sample_note_action_sustainoff, 0);
		}
		break;
		default:
		{
			// Unknown effect
			SysLog_BadEffect(pSong, 0, effect, value);
		}
	}
}

static const _kernel_oserror* convert_note_DSym(SongHdr* pSong, EffectParams* pParams, uint32_t val)
{
	const _kernel_oserror* err = NULL;
	uint32_t note, inst, effect;

	note = (val & 0x3f);
	if (note) note += NoteDelta - 1;

	inst = (val & 0x00001fc0) >> 6;
	effect = (val & 0x000fc000) >> 14;
	val = val >> 20;

	err = Loaders_StartChannel(pSong, pParams->Channel, note, inst);
	if (err) return err;

	pParams->Note = note;
	convert_effect_DSym(pSong, pParams, effect, val);

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

	return NULL;
}

typedef struct
{
	uint32_t bits;      // nr of bits required to read a code
	uint32_t shift;     // current bit shift of word preloaded from buffer
	uint32_t preloaded; // unread bits of current word preloaded from buffer
	uint32_t maxrun;    // number of small values before bit decrease
} sigma;

static const _kernel_oserror* sigma_getcode(SongHdr* pSong, sigma* s, uint32_t* pval)
{
	const _kernel_oserror* err = NULL;

	*pval = s->preloaded >> (32 - s->shift);

	if (s->shift < s->bits)
	{
		err = FileLoad_ReadInt(pSong, &s->preloaded, 4);
		if (err) return err;
		*pval += s->preloaded << s->shift;
		s->shift += 32;
	}
	s->shift -= s->bits;

	// keep only required bits
	*pval <<= (32 - s->bits);
	*pval >>= (32 - s->bits);

	return NULL;
}

static const _kernel_oserror* sigma_decode(SongHdr* pSong, uint8_t* buffer, uint32_t len)
{
	const _kernel_oserror* err = NULL;
	uint8_t* toend = buffer + len;
	sigma s;
	uint32_t val;
	uint32_t acc;
	uint32_t run;

	s.bits = 8;
	s.shift = 0;

	// ignore 4 bytes (maxrun + decoded len << 8)
	err = FileLoad_ReadByte(pSong, &s.preloaded);
	if (err) return err;
	s.maxrun = s.preloaded & 0x1f;

	err = sigma_getcode(pSong, &s, &acc);
	if (err) return err;
	*buffer++ = acc ^ 0x80;

	run = 0;

	while (buffer < toend)
	{
		err = sigma_getcode(pSong, &s, &val);
		if (err) return err;

		// increase bits marker?
		if (val == 0)
		{
			s.bits++;
			run = 0;
			continue;
		}

		if (val & 1)
			acc -= val >> 1;
		else
			acc += val >> 1;
		*buffer++ = acc ^ 0x80;

		// possible to decrease bits?
//		if (s.bits < 2)
//			continue;

		// only if we get small values for maxrun times
		if ((val << 1) >= (1 << s.bits))
		{
			run = 0;
			continue;
		}
		run++;
		if (run < s.maxrun)
			continue;

		// lower bits
		s.bits--;
		run = 0;
	}

	return NULL;
}

static const _kernel_oserror* DSym_ReadPacket(SongHdr* pSong, void* buffer, uint32_t len, uint32_t type)
{
	const _kernel_oserror* err = NULL;

	switch(type)
	{
		case 0: // normal
		case 2:
		case 3:
		{
			err = FileLoad_Read(pSong, buffer, len);
		}
		break;
		case 1: // 13-bit LZW compression
		{
			err = lzwd_decode(pSong, buffer, len, 13, 258);
		}
		break;
		case 4: // SigmaDelta compression
		case 5:
		{
			err = sigma_decode(pSong, buffer, len);
		}
		break;
		default:
		{
			err = Loaders_Error(pSong, FileLoad_GetPos(pSong), "Bad compression type");
		}
	}

	return err;
}

static const _kernel_oserror* DSym_ReadSample(SongHdr* pSong, Sample* pSmp)
{
	const _kernel_oserror* err = NULL;
	uint32_t len = pSmp->Size;
	uint32_t type;
	uint8_t* pc;
	uint8_t* pcend;
	uint8_t c;
	const uint8_t* SmpLogToLin = Table_ArcSampleLogToLin();

	err = FileLoad_ReadByte(pSong, &type);
	if (err) return err;
	if (type == 3)
	{
		pSmp->Type |= Smp_Type_16bit;
		len <<= 1;
	}

	// Allocate memory, keeping in mind word aligned usage
	err = Loaders_Alloc(pSong, (void**) &pSmp->Ptr, len + 4);
	if (err) return err;
	err = DSym_ReadPacket(pSong, pSmp->Ptr, len, type);
	if (err) return err;

	// Conversions
	switch(type)
	{
		case 0: // 8-bit log
		{
			// Convert from Log to Lin
			for (pc = pSmp->Ptr, pcend = pc + pSmp->Size; pc < pcend; pc++)
			{
				*pc = SmpLogToLin[*pc];
			}
		}
		break;
		case 1: // 8-bit signed differences
		{
			c = 0;
			for (pc = pSmp->Ptr, pcend = pc + pSmp->Size; pc < pcend; pc++)
			{
				c += *pc;
				*pc = c;
			}
		}
		break;
		case 5: // 8-bit special log
		{
			// Convert from Log to Lin
			for (pc = pSmp->Ptr, pcend = pc + pSmp->Size; pc < pcend; pc++)
			{
				c = *pc;
				if (c & 0x80)
				{
					c &= 0x7f;
					c <<= 1;
					c ^= 0xff;
				}
				else c <<= 1;
				*pc = SmpLogToLin[c];
			}
		}
		break;
	}

	return NULL;
}

const _kernel_oserror* Loader_DSym(SongHdr* pSong)
{
	const _kernel_oserror* err = NULL;
	SubSong*    pSubSong = Song_GetSubSong(pSong, 0);
	EffectParams Params;
	LHdr*       pLHdr = NULL;
	uint16_t*   pSeqBuf = NULL;
	uint32_t*   pPatBuf = NULL;
	Pattern*    pPattern;
	Sample*     pSmp;
	uint32_t    Samples;
	uint32_t    val, type, len;
	int         i, j, k;
	uint32_t*   p;

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

	// load header
	err = FileLoad_SetPos(pSong, 0);
	if (err) goto loader_err;
	err = FileLoad_Read(pSong, pLHdr, 17); // and not sizeof(LHdr) due to alignment
	if (err) goto loader_err;

	// check if really DSym Module?
	if ((pLHdr->Tag[0] != TAG_BASSTRAK[0])
	||  (pLHdr->Tag[1] != TAG_BASSTRAK[1]))
	{
		err = Loaders_NotThisType;
		goto loader_err;
	}

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

	// First read all information required for memory allocation
	pSong->Version = pLHdr->Version * 100;
	pSong->Flags = 0;
	pSong->MinPitch = NoteDelta;
	pSong->MaxPitch = NoteDelta + 12 * 3 - 1;
	pSong->TempoBase = Tempo_Base_20Hz;
	pSong->MinTempo = 8; // Should be 1 but dangerous for internal engine
	pSong->MaxTempo = 4095;
	pSubSong->Defaults.Tempo = 125*8;
	pSong->pType = Typestr_DSym;

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

	// channels panning is inverted compared to other playerss
	pSubSong->Defaults.ChDef[0].Panning = 192;
	pSubSong->Defaults.ChDef[1].Panning = 64;
	pSubSong->Defaults.ChDef[2].Panning = 64;
	pSubSong->Defaults.ChDef[3].Panning = 192;
	pSubSong->Defaults.ChDef[4].Panning = 192;
	pSubSong->Defaults.ChDef[5].Panning = 64;
	pSubSong->Defaults.ChDef[6].Panning = 64;
	pSubSong->Defaults.ChDef[7].Panning = 192;

	//------------------
	// read samples info
	for(Samples = pSong->Samples, pSmp = pSong->pSamples
		; Samples > 0; Samples--, pSmp++)
	{
		// read sample name len
		err = FileLoad_ReadByte(pSong, &val);
		if (err) goto loader_err;
		pSmp->Type = val & 0xbf; // needs to be reset later
		if (!(val & 0x80))
		{
			// read size/2
			err = FileLoad_ReadInt(pSong, &val, 3);
			if (err) goto loader_err;
			pSmp->Size = val << 1;
		}
	}

	// fill the song title
	err = FileLoad_ReadByte(pSong, &val);
	if (err) goto loader_err;
	err = FileLoad_ReadString(pSong, &pSong->pName, val, false);
	if (err) goto loader_err;

	// read effect masks
	err = FileLoad_ReadInt(pSong, &Params.Allowed[0], 4);
	if (err) goto loader_err;
	err = FileLoad_ReadInt(pSong, &Params.Allowed[1], 4);
	if (err) goto loader_err;

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

	for (i = 0; i < pSubSong->SeqLen; i++)
	{
		pSubSong->pSeqs[i] = i;
	}

	// read packing type
	err = FileLoad_ReadByte(pSong, &type);
	if (err) goto loader_err;
	// read sequence buffer
	err = Loaders_Alloc(pSong, (void**) &pSeqBuf, 2*pSubSong->SeqLen*pSong->Channels);
	if (err) goto loader_err;
	err = DSym_ReadPacket(pSong, pSeqBuf, 2*pSubSong->SeqLen*pSong->Channels, type);
	if (err) goto loader_err;
	// check sequence numbers
	for (i = 0; i < pLHdr->Orders*pLHdr->Channels; i++)
	{
		if ((pSeqBuf[i] >= pLHdr->Patterns)
		&&  (pSeqBuf[i] != 4096))
		{
			err = Loaders_Error(pSong, FileLoad_GetPos(pSong), "Bad order value");
			goto loader_err;
		}
	}

	// prepare patterns buffer
	err = Loaders_Alloc(pSong, (void**) &pPatBuf, 4*64*pLHdr->Patterns);
	if (err) goto loader_err;

	for (i = 0, p = pPatBuf; i < pLHdr->Patterns; i += 2000, p += 64*2000)
	{
		len = pLHdr->Patterns - i;
		if (len > 2000) len = 2000;
		len *= 4*64;
		// read packing type
		err = FileLoad_ReadByte(pSong, &type);
		if (err) goto loader_err;
		// read buffer
		err = DSym_ReadPacket(pSong, p, len, type);
		if (err) goto loader_err;
	}

	//---------------
	// fill the patterns

	for (i = 0, pPattern = pSong->pPatterns; i < pSong->Patterns; i++, pPattern++)
	{
		uint16_t* seq = pSeqBuf + i*pSong->Channels;

		pPattern->Rows = 64;

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

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

			for (k = 0; k < pSong->Channels; k++)
			{
				if (seq[k] != 4096)
				{
					p = pPatBuf + 64*seq[k];
					Params.Channel = k;
					err = convert_note_DSym(pSong, &Params, p[j]);
					if (err) goto loader_err;
				}
			}

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

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

	//---------------
	// fill the samples
	for (pSmp = pSong->pSamples, i = 0; i < pSong->Samples; i++, pSmp++)
	{
		// title len info
		val = pSmp->Type & 0x3f;

		if (val)
		{
			// read sample name
			err = FileLoad_ReadString(pSong, &pSmp->pName, val, false);
			if (err) goto loader_err;
		}

		if (pSmp->Type & 0x80)
			pSmp->Type = 0;
		else
		{
			pSmp->Type = 0;
			pSmp->Frequency = 8287;
			// read repeat offset/2
			err = FileLoad_ReadInt(pSong, &val, 3);
			if (err) goto loader_err;
			pSmp->SustainStart = val << 1;

			// read repeat len/2
			err = FileLoad_ReadInt(pSong, &val, 3);
			if (err) goto loader_err;
			if (val < 2)
				pSmp->SustainEnd  = pSmp->SustainStart + pSmp->Size;
			else
			{
				pSmp->SustainEnd  = pSmp->SustainStart + (val << 1);
				pSmp->Type |= Smp_Type_Sustain;
			}

			// read volume (max 64)
			err = FileLoad_ReadByte(pSong, &val); // 25
			if (err) goto loader_err;
			val = val << 2;
			if (val > 256) val = 256;
			pSmp->DefaultVolume = val;

			// read fine tune
			err = FileLoad_ReadByte(pSong, &val);
			if (err) goto loader_err;
			pSmp->FineTune = (val & 0xf) << 28;

			if (pSmp->Size > 0)
			{
				// read sample data
				err = DSym_ReadSample(pSong, pSmp);
				if (err) goto loader_err;
			}
		}
	}

	pSong->CommentsLen =  pLHdr->Comments[0]
	                   + (pLHdr->Comments[1] << 8)
	                   + (pLHdr->Comments[2] << 16);
	if (pSong->CommentsLen)
	{
		err = Loaders_Alloc(pSong, (void**) &pSong->pComments, pSong->CommentsLen + 1);
		if (err) goto loader_err;
		// read packing
		err = FileLoad_ReadByte(pSong, &type);
		if (err) goto loader_err;
		// read comments
		err = DSym_ReadPacket(pSong, pSong->pComments, pSong->CommentsLen, type);
		if (err)
		{
			SysLog_FileCorrupted(SysLog_Medium, pSong, 0, "Failed to read comments");
			pSong->CommentsLen = 0;
			err = NULL;
		}
		pSong->pComments[pSong->CommentsLen] = 0;
	}

loader_err:
	// release patterns buffer
	Loaders_Free(pSong, pPatBuf);
	Loaders_Free(pSong, pSeqBuf);
	Loaders_Free(pSong, pLHdr);

	if (err)
	{
	}

	return err;
}
