#include <string.h>
#include <stdio.h>

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

static const _kernel_oserror Err_FileCorrupted = {ErrNum_CodecError, "File badly corrupted."};
static const char CheckPattern_Error[] = "Internal error: incorrectly generated pattern &";
static const char CheckPattern_Error_Sep[] = " - &";
static const char CheckPattern_Error_At[] = " at &";

static const uint8_t ArcSampleLogToLin[] =
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
, 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF
, 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF
, 0x02, 0xFE, 0x02, 0xFE, 0x02, 0xFE, 0x02, 0xFE, 0x02, 0xFE, 0x02, 0xFE, 0x02, 0xFE, 0x02, 0xFE
, 0x02, 0xFE, 0x03, 0xFD, 0x03, 0xFD, 0x03, 0xFD, 0x03, 0xFD, 0x03, 0xFD, 0x03, 0xFD, 0x03, 0xFD
, 0x04, 0xFC, 0x04, 0xFC, 0x04, 0xFC, 0x04, 0xFC, 0x04, 0xFC, 0x05, 0xFB, 0x05, 0xFB, 0x05, 0xFB
, 0x05, 0xFB, 0x06, 0xFA, 0x06, 0xFA, 0x06, 0xFA, 0x06, 0xFA, 0x07, 0xF9, 0x07, 0xF9, 0x07, 0xF9
, 0x08, 0xF8, 0x08, 0xF8, 0x09, 0xF7, 0x09, 0xF7, 0x09, 0xF7, 0x0A, 0xF6, 0x0A, 0xF6, 0x0B, 0xF5
, 0x0B, 0xF5, 0x0C, 0xF4, 0x0C, 0xF4, 0x0D, 0xF3, 0x0D, 0xF3, 0x0E, 0xF2, 0x0F, 0xF1, 0x0F, 0xF1
, 0x10, 0xF0, 0x11, 0xEF, 0x12, 0xEE, 0x12, 0xEE, 0x13, 0xED, 0x14, 0xEC, 0x15, 0xEB, 0x16, 0xEA
, 0x17, 0xE9, 0x18, 0xE8, 0x19, 0xE7, 0x1A, 0xE6, 0x1B, 0xE5, 0x1D, 0xE3, 0x1E, 0xE2, 0x1F, 0xE1
, 0x21, 0xDF, 0x22, 0xDE, 0x24, 0xDC, 0x25, 0xDB, 0x27, 0xD9, 0x29, 0xD7, 0x2A, 0xD6, 0x2C, 0xD4
, 0x2E, 0xD2, 0x30, 0xD0, 0x33, 0xCD, 0x35, 0xCB, 0x37, 0xC9, 0x3A, 0xC6, 0x3C, 0xC4, 0x3F, 0xC1
, 0x42, 0xBE, 0x45, 0xBB, 0x48, 0xB8, 0x4B, 0xB5, 0x4E, 0xB2, 0x52, 0xAE, 0x55, 0xAB, 0x59, 0xA7
, 0x5D, 0xA3, 0x61, 0x9F, 0x66, 0x9F, 0x6A, 0x96, 0x6F, 0x91, 0x74, 0x8C, 0x79, 0x87, 0x7F, 0x80
};

static const uint8_t ArcVolumeLogToLin[] =
{ 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01
, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02
, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x03
, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04
, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x06
, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x08, 0x08
, 0x08, 0x08, 0x08, 0x09, 0x09, 0x09, 0x09, 0x09, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0B, 0x0B, 0x0B
, 0x0B, 0x0C, 0x0C, 0x0C, 0x0C, 0x0D, 0x0D, 0x0D, 0x0D, 0x0E, 0x0E, 0x0E, 0x0F, 0x0F, 0x0F, 0x10
, 0x10, 0x10, 0x11, 0x11, 0x11, 0x12, 0x12, 0x13, 0x13, 0x13, 0x14, 0x14, 0x15, 0x15, 0x16, 0x16
, 0x17, 0x17, 0x18, 0x18, 0x19, 0x19, 0x1A, 0x1A, 0x1B, 0x1B, 0x1C, 0x1D, 0x1D, 0x1E, 0x1F, 0x1F
, 0x20, 0x21, 0x21, 0x22, 0x23, 0x24, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x29, 0x2A, 0x2B, 0x2C
, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3B, 0x3C, 0x3D, 0x3F
, 0x40, 0x41, 0x43, 0x44, 0x46, 0x47, 0x49, 0x4A, 0x4C, 0x4E, 0x4F, 0x51, 0x53, 0x55, 0x57, 0x59
, 0x5B, 0x5C, 0x5F, 0x61, 0x63, 0x65, 0x67, 0x69, 0x6C, 0x6E, 0x70, 0x73, 0x75, 0x78, 0x7B, 0x7D
, 0x80, 0x83, 0x86, 0x89, 0x8C, 0x8F, 0x92, 0x95, 0x98, 0x9C, 0x9F, 0xA2, 0xA6, 0xAA, 0xAD, 0xB1
, 0xB5, 0xB9, 0xBD, 0xC1, 0xC5, 0xCA, 0xCE, 0xD3, 0xD7, 0xDC, 0xE1, 0xE6, 0xEB, 0xF0, 0xF5, 0xFB
, 0xFF
};

static const uint8_t VolLogToLin[] =
{ 0x00, 0x06, 0x06, 0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x08
, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x0A
, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0C, 0x0C, 0x0C, 0x0C
, 0x0C, 0x0D, 0x0D, 0x0D, 0x0D, 0x0D, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0F, 0x0F, 0x0F, 0x0F, 0x10
, 0x10, 0x10, 0x10, 0x10, 0x11, 0x11, 0x11, 0x11, 0x12, 0x12, 0x12, 0x13, 0x13, 0x13, 0x13, 0x14
, 0x14, 0x14, 0x15, 0x15, 0x15, 0x15, 0x16, 0x16, 0x16, 0x17, 0x17, 0x17, 0x18, 0x18, 0x19, 0x19
, 0x19, 0x1A, 0x1A, 0x1A, 0x1B, 0x1B, 0x1C, 0x1C, 0x1C, 0x1D, 0x1D, 0x1E, 0x1E, 0x1F, 0x1F, 0x20
, 0x20, 0x20, 0x21, 0x21, 0x22, 0x22, 0x23, 0x23, 0x24, 0x24, 0x25, 0x26, 0x26, 0x27, 0x27, 0x28
, 0x28, 0x29, 0x2A, 0x2A, 0x2B, 0x2B, 0x2C, 0x2D, 0x2D, 0x2E, 0x2F, 0x2F, 0x30, 0x31, 0x32, 0x32
, 0x33, 0x34, 0x35, 0x35, 0x36, 0x37, 0x38, 0x39, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40
, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50
, 0x51, 0x52, 0x54, 0x55, 0x56, 0x57, 0x59, 0x5A, 0x5B, 0x5D, 0x5E, 0x5F, 0x61, 0x62, 0x64, 0x65
, 0x67, 0x68, 0x6A, 0x6B, 0x6D, 0x6E, 0x70, 0x72, 0x73, 0x75, 0x77, 0x78, 0x7A, 0x7C, 0x7E, 0x80
, 0x81, 0x83, 0x85, 0x87, 0x89, 0x8B, 0x8D, 0x8F, 0x91, 0x93, 0x96, 0x98, 0x9A, 0x9C, 0x9E, 0xA1
, 0xA3, 0xA5, 0xA8, 0xAA, 0xAD, 0xAF, 0xB2, 0xB5, 0xB7, 0xBA, 0xBD, 0xBF, 0xC2, 0xC5, 0xC8, 0xCB
, 0xCE, 0xD1, 0xD4, 0xD7, 0xDA, 0xDD, 0xE0, 0xE4, 0xE7, 0xEA, 0xEE, 0xF1, 0xF5, 0xF8, 0xFC, 0xFF
, 0xFF
};

const uint8_t* Table_ArcSampleLogToLin(void)
{
	return ArcSampleLogToLin;
}

const uint8_t* Table_ArcVolumeLogToLin(void)
{
	return ArcVolumeLogToLin;
}

const uint8_t* Table_MUSXVolumeLogToLin(void)
{
	return VolLogToLin;
}

const _kernel_oserror* Loaders_InvalidSong(SongHdr* pSong, int32_t Offset, const void* pParam, uint32_t value)
{
	_kernel_oserror* e = &pSong->LastError;
	int i;
#ifndef USELOG
	IGNORE(Offset);
#endif

	e->errnum = Err_FileCorrupted.errnum;

	if (pParam == &pSong->pType)
		snprintf(e->errmess, sizeof(e->errmess), "File badly corrupted. Could not found all base info expected in a %s", pSong->pType);
	else if (pParam == &pSong->Channels)
		snprintf(e->errmess, sizeof(e->errmess), "File badly corrupted. Invalid nr of channels (%d)", value);
	else if (pParam == &pSong->Patterns)
		snprintf(e->errmess, sizeof(e->errmess), "File badly corrupted. Invalid nr of patterns (%d)", value);
	else if (pParam == &pSong->Samples)
		snprintf(e->errmess, sizeof(e->errmess), "File badly corrupted. Invalid nr of samples (%d)", value);
	else if (pParam == &pSong->Instruments)
		snprintf(e->errmess, sizeof(e->errmess), "File badly corrupted. Invalid nr of instruments (%d)", value);
	else if (pParam == &pSong->BaseSong.SeqLen)
		snprintf(e->errmess, sizeof(e->errmess), "File badly corrupted. Invalid nr of orders (%d) in sequence", value);
	else
	{
		i = (((char*) pParam) - ((char*) pSong->pPatterns)) / sizeof(Pattern);
		if ((i >= 0) && (i < song_max_patterns))
		{
//			const Pattern* pPattern = pSong->pPatterns + i;
			snprintf(e->errmess, sizeof(e->errmess), "File badly corrupted. Invalid nr of rows (%d) in pattern %d", value, i);
		}
/*
		else
		{
			i = (((char*) pParam) - ((char*) pSong->pSamples)) / sizeof(Sample);
			if ((i >= 0) && (i < song_max_samples))
			{
				const Sample* pSample = pSong->pSamples + i;
			}
			else
			{
				i = (((char*) pParam) - ((char*) pSong->pInstruments)) / sizeof(Instrument);
				if ((i >= 0) && (i < song_max_instruments))
				{
					const Instrument* pInst = pSong->pInstruments + i;
				}
			}
		}
*/
	}

	SysLog_FileError(pSong, Offset, "%s", e->errmess);

	return e;
}

const _kernel_oserror* Loaders_Error(SongHdr* pSong, int32_t Offset, const char* pformat, ...)
{
	_kernel_oserror* e = &pSong->LastError;
	va_list arg;

#ifndef USELOG
	IGNORE(Offset);
#endif

	e->errnum = Err_FileCorrupted.errnum;

	va_start(arg, pformat);
	SysLog_VFileError(pSong, Offset, pformat, arg);
	va_end(arg);
	strcpy(e->errmess, "File badly corrupted. ");
	va_start(arg, pformat);
	vsnprintf(e->errmess + 22, sizeof(e->errmess) - 22, pformat, arg);
	va_end(arg);

	return e;
}

const _kernel_oserror* Loaders_Alloc(SongHdr* pSong, void** p, int length)
{
	return CMem_Alloc(pSong->pGlb, p, length);
}

const _kernel_oserror* Loaders_AllocString(SongHdr* pSong, void** p, int length)
{
	return CMem_Alloc(pSong->pGlb, p, length + 1);
}

const _kernel_oserror* Loaders_Resize(SongHdr* pSong, void** p, int oldsize, int delta)
{
	return CMem_Resize(pSong->pGlb, p, oldsize, delta);
}

const _kernel_oserror* Loaders_Free(SongHdr* pSong, void* p)
{
	return CMem_Free(pSong->pGlb, p);
}

const _kernel_oserror* Loaders_String(SongHdr* pSong, uint8_t** ppTo, const uint8_t* pFrom, uint32_t length, bool bKeepAll)
{
	const _kernel_oserror* err;
	int i;
	uint8_t* pTo;

/*	// remove trailing
	while (length > 0)
	{
		if (pFrom[Length] > 32)
			break;
		length--;
	}
*/
	if (*ppTo)
	{
		CMem_Free(pSong->pGlb, *ppTo);
		*ppTo = NULL;
	}
	err = CMem_Alloc(pSong->pGlb, (void**) ppTo, length + 1);
	if (err) return err;
	pTo = *ppTo;
	memmove(pTo, pFrom, length);

	pTo[length] = 0;
	if (bKeepAll)
	{
		// loop to set blanks
		for(i = length - 1; i >= 0; i--)
		{
			if (pTo[i] < 32) pTo[i] = ' ';
		}
	}

	return NULL;
}

int Loaders_strncmp(const uint8_t* p1, const uint8_t* p2, uint32_t len)
{
	for(;len; len--, p1++, p2++)
	{
		if (*p1 != *p2)
			return (int) *p2 - (int) *p1;
	}

	return 0;
}

uint32_t Effect_Tremor(uint32_t offtime, uint32_t ontime)
{
	if (offtime > 15) offtime = 15;
	if (ontime > 15) ontime = 15;
	return offtime + (ontime << 4); // Only 1 byte available
}

void Pattern_AddEffect_Arpeggio(SongHdr* pSong, uint32_t tone1, uint32_t tone2, uint32_t type)
{
	if (tone1 > 15) tone1 = 15;
	if (tone2 > 15) tone2 = 15;
	Pattern_AddEffect_Pitch(pSong, cmd_pitch_arpeggio, tone1 + (tone2 << 4) + (type << 8));
}

void Pattern_AddEffect_RetrigCounter(SongHdr* pSong, uint32_t type, uint32_t delay)
{
	if (delay > 255) delay = 255;
	Pattern_AddEffect_Instrument(pSong, cmd_sample_note_retrig_counter, delay + (type << 8));
}

//------------------------------------------------------------------------------

const _kernel_oserror* Loaders_PreparePatterns(SongHdr* pSong)
{
	pSong->pLoaderData->pPattern = NULL;
	pSong->pLoaderData->PatternLength = 0;
	pSong->pLoaderData->HighestChannel = 0;
	pSong->pLoaderData->LoadFlags &= ~LoadFlag_HasGlbVolumeCmd;

	return NULL;
}

const _kernel_oserror* Loaders_CompletePatterns(SongHdr* pSong)
{
	pSong->Channels = pSong->pLoaderData->HighestChannel + 1;

	// Force full global volume if no global volume command is found
	if (!(pSong->pLoaderData->LoadFlags & LoadFlag_HasGlbVolumeCmd))
	{
		for (SubSong* pSubSong = &pSong->BaseSong; pSubSong; pSubSong = pSubSong->pNextSubSong)
			pSubSong->Defaults.Volume = 256;
	}

	return NULL;
}

//------------------------------------------------------------------------------

const _kernel_oserror* Loaders_EmptyPattern(SongHdr* pSong, Pattern* pPattern)
{
	const _kernel_oserror* err;

	pPattern->Size = pPattern->Rows << 1;
	err = Loaders_Alloc(pSong, (void**) &pPattern->Ptr, pPattern->Size);
	if (err) return err;
	memset(pPattern->Ptr, 0, pPattern->Size);

	return NULL;
}

const _kernel_oserror* Loaders_StartPattern(SongHdr* pSong, uint32_t index)
{
	pSong->pLoaderData->pPattern = NULL;
	pSong->pLoaderData->PatternLength = 0;
	pSong->pLoaderData->Pattern.Nr = index;

	return NULL;
}

const _kernel_oserror* Loaders_EndPattern(SongHdr* pSong, Pattern* pPattern)
{
	pPattern->Ptr = pSong->pLoaderData->pPattern;
	pPattern->Size =  pSong->pLoaderData->PatternLength;
	pSong->pLoaderData->pPattern = NULL;
	pSong->pLoaderData->PatternLength = 0;

	return NULL;
}

static const _kernel_oserror* Loaders_CheckPatternBlock(SongHdr* pSong, const uint8_t* pStart, const uint8_t* pEnd, uint32_t rows)
{
	uint32_t row;
	uint32_t rowSize;
	const uint8_t* p = pStart;
	const uint8_t* pp;
	int32_t  channel;
	int32_t  count;
	uint32_t val;

	for (row = rows; (row > 0) && (p + 1 < pEnd); row--)
	{
		// decode row length
		rowSize = p[0] + (p[1] << 8);
		p += 2;
		pp = p + rowSize;
		if (pp > pEnd)
		{
			p -= 2;
			goto CheckPattern_InError;
		}
		if (p < pp)
		{
			channel = -1;
			// check global effects
			for (; (val = *p++) != 0;)
			{
				count = val >> 4;
				val &= 0xf;
				if ((val > ctrls_glb_last)
				||  !count)
					goto CheckPattern_InError;
				p += count << 1;
			}

			// check channels
			for (; p != pp;)
			{
				if (p > pp)
					goto CheckPattern_InError;
				// read column number
				val = *p++;
				count = val & 0x3f;
				if (channel >= count)
					goto CheckPattern_InError;
				channel = count;

				// read note?
				if (val & 0x40)
				{
					if ((*p++) > Note_Reset)
						goto CheckPattern_InError;
				}
				// read instrument?
				if (val & 0x80)
					p++;

				// check effects
				for(; (val = *p++) != 0;)
				{
					count = val >> 4;
					val &= 0xf;
					if ((val > ctrls_ch_last)
					||  !count)
						goto CheckPattern_InError;
					p += count << 1;
				}
			}
		}
	}

	if ((row == 0) && (p == pEnd)) return NULL;

CheckPattern_InError:
	{
		_kernel_oserror* e = &pSong->LastError;
		e->errnum = ErrNum_InternalError;
		char* pp = e->errmess;

		strcpy(pp, CheckPattern_Error);
		pp += strlen(pp);
		ConvertHex8((uint32_t) pStart, pp, 9);
		pp += 8;
		strcpy(pp, CheckPattern_Error_Sep);
		pp += strlen(pp);
		ConvertHex8((uint32_t) pEnd, pp, 9);
		pp += 8;
		strcpy(pp, CheckPattern_Error_At);
		pp += strlen(pp);
		ConvertHex8((uint32_t) p, pp, 9);
		pp[8] = 0;

		SysLog_LogBinary("Incorrect internal pattern:", pStart, pEnd - pStart);

		return e;
	}
}

const _kernel_oserror* Loaders_CheckPattern(SongHdr* pSong, Pattern* pPattern)
{
	return Loaders_CheckPatternBlock(pSong, pPattern->Ptr, pPattern->Ptr + pPattern->Size, pPattern->Rows);
}

const _kernel_oserror* Loaders_CheckStartedPattern(SongHdr* pSong)
{
	return Loaders_CheckPatternBlock
				( pSong
				, pSong->pLoaderData->pPattern
				, pSong->pLoaderData->pPattern + pSong->pLoaderData->PatternLength
				, pSong->pLoaderData->Pattern.Row + 1
				);
}

//------------------------------------------------------------------------------

const _kernel_oserror* Loaders_StartRow(SongHdr* pSong, uint32_t index)
{
	uint8_t* p = pSong->pLoaderData->Row;

	// skip row size
	// and store 0 in global effects length
	p[2] = 0;
	pSong->pLoaderData->RowStart = p + 2;
	pSong->pLoaderData->RowEnd = p + 3;
	pSong->pLoaderData->Pattern.Row = index;
	pSong->pLoaderData->Pattern.Channel = 0;
	// reset speed zero flag
	pSong->pLoaderData->RowZeroSpeed = 0;

	if (index >= song_max_rows)
		return Loaders_InvalidSong(pSong, 0, &pSong->pPatterns[pSong->pLoaderData->Pattern.Nr].Rows, index + 1);

	return NULL;
}

static const _kernel_oserror* Loaders_CheckRowBufferSize(SongHdr* pSong, uint32_t size)
{
	size += pSong->pLoaderData->RowEnd - pSong->pLoaderData->Row;

	if (size > sizeof(pSong->pLoaderData->Row))
	{
		_kernel_oserror* e = &pSong->LastError;
		e->errnum = ErrNum_InternalError;
		char* pp = e->errmess;

		strcpy(pp, "Row buffer too small");
		return e;
	}

	return NULL;
}

const _kernel_oserror* Loaders_EndRow(SongHdr* pSong)
{
	const _kernel_oserror* err = NULL;
	int size;

	// is there a zero speed
	if (pSong->pLoaderData->RowZeroSpeed)
	{
		// yes, insert a restart song
		Pattern_AddGlbEffect_Position(pSong, gcmd_pos_jump, 0);
	}

	// check size for corruption
	err = Loaders_CheckRowBufferSize(pSong, 0);
	if (err) return err;

	// store row len
	size = pSong->pLoaderData->RowEnd - pSong->pLoaderData->RowStart;

	// if only empty glb commands list, remove it
	if (size <= 1) size = 0;
	// store length
	pSong->pLoaderData->Row[0] = (size & 0xff);
	pSong->pLoaderData->Row[1] = (size & 0xff00) >> 8;
	// resize pattern memory
	size += 2;
	err = CMem_Resize(pSong->pGlb
			, (void**) &pSong->pLoaderData->pPattern
			, pSong->pLoaderData->PatternLength
			, size);
	if (err) return err;
	//; copy row to end of pattern
	memmove(pSong->pLoaderData->pPattern + pSong->pLoaderData->PatternLength
		, pSong->pLoaderData->Row, size);
	pSong->pLoaderData->PatternLength += size;

	return NULL;
}

//------------------------------------------------------------------------------

const _kernel_oserror* Loaders_StartChannel(SongHdr* pSong
				, uint32_t channel
				, uint32_t note
				, uint32_t instrument)
{
	uint32_t cmd = note;
	uint32_t size;

	pSong->pLoaderData->Pattern.Channel = channel;

	// note cut=255, note off=254, note off xm = 253, note fade 252
	// note cut volume = 251, ...?
	if (note >= Note_CutVolume)
		note = (pSong->Flags & Song_Flag_KeyoffLosesPrevNote) ? Note_Reset : 0;

	// update max channel only if instrument + note set
	if (instrument || note)
	{
		if (pSong->pLoaderData->HighestChannel < channel)
			pSong->pLoaderData->HighestChannel = channel;
	}

	size = 16; // allow some extra for effects
	if (instrument)
	{
		channel += 0x80;
		size++;
	}
	if (note)
	{
		channel += 0x40;
		size++;
	}

	// check size for corruption
	const _kernel_oserror* err = Loaders_CheckRowBufferSize(pSong, size);
	if (err) return err;

	*pSong->pLoaderData->RowEnd++ = channel;
	pSong->pLoaderData->ChannelStart = pSong->pLoaderData->RowEnd;
	if (note) *pSong->pLoaderData->RowEnd++ = note;
	if (instrument) *pSong->pLoaderData->RowEnd++ = instrument;
	pSong->pLoaderData->ChannelEffStart = pSong->pLoaderData->RowEnd;


	if (cmd > Note_CutVolume)
	{
		cmd = cmd_sample_note_action_cut + (255 - cmd);
		Pattern_AddEffect_Instrument(pSong, cmd, 0);
	}
	else if (cmd == Note_CutVolume)
		Pattern_AddEffect_NoteVolume(pSong, cmd_set, 0);

	return NULL;
}

const _kernel_oserror* Loaders_EndChannel(SongHdr* pSong)
{
	// check size for corruption
	const _kernel_oserror* err = Loaders_CheckRowBufferSize(pSong, 1);
	if (err) return err;

	if (pSong->pLoaderData->RowEnd <= pSong->pLoaderData->ChannelStart)
		pSong->pLoaderData->RowEnd--;
	else
		*pSong->pLoaderData->RowEnd++ = 0;

	return NULL;
}

//------------------------------------------------------------------------------

static void Channel_AddEffect(LoaderData* pLoaderData, uint32_t ctrl, uint32_t cmd, uint32_t val)
{
	uint8_t* pCtrl = NULL;
	uint8_t* p;
	uint8_t* pe;
	uint8_t* ppe;
	uint8_t  byte;
	int      count;

	val &= 0xfff;

	for (p = pLoaderData->ChannelEffStart; p < pLoaderData->RowEnd;)
	{
		// load controller info
		byte = *p++;
		count = byte >> 4;
		byte &= 0xf;
		// this one?
		if (byte == ctrl)
		{
			pCtrl = p - 1;
			p += count << 1;
			break;
		}
		if (byte > ctrl)
		{
			// Insert here
			p--;
			break;
		}
		p += count << 1;
	}

	if (pCtrl)
	{
		// Make space for effect
		pe = pLoaderData->RowEnd;
		ppe = pLoaderData->RowEnd += 2;

		while(pe >= p)
			*ppe-- = *pe--;

		// Set effect
		p[0] = cmd + (val >> 8);
		p[1] = val;

		// update info byte of controller (increase effects counter)
		pCtrl[0] += 1<<4;
	}
	else
	{
		// not found, insert controller

		// Make space for controller
		pe = pLoaderData->RowEnd;
		ppe = pLoaderData->RowEnd += 3;

		while(pe >= p)
			*ppe-- = *pe--;
		// insert controller info byte with one effect
		p[0] = ctrl + (1<<4);
		p[1] = cmd + (val >> 8);
		p[2] = val;
	}
}

void Pattern_AddEffect_Instrument(SongHdr* pSong, uint32_t cmd, uint32_t val)
{
	Channel_AddEffect(pSong->pLoaderData, ctrls_ch_instrument, cmd, val);
}

void Pattern_AddEffect_Pitch(SongHdr* pSong, uint32_t cmd, uint32_t val)
{
	Channel_AddEffect(pSong->pLoaderData, (cmd >= 0x100) ? ctrls_ch_pitch2 : ctrls_ch_pitch, cmd & 0xff, val);
}

void Pattern_AddEffect_NoteVolume(SongHdr* pSong, uint32_t cmd, uint32_t val)
{
	Channel_AddEffect(pSong->pLoaderData, (cmd >= 0x100) ? ctrls_ch_ivolume2 : ctrls_ch_ivolume, cmd & 0xff, val);
}

void Pattern_AddEffect_ChannelVolume(SongHdr* pSong, uint32_t cmd, uint32_t val)
{
	Channel_AddEffect(pSong->pLoaderData, ctrls_ch_gvolume, cmd, val);
}

void Pattern_AddEffect_Panning(SongHdr* pSong, uint32_t cmd, uint32_t val)
{
	Channel_AddEffect(pSong->pLoaderData, ctrls_ch_gpanning, cmd, val);
}

//------------------------------------------------------------------------------

static void RowGlb_MakeSpace(LoaderData* pLoaderData, uint8_t* at
					, uint32_t size)
{
	uint8_t* p;
	uint8_t* pp;

	p = pLoaderData->RowEnd;
	pp = (pLoaderData->RowEnd += size);
	pLoaderData->ChannelStart += size;
	pLoaderData->ChannelEffStart += size;

	while(p >= at)
		*pp-- = *p--;
}

//------------------------------------------------------------------------------

static void Effect_Normalise(uint32_t* pcmd, uint32_t* pval)
{
	*pval |= (*pcmd) << 8;
	*pval &= 0xfff;
	*pcmd &= 0xf0;
}

static uint32_t RowGlb_FindOrInsertCtrl(LoaderData* pLoaderData, uint32_t ctrl
						, uint32_t cmd
						, uint32_t val
						, uint8_t** ppCtrl)
{
	uint32_t byte, count;
	uint8_t* p;

	// ensure params are correct
	Effect_Normalise(&cmd, &val);

	// move to start of global effects
	p = pLoaderData->RowStart;
	for (; (byte = *p++) != 0;)
	{
		// load controller info
		count = byte >> 4;
		byte &= 0xf;
		// this one?
		if (byte == ctrl)
		{
			*ppCtrl = p - 1;
			return count;
		}
		if (byte > ctrl)
			break;
		p += count << 1;
	}

	// not found, insert controller
	// we have already read the info byte of the next controller
	p--;
	// move what follows by 3 bytes
	RowGlb_MakeSpace(pLoaderData, p, 3);
	// insert controller info byte with one effect
	p[0] = ctrl + (1<<4);
	p[1] = cmd + (val >> 8);
	p[2] = val;

	return 0;
}

//------------------------------------------------------------------------------

static void RowGlb_InsertEffect(LoaderData* pLoaderData
				, uint8_t* pCtrl, uint8_t* p
				, uint32_t cmd, uint32_t val)
{
	// move what follows by 2 bytes
	RowGlb_MakeSpace(pLoaderData, p, 2);
	// insert effect
	p[0] = cmd + (val >> 8);
	p[1] = val;
	// update info byte of controller (increase effects counter)
	pCtrl[0] += 1<<4;
}

static int RowGlb_FindOrInsertEffect(LoaderData* pLoaderData, uint8_t* pCtrl
				, uint32_t cmd, uint32_t val
				, uint8_t** pp)
{
	uint8_t* p = pCtrl + 1;
	uint32_t count = pCtrl[0] >> 4;
	uint32_t eff;

	// ensure params are correct
	Effect_Normalise(&cmd, &val);

	while(count--)
	{
		// read effect
		eff = p[0] & 0xf0;
		if (eff == cmd)
		{
			// return last effect of this type
			while(count-- && ((p[2] & 0xf0) == cmd))
				p += 2;
			*pp = p;
			return 1;
		}
		else if (eff > cmd)
			break;
		p += 2;
	}

	// insert effect
	RowGlb_InsertEffect(pLoaderData, pCtrl, p, cmd, val);

	return 0;
}

//------------------------------------------------------------------------------
// Row_SetGlbEffect
//
// Owerwrites a command for a given global controller
// or inserts the command if not found
//------------------------------------------------------------------------------

static void RowGlb_SetEffect(SongHdr* pSong, uint32_t ctrl
			, uint32_t cmd, uint32_t val)
{
	uint8_t* pCtrl;
	uint8_t* p;
	// locate controller, if not present insert controller and effect
	uint32_t count = RowGlb_FindOrInsertCtrl(pSong->pLoaderData
				, ctrl, cmd, val, &pCtrl);
	if (!count) return;

	// locate effect, insert effect if not found
	if (RowGlb_FindOrInsertEffect(pSong->pLoaderData, pCtrl, cmd, val, &p))
	{
		// replace effect
		p[0] = cmd + (val >> 8);
		p[1] = val;
	}
}

//------------------------------------------------------------------------------

void Pattern_AddGlbEffect_Position(SongHdr* pSong, uint32_t cmd, uint32_t val)
{
	uint32_t count;
	uint32_t eff;
	uint8_t* pCtrl;
	uint8_t* p;

	// ensure params are correct
	Effect_Normalise(&cmd, &val);

	if (cmd == gcmd_pos_loop)
	{
		// locate controller, if not present insert controller and effect
		count = RowGlb_FindOrInsertCtrl(pSong->pLoaderData, ctrls_glb_pos, cmd, val, &pCtrl);
		if (!count) return;
		p = pCtrl + 1;
		// if we are a loop we must insert it after existing ones
		// and before the other commands
		while(count--)
		{
			eff = p[0];
			if ((eff & 0xf0) != cmd)
				break;
			p += 2;
		}
		RowGlb_InsertEffect(pSong->pLoaderData, pCtrl, p, cmd, val);
	}
	else
	{
		// overwrite break or jump effect
		RowGlb_SetEffect(pSong, ctrls_glb_pos, cmd, val);
	}
}

//------------------------------------------------------------------------------

void Pattern_AddGlbEffect_Frames(SongHdr* pSong, uint32_t cmd, uint32_t val)
{
	// ensure params are correct
	Effect_Normalise(&cmd, &val);

	// set speed of zero, means stop song which translate into a jump to pos 0
	// but which we cannot insert before row is completed because it could be overwritten
	// by another set speed command on the same row
	if (cmd == gcmd_frames_set)
	{
		// set/reset flag?
		pSong->pLoaderData->RowZeroSpeed = (val == 0);

		// do nothing if 0 speed
		// replace existing command or insert one if none exists
		if (val) RowGlb_SetEffect(pSong, ctrls_glb_frames, cmd, val);
		return;
	}
	else if (cmd == gcmd_frames_repeat)
	{
		uint8_t* pCtrl;
		uint8_t* p;
		uint32_t count;

		// locate controller, if not present insert controller and effect
		count = RowGlb_FindOrInsertCtrl(pSong->pLoaderData, ctrls_glb_frames, cmd, val, &pCtrl);
		if (!count) return;

		// locate effect, insert effect if not found
		if (RowGlb_FindOrInsertEffect(pSong->pLoaderData, pCtrl, cmd, val, &p))
		{
			// accumulate effect
			val += ((p[0] & 0xf) << 8) + p[1];
			p[0] = cmd + (val >> 8);
			p[1] = val;
		}
	}
	else
	{
		uint8_t* pCtrl;
		uint8_t* p;
		uint32_t count;

		// locate controller, if not present insert controller and effect
		count = RowGlb_FindOrInsertCtrl(pSong->pLoaderData, ctrls_glb_frames, cmd, val, &pCtrl);
		if (!count) return;
		// insert at end of controller
		p = pCtrl + 1 + (count << 1);
		RowGlb_InsertEffect(pSong->pLoaderData, pCtrl, p, cmd, val);
	}
}

//------------------------------------------------------------------------------

void Pattern_KeepFirstGlbEffect_Frames(SongHdr* pSong, uint32_t cmd, uint32_t val)
{
	uint8_t* pCtrl;
	uint8_t* p;

	// ensure params are correct
	Effect_Normalise(&cmd, &val);

	// locate controller, if not present insert controller and effect
	uint32_t count = RowGlb_FindOrInsertCtrl(pSong->pLoaderData, ctrls_glb_frames, cmd, val, &pCtrl);
	if (!count) return;

	// locate effect, insert effect if not found
	RowGlb_FindOrInsertEffect(pSong->pLoaderData, pCtrl, cmd, val, &p);

	// Do not attempt to overwrite effect
}

//-------------------------------------------------------------------------------

void Pattern_AddGlbEffect_Volume(SongHdr* pSong, uint32_t cmd, uint32_t val)
{
	pSong->pLoaderData->LoadFlags |= LoadFlag_HasGlbVolumeCmd;

	RowGlb_SetEffect(pSong, ctrls_glb_volume, cmd, val);
}

//-------------------------------------------------------------------------------

void Pattern_AddGlbEffect_Tempo(SongHdr* pSong, uint32_t cmd, uint32_t val)
{
	RowGlb_SetEffect(pSong, ctrls_glb_tempo, cmd, val);
}

//-------------------------------------------------------------------------------

const _kernel_oserror* Loaders_AllocMapTable(SongHdr* pSong, NoteMap** pptable)
{
	void* ptr;
	const _kernel_oserror* err = CMem_Alloc(pSong->pGlb, &ptr, sizeof(NoteMap)*(Note_Max+1));
	if (err) return err;

	memset(ptr, 0, sizeof(NoteMap)*(Note_Max+1));

	*pptable = ptr;

	return NULL;
}

//-------------------------------------------------------------------------------

SubSong* Song_GetSubSong(SongHdr* pSong, uint32_t index)
{
	SubSong* pSubSong = &pSong->BaseSong;

	while (pSubSong && (index > 0))
	{
		pSubSong = pSubSong->pNextSubSong;
		index--;
	}

	return pSubSong;
}

const _kernel_oserror* Song_CopySubSong(SongHdr* pSong, SubSong** ppNew, const SubSong* pOld)
{
	const _kernel_oserror* err;
	SubSong* pPrevious = &pSong->BaseSong;
	while (pPrevious->pNextSubSong)
		pPrevious = pPrevious->pNextSubSong;

	err = CMem_Alloc(pSong->pGlb, (void**) ppNew, sizeof(*pOld));
	if (err) return err;

	SubSong* pNew = *ppNew;
	memcpy(pNew, pOld, sizeof(*pOld));
	pPrevious->pNextSubSong = pNew;
	pNew->pNextSubSong = NULL;
	pNew->pSeqs = NULL;
	pNew->pTimeIndexes = NULL;
	pNew->pSecIndexes = NULL;
	pNew->pSecTempos = NULL;

	return NULL;
}

const _kernel_oserror* Loaders_AllocSeq(SongHdr* pSong, SubSong* pSubSong)
{
	const _kernel_oserror* err;
	uint32_t size = pSubSong->SeqLen;
	uint32_t* ptr;

	err = CMem_Alloc(pSong->pGlb, (void**) &ptr, size * 4* 4);
	if (err) return err;

	pSubSong->pSeqs = (int32_t*) ptr;
	pSubSong->pTimeIndexes = ptr + size;
	pSubSong->pSecIndexes = pSubSong->pTimeIndexes + size;
	pSubSong->pSecTempos = pSubSong->pSecIndexes + size;

	return NULL;
}
