shithub: ft2-clone

ref: a2f19de82ff56c52d445e88a802bee4df1c161e5
dir: /src/ft2_module_loader.c/

View raw version
// for finding memory leaks in debug mode with Visual Studio
#if defined _DEBUG && defined _MSC_VER
#include <crtdbg.h>
#endif

#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#ifndef _WIN32
#include <unistd.h>
#endif
#include "ft2_header.h"
#include "ft2_audio.h"
#include "ft2_config.h"
#include "ft2_scopes.h"
#include "ft2_trim.h"
#include "ft2_inst_ed.h"
#include "ft2_sample_ed.h"
#include "ft2_wav_renderer.h"
#include "ft2_pattern_ed.h"
#include "ft2_gui.h"
#include "ft2_diskop.h"
#include "ft2_sample_loader.h"
#include "ft2_mouse.h"
#include "ft2_scopes.h"
#include "ft2_midi.h"
#include "ft2_events.h"
#include "ft2_video.h"

/* This is a *HUGE* mess!
** I hope you never have to modify it, and you probably shouldn't either.
** You will experience a lot of headaches if you dig into this file...
** If something looks to be wrong, it's probably right!
**
** The actual module load routines are ported from FT2 and slightly modified. */

enum
{
	FORMAT_NONE = 0,
	FORMAT_XM = 1,
	FORMAT_MOD = 2,
	FORMAT_S3M = 3,
	FORMAT_STM = 4
};

// DO NOT TOUCH THESE STRUCTS!

#ifdef _MSC_VER
#pragma pack(push)
#pragma pack(1)
#endif
typedef struct songSTMinstrHeaderTyp_t
{
	char name[12];
	uint8_t nul, insDisk;
	uint16_t reserved1, len, repS, repE;
	uint8_t vol, reserved2;
	uint16_t rate;
	int32_t reserved3;
	uint16_t paraLen;
}
#ifdef __GNUC__
__attribute__ ((packed))
#endif
songSTMinstrHeaderTyp;

typedef struct songSTMHeaderTyp_t
{
	char name[20], sig[8];
	uint8_t id1a, typ;
	uint8_t verMajor, verMinor;
	uint8_t tempo, ap, vol, reserved[13];
	songSTMinstrHeaderTyp instr[31];
	uint8_t songTab[128];
}
#ifdef __GNUC__
__attribute__ ((packed))
#endif
songSTMHeaderTyp;

typedef struct songS3MinstrHeaderTyp_t
{
	uint8_t typ;
	char dosName[12];
	uint8_t memSegH;
	uint16_t memSeg;
	int32_t len, repS, repE;
	uint8_t vol, dsk, pack, flags;
	int32_t c2Spd, res1;
	uint16_t gusPos;
	uint8_t res2[6];
	char name[28], id[4];
}
#ifdef __GNUC__
__attribute__ ((packed))
#endif
songS3MinstrHeaderTyp;

typedef struct songS3MHeaderTyp_t
{
	char name[28];
	uint8_t id1a, typ;
	uint16_t res1;
	int16_t songTabLen, antInstr, antPatt;
	uint16_t flags, trackerID, ver;
	char id[4];
	uint8_t globalVol, defSpeed, defTempo, masterVol, res2[12], chanType[32];
}
#ifdef __GNUC__
__attribute__ ((packed))
#endif
songS3MHeaderTyp;
#ifdef _MSC_VER
#pragma pack(pop)
#endif

static volatile uint8_t loadedFormat;
static volatile bool stereoSamplesWarn, linearFreqTable, musicIsLoading, moduleLoaded, moduleFailedToLoad;
static uint8_t oldPlayMode, pattBuff[12288];
static const uint8_t stmEff[16] = { 0, 0, 11, 0, 10, 2, 1, 3, 4, 7, 0, 5 ,6, 0, 0, 0 };
static SDL_Thread *thread;

// these temporarily read to, then copied to real struct if load was OK (should not need to be volatile'd)
static int16_t pattLensTmp[MAX_PATTERNS];
static tonTyp *pattTmp[MAX_PATTERNS];
static instrTyp *instrTmp[1 + MAX_INST];
static songTyp songTmp;

static void setupLoadedModule(void);
static void freeTmpModule(void);
static bool loadInstrHeader(FILE *f, uint16_t i);
static void checkSampleRepeat(sampleTyp *s);
static bool loadInstrSample(FILE *f, uint16_t i);
void unpackPatt(uint8_t *dst, uint16_t inn, uint16_t len, uint8_t antChn);
static bool tmpPatternEmpty(uint16_t nr);
static bool loadPatterns(FILE *f, uint16_t antPtn);

// ft2_replayer.c
extern const char modSig[32][5];
extern const uint16_t amigaPeriod[12*8];

static bool allocateTmpInstr(int16_t nr)
{
	instrTyp *p;

	if (instrTmp[nr] != NULL)
		return false; // already allocated

	p = (instrTyp *)malloc(sizeof (instrTyp));
	if (p == NULL)
		return false;

	memset(p, 0, sizeof (instrTyp));

	for (int8_t i = 0; i < 16; i++) // set standard sample pan/vol
	{
		p->samp[i].pan = 128;
		p->samp[i].vol = 64;
	}

	instrTmp[nr] = p;
	return true;
}

static bool loadMusicMOD(FILE *f, uint32_t fileLength)
{
	char ID[16];
	bool modIsUST, modIsFEST, modIsNT;
	uint8_t bytes[4];
	int16_t i, j, k, ai;
	uint16_t a, b, period, ciaPeriod;
	tonTyp *ton;
	sampleTyp *s;
	songMOD31HeaderTyp h_MOD31;
	songMOD15HeaderTyp h_MOD15;

	// start loading MOD

	loadedFormat = FORMAT_MOD;

	rewind(f);
	fread(ID, 1, 16, f);
	fseek(f, 0x1D, SEEK_SET);
	fread(bytes, 1, 1, f);
	rewind(f);
	
	// since .mod is the last format tested, check if the file is an .it module (Impulse Tracker)
	if (!memcmp(ID, "IMPM", 4) && bytes[0] == 0)
	{
		okBoxThreadSafe(0, "System message", "Error: Impulse Tracker modules are not supported!");
		goto modLoadError;
	}

	// check if the file to load is a WAV, if so reject it
	if (!memcmp(ID, "RIFF", 4) && !memcmp(&ID[8], "WAVEfmt", 7))
	{
		okBoxThreadSafe(0, "System message", "Error: Can't load a .wav file as a module!");
		goto modLoadError;
	}

	if (fileLength < 1596 || fileLength > 20842494) // minimum and maximum possible size for an FT2 .mod
	{
		okBoxThreadSafe(0, "System message", "Error: This file is either not a module, or is not supported.");
		goto modLoadError;
	}

	if (fread(&h_MOD31, 1, sizeof (h_MOD31), f) != sizeof (h_MOD31))
	{
		okBoxThreadSafe(0, "System message", "Error: This file is either not a module, or is not supported.");
		goto modLoadError;
	}

	modIsFEST = false;
	modIsNT = false;
	modIsUST = false;

	if (!strncmp(h_MOD31.sig, "N.T.", 4))
	{
		j = 4;
		modIsNT = true;
	}
	else if (!strncmp(h_MOD31.sig, "FEST", 4) || !strncmp(h_MOD31.sig, "M&K!", 4))
	{
		modIsFEST = true;
		modIsNT = true;
		j = 4;
	}
	else if (!strncmp(h_MOD31.sig, "M!K!", 4) || !strncmp(h_MOD31.sig, "M.K.", 4) || !strncmp(h_MOD31.sig, "FLT4", 4))
	{
		j = 4;
	}
	else if (!strncmp(h_MOD31.sig, "OCTA", 4) || !strncmp(h_MOD31.sig, "FLT8", 4) || !strncmp(h_MOD31.sig, "CD81", 4))
	{
		j = 8;
	}
	else
	{
		j = 0;
		for (i = 0; i < 32; i++)
		{
			if (!strncmp(h_MOD31.sig, modSig[i], 4))
			{
				j = i + 1;
				break;
			}
			else if (j == 31)
			{
				j = -1; // ID not recignized
			}
		}
	}

	// unsupported MOD
	if (j == -1)
	{
		okBoxThreadSafe(0, "System message", "Error: This file is either not a module, or is not supported.");
		goto modLoadError;
	}

	if (j > 0)
	{
		modIsUST = false;
		if (fileLength < sizeof (h_MOD31))
		{
			okBoxThreadSafe(0, "System message", "Error: This file is either not a module, or is not supported.");
			goto modLoadError;
		}

		songTmp.antChn = (uint8_t)j;
		songTmp.len = h_MOD31.len;
		songTmp.repS = h_MOD31.repS;
		memcpy(songTmp.songTab, h_MOD31.songTab, 128);
		ai = 31;
	}
	else
	{
		modIsUST = true;
		if (fileLength < sizeof (h_MOD15))
		{
			okBoxThreadSafe(0, "System message", "Error: This file is either not a module, or is not supported.");
			goto modLoadError;
		}

		fseek(f, 0, SEEK_SET);
		if (fread(&h_MOD15, 1, sizeof (h_MOD15), f) != sizeof (h_MOD15))
		{
			okBoxThreadSafe(0, "System message", "Error: This file is either not a module, or is not supported.");
			goto modLoadError;
		}

		songTmp.antChn = 4;
		songTmp.len = h_MOD15.len;
		songTmp.repS = h_MOD15.repS;
		memcpy(songTmp.songTab, h_MOD15.songTab, 128);
		ai = 15;
	}

	if (songTmp.antChn == 0 || songTmp.len < 1)
	{
		okBoxThreadSafe(0, "System message", "Error: This file is either not a module, or is not supported.");
		goto modLoadError;
	}

	if (!strncmp(h_MOD31.sig, "M.K.", 4) && songTmp.len == 129)
		songTmp.len = 127; // fixes a specific copy of beatwave.mod by Sidewinder

	if (songTmp.len > 128 || (modIsUST && (songTmp.repS == 0 || songTmp.repS > 220)))
	{
		okBoxThreadSafe(0, "System message", "Error: This file is either not a module, or is not supported.");
		goto modLoadError;
	}

	// trim off spaces at end of name
	for (i = 19; i >= 0; i--)
	{
		if (h_MOD31.name[i] == ' ' || h_MOD31.name[i] == 0x1A)
			h_MOD31.name[i] = '\0';
		else
			break;
	}

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

	for (a = 0; a < ai; a++)
	{
		// trim off spaces at end of name
		for (i = 21; i >= 0; i--)
		{
			if (h_MOD31.instr[a].name[i] == ' ' || h_MOD31.instr[a].name[i] == 0x1A)
				h_MOD31.instr[a].name[i] = '\0';
			else
				break;
		}

		memcpy(songTmp.instrName[1+a], h_MOD31.instr[a].name, 22);
		songTmp.instrName[1+a][22] = '\0';
	}

	b = 0;
	for (a = 0; a < 128; a++)
	{
		if (songTmp.songTab[a] > b)
			b = songTmp.songTab[a];
	}

	if (songTmp.len < 255)
		memset(&songTmp.songTab[songTmp.len], 0, 256 - songTmp.len);

	for (a = 0; a <= b; a++)
	{
		pattTmp[a] = (tonTyp *)calloc((MAX_PATT_LEN * TRACK_WIDTH) + 16, 1);
		if (pattTmp[a] == NULL)
		{
			okBoxThreadSafe(0, "System message", "Not enough memory!");
			goto modLoadError;
		}

		pattLensTmp[a] = 64;
		for (j = 0; j < 64; j++)
		{
			for (k = 0; k < songTmp.antChn; k++)
			{
				ton = &pattTmp[a][(j * MAX_VOICES) + k];

				if (fread(bytes, 1, 4, f) != 4)
				{
					okBoxThreadSafe(0, "System message", "Error: This file is either not a module, or is not supported.");
					goto modLoadError;
				}

				// period to note
				period = ((bytes[0] & 0x0F) << 8) | bytes[1];
				for (i = 0; i < 8*12; i++)
				{
					if (period >= amigaPeriod[i])
					{
						ton->ton = (uint8_t)i + 1;
						break;
					}
				}

				ton->instr = (bytes[0] & 0xF0) | (bytes[2] >> 4);
				ton->effTyp = bytes[2] & 0x0F;
				ton->eff = bytes[3];

				if (ton->effTyp == 0xC)
				{
					if (ton->eff > 64)
						ton->eff = 64;
				}
				else if (ton->effTyp == 0x1)
				{
					if (ton->eff == 0)
						ton->effTyp = 0;
				}
				else if (ton->effTyp == 0x2)
				{
					if (ton->eff == 0)
						ton->effTyp = 0;
				}
				else if (ton->effTyp == 0x5)
				{
					if (ton->eff == 0)
						ton->effTyp = 0x3;
				}
				else if (ton->effTyp == 0x6)
				{
					if (ton->eff == 0)
						ton->effTyp = 0x4;
				}
				else if (ton->effTyp == 0xA)
				{
					if (ton->eff == 0)
						ton->effTyp = 0;
				}
				else if (ton->effTyp == 0xE)
				{
					// check if certain E commands are empty
					if (ton->eff == 0x10 || ton->eff == 0x20 || ton->eff == 0xA0 || ton->eff == 0xB0)
					{
						ton->effTyp = 0;
						ton->eff = 0;
					}
				}

				if (modIsUST)
				{
					if (ton->effTyp == 0x01)
					{
						// arpeggio
						ton->effTyp = 0x00;
					}
					else if (ton->effTyp == 0x02)
					{
						// pitch slide
						if (ton->eff & 0xF0)
						{
							ton->effTyp = 0x02;
							ton->eff >>= 4;
						}
						else if (ton->eff & 0x0F)
						{
							ton->effTyp = 0x01;
						}
					}

					// I don't remember why I did this...
					if (ton->effTyp == 0x0D && ton->eff > 0)
						ton->effTyp = 0x0A;
				}

				if (modIsNT && ton->effTyp == 0x0D)
					ton->eff = 0;
			}
		}

		if (tmpPatternEmpty(a))
		{
			if (pattTmp[a] != NULL)
			{
				free(pattTmp[a]);
				pattTmp[a] = NULL;
			}
		}
	}

	for (a = 0; a < ai; a++)
	{
		if (h_MOD31.instr[a].len == 0)
			continue;

		if (!allocateTmpInstr(1 + a))
		{
			okBoxThreadSafe(0, "System message", "Not enough memory!");
			goto modLoadError;
		}

		setNoEnvelope(instrTmp[1 + a]);

		s = &instrTmp[1+a]->samp[0];

		s->len = 2 * SWAP16(h_MOD31.instr[a].len);

		s->pek = (int8_t *)malloc(s->len + LOOP_FIX_LEN);
		if (s->pek == NULL)
		{
			okBoxThreadSafe(0, "System message", "Not enough memory!");
			goto modLoadError;
		}

		memcpy(s->name, songTmp.instrName[1+a], 22);

		if (modIsFEST)
			h_MOD31.instr[a].fine = (32 - (h_MOD31.instr[a].fine & 0x1F)) >> 1;

		if (!modIsUST)
			s->fine = 8 * ((2 * ((h_MOD31.instr[a].fine & 0x0F) ^ 8)) - 16);
		else
			s->fine = 0;

		s->pan = 128;

		s->vol = h_MOD31.instr[a].vol;
		if (s->vol > 64) s->vol = 64;

		s->repS = 2 * SWAP16(h_MOD31.instr[a].repS);

		if (modIsUST)
			s->repS /= 2;

		s->repL = 2 * SWAP16(h_MOD31.instr[a].repL);

		if (s->repL <= 2)
		{
			s->repS = 0;
			s->repL = 0;
		}

		if (s->repS+s->repL > s->len)
		{
			if (s->repS >= s->len)
			{
				s->repS = 0;
				s->repL = 0;
			}
			else
			{
				s->repL = s->len - s->repS;
			}
		}

		if (s->repL > 2)
			s->typ = 1;
		else
			s->typ = 0;

		if (modIsUST && (s->repS > 2 && s->repS < s->len))
		{
			s->len -= s->repS;
			fseek(f, s->repS, SEEK_CUR);
			s->repS = 0;
		}

		if (fread(s->pek, s->len, 1, f) == 1)
		{
			fixSample(s);
		}
		else
		{
			free(s->pek);
			s->pek = NULL;
			s->len = 0;
		}
	}

	songTmp.speed = 125;

	if (modIsUST)
	{
		// repS is initialBPM in UST MODs

		if (songTmp.repS != 120) // 120 is a special case and means 50Hz (125BPM)
		{
			if (songTmp.repS > 239)
				songTmp.repS = 239;

			// convert UST tempo to BPM
			const double dPALAmigaCiaClk = 709379.0;
			ciaPeriod = (240 - songTmp.repS) * 122;
			songTmp.speed = (uint16_t)round((dPALAmigaCiaClk / ciaPeriod) * (125.0 / 50.0));
		}

		songTmp.repS = 0;
	}
	else if (songTmp.repS >= songTmp.len)
	{
		songTmp.repS = 0;
	}

	fclose(f);

	songTmp.initialTempo = songTmp.tempo = 6;

	moduleLoaded = true;
	return true;

modLoadError:
	fclose(f);
	freeTmpModule();
	moduleFailedToLoad = true;
	return false;
}

static uint8_t stmTempoToBPM(uint8_t tempo) // ported from original ST2.3 replayer code
{
	const uint8_t slowdowns[16] = { 140, 50, 25, 15, 10, 7, 6, 4, 3, 3, 2, 2, 2, 2, 1, 1 };
	uint32_t bpm;
	uint16_t hz = 50;

	hz -= ((slowdowns[tempo >> 4] * (tempo & 15)) >> 4); // can and will underflow

	bpm = (uint32_t)round(hz * 2.5);
	return (uint8_t)CLAMP(bpm, 32, 255); // result can be slightly off, but close enough...
}

static bool loadMusicSTM(FILE *f, uint32_t fileLength)
{
	bool check3xx;
	uint8_t typ, tmp8, tempo;
	int16_t i, j, k, ai, ap, tmp;
	uint16_t a;
	int32_t len;
	tonTyp *ton;
	sampleTyp *s;
	songSTMHeaderTyp h_STM;

	rewind(f);

	// start loading STM

	if (fread(&h_STM, 1, sizeof (h_STM), f) != sizeof (h_STM))
		return loadMusicMOD(f, fileLength); // file is not a .stm, try to load as .mod

	if (memcmp(h_STM.sig, "!Scream!", 8) && memcmp(h_STM.sig, "BMOD2STM", 8) &&
		memcmp(h_STM.sig, "WUZAMOD!", 8) && memcmp(h_STM.sig, "SWavePro", 8))
	{
		return loadMusicMOD(f, fileLength); // file is not a .stm, try to load as .mod
	}

	loadedFormat = FORMAT_STM;

	if (h_STM.verMinor == 0 || h_STM.typ != 2)
	{
		okBoxThreadSafe(0, "System message", "Error loading .stm: Incompatible module!");
		goto stmLoadError;
	}

	songTmp.antChn = 4;
	memcpy(songTmp.songTab, h_STM.songTab, 128);

	i = 0;
	while (i < 128 && songTmp.songTab[i] < 99) i++;
	songTmp.len = i + (i == 0);

	if (songTmp.len < 255)
		memset(&songTmp.songTab[songTmp.len], 0, 256 - songTmp.len);

	// trim off spaces at end of name
	for (i = 19; i >= 0; i--)
	{
		if (h_STM.name[i] == ' ' || h_STM.name[i] == 0x1A)
			h_STM.name[i] = '\0';
		else
			break;
	}

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

	tempo = h_STM.tempo;
	if (h_STM.verMinor < 21)
		tempo = ((tempo / 10) << 4) + (tempo % 10);

	if (tempo == 0)
		tempo = 96;

	songTmp.initialTempo = songTmp.tempo = CLAMP(h_STM.tempo >> 4, 1, 31);
	songTmp.speed = stmTempoToBPM(tempo);

	if (h_STM.verMinor > 10)
		songTmp.globVol = MIN(h_STM.vol, 64);

	ap = h_STM.ap;
	for (i = 0; i < ap; i++)
	{
		pattTmp[i] = (tonTyp *)calloc((MAX_PATT_LEN * TRACK_WIDTH) + 16, 1);
		if (pattTmp[i] == NULL)
		{
			okBoxThreadSafe(0, "System message", "Not enough memory!");
			goto stmLoadError;
		}

		pattLensTmp[i] = 64;
		if (fread(pattBuff, 64 * 4 * 4, 1, f) != 1)
		{
			okBoxThreadSafe(0, "System message", "General I/O error during loading!");
			goto stmLoadError;
		}

		a = 0;
		for (j = 0; j < 64; j++)
		{
			for (k = 0; k < 4; k++)
			{
				ton = &pattTmp[i][(j * MAX_VOICES) + k];
				
				if (pattBuff[a] == 254)
				{
					ton->ton = 97;
				}
				else if (pattBuff[a] < 96)
				{
					ton->ton = (12 * (pattBuff[a] >> 4)) + (25 + (pattBuff[a] & 0x0F));
					if (ton->ton > 96)
						ton->ton = 0;
				}
				else
				{
					ton->ton = 0;
				}

				ton->instr = pattBuff[a + 1] >> 3;
				typ = (pattBuff[a + 1] & 7) + ((pattBuff[a + 2] & 0xF0) >> 1);
				if (typ <= 64)
					ton->vol = typ + 0x10;

				ton->eff = pattBuff[a + 3];

				tmp = pattBuff[a + 2] & 0x0F;
				if (tmp == 1)
				{
					ton->effTyp = 15;

					if (h_STM.verMinor < 21)
						ton->eff = ((ton->eff / 10) << 4) + (ton->eff % 10);
					
					ton->eff >>= 4;
				}
				else if (tmp == 3)
				{
					ton->effTyp = 13;
					ton->eff = 0;
				}
				else if (tmp == 2 || (tmp >= 4 && tmp <= 12))
				{
					ton->effTyp = stmEff[tmp];
					if (ton->effTyp == 0xA)
					{
						if (ton->eff & 0x0F)
							ton->eff &= 0x0F;
						else
							ton->eff &= 0xF0;
					}
				}
				else
				{
					ton->eff = 0;
				}

				/* Remove any EDx with no note.
				** SDx with no note in ST3 = does nothing
				** EDx with no note in FT2 = still retriggers */
				if (ton->effTyp == 0xE && (ton->eff & 0xF0) == 0xD0)
				{
					if (ton->ton == 0 || ton->ton == 97)
					{
						ton->eff = 0;
						ton->effTyp = 0;
					}
				}

				if (ton->effTyp > 35)
				{
					ton->effTyp = 0;
					ton->eff = 0;
				}

				a += 4;
			}
		}

		if (tmpPatternEmpty(i))
		{
			if (pattTmp[i] != NULL)
			{
				free(pattTmp[i]);
				pattTmp[i] = NULL;
			}
		}
	}

	ai = 31;
	for (i = 0; i < 31; i++)
	{
		// trim off spaces at end of name
		for (j = 11; j >= 0; j--)
		{
			if (h_STM.instr[i].name[j] == ' ' || h_STM.instr[i].name[j] == 0x1A)
				h_STM.instr[i].name[j] = '\0';
			else
				break;
		}

		memset(&songTmp.instrName[1+i], 0, sizeof (songTmp.instrName[1+i]));
		memcpy(&songTmp.instrName[1+i], h_STM.instr[i].name, 12);

		if (h_STM.instr[i].len != 0 && h_STM.instr[i].reserved1 != 0)
		{
			allocateTmpInstr(1 + i);
			setNoEnvelope(instrTmp[i]);

			s = &instrTmp[1+i]->samp[0];

			s->pek = (int8_t *)malloc(h_STM.instr[i].len + LOOP_FIX_LEN);
			if (s->pek == NULL)
			{
				okBoxThreadSafe(0, "System message", "Not enough memory!");
				goto stmLoadError;
			}

			s->len = h_STM.instr[i].len;
			tuneSample(s, h_STM.instr[i].rate);
			s->vol = h_STM.instr[i].vol;
			s->repS = h_STM.instr[i].repS;
			s->repL = h_STM.instr[i].repE - h_STM.instr[i].repS;
			s->pan = 128;

			if (s->repS < s->len && h_STM.instr[i].repE > s->repS && h_STM.instr[i].repE != 0xFFFF)
			{
				if (s->repS+s->repL > s->len)
					s->repL = s->len - s->repS;

				s->typ = 1;
			}
			else
			{
				s->repS = 0;
				s->repL = 0;
				s->typ  = 0;
			}

			if (s->vol > 64)
				s->vol = 64;

			if (fread(s->pek, s->len, 1, f) != 1)
			{
				okBoxThreadSafe(0, "System message", "General I/O error during loading! Possibly corrupt module?");
				goto stmLoadError;
			}

			fixSample(s);
		}
	}

	// non-FT2: fix overflown 9xx and illegal 3xx

	for (i = 0; i < ap; i++)
	{
		if (pattTmp[i] == NULL)
			continue;

		for (k = 0; k < songTmp.antChn; k++)
		{
			check3xx = false;
			for (j = 0; j < 64; j++)
			{
				ton = &pattTmp[i][(j * MAX_VOICES) + k];

				if (ton->ton > 0 && ton->ton < 97 && ton->effTyp != 0x3)
					check3xx = true;

				if (ton->ton > 0 && ton->ton < 97 && ton->effTyp == 0x3)
					check3xx = false;

				if (check3xx && ton->effTyp == 0x3)
				{
					if (ton->ton == 0 || ton->ton == 97)
					{
						ton->effTyp = 0;
						ton->eff = 0;
					}
				}

				if (ton->effTyp == 0x9 && ton->eff > 0)
				{
					if (ton->instr != 0 && ton->instr <= ai)
					{
						s = &instrTmp[ton->instr]->samp[0];
						len = s->len;

						tmp8 = 0;
						if (len > 0)
						{
							tmp8 = ton->eff;
							if (tmp8 >= len/256)
							{
								if (len/256 < 1)
									tmp8 = 0;
								else
									tmp8 = (uint8_t)((len/256) - 1);
							}
						}

						if (tmp8 > 0)
						{
							ton->eff = tmp8;
						}
						else
						{
							ton->effTyp = 0;
							ton->eff = 0;
						}
					}
					else
					{
						ton->effTyp = 0;
						ton->eff = 0;
					}
				}
			}
		}
	}

	fclose(f);

	moduleLoaded = true;
	return true;

stmLoadError:
	fclose(f);
	freeTmpModule();
	moduleFailedToLoad = true;
	return false;
}

static int8_t countS3MChannels(uint16_t antPtn)
{
	uint8_t j, k, channels;
	int16_t i;
	tonTyp ton;

	channels = 0;
	for (i = 0; i < antPtn; i++)
	{
		if (pattTmp[i] == NULL)
			continue;

		for (j = 0; j < 64; j++)
		{
			for (k = 0; k < MAX_VOICES; k++)
			{
				ton = pattTmp[i][(j * MAX_VOICES) + k];
				if (ton.eff == 0 && ton.effTyp == 0 && ton.instr == 0 && ton.ton == 0 && ton.vol == 0)
					continue;

				if (k > channels)
					channels = k;
			}
		}
	}
	channels++;

	return channels;
}

static bool loadMusicS3M(FILE *f, uint32_t dataLength)
{
	int8_t *tmpSmp;
	bool check3xx, illegalUxx;
	uint8_t ha[2048];
	uint8_t s3mLastDEff[32], s3mLastEEff[32], s3mLastFEff[32];
	uint8_t s3mLastSEff[32], s3mLastJEff[32], s3mLastGInstr[32], tmp8, typ;
	int16_t i, j, k, ai, ap, ver, ii, kk, tmp;
	uint16_t ptnOfs[256];
	int32_t len;
	tonTyp ton, *pattTon;
	sampleTyp *s;
	songS3MHeaderTyp h_S3M;
	songS3MinstrHeaderTyp h_S3MInstr;

	stereoSamplesWarn = false;

	rewind(f);

	// start loading S3M

	if (fread(&h_S3M, 1, sizeof (h_S3M), f) != sizeof (h_S3M))
		return loadMusicSTM(f, dataLength); // not a .s3m, try loading as .stm

	if (memcmp(h_S3M.id, "SCRM", 4))
		return loadMusicSTM(f, dataLength); // not a .s3m, try loading as .stm

	loadedFormat = FORMAT_S3M;

	if (h_S3M.antInstr > MAX_INST || h_S3M.songTabLen > 256 || h_S3M.antPatt > 256 ||
		h_S3M.typ != 16 || h_S3M.ver < 1 || h_S3M.ver > 2)
	{
		okBoxThreadSafe(0, "System message", "Error loading .s3m: Incompatible module!");
		goto s3mLoadError;
	}

	memset(songTmp.songTab, 255, sizeof (songTmp.songTab));
	if (fread(songTmp.songTab, h_S3M.songTabLen, 1, f) != 1)
	{
		okBoxThreadSafe(0, "System message", "General I/O error during loading! Is the file in use?");
		goto s3mLoadError;
	}

	// count real song table entries
	songTmp.len = 256;
	while (songTmp.len > 0 && songTmp.songTab[songTmp.len-1] == 255)
		songTmp.len--;

	if (songTmp.len == 256)
		songTmp.len = 255;

	// remove pattern separators (254)
	k = 0;
	j = 0;
	for (i = 0; i < songTmp.len; i++)
	{
		if (songTmp.songTab[i] != 254)
			songTmp.songTab[j++] = songTmp.songTab[i];
		else
			k++;
	}

	if (k <= songTmp.len)
		songTmp.len -= k;
	else
		songTmp.len = 0;
	
	// clear unused song table entries
	if (songTmp.len < 255)
		memset(&songTmp.songTab[songTmp.len], 0, 256 - songTmp.len);

	songTmp.speed = h_S3M.defTempo;
	if (songTmp.speed < 32)
		songTmp.speed = 32;

	songTmp.tempo = h_S3M.defSpeed;
	if (songTmp.tempo == 0)
		songTmp.tempo = 6;

	if (songTmp.tempo > 31)
		songTmp.tempo = 31;

	songTmp.initialTempo = songTmp.tempo;

	// trim off spaces at end of name
	for (i = 19; i >= 0; i--)
	{
		if (h_S3M.name[i] == ' ' || h_S3M.name[i] == 0x1A)
			h_S3M.name[i] = '\0';
		else
			break;
	}

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

	ap  = h_S3M.antPatt;
	ai  = h_S3M.antInstr;
	ver = h_S3M.ver;

	k = 31;
	while (k >= 0 && h_S3M.chanType[k] >= 16) k--;
	songTmp.antChn = (k + 2) & 254;

	if (fread(ha, ai + ai, 1, f) != 1)
	{
		okBoxThreadSafe(0, "System message", "General I/O error during loading! Is the file in use?");
		goto s3mLoadError;
	}

	if (fread(ptnOfs, ap + ap, 1, f) != 1)
	{
		okBoxThreadSafe(0, "System message", "General I/O error during loading! Is the file in use?");
		goto s3mLoadError;
	}

	// *** PATTERNS ***

	illegalUxx = false;

	k = 0;
	for (i = 0; i < ap; i++)
	{
		if (ptnOfs[i]  == 0)
			continue; // empty pattern

		memset(s3mLastDEff,   0, sizeof (s3mLastDEff));
		memset(s3mLastEEff,   0, sizeof (s3mLastEEff));
		memset(s3mLastFEff,   0, sizeof (s3mLastFEff));
		memset(s3mLastSEff,   0, sizeof (s3mLastSEff));
		memset(s3mLastJEff,   0, sizeof (s3mLastJEff));
		memset(s3mLastGInstr, 0, sizeof (s3mLastGInstr));

		fseek(f, ptnOfs[i] << 4, SEEK_SET);
		if (feof(f))
			continue;

		if (fread(&j, 2, 1, f) != 1)
		{
			okBoxThreadSafe(0, "System message", "General I/O error during loading! Is the file in use?");
			goto s3mLoadError;
		}

		if (j > 0 && j <= 12288)
		{
			pattTmp[i] = (tonTyp *)calloc((MAX_PATT_LEN * TRACK_WIDTH) + 16, 1);
			if (pattTmp[i] == NULL)
			{
				okBoxThreadSafe(0, "System message", "Not enough memory!");
				goto s3mLoadError;
			}

			pattLensTmp[i] = 64;
			if (fread(pattBuff, j, 1, f) != 1)
			{
				okBoxThreadSafe(0, "System message", "General I/O error during loading! Is the file in use?");
				goto s3mLoadError;
			}

			k = 0;
			kk = 0;

			while (k < j && kk < 64)
			{
				typ = pattBuff[k++];

				if (typ == 0)
				{
					kk++;
				}
				else
				{
					ii = typ & 31;

					memset(&ton, 0, sizeof (ton));

					// note and sample
					if (typ & 32)
					{
						ton.ton   = pattBuff[k++];
						ton.instr = pattBuff[k++];

						if (ton.instr > MAX_INST)
							ton.instr = 0;

						     if (ton.ton == 254) ton.ton = 97;
						else if (ton.ton == 255) ton.ton = 0;
						else
						{
							ton.ton = 1 + (ton.ton & 0xF) + (ton.ton >> 4) * 12;
							if (ton.ton > 96)
								ton.ton = 0;
						}
					}

					// volume
					if (typ & 64)
					{
						ton.vol = pattBuff[k++];

						if (ton.vol <= 64)
							ton.vol += 0x10;
						else
							ton.vol = 0;
					}

					// effect
					if (typ & 128)
					{
						ton.effTyp = pattBuff[k++];
						ton.eff = pattBuff[k++];

						if (ton.eff == 0)
						{
							if (ton.effTyp == 4)
							{
								if ((s3mLastDEff[ii] & 0xF0) == 0xF0 || (s3mLastDEff[ii] & 0x0F) == 0x0F)
									ton.eff = s3mLastDEff[ii];
							}
							else if (ton.effTyp ==  5) ton.eff = s3mLastEEff[ii];
							else if (ton.effTyp ==  6) ton.eff = s3mLastFEff[ii];
							else if (ton.effTyp == 10) ton.eff = s3mLastJEff[ii];
							else if (ton.effTyp == 19) ton.eff = s3mLastSEff[ii];
						}
						
						if (ton.eff != 0)
						{
							     if (ton.effTyp ==  4) s3mLastDEff[ii] = ton.eff;
							else if (ton.effTyp ==  5) s3mLastEEff[ii] = ton.eff;
							else if (ton.effTyp ==  6) s3mLastFEff[ii] = ton.eff;
							else if (ton.effTyp == 10) s3mLastJEff[ii] = ton.eff;
							else if (ton.effTyp == 19) s3mLastSEff[ii] = ton.eff;
						}

						switch (ton.effTyp)
						{
							case 1: // A
							{
								ton.effTyp = 0xF;
								if (ton.eff == 0 || ton.eff > 0x1F)
								{
									ton.effTyp = 0;
									ton.eff = 0;
								}
							}
							break;

							case 2: ton.effTyp = 0xB; break; // B
							case 3: ton.effTyp = 0xD; break; // C
							case 4: // D
							{
								     if ((ton.eff & 0xF0) == 0) ton.effTyp = 0xA;
								else if ((ton.eff & 0x0F) == 0) ton.effTyp = 0xA;
								else if ((ton.eff & 0xF0) == 0xF0)
								{
									ton.effTyp = 0xE;
									ton.eff = 0xB0 | (ton.eff & 15);
								}
								else if ((ton.eff & 0x0F) == 0x0F)
								{
									ton.effTyp = 0xE;
									ton.eff = 0xA0 | (ton.eff >> 4);
								}
								else
								{
									ton.effTyp = 0xA;
									if (ton.eff & 0x0F)
										ton.eff &= 0x0F;
									else
										ton.eff &= 0xF0;
								}
							}
							break;

							case 5: // E
							case 6: // F
							{
								if ((ton.eff & 0xF0) >= 0xE0)
								{
									if ((ton.eff & 0xF0) == 0xE0)
										tmp = 0x21;
									else
										tmp = 0xE;

									ton.eff &= 0x0F;

									if (ton.effTyp == 0x05)
										ton.eff |= 0x20;
									else
										ton.eff |= 0x10;

									ton.effTyp = (uint8_t)tmp;
								}
								else
								{
									ton.effTyp = 7 - ton.effTyp;
								}
							}
							break;

							case 7: // G
							{
								// fix illegal slides (to new instruments)
								if (ton.instr != 0 && ton.instr != s3mLastGInstr[ii])
									ton.instr = s3mLastGInstr[ii];

								ton.effTyp = 0x03;
							}
							break;

							case 8:  ton.effTyp = 0x04; break; // H
							case 9:  ton.effTyp = 0x1D; break; // I
							case 10: ton.effTyp = 0x00; break; // J
							case 11: ton.effTyp = 0x06; break; // K
							case 12: ton.effTyp = 0x05; break; // L
							case 15: ton.effTyp = 0x09; break; // O
							case 17: ton.effTyp = 0x1B; break; // Q
							case 18: ton.effTyp = 0x07; break; // R

							case 19: // S
							{
								ton.effTyp = 0xE;
								tmp = ton.eff >> 4;
								ton.eff &= 0x0F;

								     if (tmp == 0x1) ton.eff |= 0x30;
								else if (tmp == 0x2) ton.eff |= 0x50;
								else if (tmp == 0x3) ton.eff |= 0x40;
								else if (tmp == 0x4) ton.eff |= 0x70;
								else if (tmp == 0x08)
								{
									ton.effTyp = 0x8;
									ton.eff <<= 4;
								}
								else if (tmp == 0xB) ton.eff |= 0x60;
								else if (tmp == 0xC) ton.eff |= 0xC0;
								else if (tmp == 0xD) ton.eff |= 0xD0;
								else if (tmp == 0xE) ton.eff |= 0xE0;
								else if (tmp == 0xF) ton.eff |= 0xF0;
								else
								{
									ton.effTyp = 0;
									ton.eff = 0;
								}
							}
							break;

							case 20: // T
							{
								ton.effTyp = 0x0F;
								if (ton.eff < 0x20)
								{
									ton.effTyp = 0;
									ton.eff = 0;
								}
							}
							break;

							case 21: // U
							{
								if ((ton.eff & 0x0F) != 0)
								{
									ton.eff = (ton.eff & 0xF0) | (((ton.eff & 15) + 1) / 4);
									if ((ton.eff & 0x0F) == 0)
									{
										illegalUxx = true;
										ton.effTyp = 0;
										ton.eff = 0;
									}
									else
									{
										illegalUxx = false;
										ton.effTyp = 0x04;
									}
								}
								else
								{
									if (!illegalUxx)
									{
										ton.effTyp = 0x04;
									}
									else
									{
										ton.effTyp = 0;
										ton.eff = 0;
									}
								}
							}
							break;

							case 22: ton.effTyp = 0x10; break; // V

							default:
							{
								ton.effTyp = 0;
								ton.eff = 0;
							}
							break;
						}
					}

					if (ton.instr != 0 && ton.effTyp != 0x3)
						s3mLastGInstr[ii] = ton.instr;

					/* Remove any EDx with no note.
					** SDx with no note in ST3 = does nothing
					** EDx with no note in FT2 = still retriggers */
					if (ton.effTyp == 0xE && (ton.eff & 0xF0) == 0xD0)
					{
						if (ton.ton == 0 || ton.ton == 97)
						{
							ton.effTyp = 0;
							ton.eff = 0;
						}
					}

					pattTmp[i][(kk * MAX_VOICES) + ii] = ton;
				}
			}

			if (tmpPatternEmpty(i))
			{
				if (pattTmp[i] != NULL)
				{
					free(pattTmp[i]);
					pattTmp[i] = NULL;
				}
			}
		}
	}

	// *** SAMPLES ***

	memcpy(ptnOfs, ha, 512);
	for (i = 0; i < ai; i++)
	{
		fseek(f, ptnOfs[i] << 4, SEEK_SET);

		if (fread(&h_S3MInstr, 1, sizeof (h_S3MInstr), f) != sizeof (h_S3MInstr))
		{
			okBoxThreadSafe(0, "System message", "Not enough memory!");
			goto s3mLoadError;
		}

		// trim off spaces at end of name
		for (j = 21; j >= 0; j--)
		{
			if (h_S3MInstr.name[j] == ' ' || h_S3MInstr.name[j] == 0x1A)
				h_S3MInstr.name[j] = '\0';
			else
				break;
		}

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

		if (h_S3MInstr.typ > 1)
		{
			okBoxThreadSafe(0, "System message", "Error loading .s3m: Incompatible module!");
			goto s3mLoadError;
		}
		else if (h_S3MInstr.typ == 1)
		{
			if ((h_S3MInstr.flags & (255-1-2-4)) != 0 || h_S3MInstr.pack != 0)
			{
				okBoxThreadSafe(0, "System message", "Error loading .s3m: Incompatible module!");
				goto s3mLoadError;
			}
			else if (h_S3MInstr.memSeg > 0 && h_S3MInstr.len > 0)
			{
				if (!allocateTmpInstr(1 + i))
				{
					okBoxThreadSafe(0, "System message", "Not enough memory!");
					goto s3mLoadError;
				}

				setNoEnvelope(instrTmp[1 + i]);
				s = &instrTmp[1+i]->samp[0];

				len = h_S3MInstr.len;

				if ((h_S3MInstr.flags & 2) != 0) // stereo
				{
					stereoSamplesWarn = false;
					len *= 2;
				}

				if ((h_S3MInstr.flags & 4) != 0) // 16-bit
					len *= 2;

				tmpSmp = (int8_t *)malloc(len + LOOP_FIX_LEN);
				if (tmpSmp == NULL)
				{
					okBoxThreadSafe(0, "System message", "Not enough memory!");
					goto s3mLoadError;
				}

				memcpy(s->name, h_S3MInstr.name, 21);

				tuneSample(s, h_S3MInstr.c2Spd);

				s->len  = h_S3MInstr.len;
				s->vol  = h_S3MInstr.vol;
				s->repS = h_S3MInstr.repS;
				s->repL = h_S3MInstr.repE - h_S3MInstr.repS;

				// non-FT2: fixes "miracle man.s3m"
				if ((h_S3MInstr.memSeg<<4)+s->len > (int32_t)dataLength)
					s->len = dataLength - (h_S3MInstr.memSeg << 4);

				if (s->repL <= 2 || s->repS+s->repL > s->len)
				{
					s->repS = 0;
					s->repL = 0;
				}

				s->typ = (h_S3MInstr.flags & 1) + ((h_S3MInstr.flags & 4) << 2);

				if (s->repL == 0)
					s->typ &= 16; // turn off loop, keep 16-bit flag only

				if (s->vol > 64)
					s->vol = 64;

				s->pan = 128;

				fseek(f, h_S3MInstr.memSeg << 4, SEEK_SET);

				// non-FT2: fixes "miracle man.s3m"
				if ((h_S3MInstr.memSeg<<4)+len > (int32_t)dataLength)
					len = dataLength - (h_S3MInstr.memSeg << 4);

				if (fread(tmpSmp, len, 1, f) != 1)
				{
					free(tmpSmp);
					okBoxThreadSafe(0, "System message", "General I/O error during loading! Is the file in use?");
					goto s3mLoadError;
				}

				if (ver != 1)
				{
					if ((h_S3MInstr.flags & 4) != 0)
					{
						conv16BitSample(tmpSmp, len, h_S3MInstr.flags & 2);

						s->pek = (int8_t *)malloc((h_S3MInstr.len * 2) + LOOP_FIX_LEN);
						if (s->pek == NULL)
						{
							free(tmpSmp);
							okBoxThreadSafe(0, "System message", "Not enough memory!");
							goto s3mLoadError;
						}

						memcpy(s->pek, tmpSmp, h_S3MInstr.len * 2);

						s->len  *= 2;
						s->repS *= 2;
						s->repL *= 2;
					}
					else
					{
						conv8BitSample(tmpSmp, len, h_S3MInstr.flags & 2);

						s->pek = (int8_t *)malloc(h_S3MInstr.len + LOOP_FIX_LEN);
						if (s->pek == NULL)
						{
							free(tmpSmp);
							okBoxThreadSafe(0, "System message", "Not enough memory!");
							goto s3mLoadError;
						}

						memcpy(s->pek, tmpSmp, h_S3MInstr.len);
					}

					fixSample(s);
				}

				free(tmpSmp);
			}
		}
	}

	if (stereoSamplesWarn)
		okBoxThreadSafe(0, "System message", "Stereo samples were found and will be converted to mono.");

	// non-FT2: fix overflown 9xx and illegal 3xx slides

	for (i = 0; i < ap; i++)
	{
		if (pattTmp[i] == NULL)
			continue;

		for (k = 0; k < songTmp.antChn; k++)
		{
			check3xx = false;
			for (j = 0; j < 64; j++)
			{
				pattTon = &pattTmp[i][(j * MAX_VOICES) + k];

				if (pattTon->ton > 0 && pattTon->ton < 97)
					check3xx = pattTon->effTyp != 0x3;

				if (check3xx && pattTon->effTyp == 0x3)
				{
					if (pattTon->ton == 0 || pattTon->ton == 97)
					{
						pattTon->effTyp = 0;
						pattTon->eff = 0;
					}
				}

				if (pattTon->effTyp == 0x9 && pattTon->eff > 0)
				{
					if (pattTon->instr != 0 && pattTon->instr <= ai)
					{
						s = &instrTmp[pattTon->instr]->samp[0];

						len = s->len;

						tmp8 = 0;
						if (len > 0)
						{
							tmp8 = pattTon->eff;
							if (tmp8 >= len/256)
							{
								if (len/256 < 1)
									tmp8 = 0;
								else
									tmp8 = (uint8_t)((len/256) - 1);
							}
						}

						if (tmp8 > 0)
						{
							pattTon->eff = tmp8;
						}
						else
						{
							pattTon->effTyp = 0;
							pattTon->eff = 0;
						}
					}
					else
					{
						pattTon->effTyp = 0;
						pattTon->eff = 0;
					}
				}
			}
		}
	}

	fclose(f);

	songTmp.antChn = countS3MChannels(ap);

	if (!(config.dontShowAgainFlags & DONT_SHOW_S3M_LOAD_WARNING_FLAG))
		okBoxThreadSafe(6, "System message", "Warning: S3M channel panning is not compatible with FT2!");

	moduleLoaded = true;
	return true;

s3mLoadError:
	fclose(f);
	freeTmpModule();
	moduleFailedToLoad = true;
	return false;
}

static int32_t SDLCALL loadMusicThread(void *ptr)
{
	char tmpText[128];
	int16_t k;
	uint16_t i;
	uint32_t filelength;
	songHeaderTyp h;
	FILE *f;

	(void)ptr;

	stereoSamplesWarn = false;
	linearFreqTable = false;

	if (editor.tmpFilenameU == NULL)
	{
		okBoxThreadSafe(0, "System message", "General I/O error during loading! Is the file in use?");
		moduleFailedToLoad = true;
		return false;
	}

	f = UNICHAR_FOPEN(editor.tmpFilenameU, "rb");
	if (f == NULL)
	{
		okBoxThreadSafe(0, "System message", "General I/O error during loading! Is the file in use?");
		moduleFailedToLoad = true;
		return false;
	}

	fseek(f, 0, SEEK_END);
	filelength = ftell(f);
	rewind(f);

	// start loading
	if (fread(&h, 1, sizeof (h), f) != sizeof (h))
		return loadMusicS3M(f, filelength); // not a .xm file, try to load as .s3m

	if (memcmp(h.sig, "Extended Module: ", 17))
		return loadMusicS3M(f, filelength); // not a .xm file, try to load as .s3m

	loadedFormat = FORMAT_XM;

	if (h.ver < 0x0102 || h.ver > 0x0104)
	{
		fclose(f);

		sprintf(tmpText, "Error loading .xm: Unsupported XM version (v%1d.%1d%1d)",
			'0' + (((h.ver >> 8) & 0x0F) % 10), '0' + (((h.ver >> 4) & 0x0F)) % 10, '0' + ((h.ver & 0x0F)) % 10);
		okBoxThreadSafe(0, "System message", tmpText);

		moduleFailedToLoad = true;
		return false;
	}

	if (h.len > MAX_ORDERS)
	{
		okBoxThreadSafe(0, "System message", "Error loading .xm: The song has more than 256 orders!");
		goto xmLoadError;
	}

	if (h.antPtn > MAX_PATTERNS)
	{
		okBoxThreadSafe(0, "System message", "Error loading .xm: The song has more than 256 patterns!");
		goto xmLoadError;
	}

	if (h.antChn == 0 || h.antChn > MAX_VOICES)
	{
		okBoxThreadSafe(0, "System message", "Error loading .xm: Incompatible amount of channels!");
		goto xmLoadError;
	}

	if (h.antInstrs > MAX_INST)
		okBoxThreadSafe(0, "System message", "This module has over 128 instruments! Only the first 128 will be loaded.");

	fseek(f, 60 + h.headerSize, SEEK_SET);
	if (filelength != 336 && feof(f)) // 336 in length at this point = empty XM
	{
		okBoxThreadSafe(0, "System message", "Error loading .xm: The module is empty!");
		goto xmLoadError;
	}

	// trim off spaces at end of name
	for (k = 19; k >= 0; k--)
	{
		if (h.name[k] == ' ' || h.name[k] == 0x1A)
			h.name[k] = '\0';
		else
			break;
	}

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

	songTmp.len = h.len;
	songTmp.repS = h.repS;
	songTmp.antChn = (uint8_t)h.antChn;
	songTmp.speed = h.defSpeed ? h.defSpeed : 125;
	songTmp.tempo = h.defTempo ? h.defTempo : 6;
	songTmp.ver = h.ver;
	linearFreqTable = h.flags & 1;

	songTmp.speed = CLAMP(songTmp.speed, 32, 255);
	if (songTmp.tempo > 31)
		songTmp.tempo = 31;

	songTmp.initialTempo = songTmp.tempo;

	if (songTmp.globVol > 64)
		songTmp.globVol = 64;

	if (songTmp.len == 0)
		songTmp.len = 1; // songTmp.songTab is already empty
	else
		memcpy(songTmp.songTab, h.songTab, songTmp.len);

	if (songTmp.ver < 0x0104)
	{
		// old FT2 format

		for (i = 1; i <= h.antInstrs; i++)
		{
			if (!loadInstrHeader(f, i))
			{
				okBoxThreadSafe(0, "System message", "Error loading .xm: Either a corrupt or a non-supported module!");
				goto xmLoadError;
			}
		}

		if (!loadPatterns(f, h.antPtn))
		{
			// error message is shown inside loadPattern()
			goto xmLoadError;
		}

		for (i = 1; i <= h.antInstrs; i++)
		{
			if (!loadInstrSample(f, i))
			{
				okBoxThreadSafe(0, "System message", "Not enough memory!");
				goto xmLoadError;
			}
		}
	}
	else
	{
		// current FT2 format

		if (!loadPatterns(f, h.antPtn))
		{
			// error message is shown inside loadPattern()
			goto xmLoadError;
		}

		for (i = 1; i <= h.antInstrs; i++)
		{
			if (!loadInstrHeader(f, i))
			{
				okBoxThreadSafe(0, "System message", "Error loading .xm: Either a corrupt or a non-supported module!");
				goto xmLoadError;
			}

			if (!loadInstrSample(f, i))
			{
				okBoxThreadSafe(0, "System message", "Not enough memory!");
				goto xmLoadError;
			}
		}
	}

	if (stereoSamplesWarn)
		okBoxThreadSafe(0, "System message", "Stereo samples were found and will be converted to mono.");

	fclose(f);

	moduleLoaded = true;
	return true;

xmLoadError:
	fclose(f);
	freeTmpModule();
	moduleFailedToLoad = true;
	return false;
}

void loadMusic(UNICHAR *filenameU)
{
	if (musicIsLoading)
		return;

	mouseAnimOn();

	musicIsLoading = true;
	moduleLoaded = false;
	moduleFailedToLoad = false;
	loadedFormat = FORMAT_NONE;

	UNICHAR_STRCPY(editor.tmpFilenameU, filenameU);

	// clear deprecated pointers from possible last loading session (super important)
	memset(pattTmp,  0, sizeof (pattTmp));
	memset(instrTmp, 0, sizeof (instrTmp));

	// prevent stuck instrument names from previous module
	memset(&songTmp, 0, sizeof (songTmp));

	for (uint32_t i = 0; i < MAX_PATTERNS; i++)
		pattLensTmp[i] = 64;

	thread = SDL_CreateThread(loadMusicThread, NULL, NULL);
	if (thread == NULL)
	{
		editor.loadMusicEvent = EVENT_NONE;
		okBox(0, "System message", "Couldn't create thread!");
		musicIsLoading = false;
		return;
	}

	SDL_DetachThread(thread);
}

bool loadMusicUnthreaded(UNICHAR *filenameU) // for development testing
{
	if (editor.tmpFilenameU == NULL)
		return false;

	// clear deprecated pointers from possible last loading session (super important)
	memset(pattTmp,  0, sizeof (pattTmp));
	memset(instrTmp, 0, sizeof (instrTmp));

	// prevent stuck instrument names from previous module
	memset(&songTmp, 0, sizeof (songTmp));

	for (uint32_t i = 0; i < MAX_PATTERNS; i++)
		pattLensTmp[i] = 64;

	UNICHAR_STRCPY(editor.tmpFilenameU, filenameU);

	loadMusicThread(NULL);
	editor.loadMusicEvent = EVENT_NONE;

	if (moduleLoaded)
	{
		setupLoadedModule();
		return true;
	}

	return false;
}

static void freeTmpModule(void)
{
	uint16_t i;

	// free all patterns
	for (i = 0; i < MAX_PATTERNS; i++)
	{
		if (pattTmp[i] != NULL)
		{
			free(pattTmp[i]);
			pattTmp[i] = NULL;
		}
	}

	// free all samples
	for (i = 1; i <= MAX_INST; i++)
	{
		if (instrTmp[i] != NULL)
		{
			for (uint8_t j = 0; j < MAX_SMP_PER_INST; j++)
			{
				if (instrTmp[i]->samp[j].pek != NULL)
					free(instrTmp[i]->samp[j].pek);
			}

			free(instrTmp[i]);
			instrTmp[i] = NULL;
		}
	}
}

static bool loadInstrHeader(FILE *f, uint16_t i)
{
	int8_t k;
	uint8_t j;
	uint32_t readSize;
	instrHeaderTyp ih;
	sampleTyp *s;

	memset(&ih, 0, INSTR_HEADER_SIZE);

	fread(&ih.instrSize, 4, 1, f);

	readSize = ih.instrSize;
	if (readSize < 4 || readSize > INSTR_HEADER_SIZE)
		readSize = INSTR_HEADER_SIZE;

	// load instrument data into temp buffer
	fread(ih.name, readSize - 4, 1, f); // -4 = skip ih.instrSize

	// FT2 bugfix: skip instrument header data if instrSize is above INSTR_HEADER_SIZE
	if (ih.instrSize > INSTR_HEADER_SIZE)
		fseek(f, ih.instrSize - INSTR_HEADER_SIZE, SEEK_CUR);

	if (ih.antSamp > MAX_SMP_PER_INST)
		return false;

	if (i <= MAX_INST)
	{
		// trim off spaces at end of name
		for (k = 21; k >= 0; k--)
		{
			if (ih.name[k] == ' ' || ih.name[k] == 0x1A)
				ih.name[k] = '\0';
			else
				break;
		}

		memcpy(songTmp.instrName[i], ih.name, 22);
		songTmp.instrName[i][22] = '\0';
	}

	if (ih.antSamp > 0)
	{
		if (i <= MAX_INST)
		{
			if (!allocateTmpInstr(i))
				return false;

			// sanitize stuff for broken/unsupported instruments
			ih.midiProgram = CLAMP(ih.midiProgram, 0, 127);
			ih.midiBend = CLAMP(ih.midiBend, 0, 36);

			if (ih.midiChannel > 15) ih.midiChannel = 15;
			if (ih.mute != 1) ih.mute = 0;
			if (ih.midiOn != 1) ih.midiOn = 0;
			if (ih.vibDepth > 0x0F) ih.vibDepth = 0x0F;
			if (ih.vibRate > 0x3F) ih.vibRate = 0x3F;
			if (ih.vibTyp > 3) ih.vibTyp = 0;

			for (j = 0; j < 96; j++)
			{
				if (ih.ta[j] > 15)
					ih.ta[j] = 15;
			}

			if (ih.envVPAnt > 12) ih.envVPAnt = 12;
			if (ih.envVRepS > 11) ih.envVRepS = 11;
			if (ih.envVRepE > 11) ih.envVRepE = 11;
			if (ih.envVSust > 11) ih.envVSust = 11;
			if (ih.envPPAnt > 12) ih.envPPAnt = 12;
			if (ih.envPRepS > 11) ih.envPRepS = 11;
			if (ih.envPRepE > 11) ih.envPRepE = 11;
			if (ih.envPSust > 11) ih.envPSust = 11;

			for (j = 0; j < 12; j++)
			{
				if ((uint16_t)ih.envVP[j][0] > 32767) ih.envVP[j][0] = 32767;
				if ((uint16_t)ih.envPP[j][0] > 32767) ih.envPP[j][0] = 32767;
				if ((uint16_t)ih.envVP[j][1] > 64) ih.envVP[j][1] = 64;
				if ((uint16_t)ih.envPP[j][1] > 63) ih.envPP[j][1] = 63;
				
			}
			// ----------------------------------------

			// copy over final instrument data from temp buffer
			memcpy(instrTmp[i], ih.ta, INSTR_SIZE);
			instrTmp[i]->antSamp = ih.antSamp;
		}

		if (fread(ih.samp, ih.antSamp * sizeof (sampleHeaderTyp), 1, f) != 1)
			return false;

		if (i <= MAX_INST)
		{
			for (j = 0; j < ih.antSamp; j++)
			{
				s = &instrTmp[i]->samp[j];
				memcpy(s, &ih.samp[j], 12+4+24);
				// s->pek is set up later

				// trim off spaces at end of name
				for (k = 21; k >= 0; k--)
				{
					if (s->name[k] == ' ' || s->name[k] == 0x1A)
						s->name[k] = '\0';
					else
						break;
				}

				// sanitize stuff for malicious modules
				if (s->vol > 64)
					s->vol = 64;

				s->relTon = CLAMP(s->relTon, -48, 71);
			}
		}
	}

	return true;
}

static void checkSampleRepeat(sampleTyp *s)
{
	if (s->repS < 0) s->repS = 0;
	if (s->repL < 0) s->repL = 0;
	if (s->repS > s->len) s->repS = s->len;
	if (s->repS+s->repL > s->len) s->repL = s->len - s->repS;
	if (s->repL == 0) s->typ &= ~3; // non-FT2 fix: force loop off if looplen is 0
}

static bool loadInstrSample(FILE *f, uint16_t i)
{
	int8_t *newPtr;
	uint16_t j, k;
	int32_t l, bytesToSkip;
	sampleTyp *s;

	if (i > MAX_INST || instrTmp[i] == NULL)
		return true; // yes, let's just pretend they got loaded

	k = instrTmp[i]->antSamp;
	for (j = 0; j < k; j++)
	{
		s = &instrTmp[i]->samp[j];

		// if a sample has both forward loop and pingpong loop set, make it pingpong loop only (FT2 behavior)
		if ((s->typ & 3) == 3)
			s->typ &= 0xFE;

		l = s->len;
		if (l <= 0)
		{
			s->pek = NULL;
			s->len = 0;
			s->repL = 0;
			s->repS = 0;

			if (s->typ & 32)
				s->typ &= ~32; // remove stereo flag
		}
		else
		{
			bytesToSkip = 0;
			if (l > MAX_SAMPLE_LEN)
			{
				bytesToSkip = l - MAX_SAMPLE_LEN;
				l = MAX_SAMPLE_LEN;
			}

			s->pek = (int8_t *)malloc(l + LOOP_FIX_LEN);
			if (s->pek == NULL)
				return false;

			if (fread(s->pek, l, 1, f) != 1)
				return false;

			if (bytesToSkip > 0)
				fseek(f, bytesToSkip, SEEK_CUR);

			delta2Samp(s->pek, l, s->typ);

			if (s->typ & 32) // stereo sample - already downmixed to mono in delta2samp()
			{
				s->typ &= ~32; // remove stereo flag

				s->len /= 2;
				s->repL /= 2;
				s->repS /= 2;

				newPtr = (int8_t *)realloc(s->pek, s->len + LOOP_FIX_LEN);
				if (newPtr != NULL)
					s->pek = newPtr;

				stereoSamplesWarn = true;
			}
		}

		// NON-FT2 FIX: Align to 2-byte if 16-bit sample
		if (s->typ & 16)
		{
			s->repL &= 0xFFFFFFFE;
			s->repS &= 0xFFFFFFFE;
			s->len &= 0xFFFFFFFE;
		}

		checkSampleRepeat(s);
		fixSample(s);
	}

	return true;
}

void unpackPatt(uint8_t *dst, uint16_t inn, uint16_t len, uint8_t antChn)
{
	uint8_t note, data, *src;
	int32_t srcEnd, srcIdx;

	if (dst == NULL)
		return;

	src = dst + inn;
	srcEnd = len * TRACK_WIDTH;
	srcIdx = 0;

	for (int32_t i = 0; i < len; i++)
	{
		for (int32_t j = 0; j < antChn; j++)
		{
			if (srcIdx >= srcEnd)
				return; // error!

			note = *src++;
			if (note & 0x80)
			{
				data = 0; if (note & 0x01) data = *src++; *dst++ = data;
				data = 0; if (note & 0x02) data = *src++; *dst++ = data;
				data = 0; if (note & 0x04) data = *src++; *dst++ = data;
				data = 0; if (note & 0x08) data = *src++; *dst++ = data;
				data = 0; if (note & 0x10) data = *src++; *dst++ = data;
			}
			else
			{
				*dst++ = note;
				*dst++ = *src++;
				*dst++ = *src++;
				*dst++ = *src++;
				*dst++ = *src++;
			}

			// if note is overflowing (>97), remove it
			if (*(dst-5) > 97)
				*(dst-5) = 0;

			// non-FT2 security fix: if effect is above 35 (Z), clear effect and parameter
			if (*(dst-2) > 35)
			{
				*(dst-2) = 0;
				*(dst-1) = 0;
			}

			srcIdx += sizeof (tonTyp);
		}

		// skip unused channels
		dst += sizeof (tonTyp) * (MAX_VOICES - antChn);
	}
}

static bool tmpPatternEmpty(uint16_t nr)
{
	uint8_t *scanPtr;
	uint32_t scanLen;

	if (pattTmp[nr] == NULL)
		return true;

	scanPtr = (uint8_t *)pattTmp[nr];
	scanLen = pattLensTmp[nr] * TRACK_WIDTH;

	for (uint32_t i = 0; i < scanLen; i++)
	{
		if (scanPtr[i] != 0)
			return false;
	}

	return true;
}

void clearUnusedChannels(tonTyp *p, int16_t pattLen, uint8_t antChn)
{
	if (p == NULL || antChn >= MAX_VOICES)
		return;

	for (int32_t i = 0; i < pattLen; i++)
		memset(&p[(i * MAX_VOICES) + antChn], 0, sizeof (tonTyp) * (MAX_VOICES - antChn));
}

static bool loadPatterns(FILE *f, uint16_t antPtn)
{
	bool pattLenWarn;
	uint8_t tmpLen, *pattPtr;
	uint16_t i, a;
	patternHeaderTyp ph;

	pattLenWarn = false;

	for (i = 0; i < antPtn; i++)
	{
		if (fread(&ph.patternHeaderSize, 4, 1, f) != 1)
			goto pattCorrupt;

		if (fread(&ph.typ, 1, 1, f) != 1)
			goto pattCorrupt;

		ph.pattLen = 0;
		if (songTmp.ver == 0x0102)
		{
			if (fread(&tmpLen, 1, 1, f) != 1)
				goto pattCorrupt;

			if (fread(&ph.dataLen, 2, 1, f) != 1)
				goto pattCorrupt;

			ph.pattLen = tmpLen + 1; // +1 in v1.02

			if (ph.patternHeaderSize > 8)
				fseek(f, ph.patternHeaderSize - 8, SEEK_CUR);
		}
		else
		{
			if (fread(&ph.pattLen, 2, 1, f) != 1)
				goto pattCorrupt;

			if (fread(&ph.dataLen, 2, 1, f) != 1)
				goto pattCorrupt;

			if (ph.patternHeaderSize > 9)
				fseek(f, ph.patternHeaderSize - 9, SEEK_CUR);
		}

		if (feof(f))
			goto pattCorrupt;

		pattLensTmp[i] = ph.pattLen;

		if (ph.dataLen > 0)
		{
			pattTmp[i] = (tonTyp *)calloc((MAX_PATT_LEN * TRACK_WIDTH) + 16, 1);
			if (pattTmp[i] == NULL)
			{
				okBoxThreadSafe(0, "System message", "Not enough memory!");
				return false;
			}

			a = ph.pattLen * TRACK_WIDTH;

			pattPtr = (uint8_t *)pattTmp[i];
			memset(pattPtr, 0, a);

			if (fread(&pattPtr[a - ph.dataLen], 1, ph.dataLen, f) != ph.dataLen)
				goto pattCorrupt;

			unpackPatt(pattPtr, a - ph.dataLen, ph.pattLen, songTmp.antChn);
			clearUnusedChannels(pattTmp[i], pattLensTmp[i], songTmp.antChn);
		}

		if (tmpPatternEmpty(i))
		{
			if (pattTmp[i] != NULL)
			{
				free(pattTmp[i]);
				pattTmp[i] = NULL;
			}

			pattLensTmp[i] = 64;
		}

		if (pattLensTmp[i] > 256)
		{
			pattLensTmp[i] = 64;
			pattLenWarn = true;
		}
	}

	if (pattLenWarn)
		okBoxThreadSafe(0, "System message", "The module contains pattern lengths above 256! They will be set to 64.");

	return true;

pattCorrupt:
	okBoxThreadSafe(0, "System message", "Error loading .xm: Either a corrupt or a non-supported module!");
	return false;
}

// called from input/video thread after the module was done loading
static void setupLoadedModule(void)
{
	int16_t i;

	lockMixerCallback();

	freeAllInstr();
	freeAllPatterns();

	oldPlayMode = playMode;
	playMode = PLAYMODE_IDLE;
	songPlaying = false;

	editor.currVolEnvPoint = 0;
	editor.currPanEnvPoint = 0;

#ifdef HAS_MIDI
	midi.currMIDIVibDepth = 0;
	midi.currMIDIPitch = 0;
#endif

	memset(editor.keyOnTab, 0, sizeof (editor.keyOnTab));

	// copy over new pattern pointers and lengths
	for (i = 0; i < MAX_PATTERNS; i++)
	{
		patt[i] = pattTmp[i];
		pattLens[i] = pattLensTmp[i];
	}

	// copy over new instruments (includes sample pointers)
	for (i = 1; i <= MAX_INST; i++)
	{
		instr[i] = instrTmp[i];
		fixSampleName(i);
	}

	// copy over song struct
	memcpy(&song, &songTmp, sizeof (songTyp));
	fixSongName();

	// we are the owners of the allocated memory ptrs set by the loader thread now

	// support non-even channel numbers
	if (song.antChn & 1)
	{
		if (++song.antChn > MAX_VOICES)
			song.antChn = MAX_VOICES;
	}

	if (song.repS > song.len)
		song.repS = 0;

	song.globVol = 64;
	song.timer = 1;

	setScrollBarEnd(SB_POS_ED, (song.len - 1) + 5);
	setScrollBarPos(SB_POS_ED, 0, false);

	resetChannels();
	refreshScopes();
	setPos(0, 0, false);
	setSpeed(song.speed);

	editor.tmpPattern = editor.editPattern; // set kludge variable
	editor.speed = song.speed;
	editor.tempo = song.tempo;
	editor.timer = 1;
	editor.globalVol = song.globVol;

	setFrqTab((loadedFormat == FORMAT_XM) ? linearFreqTable : false);
	unlockMixerCallback();

	exitTextEditing();
	updateTextBoxPointers();
	resetChannelOffset();
	updateChanNums();
	resetWavRenderer();
	clearPattMark();
	song.musicTime = 0;
	resetTrimSizes();

	diskOpSetFilename(DISKOP_ITEM_MODULE, editor.tmpFilenameU);

	// redraw top part of screen
	if (editor.ui.extended)
	{
		// first exit extended mode, then re-enter
		togglePatternEditorExtended();
		togglePatternEditorExtended();
	}
	else
	{
		// redraw top screen
		hideTopScreen();
		showTopScreen(true);
	}

	updateSampleEditorSample();
	showBottomScreen(); // redraw bottom screen (also redraws pattern editor)

	if (editor.ui.instEditorShown)
		drawPiano(); // redraw piano now (since if playing = wait for next tick update)

	removeSongModifiedFlag();

	moduleFailedToLoad = false;
	moduleLoaded = false;
	editor.loadMusicEvent = EVENT_NONE;
}

bool handleModuleLoadFromArg(int argc, char **argv)
{
	int32_t filesize;
	uint32_t filenameLen;
	UNICHAR *filenameU, tmpPathU[PATH_MAX + 2];

	// this is crude, we always expect only one parameter, and that it is the module.

	if (argc != 2)
		return false;

#ifdef __APPLE__
	if (argc == 2 && !strncmp(argv[1], "-psn_", 5))
		return false; // OS X < 10.9 passes a -psn_x_xxxxx parameter on double-click launch
#endif

	filenameLen = (uint32_t)strlen(argv[1]);

	filenameU = (UNICHAR *)calloc((filenameLen + 1), sizeof (UNICHAR));
	if (filenameU == NULL)
	{
		okBox(0, "System message", "Not enough memory!");
		return false;
	}

#ifdef _WIN32
	MultiByteToWideChar(CP_UTF8, 0, argv[1], -1, filenameU, filenameLen);
#else
	strcpy(filenameU, argv[1]);
#endif

	// store old path
	UNICHAR_GETCWD(tmpPathU, PATH_MAX);

	// set binary path
	UNICHAR_CHDIR(editor.binaryPathU);

	filesize = getFileSize(filenameU);

	if (filesize == -1) // >2GB
	{
		okBox(0, "System message", "The file is too big and can't be loaded (over 2GB).");
		goto argLoadErr;
	}

	if (filesize >= 128L*1024*1024) // 128MB
	{
		if (okBox(2, "System request", "Are you sure you want to load such a big file?") != 1)
			goto argLoadErr;
	}

	editor.loadMusicEvent = EVENT_LOADMUSIC_ARGV;
	loadMusic(filenameU);
	
	UNICHAR_CHDIR(tmpPathU); // set old path back
	free(filenameU);
	return true;

argLoadErr:
	UNICHAR_CHDIR(tmpPathU); // set old path back
	free(filenameU);
	return false;
}

void loadDroppedFile(char *fullPathUTF8, bool songModifiedCheck)
{
	int32_t fullPathLen, filesize;
	UNICHAR *fullPathU;

	if (editor.ui.sysReqShown || fullPathUTF8 == NULL)
		return;

	fullPathLen = (int32_t)strlen(fullPathUTF8);
	if (fullPathLen == 0)
		return;

	fullPathU = (UNICHAR *)calloc(fullPathLen + 2, sizeof (UNICHAR));
	if (fullPathU == NULL)
	{
		okBox(0, "System message", "Not enough memory!");
		return;
	}

#ifdef _WIN32
	MultiByteToWideChar(CP_UTF8, 0, fullPathUTF8, -1, fullPathU, fullPathLen);
#else
	strcpy(fullPathU, fullPathUTF8);
#endif

	filesize = getFileSize(fullPathU);

	if (filesize == -1) // >2GB
	{
		okBox(0, "System message", "The file is too big and can't be loaded (over 2GB).");
		free(fullPathU);
		return;
	}

	if (filesize >= 128L*1024*1024) // 128MB
	{
		if (okBox(2, "System request", "Are you sure you want to load such a big file?") != 1)
		{
			free(fullPathU);
			return;
		}
	}

	// pass UTF8 to these tests so that we can test file ending in ASCII/ANSI

	if (fileIsInstrument(fullPathUTF8))
	{
		loadInstr(fullPathU);
	}
	else if (fileIsSample(fullPathUTF8))
	{
		loadSample(fullPathU, editor.curSmp, false);
	}
	else
	{
		SDL_RestoreWindow(video.window);

		if (songModifiedCheck && song.isModified)
		{
			// de-minimize window and set focus so that the user sees the message box
			SDL_RestoreWindow(video.window);
			SDL_RaiseWindow(video.window);

			if (!askUnsavedChanges(ASK_TYPE_LOAD_SONG))
			{
				free(fullPathU);
				return;
			}
		}

		editor.loadMusicEvent = EVENT_LOADMUSIC_DRAGNDROP;
		loadMusic(fullPathU);
	}

	free(fullPathU);
}

static void handleOldPlayMode(void)
{
	playMode = oldPlayMode;
	if (oldPlayMode != PLAYMODE_IDLE && oldPlayMode != PLAYMODE_EDIT)
		startPlaying(oldPlayMode, 0);

	songPlaying = (playMode >= PLAYMODE_SONG);
}

// called from input/video thread after module load thread was finished
void handleLoadMusicEvents(void)
{
	if (!moduleLoaded && !moduleFailedToLoad)
		return; // no event to handle

	if (moduleFailedToLoad)
	{
		// module failed to load from loading thread
		musicIsLoading = false;
		moduleFailedToLoad = false;
		moduleLoaded = false;
		editor.loadMusicEvent = EVENT_NONE;
		setMouseBusy(false);
		return;
	}

	if (moduleLoaded)
	{
		// module was successfully loaded from loading thread

		switch (editor.loadMusicEvent)
		{
			// module dragged and dropped *OR* user double clicked a file associated with FT2 clone
			case EVENT_LOADMUSIC_DRAGNDROP:
			{
				setupLoadedModule();
				if (editor.autoPlayOnDrop)
					startPlaying(PLAYMODE_SONG, 0);
				else
					handleOldPlayMode();
			}
			break;

			// filename passed as an exe argument *OR* user double clicked a file associated with FT2 clone
			case EVENT_LOADMUSIC_ARGV:
			{
				setupLoadedModule();
				startPlaying(PLAYMODE_SONG, 0);
			}
			break;

			// module filename pressed in Disk Op.
			case EVENT_LOADMUSIC_DISKOP:
			{
				setupLoadedModule();
				handleOldPlayMode();
			}
			break;

			default: break;
		}

		moduleLoaded = false;
		editor.loadMusicEvent = EVENT_NONE;
		musicIsLoading = false;
		mouseAnimOff();
	}
}