shithub: ft2-clone

Download patch

ref: 898c82f5468058e6d8fdb7d99c6ce9720046871b
parent: a2f19de82ff56c52d445e88a802bee4df1c161e5
author: Olav Sørensen <olav.sorensen@live.no>
date: Sat Feb 8 09:41:55 EST 2020

Pushed v1.08 code

- Critical bugfix: Saved instruments (.xi) would end up being broken!
- Linux bugfix: Loading a song by passing it to the executable's argument from a
  terminal wouldn't work in most cases...
- macOS/Linux bugfix: Don't show ".." directory when you are in root
- Code fix: We don't want our main instrument/sample structs to be packed, only
  the ones used during saving/loading of songs/instruments. This doesn't change
  the behavior of the FT2 clone, but it prevents unaligned pointer access in the
  replayer and other routines.
- Small optimizations to pattern data rendering, those routines are quite slow!
- Updated HOW-TO-COMPILE.txt

--- a/HOW-TO-COMPILE.txt
+++ b/HOW-TO-COMPILE.txt
@@ -22,9 +22,12 @@
     chmod +x make-linux.sh     (only needed once)
     ./make-linux.sh
     
+ Note: If you don't have libstdc++ and/or can't compile rtmidi, try running
+       make-linux-nomidi.sh instead.
+    
  Note: If you want faster audio mixing (for SLOW devices), pass -DLERPMIX
-       to the GCC command line (edit make-linux.sh). This will yield slightly
-       more blurry sound when interpolation is activated, though...
+       to the GCC command line (edit make-linux.sh/make-linux-nomidi.sh). This
+       will lower the resampling interpolation quality, though...
        
  Known issues: Audio recording (sampling) can update VERY slowly or not work at
                all... I have no idea why, it works really well on Windows/maCOS.
--- a/src/ft2_diskop.c
+++ b/src/ft2_diskop.c
@@ -83,7 +83,7 @@
 
 static void setDiskOpItem(uint8_t item);
 
-int32_t getFileSize(UNICHAR *fileName) // returning -1 = filesize over 2GB
+int32_t getFileSize(UNICHAR *fileNameU) // returning -1 = filesize over 2GB
 {
 #ifdef _WIN32
 	FILE *f;
@@ -93,7 +93,7 @@
 	int64_t fSize;
 
 #ifdef _WIN32
-	f = UNICHAR_FOPEN(fileName, "rb");
+	f = UNICHAR_FOPEN(fileNameU, "rb");
 	if (f == NULL)
 		return 0;
 
@@ -101,7 +101,7 @@
 	fSize = _ftelli64(f);
 	fclose(f);
 #else
-	if (stat(fileName, &st) != 0)
+	if (stat(fileNameU, &st) != 0)
 		return 0;
 
 	fSize = (int64_t)(st.st_size);
@@ -273,10 +273,10 @@
 	FReq_PatCurPathU = (UNICHAR *)calloc(PATH_MAX + 2, sizeof (UNICHAR));
 	FReq_TrkCurPathU = (UNICHAR *)calloc(PATH_MAX + 2, sizeof (UNICHAR));
 
-	if (modTmpFName      == NULL || insTmpFName      == NULL || smpTmpFName      == NULL ||
-		patTmpFName      == NULL || trkTmpFName      == NULL || FReq_NameTemp    == NULL ||
-		FReq_ModCurPathU == NULL || FReq_InsCurPathU == NULL || FReq_SmpCurPathU == NULL ||
-		FReq_PatCurPathU == NULL || FReq_TrkCurPathU == NULL)
+	if (modTmpFName == NULL || insTmpFName == NULL || smpTmpFName == NULL || patTmpFName == NULL ||
+		trkTmpFName == NULL || FReq_NameTemp == NULL || FReq_ModCurPathU == NULL ||
+		FReq_InsCurPathU == NULL || FReq_SmpCurPathU == NULL || FReq_PatCurPathU == NULL ||
+		FReq_TrkCurPathU == NULL)
 	{
 		// allocated memory is free'd lateron
 		showErrorMsgBox("Not enough memory!");
@@ -349,9 +349,9 @@
 	SHFILEOPSTRUCTW shfo;
 
 	memset(&shfo, 0, sizeof (shfo));
-	shfo.wFunc  = FO_DELETE;
+	shfo.wFunc = FO_DELETE;
 	shfo.fFlags = FOF_SILENT | FOF_NOERRORUI | FOF_NOCONFIRMATION;
-	shfo.pFrom  = strU;
+	shfo.pFrom = strU;
 
 	return (SHFileOperationW(&shfo) == 0);
 }
@@ -568,12 +568,12 @@
 {
 	int32_t i, len;
 
-	if (p == NULL)
-		return (p);
+	if (p == NULL || p[0] == '\0')
+		return p;
 
 	len = (int32_t)strlen(p);
 	if (len < 2 || p[len-1] == DIR_DELIMITER)
-		return (p);
+		return p;
 
 	// search for last directory delimiter
 	for (i = len - 1; i >= 0; i--)
@@ -583,7 +583,7 @@
 	}
 
 	if (i != 0)
-		p += i + 1; // we found a directory delimiter - skip to the last one
+		p += i+1; // we found a directory delimiter - skip to the last one
 
 	return p;
 }
@@ -594,7 +594,7 @@
 	const char illegalChars[] = "\\/:*?\"<>|";
 	char *ptr;
 
-	if (src == NULL || *src == '\0')
+	if (src == NULL || src[0] == '\0')
 		return;
 
 	// convert illegal characters to space (for making a filename the OS will accept)
@@ -624,7 +624,6 @@
 		default:
 		case DISKOP_ITEM_MODULE:
 		{
-			memset(modTmpFName, 0, PATH_MAX + 1);
 			strcpy(modTmpFName, filename);
 
 			updateCurrSongFilename(); // for window title
@@ -633,31 +632,19 @@
 		break;
 
 		case DISKOP_ITEM_INSTR:
-		{
-			memset(insTmpFName, 0, PATH_MAX + 1);
 			strcpy(insTmpFName, filename);
-		}
 		break;
 
 		case DISKOP_ITEM_SAMPLE:
-		{
-			memset(smpTmpFName, 0, PATH_MAX + 1);
 			strcpy(smpTmpFName, filename);
-		}
 		break;
 
 		case DISKOP_ITEM_PATTERN:
-		{
-			memset(patTmpFName, 0, PATH_MAX + 1);
 			strcpy(patTmpFName, filename);
-		}
 		break;
 
 		case DISKOP_ITEM_TRACK:
-		{
-			memset(trkTmpFName, 0, PATH_MAX + 1);
 			strcpy(trkTmpFName, filename);
-		}
 		break;
 	}
 
@@ -715,10 +702,21 @@
 		}
 		break;
 
-		case DISKOP_ITEM_INSTR:   loadInstr(filenameU); break;
-		case DISKOP_ITEM_SAMPLE:  loadSample(filenameU, editor.curSmp, false); break;
-		case DISKOP_ITEM_PATTERN: loadPattern(filenameU); break;
-		case DISKOP_ITEM_TRACK:   loadTrack(filenameU); break;
+		case DISKOP_ITEM_INSTR:
+			loadInstr(filenameU);
+		break;
+
+		case DISKOP_ITEM_SAMPLE:
+			loadSample(filenameU, editor.curSmp, false);
+		break;
+
+		case DISKOP_ITEM_PATTERN:
+			loadPattern(filenameU);
+		break;
+
+		case DISKOP_ITEM_TRACK:
+			loadTrack(filenameU);
+		break;
 	}
 }
 
@@ -1205,45 +1203,40 @@
 	name = unicharToCp437(nameU, false);
 	if (name == NULL)
 		return true;
+	
+	if (name[0] == '\0')
+		goto skipEntry;
 
 	nameLen = (int32_t)strlen(name);
-	if (nameLen == 0)
-	{
-		free(name);
-		return true;
-	}
 
 	// skip ".name" dirs/files
 	if (nameLen >= 2 && name[0] == '.' && name[1] != '.')
-	{
-		free(name);
-		return true;
-	}
+		goto skipEntry;
 
 	if (isDir)
 	{
 		// skip '.' directory
 		if (nameLen == 1 && name[0] == '.')
+			goto skipEntry;
+
+		// macOS/Linux: skip '..' directory if we're in root
+#ifndef _WIN32
+		if (nameLen == 2 && name[0] == '.' && name[1] == '.')
 		{
-			free(name);
-			return true;
+			if (FReq_CurPathU[0] == '/' && FReq_CurPathU[1] == '\0')
+				goto skipEntry;
 		}
+#endif
 	}
 	else if (!FReq_ShowAllFiles)
 	{
 		extOffset = getExtOffset(name, nameLen);
 		if (extOffset == -1)
-		{
-			free(name);
-			return true;
-		}
+			goto skipEntry;
 
 		extLen = (int32_t)strlen(&name[extOffset]);
 		if (extLen < 3 || extLen > 5)
-		{
-			free(name);
-			return true; // no possibly known extensions to filter out
-		}
+			goto skipEntry; // no possibly known extensions to filter out
 
 		extPtr = &name[extOffset];
 
@@ -1256,10 +1249,7 @@
 				if (extLen == 3)
 				{
 					if (_stricmp(".xm", extPtr) && _stricmp(".ft", extPtr))
-					{
-						free(name);
-						return true; // skip, none of the extensions above
-					}
+						goto skipEntry; // skip, none of the extensions above
 				}
 				else if (extLen == 4)
 				{
@@ -1267,14 +1257,12 @@
 						_stricmp(".s3m", extPtr) && _stricmp(".stm", extPtr) &&
 						_stricmp(".fst", extPtr) && _stricmp(".wav", extPtr))
 					{
-						free(name);
-						return true; // skip, none of the extensions above
+						goto skipEntry; // skip, none of the extensions above
 					}
 				}
 				else
 				{
-					free(name);
-					return true;
+					goto skipEntry;
 				}
 			}
 			break;
@@ -1284,10 +1272,7 @@
 				if (extLen == 3)
 				{
 					if (_stricmp(".xi", extPtr))
-					{
-						free(name);
-						return true; // skip, none of the extensions above
-					}
+						goto skipEntry; // skip, none of the extensions above
 				}
 				else if (extLen == 4)
 				{
@@ -1296,22 +1281,17 @@
 						_stricmp(".smp", extPtr) && _stricmp(".sam", extPtr) &&
 						_stricmp(".aif", extPtr) && _stricmp(".pat", extPtr))
 					{
-						free(name);
-						return true; // skip, none of the extensions above
+						goto skipEntry; // skip, none of the extensions above
 					}
 				}
 				else if (extLen == 5)
 				{
 					if (_stricmp(".aiff", extPtr))
-					{
-						free(name);
-						return true; // skip, not the extension above
-					}
+						goto skipEntry; // skip, not the extension above
 				}
 				else
 				{
-					free(name);
-					return true;
+					goto skipEntry;
 				}
 			}
 			break;
@@ -1325,22 +1305,17 @@
 						_stricmp(".smp", extPtr) && _stricmp(".sam", extPtr) &&
 						_stricmp(".aif", extPtr))
 					{
-						free(name);
-						return true; // skip, none of the extensions above
+						goto skipEntry; // skip, none of the extensions above
 					}
 				}
 				else if (extLen == 5)
 				{
 					if (_stricmp(".aiff", extPtr))
-					{
-						free(name);
-						return true; // skip, not the extension above
-					}
+						goto skipEntry; // skip, not the extension above
 				}
 				else
 				{
-					free(name);
-					return true;
+					goto skipEntry;
 				}
 			}
 			break;
@@ -1350,15 +1325,11 @@
 				if (extLen == 3)
 				{
 					if (_stricmp(".xp", extPtr))
-					{
-						free(name);
-						return true; // skip, not the extension above
-					}
+						goto skipEntry; // skip, not the extension above
 				}
 				else
 				{
-					free(name);
-					return true;
+					goto skipEntry;
 				}
 			}
 			break;
@@ -1368,15 +1339,11 @@
 				if (extLen == 3)
 				{
 					if (_stricmp(".xt", extPtr))
-					{
-						free(name);
-						return true; // skip, not the extension above
-					}
+						goto skipEntry;  // skip, not the extension above
 				}
 				else
 				{
-					free(name);
-					return true;
+					goto skipEntry;
 				}
 			}
 			break;
@@ -1385,6 +1352,10 @@
 
 	free(name);
 	return false; // "Show All Files" mode is enabled, don't skip entry
+
+skipEntry:
+	free(name);
+	return true;
 }
 
 static int8_t findFirst(DirRec *searchRec)
@@ -1927,6 +1898,8 @@
 	// free old buffer
 	freeDirRecBuffer();
 
+	UNICHAR_GETCWD(FReq_CurPathU, PATH_MAX);
+
 	// read first file
 	lastFindFileFlag = findFirst(&tmpBuffer);
 	if (lastFindFileFlag != LFF_DONE && lastFindFileFlag != LFF_SKIP)
@@ -1989,7 +1962,6 @@
 			okBoxThreadSafe(0, "System message", "Not enough memory!");
 	}
 
-	UNICHAR_GETCWD(FReq_CurPathU, PATH_MAX);
 	editor.diskOpReadDone = true;
 
 	setMouseBusy(false);
--- a/src/ft2_diskop.h
+++ b/src/ft2_diskop.h
@@ -21,7 +21,7 @@
 	SMP_SAVE_MODE_WAV = 2
 };
 
-int32_t getFileSize(UNICHAR *fileName);
+int32_t getFileSize(UNICHAR *fileNameU);
 uint8_t getDiskOpItem(void);
 void updateCurrSongFilename(void); // for window title
 char *getCurrSongFilename(void); // for window title
--- a/src/ft2_gui.c
+++ b/src/ft2_gui.c
@@ -49,7 +49,8 @@
 	if (mouse.lastUsedObjectID == OBJECT_ID_NONE)
 	{
 		/* if last object ID is OBJECT_ID_NONE, check if we moved the
-		** sample data loop pins, and unstuck them if so */
+		** sample data loop pins, and unstuck them if so
+		*/
 
 		if (editor.ui.leftLoopPinMoving)
 		{
@@ -311,11 +312,11 @@
 void charOut(uint16_t xPos, uint16_t yPos, uint8_t paletteIndex, char chr)
 {
 	const uint8_t *srcPtr;
-	uint32_t *dstPtr, pixVal;
+	uint32_t *dstPtr, pixVal, tmp;
 
 	assert(xPos < SCREEN_W && yPos < SCREEN_H);
 
-	chr &= 0x7F;
+	chr &= 0x7F; // this is important to get the nordic glyphs in the font
 	if (chr == ' ')
 		return;
 
@@ -327,8 +328,10 @@
 	{
 		for (uint32_t x = 0; x < FONT1_CHAR_W; x++)
 		{
-			if (srcPtr[x])
-				dstPtr[x] = pixVal;
+			// carefully written like this to generate conditional move instructions (font data is hard to predict)
+			tmp = dstPtr[x];
+			if (srcPtr[x] != 0) tmp = pixVal;
+			dstPtr[x] = tmp;
 		}
 
 		srcPtr += FONT1_WIDTH;
@@ -343,7 +346,7 @@
 
 	assert(xPos < SCREEN_W && yPos < SCREEN_H);
 
-	chr &= 0x7F;
+	chr &= 0x7F; // this is important to get the nordic glyphs in the font
 	if (chr == ' ')
 		return;
 
@@ -355,8 +358,8 @@
 
 	for (uint32_t y = 0; y < FONT1_CHAR_H; y++)
 	{
-		for (uint32_t x = 0; x < 7; x++)
-			dstPtr[x] = srcPtr[x] ? fg : bg;
+		for (uint32_t x = 0; x < FONT1_CHAR_W-1; x++)
+			dstPtr[x] = srcPtr[x] ? fg : bg; // compiles nicely into conditional move instructions
 
 		srcPtr += FONT1_WIDTH;
 		dstPtr += SCREEN_W;
@@ -376,32 +379,37 @@
 void charOutShadow(uint16_t xPos, uint16_t yPos, uint8_t paletteIndex, uint8_t shadowPaletteIndex, char chr)
 {
 	const uint8_t *srcPtr;
-	uint32_t *dstPtr, pixVal1, pixVal2;
+	uint32_t *dstPtr1, *dstPtr2, pixVal1, pixVal2, tmp;
 
 	assert(xPos < SCREEN_W && yPos < SCREEN_H);
 
-	chr &= 0x7F;
+	chr &= 0x7F; // this is important to get the nordic glyphs in the font
 	if (chr == ' ')
 		return;
 
 	pixVal1 = video.palette[paletteIndex];
 	pixVal2 = video.palette[shadowPaletteIndex];
-	srcPtr  = &font1Data[chr * FONT1_CHAR_W];
-	dstPtr  = &video.frameBuffer[(yPos * SCREEN_W) + xPos];
+	srcPtr = &font1Data[chr * FONT1_CHAR_W];
+	dstPtr1 = &video.frameBuffer[(yPos * SCREEN_W) + xPos];
+	dstPtr2 = dstPtr1 + (SCREEN_W+1);
 
 	for (uint32_t y = 0; y < FONT1_CHAR_H; y++)
 	{
 		for (uint32_t x = 0; x < FONT1_CHAR_W; x++)
 		{
-			if (srcPtr[x])
-			{
-				dstPtr[x+(SCREEN_W+1)] = pixVal2;
-				dstPtr[x] = pixVal1;
-			}
+			// carefully written like this to generate conditional move instructions (font data is hard to predict)
+			tmp = dstPtr2[x];
+			if (srcPtr[x] != 0) tmp = pixVal2;
+			dstPtr2[x] = tmp;
+
+			tmp = dstPtr1[x];
+			if (srcPtr[x] != 0) tmp = pixVal1;
+			dstPtr1[x] = tmp;
 		}
 
 		srcPtr += FONT1_WIDTH;
-		dstPtr += SCREEN_W;
+		dstPtr1 += SCREEN_W;
+		dstPtr2 += SCREEN_W;
 	}
 }
 
@@ -409,7 +417,7 @@
 {
 	const uint8_t *srcPtr;
 	uint16_t width;
-	uint32_t *dstPtr, pixVal;
+	uint32_t *dstPtr, pixVal, tmp;
 
 	assert(xPos < SCREEN_W && yPos < SCREEN_H);
 
@@ -416,7 +424,7 @@
 	if (xPos > clipX)
 		return;
 
-	chr &= 0x7F;
+	chr &= 0x7F; // this is important to get the nordic glyphs in the font
 	if (chr == ' ')
 		return;
 
@@ -432,8 +440,10 @@
 	{
 		for (uint32_t x = 0; x < width; x++)
 		{
-			if (srcPtr[x])
-				dstPtr[x] = pixVal;
+			// carefully written like this to generate conditional move instructions (font data is hard to predict)
+			tmp = dstPtr[x];
+			if (srcPtr[x] != 0) tmp = pixVal;
+			dstPtr[x] = tmp;
 		}
 
 		srcPtr += FONT1_WIDTH;
@@ -444,11 +454,11 @@
 void bigCharOut(uint16_t xPos, uint16_t yPos, uint8_t paletteIndex, char chr)
 {
 	const uint8_t *srcPtr;
-	uint32_t *dstPtr, pixVal;
+	uint32_t *dstPtr, pixVal, tmp;
 
 	assert(xPos < SCREEN_W && yPos < SCREEN_H);
 
-	chr &= 0x7F;
+	chr &= 0x7F; // this is important to get the nordic glyphs in the font
 	if (chr == ' ')
 		return;
 
@@ -460,8 +470,10 @@
 	{
 		for (uint32_t x = 0; x < FONT2_CHAR_W; x++)
 		{
-			if (srcPtr[x])
-				dstPtr[x] = pixVal;
+			// carefully written like this to generate conditional move instructions (font data is hard to predict)
+			tmp = dstPtr[x];
+			if (srcPtr[x] != 0) tmp = pixVal;
+			dstPtr[x] = tmp;
 		}
 
 		srcPtr += FONT2_WIDTH;
@@ -472,32 +484,37 @@
 static void bigCharOutShadow(uint16_t xPos, uint16_t yPos, uint8_t paletteIndex, uint8_t shadowPaletteIndex, char chr)
 {
 	const uint8_t *srcPtr;
-	uint32_t *dstPtr, pixVal1, pixVal2;
+	uint32_t *dstPtr1, *dstPtr2, pixVal1, pixVal2, tmp;
 
 	assert(xPos < SCREEN_W && yPos < SCREEN_H);
 
-	chr &= 0x7F;
+	chr &= 0x7F; // this is important to get the nordic glyphs in the font
 	if (chr == ' ')
 		return;
 
 	pixVal1 = video.palette[paletteIndex];
 	pixVal2 = video.palette[shadowPaletteIndex];
-	srcPtr  = &font2Data[chr * FONT2_CHAR_W];
-	dstPtr  = &video.frameBuffer[(yPos * SCREEN_W) + xPos];
+	srcPtr = &font2Data[chr * FONT2_CHAR_W];
+	dstPtr1 = &video.frameBuffer[(yPos * SCREEN_W) + xPos];
+	dstPtr2 = dstPtr1 + (SCREEN_W+1);
 
 	for (uint32_t y = 0; y < FONT2_CHAR_H; y++)
 	{
 		for (uint32_t x = 0; x < FONT2_CHAR_W; x++)
 		{
-			if (srcPtr[x])
-			{
-				dstPtr[x+(SCREEN_W+1)] = pixVal2;
-				dstPtr[x] = pixVal1;
-			}
+			// carefully written like this to generate conditional move instructions (font data is hard to predict)
+			tmp = dstPtr2[x];
+			if (srcPtr[x] != 0) tmp = pixVal2;
+			dstPtr2[x] = tmp;
+
+			tmp = dstPtr1[x];
+			if (srcPtr[x] != 0) tmp = pixVal1;
+			dstPtr1[x] = tmp;
 		}
 
 		srcPtr += FONT2_WIDTH;
-		dstPtr += SCREEN_W;
+		dstPtr1 += SCREEN_W;
+		dstPtr2 += SCREEN_W;
 	}
 }
 
@@ -632,7 +649,7 @@
 void hexOut(uint16_t xPos, uint16_t yPos, uint8_t paletteIndex, uint32_t val, uint8_t numDigits)
 {
 	const uint8_t *srcPtr;
-	uint32_t *dstPtr, pixVal;
+	uint32_t *dstPtr, pixVal, tmp;
 
 	assert(xPos < SCREEN_W && yPos < SCREEN_H);
 
@@ -648,8 +665,10 @@
 		{
 			for (uint32_t x = 0; x < FONT6_CHAR_W; x++)
 			{
-				if (srcPtr[x])
-					dstPtr[x] = pixVal;
+				// carefully written like this to generate conditional move instructions (font data is hard to predict)
+				tmp = dstPtr[x];
+				if (srcPtr[x] != 0) tmp = pixVal;
+				dstPtr[x] = tmp;
 			}
 
 			srcPtr += FONT6_WIDTH;
@@ -680,7 +699,7 @@
 		for (uint32_t y = 0; y < FONT6_CHAR_H; y++)
 		{
 			for (uint32_t x = 0; x < FONT6_CHAR_W; x++)
-				dstPtr[x] = srcPtr[x] ? fg : bg;
+				dstPtr[x] = srcPtr[x] ? fg : bg; // compiles nicely into conditional move instructions
 
 			srcPtr += FONT6_WIDTH;
 			dstPtr += SCREEN_W;
@@ -935,13 +954,13 @@
 
 void showTopLeftMainScreen(bool restoreScreens)
 {
-	editor.ui.diskOpShown          = false;
+	editor.ui.diskOpShown = false;
 	editor.ui.sampleEditorExtShown = false;
-	editor.ui.instEditorExtShown   = false;
-	editor.ui.transposeShown       = false;
-	editor.ui.advEditShown         = false;
-	editor.ui.wavRendererShown     = false;
-	editor.ui.trimScreenShown      = false;
+	editor.ui.instEditorExtShown = false;
+	editor.ui.transposeShown = false;
+	editor.ui.advEditShown = false;
+	editor.ui.wavRendererShown = false;
+	editor.ui.trimScreenShown = false;
 
 	editor.ui.scopesShown = true;
 	if (restoreScreens)
@@ -949,13 +968,13 @@
 		switch (editor.ui.oldTopLeftScreen)
 		{
 			default: break;
-			case 1: editor.ui.diskOpShown          = true; break;
+			case 1: editor.ui.diskOpShown = true; break;
 			case 2: editor.ui.sampleEditorExtShown = true; break;
-			case 3: editor.ui.instEditorExtShown   = true; break;
-			case 4: editor.ui.transposeShown       = true; break;
-			case 5: editor.ui.advEditShown         = true; break;
-			case 6: editor.ui.wavRendererShown     = true; break;
-			case 7: editor.ui.trimScreenShown      = true; break;
+			case 3: editor.ui.instEditorExtShown = true; break;
+			case 4: editor.ui.transposeShown = true; break;
+			case 5: editor.ui.advEditShown = true; break;
+			case 6: editor.ui.wavRendererShown = true; break;
+			case 7: editor.ui.trimScreenShown = true; break;
 		}
 
 		if (editor.ui.oldTopLeftScreen > 0)
@@ -1205,10 +1224,22 @@
 {
 	editor.ui.scopesShown = false;
 
-	     if (editor.ui.aboutScreenShown)  showAboutScreen();
-	else if (editor.ui.configScreenShown) showConfigScreen();
-	else if (editor.ui.helpScreenShown)   showHelpScreen();
-	else if (editor.ui.nibblesShown)      showNibblesScreen();
+	if (editor.ui.aboutScreenShown)
+	{
+		showAboutScreen();
+	}
+	else if (editor.ui.configScreenShown)
+	{
+		showConfigScreen();
+	}
+	else if (editor.ui.helpScreenShown)
+	{
+		showHelpScreen();
+	}
+	else if (editor.ui.nibblesShown)
+	{
+		showNibblesScreen();
+	}
 	else
 	{
 		showTopLeftMainScreen(restoreScreens); // updates editor.ui.scopesShown
--- a/src/ft2_header.h
+++ b/src/ft2_header.h
@@ -12,7 +12,7 @@
 #endif
 #include "ft2_replayer.h"
 
-#define PROG_VER_STR "1.07"
+#define PROG_VER_STR "1.08"
 
 // do NOT change these! It will only mess things up...
 
@@ -122,6 +122,7 @@
 	UNICHAR *tmpFilenameU, *tmpInstrFilenameU; // used by saving/loading threads
 	UNICHAR *configFileLocation, *audioDevConfigFileLocation, *midiConfigFileLocation;
 
+	volatile bool mainLoopOngoing;
 	volatile bool busy, scopeThreadMutex, programRunning, wavIsRendering, wavReachedEndFlag;
 	volatile bool updateCurSmp, updateCurInstr, diskOpReadDir, diskOpReadDone, updateWindowTitle;
 	volatile uint8_t loadMusicEvent;
--- a/src/ft2_inst_ed.c
+++ b/src/ft2_inst_ed.c
@@ -18,6 +18,7 @@
 #include "ft2_video.h"
 #include "ft2_sample_loader.h"
 #include "ft2_diskop.h"
+#include "ft2_module_loader.h"
 
 #ifdef _MSC_VER
 #pragma pack(push)
@@ -71,7 +72,7 @@
 	uint8_t ta[96];
 	int16_t envVP[12][2], envPP[12][2];
 	uint8_t envVPAnt, envPPAnt, envVSust, envVRepS, envVRepE, envPSust, envPRepS;
-	uint8_t envPRepE, envVtyp, envPtyp, vibTyp, vibSweep, vibDepth, vibRate;
+	uint8_t envPRepE, envVTyp, envPTyp, vibTyp, vibSweep, vibDepth, vibRate;
 	uint16_t fadeOut;
 	uint8_t midiOn, midiChannel;
 	int16_t midiProgram, midiBend;
@@ -2004,7 +2005,7 @@
 			le = -1;
 		}
 
-		curEnvP  = ins->envVP;
+		curEnvP = ins->envVP;
 		selected = editor.currVolEnvPoint;
 	}
 	else
@@ -2026,7 +2027,7 @@
 			le = -1;
 		}
 
-		curEnvP  = ins->envPP;
+		curEnvP = ins->envPP;
 		selected = editor.currPanEnvPoint;
 	}
 
@@ -2933,11 +2934,13 @@
 static int32_t SDLCALL saveInstrThread(void *ptr)
 {
 	int16_t n;
+	int32_t i;
 	size_t result;
 	FILE *f;
 	instrXIHeaderTyp ih;
-	sampleTyp *srcSmp;
-	sampleHeaderTyp *dstSmpHdr;
+	instrTyp *ins;
+	sampleTyp *s;
+	sampleHeaderTyp *dst;
 
 	(void)ptr;
 
@@ -2948,7 +2951,7 @@
 	}
 
 	n = getUsedSamples(saveInstrNr);
-	if (n == 0)
+	if (n == 0 || instr[saveInstrNr] == NULL)
 	{
 		okBoxThreadSafe(0, "System message", "Instrument is empty!");
 		return false;
@@ -2961,7 +2964,8 @@
 		return false;
 	}
 
-	memset(&ih, 0, sizeof (ih));
+	memset(&ih, 0, sizeof (ih)); // important, also clears reserved stuff
+
 	memcpy(ih.sig, "Extended Instrument: ", 21);
 	memset(ih.name, ' ', 22);
 	memcpy(ih.name, song.instrName[saveInstrNr], strlen(song.instrName[saveInstrNr]));
@@ -2969,17 +2973,53 @@
 	memcpy(ih.progName, PROG_NAME_STR, 20);
 	ih.ver = 0x0102;
 
-	memcpy(ih.ta, &instr[saveInstrNr], INSTR_SIZE);
+	// copy over instrument struct data to instrument header
+	ins = instr[saveInstrNr];
+	memcpy(ih.ta, ins->ta, 96);
+	memcpy(ih.envVP, ins->envVP, 12*2*sizeof(int16_t));
+	memcpy(ih.envPP, ins->envPP, 12*2*sizeof(int16_t));
+	ih.envVPAnt = ins->envVPAnt;
+	ih.envPPAnt = ins->envPPAnt;
+	ih.envVSust = ins->envVSust;
+	ih.envVRepS = ins->envVRepS;
+	ih.envVRepE = ins->envVRepE;
+	ih.envPSust = ins->envPSust;
+	ih.envPRepS = ins->envPRepS;
+	ih.envPRepE = ins->envPRepE;
+	ih.envVTyp = ins->envVTyp;
+	ih.envPTyp = ins->envPTyp;
+	ih.vibTyp = ins->vibTyp;
+	ih.vibSweep = ins->vibSweep;
+	ih.vibDepth = ins->vibDepth;
+	ih.vibRate = ins->vibRate;
+	ih.fadeOut = ins->fadeOut;
+	ih.midiOn = ins->midiOn ? 1 : 0;
+	ih.midiChannel = ins->midiChannel;
+	ih.midiProgram = ins->midiProgram;
+	ih.midiBend = ins->midiBend;
+	ih.mute = ins->mute ? 1 : 0;
 	ih.antSamp = n;
 
-	for (int16_t i = 0; i < n; i++)
+	// copy over sample struct datas to sample headers
+	for (i = 0; i < n; i++)
 	{
-		srcSmp = &instr[saveInstrNr]->samp[i];
-		dstSmpHdr = &ih.samp[i];
+		s = &instr[saveInstrNr]->samp[i];
+		dst = &ih.samp[i];
 
-		memcpy(&dstSmpHdr->len, &srcSmp->len, 12+4+2 + strlen(srcSmp->name));
-		if (srcSmp->pek == NULL)
-			dstSmpHdr->len = 0;
+		dst->len = s->len;
+		dst->repS = s->repS;
+		dst->repL = s->repL;
+		dst->vol = s->vol;
+		dst->fine = s->fine;
+		dst->typ = s->typ;
+		dst->pan = s->pan;
+		dst->relTon = s->relTon;
+
+		dst->nameLen = (uint8_t)strlen(s->name);
+		memcpy(dst->name, s->name, 22);
+
+		if (s->pek == NULL)
+			dst->len = 0;
 	}
 
 	result = fwrite(&ih, INSTR_XI_HEADER_SIZE + (ih.antSamp * sizeof (sampleHeaderTyp)), 1, f);
@@ -2991,20 +3031,20 @@
 	}
 
 	pauseAudio();
-	for (int16_t i = 0; i < n; i++)
+	for (i = 0; i < n; i++)
 	{
-		srcSmp = &instr[saveInstrNr]->samp[i];
-		if (srcSmp->pek != NULL)
+		s = &instr[saveInstrNr]->samp[i];
+		if (s->pek != NULL && s->len > 0)
 		{
-			restoreSample(srcSmp);
-			samp2Delta(srcSmp->pek, srcSmp->len, srcSmp->typ);
+			restoreSample(s);
+			samp2Delta(s->pek, s->len, s->typ);
 
-			result = fwrite(srcSmp->pek, 1, srcSmp->len, f);
+			result = fwrite(s->pek, 1, s->len, f);
 
-			delta2Samp(srcSmp->pek, srcSmp->len, srcSmp->typ);
-			fixSample(srcSmp);
+			delta2Samp(s->pek, s->len, s->typ);
+			fixSample(s);
 
-			if (result != (size_t)srcSmp->len) // write not OK
+			if (result != (size_t)s->len) // write not OK
 			{
 				resumeAudio();
 				fclose(f);
@@ -3044,10 +3084,7 @@
 
 static int16_t getPATNote(int32_t freq)
 {
-	double dFreq;
-
-	dFreq = ((log(freq / (440.0 * 1000.0)) / M_LN2) * 12.0) + 48.0 + 9.0;
-	return (int16_t)round(dFreq);
+	return (int16_t)round(((log(freq / (440.0 * 1000.0)) / M_LN2) * 12.0) + 48.0 + 9.0);
 }
 
 static int32_t SDLCALL loadInstrThread(void *ptr)
@@ -3055,11 +3092,13 @@
 	bool stereoWarning;
 	int8_t *newPtr;
 	int16_t a, b;
+	int32_t i, j;
 	double dFreq;
 	FILE *f;
 	instrXIHeaderTyp ih;
 	instrPATHeaderTyp ih_PAT;
 	instrPATWaveHeaderTyp ih_PATWave;
+	sampleHeaderTyp *src;
 	sampleTyp *s;
 	instrTyp *ins;
 
@@ -3093,14 +3132,16 @@
 			goto loadDone;
 		}
 
-		if (ih.ver == 0x0101)
+		if (ih.ver == 0x0101) // not even FT2.01 can save old v1.01 .XI files, so I have no way to verify this.
 		{
-			fseek(f, -2 - 15 - 1 - 2, SEEK_CUR);
+			fseek(f, -20, SEEK_CUR);
 			ih.antSamp = ih.midiProgram;
-			memset(&ih.midiProgram, 0, 2+15+1+2);
+			ih.midiProgram = 0;
+			ih.midiBend = 0;
+			ih.mute = false;
 		}
 
-		if (ih.antSamp > 16)
+		if (ih.antSamp > MAX_SMP_PER_INST)
 		{
 			okBoxThreadSafe(0, "System message", "Incompatible instrument!");
 			goto loadDone;
@@ -3122,44 +3163,67 @@
 				goto loadDone;
 			}
 
+			// copy instrument header elements to our instrument struct
+
+			ins = instr[editor.curInstr];
+			memcpy(ins->ta, ih.ta, 96);
+			memcpy(ins->envVP, ih.envVP, 12*2*sizeof(int16_t));
+			memcpy(ins->envPP, ih.envPP, 12*2*sizeof(int16_t));
+			ins->envVPAnt = ih.envVPAnt;
+			ins->envPPAnt = ih.envPPAnt;
+			ins->envVSust = ih.envVSust;
+			ins->envVRepS = ih.envVRepS;
+			ins->envVRepE = ih.envVRepE;
+			ins->envPSust = ih.envPSust;
+			ins->envPRepS = ih.envPRepS;
+			ins->envPRepE = ih.envPRepE;
+			ins->envVTyp = ih.envVTyp;
+			ins->envPTyp = ih.envPTyp;
+			ins->vibTyp = ih.vibTyp;
+			ins->vibSweep = ih.vibSweep;
+			ins->vibDepth = ih.vibDepth;
+			ins->vibRate = ih.vibRate;
+			ins->fadeOut = ih.fadeOut;
+			ins->midiOn = (ih.midiOn > 0) ? true : false;
+			ins->midiChannel = ih.midiChannel;
+			ins->midiProgram = ih.midiProgram;
+			ins->midiBend = ih.midiBend;
+			ins->mute = (ih.mute > 0) ? true : false;
+			ins->antSamp = ih.antSamp; // used in loadInstrSample()
+
 			// sanitize stuff for broken/unsupported instruments
-			ih.midiProgram = CLAMP(ih.midiProgram, 0, 127);
-			ih.midiBend = CLAMP(ih.midiBend, 0, 36);
+			ins->midiProgram = CLAMP(ins->midiProgram, 0, 127);
+			ins->midiBend = CLAMP(ins->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;
+			if (ins->midiChannel > 15) ins->midiChannel = 15;
+			if (ins->vibDepth > 0x0F) ins->vibDepth = 0x0F;
+			if (ins->vibRate > 0x3F) ins->vibRate = 0x3F;
+			if (ins->vibTyp > 3) ins->vibTyp = 0;
 
-			for (int16_t i = 0; i < 96; i++)
+			for (i = 0; i < 96; i++)
 			{
-				if (ih.ta[i] > 15)
-					ih.ta[i] = 15;
+				if (ins->ta[i] > 15)
+					ins->ta[i] = 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;
+			if (ins->envVPAnt > 12) ins->envVPAnt = 12;
+			if (ins->envVRepS > 11) ins->envVRepS = 11;
+			if (ins->envVRepE > 11) ins->envVRepE = 11;
+			if (ins->envVSust > 11) ins->envVSust = 11;
+			if (ins->envPPAnt > 12) ins->envPPAnt = 12;
+			if (ins->envPRepS > 11) ins->envPRepS = 11;
+			if (ins->envPRepE > 11) ins->envPRepE = 11;
+			if (ins->envPSust > 11) ins->envPSust = 11;
 
-			for (int16_t i = 0; i < 12; i++)
+			for (i = 0; i < 12; i++)
 			{
-				if ((uint16_t)ih.envVP[i][0] > 32767) ih.envVP[i][0] = 32767;
-				if ((uint16_t)ih.envPP[i][0] > 32767) ih.envPP[i][0] = 32767;
-				if ((uint16_t)ih.envVP[i][1] > 64) ih.envVP[i][1] = 64;
-				if ((uint16_t)ih.envPP[i][1] > 63) ih.envPP[i][1] = 63;
+				if ((uint16_t)ins->envVP[i][0] > 32767) ins->envVP[i][0] = 32767;
+				if ((uint16_t)ins->envPP[i][0] > 32767) ins->envPP[i][0] = 32767;
+				if ((uint16_t)ins->envVP[i][1] > 64) ins->envVP[i][1] = 64;
+				if ((uint16_t)ins->envPP[i][1] > 63) ins->envPP[i][1] = 63;
+				
 			}
 
-			// ----------------------------------------
-
-			memcpy(instr[editor.curInstr]->ta, ih.ta, INSTR_SIZE);
-
 			if (fread(ih.samp, sizeof (sampleHeaderTyp) * ih.antSamp, 1, f) != 1)
 			{
 				freeInstr(editor.curInstr);
@@ -3168,23 +3232,50 @@
 				goto loadDone;
 			}
 
-			for (int16_t i = 0; i < ih.antSamp; i++)
-				memcpy(&instr[editor.curInstr]->samp[i], &ih.samp[i], 12 + 4 + 24);
+			for (i = 0; i < ih.antSamp; i++)
+			{
+				s = &instr[editor.curInstr]->samp[i];
+				src = &ih.samp[i];
+
+				// copy sample header elements to our sample struct
+
+				s->len = src->len;
+				s->repS = src->repS;
+				s->repL = src->repL;
+				s->vol = src->vol;
+				s->fine = src->fine;
+				s->typ = src->typ;
+				s->pan = src->pan;
+				s->relTon = src->relTon;
+				memcpy(s->name, src->name, 22);
+				s->name[22] = '\0';
+
+				// dst->pek is set up later
+
+				// trim off spaces at end of name
+				for (j = 21; j >= 0; j--)
+				{
+					if (s->name[j] == ' ' || s->name[j] == 0x1A)
+						s->name[j] = '\0';
+					else
+						break;
+				}
+
+				// sanitize stuff broken/unsupported samples
+				if (s->vol > 64)
+					s->vol = 64;
+
+				s->relTon = CLAMP(s->relTon, -48, 71);
+			}
 		}
 
-		for (int16_t i = 0; i < ih.antSamp; i++)
+		for (i = 0; i < ih.antSamp; i++)
 		{
 			s = &instr[editor.curInstr]->samp[i];
 
-			// sanitize stuff for malicious modules
-			if (s->vol > 64)
-				s->vol = 64;
-
-			s->relTon = CLAMP(s->relTon, -48, 71);
-
-			// if a sample has both forward loop and pingpong loop set, make it pingpong loop only (FT2 behavior)
+			// if a sample has both forward loop and pingpong loop set, make it pingpong loop only (FT2 mixer behavior)
 			if ((s->typ & 3) == 3)
-				s->typ = 2;
+				s->typ &= 0xFE;
 
 			if (s->len > 0)
 			{
@@ -3223,6 +3314,7 @@
 					stereoWarning = true;
 				}
 
+				checkSampleRepeat(s);
 				fixSample(s);
 			}
 		}
@@ -3256,7 +3348,7 @@
 			memset(song.instrName[editor.curInstr], 0, 22 + 1);
 			memcpy(song.instrName[editor.curInstr], ih_PAT.instrName, 16);
 
-			for (int16_t i = 0; i < ih_PAT.antSamp; i++)
+			for (i = 0; i < ih_PAT.antSamp; i++)
 			{
 				s = &instr[editor.curInstr]->samp[i];
 				ins = instr[editor.curInstr];
@@ -3281,8 +3373,14 @@
 				if (i == 0)
 				{
 					ins->vibSweep = ih_PATWave.vibSweep;
-					ins->vibRate  = ih_PATWave.vibRate  / 2; if (ins->vibRate  > 0x3F) ins->vibRate  = 0x3F;
-					ins->vibDepth = ih_PATWave.vibDepth / 2; if (ins->vibDepth > 0x0F) ins->vibDepth = 0x0F;
+
+					ins->vibRate = ih_PATWave.vibRate / 2;
+					if (ins->vibRate > 0x3F)
+						ins->vibRate = 0x3F;
+
+					ins->vibDepth = ih_PATWave.vibDepth / 2;
+					if (ins->vibDepth > 0x0F)
+						ins->vibDepth = 0x0F;
 				}
 
 				s = &instr[editor.curInstr]->samp[i];
@@ -3298,7 +3396,7 @@
 						s->typ |= 1; // forward loop
 				}
 
-				s->pan = (ih_PATWave.pan == 8) ? 128 : (ih_PATWave.pan * 17); // FT2 does <<4 here, I don't like that!
+				s->pan = ih_PATWave.pan << 4;
 				s->len = ih_PATWave.waveSize;
 
 				s->repS = ih_PATWave.repS;
@@ -3323,13 +3421,16 @@
 				dFreq = round((1.0 + ih_PATWave.fineTune / 512.0) * ih_PATWave.sampleRate);
 				tuneSample(s, (int32_t)dFreq);
 
-				s->relTon -= (int8_t)(getPATNote(ih_PATWave.rootFrq) - (12 * 3));
-				s->relTon  = CLAMP(s->relTon, -48, 71);
+				s->relTon -= (int8_t)(getPATNote(ih_PATWave.rootFrq) - (12*3));
+				s->relTon = CLAMP(s->relTon, -48, 71);
 
-				a = getPATNote(ih_PATWave.lowFrq);   a = CLAMP(a, 0, 95);
-				b = getPATNote(ih_PATWave.highFreq); b = CLAMP(b, 0, 95);
+				a = getPATNote(ih_PATWave.lowFrq);
+				a = CLAMP(a, 0, 95);
 
-				for (int16_t j = a; j <= b; j++)
+				b = getPATNote(ih_PATWave.highFreq);
+				b = CLAMP(b, 0, 95);
+
+				for (j = a; j <= b; j++)
 					ins->ta[j] = (uint8_t)i;
 
 				if (fread(s->pek, ih_PATWave.waveSize, 1, f) != 1)
--- a/src/ft2_main.c
+++ b/src/ft2_main.c
@@ -221,6 +221,7 @@
 	setupWaitVBL();
 	handleModuleLoadFromArg(argc, argv);
 
+	editor.mainLoopOngoing = true;
 	while (editor.programRunning)
 	{
 		beginFPSCounter();
--- a/src/ft2_module_loader.c
+++ b/src/ft2_module_loader.c
@@ -10,7 +10,6 @@
 #include <unistd.h>
 #endif
 #include "ft2_header.h"
-#include "ft2_audio.h"
 #include "ft2_config.h"
 #include "ft2_scopes.h"
 #include "ft2_trim.h"
@@ -22,7 +21,6 @@
 #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"
@@ -129,12 +127,13 @@
 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);
 
+void checkSampleRepeat(sampleTyp *s);
+
 // ft2_replayer.c
 extern const char modSig[32][5];
 extern const uint16_t amigaPeriod[12*8];
@@ -162,7 +161,7 @@
 	return true;
 }
 
-static bool loadMusicMOD(FILE *f, uint32_t fileLength)
+static bool loadMusicMOD(FILE *f, uint32_t fileLength, bool fromExternalThread)
 {
 	char ID[16];
 	bool modIsUST, modIsFEST, modIsNT;
@@ -173,7 +172,10 @@
 	sampleTyp *s;
 	songMOD31HeaderTyp h_MOD31;
 	songMOD15HeaderTyp h_MOD15;
+	int16_t (*showMsg)(int16_t, const char *, const char *);
 
+	showMsg = fromExternalThread ? okBoxThreadSafe : okBox;
+
 	// start loading MOD
 
 	loadedFormat = FORMAT_MOD;
@@ -187,7 +189,7 @@
 	// 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!");
+		showMsg(0, "System message", "Error: Impulse Tracker modules are not supported!");
 		goto modLoadError;
 	}
 
@@ -194,19 +196,19 @@
 	// 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!");
+		showMsg(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.");
+		showMsg(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.");
+		showMsg(0, "System message", "Error: This file is either not a module, or is not supported.");
 		goto modLoadError;
 	}
 
@@ -253,7 +255,7 @@
 	// unsupported MOD
 	if (j == -1)
 	{
-		okBoxThreadSafe(0, "System message", "Error: This file is either not a module, or is not supported.");
+		showMsg(0, "System message", "Error: This file is either not a module, or is not supported.");
 		goto modLoadError;
 	}
 
@@ -262,7 +264,7 @@
 		modIsUST = false;
 		if (fileLength < sizeof (h_MOD31))
 		{
-			okBoxThreadSafe(0, "System message", "Error: This file is either not a module, or is not supported.");
+			showMsg(0, "System message", "Error: This file is either not a module, or is not supported.");
 			goto modLoadError;
 		}
 
@@ -277,7 +279,7 @@
 		modIsUST = true;
 		if (fileLength < sizeof (h_MOD15))
 		{
-			okBoxThreadSafe(0, "System message", "Error: This file is either not a module, or is not supported.");
+			showMsg(0, "System message", "Error: This file is either not a module, or is not supported.");
 			goto modLoadError;
 		}
 
@@ -284,7 +286,7 @@
 		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.");
+			showMsg(0, "System message", "Error: This file is either not a module, or is not supported.");
 			goto modLoadError;
 		}
 
@@ -297,7 +299,7 @@
 
 	if (songTmp.antChn == 0 || songTmp.len < 1)
 	{
-		okBoxThreadSafe(0, "System message", "Error: This file is either not a module, or is not supported.");
+		showMsg(0, "System message", "Error: This file is either not a module, or is not supported.");
 		goto modLoadError;
 	}
 
@@ -306,7 +308,7 @@
 
 	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.");
+		showMsg(0, "System message", "Error: This file is either not a module, or is not supported.");
 		goto modLoadError;
 	}
 
@@ -352,7 +354,7 @@
 		pattTmp[a] = (tonTyp *)calloc((MAX_PATT_LEN * TRACK_WIDTH) + 16, 1);
 		if (pattTmp[a] == NULL)
 		{
-			okBoxThreadSafe(0, "System message", "Not enough memory!");
+			showMsg(0, "System message", "Not enough memory!");
 			goto modLoadError;
 		}
 
@@ -365,7 +367,7 @@
 
 				if (fread(bytes, 1, 4, f) != 4)
 				{
-					okBoxThreadSafe(0, "System message", "Error: This file is either not a module, or is not supported.");
+					showMsg(0, "System message", "Error: This file is either not a module, or is not supported.");
 					goto modLoadError;
 				}
 
@@ -472,7 +474,7 @@
 
 		if (!allocateTmpInstr(1 + a))
 		{
-			okBoxThreadSafe(0, "System message", "Not enough memory!");
+			showMsg(0, "System message", "Not enough memory!");
 			goto modLoadError;
 		}
 
@@ -485,7 +487,7 @@
 		s->pek = (int8_t *)malloc(s->len + LOOP_FIX_LEN);
 		if (s->pek == NULL)
 		{
-			okBoxThreadSafe(0, "System message", "Not enough memory!");
+			showMsg(0, "System message", "Not enough memory!");
 			goto modLoadError;
 		}
 
@@ -604,7 +606,7 @@
 	return (uint8_t)CLAMP(bpm, 32, 255); // result can be slightly off, but close enough...
 }
 
-static bool loadMusicSTM(FILE *f, uint32_t fileLength)
+static bool loadMusicSTM(FILE *f, uint32_t fileLength, bool fromExternalThread)
 {
 	bool check3xx;
 	uint8_t typ, tmp8, tempo;
@@ -614,18 +616,21 @@
 	tonTyp *ton;
 	sampleTyp *s;
 	songSTMHeaderTyp h_STM;
+	int16_t (*showMsg)(int16_t, const char *, const char *);
 
+	showMsg = fromExternalThread ? okBoxThreadSafe : okBox;
+
 	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
+		return loadMusicMOD(f, fileLength, fromExternalThread); // 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
+		return loadMusicMOD(f, fileLength, fromExternalThread); // file is not a .stm, try to load as .mod
 	}
 
 	loadedFormat = FORMAT_STM;
@@ -632,7 +637,7 @@
 
 	if (h_STM.verMinor == 0 || h_STM.typ != 2)
 	{
-		okBoxThreadSafe(0, "System message", "Error loading .stm: Incompatible module!");
+		showMsg(0, "System message", "Error loading .stm: Incompatible module!");
 		goto stmLoadError;
 	}
 
@@ -677,7 +682,7 @@
 		pattTmp[i] = (tonTyp *)calloc((MAX_PATT_LEN * TRACK_WIDTH) + 16, 1);
 		if (pattTmp[i] == NULL)
 		{
-			okBoxThreadSafe(0, "System message", "Not enough memory!");
+			showMsg(0, "System message", "Not enough memory!");
 			goto stmLoadError;
 		}
 
@@ -684,7 +689,7 @@
 		pattLensTmp[i] = 64;
 		if (fread(pattBuff, 64 * 4 * 4, 1, f) != 1)
 		{
-			okBoxThreadSafe(0, "System message", "General I/O error during loading!");
+			showMsg(0, "System message", "General I/O error during loading!");
 			goto stmLoadError;
 		}
 
@@ -805,7 +810,7 @@
 			s->pek = (int8_t *)malloc(h_STM.instr[i].len + LOOP_FIX_LEN);
 			if (s->pek == NULL)
 			{
-				okBoxThreadSafe(0, "System message", "Not enough memory!");
+				showMsg(0, "System message", "Not enough memory!");
 				goto stmLoadError;
 			}
 
@@ -835,7 +840,7 @@
 
 			if (fread(s->pek, s->len, 1, f) != 1)
 			{
-				okBoxThreadSafe(0, "System message", "General I/O error during loading! Possibly corrupt module?");
+				showMsg(0, "System message", "General I/O error during loading! Possibly corrupt module?");
 				goto stmLoadError;
 			}
 
@@ -954,7 +959,7 @@
 	return channels;
 }
 
-static bool loadMusicS3M(FILE *f, uint32_t dataLength)
+static bool loadMusicS3M(FILE *f, uint32_t dataLength, bool fromExternalThread)
 {
 	int8_t *tmpSmp;
 	bool check3xx, illegalUxx;
@@ -968,7 +973,10 @@
 	sampleTyp *s;
 	songS3MHeaderTyp h_S3M;
 	songS3MinstrHeaderTyp h_S3MInstr;
+	int16_t (*showMsg)(int16_t, const char *, const char *);
 
+	showMsg = fromExternalThread ? okBoxThreadSafe : okBox;
+
 	stereoSamplesWarn = false;
 
 	rewind(f);
@@ -976,10 +984,10 @@
 	// 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
+		return loadMusicSTM(f, dataLength, fromExternalThread); // 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
+		return loadMusicSTM(f, dataLength, fromExternalThread); // not a .s3m, try loading as .stm
 
 	loadedFormat = FORMAT_S3M;
 
@@ -986,7 +994,7 @@
 	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!");
+		showMsg(0, "System message", "Error loading .s3m: Incompatible module!");
 		goto s3mLoadError;
 	}
 
@@ -993,7 +1001,7 @@
 	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?");
+		showMsg(0, "System message", "General I/O error during loading! Is the file in use?");
 		goto s3mLoadError;
 	}
 
@@ -1060,13 +1068,13 @@
 
 	if (fread(ha, ai + ai, 1, f) != 1)
 	{
-		okBoxThreadSafe(0, "System message", "General I/O error during loading! Is the file in use?");
+		showMsg(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?");
+		showMsg(0, "System message", "General I/O error during loading! Is the file in use?");
 		goto s3mLoadError;
 	}
 
@@ -1093,7 +1101,7 @@
 
 		if (fread(&j, 2, 1, f) != 1)
 		{
-			okBoxThreadSafe(0, "System message", "General I/O error during loading! Is the file in use?");
+			showMsg(0, "System message", "General I/O error during loading! Is the file in use?");
 			goto s3mLoadError;
 		}
 
@@ -1102,7 +1110,7 @@
 			pattTmp[i] = (tonTyp *)calloc((MAX_PATT_LEN * TRACK_WIDTH) + 16, 1);
 			if (pattTmp[i] == NULL)
 			{
-				okBoxThreadSafe(0, "System message", "Not enough memory!");
+				showMsg(0, "System message", "Not enough memory!");
 				goto s3mLoadError;
 			}
 
@@ -1109,7 +1117,7 @@
 			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?");
+				showMsg(0, "System message", "General I/O error during loading! Is the file in use?");
 				goto s3mLoadError;
 			}
 
@@ -1394,7 +1402,7 @@
 
 		if (fread(&h_S3MInstr, 1, sizeof (h_S3MInstr), f) != sizeof (h_S3MInstr))
 		{
-			okBoxThreadSafe(0, "System message", "Not enough memory!");
+			showMsg(0, "System message", "Not enough memory!");
 			goto s3mLoadError;
 		}
 
@@ -1412,7 +1420,7 @@
 
 		if (h_S3MInstr.typ > 1)
 		{
-			okBoxThreadSafe(0, "System message", "Error loading .s3m: Incompatible module!");
+			showMsg(0, "System message", "Error loading .s3m: Incompatible module!");
 			goto s3mLoadError;
 		}
 		else if (h_S3MInstr.typ == 1)
@@ -1419,7 +1427,7 @@
 		{
 			if ((h_S3MInstr.flags & (255-1-2-4)) != 0 || h_S3MInstr.pack != 0)
 			{
-				okBoxThreadSafe(0, "System message", "Error loading .s3m: Incompatible module!");
+				showMsg(0, "System message", "Error loading .s3m: Incompatible module!");
 				goto s3mLoadError;
 			}
 			else if (h_S3MInstr.memSeg > 0 && h_S3MInstr.len > 0)
@@ -1426,7 +1434,7 @@
 			{
 				if (!allocateTmpInstr(1 + i))
 				{
-					okBoxThreadSafe(0, "System message", "Not enough memory!");
+					showMsg(0, "System message", "Not enough memory!");
 					goto s3mLoadError;
 				}
 
@@ -1447,7 +1455,7 @@
 				tmpSmp = (int8_t *)malloc(len + LOOP_FIX_LEN);
 				if (tmpSmp == NULL)
 				{
-					okBoxThreadSafe(0, "System message", "Not enough memory!");
+					showMsg(0, "System message", "Not enough memory!");
 					goto s3mLoadError;
 				}
 
@@ -1489,7 +1497,7 @@
 				if (fread(tmpSmp, len, 1, f) != 1)
 				{
 					free(tmpSmp);
-					okBoxThreadSafe(0, "System message", "General I/O error during loading! Is the file in use?");
+					showMsg(0, "System message", "General I/O error during loading! Is the file in use?");
 					goto s3mLoadError;
 				}
 
@@ -1503,7 +1511,7 @@
 						if (s->pek == NULL)
 						{
 							free(tmpSmp);
-							okBoxThreadSafe(0, "System message", "Not enough memory!");
+							showMsg(0, "System message", "Not enough memory!");
 							goto s3mLoadError;
 						}
 
@@ -1521,7 +1529,7 @@
 						if (s->pek == NULL)
 						{
 							free(tmpSmp);
-							okBoxThreadSafe(0, "System message", "Not enough memory!");
+							showMsg(0, "System message", "Not enough memory!");
 							goto s3mLoadError;
 						}
 
@@ -1537,7 +1545,7 @@
 	}
 
 	if (stereoSamplesWarn)
-		okBoxThreadSafe(0, "System message", "Stereo samples were found and will be converted to mono.");
+		showMsg(0, "System message", "Stereo samples were found and will be converted to mono.");
 
 	// non-FT2: fix overflown 9xx and illegal 3xx slides
 
@@ -1611,7 +1619,7 @@
 	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!");
+		showMsg(6, "System message", "Warning: S3M channel panning is not compatible with FT2!");
 
 	moduleLoaded = true;
 	return true;
@@ -1623,7 +1631,7 @@
 	return false;
 }
 
-static int32_t SDLCALL loadMusicThread(void *ptr)
+bool doLoadMusic(bool fromExternalThread)
 {
 	char tmpText[128];
 	int16_t k;
@@ -1631,8 +1639,9 @@
 	uint32_t filelength;
 	songHeaderTyp h;
 	FILE *f;
+	int16_t (*showMsg)(int16_t, const char *, const char *);
 
-	(void)ptr;
+	showMsg = fromExternalThread ? okBoxThreadSafe : okBox;
 
 	stereoSamplesWarn = false;
 	linearFreqTable = false;
@@ -1639,7 +1648,7 @@
 
 	if (editor.tmpFilenameU == NULL)
 	{
-		okBoxThreadSafe(0, "System message", "General I/O error during loading! Is the file in use?");
+		showMsg(0, "System message", "Generic memory fault during loading!");
 		moduleFailedToLoad = true;
 		return false;
 	}
@@ -1647,7 +1656,7 @@
 	f = UNICHAR_FOPEN(editor.tmpFilenameU, "rb");
 	if (f == NULL)
 	{
-		okBoxThreadSafe(0, "System message", "General I/O error during loading! Is the file in use?");
+		showMsg(0, "System message", "General I/O error during loading! Is the file in use? Does it exist?");
 		moduleFailedToLoad = true;
 		return false;
 	}
@@ -1658,10 +1667,10 @@
 
 	// start loading
 	if (fread(&h, 1, sizeof (h), f) != sizeof (h))
-		return loadMusicS3M(f, filelength); // not a .xm file, try to load as .s3m
+		return loadMusicS3M(f, filelength, fromExternalThread); // 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
+		return loadMusicS3M(f, filelength, fromExternalThread); // not a .xm file, try to load as .s3m
 
 	loadedFormat = FORMAT_XM;
 
@@ -1671,7 +1680,7 @@
 
 		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);
+		showMsg(0, "System message", tmpText);
 
 		moduleFailedToLoad = true;
 		return false;
@@ -1679,29 +1688,29 @@
 
 	if (h.len > MAX_ORDERS)
 	{
-		okBoxThreadSafe(0, "System message", "Error loading .xm: The song has more than 256 orders!");
+		showMsg(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!");
+		showMsg(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!");
+		showMsg(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.");
+		showMsg(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!");
+		showMsg(0, "System message", "Error loading .xm: The module is empty!");
 		goto xmLoadError;
 	}
 
@@ -1747,7 +1756,7 @@
 		{
 			if (!loadInstrHeader(f, i))
 			{
-				okBoxThreadSafe(0, "System message", "Error loading .xm: Either a corrupt or a non-supported module!");
+				showMsg(0, "System message", "Error loading .xm: Either a corrupt or a non-supported module!");
 				goto xmLoadError;
 			}
 		}
@@ -1762,7 +1771,7 @@
 		{
 			if (!loadInstrSample(f, i))
 			{
-				okBoxThreadSafe(0, "System message", "Not enough memory!");
+				showMsg(0, "System message", "Not enough memory!");
 				goto xmLoadError;
 			}
 		}
@@ -1781,13 +1790,13 @@
 		{
 			if (!loadInstrHeader(f, i))
 			{
-				okBoxThreadSafe(0, "System message", "Error loading .xm: Either a corrupt or a non-supported module!");
+				showMsg(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!");
+				showMsg(0, "System message", "Not enough memory!");
 				goto xmLoadError;
 			}
 		}
@@ -1794,7 +1803,7 @@
 	}
 
 	if (stereoSamplesWarn)
-		okBoxThreadSafe(0, "System message", "Stereo samples were found and will be converted to mono.");
+		showMsg(0, "System message", "Stereo samples were found and will be converted to mono.");
 
 	fclose(f);
 
@@ -1808,6 +1817,12 @@
 	return false;
 }
 
+static int32_t SDLCALL loadMusicThread(void *ptr)
+{
+	(void)ptr;
+	return doLoadMusic(true);
+}
+
 void loadMusic(UNICHAR *filenameU)
 {
 	if (musicIsLoading)
@@ -1844,13 +1859,13 @@
 	SDL_DetachThread(thread);
 }
 
-bool loadMusicUnthreaded(UNICHAR *filenameU) // for development testing
+bool loadMusicUnthreaded(UNICHAR *filenameU, bool autoPlay)
 {
-	if (editor.tmpFilenameU == NULL)
+	if (filenameU == NULL || editor.tmpFilenameU == NULL)
 		return false;
 
 	// clear deprecated pointers from possible last loading session (super important)
-	memset(pattTmp,  0, sizeof (pattTmp));
+	memset(pattTmp, 0, sizeof (pattTmp));
 	memset(instrTmp, 0, sizeof (instrTmp));
 
 	// prevent stuck instrument names from previous module
@@ -1861,12 +1876,15 @@
 
 	UNICHAR_STRCPY(editor.tmpFilenameU, filenameU);
 
-	loadMusicThread(NULL);
 	editor.loadMusicEvent = EVENT_NONE;
+	doLoadMusic(false);
 
 	if (moduleLoaded)
 	{
 		setupLoadedModule();
+		if (autoPlay)
+			startPlaying(PLAYMODE_SONG, 0);
+
 		return true;
 	}
 
@@ -1910,6 +1928,8 @@
 	uint8_t j;
 	uint32_t readSize;
 	instrHeaderTyp ih;
+	instrTyp *ins;
+	sampleHeaderTyp *src;
 	sampleTyp *s;
 
 	memset(&ih, 0, INSTR_HEADER_SIZE);
@@ -1921,7 +1941,7 @@
 		readSize = INSTR_HEADER_SIZE;
 
 	// load instrument data into temp buffer
-	fread(ih.name, readSize - 4, 1, f); // -4 = skip ih.instrSize
+	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)
@@ -1952,45 +1972,66 @@
 			if (!allocateTmpInstr(i))
 				return false;
 
+			// copy instrument header elements to our instrument struct
+
+			ins = instrTmp[i];
+			memcpy(ins->ta, ih.ta, 96);
+			memcpy(ins->envVP, ih.envVP, 12*2*sizeof(int16_t));
+			memcpy(ins->envPP, ih.envPP, 12*2*sizeof(int16_t));
+			ins->envVPAnt = ih.envVPAnt;
+			ins->envPPAnt = ih.envPPAnt;
+			ins->envVSust = ih.envVSust;
+			ins->envVRepS = ih.envVRepS;
+			ins->envVRepE = ih.envVRepE;
+			ins->envPSust = ih.envPSust;
+			ins->envPRepS = ih.envPRepS;
+			ins->envPRepE = ih.envPRepE;
+			ins->envVTyp = ih.envVTyp;
+			ins->envPTyp = ih.envPTyp;
+			ins->vibTyp = ih.vibTyp;
+			ins->vibSweep = ih.vibSweep;
+			ins->vibDepth = ih.vibDepth;
+			ins->vibRate = ih.vibRate;
+			ins->fadeOut = ih.fadeOut;
+			ins->midiOn = (ih.midiOn > 0) ? true : false;
+			ins->midiChannel = ih.midiChannel;
+			ins->midiProgram = ih.midiProgram;
+			ins->midiBend = ih.midiBend;
+			ins->mute = (ih.mute > 0) ? true : false;
+			ins->antSamp = ih.antSamp; // used in loadInstrSample()
+
 			// sanitize stuff for broken/unsupported instruments
-			ih.midiProgram = CLAMP(ih.midiProgram, 0, 127);
-			ih.midiBend = CLAMP(ih.midiBend, 0, 36);
+			ins->midiProgram = CLAMP(ins->midiProgram, 0, 127);
+			ins->midiBend = CLAMP(ins->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;
+			if (ins->midiChannel > 15) ins->midiChannel = 15;
+			if (ins->vibDepth > 0x0F) ins->vibDepth = 0x0F;
+			if (ins->vibRate > 0x3F) ins->vibRate = 0x3F;
+			if (ins->vibTyp > 3) ins->vibTyp = 0;
 
 			for (j = 0; j < 96; j++)
 			{
-				if (ih.ta[j] > 15)
-					ih.ta[j] = 15;
+				if (ins->ta[j] > 15)
+					ins->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;
+			if (ins->envVPAnt > 12) ins->envVPAnt = 12;
+			if (ins->envVRepS > 11) ins->envVRepS = 11;
+			if (ins->envVRepE > 11) ins->envVRepE = 11;
+			if (ins->envVSust > 11) ins->envVSust = 11;
+			if (ins->envPPAnt > 12) ins->envPPAnt = 12;
+			if (ins->envPRepS > 11) ins->envPRepS = 11;
+			if (ins->envPRepE > 11) ins->envPRepE = 11;
+			if (ins->envPSust > 11) ins->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;
+				if ((uint16_t)ins->envVP[j][0] > 32767) ins->envVP[j][0] = 32767;
+				if ((uint16_t)ins->envPP[j][0] > 32767) ins->envPP[j][0] = 32767;
+				if ((uint16_t)ins->envVP[j][1] > 64) ins->envVP[j][1] = 64;
+				if ((uint16_t)ins->envPP[j][1] > 63) ins->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)
@@ -2001,9 +2042,23 @@
 			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
+				src = &ih.samp[j];
 
+				// copy sample header elements to our sample struct
+
+				s->len = src->len;
+				s->repS = src->repS;
+				s->repL = src->repL;
+				s->vol = src->vol;
+				s->fine = src->fine;
+				s->typ = src->typ;
+				s->pan = src->pan;
+				s->relTon = src->relTon;
+				memcpy(s->name, src->name, 22);
+				s->name[22] = '\0';
+
+				// dst->pek is set up later
+
 				// trim off spaces at end of name
 				for (k = 21; k >= 0; k--)
 				{
@@ -2013,7 +2068,7 @@
 						break;
 				}
 
-				// sanitize stuff for malicious modules
+				// sanitize stuff broken/unsupported samples
 				if (s->vol > 64)
 					s->vol = 64;
 
@@ -2025,7 +2080,7 @@
 	return true;
 }
 
-static void checkSampleRepeat(sampleTyp *s)
+void checkSampleRepeat(sampleTyp *s)
 {
 	if (s->repS < 0) s->repS = 0;
 	if (s->repL < 0) s->repL = 0;
@@ -2049,7 +2104,7 @@
 	{
 		s = &instrTmp[i]->samp[j];
 
-		// if a sample has both forward loop and pingpong loop set, make it pingpong loop only (FT2 behavior)
+		// if a sample has both forward loop and pingpong loop set, make it pingpong loop only (FT2 mixer behavior)
 		if ((s->typ & 3) == 3)
 			s->typ &= 0xFE;
 
@@ -2414,11 +2469,11 @@
 {
 	int32_t filesize;
 	uint32_t filenameLen;
-	UNICHAR *filenameU, tmpPathU[PATH_MAX + 2];
+	UNICHAR *filenameU, tmpPathU[PATH_MAX+2];
 
 	// this is crude, we always expect only one parameter, and that it is the module.
 
-	if (argc != 2)
+	if (argc != 2 || argv[1] == NULL || argv[1][0] == '\0')
 		return false;
 
 #ifdef __APPLE__
@@ -2428,7 +2483,7 @@
 
 	filenameLen = (uint32_t)strlen(argv[1]);
 
-	filenameU = (UNICHAR *)calloc((filenameLen + 1), sizeof (UNICHAR));
+	filenameU = (UNICHAR *)calloc(filenameLen+1, sizeof (UNICHAR));
 	if (filenameU == NULL)
 	{
 		okBox(0, "System message", "Not enough memory!");
@@ -2444,34 +2499,28 @@
 	// store old path
 	UNICHAR_GETCWD(tmpPathU, PATH_MAX);
 
-	// set binary path
+	// set path to where the main executable is
 	UNICHAR_CHDIR(editor.binaryPathU);
 
 	filesize = getFileSize(filenameU);
-
-	if (filesize == -1) // >2GB
+	if (filesize == -1 || filesize >= 512L*1024*1024) // >=2GB or >=512MB
 	{
-		okBox(0, "System message", "The file is too big and can't be loaded (over 2GB).");
-		goto argLoadErr;
-	}
+		okBox(0, "System message", "Error: The module is too big to be loaded!");
+		/* This is not really true, but let's add this check to prevent accidentally
+		** passing really big files to the program. And how often do you really
+		** see a >=512MB .XM/.S3M module?
+		*/
 
-	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;
+		free(filenameU);
+		UNICHAR_CHDIR(tmpPathU); // set old path back
+		return false;
 	}
 
-	editor.loadMusicEvent = EVENT_LOADMUSIC_ARGV;
-	loadMusic(filenameU);
-	
-	UNICHAR_CHDIR(tmpPathU); // set old path back
-	free(filenameU);
-	return true;
+	bool result = loadMusicUnthreaded(filenameU, true);
 
-argLoadErr:
-	UNICHAR_CHDIR(tmpPathU); // set old path back
 	free(filenameU);
-	return false;
+	UNICHAR_CHDIR(tmpPathU); // set old path back
+	return result;
 }
 
 void loadDroppedFile(char *fullPathUTF8, bool songModifiedCheck)
--- a/src/ft2_module_loader.h
+++ b/src/ft2_module_loader.h
@@ -5,9 +5,10 @@
 #include "ft2_unicode.h"
 
 void loadMusic(UNICHAR *filenameU);
-bool loadMusicUnthreaded(UNICHAR *filenameU); // for development testing
+bool loadMusicUnthreaded(UNICHAR *filenameU, bool autoPlay);
 bool handleModuleLoadFromArg(int argc, char **argv);
 void loadDroppedFile(char *fullPathUTF8, bool songModifiedCheck);
 void handleLoadMusicEvents(void);
 void clearUnusedChannels(tonTyp *p, int16_t pattLen, uint8_t antChn);
 void unpackPatt(uint8_t *dst, uint16_t inn, uint16_t len, uint8_t antChn);
+void checkSampleRepeat(sampleTyp *s);
--- a/src/ft2_module_saver.c
+++ b/src/ft2_module_saver.c
@@ -13,7 +13,8 @@
 #include "ft2_module_loader.h"
 
 /* These savers are directly ported, so they should act identical to FT2
-** except for some very minor changes. */
+** except for some very minor changes.
+*/
 
 static SDL_Thread *thread;
 
@@ -31,9 +32,10 @@
 	size_t result;
 	songHeaderTyp h;
 	patternHeaderTyp ph;
+	instrTyp *ins;
 	instrHeaderTyp ih;
-	sampleTyp *srcSmp;
-	sampleHeaderTyp *dstSmp;
+	sampleTyp *s;
+	sampleHeaderTyp *dst;
 	FILE *f;
 
 	f = UNICHAR_FOPEN(filenameU, "wb");
@@ -135,6 +137,8 @@
 		}
 	}
 
+	memset(&ih, 0, sizeof (ih)); // important, clears reserved stuff
+
 	for (i = 1; i <= ai; i++)
 	{
 		if (instr[i] == NULL)
@@ -153,19 +157,55 @@
 
 		if (a > 0)
 		{
-			memcpy(ih.ta, instr[j], INSTR_SIZE);
+			ins = instr[j];
+
+			memcpy(ih.ta, ins->ta, 96);
+			memcpy(ih.envVP, ins->envVP, 12*2*sizeof(int16_t));
+			memcpy(ih.envPP, ins->envPP, 12*2*sizeof(int16_t));
+			ih.envVPAnt = ins->envVPAnt;
+			ih.envPPAnt = ins->envPPAnt;
+			ih.envVSust = ins->envVSust;
+			ih.envVRepS = ins->envVRepS;
+			ih.envVRepE = ins->envVRepE;
+			ih.envPSust = ins->envPSust;
+			ih.envPRepS = ins->envPRepS;
+			ih.envPRepE = ins->envPRepE;
+			ih.envVTyp = ins->envVTyp;
+			ih.envPTyp = ins->envPTyp;
+			ih.vibTyp = ins->vibTyp;
+			ih.vibSweep = ins->vibSweep;
+			ih.vibDepth = ins->vibDepth;
+			ih.vibRate = ins->vibRate;
+			ih.fadeOut = ins->fadeOut;
+			ih.midiOn = ins->midiOn ? 1 : 0;
+			ih.midiChannel = ins->midiChannel;
+			ih.midiProgram = ins->midiProgram;
+			ih.midiBend = ins->midiBend;
+			ih.mute = ins->mute ? 1 : 0;
 			ih.instrSize = INSTR_HEADER_SIZE;
 			
-			for (k = 1; k <= a; k++)
+			for (k = 0; k < a; k++)
 			{
-				srcSmp = &instr[j]->samp[k-1];
-				dstSmp = &ih.samp[k-1];
+				s = &instr[j]->samp[k];
+				dst = &ih.samp[k];
 
-				memset(dstSmp->name, ' ', 22);
+				dst->len = s->len;
+				dst->repS = s->repS;
+				dst->repL = s->repL;
+				dst->vol = s->vol;
+				dst->fine = s->fine;
+				dst->typ = s->typ;
+				dst->pan = s->pan;
+				dst->relTon = s->relTon;
 
-				memcpy(dstSmp, srcSmp, 12+4+2 + strlen(srcSmp->name));
-				if (srcSmp->pek == NULL)
-					dstSmp->len = 0;
+				uint8_t nameLen = (uint8_t)strlen(s->name);
+
+				dst->nameLen = nameLen;
+				memset(dst->name, ' ', 22);
+				memcpy(dst->name, s->name, nameLen);
+
+				if (s->pek == NULL)
+					dst->len = 0;
 			}
 		}
 		else
@@ -182,18 +222,18 @@
 
 		for (k = 1; k <= a; k++)
 		{
-			srcSmp = &instr[j]->samp[k-1];
-			if (srcSmp->pek != NULL)
+			s = &instr[j]->samp[k-1];
+			if (s->pek != NULL)
 			{
-				restoreSample(srcSmp);
-				samp2Delta(srcSmp->pek, srcSmp->len, srcSmp->typ);
+				restoreSample(s);
+				samp2Delta(s->pek, s->len, s->typ);
 
-				result = fwrite(srcSmp->pek, 1, srcSmp->len, f);
+				result = fwrite(s->pek, 1, s->len, f);
 
-				delta2Samp(srcSmp->pek, srcSmp->len, srcSmp->typ);
-				fixSample(srcSmp);
+				delta2Samp(s->pek, s->len, s->typ);
+				fixSample(s);
 
-				if (result != (size_t)srcSmp->len) // write not OK
+				if (result != (size_t)s->len) // write not OK
 				{
 					fclose(f);
 					okBoxThreadSafe(0, "System message", "Error saving module: general I/O error!");
--- a/src/ft2_pattern_draw.c
+++ b/src/ft2_pattern_draw.c
@@ -560,18 +560,18 @@
 
 	for (uint32_t y = 0; y < FONT4_CHAR_H; y++)
 	{
-		for (uint32_t x1 = 0; x1 < FONT4_CHAR_W; x1++)
+		for (uint32_t x = 0; x < FONT4_CHAR_W; x++)
 		{
-			if (src1Ptr[x1])
+			if (src1Ptr[x])
 			{
-				dst1Ptr[x1] = pixVal; // left side
-				dst2Ptr[x1] = pixVal; // right side
+				dst1Ptr[x] = pixVal; // left side
+				dst2Ptr[x] = pixVal; // right side
 			}
 
-			if (src2Ptr[x1])
+			if (src2Ptr[x])
 			{
-				dst1Ptr[FONT4_CHAR_W + x1] = pixVal; // left side
-				dst2Ptr[FONT4_CHAR_W + x1] = pixVal; // right side
+				dst1Ptr[FONT4_CHAR_W+x] = pixVal; // left side
+				dst2Ptr[FONT4_CHAR_W+x] = pixVal; // right side
 			}
 		}
 
@@ -898,6 +898,7 @@
 	noteTextColors[0] = video.palette[PAL_PATTEXT]; // not selected
 	noteTextColors[1] = video.palette[PAL_FORGRND]; // selected
 
+	// increment pattern data pointer by horizontal scrollbar offset/channel
 	if (pattPtr != NULL)
 		pattPtr += editor.ui.channelOffset;
 
@@ -976,7 +977,7 @@
 void pattTwoHexOut(uint32_t xPos, uint32_t yPos, uint8_t val, uint32_t color)
 {
 	const uint8_t *ch1Ptr, *ch2Ptr;
-	uint32_t *dstPtr;
+	uint32_t *dstPtr, tmp;
 
 	ch1Ptr = &font4Ptr[(val   >> 4) * FONT4_CHAR_W];
 	ch2Ptr = &font4Ptr[(val & 0x0F) * FONT4_CHAR_W];
@@ -986,8 +987,14 @@
 	{
 		for (uint32_t x = 0; x < FONT4_CHAR_W; x++)
 		{
-			if (ch1Ptr[x]) dstPtr[x] = color;
-			if (ch2Ptr[x]) dstPtr[FONT4_CHAR_W + x] = color;
+			// carefully written like this to generate conditional move instructions (font data is hard to predict)
+			tmp = dstPtr[x];
+			if (ch1Ptr[x] != 0) tmp = color;
+			dstPtr[x] = tmp;
+
+			tmp = dstPtr[FONT4_CHAR_W+x];
+			if (ch2Ptr[x] != 0) tmp = color;
+			dstPtr[FONT4_CHAR_W+x] = tmp;
 		}
 
 		ch1Ptr += FONT4_WIDTH;
@@ -999,7 +1006,7 @@
 static void pattCharOut(uint32_t xPos, uint32_t yPos, uint8_t chr, uint8_t fontType, uint32_t color)
 {
 	const uint8_t *srcPtr;
-	uint32_t x, y, *dstPtr;
+	uint32_t x, y, *dstPtr, tmp;
 
 	dstPtr = &video.frameBuffer[(yPos * SCREEN_W) + xPos];
 
@@ -1010,8 +1017,10 @@
 		{
 			for (x = 0; x < FONT3_CHAR_W; x++)
 			{
-				if (srcPtr[x])
-					dstPtr[x] = color;
+				// carefully written like this to generate conditional move instructions (font data is hard to predict)
+				tmp = dstPtr[x];
+				if (srcPtr[x] != 0) tmp = color;
+				dstPtr[x] = tmp;
 			}
 
 			srcPtr += FONT3_WIDTH;
@@ -1025,8 +1034,10 @@
 		{
 			for (x = 0; x < FONT4_CHAR_W; x++)
 			{
-				if (srcPtr[x])
-					dstPtr[x] = color;
+				// carefully written like this to generate conditional move instructions (font data is hard to predict)
+				tmp = dstPtr[x];
+				if (srcPtr[x] != 0) tmp = color;
+				dstPtr[x] = tmp;
 			}
 
 			srcPtr += FONT4_WIDTH;
@@ -1040,8 +1051,10 @@
 		{
 			for (x = 0; x < FONT5_CHAR_W; x++)
 			{
-				if (srcPtr[x])
-					dstPtr[x] = color;
+				// carefully written like this to generate conditional move instructions (font data is hard to predict)
+				tmp = dstPtr[x];
+				if (srcPtr[x] != 0) tmp = color;
+				dstPtr[x] = tmp;
 			}
 
 			srcPtr += FONT5_WIDTH;
@@ -1055,8 +1068,10 @@
 		{
 			for (x = 0; x < FONT7_CHAR_W; x++)
 			{
-				if (srcPtr[x])
-					dstPtr[x] = color;
+				// carefully written like this to generate conditional move instructions (font data is hard to predict)
+				tmp = dstPtr[x];
+				if (srcPtr[x] != 0) tmp = color;
+				dstPtr[x] = tmp;
 			}
 
 			srcPtr += FONT7_WIDTH;
@@ -1068,7 +1083,7 @@
 static void drawEmptyNoteSmall(uint32_t xPos, uint32_t yPos, uint32_t color)
 {
 	const uint8_t *srcPtr;
-	uint32_t *dstPtr;
+	uint32_t *dstPtr, tmp;
 
 	srcPtr = &font7Data[18 * FONT7_CHAR_W];
 	dstPtr = &video.frameBuffer[(yPos * SCREEN_W) + xPos];
@@ -1077,8 +1092,10 @@
 	{
 		for (uint32_t x = 0; x < FONT7_CHAR_W*3; x++)
 		{
-			if (srcPtr[x])
-				dstPtr[x] = color;
+			// carefully written like this to generate conditional move instructions (font data is hard to predict)
+			tmp = dstPtr[x];
+			if (srcPtr[x] != 0) tmp = color;
+			dstPtr[x] = tmp;
 		}
 
 		srcPtr += FONT7_WIDTH;
@@ -1089,7 +1106,7 @@
 static void drawKeyOffSmall(uint32_t xPos, uint32_t yPos, uint32_t color)
 {
 	const uint8_t *srcPtr;
-	uint32_t *dstPtr;
+	uint32_t *dstPtr, tmp;
 
 	srcPtr = &font7Data[21 * FONT7_CHAR_W];
 	dstPtr = &video.frameBuffer[(yPos * SCREEN_W) + (xPos + 2)];
@@ -1098,8 +1115,10 @@
 	{
 		for (uint32_t x = 0; x < FONT7_CHAR_W*2; x++)
 		{
-			if (srcPtr[x])
-				dstPtr[x] = color;
+			// carefully written like this to generate conditional move instructions (font data is hard to predict)
+			tmp = dstPtr[x];
+			if (srcPtr[x] != 0) tmp = color;
+			dstPtr[x] = tmp;
 		}
 
 		srcPtr += FONT7_WIDTH;
@@ -1111,7 +1130,7 @@
 {
 	const uint8_t *ch1Ptr, *ch2Ptr, *ch3Ptr;
 	uint8_t note;
-	uint32_t *dstPtr, char1, char2, char3;
+	uint32_t *dstPtr, char1, char2, char3, tmp;
 
 	assert(ton >= 1 && ton <= 97);
 
@@ -1140,9 +1159,18 @@
 	{
 		for (uint32_t x = 0; x < FONT7_CHAR_W; x++)
 		{
-			if (ch1Ptr[x]) dstPtr[ (FONT7_CHAR_W * 0)      + x] = color;
-			if (ch2Ptr[x]) dstPtr[ (FONT7_CHAR_W * 1)      + x] = color;
-			if (ch3Ptr[x]) dstPtr[((FONT7_CHAR_W * 2) - 2) + x] = color;
+			// carefully written like this to generate conditional move instructions (font data is hard to predict)
+			tmp = dstPtr[x];
+			if (ch1Ptr[x] != 0) tmp = color;
+			dstPtr[x] = tmp;
+
+			tmp = dstPtr[FONT7_CHAR_W+x];
+			if (ch2Ptr[x] != 0) tmp = color;
+			dstPtr[FONT7_CHAR_W+x] = tmp;
+
+			tmp = dstPtr[((FONT7_CHAR_W*2)-2)+x]; // -2 to get correct alignment for ending glyph
+			if (ch3Ptr[x] != 0) tmp = color;
+			dstPtr[((FONT7_CHAR_W*2)-2)+x] = tmp;
 		}
 
 		ch1Ptr += FONT7_WIDTH;
@@ -1155,7 +1183,7 @@
 static void drawEmptyNoteMedium(uint32_t xPos, uint32_t yPos, uint32_t color)
 {
 	const uint8_t *srcPtr;
-	uint32_t *dstPtr;
+	uint32_t *dstPtr, tmp;
 
 	dstPtr = &video.frameBuffer[(yPos * SCREEN_W) + xPos];
 	srcPtr = &font4Ptr[43 * FONT4_CHAR_W];
@@ -1164,8 +1192,10 @@
 	{
 		for (uint32_t x = 0; x < FONT4_CHAR_W*3; x++)
 		{
-			if (srcPtr[x])
-				dstPtr[x] = color;
+			// carefully written like this to generate conditional move instructions (font data is hard to predict)
+			tmp = dstPtr[x];
+			if (srcPtr[x] != 0) tmp = color;
+			dstPtr[x] = tmp;
 		}
 
 		srcPtr += FONT4_WIDTH;
@@ -1176,7 +1206,7 @@
 static void drawKeyOffMedium(uint32_t xPos, uint32_t yPos, uint32_t color)
 {
 	const uint8_t *srcPtr;
-	uint32_t *dstPtr;
+	uint32_t *dstPtr, tmp;
 
 	srcPtr = &font4Ptr[40 * FONT4_CHAR_W];
 	dstPtr = &video.frameBuffer[(yPos * SCREEN_W) + xPos];
@@ -1185,8 +1215,10 @@
 	{
 		for (uint32_t x = 0; x < FONT4_CHAR_W*3; x++)
 		{
-			if (srcPtr[x])
-				dstPtr[x] = color;
+			// carefully written like this to generate conditional move instructions (font data is hard to predict)
+			tmp = dstPtr[x];
+			if (srcPtr[x] != 0) tmp = color;
+			dstPtr[x] = tmp;
 		}
 
 		srcPtr += FONT4_WIDTH;
@@ -1197,7 +1229,7 @@
 static void drawNoteMedium(uint32_t xPos, uint32_t yPos, int32_t ton, uint32_t color)
 {
 	const uint8_t *ch1Ptr, *ch2Ptr, *ch3Ptr;
-	uint32_t note, *dstPtr, char1, char2, char3;
+	uint32_t note, *dstPtr, char1, char2, char3, tmp;
 
 	ton--;
 
@@ -1224,9 +1256,18 @@
 	{
 		for (uint32_t x = 0; x < FONT4_CHAR_W; x++)
 		{
-			if (ch1Ptr[x]) dstPtr[(FONT4_CHAR_W * 0) + x] = color;
-			if (ch2Ptr[x]) dstPtr[(FONT4_CHAR_W * 1) + x] = color;
-			if (ch3Ptr[x]) dstPtr[(FONT4_CHAR_W * 2) + x] = color;
+			// carefully written like this to generate conditional move instructions (font data is hard to predict)
+			tmp = dstPtr[x];
+			if (ch1Ptr[x] != 0) tmp = color;
+			dstPtr[x] = tmp;
+
+			tmp = dstPtr[FONT4_CHAR_W+x];
+			if (ch2Ptr[x] != 0) tmp = color;
+			dstPtr[FONT4_CHAR_W+x] = tmp;
+
+			tmp = dstPtr[(FONT4_CHAR_W*2)+x];
+			if (ch3Ptr[x] != 0) tmp = color;
+			dstPtr[(FONT4_CHAR_W*2)+x] = tmp;
 		}
 
 		ch1Ptr += FONT4_WIDTH;
@@ -1239,7 +1280,7 @@
 static void drawEmptyNoteBig(uint32_t xPos, uint32_t yPos, uint32_t color)
 {
 	const uint8_t *srcPtr;
-	uint32_t *dstPtr;
+	uint32_t *dstPtr, tmp;
 
 	srcPtr = &font4Ptr[67 * FONT4_CHAR_W];
 	dstPtr = &video.frameBuffer[(yPos * SCREEN_W) + xPos];
@@ -1248,8 +1289,10 @@
 	{
 		for (uint32_t x = 0; x < FONT4_CHAR_W*6; x++)
 		{
-			if (srcPtr[x])
-				dstPtr[x] = color;
+			// carefully written like this to generate conditional move instructions (font data is hard to predict)
+			tmp = dstPtr[x];
+			if (srcPtr[x] != 0) tmp = color;
+			dstPtr[x] = tmp;
 		}
 
 		srcPtr += FONT4_WIDTH;
@@ -1260,7 +1303,7 @@
 static void drawKeyOffBig(uint32_t xPos, uint32_t yPos, uint32_t color)
 {
 	const uint8_t *srcPtr;
-	uint32_t *dstPtr;
+	uint32_t *dstPtr, tmp;
 
 	srcPtr = &font4Data[61 * FONT4_CHAR_W];
 	dstPtr = &video.frameBuffer[(yPos * SCREEN_W) + xPos];
@@ -1269,8 +1312,10 @@
 	{
 		for (uint32_t x = 0; x < FONT4_CHAR_W*6; x++)
 		{
-			if (srcPtr[x])
-				dstPtr[x] = color;
+			// carefully written like this to generate conditional move instructions (font data is hard to predict)
+			tmp = dstPtr[x];
+			if (srcPtr[x] != 0) tmp = color;
+			dstPtr[x] = tmp;
 		}
 
 		srcPtr += FONT4_WIDTH;
@@ -1282,7 +1327,7 @@
 {
 	const uint8_t *ch1Ptr, *ch2Ptr, *ch3Ptr;
 	uint8_t note;
-	uint32_t *dstPtr, char1, char2, char3;
+	uint32_t *dstPtr, char1, char2, char3, tmp;
 
 	ton--;
 
@@ -1309,9 +1354,18 @@
 	{
 		for (uint32_t x = 0; x < FONT5_CHAR_W; x++)
 		{
-			if (ch1Ptr[x]) dstPtr[(FONT5_CHAR_W * 0) + x] = color;
-			if (ch2Ptr[x]) dstPtr[(FONT5_CHAR_W * 1) + x] = color;
-			if (ch3Ptr[x]) dstPtr[(FONT5_CHAR_W * 2) + x] = color;
+			// carefully written like this to generate conditional move instructions (font data is hard to predict)
+			tmp = dstPtr[x];
+			if (ch1Ptr[x] != 0) tmp = color;
+			dstPtr[x] = tmp;
+
+			tmp = dstPtr[FONT5_CHAR_W+x];
+			if (ch2Ptr[x] != 0) tmp = color;
+			dstPtr[FONT5_CHAR_W+x] = tmp;
+
+			tmp = dstPtr[(FONT5_CHAR_W*2)+x];
+			if (ch3Ptr[x] != 0) tmp = color;
+			dstPtr[(FONT5_CHAR_W*2)+x] = tmp;
 		}
 
 		ch1Ptr += FONT5_WIDTH;
--- a/src/ft2_pattern_ed.c
+++ b/src/ft2_pattern_ed.c
@@ -59,7 +59,8 @@
 		 * do that to avoid out of bondary row look-up between out-of-sync replayer
 		 * state and tracker state (yes it used to happen, rarely). We're not wasting
 		 * too much RAM for a modern computer anyway. Worst case: 256 allocated
-		 * patterns would be ~10MB. */
+		 * patterns would be ~10MB.
+		 */
 
 		patt[nr] = (tonTyp *)calloc((MAX_PATT_LEN * TRACK_WIDTH) + 16, 1);
 		if (patt[nr] == NULL)
@@ -891,7 +892,7 @@
 	{
 		if (mouse.x < 29)
 		{
-			scrollBarScrollUp(SB_CHAN_SCROLL,   1);
+			scrollBarScrollUp(SB_CHAN_SCROLL, 1);
 			forceMarking = true;
 		}
 		else if (mouse.x > 604)
@@ -1517,6 +1518,7 @@
 	if (editor.ui.pattChanScrollShown)
 	{
 		assert(song.antChn > editor.ui.numChannelsShown);
+
 		if (channel >= editor.ui.channelOffset+editor.ui.numChannelsShown)
 			scrollBarScrollDown(SB_CHAN_SCROLL, (channel - (editor.ui.channelOffset + editor.ui.numChannelsShown)) + 1);
 		else if (channel < editor.ui.channelOffset)
@@ -2307,12 +2309,12 @@
 	if (logoType == 0)
 	{
 		pushButtons[PB_LOGO].bitmapUnpressed = &ft2LogoBadges[(154 * 32) * 0];
-		pushButtons[PB_LOGO].bitmapPressed   = &ft2LogoBadges[(154 * 32) * 1];
+		pushButtons[PB_LOGO].bitmapPressed = &ft2LogoBadges[(154 * 32) * 1];
 	}
 	else
 	{
 		pushButtons[PB_LOGO].bitmapUnpressed = &ft2LogoBadges[(154 * 32) * 2];
-		pushButtons[PB_LOGO].bitmapPressed   = &ft2LogoBadges[(154 * 32) * 3];
+		pushButtons[PB_LOGO].bitmapPressed = &ft2LogoBadges[(154 * 32) * 3];
 	}
 
 	drawPushButton(PB_LOGO);
@@ -2325,12 +2327,12 @@
 	if (badgeType == 0)
 	{
 		pushButtons[PB_BADGE].bitmapUnpressed = &ft2InfoBadges[(25 * 32) * 0];
-		pushButtons[PB_BADGE].bitmapPressed   = &ft2InfoBadges[(25 * 32) * 1];
+		pushButtons[PB_BADGE].bitmapPressed = &ft2InfoBadges[(25 * 32) * 1];
 	}
 	else
 	{
 		pushButtons[PB_BADGE].bitmapUnpressed = &ft2InfoBadges[(25 * 32) * 2];
-		pushButtons[PB_BADGE].bitmapPressed   = &ft2InfoBadges[(25 * 32) * 3];
+		pushButtons[PB_BADGE].bitmapPressed = &ft2InfoBadges[(25 * 32) * 3];
 	}
 
 	drawPushButton(PB_BADGE);
@@ -2701,7 +2703,7 @@
 	song.songPos = 0;
 	song.globVol = 64;
 
-	memset(song.name,    0, sizeof (song.name));
+	memset(song.name, 0, sizeof (song.name));
 	memset(song.songTab, 0, sizeof (song.songTab));
 
 	// zero all pattern data and reset pattern lengths
@@ -2916,7 +2918,7 @@
 		editor.pattPos = song.pattPos;
 
 		editor.ui.updatePatternEditor = true;
-		editor.ui.updatePosSections   = true;
+		editor.ui.updatePosSections = true;
 
 		unlockMixerCallback();
 		setSongModifiedFlag();
--- a/src/ft2_replayer.c
+++ b/src/ft2_replayer.c
@@ -419,7 +419,7 @@
 	ch->instrSeg = ins;
 	ch->mute = ins->mute;
 
-	if (ton > 96) // non-FT2 security (should never happen because I clamp in the patt loaders now)
+	if (ton > 96) // non-FT2 security (should never happen because I clamp in the patt. loader now)
 		ton = 96;
 
 	smp = ins->ta[ton-1] & 0xF;
@@ -1114,7 +1114,7 @@
 	ch->eff = p->eff;
 	ch->tonTyp = (p->instr << 8) | p->ton;
 
-	if (ch->stOff == 1)
+	if (ch->stOff)
 	{
 		checkMoreEffects(ch);
 		return;
@@ -1233,7 +1233,7 @@
 		}
 	}
 
-	if (ch->mute != 1)
+	if (!ch->mute)
 	{
 		// *** VOLUME ENVELOPE ***
 		envVal = 0;
--- a/src/ft2_replayer.h
+++ b/src/ft2_replayer.h
@@ -34,8 +34,8 @@
 
 // DO NOT TOUCH!
 #define MIN_BPM 32
-#define TRACK_WIDTH (5 * MAX_VOICES)
 #define MAX_VOICES 32
+#define TRACK_WIDTH (5 * MAX_VOICES)
 #define MAX_NOTES ((12 * 10 * 16) + 16)
 #define MAX_PATTERNS 256
 #define MAX_PATT_LEN 256
@@ -43,7 +43,6 @@
 #define MAX_SMP_PER_INST 16
 #define MAX_ORDERS 256
 #define STD_ENV_SIZE ((6*2*12*2*2) + (6*8*2) + (6*5*2) + (6*2*2))
-#define INSTR_SIZE 232
 #define INSTR_HEADER_SIZE 263
 #define INSTR_XI_HEADER_SIZE 298
 #define MAX_SAMPLE_LEN 0x3FFFFFFF
@@ -123,7 +122,7 @@
 	int8_t fine;
 	uint8_t typ, pan;
 	int8_t relTon;
-	uint8_t reserved;
+	uint8_t nameLen;
 	char name[22];
 }
 #ifdef __GNUC__
@@ -157,34 +156,27 @@
 #endif
 instrHeaderTyp;
 
-typedef struct sampleTyp_t  // DO NOT TOUCH!
-{
-	int32_t len, repS, repL;
-	uint8_t vol;
-	int8_t fine;
-	uint8_t typ, pan;
-	int8_t relTon;
-	uint8_t reserved;
-	char name[22 + 1]; // +1 for tracker NUL termination, not present in sample header
+#ifdef _MSC_VER
+#pragma pack(pop)
+#endif
 
-	// stuff from now on can be touched
-	int8_t *pek;
-	uint8_t fixed;
+typedef struct sampleTyp_t
+{
+	char name[22+1];
+	bool fixed;
+	int8_t fine, relTon, *pek;
+	uint8_t vol, typ, pan;
 	int16_t fixedSmp1;
 #ifndef LERPMIX
 	int16_t fixedSmp2;
 #endif
-	int32_t fixedPos;
-}
-#ifdef __GNUC__
-__attribute__ ((packed))
-#endif
-sampleTyp;
+	int32_t fixedPos, len, repS, repL;
+} sampleTyp;
 
-typedef struct instrTyp_t // DO NOT TOUCH!
+typedef struct instrTyp_t
 {
-	uint8_t ta[96];
-	int16_t envVP[12][2], envPP[12][2];
+	bool midiOn, mute;
+	uint8_t midiChannel, ta[96];
 	uint8_t envVPAnt, envPPAnt;
 	uint8_t envVSust, envVRepS, envVRepE;
 	uint8_t envPSust, envPRepS, envPRepE;
@@ -191,26 +183,18 @@
 	uint8_t envVTyp, envPTyp;
 	uint8_t vibTyp, vibSweep, vibDepth, vibRate;
 	uint16_t fadeOut;
-	uint8_t midiOn, midiChannel;
-	int16_t midiProgram, midiBend;
-	uint8_t mute, reserved[15];
-	int16_t antSamp;
+	int16_t envVP[12][2], envPP[12][2], midiProgram, midiBend;
+	int16_t antSamp; // used by loader only
 	sampleTyp samp[16];
-}
-#ifdef __GNUC__
-__attribute__ ((packed))
-#endif
-instrTyp;
-#ifdef _MSC_VER
-#pragma pack(pop)
-#endif
+} instrTyp;
 
 typedef struct stmTyp_t
 {
+	bool envSustainActive, stOff, mute;
 	volatile uint8_t status, tmpStatus;
 	int8_t relTonNr, fineTune;
-	uint8_t sampleNr, instrNr, stOff, effTyp, eff, smpOffset, tremorSave, tremorPos;
-	uint8_t globVolSlideSpeed, panningSlideSpeed, mute, waveCtrl, portaDir;
+	uint8_t sampleNr, instrNr, effTyp, eff, smpOffset, tremorSave, tremorPos;
+	uint8_t globVolSlideSpeed, panningSlideSpeed, waveCtrl, portaDir;
 	uint8_t glissFunk, vibPos, tremPos, vibSpeed, vibDepth, tremSpeed, tremDepth;
 	uint8_t pattPos, loopCnt, volSlideSpeed, fVolSlideUpSpeed, fVolSlideDownSpeed;
 	uint8_t fPortaUpSpeed, fPortaDownSpeed, ePortaUpSpeed, ePortaDownSpeed;
@@ -217,7 +201,6 @@
 	uint8_t portaUpSpeed, portaDownSpeed, retrigSpeed, retrigCnt, retrigVol;
 	uint8_t volKolVol, tonNr, envPPos, eVibPos, envVPos, realVol, oldVol, outVol;
 	uint8_t oldPan, outPan, finalPan;
-	bool envSustainActive;
 	int16_t midiCurChannel, midiCurTone, midiCurVibDepth, midiCurPeriod, midiCurPitch;
 	int16_t midiBend, midiPortaPeriod, midiPitch, realPeriod, envVIPValue, envPIPValue;
 	uint16_t finalVol, outPeriod, finalPeriod, tonTyp, wantPeriod, portaSpeed;
@@ -230,15 +213,13 @@
 
 typedef struct songTyp_t
 {
-	uint8_t antChn, pattDelTime, pattDelTime2, pBreakPos, songTab[MAX_ORDERS];
 	bool pBreakFlag, posJumpFlag, isModified;
+	char name[20+1], instrName[1+MAX_INST][22+1];
+	uint8_t curReplayerTimer, curReplayerPattPos, curReplayerSongPos, curReplayerPattNr; // used for audio/video sync queue
+	uint8_t antChn, pattDelTime, pattDelTime2, pBreakPos, songTab[MAX_ORDERS];
 	int16_t songPos, pattNr, pattPos, pattLen;
 	uint16_t len, repS, speed, tempo, globVol, timer, ver, initialTempo;
-	char name[20 + 1], instrName[1 + MAX_INST][22 + 1];
 	uint32_t musicTime;
-
-	// used for audio/video sync queue
-	uint8_t curReplayerTimer, curReplayerPattPos, curReplayerSongPos, curReplayerPattNr;
 } songTyp;
 
 typedef struct tonTyp_t
--- a/src/ft2_sysreqs.c
+++ b/src/ft2_sysreqs.c
@@ -612,6 +612,9 @@
 // WARNING: This routine must NOT be called from the main input/video thread!
 int16_t okBoxThreadSafe(int16_t typ, const char *headline, const char *text)
 {
+	if (!editor.mainLoopOngoing)
+		return 0; // main loop was not even started yet, bail out.
+
 	// block multiple calls before they are completed (for safety)
 	while (okBoxData.active)
 		SDL_Delay(1000 / VBLANK_HZ);
--- a/src/ft2_wav_renderer.c
+++ b/src/ft2_wav_renderer.c
@@ -156,7 +156,6 @@
 
 static bool dump_Init(uint32_t frq, int16_t amp, int16_t songPos)
 {
-	uint8_t i, oldMuteFlags[MAX_VOICES];
 	uint32_t maxSamplesPerTick, sampleSize;
 
 	maxSamplesPerTick = ((frq * 5) / 2) / MIN_BPM; // absolute max samples per tidck
@@ -173,16 +172,7 @@
 	playMode = PLAYMODE_SONG;
 	songPlaying = true;
 
-	// store mute states
-	for (i = 0; i < MAX_VOICES; i++)
-		oldMuteFlags[i] = stm[i].stOff;
-
 	resetChannels();
-
-	// restore mute states
-	for (i = 0; i < MAX_VOICES; i++)
-		stm[i].stOff = oldMuteFlags[i];
-
 	setNewAudioFreq(frq);
 	setAudioAmp(amp, config.masterVol, (WDBitDepth == 32));
 
--- a/vs2019_project/ft2-clone/ft2-clone.vcxproj
+++ b/vs2019_project/ft2-clone/ft2-clone.vcxproj
@@ -26,13 +26,11 @@
   </PropertyGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
-    <UseDebugLibraries>true</UseDebugLibraries>
     <PlatformToolset>v142</PlatformToolset>
     <CharacterSet>MultiByte</CharacterSet>
     <WholeProgramOptimization>true</WholeProgramOptimization>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
-    <UseDebugLibraries>true</UseDebugLibraries>
     <CharacterSet>MultiByte</CharacterSet>
     <WholeProgramOptimization>true</WholeProgramOptimization>
     <PlatformToolset>v142</PlatformToolset>