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

/*
 * Channels: [1...16]  or [1...64]
 * Patterns: [1...256] or [1...65536]
 * Rows    : [1...64]  or [1...65535]
 * Orders  : [1...256] or [1...]
 * Samples : [1...63]
 * Instr.  : 0
 * Tempo   : See below.
 * Speed   : [1...32], S frames per row.
 * Notes   : [C-0...C-1...B-3] or [C-0...C-3...B-8]
 * 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.
 *
 * Synthetic samples are not supported, Hybrid samples are treated as normal ones.
 *
 * Number of octaves:
 * File Mmd0.txt (MED V3.2) mentions F1-F4 to control octave of lower keyboard keys and a status bar
 * reporting two digits for lower and higher keyboard octave shown in the example as "12". Thus
 * this means 5 octaves in total, there is also an "E-5" note used as example in the same text.
 *
 */

#define MMD_FLAG_FILTERON   0x01
#define MMD_FLAG_JUMPINGON  0x02
#define MMD_FLAG_JUMP8TH    0x04
#define MMD_FLAG_INSTRSATT  0x08
#define MMD_FLAG_VOLHEX     0x10
#define MMD_FLAG_STSLIDE    0x20
#define MMD_FLAG_8CHANNEL   0x40
#define MMD_FLAG_SLOWHQ     0x80
#define MMD_FLAG2_BMASK     0x1F
#define MMD_FLAG2_BPM       0x20
#define MMD_FLAG2_MIX       0x80

#define NoteDelta (Note_Central-12*2)

static const uint8_t Tag_MMD[] = "MMD";
static const uint8_t Tag_MCN[] = "MCN";

static const uint8_t Typestr_MED[] = "MED";
static const uint8_t Typestr_OctaMED[] = "OctaMED";
static const uint8_t Typestr_OctaMEDStudio[] = "OctaMED Soundstudio";

#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;
	uint8_t     Size[4];
	uint8_t     OffsetSong[4];
	uint8_t     Dummy[4];
	uint8_t     OffsetPatternPtrs[4];
	uint8_t     Dummy1[4];
	uint8_t     OffsetSmpPtrs[4];
	uint8_t     Dummy2[4];
	uint8_t     OffsetExtras[4];
	uint8_t     Dummy3[15];
	uint8_t     Songs;
} LHdr;

typedef struct
{
	uint8_t     LoopStart[2];
	uint8_t     LoopLen[2];
	uint8_t     MidiCh;
	uint8_t     MidiPreset;
	uint8_t     Volume;
	int8_t      Transpose;
} LSmp;

typedef struct
{
	LSmp        Sample[63];
	uint8_t     NrPatterns[2];
	uint8_t     NrOrders[2];
	uint8_t     Orders[256];
	uint8_t     Tempo[2];
	int8_t      Transpose;
	uint8_t     Flags;
	uint8_t     Flags2;
	uint8_t     Speed;
	uint8_t     ChVolume[16];
	uint8_t     Volume;
	uint8_t     Samples;
} L0Song;

typedef struct
{
	LSmp        Sample[63];
	uint8_t     NrPatterns[2];
	uint8_t     NrSections[2];
	uint8_t     OffsetSequencePtrs[4];
	uint8_t     OffsetSections[4];
	uint8_t     OffsetChVolumes[4];
	uint8_t     Channels[2];
	uint8_t     NrSequences[2];
	uint8_t     OffsetChPannings[4];
	uint8_t     Flags3[4];
	uint8_t     VolumeAdjust[2];
	uint8_t     MixingChannels[2];
	uint8_t     EchoType;
	uint8_t     EchoDepth;
	uint8_t     EchoLen[2];
	uint8_t     StereoSep;
	uint8_t     Dummy[223];
	uint8_t     Tempo[2];
	int8_t      Transpose;
	uint8_t     Flags;
	uint8_t     Flags2;
	uint8_t     Speed;
	uint8_t     Pad[16];
	uint8_t     Volume;
	uint8_t     Samples;
} L2Song;

typedef struct
{
	uint8_t     OffsetNextLHdr[4];
	uint8_t     OffsetInstrExts[4];
	uint8_t     NrInstrExts[2];
	uint8_t     SizeInstrExt[2];
	uint8_t     OffsetAnnotation[4];
	uint8_t     SizeAnnotation[4];
	uint8_t     OffsetInstrInfos[4];
	uint8_t     NrInstrInfos[2];
	uint8_t     SizeInstrInfo[2];
	uint8_t     JumpMask[4];
	uint8_t     OffsetRGBTable[4];
	uint8_t     ChannelSplit[4];
	uint8_t     OffsetNotationInfo[4];
	uint8_t     OffsetSongName[4];
	uint8_t     SizeSongName[4];
	uint8_t     OffsetDumps[4];
	uint8_t     OffsetInfos[4];
	uint8_t     OffsetCmds[4];
	uint8_t     Dummy[12];
	uint8_t     Tag_End;
} LExtras;

typedef struct
{
	const L0Song*     pLSong;
	uint8_t     Channel;
	uint8_t	    Note;
	uint8_t	    Inst;
} ChannelInfo;

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

static int Convert_MEDTempo(const L0Song* pLSong, int value)
{
	if (pLSong->Flags & MMD_FLAG_8CHANNEL)
		if (value > 10) value = 10;
	if (value <= 10)
		return tempo_8ch[value] * 8;

	if (pLSong->Flags2 & MMD_FLAG2_BPM)
	    // BPM * LPB = Soundtracker BPM * 4 (1 beat = 4 ticks)
		value = (value * (1 + (pLSong->Flags2 & MMD_FLAG2_BMASK))) * (8 / 4);
	else
		value = (value * 715909 + 11858) /* rounding*/ / ((4 * 474326) / (8 * 10));

	if (value > 0xffff) value = 0xffff; // cf. limit of our owm Tempo Cmd_SetLarge command

	return value;
}

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

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

				val = value >> 4;
				value &= 0xf;
				Pattern_AddEffect_Arpeggio(pSong, value, val, cmd_arpeggio_type_NHL);
			}
		}
		break;
		case 1: // Pitch: Slide up, No memory
		{
			uint32_t cmd;

			if (pChInfo->pLSong->Flags & MMD_FLAG_STSLIDE)
				cmd = flag_cmd_slide_frameN0;
			else
				cmd = flag_cmd_slide_frame0N0;

			if (value) Pattern_AddEffect_Pitch(pSong, cmd_up | cmd, value << 2);
		}
		break;
		case 2: // Pitch: Slide down, No memory
		{
			uint32_t cmd;

			if (pChInfo->pLSong->Flags & MMD_FLAG_STSLIDE)
				cmd = flag_cmd_slide_frameN0;
			else
				cmd = flag_cmd_slide_frame0N0;

			if (value) Pattern_AddEffect_Pitch(pSong, cmd_down | cmd, value << 2);
		}
		break;
		case 3: // Pitch: Portamento
		{
			uint32_t cmd;

			if (pChInfo->pLSong->Flags & MMD_FLAG_STSLIDE)
				cmd = flag_cmd_slide_frameN0;
			else
				cmd = flag_cmd_slide_frame0N0;

			Pattern_AddEffect_Pitch(pSong, cmd_portamento | cmd, value << 2);
		}
		break;
		case 4: // Pitch: Vibrato
		{
			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 5: // Pitch: Portamento 0 - Volume: Slide, No memory
		        // but in MMD0, some kind of vibrato
		{
			if (pSong->Version > 0)
			{
				convert_effect(pSong, pChInfo, 0x3, 0);
				convert_effect(pSong, pChInfo, 0xa, value);
			}
			else
			{
				// Old Vibrato, plays pitch, pitch + value
				SysLog_SkipEffect(pSong, 0, oeffect, ovalue);
			}
		}
		break;
		case 6: // Pitch: Vibrato 0 - Volume: Slide, No memory
		{
			convert_effect(pSong, pChInfo, 0x4, 0);
			convert_effect(pSong, pChInfo, 0xa, value);
		}
		break;
		case 7: // Volume: Vibrato (alias Tremolo)
		{
			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 8: // Hold, Synth instrument
		{
			SysLog_SkipEffect(pSong, 0, oeffect, ovalue);
		}
		break;
		case 9: // Speed
		{
			if ((value >= 1) && (value <= pSong->MaxSpeed))
				Pattern_AddGlbEffect_Frames(pSong, gcmd_frames_set, value);
		}
		break;
		case 0xb: // Global: Jump
		{
			Pattern_AddGlbEffect_Position(pSong, gcmd_pos_jump, value);
		}
		break;
		case 0xc: // Volume: Set, invalid values are ignored
		          //         if >= 0x80 alter's the sample default volume (not implemented)
		{
			if (!(pChInfo->pLSong->Flags & 0x10))
			{
				// 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 0xa:
		case 0xd:
		{
			uint32_t cmd = flag_cmd_slide_frame0N0;
			uint32_t down = value & 0xf;
			uint32_t up = value >> 4;

			if (pChInfo->pLSong->Flags & MMD_FLAG_STSLIDE)
				cmd = flag_cmd_slide_frameN0;

			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: // Jump Line, Synth Instrument
		{
			SysLog_SkipEffect(pSong, 0, oeffect, ovalue);
		}
		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, gcmd_tempo_setlarge, Convert_MEDTempo(pChInfo->pLSong, value) >> 4);
			}
			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 0xf4:
					{
						// delay by 1/3 row
						Pattern_AddEffect_Instrument(pSong, cmd_sample_note_delay, 2);
					}
					break;
					case 0xf5:
					{
						// delay by 2/3 row
						Pattern_AddEffect_Instrument(pSong, cmd_sample_note_delay, 4);
					}
					break;
					case 0xfd:
					{
						// set pitch to last (current) note value
						Pattern_AddEffect_Pitch(pSong, cmd_pitch_semitoneset, pChInfo->Note);
					}
					break;
					case 0xff:
					{
						// Stop playing note
						Pattern_AddEffect_Instrument(pSong, cmd_sample_note_action_cut, 0);
					}
					break;
					case 0xf7: // midi
					case 0xf8: // lowpass filter off
					case 0xf9: // lowpass filter on
					case 0xfa: // midi
					case 0xfb: // midi
					case 0xfe: // stop song
					break;
					default:
					{
						SysLog_BadEffect(pSong, 0, oeffect, ovalue);
					}
				}
			}
		}
		break;
		case 0x11: // Pitch: Up, No memory
		{
			if (value) Pattern_AddEffect_Pitch(pSong, cmd_up+flag_cmd_slide_frame0, value << 2);
		}
		break;
		case 0x12: // Pitch: Down, No memory
		{
			if (value) Pattern_AddEffect_Pitch(pSong, cmd_down+flag_cmd_slide_frame0, value << 2);
		}
		break;
		case 0x14: // Pitch: Vibrato, twice finer
		{
			uint32_t speed = value >> 4;
			value &= 0xf;
			// Vibrate between [-2*y, 2*y]
			value <<= 2; // << 1 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 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 (pChInfo->Note) Pattern_AddEffect_Instrument(pSong, cmd_sample_set_finetune, svalue >> 23);
		}
		break;
		case 0x16: // Global: Loop in column
		{
			if (value > 0xf) value = 0xf;
			Pattern_AddGlbEffect_Position(pSong, gcmd_pos_loop | value, pChInfo->Channel);
		}
		break;
		case 0x18: // Note: Cut note after x ticks
		{
			Pattern_AddEffect_NoteVolume(pSong, cmd_volume_cut, value);
		}
		break;
		case 0x19: // Note: Set sample offset
		{
			if (value)
			{
				// define offset
				// set offset byte 0
				Pattern_AddEffect_Instrument(pSong, cmd_sample_set_offset, 0x000);
				// 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 (pChInfo->Note) Pattern_AddEffect_Instrument(pSong, cmd_sample_set_offset, 0xf00);
		}
		break;
		case 0x1a: // Volume: Up, No memory
		{
			if (value) Pattern_AddEffect_NoteVolume(pSong, cmd_up+flag_cmd_slide_frame0, value << 2);
		}
		break;
		case 0x1b: // Volume: Down, No memory
		{
			if (value) Pattern_AddEffect_NoteVolume(pSong, cmd_down+flag_cmd_slide_frame0, value << 2);
		}
		break;
		case 0x1d: // Global: Break
		{
			Pattern_AddGlbEffect_Position(pSong, gcmd_pos_break, value);
		}
		break;
		case 0x1e: // Global: Delay row for x rows
		{
			Pattern_AddGlbEffect_Frames(pSong, gcmd_frames_repeat, value);
		}
		break;
		case 0x1f: // Note: Delay for x ticks, retrig y
		{
			if (value >> 4) Pattern_AddEffect_Instrument(pSong, cmd_sample_note_delay, value >> 4);
			if (value & 0xf)
			{
				Pattern_AddEffect_RetrigCounter(pSong, cmd_retrig_count_type_MOD, value);
				Pattern_AddEffect_Instrument(pSong, cmd_sample_note_retrig, 8);
			}
		}
		break;
		case 0x20: // Sample: play backward or relative offset sample (signed)
		{
			if (!value)
			{
				// only if a note is defined at the same time
				if (pChInfo->Note) Pattern_AddEffect_Instrument(pSong, cmd_sample_reverse_play, 0);
				else SysLog_SkipEffect(pSong, 0, oeffect, ovalue);
			}
			else SysLog_SkipEffect(pSong, 0, oeffect, ovalue);
		}
		break;
		case 0x21: // Pitch: slide up frequency by (freq * x) >> 11
		case 0x22: // Pitch: slide down frequency by (freq * x) >> 11
		case 0x23: // Filter: sweep
		case 0x24: // Filter: cutoff
		case 0x25: // Filter: type
		case 0x29: // Sample: play section x of sample divided in y sections
		{
			SysLog_SkipEffect(pSong, 0, oeffect, ovalue);
		}
		break;
		case 0x2E: // Panning: set
		{
			if (value <= 0x10)
				Pattern_AddEffect_Panning(pSong, cmd_set, 128 + (value << 3));
			else if (value >= 0xf0)
				Pattern_AddEffect_Panning(pSong, cmd_set, 128 - ((256 - value) << 3));
			else
			{
				SysLog_BadEffectValue(pSong, 0, oeffect, ovalue);
			}
		}
		break;
		case 0x10: // Midi
		case 0x13: // Midi
		case 0x17: // Midi
		case 0x1c: // Midi
		case 0x2d: // ARexx commands
		case 0x2f: // Set stereo seperation or echo depth
		case 0x31: // Midi
		case 0x32: // Midi
		case 0x33: // Midi
		case 0x34: // Midi
		case 0x35: // Midi
		case 0x36: // Midi
		case 0x37: // Midi
		case 0x38: // Midi
		case 0x39: // Midi
		case 0x3a: // Midi
		case 0x3b: // Midi
		case 0x3c: // Midi
		case 0x3d: // Midi
		case 0x3e: // Midi
		case 0x3f: // Midi
			;
		break;
		default:
		{
			// Unknown effect
			SysLog_BadEffect(pSong, 0, oeffect, ovalue);
		}
		break;
	}
}

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

	Ch.pLSong = pLSong;

	err = Loaders_StartPattern(pSong, pPattern - pSong->pPatterns);
	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)
			{
				if (Ch.Note <= (pSong->MaxPitch + 1 - pSong->MinPitch))
				{
					Ch.Note += pSong->MinPitch - 1;
				}
				else
				{
					SysLog_BadNote(pSong, -1, Ch.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);
			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_MMD1
	( SongHdr* pSong
	, const L0Song* pLSong
	, 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;

	Ch.pLSong = pLSong;
	step2 = 2 * channels * pPattern->Rows;

	err = Loaders_StartPattern(pSong, pPattern - pSong->pPatterns);
	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 (pSong->MinPitch == Note_Central - 12 * 1)
				Ch.Note &= 0x3f;
			else
				Ch.Note &= 0x7f;

			if (Ch.Note)
			{
				if (Ch.Note <= (pSong->MaxPitch + 1 - pSong->MinPitch))
				{
					Ch.Note += pSong->MinPitch - 1;
				}
				else
				{
					SysLog_BadNote(pSong, -1, Ch.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_MMD(SongHdr* pSong)
{
	const _kernel_oserror* err = NULL;
    LHdr*       pLHdr = NULL;
    LExtras*    pLExtras = NULL;
    L0Song*     pLSong = NULL;
	Pattern*    pPattern;
	Sample*     pSmp;
	uint32_t    val;
	int         i, j;
	uint8_t*    pLBytes = NULL;
	uint8_t*    pLBytes2 = NULL;
	uint32_t*   pLPtrs = NULL;
	uint32_t    FileOffset, ExtraOffset, SmpsOffset = 0;
	ChannelDefault* pDef;
	uint32_t    subsongs;

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

//	if (pLHdr->Vers == 'C') pLHdr->Vers = '0'; // MMD0 with compressed info
	if ((pLHdr->Vers < '0') || (pLHdr->Vers > '3'))
	{
		err = Loaders_Error(pSong, 3, "Unsupported OctaMED format");
		goto loader_err;
	}

	if (pLHdr->Songs > 0)
	{
		SysLog_FileCorrupted(SysLog_High, pSong, 50, "Multiple Songs (%d)", pLHdr->Songs + 1);
	}
	subsongs = pLHdr->Songs + 1;

	pSong->Version = pLHdr->Vers - '0'; // Interval for now
	pSong->pType = Typestr_OctaMED;
	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

	while (subsongs > 0)
	{
		SubSong*    pBaseSubSong = Song_GetSubSong(pSong, 0);
		SubSong*    pSubSong;
		uint32_t    patterns;

		subsongs--;

		// Read song
		FileOffset = bigendian4(pLHdr->OffsetSong);
		err = FileLoad_SetPos(pSong, FileOffset);
		if (!err) err = FileLoad_Read(pSong, pLSong, 788);
		if (err) goto loader_err;

		// Read optional extras
		ExtraOffset = bigendian4(pLHdr->OffsetExtras);

		Loaders_Free(pSong, pLExtras);
		pLExtras = NULL;
		if (ExtraOffset)
		{
			err = Loaders_Alloc(pSong, (void**) &pLExtras, sizeof(*pLExtras));
			if (!err) err = FileLoad_SetPos(pSong, ExtraOffset);
			if (!err) err = FileLoad_Read(pSong, pLExtras, sizeof(*pLExtras));
			if (err) goto loader_err;
		}
		else subsongs = 0; // If no extra, no offset to next song

		// Common part
		if (!pBaseSubSong->SeqLen)
		{
			pSubSong = pBaseSubSong;
			SmpsOffset = bigendian4(pLHdr->OffsetSmpPtrs);
			pSong->Samples = pLSong->Samples;
			if (!pSong->Samples || (pLSong->Samples > 63))
			{
				err = Loaders_InvalidSong(pSong, FileOffset + 25, &pSong->Samples, pSong->Samples);
				goto loader_err;
			}

			// Transposition and limits
			if (pLHdr->Vers <= '2')
			{
				pSong->MinPitch = Note_Central - 12 * 1;
				pSong->MaxPitch = Note_Central + 12 * 4 - 1;
			}
			else
			{
				pSong->MinPitch = Note_Central - 12 * 3;
				pSong->MaxPitch = Note_Central + 12 * 6 - 1;
			}
			pSong->MinPitch += pLSong->Transpose;
			pSong->MaxPitch += pLSong->Transpose;

			for (i = 0, pSmp = pSong->pSamples; i < 63; i++, pSmp++)
			{
				const LSmp* pLSmp = &pLSong->Sample[i];

				pSmp->Type = 0;
				pSmp->LoopStart = bigendian2(pLSmp->LoopStart);
				pSmp->LoopEnd = bigendian2(pLSmp->LoopLen);
				pSmp->LoopStart <<= 1;
				pSmp->LoopEnd <<= 1;
				if (pSmp->LoopEnd > 2)
				{
					pSmp->LoopEnd += pSmp->LoopStart;
					pSmp->Type |= Smp_Type_Loop;
				}
				pSmp->DefaultVolume = pLSmp->Volume << 2;
				if (pSmp->DefaultVolume > 256)
					pSmp->DefaultVolume = 256;
				pSmp->RelTone = pLSmp->Transpose << 24;
				pSmp->RelTone >>= 16; // Signed
			}

			// Version dependant part
			if (pLHdr->Vers < '2')
			{
				pSong->Channels = 4;
			}
			else
			{
				const L2Song* pLSong2 = (const L2Song*) pLSong;

				pSong->Channels = bigendian2(pLSong2->Channels);
				if (!pSong->Channels || (pSong->Channels > 64))
				{
					err = Loaders_InvalidSong(pSong, FileOffset + 520, &pSong->Channels, pSong->Channels);
					goto loader_err;
				}
			}

			if (pLSong->Flags & MMD_FLAG_8CHANNEL)
				pSong->Channels = 4;

			if (pLExtras)
			{
				// Channel splitting?
				if (pLSong->Flags & MMD_FLAG_8CHANNEL)
				{
					if (!pLExtras->ChannelSplit[0]) pSong->Channels++;
					if (!pLExtras->ChannelSplit[1]) pSong->Channels++;
					if (!pLExtras->ChannelSplit[2]) pSong->Channels++;
					if (!pLExtras->ChannelSplit[3]) pSong->Channels++;
				}
			}
		}
		else
		{
			// We assume same instruments definitions between songs
			// We assume same transpose value

			// Check channels just in case
			if (pLHdr->Vers < '2')
				val = 4;
			else
			{
				const L2Song* pLSong2 = (const L2Song*) pLSong;

				val = bigendian2(pLSong2->Channels);
			}

			if (pLSong->Flags & MMD_FLAG_8CHANNEL)
				val = 4;

			if (pLExtras)
			{
				// Channel splitting?
				if (pLSong->Flags & MMD_FLAG_8CHANNEL)
				{
					if (!pLExtras->ChannelSplit[0]) val++;
					if (!pLExtras->ChannelSplit[1]) val++;
					if (!pLExtras->ChannelSplit[2]) val++;
					if (!pLExtras->ChannelSplit[3]) val++;
				}
			}

			if (pSong->Channels != val)
			{
				SysLog_Log(SysLog_Medium, "Skip subsong with differing number of channels");
				subsongs = 0;
				goto next_song;
			}

			// Allocate new subsong
			err = Song_CopySubSong(pSong, &pSubSong, pBaseSubSong);
			if (err) goto loader_err;
		}

		// 8 channels mode
		if (pLSong->Flags & MMD_FLAG_8CHANNEL)
		{
			pLSong->Flags2 &= ~MMD_FLAG2_BMASK;
			pLSong->Flags2 |= MMD_FLAG2_BPM | 0x3; // BPM on, LPB 4
		}

		pSubSong->Defaults.Speed = pLSong->Speed;
		pSubSong->Defaults.Tempo = Convert_MEDTempo(pLSong, bigendian2(pLSong->Tempo));

		// Version dependant part
		if (pLHdr->Vers < '2')
		{
			pSubSong->SeqLen = bigendian2(pLSong->NrOrders);
			if (!pSubSong->SeqLen || (pSubSong->SeqLen > 256))
			{
				err = Loaders_InvalidSong(pSong, FileOffset + 506, &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++)
				pSubSong->pSeqs[i] = pLSong->Orders[i] + pSong->Patterns;

			patterns = bigendian2(pLSong->NrPatterns);
			pSong->Patterns += patterns;
			if (!pSong->Patterns || (pSong->Patterns > 256))
			{
				err = Loaders_InvalidSong(pSong, FileOffset + 504, &pSong->Patterns, pSong->Patterns);
				goto loader_err;
			}


			for (i = 0, pDef = pSubSong->Defaults.ChDef; i < 16; i++, pDef++)
			{
				pDef->Volume = pLSong->ChVolume[i] << 2;
				if (pDef->Volume > 256)
					pDef->Volume = 256;
			}
		}
		else
		{
			const L2Song* pLSong2 = (const L2Song*) pLSong;
			uint32_t sections, orders;

			sections = bigendian2(pLSong2->NrSequences);
			if (!sections)
			{
				err = Loaders_Error(pSong, FileOffset + 522, "No Sequence (%d)", val);
				goto loader_err;
			}

			val = bigendian2(pLSong2->NrSections);
			if (val != sections)
			{
				err = Loaders_Error(pSong, FileOffset + 506, "Not the same nr of sections (%d) and sequences (%d)", val, sections);
				goto loader_err;
			}

			// Read table sequence pointers
			FileOffset = bigendian4(pLSong2->OffsetSequencePtrs);
			err = FileLoad_SetPos(pSong, FileOffset);
			if (!err) err = Loaders_Alloc(pSong, (void**) &pLPtrs, sizeof(*pLPtrs) * sections);
			if (err) goto loader_err;

			for (i = 0; i < sections; i++)
			{
				err = FileLoad_ReadReverseInt(pSong, &pLPtrs[i], 4);
				if (err) goto loader_err;
			}

			// Determine total length of sequences
			for (i = 0; i < sections; i++)
			{
				// Move to sequence, skip sequence header except sequence length
				err = FileLoad_SetPos(pSong, pLPtrs[i] + 40);
				if (!err) err = FileLoad_ReadReverseInt(pSong, &orders, 2);
				if (err) goto loader_err;

				if (!orders)
				{
					err = Loaders_InvalidSong(pSong, pLPtrs[i] + 40, &pSubSong->SeqLen, orders);
					goto loader_err;
				}

				pSubSong->SeqLen += orders;
			}
			pSubSong->SeqLen += sections - 1; // sections seperators

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

			// Read sequences
			for (i = 0, j = 0; i < sections; i++)
			{
				if (i) pSubSong->pSeqs[j++] = -1; // Section seperator

				// Move to sequence, skip sequence header except sequence length
				err = FileLoad_SetPos(pSong, pLPtrs[i] + 40);
				if (!err) err = FileLoad_ReadReverseInt(pSong, &orders, 2);
				if (err) goto loader_err;

				for (; orders > 0; orders--, j++)
				{
					err = FileLoad_ReadReverseInt(pSong, &val, 2);
					if (err) goto loader_err;
					pSubSong->pSeqs[j] = (val >= 0x8000) ? -2 : val + pSong->Patterns; // Skip large values
				}
			}

			Loaders_Free(pSong, pLPtrs);
			pLPtrs = NULL;

			patterns = bigendian2(pLSong2->NrPatterns);
			pSong->Patterns += patterns;
			if (!pSong->Patterns || (pSong->Patterns > song_max_patterns))
			{
				err = Loaders_InvalidSong(pSong, FileOffset + 504, &pSong->Patterns, pSong->Patterns);
				goto loader_err;
			}

			err = Loaders_Alloc(pSong, (void**) &pLBytes, 2*pSong->Channels);
			if (err) goto loader_err;

			// Read channel volumes
			FileOffset = bigendian4(pLSong2->OffsetChVolumes);

			if (FileOffset)
			{
				err = FileLoad_SetPos(pSong, FileOffset);
				if (err) goto loader_err;
				err = FileLoad_Read(pSong, pLBytes, pSong->Channels);
				if (err) goto loader_err;
			}
			else
			{
				for (i = 0; i < pSong->Channels; i++)
					pLBytes[i] = 64;
			}

			// Read channel panning
			FileOffset = bigendian4(pLSong2->OffsetChPannings);
			if (FileOffset)
			{
				err = FileLoad_SetPos(pSong, FileOffset);
				if (err) goto loader_err;
				err = FileLoad_Read(pSong, pLBytes + pSong->Channels, pSong->Channels);
				if (err) goto loader_err;
			}
			else
			{
				for (i = 0; i < pSong->Channels; i++)
					pLBytes[pSong->Channels + i] = 0;
			}

			for (i = 0, pDef = pSubSong->Defaults.ChDef; i < pSong->Channels; i++, pDef++)
			{
				pDef->Volume = pLBytes[i] << 2;
				if (pDef->Volume > 256)
					pDef->Volume = 256;
				val = pLBytes[pSong->Channels + i];
				if (val <= 0x10)
					pDef->Panning = 128 + (val << 3);
				else if (val >= 0xf0)
					pDef->Panning = 128 - ((256 - val) << 3);
				else
					pDef->Panning = 128;
			}

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

		// Read extras
		if (pLSong->Flags & MMD_FLAG_8CHANNEL)
			pSong->Channels = 4;

		if (pLExtras)
		{
			uint32_t count, size;

			// Channel splitting?
			if (pLSong->Flags & MMD_FLAG_8CHANNEL)
			{
				if (!pLExtras->ChannelSplit[0]) pSong->Channels++;
				if (!pLExtras->ChannelSplit[1]) pSong->Channels++;
				if (!pLExtras->ChannelSplit[2]) pSong->Channels++;
				if (!pLExtras->ChannelSplit[3]) pSong->Channels++;
			}

			// Read InstrExts
			FileOffset = bigendian4(pLExtras->OffsetInstrExts);

			if (FileOffset && (pSubSong == pBaseSubSong))
			{
				count = bigendian2(pLExtras->NrInstrExts);
				size = bigendian2(pLExtras->SizeInstrExt);

				if (count > pSong->Samples)
				{
					err = Loaders_Error(pSong, bigendian4(pLHdr->OffsetExtras) + 4, "Invalid InstrExt setup");
					goto loader_err;
				}

if (size > 18) SysLog_Log(126, "Instrument Extension: %d bytes", size);

				for (i = 0, pSmp = pSong->pSamples; i < count; i++, pSmp++)
				{
					err = FileLoad_SetPos(pSong, FileOffset);
					if (err) goto loader_err;
					if (size >= 4)
					{
						err = FileLoad_Skip(pSong, 3); // Hold, Decay, Midi suppress
						if (!err) err = FileLoad_ReadByte(pSong, &val); // FineTune
						if (err) goto loader_err;
						pSmp->FineTune = (val & 0xf) << 28;
					}
					if (size >= 5)
					{
						err = FileLoad_ReadByte(pSong, &val); // Default note nr
						if (err) goto loader_err;
						if (val && (val <= 6*12))
						{
							pSmp->RelTone = (val + pSong->MinPitch - 1 - Note_Central) << 24;
							pSmp->RelTone >>= 16; // Signed
SysLog_Log(126, "Reltone Smp %d: %d", i, pSmp->RelTone >> 8);
	                    }
					}
					if (size >= 6)
					{
						err = FileLoad_ReadByte(pSong, &val); // Flags
						if (err) goto loader_err;

						if (val & 0x01)
							pSmp->Type |= Smp_Type_Loop;
						else if (pSmp->Type & Smp_Type_Loop)
							SysLog_FileCorrupted(SysLog_Low, pSong, FileOffset + 5, "Sample %d has loop but no loop flag", i + 1);
						if (val & 0x08)
							pSmp->Type |= Smp_Type_Loop_Bidi;
					}
					if (size >= 18)
					{
						err = FileLoad_Skip(pSong, 4);
						if (err) goto loader_err;
						err = FileLoad_ReadReverseInt(pSong, &val, 4);
						if (err) goto loader_err;
						pSmp->LoopStart = val;
						err = FileLoad_ReadReverseInt(pSong, &val, 4);
						if (err) goto loader_err;
						pSmp->LoopEnd = pSmp->LoopStart + val;
					}
					if (size >= 19)
					{
						err = FileLoad_ReadByte(pSong, &val);
						pSmp->DefaultVolume = val <<= 1;
						if (pSmp->DefaultVolume > 256) pSmp->DefaultVolume = 256;
					}

					FileOffset += size;
				}
			}

			// Read comments
			FileOffset = bigendian4(pLExtras->OffsetAnnotation);
			size = bigendian4(pLExtras->SizeAnnotation);
			if (FileOffset && size && !pSong->CommentsLen)
			{
				err = FileLoad_SetPos(pSong, FileOffset);
				if (!err) err = FileLoad_ReadString(pSong, &pSong->pComments, size, false);
				pSong->CommentsLen = size;
			}

			// Read InstrInfos
			FileOffset = bigendian4(pLExtras->OffsetInstrInfos);

			if (FileOffset && (pSubSong == pBaseSubSong))
			{
				count = bigendian2(pLExtras->NrInstrInfos);
				size = bigendian2(pLExtras->SizeInstrInfo);

				if ((count > pSong->Samples)
				||  (size < 40))
				{
					err = Loaders_Error(pSong, bigendian4(pLHdr->OffsetExtras) + 22, "Invalid InstrInfo setup");
					goto loader_err;
				}

				for (i = 0, pSmp = pSong->pSamples; i < count; i++, pSmp++)
				{
					err = FileLoad_SetPos(pSong, FileOffset);
					if (!err) err = FileLoad_ReadString(pSong, &pSmp->pName, 40, false);

					FileOffset += size;
				}
			}

			// Read song name
			FileOffset = bigendian4(pLExtras->OffsetSongName);
			size = bigendian4(pLExtras->SizeSongName);
			if (FileOffset && size && !pSong->pName)
			{
				err = FileLoad_SetPos(pSong, FileOffset);
				if (!err) err = FileLoad_ReadString(pSong, &pSong->pName, size, false);
			}
		}

		// Read patterns
		FileOffset = bigendian4(pLHdr->OffsetPatternPtrs);

		if (!FileOffset)
		{
			err = Loaders_Error(pSong, 24, "Patterns are not included in file");
			goto loader_err;
		}

		err = FileLoad_SetPos(pSong, FileOffset);
		if (err) goto loader_err;

		err = Loaders_Alloc(pSong, (void**) &pLPtrs, sizeof(*pLPtrs) * patterns);
		for (i = 0; i < patterns; i++)
		{
			err = FileLoad_ReadReverseInt(pSong, &pLPtrs[i], 4);
			if (err) goto loader_err;
		}

		for (i = 0, pPattern = pSong->pPatterns + (pSong->Patterns - patterns); i < patterns; i++, pPattern++)
		{
			uint32_t size;

			err = FileLoad_SetPos(pSong, pLPtrs[i]);
			if (err) goto loader_err;

			if (pLHdr->Vers == '0')
			{
				err = FileLoad_ReadByte(pSong, &val);
				if (!err) err = FileLoad_ReadByte(pSong, &pPattern->Rows);
				if (err) goto loader_err;

				if (!val || (val > 64))
				{
					err = Loaders_Error(pSong, -2, "Invalid Nr of channels (%d) in pattern %d", val, i);
					goto loader_err;
				}
				pPattern->Rows++;

				size = 3*val*pPattern->Rows;
				err = Loaders_Alloc(pSong, (void**) &pLBytes, size);
				if (err) goto loader_err;
				err = FileLoad_Read(pSong, pLBytes, size);
				if (err) goto loader_err;
				err = Pattern_MMD0(pSong, pLSong, pPattern, val, pLBytes);
				if (err) goto loader_err;
			}
			else
			{
				uint32_t blockinfo;
				uint32_t pagetable;
				uint32_t nrChannels;
				uint32_t nrExtraCmds = 0;

				err = FileLoad_ReadReverseInt(pSong, &nrChannels, 2);
				if (!err) err = FileLoad_ReadReverseInt(pSong, &pPattern->Rows, 2);
				if (!err) err = FileLoad_ReadReverseInt(pSong, &blockinfo, 4);
				if (err) goto loader_err;

				if (!nrChannels || (nrChannels > 64))
				{
					err = Loaders_Error(pSong, -8, "Invalid Nr of channels (%d) in pattern %d", nrChannels, i);
					goto loader_err;
				}
				pPattern->Rows++;
				if (pPattern->Rows > song_max_rows)
				{
					err = Loaders_InvalidSong(pSong, -6, &pPattern->Rows, pPattern->Rows);
					goto loader_err;
				}

				size = 4*nrChannels*pPattern->Rows;
				err = Loaders_Alloc(pSong, (void**) &pLBytes, size);
				if (err) goto loader_err;
				err = FileLoad_Read(pSong, pLBytes, size);
				if (err) goto loader_err;

				if (blockinfo)
				{
					err = FileLoad_SetPos(pSong, blockinfo);
					if (!err) err = FileLoad_Skip(pSong, 12);
					if (!err) err = FileLoad_ReadReverseInt(pSong, &pagetable, 4);
					if (err) goto loader_err;

					if (pagetable)
					{
						err = FileLoad_SetPos(pSong, pagetable);
						if (!err) err = FileLoad_ReadReverseInt(pSong, &nrExtraCmds, 2);
						if (!err) err = FileLoad_ReadReverseInt(pSong, &val, 2);
						if (err) goto loader_err;
						if (size)
						{
//							SysLog_Log(126, "Pattern %d Extra commands (%d x %d ch x %d rows) at %x -> %x"
//								, i, nrExtraCmds, nrChannels, pPattern->Rows, blockinfo, pagetable);

							size = 2*nrChannels*pPattern->Rows;
							err = Loaders_Alloc(pSong, (void**) &pLBytes2, nrExtraCmds*size);

							for (j = 0; j < nrExtraCmds; j++)
							{
								err = FileLoad_SetPos(pSong, pagetable + 4 + 4*j);
								if (!err) err = FileLoad_ReadReverseInt(pSong, &val, 4);
								if (!err) err = FileLoad_SetPos(pSong, val);
								if (!err) err = FileLoad_Read(pSong, pLBytes2 + j * size, size);
								if (err) goto loader_err;
							}
						}
					}
				}

				err = Pattern_MMD1(pSong, pLSong, pPattern, nrChannels, pLBytes, pLBytes2, nrExtraCmds);
				if (err) goto loader_err;
			}

			Loaders_Free(pSong, pLBytes);
			pLBytes = NULL;
			Loaders_Free(pSong, pLBytes2);
			pLBytes2 = NULL;
		}

next_song:
		Loaders_Free(pSong, pLPtrs);
		pLPtrs = NULL;
		Loaders_Free(pSong, pLBytes);
		pLBytes = NULL;
		Loaders_Free(pSong, pLBytes2);
		pLBytes2 = NULL;

		// Read next song hdr
		if (!subsongs || !pLExtras)
			break;

		FileOffset = bigendian4(pLExtras->OffsetNextLHdr);
		if (!FileOffset)
			break;

		err = FileLoad_SetPos(pSong, FileOffset);
		if (!err) err = FileLoad_Read(pSong, pLHdr, 52);
		if (err) goto loader_err;

		if (Loaders_strncmp(&pLHdr->Type[0], Tag_MCN, sizeof(pLHdr->Type)))
		{
			SysLog_Log(SysLog_Medium, "Sub-song header not found, skipping");
			break;
		}
	}

	Loaders_Free(pSong, pLPtrs);
	pLPtrs = NULL;

	// Read samples data
	if (!SmpsOffset)
	{
		err = Loaders_Error(pSong, 24, "Samples are not included in file");
		goto loader_err;
	}

	err = FileLoad_SetPos(pSong, SmpsOffset);
	if (err) goto loader_err;

	err = Loaders_Alloc(pSong, (void**) &pLPtrs, sizeof(*pLPtrs) * pSong->Samples);
	for (i = 0; i < pSong->Samples; i++)
	{
		err = FileLoad_ReadReverseInt(pSong, &pLPtrs[i], 4);
		if (err) goto loader_err;
	}

	for(i = 0, pSmp = pSong->pSamples; i < pSong->Samples; i++, pSmp++)
	{
		if (!pLPtrs[i]) continue; // Not stored

		err = FileLoad_SetPos(pSong, pLPtrs[i]);
		if (!err) 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, 272);
			if (!err) err = FileLoad_ReadReverseInt(pSong, &val, 4);
			if (err) goto smp_err;
			err = FileLoad_SetPos(pSong, pLPtrs[i] + val);
			if (!err) err = FileLoad_ReadReverseInt(pSong, &pSmp->Size, 4);
			if (!err) err = FileLoad_ReadReverseInt(pSong, &val, 2);
			if (err) goto smp_err;
		}

		if (!(val & 0x8000))
		{
			if (val & 0x0f)
			{
				SysLog_FileCorrupted(SysLog_Low, pSong, -2, "%x Octave sample %d", val & 0xf, i + 1);
			}
			if (val & 0x10)
			{
				pSmp->Type |= Smp_Type_16bit | Smp_Type_BigEndian;
				pSmp->Size >>= 1;
			}
			if (val & 0x20)
			{
				// Not interleaved, so we just read 1 channel
				SysLog_FileCorrupted(SysLog_Low, pSong, -2, "Stereo sample %d", i + 1);
			}
			if (val & 0x40)
			{
				pSmp->Type |= Smp_Type_Delta;
				SysLog_FileCorrupted(SysLog_High, pSong, -2, "Delta sample %d", i + 1);
			}

			if (val & 0x80)
			{
				SysLog_FileCorrupted(SysLog_High, pSong, -2, "Packed sample %d", i + 1);
				Loaders_String(pSong, &pSmp->pName, (uint8_t*) "[Packed sample]", 18, false);
				pSmp->Size = 0;
			}
			else
			{
				err = FileLoad_ReadSample(pSong, pSmp);
				if (err) goto loader_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
		{
			SysLog_FileCorrupted(SysLog_High, pSong, -2, "Unknown sample type %d", i + 1);
			Loaders_String(pSong, &pSmp->pName, (uint8_t*) "[Unknown sample]", 18, false);
			pSmp->Size = 0;
		}

		{
			const LSmp* pLSmp = &pLSong->Sample[i];

			if (pLSmp->MidiCh)
				Loaders_String(pSong, &pSmp->pName, (uint8_t*) "[Midi sample]", 18, false);
		}
	}

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 == '0')
	{
		if (pSong->Channels > 4)
			pSong->Version = 200;
		else
		{
			pSong->pType = Typestr_MED;
			pSong->Version = 210;
		}
	}
	else if (pLHdr->Vers == '1')
			pSong->Version = 300;
	else if (pLHdr->Vers == '2')
			pSong->Version = 500;
	else
	{
		pSong->pType = Typestr_OctaMEDStudio;
		pSong->Version = 0;
	}

	Loaders_Free(pSong, pLHdr);
	Loaders_Free(pSong, pLExtras);
	Loaders_Free(pSong, pLSong);
	Loaders_Free(pSong, pLBytes);
	Loaders_Free(pSong, pLBytes2);
	Loaders_Free(pSong, pLPtrs);

	return err;
}
