/* (Lossy) Impulse Tracker module loader.
**
** It makes little sense to convert this format to XM, as it results
** in severe conversion losses. The reason I wrote this loader anyway,
** is so that you can import IT files to extract samples, pattern data
** and so on.
**
** Note: Data sanitation is done in the last stage
** of module loading, so you don't need to do that here.
*/

#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include "../ft2_header.h"
#include "../ft2_module_loader.h"
#include "../ft2_sample_ed.h"
#include "../ft2_sysreqs.h"

#ifdef _MSC_VER
#pragma pack(push)
#pragma pack(1)
#endif
typedef struct itHdr_t
{
	char ID[4], songName[26];
	uint16_t rowHighlight, ordNum, insNum, smpNum, patNum, cwtv, cmwt, flags, special;
	uint8_t globalVol, mixingVol, speed, BPM, panSep, pitchWheelDepth;
	uint16_t msgLen;
	uint32_t msgOffs, reserved;
	uint8_t initialPans[64], initialVols[64];
}
#ifdef __GNUC__
__attribute__ ((packed))
#endif
itHdr_t;

typedef struct envNode_t
{
	int8_t magnitude;
	uint16_t tick;
}
#ifdef __GNUC__
__attribute__ ((packed))
#endif
envNode_t;

typedef struct env_t
{
	uint8_t flags, num, loopBegin, loopEnd, sustainLoopBegin, sustainLoopEnd;
	envNode_t nodePoints[25];
	uint8_t reserved;
}
#ifdef __GNUC__
__attribute__ ((packed))
#endif
env_t;

typedef struct itInsHdr_t
{
	char ID[4], dosFilename[12+1];
	uint8_t NNA, DCT, DCA;
	uint16_t fadeOut;
	uint8_t pitchPanSep, pitchPanCenter, globVol, defPan, randVol, randPan;
	uint16_t trackerVer;
	uint8_t numSamples, res1;
	char instrumentName[26];
	uint8_t filterCutoff, filterResonance, midiChn, midiProg;
	uint16_t midiBank;
	uint16_t smpNoteTable[120];
	env_t volEnv, panEnv, pitchEnv;
}
#ifdef __GNUC__
__attribute__ ((packed))
#endif
itInsHdr_t;

typedef struct itOldInsHdr_t
{
	char ID[4], dosFilename[12+1];
	uint8_t volEnvFlags, volEnvLoopBegin, volEnvLoopEnd, volEnvSusLoopBegin, volEnvSusLoopEnd;
	uint16_t res1, fadeOut;
	uint8_t NNA, DNC;
	uint16_t trackerVer;
	uint8_t numSamples, res2;
	char instrumentName[26];
	uint8_t res3[6];
	uint16_t smpNoteTable[120];
	uint8_t volEnv[200];
	uint16_t volEnvPoints[25];
}
#ifdef __GNUC__
__attribute__ ((packed))
#endif
itOldInsHdr_t;

typedef struct itSmpHdr_t
{
	char ID[4], dosFilename[12+1];
	uint8_t globVol, flags, vol;
	char sampleName[26];
	uint8_t cvt, defPan;
	uint32_t length, loopBegin, loopEnd, c5Speed, sustainLoopBegin, sustainLoopEnd, offsetInFile;
	uint8_t autoVibratoSpeed, autoVibratoDepth, autoVibratoRate, autoVibratoWaveform;
}
#ifdef __GNUC__
__attribute__ ((packed))
#endif
itSmpHdr_t;

#ifdef _MSC_VER
#pragma pack(pop)
#endif

static uint8_t decompBuffer[65536];
static uint8_t volPortaConv[9] = { 1, 4, 8, 16, 32, 64, 96, 128, 255 };

static bool loadCompressed16BitSample(FILE *f, sample_t *s, bool deltaEncoded);
static bool loadCompressed8BitSample(FILE *f, sample_t *s, bool deltaEncoded);
static void setAutoVibrato(instr_t *ins, itSmpHdr_t *itSmp);
static bool loadSample(FILE *f, sample_t *s, itSmpHdr_t *itSmp);

bool loadIT(FILE *f, uint32_t filesize)
{
	uint32_t insOffs[256], smpOffs[256], patOffs[256];
	itSmpHdr_t *itSmp, smpHdrs[256];
	itHdr_t itHdr;

	if (filesize < sizeof (itHdr))
	{
		loaderMsgBox("This IT module is not supported or is corrupt!");
		goto error;
	}

	fread(&itHdr, sizeof (itHdr), 1, f);

	if (itHdr.ordNum > 257 || itHdr.insNum > 256 || itHdr.smpNum > 256 || itHdr.patNum > 256)
	{
		loaderMsgBox("This IT module is not supported or is corrupt!");
		goto error;
	}

	tmpLinearPeriodsFlag = !!(itHdr.flags & 8);

	songTmp.pattNum = itHdr.patNum;
	songTmp.speed = itHdr.speed;
	songTmp.BPM = itHdr.BPM;

	memcpy(songTmp.name, itHdr.songName, 20);
	songTmp.name[20] = '\0';

	bool oldFormat = (itHdr.cmwt < 0x200);
	bool songUsesInstruments = !!(itHdr.flags & 4);
	bool oldEffects = !!(itHdr.flags & 16);
	bool compatGxx = !!(itHdr.flags & 32);

	// read order list
	for (int32_t i = 0; i < MAX_ORDERS; i++)
	{
		const uint8_t patt = (uint8_t)fgetc(f);
		if (patt == 254) // separator ("+++"), skip it
			continue;

		if (patt == 255) // end of pattern list
			break;

		songTmp.orders[songTmp.songLength] = patt;

		songTmp.songLength++;
		if (songTmp.songLength == MAX_ORDERS-1)
			break;
	}

	// read file pointers
	fseek(f, sizeof (itHdr) + itHdr.ordNum, SEEK_SET);
	fread(insOffs, 4, itHdr.insNum, f);
	fread(smpOffs, 4, itHdr.smpNum, f);
	fread(patOffs, 4, itHdr.patNum, f);

	for (int32_t i = 0; i < itHdr.smpNum; i++)
	{
		fseek(f, smpOffs[i], SEEK_SET);
		fread(&smpHdrs[i], sizeof (itSmpHdr_t), 1, f);
	}

	if (!songUsesInstruments) // read samples (as instruments)
	{
		int32_t numIns = MIN(itHdr.smpNum, MAX_INST);

		itSmp = smpHdrs;
		for (int16_t i = 0; i < numIns; i++, itSmp++)
		{
			if (!allocateTmpInstr(1 + i))
			{
				loaderMsgBox("Not enough memory!");
				return false;
			}

			instr_t *ins = instrTmp[1+i];
			sample_t *s = &ins->smp[0];

			memcpy(songTmp.instrName[1+i], itSmp->sampleName, 22);
			songTmp.instrName[1+i][22] = '\0';

			ins->numSamples = (itSmp->length > 0) ? 1 : 0;
			if (ins->numSamples > 0)
			{
				setAutoVibrato(ins, itSmp);

				if (!loadSample(f, s, itSmp))
				{
					loaderMsgBox("Not enough memory!");
					goto error;
				}
			}
		}
	}
	else if (oldFormat) // read instruments (old format version)
	{
		itOldInsHdr_t itIns;

		int32_t numIns = MIN(itHdr.insNum, MAX_INST);
		for (int16_t i = 0; i < numIns; i++)
		{
			fseek(f, insOffs[i], SEEK_SET);
			fread(&itIns, sizeof (itIns), 1, f);

			if (!allocateTmpInstr(1 + i))
			{
				loaderMsgBox("Not enough memory!");
				return false;
			}

			instr_t *ins = instrTmp[1+i];

			memcpy(songTmp.instrName[1+i], itIns.instrumentName, 22);
			songTmp.instrName[1+i][22] = '\0';

			ins->fadeout = itIns.fadeOut * 64; // 0..64 -> 0..4096
			if (ins->fadeout > 4095)
				ins->fadeout = 4095;

			// find out what samples to load into this XM instrument header

			int16_t numSamples = 0;
			uint8_t sampleList[MAX_SMP_PER_INST];

			bool sampleAdded[256];
			memset(sampleList, 0, sizeof (sampleList));
			memset(sampleAdded, 0, sizeof (sampleAdded));

			for (int32_t j = 0; j < 96; j++)
			{
				uint8_t sample = itIns.smpNoteTable[12+j] >> 8;
				if (sample > 0 && !sampleAdded[sample-1] && numSamples < MAX_SMP_PER_INST)
				{
					sampleAdded[sample-1] = true;
					sampleList[numSamples] = sample-1;
					numSamples++;
				}
			}

			/* If instrument only has one sample, copy over the sample's
			** auto-vibrato parameters to this instrument.
			*/
			bool singleSample = true;
			if (numSamples > 1)
			{
				uint8_t firstSample = sampleList[0];
				for (int32_t j = 1; j < numSamples; j++)
				{
					if (sampleList[j] != firstSample)
					{
						singleSample = false;
						break;
					}
				}
			}

			if (singleSample)
				setAutoVibrato(ins, &smpHdrs[sampleList[0]]);

			// create new note-to-sample table
			for (int32_t j = 0; j < 8*12; j++)
			{
				uint8_t inSmp = itIns.smpNoteTable[(1 * 12) + j] >> 8;

				uint8_t outSmp = 0;
				if (inSmp > 0)
				{
					inSmp--;
					for (; outSmp < numSamples; outSmp++)
					{
						if (inSmp == sampleList[outSmp])
							break;
					}

					if (outSmp >= numSamples)
						outSmp = 0;
				}

				ins->note2SampleLUT[j] = outSmp;
			}

			// load volume envelope
			if (itIns.volEnvFlags & 1)
			{
				bool volEnvLoopOn = !!(itIns.volEnvFlags & 2);
				bool volEnvSusOn = !!(itIns.volEnvFlags & 4);

				ins->volEnvFlags |= ENV_ENABLED;
				if (volEnvLoopOn) ins->volEnvFlags |= ENV_LOOP;
				if (volEnvSusOn) ins->volEnvFlags |= ENV_SUSTAIN;
				
				ins->volEnvLoopStart = MIN(itIns.volEnvLoopBegin, 11);
				ins->volEnvLoopEnd = MIN(itIns.volEnvLoopEnd, 11);
				ins->volEnvSustain = MIN(itIns.volEnvSusLoopEnd, 11);

				// hack: if sus loop only, set as normal loop + set sustain point
				if (!volEnvLoopOn && volEnvSusOn)
				{
					ins->volEnvLoopStart = MIN(itIns.volEnvSusLoopBegin, 11);
					ins->volEnvLoopEnd = MIN(itIns.volEnvSusLoopEnd, 11);
					ins->volEnvSustain = MIN(itIns.volEnvSusLoopEnd, 11);
					ins->volEnvFlags |= ENV_LOOP + ENV_SUSTAIN;
				}

				int32_t j = 0;
				for (; j < 12; j++)
				{
					if (itIns.volEnvPoints[j] >> 8 == 0xFF)
						break; // end of volume envelope

					ins->volEnvPoints[j][0] = itIns.volEnvPoints[j] & 0xFF;
					ins->volEnvPoints[j][1] = itIns.volEnvPoints[j] >> 8;
				}
				ins->volEnvLength = (uint8_t)j;

				// increase loop end point tick by one to better match IT style env looping
				if (ins->volEnvFlags & ENV_LOOP)
					ins->volEnvPoints[ins->volEnvLoopEnd][0]++;
			}

			ins->numSamples = numSamples;
			if (ins->numSamples > 0)
			{
				sample_t *s = ins->smp;
				for (int32_t j = 0; j < ins->numSamples; j++, s++)
				{
					if (!loadSample(f, s, &smpHdrs[sampleList[j]]))
					{
						loaderMsgBox("Not enough memory!");
						goto error;
					}
				}
			}
		}
	}
	else // read instruments (later format version)
	{
		itInsHdr_t itIns;

		int32_t numIns = MIN(itHdr.insNum, MAX_INST);
		for (int16_t i = 0; i < numIns; i++)
		{
			fseek(f, insOffs[i], SEEK_SET);
			fread(&itIns, sizeof (itIns), 1, f);

			if (!allocateTmpInstr(1 + i))
			{
				loaderMsgBox("Not enough memory!");
				return false;
			}

			instr_t *ins = instrTmp[1+i];

			memcpy(songTmp.instrName[1+i], itIns.instrumentName, 22);
			songTmp.instrName[1+i][22] = '\0';

			ins->fadeout = itIns.fadeOut * 32; // 0..128 -> 0..4096
			if (ins->fadeout > 4095)
				ins->fadeout = 4095;

			// find out what samples to load into this XM instrument header

			int16_t numSamples = 0;
			uint8_t sampleList[MAX_SMP_PER_INST];

			bool sampleAdded[256];
			memset(sampleList, 0, sizeof (sampleList));
			memset(sampleAdded, 0, sizeof (sampleAdded));

			for (int32_t j = 0; j < 96; j++)
			{
				uint8_t sample = itIns.smpNoteTable[12+j] >> 8;
				if (sample > 0 && !sampleAdded[sample-1] && numSamples < MAX_SMP_PER_INST)
				{
					sampleAdded[sample-1] = true;
					sampleList[numSamples] = sample-1;
					numSamples++;
				}
			}

			/* If instrument only has one sample, copy over the sample's
			** auto-vibrato parameters to this instrument.
			*/
			bool singleSample = true;
			if (numSamples > 1)
			{
				uint8_t firstSample = sampleList[0];
				for (int32_t j = 1; j < numSamples; j++)
				{
					if (sampleList[j] != firstSample)
					{
						singleSample = false;
						break;
					}
				}
			}

			if (singleSample)
				setAutoVibrato(ins, &smpHdrs[sampleList[0]]);

			// create new note-to-sample table
			for (int32_t j = 0; j < 8*12; j++)
			{
				uint8_t inSmp = itIns.smpNoteTable[(1 * 12) + j] >> 8;

				uint8_t outSmp = 0;
				if (inSmp > 0)
				{
					inSmp--;
					for (; outSmp < numSamples; outSmp++)
					{
						if (inSmp == sampleList[outSmp])
							break;
					}

					if (outSmp >= numSamples)
						outSmp = 0;
				}

				ins->note2SampleLUT[j] = outSmp;
			}

			// load volume envelope
			env_t *volEnv = &itIns.volEnv;
			bool volEnvEnabled = !!(volEnv->flags & 1);
			if (volEnvEnabled && volEnv->num > 0)
			{
				bool volEnvLoopOn = !!(volEnv->flags & 2);
				bool volEnvSusOn = !!(volEnv->flags & 4);

				ins->volEnvFlags |= ENV_ENABLED;
				if (volEnvLoopOn) ins->volEnvFlags |= ENV_LOOP;
				if (volEnvSusOn) ins->volEnvFlags |= ENV_SUSTAIN;
				
				ins->volEnvLength = MIN(volEnv->num, 12);
				ins->volEnvLoopStart = MIN(volEnv->loopBegin, 11);
				ins->volEnvLoopEnd = MIN(volEnv->loopEnd, 11);
				ins->volEnvSustain = MIN(volEnv->sustainLoopEnd, 11);

				// hack: if sus loop only, set as normal loop + set sustain point
				if (!volEnvLoopOn && volEnvSusOn)
				{
					ins->volEnvLoopStart = MIN(volEnv->sustainLoopBegin, 11);
					ins->volEnvLoopEnd = MIN(volEnv->sustainLoopEnd, 11);
					ins->volEnvSustain = MIN(volEnv->sustainLoopEnd, 11);
					ins->volEnvFlags |= ENV_LOOP + ENV_SUSTAIN;
				}

				for (int32_t j = 0; j < ins->volEnvLength; j++)
				{
					ins->volEnvPoints[j][0] = volEnv->nodePoints[j].tick;
					ins->volEnvPoints[j][1] = volEnv->nodePoints[j].magnitude;
				}

				// increase loop end point tick by one to better match IT style env looping
				if (ins->volEnvFlags & ENV_LOOP)
					ins->volEnvPoints[ins->volEnvLoopEnd][0]++;
			}

			// load pan envelope
			env_t *panEnv = &itIns.panEnv;
			bool panEnvEnabled = !!(panEnv->flags & 1);
			if (panEnvEnabled && panEnv->num > 0)
			{
				bool panEnvLoopOn = !!(panEnv->flags & 2);
				bool panEnvSusOn = !!(panEnv->flags & 4);

				ins->panEnvFlags |= ENV_ENABLED;
				if (panEnvLoopOn) ins->panEnvFlags |= ENV_LOOP;
				if (panEnvSusOn) ins->panEnvFlags |= ENV_SUSTAIN;
				
				ins->panEnvLength = MIN(panEnv->num, 12);
				ins->panEnvLoopStart = MIN(panEnv->loopBegin, 11);
				ins->panEnvLoopEnd = MIN(panEnv->loopEnd, 11);
				ins->panEnvSustain = MIN(panEnv->sustainLoopEnd, 11);

				// hack: if sus loop only, set as normal loop + set sustain point
				if (!panEnvLoopOn && panEnvSusOn)
				{
					ins->panEnvLoopStart = MIN(panEnv->sustainLoopBegin, 11);
					ins->panEnvLoopEnd = MIN(panEnv->sustainLoopEnd, 11);
					ins->panEnvSustain = MIN(panEnv->sustainLoopEnd, 11);
					ins->panEnvFlags |= ENV_LOOP + ENV_SUSTAIN;
				}

				for (int32_t j = 0; j < ins->panEnvLength; j++)
				{
					ins->panEnvPoints[j][0] = panEnv->nodePoints[j].tick;
					ins->panEnvPoints[j][1] = panEnv->nodePoints[j].magnitude + 32;
				}

				// increase loop end point tick by one to better match IT style env looping
				if (ins->panEnvFlags & ENV_LOOP)
					ins->panEnvPoints[ins->panEnvLoopEnd][0] = panEnv->nodePoints[ins->panEnvLoopEnd].tick + 1;
			}

			ins->numSamples = numSamples;
			if (ins->numSamples > 0)
			{
				sample_t *s = ins->smp;
				for (int32_t j = 0; j < ins->numSamples; j++, s++)
				{
					if (!loadSample(f, s, &smpHdrs[sampleList[j]]))
					{
						loaderMsgBox("Not enough memory!");
						goto error;
					}
				}
			}
		}
	}

	// load pattern data

	uint32_t numChannels = 0;
	for (int32_t i = 0; i < songTmp.pattNum; i++)
	{
		if (patOffs[i] == 0)
			continue;
	
		fseek(f, patOffs[i], SEEK_SET);

		uint16_t length, numRows;
		fread(&length, 2, 1, f);
		fread(&numRows, 2, 1, f);
		fseek(f, 4, SEEK_CUR);

		numRows = MIN(numRows, MAX_PATT_LEN);
		if (numRows == 0)
			continue;

		if (!allocateTmpPatt(i, numRows))
		{
			loaderMsgBox("Not enough memory!");
			goto error;
		}

		uint8_t lastMask[64];
		memset(lastMask, 0, sizeof (lastMask));

		note_t lastNote[64];
		memset(lastNote, 0, sizeof (lastNote));

		note_t *patt = patternTmp[i];

		int32_t bytesRead = 0;
		int32_t row = 0;
		while (bytesRead < length && row < numRows)
		{
			uint8_t byte = (uint8_t)fgetc(f);
			bytesRead++;

			if (byte == 0)
			{
				row++;
				continue;
			}

			const uint8_t ch = (byte - 1) & 63;
			if (ch > numChannels)
				numChannels = ch;

			note_t emptyNote;
			note_t *p = (ch >= MAX_CHANNELS) ? &emptyNote : &patt[(row * MAX_CHANNELS) + ch];

			if (byte & 128)
			{
				lastMask[ch] = (uint8_t)fgetc(f);
				bytesRead++;
			}

			if (lastMask[ch] & 16)
				p->note = lastNote[ch].note;

			if (lastMask[ch] & 32)
				p->instr = lastNote[ch].instr;

			if (lastMask[ch] & 64)
				p->vol = lastNote[ch].vol;

			if (lastMask[ch] & 128)
			{
				p->efx = lastNote[ch].efx;
				p->efxData = lastNote[ch].efxData;
			}

			if (lastMask[ch] & 1)
			{
				uint8_t note = (uint8_t)fgetc(f);
				bytesRead++;

				if (note < 120)
				{
					note++;
					if (note < 12 || note >= 96+12)
						note = 0;
					else
						note -= 12;
				}
				else if (note != 254)
				{
					note = NOTE_OFF;
				}

				if (note > NOTE_OFF && note != 254)
					note = 0; // remove note

				// 254 (note cut) is handled later!

				p->note = lastNote[ch].note = note;
			}

			if (lastMask[ch] & 2)
			{
				uint8_t ins = (uint8_t)fgetc(f);
				bytesRead++;

				if (ins > MAX_INST)
					ins = 0;

				p->instr = lastNote[ch].instr = ins;
			}

			if (lastMask[ch] & 4)
			{
				p->vol = lastNote[ch].vol = 1 + (uint8_t)fgetc(f);
				bytesRead++;
			}

			if (lastMask[ch] & 8)
			{
				p->efx = lastNote[ch].efx = (uint8_t)fgetc(f);
				bytesRead++;;

				p->efxData = lastNote[ch].efxData = (uint8_t)fgetc(f);
				bytesRead++;
			}
		}
	}
	numChannels++;

	songTmp.numChannels = MIN((numChannels + 1) & ~1, MAX_CHANNELS);

	// convert pattern data

	uint8_t lastInstr[MAX_CHANNELS], lastGInstr[MAX_CHANNELS];
	uint8_t lastDxy[MAX_CHANNELS], lastExy[MAX_CHANNELS], lastFxy[MAX_CHANNELS];
	uint8_t lastJxy[MAX_CHANNELS], lastKxy[MAX_CHANNELS], lastLxy[MAX_CHANNELS];
	uint8_t lastOxx[MAX_CHANNELS];

	memset(lastInstr, 0, sizeof (lastInstr));
	memset(lastGInstr, 0, sizeof (lastGInstr));
	memset(lastDxy, 0, sizeof (lastDxy));
	memset(lastExy, 0, sizeof (lastExy));
	memset(lastFxy, 0, sizeof (lastFxy));
	memset(lastJxy, 0, sizeof (lastJxy));
	memset(lastKxy, 0, sizeof (lastKxy));
	memset(lastLxy, 0, sizeof (lastLxy));
	memset(lastOxx, 0, sizeof (lastOxx));

	for (int32_t i = 0; i < songTmp.pattNum; i++)
	{
		note_t *p = patternTmp[i];
		if (p == NULL)
			continue;

		for (int32_t j = 0; j < patternNumRowsTmp[i]; j++)
		{
			for (int32_t ch = 0; ch < songTmp.numChannels; ch++, p++)
			{
				if (p->instr > 0)
					lastInstr[ch] = p->instr;

				// effect
				if (p->efx != 0)
				{
					const uint8_t itEfx = 'A' + (p->efx - 1);
					switch (itEfx)
					{
						case 'A': // set speed
						{
							if (p->efxData == 0) // A00 is ignored in IT
							{
								p->efx = p->efxData = 0;
							}
							else
							{
								p->efx = 0xF;
								if (p->efxData > 31)
									p->efxData = 31;
							}
						}
						break;

						case 'B': p->efx = 0xB; break; // position jump
						case 'C': p->efx = 0xD; break; // pattern break

						case 'D': // volume slide
						{
							if (p->efxData == 0)
							{
								bool lastWasFineSlide = (lastDxy[ch] & 0x0F) == 0x0F || (lastDxy[ch] >> 4) == 0x0F;
								if (lastWasFineSlide)
									p->efxData = lastDxy[ch];
							}
							else
							{
								lastDxy[ch] = p->efxData;
							}

							if ((p->efxData & 0x0F) == 0x0F && (p->efxData >> 4) > 0)
							{
								p->efx = 0xE;
								p->efxData = 0xA0 + (p->efxData >> 4);
							}
							else if ((p->efxData >> 4) == 0x0F && (p->efxData & 0x0F) > 0)
							{
								p->efx = 0xE;
								p->efxData = 0xB0 + (p->efxData & 0x0F);
							}
							else
							{
								p->efx = 0xA;
							}
						}
						break;

						case 'E': // portamento down
						{
							if (p->efxData == 0)
							{
								bool lastWasFineSlide = (lastExy[ch] & 0x0F) == 0x0F || (lastExy[ch] >> 4) == 0x0F;
								bool lastWasExtraFineSlide = (lastExy[ch] & 0x0F) == 0x0E || (lastExy[ch] >> 4) == 0x0E;

								if (lastWasFineSlide || lastWasExtraFineSlide)
									p->efxData = lastExy[ch];
							}
							else
							{
								lastExy[ch] = p->efxData;
							}

							if (p->efxData < 224)
							{
								p->efx = 0x2;
							}
							else if ((p->efxData >> 4) == 0x0E)
							{
								p->efx = 16 + ('X' - 'G');
								p->efxData = 0x20 + (p->efxData & 0x0F);
							}
							else if ((p->efxData >> 4) == 0x0F)
							{
								p->efx = 0xE;
								p->efxData = 0x20 + (p->efxData & 0x0F);
							}
						}
						break;

						case 'F': // portamento up
						{
							if (p->efxData == 0)
							{
								bool lastWasFineSlide = (lastFxy[ch] & 0x0F) == 0x0F || (lastFxy[ch] >> 4) == 0x0F;
								bool lastWasExtraFineSlide = (lastFxy[ch] & 0x0F) == 0x0E || (lastFxy[ch] >> 4) == 0x0E;

								if (lastWasFineSlide || lastWasExtraFineSlide)
									p->efxData = lastFxy[ch];
							}
							else
							{
								lastFxy[ch] = p->efxData;
							}

							if (p->efxData < 224)
							{
								p->efx = 0x1;
							}
							else if ((p->efxData >> 4) == 0x0E)
							{
								p->efx = 16 + ('X' - 'G');
								p->efxData = 0x10 + (p->efxData & 0x0F);
							}
							else if ((p->efxData >> 4) == 0x0F)
							{
								p->efx = 0xE;
								p->efxData = 0x10 + (p->efxData & 0x0F);
							}
						}
						break;

						case 'G': // tone portamento
						{
							p->efx = 3;

							// remove illegal slides (this is not quite right, but good enough)
							if (!compatGxx && p->instr != 0 && p->instr != lastGInstr[ch])
								p->efx = p->efxData = 0;
						}
						break;

						case 'H': // vibrato
						{
							p->efx = 4;
							if (!oldEffects && p->efxData > 0)
								p->efxData = (p->efxData & 0xF0) | ((p->efxData & 0x0F) >> 1);
						}
						break;

						case 'I': // tremor
						{
							p->efx = 16 + ('T' - 'G');

							int8_t onTime = p->efxData >> 4;
							if (onTime > 0) // closer to IT2 (but still off)
								onTime--;

							int8_t offTime = p->efxData & 0x0F;
							if (offTime > 0) // ---
								offTime--;

							p->efxData = (onTime << 4) | offTime;
						}
						break;

						case 'J': // arpeggio
						{
							p->efx = 0;

							if (p->efxData != 0)
								p->efxData = lastJxy[ch] = (p->efxData >> 4) | (p->efxData << 4); // swap order (FT2 = reversed)
							else
								p->efxData = lastJxy[ch];
						}
						break;

						case 'K': // volume slide + vibrato
						{
							if (p->efxData == 0)
							{
								bool lastWasFineSlide = (lastKxy[ch] & 0x0F) == 0x0F || (lastKxy[ch] >> 4) == 0x0F;
								if (lastWasFineSlide)
									p->efxData = lastKxy[ch];
							}
							else
							{
								lastKxy[ch] = p->efxData;
							}

							if ((p->efxData & 0x0F) == 0x0F && (p->efxData >> 4) > 0)
							{
								if (p->vol == 0)
									p->vol = 1+203; // IT2 vibrato of param 0 (to be converted)

								p->efx = 0xE;
								p->efxData = 0xA0 + (p->efxData >> 4);
							}
							else if ((p->efxData >> 4) == 0x0F && (p->efxData & 0x0F) > 0)
							{
								if (p->vol == 0)
									p->vol = 1+203; // IT2 vibrato of param 0 (to be converted)

								p->efx = 0xE;
								p->efxData = 0xB0 + (p->efxData & 0x0F);
							}
							else
							{
								p->efx = 0x6;
							}
						}
						break;

						case 'L': // volume slide + tone portamento
						{
							if (p->efxData == 0)
							{
								bool lastWasFineSlide = (lastLxy[ch] & 0x0F) == 0x0F || (lastLxy[ch] >> 4) == 0x0F;
								if (lastWasFineSlide)
									p->efxData = lastLxy[ch];
							}
							else
							{
								lastLxy[ch] = p->efxData;
							}

							if ((p->efxData & 0x0F) == 0x0F && (p->efxData >> 4) > 0)
							{
								if (p->vol == 0)
									p->vol = 1+193; // IT2 tone portamento of param 0 (to be converted)

								p->efx = 0xE;
								p->efxData = 0xA0 + (p->efxData >> 4);
							}
							else if ((p->efxData >> 4) == 0x0F && (p->efxData & 0x0F) > 0)
							{
								if (p->vol == 0)
									p->vol = 1+193; // IT2 tone portamento of param 0 (to be converted)

								p->efx = 0xE;
								p->efxData = 0xB0 + (p->efxData & 0x0F);
							}
							else
							{
								p->efx = 0x5;
							}
						}
						break;

						case 'O': // set sample offset
						{
							p->efx = 0x9;

							if (p->efxData > 0)
								lastOxx[ch] = p->efxData;

							// handle cases where the sample offset is after the end of the sample
							if (lastInstr[ch] > 0 && lastOxx[ch] > 0 && p->note > 0 && p->note <= 96)
							{
								instr_t *ins = instrTmp[lastInstr[ch]];
								if (ins != NULL)
								{
									const uint8_t sample = ins->note2SampleLUT[p->note-1];
									if (sample < MAX_SMP_PER_INST)
									{
										sample_t *s = &ins->smp[sample];
										if (s->length > 0)
										{
											const bool loopEnabled = (GET_LOOPTYPE(s->flags) != LOOP_DISABLED);
											const uint32_t sampleEnd = loopEnabled ? s->loopStart+s->loopLength : s->length;

											if (lastOxx[ch]*256UL >= sampleEnd)
											{
												if (oldEffects)
												{
													if (loopEnabled)
														p->efxData = (uint8_t)(sampleEnd >> 8);
												}
												else
												{
													p->efx = p->efxData = 0;
												}
											}
										}
									}
								}
							}
						}
						break;

						case 'P': // panning slide
						{
							p->efx = 16 + ('P' - 'G');

							if ((p->efxData >> 4) == 0)
							{
								uint8_t param = (((p->efxData & 0x0F) * 255) + 32) / 64;
								if (param > 15)
									param = 15;

								p->efxData = param << 4;
							}
							else if ((p->efxData & 0x0F) == 0)
							{
								uint8_t param = (((p->efxData >> 4) * 255) + 32) / 64;
								if (param > 15)
									param = 15;

								p->efxData = param;
							}
						}
						break;

						case 'Q': // note retrigger
						{
							p->efx = 16 + ('R' - 'G');

							if ((p->efxData & 0xF0) == 0x00)
								p->efxData |= 0x80;
						}
						break;

						case 'R': // tremolo
						{
							p->efx = 7;
							p->efxData = (p->efxData & 0xF0) | ((p->efxData & 0x0F) >> 1);
						}
						break;

						case 'S': // special effects
						{
							switch (p->efxData >> 4)
							{
								case 0x1: p->efx = 0xE3; break; // set glissando control

								case 0x3: // set vibrato waveform
								{
									if ((p->efxData & 0x0F) > 2)
										p->efx = p->efxData = 0;
									else
										p->efx = 0xE4;
								}
								break;

								case 0x4: // set tremolo waveform
								{
									if ((p->efxData & 0x0F) > 2)
										p->efx = p->efxData = 0;
									else
										p->efx = 0xE7;
								}
								break;

								case 0x8:
									p->efx = 0x08;
									p->efxData = (p->efxData << 4) | (p->efxData & 0x0F);
								break;

								case 0xB: p->efx = 0xE6; break; // pattern loop
								case 0xC: p->efx = 0xEC; break; // note cut
								case 0xD: p->efx = 0xED; break; // note delay
								case 0xE: p->efx = 0xEE; break; // pattern delay

								default:
									p->efx = p->efxData = 0;
								break;
							}
						}
						break;

						case 'T': // set tempo (BPM)
						{
							p->efx = 0xF;
							if (p->efxData < 32)
								p->efx = p->efxData = 0; // tempo slide is not supported
						}
						break;

						case 'V': // set global volume
						{
							p->efx = 16 + ('G' - 'G');
							p->efxData >>= 1; // IT2 g.vol. ranges 0..128, FT2 g.vol. ranges 0..64

							if (p->efxData > 64)
								p->efxData = 64;
						}
						break;

						case 'W': // global volume slide
						{
							p->efx = 16 + ('H' - 'G');

							// IT2 g.vol. ranges 0..128, FT2 g.vol. ranges 0..64
							if (p->efxData >> 4 == 0)
							{
								uint8_t param = p->efxData & 0x0F;
								if (param > 1)
									p->efxData = param >> 1;
							}
							else if ((p->efxData & 0x0F) == 0)
							{
								uint8_t param = p->efxData >> 4;
								if (param > 1)
									p->efxData = (param >> 1) << 4;
							}
						}
						break;

						case 'X': p->efx = 8; break; // set 8-bit panning

						default:
							p->efx = p->efxData = 0;
						break;
					}
				}
				else
				{
					p->efxData = 0;
				}

				if (p->instr != 0 && p->efx != 0x3)
					lastGInstr[ch] = p->instr;

				// volume column
				if (p->vol > 0)
				{
					p->vol--;
					if (p->vol <= 64) // set volume
					{
						p->vol += 0x10;
					}
					else if (p->vol <= 74) // fine volume slide up
					{
						p->vol = 0x90 + (p->vol - 65);
					}
					else if (p->vol <= 84) // fine volume slide down
					{
						p->vol = 0x80 + (p->vol - 75);
					}
					else if (p->vol <= 94) // volume slide up
					{
						p->vol = 0x70 + (p->vol - 85);
					}
					else if (p->vol <= 104) // volume slide down
					{
						p->vol = 0x60 + (p->vol - 95);
					}
					else if (p->vol <= 114) // pitch slide down
					{
						uint8_t param = p->vol - 105;
						p->vol = 0;

						if (p->efx == 0 && p->efxData == 0)
						{
							p->efx = 2;
							p->efxData = param * 4;
						}
					}
					else if (p->vol <= 124) // pitch slide up
					{
						uint8_t param = p->vol - 115;
						p->vol = 0;

						if (p->efx == 0 && p->efxData == 0)
						{
							p->efx = 1;
							p->efxData = param * 4;
						}
					}
					else if (p->vol <= 192) // set panning
					{
						p->vol = 0xC0 + (((p->vol - 128) * 15) / 64);
					}
					else if (p->vol >= 193 && p->vol <= 202) // portamento
					{
						uint8_t param = p->vol - 193;
					
						if (p->efx == 0 && p->efxData == 0)
						{
							p->vol = 0;

							p->efx = 3;
							p->efxData = (param == 0) ? 0 : volPortaConv[param-1];
						}
						else
						{
							p->vol = 0xF0 + param;
						}
					}
					else if (p->vol <= 212) // vibrato
					{
						p->vol = 0xB0 + (p->vol - 203);
					}
				}

				// note
				if (p->note == 254) // note cut
				{
					p->note = 0;
					if (p->efx == 0 && p->efxData == 0)
					{
						// EC0 (instant note cut)
						p->efx = 0xE;
						p->efxData = 0xC0;
					}
					else if (p->vol == 0)
					{
						// volume command vol 0
						p->vol = 0x10;
					}
				}
			}

			p += MAX_CHANNELS - songTmp.numChannels;
		}
	}

	// removing this message is considered a criminal act!!!
	loaderMsgBox("Loading of this format has severe issues. Don't use this for listening to .ITs!");

	return true;

error:
	return false;
}

static void decompress16BitData(int16_t *dst, const uint8_t *src, uint32_t blockLength)
{
	uint8_t byte8, bitDepth, bitDepthInv, bitsRead;
	uint16_t bytes16, lastVal;
	uint32_t bytes32;

	lastVal = 0;
	bitDepth = 17;
	bitDepthInv = bitsRead = 0;

	blockLength >>= 1;
	while (blockLength != 0)
	{
		bytes32 = (*(uint32_t *)src) >> bitsRead;

		bitsRead += bitDepth;
		src += bitsRead >> 3;
		bitsRead &= 7;

		if (bitDepth <= 6)
		{
			bytes32 <<= bitDepthInv & 0x1F;

			bytes16 = (uint16_t)bytes32;
			if (bytes16 != 0x8000)
			{
				lastVal += (int16_t)bytes16 >> (bitDepthInv & 0x1F); // arithmetic shift
				*dst++ = lastVal;
				blockLength--;
			}
			else
			{
				byte8 = ((bytes32 >> 16) & 0xF) + 1;
				if (byte8 >= bitDepth)
					byte8++;
				bitDepth = byte8;

				bitDepthInv = 16;
				if (bitDepthInv < bitDepth)
					bitDepthInv++;
				bitDepthInv -= bitDepth;

				bitsRead += 4;
			}

			continue;
		}

		bytes16 = (uint16_t)bytes32;

		if (bitDepth <= 16)
		{
			uint16_t tmp16 = 0xFFFF >> (bitDepthInv & 0x1F);
			bytes16 &= tmp16;
			tmp16 = (tmp16 >> 1) - 8;

			if (bytes16 > tmp16+16 || bytes16 <= tmp16)
			{
				bytes16 <<= bitDepthInv & 0x1F;
				bytes16 = (int16_t)bytes16 >> (bitDepthInv & 0x1F); // arithmetic shift
				lastVal += bytes16;
				*dst++ = lastVal;
				blockLength--;
				continue;
			}

			byte8 = (uint8_t)(bytes16 - tmp16);
			if (byte8 >= bitDepth)
				byte8++;
			bitDepth = byte8;

			bitDepthInv = 16;
			if (bitDepthInv < bitDepth)
				bitDepthInv++;
			bitDepthInv -= bitDepth;
			continue;
		}

		if (bytes32 & 0x10000)
		{
			bitDepth = (uint8_t)(bytes16 + 1);
			bitDepthInv = 16 - bitDepth;
		}
		else
		{
			lastVal += bytes16;
			*dst++ = lastVal;
			blockLength--;
		}
	}
}

static void decompress8BitData(int8_t *dst, const uint8_t *src, uint32_t blockLength)
{
	uint8_t lastVal, byte8, bitDepth, bitDepthInv, bitsRead;
	uint16_t bytes16;

	lastVal = 0;
	bitDepth = 9;
	bitDepthInv = bitsRead = 0;

	while (blockLength != 0)
	{
		bytes16 = (*(uint16_t *)src) >> bitsRead;

		bitsRead += bitDepth;
		src += (bitsRead >> 3);
		bitsRead &= 7;

		byte8 = bytes16 & 0xFF;

		if (bitDepth <= 6)
		{
			bytes16 <<= (bitDepthInv & 0x1F);
			byte8 = bytes16 & 0xFF;

			if (byte8 != 0x80)
			{
				lastVal += (int8_t)byte8 >> (bitDepthInv & 0x1F); // arithmetic shift
				*dst++ = lastVal;
				blockLength--;
				continue;
			}

			byte8 = (bytes16 >> 8) & 7;
			bitsRead += 3;
			src += (bitsRead >> 3);
			bitsRead &= 7;
		}
		else
		{
			if (bitDepth == 8)
			{
				if (byte8 < 0x7C || byte8 > 0x83)
				{
					lastVal += byte8;
					*dst++ = lastVal;
					blockLength--;
					continue;
				}
				byte8 -= 0x7C;
			}
			else if (bitDepth < 8)
			{
				byte8 <<= 1;
				if (byte8 < 0x78 || byte8 > 0x86)
				{
					lastVal += (int8_t)byte8 >> (bitDepthInv & 0x1F); // arithmetic shift
					*dst++ = lastVal;
					blockLength--;
					continue;
				}
				byte8 = (byte8 >> 1) - 0x3C;
			}
			else
			{
				bytes16 &= 0x1FF;
				if ((bytes16 & 0x100) == 0)
				{
					lastVal += byte8;
					*dst++ = lastVal;
					blockLength--;
					continue;
				}
			}
		}

		byte8++;
		if (byte8 >= bitDepth)
			byte8++;
		bitDepth = byte8;

		bitDepthInv = 8;
		if (bitDepthInv < bitDepth)
			bitDepthInv++;
		bitDepthInv -= bitDepth;
	}
}

static bool loadCompressed16BitSample(FILE *f, sample_t *s, bool deltaEncoded)
{
	int8_t *dstPtr = (int8_t *)s->dataPtr;

	uint32_t i = s->length * 2;
	while (i > 0)
	{
		uint32_t bytesToUnpack = 32768;
		if (bytesToUnpack > i)
			bytesToUnpack = i;

		uint16_t packedLen;
		fread(&packedLen, sizeof (uint16_t), 1, f);
		fread(decompBuffer, 1, packedLen, f);

		decompress16BitData((int16_t *)dstPtr, decompBuffer, bytesToUnpack);

		if (deltaEncoded) // convert from delta values to PCM
		{
			int16_t *ptr16 = (int16_t *)dstPtr;
			int16_t lastSmp16 = 0; // yes, reset this every block!

			const uint32_t length = bytesToUnpack >> 1;
			for (uint32_t j = 0; j < length; j++)
			{
				lastSmp16 += ptr16[j];
				ptr16[j] = lastSmp16;
			}
		}

		dstPtr += bytesToUnpack;
		i -= bytesToUnpack;
	}

	return true;
}

static bool loadCompressed8BitSample(FILE *f, sample_t *s, bool deltaEncoded)
{
	int8_t *dstPtr = (int8_t *)s->dataPtr;

	uint32_t i = s->length;
	while (i > 0)
	{
		uint32_t bytesToUnpack = 32768;
		if (bytesToUnpack > i)
			bytesToUnpack = i;

		uint16_t packedLen;
		fread(&packedLen, sizeof (uint16_t), 1, f);
		fread(decompBuffer, 1, packedLen, f);

		decompress8BitData(dstPtr, decompBuffer, bytesToUnpack);

		if (deltaEncoded) // convert from delta values to PCM
		{
			int8_t lastSmp8 = 0; // yes, reset this every block!
			for (uint32_t j = 0; j < bytesToUnpack; j++)
			{
				lastSmp8 += dstPtr[j];
				dstPtr[j] = lastSmp8;
			}
		}

		dstPtr += bytesToUnpack;
		i -= bytesToUnpack;
	}

	return true;
}

static void setAutoVibrato(instr_t *ins, itSmpHdr_t *itSmp)
{
	ins->autoVibType = itSmp->autoVibratoWaveform;
	if (ins->autoVibType > 3 || itSmp->autoVibratoRate == 0)
	{
		// turn off auto-vibrato
		ins->autoVibDepth = ins->autoVibRate = ins->autoVibSweep = ins->autoVibType = 0;
		return;
	}

	ins->autoVibRate = itSmp->autoVibratoSpeed;
	if (ins->autoVibRate > 63)
		ins->autoVibRate = 63;

	int32_t autoVibSweep = ((itSmp->autoVibratoDepth * 256) + 128) / itSmp->autoVibratoRate;
	if (autoVibSweep > 255)
		autoVibSweep = 255;
	ins->autoVibSweep = (uint8_t)autoVibSweep;

	ins->autoVibDepth = itSmp->autoVibratoDepth;
	if (ins->autoVibDepth > 15)
		ins->autoVibDepth = 15;
}

static bool loadSample(FILE *f, sample_t *s, itSmpHdr_t *itSmp)
{
	bool sampleIs16Bit = !!(itSmp->flags & 2);
	bool compressed = !!(itSmp->flags & 8);
	bool hasLoop = !!(itSmp->flags & 16);
	bool bidiLoop = !!(itSmp->flags & 64);
	bool signedSamples = !!(itSmp->cvt & 1);
	bool deltaEncoded = !!(itSmp->cvt & 4);

	if (sampleIs16Bit)
		s->flags |= SAMPLE_16BIT;

	if (hasLoop)
		s->flags |= bidiLoop ? LOOP_BIDI : LOOP_FWD;

	s->length = itSmp->length;
	s->loopStart = itSmp->loopBegin;
	s->loopLength = itSmp->loopEnd - itSmp->loopBegin;
	s->volume = itSmp->vol;

	s->panning = 128;
	if (itSmp->defPan & 128) // use panning?
	{
		int32_t pan = (itSmp->defPan & 127) * 4; // 0..64 -> 0..256
		if (pan > 255)
			pan = 255;

		s->panning = (uint8_t)pan;
	}

	memcpy(s->name, itSmp->sampleName, 22);
	s->name[22] = '\0';

	setSampleC4Hz(s, itSmp->c5Speed);

	if (s->length <= 0 || itSmp->offsetInFile == 0)
		return true; // empty sample, skip data loading

	if (!allocateSmpData(s, s->length, sampleIs16Bit))
		return false;

	// begin sample loading

	fseek(f, itSmp->offsetInFile, SEEK_SET);

	if (compressed)
	{
		if (sampleIs16Bit)
			loadCompressed16BitSample(f, s, deltaEncoded);
		else
			loadCompressed8BitSample(f, s, deltaEncoded);
	}
	else
	{
		fread(s->dataPtr, 1+(size_t)sampleIs16Bit, s->length, f);

		if (!signedSamples)
		{
			if (sampleIs16Bit)
			{
				int16_t *ptr16 = (int16_t *)s->dataPtr;
				for (int32_t i = 0; i < s->length; i++)
					ptr16[i] ^= 0x8000;
			}
			else
			{
				int8_t *ptr8 = (int8_t *)s->dataPtr;
				for (int32_t i = 0; i < s->length; i++)
					ptr8[i] ^= 0x80;
			}
		}
	}

	return true;
}
