shithub: pt2-clone

Download patch

ref: d3ed7576858d869b5a5c3ae5061162f2aa6765df
parent: 8b9bbe660ea1dad5f1f8e695e752ed24293533ec
author: Olav Sørensen <[email protected]>
date: Mon May 4 18:17:47 EDT 2020

Pushed v1.13 code

- Windows bugfix: Left Amiga key (Win-key) didn't work!
- Windows: Removed a broken Num Lock-specific hack. Seems like it didn't work
  anymore in later SDL2 versions. This means that the drumpad feature is pretty
  useless...
- Bugfix: Saving samples as .IFF would result in broken .IFF files
- Bugfix: Don't allow sample modifications in "sample zero" mode
- Bugfix: The "numpad 0" key wasn't behaving the way it should
- Bugfix: The "numpad enter" key wasn't behaving the way it should if
  the current sample was set to 16 (0010).
- Code cleanup. Moved certain chunks to new files, renamed modEntry struct etc.

--- /dev/null
+++ b/release/other/known bugs.txt
@@ -1,0 +1,7 @@
+Windows (and probably Linux?):
+- The num lock key (which is used to set the current sample to 1 or 17) will
+  also toggle the num lock state, which means that some of the numpad keys
+  work like arrow keys instead. This makes the drumpad function useless! I
+  used to have a Win32 API routine to fix this, but it seems to not work
+  anymore after some SDL2 version, so I removed it. SDL2 seems to handle num
+  lock internally these days, which ruins it.
--- /dev/null
+++ b/release/win32/known bugs.txt
@@ -1,0 +1,7 @@
+Windows (and probably Linux?):
+- The num lock key (which is used to set the current sample to 1 or 17) will
+  also toggle the num lock state, which means that some of the numpad keys
+  work like arrow keys instead. This makes the drumpad function useless! I
+  used to have a Win32 API routine to fix this, but it seems to not work
+  anymore after some SDL2 version, so I removed it. SDL2 seems to handle num
+  lock internally these days, which ruins it.
--- /dev/null
+++ b/release/win64/known bugs.txt
@@ -1,0 +1,7 @@
+Windows (and probably Linux?):
+- The num lock key (which is used to set the current sample to 1 or 17) will
+  also toggle the num lock state, which means that some of the numpad keys
+  work like arrow keys instead. This makes the drumpad function useless! I
+  used to have a Win32 API routine to fix this, but it seems to not work
+  anymore after some SDL2 version, so I removed it. SDL2 seems to handle num
+  lock internally these days, which ruins it.
--- a/src/pt2_audio.c
+++ b/src/pt2_audio.c
@@ -319,10 +319,10 @@
 
 	for (int32_t i = 0; i < AMIGA_VOICES; i++)
 	{
-		ch = &modEntry->channels[i];
+		ch = &song->channels[i];
 		if (ch->n_samplenum == editor.currSample)
 		{
-			s = &modEntry->samples[editor.currSample];
+			s = &song->samples[editor.currSample];
 			paulaSetData(i, ch->n_start + s->loopStart);
 			paulaSetLength(i, s->loopLength >> 1);
 		}
@@ -473,13 +473,13 @@
 	moduleSample_t *s;
 	scopeChannelExt_t *se, tmp;
 
-	smp = modEntry->channels[ch].n_samplenum;
+	smp = song->channels[ch].n_samplenum;
 	assert(smp <= 30);
-	s = &modEntry->samples[smp];
+	s = &song->samples[smp];
 
 	// set voice data
 	if (src == NULL)
-		src = &modEntry->sampleData[RESERVED_SAMPLE_OFFSET]; // dummy sample
+		src = &song->sampleData[RESERVED_SAMPLE_OFFSET]; // dummy sample
 
 	paula[ch].newData = src;
 
@@ -513,7 +513,7 @@
 
 	dat = v->newData;
 	if (dat == NULL)
-		dat = &modEntry->sampleData[RESERVED_SAMPLE_OFFSET]; // dummy sample
+		dat = &song->sampleData[RESERVED_SAMPLE_OFFSET]; // dummy sample
 
 	length = v->newLength;
 	if (length < 2)
@@ -533,7 +533,7 @@
 
 	dat = se->newData;
 	if (dat == NULL)
-		dat = &modEntry->sampleData[RESERVED_SAMPLE_OFFSET]; // dummy sample
+		dat = &song->sampleData[RESERVED_SAMPLE_OFFSET]; // dummy sample
 
 	s.length = length;
 	s.data = dat;
@@ -1138,7 +1138,7 @@
 
 	uint32_t maxSamplesToMix;
 	if (MOD2WAV_FREQ > audio.outputRate)
-		 maxSamplesToMix = audio.bpmTabMod2Wav[32-32]; // BPM 32
+		maxSamplesToMix = audio.bpmTabMod2Wav[32-32]; // BPM 32
 	else
 		maxSamplesToMix = audio.bpmTab[32-32]; // BPM 32
 
--- /dev/null
+++ b/src/pt2_bmp.c
@@ -1,0 +1,290 @@
+// for finding memory leaks in debug mode with Visual Studio 
+#if defined _DEBUG && defined _MSC_VER
+#include <crtdbg.h>
+#endif
+
+#include <stdint.h>
+#include <stdbool.h>
+#include "pt2_palette.h"
+#include "pt2_structs.h"
+#include "pt2_helpers.h"
+#include "pt2_bmp.h"
+#include "pt2_tables.h"
+
+uint32_t *aboutScreenBMP   = NULL, *clearDialogBMP     = NULL;
+uint32_t *diskOpScreenBMP  = NULL, *editOpModeCharsBMP = NULL, *mod2wavBMP         = NULL;
+uint32_t *editOpScreen1BMP = NULL, *editOpScreen2BMP   = NULL, *samplerVolumeBMP   = NULL;
+uint32_t *editOpScreen3BMP = NULL, *editOpScreen4BMP   = NULL, *spectrumVisualsBMP = NULL;
+uint32_t *muteButtonsBMP   = NULL, *posEdBMP           = NULL, *samplerFiltersBMP  = NULL;
+uint32_t *samplerScreenBMP = NULL, *pat2SmpDialogBMP   = NULL, *trackerFrameBMP    = NULL;
+uint32_t *yesNoDialogBMP   = NULL, *bigYesNoDialogBMP  = NULL;
+
+void createBitmaps(void)
+{
+	uint8_t r8, g8, b8, r8_2, g8_2, b8_2;
+	uint16_t pixel12;
+	uint32_t i, j, x, y, pixel24;
+
+	pixel24 = video.palette[PAL_PATCURSOR];
+	for (y = 0; y < 14; y++)
+	{
+		// top two rows have a lighter color
+		if (y < 2)
+		{
+			r8 = R24(pixel24);
+			g8 = G24(pixel24);
+			b8 = B24(pixel24);
+
+			if (r8 <= 0xFF-0x33)
+				r8 += 0x33;
+			else
+				r8 = 0xFF;
+
+			if (g8 <= 0xFF-0x33)
+				g8 += 0x33;
+			else
+				g8 = 0xFF;
+
+			if (b8 <= 0xFF-0x33)
+				b8 += 0x33;
+			else
+				b8 = 0xFF;
+
+			for (x = 0; x < 11; x++)
+				patternCursorBMP[(y * 11) + x] = RGB24(r8, g8, b8);
+		}
+
+		// sides (same color)
+		if (y >= 2 && y <= 12)
+		{
+			patternCursorBMP[(y * 11) + 0] = pixel24;
+
+			for (x = 1; x < 10; x++)
+				patternCursorBMP[(y * 11) + x] = video.palette[PAL_COLORKEY];
+
+			patternCursorBMP[(y * 11) + 10] = pixel24;
+		}
+
+		// bottom two rows have a darker color
+		if (y > 11)
+		{
+			r8 = R24(pixel24);
+			g8 = G24(pixel24);
+			b8 = B24(pixel24);
+
+			if (r8 >= 0x33)
+				r8 -= 0x33;
+			else
+				r8 = 0x00;
+
+			if (g8 >= 0x33)
+				g8 -= 0x33;
+			else
+				g8 = 0x00;
+
+			if (b8 >= 0x33)
+				b8 -= 0x33;
+			else
+				b8 = 0x00;
+
+			for (x = 0; x < 11; x++)
+				patternCursorBMP[(y * 11) + x] = RGB24(r8, g8, b8);
+		}
+	}
+
+	// create spectrum analyzer bar graphics
+	for (i = 0; i < 36; i++)
+		spectrumAnaBMP[i] = RGB12_to_RGB24(analyzerColors[35-i]);
+
+	// create VU-Meter bar graphics
+	for (i = 0; i < 48; i++)
+	{
+		pixel12 = vuMeterColors[47-i];
+
+		r8_2 = r8 = R12_to_R24(pixel12);
+		g8_2 = g8 = G12_to_G24(pixel12);
+		b8_2 = b8 = B12_to_B24(pixel12);
+
+		// brighter pixels on the left side
+
+		if (r8_2 <= 0xFF-0x33)
+			r8_2 += 0x33;
+		else
+			r8_2 = 0xFF;
+
+		if (g8_2 <= 0xFF-0x33)
+			g8_2 += 0x33;
+		else
+			g8_2 = 0xFF;
+
+		if (b8_2 <= 0xFF-0x33)
+			b8_2 += 0x33;
+		else
+			b8_2 = 0xFF;
+
+		pixel24 = RGB24(r8_2, g8_2, b8_2);
+
+		vuMeterBMP[(i * 10) + 0] = pixel24;
+		vuMeterBMP[(i * 10) + 1] = pixel24;
+
+		// main pixels
+		for (j = 2; j < 8; j++)
+			vuMeterBMP[(i * 10) + j] = RGB24(r8, g8, b8);
+
+		// darker pixels on the right side
+		r8_2 = r8;
+		g8_2 = g8;
+		b8_2 = b8;
+
+		if (r8_2 >= 0x33)
+			r8_2 -= 0x33;
+		else
+			r8_2 = 0x00;
+
+		if (g8_2 >= 0x33)
+			g8_2 -= 0x33;
+		else
+			g8_2 = 0x00;
+
+		if (b8_2 >= 0x33)
+			b8_2 -= 0x33;
+		else
+			b8_2 = 0x00;
+
+		pixel24 = RGB24(r8_2, g8_2, b8_2);
+
+		vuMeterBMP[(i * 10) + 8] = pixel24;
+		vuMeterBMP[(i * 10) + 9] = pixel24;
+	}
+}
+
+void freeBMPs(void)
+{
+	if (trackerFrameBMP != NULL) free(trackerFrameBMP);
+	if (samplerScreenBMP != NULL) free(samplerScreenBMP);
+	if (samplerVolumeBMP != NULL) free(samplerVolumeBMP);
+	if (samplerFiltersBMP != NULL) free(samplerFiltersBMP);
+	if (clearDialogBMP != NULL) free(clearDialogBMP);
+	if (diskOpScreenBMP != NULL) free(diskOpScreenBMP);
+	if (mod2wavBMP != NULL) free(mod2wavBMP);
+	if (posEdBMP != NULL) free(posEdBMP);
+	if (spectrumVisualsBMP != NULL) free(spectrumVisualsBMP);
+	if (yesNoDialogBMP != NULL) free(yesNoDialogBMP);
+	if (bigYesNoDialogBMP != NULL) free(bigYesNoDialogBMP);
+	if (pat2SmpDialogBMP != NULL) free(pat2SmpDialogBMP);
+	if (editOpScreen1BMP != NULL) free(editOpScreen1BMP);
+	if (editOpScreen2BMP != NULL) free(editOpScreen2BMP);
+	if (editOpScreen3BMP != NULL) free(editOpScreen3BMP);
+	if (editOpScreen4BMP != NULL) free(editOpScreen4BMP);
+	if (aboutScreenBMP != NULL) free(aboutScreenBMP);
+	if (muteButtonsBMP != NULL) free(muteButtonsBMP);
+	if (editOpModeCharsBMP != NULL) free(editOpModeCharsBMP);
+}
+
+uint32_t *unpackBMP(const uint8_t *src, uint32_t packedLen)
+{
+	const uint8_t *packSrc;
+	uint8_t *tmpBuffer, *packDst, byteIn;
+	int16_t count;
+	uint32_t *dst, decodedLength, i;
+
+	// RLE decode
+	decodedLength = (src[0] << 24) | (src[1] << 16) | (src[2] << 8) | src[3];
+
+	// 2-bit to 8-bit conversion
+	dst = (uint32_t *)malloc((decodedLength * 4) * sizeof (int32_t));
+	if (dst == NULL)
+		return NULL;
+
+	tmpBuffer = (uint8_t *)malloc(decodedLength + 512); // some margin is needed, the packer is buggy
+	if (tmpBuffer == NULL)
+	{
+		free(dst);
+		return NULL;
+	}
+
+	packSrc = src + 4;
+	packDst = tmpBuffer;
+
+	i = packedLen - 4;
+	while (i > 0)
+	{
+		byteIn = *packSrc++;
+		if (byteIn == 0xCC) // compactor code
+		{
+			count  = *packSrc++;
+			byteIn = *packSrc++;
+
+			while (count-- >= 0)
+				*packDst++ = byteIn;
+
+			i -= 2;
+		}
+		else
+		{
+			*packDst++ = byteIn;
+		}
+
+		i--;
+	}
+
+	for (i = 0; i < decodedLength; i++)
+	{
+		byteIn = (tmpBuffer[i] & 0xC0) >> 6;
+		assert(byteIn < PALETTE_NUM);
+		dst[(i * 4) + 0] = video.palette[byteIn];
+
+		byteIn = (tmpBuffer[i] & 0x30) >> 4;
+		assert(byteIn < PALETTE_NUM);
+		dst[(i * 4) + 1] = video.palette[byteIn];
+
+		byteIn = (tmpBuffer[i] & 0x0C) >> 2;
+		assert(byteIn < PALETTE_NUM);
+		dst[(i * 4) + 2] = video.palette[byteIn];
+
+		byteIn = (tmpBuffer[i] & 0x03) >> 0;
+		assert(byteIn < PALETTE_NUM);
+		dst[(i * 4) + 3] = video.palette[byteIn];
+	}
+
+	free(tmpBuffer);
+	return dst;
+}
+
+bool unpackBMPs(void)
+{
+	trackerFrameBMP = unpackBMP(trackerFramePackedBMP, sizeof (trackerFramePackedBMP));
+	samplerScreenBMP = unpackBMP(samplerScreenPackedBMP, sizeof (samplerScreenPackedBMP));
+	samplerVolumeBMP = unpackBMP(samplerVolumePackedBMP, sizeof (samplerVolumePackedBMP));
+	samplerFiltersBMP = unpackBMP(samplerFiltersPackedBMP, sizeof (samplerFiltersPackedBMP));
+	clearDialogBMP = unpackBMP(clearDialogPackedBMP, sizeof (clearDialogPackedBMP));
+	diskOpScreenBMP = unpackBMP(diskOpScreenPackedBMP, sizeof (diskOpScreenPackedBMP));
+	mod2wavBMP = unpackBMP(mod2wavPackedBMP, sizeof (mod2wavPackedBMP));
+	posEdBMP = unpackBMP(posEdPackedBMP, sizeof (posEdPackedBMP));
+	spectrumVisualsBMP = unpackBMP(spectrumVisualsPackedBMP, sizeof (spectrumVisualsPackedBMP));
+	yesNoDialogBMP = unpackBMP(yesNoDialogPackedBMP, sizeof (yesNoDialogPackedBMP));
+	bigYesNoDialogBMP = unpackBMP(bigYesNoDialogPackedBMP, sizeof (bigYesNoDialogPackedBMP));
+	pat2SmpDialogBMP = unpackBMP(pat2SmpDialogPackedBMP, sizeof (pat2SmpDialogPackedBMP));
+	editOpScreen1BMP = unpackBMP(editOpScreen1PackedBMP, sizeof (editOpScreen1PackedBMP));
+	editOpScreen2BMP = unpackBMP(editOpScreen2PackedBMP, sizeof (editOpScreen2PackedBMP));
+	editOpScreen3BMP = unpackBMP(editOpScreen3PackedBMP, sizeof (editOpScreen3PackedBMP));
+	editOpScreen4BMP = unpackBMP(editOpScreen4PackedBMP, sizeof (editOpScreen4PackedBMP));
+	aboutScreenBMP = unpackBMP(aboutScreenPackedBMP, sizeof (aboutScreenPackedBMP));
+	muteButtonsBMP = unpackBMP(muteButtonsPackedBMP, sizeof (muteButtonsPackedBMP));
+	editOpModeCharsBMP = unpackBMP(editOpModeCharsPackedBMP, sizeof (editOpModeCharsPackedBMP));
+
+	if (trackerFrameBMP    == NULL || samplerScreenBMP   == NULL || samplerVolumeBMP  == NULL ||
+		clearDialogBMP     == NULL || diskOpScreenBMP    == NULL || mod2wavBMP        == NULL ||
+		posEdBMP           == NULL || spectrumVisualsBMP == NULL || yesNoDialogBMP    == NULL ||
+		editOpScreen1BMP   == NULL || editOpScreen2BMP   == NULL || editOpScreen3BMP  == NULL ||
+		editOpScreen4BMP   == NULL || aboutScreenBMP     == NULL || muteButtonsBMP    == NULL ||
+		editOpModeCharsBMP == NULL || samplerFiltersBMP  == NULL || yesNoDialogBMP    == NULL ||
+		bigYesNoDialogBMP  == NULL)
+	{
+		showErrorMsgBox("Out of memory!");
+		return false; // BMPs are free'd in cleanUp()
+	}
+
+	createBitmaps();
+	return true;
+}
--- /dev/null
+++ b/src/pt2_bmp.h
@@ -1,0 +1,69 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#define EDOP_MODE_BMP_A_OFS ((7 * 6) * 0)
+#define EDOP_MODE_BMP_C_OFS ((7 * 6) * 1)
+#define EDOP_MODE_BMP_H_OFS ((7 * 6) * 2)
+#define EDOP_MODE_BMP_N_OFS ((7 * 6) * 3)
+#define EDOP_MODE_BMP_O_OFS ((7 * 6) * 4)
+#define EDOP_MODE_BMP_P_OFS ((7 * 6) * 5)
+#define EDOP_MODE_BMP_S_OFS ((7 * 6) * 6)
+#define EDOP_MODE_BMP_T_OFS ((7 * 6) * 7)
+
+// GFX
+extern uint32_t iconBMP[1024];
+extern const uint8_t mousePointerBMP[256];
+extern const uint8_t fontBMP[6096];
+
+// PACKED GFX
+extern const uint8_t aboutScreenPackedBMP[1684];
+extern const uint8_t clearDialogPackedBMP[525];
+extern const uint8_t diskOpScreenPackedBMP[1898];
+extern const uint8_t editOpModeCharsPackedBMP[88];
+extern const uint8_t editOpScreen1PackedBMP[1481];
+extern const uint8_t editOpScreen2PackedBMP[1502];
+extern const uint8_t editOpScreen3PackedBMP[1736];
+extern const uint8_t editOpScreen4PackedBMP[1713];
+extern const uint8_t mod2wavPackedBMP[607];
+extern const uint8_t muteButtonsPackedBMP[46];
+extern const uint8_t posEdPackedBMP[1375];
+extern const uint8_t samplerVolumePackedBMP[706];
+extern const uint8_t samplerFiltersPackedBMP[933];
+extern const uint8_t samplerScreenPackedBMP[3056];
+extern const uint8_t spectrumVisualsPackedBMP[2217];
+extern const uint8_t trackerFramePackedBMP[8486];
+extern const uint8_t yesNoDialogPackedBMP[476];
+extern const uint8_t bigYesNoDialogPackedBMP[472];
+extern const uint8_t pat2SmpDialogPackedBMP[520];
+
+// these are filled/normalized on init, so no const
+extern uint32_t vuMeterBMP[480];
+extern uint32_t loopPinsBMP[512];
+extern uint32_t samplingPosBMP[64];
+extern uint32_t spectrumAnaBMP[36];
+extern uint32_t patternCursorBMP[154];
+extern uint32_t *editOpScreen1BMP;
+extern uint32_t *editOpScreen2BMP;
+extern uint32_t *editOpScreen3BMP;
+extern uint32_t *editOpScreen4BMP;
+extern uint32_t *yesNoDialogBMP;
+extern uint32_t *bigYesNoDialogBMP;
+extern uint32_t *spectrumVisualsBMP;
+extern uint32_t *posEdBMP;
+extern uint32_t *mod2wavBMP;
+extern uint32_t *diskOpScreenBMP;
+extern uint32_t *clearDialogBMP;
+extern uint32_t *samplerVolumeBMP;
+extern uint32_t *samplerFiltersBMP;
+extern uint32_t *samplerScreenBMP;
+extern uint32_t *trackerFrameBMP;
+extern uint32_t *aboutScreenBMP;
+extern uint32_t *muteButtonsBMP;
+extern uint32_t *editOpModeCharsBMP;
+extern uint32_t *pat2SmpDialogBMP;
+
+bool unpackBMPs(void);
+void createBitmaps(void);
+void freeBMPs(void);
--- a/src/pt2_config.c
+++ b/src/pt2_config.c
@@ -13,13 +13,12 @@
 #include <unistd.h>
 #include <limits.h>
 #endif
-#include "pt2_helpers.h"
 #include "pt2_header.h"
+#include "pt2_helpers.h"
 #include "pt2_config.h"
 #include "pt2_tables.h"
 #include "pt2_audio.h"
 #include "pt2_diskop.h"
-#include "pt2_config.h"
 #include "pt2_textout.h"
 #include "pt2_sampler.h"
 
--- a/src/pt2_diskop.c
+++ b/src/pt2_diskop.c
@@ -29,7 +29,7 @@
 #include "pt2_diskop.h"
 #include "pt2_tables.h"
 #include "pt2_palette.h"
-#include "pt2_modloader.h"
+#include "pt2_module_loader.h"
 #include "pt2_audio.h"
 #include "pt2_sampler.h"
 #include "pt2_config.h"
@@ -36,7 +36,7 @@
 #include "pt2_helpers.h"
 #include "pt2_keyboard.h"
 #include "pt2_visuals.h"
-#include "pt2_sampleloader.h"
+#include "pt2_sample_loader.h"
 
 typedef struct fileEntry_t
 {
@@ -68,6 +68,16 @@
 static UNICHAR pathTmp[PATH_MAX + 2];
 static fileEntry_t *diskOpEntry;
 
+void addSampleFileExt(char *fileName)
+{
+	switch (diskop.smpSaveType)
+	{
+		case DISKOP_SMP_WAV: strcat(fileName, ".wav"); break;
+		case DISKOP_SMP_IFF: strcat(fileName, ".iff"); break;
+		default: case DISKOP_SMP_RAW: break;
+	}
+}
+
 static fileEntry_t *bufferCreateEmptyDir(void) // special case: creates a dir entry with a ".." directory
 {
 	fileEntry_t *dirEntry;
@@ -861,7 +871,6 @@
 {
 	uint8_t oldMode, oldPlayMode;
 	UNICHAR *filePath;
-	module_t *tempMod;
 
 	// if we clicked on an empty space, return...
 	if (diskOpEntryIsEmpty(fileEntryRow))
@@ -878,7 +887,7 @@
 		{
 			if (diskop.mode == DISKOP_MODE_MOD)
 			{
-				if (songModifiedCheck && modEntry->modified)
+				if (songModifiedCheck && song->modified)
 				{
 					oldFileEntryRow = fileEntryRow;
 					showSongUnsavedAskBox(ASK_DISCARD_SONG);
@@ -885,8 +894,8 @@
 					return;
 				}
 
-				tempMod = modLoad(filePath);
-				if (tempMod != NULL)
+				module_t *newSong = modLoad(filePath);
+				if (newSong != NULL)
 				{
 					oldMode = editor.currMode;
 					oldPlayMode = editor.playMode;
@@ -894,9 +903,9 @@
 					modStop();
 					modFree();
 
-					modEntry = tempMod;
+					song = newSong;
 					setupNewMod();
-					modEntry->moduleLoaded = true;
+					song->loaded = true;
 
 					statusAllRight();
 
--- a/src/pt2_diskop.h
+++ b/src/pt2_diskop.h
@@ -11,6 +11,7 @@
 
 #define DISKOP_LINES 10
 
+void addSampleFileExt(char *fileName);
 void diskOpShowSelectText(void);
 void diskOpLoadFile(uint32_t fileEntryRow, bool songModifiedCheck);
 void diskOpLoadFile2(void);
--- a/src/pt2_edit.c
+++ b/src/pt2_edit.c
@@ -19,7 +19,6 @@
 #include "pt2_textout.h"
 #include "pt2_tables.h"
 #include "pt2_audio.h"
-#include "pt2_helpers.h"
 #include "pt2_palette.h"
 #include "pt2_diskop.h"
 #include "pt2_mouse.h"
@@ -28,6 +27,7 @@
 #include "pt2_keyboard.h"
 #include "pt2_scopes.h"
 #include "pt2_structs.h"
+#include "pt2_config.h"
 
 const int8_t scancode2NoteLo[52] = // "USB usage page standard" order
 {
@@ -151,7 +151,7 @@
 		if (ui.editObject == PTB_SONGNAME)
 		{
 			for (i = 0; i < 20; i++)
-				modEntry->head.moduleTitle[i] = (char)tolower(modEntry->head.moduleTitle[i]);
+				song->header.name[i] = (char)tolower(song->header.name[i]);
 		}
 
 		pointerSetPreviousMode();
@@ -163,7 +163,7 @@
 	{
 		// set back GUI text pointers and update values (if requested)
 
-		s = &modEntry->samples[editor.currSample];
+		s = &song->samples[editor.currSample];
 		switch (ui.editObject)
 		{
 			case PTB_SA_FIL_LP_CUTOFF:
@@ -247,8 +247,8 @@
 				if (updateValue)
 				{
 					editor.samplePos = ui.tmpDisp16;
-					if (editor.samplePos > modEntry->samples[editor.currSample].length)
-						editor.samplePos = modEntry->samples[editor.currSample].length;
+					if (editor.samplePos > song->samples[editor.currSample].length)
+						editor.samplePos = song->samples[editor.currSample].length;
 
 					ui.updatePosText = true;
 				}
@@ -336,11 +336,11 @@
 
 			case PTB_PE_PATT:
 			{
-				posEdPos = modEntry->currOrder;
-				if (posEdPos > modEntry->head.orderCount-1)
-					posEdPos = modEntry->head.orderCount-1;
+				posEdPos = song->currOrder;
+				if (posEdPos > song->header.numOrders-1)
+					posEdPos = song->header.numOrders-1;
 
-				editor.currPosEdPattDisp = &modEntry->head.order[posEdPos];
+				editor.currPosEdPattDisp = &song->header.order[posEdPos];
 
 				if (updateValue)
 				{
@@ -347,7 +347,7 @@
 					if (ui.tmpDisp16 > MAX_PATTERNS-1)
 						ui.tmpDisp16 = MAX_PATTERNS-1;
 
-					modEntry->head.order[posEdPos] = ui.tmpDisp16;
+					song->header.order[posEdPos] = ui.tmpDisp16;
 
 					updateWindowTitle(MOD_IS_MODIFIED);
 
@@ -362,7 +362,7 @@
 
 			case PTB_POSS:
 			{
-				editor.currPosDisp = &modEntry->currOrder;
+				editor.currPosDisp = &song->currOrder;
 
 				if (updateValue)
 				{
@@ -370,10 +370,10 @@
 					if (tmp16 > 126)
 						tmp16 = 126;
 
-					if (modEntry->currOrder != tmp16)
+					if (song->currOrder != tmp16)
 					{
-						modEntry->currOrder = tmp16;
-						editor.currPatternDisp = &modEntry->head.order[modEntry->currOrder];
+						song->currOrder = tmp16;
+						editor.currPatternDisp = &song->header.order[song->currOrder];
 
 						if (ui.posEdScreenShown)
 							ui.updatePosEd = true;
@@ -387,7 +387,7 @@
 
 			case PTB_PATTERNS:
 			{
-				editor.currPatternDisp = &modEntry->head.order[modEntry->currOrder];
+				editor.currPatternDisp = &song->header.order[song->currOrder];
 
 				if (updateValue)
 				{
@@ -395,9 +395,9 @@
 					if (tmp16 > MAX_PATTERNS-1)
 						tmp16 = MAX_PATTERNS-1;
 
-					if (modEntry->head.order[modEntry->currOrder] != tmp16)
+					if (song->header.order[song->currOrder] != tmp16)
 					{
-						modEntry->head.order[modEntry->currOrder] = tmp16;
+						song->header.order[song->currOrder] = tmp16;
 
 						updateWindowTitle(MOD_IS_MODIFIED);
 
@@ -413,21 +413,21 @@
 
 			case PTB_LENGTHS:
 			{
-				editor.currLengthDisp = &modEntry->head.orderCount;
+				editor.currLengthDisp = &song->header.numOrders;
 
 				if (updateValue)
 				{
 					tmp16 = CLAMP(ui.tmpDisp16, 1, 127);
 
-					if (modEntry->head.orderCount != tmp16)
+					if (song->header.numOrders != tmp16)
 					{
-						modEntry->head.orderCount = tmp16;
+						song->header.numOrders = tmp16;
 
-						posEdPos = modEntry->currOrder;
-						if (posEdPos > modEntry->head.orderCount-1)
-							posEdPos = modEntry->head.orderCount-1;
+						posEdPos = song->currOrder;
+						if (posEdPos > song->header.numOrders-1)
+							posEdPos = song->header.numOrders-1;
 
-						editor.currPosEdPattDisp = &modEntry->head.order[posEdPos];
+						editor.currPosEdPattDisp = &song->header.order[posEdPos];
 
 						if (ui.posEdScreenShown)
 							ui.updatePosEd = true;
@@ -442,11 +442,11 @@
 
 			case PTB_PATTDATA:
 			{
-				editor.currEditPatternDisp = &modEntry->currPattern;
+				editor.currEditPatternDisp = &song->currPattern;
 
 				if (updateValue)
 				{
-					if (modEntry->currPattern != ui.tmpDisp16)
+					if (song->currPattern != ui.tmpDisp16)
 					{
 						setPattern(ui.tmpDisp16);
 						ui.updatePatternData = true;
@@ -692,7 +692,7 @@
 		if (handleSpecialKeys(scancode))
 		{
 			if (editor.currMode != MODE_RECORD)
-				modSetPos(DONT_SET_ORDER, (modEntry->currRow + editor.editMoveAdd) & 0x3F);
+				modSetPos(DONT_SET_ORDER, (song->currRow + editor.editMoveAdd) & 0x3F);
 
 			return;
 		}
@@ -736,7 +736,7 @@
 				key += hexKey;
 			}
 
-			note = &modEntry->patterns[modEntry->currPattern][(modEntry->currRow * AMIGA_VOICES) + cursor.channel];
+			note = &song->patterns[song->currPattern][(song->currRow * AMIGA_VOICES) + cursor.channel];
 
 			switch (cursor.mode)
 			{
@@ -747,7 +747,7 @@
 						note->sample = (uint8_t)((note->sample % 0x10) | (key << 4));
 
 						if (editor.currMode != MODE_RECORD)
-							modSetPos(DONT_SET_ORDER, (modEntry->currRow + editor.editMoveAdd) & 0x3F);
+							modSetPos(DONT_SET_ORDER, (song->currRow + editor.editMoveAdd) & 0x3F);
 
 						updateWindowTitle(MOD_IS_MODIFIED);
 					}
@@ -761,7 +761,7 @@
 						note->sample = (uint8_t)((note->sample & 16) | key);
 
 						if (editor.currMode != MODE_RECORD)
-							modSetPos(DONT_SET_ORDER, (modEntry->currRow + editor.editMoveAdd) & 0x3F);
+							modSetPos(DONT_SET_ORDER, (song->currRow + editor.editMoveAdd) & 0x3F);
 
 						updateWindowTitle(MOD_IS_MODIFIED);
 					}
@@ -775,7 +775,7 @@
 						note->command = (uint8_t)key;
 
 						if (editor.currMode != MODE_RECORD)
-							modSetPos(DONT_SET_ORDER, (modEntry->currRow + editor.editMoveAdd) & 0x3F);
+							modSetPos(DONT_SET_ORDER, (song->currRow + editor.editMoveAdd) & 0x3F);
 
 						updateWindowTitle(MOD_IS_MODIFIED);
 					}
@@ -789,7 +789,7 @@
 						note->param = (uint8_t)((note->param % 0x10) | (key << 4));
 
 						if (editor.currMode != MODE_RECORD)
-							modSetPos(DONT_SET_ORDER, (modEntry->currRow + editor.editMoveAdd) & 0x3F);
+							modSetPos(DONT_SET_ORDER, (song->currRow + editor.editMoveAdd) & 0x3F);
 
 						updateWindowTitle(MOD_IS_MODIFIED);
 					}
@@ -803,7 +803,7 @@
 						note->param = (uint8_t)((note->param & 0xF0) | key);
 
 						if (editor.currMode != MODE_RECORD)
-							modSetPos(DONT_SET_ORDER, (modEntry->currRow + editor.editMoveAdd) & 0x3F);
+							modSetPos(DONT_SET_ORDER, (song->currRow + editor.editMoveAdd) & 0x3F);
 
 						updateWindowTitle(MOD_IS_MODIFIED);
 					}
@@ -820,7 +820,7 @@
 		{
 			if (editor.currMode == MODE_EDIT || editor.currMode == MODE_RECORD)
 			{
-				note = &modEntry->patterns[modEntry->currPattern][(modEntry->currRow * AMIGA_VOICES) + cursor.channel];
+				note = &song->patterns[song->currPattern][(song->currRow * AMIGA_VOICES) + cursor.channel];
 
 				if (!keyb.leftAltPressed)
 				{
@@ -835,7 +835,7 @@
 				}
 
 				if (editor.currMode != MODE_RECORD)
-					modSetPos(DONT_SET_ORDER, (modEntry->currRow + editor.editMoveAdd) & 0x3F);
+					modSetPos(DONT_SET_ORDER, (song->currRow + editor.editMoveAdd) & 0x3F);
 
 				updateWindowTitle(MOD_IS_MODIFIED);
 			}
@@ -858,9 +858,9 @@
 	if (!keyb.leftAltPressed)
 		return false;
 
-	patt = modEntry->patterns[modEntry->currPattern];
-	note = &patt[(modEntry->currRow * AMIGA_VOICES) + cursor.channel];
-	prevNote = &patt[(((modEntry->currRow - 1) & 0x3F) * AMIGA_VOICES) + cursor.channel];
+	patt = song->patterns[song->currPattern];
+	note = &patt[(song->currRow * AMIGA_VOICES) + cursor.channel];
+	prevNote = &patt[(((song->currRow - 1) & 0x3F) * AMIGA_VOICES) + cursor.channel];
 
 	if (scancode >= SDL_SCANCODE_1 && scancode <= SDL_SCANCODE_0)
 	{
@@ -918,13 +918,13 @@
 	ch = cursor.channel;
 	assert(ch < AMIGA_VOICES);
 
-	chn = &modEntry->channels[ch];
-	note = &modEntry->patterns[modEntry->currPattern][(quantizeCheck(modEntry->currRow) * AMIGA_VOICES) + ch];
+	chn = &song->channels[ch];
+	note = &song->patterns[song->currPattern][(quantizeCheck(song->currRow) * AMIGA_VOICES) + ch];
 
 	noteVal = normalMode ? keyToNote(scancode) : pNoteTable[editor.currSample];
 	if (noteVal >= 0)
 	{
-		s = &modEntry->samples[editor.currSample];
+		s = &song->samples[editor.currSample];
 
 		tempPeriod  = periodTable[((s->fineTune & 0xF) * 37) + noteVal];
 		cleanPeriod = periodTable[noteVal];
@@ -939,9 +939,9 @@
 			chn->n_samplenum = editor.currSample;
 			chn->n_volume = s->volume;
 			chn->n_period = tempPeriod;
-			chn->n_start = &modEntry->sampleData[s->offset];
+			chn->n_start = &song->sampleData[s->offset];
 			chn->n_length = (s->loopStart > 0) ? (s->loopStart + s->loopLength) >> 1 : s->length >> 1;
-			chn->n_loopstart = &modEntry->sampleData[s->offset + s->loopStart];
+			chn->n_loopstart = &song->sampleData[s->offset + s->loopStart];
 			chn->n_replen = s->loopLength >> 1;
 
 			if (chn->n_length == 0)
@@ -980,7 +980,7 @@
 					}
 
 					if (editor.currMode != MODE_RECORD)
-						modSetPos(DONT_SET_ORDER, (modEntry->currRow + editor.editMoveAdd) & 0x3F);
+						modSetPos(DONT_SET_ORDER, (song->currRow + editor.editMoveAdd) & 0x3F);
 
 					updateWindowTitle(MOD_IS_MODIFIED);
 				}
@@ -1004,7 +1004,7 @@
 				note->sample = 0;
 
 				if (editor.currMode != MODE_RECORD)
-					modSetPos(DONT_SET_ORDER, (modEntry->currRow + editor.editMoveAdd) & 0x3F);
+					modSetPos(DONT_SET_ORDER, (song->currRow + editor.editMoveAdd) & 0x3F);
 
 				updateWindowTitle(MOD_IS_MODIFIED);
 			}
@@ -1014,9 +1014,11 @@
 
 uint8_t quantizeCheck(uint8_t row)
 {
-	uint8_t tempRow, quantize;
+	assert(song != NULL);
+	if (song == NULL)
+		return row;
 
-	quantize = (uint8_t)config.quantizeValue;
+	const uint8_t quantize = (uint8_t)config.quantizeValue;
 
 	editor.didQuantize = false;
 	if (editor.currMode == MODE_RECORD)
@@ -1027,7 +1029,7 @@
 		}
 		else if (quantize == 1)
 		{
-			if (editor.modTick > editor.modSpeed/2)
+			if (song->tick > song->speed>>1)
 			{
 				row = (row + 1) & 0x3F;
 				editor.didQuantize = true;
@@ -1035,7 +1037,7 @@
 		}
 		else
 		{
-			tempRow = ((((quantize / 2) + row) & 0x3F) / quantize) * quantize;
+			uint8_t tempRow = ((((quantize >> 1) + row) & 0x3F) / quantize) * quantize;
 			if (tempRow > row)
 				editor.didQuantize = true;
 
@@ -1048,7 +1050,7 @@
 
 void saveUndo(void)
 {
-	memcpy(editor.undoBuffer, modEntry->patterns[modEntry->currPattern], sizeof (note_t) * (AMIGA_VOICES * MOD_ROWS));
+	memcpy(editor.undoBuffer, song->patterns[song->currPattern], sizeof (note_t) * (AMIGA_VOICES * MOD_ROWS));
 }
 
 void undoLastChange(void)
@@ -1058,8 +1060,8 @@
 	for (uint16_t i = 0; i < MOD_ROWS*AMIGA_VOICES; i++)
 	{
 		data = editor.undoBuffer[i];
-		editor.undoBuffer[i] = modEntry->patterns[modEntry->currPattern][i];
-		modEntry->patterns[modEntry->currPattern][i] = data;
+		editor.undoBuffer[i] = song->patterns[song->currPattern][i];
+		song->patterns[song->currPattern][i] = data;
 	}
 
 	updateWindowTitle(MOD_IS_MODIFIED);
@@ -1084,8 +1086,8 @@
 			return;
 		}
 
-		smpTo = &modEntry->samples[editor.sampleTo - 1];
-		smpFrom = &modEntry->samples[editor.sampleFrom - 1];
+		smpTo = &song->samples[editor.sampleTo - 1];
+		smpFrom = &song->samples[editor.sampleFrom - 1];
 
 		turnOffVoices();
 
@@ -1101,7 +1103,7 @@
 		smpTo->loopLengthDisp = &smpTo->loopLength;
 
 		// copy sample data
-		memcpy(&modEntry->sampleData[smpTo->offset], &modEntry->sampleData[smpFrom->offset], MAX_SAMPLE_LEN);
+		memcpy(&song->sampleData[smpTo->offset], &song->sampleData[smpFrom->offset], MAX_SAMPLE_LEN);
 
 		updateCurrSample();
 		ui.updateSongSize = true;
@@ -1113,7 +1115,7 @@
 		{
 			for (i = 0; i < MOD_ROWS; i++)
 			{
-				noteSrc = &modEntry->patterns[modEntry->currPattern][(i * AMIGA_VOICES) + cursor.channel];
+				noteSrc = &song->patterns[song->currPattern][(i * AMIGA_VOICES) + cursor.channel];
 				if (noteSrc->sample == editor.sampleFrom)
 					noteSrc->sample = editor.sampleTo;
 			}
@@ -1124,7 +1126,7 @@
 			{
 				for (uint8_t j = 0; j < MOD_ROWS; j++)
 				{
-					noteSrc = &modEntry->patterns[modEntry->currPattern][(j * AMIGA_VOICES) + i];
+					noteSrc = &song->patterns[song->currPattern][(j * AMIGA_VOICES) + i];
 					if (noteSrc->sample == editor.sampleFrom)
 						noteSrc->sample = editor.sampleTo;
 				}
@@ -1158,8 +1160,8 @@
 			return;
 		}
 
-		smpTo = &modEntry->samples[editor.sampleTo-1];
-		smpFrom = &modEntry->samples[editor.sampleFrom-1];
+		smpTo = &song->samples[editor.sampleTo-1];
+		smpFrom = &song->samples[editor.sampleFrom-1];
 
 		turnOffVoices();
 
@@ -1186,9 +1188,9 @@
 		// swap sample data
 		for (i = 0; i < MAX_SAMPLE_LEN; i++)
 		{
-			smp = modEntry->sampleData[smpFrom->offset+i];
-			modEntry->sampleData[smpFrom->offset+i] = modEntry->sampleData[smpTo->offset+i];
-			modEntry->sampleData[smpTo->offset+i] = smp;
+			smp = song->sampleData[smpFrom->offset+i];
+			song->sampleData[smpFrom->offset+i] = song->sampleData[smpTo->offset+i];
+			song->sampleData[smpTo->offset+i] = smp;
 		}
 
 		editor.sampleZero = false;
@@ -1202,7 +1204,7 @@
 		{
 			for (i = 0; i < MOD_ROWS; i++)
 			{
-				noteSrc = &modEntry->patterns[modEntry->currPattern][(i * AMIGA_VOICES) + cursor.channel];
+				noteSrc = &song->patterns[song->currPattern][(i * AMIGA_VOICES) + cursor.channel];
 
 				     if (noteSrc->sample == editor.sampleFrom) noteSrc->sample = editor.sampleTo;
 				else if (noteSrc->sample == editor.sampleTo) noteSrc->sample = editor.sampleFrom;
@@ -1214,7 +1216,7 @@
 			{
 				for (uint8_t j = 0; j < MOD_ROWS; j++)
 				{
-					noteSrc = &modEntry->patterns[modEntry->currPattern][(j * AMIGA_VOICES) + i];
+					noteSrc = &song->patterns[song->currPattern][(j * AMIGA_VOICES) + i];
 
 					     if (noteSrc->sample == editor.sampleFrom) noteSrc->sample = editor.sampleTo;
 					else if (noteSrc->sample == editor.sampleTo) noteSrc->sample = editor.sampleFrom;
@@ -1241,7 +1243,7 @@
 	{
 		for (i = 0; i < MOD_ROWS; i++)
 		{
-			noteSrc = &modEntry->patterns[modEntry->currPattern][(i * AMIGA_VOICES) + cursor.channel];
+			noteSrc = &song->patterns[song->currPattern][(i * AMIGA_VOICES) + cursor.channel];
 			if (noteSrc->sample == editor.currSample+1)
 			{
 				noteSrc->period = 0;
@@ -1257,7 +1259,7 @@
 		{
 			for (uint8_t j = 0; j < MOD_ROWS; j++)
 			{
-				noteSrc = &modEntry->patterns[modEntry->currPattern][(j * AMIGA_VOICES) + i];
+				noteSrc = &song->patterns[song->currPattern][(j * AMIGA_VOICES) + i];
 				if (noteSrc->sample == editor.currSample+1)
 				{
 					noteSrc->period = 0;
@@ -1289,7 +1291,7 @@
 	saveUndo();
 	for (uint8_t i = from; i <= to; i++)
 	{
-		noteSrc = &modEntry->patterns[modEntry->currPattern][(i * AMIGA_VOICES) + cursor.channel];
+		noteSrc = &song->patterns[song->currPattern][(i * AMIGA_VOICES) + cursor.channel];
 
 		if (!sampleAllFlag && noteSrc->sample != editor.currSample+1)
 			continue;
@@ -1342,7 +1344,7 @@
 	saveUndo();
 	for (uint8_t i = from; i <= to; i++)
 	{
-		noteSrc = &modEntry->patterns[modEntry->currPattern][(i * AMIGA_VOICES) + cursor.channel];
+		noteSrc = &song->patterns[song->currPattern][(i * AMIGA_VOICES) + cursor.channel];
 
 		if (!sampleAllFlag && noteSrc->sample != editor.currSample+1)
 			continue;
@@ -1397,7 +1399,7 @@
 	saveUndo();
 	for (uint8_t i = from; i <= to; i++)
 	{
-		noteSrc = &modEntry->patterns[modEntry->currPattern][(i * AMIGA_VOICES) + cursor.channel];
+		noteSrc = &song->patterns[song->currPattern][(i * AMIGA_VOICES) + cursor.channel];
 
 		if (!sampleAllFlag && noteSrc->sample != editor.currSample+1)
 			continue;
@@ -1456,7 +1458,7 @@
 	saveUndo();
 	for (uint8_t i = from; i <= to; i++)
 	{
-		noteSrc = &modEntry->patterns[modEntry->currPattern][(i * AMIGA_VOICES) + cursor.channel];
+		noteSrc = &song->patterns[song->currPattern][(i * AMIGA_VOICES) + cursor.channel];
 
 		if (!sampleAllFlag && noteSrc->sample != editor.currSample+1)
 			continue;
@@ -1502,7 +1504,7 @@
 	{
 		for (uint8_t j = 0; j < MOD_ROWS; j++)
 		{
-			noteSrc = &modEntry->patterns[modEntry->currPattern][(j * AMIGA_VOICES) + i];
+			noteSrc = &song->patterns[song->currPattern][(j * AMIGA_VOICES) + i];
 
 			if (!sampleAllFlag && noteSrc->sample != editor.currSample+1)
 				continue;
@@ -1551,7 +1553,7 @@
 	{
 		for (uint8_t j = 0; j < MOD_ROWS; j++)
 		{
-			noteSrc = &modEntry->patterns[modEntry->currPattern][(j * AMIGA_VOICES) + i];
+			noteSrc = &song->patterns[song->currPattern][(j * AMIGA_VOICES) + i];
 
 			if (!sampleAllFlag && noteSrc->sample != editor.currSample+1)
 				continue;
@@ -1600,7 +1602,7 @@
 	{
 		for (uint8_t j = 0; j < MOD_ROWS; j++)
 		{
-			noteSrc = &modEntry->patterns[modEntry->currPattern][(j * AMIGA_VOICES) + i];
+			noteSrc = &song->patterns[song->currPattern][(j * AMIGA_VOICES) + i];
 
 			if (!sampleAllFlag && noteSrc->sample != editor.currSample+1)
 				continue;
@@ -1647,7 +1649,7 @@
 	{
 		for (uint8_t j = 0; j < MOD_ROWS; j++)
 		{
-			noteSrc = &modEntry->patterns[modEntry->currPattern][(j * AMIGA_VOICES) + i];
+			noteSrc = &song->patterns[song->currPattern][(j * AMIGA_VOICES) + i];
 
 			if (!sampleAllFlag && noteSrc->sample != editor.currSample+1)
 				continue;
--- a/src/pt2_header.h
+++ b/src/pt2_header.h
@@ -6,7 +6,7 @@
 #include <assert.h>
 #ifdef _WIN32
 #define WIN32_MEAN_AND_LEAN
-#include <windows.h>
+#include <windows.h> // MAX_PATH
 #else
 #include <limits.h> // PATH_MAX
 #endif
@@ -14,7 +14,7 @@
 #include "pt2_unicode.h"
 #include "pt2_palette.h"
 
-#define PROG_VER_STR "1.12"
+#define PROG_VER_STR "1.13"
 
 #ifdef _WIN32
 #define DIR_DELIMITER '\\'
@@ -21,11 +21,6 @@
 #define PATH_MAX MAX_PATH
 #else
 #define DIR_DELIMITER '/'
-#endif
-
-#include "pt2_config.h" // this must be included after PATH_MAX definition
-
-#ifndef _WIN32
 #define _stricmp strcasecmp
 #define _strnicmp strncasecmp
 #endif
@@ -37,12 +32,14 @@
 #define SCREEN_H 255
 
 /* "60Hz" ranges everywhere from 59..61Hz depending on the monitor, so with
-** no vsync we will get stuttering because the rate is not perfect... */
+** no vsync we will get stuttering because the rate is not perfect...
+*/
 #define VBLANK_HZ 60
 
 /* Scopes are clocked at 64Hz instead of 60Hz to prevent +/- interference
 ** from monitors not being exactly 60Hz (and unstable non-vsync mode).
-** Sadly the scopes might midly flicker from this. */
+** Sadly the scopes might midly flicker from this.
+*/
 #define SCOPE_HZ 64
 
 #define AMIGA_PAL_VBLANK_HZ 50
--- a/src/pt2_helpers.c
+++ b/src/pt2_helpers.c
@@ -20,6 +20,7 @@
 #include "pt2_tables.h"
 #include "pt2_palette.h"
 #include "pt2_structs.h"
+#include "pt2_config.h"
 
 // used for Windows usleep() implementation
 #ifdef _WIN32
@@ -120,25 +121,25 @@
 	char titleTemp[128];
 
 	if (modified)
-		modEntry->modified = true;
+		song->modified = true;
 	else
-		modEntry->modified = false;
+		song->modified = false;
 
-	if (modEntry->head.moduleTitle[0] != '\0')
+	if (song->header.name[0] != '\0')
 	{
 		if (modified)
 		{
 			if (config.modDot)
-				sprintf(titleTemp, "ProTracker 2 clone v%s - \"mod.%s\" (unsaved)", PROG_VER_STR, modEntry->head.moduleTitle);
+				sprintf(titleTemp, "ProTracker 2 clone v%s - \"mod.%s\" (unsaved)", PROG_VER_STR, song->header.name);
 			else
-				sprintf(titleTemp, "ProTracker 2 clone v%s - \"%s.mod\" (unsaved)", PROG_VER_STR, modEntry->head.moduleTitle);
+				sprintf(titleTemp, "ProTracker 2 clone v%s - \"%s.mod\" (unsaved)", PROG_VER_STR, song->header.name);
 		}
 		else
 		{
 			if (config.modDot)
-				sprintf(titleTemp, "ProTracker 2 clone v%s - \"mod.%s\"", PROG_VER_STR, modEntry->head.moduleTitle);
+				sprintf(titleTemp, "ProTracker 2 clone v%s - \"mod.%s\"", PROG_VER_STR, song->header.name);
 			else
-				sprintf(titleTemp, "ProTracker 2 clone v%s - \"%s.mod\"", PROG_VER_STR, modEntry->head.moduleTitle);
+				sprintf(titleTemp, "ProTracker 2 clone v%s - \"%s.mod\"", PROG_VER_STR, song->header.name);
 		}
 	}
 	else
@@ -168,7 +169,7 @@
 	int32_t len;
 	moduleSample_t *s;
 
-	s = &modEntry->samples[editor.currSample];
+	s = &song->samples[editor.currSample];
 
 	if (editor.chordLengthMin)
 	{
--- a/src/pt2_keyboard.c
+++ b/src/pt2_keyboard.c
@@ -23,9 +23,11 @@
 #include "pt2_audio.h"
 #include "pt2_keyboard.h"
 #include "pt2_tables.h"
-#include "pt2_modloader.h"
+#include "pt2_module_loader.h"
+#include "pt2_module_saver.h"
 #include "pt2_mouse.h"
 #include "pt2_unicode.h"
+#include "pt2_config.h"
 
 #if defined _WIN32 && !defined _DEBUG
 extern bool windowsKeyIsDown;
@@ -52,10 +54,8 @@
 
 void readKeyModifiers(void)
 {
-	uint32_t modState;
+	const SDL_Keymod modState = SDL_GetModState();
 
-	modState = SDL_GetModState();
-
 	keyb.leftCtrlPressed = (modState & KMOD_LCTRL)  ? true : false;
 	keyb.leftAltPressed = (modState & KMOD_LALT) ? true : false;
 	keyb.shiftPressed = (modState & (KMOD_LSHIFT + KMOD_RSHIFT)) ? true : false;
@@ -64,100 +64,75 @@
 	keyb.leftCommandPressed = (modState & KMOD_LGUI) ? true : false;
 #endif
 
-#if defined _WIN32 && !defined _DEBUG // Windows: handled in lowLevelKeyboardProc
+#if defined _WIN32 && !defined _DEBUG
+	keyb.leftAmigaPressed = windowsKeyIsDown; // Windows: handled in lowLevelKeyboardProc
+#else
 	keyb.leftAmigaPressed = (modState & KMOD_LGUI) ? true : false;
 #endif
 }
 
 #if defined _WIN32 && !defined _DEBUG
-/* For taking control over windows key and numlock on keyboard if app has focus.
+/* For taking control over windows key if the program has focus.
 ** Warning: Don't do this in debug mode, it will completely ruin the keyboard input
-** latency when the debugger is breaking.
+** latency (in the OS in general) when the debugger is breaking.
 */
 LRESULT CALLBACK lowLevelKeyboardProc(int32_t nCode, WPARAM wParam, LPARAM lParam)
 {
-	bool bEatKeystroke;
-	KBDLLHOOKSTRUCT *p;
 	SDL_Event inputEvent;
+	SDL_Window *window = video.window;
 
-	if (nCode < 0 || nCode != HC_ACTION) // do not process message
+	if (window == NULL || nCode < 0 || nCode != HC_ACTION) // do not process message
 		return CallNextHookEx(g_hKeyboardHook, nCode, wParam, lParam);
 
-	bEatKeystroke = false;
-	p = (KBDLLHOOKSTRUCT *)lParam;
-
+	KBDLLHOOKSTRUCT *p = (KBDLLHOOKSTRUCT *)lParam;
 	switch (wParam)
 	{
 		case WM_KEYUP:
 		case WM_KEYDOWN:
 		{
-			bEatKeystroke = (SDL_GetWindowFlags(video.window) & SDL_WINDOW_INPUT_FOCUS) && (p->vkCode == VK_LWIN || p->vkCode == VK_NUMLOCK);
+			const bool windowHasFocus = SDL_GetWindowFlags(window) & SDL_WINDOW_INPUT_FOCUS;
 
-			if (bEatKeystroke)
-			{
-				if (wParam == WM_KEYDOWN)
-				{
-					if (p->vkCode == VK_NUMLOCK)
-					{
-						memset(&inputEvent, 0, sizeof (SDL_Event));
-						inputEvent.type = SDL_KEYDOWN;
-						inputEvent.key.type = SDL_KEYDOWN;
-						inputEvent.key.state = 1;
-						inputEvent.key.keysym.scancode = (SDL_Scancode)69;
-						inputEvent.key.keysym.mod = KMOD_NUM;
-						inputEvent.key.keysym.scancode = SDL_SCANCODE_NUMLOCKCLEAR;
+			const bool bEatKeystroke = windowHasFocus && p->vkCode == VK_LWIN;
+			if (!bEatKeystroke)
+				return CallNextHookEx(g_hKeyboardHook, nCode, wParam, lParam);
 
-						SDL_PushEvent(&inputEvent);
-					}
-					else if (!windowsKeyIsDown)
-					{
-						windowsKeyIsDown = true;
-						keyb.leftAmigaPressed = true;
+			memset(&inputEvent, 0, sizeof (SDL_Event));
 
-						memset(&inputEvent, 0, sizeof (SDL_Event));
-						inputEvent.type = SDL_KEYDOWN;
-						inputEvent.key.type = SDL_KEYDOWN;
-						inputEvent.key.state = 1;
-						inputEvent.key.keysym.scancode = (SDL_Scancode)91;
-						inputEvent.key.keysym.scancode = SDL_SCANCODE_LGUI;
+			const bool keyDown = (wParam == WM_KEYDOWN);
+			if (keyDown)
+			{
+				if (windowsKeyIsDown)
+					break; // Windows-key is already down (XXX: Do we need this check?)
 
-						SDL_PushEvent(&inputEvent);
-					}
-				}
-				else if (wParam == WM_KEYUP)
-				{
-					if (p->vkCode == VK_NUMLOCK)
-					{
-						memset(&inputEvent, 0, sizeof (SDL_Event));
-						inputEvent.type = SDL_KEYUP;
-						inputEvent.key.type = SDL_KEYUP;
-						inputEvent.key.keysym.scancode = (SDL_Scancode)69;
-						inputEvent.key.keysym.scancode = SDL_SCANCODE_NUMLOCKCLEAR;
+				inputEvent.type = SDL_KEYDOWN;
+				inputEvent.key.type = SDL_KEYDOWN;
+				inputEvent.key.state = SDL_PRESSED;
 
-						SDL_PushEvent(&inputEvent);
-					}
-					else
-					{
-						windowsKeyIsDown = false;
-						keyb.leftAmigaPressed = false;
+				windowsKeyIsDown = true;
+			}
+			else
+			{
+				inputEvent.type = SDL_KEYUP;
+				inputEvent.key.type = SDL_KEYUP;
+				inputEvent.key.state = SDL_RELEASED;
 
-						memset(&inputEvent, 0, sizeof (SDL_Event));
-						inputEvent.type = SDL_KEYUP;
-						inputEvent.key.type = SDL_KEYUP;
-						inputEvent.key.keysym.scancode = (SDL_Scancode)91;
-						inputEvent.key.keysym.scancode = SDL_SCANCODE_LGUI;
-
-						SDL_PushEvent(&inputEvent);
-					}
-				}
+				windowsKeyIsDown = false;
 			}
-			break;
+
+			inputEvent.key.keysym.sym = SDLK_LGUI;
+			inputEvent.key.keysym.scancode = SDL_SCANCODE_LGUI;
+			inputEvent.key.keysym.mod = SDL_GetModState();
+			inputEvent.key.timestamp = SDL_GetTicks();
+			inputEvent.key.windowID = SDL_GetWindowID(window);
+
+			SDL_PushEvent(&inputEvent);
 		}
+		break;
 
 		default: break;
 	}
 
-	return bEatKeystroke ? true : CallNextHookEx(g_hKeyboardHook, nCode, wParam, lParam);
+	return true;
 }
 #endif
 
@@ -489,7 +464,7 @@
 			if (keyb.leftAltPressed)
 			{
 				if (handleSpecialKeys(scancode) && editor.currMode != MODE_RECORD)
-					modSetPos(DONT_SET_ORDER, (modEntry->currRow + editor.editMoveAdd) & 0x3F);
+					modSetPos(DONT_SET_ORDER, (song->currRow + editor.editMoveAdd) & 0x3F);
 			}
 			else
 			{
@@ -523,7 +498,7 @@
 			if (!ui.askScreenShown)
 			{
 				editor.playMode = PLAY_MODE_NORMAL;
-				modPlay(DONT_SET_PATTERN, modEntry->currOrder, DONT_SET_ROW);
+				modPlay(DONT_SET_PATTERN, song->currOrder, DONT_SET_ROW);
 				editor.currMode = MODE_PLAY;
 				pointerSetMode(POINTER_MODE_PLAY, DO_CARRY);
 				statusAllRight();
@@ -541,7 +516,7 @@
 			if (!ui.askScreenShown)
 			{
 				editor.playMode = PLAY_MODE_PATTERN;
-				modPlay(modEntry->currPattern, DONT_SET_ORDER, DONT_SET_ROW);
+				modPlay(song->currPattern, DONT_SET_ORDER, DONT_SET_ROW);
 				editor.currMode = MODE_PLAY;
 				pointerSetMode(POINTER_MODE_PLAY, DO_CARRY);
 				statusAllRight();
@@ -555,7 +530,7 @@
 			if (!ui.samplerScreenShown && !ui.askScreenShown)
 			{
 				editor.playMode = PLAY_MODE_PATTERN;
-				modPlay(modEntry->currPattern, DONT_SET_ORDER, DONT_SET_ROW);
+				modPlay(song->currPattern, DONT_SET_ORDER, DONT_SET_ROW);
 				editor.currMode = MODE_RECORD;
 				pointerSetMode(POINTER_MODE_EDIT, DO_CARRY);
 				statusAllRight();
@@ -614,10 +589,10 @@
 		{
 			if (ui.posEdScreenShown)
 			{
-				if (modEntry->currOrder > 0)
+				if (song->currOrder > 0)
 				{
-					if (modEntry->currOrder-(POSED_LIST_SIZE-1) > 0)
-						modSetPos(modEntry->currOrder-(POSED_LIST_SIZE-1), DONT_SET_ROW);
+					if (song->currOrder-(POSED_LIST_SIZE-1) > 0)
+						modSetPos(song->currOrder-(POSED_LIST_SIZE-1), DONT_SET_ROW);
 					else
 						modSetPos(0, DONT_SET_ROW);
 				}
@@ -634,12 +609,12 @@
 			{
 				if (editor.currMode == MODE_IDLE || editor.currMode == MODE_EDIT)
 				{
-					if (modEntry->currRow == 63)
-						modSetPos(DONT_SET_ORDER, modEntry->currRow - 15);
-					else if (modEntry->currRow == 15)
+					if (song->currRow == 63)
+						modSetPos(DONT_SET_ORDER, song->currRow - 15);
+					else if (song->currRow == 15)
 						modSetPos(DONT_SET_ORDER, 0); // 15-16 would turn into -1, which is "DON'T SET ROW" flag
 					else
-						modSetPos(DONT_SET_ORDER, modEntry->currRow - 16);
+						modSetPos(DONT_SET_ORDER, song->currRow - 16);
 				}
 			}
 
@@ -655,12 +630,12 @@
 		{
 			if (ui.posEdScreenShown)
 			{
-				if (modEntry->currOrder != modEntry->head.orderCount-1)
+				if (song->currOrder != song->header.numOrders-1)
 				{
-					if (modEntry->currOrder+(POSED_LIST_SIZE-1) <= modEntry->head.orderCount-1)
-						modSetPos(modEntry->currOrder+(POSED_LIST_SIZE-1), DONT_SET_ROW);
+					if (song->currOrder+(POSED_LIST_SIZE-1) <= song->header.numOrders-1)
+						modSetPos(song->currOrder+(POSED_LIST_SIZE-1), DONT_SET_ROW);
 					else
-						modSetPos(modEntry->head.orderCount - 1, DONT_SET_ROW);
+						modSetPos(song->header.numOrders - 1, DONT_SET_ROW);
 				}
 			}
 			else if (ui.diskOpScreenShown)
@@ -677,7 +652,7 @@
 			else
 			{
 				if (editor.currMode == MODE_IDLE || editor.currMode == MODE_EDIT)
-					modSetPos(DONT_SET_ORDER, modEntry->currRow + 16);
+					modSetPos(DONT_SET_ORDER, song->currRow + 16);
 			}
 
 			if (!keyb.repeatKey)
@@ -692,7 +667,7 @@
 		{
 			if (ui.posEdScreenShown)
 			{
-				if (modEntry->currOrder > 0)
+				if (song->currOrder > 0)
 					modSetPos(0, DONT_SET_ROW);
 			}
 			else if (ui.diskOpScreenShown)
@@ -715,7 +690,7 @@
 		{
 			if (ui.posEdScreenShown)
 			{
-				modSetPos(modEntry->head.orderCount - 1, DONT_SET_ROW);
+				modSetPos(song->header.numOrders - 1, DONT_SET_ROW);
 			}
 			else if (ui.diskOpScreenShown)
 			{
@@ -749,7 +724,7 @@
 				editor.timingMode ^= 1;
 				if (editor.timingMode == TEMPO_MODE_VBLANK)
 				{
-					editor.oldTempo = modEntry->currBPM;
+					editor.oldTempo = song->currBPM;
 					modSetTempo(125);
 				}
 				else
@@ -787,17 +762,17 @@
 					saveUndo();
 					if (keyb.leftAltPressed && !keyb.leftCtrlPressed)
 					{
-						if (modEntry->currRow < 63)
+						if (song->currRow < 63)
 						{
 							for (i = 0; i < AMIGA_VOICES; i++)
 							{
-								for (j = 62; j >= modEntry->currRow; j--)
+								for (j = 62; j >= song->currRow; j--)
 								{
-									noteSrc = &modEntry->patterns[modEntry->currPattern][(j * AMIGA_VOICES) + i];
-									modEntry->patterns[modEntry->currPattern][((j + 1) * AMIGA_VOICES) + i] = *noteSrc;
+									noteSrc = &song->patterns[song->currPattern][(j * AMIGA_VOICES) + i];
+									song->patterns[song->currPattern][((j + 1) * AMIGA_VOICES) + i] = *noteSrc;
 								}
 
-								noteDst = &modEntry->patterns[modEntry->currPattern][((j + 1) * AMIGA_VOICES) + i];
+								noteDst = &song->patterns[song->currPattern][((j + 1) * AMIGA_VOICES) + i];
 
 								noteDst->period = 0;
 								noteDst->sample = 0;
@@ -805,7 +780,7 @@
 								noteDst->param = 0;
 							}
 
-							modEntry->currRow++;
+							song->currRow++;
 
 							updateWindowTitle(MOD_IS_MODIFIED);
 							ui.updatePatternData = true;
@@ -813,12 +788,12 @@
 					}
 					else
 					{
-						if (modEntry->currRow < 63)
+						if (song->currRow < 63)
 						{
-							for (i = 62; i >= modEntry->currRow; i--)
+							for (i = 62; i >= song->currRow; i--)
 							{
-								noteSrc = &modEntry->patterns[modEntry->currPattern][((i + 0) * AMIGA_VOICES) + cursor.channel];
-								noteDst = &modEntry->patterns[modEntry->currPattern][((i + 1) * AMIGA_VOICES) + cursor.channel];
+								noteSrc = &song->patterns[song->currPattern][((i + 0) * AMIGA_VOICES) + cursor.channel];
+								noteDst = &song->patterns[song->currPattern][((i + 1) * AMIGA_VOICES) + cursor.channel];
 
 								if (keyb.leftCtrlPressed)
 								{
@@ -831,7 +806,7 @@
 								}
 							}
 
-							noteDst = &modEntry->patterns[modEntry->currPattern][((i + 1) * AMIGA_VOICES) + cursor.channel];
+							noteDst = &song->patterns[song->currPattern][((i + 1) * AMIGA_VOICES) + cursor.channel];
 
 							if (!keyb.leftCtrlPressed)
 							{
@@ -842,7 +817,7 @@
 							noteDst->command = 0;
 							noteDst->param = 0;
 
-							modEntry->currRow++;
+							song->currRow++;
 
 							updateWindowTitle(MOD_IS_MODIFIED);
 							ui.updatePatternData = true;
@@ -855,7 +830,7 @@
 					editor.stepPlayBackwards = false;
 
 					doStopIt(true);
-					playPattern(modEntry->currRow);
+					playPattern(song->currRow);
 				}
 			}
 		}
@@ -910,7 +885,7 @@
 					noteDst = editor.trackBuffer;
 					for (i = 0; i < MOD_ROWS; i++)
 					{
-						noteSrc = &modEntry->patterns[modEntry->currPattern][(i * AMIGA_VOICES) + cursor.channel];
+						noteSrc = &song->patterns[song->currPattern][(i * AMIGA_VOICES) + cursor.channel];
 						*noteDst++ = *noteSrc;
 
 						noteSrc->period = 0;
@@ -927,10 +902,10 @@
 					// cut pattern and put in buffer
 					saveUndo();
 
-					memcpy(editor.patternBuffer, modEntry->patterns[modEntry->currPattern],
+					memcpy(editor.patternBuffer, song->patterns[song->currPattern],
 						sizeof (note_t) * (AMIGA_VOICES * MOD_ROWS));
 
-					memset(modEntry->patterns[modEntry->currPattern], 0,
+					memset(song->patterns[song->currPattern], 0,
 						sizeof (note_t) * (AMIGA_VOICES * MOD_ROWS));
 
 					updateWindowTitle(MOD_IS_MODIFIED);
@@ -944,7 +919,7 @@
 					noteDst = editor.cmdsBuffer;
 					for (i = 0; i < MOD_ROWS; i++)
 					{
-						noteSrc = &modEntry->patterns[modEntry->currPattern][(i * AMIGA_VOICES) + cursor.channel];
+						noteSrc = &song->patterns[song->currPattern][(i * AMIGA_VOICES) + cursor.channel];
 						*noteDst++ = *noteSrc;
 
 						noteSrc->command = 0;
@@ -972,13 +947,13 @@
 
 					noteDst = editor.trackBuffer;
 					for (i = 0; i < MOD_ROWS; i++)
-						*noteDst++ = modEntry->patterns[modEntry->currPattern][(i * AMIGA_VOICES) + cursor.channel];
+						*noteDst++ = song->patterns[song->currPattern][(i * AMIGA_VOICES) + cursor.channel];
 				}
 				else if (keyb.leftAltPressed)
 				{
 					// copy pattern to buffer
 
-					memcpy(editor.patternBuffer, modEntry->patterns[modEntry->currPattern],
+					memcpy(editor.patternBuffer, song->patterns[song->currPattern],
 						sizeof (note_t) * (AMIGA_VOICES * MOD_ROWS));
 				}
 				else if (keyb.leftCtrlPressed)
@@ -988,7 +963,7 @@
 					noteDst = editor.cmdsBuffer;
 					for (i = 0; i < MOD_ROWS; i++)
 					{
-						noteSrc = &modEntry->patterns[modEntry->currPattern][(i * AMIGA_VOICES) + cursor.channel];
+						noteSrc = &song->patterns[song->currPattern][(i * AMIGA_VOICES) + cursor.channel];
 						noteDst->command = noteSrc->command;
 						noteDst->param = noteSrc->param;
 
@@ -1014,7 +989,7 @@
 
 					noteSrc = editor.trackBuffer;
 					for (i = 0; i < MOD_ROWS; i++)
-						modEntry->patterns[modEntry->currPattern][(i * AMIGA_VOICES) + cursor.channel] = *noteSrc++;
+						song->patterns[song->currPattern][(i * AMIGA_VOICES) + cursor.channel] = *noteSrc++;
 
 					updateWindowTitle(MOD_IS_MODIFIED);
 					ui.updatePatternData = true;
@@ -1024,7 +999,7 @@
 					// paste pattern buffer to pattern
 					saveUndo();
 
-					memcpy(modEntry->patterns[modEntry->currPattern],
+					memcpy(song->patterns[song->currPattern],
 						editor.patternBuffer, sizeof (note_t) * (AMIGA_VOICES * MOD_ROWS));
 
 					updateWindowTitle(MOD_IS_MODIFIED);
@@ -1038,7 +1013,7 @@
 					noteSrc = editor.cmdsBuffer;
 					for (i = 0; i < MOD_ROWS; i++)
 					{
-						noteDst = &modEntry->patterns[modEntry->currPattern][(i * AMIGA_VOICES) + cursor.channel];
+						noteDst = &song->patterns[song->currPattern][(i * AMIGA_VOICES) + cursor.channel];
 						noteDst->command = noteSrc->command;
 						noteDst->param = noteSrc->param;
 
@@ -1056,7 +1031,7 @@
 		{
 			if (keyb.shiftPressed)
 			{
-				editor.f6Pos = modEntry->currRow;
+				editor.f6Pos = song->currRow;
 				displayMsg("POSITION SET");
 			}
 			else
@@ -1064,7 +1039,7 @@
 				if (keyb.leftAltPressed)
 				{
 					editor.playMode = PLAY_MODE_PATTERN;
-					modPlay(modEntry->currPattern, DONT_SET_ORDER, editor.f6Pos);
+					modPlay(song->currPattern, DONT_SET_ORDER, editor.f6Pos);
 
 					editor.currMode = MODE_PLAY;
 					pointerSetMode(POINTER_MODE_PLAY, DO_CARRY);
@@ -1075,7 +1050,7 @@
 					if (!ui.samplerScreenShown)
 					{
 						editor.playMode = PLAY_MODE_PATTERN;
-						modPlay(modEntry->currPattern, DONT_SET_ORDER, editor.f6Pos);
+						modPlay(song->currPattern, DONT_SET_ORDER, editor.f6Pos);
 
 						editor.currMode = MODE_RECORD;
 						pointerSetMode(POINTER_MODE_EDIT, DO_CARRY);
@@ -1085,7 +1060,7 @@
 				else if (keyb.leftAmigaPressed)
 				{
 					editor.playMode = PLAY_MODE_NORMAL;
-					modPlay(DONT_SET_PATTERN, modEntry->currOrder, editor.f6Pos);
+					modPlay(DONT_SET_PATTERN, song->currOrder, editor.f6Pos);
 
 					editor.currMode = MODE_PLAY;
 					pointerSetMode(POINTER_MODE_PLAY, DO_CARRY);
@@ -1103,7 +1078,7 @@
 		{
 			if (keyb.shiftPressed)
 			{
-				editor.f7Pos = modEntry->currRow;
+				editor.f7Pos = song->currRow;
 				displayMsg("POSITION SET");
 			}
 			else
@@ -1111,7 +1086,7 @@
 				if (keyb.leftAltPressed)
 				{
 					editor.playMode = PLAY_MODE_PATTERN;
-					modPlay(modEntry->currPattern, DONT_SET_ORDER, editor.f7Pos);
+					modPlay(song->currPattern, DONT_SET_ORDER, editor.f7Pos);
 
 					editor.currMode = MODE_PLAY;
 					pointerSetMode(POINTER_MODE_PLAY, DO_CARRY);
@@ -1122,7 +1097,7 @@
 					if (!ui.samplerScreenShown)
 					{
 						editor.playMode = PLAY_MODE_PATTERN;
-						modPlay(modEntry->currPattern, DONT_SET_ORDER, editor.f7Pos);
+						modPlay(song->currPattern, DONT_SET_ORDER, editor.f7Pos);
 
 						editor.currMode = MODE_RECORD;
 						pointerSetMode(POINTER_MODE_EDIT, DO_CARRY);
@@ -1132,7 +1107,7 @@
 				else if (keyb.leftAmigaPressed)
 				{
 					editor.playMode = PLAY_MODE_NORMAL;
-					modPlay(DONT_SET_PATTERN, modEntry->currOrder, editor.f7Pos);
+					modPlay(DONT_SET_PATTERN, song->currOrder, editor.f7Pos);
 
 					editor.currMode = MODE_PLAY;
 					pointerSetMode(POINTER_MODE_PLAY, DO_CARRY);
@@ -1150,7 +1125,7 @@
 		{
 			if (keyb.shiftPressed)
 			{
-				editor.f8Pos = modEntry->currRow;
+				editor.f8Pos = song->currRow;
 				displayMsg("POSITION SET");
 			}
 			else
@@ -1158,7 +1133,7 @@
 				if (keyb.leftAltPressed)
 				{
 					editor.playMode = PLAY_MODE_PATTERN;
-					modPlay(modEntry->currPattern, DONT_SET_ORDER, editor.f8Pos);
+					modPlay(song->currPattern, DONT_SET_ORDER, editor.f8Pos);
 
 					editor.currMode = MODE_PLAY;
 					pointerSetMode(POINTER_MODE_PLAY, DO_CARRY);
@@ -1169,7 +1144,7 @@
 					if (!ui.samplerScreenShown)
 					{
 						editor.playMode = PLAY_MODE_PATTERN;
-						modPlay(modEntry->currPattern, DONT_SET_ORDER, editor.f8Pos);
+						modPlay(song->currPattern, DONT_SET_ORDER, editor.f8Pos);
 
 						editor.currMode = MODE_RECORD;
 						pointerSetMode(POINTER_MODE_EDIT, DO_CARRY);
@@ -1179,7 +1154,7 @@
 				else if (keyb.leftAmigaPressed)
 				{
 					editor.playMode = PLAY_MODE_NORMAL;
-					modPlay(DONT_SET_PATTERN, modEntry->currOrder, editor.f8Pos);
+					modPlay(DONT_SET_PATTERN, song->currOrder, editor.f8Pos);
 
 					editor.currMode = MODE_PLAY;
 					pointerSetMode(POINTER_MODE_PLAY, DO_CARRY);
@@ -1197,7 +1172,7 @@
 		{
 			if (keyb.shiftPressed)
 			{
-				editor.f9Pos = modEntry->currRow;
+				editor.f9Pos = song->currRow;
 				displayMsg("POSITION SET");
 			}
 			else
@@ -1205,7 +1180,7 @@
 				if (keyb.leftAltPressed)
 				{
 					editor.playMode = PLAY_MODE_PATTERN;
-					modPlay(modEntry->currPattern, DONT_SET_ORDER, editor.f9Pos);
+					modPlay(song->currPattern, DONT_SET_ORDER, editor.f9Pos);
 
 					editor.currMode = MODE_PLAY;
 					pointerSetMode(POINTER_MODE_PLAY, DO_CARRY);
@@ -1216,7 +1191,7 @@
 					if (!ui.samplerScreenShown)
 					{
 						editor.playMode = PLAY_MODE_PATTERN;
-						modPlay(modEntry->currPattern, DONT_SET_ORDER, editor.f9Pos);
+						modPlay(song->currPattern, DONT_SET_ORDER, editor.f9Pos);
 
 						editor.currMode = MODE_RECORD;
 						pointerSetMode(POINTER_MODE_EDIT, DO_CARRY);
@@ -1226,7 +1201,7 @@
 				else if (keyb.leftAmigaPressed)
 				{
 					editor.playMode = PLAY_MODE_NORMAL;
-					modPlay(DONT_SET_PATTERN, modEntry->currOrder, editor.f9Pos);
+					modPlay(DONT_SET_PATTERN, song->currOrder, editor.f9Pos);
 
 					editor.currMode = MODE_PLAY;
 					pointerSetMode(POINTER_MODE_PLAY, DO_CARRY);
@@ -1244,7 +1219,7 @@
 		{
 			if (keyb.shiftPressed)
 			{
-				editor.f10Pos = modEntry->currRow;
+				editor.f10Pos = song->currRow;
 				displayMsg("POSITION SET");
 			}
 			else
@@ -1252,7 +1227,7 @@
 				if (keyb.leftAltPressed)
 				{
 					editor.playMode = PLAY_MODE_PATTERN;
-					modPlay(modEntry->currPattern, DONT_SET_ORDER, editor.f10Pos);
+					modPlay(song->currPattern, DONT_SET_ORDER, editor.f10Pos);
 
 					editor.currMode = MODE_PLAY;
 					pointerSetMode(POINTER_MODE_PLAY, DO_CARRY);
@@ -1263,7 +1238,7 @@
 					if (!ui.samplerScreenShown)
 					{
 						editor.playMode = PLAY_MODE_PATTERN;
-						modPlay(modEntry->currPattern, DONT_SET_ORDER, editor.f10Pos);
+						modPlay(song->currPattern, DONT_SET_ORDER, editor.f10Pos);
 
 						editor.currMode = MODE_RECORD;
 						pointerSetMode(POINTER_MODE_EDIT, DO_CARRY);
@@ -1273,7 +1248,7 @@
 				else if (keyb.leftAmigaPressed)
 				{
 					editor.playMode = PLAY_MODE_NORMAL;
-					modPlay(DONT_SET_PATTERN, modEntry->currOrder, editor.f10Pos);
+					modPlay(DONT_SET_PATTERN, song->currOrder, editor.f10Pos);
 
 					editor.currMode = MODE_PLAY;
 					pointerSetMode(POINTER_MODE_PLAY, DO_CARRY);
@@ -1316,7 +1291,7 @@
 			}
 			else if (keyb.shiftPressed)
 			{
-				noteSrc = &modEntry->patterns[modEntry->currPattern][(modEntry->currRow * AMIGA_VOICES) + cursor.channel];
+				noteSrc = &song->patterns[song->currPattern][(song->currRow * AMIGA_VOICES) + cursor.channel];
 				editor.effectMacros[9] = (noteSrc->command << 8) | noteSrc->param;
 				displayMsg("COMMAND STORED!");
 			}
@@ -1341,7 +1316,7 @@
 			}
 			else if (keyb.shiftPressed)
 			{
-				noteSrc = &modEntry->patterns[modEntry->currPattern][(modEntry->currRow * AMIGA_VOICES) + cursor.channel];
+				noteSrc = &song->patterns[song->currPattern][(song->currRow * AMIGA_VOICES) + cursor.channel];
 				editor.effectMacros[0] = (noteSrc->command << 8) | noteSrc->param;
 				displayMsg("COMMAND STORED!");
 			}
@@ -1370,7 +1345,7 @@
 			}
 			else if (keyb.shiftPressed)
 			{
-				noteSrc = &modEntry->patterns[modEntry->currPattern][(modEntry->currRow * AMIGA_VOICES) + cursor.channel];
+				noteSrc = &song->patterns[song->currPattern][(song->currRow * AMIGA_VOICES) + cursor.channel];
 				editor.effectMacros[1] = (noteSrc->command << 8) | noteSrc->param;
 				displayMsg("COMMAND STORED!");
 			}
@@ -1399,7 +1374,7 @@
 			}
 			else if (keyb.shiftPressed)
 			{
-				noteSrc = &modEntry->patterns[modEntry->currPattern][(modEntry->currRow * AMIGA_VOICES) + cursor.channel];
+				noteSrc = &song->patterns[song->currPattern][(song->currRow * AMIGA_VOICES) + cursor.channel];
 				editor.effectMacros[2] = (noteSrc->command << 8) | noteSrc->param;
 				displayMsg("COMMAND STORED!");
 			}
@@ -1428,7 +1403,7 @@
 			}
 			else if (keyb.shiftPressed)
 			{
-				noteSrc = &modEntry->patterns[modEntry->currPattern][(modEntry->currRow * AMIGA_VOICES) + cursor.channel];
+				noteSrc = &song->patterns[song->currPattern][(song->currRow * AMIGA_VOICES) + cursor.channel];
 				editor.effectMacros[3] = (noteSrc->command << 8) | noteSrc->param;
 				displayMsg("COMMAND STORED!");
 			}
@@ -1453,7 +1428,7 @@
 			}
 			else if (keyb.shiftPressed)
 			{
-				noteSrc = &modEntry->patterns[modEntry->currPattern][(modEntry->currRow * AMIGA_VOICES) + cursor.channel];
+				noteSrc = &song->patterns[song->currPattern][(song->currRow * AMIGA_VOICES) + cursor.channel];
 				editor.effectMacros[4] = (noteSrc->command << 8) | noteSrc->param;
 				displayMsg("COMMAND STORED!");
 			}
@@ -1474,7 +1449,7 @@
 			}
 			else if (keyb.shiftPressed)
 			{
-				noteSrc = &modEntry->patterns[modEntry->currPattern][(modEntry->currRow * AMIGA_VOICES) + cursor.channel];
+				noteSrc = &song->patterns[song->currPattern][(song->currRow * AMIGA_VOICES) + cursor.channel];
 				editor.effectMacros[5] = (noteSrc->command << 8) | noteSrc->param;
 				displayMsg("COMMAND STORED!");
 			}
@@ -1495,7 +1470,7 @@
 			}
 			else if (keyb.shiftPressed)
 			{
-				noteSrc = &modEntry->patterns[modEntry->currPattern][(modEntry->currRow * AMIGA_VOICES) + cursor.channel];
+				noteSrc = &song->patterns[song->currPattern][(song->currRow * AMIGA_VOICES) + cursor.channel];
 				editor.effectMacros[6] = (noteSrc->command << 8) | noteSrc->param;
 				displayMsg("COMMAND STORED!");
 			}
@@ -1516,7 +1491,7 @@
 			}
 			else if (keyb.shiftPressed)
 			{
-				noteSrc = &modEntry->patterns[modEntry->currPattern][(modEntry->currRow * AMIGA_VOICES) + cursor.channel];
+				noteSrc = &song->patterns[song->currPattern][(song->currRow * AMIGA_VOICES) + cursor.channel];
 				editor.effectMacros[7] = (noteSrc->command << 8) | noteSrc->param;
 				displayMsg("COMMAND STORED!");
 			}
@@ -1537,7 +1512,7 @@
 			}
 			else if (keyb.shiftPressed)
 			{
-				noteSrc = &modEntry->patterns[modEntry->currPattern][(modEntry->currRow * AMIGA_VOICES) + cursor.channel];
+				noteSrc = &song->patterns[song->currPattern][(song->currRow * AMIGA_VOICES) + cursor.channel];
 				editor.effectMacros[8] = (noteSrc->command << 8) | noteSrc->param;
 				displayMsg("COMMAND STORED!");
 			}
@@ -1550,7 +1525,17 @@
 
 		case SDL_SCANCODE_KP_0:
 		{
-			editor.sampleZero = true;
+			if (editor.hiLowInstr >= 0x10)
+			{
+				editor.sampleZero = false;
+				editor.currSample = 0x10-1;
+			}
+			else
+			{
+				editor.sampleZero = true;
+				editor.currSample = 0x00;
+			}
+
 			updateCurrSample();
 		}
 		break;
@@ -1558,7 +1543,7 @@
 		case SDL_SCANCODE_KP_1:
 		{
 			editor.sampleZero = false;
-			editor.currSample = editor.keypadSampleOffset + 12;
+			editor.currSample = editor.hiLowInstr + 12;
 
 			updateCurrSample();
 			if (keyb.leftAltPressed && editor.pNoteFlag > 0)
@@ -1577,7 +1562,7 @@
 		case SDL_SCANCODE_KP_2:
 		{
 			editor.sampleZero = false;
-			editor.currSample = editor.keypadSampleOffset + 13;
+			editor.currSample = editor.hiLowInstr + 13;
 
 			updateCurrSample();
 			if (keyb.leftAltPressed && editor.pNoteFlag > 0)
@@ -1596,7 +1581,7 @@
 		case SDL_SCANCODE_KP_3:
 		{
 			editor.sampleZero = false;
-			editor.currSample = editor.keypadSampleOffset + 14;
+			editor.currSample = editor.hiLowInstr + 14;
 
 			updateCurrSample();
 			if (keyb.leftAltPressed && editor.pNoteFlag > 0)
@@ -1615,7 +1600,7 @@
 		case SDL_SCANCODE_KP_4:
 		{
 			editor.sampleZero = false;
-			editor.currSample = editor.keypadSampleOffset + 8;
+			editor.currSample = editor.hiLowInstr + 8;
 
 			updateCurrSample();
 			if (keyb.leftAltPressed && editor.pNoteFlag > 0)
@@ -1634,7 +1619,7 @@
 		case SDL_SCANCODE_KP_5:
 		{
 			editor.sampleZero = false;
-			editor.currSample = editor.keypadSampleOffset + 9;
+			editor.currSample = editor.hiLowInstr + 9;
 
 			updateCurrSample();
 			if (keyb.leftAltPressed && editor.pNoteFlag > 0)
@@ -1653,7 +1638,7 @@
 		case SDL_SCANCODE_KP_6:
 		{
 			editor.sampleZero = false;
-			editor.currSample = editor.keypadSampleOffset + 10;
+			editor.currSample = editor.hiLowInstr + 10;
 
 			updateCurrSample();
 			if (keyb.leftAltPressed && editor.pNoteFlag > 0)
@@ -1672,7 +1657,7 @@
 		case SDL_SCANCODE_KP_7:
 		{
 			editor.sampleZero = false;
-			editor.currSample = editor.keypadSampleOffset + 4;
+			editor.currSample = editor.hiLowInstr + 4;
 
 			updateCurrSample();
 			if (keyb.leftAltPressed && editor.pNoteFlag > 0)
@@ -1691,7 +1676,7 @@
 		case SDL_SCANCODE_KP_8:
 		{
 			editor.sampleZero = false;
-			editor.currSample = editor.keypadSampleOffset + 5;
+			editor.currSample = editor.hiLowInstr + 5;
 
 			updateCurrSample();
 			if (keyb.leftAltPressed && editor.pNoteFlag > 0)
@@ -1710,7 +1695,7 @@
 		case SDL_SCANCODE_KP_9:
 		{
 			editor.sampleZero = false;
-			editor.currSample = editor.keypadSampleOffset + 6;
+			editor.currSample = editor.hiLowInstr + 6;
 
 			updateCurrSample();
 			if (keyb.leftAltPressed && editor.pNoteFlag > 0)
@@ -1737,24 +1722,24 @@
 			}
 			else
 			{
-				editor.sampleZero = false;
+				editor.hiLowInstr ^= 0x10;
 
-				editor.currSample++;
-				if (editor.currSample >= 0x10)
+				if (editor.sampleZero)
 				{
-					editor.keypadSampleOffset = 0x00;
-
-					editor.currSample -= 0x10;
-					if (editor.currSample < 0x01)
-						editor.currSample = 0x01;
+					editor.currSample = 15;
+					editor.sampleZero = false;
 				}
 				else
 				{
-					editor.currSample += 0x10;
-					editor.keypadSampleOffset = 0x10;
+					editor.currSample ^= 0x10;
 				}
-				editor.currSample--;
 
+				if (editor.currSample == 31) // kludge if sample was 15 (0010 in UI) before key press
+				{
+					editor.currSample = 15;
+					editor.sampleZero ^= 1;
+				}
+
 				updateCurrSample();
 				if (keyb.leftAltPressed && editor.pNoteFlag > 0)
 				{
@@ -1776,9 +1761,9 @@
 
 			// the Amiga numpad has one more key, so we need to use this key for two sample numbers...
 			if (editor.keypadToggle8CFlag)
-				editor.currSample = editor.keypadSampleOffset + (0x0C - 1);
+				editor.currSample = editor.hiLowInstr + (0x0C - 1);
 			else
-				editor.currSample = editor.keypadSampleOffset + (0x08 - 1);
+				editor.currSample = editor.hiLowInstr + (0x08 - 1);
 
 			updateCurrSample();
 			if (keyb.leftAltPressed && editor.pNoteFlag > 0)
@@ -1791,7 +1776,7 @@
 		case SDL_SCANCODE_KP_MINUS:
 		{
 			editor.sampleZero = false;
-			editor.currSample = editor.keypadSampleOffset + 3;
+			editor.currSample = editor.hiLowInstr + 3;
 
 			updateCurrSample();
 			if (keyb.leftAltPressed && editor.pNoteFlag > 0)
@@ -1810,7 +1795,7 @@
 		case SDL_SCANCODE_KP_MULTIPLY:
 		{
 			editor.sampleZero = false;
-			editor.currSample = editor.keypadSampleOffset + 2;
+			editor.currSample = editor.hiLowInstr + 2;
 
 			updateCurrSample();
 			if (keyb.leftAltPressed && editor.pNoteFlag > 0)
@@ -1829,7 +1814,7 @@
 		case SDL_SCANCODE_KP_DIVIDE:
 		{
 			editor.sampleZero = false;
-			editor.currSample = editor.keypadSampleOffset + 1;
+			editor.currSample = editor.hiLowInstr + 1;
 
 			updateCurrSample();
 			if (keyb.leftAltPressed && editor.pNoteFlag > 0)
@@ -1848,7 +1833,7 @@
 		case SDL_SCANCODE_NUMLOCKCLEAR:
 		{
 			editor.sampleZero = false;
-			editor.currSample = editor.keypadSampleOffset + 0;
+			editor.currSample = editor.hiLowInstr + 0;
 
 			updateCurrSample();
 			if (keyb.leftAltPressed && editor.pNoteFlag > 0)
@@ -1903,12 +1888,12 @@
 			}
 			else if (ui.posEdScreenShown)
 			{
-				if (modEntry->currOrder != modEntry->head.orderCount-1)
+				if (song->currOrder != song->header.numOrders-1)
 				{
-					if (++modEntry->currOrder > modEntry->head.orderCount-1)
-						modEntry->currOrder = modEntry->head.orderCount-1;
+					if (++song->currOrder > song->header.numOrders-1)
+						song->currOrder = song->header.numOrders-1;
 
-					modSetPos(modEntry->currOrder, DONT_SET_ROW);
+					modSetPos(song->currOrder, DONT_SET_ROW);
 					ui.updatePosEd = true;
 				}
 
@@ -1921,7 +1906,7 @@
 			else if (!ui.samplerScreenShown)
 			{
 				if (editor.currMode != MODE_PLAY && editor.currMode != MODE_RECORD)
-					modSetPos(DONT_SET_ORDER, (modEntry->currRow + 1) & 0x3F);
+					modSetPos(DONT_SET_ORDER, (song->currRow + 1) & 0x3F);
 
 				keyb.repeatKey = true;
 			}
@@ -1952,9 +1937,9 @@
 			}
 			else if (ui.posEdScreenShown)
 			{
-				if (modEntry->currOrder > 0)
+				if (song->currOrder > 0)
 				{
-					modSetPos(modEntry->currOrder - 1, DONT_SET_ROW);
+					modSetPos(song->currOrder - 1, DONT_SET_ROW);
 					ui.updatePosEd = true;
 				}
 
@@ -1967,7 +1952,7 @@
 			else if (!ui.samplerScreenShown)
 			{
 				if ((editor.currMode != MODE_PLAY) && (editor.currMode != MODE_RECORD))
-					modSetPos(DONT_SET_ORDER, (modEntry->currRow - 1) & 0x3F);
+					modSetPos(DONT_SET_ORDER, (song->currRow - 1) & 0x3F);
 
 				keyb.repeatKey = true;
 			}
@@ -1990,9 +1975,9 @@
 			}
 			else if (keyb.shiftPressed)
 			{
-				if (modEntry->currOrder > 0)
+				if (song->currOrder > 0)
 				{
-					modSetPos(modEntry->currOrder - 1, DONT_SET_ROW);
+					modSetPos(song->currOrder - 1, DONT_SET_ROW);
 					if (editor.repeatKeyFlag)
 					{
 						keyb.delayKey = true;
@@ -2033,9 +2018,9 @@
 			}
 			else if (keyb.shiftPressed)
 			{
-				if (modEntry->currOrder < 126)
+				if (song->currOrder < 126)
 				{
-					modSetPos(modEntry->currOrder + 1, DONT_SET_ROW);
+					modSetPos(song->currOrder + 1, DONT_SET_ROW);
 					if (editor.repeatKeyFlag)
 					{
 						keyb.delayKey = true;
@@ -2115,8 +2100,8 @@
 				else
 				{
 					editor.blockMarkFlag = true;
-					editor.blockFromPos = modEntry->currRow;
-					editor.blockToPos = modEntry->currRow;
+					editor.blockFromPos = song->currRow;
+					editor.blockToPos = song->currRow;
 				}
 
 				ui.updateStatusText = true;
@@ -2123,10 +2108,10 @@
 			}
 			else if (keyb.leftAltPressed)
 			{
-				s = &modEntry->samples[editor.currSample];
+				s = &song->samples[editor.currSample];
 				if (s->length == 0)
 				{
-					displayErrorMsg("SAMPLE IS EMPTY");
+					statusSampleIsEmpty();
 					break;
 				}
 
@@ -2166,7 +2151,7 @@
 				editor.blockBufferFlag = true;
 
 				for (i = 0; i < MOD_ROWS; i++)
-					editor.blockBuffer[i] = modEntry->patterns[modEntry->currPattern][(i * AMIGA_VOICES) + cursor.channel];
+					editor.blockBuffer[i] = song->patterns[song->currPattern][(i * AMIGA_VOICES) + cursor.channel];
 
 				if (editor.blockFromPos > editor.blockToPos)
 				{
@@ -2258,16 +2243,16 @@
 			{
 				saveUndo();
 
-				j = modEntry->currRow + 1;
+				j = song->currRow + 1;
 				while (j < MOD_ROWS)
 				{
 					for (i = 62; i >= j; i--)
 					{
-						noteSrc = &modEntry->patterns[modEntry->currPattern][(i * AMIGA_VOICES) + cursor.channel];
-						modEntry->patterns[modEntry->currPattern][((i + 1) * AMIGA_VOICES) + cursor.channel] = *noteSrc;
+						noteSrc = &song->patterns[song->currPattern][(i * AMIGA_VOICES) + cursor.channel];
+						song->patterns[song->currPattern][((i + 1) * AMIGA_VOICES) + cursor.channel] = *noteSrc;
 					}
 
-					noteDst = &modEntry->patterns[modEntry->currPattern][((i + 1) * AMIGA_VOICES) + cursor.channel];
+					noteDst = &song->patterns[song->currPattern][((i + 1) * AMIGA_VOICES) + cursor.channel];
 					noteDst->period = 0;
 					noteDst->sample = 0;
 					noteDst->command = 0;
@@ -2310,10 +2295,10 @@
 			}
 			else if (keyb.leftAltPressed)
 			{
-				s = &modEntry->samples[editor.currSample];
+				s = &song->samples[editor.currSample];
 				if (s->length == 0)
 				{
-					displayErrorMsg("SAMPLE IS EMPTY");
+					statusSampleIsEmpty();
 					break;
 				}
 
@@ -2385,14 +2370,14 @@
 					return;
 				}
 
-				if (modEntry->currRow < 63)
+				if (song->currRow < 63)
 				{
 					for (i = 0; i <= editor.buffToPos-editor.buffFromPos; i++)
 					{
-						for (j = 62; j >= modEntry->currRow; j--)
+						for (j = 62; j >= song->currRow; j--)
 						{
-							noteSrc = &modEntry->patterns[modEntry->currPattern][(j * AMIGA_VOICES) + cursor.channel];
-							modEntry->patterns[modEntry->currPattern][((j + 1) * AMIGA_VOICES) + cursor.channel] = *noteSrc;
+							noteSrc = &song->patterns[song->currPattern][(j * AMIGA_VOICES) + cursor.channel];
+							song->patterns[song->currPattern][((j + 1) * AMIGA_VOICES) + cursor.channel] = *noteSrc;
 						}
 					}
 				}
@@ -2400,18 +2385,18 @@
 				saveUndo();
 				for (i = 0; i <= editor.buffToPos-editor.buffFromPos; i++)
 				{
-					if (modEntry->currRow+i > 63)
+					if (song->currRow+i > 63)
 						break;
 
-					modEntry->patterns[modEntry->currPattern][((modEntry->currRow + i) * AMIGA_VOICES) + cursor.channel]
+					song->patterns[song->currPattern][((song->currRow + i) * AMIGA_VOICES) + cursor.channel]
 						= editor.blockBuffer[editor.buffFromPos + i];
 				}
 
 				if (!keyb.shiftPressed)
 				{
-					modEntry->currRow += i & 0xFF;
-					if (modEntry->currRow > 63)
-						modEntry->currRow = 0;
+					song->currRow += i & 0xFF;
+					if (song->currRow > 63)
+						song->currRow = 0;
 				}
 
 				updateWindowTitle(MOD_IS_MODIFIED);
@@ -2442,8 +2427,8 @@
 				saveUndo();
 
 				i = editor.buffFromPos;
-				j = modEntry->currRow;
-				patt = modEntry->patterns[modEntry->currPattern];
+				j = song->currRow;
+				patt = song->patterns[song->currPattern];
 				while (true)
 				{
 					noteDst = &patt[(j * AMIGA_VOICES) + cursor.channel];
@@ -2467,9 +2452,9 @@
 
 				if (!keyb.shiftPressed)
 				{
-					modEntry->currRow += (editor.buffToPos-editor.buffFromPos) + 1;
-					if (modEntry->currRow > 63)
-						modEntry->currRow = 0;
+					song->currRow += (editor.buffToPos-editor.buffFromPos) + 1;
+					if (song->currRow > 63)
+						song->currRow = 0;
 				}
 
 				updateWindowTitle(MOD_IS_MODIFIED);
@@ -2488,7 +2473,7 @@
 			{
 				for (i = 0; i < MOD_ROWS; i++)
 				{
-					noteSrc = &modEntry->patterns[modEntry->currPattern][(i * AMIGA_VOICES) + cursor.channel];
+					noteSrc = &song->patterns[song->currPattern][(i * AMIGA_VOICES) + cursor.channel];
 					if (noteSrc->sample == editor.currSample+1)
 					{
 						noteSrc->period = 0;
@@ -2505,13 +2490,13 @@
 			{
 				saveUndo();
 
-				i = modEntry->currRow;
+				i = song->currRow;
 				if (keyb.shiftPressed)
 				{
 					// kill to start
 					while (i >= 0)
 					{
-						noteDst = &modEntry->patterns[modEntry->currPattern][(i * AMIGA_VOICES) + cursor.channel];
+						noteDst = &song->patterns[song->currPattern][(i * AMIGA_VOICES) + cursor.channel];
 						noteDst->period = 0;
 						noteDst->sample = 0;
 						noteDst->command = 0;
@@ -2525,7 +2510,7 @@
 					// kill to end
 					while (i < MOD_ROWS)
 					{
-						noteDst = &modEntry->patterns[modEntry->currPattern][(i * AMIGA_VOICES) + cursor.channel];
+						noteDst = &song->patterns[song->currPattern][(i * AMIGA_VOICES) + cursor.channel];
 						noteDst->period = 0;
 						noteDst->sample = 0;
 						noteDst->command = 0;
@@ -2593,7 +2578,7 @@
 			if (keyb.leftCtrlPressed)
 			{
 				editor.blockMarkFlag = true;
-				modEntry->currRow = editor.blockToPos;
+				song->currRow = editor.blockToPos;
 			}
 			else
 			{
@@ -2610,17 +2595,17 @@
 
 				saveUndo();
 
-				j = modEntry->currRow + 1;
+				j = song->currRow + 1;
 				while (j < MOD_ROWS)
 				{
 					for (i = j; i < MOD_ROWS-1; i++)
 					{
-						noteSrc = &modEntry->patterns[modEntry->currPattern][((i + 1) * AMIGA_VOICES) + cursor.channel];
-						modEntry->patterns[modEntry->currPattern][(i * AMIGA_VOICES) + cursor.channel] = *noteSrc;
+						noteSrc = &song->patterns[song->currPattern][((i + 1) * AMIGA_VOICES) + cursor.channel];
+						song->patterns[song->currPattern][(i * AMIGA_VOICES) + cursor.channel] = *noteSrc;
 					}
 
 					// clear newly made row on very bottom
-					noteDst = &modEntry->patterns[modEntry->currPattern][(63 * AMIGA_VOICES) + cursor.channel];
+					noteDst = &song->patterns[song->currPattern][(63 * AMIGA_VOICES) + cursor.channel];
 					noteDst->period = 0;
 					noteDst->sample = 0;
 					noteDst->command = 0;
@@ -2652,8 +2637,8 @@
 				saveUndo();
 
 				i = editor.buffFromPos;
-				j = modEntry->currRow;
-				patt = modEntry->patterns[modEntry->currPattern];
+				j = song->currRow;
+				patt = song->patterns[song->currPattern];
 				while (true)
 				{
 					noteDst = &patt[(j * AMIGA_VOICES) + cursor.channel];
@@ -2668,9 +2653,9 @@
 
 				if (!keyb.shiftPressed)
 				{
-					modEntry->currRow += (editor.buffToPos-editor.buffFromPos) + 1;
-					if (modEntry->currRow > 63)
-						modEntry->currRow = 0;
+					song->currRow += (editor.buffToPos-editor.buffFromPos) + 1;
+					if (song->currRow > 63)
+						song->currRow = 0;
 				}
 
 				updateWindowTitle(MOD_IS_MODIFIED);
@@ -2869,8 +2854,8 @@
 				saveUndo();
 
 				i = editor.buffFromPos;
-				j = modEntry->currRow;
-				patt = modEntry->patterns[modEntry->currPattern];
+				j = song->currRow;
+				patt = song->patterns[song->currPattern];
 				while (true)
 				{
 					noteDst = &patt[(j * AMIGA_VOICES) + cursor.channel];
@@ -2894,9 +2879,9 @@
 
 				if (!keyb.shiftPressed)
 				{
-					modEntry->currRow += (editor.buffToPos-editor.buffFromPos) + 1;
-					if (modEntry->currRow > 63)
-						modEntry->currRow = 0;
+					song->currRow += (editor.buffToPos-editor.buffFromPos) + 1;
+					if (song->currRow > 63)
+						song->currRow = 0;
 				}
 
 				updateWindowTitle(MOD_IS_MODIFIED);
@@ -2934,7 +2919,7 @@
 				editor.blockBufferFlag = true;
 
 				for (i = 0; i < MOD_ROWS; i++)
-					editor.blockBuffer[i] = modEntry->patterns[modEntry->currPattern][(i * AMIGA_VOICES) + cursor.channel];
+					editor.blockBuffer[i] = song->patterns[song->currPattern][(i * AMIGA_VOICES) + cursor.channel];
 
 				if (editor.blockFromPos > editor.blockToPos)
 				{
@@ -2949,7 +2934,7 @@
 
 				for (i = editor.buffFromPos; i <= editor.buffToPos; i++)
 				{
-					noteDst = &modEntry->patterns[modEntry->currPattern][(i * AMIGA_VOICES) + cursor.channel];
+					noteDst = &song->patterns[song->currPattern][(i * AMIGA_VOICES) + cursor.channel];
 					noteDst->period = 0;
 					noteDst->sample = 0;
 					noteDst->command = 0;
@@ -3002,8 +2987,8 @@
 
 				while (blockFrom < blockTo)
 				{
-					noteDst = &modEntry->patterns[modEntry->currPattern][(blockFrom * AMIGA_VOICES) + cursor.channel];
-					noteSrc = &modEntry->patterns[modEntry->currPattern][(blockTo * AMIGA_VOICES) + cursor.channel];
+					noteDst = &song->patterns[song->currPattern][(blockFrom * AMIGA_VOICES) + cursor.channel];
+					noteSrc = &song->patterns[song->currPattern][(blockTo * AMIGA_VOICES) + cursor.channel];
 
 					noteTmp = *noteDst;
 					*noteDst = *noteSrc;
@@ -3056,7 +3041,7 @@
 
 					for (i = 0; i < AMIGA_VOICES; i++)
 					{
-						ch = &modEntry->channels[i];
+						ch = &song->channels[i];
 						ch->n_wavecontrol = 0;
 						ch->n_glissfunk = 0;
 						ch->n_finetune = 0;
@@ -3167,8 +3152,8 @@
 
 				if (ui.posEdScreenShown)
 				{
-					if (modEntry->currOrder-(POSED_LIST_SIZE-1) > 0)
-						modSetPos(modEntry->currOrder-(POSED_LIST_SIZE-1), DONT_SET_ROW);
+					if (song->currOrder-(POSED_LIST_SIZE-1) > 0)
+						modSetPos(song->currOrder-(POSED_LIST_SIZE-1), DONT_SET_ROW);
 					else
 						modSetPos(0, DONT_SET_ROW);
 				}
@@ -3185,12 +3170,12 @@
 				}
 				else if (editor.currMode == MODE_IDLE || editor.currMode == MODE_EDIT)
 				{
-					if (modEntry->currRow == 63)
-						modSetPos(DONT_SET_ORDER, modEntry->currRow - 15);
-					else if (modEntry->currRow == 15)
+					if (song->currRow == 63)
+						modSetPos(DONT_SET_ORDER, song->currRow - 15);
+					else if (song->currRow == 15)
 						modSetPos(DONT_SET_ORDER, 0); // 15-16 would turn into -1, which is "DON'T SET ROW" flag
 					else
-						modSetPos(DONT_SET_ORDER, modEntry->currRow - 16);
+						modSetPos(DONT_SET_ORDER, song->currRow - 16);
 				}
 			}
 		}
@@ -3204,10 +3189,10 @@
 
 				if (ui.posEdScreenShown)
 				{
-					if (modEntry->currOrder+(POSED_LIST_SIZE-1) <= modEntry->head.orderCount-1)
-						modSetPos(modEntry->currOrder+(POSED_LIST_SIZE-1), DONT_SET_ROW);
+					if (song->currOrder+(POSED_LIST_SIZE-1) <= song->header.numOrders-1)
+						modSetPos(song->currOrder+(POSED_LIST_SIZE-1), DONT_SET_ROW);
 					else
-						modSetPos(modEntry->head.orderCount - 1, DONT_SET_ROW);
+						modSetPos(song->header.numOrders - 1, DONT_SET_ROW);
 				}
 				else if (ui.diskOpScreenShown)
 				{
@@ -3222,7 +3207,7 @@
 				}
 				else if (editor.currMode == MODE_IDLE || editor.currMode == MODE_EDIT)
 				{
-					modSetPos(DONT_SET_ORDER, modEntry->currRow + 16);
+					modSetPos(DONT_SET_ORDER, song->currRow + 16);
 				}
 			}
 		}
@@ -3253,8 +3238,8 @@
 					if (keyb.repeatCounter >= 6)
 					{
 						keyb.repeatCounter = 0;
-						if (modEntry->currOrder > 0)
-							modSetPos(modEntry->currOrder - 1, DONT_SET_ROW);
+						if (song->currOrder > 0)
+							modSetPos(song->currOrder - 1, DONT_SET_ROW);
 					}
 				}
 				else if (keyb.leftAltPressed)
@@ -3303,8 +3288,8 @@
 					if (keyb.repeatCounter >= 6)
 					{
 						keyb.repeatCounter = 0;
-						if (modEntry->currOrder < 126)
-							modSetPos(modEntry->currOrder + 1, DONT_SET_ROW);
+						if (song->currOrder < 126)
+							modSetPos(song->currOrder + 1, DONT_SET_ROW);
 					}
 				}
 				else if (keyb.leftAltPressed)
@@ -3351,9 +3336,9 @@
 				if (keyb.repeatCounter >= 3)
 				{
 					keyb.repeatCounter = 0;
-					if (modEntry->currOrder > 0)
+					if (song->currOrder > 0)
 					{
-						modSetPos(modEntry->currOrder - 1, DONT_SET_ROW);
+						modSetPos(song->currOrder - 1, DONT_SET_ROW);
 						ui.updatePosEd = true;
 					}
 				}
@@ -3371,7 +3356,7 @@
 					if (keyb.repeatCounter >= repeatNum)
 					{
 						keyb.repeatCounter = 0;
-						modSetPos(DONT_SET_ORDER, (modEntry->currRow - 1) & 0x3F);
+						modSetPos(DONT_SET_ORDER, (song->currRow - 1) & 0x3F);
 					}
 				}
 			}
@@ -3405,12 +3390,12 @@
 				{
 					keyb.repeatCounter = 0;
 
-					if (modEntry->currOrder != modEntry->head.orderCount-1)
+					if (song->currOrder != song->header.numOrders-1)
 					{
-						if (++modEntry->currOrder > modEntry->head.orderCount-1)
-							modEntry->currOrder = modEntry->head.orderCount-1;
+						if (++song->currOrder > song->header.numOrders-1)
+							song->currOrder = song->header.numOrders-1;
 
-						modSetPos(modEntry->currOrder, DONT_SET_ROW);
+						modSetPos(song->currOrder, DONT_SET_ROW);
 						ui.updatePosEd = true;
 					}
 				}
@@ -3428,7 +3413,7 @@
 					if (keyb.repeatCounter >= repeatNum)
 					{
 						keyb.repeatCounter = 0;
-						modSetPos(DONT_SET_ORDER, (modEntry->currRow + 1) & 0x3F);
+						modSetPos(DONT_SET_ORDER, (song->currRow + 1) & 0x3F);
 					}
 				}
 			}
@@ -3692,10 +3677,10 @@
 			{
 				for (i = 0; i < MOD_ROWS; i++)
 				{
-					noteSrc = &modEntry->patterns[modEntry->currPattern][(i * AMIGA_VOICES) + cursor.channel];
-					noteTmp = modEntry->patterns[modEntry->currPattern][i * AMIGA_VOICES];
+					noteSrc = &song->patterns[song->currPattern][(i * AMIGA_VOICES) + cursor.channel];
+					noteTmp = song->patterns[song->currPattern][i * AMIGA_VOICES];
 
-					modEntry->patterns[modEntry->currPattern][i * AMIGA_VOICES] = *noteSrc;
+					song->patterns[song->currPattern][i * AMIGA_VOICES] = *noteSrc;
 					*noteSrc = noteTmp;
 				}
 
@@ -3710,10 +3695,10 @@
 			{
 				for (i = 0; i < MOD_ROWS; i++)
 				{
-					noteSrc = &modEntry->patterns[modEntry->currPattern][(i * AMIGA_VOICES) + cursor.channel];
-					noteTmp = modEntry->patterns[modEntry->currPattern][(i * AMIGA_VOICES) + 1];
+					noteSrc = &song->patterns[song->currPattern][(i * AMIGA_VOICES) + cursor.channel];
+					noteTmp = song->patterns[song->currPattern][(i * AMIGA_VOICES) + 1];
 
-					modEntry->patterns[modEntry->currPattern][(i * AMIGA_VOICES) + 1] = *noteSrc;
+					song->patterns[song->currPattern][(i * AMIGA_VOICES) + 1] = *noteSrc;
 					*noteSrc = noteTmp;
 				}
 
@@ -3728,10 +3713,10 @@
 			{
 				for (i = 0; i < MOD_ROWS; i++)
 				{
-					noteSrc = &modEntry->patterns[modEntry->currPattern][(i * AMIGA_VOICES) + cursor.channel];
-					noteTmp = modEntry->patterns[modEntry->currPattern][(i * AMIGA_VOICES) + 2];
+					noteSrc = &song->patterns[song->currPattern][(i * AMIGA_VOICES) + cursor.channel];
+					noteTmp = song->patterns[song->currPattern][(i * AMIGA_VOICES) + 2];
 
-					modEntry->patterns[modEntry->currPattern][(i * AMIGA_VOICES) + 2] = *noteSrc;
+					song->patterns[song->currPattern][(i * AMIGA_VOICES) + 2] = *noteSrc;
 					*noteSrc = noteTmp;
 				}
 
@@ -3746,10 +3731,10 @@
 			{
 				for (i = 0; i < MOD_ROWS; i++)
 				{
-					noteSrc = &modEntry->patterns[modEntry->currPattern][(i * AMIGA_VOICES) + cursor.channel];
-					noteTmp = modEntry->patterns[modEntry->currPattern][(i * AMIGA_VOICES) + 3];
+					noteSrc = &song->patterns[song->currPattern][(i * AMIGA_VOICES) + cursor.channel];
+					noteTmp = song->patterns[song->currPattern][(i * AMIGA_VOICES) + 3];
 
-					modEntry->patterns[modEntry->currPattern][(i * AMIGA_VOICES) + 3] = *noteSrc;
+					song->patterns[song->currPattern][(i * AMIGA_VOICES) + 3] = *noteSrc;
 					*noteSrc = noteTmp;
 				}
 
@@ -4227,18 +4212,18 @@
 					saveUndo();
 					if (keyb.leftAltPressed && !keyb.leftCtrlPressed)
 					{
-						if (modEntry->currRow > 0)
+						if (song->currRow > 0)
 						{
 							for (i = 0; i < AMIGA_VOICES; i++)
 							{
-								for (j = (modEntry->currRow - 1); j < MOD_ROWS; j++)
+								for (j = (song->currRow - 1); j < MOD_ROWS; j++)
 								{
-									noteSrc = &modEntry->patterns[modEntry->currPattern][((j + 1) * AMIGA_VOICES) + i];
-									modEntry->patterns[modEntry->currPattern][(j * AMIGA_VOICES) + i] = *noteSrc;
+									noteSrc = &song->patterns[song->currPattern][((j + 1) * AMIGA_VOICES) + i];
+									song->patterns[song->currPattern][(j * AMIGA_VOICES) + i] = *noteSrc;
 								}
 
 								// clear newly made row on very bottom
-								noteDst = &modEntry->patterns[modEntry->currPattern][(63 * AMIGA_VOICES) + i];
+								noteDst = &song->patterns[song->currPattern][(63 * AMIGA_VOICES) + i];
 								noteDst->period = 0;
 								noteDst->sample = 0;
 								noteDst->command = 0;
@@ -4245,18 +4230,18 @@
 								noteDst->param = 0;
 							}
 
-							modEntry->currRow--;
+							song->currRow--;
 							ui.updatePatternData = true;
 						}
 					}
 					else
 					{
-						if (modEntry->currRow > 0)
+						if (song->currRow > 0)
 						{
-							for (i = modEntry->currRow-1; i < MOD_ROWS-1; i++)
+							for (i = song->currRow-1; i < MOD_ROWS-1; i++)
 							{
-								noteSrc = &modEntry->patterns[modEntry->currPattern][((i + 1) * AMIGA_VOICES) + cursor.channel];
-								noteDst = &modEntry->patterns[modEntry->currPattern][(i * AMIGA_VOICES) + cursor.channel];
+								noteSrc = &song->patterns[song->currPattern][((i + 1) * AMIGA_VOICES) + cursor.channel];
+								noteDst = &song->patterns[song->currPattern][(i * AMIGA_VOICES) + cursor.channel];
 
 								if (keyb.leftCtrlPressed)
 								{
@@ -4270,13 +4255,13 @@
 							}
 
 							// clear newly made row on very bottom
-							noteDst = &modEntry->patterns[modEntry->currPattern][(63 * AMIGA_VOICES) + cursor.channel];
+							noteDst = &song->patterns[song->currPattern][(63 * AMIGA_VOICES) + cursor.channel];
 							noteDst->period = 0;
 							noteDst->sample = 0;
 							noteDst->command = 0;
 							noteDst->param = 0;
 
-							modEntry->currRow--;
+							song->currRow--;
 							ui.updatePatternData = true;
 						}
 					}
@@ -4287,7 +4272,7 @@
 					editor.stepPlayBackwards = true;
 
 					doStopIt(true);
-					playPattern((modEntry->currRow - 1) & 0x3F);
+					playPattern((song->currRow - 1) & 0x3F);
 				}
 			}
 		}
--- a/src/pt2_main.c
+++ b/src/pt2_main.c
@@ -28,17 +28,19 @@
 #include "pt2_config.h"
 #include "pt2_visuals.h"
 #include "pt2_edit.h"
-#include "pt2_modloader.h"
-#include "pt2_sampleloader.h"
+#include "pt2_module_loader.h"
+#include "pt2_module_saver.h"
+#include "pt2_sample_loader.h"
 #include "pt2_unicode.h"
 #include "pt2_scopes.h"
 #include "pt2_audio.h"
+#include "pt2_bmp.h"
 
 #define CRASH_TEXT "Oh no!\nThe ProTracker 2 clone has crashed...\n\nA backup .mod was hopefully " \
                    "saved to the current module directory.\n\nPlease report this bug if you can.\n" \
                    "Try to mention what you did before the crash happened."
 
-module_t *modEntry = NULL; // globalized
+module_t *song = NULL; // globalized
 
 static bool backupMadeAfterCrash;
 
@@ -198,6 +200,7 @@
 
 #ifdef _WIN32
 
+	// Windows: the Win key has to be taken over to work like the Amiga key
 #ifndef _DEBUG
 	windowsKeyIsDown = false;
 	g_hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, lowLevelKeyboardProc, GetModuleHandle(NULL), 0);
@@ -244,8 +247,8 @@
 	setupSprites();
 	setupPerfFreq();
 
-	modEntry = createNewMod();
-	if (modEntry == NULL)
+	song = createNewMod();
+	if (song == NULL)
 	{
 		cleanUp();
 		SDL_Quit();
@@ -273,7 +276,7 @@
 		loadModFromArg(argv[1]);
 
 		// play song
-		if (modEntry->moduleLoaded)
+		if (song->loaded)
 		{
 			editor.playMode = PLAY_MODE_NORMAL;
 			modPlay(DONT_SET_PATTERN, 0, DONT_SET_ROW);
@@ -282,10 +285,9 @@
 			statusAllRight();
 		}
 	}
-	else
+	else if (!editor.configFound)
 	{
-		if (!editor.configFound)
-			displayErrorMsg("CONFIG NOT FOUND!");
+		displayErrorMsg("CONFIG NOT FOUND!");
 	}
 
 	displayMainScreen();
@@ -448,8 +450,6 @@
 
 	editor.repeatKeyFlag = (SDL_GetModState() & KMOD_CAPS) ? true : false;
 
-	modEntry = NULL;
-
 	strcpy(editor.mixText, "MIX 01+02 TO 03");
 
 	// allocate some memory
@@ -538,7 +538,7 @@
 
 static void handleSigTerm(void)
 {
-	if (modEntry->modified)
+	if (song->modified)
 	{
 		resetAllScreens();
 
@@ -862,8 +862,9 @@
 
 static void cleanUp(void) // never call this inside the main loop!
 {
-	audioClose();
+	modStop();
 	modFree();
+	audioClose();
 	deAllocSamplerVars();
 	freeDiskOpMem();
 	freeDiskOpEntryMem();
--- a/src/pt2_mod2wav.c
+++ b/src/pt2_mod2wav.c
@@ -178,7 +178,7 @@
 
 	editor.abortMod2Wav = false;
 
-	modSetTempo(modEntry->currBPM); // update BPM with MOD2WAV audio output rate
+	modSetTempo(song->currBPM); // update BPM with MOD2WAV audio output rate
 
 	editor.mod2WavThread = SDL_CreateThread(mod2WavThreadFunc, NULL, fOut);
 	if (editor.mod2WavThread != NULL)
@@ -217,12 +217,12 @@
 	memset(n_pattpos, 0, sizeof (n_pattpos));
 	memset(n_loopcount, 0, sizeof (n_loopcount));
 
-	modEntry->rowsCounter = 0;
-	modEntry->rowsInTotal = 0;
+	song->rowsCounter = 0;
+	song->rowsInTotal = 0;
 
 	modRow = 0;
 	modOrder = 0;
-	modPattern = modEntry->head.order[0];
+	modPattern = song->header.order[0];
 	pBreakPosition = 0;
 	posJumpAssert = false;
 	pBreakFlag = false;
@@ -235,7 +235,7 @@
 
 		for (ch = 0; ch < AMIGA_VOICES; ch++)
 		{
-			note = &modEntry->patterns[modPattern][(modRow * AMIGA_VOICES) + ch];
+			note = &song->patterns[modPattern][(modRow * AMIGA_VOICES) + ch];
 			if (note->command == 0x0B) // Bxx - Position Jump
 			{
 				modOrder = note->param - 1;
@@ -284,7 +284,7 @@
 		}
 
 		modRow++;
-		modEntry->rowsInTotal++;
+		song->rowsInTotal++;
 
 		if (pBreakFlag)
 		{
@@ -300,7 +300,7 @@
 			posJumpAssert = false;
 
 			modOrder = (modOrder + 1) & 0x7F;
-			if (modOrder >= modEntry->head.orderCount)
+			if (modOrder >= song->header.numOrders)
 			{
 				modOrder = 0;
 				calcingRows = false;
@@ -307,7 +307,7 @@
 				break;
 			}
 
-			modPattern = modEntry->head.order[modOrder];
+			modPattern = song->header.order[modOrder];
 			if (modPattern > MAX_PATTERNS-1)
 				modPattern = MAX_PATTERNS-1;
 		}
--- a/src/pt2_modloader.c
+++ /dev/null
@@ -1,1459 +1,0 @@
-// for finding memory leaks in debug mode with Visual Studio 
-#if defined _DEBUG && defined _MSC_VER
-#include <crtdbg.h>
-#endif
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdint.h>
-#include <stdbool.h>
-#include <ctype.h> // tolower()
-#ifdef _WIN32
-#include <io.h>
-#else
-#include <unistd.h>
-#endif
-#include <fcntl.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include "pt2_mouse.h"
-#include "pt2_header.h"
-#include "pt2_sampler.h"
-#include "pt2_textout.h"
-#include "pt2_audio.h"
-#include "pt2_helpers.h"
-#include "pt2_visuals.h"
-#include "pt2_unicode.h"
-#include "pt2_modloader.h"
-#include "pt2_sampleloader.h"
-#
-typedef struct mem_t
-{
-	bool _eof;
-	uint8_t *_ptr, *_base;
-	uint32_t _cnt, _bufsiz;
-} MEMFILE;
-
-static bool oldAutoPlay;
-static char oldFullPath[(PATH_MAX * 2) + 2];
-static uint32_t oldFullPathLen;
-static module_t *tempMod;
-
-static MEMFILE *mopen(const uint8_t *src, uint32_t length);
-static void mclose(MEMFILE **buf);
-static int32_t mgetc(MEMFILE *buf);
-static size_t mread(void *buffer, size_t size, size_t count, MEMFILE *buf);
-static void mseek(MEMFILE *buf, int32_t offset, int32_t whence);
-static void mrewind(MEMFILE *buf);
-static uint8_t ppdecrunch(uint8_t *src, uint8_t *dst, uint8_t *offsetLens, uint32_t srcLen, uint32_t dstLen, uint8_t skipBits);
-
-void showSongUnsavedAskBox(int8_t askScreenType)
-{
-	ui.askScreenShown = true;
-	ui.askScreenType = askScreenType;
-
-	pointerSetMode(POINTER_MODE_MSG1, NO_CARRY);
-	setStatusMessage("SONG IS UNSAVED !", NO_CARRY);
-	renderAskDialog();
-}
-
-bool modSave(char *fileName)
-{
-	int16_t tempPatternCount;
-	int32_t i;
-	uint32_t tempLoopLength, tempLoopStart, j, k;
-	note_t tmp;
-	FILE *fmodule;
-
-	tempPatternCount = 0;
-
-	fmodule = fopen(fileName, "wb");
-	if (fmodule == NULL)
-	{
-		displayErrorMsg("FILE I/O ERROR !");
-		return false;
-	}
-
-	for (i = 0; i < 20; i++)
-		fputc(tolower(modEntry->head.moduleTitle[i]), fmodule);
-
-	for (i = 0; i < MOD_SAMPLES; i++)
-	{
-		for (j = 0; j < 22; j++)
-			fputc(tolower(modEntry->samples[i].text[j]), fmodule);
-
-		fputc(modEntry->samples[i].length >> 9, fmodule);
-		fputc(modEntry->samples[i].length >> 1, fmodule);
-		fputc(modEntry->samples[i].fineTune & 0x0F, fmodule);
-		fputc((modEntry->samples[i].volume > 64) ? 64 : modEntry->samples[i].volume, fmodule);
-
-		tempLoopLength = modEntry->samples[i].loopLength;
-		if (tempLoopLength < 2)
-			tempLoopLength = 2;
-
-		tempLoopStart = modEntry->samples[i].loopStart;
-		if (tempLoopLength == 2)
-			tempLoopStart = 0;
-
-		fputc(tempLoopStart >> 9, fmodule);
-		fputc(tempLoopStart >> 1, fmodule);
-		fputc(tempLoopLength >> 9, fmodule);
-		fputc(tempLoopLength >> 1, fmodule);
-	}
-
-	fputc(modEntry->head.orderCount & 0x00FF, fmodule);
-	fputc(0x7F, fmodule); // ProTracker puts 0x7F at this place (restart pos/BPM in other trackers)
-
-	for (i = 0; i < MOD_ORDERS; i++)
-		fputc(modEntry->head.order[i] & 0xFF, fmodule);
-
-	tempPatternCount = 0;
-	for (i = 0; i < MOD_ORDERS; i++)
-	{
-		if (tempPatternCount < modEntry->head.order[i])
-			tempPatternCount = modEntry->head.order[i];
-	}
-
-	if (++tempPatternCount > MAX_PATTERNS)
-		  tempPatternCount = MAX_PATTERNS;
-
-	fwrite((tempPatternCount <= 64) ? "M.K." : "M!K!", 1, 4, fmodule);
-
-	for (i = 0; i < tempPatternCount; i++)
-	{
-		for (j = 0; j < MOD_ROWS; j++)
-		{
-			for (k = 0; k < AMIGA_VOICES; k++)
-			{
-				tmp = modEntry->patterns[i][(j * AMIGA_VOICES) + k];
-
-				fputc((tmp.sample & 0xF0) | ((tmp.period >> 8) & 0x0F), fmodule);
-				fputc(tmp.period & 0xFF, fmodule);
-				fputc(((tmp.sample << 4) & 0xF0) | (tmp.command & 0x0F), fmodule);
-				fputc(tmp.param, fmodule);
-			}
-		}
-	}
-
-	for (i = 0; i < MOD_SAMPLES; i++)
-	{
-		// Amiga ProTracker stuck "BEEP" sample fix
-		if (modEntry->samples[i].length >= 2 && modEntry->samples[i].loopStart+modEntry->samples[i].loopLength == 2)
-		{
-			fputc(0, fmodule);
-			fputc(0, fmodule);
-
-			k = modEntry->samples[i].length;
-			for (j = 2; j < k; j++)
-				fputc(modEntry->sampleData[modEntry->samples[i].offset+j], fmodule);
-		}
-		else
-		{
-			fwrite(&modEntry->sampleData[MAX_SAMPLE_LEN * i], 1, modEntry->samples[i].length, fmodule);
-		}
-	}
-
-	fclose(fmodule);
-
-	displayMsg("MODULE SAVED !");
-	setMsgPointer();
-
-	diskop.cached = false;
-	if (ui.diskOpScreenShown)
-		ui.updateDiskOpFileList = true;
-
-	updateWindowTitle(MOD_NOT_MODIFIED);
-	return true;
-}
-
-#define IS_ID(s, b) !strncmp(s, b, 4)
-
-static uint8_t getModType(uint8_t *numChannels, const char *id)
-{
-	*numChannels = 4;
-
-	if (IS_ID("M.K.", id) || IS_ID("M!K!", id) || IS_ID("NSMS", id) || IS_ID("LARD", id) || IS_ID("PATT", id))
-	{
-		return FORMAT_MK; // ProTracker (or compatible)
-	}
-	else if (IS_ID("FLT4", id))
-	{
-		return FORMAT_FLT; // Startrekker (4 channels)
-	}
-	else if (IS_ID("N.T.", id))
-	{
-		return FORMAT_NT; // NoiseTracker
-	}
-	else if (IS_ID("M&K!", id) || IS_ID("FEST", id))
-	{
-		return FORMAT_HMNT; // His Master's NoiseTracker
-	}
-	else if (id[1] == 'C' && id[2] == 'H' && id[3] == 'N')
-	{
-		*numChannels = id[0] - '0';
-		return FORMAT_FT2; // Fasttracker II 1..9 channels (or other trackers)
-	}
-
-	return FORMAT_UNKNOWN; // may be The Ultimate Soundtracker (set to FORMAT_STK later)
-}
-
-// converts zeroes to spaces in a string, up until the last zero found
-static void fixZeroesInString(char *str, uint32_t maxLength)
-{
-	int32_t i;
-
-	for (i = maxLength-1; i >= 0; i--)
-	{
-		if (str[i] != '\0')
-			break;
-	}
-
-	// convert zeroes to spaces
-	if (i > 0)
-	{
-		for (int32_t j = 0; j < i; j++)
-		{
-			if (str[j] == '\0')
-				str[j] = ' ';
-		}
-	}
-}
-
-static uint8_t *unpackPPModule(FILE *f, uint32_t *filesize)
-{
-	uint8_t *modBuffer, ppCrunchData[4], *ppBuffer;
-	uint32_t ppPackLen, ppUnpackLen;
-
-	ppPackLen = *filesize;
-	if ((ppPackLen & 3) || ppPackLen <= 12)
-	{
-		displayErrorMsg("POWERPACKER ERROR");
-		return NULL;
-	}
-
-	ppBuffer = (uint8_t *)malloc(ppPackLen);
-	if (ppBuffer == NULL)
-	{
-		statusOutOfMemory();
-		return NULL;
-	}
-
-	fseek(f, ppPackLen-4, SEEK_SET);
-
-	ppCrunchData[0] = (uint8_t)fgetc(f);
-	ppCrunchData[1] = (uint8_t)fgetc(f);
-	ppCrunchData[2] = (uint8_t)fgetc(f);
-	ppCrunchData[3] = (uint8_t)fgetc(f);
-
-	ppUnpackLen = (ppCrunchData[0] << 16) | (ppCrunchData[1] << 8) | ppCrunchData[2];
-
-	modBuffer = (uint8_t *)malloc(ppUnpackLen);
-	if (modBuffer == NULL)
-	{
-		free(ppBuffer);
-		statusOutOfMemory();
-		return NULL;
-	}
-
-	rewind(f);
-	fread(ppBuffer, 1, ppPackLen, f);
-	fclose(f);
-
-	if (!ppdecrunch(ppBuffer+8, modBuffer, ppBuffer+4, ppPackLen-12, ppUnpackLen, ppCrunchData[3]))
-	{
-		free(ppBuffer);
-		displayErrorMsg("POWERPACKER ERROR");
-		return NULL;
-	}
-
-	free(ppBuffer);
-	*filesize = ppUnpackLen;
-	return modBuffer;
-}
-
-module_t *modLoad(UNICHAR *fileName)
-{
-	bool mightBeSTK, lateSTKVerFlag, veryLateSTKVerFlag;
-	char modID[4], tmpChar;
-	int8_t numSamples;
-	uint8_t bytes[4], restartPos, modFormat;
-	uint8_t *modBuffer, numChannels;
-	int32_t i, j, k, loopStart, loopLength, loopOverflowVal, numPatterns;
-	uint32_t powerPackerID, filesize;
-	FILE *f;
-	MEMFILE *m;
-	module_t *newMod;
-	moduleSample_t *s;
-	note_t *note;
-
-	veryLateSTKVerFlag = false; // "DFJ SoundTracker III" and later
-	lateSTKVerFlag = false; // "TJC SoundTracker II" and later
-	mightBeSTK = false;
-
-	m = NULL;
-	f = NULL;
-	modBuffer = NULL;
-
-	newMod = (module_t *)calloc(1, sizeof (module_t));
-	if (newMod == NULL)
-	{
-		statusOutOfMemory();
-		goto modLoadError;
-	}
-
-	f = UNICHAR_FOPEN(fileName, "rb");
-	if (f == NULL)
-	{
-		displayErrorMsg("FILE I/O ERROR !");
-		goto modLoadError;
-	}
-
-	fseek(f, 0, SEEK_END);
-	filesize = ftell(f);
-	rewind(f);
-
-	// check if mod is a powerpacker mod
-	fread(&powerPackerID, 4, 1, f);
-	if (powerPackerID == 0x30325850) // "PX20"
-	{
-		displayErrorMsg("ENCRYPTED MOD !");
-		goto modLoadError;
-	}
-	else if (powerPackerID == 0x30325050) // "PP20"
-	{
-		modBuffer = unpackPPModule(f, &filesize);
-		if (modBuffer == NULL)
-			goto modLoadError; // error msg is set in unpackPPModule()
-	}
-	else
-	{
-		modBuffer = (uint8_t *)malloc(filesize);
-		if (modBuffer == NULL)
-		{
-			statusOutOfMemory();
-			goto modLoadError;
-		}
-
-		fseek(f, 0, SEEK_SET);
-		fread(modBuffer, 1, filesize, f);
-		fclose(f);
-	}
-
-	// smallest and biggest possible PT .MOD
-	if (filesize < 2108 || filesize > 4195326)
-	{
-		displayErrorMsg("NOT A MOD FILE !");
-		goto modLoadError;
-	}
-
-	// Use MEMFILE functions on module buffer (similar to FILE functions)
-
-	m = mopen(modBuffer, filesize);
-	if (m == NULL)
-	{
-		displayErrorMsg("FILE I/O ERROR !");
-		goto modLoadError;
-	}
-
-	// check magic ID
-	memset(modID, 0, 4); // in case mread fails
-	mseek(m, 1080, SEEK_SET);
-	mread(modID, 1, 4, m);
-
-	modFormat = getModType(&numChannels, modID);
-	if (numChannels == 0 || numChannels > AMIGA_VOICES)
-	{
-		displayErrorMsg("UNSUPPORTED MOD !");
-		goto modLoadError;
-	}
-
-	if (modFormat == FORMAT_UNKNOWN)
-		mightBeSTK = true;
-
-	mrewind(m);
-	mread(newMod->head.moduleTitle, 1, 20, m);
-	newMod->head.moduleTitle[20] = '\0';
-
-	// convert illegal song name characters to space
-	for (i = 0; i < 20; i++)
-	{
-		tmpChar = newMod->head.moduleTitle[i];
-		if ((tmpChar < ' ' || tmpChar > '~') && tmpChar != '\0')
-			tmpChar = ' ';
-
-		newMod->head.moduleTitle[i] = (char)tolower(tmpChar);
-	}
-
-	fixZeroesInString(newMod->head.moduleTitle, 20);
-
-	// read sample headers
-	s = newMod->samples;
-	for (i = 0; i < MOD_SAMPLES; i++, s++)
-	{
-		if (mightBeSTK && i >= 15) // skip reading sample headers past sample slot 15 in STK/UST modules
-		{
-			s->loopLength = 2; // this be set though
-			continue;
-		}
-
-		mread(s->text, 1, 22, m);
-		s->text[22] = '\0';
-
-		if (modFormat == FORMAT_HMNT)
-		{
-			// most of "His Master's Noisetracker" songs have junk sample names, so let's wipe it.
-			memset(s->text, 0, 22);
-		}
-		else
-		{
-			// convert illegal sample name characters to space
-			for (j = 0; j < 22; j++)
-			{
-				tmpChar = s->text[j];
-				if ((tmpChar < ' ' || tmpChar > '~') && tmpChar != '\0')
-					tmpChar = ' ';
-
-				s->text[j] = (char)tolower(tmpChar);
-			}
-
-			fixZeroesInString(s->text, 22);
-		}
-
-		s->length = (uint16_t)(((mgetc(m) << 8) | mgetc(m)) * 2);
-
-		/* Only late versions of Ultimate SoundTracker could have samples larger than 9999 bytes.
-		** If found, we know for sure that this is a late STK module.
-		*/
-		if (mightBeSTK && s->length > 9999)
-			lateSTKVerFlag = true;
-
-		if (modFormat == FORMAT_HMNT) // finetune in "His Master's NoiseTracker" is different
-			s->fineTune = (uint8_t)((-mgetc(m) & 0x1F) / 2); // one more bit of precision, + inverted
-		else
-			s->fineTune = (uint8_t)mgetc(m) & 0xF;
-
-		if (mightBeSTK)
-			s->fineTune = 0; // this is high byte of volume in STK/UST (has no finetune), set to zero
-
-		s->volume = (int8_t)mgetc(m);
-		if ((uint8_t)s->volume > 64)
-			s->volume = 64;
-
-		loopStart = ((mgetc(m) << 8) | mgetc(m)) * 2;
-		loopLength = ((mgetc(m) << 8) | mgetc(m)) * 2;
-
-		if (loopLength < 2)
-			loopLength = 2; // fixes empty samples in .MODs saved from FT2
-
-		// we don't support samples bigger than 65534 bytes, disable uncompatible loops
-		if (loopStart > MAX_SAMPLE_LEN || loopStart+loopLength > MAX_SAMPLE_LEN)
-		{
-			s->loopStart = 0;
-			s->loopLength = 2;
-		}
-		else
-		{
-			s->loopStart = (uint16_t)loopStart;
-			s->loopLength = (uint16_t)loopLength;
-		}
-
-		// in The Ultimate SoundTracker, sample loop start is in bytes, not words
-		if (mightBeSTK)
-			s->loopStart /= 2;
-
-		// fix for poorly converted STK (< v2.5) -> PT/NT modules (FIXME: Worth keeping or not?)
-		if (!mightBeSTK && s->loopLength > 2 && s->loopStart+s->loopLength > s->length)
-		{
-			if ((s->loopStart/2) + s->loopLength <= s->length)
-				s->loopStart /= 2;
-		}
-	}
-
-	newMod->head.orderCount = (uint8_t)mgetc(m);
-
-	if (modFormat == FORMAT_MK && newMod->head.orderCount == 129)
-		newMod->head.orderCount = 127; // fixes a specific copy of beatwave.mod
-
-	if (newMod->head.orderCount > 129)
-	{
-		displayErrorMsg("NOT A MOD FILE !");
-		goto modLoadError;
-	}
-
-	if (newMod->head.orderCount == 0)
-	{
-		displayErrorMsg("NOT A MOD FILE !");
-		goto modLoadError;
-	}
-
-	restartPos = (uint8_t)mgetc(m);
-	if (mightBeSTK && restartPos > 220)
-	{
-		displayErrorMsg("NOT A MOD FILE !");
-		goto modLoadError;
-	}
-
-	newMod->head.initialTempo = 125;
-	if (mightBeSTK)
-	{
-		/* If we're still here at this point and the mightBeSTK flag is set,
-		** then it's most likely a proper The Ultimate SoundTracker (STK/UST) module.
-		*/
-		modFormat = FORMAT_STK;
-
-		if (restartPos == 0)
-			restartPos = 120;
-
-		// jjk55.mod by Jesper Kyd has a bogus STK tempo value that should be ignored
-		if (!strcmp("jjk55", newMod->head.moduleTitle))
-			restartPos = 120;
-
-		// the "restart pos" field in STK is the inital tempo (must be converted to BPM first)
-		if (restartPos != 120) // 120 is a special case and means 50Hz (125BPM)
-		{
-			if (restartPos > 220)
-				restartPos = 220;
-
-			// convert UST tempo to BPM
-			uint16_t ciaPeriod = (240 - restartPos) * 122;
-			double dHz = (double)CIA_PAL_CLK / ciaPeriod;
-			int32_t BPM = (int32_t)((dHz * (125.0 / 50.0)) + 0.5);
-
-			newMod->head.initialTempo = (uint16_t)BPM;
-		}
-	}
-
-	// read orders and count number of patterns
-	numPatterns = 0;
-	for (i = 0; i < MOD_ORDERS; i++)
-	{
-		newMod->head.order[i] = (int16_t)mgetc(m);
-		if (newMod->head.order[i] > numPatterns)
-			numPatterns = newMod->head.order[i];
-	}
-	numPatterns++;
-
-	if (numPatterns > MAX_PATTERNS)
-	{
-		displayErrorMsg("UNSUPPORTED MOD !");
-		goto modLoadError;
-	}
-
-	// skip magic ID (The Ultimate SoundTracker MODs doesn't have it)
-	if (modFormat != FORMAT_STK)
-		mseek(m, 4, SEEK_CUR);
-
-	// allocate 100 patterns
-	for (i = 0; i < MAX_PATTERNS; i++)
-	{
-		newMod->patterns[i] = (note_t *)calloc(MOD_ROWS * AMIGA_VOICES, sizeof (note_t));
-		if (newMod->patterns[i] == NULL)
-		{
-			statusOutOfMemory();
-			goto modLoadError;
-		}
-	}
-
-	// load pattern data
-	for (i = 0; i < numPatterns; i++)
-	{
-		note = newMod->patterns[i];
-		for (j = 0; j < MOD_ROWS; j++)
-		{
-			for (k = 0; k < numChannels; k++, note++)
-			{
-				mread(bytes, 1, 4, m);
-
-				note->period = ((bytes[0] & 0x0F) << 8) | bytes[1];
-				note->sample = (bytes[0] & 0xF0) | (bytes[2] >> 4);
-				note->command = bytes[2] & 0x0F;
-				note->param = bytes[3];
-
-				if (modFormat == FORMAT_STK)
-				{
-					if (note->command == 0xC || note->command == 0xD || note->command == 0xE)
-					{
-						// "TJC SoundTracker II" and later
-						lateSTKVerFlag = true;
-					}
-
-					if (note->command == 0xF)
-					{
-						// "DFJ SoundTracker III" and later
-						lateSTKVerFlag = true;
-						veryLateSTKVerFlag = true;
-					}
-				}
-			}
-
-			if (numChannels < AMIGA_VOICES)
-				note += AMIGA_VOICES-numChannels;
-		}
-	}
-
-	// pattern command conversion for non-PT formats
-	if (modFormat == FORMAT_STK || modFormat == FORMAT_FT2 || modFormat == FORMAT_NT || modFormat == FORMAT_HMNT || modFormat == FORMAT_FLT)
-	{
-		for (i = 0; i < numPatterns; i++)
-		{
-			note = newMod->patterns[i];
-			for (j = 0; j < MOD_ROWS*4; j++, note++)
-			{
-				if (modFormat == FORMAT_NT || modFormat == FORMAT_HMNT)
-				{
-					// any Dxx == D00 in NT/HMNT
-					if (note->command == 0xD)
-						note->param = 0;
-
-					// effect F with param 0x00 does nothing in NT/HMNT
-					if (note->command == 0xF && note->param == 0)
-						note->command = 0;
-				}
-				else if (modFormat == FORMAT_FLT) // Startrekker (4 channels)
-				{
-					if (note->command == 0xE) // remove unsupported "assembly macros" command
-					{
-						note->command = 0;
-						note->param = 0;
-					}
-
-					// Startrekker is always in vblank mode, and limits speed to 0x1F
-					if (note->command == 0xF && note->param > 0x1F)
-						note->param = 0x1F;
-				}
-				else if (modFormat == FORMAT_STK)
-				{
-					// convert STK effects to PT effects
-
-					if (!lateSTKVerFlag)
-					{
-						// old SoundTracker 1.x commands
-
-						if (note->command == 1)
-						{
-							// arpeggio
-							note->command = 0;
-						}
-						else if (note->command == 2)
-						{
-							// pitch slide
-							if (note->param & 0xF0)
-							{
-								// pitch slide down
-								note->command = 2;
-								note->param >>= 4;
-							}
-							else if (note->param & 0x0F)
-							{
-								// pitch slide up
-								note->command = 1;
-							}
-						}
-					}
-					else
-					{
-						// "DFJ SoundTracker II" or later
-
-						// TODO: This needs more detection and is NOT correct!
-						if (note->command == 0xD)
-						{
-							if (veryLateSTKVerFlag) // "DFJ SoundTracker III" or later
-							{
-								// pattern break w/ no param (param must be cleared to fix some songs)
-								note->param = 0;
-							}
-							else
-							{
-								// volume slide
-								note->command = 0xA;
-							}
-						}
-					}
-
-					// effect F with param 0x00 does nothing in UST/STK (I think?)
-					if (note->command == 0xF && note->param == 0)
-						note->command = 0;
-				}
-
-				// remove sample-trashing effects that were only present in ProTracker
-
-				// remove E8x (Karplus-Strong in ProTracker)
-				if (note->command == 0xE && (note->param >> 4) == 0x8)
-				{
-					note->command = 0;
-					note->param = 0;
-				}
-
-				// remove EFx (Invert Loop in ProTracker)
-				if (note->command == 0xE && (note->param >> 4) == 0xF)
-				{
-					note->command = 0;
-					note->param = 0;
-				}
-			}
-		}
-	}
-
-	// allocate sample data (+2 sample slots for overflow safety (Paula and scopes))
-	newMod->sampleData = (int8_t *)calloc(MOD_SAMPLES + 2, MAX_SAMPLE_LEN);
-	if (newMod->sampleData == NULL)
-	{
-		statusOutOfMemory();
-		goto modLoadError;
-	}
-
-	// set sample data offsets (sample data = one huge buffer to rule them all)
-	for (i = 0; i < MOD_SAMPLES; i++)
-		newMod->samples[i].offset = MAX_SAMPLE_LEN * i;
-
-	// load sample data
-	numSamples = (modFormat == FORMAT_STK) ? 15 : 31;
-	s = newMod->samples;
-	for (i = 0; i < numSamples; i++, s++)
-	{
-		uint32_t bytesToSkip = 0;
-
-		/* For Ultimate SoundTracker modules, only the loop area of a looped sample is played.
-		** Skip loading of eventual data present before loop start.
-		*/
-		if (modFormat == FORMAT_STK && s->loopStart > 0 && s->loopLength < s->length)
-		{
-			s->length -= s->loopStart;
-			mseek(m, s->loopStart, SEEK_CUR);
-			s->loopStart = 0;
-		}
-
-		/* We don't support loading samples bigger than 65534 bytes in our PT2 clone,
-		** so clamp length and skip overflown data.
-		*/
-		if (s->length > MAX_SAMPLE_LEN)
-		{
-			s->length = MAX_SAMPLE_LEN;
-			bytesToSkip = s->length - MAX_SAMPLE_LEN;
-		}
-
-		// For Ultimate SoundTracker modules, don't load sample data after loop end
-		uint16_t loopEnd = s->loopStart + s->loopLength;
-		if (modFormat == FORMAT_STK && loopEnd > 2 && s->length > loopEnd)
-		{
-			bytesToSkip += s->length-loopEnd;
-			s->length = loopEnd;
-		}
-
-		mread(&newMod->sampleData[s->offset], 1, s->length, m);
-
-		if (bytesToSkip > 0)
-			mseek(m, bytesToSkip, SEEK_CUR);
-
-		// clear first two bytes of non-looping samples to prevent beep after sample has been played
-		if (s->length >= 2 && loopEnd <= 2)
-		{
-			newMod->sampleData[s->offset+0] = 0;
-			newMod->sampleData[s->offset+1] = 0;
-		}
-
-		// some modules are broken like this, adjust sample length if possible (this is ok if we have room)
-		if (s->length > 0 && s->loopLength > 2 && s->loopStart+s->loopLength > s->length)
-		{
-			loopOverflowVal = (s->loopStart + s->loopLength) - s->length;
-			if (s->length+loopOverflowVal <= MAX_SAMPLE_LEN)
-			{
-				s->length = (uint16_t)(s->length + loopOverflowVal); // this is safe, we're allocating 65534 bytes per sample slot
-			}
-			else
-			{
-				s->loopStart = 0;
-				s->loopLength = 2;
-			}
-		}
-	}
-
-	mclose(&m);
-	free(modBuffer);
-
-	for (i = 0; i < AMIGA_VOICES; i++)
-		newMod->channels[i].n_chanindex = (int8_t)i;
-
-	return newMod;
-
-modLoadError:
-	if (m != NULL)
-		mclose(&m);
-
-	if (modBuffer != NULL)
-		free(modBuffer);
-
-	if (newMod != NULL)
-	{
-		for (i = 0; i < MAX_PATTERNS; i++)
-		{
-			if (newMod->patterns[i] != NULL)
-				free(newMod->patterns[i]);
-		}
-
-		free(newMod);
-	}
-
-	return NULL;
-}
-
-bool saveModule(bool checkIfFileExist, bool giveNewFreeFilename)
-{
-	char fileName[128], tmpBuffer[64];
-	uint16_t i;
-	struct stat statBuffer;
-
-	memset(fileName, 0, sizeof (fileName));
-
-	if (config.modDot)
-	{
-		// extension.filename
-		if (*modEntry->head.moduleTitle == '\0')
-		{
-			strcat(fileName, "mod.untitled");
-		}
-		else
-		{
-			strcat(fileName, "mod.");
-			for (i = 4; i < 20+4; i++)
-			{
-				fileName[i] = (char)tolower(modEntry->head.moduleTitle[i-4]);
-				if (fileName[i] == '\0') break;
-				sanitizeFilenameChar(&fileName[i]);
-			}
-		}
-	}
-	else
-	{
-		// filename.extension
-		if (*modEntry->head.moduleTitle == '\0')
-		{
-			strcat(fileName, "untitled.mod");
-		}
-		else
-		{
-			for (i = 0; i < 20; i++)
-			{
-				fileName[i] = (char)tolower(modEntry->head.moduleTitle[i]);
-				if (fileName[i] == '\0') break;
-				sanitizeFilenameChar(&fileName[i]);
-			}
-			strcat(fileName, ".mod");
-		}
-	}
-
-	if (giveNewFreeFilename && stat(fileName, &statBuffer) == 0)
-	{
-		for (uint16_t j = 1; j <= 9999; j++)
-		{
-			memset(fileName, 0, sizeof (fileName));
-			if (config.modDot)
-			{
-				// extension.filename
-				if (*modEntry->head.moduleTitle == '\0')
-				{
-					sprintf(fileName, "mod.untitled-%d", j);
-				}
-				else
-				{
-					for (i = 0; i < 20; i++)
-					{
-						tmpBuffer[i] = (char)tolower(modEntry->head.moduleTitle[i]);
-						if (tmpBuffer[i] == '\0') break;
-						sanitizeFilenameChar(&tmpBuffer[i]);
-					}
-					sprintf(fileName, "mod.%s-%d", tmpBuffer, j);
-				}
-			}
-			else
-			{
-				// filename.extension
-				if (*modEntry->head.moduleTitle == '\0')
-				{
-					sprintf(fileName, "untitled-%d.mod", j);
-				}
-				else
-				{
-					for (i = 0; i < 20; i++)
-					{
-						tmpBuffer[i] = (char)tolower(modEntry->head.moduleTitle[i]);
-						if (tmpBuffer[i] == '\0') break;
-						sanitizeFilenameChar(&tmpBuffer[i]);
-					}
-					sprintf(fileName, "%s-%d.mod", tmpBuffer, j);
-				}
-			}
-
-			if (stat(fileName, &statBuffer) != 0)
-				break;
-		}
-	}
-
-	if (checkIfFileExist)
-	{
-		if (stat(fileName, &statBuffer) == 0)
-		{
-			ui.askScreenShown = true;
-			ui.askScreenType = ASK_SAVEMOD_OVERWRITE;
-			pointerSetMode(POINTER_MODE_MSG1, NO_CARRY);
-			setStatusMessage("OVERWRITE FILE ?", NO_CARRY);
-			renderAskDialog();
-			return -1;
-		}
-	}
-
-	if (ui.askScreenShown)
-	{
-		ui.answerNo = false;
-		ui.answerYes = false;
-		ui.askScreenShown = false;
-	}
-
-	return modSave(fileName);
-}
-
-static MEMFILE *mopen(const uint8_t *src, uint32_t length)
-{
-	MEMFILE *b;
-
-	if (src == NULL || length == 0)
-		return NULL;
-
-	b = (MEMFILE *)malloc(sizeof (MEMFILE));
-	if (b == NULL)
-		return NULL;
-
-	b->_base = (uint8_t *)src;
-	b->_ptr = (uint8_t *)src;
-	b->_cnt = length;
-	b->_bufsiz = length;
-	b->_eof = false;
-
-	return b;
-}
-
-static void mclose(MEMFILE **buf)
-{
-	if (*buf != NULL)
-	{
-		free(*buf);
-		*buf = NULL;
-	}
-}
-
-static int32_t mgetc(MEMFILE *buf)
-{
-	int32_t b;
-
-	if (buf == NULL || buf->_ptr == NULL || buf->_cnt <= 0)
-		return 0;
-
-	b = *buf->_ptr;
-
-	buf->_cnt--;
-	buf->_ptr++;
-
-	if (buf->_cnt <= 0)
-	{
-		buf->_ptr = buf->_base + buf->_bufsiz;
-		buf->_cnt = 0;
-		buf->_eof = true;
-	}
-
-	return (int32_t)b;
-}
-
-static size_t mread(void *buffer, size_t size, size_t count, MEMFILE *buf)
-{
-	int32_t pcnt;
-	size_t wrcnt;
-
-	if (buf == NULL || buf->_ptr == NULL)
-		return 0;
-
-	wrcnt = size * count;
-	if (size == 0 || buf->_eof)
-		return 0;
-
-	pcnt = (buf->_cnt > (uint32_t)wrcnt) ? (uint32_t)wrcnt : buf->_cnt;
-	memcpy(buffer, buf->_ptr, pcnt);
-
-	buf->_cnt -= pcnt;
-	buf->_ptr += pcnt;
-
-	if (buf->_cnt <= 0)
-	{
-		buf->_ptr = buf->_base + buf->_bufsiz;
-		buf->_cnt = 0;
-		buf->_eof = true;
-	}
-
-	return pcnt / size;
-}
-
-static void mseek(MEMFILE *buf, int32_t offset, int32_t whence)
-{
-	if (buf == NULL)
-		return;
-
-	if (buf->_base)
-	{
-		switch (whence)
-		{
-			case SEEK_SET: buf->_ptr = buf->_base + offset; break;
-			case SEEK_CUR: buf->_ptr += offset; break;
-			case SEEK_END: buf->_ptr = buf->_base + buf->_bufsiz + offset; break;
-			default: break;
-		}
-
-		buf->_eof = false;
-		if (buf->_ptr >= buf->_base+buf->_bufsiz)
-		{
-			buf->_ptr = buf->_base + buf->_bufsiz;
-			buf->_eof = true;
-		}
-
-		buf->_cnt = (uint32_t)((buf->_base + buf->_bufsiz) - buf->_ptr);
-	}
-}
-
-static void mrewind(MEMFILE *buf)
-{
-	mseek(buf, 0, SEEK_SET);
-}
-
-/* PowerPacker unpack code taken from Heikki Orsila's amigadepack. Seems to have no license,
-** so I'll assume it fits into BSD 3-Clause. If not, feel free to contact me at my email
-** address found at the bottom of 16-bits.org.
-**
-** Modified by 8bitbubsy (me).
-*/
-
-#define PP_READ_BITS(nbits, var) \
-	bitCnt = (nbits); \
-	while (bitsLeft < bitCnt) \
-	{ \
-		if (bufSrc < src) \
-			return false; \
-		bitBuffer |= ((*--bufSrc) << bitsLeft); \
-		bitsLeft += 8; \
-	} \
-	(var) = 0; \
-	bitsLeft -= bitCnt; \
-	while (bitCnt--) \
-	{ \
-		(var) = ((var) << 1) | (bitBuffer & 1); \
-		bitBuffer >>= 1; \
-	} \
-
-static uint8_t ppdecrunch(uint8_t *src, uint8_t *dst, uint8_t *offsetLens, uint32_t srcLen, uint32_t dstLen, uint8_t skipBits)
-{
-	uint8_t *bufSrc, *dstEnd, *out, bitsLeft, bitCnt;
-	uint32_t x, todo, offBits, offset, written, bitBuffer;
-
-	if (src == NULL || dst == NULL || offsetLens == NULL)
-		return false;
-
-	bitsLeft = 0;
-	bitBuffer = 0;
-	written = 0;
-	bufSrc = src + srcLen;
-	out = dst + dstLen;
-	dstEnd = out;
-
-	PP_READ_BITS(skipBits, x);
-	while (written < dstLen)
-	{
-		PP_READ_BITS(1, x);
-		if (x == 0)
-		{
-			todo = 1;
-
-			do
-			{
-				PP_READ_BITS(2, x);
-				todo += x;
-			}
-			while (x == 3);
-
-			while (todo--)
-			{
-				PP_READ_BITS(8, x);
-				if (out <= dst)
-					return false;
-
-				*--out = (uint8_t)x;
-				written++;
-			}
-
-			if (written == dstLen)
-				break;
-		}
-
-		PP_READ_BITS(2, x);
-		offBits = offsetLens[x];
-		todo = x + 2;
-
-		if (x == 3)
-		{
-			PP_READ_BITS(1, x);
-			if (x == 0) offBits = 7;
-
-			PP_READ_BITS((uint8_t)offBits, offset);
-			do
-			{
-				PP_READ_BITS(3, x);
-				todo += x;
-			}
-			while (x == 7);
-		}
-		else
-		{
-			PP_READ_BITS((uint8_t)offBits, offset);
-		}
-
-		if (out+offset >= dstEnd)
-			return false;
-
-		while (todo--)
-		{
-			x = out[offset];
-			if (out <= dst)
-				return false;
-
-			*--out = (uint8_t)x;
-			written++;
-		}
-	}
-
-	return true;
-}
-
-void setupNewMod(void)
-{
-	int8_t i;
-
-	// setup GUI text pointers
-	for (i = 0; i < MOD_SAMPLES; i++)
-	{
-		modEntry->samples[i].volumeDisp = &modEntry->samples[i].volume;
-		modEntry->samples[i].lengthDisp = &modEntry->samples[i].length;
-		modEntry->samples[i].loopStartDisp = &modEntry->samples[i].loopStart;
-		modEntry->samples[i].loopLengthDisp = &modEntry->samples[i].loopLength;
-
-		fillSampleRedoBuffer(i);
-	}
-
-	modSetPos(0, 0);
-	modSetPattern(0); // set pattern to 00 instead of first order's pattern
-
-	editor.currEditPatternDisp = &modEntry->currPattern;
-	editor.currPosDisp = &modEntry->currOrder;
-	editor.currPatternDisp = &modEntry->head.order[0];
-	editor.currPosEdPattDisp = &modEntry->head.order[0];
-	editor.currLengthDisp = &modEntry->head.orderCount;
-
-	// calculate MOD size
-	ui.updateSongSize = true;
-
-	editor.muted[0] = false;
-	editor.muted[1] = false;
-	editor.muted[2] = false;
-	editor.muted[3] = false;
-
-	editor.editMoveAdd = 1;
-	editor.currSample = 0;
-	editor.musicTime = 0;
-	editor.modLoaded = true;
-	editor.blockMarkFlag = false;
-	editor.sampleZero = false;
-	editor.keypadSampleOffset = 0;
-
-	setLEDFilter(false); // real PT doesn't do this, but that's insane
-
-	updateWindowTitle(MOD_NOT_MODIFIED);
-
-	editor.timingMode = TEMPO_MODE_CIA;
-
-	modSetSpeed(6);
-	modSetTempo(modEntry->head.initialTempo); // 125 for normal MODs, custom value for certain STK/UST MODs
-
-	updateCurrSample();
-	editor.samplePos = 0;
-	updateSamplePos();
-}
-
-void loadModFromArg(char *arg)
-{
-	uint32_t filenameLen;
-	UNICHAR *filenameU;
-
-	ui.introScreenShown = false;
-	statusAllRight();
-
-	filenameLen = (uint32_t)strlen(arg);
-
-	filenameU = (UNICHAR *)calloc(filenameLen + 2, sizeof (UNICHAR));
-	if (filenameU == NULL)
-	{
-		statusOutOfMemory();
-		return;
-	}
-
-#ifdef _WIN32
-	MultiByteToWideChar(CP_UTF8, 0, arg, -1, filenameU, filenameLen);
-#else
-	strcpy(filenameU, arg);
-#endif
-
-	tempMod = modLoad(filenameU);
-	if (tempMod != NULL)
-	{
-		modEntry->moduleLoaded = false;
-		modFree();
-		modEntry = tempMod;
-		setupNewMod();
-		modEntry->moduleLoaded = true;
-	}
-	else
-	{
-		editor.errorMsgActive = true;
-		editor.errorMsgBlock = true;
-		editor.errorMsgCounter = 0;
-
-		// status/error message is set in the mod loader
-		setErrPointer();
-	}
-
-	free(filenameU);
-}
-
-static bool testExtension(char *ext, uint8_t extLen, char *fullPath)
-{
-	// checks for EXT.filename and filename.EXT
-	char *fileName, begStr[8], endStr[8];
-	uint32_t fileNameLen;
-
-	extLen++; // add one to length (dot)
-
-	fileName = strrchr(fullPath, DIR_DELIMITER);
-	if (fileName != NULL)
-		fileName++;
-	else
-		fileName = fullPath;
-
-	fileNameLen = (uint32_t)strlen(fileName);
-	if (fileNameLen >= extLen)
-	{
-		sprintf(begStr, "%s.", ext);
-		if (!_strnicmp(begStr, fileName, extLen))
-			return true;
-
-		sprintf(endStr, ".%s", ext);
-		if (!_strnicmp(endStr, fileName + (fileNameLen - extLen), extLen))
-			return true;
-	}
-
-	return false;
-}
-
-void loadDroppedFile(char *fullPath, uint32_t fullPathLen, bool autoPlay, bool songModifiedCheck)
-{
-	bool isMod;
-	char *fileName, *ansiName;
-	uint8_t oldMode, oldPlayMode;
-	UNICHAR *fullPathU;
-
-	// don't allow drag n' drop if the tracker is busy
-	if (ui.pointerMode == POINTER_MODE_MSG1 || diskop.isFilling ||
-		editor.isWAVRendering || ui.samplerFiltersBoxShown || ui.samplerVolBoxShown)
-	{
-		return;
-	}
-
-	ansiName = (char *)calloc(fullPathLen + 10, sizeof (char));
-	if (ansiName == NULL)
-	{
-		statusOutOfMemory();
-		return;
-	}
-
-	fullPathU = (UNICHAR *)calloc(fullPathLen + 2, sizeof (UNICHAR));
-	if (fullPathU == NULL)
-	{
-		statusOutOfMemory();
-		return;
-	}
-
-#ifdef _WIN32
-	MultiByteToWideChar(CP_UTF8, 0, fullPath, -1, fullPathU, fullPathLen);
-#else
-	strcpy(fullPathU, fullPath);
-#endif
-
-	unicharToAnsi(ansiName, fullPathU, fullPathLen);
-
-	// make a new pointer point to filename (strip path)
-	fileName = strrchr(ansiName, DIR_DELIMITER);
-	if (fileName != NULL)
-		fileName++;
-	else
-		fileName = ansiName;
-
-	// check if the file extension is a module (FIXME: check module by content instead..?)
-	isMod = false;
-	     if (testExtension("MOD", 3, fileName)) isMod = true;
-	else if (testExtension("M15", 3, fileName)) isMod = true;
-	else if (testExtension("STK", 3, fileName)) isMod = true;
-	else if (testExtension("NST", 3, fileName)) isMod = true;
-	else if (testExtension("UST", 3, fileName)) isMod = true;
-	else if (testExtension("PP",  2, fileName)) isMod = true;
-	else if (testExtension("NT",  2, fileName)) isMod = true;
-
-	if (isMod)
-	{
-		if (songModifiedCheck && modEntry->modified)
-		{
-			free(ansiName);
-			free(fullPathU);
-
-			memcpy(oldFullPath, fullPath, fullPathLen);
-			oldFullPath[fullPathLen+0] = 0;
-			oldFullPath[fullPathLen+1] = 0;
-
-			oldFullPathLen = fullPathLen;
-			oldAutoPlay = autoPlay;
-
-			// de-minimize window and set focus so that the user sees the message box
-			SDL_RestoreWindow(video.window);
-			SDL_RaiseWindow(video.window);
-
-			showSongUnsavedAskBox(ASK_DISCARD_SONG_DRAGNDROP);
-			return;
-		}
-
-		tempMod = modLoad(fullPathU);
-		if (tempMod != NULL)
-		{
-			oldMode = editor.currMode;
-			oldPlayMode = editor.playMode;
-
-			modStop();
-			modFree();
-
-			modEntry = tempMod;
-			setupNewMod();
-			modEntry->moduleLoaded = true;
-
-			statusAllRight();
-
-			if (autoPlay)
-			{
-				editor.playMode = PLAY_MODE_NORMAL;
-				editor.currMode = MODE_PLAY;
-
-				// start normal playback
-				modPlay(DONT_SET_PATTERN, 0, 0);
-
-				pointerSetMode(POINTER_MODE_PLAY, DO_CARRY);
-			}
-			else if (oldMode == MODE_PLAY || oldMode == MODE_RECORD)
-			{
-				// use last mode
-				editor.playMode = oldPlayMode;
-				if (oldPlayMode == PLAY_MODE_PATTERN || oldMode == MODE_RECORD)
-					modPlay(0, 0, 0);
-				else
-					modPlay(DONT_SET_PATTERN, 0, 0);
-				editor.currMode = oldMode;
-
-				if (oldMode == MODE_RECORD)
-					pointerSetMode(POINTER_MODE_RECORD, DO_CARRY);
-				else
-					pointerSetMode(POINTER_MODE_PLAY, DO_CARRY);
-			}
-			else
-			{
-				// stop playback
-				editor.playMode = PLAY_MODE_NORMAL;
-				editor.currMode = MODE_IDLE;
-				editor.songPlaying = false;
-				pointerSetMode(POINTER_MODE_IDLE, DO_CARRY);
-			}
-
-			displayMainScreen();
-		}
-		else
-		{
-			editor.errorMsgActive = true;
-			editor.errorMsgBlock = true;
-			editor.errorMsgCounter = 0;
-			setErrPointer(); // status/error message is set in the mod loader
-		}
-	}
-	else
-	{
-		loadSample(fullPathU, fileName);
-	}
-
-	free(ansiName);
-	free(fullPathU);
-}
-
-void loadDroppedFile2(void)
-{
-	loadDroppedFile(oldFullPath, oldFullPathLen, oldAutoPlay, false);
-}
-
-module_t *createNewMod(void)
-{
-	uint8_t i;
-	module_t *newMod;
-
-	newMod = (module_t *)calloc(1, sizeof (module_t));
-	if (newMod == NULL)
-		goto oom;
-
-	for (i = 0; i < MAX_PATTERNS; i++)
-	{
-		newMod->patterns[i] = (note_t *)calloc(1, MOD_ROWS * sizeof (note_t) * AMIGA_VOICES);
-		if (newMod->patterns[i] == NULL)
-			goto oom;
-	}
-
-	// +2 sample slots for overflow safety (Paula and scopes)
-	newMod->sampleData = (int8_t *)calloc(MOD_SAMPLES + 2, MAX_SAMPLE_LEN);
-	if (newMod->sampleData == NULL)
-		goto oom;
-
-	newMod->head.orderCount = 1;
-
-	for (i = 0; i < MOD_SAMPLES; i++)
-	{
-		newMod->samples[i].offset = MAX_SAMPLE_LEN * i;
-		newMod->samples[i].loopLength = 2;
-
-		// setup GUI text pointers
-		newMod->samples[i].volumeDisp = &newMod->samples[i].volume;
-		newMod->samples[i].lengthDisp = &newMod->samples[i].length;
-		newMod->samples[i].loopStartDisp = &newMod->samples[i].loopStart;
-		newMod->samples[i].loopLengthDisp = &newMod->samples[i].loopLength;
-	}
-
-	for (i = 0; i < AMIGA_VOICES; i++)
-		newMod->channels[i].n_chanindex = i;
-
-	// setup GUI text pointers
-	editor.currEditPatternDisp = &newMod->currPattern;
-	editor.currPosDisp = &newMod->currOrder;
-	editor.currPatternDisp = &newMod->head.order[0];
-	editor.currPosEdPattDisp = &newMod->head.order[0];
-	editor.currLengthDisp = &newMod->head.orderCount;
-
-	ui.updateSongSize = true;
-	return newMod;
-
-oom:
-	showErrorMsgBox("Out of memory!");
-	return NULL;
-}
--- a/src/pt2_modloader.h
+++ /dev/null
@@ -1,17 +1,0 @@
-#pragma once
-
-#include <stdint.h>
-#include <stdbool.h>
-#include "pt2_header.h"
-#include "pt2_unicode.h"
-#include "pt2_structs.h"
-
-void showSongUnsavedAskBox(int8_t askScreenType);
-void loadModFromArg(char *arg);
-void loadDroppedFile(char *fullPath, uint32_t fullPathLen, bool autoPlay, bool songModifiedCheck);
-void loadDroppedFile2(void);
-module_t *createNewMod(void);
-bool saveModule(bool checkIfFileExist, bool giveNewFreeFilename);
-bool modSave(char *fileName);
-module_t *modLoad(UNICHAR *fileName);
-void setupNewMod(void);
--- a/src/pt2_modplayer.c
+++ /dev/null
@@ -1,1549 +1,0 @@
-/* Very accurate C port of ProTracker 2.3D's replayer by 8bitbubsy, slightly modified.
-** Earlier versions of the PT clone used a completely different and less accurate replayer.
-*/
-
-// for finding memory leaks in debug mode with Visual Studio 
-#if defined _DEBUG && defined _MSC_VER
-#include <crtdbg.h>
-#endif
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdint.h>
-#include <stdbool.h>
-#include <math.h>
-#include "pt2_header.h"
-#include "pt2_audio.h"
-#include "pt2_helpers.h"
-#include "pt2_palette.h"
-#include "pt2_tables.h"
-#include "pt2_modloader.h"
-#include "pt2_config.h"
-#include "pt2_sampler.h"
-#include "pt2_visuals.h"
-#include "pt2_textout.h"
-#include "pt2_scopes.h"
-
-static bool posJumpAssert, pBreakFlag, updateUIPositions, modHasBeenPlayed;
-static int8_t pBreakPosition, oldRow, modPattern;
-static uint8_t pattDelTime, setBPMFlag, lowMask = 0xFF, pattDelTime2, oldSpeed;
-static int16_t modOrder, oldPattern, oldOrder;
-static uint16_t modBPM, oldBPM;
-
-static const int8_t vuMeterHeights[65] =
-{
-	 0,  0,  1,  2,  2,  3,  4,  5,
-	 5,  6,  7,  8,  8,  9, 10, 11,
-	11, 12, 13, 14, 14, 15, 16, 17,
-	17, 18, 19, 20, 20, 21, 22, 23,
-	23, 24, 25, 26, 26, 27, 28, 29,
-	29, 30, 31, 32, 32, 33, 34, 35,
-	35, 36, 37, 38, 38, 39, 40, 41,
-	41, 42, 43, 44, 44, 45, 46, 47,
-	47
-};
-
-static const uint8_t funkTable[16] = // EFx (FunkRepeat/InvertLoop)
-{
-	0x00, 0x05, 0x06, 0x07, 0x08, 0x0A, 0x0B, 0x0D,
-	0x10, 0x13, 0x16, 0x1A, 0x20, 0x2B, 0x40, 0x80
-};
-
-void modSetSpeed(uint8_t speed)
-{
-	editor.modSpeed = speed;
-	modEntry->currSpeed = speed;
-	editor.modTick = 0;
-}
-
-void doStopIt(bool resetPlayMode)
-{
-	moduleChannel_t *c;
-	uint8_t i;
-
-	editor.songPlaying = false;
-
-	resetCachedMixerPeriod();
-
-	pattDelTime = 0;
-	pattDelTime2 = 0;
-
-	if (resetPlayMode)
-	{
-		editor.playMode = PLAY_MODE_NORMAL;
-		editor.currMode = MODE_IDLE;
-
-		pointerSetMode(POINTER_MODE_IDLE, DO_CARRY);
-	}
-
-	for (i = 0; i < AMIGA_VOICES; i++)
-	{
-		c = &modEntry->channels[i];
-		c->n_wavecontrol = 0;
-		c->n_glissfunk = 0;
-		c->n_finetune = 0;
-		c->n_loopcount = 0;
-	}
-}
-
-void setPattern(int16_t pattern)
-{
-	if (pattern > MAX_PATTERNS-1)
-		pattern = MAX_PATTERNS-1;
-
-	modEntry->currPattern = modPattern = (int8_t)pattern;
-}
-
-void storeTempVariables(void) // this one is accessed in other files, so non-static
-{
-	oldBPM = modEntry->currBPM;
-	oldRow = modEntry->currRow;
-	oldOrder = modEntry->currOrder;
-	oldSpeed = modEntry->currSpeed;
-	oldPattern = modEntry->currPattern;
-}
-
-static void setVUMeterHeight(moduleChannel_t *ch)
-{
-	uint8_t vol;
-
-	if (editor.muted[ch->n_chanindex])
-		return;
-
-	vol = ch->n_volume;
-	if ((ch->n_cmd & 0xF00) == 0xC00) // handle Cxx effect
-		vol = ch->n_cmd & 0xFF;
-
-	if (vol > 64)
-		vol = 64;
-
-	editor.vuMeterVolumes[ch->n_chanindex] = vuMeterHeights[vol];
-}
-
-static void updateFunk(moduleChannel_t *ch)
-{
-	int8_t funkspeed;
-
-	funkspeed = ch->n_glissfunk >> 4;
-	if (funkspeed == 0)
-		return;
-
-	ch->n_funkoffset += funkTable[funkspeed];
-	if (ch->n_funkoffset >= 128)
-	{
-		ch->n_funkoffset = 0;
-
-		if (ch->n_loopstart != NULL && ch->n_wavestart != NULL) // SAFETY BUG FIX
-		{
-			if (++ch->n_wavestart >= ch->n_loopstart+ch->n_replen)
-				ch->n_wavestart = ch->n_loopstart;
-
-			*ch->n_wavestart = -1 - *ch->n_wavestart;
-		}
-	}
-}
-
-static void setGlissControl(moduleChannel_t *ch)
-{
-	ch->n_glissfunk = (ch->n_glissfunk & 0xF0) | (ch->n_cmd & 0x0F);
-}
-
-static void setVibratoControl(moduleChannel_t *ch)
-{
-	ch->n_wavecontrol = (ch->n_wavecontrol & 0xF0) | (ch->n_cmd & 0x0F);
-}
-
-static void setFineTune(moduleChannel_t *ch)
-{
-	ch->n_finetune = ch->n_cmd & 0xF;
-}
-
-static void jumpLoop(moduleChannel_t *ch)
-{
-	uint8_t tempParam;
-
-	if (editor.modTick != 0)
-		return;
-
-	if ((ch->n_cmd & 0xF) == 0)
-	{
-		ch->n_pattpos = modEntry->row;
-	}
-	else
-	{
-		if (ch->n_loopcount == 0)
-		{
-			ch->n_loopcount = ch->n_cmd & 0xF;
-		}
-		else
-		{
-			if (--ch->n_loopcount == 0)
-				return;
-		}
-
-		pBreakPosition = ch->n_pattpos;
-		pBreakFlag = 1;
-
-		if (editor.isWAVRendering)
-		{
-			for (tempParam = pBreakPosition; tempParam <= modEntry->row; tempParam++)
-				editor.rowVisitTable[(modOrder * MOD_ROWS) + tempParam] = false;
-		}
-	}
-}
-
-static void setTremoloControl(moduleChannel_t *ch)
-{
-	ch->n_wavecontrol = ((ch->n_cmd & 0xF) << 4) | (ch->n_wavecontrol & 0xF);
-}
-
-static void karplusStrong(moduleChannel_t *ch)
-{
-	(void)ch; // this effect is *horrible* and never used, I'm not implementing it.
-}
-
-static void doRetrg(moduleChannel_t *ch)
-{
-	paulaSetData(ch->n_chanindex, ch->n_start); // n_start is increased on 9xx
-	paulaSetLength(ch->n_chanindex, ch->n_length);
-	paulaSetPeriod(ch->n_chanindex, ch->n_period);
-	paulaStartDMA(ch->n_chanindex);
-
-	// these take effect after the current DMA cycle is done
-	paulaSetData(ch->n_chanindex, ch->n_loopstart);
-	paulaSetLength(ch->n_chanindex, ch->n_replen);
-
-	updateSpectrumAnalyzer(ch->n_volume, ch->n_period);
-	setVUMeterHeight(ch);
-}
-
-static void retrigNote(moduleChannel_t *ch)
-{
-	if ((ch->n_cmd & 0xF) > 0)
-	{
-		if (editor.modTick == 0 && (ch->n_note & 0xFFF) > 0)
-				return;
-
-		if (editor.modTick % (ch->n_cmd & 0xF) == 0)
-			doRetrg(ch);
-	}
-}
-
-static void volumeSlide(moduleChannel_t *ch)
-{
-	uint8_t cmd = ch->n_cmd & 0xFF;
-
-	if ((cmd & 0xF0) == 0)
-	{
-		ch->n_volume -= cmd & 0x0F;
-		if (ch->n_volume < 0)
-			ch->n_volume = 0;
-	}
-	else
-	{
-		ch->n_volume += cmd >> 4;
-		if (ch->n_volume > 64)
-			ch->n_volume = 64;
-	}
-}
-
-static void volumeFineUp(moduleChannel_t *ch)
-{
-	if (editor.modTick == 0)
-	{
-		ch->n_volume += ch->n_cmd & 0xF;
-		if (ch->n_volume > 64)
-			ch->n_volume = 64;
-	}
-}
-
-static void volumeFineDown(moduleChannel_t *ch)
-{
-	if (editor.modTick == 0)
-	{
-		ch->n_volume -= ch->n_cmd & 0xF;
-		if (ch->n_volume < 0)
-			ch->n_volume = 0;
-	}
-}
-
-static void noteCut(moduleChannel_t *ch)
-{
-	if (editor.modTick == (ch->n_cmd & 0xF))
-		ch->n_volume = 0;
-}
-
-static void noteDelay(moduleChannel_t *ch)
-{
-	if (editor.modTick == (ch->n_cmd & 0xF) && (ch->n_note & 0xFFF) > 0)
-		doRetrg(ch);
-}
-
-static void patternDelay(moduleChannel_t *ch)
-{
-	if (editor.modTick == 0 && pattDelTime2 == 0)
-		pattDelTime = (ch->n_cmd & 0xF) + 1;
-}
-
-static void funkIt(moduleChannel_t *ch)
-{
-	if (editor.modTick == 0)
-	{
-		ch->n_glissfunk = ((ch->n_cmd & 0xF) << 4) | (ch->n_glissfunk & 0xF);
-		if ((ch->n_glissfunk & 0xF0) > 0)
-			updateFunk(ch);
-	}
-}
-
-static void positionJump(moduleChannel_t *ch)
-{
-	modOrder = (ch->n_cmd & 0xFF) - 1; // 0xFF (B00) jumps to pat 0
-	pBreakPosition = 0;
-	posJumpAssert = true;
-}
-
-static void volumeChange(moduleChannel_t *ch)
-{
-	ch->n_volume = ch->n_cmd & 0xFF;
-	if ((uint8_t)ch->n_volume > 64)
-		ch->n_volume = 64;
-}
-
-static void patternBreak(moduleChannel_t *ch)
-{
-	pBreakPosition = (((ch->n_cmd & 0xF0) >> 4) * 10) + (ch->n_cmd & 0x0F);
-	if ((uint8_t)pBreakPosition > 63)
-		pBreakPosition = 0;
-
-	posJumpAssert = true;
-}
-
-static void setSpeed(moduleChannel_t *ch)
-{
-	if ((ch->n_cmd & 0xFF) > 0)
-	{
-		editor.modTick = 0;
-
-		if (editor.timingMode == TEMPO_MODE_VBLANK || (ch->n_cmd & 0xFF) < 32)
-			modSetSpeed(ch->n_cmd & 0xFF);
-		else
-			setBPMFlag = ch->n_cmd & 0xFF; // CIA doesn't refresh its registers until the next interrupt, so change it later
-	}
-	else
-	{
-		editor.songPlaying = false;
-		editor.playMode = PLAY_MODE_NORMAL;
-		editor.currMode = MODE_IDLE;
-
-		pointerSetMode(POINTER_MODE_IDLE, DO_CARRY);
-	}
-}
-
-static void arpeggio(moduleChannel_t *ch)
-{
-	uint8_t arpTick, arpNote;
-	const int16_t *periods;
-
-	assert(editor.modTick < 32);
-	arpTick = arpTickTable[editor.modTick]; // 0, 1, 2
-
-	if (arpTick == 1)
-	{
-		arpNote = (uint8_t)(ch->n_cmd >> 4);
-	}
-	else if (arpTick == 2)
-	{
-		arpNote = ch->n_cmd & 0xF;
-	}
-	else // arpTick 0
-	{
-		paulaSetPeriod(ch->n_chanindex, ch->n_period);
-		return;
-	}
-
-	/* 8bitbubsy: If the finetune is -1, this can overflow up to
-	** 15 words outside of the table. The table is padded with
-	** the correct overflow values to allow this to safely happen
-	** and sound correct at the same time.
-	*/
-	periods = &periodTable[ch->n_finetune * 37];
-	for (int32_t baseNote = 0; baseNote < 37; baseNote++)
-	{
-		if (ch->n_period >= periods[baseNote])
-		{
-			paulaSetPeriod(ch->n_chanindex, periods[baseNote+arpNote]);
-			break;
-		}
-	}
-}
-
-static void portaUp(moduleChannel_t *ch)
-{
-	ch->n_period -= (ch->n_cmd & 0xFF) & lowMask;
-	lowMask = 0xFF;
-
-	if ((ch->n_period & 0xFFF) < 113)
-		ch->n_period = (ch->n_period & 0xF000) | 113;
-
-	paulaSetPeriod(ch->n_chanindex, ch->n_period & 0xFFF);
-}
-
-static void portaDown(moduleChannel_t *ch)
-{
-	ch->n_period += (ch->n_cmd & 0xFF) & lowMask;
-	lowMask = 0xFF;
-
-	if ((ch->n_period & 0xFFF) > 856)
-		ch->n_period = (ch->n_period & 0xF000) | 856;
-
-	paulaSetPeriod(ch->n_chanindex, ch->n_period & 0xFFF);
-}
-
-static void filterOnOff(moduleChannel_t *ch)
-{
-	setLEDFilter(!(ch->n_cmd & 1));
-}
-
-static void finePortaUp(moduleChannel_t *ch)
-{
-	if (editor.modTick == 0)
-	{
-		lowMask = 0xF;
-		portaUp(ch);
-	}
-}
-
-static void finePortaDown(moduleChannel_t *ch)
-{
-	if (editor.modTick == 0)
-	{
-		lowMask = 0xF;
-		portaDown(ch);
-	}
-}
-
-static void setTonePorta(moduleChannel_t *ch)
-{
-	uint8_t i;
-	const int16_t *portaPointer;
-	uint16_t note;
-
-	note = ch->n_note & 0xFFF;
-	portaPointer = &periodTable[ch->n_finetune * 37];
-
-	i = 0;
-	while (true)
-	{
-		// portaPointer[36] = 0, so i=36 is safe
-		if (note >= portaPointer[i])
-			break;
-
-		if (++i >= 37)
-		{
-			i = 35;
-			break;
-		}
-	}
-
-	if ((ch->n_finetune & 8) && i > 0)
-		i--;
-
-	ch->n_wantedperiod = portaPointer[i];
-	ch->n_toneportdirec = 0;
-
-	     if (ch->n_period == ch->n_wantedperiod) ch->n_wantedperiod = 0;
-	else if (ch->n_period > ch->n_wantedperiod) ch->n_toneportdirec = 1;
-}
-
-static void tonePortNoChange(moduleChannel_t *ch)
-{
-	uint8_t i;
-	const int16_t *portaPointer;
-
-	if (ch->n_wantedperiod <= 0)
-		return;
-
-	if (ch->n_toneportdirec > 0)
-	{
-		ch->n_period -= ch->n_toneportspeed;
-		if (ch->n_period <= ch->n_wantedperiod)
-		{
-			ch->n_period = ch->n_wantedperiod;
-			ch->n_wantedperiod = 0;
-		}
-	}
-	else
-	{
-		ch->n_period += ch->n_toneportspeed;
-		if (ch->n_period >= ch->n_wantedperiod)
-		{
-			ch->n_period = ch->n_wantedperiod;
-			ch->n_wantedperiod = 0;
-		}
-	}
-
-	if ((ch->n_glissfunk & 0xF) == 0)
-	{
-		paulaSetPeriod(ch->n_chanindex, ch->n_period);
-	}
-	else
-	{
-		portaPointer = &periodTable[ch->n_finetune * 37];
-
-		i = 0;
-		while (true)
-		{
-			// portaPointer[36] = 0, so i=36 is safe
-			if (ch->n_period >= portaPointer[i])
-				break;
-
-			if (++i >= 37)
-			{
-				i = 35;
-				break;
-			}
-		}
-
-		paulaSetPeriod(ch->n_chanindex, portaPointer[i]);
-	}
-}
-
-static void tonePortamento(moduleChannel_t *ch)
-{
-	if ((ch->n_cmd & 0xFF) > 0)
-	{
-		ch->n_toneportspeed = ch->n_cmd & 0xFF;
-		ch->n_cmd &= 0xFF00;
-	}
-
-	tonePortNoChange(ch);
-}
-
-static void vibratoNoChange(moduleChannel_t *ch)
-{
-	uint8_t vibratoTemp;
-	int16_t vibratoData;
-
-	vibratoTemp = (ch->n_vibratopos / 4) & 31;
-	vibratoData = ch->n_wavecontrol & 3;
-
-	if (vibratoData == 0)
-	{
-		vibratoData = vibratoTable[vibratoTemp];
-	}
-	else
-	{
-		if (vibratoData == 1)
-		{
-			if (ch->n_vibratopos < 0)
-				vibratoData = 255 - (vibratoTemp * 8);
-			else
-				vibratoData = vibratoTemp * 8;
-		}
-		else
-		{
-			vibratoData = 255;
-		}
-	}
-
-	vibratoData = (vibratoData * (ch->n_vibratocmd & 0xF)) / 128;
-
-	if (ch->n_vibratopos < 0)
-		vibratoData = ch->n_period - vibratoData;
-	else
-		vibratoData = ch->n_period + vibratoData;
-
-	paulaSetPeriod(ch->n_chanindex, vibratoData);
-
-	ch->n_vibratopos += ((ch->n_vibratocmd >> 4) * 4);
-}
-
-static void vibrato(moduleChannel_t *ch)
-{
-	if ((ch->n_cmd & 0xFF) > 0)
-	{
-		if ((ch->n_cmd & 0x0F) > 0)
-			ch->n_vibratocmd = (ch->n_vibratocmd & 0xF0) | (ch->n_cmd & 0x0F);
-
-		if ((ch->n_cmd & 0xF0) > 0)
-			ch->n_vibratocmd = (ch->n_cmd & 0xF0) | (ch->n_vibratocmd & 0x0F);
-	}
-
-	vibratoNoChange(ch);
-}
-
-static void tonePlusVolSlide(moduleChannel_t *ch)
-{
-	tonePortNoChange(ch);
-	volumeSlide(ch);
-}
-
-static void vibratoPlusVolSlide(moduleChannel_t *ch)
-{
-	vibratoNoChange(ch);
-	volumeSlide(ch);
-}
-
-static void tremolo(moduleChannel_t *ch)
-{
-	int8_t tremoloTemp;
-	int16_t tremoloData;
-
-	if ((ch->n_cmd & 0xFF) > 0)
-	{
-		if ((ch->n_cmd & 0x0F) > 0)
-			ch->n_tremolocmd = (ch->n_tremolocmd & 0xF0) | (ch->n_cmd & 0x0F);
-
-		if ((ch->n_cmd & 0xF0) > 0)
-			ch->n_tremolocmd = (ch->n_cmd & 0xF0) | (ch->n_tremolocmd & 0x0F);
-	}
-
-	tremoloTemp = (ch->n_tremolopos / 4) & 31;
-	tremoloData = (ch->n_wavecontrol >> 4) & 3;
-
-	if (!tremoloData)
-	{
-		tremoloData = vibratoTable[tremoloTemp];
-	}
-	else
-	{
-		if (tremoloData == 1)
-		{
-			if (ch->n_vibratopos < 0) // PT bug, should've been n_tremolopos
-				tremoloData = 255 - (tremoloTemp * 8);
-			else
-				tremoloData = tremoloTemp * 8;
-		}
-		else
-		{
-			tremoloData = 255;
-		}
-	}
-
-	tremoloData = (tremoloData * (ch->n_tremolocmd & 0xF)) / 64;
-
-	if (ch->n_tremolopos < 0)
-	{
-		tremoloData = ch->n_volume - tremoloData;
-		if (tremoloData < 0)
-			tremoloData = 0;
-	}
-	else
-	{
-		tremoloData = ch->n_volume + tremoloData;
-		if (tremoloData > 64)
-			tremoloData = 64;
-	}
-
-	paulaSetVolume(ch->n_chanindex, tremoloData);
-
-	ch->n_tremolopos += (ch->n_tremolocmd >> 4) * 4;
-}
-
-static void sampleOffset(moduleChannel_t *ch)
-{
-	uint16_t newOffset;
-
-	if ((ch->n_cmd & 0xFF) > 0)
-		ch->n_sampleoffset = ch->n_cmd & 0xFF;
-
-	newOffset = ch->n_sampleoffset << 7;
-
-	if ((int16_t)newOffset < (int16_t)ch->n_length)
-	{
-		ch->n_length -= newOffset;
-		ch->n_start += newOffset*2;
-	}
-	else
-	{
-		ch->n_length = 1;
-	}
-}
-
-static void E_Commands(moduleChannel_t *ch)
-{
-	uint8_t cmd;
-
-	cmd = (ch->n_cmd & 0xF0) >> 4;
-	switch (cmd)
-	{
-		case 0x0: filterOnOff(ch);       break;
-		case 0x1: finePortaUp(ch);       break;
-		case 0x2: finePortaDown(ch);     break;
-		case 0x3: setGlissControl(ch);   break;
-		case 0x4: setVibratoControl(ch); break;
-		case 0x5: setFineTune(ch);       break;
-		case 0x6: jumpLoop(ch);          break;
-		case 0x7: setTremoloControl(ch); break;
-		case 0x8: karplusStrong(ch);     break;
-		default: break;
-	}
-
-	if (editor.muted[ch->n_chanindex])
-		return;
-
-	switch (cmd)
-	{
-		case 0x9: retrigNote(ch);     break;
-		case 0xA: volumeFineUp(ch);   break;
-		case 0xB: volumeFineDown(ch); break;
-		case 0xC: noteCut(ch);        break;
-		case 0xD: noteDelay(ch);      break;
-		case 0xE: patternDelay(ch);   break;
-		case 0xF: funkIt(ch);         break;
-		default: break;
-	}
-}
-
-static void checkMoreEffects(moduleChannel_t *ch)
-{
-	switch ((ch->n_cmd & 0xF00) >> 8)
-	{
-		case 0x9: sampleOffset(ch); break;
-		case 0xB: positionJump(ch); break;
-
-		case 0xC:
-		{
-			if (!editor.muted[ch->n_chanindex])
-				volumeChange(ch);
-		}
-		break;
-
-		case 0xD: patternBreak(ch); break;
-		case 0xE: E_Commands(ch);   break;
-		case 0xF: setSpeed(ch);     break;
-
-		default:
-		{
-			if (!editor.muted[ch->n_chanindex])
-				paulaSetPeriod(ch->n_chanindex, ch->n_period);
-		}
-		break;
-	}
-}
-
-static void checkEffects(moduleChannel_t *ch)
-{
-	uint8_t effect;
-
-	if (editor.muted[ch->n_chanindex])
-		return;
-
-	updateFunk(ch);
-
-	effect = (ch->n_cmd & 0xF00) >> 8;
-
-	if ((ch->n_cmd & 0xFFF) > 0)
-	{
-		switch (effect)
-		{
-			case 0x0: arpeggio(ch);            break;
-			case 0x1: portaUp(ch);             break;
-			case 0x2: portaDown(ch);           break;
-			case 0x3: tonePortamento(ch);      break;
-			case 0x4: vibrato(ch);             break;
-			case 0x5: tonePlusVolSlide(ch);    break;
-			case 0x6: vibratoPlusVolSlide(ch); break;
-			case 0xE: E_Commands(ch);          break;
-
-			case 0x7:
-			{
-				paulaSetPeriod(ch->n_chanindex, ch->n_period);
-				tremolo(ch);
-			}
-			break;
-
-			case 0xA:
-			{
-				paulaSetPeriod(ch->n_chanindex, ch->n_period);
-				volumeSlide(ch);
-			}
-			break;
-
-			default: paulaSetPeriod(ch->n_chanindex, ch->n_period); break;
-		}
-	}
-
-	if (effect != 0x7)
-		paulaSetVolume(ch->n_chanindex, ch->n_volume);
-}
-
-static void setPeriod(moduleChannel_t *ch)
-{
-	uint8_t i;
-	uint16_t note;
-
-	note = ch->n_note & 0xFFF;
-	for (i = 0; i < 37; i++)
-	{
-		// periodTable[36] = 0, so i=36 is safe
-		if (note >= periodTable[i])
-			break;
-	}
-
-	// BUG: yes it's 'safe' if i=37 because of padding at the end of period table
-	ch->n_period = periodTable[(ch->n_finetune * 37) + i];
-
-	if ((ch->n_cmd & 0xFF0) != 0xED0) // no note delay
-	{
-		if ((ch->n_wavecontrol & 0x04) == 0) ch->n_vibratopos = 0;
-		if ((ch->n_wavecontrol & 0x40) == 0) ch->n_tremolopos = 0;
-
-		paulaSetLength(ch->n_chanindex, ch->n_length);
-		paulaSetData(ch->n_chanindex, ch->n_start);
-
-		if (ch->n_start == NULL)
-		{
-			ch->n_loopstart = NULL;
-			paulaSetLength(ch->n_chanindex, 1);
-			ch->n_replen = 1;
-		}
-
-		paulaSetPeriod(ch->n_chanindex, ch->n_period);
-
-		if (!editor.muted[ch->n_chanindex])
-		{
-			paulaStartDMA(ch->n_chanindex);
-			updateSpectrumAnalyzer(ch->n_volume, ch->n_period);
-			setVUMeterHeight(ch);
-		}
-		else
-		{
-			paulaStopDMA(ch->n_chanindex);
-		}
-	}
-
-	checkMoreEffects(ch);
-}
-
-static void checkMetronome(moduleChannel_t *ch, note_t *note)
-{
-	if (editor.metroFlag && editor.metroChannel > 0)
-	{
-		if (ch->n_chanindex == editor.metroChannel-1 && (modEntry->row % editor.metroSpeed) == 0)
-		{
-			note->sample = 0x1F;
-			note->period = (((modEntry->row / editor.metroSpeed) % editor.metroSpeed) == 0) ? 160 : 214;
-		}
-	}
-}
-
-static void playVoice(moduleChannel_t *ch)
-{
-	uint8_t cmd;
-	moduleSample_t *s;
-	note_t note;
-
-	if (ch->n_note == 0 && ch->n_cmd == 0)
-		paulaSetPeriod(ch->n_chanindex, ch->n_period);
-
-	note = modEntry->patterns[modPattern][(modEntry->row * AMIGA_VOICES) + ch->n_chanindex];
-	checkMetronome(ch, &note);
-
-	ch->n_note = note.period;
-	ch->n_cmd = (note.command << 8) | note.param;
-
-	if (note.sample >= 1 && note.sample <= 31) // SAFETY BUG FIX: don't handle sample-numbers >31
-	{
-		ch->n_samplenum = note.sample - 1;
-		s = &modEntry->samples[ch->n_samplenum];
-
-		ch->n_start = &modEntry->sampleData[s->offset];
-		ch->n_finetune = s->fineTune;
-		ch->n_volume = s->volume;
-		ch->n_length = s->length / 2;
-		ch->n_replen = s->loopLength / 2;
-
-		if (s->loopStart > 0)
-		{
-			ch->n_loopstart = ch->n_start + s->loopStart;
-			ch->n_wavestart = ch->n_loopstart;
-			ch->n_length = (s->loopStart / 2) + ch->n_replen;
-		}
-		else
-		{
-			ch->n_loopstart = ch->n_start;
-			ch->n_wavestart = ch->n_start;
-		}
-
-		// non-PT2 quirk
-		if (ch->n_length == 0)
-			ch->n_loopstart = ch->n_wavestart = &modEntry->sampleData[RESERVED_SAMPLE_OFFSET]; // dummy sample
-	}
-
-	if ((ch->n_note & 0xFFF) > 0)
-	{
-		if ((ch->n_cmd & 0xFF0) == 0xE50) // set finetune
-		{
-			setFineTune(ch);
-			setPeriod(ch);
-		}
-		else
-		{
-			cmd = (ch->n_cmd & 0xF00) >> 8;
-			if (cmd == 3 || cmd == 5)
-			{
-				setVUMeterHeight(ch);
-				setTonePorta(ch);
-				checkMoreEffects(ch);
-			}
-			else if (cmd == 9)
-			{
-				checkMoreEffects(ch);
-				setPeriod(ch);
-			}
-			else
-			{
-				setPeriod(ch);
-			}
-		}
-	}
-	else
-	{
-		checkMoreEffects(ch);
-	}
-}
-
-static void nextPosition(void)
-{
-	modEntry->row = pBreakPosition;
-	pBreakPosition = 0;
-	posJumpAssert = false;
-
-	if (editor.playMode != PLAY_MODE_PATTERN ||
-		(editor.currMode == MODE_RECORD && editor.recordMode != RECORD_PATT))
-	{
-		if (editor.stepPlayEnabled)
-		{
-			doStopIt(true);
-
-			editor.stepPlayEnabled = false;
-			editor.stepPlayBackwards = false;
-
-			if (!editor.isWAVRendering && !editor.isSMPRendering)
-				modEntry->currRow = modEntry->row;
-
-			return;
-		}
-
-		modOrder = (modOrder + 1) & 0x7F;
-		if (modOrder >= modEntry->head.orderCount)
-		{
-			modOrder = 0;
-			modHasBeenPlayed = true;
-
-			if (config.compoMode) // stop song for music competitions playing
-			{
-				doStopIt(true);
-				turnOffVoices();
-
-				modEntry->currOrder = 0;
-				modEntry->currRow = modEntry->row = 0;
-				modEntry->currPattern = modPattern = (int8_t)modEntry->head.order[0];
-
-				editor.currPatternDisp = &modEntry->currPattern;
-				editor.currPosEdPattDisp = &modEntry->currPattern;
-				editor.currPatternDisp = &modEntry->currPattern;
-				editor.currPosEdPattDisp = &modEntry->currPattern;
-
-				if (ui.posEdScreenShown)
-					ui.updatePosEd = true;
-
-				ui.updateSongPos = true;
-				ui.updateSongPattern = true;
-				ui.updateCurrPattText = true;
-			}
-		}
-
-		modPattern = (int8_t)modEntry->head.order[modOrder];
-		if (modPattern > MAX_PATTERNS-1)
-			modPattern = MAX_PATTERNS-1;
-
-		updateUIPositions = true;
-	}
-}
-
-bool intMusic(void)
-{
-	uint8_t i;
-	uint16_t *patt;
-	moduleChannel_t *c;
-
-	if (modBPM > 0)
-		editor.musicTime += (65536 / modBPM); // for playback counter
-
-	if (updateUIPositions)
-	{
-		updateUIPositions = false;
-
-		if (!editor.isWAVRendering && !editor.isSMPRendering)
-		{
-			if (editor.playMode != PLAY_MODE_PATTERN)
-			{
-				modEntry->currOrder = modOrder;
-				modEntry->currPattern = modPattern;
-
-				patt = &modEntry->head.order[modOrder];
-				editor.currPatternDisp = patt;
-				editor.currPosEdPattDisp = patt;
-				editor.currPatternDisp = patt;
-				editor.currPosEdPattDisp = patt;
-
-				if (ui.posEdScreenShown)
-					ui.updatePosEd = true;
-
-				ui.updateSongPos = true;
-				ui.updateSongPattern = true;
-				ui.updateCurrPattText = true;
-			}
-		}
-	}
-
-	// PT quirk: CIA refreshes its timer values on the next interrupt, so do the real tempo change here
-	if (setBPMFlag != 0)
-	{
-		modSetTempo(setBPMFlag);
-		setBPMFlag = 0;
-	}
-
-	if (editor.isWAVRendering && editor.modTick == 0)
-		editor.rowVisitTable[(modOrder * MOD_ROWS) + modEntry->row] = true;
-
-	if (!editor.stepPlayEnabled)
-		editor.modTick++;
-
-	if (editor.modTick >= editor.modSpeed || editor.stepPlayEnabled)
-	{
-		editor.modTick = 0;
-
-		if (pattDelTime2 == 0)
-		{
-			for (i = 0; i < AMIGA_VOICES; i++)
-			{
-				c = &modEntry->channels[i];
-
-				playVoice(c);
-				paulaSetVolume(i, c->n_volume);
-
-				// these take effect after the current DMA cycle is done
-				paulaSetData(i, c->n_loopstart);
-				paulaSetLength(i, c->n_replen);
-			}
-		}
-		else
-		{
-			for (i = 0; i < AMIGA_VOICES; i++)
-				checkEffects(&modEntry->channels[i]);
-		}
-
-		if (!editor.isWAVRendering && !editor.isSMPRendering)
-		{
-			modEntry->currRow = modEntry->row;
-			ui.updatePatternData = true;
-		}
-
-		if (!editor.stepPlayBackwards)
-		{
-			modEntry->row++;
-			modEntry->rowsCounter++;
-		}
-
-		if (pattDelTime > 0)
-		{
-			pattDelTime2 = pattDelTime;
-			pattDelTime = 0;
-		}
-
-		if (pattDelTime2 > 0)
-		{
-			if (--pattDelTime2 > 0)
-				modEntry->row--;
-		}
-
-		if (pBreakFlag)
-		{
-			modEntry->row = pBreakPosition;
-			pBreakPosition = 0;
-			pBreakFlag = false;
-		}
-
-		if (editor.blockMarkFlag)
-			ui.updateStatusText = true;
-
-		if (editor.stepPlayEnabled)
-		{
-			doStopIt(true);
-
-			modEntry->currRow = modEntry->row & 0x3F;
-			ui.updatePatternData = true;
-
-			editor.stepPlayEnabled = false;
-			editor.stepPlayBackwards = false;
-			ui.updatePatternData = true;
-
-			return true;
-		}
-
-		if (modEntry->row >= MOD_ROWS || posJumpAssert)
-		{
-			if (editor.isSMPRendering)
-				modHasBeenPlayed = true;
-
-			nextPosition();
-		}
-
-		if (editor.isWAVRendering && !pattDelTime2 && editor.rowVisitTable[(modOrder * MOD_ROWS) + modEntry->row])
-			modHasBeenPlayed = true;
-	}
-	else
-	{
-		for (i = 0; i < AMIGA_VOICES; i++)
-			checkEffects(&modEntry->channels[i]);
-
-		if (posJumpAssert)
-			nextPosition();
-	}
-
-	if ((editor.isSMPRendering || editor.isWAVRendering) && modHasBeenPlayed && editor.modTick == editor.modSpeed-1)
-	{
-		modHasBeenPlayed = false;
-		return false;
-	}
-
-	return true;
-}
-
-void modSetPattern(uint8_t pattern)
-{
-	modPattern = pattern;
-	modEntry->currPattern = modPattern;
-	ui.updateCurrPattText = true;
-}
-
-void modSetPos(int16_t order, int16_t row)
-{
-	int16_t posEdPos;
-
-	if (row != -1)
-	{
-		row = CLAMP(row, 0, 63);
-
-		editor.modTick = 0;
-		modEntry->row = (int8_t)row;
-		modEntry->currRow = (int8_t)row;
-	}
-
-	if (order != -1)
-	{
-		if (order >= 0)
-		{
-			modOrder = order;
-			modEntry->currOrder = order;
-			ui.updateSongPos = true;
-
-			if (editor.currMode == MODE_PLAY && editor.playMode == PLAY_MODE_NORMAL)
-			{
-				modPattern = (int8_t)modEntry->head.order[order];
-				if (modPattern > MAX_PATTERNS-1)
-					modPattern = MAX_PATTERNS-1;
-
-				modEntry->currPattern = modPattern;
-				ui.updateCurrPattText = true;
-			}
-
-			ui.updateSongPattern = true;
-			editor.currPatternDisp = &modEntry->head.order[modOrder];
-
-			posEdPos = modEntry->currOrder;
-			if (posEdPos > modEntry->head.orderCount-1)
-				posEdPos = modEntry->head.orderCount-1;
-
-			editor.currPosEdPattDisp = &modEntry->head.order[posEdPos];
-
-			if (ui.posEdScreenShown)
-				ui.updatePosEd = true;
-		}
-	}
-
-	ui.updatePatternData = true;
-
-	if (editor.blockMarkFlag)
-		ui.updateStatusText = true;
-}
-
-void modSetTempo(uint16_t bpm)
-{
-	uint32_t smpsPerTick;
-
-	if (bpm < 32)
-		return;
-
-	const bool audioWasntLocked = !audio.locked;
-	if (audioWasntLocked)
-		lockAudio();
-
-	modBPM = bpm;
-	if (!editor.isSMPRendering && !editor.isWAVRendering)
-	{
-		modEntry->currBPM = bpm;
-		ui.updateSongBPM = true;
-	}
-
-	bpm -= 32; // 32..255 -> 0..223
-
-	if (editor.isSMPRendering)
-		smpsPerTick = editor.pat2SmpHQ ? audio.bpmTab28kHz[bpm] : audio.bpmTab22kHz[bpm];
-	else if (editor.isWAVRendering)
-		smpsPerTick = audio.bpmTabMod2Wav[bpm];
-	else
-		smpsPerTick = audio.bpmTab[bpm];
-
-	mixerSetSamplesPerTick(smpsPerTick);
-
-	if (audioWasntLocked)
-		unlockAudio();
-}
-
-void modStop(void)
-{
-	moduleChannel_t *ch;
-
-	editor.songPlaying = false;
-	turnOffVoices();
-
-	for (uint8_t i = 0; i < AMIGA_VOICES; i++)
-	{
-		ch = &modEntry->channels[i];
-
-		ch->n_wavecontrol = 0;
-		ch->n_glissfunk = 0;
-		ch->n_finetune = 0;
-		ch->n_loopcount = 0;
-	}
-
-	pBreakFlag = false;
-	pattDelTime = 0;
-	pattDelTime2 = 0;
-	pBreakPosition = 0;
-	posJumpAssert = false;
-	modHasBeenPlayed = true;
-}
-
-void playPattern(int8_t startRow)
-{
-	modEntry->row = startRow & 0x3F;
-	modEntry->currRow  = modEntry->row;
-	editor.modTick = 0;
-	editor.playMode = PLAY_MODE_PATTERN;
-	editor.currMode = MODE_PLAY;
-	editor.didQuantize = false;
-
-	if (!editor.stepPlayEnabled)
-		pointerSetMode(POINTER_MODE_PLAY, DO_CARRY);
-
-	editor.songPlaying = true;
-	mixerClearSampleCounter();
-}
-
-void incPatt(void)
-{
-	if (++modPattern > MAX_PATTERNS-1)
-		modPattern = 0;
-
-	modEntry->currPattern = modPattern;
-
-	ui.updatePatternData = true;
-	ui.updateCurrPattText = true;
-}
-
-void decPatt(void)
-{
-	if (--modPattern < 0)
-		modPattern = MAX_PATTERNS - 1;
-
-	modEntry->currPattern = modPattern;
-
-	ui.updatePatternData = true;
-	ui.updateCurrPattText = true;
-}
-
-void modPlay(int16_t patt, int16_t order, int8_t row)
-{
-	uint8_t oldPlayMode, oldMode;
-
-	doStopIt(false);
-	turnOffVoices();
-	mixerClearSampleCounter();
-
-	if (row != -1)
-	{
-		if (row >= 0 && row <= 63)
-		{
-			modEntry->row = row;
-			modEntry->currRow = row;
-		}
-	}
-	else
-	{
-		modEntry->row = 0;
-		modEntry->currRow = 0;
-	}
-
-	if (editor.playMode != PLAY_MODE_PATTERN)
-	{
-		if (modOrder >= modEntry->head.orderCount)
-		{
-			modOrder = 0;
-			modEntry->currOrder = 0;
-		}
-
-		if (order >= 0 && order < modEntry->head.orderCount)
-		{
-			modOrder = order;
-			modEntry->currOrder = order;
-		}
-
-		if (order >= modEntry->head.orderCount)
-		{
-			modOrder = 0;
-			modEntry->currOrder = 0;
-		}
-	}
-
-	if (patt >= 0 && patt <= MAX_PATTERNS-1)
-		modEntry->currPattern = modPattern = (int8_t)patt;
-	else
-		modEntry->currPattern = modPattern = (int8_t)modEntry->head.order[modOrder];
-
-	editor.currPatternDisp = &modEntry->head.order[modOrder];
-	editor.currPosEdPattDisp = &modEntry->head.order[modOrder];
-
-	oldPlayMode = editor.playMode;
-	oldMode = editor.currMode;
-
-	editor.playMode = oldPlayMode;
-	editor.currMode = oldMode;
-
-	editor.modTick = editor.modSpeed;
-	modHasBeenPlayed = false;
-	editor.songPlaying = true;
-	editor.didQuantize = false;
-	editor.musicTime = 0;
-
-	if (!editor.isSMPRendering && !editor.isWAVRendering)
-	{
-		ui.updateSongPos = true;
-		ui.updatePatternData = true;
-		ui.updateSongPattern = true;
-		ui.updateCurrPattText = true;
-	}
-}
-
-void clearSong(void)
-{
-	uint8_t i;
-	moduleChannel_t *ch;
-
-	if (modEntry != NULL)
-	{
-		memset(modEntry->head.order, 0, sizeof (modEntry->head.order));
-		memset(modEntry->head.moduleTitle, 0, sizeof (modEntry->head.moduleTitle));
-
-		editor.muted[0] = false;
-		editor.muted[1] = false;
-		editor.muted[2] = false;
-		editor.muted[3] = false;
-
-		editor.f6Pos = 0;
-		editor.f7Pos = 16;
-		editor.f8Pos = 32;
-		editor.f9Pos = 48;
-		editor.f10Pos = 63;
-
-		editor.musicTime = 0;
-
-		editor.metroFlag = false;
-		editor.currSample = 0;
-		editor.editMoveAdd = 1;
-		editor.blockMarkFlag = false;
-		editor.swapChannelFlag = false;
-
-		modEntry->head.orderCount = 1;
-
-		for (i = 0; i < MAX_PATTERNS; i++)
-			memset(modEntry->patterns[i], 0, (MOD_ROWS * AMIGA_VOICES) * sizeof (note_t));
-
-		for (i = 0; i < AMIGA_VOICES; i++)
-		{
-			ch = &modEntry->channels[i];
-
-			ch->n_wavecontrol = 0;
-			ch->n_glissfunk = 0;
-			ch->n_finetune = 0;
-			ch->n_loopcount = 0;
-		}
-
-		modSetPos(0, 0); // this also refreshes pattern data
-
-		modEntry->currOrder = 0;
-		modEntry->currPattern = 0;
-		editor.currPatternDisp = &modEntry->head.order[0];
-		editor.currPosEdPattDisp = &modEntry->head.order[0];
-
-		modSetTempo(editor.initialTempo);
-		modSetSpeed(editor.initialSpeed);
-
-		setLEDFilter(false); // real PT doesn't do this there, but that's insane
-		updateCurrSample();
-
-		ui.updateSongSize = true;
-		renderMuteButtons();
-		updateWindowTitle(MOD_IS_MODIFIED);
-	}
-}
-
-void clearSamples(void)
-{
-	moduleSample_t *s;
-
-	if (modEntry == NULL)
-		return;
-
-	for (uint8_t i = 0; i < MOD_SAMPLES; i++)
-	{
-		s = &modEntry->samples[i];
-
-		s->fineTune = 0;
-		s->length = 0;
-		s->loopLength = 2;
-		s->loopStart = 0;
-		s->volume = 0;
-
-		memset(s->text, 0, sizeof (s->text));
-	}
-
-	memset(modEntry->sampleData, 0, (MOD_SAMPLES + 1) * MAX_SAMPLE_LEN);
-
-	editor.currSample = 0;
-	editor.keypadSampleOffset = 0;
-	editor.sampleZero = false;
-	ui.editOpScreenShown = false;
-	ui.aboutScreenShown = false;
-	editor.blockMarkFlag = false;
-
-	editor.samplePos = 0;
-	updateCurrSample();
-
-	updateWindowTitle(MOD_IS_MODIFIED);
-}
-
-void clearAll(void)
-{
-	if (modEntry != NULL)
-	{
-		clearSamples();
-		clearSong();
-	}
-}
-
-void modFree(void)
-{
-	uint8_t i;
-
-	if (modEntry == NULL)
-		return; // not allocated
-
-	const bool audioWasntLocked = !audio.locked;
-	if (audioWasntLocked)
-		lockAudio();
-
-	turnOffVoices();
-
-	for (i = 0; i < MAX_PATTERNS; i++)
-	{
-		if (modEntry->patterns[i] != NULL)
-			free(modEntry->patterns[i]);
-	}
-
-	if (modEntry->sampleData != NULL)
-		free(modEntry->sampleData);
-
-	free(modEntry);
-	modEntry = NULL;
-
-	if (audioWasntLocked)
-		unlockAudio();
-}
-
-void restartSong(void) // for the beginning of MOD2WAV/PAT2SMP
-{
-	if (editor.songPlaying)
-		modStop();
-
-	editor.playMode = PLAY_MODE_NORMAL;
-	editor.blockMarkFlag = false;
-	audio.forceMixerOff = true;
-
-	modEntry->row = 0;
-	modEntry->currRow = 0;
-	modEntry->rowsCounter = 0;
-
-	memset(editor.rowVisitTable, 0, MOD_ORDERS * MOD_ROWS); // for MOD2WAV
-
-	if (editor.isSMPRendering)
-	{
-		modPlay(DONT_SET_PATTERN, DONT_SET_ORDER, DONT_SET_ROW);
-	}
-	else
-	{
-		modEntry->currSpeed = 6;
-		modEntry->currBPM = 125;
-		modSetSpeed(6);
-		modSetTempo(125);
-
-		modPlay(DONT_SET_PATTERN, 0, 0);
-	}
-}
-
-// this function is meant for the end of MOD2WAV/PAT2SMP
-void resetSong(void) // only call this after storeTempVariables() has been called!
-{
-	modStop();
-
-	editor.songPlaying = false;
-	editor.playMode = PLAY_MODE_NORMAL;
-	editor.currMode = MODE_IDLE;
-
-	turnOffVoices();
-
-	memset((int8_t *)editor.vuMeterVolumes,0, sizeof (editor.vuMeterVolumes));
-	memset((int8_t *)editor.realVuMeterVolumes, 0, sizeof (editor.realVuMeterVolumes));
-	memset((int8_t *)editor.spectrumVolumes, 0, sizeof (editor.spectrumVolumes));
-
-	memset(modEntry->channels, 0, sizeof (modEntry->channels));
-	for (uint8_t i = 0; i < AMIGA_VOICES; i++)
-		modEntry->channels[i].n_chanindex = i;
-
-	modOrder = oldOrder;
-	modPattern = (int8_t)oldPattern;
-
-	modEntry->row = oldRow;
-	modEntry->currRow = oldRow;
-	modEntry->currBPM = oldBPM;
-	modEntry->currOrder = oldOrder;
-	modEntry->currPattern = oldPattern;
-
-	editor.currPosDisp = &modEntry->currOrder;
-	editor.currEditPatternDisp = &modEntry->currPattern;
-	editor.currPatternDisp = &modEntry->head.order[modEntry->currOrder];
-	editor.currPosEdPattDisp = &modEntry->head.order[modEntry->currOrder];
-
-	modSetSpeed(oldSpeed);
-	modSetTempo(oldBPM);
-
-	doStopIt(true);
-
-	editor.modTick = 0;
-	modHasBeenPlayed = false;
-	audio.forceMixerOff = false;
-}
--- /dev/null
+++ b/src/pt2_module_loader.c
@@ -1,0 +1,1234 @@
+// for finding memory leaks in debug mode with Visual Studio 
+#if defined _DEBUG && defined _MSC_VER
+#include <crtdbg.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#ifdef _WIN32
+#include <io.h>
+#else
+#include <unistd.h>
+#endif
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "pt2_mouse.h"
+#include "pt2_header.h"
+#include "pt2_sampler.h"
+#include "pt2_textout.h"
+#include "pt2_audio.h"
+#include "pt2_helpers.h"
+#include "pt2_visuals.h"
+#include "pt2_unicode.h"
+#include "pt2_module_loader.h"
+#include "pt2_sample_loader.h"
+#include "pt2_config.h"
+
+typedef struct mem_t
+{
+	bool _eof;
+	uint8_t *_ptr, *_base;
+	uint32_t _cnt, _bufsiz;
+} MEMFILE;
+
+static bool oldAutoPlay;
+static char oldFullPath[(PATH_MAX * 2) + 2];
+static uint32_t oldFullPathLen;
+
+static MEMFILE *mopen(const uint8_t *src, uint32_t length);
+static void mclose(MEMFILE **buf);
+static int32_t mgetc(MEMFILE *buf);
+static size_t mread(void *buffer, size_t size, size_t count, MEMFILE *buf);
+static void mseek(MEMFILE *buf, int32_t offset, int32_t whence);
+static void mrewind(MEMFILE *buf);
+static uint8_t ppdecrunch(uint8_t *src, uint8_t *dst, uint8_t *offsetLens, uint32_t srcLen, uint32_t dstLen, uint8_t skipBits);
+
+void showSongUnsavedAskBox(int8_t askScreenType)
+{
+	ui.askScreenShown = true;
+	ui.askScreenType = askScreenType;
+
+	pointerSetMode(POINTER_MODE_MSG1, NO_CARRY);
+	setStatusMessage("SONG IS UNSAVED !", NO_CARRY);
+	renderAskDialog();
+}
+
+#define IS_ID(s, b) !strncmp(s, b, 4)
+
+static uint8_t getModType(uint8_t *numChannels, const char *id)
+{
+	*numChannels = 4;
+
+	if (IS_ID("M.K.", id) || IS_ID("M!K!", id) || IS_ID("NSMS", id) || IS_ID("LARD", id) || IS_ID("PATT", id))
+	{
+		return FORMAT_MK; // ProTracker (or compatible)
+	}
+	else if (IS_ID("FLT4", id))
+	{
+		return FORMAT_FLT; // Startrekker (4 channels)
+	}
+	else if (IS_ID("N.T.", id))
+	{
+		return FORMAT_NT; // NoiseTracker
+	}
+	else if (IS_ID("M&K!", id) || IS_ID("FEST", id))
+	{
+		return FORMAT_HMNT; // His Master's NoiseTracker
+	}
+	else if (id[1] == 'C' && id[2] == 'H' && id[3] == 'N')
+	{
+		*numChannels = id[0] - '0';
+		return FORMAT_FT2; // Fasttracker II 1..9 channels (or other trackers)
+	}
+
+	return FORMAT_UNKNOWN; // may be The Ultimate Soundtracker (set to FORMAT_STK later)
+}
+
+// converts zeroes to spaces in a string, up until the last zero found
+static void fixZeroesInString(char *str, uint32_t maxLength)
+{
+	int32_t i;
+
+	for (i = maxLength-1; i >= 0; i--)
+	{
+		if (str[i] != '\0')
+			break;
+	}
+
+	// convert zeroes to spaces
+	if (i > 0)
+	{
+		for (int32_t j = 0; j < i; j++)
+		{
+			if (str[j] == '\0')
+				str[j] = ' ';
+		}
+	}
+}
+
+static uint8_t *unpackPPModule(FILE *f, uint32_t *filesize)
+{
+	uint8_t *modBuffer, ppCrunchData[4], *ppBuffer;
+	uint32_t ppPackLen, ppUnpackLen;
+
+	ppPackLen = *filesize;
+	if ((ppPackLen & 3) || ppPackLen <= 12)
+	{
+		displayErrorMsg("POWERPACKER ERROR");
+		return NULL;
+	}
+
+	ppBuffer = (uint8_t *)malloc(ppPackLen);
+	if (ppBuffer == NULL)
+	{
+		statusOutOfMemory();
+		return NULL;
+	}
+
+	fseek(f, ppPackLen-4, SEEK_SET);
+
+	ppCrunchData[0] = (uint8_t)fgetc(f);
+	ppCrunchData[1] = (uint8_t)fgetc(f);
+	ppCrunchData[2] = (uint8_t)fgetc(f);
+	ppCrunchData[3] = (uint8_t)fgetc(f);
+
+	ppUnpackLen = (ppCrunchData[0] << 16) | (ppCrunchData[1] << 8) | ppCrunchData[2];
+
+	modBuffer = (uint8_t *)malloc(ppUnpackLen);
+	if (modBuffer == NULL)
+	{
+		free(ppBuffer);
+		statusOutOfMemory();
+		return NULL;
+	}
+
+	rewind(f);
+	fread(ppBuffer, 1, ppPackLen, f);
+	fclose(f);
+
+	if (!ppdecrunch(ppBuffer+8, modBuffer, ppBuffer+4, ppPackLen-12, ppUnpackLen, ppCrunchData[3]))
+	{
+		free(ppBuffer);
+		displayErrorMsg("POWERPACKER ERROR");
+		return NULL;
+	}
+
+	free(ppBuffer);
+	*filesize = ppUnpackLen;
+	return modBuffer;
+}
+
+module_t *modLoad(UNICHAR *fileName)
+{
+	bool mightBeSTK, lateSTKVerFlag, veryLateSTKVerFlag;
+	char modID[4], tmpChar;
+	int8_t numSamples;
+	uint8_t bytes[4], restartPos, modFormat;
+	uint8_t *modBuffer, numChannels;
+	int32_t i, j, k, loopStart, loopLength, loopOverflowVal, numPatterns;
+	uint32_t powerPackerID, filesize;
+	FILE *f;
+	MEMFILE *m;
+	module_t *newMod;
+	moduleSample_t *s;
+	note_t *note;
+
+	veryLateSTKVerFlag = false; // "DFJ SoundTracker III" and later
+	lateSTKVerFlag = false; // "TJC SoundTracker II" and later
+	mightBeSTK = false;
+
+	m = NULL;
+	f = NULL;
+	modBuffer = NULL;
+
+	newMod = (module_t *)calloc(1, sizeof (module_t));
+	if (newMod == NULL)
+	{
+		statusOutOfMemory();
+		goto modLoadError;
+	}
+
+	f = UNICHAR_FOPEN(fileName, "rb");
+	if (f == NULL)
+	{
+		displayErrorMsg("FILE I/O ERROR !");
+		goto modLoadError;
+	}
+
+	fseek(f, 0, SEEK_END);
+	filesize = ftell(f);
+	rewind(f);
+
+	// check if mod is a powerpacker mod
+	fread(&powerPackerID, 4, 1, f);
+	if (powerPackerID == 0x30325850) // "PX20"
+	{
+		displayErrorMsg("ENCRYPTED MOD !");
+		goto modLoadError;
+	}
+	else if (powerPackerID == 0x30325050) // "PP20"
+	{
+		modBuffer = unpackPPModule(f, &filesize);
+		if (modBuffer == NULL)
+			goto modLoadError; // error msg is set in unpackPPModule()
+	}
+	else
+	{
+		modBuffer = (uint8_t *)malloc(filesize);
+		if (modBuffer == NULL)
+		{
+			statusOutOfMemory();
+			goto modLoadError;
+		}
+
+		fseek(f, 0, SEEK_SET);
+		fread(modBuffer, 1, filesize, f);
+		fclose(f);
+	}
+
+	// smallest and biggest possible PT .MOD
+	if (filesize < 2108 || filesize > 4195326)
+	{
+		displayErrorMsg("NOT A MOD FILE !");
+		goto modLoadError;
+	}
+
+	// Use MEMFILE functions on module buffer (similar to FILE functions)
+
+	m = mopen(modBuffer, filesize);
+	if (m == NULL)
+	{
+		displayErrorMsg("FILE I/O ERROR !");
+		goto modLoadError;
+	}
+
+	// check magic ID
+	memset(modID, 0, 4); // in case mread fails
+	mseek(m, 1080, SEEK_SET);
+	mread(modID, 1, 4, m);
+
+	modFormat = getModType(&numChannels, modID);
+	if (numChannels == 0 || numChannels > AMIGA_VOICES)
+	{
+		displayErrorMsg("UNSUPPORTED MOD !");
+		goto modLoadError;
+	}
+
+	if (modFormat == FORMAT_UNKNOWN)
+		mightBeSTK = true;
+
+	mrewind(m);
+	mread(newMod->header.name, 1, 20, m);
+	newMod->header.name[20] = '\0';
+
+	// convert illegal song name characters to space
+	for (i = 0; i < 20; i++)
+	{
+		tmpChar = newMod->header.name[i];
+		if ((tmpChar < ' ' || tmpChar > '~') && tmpChar != '\0')
+			tmpChar = ' ';
+
+		newMod->header.name[i] = tmpChar;
+	}
+
+	fixZeroesInString(newMod->header.name, 20);
+
+	// read sample headers
+	s = newMod->samples;
+	for (i = 0; i < MOD_SAMPLES; i++, s++)
+	{
+		if (mightBeSTK && i >= 15) // skip reading sample headers past sample slot 15 in STK/UST modules
+		{
+			s->loopLength = 2; // this be set though
+			continue;
+		}
+
+		mread(s->text, 1, 22, m);
+		s->text[22] = '\0';
+
+		if (modFormat == FORMAT_HMNT)
+		{
+			// most of "His Master's Noisetracker" songs have junk sample names, so let's wipe it.
+			memset(s->text, 0, 22);
+		}
+		else
+		{
+			// convert illegal sample name characters to space
+			for (j = 0; j < 22; j++)
+			{
+				tmpChar = s->text[j];
+				if ((tmpChar < ' ' || tmpChar > '~') && tmpChar != '\0')
+					tmpChar = ' ';
+
+				s->text[j] = tmpChar;
+			}
+
+			fixZeroesInString(s->text, 22);
+		}
+
+		s->length = (uint16_t)(((mgetc(m) << 8) | mgetc(m)) * 2);
+
+		/* Only late versions of Ultimate SoundTracker could have samples larger than 9999 bytes.
+		** If found, we know for sure that this is a late STK module.
+		*/
+		if (mightBeSTK && s->length > 9999)
+			lateSTKVerFlag = true;
+
+		if (modFormat == FORMAT_HMNT) // finetune in "His Master's NoiseTracker" is different
+			s->fineTune = (uint8_t)((-mgetc(m) & 0x1F) / 2); // one more bit of precision, + inverted
+		else
+			s->fineTune = (uint8_t)mgetc(m) & 0xF;
+
+		if (mightBeSTK)
+			s->fineTune = 0; // this is high byte of volume in STK/UST (has no finetune), set to zero
+
+		s->volume = (int8_t)mgetc(m);
+		if ((uint8_t)s->volume > 64)
+			s->volume = 64;
+
+		loopStart = ((mgetc(m) << 8) | mgetc(m)) * 2;
+		loopLength = ((mgetc(m) << 8) | mgetc(m)) * 2;
+
+		if (loopLength < 2)
+			loopLength = 2; // fixes empty samples in .MODs saved from FT2
+
+		// we don't support samples bigger than 65534 bytes, disable uncompatible loops
+		if (loopStart > MAX_SAMPLE_LEN || loopStart+loopLength > MAX_SAMPLE_LEN)
+		{
+			s->loopStart = 0;
+			s->loopLength = 2;
+		}
+		else
+		{
+			s->loopStart = (uint16_t)loopStart;
+			s->loopLength = (uint16_t)loopLength;
+		}
+
+		// in The Ultimate SoundTracker, sample loop start is in bytes, not words
+		if (mightBeSTK)
+			s->loopStart /= 2;
+
+		// fix for poorly converted STK (< v2.5) -> PT/NT modules (FIXME: Worth keeping or not?)
+		if (!mightBeSTK && s->loopLength > 2 && s->loopStart+s->loopLength > s->length)
+		{
+			if ((s->loopStart/2) + s->loopLength <= s->length)
+				s->loopStart /= 2;
+		}
+	}
+
+	newMod->header.numOrders = (uint8_t)mgetc(m);
+
+	if (modFormat == FORMAT_MK && newMod->header.numOrders == 129)
+		newMod->header.numOrders = 127; // fixes a specific copy of beatwave.mod
+
+	if (newMod->header.numOrders > 129)
+	{
+		displayErrorMsg("NOT A MOD FILE !");
+		goto modLoadError;
+	}
+
+	if (newMod->header.numOrders == 0)
+	{
+		displayErrorMsg("NOT A MOD FILE !");
+		goto modLoadError;
+	}
+
+	restartPos = (uint8_t)mgetc(m);
+	if (mightBeSTK && restartPos > 220)
+	{
+		displayErrorMsg("NOT A MOD FILE !");
+		goto modLoadError;
+	}
+
+	newMod->header.initialTempo = 125;
+	if (mightBeSTK)
+	{
+		/* If we're still here at this point and the mightBeSTK flag is set,
+		** then it's most likely a proper The Ultimate SoundTracker (STK/UST) module.
+		*/
+		modFormat = FORMAT_STK;
+
+		if (restartPos == 0)
+			restartPos = 120;
+
+		// jjk55.mod by Jesper Kyd has a bogus STK tempo value that should be ignored
+		if (!strcmp("jjk55", newMod->header.name))
+			restartPos = 120;
+
+		// the "restart pos" field in STK is the inital tempo (must be converted to BPM first)
+		if (restartPos != 120) // 120 is a special case and means 50Hz (125BPM)
+		{
+			if (restartPos > 220)
+				restartPos = 220;
+
+			// convert UST tempo to BPM
+			uint16_t ciaPeriod = (240 - restartPos) * 122;
+			double dHz = (double)CIA_PAL_CLK / ciaPeriod;
+			int32_t BPM = (int32_t)((dHz * (125.0 / 50.0)) + 0.5);
+
+			newMod->header.initialTempo = (uint16_t)BPM;
+		}
+	}
+
+	// read orders and count number of patterns
+	numPatterns = 0;
+	for (i = 0; i < MOD_ORDERS; i++)
+	{
+		newMod->header.order[i] = (int16_t)mgetc(m);
+		if (newMod->header.order[i] > numPatterns)
+			numPatterns = newMod->header.order[i];
+	}
+	numPatterns++;
+
+	if (numPatterns > MAX_PATTERNS)
+	{
+		displayErrorMsg("UNSUPPORTED MOD !");
+		goto modLoadError;
+	}
+
+	// skip magic ID (The Ultimate SoundTracker MODs doesn't have it)
+	if (modFormat != FORMAT_STK)
+		mseek(m, 4, SEEK_CUR);
+
+	// allocate 100 patterns
+	for (i = 0; i < MAX_PATTERNS; i++)
+	{
+		newMod->patterns[i] = (note_t *)calloc(MOD_ROWS * AMIGA_VOICES, sizeof (note_t));
+		if (newMod->patterns[i] == NULL)
+		{
+			statusOutOfMemory();
+			goto modLoadError;
+		}
+	}
+
+	// load pattern data
+	for (i = 0; i < numPatterns; i++)
+	{
+		note = newMod->patterns[i];
+		for (j = 0; j < MOD_ROWS; j++)
+		{
+			for (k = 0; k < numChannels; k++, note++)
+			{
+				mread(bytes, 1, 4, m);
+
+				note->period = ((bytes[0] & 0x0F) << 8) | bytes[1];
+				note->sample = (bytes[0] & 0xF0) | (bytes[2] >> 4);
+				note->command = bytes[2] & 0x0F;
+				note->param = bytes[3];
+
+				if (modFormat == FORMAT_STK)
+				{
+					if (note->command == 0xC || note->command == 0xD || note->command == 0xE)
+					{
+						// "TJC SoundTracker II" and later
+						lateSTKVerFlag = true;
+					}
+
+					if (note->command == 0xF)
+					{
+						// "DFJ SoundTracker III" and later
+						lateSTKVerFlag = true;
+						veryLateSTKVerFlag = true;
+					}
+				}
+			}
+
+			if (numChannels < AMIGA_VOICES)
+				note += AMIGA_VOICES-numChannels;
+		}
+	}
+
+	// pattern command conversion for non-PT formats
+	if (modFormat == FORMAT_STK || modFormat == FORMAT_FT2 || modFormat == FORMAT_NT || modFormat == FORMAT_HMNT || modFormat == FORMAT_FLT)
+	{
+		for (i = 0; i < numPatterns; i++)
+		{
+			note = newMod->patterns[i];
+			for (j = 0; j < MOD_ROWS*4; j++, note++)
+			{
+				if (modFormat == FORMAT_NT || modFormat == FORMAT_HMNT)
+				{
+					// any Dxx == D00 in NT/HMNT
+					if (note->command == 0xD)
+						note->param = 0;
+
+					// effect F with param 0x00 does nothing in NT/HMNT
+					if (note->command == 0xF && note->param == 0)
+						note->command = 0;
+				}
+				else if (modFormat == FORMAT_FLT) // Startrekker (4 channels)
+				{
+					if (note->command == 0xE) // remove unsupported "assembly macros" command
+					{
+						note->command = 0;
+						note->param = 0;
+					}
+
+					// Startrekker is always in vblank mode, and limits speed to 0x1F
+					if (note->command == 0xF && note->param > 0x1F)
+						note->param = 0x1F;
+				}
+				else if (modFormat == FORMAT_STK)
+				{
+					// convert STK effects to PT effects
+
+					if (!lateSTKVerFlag)
+					{
+						// old SoundTracker 1.x commands
+
+						if (note->command == 1)
+						{
+							// arpeggio
+							note->command = 0;
+						}
+						else if (note->command == 2)
+						{
+							// pitch slide
+							if (note->param & 0xF0)
+							{
+								// pitch slide down
+								note->command = 2;
+								note->param >>= 4;
+							}
+							else if (note->param & 0x0F)
+							{
+								// pitch slide up
+								note->command = 1;
+							}
+						}
+					}
+					else
+					{
+						// "DFJ SoundTracker II" or later
+
+						// TODO: This needs more detection and is NOT correct!
+						if (note->command == 0xD)
+						{
+							if (veryLateSTKVerFlag) // "DFJ SoundTracker III" or later
+							{
+								// pattern break w/ no param (param must be cleared to fix some songs)
+								note->param = 0;
+							}
+							else
+							{
+								// volume slide
+								note->command = 0xA;
+							}
+						}
+					}
+
+					// effect F with param 0x00 does nothing in UST/STK (I think?)
+					if (note->command == 0xF && note->param == 0)
+						note->command = 0;
+				}
+
+				// remove sample-trashing effects that were only present in ProTracker
+
+				// remove E8x (Karplus-Strong in ProTracker)
+				if (note->command == 0xE && (note->param >> 4) == 0x8)
+				{
+					note->command = 0;
+					note->param = 0;
+				}
+
+				// remove EFx (Invert Loop in ProTracker)
+				if (note->command == 0xE && (note->param >> 4) == 0xF)
+				{
+					note->command = 0;
+					note->param = 0;
+				}
+			}
+		}
+	}
+
+	// allocate sample data (+2 sample slots for overflow safety (Paula and scopes))
+	newMod->sampleData = (int8_t *)calloc(MOD_SAMPLES + 2, MAX_SAMPLE_LEN);
+	if (newMod->sampleData == NULL)
+	{
+		statusOutOfMemory();
+		goto modLoadError;
+	}
+
+	// set sample data offsets (sample data = one huge buffer to rule them all)
+	for (i = 0; i < MOD_SAMPLES; i++)
+		newMod->samples[i].offset = MAX_SAMPLE_LEN * i;
+
+	// load sample data
+	numSamples = (modFormat == FORMAT_STK) ? 15 : 31;
+	s = newMod->samples;
+	for (i = 0; i < numSamples; i++, s++)
+	{
+		uint32_t bytesToSkip = 0;
+
+		/* For Ultimate SoundTracker modules, only the loop area of a looped sample is played.
+		** Skip loading of eventual data present before loop start.
+		*/
+		if (modFormat == FORMAT_STK && s->loopStart > 0 && s->loopLength < s->length)
+		{
+			s->length -= s->loopStart;
+			mseek(m, s->loopStart, SEEK_CUR);
+			s->loopStart = 0;
+		}
+
+		/* We don't support loading samples bigger than 65534 bytes in our PT2 clone,
+		** so clamp length and skip overflown data.
+		*/
+		if (s->length > MAX_SAMPLE_LEN)
+		{
+			s->length = MAX_SAMPLE_LEN;
+			bytesToSkip = s->length - MAX_SAMPLE_LEN;
+		}
+
+		// For Ultimate SoundTracker modules, don't load sample data after loop end
+		uint16_t loopEnd = s->loopStart + s->loopLength;
+		if (modFormat == FORMAT_STK && loopEnd > 2 && s->length > loopEnd)
+		{
+			bytesToSkip += s->length-loopEnd;
+			s->length = loopEnd;
+		}
+
+		mread(&newMod->sampleData[s->offset], 1, s->length, m);
+
+		if (bytesToSkip > 0)
+			mseek(m, bytesToSkip, SEEK_CUR);
+
+		// clear first two bytes of non-looping samples to prevent beep after sample has been played
+		if (s->length >= 2 && loopEnd <= 2)
+		{
+			newMod->sampleData[s->offset+0] = 0;
+			newMod->sampleData[s->offset+1] = 0;
+		}
+
+		// some modules are broken like this, adjust sample length if possible (this is ok if we have room)
+		if (s->length > 0 && s->loopLength > 2 && s->loopStart+s->loopLength > s->length)
+		{
+			loopOverflowVal = (s->loopStart + s->loopLength) - s->length;
+			if (s->length+loopOverflowVal <= MAX_SAMPLE_LEN)
+			{
+				s->length = (uint16_t)(s->length + loopOverflowVal); // this is safe, we're allocating 65534 bytes per sample slot
+			}
+			else
+			{
+				s->loopStart = 0;
+				s->loopLength = 2;
+			}
+		}
+	}
+
+	mclose(&m);
+	free(modBuffer);
+
+	for (i = 0; i < AMIGA_VOICES; i++)
+		newMod->channels[i].n_chanindex = (int8_t)i;
+
+	return newMod;
+
+modLoadError:
+	if (m != NULL)
+		mclose(&m);
+
+	if (modBuffer != NULL)
+		free(modBuffer);
+
+	if (newMod != NULL)
+	{
+		for (i = 0; i < MAX_PATTERNS; i++)
+		{
+			if (newMod->patterns[i] != NULL)
+				free(newMod->patterns[i]);
+		}
+
+		free(newMod);
+	}
+
+	return NULL;
+}
+
+static MEMFILE *mopen(const uint8_t *src, uint32_t length)
+{
+	MEMFILE *b;
+
+	if (src == NULL || length == 0)
+		return NULL;
+
+	b = (MEMFILE *)malloc(sizeof (MEMFILE));
+	if (b == NULL)
+		return NULL;
+
+	b->_base = (uint8_t *)src;
+	b->_ptr = (uint8_t *)src;
+	b->_cnt = length;
+	b->_bufsiz = length;
+	b->_eof = false;
+
+	return b;
+}
+
+static void mclose(MEMFILE **buf)
+{
+	if (*buf != NULL)
+	{
+		free(*buf);
+		*buf = NULL;
+	}
+}
+
+static int32_t mgetc(MEMFILE *buf)
+{
+	int32_t b;
+
+	if (buf == NULL || buf->_ptr == NULL || buf->_cnt <= 0)
+		return 0;
+
+	b = *buf->_ptr;
+
+	buf->_cnt--;
+	buf->_ptr++;
+
+	if (buf->_cnt <= 0)
+	{
+		buf->_ptr = buf->_base + buf->_bufsiz;
+		buf->_cnt = 0;
+		buf->_eof = true;
+	}
+
+	return (int32_t)b;
+}
+
+static size_t mread(void *buffer, size_t size, size_t count, MEMFILE *buf)
+{
+	int32_t pcnt;
+	size_t wrcnt;
+
+	if (buf == NULL || buf->_ptr == NULL)
+		return 0;
+
+	wrcnt = size * count;
+	if (size == 0 || buf->_eof)
+		return 0;
+
+	pcnt = (buf->_cnt > (uint32_t)wrcnt) ? (uint32_t)wrcnt : buf->_cnt;
+	memcpy(buffer, buf->_ptr, pcnt);
+
+	buf->_cnt -= pcnt;
+	buf->_ptr += pcnt;
+
+	if (buf->_cnt <= 0)
+	{
+		buf->_ptr = buf->_base + buf->_bufsiz;
+		buf->_cnt = 0;
+		buf->_eof = true;
+	}
+
+	return pcnt / size;
+}
+
+static void mseek(MEMFILE *buf, int32_t offset, int32_t whence)
+{
+	if (buf == NULL)
+		return;
+
+	if (buf->_base)
+	{
+		switch (whence)
+		{
+			case SEEK_SET: buf->_ptr = buf->_base + offset; break;
+			case SEEK_CUR: buf->_ptr += offset; break;
+			case SEEK_END: buf->_ptr = buf->_base + buf->_bufsiz + offset; break;
+			default: break;
+		}
+
+		buf->_eof = false;
+		if (buf->_ptr >= buf->_base+buf->_bufsiz)
+		{
+			buf->_ptr = buf->_base + buf->_bufsiz;
+			buf->_eof = true;
+		}
+
+		buf->_cnt = (uint32_t)((buf->_base + buf->_bufsiz) - buf->_ptr);
+	}
+}
+
+static void mrewind(MEMFILE *buf)
+{
+	mseek(buf, 0, SEEK_SET);
+}
+
+/* PowerPacker unpack code taken from Heikki Orsila's amigadepack. Seems to have no license,
+** so I'll assume it fits into BSD 3-Clause. If not, feel free to contact me at my email
+** address found at the bottom of 16-bits.org.
+**
+** Modified by 8bitbubsy (me).
+*/
+
+#define PP_READ_BITS(nbits, var) \
+	bitCnt = (nbits); \
+	while (bitsLeft < bitCnt) \
+	{ \
+		if (bufSrc < src) \
+			return false; \
+		bitBuffer |= ((*--bufSrc) << bitsLeft); \
+		bitsLeft += 8; \
+	} \
+	(var) = 0; \
+	bitsLeft -= bitCnt; \
+	while (bitCnt--) \
+	{ \
+		(var) = ((var) << 1) | (bitBuffer & 1); \
+		bitBuffer >>= 1; \
+	} \
+
+static uint8_t ppdecrunch(uint8_t *src, uint8_t *dst, uint8_t *offsetLens, uint32_t srcLen, uint32_t dstLen, uint8_t skipBits)
+{
+	uint8_t *bufSrc, *dstEnd, *out, bitsLeft, bitCnt;
+	uint32_t x, todo, offBits, offset, written, bitBuffer;
+
+	if (src == NULL || dst == NULL || offsetLens == NULL)
+		return false;
+
+	bitsLeft = 0;
+	bitBuffer = 0;
+	written = 0;
+	bufSrc = src + srcLen;
+	out = dst + dstLen;
+	dstEnd = out;
+
+	PP_READ_BITS(skipBits, x);
+	while (written < dstLen)
+	{
+		PP_READ_BITS(1, x);
+		if (x == 0)
+		{
+			todo = 1;
+
+			do
+			{
+				PP_READ_BITS(2, x);
+				todo += x;
+			}
+			while (x == 3);
+
+			while (todo--)
+			{
+				PP_READ_BITS(8, x);
+				if (out <= dst)
+					return false;
+
+				*--out = (uint8_t)x;
+				written++;
+			}
+
+			if (written == dstLen)
+				break;
+		}
+
+		PP_READ_BITS(2, x);
+		offBits = offsetLens[x];
+		todo = x + 2;
+
+		if (x == 3)
+		{
+			PP_READ_BITS(1, x);
+			if (x == 0) offBits = 7;
+
+			PP_READ_BITS((uint8_t)offBits, offset);
+			do
+			{
+				PP_READ_BITS(3, x);
+				todo += x;
+			}
+			while (x == 7);
+		}
+		else
+		{
+			PP_READ_BITS((uint8_t)offBits, offset);
+		}
+
+		if (out+offset >= dstEnd)
+			return false;
+
+		while (todo--)
+		{
+			x = out[offset];
+			if (out <= dst)
+				return false;
+
+			*--out = (uint8_t)x;
+			written++;
+		}
+	}
+
+	return true;
+}
+
+void setupNewMod(void)
+{
+	int8_t i;
+
+	// setup GUI text pointers
+	for (i = 0; i < MOD_SAMPLES; i++)
+	{
+		song->samples[i].volumeDisp = &song->samples[i].volume;
+		song->samples[i].lengthDisp = &song->samples[i].length;
+		song->samples[i].loopStartDisp = &song->samples[i].loopStart;
+		song->samples[i].loopLengthDisp = &song->samples[i].loopLength;
+
+		fillSampleRedoBuffer(i);
+	}
+
+	modSetPos(0, 0);
+	modSetPattern(0); // set pattern to 00 instead of first order's pattern
+
+	editor.currEditPatternDisp = &song->currPattern;
+	editor.currPosDisp = &song->currOrder;
+	editor.currPatternDisp = &song->header.order[0];
+	editor.currPosEdPattDisp = &song->header.order[0];
+	editor.currLengthDisp = &song->header.numOrders;
+
+	// calculate MOD size
+	ui.updateSongSize = true;
+
+	editor.muted[0] = false;
+	editor.muted[1] = false;
+	editor.muted[2] = false;
+	editor.muted[3] = false;
+
+	editor.editMoveAdd = 1;
+	editor.currSample = 0;
+	editor.musicTime = 0;
+	editor.modLoaded = true;
+	editor.blockMarkFlag = false;
+	editor.sampleZero = false;
+	editor.hiLowInstr = 0;
+
+	setLEDFilter(false); // real PT doesn't do this, but that's insane
+
+	updateWindowTitle(MOD_NOT_MODIFIED);
+
+	editor.timingMode = TEMPO_MODE_CIA;
+
+	modSetSpeed(6);
+	modSetTempo(song->header.initialTempo); // 125 for normal MODs, custom value for certain STK/UST MODs
+
+	updateCurrSample();
+	editor.samplePos = 0;
+	updateSamplePos();
+}
+
+void loadModFromArg(char *arg)
+{
+	uint32_t filenameLen;
+	UNICHAR *filenameU;
+
+	ui.introScreenShown = false;
+	statusAllRight();
+
+	filenameLen = (uint32_t)strlen(arg);
+
+	filenameU = (UNICHAR *)calloc(filenameLen + 2, sizeof (UNICHAR));
+	if (filenameU == NULL)
+	{
+		statusOutOfMemory();
+		return;
+	}
+
+#ifdef _WIN32
+	MultiByteToWideChar(CP_UTF8, 0, arg, -1, filenameU, filenameLen);
+#else
+	strcpy(filenameU, arg);
+#endif
+
+	module_t *tempSong = modLoad(filenameU);
+	if (tempSong != NULL)
+	{
+		song->loaded = false;
+		modFree();
+		song = tempSong;
+		setupNewMod();
+		song->loaded = true;
+	}
+	else
+	{
+		editor.errorMsgActive = true;
+		editor.errorMsgBlock = true;
+		editor.errorMsgCounter = 0;
+
+		// status/error message is set in the mod loader
+		setErrPointer();
+	}
+
+	free(filenameU);
+}
+
+static bool testExtension(char *ext, uint8_t extLen, char *fullPath)
+{
+	// checks for EXT.filename and filename.EXT
+	char *fileName, begStr[8], endStr[8];
+	uint32_t fileNameLen;
+
+	extLen++; // add one to length (dot)
+
+	fileName = strrchr(fullPath, DIR_DELIMITER);
+	if (fileName != NULL)
+		fileName++;
+	else
+		fileName = fullPath;
+
+	fileNameLen = (uint32_t)strlen(fileName);
+	if (fileNameLen >= extLen)
+	{
+		sprintf(begStr, "%s.", ext);
+		if (!_strnicmp(begStr, fileName, extLen))
+			return true;
+
+		sprintf(endStr, ".%s", ext);
+		if (!_strnicmp(endStr, fileName + (fileNameLen - extLen), extLen))
+			return true;
+	}
+
+	return false;
+}
+
+void loadDroppedFile(char *fullPath, uint32_t fullPathLen, bool autoPlay, bool songModifiedCheck)
+{
+	bool isMod;
+	char *fileName, *ansiName;
+	uint8_t oldMode, oldPlayMode;
+	UNICHAR *fullPathU;
+
+	// don't allow drag n' drop if the tracker is busy
+	if (ui.pointerMode == POINTER_MODE_MSG1 || diskop.isFilling ||
+		editor.isWAVRendering || ui.samplerFiltersBoxShown || ui.samplerVolBoxShown)
+	{
+		return;
+	}
+
+	ansiName = (char *)calloc(fullPathLen + 10, sizeof (char));
+	if (ansiName == NULL)
+	{
+		statusOutOfMemory();
+		return;
+	}
+
+	fullPathU = (UNICHAR *)calloc(fullPathLen + 2, sizeof (UNICHAR));
+	if (fullPathU == NULL)
+	{
+		statusOutOfMemory();
+		return;
+	}
+
+#ifdef _WIN32
+	MultiByteToWideChar(CP_UTF8, 0, fullPath, -1, fullPathU, fullPathLen);
+#else
+	strcpy(fullPathU, fullPath);
+#endif
+
+	unicharToAnsi(ansiName, fullPathU, fullPathLen);
+
+	// make a new pointer point to filename (strip path)
+	fileName = strrchr(ansiName, DIR_DELIMITER);
+	if (fileName != NULL)
+		fileName++;
+	else
+		fileName = ansiName;
+
+	// check if the file extension is a module (FIXME: check module by content instead..?)
+	isMod = false;
+	     if (testExtension("MOD", 3, fileName)) isMod = true;
+	else if (testExtension("M15", 3, fileName)) isMod = true;
+	else if (testExtension("STK", 3, fileName)) isMod = true;
+	else if (testExtension("NST", 3, fileName)) isMod = true;
+	else if (testExtension("UST", 3, fileName)) isMod = true;
+	else if (testExtension("PP",  2, fileName)) isMod = true;
+	else if (testExtension("NT",  2, fileName)) isMod = true;
+
+	if (isMod)
+	{
+		if (songModifiedCheck && song->modified)
+		{
+			free(ansiName);
+			free(fullPathU);
+
+			memcpy(oldFullPath, fullPath, fullPathLen);
+			oldFullPath[fullPathLen+0] = 0;
+			oldFullPath[fullPathLen+1] = 0;
+
+			oldFullPathLen = fullPathLen;
+			oldAutoPlay = autoPlay;
+
+			// de-minimize window and set focus so that the user sees the message box
+			SDL_RestoreWindow(video.window);
+			SDL_RaiseWindow(video.window);
+
+			showSongUnsavedAskBox(ASK_DISCARD_SONG_DRAGNDROP);
+			return;
+		}
+
+		module_t *tempSong = modLoad(fullPathU);
+		if (tempSong != NULL)
+		{
+			oldMode = editor.currMode;
+			oldPlayMode = editor.playMode;
+
+			modStop();
+			modFree();
+
+			song = tempSong;
+			setupNewMod();
+			song->loaded = true;
+
+			statusAllRight();
+
+			if (autoPlay)
+			{
+				editor.playMode = PLAY_MODE_NORMAL;
+				editor.currMode = MODE_PLAY;
+
+				// start normal playback
+				modPlay(DONT_SET_PATTERN, 0, 0);
+
+				pointerSetMode(POINTER_MODE_PLAY, DO_CARRY);
+			}
+			else if (oldMode == MODE_PLAY || oldMode == MODE_RECORD)
+			{
+				// use last mode
+				editor.playMode = oldPlayMode;
+				if (oldPlayMode == PLAY_MODE_PATTERN || oldMode == MODE_RECORD)
+					modPlay(0, 0, 0);
+				else
+					modPlay(DONT_SET_PATTERN, 0, 0);
+				editor.currMode = oldMode;
+
+				if (oldMode == MODE_RECORD)
+					pointerSetMode(POINTER_MODE_RECORD, DO_CARRY);
+				else
+					pointerSetMode(POINTER_MODE_PLAY, DO_CARRY);
+			}
+			else
+			{
+				// stop playback
+				editor.playMode = PLAY_MODE_NORMAL;
+				editor.currMode = MODE_IDLE;
+				editor.songPlaying = false;
+				pointerSetMode(POINTER_MODE_IDLE, DO_CARRY);
+			}
+
+			displayMainScreen();
+		}
+		else
+		{
+			editor.errorMsgActive = true;
+			editor.errorMsgBlock = true;
+			editor.errorMsgCounter = 0;
+			setErrPointer(); // status/error message is set in the mod loader
+		}
+	}
+	else
+	{
+		loadSample(fullPathU, fileName);
+	}
+
+	free(ansiName);
+	free(fullPathU);
+}
+
+void loadDroppedFile2(void)
+{
+	loadDroppedFile(oldFullPath, oldFullPathLen, oldAutoPlay, false);
+}
+
+module_t *createNewMod(void)
+{
+	uint8_t i;
+	module_t *newMod;
+
+	newMod = (module_t *)calloc(1, sizeof (module_t));
+	if (newMod == NULL)
+		goto oom;
+
+	for (i = 0; i < MAX_PATTERNS; i++)
+	{
+		newMod->patterns[i] = (note_t *)calloc(1, MOD_ROWS * sizeof (note_t) * AMIGA_VOICES);
+		if (newMod->patterns[i] == NULL)
+			goto oom;
+	}
+
+	// +2 sample slots for overflow safety (Paula and scopes)
+	newMod->sampleData = (int8_t *)calloc(MOD_SAMPLES + 2, MAX_SAMPLE_LEN);
+	if (newMod->sampleData == NULL)
+		goto oom;
+
+	newMod->header.numOrders = 1;
+
+	for (i = 0; i < MOD_SAMPLES; i++)
+	{
+		newMod->samples[i].offset = MAX_SAMPLE_LEN * i;
+		newMod->samples[i].loopLength = 2;
+
+		// setup GUI text pointers
+		newMod->samples[i].volumeDisp = &newMod->samples[i].volume;
+		newMod->samples[i].lengthDisp = &newMod->samples[i].length;
+		newMod->samples[i].loopStartDisp = &newMod->samples[i].loopStart;
+		newMod->samples[i].loopLengthDisp = &newMod->samples[i].loopLength;
+	}
+
+	for (i = 0; i < AMIGA_VOICES; i++)
+		newMod->channels[i].n_chanindex = i;
+
+	// setup GUI text pointers
+	editor.currEditPatternDisp = &newMod->currPattern;
+	editor.currPosDisp = &newMod->currOrder;
+	editor.currPatternDisp = &newMod->header.order[0];
+	editor.currPosEdPattDisp = &newMod->header.order[0];
+	editor.currLengthDisp = &newMod->header.numOrders;
+
+	ui.updateSongSize = true;
+	return newMod;
+
+oom:
+	showErrorMsgBox("Out of memory!");
+	return NULL;
+}
--- /dev/null
+++ b/src/pt2_module_loader.h
@@ -1,0 +1,15 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+#include "pt2_header.h"
+#include "pt2_unicode.h"
+#include "pt2_structs.h"
+
+void showSongUnsavedAskBox(int8_t askScreenType);
+void loadModFromArg(char *arg);
+void loadDroppedFile(char *fullPath, uint32_t fullPathLen, bool autoPlay, bool songModifiedCheck);
+void loadDroppedFile2(void);
+module_t *createNewMod(void);
+module_t *modLoad(UNICHAR *fileName);
+void setupNewMod(void);
--- /dev/null
+++ b/src/pt2_module_saver.c
@@ -1,0 +1,235 @@
+// for finding memory leaks in debug mode with Visual Studio 
+#if defined _DEBUG && defined _MSC_VER
+#include <crtdbg.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <sys/stat.h>
+#include "pt2_mouse.h"
+#include "pt2_header.h"
+#include "pt2_textout.h"
+#include "pt2_helpers.h"
+#include "pt2_visuals.h"
+#include "pt2_sampler.h"
+#include "pt2_config.h"
+
+bool modSave(char *fileName)
+{
+	int32_t i, j;
+	FILE *f;
+
+	f = fopen(fileName, "wb");
+	if (f == NULL)
+	{
+		displayErrorMsg("FILE I/O ERROR !");
+		return false;
+	}
+
+	fwrite(song->header.name, 1, 20, f);
+
+	for (i = 0; i < MOD_SAMPLES; i++)
+	{
+		moduleSample_t *s = &song->samples[i];
+
+		fwrite(s->text, 1, 22, f);
+
+		uint16_t length = SWAP16(s->length >> 1);
+		fwrite(&length, sizeof (int16_t), 1, f);
+
+		fputc(s->fineTune & 0xF, f);
+		fputc(((uint8_t)s->volume > 64) ? 64 : s->volume, f);
+
+		uint16_t loopStart = s->loopStart;
+		uint16_t loopLength = s->loopLength;
+
+		if (loopLength < 2)
+			loopLength = 2;
+
+		if (loopStart+loopLength <= 2 || loopStart+loopLength > s->length)
+		{
+			loopStart = 0;
+			loopLength = 2;
+		}
+
+		loopLength = SWAP16(loopLength >> 1);
+		loopStart = SWAP16(loopStart >> 1);
+
+		fwrite(&loopStart, sizeof (int16_t), 1, f);
+		fwrite(&loopLength, sizeof (int16_t), 1, f);
+	}
+
+	fputc((uint8_t)song->header.numOrders, f);
+	fputc(0x7F, f); // ProTracker puts 0x7F at this place (restartPos/tempo in other trackers)
+
+	for (i = 0; i < MOD_ORDERS; i++)
+		fputc((uint8_t)song->header.order[i], f);
+
+	int32_t numPatterns = 0;
+	for (i = 0; i < MOD_ORDERS; i++)
+	{
+		if (song->header.order[i] > numPatterns)
+			numPatterns = song->header.order[i];
+	}
+
+	numPatterns++;
+	if (numPatterns > MAX_PATTERNS)
+		numPatterns = MAX_PATTERNS;
+
+	fwrite((numPatterns <= 64) ? "M.K." : "M!K!", 1, 4, f);
+
+	for (i = 0; i < numPatterns; i++)
+	{
+		note_t *note = song->patterns[i];
+		for (j = 0; j < MOD_ROWS * AMIGA_VOICES; j++, note++)
+		{
+			fputc((note->sample & 0xF0) | (note->period >> 8), f);
+			fputc(note->period & 0xFF, f);
+			fputc(((note->sample << 4) & 0xF0) | (note->command & 0x0F), f);
+			fputc(note->param, f);
+		}
+	}
+
+	for (i = 0; i < MOD_SAMPLES; i++)
+	{
+		moduleSample_t *s = &song->samples[i];
+		const int8_t *smpPtr8 = &song->sampleData[s->offset];
+
+		// clear first two bytes of non-looping samples (prevents stuck "BEEEEEP")
+		if (s->length >= 2 && s->loopStart+s->loopLength == 2)
+		{
+			fputc(0, f);
+			fputc(0, f);
+
+			fwrite(&smpPtr8[2], 1, s->length-2, f);
+		}
+		else
+		{
+			fwrite(smpPtr8, 1, s->length, f);
+		}
+	}
+
+	fclose(f);
+
+	displayMsg("MODULE SAVED !");
+	setMsgPointer();
+
+	diskop.cached = false;
+	if (ui.diskOpScreenShown)
+		ui.updateDiskOpFileList = true;
+
+	updateWindowTitle(MOD_NOT_MODIFIED);
+	return true;
+}
+
+bool saveModule(bool checkIfFileExist, bool giveNewFreeFilename)
+{
+	char fileName[128], tmpBuffer[64];
+	int32_t i, j;
+	struct stat statBuffer;
+
+	if (config.modDot)
+	{
+		// extension.filename
+		if (song->header.name[0] == '\0')
+		{
+			strcat(fileName, "mod.untitled");
+		}
+		else
+		{
+			strcat(fileName, "mod.");
+			for (i = 4; i < 20+4; i++)
+			{
+				fileName[i] = (char)tolower(song->header.name[i-4]);
+				if (fileName[i] == '\0') break;
+				sanitizeFilenameChar(&fileName[i]);
+			}
+		}
+	}
+	else
+	{
+		// filename.extension
+		if (song->header.name[0] == '\0')
+		{
+			strcat(fileName, "untitled.mod");
+		}
+		else
+		{
+			for (i = 0; i < 20; i++)
+			{
+				fileName[i] = (char)tolower(song->header.name[i]);
+				if (fileName[i] == '\0') break;
+				sanitizeFilenameChar(&fileName[i]);
+			}
+			strcat(fileName, ".mod");
+		}
+	}
+
+	if (giveNewFreeFilename && stat(fileName, &statBuffer) == 0)
+	{
+		for (i = 1; i <= 999; i++)
+		{
+			if (config.modDot)
+			{
+				// extension.filename
+				if (song->header.name[0] == '\0')
+				{
+					sprintf(fileName, "mod.untitled-%d", i);
+				}
+				else
+				{
+					for (j = 0; j < 20; j++)
+					{
+						tmpBuffer[j] = (char)tolower(song->header.name[j]);
+						if (tmpBuffer[j] == '\0') break;
+						sanitizeFilenameChar(&tmpBuffer[j]);
+					}
+					sprintf(fileName, "mod.%s-%d", tmpBuffer, i);
+				}
+			}
+			else
+			{
+				// filename.extension
+				if (song->header.name[0] == '\0')
+				{
+					sprintf(fileName, "untitled-%d.mod", i);
+				}
+				else
+				{
+					for (j = 0; j < 20; j++)
+					{
+						tmpBuffer[j] = (char)tolower(song->header.name[j]);
+						if (tmpBuffer[j] == '\0') break;
+						sanitizeFilenameChar(&tmpBuffer[j]);
+					}
+					sprintf(fileName, "%s-%d.mod", tmpBuffer, i);
+				}
+			}
+
+			if (stat(fileName, &statBuffer) != 0)
+				break; // this filename can be used
+		}
+	}
+
+	if (checkIfFileExist && stat(fileName, &statBuffer) == 0)
+	{
+		ui.askScreenShown = true;
+		ui.askScreenType = ASK_SAVEMOD_OVERWRITE;
+		pointerSetMode(POINTER_MODE_MSG1, NO_CARRY);
+		setStatusMessage("OVERWRITE FILE ?", NO_CARRY);
+		renderAskDialog();
+		return -1;
+	}
+
+	if (ui.askScreenShown)
+	{
+		ui.answerNo = false;
+		ui.answerYes = false;
+		ui.askScreenShown = false;
+	}
+
+	return modSave(fileName);
+}
+
--- /dev/null
+++ b/src/pt2_module_saver.h
@@ -1,0 +1,6 @@
+#pragma once
+
+#include <stdbool.h>
+
+bool saveModule(bool checkIfFileExist, bool giveNewFreeFilename);
+bool modSave(char *fileName);
--- a/src/pt2_mouse.c
+++ b/src/pt2_mouse.c
@@ -15,14 +15,16 @@
 #include "pt2_palette.h"
 #include "pt2_diskop.h"
 #include "pt2_sampler.h"
-#include "pt2_modloader.h"
+#include "pt2_module_loader.h"
 #include "pt2_edit.h"
-#include "pt2_sampleloader.h"
+#include "pt2_sample_loader.h"
 #include "pt2_visuals.h"
 #include "pt2_tables.h"
 #include "pt2_audio.h"
 #include "pt2_textout.h"
 #include "pt2_keyboard.h"
+#include "pt2_config.h"
+#include "pt2_bmp.h"
 
 /* TODO: Move irrelevant routines outta here! Disgusting design!
 ** Keep in mind that this was programmed in my early programming days...
@@ -574,8 +576,8 @@
 		}
 	}
 
-	if (editor.samplePos > modEntry->samples[editor.currSample].length)
-		editor.samplePos = modEntry->samples[editor.currSample].length;
+	if (editor.samplePos > song->samples[editor.currSample].length)
+		editor.samplePos = song->samples[editor.currSample].length;
 
 	ui.updatePosText = true;
 }
@@ -712,12 +714,12 @@
 
 void sampleFineTuneUpButton(void)
 {
-	int8_t finetune = modEntry->samples[editor.currSample].fineTune & 0xF;
+	int8_t finetune = song->samples[editor.currSample].fineTune & 0xF;
 	if (finetune != 7)
-		modEntry->samples[editor.currSample].fineTune = (finetune + 1) & 0xF;
+		song->samples[editor.currSample].fineTune = (finetune + 1) & 0xF;
 
 	if (mouse.rightButtonPressed)
-		modEntry->samples[editor.currSample].fineTune = 0;
+		song->samples[editor.currSample].fineTune = 0;
 
 	recalcChordLength();
 	ui.updateCurrSampleFineTune = true;
@@ -725,12 +727,12 @@
 
 void sampleFineTuneDownButton(void)
 {
-	int8_t finetune = modEntry->samples[editor.currSample].fineTune & 0xF;
+	int8_t finetune = song->samples[editor.currSample].fineTune & 0xF;
 	if (finetune != 8)
-		modEntry->samples[editor.currSample].fineTune = (finetune - 1) & 0xF;
+		song->samples[editor.currSample].fineTune = (finetune - 1) & 0xF;
 
 	if (mouse.rightButtonPressed)
-		modEntry->samples[editor.currSample].fineTune = 0;
+		song->samples[editor.currSample].fineTune = 0;
 
 	recalcChordLength();
 	ui.updateCurrSampleFineTune = true;
@@ -738,7 +740,7 @@
 
 void sampleVolumeUpButton(void)
 {
-	int8_t val = modEntry->samples[editor.currSample].volume;
+	int8_t val = song->samples[editor.currSample].volume;
 
 	if (mouse.rightButtonPressed)
 		val += 16;
@@ -748,13 +750,13 @@
 	if (val > 64)
 		val = 64;
 
-	modEntry->samples[editor.currSample].volume = (uint8_t)val;
+	song->samples[editor.currSample].volume = (uint8_t)val;
 	ui.updateCurrSampleVolume = true;
 }
 
 void sampleVolumeDownButton(void)
 {
-	int8_t val = modEntry->samples[editor.currSample].volume;
+	int8_t val = song->samples[editor.currSample].volume;
 
 	if (mouse.rightButtonPressed)
 		val -= 16;
@@ -764,7 +766,7 @@
 	if (val < 0)
 		val = 0;
 
-	modEntry->samples[editor.currSample].volume = (uint8_t)val;
+	song->samples[editor.currSample].volume = (uint8_t)val;
 	ui.updateCurrSampleVolume = true;
 }
 
@@ -772,10 +774,10 @@
 {
 	int32_t val;
 
-	if (modEntry->samples[editor.currSample].length == MAX_SAMPLE_LEN)
+	if (song->samples[editor.currSample].length == MAX_SAMPLE_LEN)
 		return;
 
-	val = modEntry->samples[editor.currSample].length;
+	val = song->samples[editor.currSample].length;
 	if (mouse.rightButtonPressed)
 	{
 		if (fast)
@@ -794,7 +796,7 @@
 	if (val > MAX_SAMPLE_LEN)
 		val = MAX_SAMPLE_LEN;
 
-	modEntry->samples[editor.currSample].length = (uint16_t)val;
+	song->samples[editor.currSample].length = (uint16_t)val;
 	ui.updateCurrSampleLength = true;
 }
 
@@ -803,7 +805,7 @@
 	int32_t val;
 	moduleSample_t *s;
 
-	s = &modEntry->samples[editor.currSample];
+	s = &song->samples[editor.currSample];
 	if (s->loopStart+s->loopLength > 2)
 	{
 		if (s->length == s->loopStart+s->loopLength)
@@ -815,7 +817,7 @@
 			return;
 	}
 
-	val = modEntry->samples[editor.currSample].length;
+	val = song->samples[editor.currSample].length;
 	if (mouse.rightButtonPressed)
 	{
 		if (fast)
@@ -849,13 +851,13 @@
 {
 	int32_t val, loopLen, len;
 
-	val = modEntry->samples[editor.currSample].loopStart;
-	loopLen = modEntry->samples[editor.currSample].loopLength;
-	len = modEntry->samples[editor.currSample].length;
+	val = song->samples[editor.currSample].loopStart;
+	loopLen = song->samples[editor.currSample].loopLength;
+	len = song->samples[editor.currSample].length;
 
 	if (len == 0)
 	{
-		modEntry->samples[editor.currSample].loopStart = 0;
+		song->samples[editor.currSample].loopStart = 0;
 		return;
 	}
 
@@ -877,7 +879,7 @@
 	if (val > len-loopLen)
 		val = len-loopLen;
 
-	modEntry->samples[editor.currSample].loopStart = (uint16_t)val;
+	song->samples[editor.currSample].loopStart = (uint16_t)val;
 	ui.updateCurrSampleRepeat = true;
 
 	mixerUpdateLoops();
@@ -893,12 +895,12 @@
 {
 	int32_t val, len;
 
-	val = modEntry->samples[editor.currSample].loopStart;
-	len = modEntry->samples[editor.currSample].length;
+	val = song->samples[editor.currSample].loopStart;
+	len = song->samples[editor.currSample].length;
 
 	if (len == 0)
 	{
-		modEntry->samples[editor.currSample].loopStart = 0;
+		song->samples[editor.currSample].loopStart = 0;
 		return;
 	}
 
@@ -920,7 +922,7 @@
 	if (val < 0)
 		val = 0;
 
-	modEntry->samples[editor.currSample].loopStart = (uint16_t)val;
+	song->samples[editor.currSample].loopStart = (uint16_t)val;
 	ui.updateCurrSampleRepeat = true;
 
 	mixerUpdateLoops();
@@ -936,13 +938,13 @@
 {
 	int32_t val, loopStart, len;
 
-	val = modEntry->samples[editor.currSample].loopLength;
-	loopStart = modEntry->samples[editor.currSample].loopStart;
-	len = modEntry->samples[editor.currSample].length;
+	val = song->samples[editor.currSample].loopLength;
+	loopStart = song->samples[editor.currSample].loopStart;
+	len = song->samples[editor.currSample].length;
 
 	if (len == 0)
 	{
-		modEntry->samples[editor.currSample].loopLength = 2;
+		song->samples[editor.currSample].loopLength = 2;
 		return;
 	}
 
@@ -964,7 +966,7 @@
 	if (val > len-loopStart)
 		val = len-loopStart;
 
-	modEntry->samples[editor.currSample].loopLength = (uint16_t)val;
+	song->samples[editor.currSample].loopLength = (uint16_t)val;
 	ui.updateCurrSampleReplen = true;
 
 	mixerUpdateLoops();
@@ -980,12 +982,12 @@
 {
 	int32_t val, len;
 
-	val = modEntry->samples[editor.currSample].loopLength;
-	len = modEntry->samples[editor.currSample].length;
+	val = song->samples[editor.currSample].loopLength;
+	len = song->samples[editor.currSample].length;
 
 	if (len == 0)
 	{
-		modEntry->samples[editor.currSample].loopLength = 2;
+		song->samples[editor.currSample].loopLength = 2;
 		return;
 	}
 
@@ -1007,7 +1009,7 @@
 	if (val < 2)
 		val = 2;
 
-	modEntry->samples[editor.currSample].loopLength = (uint16_t)val;
+	song->samples[editor.currSample].loopLength = (uint16_t)val;
 	ui.updateCurrSampleReplen = true;
 
 	mixerUpdateLoops();
@@ -1026,7 +1028,7 @@
 	if (editor.timingMode == TEMPO_MODE_VBLANK)
 		return;
 
-	val = modEntry->currBPM;
+	val = song->currBPM;
 	if (mouse.rightButtonPressed)
 		val += 10;
 	else
@@ -1035,8 +1037,8 @@
 	if (val > 255)
 		val = 255;
 
-	modEntry->currBPM = val;
-	modSetTempo(modEntry->currBPM);
+	song->currBPM = val;
+	modSetTempo(song->currBPM);
 	ui.updateSongBPM = true;
 }
 
@@ -1047,7 +1049,7 @@
 	if (editor.timingMode == TEMPO_MODE_VBLANK)
 		return;
 
-	val = modEntry->currBPM;
+	val = song->currBPM;
 	if (mouse.rightButtonPressed)
 		val -= 10;
 	else
@@ -1056,8 +1058,8 @@
 	if (val < 32)
 		val = 32;
 
-	modEntry->currBPM = val;
-	modSetTempo(modEntry->currBPM);
+	song->currBPM = val;
+	modSetTempo(song->currBPM);
 	ui.updateSongBPM = true;
 }
 
@@ -1065,7 +1067,7 @@
 {
 	int16_t val;
 
-	val = modEntry->head.orderCount;
+	val = song->header.numOrders;
 	if (mouse.rightButtonPressed)
 		val += 10;
 	else
@@ -1074,19 +1076,19 @@
 	if (val > 128)
 		val = 128;
 
-	modEntry->head.orderCount = (uint8_t)val;
+	song->header.numOrders = (uint8_t)val;
 
-	val = modEntry->currOrder;
-	if (val > modEntry->head.orderCount-1)
-		val = modEntry->head.orderCount-1;
+	val = song->currOrder;
+	if (val > song->header.numOrders-1)
+		val = song->header.numOrders-1;
 
-	editor.currPosEdPattDisp = &modEntry->head.order[val];
+	editor.currPosEdPattDisp = &song->header.order[val];
 	ui.updateSongLength = true;
 }
 
 void songLengthDownButton(void)
 {
-	int16_t val = modEntry->head.orderCount;
+	int16_t val = song->header.numOrders;
 
 	if (mouse.rightButtonPressed)
 		val -= 10;
@@ -1096,19 +1098,19 @@
 	if (val < 1)
 		val = 1;
 
-	modEntry->head.orderCount = (uint8_t)val;
+	song->header.numOrders = (uint8_t)val;
 
-	val = modEntry->currOrder;
-	if (val > modEntry->head.orderCount-1)
-		val = modEntry->head.orderCount-1;
+	val = song->currOrder;
+	if (val > song->header.numOrders-1)
+		val = song->header.numOrders-1;
 
-	editor.currPosEdPattDisp = &modEntry->head.order[val];
+	editor.currPosEdPattDisp = &song->header.order[val];
 	ui.updateSongLength = true;
 }
 
 void patternUpButton(void)
 {
-	int16_t val = modEntry->head.order[modEntry->currOrder];
+	int16_t val = song->header.order[song->currOrder];
 
 	if (mouse.rightButtonPressed)
 		val += 10;
@@ -1118,7 +1120,7 @@
 	if (val > MAX_PATTERNS-1)
 		val = MAX_PATTERNS-1;
 
-	modEntry->head.order[modEntry->currOrder] = (uint8_t)val;
+	song->header.order[song->currOrder] = (uint8_t)val;
 
 	if (ui.posEdScreenShown)
 		ui.updatePosEd = true;
@@ -1128,7 +1130,7 @@
 
 void patternDownButton(void)
 {
-	int16_t val = modEntry->head.order[modEntry->currOrder];
+	int16_t val = song->header.order[song->currOrder];
 
 	if (mouse.rightButtonPressed)
 		val -= 10;
@@ -1138,7 +1140,7 @@
 	if (val < 0)
 		val = 0;
 
-	modEntry->head.order[modEntry->currOrder] = (uint8_t)val;
+	song->header.order[song->currOrder] = (uint8_t)val;
 
 	if (ui.posEdScreenShown)
 		ui.updatePosEd = true;
@@ -1148,7 +1150,7 @@
 
 void positionUpButton(void)
 {
-	int16_t val = modEntry->currOrder;
+	int16_t val = song->currOrder;
 
 	if (mouse.rightButtonPressed)
 		val += 10;
@@ -1163,7 +1165,7 @@
 
 void positionDownButton(void)
 {
-	int16_t val = modEntry->currOrder;
+	int16_t val = song->currOrder;
 
 	if (mouse.rightButtonPressed)
 		val -= 10;
@@ -1321,14 +1323,20 @@
 			// NORMALIZE
 			if (mouse.x >= 101 && mouse.x <= 143)
 			{
-				s = &modEntry->samples[editor.currSample];
+				if (editor.sampleZero)
+				{
+					statusNotSampleZero();
+					return;
+				}
+
+				s = &song->samples[editor.currSample];
 				if (s->length == 0)
 				{
-					displayErrorMsg("SAMPLE IS EMPTY");
+					statusSampleIsEmpty();
 					return;
 				}
 
-				sampleData = &modEntry->sampleData[s->offset];
+				sampleData = &song->sampleData[s->offset];
 				if (editor.markStartOfs != -1)
 				{
 					sampleData += editor.markStartOfs;
@@ -1424,10 +1432,16 @@
 			// RAMP
 			else if (mouse.x >= 72 && mouse.x <= 100)
 			{
-				s = &modEntry->samples[editor.currSample];
+				if (editor.sampleZero)
+				{
+					statusNotSampleZero();
+					return;
+				}
+
+				s = &song->samples[editor.currSample];
 				if (s->length == 0)
 				{
-					displayErrorMsg("SAMPLE IS EMPTY");
+					statusSampleIsEmpty();
 					return;
 				}
 
@@ -1438,7 +1452,7 @@
 					return;
 				}
 
-				sampleData = &modEntry->sampleData[s->offset];
+				sampleData = &song->sampleData[s->offset];
 				if (editor.markStartOfs != -1)
 				{
 					sampleData += editor.markStartOfs;
@@ -1674,14 +1688,20 @@
 	// UNDO
 	if (mouse.x >= 65 && mouse.x <= 75 && mouse.y >= 154 && mouse.y <= 184)
 	{
-		s = &modEntry->samples[editor.currSample];
+		if (editor.sampleZero)
+		{
+			statusNotSampleZero();
+			return;
+		}
+
+		s = &song->samples[editor.currSample];
 		if (s->length == 0)
 		{
-			displayErrorMsg("SAMPLE IS EMPTY");
+			statusSampleIsEmpty();
 		}
 		else
 		{
-			memcpy(&modEntry->sampleData[s->offset], editor.tempSample, MAX_SAMPLE_LEN);
+			memcpy(&song->sampleData[s->offset], editor.tempSample, MAX_SAMPLE_LEN);
 			redrawSample();
 			updateWindowTitle(MOD_IS_MODIFIED);
 			renderSamplerFiltersBox();
@@ -2053,9 +2073,9 @@
 				ui.updateDiskOpFileList = true;
 			}
 		}
-		else if (ui.posEdScreenShown && modEntry->currOrder > 0)
+		else if (ui.posEdScreenShown && song->currOrder > 0)
 		{
-			modSetPos(modEntry->currOrder - 1, DONT_SET_ROW);
+			modSetPos(song->currOrder - 1, DONT_SET_ROW);
 		}
 	}
 	else if (ui.samplerScreenShown)
@@ -2062,9 +2082,9 @@
 	{
 		samplerZoomInMouseWheel(); // lower part of screen
 	}
-	else if (!editor.songPlaying && modEntry->currRow > 0)
+	else if (!editor.songPlaying && song->currRow > 0)
 	{
-		modSetPos(DONT_SET_ORDER, modEntry->currRow - 1); // pattern data
+		modSetPos(DONT_SET_ORDER, song->currRow - 1); // pattern data
 	}
 }
 
@@ -2084,9 +2104,9 @@
 				ui.updateDiskOpFileList = true;
 			}
 		}
-		else if (ui.posEdScreenShown && modEntry->currOrder < modEntry->head.orderCount-1)
+		else if (ui.posEdScreenShown && song->currOrder < song->header.numOrders-1)
 		{
-			modSetPos(modEntry->currOrder + 1, DONT_SET_ROW);
+			modSetPos(song->currOrder + 1, DONT_SET_ROW);
 		}
 	}
 	else if (ui.samplerScreenShown)
@@ -2093,9 +2113,9 @@
 	{
 		samplerZoomOutMouseWheel(); // lower part of screen
 	}
-	else if (!editor.songPlaying && modEntry->currRow < MOD_ROWS)
+	else if (!editor.songPlaying && song->currRow < MOD_ROWS)
 	{
-		modSetPos(DONT_SET_ORDER, modEntry->currRow + 1); // pattern data
+		modSetPos(DONT_SET_ORDER, song->currRow + 1); // pattern data
 	}
 }
 
@@ -2371,7 +2391,7 @@
 			if (editor.songPlaying)
 				sprintf(pat2SmpText, "ROW 00 TO SMP %02X?", editor.currSample + 1);
 			else
-				sprintf(pat2SmpText, "ROW %02d TO SMP %02X?", modEntry->currRow, editor.currSample + 1);
+				sprintf(pat2SmpText, "ROW %02d TO SMP %02X?", song->currRow, editor.currSample + 1);
 
 			setStatusMessage(pat2SmpText, NO_CARRY);
 			renderAskDialog();
@@ -2591,10 +2611,16 @@
 			}
 			else
 			{
-				s = &modEntry->samples[editor.currSample];
+				if (editor.sampleZero)
+				{
+					statusNotSampleZero();
+					break;
+				}
+
+				s = &song->samples[editor.currSample];
 				if (s->length == 0)
 				{
-					displayErrorMsg("SAMPLE IS EMPTY");
+					statusSampleIsEmpty();
 					break;
 				}
 
@@ -2611,10 +2637,10 @@
 					return true;
 				}
 
-				memcpy(ptr8_1, &modEntry->sampleData[s->offset], MAX_SAMPLE_LEN);
+				memcpy(ptr8_1, &song->sampleData[s->offset], MAX_SAMPLE_LEN);
 
-				ptr8_2 = &modEntry->sampleData[s->offset+editor.samplePos];
-				ptr8_3 = &modEntry->sampleData[s->offset+s->length-1];
+				ptr8_2 = &song->sampleData[s->offset+editor.samplePos];
+				ptr8_3 = &song->sampleData[s->offset+s->length-1];
 				ptr8_4 = ptr8_1;
 
 				editor.modulateOffset = 0;
@@ -2637,12 +2663,12 @@
 					{
 						editor.modulatePos += editor.modulateSpeed;
 
-						modTmp = (editor.modulatePos / 4096) & 0xFF;
-						modDat = vibratoTable[modTmp & 0x1F] / 4;
+						modTmp = (editor.modulatePos >> 12) & 0xFF;
+						modDat = vibratoTable[modTmp & 0x1F] >> 2;
 						modPos = ((modTmp & 32) ? (editor.modulateOffset - modDat) : (editor.modulateOffset + modDat)) + 2048;
 
 						editor.modulateOffset = modPos;
-						modPos /= 2048;
+						modPos >>= 11;
 						modPos = CLAMP(modPos, 0, s->length - 1);
 						ptr8_1 = &ptr8_4[modPos];
 					}
@@ -2661,10 +2687,16 @@
 
 		case PTB_EO_ECHO:
 		{
-			s = &modEntry->samples[editor.currSample];
+			if (editor.sampleZero)
+			{
+				statusNotSampleZero();
+				break;
+			}
+
+			s = &song->samples[editor.currSample];
 			if (s->length == 0)
 			{
-				displayErrorMsg("SAMPLE IS EMPTY");
+				statusSampleIsEmpty();
 				break;
 			}
 
@@ -2680,8 +2712,8 @@
 				break;
 			}
 
-			ptr8_1 = &modEntry->sampleData[s->offset+editor.samplePos];
-			ptr8_2 = &modEntry->sampleData[s->offset];
+			ptr8_1 = &song->sampleData[s->offset+editor.samplePos];
+			ptr8_2 = &song->sampleData[s->offset];
 			ptr8_3 = ptr8_2;
 
 			editor.modulateOffset = 0;
@@ -2702,12 +2734,12 @@
 				{
 					editor.modulatePos += editor.modulateSpeed;
 
-					modTmp = (editor.modulatePos / 4096) & 0xFF;
-					modDat = vibratoTable[modTmp & 0x1F] / 4;
+					modTmp = (editor.modulatePos >> 12) & 0xFF;
+					modDat = vibratoTable[modTmp & 0x1F] >> 2;
 					modPos = ((modTmp & 32) ? (editor.modulateOffset - modDat) : (editor.modulateOffset + modDat)) + 2048;
 
 					editor.modulateOffset = modPos;
-					modPos /= 2048;
+					modPos >>= 11;
 					modPos = CLAMP(modPos, 0, s->length - 1);
 					ptr8_2 = &ptr8_3[modPos];
 				}
@@ -2756,10 +2788,16 @@
 
 		case PTB_EO_BOOST: // this is actually treble increase
 		{
-			s = &modEntry->samples[editor.currSample];
+			if (editor.sampleZero)
+			{
+				statusNotSampleZero();
+				break;
+			}
+
+			s = &song->samples[editor.currSample];
 			if (s->length == 0)
 			{
-				displayErrorMsg("SAMPLE IS EMPTY");
+				statusSampleIsEmpty();
 				break;
 			}
 
@@ -2773,10 +2811,16 @@
 
 		case PTB_EO_FILTER: // this is actually treble decrease
 		{
-			s = &modEntry->samples[editor.currSample];
+			if (editor.sampleZero)
+			{
+				statusNotSampleZero();
+				break;
+			}
+
+			s = &song->samples[editor.currSample];
 			if (s->length == 0)
 			{
-				displayErrorMsg("SAMPLE IS EMPTY");
+				statusSampleIsEmpty();
 				break;
 			}
 
@@ -2800,10 +2844,16 @@
 
 		case PTB_EO_MOD:
 		{
-			s = &modEntry->samples[editor.currSample];
+			if (editor.sampleZero)
+			{
+				statusNotSampleZero();
+				break;
+			}
+
+			s = &song->samples[editor.currSample];
 			if (s->length == 0)
 			{
-				displayErrorMsg("SAMPLE IS EMPTY");
+				statusSampleIsEmpty();
 				break;
 			}
 
@@ -2813,7 +2863,7 @@
 				break;
 			}
 
-			ptr8_1 = &modEntry->sampleData[s->offset];
+			ptr8_1 = &song->sampleData[s->offset];
 
 			ptr8_3 = (int8_t *)malloc(MAX_SAMPLE_LEN);
 			if (ptr8_3 == NULL)
@@ -2835,13 +2885,13 @@
 
 				editor.modulatePos += editor.modulateSpeed;
 
-				modTmp = (editor.modulatePos / 4096) & 0xFF;
-				modDat = vibratoTable[modTmp & 0x1F] / 4;
+				modTmp = (editor.modulatePos >> 12) & 0xFF;
+				modDat = vibratoTable[modTmp & 0x1F] >> 2;
 				modPos = ((modTmp & 32) ? (editor.modulateOffset - modDat) : (editor.modulateOffset + modDat)) + 2048;
 
 				editor.modulateOffset = modPos;
 
-				modPos /= 2048;
+				modPos >>= 11;
 				modPos = CLAMP(modPos, 0, s->length - 1);
 				ptr8_2 = &ptr8_3[modPos];
 			}
@@ -2861,16 +2911,22 @@
 
 		case PTB_EO_X_FADE:
 		{
-			s = &modEntry->samples[editor.currSample];
+			if (editor.sampleZero)
+			{
+				statusNotSampleZero();
+				break;
+			}
 
+			s = &song->samples[editor.currSample];
+
 			if (s->length == 0)
 			{
-				displayErrorMsg("SAMPLE IS EMPTY");
+				statusSampleIsEmpty();
 				break;
 			}
 
-			ptr8_1 = &modEntry->sampleData[s->offset];
-			ptr8_2 = &modEntry->sampleData[s->offset+s->length-1];
+			ptr8_1 = &song->sampleData[s->offset];
+			ptr8_2 = &song->sampleData[s->offset+s->length-1];
 
 			do
 			{
@@ -2896,22 +2952,28 @@
 
 		case PTB_EO_BACKWD:
 		{
-			s = &modEntry->samples[editor.currSample];
+			if (editor.sampleZero)
+			{
+				statusNotSampleZero();
+				break;
+			}
+
+			s = &song->samples[editor.currSample];
 			if (s->length == 0)
 			{
-				displayErrorMsg("SAMPLE IS EMPTY");
+				statusSampleIsEmpty();
 				break;
 			}
 
 			if (editor.markStartOfs != -1 && editor.markStartOfs != editor.markEndOfs && editor.markEndOfs != 0)
 			{
-				ptr8_1 = &modEntry->sampleData[s->offset+editor.markStartOfs];
-				ptr8_2 = &modEntry->sampleData[s->offset+editor.markEndOfs-1];
+				ptr8_1 = &song->sampleData[s->offset+editor.markStartOfs];
+				ptr8_2 = &song->sampleData[s->offset+editor.markEndOfs-1];
 			}
 			else
 			{
-				ptr8_1 = &modEntry->sampleData[s->offset];
-				ptr8_2 = &modEntry->sampleData[s->offset+s->length-1];
+				ptr8_1 = &song->sampleData[s->offset];
+				ptr8_2 = &song->sampleData[s->offset+s->length-1];
 			}
 
 			do
@@ -2932,10 +2994,16 @@
 
 		case PTB_EO_CB:
 		{
-			s = &modEntry->samples[editor.currSample];
+			if (editor.sampleZero)
+			{
+				statusNotSampleZero();
+				break;
+			}
+
+			s = &song->samples[editor.currSample];
 			if (s->length == 0)
 			{
-				displayErrorMsg("SAMPLE IS EMPTY");
+				statusSampleIsEmpty();
 				break;
 			}
 
@@ -2953,8 +3021,8 @@
 
 			turnOffVoices();
 
-			memcpy(&modEntry->sampleData[s->offset], &modEntry->sampleData[s->offset + editor.samplePos], MAX_SAMPLE_LEN - editor.samplePos);
-			memset(&modEntry->sampleData[s->offset + (MAX_SAMPLE_LEN - editor.samplePos)], 0, editor.samplePos);
+			memcpy(&song->sampleData[s->offset], &song->sampleData[s->offset + editor.samplePos], MAX_SAMPLE_LEN - editor.samplePos);
+			memset(&song->sampleData[s->offset + (MAX_SAMPLE_LEN - editor.samplePos)], 0, editor.samplePos);
 
 			if (editor.samplePos > s->loopStart)
 			{
@@ -2986,10 +3054,16 @@
 		// fade up
 		case PTB_EO_FU:
 		{
-			s = &modEntry->samples[editor.currSample];
+			if (editor.sampleZero)
+			{
+				statusNotSampleZero();
+				break;
+			}
+
+			s = &song->samples[editor.currSample];
 			if (s->length == 0)
 			{
-				displayErrorMsg("SAMPLE IS EMPTY");
+				statusSampleIsEmpty();
 				break;
 			}
 
@@ -3001,7 +3075,7 @@
 
 			double dSamplePosMul = 1.0 / editor.samplePos;
 
-			ptr8_1 = &modEntry->sampleData[s->offset];
+			ptr8_1 = &song->sampleData[s->offset];
 			for (j = 0; j < editor.samplePos; j++)
 			{
 				dSmp = ((*ptr8_1) * j) * dSamplePosMul;
@@ -3021,10 +3095,16 @@
 		// fade down
 		case PTB_EO_FD:
 		{
-			s = &modEntry->samples[editor.currSample];
+			if (editor.sampleZero)
+			{
+				statusNotSampleZero();
+				break;
+			}
+
+			s = &song->samples[editor.currSample];
 			if (s->length == 0)
 			{
-				displayErrorMsg("SAMPLE IS EMPTY");
+				statusSampleIsEmpty();
 				break;
 			}
 
@@ -3040,13 +3120,17 @@
 
 			double dSampleMul = 1.0 / tmp32;
 
-			ptr8_1 = &modEntry->sampleData[s->offset+s->length-1];
+			ptr8_1 = &song->sampleData[s->offset+s->length-1];
+
+			int32_t idx = 0;
 			for (j = editor.samplePos; j < s->length; j++)
 			{
-				dSmp = ((*ptr8_1) * (j - editor.samplePos)) * dSampleMul;
+				dSmp = ((*ptr8_1) * idx) * dSampleMul;
 				smp32 = (int32_t)dSmp;
 				CLAMP8(smp32);
 				*ptr8_1-- = (int8_t)smp32;
+
+				idx++;
 			}
 
 			fixSampleBeep(s);
@@ -3059,10 +3143,10 @@
 
 		case PTB_EO_UPSAMP:
 		{
-			s = &modEntry->samples[editor.currSample];
+			s = &song->samples[editor.currSample];
 			if (s->length == 0)
 			{
-				displayErrorMsg("SAMPLE IS EMPTY");
+				statusSampleIsEmpty();
 				break;
 			}
 
@@ -3076,10 +3160,10 @@
 
 		case PTB_EO_DNSAMP:
 		{
-			s = &modEntry->samples[editor.currSample];
+			s = &song->samples[editor.currSample];
 			if (s->length == 0)
 			{
-				displayErrorMsg("SAMPLE IS EMPTY");
+				statusSampleIsEmpty();
 				break;
 			}
 
@@ -3112,21 +3196,27 @@
 
 		case PTB_EO_VOL:
 		{
-			s = &modEntry->samples[editor.currSample];
+			if (editor.sampleZero)
+			{
+				statusNotSampleZero();
+				break;
+			}
+
+			s = &song->samples[editor.currSample];
 			if (s->length == 0)
 			{
-				displayErrorMsg("SAMPLE IS EMPTY");
+				statusSampleIsEmpty();
 				break;
 			}
 
 			if (editor.sampleVol != 100)
 			{
-				ptr8_1 = &modEntry->sampleData[modEntry->samples[editor.currSample].offset];
-				double dSampleMul = editor.sampleVol / 100.0;
+				ptr8_1 = &song->sampleData[s->offset];
+				int32_t sampleMul = (((1UL << 19) * editor.sampleVol) + 50) / 100;
 
 				for (j = 0; j < s->length; j++)
 				{
-					tmp16 = (int16_t)(ptr8_1[j] * dSampleMul);
+					tmp16 = (ptr8_1[j] * sampleMul) >> 19;
 					CLAMP8(tmp16);
 					ptr8_1[j] = (int8_t)tmp16;
 				}
@@ -3442,7 +3532,7 @@
 
 		case PTB_EO_LENGTH:
 		{
-			if (modEntry->samples[editor.currSample].loopLength == 2 && modEntry->samples[editor.currSample].loopStart == 0)
+			if (song->samples[editor.currSample].loopLength == 2 && song->samples[editor.currSample].loopStart == 0)
 			{
 				editor.chordLengthMin = mouse.rightButtonPressed ? true : false;
 				recalcChordLength();
@@ -3497,11 +3587,11 @@
 		{
 			if (editor.currMode == MODE_IDLE || editor.currMode == MODE_EDIT)
 			{
-				ui.tmpDisp16 = modEntry->currOrder;
-				if (ui.tmpDisp16 > modEntry->head.orderCount-1)
-					ui.tmpDisp16 = modEntry->head.orderCount-1;
+				ui.tmpDisp16 = song->currOrder;
+				if (ui.tmpDisp16 > song->header.numOrders-1)
+					ui.tmpDisp16 = song->header.numOrders-1;
 
-				ui.tmpDisp16 = modEntry->head.order[ui.tmpDisp16];
+				ui.tmpDisp16 = song->header.order[ui.tmpDisp16];
 				editor.currPosEdPattDisp = &ui.tmpDisp16;
 				ui.numPtr16 = &ui.tmpDisp16;
 				ui.numLen = 2;
@@ -3513,7 +3603,7 @@
 
 		case PTB_PE_SCROLLTOP:
 		{
-			if (modEntry->currOrder != 0)
+			if (song->currOrder != 0)
 				modSetPos(0, DONT_SET_ROW);
 		}
 		break;
@@ -3520,22 +3610,22 @@
 
 		case PTB_PE_SCROLLUP:
 		{
-			if (modEntry->currOrder > 0)
-				modSetPos(modEntry->currOrder - 1, DONT_SET_ROW);
+			if (song->currOrder > 0)
+				modSetPos(song->currOrder - 1, DONT_SET_ROW);
 		}
 		break;
 
 		case PTB_PE_SCROLLDOWN:
 		{
-			if (modEntry->currOrder < modEntry->head.orderCount-1)
-				modSetPos(modEntry->currOrder + 1, DONT_SET_ROW);
+			if (song->currOrder < song->header.numOrders-1)
+				modSetPos(song->currOrder + 1, DONT_SET_ROW);
 		}
 		break;
 
 		case PTB_PE_SCROLLBOT:
 		{
-			if (modEntry->currOrder != modEntry->head.orderCount-1)
-				modSetPos(modEntry->head.orderCount - 1, DONT_SET_ROW);
+			if (song->currOrder != song->header.numOrders-1)
+				modSetPos(song->header.numOrders - 1, DONT_SET_ROW);
 		}
 		break;
 
@@ -3569,8 +3659,8 @@
 			{
 				if (mouse.rightButtonPressed)
 				{
-					modEntry->currOrder = 0;
-					editor.currPatternDisp = &modEntry->head.order[modEntry->currOrder];
+					song->currOrder = 0;
+					editor.currPatternDisp = &song->header.order[song->currOrder];
 
 					if (ui.posEdScreenShown)
 						ui.updatePosEd = true;
@@ -3577,7 +3667,7 @@
 				}
 				else
 				{
-					ui.tmpDisp16 = modEntry->currOrder;
+					ui.tmpDisp16 = song->currOrder;
 					editor.currPosDisp = &ui.tmpDisp16;
 					ui.numPtr16 = &ui.tmpDisp16;
 					ui.numLen = 3;
@@ -3594,7 +3684,7 @@
 			{
 				if (mouse.rightButtonPressed)
 				{
-					modEntry->head.order[modEntry->currOrder] = 0;
+					song->header.order[song->currOrder] = 0;
 
 					ui.updateSongSize = true;
 					updateWindowTitle(MOD_IS_MODIFIED);
@@ -3604,7 +3694,7 @@
 				}
 				else
 				{
-					ui.tmpDisp16 = modEntry->head.order[modEntry->currOrder];
+					ui.tmpDisp16 = song->header.order[song->currOrder];
 					editor.currPatternDisp = &ui.tmpDisp16;
 					ui.numPtr16 = &ui.tmpDisp16;
 					ui.numLen = 2;
@@ -3621,13 +3711,13 @@
 			{
 				if (mouse.rightButtonPressed)
 				{
-					modEntry->head.orderCount = 1;
+					song->header.numOrders = 1;
 
-					tmp16 = modEntry->currOrder;
-					if (tmp16 > modEntry->head.orderCount-1)
-						tmp16 = modEntry->head.orderCount-1;
+					tmp16 = song->currOrder;
+					if (tmp16 > song->header.numOrders-1)
+						tmp16 = song->header.numOrders-1;
 
-					editor.currPosEdPattDisp = &modEntry->head.order[tmp16];
+					editor.currPosEdPattDisp = &song->header.order[tmp16];
 
 					ui.updateSongSize = true;
 					updateWindowTitle(MOD_IS_MODIFIED);
@@ -3637,7 +3727,7 @@
 				}
 				else
 				{
-					ui.tmpDisp16 = modEntry->head.orderCount;
+					ui.tmpDisp16 = song->header.numOrders;
 					editor.currLengthDisp = &ui.tmpDisp16;
 					ui.numPtr16 = &ui.tmpDisp16;
 					ui.numLen = 3;
@@ -3653,7 +3743,7 @@
 		{
 			if (!ui.introScreenShown && (editor.currMode == MODE_IDLE || editor.currMode == MODE_EDIT || editor.playMode != PLAY_MODE_NORMAL))
 			{
-				ui.tmpDisp16 = modEntry->currPattern;
+				ui.tmpDisp16 = song->currPattern;
 				editor.currEditPatternDisp = &ui.tmpDisp16;
 				ui.numPtr16 = &ui.tmpDisp16;
 				ui.numLen = 2;
@@ -3665,7 +3755,12 @@
 
 		case PTB_SAMPLES:
 		{
-			editor.sampleZero = false;
+			if (editor.sampleZero)
+			{
+				editor.sampleZero = false;
+				ui.updateCurrSampleNum = true;
+			}
+
 			ui.tmpDisp8 = editor.currSample;
 			editor.currSampleDisp = &ui.tmpDisp8;
 			ui.numPtr8 = &ui.tmpDisp8;
@@ -3678,14 +3773,20 @@
 
 		case PTB_SVOLUMES:
 		{
+			if (editor.sampleZero)
+			{
+				statusNotSampleZero();
+				break;
+			}
+
 			if (mouse.rightButtonPressed)
 			{
-				modEntry->samples[editor.currSample].volume = 0;
+				song->samples[editor.currSample].volume = 0;
 			}
 			else
 			{
-				ui.tmpDisp8 = modEntry->samples[editor.currSample].volume;
-				modEntry->samples[editor.currSample].volumeDisp = &ui.tmpDisp8;
+				ui.tmpDisp8 = song->samples[editor.currSample].volume;
+				song->samples[editor.currSample].volumeDisp = &ui.tmpDisp8;
 				ui.numPtr8 = &ui.tmpDisp8;
 				ui.numLen = 2;
 				ui.numBits = 8;
@@ -3697,9 +3798,15 @@
 
 		case PTB_SLENGTHS:
 		{
+			if (editor.sampleZero)
+			{
+				statusNotSampleZero();
+				break;
+			}
+
 			if (mouse.rightButtonPressed)
 			{
-				s = &modEntry->samples[editor.currSample];
+				s = &song->samples[editor.currSample];
 
 				turnOffVoices();
 
@@ -3721,8 +3828,8 @@
 			}
 			else
 			{
-				ui.tmpDisp16 = modEntry->samples[editor.currSample].length;
-				modEntry->samples[editor.currSample].lengthDisp = &ui.tmpDisp16;
+				ui.tmpDisp16 = song->samples[editor.currSample].length;
+				song->samples[editor.currSample].lengthDisp = &ui.tmpDisp16;
 				ui.numPtr16 = &ui.tmpDisp16;
 				ui.numLen = 4;
 				ui.numBits = 16;
@@ -3734,9 +3841,15 @@
 
 		case PTB_SREPEATS:
 		{
+			if (editor.sampleZero)
+			{
+				statusNotSampleZero();
+				break;
+			}
+
 			if (mouse.rightButtonPressed)
 			{
-				s = &modEntry->samples[editor.currSample];
+				s = &song->samples[editor.currSample];
 
 				s->loopStart = 0;
 				if (s->length >= s->loopLength)
@@ -3761,8 +3874,8 @@
 			}
 			else
 			{
-				ui.tmpDisp16 = modEntry->samples[editor.currSample].loopStart;
-				modEntry->samples[editor.currSample].loopStartDisp = &ui.tmpDisp16;
+				ui.tmpDisp16 = song->samples[editor.currSample].loopStart;
+				song->samples[editor.currSample].loopStartDisp = &ui.tmpDisp16;
 				ui.numPtr16 = &ui.tmpDisp16;
 				ui.numLen = 4;
 				ui.numBits = 16;
@@ -3774,9 +3887,15 @@
 
 		case PTB_SREPLENS:
 		{
+			if (editor.sampleZero)
+			{
+				statusNotSampleZero();
+				break;
+			}
+
 			if (mouse.rightButtonPressed)
 			{
-				s = &modEntry->samples[editor.currSample];
+				s = &song->samples[editor.currSample];
 
 				s->loopLength = 0;
 				if (s->length >= s->loopStart)
@@ -3804,8 +3923,8 @@
 			}
 			else
 			{
-				ui.tmpDisp16 = modEntry->samples[editor.currSample].loopLength;
-				modEntry->samples[editor.currSample].loopLengthDisp = &ui.tmpDisp16;
+				ui.tmpDisp16 = song->samples[editor.currSample].loopLength;
+				song->samples[editor.currSample].loopLengthDisp = &ui.tmpDisp16;
 				ui.numPtr16 = &ui.tmpDisp16;
 				ui.numLen = 4;
 				ui.numBits = 16;
@@ -4025,15 +4144,15 @@
 
 		case PTB_POSINS:
 		{
-			if ((editor.currMode == MODE_IDLE || editor.currMode == MODE_EDIT) && modEntry->head.orderCount < 128)
+			if ((editor.currMode == MODE_IDLE || editor.currMode == MODE_EDIT) && song->header.numOrders < 128)
 			{
-				for (i = 0; i < 127-modEntry->currOrder; i++)
-					modEntry->head.order[127-i] = modEntry->head.order[(127-i)-1];
-				modEntry->head.order[modEntry->currOrder] = 0;
+				for (i = 0; i < 127-song->currOrder; i++)
+					song->header.order[127-i] = song->header.order[(127-i)-1];
+				song->header.order[song->currOrder] = 0;
 
-				modEntry->head.orderCount++;
-				if (modEntry->currOrder > modEntry->head.orderCount-1)
-					editor.currPosEdPattDisp = &modEntry->head.order[modEntry->head.orderCount-1];
+				song->header.numOrders++;
+				if (song->currOrder > song->header.numOrders-1)
+					editor.currPosEdPattDisp = &song->header.order[song->header.numOrders-1];
 
 				updateWindowTitle(MOD_IS_MODIFIED);
 
@@ -4049,15 +4168,15 @@
 
 		case PTB_POSDEL:
 		{
-			if ((editor.currMode == MODE_IDLE || editor.currMode == MODE_EDIT) && modEntry->head.orderCount > 1)
+			if ((editor.currMode == MODE_IDLE || editor.currMode == MODE_EDIT) && song->header.numOrders > 1)
 			{
-				for (i = 0; i < 128-modEntry->currOrder; i++)
-					modEntry->head.order[modEntry->currOrder+i] = modEntry->head.order[modEntry->currOrder+i+1];
-				modEntry->head.order[127] = 0;
+				for (i = 0; i < 128-song->currOrder; i++)
+					song->header.order[song->currOrder+i] = song->header.order[song->currOrder+i+1];
+				song->header.order[127] = 0;
 
-				modEntry->head.orderCount--;
-				if (modEntry->currOrder > modEntry->head.orderCount-1)
-					editor.currPosEdPattDisp = &modEntry->head.order[modEntry->head.orderCount-1];
+				song->header.numOrders--;
+				if (song->currOrder > song->header.numOrders-1)
+					editor.currPosEdPattDisp = &song->header.order[song->header.numOrders-1];
 
 				updateWindowTitle(MOD_IS_MODIFIED);
 
@@ -4112,14 +4231,14 @@
 		{
 			if (mouse.rightButtonPressed)
 			{
-				memset(modEntry->head.moduleTitle, 0, sizeof (modEntry->head.moduleTitle));
+				memset(song->header.name, 0, sizeof (song->header.name));
 				ui.updateSongName = true;
 				updateWindowTitle(MOD_IS_MODIFIED);
 			}
 			else
 			{
-				ui.showTextPtr = modEntry->head.moduleTitle;
-				ui.textEndPtr = modEntry->head.moduleTitle + 19;
+				ui.showTextPtr = song->header.name;
+				ui.textEndPtr = song->header.name + 19;
 				ui.textLength = 20;
 				ui.editTextPos = 4133; // (y * 40) + x
 				ui.dstOffset = NULL;
@@ -4133,14 +4252,14 @@
 		{
 			if (mouse.rightButtonPressed)
 			{
-				memset(modEntry->samples[editor.currSample].text, 0, sizeof (modEntry->samples[editor.currSample].text));
+				memset(song->samples[editor.currSample].text, 0, sizeof (song->samples[editor.currSample].text));
 				ui.updateCurrSampleName = true;
 				updateWindowTitle(MOD_IS_MODIFIED);
 			}
 			else
 			{
-				ui.showTextPtr = modEntry->samples[editor.currSample].text;
-				ui.textEndPtr = modEntry->samples[editor.currSample].text + 21;
+				ui.showTextPtr = song->samples[editor.currSample].text;
+				ui.textEndPtr = song->samples[editor.currSample].text + 21;
 				ui.textLength = 22;
 				ui.editTextPos = 4573; // (y * 40) + x
 				ui.dstOffset = NULL;
@@ -4376,9 +4495,9 @@
 			editor.playMode = PLAY_MODE_NORMAL;
 
 			if (mouse.rightButtonPressed)
-				modPlay(DONT_SET_PATTERN, modEntry->currOrder, modEntry->currRow);
+				modPlay(DONT_SET_PATTERN, song->currOrder, song->currRow);
 			else
-				modPlay(DONT_SET_PATTERN, modEntry->currOrder, DONT_SET_ROW);
+				modPlay(DONT_SET_PATTERN, song->currOrder, DONT_SET_ROW);
 
 			editor.currMode = MODE_PLAY;
 			pointerSetMode(POINTER_MODE_PLAY, DO_CARRY);
@@ -4391,9 +4510,9 @@
 			editor.playMode = PLAY_MODE_PATTERN;
 
 			if (mouse.rightButtonPressed)
-				modPlay(modEntry->currPattern, DONT_SET_ORDER, modEntry->currRow);
+				modPlay(song->currPattern, DONT_SET_ORDER, song->currRow);
 			else
-				modPlay(modEntry->currPattern, DONT_SET_ORDER, DONT_SET_ROW);
+				modPlay(song->currPattern, DONT_SET_ORDER, DONT_SET_ROW);
 
 			editor.currMode = MODE_PLAY;
 			pointerSetMode(POINTER_MODE_PLAY, DO_CARRY);
@@ -4421,9 +4540,9 @@
 				editor.playMode = PLAY_MODE_PATTERN;
 
 				if (mouse.rightButtonPressed)
-					modPlay(modEntry->currPattern, DONT_SET_ORDER, modEntry->currRow);
+					modPlay(song->currPattern, DONT_SET_ORDER, song->currRow);
 				else
-					modPlay(modEntry->currPattern, DONT_SET_ORDER, DONT_SET_ROW);
+					modPlay(song->currPattern, DONT_SET_ORDER, DONT_SET_ROW);
 
 				editor.currMode = MODE_RECORD;
 				pointerSetMode(POINTER_MODE_EDIT, DO_CARRY);
@@ -4523,7 +4642,7 @@
 
 		case PTB_FTUNEU:
 		{
-			if ((modEntry->samples[editor.currSample].fineTune & 0xF) != 7)
+			if (!editor.sampleZero && (song->samples[editor.currSample].fineTune & 0xF) != 7)
 			{
 				sampleFineTuneUpButton();
 				updateWindowTitle(MOD_IS_MODIFIED);
@@ -4533,11 +4652,10 @@
 
 		case PTB_FTUNED:
 		{
-			if ((modEntry->samples[editor.currSample].fineTune & 0xF) != 8)
+			if (!editor.sampleZero && (song->samples[editor.currSample].fineTune & 0xF) != 8)
 			{
 				sampleFineTuneDownButton();
 				updateWindowTitle(MOD_IS_MODIFIED);
-
 			}
 		}
 		break;
@@ -4544,7 +4662,7 @@
 
 		case PTB_SVOLUMEU:
 		{
-			if (modEntry->samples[editor.currSample].volume < 64)
+			if (!editor.sampleZero && song->samples[editor.currSample].volume < 64)
 			{
 				sampleVolumeUpButton();
 				updateWindowTitle(MOD_IS_MODIFIED);
@@ -4554,7 +4672,7 @@
 
 		case PTB_SVOLUMED:
 		{
-			if (modEntry->samples[editor.currSample].volume > 0)
+			if (!editor.sampleZero && song->samples[editor.currSample].volume > 0)
 			{
 				sampleVolumeDownButton();
 				updateWindowTitle(MOD_IS_MODIFIED);
@@ -4564,7 +4682,7 @@
 
 		case PTB_SLENGTHU:
 		{
-			if (modEntry->samples[editor.currSample].length < MAX_SAMPLE_LEN)
+			if (!editor.sampleZero && song->samples[editor.currSample].length < MAX_SAMPLE_LEN)
 			{
 				sampleLengthUpButton(INCREMENT_SLOW);
 				updateWindowTitle(MOD_IS_MODIFIED);
@@ -4574,7 +4692,7 @@
 
 		case PTB_SLENGTHD:
 		{
-			if (modEntry->samples[editor.currSample].length > 0)
+			if (!editor.sampleZero && song->samples[editor.currSample].length > 0)
 			{
 				sampleLengthDownButton(INCREMENT_SLOW);
 				updateWindowTitle(MOD_IS_MODIFIED);
@@ -4584,37 +4702,49 @@
 
 		case PTB_SREPEATU:
 		{
-			oldVal = modEntry->samples[editor.currSample].loopStart;
-			sampleRepeatUpButton(INCREMENT_SLOW);
-			if (modEntry->samples[editor.currSample].loopStart != oldVal)
-				updateWindowTitle(MOD_IS_MODIFIED);
+			if (!editor.sampleZero)
+			{
+				oldVal = song->samples[editor.currSample].loopStart;
+				sampleRepeatUpButton(INCREMENT_SLOW);
+				if (song->samples[editor.currSample].loopStart != oldVal)
+					updateWindowTitle(MOD_IS_MODIFIED);
+			}
 		}
 		break;
 
 		case PTB_SREPEATD:
 		{
-			oldVal = modEntry->samples[editor.currSample].loopStart;
-			sampleRepeatDownButton(INCREMENT_SLOW);
-			if (modEntry->samples[editor.currSample].loopStart != oldVal)
-				updateWindowTitle(MOD_IS_MODIFIED);
+			if (!editor.sampleZero)
+			{
+				oldVal = song->samples[editor.currSample].loopStart;
+				sampleRepeatDownButton(INCREMENT_SLOW);
+				if (song->samples[editor.currSample].loopStart != oldVal)
+					updateWindowTitle(MOD_IS_MODIFIED);
+			}
 		}
 		break;
 
 		case PTB_SREPLENU:
 		{
-			oldVal = modEntry->samples[editor.currSample].loopLength;
-			sampleRepeatLengthUpButton(INCREMENT_SLOW);
-			if (modEntry->samples[editor.currSample].loopLength != oldVal)
-				updateWindowTitle(MOD_IS_MODIFIED);
+			if (!editor.sampleZero)
+			{
+				oldVal = song->samples[editor.currSample].loopLength;
+				sampleRepeatLengthUpButton(INCREMENT_SLOW);
+				if (song->samples[editor.currSample].loopLength != oldVal)
+					updateWindowTitle(MOD_IS_MODIFIED);
+			}
 		}
 		break;
 
 		case PTB_SREPLEND:
 		{
-			oldVal = modEntry->samples[editor.currSample].loopLength;
-			sampleRepeatLengthDownButton(INCREMENT_SLOW);
-			if (modEntry->samples[editor.currSample].loopLength != oldVal)
-				updateWindowTitle(MOD_IS_MODIFIED);
+			if (!editor.sampleZero)
+			{
+				oldVal = song->samples[editor.currSample].loopLength;
+				sampleRepeatLengthDownButton(INCREMENT_SLOW);
+				if (song->samples[editor.currSample].loopLength != oldVal)
+					updateWindowTitle(MOD_IS_MODIFIED);
+			}
 		}
 		break;
 
@@ -4623,7 +4753,7 @@
 
 		case PTB_LENGTHU:
 		{
-			if (modEntry->head.orderCount < 128)
+			if (song->header.numOrders < 128)
 			{
 				songLengthUpButton();
 				updateWindowTitle(MOD_IS_MODIFIED);
@@ -4633,7 +4763,7 @@
 
 		case PTB_LENGTHD:
 		{
-			if (modEntry->head.orderCount > 1)
+			if (song->header.numOrders > 1)
 			{
 				songLengthDownButton();
 				updateWindowTitle(MOD_IS_MODIFIED);
@@ -4643,7 +4773,7 @@
 
 		case PTB_PATTERNU:
 		{
-			if (modEntry->head.order[modEntry->currOrder] < 99)
+			if (song->header.order[song->currOrder] < 99)
 			{
 				patternUpButton();
 				updateWindowTitle(MOD_IS_MODIFIED);
@@ -4653,7 +4783,7 @@
 
 		case PTB_PATTERND:
 		{
-			if (modEntry->head.order[modEntry->currOrder] > 0)
+			if (song->header.order[song->currOrder] > 0)
 			{
 				patternDownButton();
 				updateWindowTitle(MOD_IS_MODIFIED);
@@ -5083,8 +5213,8 @@
 			if (mouse.repeatCounter >= 2)
 			{
 				mouse.repeatCounter = 0;
-				if (modEntry->currOrder > 0)
-					modSetPos(modEntry->currOrder - 1, DONT_SET_ROW);
+				if (song->currOrder > 0)
+					modSetPos(song->currOrder - 1, DONT_SET_ROW);
 			}
 		}
 		break;
@@ -5094,8 +5224,8 @@
 			if (mouse.repeatCounter >= 2)
 			{
 				mouse.repeatCounter = 0;
-				if (modEntry->currOrder < modEntry->head.orderCount-1)
-					modSetPos(modEntry->currOrder + 1, DONT_SET_ROW);
+				if (song->currOrder < song->header.numOrders-1)
+					modSetPos(song->currOrder + 1, DONT_SET_ROW);
 			}
 		}
 		break;
--- a/src/pt2_palette.c
+++ b/src/pt2_palette.c
@@ -3,6 +3,7 @@
 
 void setDefaultPalette(void)
 {
+	// default ProTracker palette
 	video.palette[PAL_BACKGRD] = 0x000000;
 	video.palette[PAL_BORDER] = 0xBBBBBB;
 	video.palette[PAL_GENBKG] = 0x888888;
@@ -17,5 +18,6 @@
 	video.palette[PAL_MOUSE_1] = 0x444444;
 	video.palette[PAL_MOUSE_2] = 0x777777;
 	video.palette[PAL_MOUSE_3] = 0xAAAAAA;
+
 	video.palette[PAL_COLORKEY] = 0xC0FFEE;
 }
--- a/src/pt2_palette.h
+++ b/src/pt2_palette.h
@@ -11,7 +11,6 @@
 	PAL_PATCURSOR = 5,
 	PAL_GENTXT = 6,
 	PAL_PATTXT = 7,
-	// -----------------------------
 	PAL_SAMPLLINE = 8,
 	PAL_LOOPPIN = 9,
 	PAL_TEXTMARK = 10,
--- a/src/pt2_pat2smp.c
+++ b/src/pt2_pat2smp.c
@@ -23,6 +23,12 @@
 
 	ui.pat2SmpDialogShown = false;
 
+	if (editor.sampleZero)
+	{
+		statusNotSampleZero();
+		return;
+	}
+
 	editor.pat2SmpBuf = (int16_t *)malloc(MAX_SAMPLE_LEN * sizeof (int16_t));
 	if (editor.pat2SmpBuf == NULL)
 	{
@@ -30,19 +36,19 @@
 		return;
 	}
 
-	int8_t oldRow = editor.songPlaying ? 0 : modEntry->currRow;
+	int8_t oldRow = editor.songPlaying ? 0 : song->currRow;
 	uint32_t oldSamplesPerTick = samplesPerTick;
 
 	editor.isSMPRendering = true; // this must be set before restartSong()
 	storeTempVariables();
 	restartSong();
-	modEntry->row = oldRow;
-	modEntry->currRow = modEntry->row;
+	song->row = oldRow;
+	song->currRow = song->row;
 
 	editor.blockMarkFlag = false;
 	pointerSetMode(POINTER_MODE_MSG2, NO_CARRY);
 	setStatusMessage("RENDERING...", NO_CARRY);
-	modSetTempo(modEntry->currBPM);
+	modSetTempo(song->currBPM);
 	editor.pat2SmpPos = 0;
 
 	editor.smpRenderingDone = false;
@@ -57,21 +63,21 @@
 	resetSong();
 
 	// set back old row and samplesPerTick
-	modEntry->row = oldRow;
-	modEntry->currRow = modEntry->row;
+	song->row = oldRow;
+	song->currRow = song->row;
 	mixerSetSamplesPerTick(oldSamplesPerTick);
 
 	normalize16bitSigned(editor.pat2SmpBuf, MIN(editor.pat2SmpPos, MAX_SAMPLE_LEN));
 
-	s = &modEntry->samples[editor.currSample];
+	s = &song->samples[editor.currSample];
 
 	// quantize to 8-bit
 	for (int32_t i = 0; i < editor.pat2SmpPos; i++)
-		modEntry->sampleData[s->offset+i] = editor.pat2SmpBuf[i] >> 8;
+		song->sampleData[s->offset+i] = editor.pat2SmpBuf[i] >> 8;
 
 	// clear the rest of the sample
 	if (editor.pat2SmpPos < MAX_SAMPLE_LEN)
-		memset(&modEntry->sampleData[s->offset+editor.pat2SmpPos], 0, MAX_SAMPLE_LEN - editor.pat2SmpPos);
+		memset(&song->sampleData[s->offset+editor.pat2SmpPos], 0, MAX_SAMPLE_LEN - editor.pat2SmpPos);
 
 	free(editor.pat2SmpBuf);
 
--- /dev/null
+++ b/src/pt2_pattern_viewer.c
@@ -1,0 +1,251 @@
+#include <stdint.h>
+#include <stdbool.h>
+#include "pt2_header.h"
+#include "pt2_palette.h"
+#include "pt2_tables.h"
+#include "pt2_textout.h"
+#include "pt2_structs.h"
+#include "pt2_config.h"
+
+#define MIDDLE_ROW 7
+#define VISIBLE_ROWS 15
+
+static const char *emptyRowNum = "  ";
+static const char *emptyRowData = "        ";
+static const char emptyDottedEffect[4] = { 0x02, 0x02, 0x02, 0x00 };
+static const char emptyDottedSample[3] = { 0x02, 0x02, 0x00 };
+
+static int32_t periodToNote(int32_t period) // 0 = no note, 1 = illegal note, 2..37 = note
+{
+	int32_t beg, end, tableVal;
+
+	if (period == 0)
+		return 0;
+
+	beg = 0;
+	end = 36 - 1;
+
+	// do binary search
+	while (beg <= end)
+	{
+		const int32_t mid = (beg + end) >> 1;
+
+		tableVal = periodTable[mid];
+		if (period == tableVal)
+			return 2+mid;
+
+		if (period < tableVal)
+			beg = mid+1;
+		else
+			end = mid-1;
+	}
+
+	return 1; // illegal note
+}
+
+static void drawPatternNormal(void)
+{
+	const char **noteNames;
+	char smpChar;
+	int32_t row, j, x, y;
+	note_t *patt, *note;
+
+	if (config.accidental)
+		noteNames = (const char **)noteNames2;
+	else
+		noteNames = (const char **)noteNames1;
+
+	patt = song->patterns[song->currPattern];
+	row = song->currRow - MIDDLE_ROW;
+	y = 140;
+
+	for (int32_t i = 0; i < VISIBLE_ROWS; i++, y += 7, row++)
+	{
+		if (row < 0 || row >= MOD_ROWS)
+		{
+			// clear empty rows outside of pattern data
+			textOutBg(8, y, emptyRowNum, video.palette[PAL_PATTXT], video.palette[PAL_BACKGRD]);
+			textOutBg(32+(0*72), y, emptyRowData, video.palette[PAL_PATTXT], video.palette[PAL_BACKGRD]);
+			textOutBg(32+(1*72), y, emptyRowData, video.palette[PAL_PATTXT], video.palette[PAL_BACKGRD]);
+			textOutBg(32+(2*72), y, emptyRowData, video.palette[PAL_PATTXT], video.palette[PAL_BACKGRD]);
+			textOutBg(32+(3*72), y, emptyRowData, video.palette[PAL_PATTXT], video.palette[PAL_BACKGRD]);
+		}
+		else
+		{
+			if (i == MIDDLE_ROW) // middle row has twice as tall glyphs
+			{
+				y++;
+				printTwoDecimalsBigBg(8, y, row, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
+
+				note = patt + (row << 2);
+				x = 32;
+				for (j = 0; j < AMIGA_VOICES; j++, note++)
+				{
+					textOutBigBg(x, y, noteNames[periodToNote(note->period)], video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
+					x += 8*3;
+
+					smpChar = (config.blankZeroFlag && !(note->sample & 0xF0)) ? ' ' : hexTable[note->sample >> 4];
+					charOutBigBg(x, y, smpChar, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
+					x += 8;
+
+					printOneHexBigBg(x, y, note->sample & 0x0F, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
+					x += 8;
+
+					printOneHexBigBg(x, y, note->command, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
+					x += 8;
+
+					printTwoHexBigBg(x, y, note->param, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
+					x += (8*2)+8;
+				}
+				y += 6;
+			}
+			else // non-middle rows
+			{
+				printTwoDecimalsBg(8, y, row, video.palette[PAL_PATTXT], video.palette[PAL_BACKGRD]);
+
+				note = patt + (row << 2);
+				x = 32;
+				for (j = 0; j < AMIGA_VOICES; j++, note++)
+				{
+					textOutBg(x, y, noteNames[periodToNote(note->period)], video.palette[PAL_PATTXT], video.palette[PAL_BACKGRD]);
+					x += 8*3;
+
+					smpChar = (config.blankZeroFlag && !(note->sample & 0xF0)) ? ' ' : hexTable[note->sample >> 4];
+					charOutBg(x, y, smpChar, video.palette[PAL_PATTXT], video.palette[PAL_BACKGRD]);
+					x += 8;
+
+					printOneHexBg(x , y, note->sample & 0x0F, video.palette[PAL_PATTXT], video.palette[PAL_BACKGRD]);
+					x += 8;
+
+					printOneHexBg(x, y, note->command, video.palette[PAL_PATTXT], video.palette[PAL_BACKGRD]);
+					x += 8;
+
+					printTwoHexBg(x, y, note->param, video.palette[PAL_PATTXT], video.palette[PAL_BACKGRD]);
+					x += (8*2)+8;
+				}
+			}
+		}
+	}
+}
+
+static void drawPatternDotted(void)
+{
+	char smpChar;
+	const char **noteNames;
+	int32_t row, j, x, y;
+	note_t *patt, *note;
+
+	if (config.accidental)
+		noteNames = (const char **)noteNames4;
+	else
+		noteNames = (const char **)noteNames3;
+
+	patt = song->patterns[song->currPattern];
+	row = song->currRow - MIDDLE_ROW;
+	y = 140; 
+
+	for (int32_t i = 0; i < VISIBLE_ROWS; i++, y += 7, row++)
+	{
+		if (row < 0 || row >= MOD_ROWS)
+		{
+			// clear empty rows outside of pattern data
+			textOutBg(8, y, emptyRowNum, video.palette[PAL_PATTXT], video.palette[PAL_BACKGRD]);
+			textOutBg(32+(0*72), y, emptyRowData, video.palette[PAL_PATTXT], video.palette[PAL_BACKGRD]);
+			textOutBg(32+(1*72), y, emptyRowData, video.palette[PAL_PATTXT], video.palette[PAL_BACKGRD]);
+			textOutBg(32+(2*72), y, emptyRowData, video.palette[PAL_PATTXT], video.palette[PAL_BACKGRD]);
+			textOutBg(32+(3*72), y, emptyRowData, video.palette[PAL_PATTXT], video.palette[PAL_BACKGRD]);
+		}
+		else
+		{
+			if (i == MIDDLE_ROW) // middle row has twice as tall glyphs
+			{
+				y++;
+				printTwoDecimalsBigBg(8, y, row, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
+
+				note = patt + (row << 2);
+				x = 32;
+				for (j = 0; j < AMIGA_VOICES; j++, note++)
+				{
+					textOutBigBg(x, y, noteNames[periodToNote(note->period)], video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
+					x += 8*3;
+
+					if (note->sample == 0)
+					{
+						textOutBigBg(x, y, emptyDottedSample, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
+						x += 8*2;
+					}
+					else
+					{
+						smpChar = (note->sample & 0xF0) ? hexTable[note->sample >> 4] : 0x02;
+						charOutBigBg(x, y, smpChar, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
+						x += 8;
+						printOneHexBigBg(x, y, note->sample & 0x0F, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
+						x += 8;
+					}
+
+					if (note->command == 0 && note->param == 0)
+					{
+						textOutBigBg(x, y, emptyDottedEffect, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
+						x += (8*3)+8;
+					}
+					else
+					{
+						printOneHexBigBg(x, y, note->command, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
+						x += 8;
+						printTwoHexBigBg(x, y, note->param, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
+						x += (8*2)+8;
+					}
+				}
+				y += 6;
+			}
+			else // non-middle rows
+			{
+				printTwoDecimalsBg(8, y, row, video.palette[PAL_PATTXT], video.palette[PAL_BACKGRD]);
+
+				// pattern data
+				note = patt + (row << 2);
+				x = 32;
+				for (j = 0; j < AMIGA_VOICES; j++, note++)
+				{
+					textOutBg(x, y, noteNames[periodToNote(note->period)], video.palette[PAL_PATTXT], video.palette[PAL_BACKGRD]);
+					x += 8*3;
+
+					if (note->sample == 0)
+					{
+						textOutBg(x, y, emptyDottedSample, video.palette[PAL_PATTXT], video.palette[PAL_BACKGRD]);
+						x += 8*2;
+					}
+					else
+					{
+						smpChar = (note->sample & 0xF0) ? hexTable[note->sample >> 4] : 0x02;
+						charOutBg(x, y, smpChar, video.palette[PAL_PATTXT], video.palette[PAL_BACKGRD]);
+						x += 8;
+						printOneHexBg(x, y, note->sample & 0x0F, video.palette[PAL_PATTXT], video.palette[PAL_BACKGRD]);
+						x += 8;
+					}
+
+					if (note->command == 0 && note->param == 0)
+					{
+						textOutBg(x, y, emptyDottedEffect, video.palette[PAL_PATTXT], video.palette[PAL_BACKGRD]);
+						x += (8*3)+8;
+					}
+					else
+					{
+						printOneHexBg(x, y, note->command, video.palette[PAL_PATTXT], video.palette[PAL_BACKGRD]);
+						x += 8;
+						printTwoHexBg(x, y, note->param, video.palette[PAL_PATTXT], video.palette[PAL_BACKGRD]);
+						x += (8*2)+8;
+					}
+				}
+			}
+		}
+	}
+}
+
+void redrawPattern(void)
+{
+	if (config.pattDots)
+		drawPatternDotted();
+	else
+		drawPatternNormal();
+}
--- /dev/null
+++ b/src/pt2_pattern_viewer.h
@@ -1,0 +1,3 @@
+#pragma once
+
+void redrawPattern(void);
--- a/src/pt2_patternviewer.c
+++ /dev/null
@@ -1,250 +1,0 @@
-#include <stdint.h>
-#include <stdbool.h>
-#include "pt2_header.h"
-#include "pt2_palette.h"
-#include "pt2_tables.h"
-#include "pt2_textout.h"
-#include "pt2_structs.h"
-
-#define MIDDLE_ROW 7
-#define VISIBLE_ROWS 15
-
-static const char *emptyRowNum = "  ";
-static const char *emptyRowData = "        ";
-static const char emptyDottedEffect[4] = { 0x02, 0x02, 0x02, 0x00 };
-static const char emptyDottedSample[3] = { 0x02, 0x02, 0x00 };
-
-static int32_t periodToNote(int32_t period) // 0 = no note, 1 = illegal note, 2..37 = note
-{
-	int32_t beg, end, tableVal;
-
-	if (period == 0)
-		return 0;
-
-	beg = 0;
-	end = 36 - 1;
-
-	// do binary search
-	while (beg <= end)
-	{
-		const int32_t mid = (beg + end) >> 1;
-
-		tableVal = periodTable[mid];
-		if (period == tableVal)
-			return 2+mid;
-
-		if (period < tableVal)
-			beg = mid+1;
-		else
-			end = mid-1;
-	}
-
-	return 1; // illegal note
-}
-
-static void drawPatternNormal(void)
-{
-	const char **noteNames;
-	char smpChar;
-	int32_t row, j, x, y;
-	note_t *patt, *note;
-
-	if (config.accidental)
-		noteNames = (const char **)noteNames2;
-	else
-		noteNames = (const char **)noteNames1;
-
-	patt = modEntry->patterns[modEntry->currPattern];
-	row = modEntry->currRow - MIDDLE_ROW;
-	y = 140;
-
-	for (int32_t i = 0; i < VISIBLE_ROWS; i++, y += 7, row++)
-	{
-		if (row < 0 || row >= MOD_ROWS)
-		{
-			// clear empty rows outside of pattern data
-			textOutBg(8, y, emptyRowNum, video.palette[PAL_PATTXT], video.palette[PAL_BACKGRD]);
-			textOutBg(32+(0*72), y, emptyRowData, video.palette[PAL_PATTXT], video.palette[PAL_BACKGRD]);
-			textOutBg(32+(1*72), y, emptyRowData, video.palette[PAL_PATTXT], video.palette[PAL_BACKGRD]);
-			textOutBg(32+(2*72), y, emptyRowData, video.palette[PAL_PATTXT], video.palette[PAL_BACKGRD]);
-			textOutBg(32+(3*72), y, emptyRowData, video.palette[PAL_PATTXT], video.palette[PAL_BACKGRD]);
-		}
-		else
-		{
-			if (i == MIDDLE_ROW) // middle row has twice as tall glyphs
-			{
-				y++;
-				printTwoDecimalsBigBg(8, y, row, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
-
-				note = patt + (row << 2);
-				x = 32;
-				for (j = 0; j < AMIGA_VOICES; j++, note++)
-				{
-					textOutBigBg(x, y, noteNames[periodToNote(note->period)], video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
-					x += 8*3;
-
-					smpChar = (config.blankZeroFlag && !(note->sample & 0xF0)) ? ' ' : hexTable[note->sample >> 4];
-					charOutBigBg(x, y, smpChar, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
-					x += 8;
-
-					printOneHexBigBg(x, y, note->sample & 0x0F, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
-					x += 8;
-
-					printOneHexBigBg(x, y, note->command, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
-					x += 8;
-
-					printTwoHexBigBg(x, y, note->param, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
-					x += (8*2)+8;
-				}
-				y += 6;
-			}
-			else // non-middle rows
-			{
-				printTwoDecimalsBg(8, y, row, video.palette[PAL_PATTXT], video.palette[PAL_BACKGRD]);
-
-				note = patt + (row << 2);
-				x = 32;
-				for (j = 0; j < AMIGA_VOICES; j++, note++)
-				{
-					textOutBg(x, y, noteNames[periodToNote(note->period)], video.palette[PAL_PATTXT], video.palette[PAL_BACKGRD]);
-					x += 8*3;
-
-					smpChar = (config.blankZeroFlag && !(note->sample & 0xF0)) ? ' ' : hexTable[note->sample >> 4];
-					charOutBg(x, y, smpChar, video.palette[PAL_PATTXT], video.palette[PAL_BACKGRD]);
-					x += 8;
-
-					printOneHexBg(x , y, note->sample & 0x0F, video.palette[PAL_PATTXT], video.palette[PAL_BACKGRD]);
-					x += 8;
-
-					printOneHexBg(x, y, note->command, video.palette[PAL_PATTXT], video.palette[PAL_BACKGRD]);
-					x += 8;
-
-					printTwoHexBg(x, y, note->param, video.palette[PAL_PATTXT], video.palette[PAL_BACKGRD]);
-					x += (8*2)+8;
-				}
-			}
-		}
-	}
-}
-
-static void drawPatternDotted(void)
-{
-	char smpChar;
-	const char **noteNames;
-	int32_t row, j, x, y;
-	note_t *patt, *note;
-
-	if (config.accidental)
-		noteNames = (const char **)noteNames4;
-	else
-		noteNames = (const char **)noteNames3;
-
-	patt = modEntry->patterns[modEntry->currPattern];
-	row = modEntry->currRow - MIDDLE_ROW;
-	y = 140; 
-
-	for (int32_t i = 0; i < VISIBLE_ROWS; i++, y += 7, row++)
-	{
-		if (row < 0 || row >= MOD_ROWS)
-		{
-			// clear empty rows outside of pattern data
-			textOutBg(8, y, emptyRowNum, video.palette[PAL_PATTXT], video.palette[PAL_BACKGRD]);
-			textOutBg(32+(0*72), y, emptyRowData, video.palette[PAL_PATTXT], video.palette[PAL_BACKGRD]);
-			textOutBg(32+(1*72), y, emptyRowData, video.palette[PAL_PATTXT], video.palette[PAL_BACKGRD]);
-			textOutBg(32+(2*72), y, emptyRowData, video.palette[PAL_PATTXT], video.palette[PAL_BACKGRD]);
-			textOutBg(32+(3*72), y, emptyRowData, video.palette[PAL_PATTXT], video.palette[PAL_BACKGRD]);
-		}
-		else
-		{
-			if (i == MIDDLE_ROW) // middle row has twice as tall glyphs
-			{
-				y++;
-				printTwoDecimalsBigBg(8, y, row, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
-
-				note = patt + (row << 2);
-				x = 32;
-				for (j = 0; j < AMIGA_VOICES; j++, note++)
-				{
-					textOutBigBg(x, y, noteNames[periodToNote(note->period)], video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
-					x += 8*3;
-
-					if (note->sample == 0)
-					{
-						textOutBigBg(x, y, emptyDottedSample, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
-						x += 8*2;
-					}
-					else
-					{
-						smpChar = (note->sample & 0xF0) ? hexTable[note->sample >> 4] : 0x02;
-						charOutBigBg(x, y, smpChar, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
-						x += 8;
-						printOneHexBigBg(x, y, note->sample & 0x0F, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
-						x += 8;
-					}
-
-					if (note->command == 0 && note->param == 0)
-					{
-						textOutBigBg(x, y, emptyDottedEffect, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
-						x += (8*3)+8;
-					}
-					else
-					{
-						printOneHexBigBg(x, y, note->command, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
-						x += 8;
-						printTwoHexBigBg(x, y, note->param, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
-						x += (8*2)+8;
-					}
-				}
-				y += 6;
-			}
-			else // non-middle rows
-			{
-				printTwoDecimalsBg(8, y, row, video.palette[PAL_PATTXT], video.palette[PAL_BACKGRD]);
-
-				// pattern data
-				note = patt + (row << 2);
-				x = 32;
-				for (j = 0; j < AMIGA_VOICES; j++, note++)
-				{
-					textOutBg(x, y, noteNames[periodToNote(note->period)], video.palette[PAL_PATTXT], video.palette[PAL_BACKGRD]);
-					x += 8*3;
-
-					if (note->sample == 0)
-					{
-						textOutBg(x, y, emptyDottedSample, video.palette[PAL_PATTXT], video.palette[PAL_BACKGRD]);
-						x += 8*2;
-					}
-					else
-					{
-						smpChar = (note->sample & 0xF0) ? hexTable[note->sample >> 4] : 0x02;
-						charOutBg(x, y, smpChar, video.palette[PAL_PATTXT], video.palette[PAL_BACKGRD]);
-						x += 8;
-						printOneHexBg(x, y, note->sample & 0x0F, video.palette[PAL_PATTXT], video.palette[PAL_BACKGRD]);
-						x += 8;
-					}
-
-					if (note->command == 0 && note->param == 0)
-					{
-						textOutBg(x, y, emptyDottedEffect, video.palette[PAL_PATTXT], video.palette[PAL_BACKGRD]);
-						x += (8*3)+8;
-					}
-					else
-					{
-						printOneHexBg(x, y, note->command, video.palette[PAL_PATTXT], video.palette[PAL_BACKGRD]);
-						x += 8;
-						printTwoHexBg(x, y, note->param, video.palette[PAL_PATTXT], video.palette[PAL_BACKGRD]);
-						x += (8*2)+8;
-					}
-				}
-			}
-		}
-	}
-}
-
-void redrawPattern(void)
-{
-	if (config.pattDots)
-		drawPatternDotted();
-	else
-		drawPatternNormal();
-}
--- a/src/pt2_patternviewer.h
+++ /dev/null
@@ -1,3 +1,0 @@
-#pragma once
-
-void redrawPattern(void);
--- /dev/null
+++ b/src/pt2_replayer.c
@@ -1,0 +1,1549 @@
+// C port of ProTracker 2.3D's replayer by 8bitbubsy, slightly modified.
+
+// for finding memory leaks in debug mode with Visual Studio 
+#if defined _DEBUG && defined _MSC_VER
+#include <crtdbg.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <math.h>
+#include "pt2_header.h"
+#include "pt2_audio.h"
+#include "pt2_helpers.h"
+#include "pt2_palette.h"
+#include "pt2_tables.h"
+#include "pt2_module_loader.h"
+#include "pt2_config.h"
+#include "pt2_sampler.h"
+#include "pt2_visuals.h"
+#include "pt2_textout.h"
+#include "pt2_scopes.h"
+
+static bool posJumpAssert, pBreakFlag, updateUIPositions, modHasBeenPlayed;
+static int8_t pBreakPosition, oldRow, modPattern;
+static uint8_t pattDelTime, setBPMFlag, lowMask = 0xFF, pattDelTime2, oldSpeed;
+static int16_t modOrder, oldPattern, oldOrder;
+static uint16_t modBPM, oldBPM;
+
+static const int8_t vuMeterHeights[65] =
+{
+	 0,  0,  1,  2,  2,  3,  4,  5,
+	 5,  6,  7,  8,  8,  9, 10, 11,
+	11, 12, 13, 14, 14, 15, 16, 17,
+	17, 18, 19, 20, 20, 21, 22, 23,
+	23, 24, 25, 26, 26, 27, 28, 29,
+	29, 30, 31, 32, 32, 33, 34, 35,
+	35, 36, 37, 38, 38, 39, 40, 41,
+	41, 42, 43, 44, 44, 45, 46, 47,
+	47
+};
+
+static const uint8_t funkTable[16] = // EFx (FunkRepeat/InvertLoop)
+{
+	0x00, 0x05, 0x06, 0x07, 0x08, 0x0A, 0x0B, 0x0D,
+	0x10, 0x13, 0x16, 0x1A, 0x20, 0x2B, 0x40, 0x80
+};
+
+void modSetSpeed(uint8_t speed)
+{
+	song->speed = speed;
+	song->currSpeed = speed;
+	song->tick = 0;
+}
+
+void doStopIt(bool resetPlayMode)
+{
+	editor.songPlaying = false;
+
+	resetCachedMixerPeriod();
+
+	pattDelTime = 0;
+	pattDelTime2 = 0;
+
+	if (resetPlayMode)
+	{
+		editor.playMode = PLAY_MODE_NORMAL;
+		editor.currMode = MODE_IDLE;
+
+		pointerSetMode(POINTER_MODE_IDLE, DO_CARRY);
+	}
+
+	for (int32_t i = 0; i < AMIGA_VOICES; i++)
+	{
+		moduleChannel_t *c = &song->channels[i];
+
+		c->n_wavecontrol = 0;
+		c->n_glissfunk = 0;
+		c->n_finetune = 0;
+		c->n_loopcount = 0;
+	}
+}
+
+void setPattern(int16_t pattern)
+{
+	if (pattern > MAX_PATTERNS-1)
+		pattern = MAX_PATTERNS-1;
+
+	song->currPattern = modPattern = (int8_t)pattern;
+}
+
+void storeTempVariables(void) // this one is accessed in other files, so non-static
+{
+	oldBPM = song->currBPM;
+	oldRow = song->currRow;
+	oldOrder = song->currOrder;
+	oldSpeed = song->currSpeed;
+	oldPattern = song->currPattern;
+}
+
+static void setVUMeterHeight(moduleChannel_t *ch)
+{
+	uint8_t vol;
+
+	if (editor.muted[ch->n_chanindex])
+		return;
+
+	vol = ch->n_volume;
+	if ((ch->n_cmd & 0xF00) == 0xC00) // handle Cxx effect
+		vol = ch->n_cmd & 0xFF;
+
+	if (vol > 64)
+		vol = 64;
+
+	editor.vuMeterVolumes[ch->n_chanindex] = vuMeterHeights[vol];
+}
+
+static void updateFunk(moduleChannel_t *ch)
+{
+	const int8_t funkSpeed = ch->n_glissfunk >> 4;
+	if (funkSpeed == 0)
+		return;
+
+	ch->n_funkoffset += funkTable[funkSpeed];
+	if (ch->n_funkoffset >= 128)
+	{
+		ch->n_funkoffset = 0;
+
+		if (ch->n_loopstart != NULL && ch->n_wavestart != NULL) // non-PT2 bug fix
+		{
+			if (++ch->n_wavestart >= ch->n_loopstart+ch->n_replen)
+				ch->n_wavestart = ch->n_loopstart;
+
+			*ch->n_wavestart = -1 - *ch->n_wavestart;
+		}
+	}
+}
+
+static void setGlissControl(moduleChannel_t *ch)
+{
+	ch->n_glissfunk = (ch->n_glissfunk & 0xF0) | (ch->n_cmd & 0x0F);
+}
+
+static void setVibratoControl(moduleChannel_t *ch)
+{
+	ch->n_wavecontrol = (ch->n_wavecontrol & 0xF0) | (ch->n_cmd & 0x0F);
+}
+
+static void setFineTune(moduleChannel_t *ch)
+{
+	ch->n_finetune = ch->n_cmd & 0xF;
+}
+
+static void jumpLoop(moduleChannel_t *ch)
+{
+	uint8_t tempParam;
+
+	if (song->tick != 0)
+		return;
+
+	if ((ch->n_cmd & 0xF) == 0)
+	{
+		ch->n_pattpos = song->row;
+	}
+	else
+	{
+		if (ch->n_loopcount == 0)
+		{
+			ch->n_loopcount = ch->n_cmd & 0xF;
+		}
+		else if (--ch->n_loopcount == 0)
+		{
+			return;
+		}
+
+		pBreakPosition = ch->n_pattpos;
+		pBreakFlag = true;
+
+		// stuff used for MOD2WAV to determine if the song has reached its end
+		if (editor.isWAVRendering)
+		{
+			for (tempParam = pBreakPosition; tempParam <= song->row; tempParam++)
+				editor.rowVisitTable[(modOrder * MOD_ROWS) + tempParam] = false;
+		}
+	}
+}
+
+static void setTremoloControl(moduleChannel_t *ch)
+{
+	ch->n_wavecontrol = ((ch->n_cmd & 0xF) << 4) | (ch->n_wavecontrol & 0xF);
+}
+
+static void karplusStrong(moduleChannel_t *ch)
+{
+	/* This effect is definitely the least used PT effect there is!
+	** It trashes (filters) the sample data.
+	** The reason I'm not implementing it is because a lot of songs used
+	** E8x for syncing to demos/intros, and because I have never ever
+	** seen this effect being used intentionally.
+	*/
+
+	(void)ch;
+}
+
+static void doRetrg(moduleChannel_t *ch)
+{
+	paulaSetData(ch->n_chanindex, ch->n_start); // n_start is increased on 9xx
+	paulaSetLength(ch->n_chanindex, ch->n_length);
+	paulaSetPeriod(ch->n_chanindex, ch->n_period);
+	paulaStartDMA(ch->n_chanindex);
+
+	// these take effect after the current DMA cycle is done
+	paulaSetData(ch->n_chanindex, ch->n_loopstart);
+	paulaSetLength(ch->n_chanindex, ch->n_replen);
+
+	updateSpectrumAnalyzer(ch->n_volume, ch->n_period);
+	setVUMeterHeight(ch);
+}
+
+static void retrigNote(moduleChannel_t *ch)
+{
+	if ((ch->n_cmd & 0xF) > 0)
+	{
+		if (song->tick == 0 && (ch->n_note & 0xFFF) > 0)
+			return;
+
+		if (song->tick % (ch->n_cmd & 0xF) == 0)
+			doRetrg(ch);
+	}
+}
+
+static void volumeSlide(moduleChannel_t *ch)
+{
+	uint8_t cmd = ch->n_cmd & 0xFF;
+
+	if ((cmd & 0xF0) == 0)
+	{
+		ch->n_volume -= cmd & 0x0F;
+		if (ch->n_volume < 0)
+			ch->n_volume = 0;
+	}
+	else
+	{
+		ch->n_volume += cmd >> 4;
+		if (ch->n_volume > 64)
+			ch->n_volume = 64;
+	}
+}
+
+static void volumeFineUp(moduleChannel_t *ch)
+{
+	if (song->tick == 0)
+	{
+		ch->n_volume += ch->n_cmd & 0xF;
+		if (ch->n_volume > 64)
+			ch->n_volume = 64;
+	}
+}
+
+static void volumeFineDown(moduleChannel_t *ch)
+{
+	if (song->tick == 0)
+	{
+		ch->n_volume -= ch->n_cmd & 0xF;
+		if (ch->n_volume < 0)
+			ch->n_volume = 0;
+	}
+}
+
+static void noteCut(moduleChannel_t *ch)
+{
+	if (song->tick == (ch->n_cmd & 0xF))
+		ch->n_volume = 0;
+}
+
+static void noteDelay(moduleChannel_t *ch)
+{
+	if (song->tick == (ch->n_cmd & 0xF) && (ch->n_note & 0xFFF) > 0)
+		doRetrg(ch);
+}
+
+static void patternDelay(moduleChannel_t *ch)
+{
+	if (song->tick == 0 && pattDelTime2 == 0)
+		pattDelTime = (ch->n_cmd & 0xF) + 1;
+}
+
+static void funkIt(moduleChannel_t *ch)
+{
+	if (song->tick == 0)
+	{
+		ch->n_glissfunk = ((ch->n_cmd & 0xF) << 4) | (ch->n_glissfunk & 0xF);
+		if ((ch->n_glissfunk & 0xF0) > 0)
+			updateFunk(ch);
+	}
+}
+
+static void positionJump(moduleChannel_t *ch)
+{
+	modOrder = (ch->n_cmd & 0xFF) - 1; // 0xFF (B00) jumps to pat 0
+	pBreakPosition = 0;
+	posJumpAssert = true;
+}
+
+static void volumeChange(moduleChannel_t *ch)
+{
+	ch->n_volume = ch->n_cmd & 0xFF;
+	if ((uint8_t)ch->n_volume > 64)
+		ch->n_volume = 64;
+}
+
+static void patternBreak(moduleChannel_t *ch)
+{
+	pBreakPosition = (((ch->n_cmd & 0xF0) >> 4) * 10) + (ch->n_cmd & 0x0F);
+	if ((uint8_t)pBreakPosition > 63)
+		pBreakPosition = 0;
+
+	posJumpAssert = true;
+}
+
+static void setSpeed(moduleChannel_t *ch)
+{
+	if ((ch->n_cmd & 0xFF) > 0)
+	{
+		song->tick = 0;
+
+		if (editor.timingMode == TEMPO_MODE_VBLANK || (ch->n_cmd & 0xFF) < 32)
+			modSetSpeed(ch->n_cmd & 0xFF);
+		else
+			setBPMFlag = ch->n_cmd & 0xFF; // CIA doesn't refresh its registers until the next interrupt, so change it later
+	}
+	else
+	{
+		editor.songPlaying = false;
+		editor.playMode = PLAY_MODE_NORMAL;
+		editor.currMode = MODE_IDLE;
+
+		pointerSetMode(POINTER_MODE_IDLE, DO_CARRY);
+	}
+}
+
+static void arpeggio(moduleChannel_t *ch)
+{
+	uint8_t arpTick, arpNote;
+	const int16_t *periods;
+
+	assert(song->tick < 32);
+	arpTick = arpTickTable[song->tick]; // 0, 1, 2
+
+	if (arpTick == 1)
+	{
+		arpNote = (uint8_t)(ch->n_cmd >> 4);
+	}
+	else if (arpTick == 2)
+	{
+		arpNote = ch->n_cmd & 0xF;
+	}
+	else // arpTick 0
+	{
+		paulaSetPeriod(ch->n_chanindex, ch->n_period);
+		return;
+	}
+
+	/* 8bitbubsy: If the finetune is -1, this can overflow up to
+	** 15 words outside of the table. The table is padded with
+	** the correct overflow values to allow this to safely happen
+	** and sound correct at the same time.
+	*/
+	periods = &periodTable[ch->n_finetune * 37];
+	for (int32_t baseNote = 0; baseNote < 37; baseNote++)
+	{
+		if (ch->n_period >= periods[baseNote])
+		{
+			paulaSetPeriod(ch->n_chanindex, periods[baseNote+arpNote]);
+			break;
+		}
+	}
+}
+
+static void portaUp(moduleChannel_t *ch)
+{
+	ch->n_period -= (ch->n_cmd & 0xFF) & lowMask;
+	lowMask = 0xFF;
+
+	if ((ch->n_period & 0xFFF) < 113)
+		ch->n_period = (ch->n_period & 0xF000) | 113;
+
+	paulaSetPeriod(ch->n_chanindex, ch->n_period & 0xFFF);
+}
+
+static void portaDown(moduleChannel_t *ch)
+{
+	ch->n_period += (ch->n_cmd & 0xFF) & lowMask;
+	lowMask = 0xFF;
+
+	if ((ch->n_period & 0xFFF) > 856)
+		ch->n_period = (ch->n_period & 0xF000) | 856;
+
+	paulaSetPeriod(ch->n_chanindex, ch->n_period & 0xFFF);
+}
+
+static void filterOnOff(moduleChannel_t *ch)
+{
+	setLEDFilter(!(ch->n_cmd & 1));
+}
+
+static void finePortaUp(moduleChannel_t *ch)
+{
+	if (song->tick == 0)
+	{
+		lowMask = 0xF;
+		portaUp(ch);
+	}
+}
+
+static void finePortaDown(moduleChannel_t *ch)
+{
+	if (song->tick == 0)
+	{
+		lowMask = 0xF;
+		portaDown(ch);
+	}
+}
+
+static void setTonePorta(moduleChannel_t *ch)
+{
+	uint8_t i;
+	const int16_t *portaPointer;
+	uint16_t note;
+
+	note = ch->n_note & 0xFFF;
+	portaPointer = &periodTable[ch->n_finetune * 37];
+
+	i = 0;
+	while (true)
+	{
+		// portaPointer[36] = 0, so i=36 is safe
+		if (note >= portaPointer[i])
+			break;
+
+		if (++i >= 37)
+		{
+			i = 35;
+			break;
+		}
+	}
+
+	if ((ch->n_finetune & 8) && i > 0)
+		i--;
+
+	ch->n_wantedperiod = portaPointer[i];
+	ch->n_toneportdirec = 0;
+
+	     if (ch->n_period == ch->n_wantedperiod) ch->n_wantedperiod = 0;
+	else if (ch->n_period > ch->n_wantedperiod) ch->n_toneportdirec = 1;
+}
+
+static void tonePortNoChange(moduleChannel_t *ch)
+{
+	uint8_t i;
+	const int16_t *portaPointer;
+
+	if (ch->n_wantedperiod <= 0)
+		return;
+
+	if (ch->n_toneportdirec > 0)
+	{
+		ch->n_period -= ch->n_toneportspeed;
+		if (ch->n_period <= ch->n_wantedperiod)
+		{
+			ch->n_period = ch->n_wantedperiod;
+			ch->n_wantedperiod = 0;
+		}
+	}
+	else
+	{
+		ch->n_period += ch->n_toneportspeed;
+		if (ch->n_period >= ch->n_wantedperiod)
+		{
+			ch->n_period = ch->n_wantedperiod;
+			ch->n_wantedperiod = 0;
+		}
+	}
+
+	if ((ch->n_glissfunk & 0xF) == 0)
+	{
+		paulaSetPeriod(ch->n_chanindex, ch->n_period);
+	}
+	else
+	{
+		portaPointer = &periodTable[ch->n_finetune * 37];
+
+		i = 0;
+		while (true)
+		{
+			// portaPointer[36] = 0, so i=36 is safe
+			if (ch->n_period >= portaPointer[i])
+				break;
+
+			if (++i >= 37)
+			{
+				i = 35;
+				break;
+			}
+		}
+
+		paulaSetPeriod(ch->n_chanindex, portaPointer[i]);
+	}
+}
+
+static void tonePortamento(moduleChannel_t *ch)
+{
+	if ((ch->n_cmd & 0xFF) > 0)
+	{
+		ch->n_toneportspeed = ch->n_cmd & 0xFF;
+		ch->n_cmd &= 0xFF00;
+	}
+
+	tonePortNoChange(ch);
+}
+
+static void vibratoNoChange(moduleChannel_t *ch)
+{
+	uint8_t vibratoTemp;
+	int16_t vibratoData;
+
+	vibratoTemp = (ch->n_vibratopos / 4) & 31;
+	vibratoData = ch->n_wavecontrol & 3;
+
+	if (vibratoData == 0)
+	{
+		vibratoData = vibratoTable[vibratoTemp];
+	}
+	else
+	{
+		if (vibratoData == 1)
+		{
+			if (ch->n_vibratopos < 0)
+				vibratoData = 255 - (vibratoTemp * 8);
+			else
+				vibratoData = vibratoTemp * 8;
+		}
+		else
+		{
+			vibratoData = 255;
+		}
+	}
+
+	vibratoData = (vibratoData * (ch->n_vibratocmd & 0xF)) / 128;
+
+	if (ch->n_vibratopos < 0)
+		vibratoData = ch->n_period - vibratoData;
+	else
+		vibratoData = ch->n_period + vibratoData;
+
+	paulaSetPeriod(ch->n_chanindex, vibratoData);
+
+	ch->n_vibratopos += ((ch->n_vibratocmd >> 4) * 4);
+}
+
+static void vibrato(moduleChannel_t *ch)
+{
+	if ((ch->n_cmd & 0xFF) > 0)
+	{
+		if ((ch->n_cmd & 0x0F) > 0)
+			ch->n_vibratocmd = (ch->n_vibratocmd & 0xF0) | (ch->n_cmd & 0x0F);
+
+		if ((ch->n_cmd & 0xF0) > 0)
+			ch->n_vibratocmd = (ch->n_cmd & 0xF0) | (ch->n_vibratocmd & 0x0F);
+	}
+
+	vibratoNoChange(ch);
+}
+
+static void tonePlusVolSlide(moduleChannel_t *ch)
+{
+	tonePortNoChange(ch);
+	volumeSlide(ch);
+}
+
+static void vibratoPlusVolSlide(moduleChannel_t *ch)
+{
+	vibratoNoChange(ch);
+	volumeSlide(ch);
+}
+
+static void tremolo(moduleChannel_t *ch)
+{
+	int8_t tremoloTemp;
+	int16_t tremoloData;
+
+	if ((ch->n_cmd & 0xFF) > 0)
+	{
+		if ((ch->n_cmd & 0x0F) > 0)
+			ch->n_tremolocmd = (ch->n_tremolocmd & 0xF0) | (ch->n_cmd & 0x0F);
+
+		if ((ch->n_cmd & 0xF0) > 0)
+			ch->n_tremolocmd = (ch->n_cmd & 0xF0) | (ch->n_tremolocmd & 0x0F);
+	}
+
+	tremoloTemp = (ch->n_tremolopos / 4) & 31;
+	tremoloData = (ch->n_wavecontrol >> 4) & 3;
+
+	if (!tremoloData)
+	{
+		tremoloData = vibratoTable[tremoloTemp];
+	}
+	else
+	{
+		if (tremoloData == 1)
+		{
+			if (ch->n_vibratopos < 0) // PT bug, should've been n_tremolopos
+				tremoloData = 255 - (tremoloTemp * 8);
+			else
+				tremoloData = tremoloTemp * 8;
+		}
+		else
+		{
+			tremoloData = 255;
+		}
+	}
+
+	tremoloData = (tremoloData * (ch->n_tremolocmd & 0xF)) / 64;
+
+	if (ch->n_tremolopos < 0)
+	{
+		tremoloData = ch->n_volume - tremoloData;
+		if (tremoloData < 0)
+			tremoloData = 0;
+	}
+	else
+	{
+		tremoloData = ch->n_volume + tremoloData;
+		if (tremoloData > 64)
+			tremoloData = 64;
+	}
+
+	paulaSetVolume(ch->n_chanindex, tremoloData);
+
+	ch->n_tremolopos += (ch->n_tremolocmd >> 4) * 4;
+}
+
+static void sampleOffset(moduleChannel_t *ch)
+{
+	uint16_t newOffset;
+
+	if ((ch->n_cmd & 0xFF) > 0)
+		ch->n_sampleoffset = ch->n_cmd & 0xFF;
+
+	newOffset = ch->n_sampleoffset << 7;
+
+	if ((int16_t)newOffset < (int16_t)ch->n_length)
+	{
+		ch->n_length -= newOffset;
+		ch->n_start += newOffset*2;
+	}
+	else
+	{
+		ch->n_length = 1;
+	}
+}
+
+static void E_Commands(moduleChannel_t *ch)
+{
+	uint8_t cmd;
+
+	cmd = (ch->n_cmd & 0xF0) >> 4;
+	switch (cmd)
+	{
+		case 0x0: filterOnOff(ch);       break;
+		case 0x1: finePortaUp(ch);       break;
+		case 0x2: finePortaDown(ch);     break;
+		case 0x3: setGlissControl(ch);   break;
+		case 0x4: setVibratoControl(ch); break;
+		case 0x5: setFineTune(ch);       break;
+		case 0x6: jumpLoop(ch);          break;
+		case 0x7: setTremoloControl(ch); break;
+		case 0x8: karplusStrong(ch);     break;
+		default: break;
+	}
+
+	if (editor.muted[ch->n_chanindex])
+		return;
+
+	switch (cmd)
+	{
+		case 0x9: retrigNote(ch);     break;
+		case 0xA: volumeFineUp(ch);   break;
+		case 0xB: volumeFineDown(ch); break;
+		case 0xC: noteCut(ch);        break;
+		case 0xD: noteDelay(ch);      break;
+		case 0xE: patternDelay(ch);   break;
+		case 0xF: funkIt(ch);         break;
+		default: break;
+	}
+}
+
+static void checkMoreEffects(moduleChannel_t *ch)
+{
+	switch ((ch->n_cmd & 0xF00) >> 8)
+	{
+		case 0x9: sampleOffset(ch); break;
+		case 0xB: positionJump(ch); break;
+
+		case 0xC:
+		{
+			if (!editor.muted[ch->n_chanindex])
+				volumeChange(ch);
+		}
+		break;
+
+		case 0xD: patternBreak(ch); break;
+		case 0xE: E_Commands(ch);   break;
+		case 0xF: setSpeed(ch);     break;
+
+		default:
+		{
+			if (!editor.muted[ch->n_chanindex])
+				paulaSetPeriod(ch->n_chanindex, ch->n_period);
+		}
+		break;
+	}
+}
+
+static void checkEffects(moduleChannel_t *ch)
+{
+	uint8_t effect;
+
+	if (editor.muted[ch->n_chanindex])
+		return;
+
+	updateFunk(ch);
+
+	effect = (ch->n_cmd & 0xF00) >> 8;
+
+	if ((ch->n_cmd & 0xFFF) > 0)
+	{
+		switch (effect)
+		{
+			case 0x0: arpeggio(ch);            break;
+			case 0x1: portaUp(ch);             break;
+			case 0x2: portaDown(ch);           break;
+			case 0x3: tonePortamento(ch);      break;
+			case 0x4: vibrato(ch);             break;
+			case 0x5: tonePlusVolSlide(ch);    break;
+			case 0x6: vibratoPlusVolSlide(ch); break;
+			case 0xE: E_Commands(ch);          break;
+
+			case 0x7:
+			{
+				paulaSetPeriod(ch->n_chanindex, ch->n_period);
+				tremolo(ch);
+			}
+			break;
+
+			case 0xA:
+			{
+				paulaSetPeriod(ch->n_chanindex, ch->n_period);
+				volumeSlide(ch);
+			}
+			break;
+
+			default: paulaSetPeriod(ch->n_chanindex, ch->n_period); break;
+		}
+	}
+
+	if (effect != 0x7)
+		paulaSetVolume(ch->n_chanindex, ch->n_volume);
+}
+
+static void setPeriod(moduleChannel_t *ch)
+{
+	uint8_t i;
+	uint16_t note;
+
+	note = ch->n_note & 0xFFF;
+	for (i = 0; i < 37; i++)
+	{
+		// periodTable[36] = 0, so i=36 is safe
+		if (note >= periodTable[i])
+			break;
+	}
+
+	// BUG: yes it's 'safe' if i=37 because of padding at the end of period table
+	ch->n_period = periodTable[(ch->n_finetune * 37) + i];
+
+	if ((ch->n_cmd & 0xFF0) != 0xED0) // no note delay
+	{
+		if ((ch->n_wavecontrol & 0x04) == 0) ch->n_vibratopos = 0;
+		if ((ch->n_wavecontrol & 0x40) == 0) ch->n_tremolopos = 0;
+
+		paulaSetLength(ch->n_chanindex, ch->n_length);
+		paulaSetData(ch->n_chanindex, ch->n_start);
+
+		if (ch->n_start == NULL)
+		{
+			ch->n_loopstart = NULL;
+			paulaSetLength(ch->n_chanindex, 1);
+			ch->n_replen = 1;
+		}
+
+		paulaSetPeriod(ch->n_chanindex, ch->n_period);
+
+		if (!editor.muted[ch->n_chanindex])
+		{
+			paulaStartDMA(ch->n_chanindex);
+			updateSpectrumAnalyzer(ch->n_volume, ch->n_period);
+			setVUMeterHeight(ch);
+		}
+		else
+		{
+			paulaStopDMA(ch->n_chanindex);
+		}
+	}
+
+	checkMoreEffects(ch);
+}
+
+static void checkMetronome(moduleChannel_t *ch, note_t *note)
+{
+	if (editor.metroFlag && editor.metroChannel > 0)
+	{
+		if (ch->n_chanindex == editor.metroChannel-1 && (song->row % editor.metroSpeed) == 0)
+		{
+			note->sample = 0x1F;
+			note->period = (((song->row / editor.metroSpeed) % editor.metroSpeed) == 0) ? 160 : 214;
+		}
+	}
+}
+
+static void playVoice(moduleChannel_t *ch)
+{
+	uint8_t cmd;
+	moduleSample_t *s;
+	note_t note;
+
+	if (ch->n_note == 0 && ch->n_cmd == 0)
+		paulaSetPeriod(ch->n_chanindex, ch->n_period);
+
+	note = song->patterns[modPattern][(song->row * AMIGA_VOICES) + ch->n_chanindex];
+	checkMetronome(ch, &note);
+
+	ch->n_note = note.period;
+	ch->n_cmd = (note.command << 8) | note.param;
+
+	if (note.sample >= 1 && note.sample <= 31) // SAFETY BUG FIX: don't handle sample-numbers >31
+	{
+		ch->n_samplenum = note.sample - 1;
+		s = &song->samples[ch->n_samplenum];
+
+		ch->n_start = &song->sampleData[s->offset];
+		ch->n_finetune = s->fineTune;
+		ch->n_volume = s->volume;
+		ch->n_length = s->length / 2;
+		ch->n_replen = s->loopLength / 2;
+
+		if (s->loopStart > 0)
+		{
+			ch->n_loopstart = ch->n_start + s->loopStart;
+			ch->n_wavestart = ch->n_loopstart;
+			ch->n_length = (s->loopStart / 2) + ch->n_replen;
+		}
+		else
+		{
+			ch->n_loopstart = ch->n_start;
+			ch->n_wavestart = ch->n_start;
+		}
+
+		// non-PT2 quirk
+		if (ch->n_length == 0)
+			ch->n_loopstart = ch->n_wavestart = &song->sampleData[RESERVED_SAMPLE_OFFSET]; // dummy sample
+	}
+
+	if ((ch->n_note & 0xFFF) > 0)
+	{
+		if ((ch->n_cmd & 0xFF0) == 0xE50) // set finetune
+		{
+			setFineTune(ch);
+			setPeriod(ch);
+		}
+		else
+		{
+			cmd = (ch->n_cmd & 0xF00) >> 8;
+			if (cmd == 3 || cmd == 5)
+			{
+				setVUMeterHeight(ch);
+				setTonePorta(ch);
+				checkMoreEffects(ch);
+			}
+			else if (cmd == 9)
+			{
+				checkMoreEffects(ch);
+				setPeriod(ch);
+			}
+			else
+			{
+				setPeriod(ch);
+			}
+		}
+	}
+	else
+	{
+		checkMoreEffects(ch);
+	}
+}
+
+static void nextPosition(void)
+{
+	song->row = pBreakPosition;
+	pBreakPosition = 0;
+	posJumpAssert = false;
+
+	if (editor.playMode != PLAY_MODE_PATTERN ||
+		(editor.currMode == MODE_RECORD && editor.recordMode != RECORD_PATT))
+	{
+		if (editor.stepPlayEnabled)
+		{
+			doStopIt(true);
+
+			editor.stepPlayEnabled = false;
+			editor.stepPlayBackwards = false;
+
+			if (!editor.isWAVRendering && !editor.isSMPRendering)
+				song->currRow = song->row;
+
+			return;
+		}
+
+		modOrder = (modOrder + 1) & 0x7F;
+		if (modOrder >= song->header.numOrders)
+		{
+			modOrder = 0;
+			modHasBeenPlayed = true;
+
+			if (config.compoMode) // stop song for music competitions playing
+			{
+				doStopIt(true);
+				turnOffVoices();
+
+				song->currOrder = 0;
+				song->currRow = song->row = 0;
+				song->currPattern = modPattern = (int8_t)song->header.order[0];
+
+				editor.currPatternDisp = &song->currPattern;
+				editor.currPosEdPattDisp = &song->currPattern;
+				editor.currPatternDisp = &song->currPattern;
+				editor.currPosEdPattDisp = &song->currPattern;
+
+				if (ui.posEdScreenShown)
+					ui.updatePosEd = true;
+
+				ui.updateSongPos = true;
+				ui.updateSongPattern = true;
+				ui.updateCurrPattText = true;
+			}
+		}
+
+		modPattern = (int8_t)song->header.order[modOrder];
+		if (modPattern > MAX_PATTERNS-1)
+			modPattern = MAX_PATTERNS-1;
+
+		updateUIPositions = true;
+	}
+}
+
+bool intMusic(void)
+{
+	uint8_t i;
+	uint16_t *patt;
+	moduleChannel_t *c;
+
+	if (modBPM > 0)
+		editor.musicTime += (65536 / modBPM); // for playback counter
+
+	if (updateUIPositions)
+	{
+		updateUIPositions = false;
+
+		if (!editor.isWAVRendering && !editor.isSMPRendering)
+		{
+			if (editor.playMode != PLAY_MODE_PATTERN)
+			{
+				song->currOrder = modOrder;
+				song->currPattern = modPattern;
+
+				patt = &song->header.order[modOrder];
+				editor.currPatternDisp = patt;
+				editor.currPosEdPattDisp = patt;
+				editor.currPatternDisp = patt;
+				editor.currPosEdPattDisp = patt;
+
+				if (ui.posEdScreenShown)
+					ui.updatePosEd = true;
+
+				ui.updateSongPos = true;
+				ui.updateSongPattern = true;
+				ui.updateCurrPattText = true;
+			}
+		}
+	}
+
+	// PT quirk: CIA refreshes its timer values on the next interrupt, so do the real tempo change here
+	if (setBPMFlag != 0)
+	{
+		modSetTempo(setBPMFlag);
+		setBPMFlag = 0;
+	}
+
+	if (editor.isWAVRendering && song->tick == 0)
+		editor.rowVisitTable[(modOrder * MOD_ROWS) + song->row] = true;
+
+	if (!editor.stepPlayEnabled)
+		song->tick++;
+
+	if (song->tick >= song->speed || editor.stepPlayEnabled)
+	{
+		song->tick = 0;
+
+		if (pattDelTime2 == 0)
+		{
+			for (i = 0; i < AMIGA_VOICES; i++)
+			{
+				c = &song->channels[i];
+
+				playVoice(c);
+				paulaSetVolume(i, c->n_volume);
+
+				// these take effect after the current DMA cycle is done
+				paulaSetData(i, c->n_loopstart);
+				paulaSetLength(i, c->n_replen);
+			}
+		}
+		else
+		{
+			for (i = 0; i < AMIGA_VOICES; i++)
+				checkEffects(&song->channels[i]);
+		}
+
+		if (!editor.isWAVRendering && !editor.isSMPRendering)
+		{
+			song->currRow = song->row;
+			ui.updatePatternData = true;
+		}
+
+		if (!editor.stepPlayBackwards)
+		{
+			song->row++;
+			song->rowsCounter++;
+		}
+
+		if (pattDelTime > 0)
+		{
+			pattDelTime2 = pattDelTime;
+			pattDelTime = 0;
+		}
+
+		if (pattDelTime2 > 0)
+		{
+			if (--pattDelTime2 > 0)
+				song->row--;
+		}
+
+		if (pBreakFlag)
+		{
+			song->row = pBreakPosition;
+			pBreakPosition = 0;
+			pBreakFlag = false;
+		}
+
+		if (editor.blockMarkFlag)
+			ui.updateStatusText = true;
+
+		if (editor.stepPlayEnabled)
+		{
+			doStopIt(true);
+
+			song->currRow = song->row & 0x3F;
+			ui.updatePatternData = true;
+
+			editor.stepPlayEnabled = false;
+			editor.stepPlayBackwards = false;
+			ui.updatePatternData = true;
+
+			return true;
+		}
+
+		if (song->row >= MOD_ROWS || posJumpAssert)
+		{
+			if (editor.isSMPRendering)
+				modHasBeenPlayed = true;
+
+			nextPosition();
+		}
+
+		if (editor.isWAVRendering && !pattDelTime2 && editor.rowVisitTable[(modOrder * MOD_ROWS) + song->row])
+			modHasBeenPlayed = true;
+	}
+	else
+	{
+		for (i = 0; i < AMIGA_VOICES; i++)
+			checkEffects(&song->channels[i]);
+
+		if (posJumpAssert)
+			nextPosition();
+	}
+
+	if ((editor.isSMPRendering || editor.isWAVRendering) && modHasBeenPlayed && song->tick == song->speed-1)
+	{
+		modHasBeenPlayed = false;
+		return false;
+	}
+
+	return true;
+}
+
+void modSetPattern(uint8_t pattern)
+{
+	modPattern = pattern;
+	song->currPattern = modPattern;
+	ui.updateCurrPattText = true;
+}
+
+void modSetPos(int16_t order, int16_t row)
+{
+	int16_t posEdPos;
+
+	if (row != -1)
+	{
+		row = CLAMP(row, 0, 63);
+
+		song->tick = 0;
+		song->row = (int8_t)row;
+		song->currRow = (int8_t)row;
+	}
+
+	if (order != -1)
+	{
+		if (order >= 0)
+		{
+			modOrder = order;
+			song->currOrder = order;
+			ui.updateSongPos = true;
+
+			if (editor.currMode == MODE_PLAY && editor.playMode == PLAY_MODE_NORMAL)
+			{
+				modPattern = (int8_t)song->header.order[order];
+				if (modPattern > MAX_PATTERNS-1)
+					modPattern = MAX_PATTERNS-1;
+
+				song->currPattern = modPattern;
+				ui.updateCurrPattText = true;
+			}
+
+			ui.updateSongPattern = true;
+			editor.currPatternDisp = &song->header.order[modOrder];
+
+			posEdPos = song->currOrder;
+			if (posEdPos > song->header.numOrders-1)
+				posEdPos = song->header.numOrders-1;
+
+			editor.currPosEdPattDisp = &song->header.order[posEdPos];
+
+			if (ui.posEdScreenShown)
+				ui.updatePosEd = true;
+		}
+	}
+
+	ui.updatePatternData = true;
+
+	if (editor.blockMarkFlag)
+		ui.updateStatusText = true;
+}
+
+void modSetTempo(uint16_t bpm)
+{
+	uint32_t smpsPerTick;
+
+	if (bpm < 32)
+		return;
+
+	const bool audioWasntLocked = !audio.locked;
+	if (audioWasntLocked)
+		lockAudio();
+
+	modBPM = bpm;
+	if (!editor.isSMPRendering && !editor.isWAVRendering)
+	{
+		song->currBPM = bpm;
+		ui.updateSongBPM = true;
+	}
+
+	bpm -= 32; // 32..255 -> 0..223
+
+	if (editor.isSMPRendering)
+		smpsPerTick = editor.pat2SmpHQ ? audio.bpmTab28kHz[bpm] : audio.bpmTab22kHz[bpm];
+	else if (editor.isWAVRendering)
+		smpsPerTick = audio.bpmTabMod2Wav[bpm];
+	else
+		smpsPerTick = audio.bpmTab[bpm];
+
+	mixerSetSamplesPerTick(smpsPerTick);
+
+	if (audioWasntLocked)
+		unlockAudio();
+}
+
+void modStop(void)
+{
+	editor.songPlaying = false;
+	turnOffVoices();
+
+	for (int32_t i = 0; i < AMIGA_VOICES; i++)
+	{
+		moduleChannel_t *c = &song->channels[i];
+
+		c->n_wavecontrol = 0;
+		c->n_glissfunk = 0;
+		c->n_finetune = 0;
+		c->n_loopcount = 0;
+	}
+
+	pBreakFlag = false;
+	pattDelTime = 0;
+	pattDelTime2 = 0;
+	pBreakPosition = 0;
+	posJumpAssert = false;
+	modHasBeenPlayed = true;
+}
+
+void playPattern(int8_t startRow)
+{
+	if (!editor.stepPlayEnabled)
+		pointerSetMode(POINTER_MODE_PLAY, DO_CARRY);
+
+	mixerClearSampleCounter();
+
+	song->currRow = song->row = startRow & 0x3F;
+	song->tick = song->speed;
+
+	editor.playMode = PLAY_MODE_PATTERN;
+	editor.currMode = MODE_PLAY;
+	editor.didQuantize = false;
+	editor.songPlaying = true;
+}
+
+void incPatt(void)
+{
+	modPattern++;
+	if (modPattern > MAX_PATTERNS-1)
+		modPattern = 0;
+
+	song->currPattern = modPattern;
+
+	ui.updatePatternData = true;
+	ui.updateCurrPattText = true;
+}
+
+void decPatt(void)
+{
+	modPattern--;
+	if (modPattern < 0)
+		modPattern = MAX_PATTERNS - 1;
+
+	song->currPattern = modPattern;
+
+	ui.updatePatternData = true;
+	ui.updateCurrPattText = true;
+}
+
+void modPlay(int16_t patt, int16_t order, int8_t row)
+{
+	uint8_t oldPlayMode, oldMode;
+
+	doStopIt(false);
+	turnOffVoices();
+	mixerClearSampleCounter();
+
+	if (row != -1)
+	{
+		if (row >= 0 && row <= 63)
+		{
+			song->row = row;
+			song->currRow = row;
+		}
+	}
+	else
+	{
+		song->row = 0;
+		song->currRow = 0;
+	}
+
+	if (editor.playMode != PLAY_MODE_PATTERN)
+	{
+		if (modOrder >= song->header.numOrders)
+		{
+			modOrder = 0;
+			song->currOrder = 0;
+		}
+
+		if (order >= 0 && order < song->header.numOrders)
+		{
+			modOrder = order;
+			song->currOrder = order;
+		}
+
+		if (order >= song->header.numOrders)
+		{
+			modOrder = 0;
+			song->currOrder = 0;
+		}
+	}
+
+	if (patt >= 0 && patt <= MAX_PATTERNS-1)
+		song->currPattern = modPattern = (int8_t)patt;
+	else
+		song->currPattern = modPattern = (int8_t)song->header.order[modOrder];
+
+	editor.currPatternDisp = &song->header.order[modOrder];
+	editor.currPosEdPattDisp = &song->header.order[modOrder];
+
+	oldPlayMode = editor.playMode;
+	oldMode = editor.currMode;
+
+	editor.playMode = oldPlayMode;
+	editor.currMode = oldMode;
+
+	song->tick = song->speed;
+	modHasBeenPlayed = false;
+	editor.songPlaying = true;
+	editor.didQuantize = false;
+	editor.musicTime = 0;
+
+	if (!editor.isSMPRendering && !editor.isWAVRendering)
+	{
+		ui.updateSongPos = true;
+		ui.updatePatternData = true;
+		ui.updateSongPattern = true;
+		ui.updateCurrPattText = true;
+	}
+}
+
+void clearSong(void)
+{
+	uint8_t i;
+	moduleChannel_t *ch;
+
+	assert(song != NULL);
+	if (song == NULL)
+		return;
+
+	memset(song->header.order, 0, sizeof (song->header.order));
+	memset(song->header.name, 0, sizeof (song->header.name));
+
+	editor.muted[0] = false;
+	editor.muted[1] = false;
+	editor.muted[2] = false;
+	editor.muted[3] = false;
+
+	editor.f6Pos = 0;
+	editor.f7Pos = 16;
+	editor.f8Pos = 32;
+	editor.f9Pos = 48;
+	editor.f10Pos = 63;
+
+	editor.musicTime = 0;
+
+	editor.metroFlag = false;
+	editor.currSample = 0;
+	editor.editMoveAdd = 1;
+	editor.blockMarkFlag = false;
+	editor.swapChannelFlag = false;
+
+	song->header.numOrders = 1;
+
+	for (i = 0; i < MAX_PATTERNS; i++)
+		memset(song->patterns[i], 0, (MOD_ROWS * AMIGA_VOICES) * sizeof (note_t));
+
+	for (i = 0; i < AMIGA_VOICES; i++)
+	{
+		ch = &song->channels[i];
+
+		ch->n_wavecontrol = 0;
+		ch->n_glissfunk = 0;
+		ch->n_finetune = 0;
+		ch->n_loopcount = 0;
+	}
+
+	modSetPos(0, 0); // this also refreshes pattern data
+
+	song->currOrder = 0;
+	song->currPattern = 0;
+	editor.currPatternDisp = &song->header.order[0];
+	editor.currPosEdPattDisp = &song->header.order[0];
+
+	modSetTempo(editor.initialTempo);
+	modSetSpeed(editor.initialSpeed);
+
+	setLEDFilter(false); // real PT doesn't do this there, but that's insane
+	updateCurrSample();
+
+	ui.updateSongSize = true;
+	renderMuteButtons();
+	updateWindowTitle(MOD_IS_MODIFIED);
+}
+
+void clearSamples(void)
+{
+	moduleSample_t *s;
+
+	assert(song != NULL);
+	if (song == NULL)
+		return;
+
+	for (uint8_t i = 0; i < MOD_SAMPLES; i++)
+	{
+		s = &song->samples[i];
+
+		s->fineTune = 0;
+		s->length = 0;
+		s->loopLength = 2;
+		s->loopStart = 0;
+		s->volume = 0;
+
+		memset(s->text, 0, sizeof (s->text));
+	}
+
+	memset(song->sampleData, 0, (MOD_SAMPLES + 1) * MAX_SAMPLE_LEN);
+
+	editor.currSample = 0;
+	editor.hiLowInstr = 0;
+	editor.sampleZero = false;
+	ui.editOpScreenShown = false;
+	ui.aboutScreenShown = false;
+	editor.blockMarkFlag = false;
+
+	editor.samplePos = 0;
+	updateCurrSample();
+
+	updateWindowTitle(MOD_IS_MODIFIED);
+}
+
+void clearAll(void)
+{
+	clearSamples();
+	clearSong();
+}
+
+void modFree(void)
+{
+	uint8_t i;
+
+	if (song == NULL)
+		return; // not allocated
+
+	const bool audioWasntLocked = !audio.locked;
+	if (audioWasntLocked)
+		lockAudio();
+
+	turnOffVoices();
+
+	for (i = 0; i < MAX_PATTERNS; i++)
+	{
+		if (song->patterns[i] != NULL)
+			free(song->patterns[i]);
+	}
+
+	if (song->sampleData != NULL)
+		free(song->sampleData);
+
+	free(song);
+	song = NULL;
+
+	if (audioWasntLocked)
+		unlockAudio();
+}
+
+void restartSong(void) // for the beginning of MOD2WAV/PAT2SMP
+{
+	if (editor.songPlaying)
+		modStop();
+
+	editor.playMode = PLAY_MODE_NORMAL;
+	editor.blockMarkFlag = false;
+	audio.forceMixerOff = true;
+
+	song->row = 0;
+	song->currRow = 0;
+	song->rowsCounter = 0;
+
+	memset(editor.rowVisitTable, 0, MOD_ORDERS * MOD_ROWS); // for MOD2WAV
+
+	if (editor.isSMPRendering)
+	{
+		modPlay(DONT_SET_PATTERN, DONT_SET_ORDER, DONT_SET_ROW);
+	}
+	else
+	{
+		song->currSpeed = 6;
+		song->currBPM = 125;
+		modSetSpeed(6);
+		modSetTempo(125);
+
+		modPlay(DONT_SET_PATTERN, 0, 0);
+	}
+}
+
+// this function is meant for the end of MOD2WAV/PAT2SMP
+void resetSong(void) // only call this after storeTempVariables() has been called!
+{
+	modStop();
+
+	editor.songPlaying = false;
+	editor.playMode = PLAY_MODE_NORMAL;
+	editor.currMode = MODE_IDLE;
+
+	turnOffVoices();
+
+	memset((int8_t *)editor.vuMeterVolumes,0, sizeof (editor.vuMeterVolumes));
+	memset((int8_t *)editor.realVuMeterVolumes, 0, sizeof (editor.realVuMeterVolumes));
+	memset((int8_t *)editor.spectrumVolumes, 0, sizeof (editor.spectrumVolumes));
+
+	memset(song->channels, 0, sizeof (song->channels));
+	for (uint8_t i = 0; i < AMIGA_VOICES; i++)
+		song->channels[i].n_chanindex = i;
+
+	modOrder = oldOrder;
+	modPattern = (int8_t)oldPattern;
+
+	song->row = oldRow;
+	song->currRow = oldRow;
+	song->currBPM = oldBPM;
+	song->currOrder = oldOrder;
+	song->currPattern = oldPattern;
+
+	editor.currPosDisp = &song->currOrder;
+	editor.currEditPatternDisp = &song->currPattern;
+	editor.currPatternDisp = &song->header.order[song->currOrder];
+	editor.currPosEdPattDisp = &song->header.order[song->currOrder];
+
+	modSetSpeed(oldSpeed);
+	modSetTempo(oldBPM);
+
+	doStopIt(true);
+
+	song->tick = 0;
+	modHasBeenPlayed = false;
+	audio.forceMixerOff = false;
+}
--- /dev/null
+++ b/src/pt2_sample_loader.c
@@ -1,0 +1,1773 @@
+// for finding memory leaks in debug mode with Visual Studio 
+#if defined _DEBUG && defined _MSC_VER
+#include <crtdbg.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <ctype.h> // tolower()
+#include "pt2_header.h"
+#include "pt2_textout.h"
+#include "pt2_mouse.h"
+#include "pt2_structs.h"
+#include "pt2_sampler.h" // fixSampleBeep()
+#include "pt2_audio.h"
+#include "pt2_visuals.h"
+#include "pt2_helpers.h"
+#include "pt2_unicode.h"
+#include "pt2_config.h"
+
+#define DOWNSAMPLE_CUTOFF_FACTOR 4.0
+
+enum
+{
+	WAV_FORMAT_PCM = 0x0001,
+	WAV_FORMAT_IEEE_FLOAT = 0x0003
+};
+
+static bool loadWAVSample(UNICHAR *fileName, char *entryName, int8_t forceDownSampling);
+static bool loadIFFSample(UNICHAR *fileName, char *entryName);
+static bool loadRAWSample(UNICHAR *fileName, char *entryName);
+static bool loadAIFFSample(UNICHAR *fileName, char *entryName, int8_t forceDownSampling);
+
+static bool loadedFileWasAIFF;
+
+static bool lowPassSample8Bit(int8_t *buffer, int32_t length, int32_t sampleFrequency, double cutoff)
+{
+	rcFilter_t filter;
+
+	if (buffer == NULL || length == 0 || cutoff == 0.0)
+		return false;
+
+	calcRCFilterCoeffs(sampleFrequency, cutoff, &filter);
+	clearRCFilterState(&filter);
+
+	for (int32_t i = 0; i < length; i++)
+	{
+		int32_t sample;
+		double dSample;
+
+		RCLowPassFilterMono(&filter, buffer[i], &dSample);
+		sample = (int32_t)dSample;
+
+		buffer[i] = (int8_t)CLAMP(sample, INT8_MIN, INT8_MAX);
+	}
+	
+	return true;
+}
+
+static bool lowPassSample8BitUnsigned(uint8_t *buffer, int32_t length, int32_t sampleFrequency, double cutoff)
+{
+	rcFilter_t filter;
+
+	if (buffer == NULL || length == 0 || cutoff == 0.0)
+		return false;
+
+	calcRCFilterCoeffs(sampleFrequency, cutoff, &filter);
+	clearRCFilterState(&filter);
+
+	for (int32_t i = 0; i < length; i++)
+	{
+		int32_t sample;
+		double dSample;
+
+		RCLowPassFilterMono(&filter, buffer[i] - 128, &dSample);
+		sample = (int32_t)dSample;
+
+		sample = CLAMP(sample, INT8_MIN, INT8_MAX);
+		buffer[i] = (uint8_t)(sample + 128);
+	}
+
+	return true;
+}
+
+static bool lowPassSample16Bit(int16_t *buffer, int32_t length, int32_t sampleFrequency, double cutoff)
+{
+	rcFilter_t filter;
+
+	if (buffer == NULL || length == 0 || cutoff == 0.0)
+		return false;
+
+	calcRCFilterCoeffs(sampleFrequency, cutoff, &filter);
+	clearRCFilterState(&filter);
+
+	for (int32_t i = 0; i < length; i++)
+	{
+		int32_t sample;
+		double dSample;
+
+		RCLowPassFilterMono(&filter, buffer[i], &dSample);
+		sample = (int32_t)dSample;
+
+		buffer[i] = (int16_t)CLAMP(sample, INT16_MIN, INT16_MAX);
+	}
+
+	return true;
+}
+
+static bool lowPassSample32Bit(int32_t *buffer, int32_t length, int32_t sampleFrequency, double cutoff)
+{
+	rcFilter_t filter;
+
+	if (buffer == NULL || length == 0 || cutoff == 0.0)
+		return false;
+
+	calcRCFilterCoeffs(sampleFrequency, cutoff, &filter);
+	clearRCFilterState(&filter);
+
+	for (int32_t i = 0; i < length; i++)
+	{
+		int64_t sample;
+		double dSample;
+
+		RCLowPassFilterMono(&filter, buffer[i], &dSample);
+		sample = (int32_t)dSample;
+
+		buffer[i] = (int32_t)CLAMP(sample, INT32_MIN, INT32_MAX);
+	}
+
+	return true;
+}
+
+static bool lowPassSampleFloat(float *buffer, int32_t length, int32_t sampleFrequency, double cutoff)
+{
+	rcFilter_t filter;
+
+	if (buffer == NULL || length == 0 || cutoff == 0.0)
+		return false;
+
+	calcRCFilterCoeffs(sampleFrequency, cutoff, &filter);
+	clearRCFilterState(&filter);
+
+	for (int32_t i = 0; i < length; i++)
+	{
+		double dSample;
+
+		RCLowPassFilterMono(&filter, buffer[i], &dSample);
+		buffer[i] = (float)dSample;
+	}
+
+	return true;
+}
+
+static bool lowPassSampleDouble(double *buffer, int32_t length, int32_t sampleFrequency, double cutoff)
+{
+	rcFilter_t filter;
+
+	if (buffer == NULL || length == 0 || cutoff == 0.0)
+		return false;
+
+	calcRCFilterCoeffs(sampleFrequency, cutoff, &filter);
+	clearRCFilterState(&filter);
+
+	for (int32_t i = 0; i < length; i++)
+	{
+		double dSample;
+		RCLowPassFilterMono(&filter, buffer[i], &dSample);
+
+		buffer[i] = dSample;
+	}
+
+	return true;
+}
+
+void extLoadWAVOrAIFFSampleCallback(bool downsample)
+{
+	if (loadedFileWasAIFF)
+		loadAIFFSample(editor.fileNameTmpU, editor.entryNameTmp, downsample);
+	else
+		loadWAVSample(editor.fileNameTmpU, editor.entryNameTmp, downsample);
+}
+
+bool loadWAVSample(UNICHAR *fileName, char *entryName, int8_t forceDownSampling)
+{
+	bool wavSampleNameFound;
+	uint8_t *audioDataU8;
+	int16_t *audioDataS16, tempVol, smp16;
+	uint16_t audioFormat, numChannels, bitsPerSample;
+	int32_t *audioDataS32, smp32;
+	uint32_t *audioDataU32, i, nameLen, chunkID, chunkSize;
+	uint32_t sampleLength, sampleRate, filesize, loopFlags;
+	uint32_t loopStart, loopEnd, dataPtr, dataLen, fmtPtr, endOfChunk, bytesRead;
+	uint32_t fmtLen, inamPtr, inamLen, smplPtr, smplLen, xtraPtr, xtraLen;
+	float *fAudioDataFloat, fSmp;
+	double *dAudioDataDouble, dSmp;
+	FILE *f;
+	moduleSample_t *s;
+
+	loadedFileWasAIFF = false;
+
+	// zero out chunk pointers and lengths
+	fmtPtr  = 0; fmtLen = 0;
+	dataPtr = 0; dataLen = 0;
+	inamPtr = 0; inamLen = 0;
+	xtraPtr = 0; xtraLen = 0;
+	smplPtr = 0; smplLen = 0;
+
+	wavSampleNameFound = false;
+
+	s = &song->samples[editor.currSample];
+
+	if (forceDownSampling == -1)
+	{
+		// these two *must* be fully wiped, for outputting reasons
+		memset(editor.fileNameTmpU, 0, PATH_MAX);
+		memset(editor.entryNameTmp, 0, PATH_MAX);
+		UNICHAR_STRCPY(editor.fileNameTmpU, fileName);
+		strcpy(editor.entryNameTmp, entryName);
+	}
+
+	f = UNICHAR_FOPEN(fileName, "rb");
+	if (f == NULL)
+	{
+		displayErrorMsg("FILE I/O ERROR !");
+		return false;
+	}
+
+	fseek(f, 0, SEEK_END);
+	filesize = ftell(f);
+	if (filesize == 0)
+	{
+		fclose(f);
+
+		displayErrorMsg("NOT A WAV !");
+		return false;
+	}
+
+	// look for wanted chunks and set up pointers + lengths
+	fseek(f, 12, SEEK_SET);
+
+	bytesRead = 0;
+	while (!feof(f) && bytesRead < filesize-12)
+	{
+		fread(&chunkID, 4, 1, f); if (feof(f)) break;
+		fread(&chunkSize, 4, 1, f); if (feof(f)) break;
+
+		endOfChunk = (ftell(f) + chunkSize) + (chunkSize & 1);
+		switch (chunkID)
+		{
+			case 0x20746D66: // "fmt "
+			{
+				fmtPtr = ftell(f);
+				fmtLen = chunkSize;
+			}
+			break;
+
+			case 0x61746164: // "data"
+			{
+				dataPtr = ftell(f);
+				dataLen = chunkSize;
+			}
+			break;
+
+			case 0x5453494C: // "LIST"
+			{
+				if (chunkSize >= 4)
+				{
+					fread(&chunkID, 4, 1, f);
+					if (chunkID == 0x4F464E49) // "INFO"
+					{
+						bytesRead = 0;
+						while (!feof(f) && (bytesRead < chunkSize))
+						{
+							fread(&chunkID, 4, 1, f);
+							fread(&chunkSize, 4, 1, f);
+
+							switch (chunkID)
+							{
+								case 0x4D414E49: // "INAM"
+								{
+									inamPtr = ftell(f);
+									inamLen = chunkSize;
+								}
+								break;
+
+								default: break;
+							}
+
+							bytesRead += (chunkSize + (chunkSize & 1));
+						}
+					}
+				}
+			}
+			break;
+
+			case 0x61727478: // "xtra"
+			{
+				xtraPtr = ftell(f);
+				xtraLen = chunkSize;
+			}
+			break;
+
+			case 0x6C706D73: // "smpl"
+			{
+				smplPtr = ftell(f);
+				smplLen = chunkSize;
+			}
+			break;
+
+			default: break;
+		}
+
+		bytesRead += chunkSize + (chunkSize & 1);
+		fseek(f, endOfChunk, SEEK_SET);
+	}
+
+	// we need at least "fmt " and "data" - check if we found them sanely
+	if ((fmtPtr == 0 || fmtLen < 16) || (dataPtr == 0 || dataLen == 0))
+	{
+		fclose(f);
+		displayErrorMsg("NOT A WAV !");
+		return false;
+	}
+
+	// ---- READ "fmt " CHUNK ----
+	fseek(f, fmtPtr, SEEK_SET);
+	fread(&audioFormat, 2, 1, f);
+	fread(&numChannels, 2, 1, f);
+	fread(&sampleRate,  4, 1, f);
+	fseek(f, 6, SEEK_CUR);
+	fread(&bitsPerSample, 2, 1, f);
+	sampleLength = dataLen;
+	// ---------------------------
+
+	if (sampleRate == 0 || sampleLength == 0 || sampleLength >= filesize*(bitsPerSample/8))
+	{
+		fclose(f);
+		displayErrorMsg("WAV CORRUPT !");
+		return false;
+	}
+
+	if (audioFormat != WAV_FORMAT_PCM && audioFormat != WAV_FORMAT_IEEE_FLOAT)
+	{
+		fclose(f);
+		displayErrorMsg("WAV UNSUPPORTED !");
+		return false;
+	}
+
+	if ((numChannels == 0) || (numChannels > 2))
+	{
+		fclose(f);
+		displayErrorMsg("WAV UNSUPPORTED !");
+		return false;
+	}
+
+	if (audioFormat == WAV_FORMAT_IEEE_FLOAT && bitsPerSample != 32 && bitsPerSample != 64)
+	{
+		fclose(f);
+		displayErrorMsg("WAV UNSUPPORTED !");
+		return false;
+	}
+
+	if (bitsPerSample != 8 && bitsPerSample != 16 && bitsPerSample != 24 && bitsPerSample != 32 && bitsPerSample != 64)
+	{
+		fclose(f);
+		displayErrorMsg("WAV UNSUPPORTED !");
+		return false;
+	}
+
+	if (sampleRate > 22050)
+	{
+		if (forceDownSampling == -1)
+		{
+			showDownsampleAskDialog();
+			fclose(f);
+			return true;
+		}
+	}
+	else
+	{
+		forceDownSampling = false;
+	}
+
+	// ---- READ SAMPLE DATA ----
+	fseek(f, dataPtr, SEEK_SET);
+
+	if (bitsPerSample == 8) // 8-BIT INTEGER SAMPLE
+	{
+		if (sampleLength > MAX_SAMPLE_LEN*4)
+			sampleLength = MAX_SAMPLE_LEN*4;
+
+		audioDataU8 = (uint8_t *)malloc(sampleLength * sizeof (uint8_t));
+		if (audioDataU8 == NULL)
+		{
+			fclose(f);
+			statusOutOfMemory();
+			return false;
+		}
+
+		// read sample data
+		if (fread(audioDataU8, 1, sampleLength, f) != sampleLength)
+		{
+			fclose(f);
+			free(audioDataU8);
+			displayErrorMsg("I/O ERROR !");
+			return false;
+		}
+
+		// convert from stereo to mono (if needed)
+		if (numChannels == 2)
+		{
+			sampleLength >>= 1;
+			for (i = 0; i < sampleLength-1; i++) // add right channel to left channel
+			{
+				smp16 = (audioDataU8[(i << 1) + 0] - 128) + (audioDataU8[(i << 1) + 1] - 128);
+				smp16 = 128 + (smp16 >> 1);
+				audioDataU8[i] = (uint8_t)smp16;
+			}
+		}
+
+		// 2x downsampling - remove every other sample (if needed)
+		if (forceDownSampling)
+		{
+			if (config.sampleLowpass)
+				lowPassSample8BitUnsigned(audioDataU8, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
+
+			sampleLength >>= 1;
+			for (i = 1; i < sampleLength; i++)
+				audioDataU8[i] = audioDataU8[i << 1];
+		}
+
+		if (sampleLength > MAX_SAMPLE_LEN)
+			sampleLength = MAX_SAMPLE_LEN;
+
+		turnOffVoices();
+		for (i = 0; i < MAX_SAMPLE_LEN; i++)
+		{
+			if (i <= (sampleLength & 0xFFFFFFFE))
+				song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = audioDataU8[i] - 128;
+			else
+				song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample
+		}
+
+		free(audioDataU8);
+	}
+	else if (bitsPerSample == 16) // 16-BIT INTEGER SAMPLE
+	{
+		sampleLength >>= 1;
+		if (sampleLength > MAX_SAMPLE_LEN*4)
+			sampleLength = MAX_SAMPLE_LEN*4;
+
+		audioDataS16 = (int16_t *)malloc(sampleLength * sizeof (int16_t));
+		if (audioDataS16 == NULL)
+		{
+			fclose(f);
+			statusOutOfMemory();
+			return false;
+		}
+
+		// read sample data
+		if (fread(audioDataS16, 2, sampleLength, f) != sampleLength)
+		{
+			fclose(f);
+			free(audioDataS16);
+			displayErrorMsg("I/O ERROR !");
+			return false;
+		}
+
+		// convert from stereo to mono (if needed)
+		if (numChannels == 2)
+		{
+			sampleLength >>= 1;
+			for (i = 0; i < sampleLength-1; i++) // add right channel to left channel
+			{
+				smp32 = (audioDataS16[(i << 1) + 0] + audioDataS16[(i << 1) + 1]) >> 1;
+				audioDataS16[i] = (int16_t)smp32;
+			}
+		}
+
+		// 2x downsampling - remove every other sample (if needed)
+		if (forceDownSampling)
+		{
+			if (config.sampleLowpass)
+				lowPassSample16Bit(audioDataS16, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
+
+			sampleLength >>= 1;
+			for (i = 1; i < sampleLength; i++)
+				audioDataS16[i] = audioDataS16[i << 1];
+		}
+
+		if (sampleLength > MAX_SAMPLE_LEN)
+			sampleLength = MAX_SAMPLE_LEN;
+
+		normalize16bitSigned(audioDataS16, sampleLength);
+
+		turnOffVoices();
+		for (i = 0; i < MAX_SAMPLE_LEN; i++)
+		{
+			if (i <= (sampleLength & 0xFFFFFFFE))
+				song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = audioDataS16[i] >> 8;
+			else
+				song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample
+		}
+
+		free(audioDataS16);
+	}
+	else if (bitsPerSample == 24) // 24-BIT INTEGER SAMPLE
+	{
+		sampleLength /= 3;
+		if (sampleLength > MAX_SAMPLE_LEN*4)
+			sampleLength = MAX_SAMPLE_LEN*4;
+
+		audioDataS32 = (int32_t *)malloc(sampleLength * sizeof (int32_t));
+		if (audioDataS32 == NULL)
+		{
+			fclose(f);
+			statusOutOfMemory();
+			return false;
+		}
+
+		// read sample data
+		audioDataU8 = (uint8_t *)audioDataS32;
+		for (i = 0; i < sampleLength; i++)
+		{
+			audioDataU8[0] = 0;
+			fread(&audioDataU8[1], 3, 1, f);
+			audioDataU8 += sizeof (int32_t);
+		}
+
+		// convert from stereo to mono (if needed)
+		if (numChannels == 2)
+		{
+			sampleLength >>= 1;
+			for (i = 0; i < sampleLength-1; i++) // add right channel to left channel
+			{
+				int64_t smp = ((int64_t)audioDataS32[(i << 1) + 0] + audioDataS32[(i << 1) + 1]) >> 1;
+				audioDataS32[i] = (int32_t)smp;
+			}
+		}
+
+		// 2x downsampling - remove every other sample (if needed)
+		if (forceDownSampling)
+		{
+			if (config.sampleLowpass)
+				lowPassSample32Bit(audioDataS32, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
+
+			sampleLength >>= 1;
+			for (i = 1; i < sampleLength; i++)
+				audioDataS32[i] = audioDataS32[i << 1];
+		}
+
+		if (sampleLength > MAX_SAMPLE_LEN)
+			sampleLength = MAX_SAMPLE_LEN;
+
+		normalize32bitSigned(audioDataS32, sampleLength);
+
+		turnOffVoices();
+		for (i = 0; i < MAX_SAMPLE_LEN; i++)
+		{
+			if (i <= (sampleLength & 0xFFFFFFFE))
+				song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = (int8_t)(audioDataS32[i] >> 24);
+			else
+				song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample
+		}
+
+		free(audioDataS32);
+	}
+	else if (audioFormat == WAV_FORMAT_PCM && bitsPerSample == 32) // 32-BIT INTEGER SAMPLE
+	{
+		sampleLength >>= 2;
+		if (sampleLength > MAX_SAMPLE_LEN*4)
+			sampleLength = MAX_SAMPLE_LEN*4;
+
+		audioDataS32 = (int32_t *)malloc(sampleLength * sizeof (int32_t));
+		if (audioDataS32 == NULL)
+		{
+			fclose(f);
+			statusOutOfMemory();
+			return false;
+		}
+
+		// read sample data
+		if (fread(audioDataS32, 4, sampleLength, f) != sampleLength)
+		{
+			fclose(f);
+			free(audioDataS32);
+			displayErrorMsg("I/O ERROR !");
+			return false;
+		}
+
+		// convert from stereo to mono (if needed)
+		if (numChannels == 2)
+		{
+			sampleLength >>= 1;
+			for (i = 0; i < sampleLength-1; i++) // add right channel to left channel
+			{
+				int64_t smp = ((int64_t)audioDataS32[(i << 1) + 0] + audioDataS32[(i << 1) + 1]) >> 1;
+				audioDataS32[i] = (int32_t)smp;
+			}
+		}
+
+		// 2x downsampling - remove every other sample (if needed)
+		if (forceDownSampling)
+		{
+			if (config.sampleLowpass)
+				lowPassSample32Bit(audioDataS32, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
+
+			sampleLength >>= 1;
+			for (i = 1; i < sampleLength; i++)
+				audioDataS32[i] = audioDataS32[i << 1];
+		}
+
+		if (sampleLength > MAX_SAMPLE_LEN)
+			sampleLength = MAX_SAMPLE_LEN;
+
+		normalize32bitSigned(audioDataS32, sampleLength);
+
+		turnOffVoices();
+		for (i = 0; i < MAX_SAMPLE_LEN; i++)
+		{
+			if (i <= (sampleLength & 0xFFFFFFFE))
+				song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = (int8_t)(audioDataS32[i] >> 24);
+			else
+				song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample
+		}
+
+		free(audioDataS32);
+	}
+	else if (audioFormat == WAV_FORMAT_IEEE_FLOAT && bitsPerSample == 32) // 32-BIT FLOATING POINT SAMPLE
+	{
+		sampleLength >>= 2;
+		if (sampleLength > MAX_SAMPLE_LEN*4)
+			sampleLength = MAX_SAMPLE_LEN*4;
+
+		audioDataU32 = (uint32_t *)malloc(sampleLength * sizeof (uint32_t));
+		if (audioDataU32 == NULL)
+		{
+			fclose(f);
+			statusOutOfMemory();
+			return false;
+		}
+
+		// read sample data
+		if (fread(audioDataU32, 4, sampleLength, f) != sampleLength)
+		{
+			fclose(f);
+			free(audioDataU32);
+			displayErrorMsg("I/O ERROR !");
+			return false;
+		}
+
+		fAudioDataFloat = (float *)audioDataU32;
+
+		// convert from stereo to mono (if needed)
+		if (numChannels == 2)
+		{
+			sampleLength >>= 1;
+			for (i = 0; i < sampleLength-1; i++) // add right channel to left channel
+			{
+				fSmp = (fAudioDataFloat[(i * 2) + 0] + fAudioDataFloat[(i * 2) + 1]) * 0.5f;
+				fAudioDataFloat[i] = fSmp;
+			}
+		}
+
+		// 2x downsampling - remove every other sample (if needed)
+		if (forceDownSampling)
+		{
+			if (config.sampleLowpass)
+				lowPassSampleFloat(fAudioDataFloat, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
+
+			sampleLength >>= 1;
+			for (i = 1; i < sampleLength; i++)
+				fAudioDataFloat[i] = fAudioDataFloat[i << 1];
+		}
+
+		if (sampleLength > MAX_SAMPLE_LEN)
+			sampleLength = MAX_SAMPLE_LEN;
+
+		normalize8bitFloatSigned(fAudioDataFloat, sampleLength);
+
+		turnOffVoices();
+		for (i = 0; i < MAX_SAMPLE_LEN; i++)
+		{
+			if (i <= (sampleLength & 0xFFFFFFFE))
+			{
+				smp32 = (int32_t)fAudioDataFloat[i];
+				CLAMP8(smp32);
+				song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = (int8_t)smp32;
+			}
+			else
+			{
+				song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample
+			}
+		}
+
+		free(audioDataU32);
+	}
+	else if (audioFormat == WAV_FORMAT_IEEE_FLOAT && bitsPerSample == 64) // 64-BIT FLOATING POINT SAMPLE
+	{
+		sampleLength >>= 3;
+		if (sampleLength > MAX_SAMPLE_LEN*4)
+			sampleLength = MAX_SAMPLE_LEN*4;
+
+		audioDataU32 = (uint32_t *)malloc(sampleLength * (sizeof (uint32_t) * 2));
+		if (audioDataU32 == NULL)
+		{
+			fclose(f);
+			statusOutOfMemory();
+			return false;
+		}
+
+		// read sample data
+		if (fread(audioDataU32, 8, sampleLength, f) != sampleLength)
+		{
+			fclose(f);
+			free(audioDataU32);
+			displayErrorMsg("I/O ERROR !");
+			return false;
+		}
+
+		dAudioDataDouble = (double *)audioDataU32;
+
+		// convert from stereo to mono (if needed)
+		if (numChannels == 2)
+		{
+			sampleLength >>= 1;
+			for (i = 0; i < sampleLength-1; i++) // add right channel to left channel
+			{
+				dSmp = (dAudioDataDouble[(i * 2) + 0] + dAudioDataDouble[(i * 2) + 1]) * 0.5;
+				dAudioDataDouble[i] = dSmp;
+			}
+		}
+
+		// 2x downsampling - remove every other sample (if needed)
+		if (forceDownSampling)
+		{
+			if (config.sampleLowpass)
+				lowPassSampleDouble(dAudioDataDouble, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
+
+			sampleLength >>= 1;
+			for (i = 1; i < sampleLength; i++)
+				dAudioDataDouble[i] = dAudioDataDouble[i << 1];
+		}
+
+		if (sampleLength > MAX_SAMPLE_LEN)
+			sampleLength = MAX_SAMPLE_LEN;
+
+		normalize8bitDoubleSigned(dAudioDataDouble, sampleLength);
+
+		turnOffVoices();
+		for (i = 0; i < MAX_SAMPLE_LEN; i++)
+		{
+			if (i <= (sampleLength & 0xFFFFFFFE))
+			{
+				smp32 = (int32_t)dAudioDataDouble[i];
+				CLAMP8(smp32);
+				song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = (int8_t)smp32;
+			}
+			else
+			{
+				song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample
+			}
+		}
+
+		free(audioDataU32);
+	}
+
+	// set sample length
+	if (sampleLength & 1)
+	{
+		if (++sampleLength > MAX_SAMPLE_LEN)
+			sampleLength = MAX_SAMPLE_LEN;
+	}
+
+	s->length = (uint16_t)sampleLength;
+	s->fineTune = 0;
+	s->volume = 64;
+	s->loopStart = 0;
+	s->loopLength = 2;
+
+	// ---- READ "smpl" chunk ----
+	if (smplPtr != 0 && smplLen > 52)
+	{
+		fseek(f, smplPtr + 28, SEEK_SET); // seek to first wanted byte
+
+		fread(&loopFlags, 4, 1, f);
+		fseek(f, 12, SEEK_CUR);
+		fread(&loopStart, 4, 1, f);
+		fread(&loopEnd, 4, 1, f);
+		loopEnd++;
+
+		if (forceDownSampling)
+		{
+			// we already downsampled 2x, so we're half the original length
+			loopStart >>= 1;
+			loopEnd >>= 1;
+		}
+
+		loopStart &= 0xFFFFFFFE;
+		loopEnd &= 0xFFFFFFFE;
+
+		if (loopFlags)
+		{
+			if (loopStart+(loopEnd-loopStart) <= s->length)
+			{
+				s->loopStart = (uint16_t)loopStart;
+				s->loopLength = (uint16_t)(loopEnd - loopStart);
+
+				if (s->loopLength < 2)
+				{
+					s->loopStart = 0;
+					s->loopLength = 2;
+				}
+			}
+		}
+	}
+	// ---------------------------
+
+	// ---- READ "xtra" chunk ----
+	if (xtraPtr != 0 && xtraLen >= 8)
+	{
+		fseek(f, xtraPtr + 4, SEEK_SET); // seek to first wanted byte
+
+		// volume (0..256)
+		fseek(f, 2, SEEK_CUR);
+		fread(&tempVol, 2, 1, f);
+		if (tempVol > 256)
+			tempVol = 256;
+
+		tempVol >>= 2; // 0..256 -> 0..64
+
+		s->volume = (int8_t)tempVol;
+	}
+	// ---------------------------
+
+	// ---- READ "INAM" chunk ----
+	if (inamPtr != 0 && inamLen > 0)
+	{
+		fseek(f, inamPtr, SEEK_SET); // seek to first wanted byte
+
+		for (i = 0; i < 21; i++)
+		{
+			if (i < inamLen)
+				s->text[i] = (char)tolower(fgetc(f));
+			else
+				s->text[i] = '\0';
+		}
+
+		s->text[21] = '\0';
+		s->text[22] = '\0';
+
+		wavSampleNameFound = true;
+	}
+	// ---------------------------
+
+	fclose(f);
+
+	// copy over sample name
+	if (!wavSampleNameFound)
+	{
+		nameLen = (uint32_t)strlen(entryName);
+		for (i = 0; i < 21; i++)
+			s->text[i] = (i < nameLen) ? (char)tolower(entryName[i]) : '\0';
+
+		s->text[21] = '\0';
+		s->text[22] = '\0';
+	}
+
+	// remove .wav from end of sample name (if present)
+	nameLen = (uint32_t)strlen(s->text);
+	if (nameLen >= 4 && !_strnicmp(&s->text[nameLen-4], ".WAV", 4))
+		 memset(&s->text[nameLen-4], '\0', 4);
+
+	editor.sampleZero = false;
+	editor.samplePos = 0;
+
+	fixSampleBeep(s);
+	updateCurrSample();
+
+	updateWindowTitle(MOD_IS_MODIFIED);
+	return true;
+}
+
+bool loadIFFSample(UNICHAR *fileName, char *entryName)
+{
+	bool nameFound, is16Bit;
+	char tmpCharBuf[23];
+	int8_t *sampleData;
+	int16_t sample16, *ptr16;
+	int32_t filesize;
+	uint32_t i, sampleLength, sampleLoopStart, sampleLoopLength;
+	uint32_t sampleVolume, blockName, blockSize;
+	uint32_t vhdrPtr, vhdrLen, bodyPtr, bodyLen, namePtr, nameLen;
+	FILE *f;
+	moduleSample_t *s;
+
+	s = &song->samples[editor.currSample];
+
+	vhdrPtr = 0; vhdrLen = 0;
+	bodyPtr = 0; bodyLen = 0;
+	namePtr = 0; nameLen = 0;
+
+	f = UNICHAR_FOPEN(fileName, "rb");
+	if (f == NULL)
+	{
+		displayErrorMsg("FILE I/O ERROR !");
+		return false;
+	}
+
+	fseek(f, 0, SEEK_END);
+	filesize = ftell(f);
+	if (filesize == 0)
+	{
+		displayErrorMsg("IFF IS CORRUPT !");
+		return false;
+	}
+
+	fseek(f, 8, SEEK_SET);
+	fread(tmpCharBuf, 1, 4, f);
+	is16Bit = !strncmp(tmpCharBuf, "16SV", 4);
+
+	sampleLength = 0;
+	nameFound = false;
+	sampleVolume = 65536; // max volume
+
+	fseek(f, 12, SEEK_SET);
+	while (!feof(f) && ftell(f) < filesize-12)
+	{
+		fread(&blockName, 4, 1, f); if (feof(f)) break;
+		fread(&blockSize, 4, 1, f); if (feof(f)) break;
+
+		blockName = SWAP32(blockName);
+		blockSize = SWAP32(blockSize);
+
+		switch (blockName)
+		{
+			case 0x56484452: // VHDR
+			{
+				vhdrPtr = ftell(f);
+				vhdrLen = blockSize;
+			}
+			break;
+
+			case 0x4E414D45: // NAME
+			{
+				namePtr = ftell(f);
+				nameLen = blockSize;
+			}
+			break;
+
+			case 0x424F4459: // BODY
+			{
+				bodyPtr = ftell(f);
+				bodyLen = blockSize;
+			}
+			break;
+
+			default: break;
+		}
+
+		fseek(f, blockSize + (blockSize & 1), SEEK_CUR);
+	}
+
+	if (vhdrPtr == 0 || vhdrLen < 20 || bodyPtr == 0)
+	{
+		fclose(f);
+		displayErrorMsg("NOT A VALID IFF !");
+		return false;
+	}
+
+	// kludge for some really strange IFFs
+	if (bodyLen == 0)
+		bodyLen = filesize - bodyPtr;
+
+	if (bodyPtr+bodyLen > (uint32_t)filesize)
+		bodyLen = filesize - bodyPtr;
+
+	fseek(f, vhdrPtr, SEEK_SET);
+	fread(&sampleLoopStart,  4, 1, f); sampleLoopStart  = SWAP32(sampleLoopStart);
+	fread(&sampleLoopLength, 4, 1, f); sampleLoopLength = SWAP32(sampleLoopLength);
+
+	fseek(f, 4 + 2 + 1, SEEK_CUR);
+
+	if (fgetc(f) != 0) // sample type
+	{
+		fclose(f);
+		displayErrorMsg("UNSUPPORTED IFF !");
+		return false;
+	}
+
+	fread(&sampleVolume, 4, 1, f); sampleVolume = SWAP32(sampleVolume);
+	if (sampleVolume > 65536)
+		sampleVolume = 65536;
+
+	sampleVolume = (sampleVolume + 512) / 1024; // rounded
+	if (sampleVolume > 64)
+		sampleVolume = 64;
+
+	sampleLength = bodyLen;
+	if (is16Bit)
+	{
+		if (sampleLength > MAX_SAMPLE_LEN*2)
+			sampleLength = MAX_SAMPLE_LEN*2;
+	}
+	else
+	{
+		if (sampleLength > MAX_SAMPLE_LEN)
+			sampleLength = MAX_SAMPLE_LEN;
+	}
+
+	if (sampleLength == 0)
+	{
+		fclose(f);
+		displayErrorMsg("NOT A VALID IFF !");
+		return false;
+	}
+
+	sampleData = (int8_t *)malloc(sampleLength);
+	if (sampleData == NULL)
+	{
+		fclose(f);
+		statusOutOfMemory();
+		return false;
+	}
+
+	if (is16Bit)
+	{
+		sampleLength >>= 1;
+		sampleLoopStart >>= 1;
+		sampleLoopLength >>= 1;
+	}
+
+	sampleLength &= 0xFFFFFFFE;
+	sampleLoopStart &= 0xFFFFFFFE;
+	sampleLoopLength &= 0xFFFFFFFE;
+
+	if (sampleLength > MAX_SAMPLE_LEN)
+		sampleLength = MAX_SAMPLE_LEN;
+
+	if (sampleLoopLength < 2)
+	{
+		sampleLoopStart = 0;
+		sampleLoopLength = 2;
+	}
+
+	if (sampleLoopStart >= MAX_SAMPLE_LEN || sampleLoopLength > MAX_SAMPLE_LEN)
+	{
+		sampleLoopStart= 0;
+		sampleLoopLength = 2;
+	}
+
+	if (sampleLoopStart+sampleLoopLength > sampleLength)
+	{
+		sampleLoopStart = 0;
+		sampleLoopLength = 2;
+	}
+
+	if (sampleLoopStart > sampleLength-2)
+	{
+		sampleLoopStart = 0;
+		sampleLoopLength = 2;
+	}
+
+	turnOffVoices();
+
+	fseek(f, bodyPtr, SEEK_SET);
+	if (is16Bit) // FT2 specific 16SV format (little-endian samples)
+	{
+		fread(sampleData, 1, sampleLength << 1, f);
+
+		ptr16 = (int16_t *)sampleData;
+		for (i = 0; i < sampleLength; i++)
+		{
+			sample16 = ptr16[i];
+			song->sampleData[s->offset+i] = sample16 >> 8;
+		}
+	}
+	else
+	{
+		fread(sampleData, 1, sampleLength, f);
+		memcpy(&song->sampleData[s->offset], sampleData, sampleLength);
+	}
+
+	if (sampleLength < MAX_SAMPLE_LEN) // clear rest of sample data
+		memset(&song->sampleData[s->offset + sampleLength], 0, MAX_SAMPLE_LEN - sampleLength);
+
+	free(sampleData);
+
+	// set sample attributes
+	s->volume = (int8_t)sampleVolume;
+	s->fineTune = 0;
+	s->length = (uint16_t)sampleLength;
+	s->loopStart = (uint16_t)sampleLoopStart;
+	s->loopLength = (uint16_t)sampleLoopLength;
+
+	// read name
+	if (namePtr != 0 && nameLen > 0)
+	{
+		fseek(f, namePtr, SEEK_SET);
+		memset(tmpCharBuf, 0, sizeof (tmpCharBuf));
+
+		if (nameLen > 21)
+		{
+			fread(tmpCharBuf, 1, 21, f);
+			fseek(f, nameLen - 21, SEEK_CUR);
+		}
+		else
+		{
+			fread(tmpCharBuf, 1, nameLen, f);
+		}
+
+		nameFound = true;
+	}
+
+	fclose(f);
+
+	// copy over sample name
+	memset(s->text, '\0', sizeof (s->text));
+
+	if (nameFound)
+	{
+		nameLen = (uint32_t)strlen(tmpCharBuf);
+		if (nameLen > 21)
+			nameLen = 21;
+
+		for (i = 0; i < nameLen; i++)
+			s->text[i] = (char)tolower(tmpCharBuf[i]);
+	}
+	else
+	{
+		nameLen = (uint32_t)strlen(entryName);
+		if (nameLen > 21)
+			nameLen = 21;
+
+		for (i = 0; i < nameLen; i++)
+			s->text[i] = (char)tolower(entryName[i]);
+	}
+
+	// remove .iff from end of sample name (if present)
+	nameLen = (uint32_t)strlen(s->text);
+	if (nameLen >= 4 && !strncmp(&s->text[nameLen-4], ".IFF", 4))
+		memset(&s->text[nameLen-4], '\0', 4);
+
+	editor.sampleZero = false;
+	editor.samplePos = 0;
+
+	fixSampleBeep(s);
+	updateCurrSample();
+
+	updateWindowTitle(MOD_IS_MODIFIED);
+	return false;
+}
+
+bool loadRAWSample(UNICHAR *fileName, char *entryName)
+{
+	uint8_t i;
+	uint32_t nameLen, fileSize;
+	FILE *f;
+	moduleSample_t *s;
+
+	s = &song->samples[editor.currSample];
+
+	f = UNICHAR_FOPEN(fileName, "rb");
+	if (f == NULL)
+	{
+		displayErrorMsg("FILE I/O ERROR !");
+		return false;
+	}
+
+	fseek(f, 0, SEEK_END);
+	fileSize = ftell(f);
+	fseek(f, 0, SEEK_SET);
+
+	fileSize &= 0xFFFFFFFE;
+	if (fileSize > MAX_SAMPLE_LEN)
+		fileSize = MAX_SAMPLE_LEN;
+
+	turnOffVoices();
+
+	fread(&song->sampleData[s->offset], 1, fileSize, f);
+	fclose(f);
+
+	if (fileSize < MAX_SAMPLE_LEN)
+		memset(&song->sampleData[s->offset + fileSize], 0, MAX_SAMPLE_LEN - fileSize);
+
+	// set sample attributes
+	s->volume = 64;
+	s->fineTune = 0;
+	s->length = (uint16_t)fileSize;
+	s->loopStart = 0;
+	s->loopLength = 2;
+
+	// copy over sample name
+	nameLen = (uint32_t)strlen(entryName);
+	for (i = 0; i < 21; i++)
+		s->text[i] = (i < nameLen) ? (char)tolower(entryName[i]) : '\0';
+
+	s->text[21] = '\0';
+	s->text[22] = '\0';
+
+	editor.sampleZero = false;
+	editor.samplePos = 0;
+
+	fixSampleBeep(s);
+	updateCurrSample();
+
+	updateWindowTitle(MOD_IS_MODIFIED);
+	return true;
+}
+
+static int32_t getAIFFRate(uint8_t *in)
+{
+	int32_t exp;
+	uint32_t lo, hi;
+	double dOut;
+
+	exp = (int32_t)(((in[0] & 0x7F) << 8) | in[1]);
+	lo  = (in[2] << 24) | (in[3] << 16) | (in[4] << 8) | in[5];
+	hi  = (in[6] << 24) | (in[7] << 16) | (in[8] << 8) | in[9];
+
+	if (exp == 0 && lo == 0 && hi == 0)
+		return 0;
+
+	exp -= 16383;
+
+	dOut = ldexp(lo, -31 + exp) + ldexp(hi, -63 + exp);
+	return (int32_t)(dOut + 0.5);
+}
+
+bool loadAIFFSample(UNICHAR *fileName, char *entryName, int8_t forceDownSampling)
+{
+	bool unsigned8bit;
+	char compType[4];
+	int8_t *audioDataS8;
+	uint8_t *audioDataU8, sampleRateBytes[10];
+	int16_t *audioDataS16, smp16;
+	uint16_t bitDepth, numChannels;
+	int32_t filesize, *audioDataS32, smp32;
+	uint32_t nameLen, i, offset, sampleRate, sampleLength, blockName, blockSize;
+	uint32_t commPtr, commLen, ssndPtr, ssndLen;
+	FILE *f;
+	moduleSample_t *s;
+
+	unsigned8bit = false;
+	loadedFileWasAIFF = true;
+
+	if (forceDownSampling == -1)
+	{
+		// these two *must* be fully wiped, for outputting reasons
+		memset(editor.fileNameTmpU, 0, PATH_MAX);
+		memset(editor.entryNameTmp, 0, PATH_MAX);
+		UNICHAR_STRCPY(editor.fileNameTmpU, fileName);
+		strcpy(editor.entryNameTmp, entryName);
+	}
+
+	s = &song->samples[editor.currSample];
+
+	commPtr = 0; commLen = 0;
+	ssndPtr = 0; ssndLen = 0;
+
+	f = UNICHAR_FOPEN(fileName, "rb");
+	if (f == NULL)
+	{
+		displayErrorMsg("FILE I/O ERROR !");
+		return false;
+	}
+
+	fseek(f, 0, SEEK_END);
+	filesize = ftell(f);
+	if (filesize == 0)
+	{
+		displayErrorMsg("AIFF IS CORRUPT !");
+		return false;
+	}
+
+	fseek(f, 12, SEEK_SET);
+	while (!feof(f) && ftell(f) < filesize-12)
+	{
+		fread(&blockName, 4, 1, f); if (feof(f)) break;
+		fread(&blockSize, 4, 1, f); if (feof(f)) break;
+
+		blockName = SWAP32(blockName);
+		blockSize = SWAP32(blockSize);
+
+		switch (blockName)
+		{
+			case 0x434F4D4D: // "COMM"
+			{
+				commPtr = ftell(f);
+				commLen = blockSize;
+			}
+			break;
+
+			case 0x53534E44: // "SSND"
+			{
+				ssndPtr = ftell(f);
+				ssndLen = blockSize;
+			}
+			break;
+
+			default: break;
+		}
+
+		fseek(f, blockSize + (blockSize & 1), SEEK_CUR);
+	}
+
+	if (commPtr == 0 || commLen < 18 || ssndPtr == 0)
+	{
+		fclose(f);
+		displayErrorMsg("NOT A VALID AIFF!");
+		return false;
+	}
+
+	// kludge for some really strange AIFFs
+	if (ssndLen == 0)
+		ssndLen = filesize - ssndPtr;
+
+	if (ssndPtr+ssndLen > (uint32_t)filesize)
+		ssndLen = filesize - ssndPtr;
+
+	fseek(f, commPtr, SEEK_SET);
+	fread(&numChannels, 2, 1, f); numChannels = SWAP16(numChannels);
+	fseek(f, 4, SEEK_CUR);
+	fread(&bitDepth, 2, 1, f); bitDepth = SWAP16(bitDepth);
+	fread(sampleRateBytes, 1, 10, f);
+
+	fseek(f, 4 + 2 + 1, SEEK_CUR);
+
+	if (numChannels != 1 && numChannels != 2) // sample type
+	{
+		fclose(f);
+		displayErrorMsg("UNSUPPORTED AIFF!");
+		return false;
+	}
+
+	if (bitDepth != 8 && bitDepth != 16 && bitDepth != 24 && bitDepth != 32)
+	{
+		fclose(f);
+		displayErrorMsg("UNSUPPORTED AIFF!");
+		return false;
+	}
+
+	// read compression type (if present)
+	if (commLen > 18)
+	{
+		fread(&compType, 1, 4, f);
+		if (memcmp(compType, "NONE", 4))
+		{
+			fclose(f);
+			displayErrorMsg("UNSUPPORTED AIFF!");
+			return false;
+		}
+	}
+
+	sampleRate = getAIFFRate(sampleRateBytes);
+
+	// sample data chunk
+
+	fseek(f, ssndPtr, SEEK_SET);
+
+	fread(&offset, 4, 1, f);
+	if (offset > 0)
+	{
+		fclose(f);
+		displayErrorMsg("UNSUPPORTED AIFF!");
+		return false;
+	}
+
+	fseek(f, 4, SEEK_CUR);
+
+	ssndLen -= 8; // don't include offset and blockSize datas
+
+	sampleLength = ssndLen;
+	if (sampleLength == 0)
+	{
+		fclose(f);
+		displayErrorMsg("NOT A VALID AIFF!");
+		return false;
+	}
+
+	if (sampleRate > 22050)
+	{
+		if (forceDownSampling == -1)
+		{
+			showDownsampleAskDialog();
+			fclose(f);
+			return true;
+		}
+	}
+	else
+	{
+		forceDownSampling = false;
+	}
+
+	if (bitDepth == 8) // 8-BIT INTEGER SAMPLE
+	{
+		if (sampleLength > MAX_SAMPLE_LEN*4)
+			sampleLength = MAX_SAMPLE_LEN*4;
+
+		audioDataS8 = (int8_t *)malloc(sampleLength * sizeof (int8_t));
+		if (audioDataS8 == NULL)
+		{
+			fclose(f);
+			statusOutOfMemory();
+			return false;
+		}
+
+		// read sample data
+		if (fread(audioDataS8, 1, sampleLength, f) != sampleLength)
+		{
+			fclose(f);
+			free(audioDataS8);
+			displayErrorMsg("I/O ERROR !");
+			return false;
+		}
+
+		if (unsigned8bit)
+		{
+			for (i = 0; i < sampleLength; i++)
+				audioDataS8[i] ^= 0x80;
+		}
+
+		// convert from stereo to mono (if needed)
+		if (numChannels == 2)
+		{
+			sampleLength >>= 1;
+			for (i = 0; i < sampleLength-1; i++) // add right channel to left channel
+			{
+				smp16 = (audioDataS8[(i * 2) + 0] + audioDataS8[(i * 2) + 1]) >> 1;
+				audioDataS8[i] = (uint8_t)smp16;
+			}
+		}
+
+		// 2x downsampling - remove every other sample (if needed)
+		if (forceDownSampling)
+		{
+			if (config.sampleLowpass)
+				lowPassSample8Bit(audioDataS8, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
+
+			sampleLength >>= 1;
+			for (i = 1; i < sampleLength; i++)
+				audioDataS8[i] = audioDataS8[i << 1];
+		}
+
+		if (sampleLength > MAX_SAMPLE_LEN)
+			sampleLength = MAX_SAMPLE_LEN;
+
+		turnOffVoices();
+		for (i = 0; i < MAX_SAMPLE_LEN; i++)
+		{
+			if (i <= (sampleLength & 0xFFFFFFFE))
+				song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = audioDataS8[i];
+			else
+				song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample
+		}
+
+		free(audioDataS8);
+	}
+	else if (bitDepth == 16) // 16-BIT INTEGER SAMPLE
+	{
+		sampleLength >>= 1;
+		if (sampleLength > MAX_SAMPLE_LEN*4)
+			sampleLength = MAX_SAMPLE_LEN*4;
+
+		audioDataS16 = (int16_t *)malloc(sampleLength * sizeof (int16_t));
+		if (audioDataS16 == NULL)
+		{
+			fclose(f);
+			statusOutOfMemory();
+			return false;
+		}
+
+		// read sample data
+		if (fread(audioDataS16, 2, sampleLength, f) != sampleLength)
+		{
+			fclose(f);
+			free(audioDataS16);
+			displayErrorMsg("I/O ERROR !");
+			return false;
+		}
+
+		// fix endianness
+		for (i = 0; i < sampleLength; i++)
+			audioDataS16[i] = SWAP16(audioDataS16[i]);
+
+		// convert from stereo to mono (if needed)
+		if (numChannels == 2)
+		{
+			sampleLength >>= 1;
+			for (i = 0; i < sampleLength-1; i++) // add right channel to left channel
+			{
+				smp32 = (audioDataS16[(i << 1) + 0] + audioDataS16[(i << 1) + 1]) >> 1;
+				audioDataS16[i] = (int16_t)(smp32);
+			}
+		}
+
+		// 2x downsampling - remove every other sample (if needed)
+		if (forceDownSampling)
+		{
+			if (config.sampleLowpass)
+				lowPassSample16Bit(audioDataS16, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
+
+			sampleLength >>= 1;
+			for (i = 1; i < sampleLength; i++)
+				audioDataS16[i] = audioDataS16[i << 1];
+		}
+
+		if (sampleLength > MAX_SAMPLE_LEN)
+			sampleLength = MAX_SAMPLE_LEN;
+
+		normalize16bitSigned(audioDataS16, sampleLength);
+
+		turnOffVoices();
+		for (i = 0; i < MAX_SAMPLE_LEN; i++)
+		{
+			if (i <= (sampleLength & 0xFFFFFFFE))
+				song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = audioDataS16[i] >> 8;
+			else
+				song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample
+		}
+
+		free(audioDataS16);
+	}
+	else if (bitDepth == 24) // 24-BIT INTEGER SAMPLE
+	{
+		sampleLength /= 3;
+		if (sampleLength > MAX_SAMPLE_LEN*4)
+			sampleLength = MAX_SAMPLE_LEN*4;
+
+		audioDataS32 = (int32_t *)malloc(sampleLength * sizeof (int32_t));
+		if (audioDataS32 == NULL)
+		{
+			fclose(f);
+			statusOutOfMemory();
+			return false;
+		}
+
+		// read sample data
+		if (fread(&audioDataS32[sampleLength >> 2], 3, sampleLength, f) != sampleLength)
+		{
+			fclose(f);
+			free(audioDataS32);
+			displayErrorMsg("I/O ERROR !");
+			return false;
+		}
+
+		// convert to 32-bit
+		audioDataU8 = (uint8_t *)audioDataS32 + sampleLength;
+		for (i = 0; i < sampleLength; i++)
+		{
+			audioDataS32[i] = (audioDataU8[0] << 24) | (audioDataU8[1] << 16) | (audioDataU8[2] << 8);
+			audioDataU8 += 3;
+		}
+
+		// convert from stereo to mono (if needed)
+		if (numChannels == 2)
+		{
+			sampleLength >>= 1;
+			for (i = 0; i < sampleLength-1; i++) // add right channel to left channel
+			{
+				int64_t smp = ((int64_t)audioDataS32[(i << 1) + 0] + audioDataS32[(i << 1) + 1]) >> 1;
+				audioDataS32[i] = (int32_t)smp;
+			}
+		}
+
+		// 2x downsampling - remove every other sample (if needed)
+		if (forceDownSampling)
+		{
+			if (config.sampleLowpass)
+				lowPassSample32Bit(audioDataS32, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
+
+			sampleLength >>= 1;
+			for (i = 1; i < sampleLength; i++)
+				audioDataS32[i] = audioDataS32[i << 1];
+		}
+
+		if (sampleLength > MAX_SAMPLE_LEN)
+			sampleLength = MAX_SAMPLE_LEN;
+
+		normalize32bitSigned(audioDataS32, sampleLength);
+
+		turnOffVoices();
+		for (i = 0; i < MAX_SAMPLE_LEN; i++)
+		{
+			if (i <= (sampleLength & 0xFFFFFFFE))
+				song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = (int8_t)(audioDataS32[i] >> 24);
+			else
+				song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample
+		}
+
+		free(audioDataS32);
+	}
+	else if (bitDepth == 32) // 32-BIT INTEGER SAMPLE
+	{
+		sampleLength >>= 2;
+		if (sampleLength > MAX_SAMPLE_LEN*4)
+			sampleLength = MAX_SAMPLE_LEN*4;
+
+		audioDataS32 = (int32_t *)malloc(sampleLength * sizeof (int32_t));
+		if (audioDataS32 == NULL)
+		{
+			fclose(f);
+			statusOutOfMemory();
+			return false;
+		}
+
+		// read sample data
+		if (fread(audioDataS32, 4, sampleLength, f) != sampleLength)
+		{
+			fclose(f);
+			free(audioDataS32);
+			displayErrorMsg("I/O ERROR !");
+			return false;
+		}
+
+		// fix endianness
+		for (i = 0; i < sampleLength; i++)
+			audioDataS32[i] = SWAP32(audioDataS32[i]);
+
+		// convert from stereo to mono (if needed)
+		if (numChannels == 2)
+		{
+			sampleLength >>= 1;
+			for (i = 0; i < sampleLength-1; i++) // add right channel to left channel
+			{
+				int64_t smp = ((int64_t)audioDataS32[(i << 1) + 0] + audioDataS32[(i << 1) + 1]) >> 1;
+				audioDataS32[i] = (int32_t)smp;
+			}
+		}
+
+		// 2x downsampling - remove every other sample (if needed)
+		if (forceDownSampling)
+		{
+			if (config.sampleLowpass)
+				lowPassSample32Bit(audioDataS32, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
+
+			sampleLength >>= 1;
+			for (i = 1; i < sampleLength; i++)
+				audioDataS32[i] = audioDataS32[i << 1];
+		}
+
+		if (sampleLength > MAX_SAMPLE_LEN)
+			sampleLength = MAX_SAMPLE_LEN;
+
+		normalize32bitSigned(audioDataS32, sampleLength);
+
+		turnOffVoices();
+		for (i = 0; i < MAX_SAMPLE_LEN; i++)
+		{
+			if (i <= (sampleLength & 0xFFFFFFFE))
+				song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = (int8_t)(audioDataS32[i] >> 24);
+			else
+				song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample
+		}
+
+		free(audioDataS32);
+	}
+
+	// set sample length
+	if (sampleLength & 1)
+	{
+		if (++sampleLength > MAX_SAMPLE_LEN)
+			sampleLength = MAX_SAMPLE_LEN;
+	}
+
+	s->length = (uint16_t)sampleLength;
+	s->fineTune = 0;
+	s->volume = 64;
+	s->loopStart = 0;
+	s->loopLength = 2;
+
+	fclose(f);
+
+	// copy over sample name
+	nameLen = (uint32_t)strlen(entryName);
+	for (i = 0; i < 21; i++)
+		s->text[i] = (i < nameLen) ? (char)tolower(entryName[i]) : '\0';
+
+	s->text[21] = '\0';
+	s->text[22] = '\0';
+
+	// remove .aiff from end of sample name (if present)
+	nameLen = (uint32_t)strlen(s->text);
+	if (nameLen >= 5 && !_strnicmp(&s->text[nameLen-5], ".AIFF", 5))
+		memset(&s->text[nameLen-5], '\0', 5);
+
+	editor.sampleZero = false;
+	editor.samplePos = 0;
+
+	fixSampleBeep(s);
+	updateCurrSample();
+
+	updateWindowTitle(MOD_IS_MODIFIED);
+	return true;
+}
+
+bool loadSample(UNICHAR *fileName, char *entryName)
+{
+	uint32_t fileSize, ID;
+	FILE *f;
+
+	if (editor.sampleZero)
+	{
+		statusNotSampleZero();
+		return false;
+	}
+
+	f = UNICHAR_FOPEN(fileName, "rb");
+	if (f == NULL)
+	{
+		displayErrorMsg("FILE I/O ERROR !");
+		return false;
+	}
+
+	fseek(f, 0, SEEK_END);
+	fileSize = ftell(f);
+	fseek(f, 0, SEEK_SET);
+
+	// first, check heades before we eventually load as RAW
+	if (fileSize > 16)
+	{
+		fread(&ID, 4, 1, f);
+
+		// check if it's actually a WAV sample
+		if (ID == 0x46464952) // "RIFF"
+		{
+			fseek(f, 4, SEEK_CUR);
+			fread(&ID, 4, 1, f);
+
+			if (ID == 0x45564157) // "WAVE"
+			{
+				fread(&ID, 4, 1, f);
+				if (ID == 0x20746D66) // "fmt "
+				{
+					fclose(f);
+					return loadWAVSample(fileName, entryName, -1);
+				}
+			}
+		}	
+		else if (ID == 0x4D524F46) // "FORM"
+		{
+			fseek(f, 4, SEEK_CUR);
+			fread(&ID, 4, 1, f);
+
+			// check if it's an Amiga IFF sample
+			if (ID == 0x58565338 || ID == 0x56533631) // "8SVX" (normal) and "16SV" (FT2 sample)
+			{
+				fclose(f);
+				return loadIFFSample(fileName, entryName);
+			}
+
+			// check if it's an AIFF sample
+			else if (ID == 0x46464941) // "AIFF"
+			{
+				fclose(f);
+				return loadAIFFSample(fileName, entryName, -1);
+			}
+
+			else if (ID == 0x43464941) // "AIFC" (compressed AIFF)
+			{
+				fclose(f);
+				displayErrorMsg("UNSUPPORTED AIFF!");
+				return false;
+			}
+		}
+	}
+
+	// nope, continue loading as RAW
+	fclose(f);
+
+	return loadRAWSample(fileName, entryName);
+}
--- /dev/null
+++ b/src/pt2_sample_loader.h
@@ -1,0 +1,8 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+#include "pt2_unicode.h"
+
+void extLoadWAVOrAIFFSampleCallback(bool downsample);
+bool loadSample(UNICHAR *fileName, char *entryName);
--- /dev/null
+++ b/src/pt2_sample_saver.c
@@ -1,0 +1,288 @@
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <ctype.h> // tolower()
+#include <sys/stat.h>
+#include "pt2_header.h"
+#include "pt2_structs.h"
+#include "pt2_textout.h"
+#include "pt2_mouse.h"
+#include "pt2_visuals.h"
+#include "pt2_helpers.h"
+#include "pt2_diskop.h"
+
+#define PLAYBACK_FREQ 16574 /* C-3 */
+
+static void removeSampleFileExt(char *text) // for sample saver
+{
+	uint32_t fileExtPos;
+	uint32_t filenameLength;
+
+	if (text == NULL || text[0] == '\0')
+		return;
+	
+	filenameLength = (uint32_t)strlen(text);
+	if (filenameLength < 5)
+		return;
+
+	// remove .wav/.iff/from end of sample name (if present)
+	fileExtPos = filenameLength - 4;
+	if (fileExtPos > 0 && (!strncmp(&text[fileExtPos], ".wav", 4) || !strncmp(&text[fileExtPos], ".iff", 4)))
+		text[fileExtPos] = '\0';
+}
+
+static void iffWriteChunkHeader(FILE *f, char *chunkName, uint32_t chunkLen)
+{
+	fwrite(chunkName, sizeof (int32_t), 1, f);
+	chunkLen = SWAP32(chunkLen);
+	fwrite(&chunkLen, sizeof (int32_t), 1, f);
+}
+
+
+static void iffWriteUint32(FILE *f, uint32_t value)
+{
+	value = SWAP32(value);
+	fwrite(&value, sizeof (int32_t), 1, f);
+}
+
+static void iffWriteUint16(FILE *f, uint16_t value)
+{
+	value = SWAP16(value);
+	fwrite(&value, sizeof (int16_t), 1, f);
+}
+
+static void iffWriteUint8(FILE *f, const uint8_t value)
+{
+	fwrite(&value, sizeof (int8_t), 1, f);
+}
+
+static void iffWriteChunkData(FILE *f, const void *data, size_t length)
+{
+	fwrite(data, sizeof (int8_t), length, f);
+	if (length & 1) fputc(0, f); // write pad byte if chunk size is uneven
+}
+
+bool saveSample(bool checkIfFileExist, bool giveNewFreeFilename)
+{
+	char fileName[128], tmpBuffer[64];
+	uint32_t i, j, chunkLen;
+	FILE *f;
+	struct stat statBuffer;
+	wavHeader_t wavHeader;
+	samplerChunk_t samplerChunk;
+	mptExtraChunk_t mptExtraChunk;
+
+	const moduleSample_t *s = &song->samples[editor.currSample];
+
+	if (s->length == 0)
+	{
+		statusSampleIsEmpty();
+		return false;
+	}
+
+	// get sample filename
+	if (s->text[0] == '\0')
+	{
+		strcpy(fileName, "untitled");
+	}
+	else
+	{
+		for (i = 0; i < 22; i++)
+		{
+			tmpBuffer[i] = (char)tolower(song->samples[editor.currSample].text[i]);
+			if (tmpBuffer[i] == '\0') break;
+			sanitizeFilenameChar(&tmpBuffer[i]);
+		}
+
+		strcpy(fileName, tmpBuffer);
+	}
+	
+	removeSampleFileExt(fileName);
+	addSampleFileExt(fileName);
+
+	// if the user picked "no" to overwriting the file, generate a new filename
+	if (giveNewFreeFilename && stat(fileName, &statBuffer) == 0)
+	{
+		for (j = 1; j <= 999; j++)
+		{
+			if (s->text[0] == '\0')
+			{
+				sprintf(fileName, "untitled-%d", j);
+			}
+			else
+			{
+				for (i = 0; i < 22; i++)
+				{
+					tmpBuffer[i] = (char)tolower(song->samples[editor.currSample].text[i]);
+					if (tmpBuffer[i] == '\0') break;
+					sanitizeFilenameChar(&tmpBuffer[i]);
+				}
+
+				removeSampleFileExt(tmpBuffer);
+				sprintf(fileName, "%s-%d", tmpBuffer, j);
+			}
+
+			addSampleFileExt(fileName);
+
+			if (stat(fileName, &statBuffer) != 0)
+				break; // this filename can be used
+		}
+	}
+
+	// check if we need to overwrite file...
+
+	if (checkIfFileExist && stat(fileName, &statBuffer) == 0)
+	{
+		ui.askScreenShown = true;
+		ui.askScreenType = ASK_SAVESMP_OVERWRITE;
+		pointerSetMode(POINTER_MODE_MSG1, NO_CARRY);
+		setStatusMessage("OVERWRITE FILE ?", NO_CARRY);
+		renderAskDialog();
+		return -1;
+	}
+
+	if (ui.askScreenShown)
+	{
+		ui.answerNo = false;
+		ui.answerYes = false;
+		ui.askScreenShown = false;
+	}
+
+	f = fopen(fileName, "wb");
+	if (f == NULL)
+	{
+		displayErrorMsg("FILE I/O ERROR !");
+		return false;
+	}
+
+	const int8_t *sampleData = &song->sampleData[s->offset];
+	const uint32_t sampleLength = s->length;
+	const uint32_t loopStart = s->loopStart & 0xFFFE;
+	const uint32_t loopLength = s->loopLength & 0xFFFE;
+
+	switch (diskop.smpSaveType)
+	{
+		default:
+		case DISKOP_SMP_WAV:
+		{
+			wavHeader.format = 0x45564157; // "WAVE"
+			wavHeader.chunkID = 0x46464952; // "RIFF"
+			wavHeader.subchunk1ID = 0x20746D66; // "fmt "
+			wavHeader.subchunk2ID = 0x61746164; // "data"
+			wavHeader.subchunk1Size = 16;
+			wavHeader.subchunk2Size = sampleLength;
+			wavHeader.chunkSize = 36 + wavHeader.subchunk2Size;
+			wavHeader.audioFormat = 1;
+			wavHeader.numChannels = 1;
+			wavHeader.bitsPerSample = 8;
+			wavHeader.sampleRate = PLAYBACK_FREQ;
+			wavHeader.byteRate = wavHeader.sampleRate * wavHeader.numChannels * wavHeader.bitsPerSample / 8;
+			wavHeader.blockAlign = wavHeader.numChannels * wavHeader.bitsPerSample / 8;
+
+			// set "sampler" chunk if loop is enabled
+			if (loopStart+loopLength > 2) // loop enabled?
+			{
+				wavHeader.chunkSize += sizeof (samplerChunk_t);
+				memset(&samplerChunk, 0, sizeof (samplerChunk_t));
+				samplerChunk.chunkID = 0x6C706D73; // "smpl"
+				samplerChunk.chunkSize = 60;
+				samplerChunk.dwSamplePeriod = 1000000000 / PLAYBACK_FREQ;
+				samplerChunk.dwMIDIUnityNote = 60; // 60 = MIDI middle-C
+				samplerChunk.cSampleLoops = 1;
+				samplerChunk.loop.dwStart = loopStart;
+				samplerChunk.loop.dwEnd = (loopStart + loopLength) - 1;
+			}
+
+			// set ModPlug Tracker chunk (used for sample volume only in this case)
+			wavHeader.chunkSize += sizeof (mptExtraChunk);
+			memset(&mptExtraChunk, 0, sizeof (mptExtraChunk));
+			mptExtraChunk.chunkID = 0x61727478; // "xtra"
+			mptExtraChunk.chunkSize = sizeof (mptExtraChunk) - 4 - 4;
+			mptExtraChunk.defaultPan = 128; // 0..255
+			mptExtraChunk.defaultVolume = s->volume * 4; // 0..256
+			mptExtraChunk.globalVolume = 64; // 0..64
+
+			fwrite(&wavHeader, sizeof (wavHeader_t), 1, f);
+
+			for (i = 0; i < sampleLength; i++)
+				fputc((uint8_t)(sampleData[i] + 128), f);
+
+			if (sampleLength & 1)
+				fputc(0, f); // pad align byte
+
+			if (loopStart+loopLength > 2) // loop enabled?
+				fwrite(&samplerChunk, sizeof (samplerChunk), 1, f);
+
+			fwrite(&mptExtraChunk, sizeof (mptExtraChunk), 1, f);
+		}
+		break;
+
+		case DISKOP_SMP_IFF:
+		{
+			// "FORM" chunk
+			iffWriteChunkHeader(f, "FORM", 0); // "FORM" chunk size is overwritten later
+			iffWriteUint32(f, 0x38535658); // "8SVX"
+
+			// "VHDR" chunk
+			iffWriteChunkHeader(f, "VHDR", 20);
+
+			if (loopStart+loopLength > 2) // loop enabled?
+			{
+				iffWriteUint32(f, loopStart); // oneShotHiSamples
+				iffWriteUint32(f, loopLength); // repeatHiSamples
+			}
+			else
+			{
+				iffWriteUint32(f, 0); // oneShotHiSamples
+				iffWriteUint32(f, 0); // repeatHiSamples
+			}
+
+			iffWriteUint32(f, 0); // samplesPerHiCycle
+			iffWriteUint16(f, PLAYBACK_FREQ); // samplesPerSec
+			iffWriteUint8(f, 1); // ctOctave (number of samples)
+			iffWriteUint8(f, 0); // sCompression
+			iffWriteUint32(f, s->volume * 1024); // volume (max: 65536/0x10000)
+
+			// "NAME" chunk
+			chunkLen = (uint32_t)strlen(s->text);
+			if (chunkLen > 0)
+			{
+				iffWriteChunkHeader(f, "NAME", chunkLen);
+				iffWriteChunkData(f, s->text, chunkLen);
+			}
+
+			// "ANNO" chunk (we put the program name here)
+			const char annoStr[] = "ProTracker 2 clone";
+			chunkLen = sizeof (annoStr) - 1;
+			iffWriteChunkHeader(f, "ANNO", chunkLen);
+			iffWriteChunkData(f, annoStr, chunkLen);
+
+			// "BODY" chunk
+			chunkLen = sampleLength;
+			iffWriteChunkHeader(f, "BODY", chunkLen);
+			iffWriteChunkData(f, sampleData, chunkLen);
+
+			// go back and fill in "FORM" chunk size
+			chunkLen = ftell(f) - 8;
+			fseek(f, 4, SEEK_SET);
+			iffWriteUint32(f, chunkLen);
+		}
+		break;
+
+		case DISKOP_SMP_RAW:
+			fwrite(sampleData, 1, sampleLength, f);
+		break;
+	}
+
+	fclose(f);
+
+	displayMsg("SAMPLE SAVED !");
+	setMsgPointer();
+
+	diskop.cached = false;
+	if (ui.diskOpScreenShown)
+		ui.updateDiskOpFileList = true;
+
+	return true;
+}
--- /dev/null
+++ b/src/pt2_sample_saver.h
@@ -1,0 +1,5 @@
+#pragma once
+
+#include <stdbool.h>
+
+bool saveSample(bool checkIfFileExist, bool giveNewFreeFilename);
--- a/src/pt2_sampleloader.c
+++ /dev/null
@@ -1,2025 +1,0 @@
-// for finding memory leaks in debug mode with Visual Studio 
-#if defined _DEBUG && defined _MSC_VER
-#include <crtdbg.h>
-#endif
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdint.h>
-#include <stdbool.h>
-#include <ctype.h> // tolower()/toupper()
-#ifdef _WIN32
-#include <io.h>
-#else
-#include <unistd.h>
-#endif
-#include <fcntl.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include "pt2_header.h"
-#include "pt2_textout.h"
-#include "pt2_mouse.h"
-#include "pt2_sampler.h"
-#include "pt2_audio.h"
-#include "pt2_sampleloader.h"
-#include "pt2_visuals.h"
-#include "pt2_helpers.h"
-#include "pt2_unicode.h"
-
-#define DOWNSAMPLE_CUTOFF_FACTOR 4.0
-
-enum
-{
-	WAV_FORMAT_PCM = 0x0001,
-	WAV_FORMAT_IEEE_FLOAT = 0x0003
-};
-
-static bool loadWAVSample(UNICHAR *fileName, char *entryName, int8_t forceDownSampling);
-static bool loadIFFSample(UNICHAR *fileName, char *entryName);
-static bool loadRAWSample(UNICHAR *fileName, char *entryName);
-static bool loadAIFFSample(UNICHAR *fileName, char *entryName, int8_t forceDownSampling);
-
-static bool loadedFileWasAIFF;
-
-static bool lowPassSample8Bit(int8_t *buffer, int32_t length, int32_t sampleFrequency, double cutoff)
-{
-	rcFilter_t filter;
-
-	if (buffer == NULL || length == 0 || cutoff == 0.0)
-		return false;
-
-	calcRCFilterCoeffs(sampleFrequency, cutoff, &filter);
-	clearRCFilterState(&filter);
-
-	for (int32_t i = 0; i < length; i++)
-	{
-		int32_t sample;
-		double dSample;
-
-		RCLowPassFilterMono(&filter, buffer[i], &dSample);
-		sample = (int32_t)dSample;
-
-		buffer[i] = (int8_t)CLAMP(sample, INT8_MIN, INT8_MAX);
-	}
-	
-	return true;
-}
-
-static bool lowPassSample8BitUnsigned(uint8_t *buffer, int32_t length, int32_t sampleFrequency, double cutoff)
-{
-	rcFilter_t filter;
-
-	if (buffer == NULL || length == 0 || cutoff == 0.0)
-		return false;
-
-	calcRCFilterCoeffs(sampleFrequency, cutoff, &filter);
-	clearRCFilterState(&filter);
-
-	for (int32_t i = 0; i < length; i++)
-	{
-		int32_t sample;
-		double dSample;
-
-		RCLowPassFilterMono(&filter, buffer[i] - 128, &dSample);
-		sample = (int32_t)dSample;
-
-		sample = CLAMP(sample, INT8_MIN, INT8_MAX);
-		buffer[i] = (uint8_t)(sample + 128);
-	}
-
-	return true;
-}
-
-static bool lowPassSample16Bit(int16_t *buffer, int32_t length, int32_t sampleFrequency, double cutoff)
-{
-	rcFilter_t filter;
-
-	if (buffer == NULL || length == 0 || cutoff == 0.0)
-		return false;
-
-	calcRCFilterCoeffs(sampleFrequency, cutoff, &filter);
-	clearRCFilterState(&filter);
-
-	for (int32_t i = 0; i < length; i++)
-	{
-		int32_t sample;
-		double dSample;
-
-		RCLowPassFilterMono(&filter, buffer[i], &dSample);
-		sample = (int32_t)dSample;
-
-		buffer[i] = (int16_t)CLAMP(sample, INT16_MIN, INT16_MAX);
-	}
-
-	return true;
-}
-
-static bool lowPassSample32Bit(int32_t *buffer, int32_t length, int32_t sampleFrequency, double cutoff)
-{
-	rcFilter_t filter;
-
-	if (buffer == NULL || length == 0 || cutoff == 0.0)
-		return false;
-
-	calcRCFilterCoeffs(sampleFrequency, cutoff, &filter);
-	clearRCFilterState(&filter);
-
-	for (int32_t i = 0; i < length; i++)
-	{
-		int64_t sample;
-		double dSample;
-
-		RCLowPassFilterMono(&filter, buffer[i], &dSample);
-		sample = (int32_t)dSample;
-
-		buffer[i] = (int32_t)CLAMP(sample, INT32_MIN, INT32_MAX);
-	}
-
-	return true;
-}
-
-static bool lowPassSampleFloat(float *buffer, int32_t length, int32_t sampleFrequency, double cutoff)
-{
-	rcFilter_t filter;
-
-	if (buffer == NULL || length == 0 || cutoff == 0.0)
-		return false;
-
-	calcRCFilterCoeffs(sampleFrequency, cutoff, &filter);
-	clearRCFilterState(&filter);
-
-	for (int32_t i = 0; i < length; i++)
-	{
-		double dSample;
-
-		RCLowPassFilterMono(&filter, buffer[i], &dSample);
-		buffer[i] = (float)dSample;
-	}
-
-	return true;
-}
-
-static bool lowPassSampleDouble(double *buffer, int32_t length, int32_t sampleFrequency, double cutoff)
-{
-	rcFilter_t filter;
-
-	if (buffer == NULL || length == 0 || cutoff == 0.0)
-		return false;
-
-	calcRCFilterCoeffs(sampleFrequency, cutoff, &filter);
-	clearRCFilterState(&filter);
-
-	for (int32_t i = 0; i < length; i++)
-	{
-		double dSample;
-		RCLowPassFilterMono(&filter, buffer[i], &dSample);
-
-		buffer[i] = dSample;
-	}
-
-	return true;
-}
-
-void extLoadWAVOrAIFFSampleCallback(bool downsample)
-{
-	if (loadedFileWasAIFF)
-		loadAIFFSample(editor.fileNameTmpU, editor.entryNameTmp, downsample);
-	else
-		loadWAVSample(editor.fileNameTmpU, editor.entryNameTmp, downsample);
-}
-
-bool loadWAVSample(UNICHAR *fileName, char *entryName, int8_t forceDownSampling)
-{
-	bool wavSampleNameFound;
-	uint8_t *audioDataU8;
-	int16_t *audioDataS16, tempVol, smp16;
-	uint16_t audioFormat, numChannels, bitsPerSample;
-	int32_t *audioDataS32, smp32;
-	uint32_t *audioDataU32, i, nameLen, chunkID, chunkSize;
-	uint32_t sampleLength, sampleRate, filesize, loopFlags;
-	uint32_t loopStart, loopEnd, dataPtr, dataLen, fmtPtr, endOfChunk, bytesRead;
-	uint32_t fmtLen, inamPtr, inamLen, smplPtr, smplLen, xtraPtr, xtraLen;
-	float *fAudioDataFloat, fSmp;
-	double *dAudioDataDouble, dSmp;
-	FILE *f;
-	moduleSample_t *s;
-
-	loadedFileWasAIFF = false;
-
-	// zero out chunk pointers and lengths
-	fmtPtr  = 0; fmtLen = 0;
-	dataPtr = 0; dataLen = 0;
-	inamPtr = 0; inamLen = 0;
-	xtraPtr = 0; xtraLen = 0;
-	smplPtr = 0; smplLen = 0;
-
-	wavSampleNameFound = false;
-
-	s = &modEntry->samples[editor.currSample];
-
-	if (forceDownSampling == -1)
-	{
-		// these two *must* be fully wiped, for outputting reasons
-		memset(editor.fileNameTmpU, 0, PATH_MAX);
-		memset(editor.entryNameTmp, 0, PATH_MAX);
-		UNICHAR_STRCPY(editor.fileNameTmpU, fileName);
-		strcpy(editor.entryNameTmp, entryName);
-	}
-
-	f = UNICHAR_FOPEN(fileName, "rb");
-	if (f == NULL)
-	{
-		displayErrorMsg("FILE I/O ERROR !");
-		return false;
-	}
-
-	fseek(f, 0, SEEK_END);
-	filesize = ftell(f);
-	if (filesize == 0)
-	{
-		fclose(f);
-
-		displayErrorMsg("NOT A WAV !");
-		return false;
-	}
-
-	// look for wanted chunks and set up pointers + lengths
-	fseek(f, 12, SEEK_SET);
-
-	bytesRead = 0;
-	while (!feof(f) && bytesRead < filesize-12)
-	{
-		fread(&chunkID, 4, 1, f); if (feof(f)) break;
-		fread(&chunkSize, 4, 1, f); if (feof(f)) break;
-
-		endOfChunk = (ftell(f) + chunkSize) + (chunkSize & 1);
-		switch (chunkID)
-		{
-			case 0x20746D66: // "fmt "
-			{
-				fmtPtr = ftell(f);
-				fmtLen = chunkSize;
-			}
-			break;
-
-			case 0x61746164: // "data"
-			{
-				dataPtr = ftell(f);
-				dataLen = chunkSize;
-			}
-			break;
-
-			case 0x5453494C: // "LIST"
-			{
-				if (chunkSize >= 4)
-				{
-					fread(&chunkID, 4, 1, f);
-					if (chunkID == 0x4F464E49) // "INFO"
-					{
-						bytesRead = 0;
-						while (!feof(f) && (bytesRead < chunkSize))
-						{
-							fread(&chunkID, 4, 1, f);
-							fread(&chunkSize, 4, 1, f);
-
-							switch (chunkID)
-							{
-								case 0x4D414E49: // "INAM"
-								{
-									inamPtr = ftell(f);
-									inamLen = chunkSize;
-								}
-								break;
-
-								default: break;
-							}
-
-							bytesRead += (chunkSize + (chunkSize & 1));
-						}
-					}
-				}
-			}
-			break;
-
-			case 0x61727478: // "xtra"
-			{
-				xtraPtr = ftell(f);
-				xtraLen = chunkSize;
-			}
-			break;
-
-			case 0x6C706D73: // "smpl"
-			{
-				smplPtr = ftell(f);
-				smplLen = chunkSize;
-			}
-			break;
-
-			default: break;
-		}
-
-		bytesRead += chunkSize + (chunkSize & 1);
-		fseek(f, endOfChunk, SEEK_SET);
-	}
-
-	// we need at least "fmt " and "data" - check if we found them sanely
-	if ((fmtPtr == 0 || fmtLen < 16) || (dataPtr == 0 || dataLen == 0))
-	{
-		fclose(f);
-		displayErrorMsg("NOT A WAV !");
-		return false;
-	}
-
-	// ---- READ "fmt " CHUNK ----
-	fseek(f, fmtPtr, SEEK_SET);
-	fread(&audioFormat, 2, 1, f);
-	fread(&numChannels, 2, 1, f);
-	fread(&sampleRate,  4, 1, f);
-	fseek(f, 6, SEEK_CUR);
-	fread(&bitsPerSample, 2, 1, f);
-	sampleLength = dataLen;
-	// ---------------------------
-
-	if (sampleRate == 0 || sampleLength == 0 || sampleLength >= filesize*(bitsPerSample/8))
-	{
-		fclose(f);
-		displayErrorMsg("WAV CORRUPT !");
-		return false;
-	}
-
-	if (audioFormat != WAV_FORMAT_PCM && audioFormat != WAV_FORMAT_IEEE_FLOAT)
-	{
-		fclose(f);
-		displayErrorMsg("WAV UNSUPPORTED !");
-		return false;
-	}
-
-	if ((numChannels == 0) || (numChannels > 2))
-	{
-		fclose(f);
-		displayErrorMsg("WAV UNSUPPORTED !");
-		return false;
-	}
-
-	if (audioFormat == WAV_FORMAT_IEEE_FLOAT && bitsPerSample != 32 && bitsPerSample != 64)
-	{
-		fclose(f);
-		displayErrorMsg("WAV UNSUPPORTED !");
-		return false;
-	}
-
-	if (bitsPerSample != 8 && bitsPerSample != 16 && bitsPerSample != 24 && bitsPerSample != 32 && bitsPerSample != 64)
-	{
-		fclose(f);
-		displayErrorMsg("WAV UNSUPPORTED !");
-		return false;
-	}
-
-	if (sampleRate > 22050)
-	{
-		if (forceDownSampling == -1)
-		{
-			showDownsampleAskDialog();
-			fclose(f);
-			return true;
-		}
-	}
-	else
-	{
-		forceDownSampling = false;
-	}
-
-	// ---- READ SAMPLE DATA ----
-	fseek(f, dataPtr, SEEK_SET);
-
-	if (bitsPerSample == 8) // 8-BIT INTEGER SAMPLE
-	{
-		if (sampleLength > MAX_SAMPLE_LEN*4)
-			sampleLength = MAX_SAMPLE_LEN*4;
-
-		audioDataU8 = (uint8_t *)malloc(sampleLength * sizeof (uint8_t));
-		if (audioDataU8 == NULL)
-		{
-			fclose(f);
-			statusOutOfMemory();
-			return false;
-		}
-
-		// read sample data
-		if (fread(audioDataU8, 1, sampleLength, f) != sampleLength)
-		{
-			fclose(f);
-			free(audioDataU8);
-			displayErrorMsg("I/O ERROR !");
-			return false;
-		}
-
-		// convert from stereo to mono (if needed)
-		if (numChannels == 2)
-		{
-			sampleLength >>= 1;
-			for (i = 0; i < sampleLength-1; i++) // add right channel to left channel
-			{
-				smp16 = (audioDataU8[(i << 1) + 0] - 128) + (audioDataU8[(i << 1) + 1] - 128);
-				smp16 = 128 + (smp16 >> 1);
-				audioDataU8[i] = (uint8_t)smp16;
-			}
-		}
-
-		// 2x downsampling - remove every other sample (if needed)
-		if (forceDownSampling)
-		{
-			if (config.sampleLowpass)
-				lowPassSample8BitUnsigned(audioDataU8, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
-
-			sampleLength >>= 1;
-			for (i = 1; i < sampleLength; i++)
-				audioDataU8[i] = audioDataU8[i << 1];
-		}
-
-		if (sampleLength > MAX_SAMPLE_LEN)
-			sampleLength = MAX_SAMPLE_LEN;
-
-		turnOffVoices();
-		for (i = 0; i < MAX_SAMPLE_LEN; i++)
-		{
-			if (i <= (sampleLength & 0xFFFFFFFE))
-				modEntry->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = audioDataU8[i] - 128;
-			else
-				modEntry->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample
-		}
-
-		free(audioDataU8);
-	}
-	else if (bitsPerSample == 16) // 16-BIT INTEGER SAMPLE
-	{
-		sampleLength >>= 1;
-		if (sampleLength > MAX_SAMPLE_LEN*4)
-			sampleLength = MAX_SAMPLE_LEN*4;
-
-		audioDataS16 = (int16_t *)malloc(sampleLength * sizeof (int16_t));
-		if (audioDataS16 == NULL)
-		{
-			fclose(f);
-			statusOutOfMemory();
-			return false;
-		}
-
-		// read sample data
-		if (fread(audioDataS16, 2, sampleLength, f) != sampleLength)
-		{
-			fclose(f);
-			free(audioDataS16);
-			displayErrorMsg("I/O ERROR !");
-			return false;
-		}
-
-		// convert from stereo to mono (if needed)
-		if (numChannels == 2)
-		{
-			sampleLength >>= 1;
-			for (i = 0; i < sampleLength-1; i++) // add right channel to left channel
-			{
-				smp32 = (audioDataS16[(i << 1) + 0] + audioDataS16[(i << 1) + 1]) >> 1;
-				audioDataS16[i] = (int16_t)smp32;
-			}
-		}
-
-		// 2x downsampling - remove every other sample (if needed)
-		if (forceDownSampling)
-		{
-			if (config.sampleLowpass)
-				lowPassSample16Bit(audioDataS16, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
-
-			sampleLength >>= 1;
-			for (i = 1; i < sampleLength; i++)
-				audioDataS16[i] = audioDataS16[i << 1];
-		}
-
-		if (sampleLength > MAX_SAMPLE_LEN)
-			sampleLength = MAX_SAMPLE_LEN;
-
-		normalize16bitSigned(audioDataS16, sampleLength);
-
-		turnOffVoices();
-		for (i = 0; i < MAX_SAMPLE_LEN; i++)
-		{
-			if (i <= (sampleLength & 0xFFFFFFFE))
-				modEntry->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = audioDataS16[i] >> 8;
-			else
-				modEntry->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample
-		}
-
-		free(audioDataS16);
-	}
-	else if (bitsPerSample == 24) // 24-BIT INTEGER SAMPLE
-	{
-		sampleLength /= 3;
-		if (sampleLength > MAX_SAMPLE_LEN*4)
-			sampleLength = MAX_SAMPLE_LEN*4;
-
-		audioDataS32 = (int32_t *)malloc(sampleLength * sizeof (int32_t));
-		if (audioDataS32 == NULL)
-		{
-			fclose(f);
-			statusOutOfMemory();
-			return false;
-		}
-
-		// read sample data
-		audioDataU8 = (uint8_t *)audioDataS32;
-		for (i = 0; i < sampleLength; i++)
-		{
-			audioDataU8[0] = 0;
-			fread(&audioDataU8[1], 3, 1, f);
-			audioDataU8 += sizeof (int32_t);
-		}
-
-		// convert from stereo to mono (if needed)
-		if (numChannels == 2)
-		{
-			sampleLength >>= 1;
-			for (i = 0; i < sampleLength-1; i++) // add right channel to left channel
-			{
-				int64_t smp = ((int64_t)audioDataS32[(i << 1) + 0] + audioDataS32[(i << 1) + 1]) >> 1;
-				audioDataS32[i] = (int32_t)smp;
-			}
-		}
-
-		// 2x downsampling - remove every other sample (if needed)
-		if (forceDownSampling)
-		{
-			if (config.sampleLowpass)
-				lowPassSample32Bit(audioDataS32, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
-
-			sampleLength >>= 1;
-			for (i = 1; i < sampleLength; i++)
-				audioDataS32[i] = audioDataS32[i << 1];
-		}
-
-		if (sampleLength > MAX_SAMPLE_LEN)
-			sampleLength = MAX_SAMPLE_LEN;
-
-		normalize32bitSigned(audioDataS32, sampleLength);
-
-		turnOffVoices();
-		for (i = 0; i < MAX_SAMPLE_LEN; i++)
-		{
-			if (i <= (sampleLength & 0xFFFFFFFE))
-				modEntry->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = (int8_t)(audioDataS32[i] >> 24);
-			else
-				modEntry->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample
-		}
-
-		free(audioDataS32);
-	}
-	else if (audioFormat == WAV_FORMAT_PCM && bitsPerSample == 32) // 32-BIT INTEGER SAMPLE
-	{
-		sampleLength >>= 2;
-		if (sampleLength > MAX_SAMPLE_LEN*4)
-			sampleLength = MAX_SAMPLE_LEN*4;
-
-		audioDataS32 = (int32_t *)malloc(sampleLength * sizeof (int32_t));
-		if (audioDataS32 == NULL)
-		{
-			fclose(f);
-			statusOutOfMemory();
-			return false;
-		}
-
-		// read sample data
-		if (fread(audioDataS32, 4, sampleLength, f) != sampleLength)
-		{
-			fclose(f);
-			free(audioDataS32);
-			displayErrorMsg("I/O ERROR !");
-			return false;
-		}
-
-		// convert from stereo to mono (if needed)
-		if (numChannels == 2)
-		{
-			sampleLength >>= 1;
-			for (i = 0; i < sampleLength-1; i++) // add right channel to left channel
-			{
-				int64_t smp = ((int64_t)audioDataS32[(i << 1) + 0] + audioDataS32[(i << 1) + 1]) >> 1;
-				audioDataS32[i] = (int32_t)smp;
-			}
-		}
-
-		// 2x downsampling - remove every other sample (if needed)
-		if (forceDownSampling)
-		{
-			if (config.sampleLowpass)
-				lowPassSample32Bit(audioDataS32, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
-
-			sampleLength >>= 1;
-			for (i = 1; i < sampleLength; i++)
-				audioDataS32[i] = audioDataS32[i << 1];
-		}
-
-		if (sampleLength > MAX_SAMPLE_LEN)
-			sampleLength = MAX_SAMPLE_LEN;
-
-		normalize32bitSigned(audioDataS32, sampleLength);
-
-		turnOffVoices();
-		for (i = 0; i < MAX_SAMPLE_LEN; i++)
-		{
-			if (i <= (sampleLength & 0xFFFFFFFE))
-				modEntry->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = (int8_t)(audioDataS32[i] >> 24);
-			else
-				modEntry->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample
-		}
-
-		free(audioDataS32);
-	}
-	else if (audioFormat == WAV_FORMAT_IEEE_FLOAT && bitsPerSample == 32) // 32-BIT FLOATING POINT SAMPLE
-	{
-		sampleLength >>= 2;
-		if (sampleLength > MAX_SAMPLE_LEN*4)
-			sampleLength = MAX_SAMPLE_LEN*4;
-
-		audioDataU32 = (uint32_t *)malloc(sampleLength * sizeof (uint32_t));
-		if (audioDataU32 == NULL)
-		{
-			fclose(f);
-			statusOutOfMemory();
-			return false;
-		}
-
-		// read sample data
-		if (fread(audioDataU32, 4, sampleLength, f) != sampleLength)
-		{
-			fclose(f);
-			free(audioDataU32);
-			displayErrorMsg("I/O ERROR !");
-			return false;
-		}
-
-		fAudioDataFloat = (float *)audioDataU32;
-
-		// convert from stereo to mono (if needed)
-		if (numChannels == 2)
-		{
-			sampleLength >>= 1;
-			for (i = 0; i < sampleLength-1; i++) // add right channel to left channel
-			{
-				fSmp = (fAudioDataFloat[(i * 2) + 0] + fAudioDataFloat[(i * 2) + 1]) * 0.5f;
-				fAudioDataFloat[i] = fSmp;
-			}
-		}
-
-		// 2x downsampling - remove every other sample (if needed)
-		if (forceDownSampling)
-		{
-			if (config.sampleLowpass)
-				lowPassSampleFloat(fAudioDataFloat, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
-
-			sampleLength >>= 1;
-			for (i = 1; i < sampleLength; i++)
-				fAudioDataFloat[i] = fAudioDataFloat[i << 1];
-		}
-
-		if (sampleLength > MAX_SAMPLE_LEN)
-			sampleLength = MAX_SAMPLE_LEN;
-
-		normalize8bitFloatSigned(fAudioDataFloat, sampleLength);
-
-		turnOffVoices();
-		for (i = 0; i < MAX_SAMPLE_LEN; i++)
-		{
-			if (i <= (sampleLength & 0xFFFFFFFE))
-			{
-				smp32 = (int32_t)fAudioDataFloat[i];
-				CLAMP8(smp32);
-				modEntry->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = (int8_t)smp32;
-			}
-			else
-			{
-				modEntry->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample
-			}
-		}
-
-		free(audioDataU32);
-	}
-	else if (audioFormat == WAV_FORMAT_IEEE_FLOAT && bitsPerSample == 64) // 64-BIT FLOATING POINT SAMPLE
-	{
-		sampleLength >>= 3;
-		if (sampleLength > MAX_SAMPLE_LEN*4)
-			sampleLength = MAX_SAMPLE_LEN*4;
-
-		audioDataU32 = (uint32_t *)malloc(sampleLength * (sizeof (uint32_t) * 2));
-		if (audioDataU32 == NULL)
-		{
-			fclose(f);
-			statusOutOfMemory();
-			return false;
-		}
-
-		// read sample data
-		if (fread(audioDataU32, 8, sampleLength, f) != sampleLength)
-		{
-			fclose(f);
-			free(audioDataU32);
-			displayErrorMsg("I/O ERROR !");
-			return false;
-		}
-
-		dAudioDataDouble = (double *)audioDataU32;
-
-		// convert from stereo to mono (if needed)
-		if (numChannels == 2)
-		{
-			sampleLength >>= 1;
-			for (i = 0; i < sampleLength-1; i++) // add right channel to left channel
-			{
-				dSmp = (dAudioDataDouble[(i * 2) + 0] + dAudioDataDouble[(i * 2) + 1]) * 0.5;
-				dAudioDataDouble[i] = dSmp;
-			}
-		}
-
-		// 2x downsampling - remove every other sample (if needed)
-		if (forceDownSampling)
-		{
-			if (config.sampleLowpass)
-				lowPassSampleDouble(dAudioDataDouble, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
-
-			sampleLength >>= 1;
-			for (i = 1; i < sampleLength; i++)
-				dAudioDataDouble[i] = dAudioDataDouble[i << 1];
-		}
-
-		if (sampleLength > MAX_SAMPLE_LEN)
-			sampleLength = MAX_SAMPLE_LEN;
-
-		normalize8bitDoubleSigned(dAudioDataDouble, sampleLength);
-
-		turnOffVoices();
-		for (i = 0; i < MAX_SAMPLE_LEN; i++)
-		{
-			if (i <= (sampleLength & 0xFFFFFFFE))
-			{
-				smp32 = (int32_t)dAudioDataDouble[i];
-				CLAMP8(smp32);
-				modEntry->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = (int8_t)smp32;
-			}
-			else
-			{
-				modEntry->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample
-			}
-		}
-
-		free(audioDataU32);
-	}
-
-	// set sample length
-	if (sampleLength & 1)
-	{
-		if (++sampleLength > MAX_SAMPLE_LEN)
-			sampleLength = MAX_SAMPLE_LEN;
-	}
-
-	s->length = (uint16_t)sampleLength;
-	s->fineTune = 0;
-	s->volume = 64;
-	s->loopStart = 0;
-	s->loopLength = 2;
-
-	// ---- READ "smpl" chunk ----
-	if (smplPtr != 0 && smplLen > 52)
-	{
-		fseek(f, smplPtr + 28, SEEK_SET); // seek to first wanted byte
-
-		fread(&loopFlags, 4, 1, f);
-		fseek(f, 12, SEEK_CUR);
-		fread(&loopStart, 4, 1, f);
-		fread(&loopEnd, 4, 1, f);
-		loopEnd++;
-
-		if (forceDownSampling)
-		{
-			// we already downsampled 2x, so we're half the original length
-			loopStart >>= 1;
-			loopEnd >>= 1;
-		}
-
-		loopStart &= 0xFFFFFFFE;
-		loopEnd &= 0xFFFFFFFE;
-
-		if (loopFlags)
-		{
-			if (loopStart+(loopEnd-loopStart) <= s->length)
-			{
-				s->loopStart = (uint16_t)loopStart;
-				s->loopLength = (uint16_t)(loopEnd - loopStart);
-
-				if (s->loopLength < 2)
-				{
-					s->loopStart = 0;
-					s->loopLength = 2;
-				}
-			}
-		}
-	}
-	// ---------------------------
-
-	// ---- READ "xtra" chunk ----
-	if (xtraPtr != 0 && xtraLen >= 8)
-	{
-		fseek(f, xtraPtr + 4, SEEK_SET); // seek to first wanted byte
-
-		// volume (0..256)
-		fseek(f, 2, SEEK_CUR);
-		fread(&tempVol, 2, 1, f);
-		if (tempVol > 256)
-			tempVol = 256;
-
-		s->volume = (int8_t)(tempVol >> 2);
-	}
-	// ---------------------------
-
-	// ---- READ "INAM" chunk ----
-	if (inamPtr != 0 && inamLen > 0)
-	{
-		fseek(f, inamPtr, SEEK_SET); // seek to first wanted byte
-
-		for (i = 0; i < 21; i++)
-		{
-			if (i < inamLen)
-				s->text[i] = (char)toupper(fgetc(f));
-			else
-				s->text[i] = '\0';
-		}
-
-		s->text[21] = '\0';
-		s->text[22] = '\0';
-
-		wavSampleNameFound = true;
-	}
-	// ---------------------------
-
-	fclose(f);
-
-	// copy over sample name
-	if (!wavSampleNameFound)
-	{
-		nameLen = (uint32_t)strlen(entryName);
-		for (i = 0; i < 21; i++)
-			s->text[i] = (i < nameLen) ? (char)toupper(entryName[i]) : '\0';
-
-		s->text[21] = '\0';
-		s->text[22] = '\0';
-	}
-
-	// remove .wav from end of sample name (if present)
-	nameLen = (uint32_t)strlen(s->text);
-	if (nameLen >= 4 && !_strnicmp(&s->text[nameLen-4], ".WAV", 4))
-		 memset(&s->text[nameLen-4], '\0', 4);
-
-	editor.sampleZero = false;
-	editor.samplePos = 0;
-
-	fixSampleBeep(s);
-	updateCurrSample();
-
-	updateWindowTitle(MOD_IS_MODIFIED);
-	return true;
-}
-
-bool loadIFFSample(UNICHAR *fileName, char *entryName)
-{
-	bool nameFound, is16Bit;
-	char tmpCharBuf[23];
-	int8_t *sampleData;
-	int16_t sample16, *ptr16;
-	int32_t filesize;
-	uint32_t i, sampleLength, sampleLoopStart, sampleLoopLength;
-	uint32_t sampleVolume, blockName, blockSize;
-	uint32_t vhdrPtr, vhdrLen, bodyPtr, bodyLen, namePtr, nameLen;
-	FILE *f;
-	moduleSample_t *s;
-
-	s = &modEntry->samples[editor.currSample];
-
-	vhdrPtr = 0; vhdrLen = 0;
-	bodyPtr = 0; bodyLen = 0;
-	namePtr = 0; nameLen = 0;
-
-	f = UNICHAR_FOPEN(fileName, "rb");
-	if (f == NULL)
-	{
-		displayErrorMsg("FILE I/O ERROR !");
-		return false;
-	}
-
-	fseek(f, 0, SEEK_END);
-	filesize = ftell(f);
-	if (filesize == 0)
-	{
-		displayErrorMsg("IFF IS CORRUPT !");
-		return false;
-	}
-
-	fseek(f, 8, SEEK_SET);
-	fread(tmpCharBuf, 1, 4, f);
-	is16Bit = !strncmp(tmpCharBuf, "16SV", 4);
-
-	sampleLength = 0;
-	nameFound = false;
-	sampleVolume = 65536; // max volume
-
-	fseek(f, 12, SEEK_SET);
-	while (!feof(f) && ftell(f) < filesize-12)
-	{
-		fread(&blockName, 4, 1, f); if (feof(f)) break;
-		fread(&blockSize, 4, 1, f); if (feof(f)) break;
-
-		blockName = SWAP32(blockName);
-		blockSize = SWAP32(blockSize);
-
-		switch (blockName)
-		{
-			case 0x56484452: // VHDR
-			{
-				vhdrPtr = ftell(f);
-				vhdrLen = blockSize;
-			}
-			break;
-
-			case 0x4E414D45: // NAME
-			{
-				namePtr = ftell(f);
-				nameLen = blockSize;
-			}
-			break;
-
-			case 0x424F4459: // BODY
-			{
-				bodyPtr = ftell(f);
-				bodyLen = blockSize;
-			}
-			break;
-
-			default: break;
-		}
-
-		fseek(f, blockSize + (blockSize & 1), SEEK_CUR);
-	}
-
-	if (vhdrPtr == 0 || vhdrLen < 20 || bodyPtr == 0)
-	{
-		fclose(f);
-		displayErrorMsg("NOT A VALID IFF !");
-		return false;
-	}
-
-	// kludge for some really strange IFFs
-	if (bodyLen == 0)
-		bodyLen = filesize - bodyPtr;
-
-	if ((bodyPtr + bodyLen) > (uint32_t)filesize)
-		bodyLen = filesize - bodyPtr;
-
-	fseek(f, vhdrPtr, SEEK_SET);
-	fread(&sampleLoopStart,  4, 1, f); sampleLoopStart  = SWAP32(sampleLoopStart);
-	fread(&sampleLoopLength, 4, 1, f); sampleLoopLength = SWAP32(sampleLoopLength);
-
-	fseek(f, 4 + 2 + 1, SEEK_CUR);
-
-	if (fgetc(f) != 0) // sample type
-	{
-		fclose(f);
-		displayErrorMsg("UNSUPPORTED IFF !");
-		return false;
-	}
-
-	fread(&sampleVolume, 4, 1, f); sampleVolume = SWAP32(sampleVolume);
-	if (sampleVolume > 65536)
-		sampleVolume = 65536;
-
-	sampleVolume = (sampleVolume + 512) / 1024; // rounded
-	if (sampleVolume > 64)
-		sampleVolume = 64;
-
-	sampleLength = bodyLen;
-	if (is16Bit)
-	{
-		if (sampleLength > MAX_SAMPLE_LEN*2)
-			sampleLength = MAX_SAMPLE_LEN*2;
-	}
-	else
-	{
-		if (sampleLength > MAX_SAMPLE_LEN)
-			sampleLength = MAX_SAMPLE_LEN;
-	}
-
-	if (sampleLength == 0)
-	{
-		fclose(f);
-		displayErrorMsg("NOT A VALID IFF !");
-		return false;
-	}
-
-	sampleData = (int8_t *)malloc(sampleLength);
-	if (sampleData == NULL)
-	{
-		fclose(f);
-		statusOutOfMemory();
-		return false;
-	}
-
-	if (is16Bit)
-	{
-		sampleLength >>= 1;
-		sampleLoopStart >>= 1;
-		sampleLoopLength >>= 1;
-	}
-
-	sampleLength &= 0xFFFFFFFE;
-	sampleLoopStart &= 0xFFFFFFFE;
-	sampleLoopLength &= 0xFFFFFFFE;
-
-	if (sampleLength > MAX_SAMPLE_LEN)
-		sampleLength = MAX_SAMPLE_LEN;
-
-	if (sampleLoopLength < 2)
-	{
-		sampleLoopStart = 0;
-		sampleLoopLength = 2;
-	}
-
-	if (sampleLoopStart >= MAX_SAMPLE_LEN || sampleLoopLength > MAX_SAMPLE_LEN)
-	{
-		sampleLoopStart= 0;
-		sampleLoopLength = 2;
-	}
-
-	if (sampleLoopStart+sampleLoopLength > sampleLength)
-	{
-		sampleLoopStart = 0;
-		sampleLoopLength = 2;
-	}
-
-	if (sampleLoopStart > sampleLength-2)
-	{
-		sampleLoopStart = 0;
-		sampleLoopLength = 2;
-	}
-
-	turnOffVoices();
-
-	fseek(f, bodyPtr, SEEK_SET);
-	if (is16Bit) // FT2 specific 16SV format (little-endian samples)
-	{
-		fread(sampleData, 1, sampleLength << 1, f);
-
-		ptr16 = (int16_t *)sampleData;
-		for (i = 0; i < sampleLength; i++)
-		{
-			sample16 = ptr16[i];
-			modEntry->sampleData[s->offset+i] = sample16 >> 8;
-		}
-	}
-	else
-	{
-		fread(sampleData, 1, sampleLength, f);
-		memcpy(&modEntry->sampleData[s->offset], sampleData, sampleLength);
-	}
-
-	if (sampleLength < MAX_SAMPLE_LEN) // clear rest of sample data
-		memset(&modEntry->sampleData[s->offset + sampleLength], 0, MAX_SAMPLE_LEN - sampleLength);
-
-	free(sampleData);
-
-	// set sample attributes
-	s->volume = (int8_t)sampleVolume;
-	s->fineTune = 0;
-	s->length = (uint16_t)sampleLength;
-	s->loopStart = (uint16_t)sampleLoopStart;
-	s->loopLength = (uint16_t)sampleLoopLength;
-
-	// read name
-	if (namePtr != 0 && nameLen > 0)
-	{
-		fseek(f, namePtr, SEEK_SET);
-		memset(tmpCharBuf, 0, sizeof (tmpCharBuf));
-
-		if (nameLen > 21)
-		{
-			fread(tmpCharBuf, 1, 21, f);
-			fseek(f, nameLen - 21, SEEK_CUR);
-		}
-		else
-		{
-			fread(tmpCharBuf, 1, nameLen, f);
-		}
-
-		nameFound = true;
-	}
-
-	fclose(f);
-
-	// copy over sample name
-	memset(s->text, '\0', sizeof (s->text));
-
-	if (nameFound)
-	{
-		nameLen = (uint32_t)strlen(tmpCharBuf);
-		if (nameLen > 21)
-			nameLen = 21;
-
-		for (i = 0; i < nameLen; i++)
-			s->text[i] = (char)toupper(tmpCharBuf[i]);
-	}
-	else
-	{
-		nameLen = (uint32_t)strlen(entryName);
-		if (nameLen > 21)
-			nameLen = 21;
-
-		for (i = 0; i < nameLen; i++)
-			s->text[i] = (char)toupper(entryName[i]);
-	}
-
-	// remove .iff from end of sample name (if present)
-	nameLen = (uint32_t)strlen(s->text);
-	if (nameLen >= 4 && !strncmp(&s->text[nameLen-4], ".IFF", 4))
-		memset(&s->text[nameLen-4], '\0', 4);
-
-	editor.sampleZero = false;
-	editor.samplePos = 0;
-
-	fixSampleBeep(s);
-	updateCurrSample();
-
-	updateWindowTitle(MOD_IS_MODIFIED);
-	return false;
-}
-
-bool loadRAWSample(UNICHAR *fileName, char *entryName)
-{
-	uint8_t i;
-	uint32_t nameLen, fileSize;
-	FILE *f;
-	moduleSample_t *s;
-
-	s = &modEntry->samples[editor.currSample];
-
-	f = UNICHAR_FOPEN(fileName, "rb");
-	if (f == NULL)
-	{
-		displayErrorMsg("FILE I/O ERROR !");
-		return false;
-	}
-
-	fseek(f, 0, SEEK_END);
-	fileSize = ftell(f);
-	fseek(f, 0, SEEK_SET);
-
-	fileSize &= 0xFFFFFFFE;
-	if (fileSize > MAX_SAMPLE_LEN)
-		fileSize = MAX_SAMPLE_LEN;
-
-	turnOffVoices();
-
-	fread(&modEntry->sampleData[s->offset], 1, fileSize, f);
-	fclose(f);
-
-	if (fileSize < MAX_SAMPLE_LEN)
-		memset(&modEntry->sampleData[s->offset + fileSize], 0, MAX_SAMPLE_LEN - fileSize);
-
-	// set sample attributes
-	s->volume = 64;
-	s->fineTune = 0;
-	s->length = (uint16_t)fileSize;
-	s->loopStart = 0;
-	s->loopLength = 2;
-
-	// copy over sample name
-	nameLen = (uint32_t)strlen(entryName);
-	for (i = 0; i < 21; i++)
-		s->text[i] = (i < nameLen) ? (char)toupper(entryName[i]) : '\0';
-
-	s->text[21] = '\0';
-	s->text[22] = '\0';
-
-	editor.sampleZero = false;
-	editor.samplePos = 0;
-
-	fixSampleBeep(s);
-	updateCurrSample();
-
-	updateWindowTitle(MOD_IS_MODIFIED);
-	return true;
-}
-
-static int32_t getAIFFRate(uint8_t *in)
-{
-	int32_t exp;
-	uint32_t lo, hi;
-	double dOut;
-
-	exp = (int32_t)(((in[0] & 0x7F) << 8) | in[1]);
-	lo  = (in[2] << 24) | (in[3] << 16) | (in[4] << 8) | in[5];
-	hi  = (in[6] << 24) | (in[7] << 16) | (in[8] << 8) | in[9];
-
-	if (exp == 0 && lo == 0 && hi == 0)
-		return 0;
-
-	exp -= 16383;
-
-	dOut = ldexp(lo, -31 + exp) + ldexp(hi, -63 + exp);
-	return (int32_t)(dOut + 0.5);
-}
-
-bool loadAIFFSample(UNICHAR *fileName, char *entryName, int8_t forceDownSampling)
-{
-	bool unsigned8bit;
-	char compType[4];
-	int8_t *audioDataS8;
-	uint8_t *audioDataU8, sampleRateBytes[10];
-	int16_t *audioDataS16, smp16;
-	uint16_t bitDepth, numChannels;
-	int32_t filesize, *audioDataS32, smp32;
-	uint32_t nameLen, i, offset, sampleRate, sampleLength, blockName, blockSize;
-	uint32_t commPtr, commLen, ssndPtr, ssndLen;
-	FILE *f;
-	moduleSample_t *s;
-
-	unsigned8bit = false;
-	loadedFileWasAIFF = true;
-
-	if (forceDownSampling == -1)
-	{
-		// these two *must* be fully wiped, for outputting reasons
-		memset(editor.fileNameTmpU, 0, PATH_MAX);
-		memset(editor.entryNameTmp, 0, PATH_MAX);
-		UNICHAR_STRCPY(editor.fileNameTmpU, fileName);
-		strcpy(editor.entryNameTmp, entryName);
-	}
-
-	s = &modEntry->samples[editor.currSample];
-
-	commPtr = 0; commLen = 0;
-	ssndPtr = 0; ssndLen = 0;
-
-	f = UNICHAR_FOPEN(fileName, "rb");
-	if (f == NULL)
-	{
-		displayErrorMsg("FILE I/O ERROR !");
-		return false;
-	}
-
-	fseek(f, 0, SEEK_END);
-	filesize = ftell(f);
-	if (filesize == 0)
-	{
-		displayErrorMsg("AIFF IS CORRUPT !");
-		return false;
-	}
-
-	fseek(f, 12, SEEK_SET);
-	while (!feof(f) && ftell(f) < filesize-12)
-	{
-		fread(&blockName, 4, 1, f); if (feof(f)) break;
-		fread(&blockSize, 4, 1, f); if (feof(f)) break;
-
-		blockName = SWAP32(blockName);
-		blockSize = SWAP32(blockSize);
-
-		switch (blockName)
-		{
-			case 0x434F4D4D: // "COMM"
-			{
-				commPtr = ftell(f);
-				commLen = blockSize;
-			}
-			break;
-
-			case 0x53534E44: // "SSND"
-			{
-				ssndPtr = ftell(f);
-				ssndLen = blockSize;
-			}
-			break;
-
-			default: break;
-		}
-
-		fseek(f, blockSize + (blockSize & 1), SEEK_CUR);
-	}
-
-	if (commPtr == 0 || commLen < 18 || ssndPtr == 0)
-	{
-		fclose(f);
-		displayErrorMsg("NOT A VALID AIFF!");
-		return false;
-	}
-
-	// kludge for some really strange AIFFs
-	if (ssndLen == 0)
-		ssndLen = filesize - ssndPtr;
-
-	if (ssndPtr+ssndLen > (uint32_t)filesize)
-		ssndLen = filesize - ssndPtr;
-
-	fseek(f, commPtr, SEEK_SET);
-	fread(&numChannels, 2, 1, f); numChannels = SWAP16(numChannels);
-	fseek(f, 4, SEEK_CUR);
-	fread(&bitDepth, 2, 1, f); bitDepth = SWAP16(bitDepth);
-	fread(sampleRateBytes, 1, 10, f);
-
-	fseek(f, 4 + 2 + 1, SEEK_CUR);
-
-	if (numChannels != 1 && numChannels != 2) // sample type
-	{
-		fclose(f);
-		displayErrorMsg("UNSUPPORTED AIFF!");
-		return false;
-	}
-
-	if (bitDepth != 8 && bitDepth != 16 && bitDepth != 24 && bitDepth != 32)
-	{
-		fclose(f);
-		displayErrorMsg("UNSUPPORTED AIFF!");
-		return false;
-	}
-
-	// read compression type (if present)
-	if (commLen > 18)
-	{
-		fread(&compType, 1, 4, f);
-		if (memcmp(compType, "NONE", 4))
-		{
-			fclose(f);
-			displayErrorMsg("UNSUPPORTED AIFF!");
-			return false;
-		}
-	}
-
-	sampleRate = getAIFFRate(sampleRateBytes);
-
-	// sample data chunk
-
-	fseek(f, ssndPtr, SEEK_SET);
-
-	fread(&offset, 4, 1, f);
-	if (offset > 0)
-	{
-		fclose(f);
-		displayErrorMsg("UNSUPPORTED AIFF!");
-		return false;
-	}
-
-	fseek(f, 4, SEEK_CUR);
-
-	ssndLen -= 8; // don't include offset and blockSize datas
-
-	sampleLength = ssndLen;
-	if (sampleLength == 0)
-	{
-		fclose(f);
-		displayErrorMsg("NOT A VALID AIFF!");
-		return false;
-	}
-
-	if (sampleRate > 22050)
-	{
-		if (forceDownSampling == -1)
-		{
-			showDownsampleAskDialog();
-			fclose(f);
-			return true;
-		}
-	}
-	else
-	{
-		forceDownSampling = false;
-	}
-
-	if (bitDepth == 8) // 8-BIT INTEGER SAMPLE
-	{
-		if (sampleLength > MAX_SAMPLE_LEN*4)
-			sampleLength = MAX_SAMPLE_LEN*4;
-
-		audioDataS8 = (int8_t *)malloc(sampleLength * sizeof (int8_t));
-		if (audioDataS8 == NULL)
-		{
-			fclose(f);
-			statusOutOfMemory();
-			return false;
-		}
-
-		// read sample data
-		if (fread(audioDataS8, 1, sampleLength, f) != sampleLength)
-		{
-			fclose(f);
-			free(audioDataS8);
-			displayErrorMsg("I/O ERROR !");
-			return false;
-		}
-
-		if (unsigned8bit)
-		{
-			for (i = 0; i < sampleLength; i++)
-				audioDataS8[i] ^= 0x80;
-		}
-
-		// convert from stereo to mono (if needed)
-		if (numChannels == 2)
-		{
-			sampleLength >>= 1;
-			for (i = 0; i < sampleLength-1; i++) // add right channel to left channel
-			{
-				smp16 = (audioDataS8[(i * 2) + 0] + audioDataS8[(i * 2) + 1]) >> 1;
-				audioDataS8[i] = (uint8_t)smp16;
-			}
-		}
-
-		// 2x downsampling - remove every other sample (if needed)
-		if (forceDownSampling)
-		{
-			if (config.sampleLowpass)
-				lowPassSample8Bit(audioDataS8, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
-
-			sampleLength >>= 1;
-			for (i = 1; i < sampleLength; i++)
-				audioDataS8[i] = audioDataS8[i << 1];
-		}
-
-		if (sampleLength > MAX_SAMPLE_LEN)
-			sampleLength = MAX_SAMPLE_LEN;
-
-		turnOffVoices();
-		for (i = 0; i < MAX_SAMPLE_LEN; i++)
-		{
-			if (i <= (sampleLength & 0xFFFFFFFE))
-				modEntry->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = audioDataS8[i];
-			else
-				modEntry->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample
-		}
-
-		free(audioDataS8);
-	}
-	else if (bitDepth == 16) // 16-BIT INTEGER SAMPLE
-	{
-		sampleLength >>= 1;
-		if (sampleLength > MAX_SAMPLE_LEN*4)
-			sampleLength = MAX_SAMPLE_LEN*4;
-
-		audioDataS16 = (int16_t *)malloc(sampleLength * sizeof (int16_t));
-		if (audioDataS16 == NULL)
-		{
-			fclose(f);
-			statusOutOfMemory();
-			return false;
-		}
-
-		// read sample data
-		if (fread(audioDataS16, 2, sampleLength, f) != sampleLength)
-		{
-			fclose(f);
-			free(audioDataS16);
-			displayErrorMsg("I/O ERROR !");
-			return false;
-		}
-
-		// fix endianness
-		for (i = 0; i < sampleLength; i++)
-			audioDataS16[i] = SWAP16(audioDataS16[i]);
-
-		// convert from stereo to mono (if needed)
-		if (numChannels == 2)
-		{
-			sampleLength >>= 1;
-			for (i = 0; i < sampleLength-1; i++) // add right channel to left channel
-			{
-				smp32 = (audioDataS16[(i << 1) + 0] + audioDataS16[(i << 1) + 1]) >> 1;
-				audioDataS16[i] = (int16_t)(smp32);
-			}
-		}
-
-		// 2x downsampling - remove every other sample (if needed)
-		if (forceDownSampling)
-		{
-			if (config.sampleLowpass)
-				lowPassSample16Bit(audioDataS16, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
-
-			sampleLength >>= 1;
-			for (i = 1; i < sampleLength; i++)
-				audioDataS16[i] = audioDataS16[i << 1];
-		}
-
-		if (sampleLength > MAX_SAMPLE_LEN)
-			sampleLength = MAX_SAMPLE_LEN;
-
-		normalize16bitSigned(audioDataS16, sampleLength);
-
-		turnOffVoices();
-		for (i = 0; i < MAX_SAMPLE_LEN; i++)
-		{
-			if (i <= (sampleLength & 0xFFFFFFFE))
-				modEntry->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = audioDataS16[i] >> 8;
-			else
-				modEntry->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample
-		}
-
-		free(audioDataS16);
-	}
-	else if (bitDepth == 24) // 24-BIT INTEGER SAMPLE
-	{
-		sampleLength /= 3;
-		if (sampleLength > MAX_SAMPLE_LEN*4)
-			sampleLength = MAX_SAMPLE_LEN*4;
-
-		audioDataS32 = (int32_t *)malloc(sampleLength * sizeof (int32_t));
-		if (audioDataS32 == NULL)
-		{
-			fclose(f);
-			statusOutOfMemory();
-			return false;
-		}
-
-		// read sample data
-		if (fread(&audioDataS32[sampleLength >> 2], 3, sampleLength, f) != sampleLength)
-		{
-			fclose(f);
-			free(audioDataS32);
-			displayErrorMsg("I/O ERROR !");
-			return false;
-		}
-
-		// convert to 32-bit
-		audioDataU8 = (uint8_t *)audioDataS32 + sampleLength;
-		for (i = 0; i < sampleLength; i++)
-		{
-			audioDataS32[i] = (audioDataU8[0] << 24) | (audioDataU8[1] << 16) | (audioDataU8[2] << 8);
-			audioDataU8 += 3;
-		}
-
-		// convert from stereo to mono (if needed)
-		if (numChannels == 2)
-		{
-			sampleLength >>= 1;
-			for (i = 0; i < sampleLength-1; i++) // add right channel to left channel
-			{
-				int64_t smp = ((int64_t)audioDataS32[(i << 1) + 0] + audioDataS32[(i << 1) + 1]) >> 1;
-				audioDataS32[i] = (int32_t)smp;
-			}
-		}
-
-		// 2x downsampling - remove every other sample (if needed)
-		if (forceDownSampling)
-		{
-			if (config.sampleLowpass)
-				lowPassSample32Bit(audioDataS32, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
-
-			sampleLength >>= 1;
-			for (i = 1; i < sampleLength; i++)
-				audioDataS32[i] = audioDataS32[i << 1];
-		}
-
-		if (sampleLength > MAX_SAMPLE_LEN)
-			sampleLength = MAX_SAMPLE_LEN;
-
-		normalize32bitSigned(audioDataS32, sampleLength);
-
-		turnOffVoices();
-		for (i = 0; i < MAX_SAMPLE_LEN; i++)
-		{
-			if (i <= (sampleLength & 0xFFFFFFFE))
-				modEntry->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = (int8_t)(audioDataS32[i] >> 24);
-			else
-				modEntry->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample
-		}
-
-		free(audioDataS32);
-	}
-	else if (bitDepth == 32) // 32-BIT INTEGER SAMPLE
-	{
-		sampleLength >>= 2;
-		if (sampleLength > MAX_SAMPLE_LEN*4)
-			sampleLength = MAX_SAMPLE_LEN*4;
-
-		audioDataS32 = (int32_t *)malloc(sampleLength * sizeof (int32_t));
-		if (audioDataS32 == NULL)
-		{
-			fclose(f);
-			statusOutOfMemory();
-			return false;
-		}
-
-		// read sample data
-		if (fread(audioDataS32, 4, sampleLength, f) != sampleLength)
-		{
-			fclose(f);
-			free(audioDataS32);
-			displayErrorMsg("I/O ERROR !");
-			return false;
-		}
-
-		// fix endianness
-		for (i = 0; i < sampleLength; i++)
-			audioDataS32[i] = SWAP32(audioDataS32[i]);
-
-		// convert from stereo to mono (if needed)
-		if (numChannels == 2)
-		{
-			sampleLength >>= 1;
-			for (i = 0; i < sampleLength-1; i++) // add right channel to left channel
-			{
-				int64_t smp = ((int64_t)audioDataS32[(i << 1) + 0] + audioDataS32[(i << 1) + 1]) >> 1;
-				audioDataS32[i] = (int32_t)smp;
-			}
-		}
-
-		// 2x downsampling - remove every other sample (if needed)
-		if (forceDownSampling)
-		{
-			if (config.sampleLowpass)
-				lowPassSample32Bit(audioDataS32, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
-
-			sampleLength >>= 1;
-			for (i = 1; i < sampleLength; i++)
-				audioDataS32[i] = audioDataS32[i << 1];
-		}
-
-		if (sampleLength > MAX_SAMPLE_LEN)
-			sampleLength = MAX_SAMPLE_LEN;
-
-		normalize32bitSigned(audioDataS32, sampleLength);
-
-		turnOffVoices();
-		for (i = 0; i < MAX_SAMPLE_LEN; i++)
-		{
-			if (i <= (sampleLength & 0xFFFFFFFE))
-				modEntry->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = (int8_t)(audioDataS32[i] >> 24);
-			else
-				modEntry->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample
-		}
-
-		free(audioDataS32);
-	}
-
-	// set sample length
-	if (sampleLength & 1)
-	{
-		if (++sampleLength > MAX_SAMPLE_LEN)
-			sampleLength = MAX_SAMPLE_LEN;
-	}
-
-	s->length = (uint16_t)sampleLength;
-	s->fineTune = 0;
-	s->volume = 64;
-	s->loopStart = 0;
-	s->loopLength = 2;
-
-	fclose(f);
-
-	// copy over sample name
-	nameLen = (uint32_t)strlen(entryName);
-	for (i = 0; i < 21; i++)
-		s->text[i] = (i < nameLen) ? (char)toupper(entryName[i]) : '\0';
-
-	s->text[21] = '\0';
-	s->text[22] = '\0';
-
-	// remove .aiff from end of sample name (if present)
-	nameLen = (uint32_t)strlen(s->text);
-	if (nameLen >= 5 && !_strnicmp(&s->text[nameLen-5], ".AIFF", 5))
-		memset(&s->text[nameLen-5], '\0', 5);
-
-	editor.sampleZero = false;
-	editor.samplePos = 0;
-
-	fixSampleBeep(s);
-	updateCurrSample();
-
-	updateWindowTitle(MOD_IS_MODIFIED);
-	return true;
-}
-
-bool loadSample(UNICHAR *fileName, char *entryName)
-{
-	uint32_t fileSize, ID;
-	FILE *f;
-
-	f = UNICHAR_FOPEN(fileName, "rb");
-	if (f == NULL)
-	{
-		displayErrorMsg("FILE I/O ERROR !");
-		return false;
-	}
-
-	fseek(f, 0, SEEK_END);
-	fileSize = ftell(f);
-	fseek(f, 0, SEEK_SET);
-
-	// first, check heades before we eventually load as RAW
-	if (fileSize > 16)
-	{
-		fread(&ID, 4, 1, f);
-
-		// check if it's actually a WAV sample
-		if (ID == 0x46464952) // "RIFF"
-		{
-			fseek(f, 4, SEEK_CUR);
-			fread(&ID, 4, 1, f);
-
-			if (ID == 0x45564157) // "WAVE"
-			{
-				fread(&ID, 4, 1, f);
-				if (ID == 0x20746D66) // "fmt "
-				{
-					fclose(f);
-					return loadWAVSample(fileName, entryName, -1);
-				}
-			}
-		}	
-		else if (ID == 0x4D524F46) // "FORM"
-		{
-			fseek(f, 4, SEEK_CUR);
-			fread(&ID, 4, 1, f);
-
-			// check if it's an Amiga IFF sample
-			if (ID == 0x58565338 || ID == 0x56533631) // "8SVX" (normal) and "16SV" (FT2 sample)
-			{
-				fclose(f);
-				return loadIFFSample(fileName, entryName);
-			}
-
-			// check if it's an AIFF sample
-			else if (ID == 0x46464941) // "AIFF"
-			{
-				fclose(f);
-				return loadAIFFSample(fileName, entryName, -1);
-			}
-
-			else if (ID == 0x43464941) // "AIFC" (compressed AIFF)
-			{
-				fclose(f);
-				displayErrorMsg("UNSUPPORTED AIFF!");
-				return false;
-			}
-		}
-	}
-
-	// nope, continue loading as RAW
-	fclose(f);
-
-	return loadRAWSample(fileName, entryName);
-}
-
-static void removeWavIffExt(char *text) // for sample saver
-{
-	uint32_t fileExtPos;
-	uint32_t filenameLength;
-	
-	filenameLength = (uint32_t)strlen(text);
-	if (filenameLength < 5)
-		return;
-
-	// remove .wav/.iff/from end of sample name (if present)
-	fileExtPos = filenameLength - 4;
-	if (fileExtPos > 0 && (!strncmp(&text[fileExtPos], ".wav", 4) || !strncmp(&text[fileExtPos], ".iff", 4)))
-		text[fileExtPos] = '\0';
-}
-
-bool saveSample(bool checkIfFileExist, bool giveNewFreeFilename)
-{
-	char fileName[128], tmpBuffer[64];
-	uint8_t smp;
-	uint16_t j;
-	int32_t i;
-	uint32_t sampleSize, iffSize, iffSampleSize;
-	uint32_t loopStart, loopLen, tmp32;
-	FILE *f;
-	struct stat statBuffer;
-	moduleSample_t *s;
-	wavHeader_t wavHeader;
-	samplerChunk_t samplerChunk;
-
-	if (modEntry->samples[editor.currSample].length == 0)
-	{
-		displayErrorMsg("SAMPLE IS EMPTY");
-		return false;
-	}
-
-	memset(fileName, 0, sizeof (fileName));
-
-	if (*modEntry->samples[editor.currSample].text == '\0')
-	{
-		strcpy(fileName, "untitled");
-	}
-	else
-	{
-		for (i = 0; i < 22; i++)
-		{
-			tmpBuffer[i] = (char)tolower(modEntry->samples[editor.currSample].text[i]);
-			if (tmpBuffer[i] == '\0') break;
-			sanitizeFilenameChar(&tmpBuffer[i]);
-		}
-
-		strcpy(fileName, tmpBuffer);
-	}
-	
-	removeWavIffExt(fileName);
-
-	switch (diskop.smpSaveType)
-	{
-		case DISKOP_SMP_WAV: strcat(fileName, ".wav"); break;
-		case DISKOP_SMP_IFF: strcat(fileName, ".iff"); break;
-		case DISKOP_SMP_RAW: break;
-		default: return false; // make compiler happy
-	}
-
-	if (giveNewFreeFilename)
-	{
-		if (stat(fileName, &statBuffer) == 0)
-		{
-			for (j = 1; j <= 9999; j++) // This number should satisfy all! ;)
-			{
-				memset(fileName, 0, sizeof (fileName));
-
-				if (*modEntry->samples[editor.currSample].text == '\0')
-				{
-					sprintf(fileName, "untitled-%d", j);
-				}
-				else
-				{
-					for (i = 0; i < 22; i++)
-					{
-						tmpBuffer[i] = (char)tolower(modEntry->samples[editor.currSample].text[i]);
-						if (tmpBuffer[i] == '\0') break;
-						sanitizeFilenameChar(&tmpBuffer[i]);
-					}
-
-					removeWavIffExt(tmpBuffer);
-					sprintf(fileName, "%s-%d", tmpBuffer, j);
-				}
-
-				switch (diskop.smpSaveType)
-				{
-					default: case DISKOP_SMP_WAV: strcat(fileName, ".wav"); break;
-					         case DISKOP_SMP_IFF: strcat(fileName, ".iff"); break;
-					         case DISKOP_SMP_RAW: break;
-				}
-
-				if (stat(fileName, &statBuffer) != 0)
-					break;
-			}
-		}
-	}
-
-	if (checkIfFileExist && stat(fileName, &statBuffer) == 0)
-	{
-		ui.askScreenShown = true;
-		ui.askScreenType = ASK_SAVESMP_OVERWRITE;
-		pointerSetMode(POINTER_MODE_MSG1, NO_CARRY);
-		setStatusMessage("OVERWRITE FILE ?", NO_CARRY);
-		renderAskDialog();
-		return -1;
-	}
-
-	if (ui.askScreenShown)
-	{
-		ui.answerNo = false;
-		ui.answerYes = false;
-		ui.askScreenShown = false;
-	}
-
-	f = fopen(fileName, "wb");
-	if (f == NULL)
-	{
-		displayErrorMsg("FILE I/O ERROR !");
-		return false;
-	}
-
-	sampleSize = modEntry->samples[editor.currSample].length;
-
-	switch (diskop.smpSaveType)
-	{
-		default:
-		case DISKOP_SMP_WAV:
-		{
-			s = &modEntry->samples[editor.currSample];
-
-			wavHeader.format = 0x45564157; // "WAVE"
-			wavHeader.chunkID = 0x46464952; // "RIFF"
-			wavHeader.subchunk1ID = 0x20746D66; // "fmt "
-			wavHeader.subchunk2ID = 0x61746164; // "data"
-			wavHeader.subchunk1Size = 16;
-			wavHeader.subchunk2Size = sampleSize;
-			wavHeader.chunkSize = wavHeader.subchunk2Size + 36;
-			wavHeader.audioFormat = 1;
-			wavHeader.numChannels = 1;
-			wavHeader.bitsPerSample = 8;
-			wavHeader.sampleRate = 16574;
-			wavHeader.byteRate = wavHeader.sampleRate * wavHeader.numChannels * wavHeader.bitsPerSample / 8;
-			wavHeader.blockAlign = wavHeader.numChannels * wavHeader.bitsPerSample / 8;
-
-			if (s->loopLength > 2)
-			{
-				wavHeader.chunkSize += sizeof (samplerChunk_t);
-				memset(&samplerChunk, 0, sizeof (samplerChunk_t));
-				samplerChunk.chunkID = 0x6C706D73; // "smpl"
-				samplerChunk.chunkSize = 60;
-				samplerChunk.dwSamplePeriod = 1000000000 / 16574;
-				samplerChunk.dwMIDIUnityNote = 60; // 60 = C-4
-				samplerChunk.cSampleLoops = 1;
-				samplerChunk.loop.dwStart = (uint32_t)s->loopStart;
-				samplerChunk.loop.dwEnd = (uint32_t)((s->loopStart + s->loopLength) - 1);
-			}
-
-			fwrite(&wavHeader, sizeof (wavHeader_t), 1, f);
-
-			for (i = 0; i < (int32_t)sampleSize; i++)
-			{
-				smp = modEntry->sampleData[modEntry->samples[editor.currSample].offset + i] + 128;
-				fputc(smp, f);
-			}
-
-			if (sampleSize & 1)
-				fputc(0, f); // pad align byte
-
-			if (s->loopLength > 2)
-				fwrite(&samplerChunk, sizeof (samplerChunk), 1, f);
-		}
-		break;
-
-		case DISKOP_SMP_IFF:
-		{
-			// dwords are big-endian in IFF
-			loopStart = modEntry->samples[editor.currSample].loopStart  & 0xFFFFFFFE;
-			loopLen = modEntry->samples[editor.currSample].loopLength & 0xFFFFFFFE;
-
-			loopStart = SWAP32(loopStart);
-			loopLen = SWAP32(loopLen);
-			iffSize = SWAP32(sampleSize + 100);
-			iffSampleSize = SWAP32(sampleSize);
-
-			fputc(0x46, f);fputc(0x4F, f);fputc(0x52, f);fputc(0x4D, f); // "FORM"
-			fwrite(&iffSize, 4, 1, f);
-
-			fputc(0x38, f);fputc(0x53, f);fputc(0x56, f);fputc(0x58, f); // "8SVX"
-			fputc(0x56, f);fputc(0x48, f);fputc(0x44, f);fputc(0x52, f); // "VHDR"
-			fputc(0x00, f);fputc(0x00, f);fputc(0x00, f);fputc(0x14, f); // 0x00000014
-
-			if (modEntry->samples[editor.currSample].loopLength > 2)
-			{
-				fwrite(&loopStart, 4, 1, f);
-				fwrite(&loopLen, 4, 1, f);
-			}
-			else
-			{
-				fwrite(&iffSampleSize, 4, 1, f);
-				fputc(0x00, f);fputc(0x00, f);fputc(0x00, f);fputc(0x00, f); // 0x00000000
-			}
-
-			fputc(0x00, f);fputc(0x00, f);fputc(0x00, f);fputc(0x00, f); // 0x00000000
-
-			fputc(0x41, f);fputc(0x56, f); // 16726 (rate)
-			fputc(0x01, f);fputc(0x00, f); // numSamples and compression
-
-			tmp32 = modEntry->samples[editor.currSample].volume * 1024;
-			tmp32 = SWAP32(tmp32);
-			fwrite(&tmp32, 4, 1, f);
-
-			fputc(0x4E, f);fputc(0x41, f);fputc(0x4D, f);fputc(0x45, f); // "NAME"
-			fputc(0x00, f);fputc(0x00, f);fputc(0x00, f);fputc(0x16, f); // 0x00000016
-
-			for (i = 0; i < 22; i++)
-				fputc(tolower(modEntry->samples[editor.currSample].text[i]), f);
-
-			fputc(0x41, f);fputc(0x4E, f);fputc(0x4E, f);fputc(0x4F, f); // "ANNO"
-			fputc(0x00, f);fputc(0x00, f);fputc(0x00, f);fputc(0x15, f); // 0x00000012
-			fprintf(f, "ProTracker 2 clone");
-			fputc(0x00, f); // even padding
-
-			fputc(0x42, f);fputc(0x4F, f);fputc(0x44, f);fputc(0x59, f);    // "BODY"
-			fwrite(&iffSampleSize, 4, 1, f);
-			fwrite(modEntry->sampleData + modEntry->samples[editor.currSample].offset, 1, sampleSize, f);
-
-			// shouldn't happen, but in just case: safety even padding
-			if (sampleSize & 1)
-				fputc(0x00, f);
-		}
-		break;
-
-		case DISKOP_SMP_RAW:
-			fwrite(modEntry->sampleData + modEntry->samples[editor.currSample].offset, 1, sampleSize, f);
-		break;
-	}
-
-	fclose(f);
-
-	diskop.cached = false;
-	if (ui.diskOpScreenShown)
-		ui.updateDiskOpFileList = true;
-
-	displayMsg("SAMPLE SAVED !");
-	setMsgPointer();
-
-	return true;
-}
--- a/src/pt2_sampleloader.h
+++ /dev/null
@@ -1,9 +1,0 @@
-#pragma once
-
-#include <stdint.h>
-#include <stdbool.h>
-#include "pt2_unicode.h"
-
-void extLoadWAVOrAIFFSampleCallback(bool downSample);
-bool saveSample(bool checkIfFileExist, bool giveNewFreeFilename);
-bool loadSample(UNICHAR *fileName, char *entryName);
--- a/src/pt2_sampler.c
+++ b/src/pt2_sampler.c
@@ -19,6 +19,8 @@
 #include "pt2_scopes.h"
 #include "pt2_sampler.h"
 #include "pt2_structs.h"
+#include "pt2_config.h"
+#include "pt2_bmp.h"
 
 #define CENTER_LINE_COLOR 0x303030
 #define MARK_COLOR_1 0x666666 /* inverted background */
@@ -49,8 +51,14 @@
 
 void upSample(void)
 {
-	moduleSample_t *s = &modEntry->samples[editor.currSample];
+	if (editor.sampleZero)
+	{
+		statusNotSampleZero();
+		return;
+	}
 
+	moduleSample_t *s = &song->samples[editor.currSample];
+
 	uint32_t newLength = (s->length >> 1) & 0xFFFE;
 	if (newLength < 2)
 		return;
@@ -58,7 +66,7 @@
 	turnOffVoices();
 
 	// upsample
-	int8_t *ptr8 = &modEntry->sampleData[s->offset];
+	int8_t *ptr8 = &song->sampleData[s->offset];
 	for (uint32_t i = 0; i < newLength; i++)
 		ptr8[i] = ptr8[i << 1];
 
@@ -85,8 +93,14 @@
 
 void downSample(void)
 {
-	moduleSample_t *s = &modEntry->samples[editor.currSample];
+	if (editor.sampleZero)
+	{
+		statusNotSampleZero();
+		return;
+	}
 
+	moduleSample_t *s = &song->samples[editor.currSample];
+
 	uint32_t newLength = s->length << 1;
 	if (newLength > MAX_SAMPLE_LEN)
 		newLength = MAX_SAMPLE_LEN;
@@ -95,7 +109,7 @@
 
 	// downsample
 
-	int8_t *ptr8 = &modEntry->sampleData[s->offset];
+	int8_t *ptr8 = &song->sampleData[s->offset];
 	int8_t *ptr8_2 = ptr8 - 1;
 	for (int32_t i = s->length-1; i > 0; i--)
 	{
@@ -153,8 +167,8 @@
 {
 	if (s->length >= 2 && s->loopStart+s->loopLength <= 2)
 	{
-		modEntry->sampleData[s->offset+0] = 0;
-		modEntry->sampleData[s->offset+1] = 0;
+		song->sampleData[s->offset+0] = 0;
+		song->sampleData[s->offset+1] = 0;
 	}
 }
 
@@ -165,7 +179,7 @@
 	assert(editor.currSample >= 0 && editor.currSample <= 30);
 	if (editor.currSample >= 0 && editor.currSample <= 30)
 	{
-		s = &modEntry->samples[editor.currSample];
+		s = &song->samples[editor.currSample];
 		if (editor.samplePos > s->length)
 			editor.samplePos = s->length;
 
@@ -181,8 +195,8 @@
 	assert(editor.currSample >= 0 && editor.currSample <= 30);
 	if (editor.currSample >= 0 && editor.currSample <= 30)
 	{
-		s = &modEntry->samples[editor.currSample];
-		memcpy(editor.tempSample, &modEntry->sampleData[s->offset], s->length);
+		s = &song->samples[editor.currSample];
+		memcpy(editor.tempSample, &song->sampleData[s->offset], s->length);
 	}
 }
 
@@ -376,7 +390,7 @@
 	uint32_t *dstPtr, pixel;
 	moduleSample_t *s;
 
-	s = &modEntry->samples[editor.currSample];
+	s = &song->samples[editor.currSample];
 
 	// clear sample data background
 
@@ -421,7 +435,7 @@
 			oldMin = y1;
 			oldMax = y1;
 
-			smpPtr = &modEntry->sampleData[s->offset];
+			smpPtr = &song->sampleData[s->offset];
 			for (x = 0; x < SAMPLE_AREA_WIDTH; x++)
 			{
 				smpIdx = scr2SmpPos(x);
@@ -518,10 +532,10 @@
 		sampler.samOffset = 0;
 		updateSamOffset();
 
-		s = &modEntry->samples[editor.currSample];
+		s = &song->samples[editor.currSample];
 		if (s->length > 0)
 		{
-			sampler.samStart = &modEntry->sampleData[s->offset];
+			sampler.samStart = &song->sampleData[s->offset];
 			sampler.samDisplay = s->length;
 			sampler.samLength = s->length;
 		}
@@ -554,6 +568,12 @@
 
 	assert(editor.currSample >= 0 && editor.currSample <= 30);
 
+	if (editor.sampleZero)
+	{
+		statusNotSampleZero();
+		return;
+	}
+
 	if (cutOff == 0)
 	{
 		displayErrorMsg("CUTOFF CAN'T BE 0");
@@ -560,10 +580,10 @@
 		return;
 	}
 
-	s = &modEntry->samples[editor.currSample];
+	s = &song->samples[editor.currSample];
 	if (s->length == 0)
 	{
-		displayErrorMsg("SAMPLE IS EMPTY");
+		statusSampleIsEmpty();
 		return;
 	}
 
@@ -609,7 +629,7 @@
 
 	// copy over sample data to double buffer
 	for (i = 0; i < s->length; i++)
-		dSampleData[i] = modEntry->sampleData[s->offset+i];
+		dSampleData[i] = song->sampleData[s->offset+i];
 
 	clearRCFilterState(&filterHi);
 	if (to <= s->length)
@@ -625,7 +645,7 @@
 	{
 		smp32 = (int32_t)dSampleData[i];
 		CLAMP8(smp32);
-		modEntry->sampleData[s->offset + i] = (int8_t)smp32;
+		song->sampleData[s->offset + i] = (int8_t)smp32;
 	}
 
 	free(dSampleData);
@@ -642,6 +662,12 @@
 	moduleSample_t *s;
 	rcFilter_t filterLo;
 
+	if (editor.sampleZero)
+	{
+		statusNotSampleZero();
+		return;
+	}
+
 	assert(editor.currSample >= 0 && editor.currSample <= 30);
 
 	if (cutOff == 0)
@@ -650,10 +676,10 @@
 		return;
 	}
 
-	s = &modEntry->samples[editor.currSample];
+	s = &song->samples[editor.currSample];
 	if (s->length == 0)
 	{
-		displayErrorMsg("SAMPLE IS EMPTY");
+		statusSampleIsEmpty();
 		return;
 	}
 
@@ -699,7 +725,7 @@
 
 	// copy over sample data to double buffer
 	for (i = 0; i < s->length; i++)
-		dSampleData[i] = modEntry->sampleData[s->offset+i];
+		dSampleData[i] = song->sampleData[s->offset+i];
 
 	clearRCFilterState(&filterLo);
 	if (to <= s->length)
@@ -715,7 +741,7 @@
 	{
 		smp32 = (int32_t)dSampleData[i];
 		CLAMP8(smp32);
-		modEntry->sampleData[s->offset + i] = (int8_t)smp32;
+		song->sampleData[s->offset + i] = (int8_t)smp32;
 	}
 
 	free(dSampleData);
@@ -727,24 +753,30 @@
 
 void redoSampleData(int8_t sample)
 {
+	if (editor.sampleZero)
+	{
+		statusNotSampleZero();
+		return;
+	}
+
 	moduleSample_t *s;
 
 	assert(sample >= 0 && sample <= 30);
 
-	s = &modEntry->samples[sample];
+	s = &song->samples[sample];
 
 	turnOffVoices();
 
 	if (editor.smpRedoBuffer[sample] != NULL && editor.smpRedoLengths[sample] > 0)
 	{
-		memcpy(&modEntry->sampleData[s->offset], editor.smpRedoBuffer[sample], editor.smpRedoLengths[sample]);
+		memcpy(&song->sampleData[s->offset], editor.smpRedoBuffer[sample], editor.smpRedoLengths[sample]);
 
 		if (editor.smpRedoLengths[sample] < MAX_SAMPLE_LEN)
-			memset(&modEntry->sampleData[s->offset + editor.smpRedoLengths[sample]], 0, MAX_SAMPLE_LEN - editor.smpRedoLengths[sample]);
+			memset(&song->sampleData[s->offset + editor.smpRedoLengths[sample]], 0, MAX_SAMPLE_LEN - editor.smpRedoLengths[sample]);
 	}
 	else
 	{
-		memset(&modEntry->sampleData[s->offset], 0, MAX_SAMPLE_LEN);
+		memset(&song->sampleData[s->offset], 0, MAX_SAMPLE_LEN);
 	}
 
 	s->fineTune = editor.smpRedoFinetunes[sample];
@@ -774,7 +806,7 @@
 
 	assert(sample >= 0 && sample <= 30);
 
-	s = &modEntry->samples[sample];
+	s = &song->samples[sample];
 
 	if (editor.smpRedoBuffer[sample] != NULL)
 	{
@@ -792,7 +824,7 @@
 	{
 		editor.smpRedoBuffer[sample] = (int8_t *)malloc(s->length);
 		if (editor.smpRedoBuffer[sample] != NULL)
-			memcpy(editor.smpRedoBuffer[sample], &modEntry->sampleData[s->offset], s->length);
+			memcpy(editor.smpRedoBuffer[sample], &song->sampleData[s->offset], s->length);
 	}
 }
 
@@ -821,7 +853,7 @@
 		sampler.blankSample = NULL;
 	}
 
-	for (uint8_t i = 0; i < MOD_SAMPLES; i++)
+	for (int32_t i = 0; i < MOD_SAMPLES; i++)
 	{
 		if (editor.smpRedoBuffer[i] != NULL)
 		{
@@ -837,16 +869,22 @@
 	int32_t smp32, i, from, to, offset;
 	moduleSample_t *s;
 
+	if (editor.sampleZero)
+	{
+		statusNotSampleZero();
+		return;
+	}
+
 	assert(editor.currSample >= 0 && editor.currSample <= 30);
 
-	s = &modEntry->samples[editor.currSample];
+	s = &song->samples[editor.currSample];
 	if (s->length == 0)
 	{
-		displayErrorMsg("SAMPLE IS EMPTY");
+		statusSampleIsEmpty();
 		return;
 	}
 
-	smpDat = &modEntry->sampleData[s->offset];
+	smpDat = &song->sampleData[s->offset];
 
 	from = 0;
 	to = s->length;
@@ -921,6 +959,12 @@
 	sampleMixer_t mixCh[4], *v;
 	moduleSample_t *s;
 
+	if (editor.sampleZero)
+	{
+		statusNotSampleZero();
+		return;
+	}
+
 	assert(editor.currSample >= 0 && editor.currSample <= 30);
 	assert(editor.tuningNote <= 35);
 
@@ -930,9 +974,9 @@
 		return;
 	}
 
-	if (modEntry->samples[editor.currSample].length == 0)
+	if (song->samples[editor.currSample].length == 0)
 	{
-		displayErrorMsg("SAMPLE IS EMPTY");
+		statusSampleIsEmpty();
 		return;
 	}
 
@@ -981,11 +1025,11 @@
 
 	// setup some variables
 
-	smpLoopStart = modEntry->samples[editor.currSample].loopStart;
-	smpLoopLength = modEntry->samples[editor.currSample].loopLength;
+	smpLoopStart = song->samples[editor.currSample].loopStart;
+	smpLoopLength = song->samples[editor.currSample].loopLength;
 	smpLoopFlag = (smpLoopStart + smpLoopLength) > 2;
-	smpEnd = smpLoopFlag ? (smpLoopStart + smpLoopLength) : modEntry->samples[editor.currSample].length;
-	smpData = &modEntry->sampleData[modEntry->samples[editor.currSample].offset];
+	smpEnd = smpLoopFlag ? (smpLoopStart + smpLoopLength) : song->samples[editor.currSample].length;
+	smpData = &song->sampleData[song->samples[editor.currSample].offset];
 
 	if (editor.newOldFlag == 0)
 	{
@@ -993,7 +1037,7 @@
 
 		for (i = 0; i < MOD_SAMPLES; i++)
 		{
-			if (modEntry->samples[i].length == 0)
+			if (song->samples[i].length == 0)
 				break;
 		}
 
@@ -1003,11 +1047,11 @@
 			return;
 		}
 
-		smpFinetune = modEntry->samples[editor.currSample].fineTune;
-		smpVolume = modEntry->samples[editor.currSample].volume;
-		memcpy(smpText, modEntry->samples[editor.currSample].text, sizeof (smpText));
+		smpFinetune = song->samples[editor.currSample].fineTune;
+		smpVolume = song->samples[editor.currSample].volume;
+		memcpy(smpText, song->samples[editor.currSample].text, sizeof (smpText));
 
-		s = &modEntry->samples[i];
+		s = &song->samples[i];
 		s->fineTune = smpFinetune;
 		s->volume = smpVolume;
 
@@ -1017,7 +1061,7 @@
 	else
 	{
 		// overwrite current sample
-		s = &modEntry->samples[editor.currSample];
+		s = &song->samples[editor.currSample];
 	}
 
 	mixData = (int32_t *)calloc(MAX_SAMPLE_LEN, sizeof (int32_t));
@@ -1121,10 +1165,10 @@
 
 	// normalize gain and quantize to 8-bit
 	for (i = 0; i < s->length; i++)
-		modEntry->sampleData[s->offset + i] = (int8_t)(mixData[i] >> 24);
+		song->sampleData[s->offset + i] = (int8_t)(mixData[i] >> 24);
 
 	if (s->length < MAX_SAMPLE_LEN)
-		memset(&modEntry->sampleData[s->offset + s->length], 0, MAX_SAMPLE_LEN - s->length);
+		memset(&song->sampleData[s->offset + s->length], 0, MAX_SAMPLE_LEN - s->length);
 
 	// we're done
 
@@ -1146,13 +1190,19 @@
 	uint32_t posFrac, delta;
 	moduleSample_t *s;
 
+	if (editor.sampleZero)
+	{
+		statusNotSampleZero();
+		return;
+	}
+
 	assert(editor.currSample >= 0 && editor.currSample <= 30);
 	assert(editor.tuningNote <= 35 && editor.resampleNote <= 35);
 
-	s = &modEntry->samples[editor.currSample];
+	s = &song->samples[editor.currSample];
 	if (s->length == 0)
 	{
-		displayErrorMsg("SAMPLE IS EMPTY");
+		statusSampleIsEmpty();
 		return;
 	}
 
@@ -1159,7 +1209,7 @@
 	// setup resampling variables
 	readPos = 0;
 	writePos = 0;
-	writeData = &modEntry->sampleData[s->offset];
+	writeData = &song->sampleData[s->offset];
 	refPeriod = periodTable[editor.tuningNote];
 	newPeriod = periodTable[(37 * (s->fineTune & 0xF)) + editor.resampleNote];
 	readLength = s->length;
@@ -1297,9 +1347,9 @@
 		return;
 	}
 
-	s1 = &modEntry->samples[--smpFrom1];
-	s2 = &modEntry->samples[--smpFrom2];
-	s3 = &modEntry->samples[--smpTo];
+	s1 = &song->samples[--smpFrom1];
+	s2 = &song->samples[--smpFrom2];
+	s3 = &song->samples[--smpTo];
 
 	if (s1->length == 0 || s2->length == 0)
 	{
@@ -1309,14 +1359,14 @@
 
 	if (s1->length > s2->length)
 	{
-		fromPtr1 = &modEntry->sampleData[s1->offset];
-		fromPtr2 = &modEntry->sampleData[s2->offset];
+		fromPtr1 = &song->sampleData[s1->offset];
+		fromPtr2 = &song->sampleData[s2->offset];
 		mixLength = s1->length;
 	}
 	else
 	{
-		fromPtr1 = &modEntry->sampleData[s2->offset];
-		fromPtr2 = &modEntry->sampleData[s1->offset];
+		fromPtr1 = &song->sampleData[s2->offset];
+		fromPtr2 = &song->sampleData[s1->offset];
 		mixLength = s2->length;
 	}
 
@@ -1339,9 +1389,9 @@
 		mixPtr[i] = (int8_t)tmp16;
 	}
 
-	memcpy(&modEntry->sampleData[s3->offset], mixPtr, mixLength);
+	memcpy(&song->sampleData[s3->offset], mixPtr, mixLength);
 	if (mixLength < MAX_SAMPLE_LEN)
-		memset(&modEntry->sampleData[s3->offset + mixLength], 0, MAX_SAMPLE_LEN - mixLength);
+		memset(&song->sampleData[s3->offset + mixLength], 0, MAX_SAMPLE_LEN - mixLength);
 
 	free(mixPtr);
 
@@ -1369,11 +1419,11 @@
 
 	assert(sample >= 0 && sample <= 30);
 
-	s = &modEntry->samples[sample];
+	s = &song->samples[sample];
 	if (s->length == 0)
 		return; // don't display warning/show warning pointer, it is done elsewhere
 
-	smpDat = &modEntry->sampleData[s->offset];
+	smpDat = &song->sampleData[s->offset];
 
 	from = 0;
 	to = s->length;
@@ -1425,11 +1475,11 @@
 
 	assert(sample >= 0 && sample <= 30);
 
-	s = &modEntry->samples[sample];
+	s = &song->samples[sample];
 	if (s->length == 0)
 		return; // don't display warning/show warning pointer, it is done elsewhere
 
-	smpDat = &modEntry->sampleData[s->offset];
+	smpDat = &song->sampleData[s->offset];
 
 	from = 1;
 	to = s->length;
@@ -1482,7 +1532,7 @@
 		if (editor.tuningNote > 35)
 			editor.tuningNote = 35;
 
-		modEntry->channels[editor.tuningChan].n_volume = 64; // we need this for the scopes
+		song->channels[editor.tuningChan].n_volume = 64; // we need this for the scopes
 
 		paulaSetPeriod(editor.tuningChan, periodTable[editor.tuningNote]);
 		paulaSetVolume(editor.tuningChan, 64);
@@ -1506,7 +1556,7 @@
 
 	assert(editor.currSample >= 0 && editor.currSample <= 30);
 
-	s = &modEntry->samples[editor.currSample];
+	s = &song->samples[editor.currSample];
 	if (s->length == 0)
 	{
 		invertRange();
@@ -1540,7 +1590,7 @@
 
 	assert(editor.currSample >= 0 && editor.currSample <= 30);
 
-	s = &modEntry->samples[editor.currSample];
+	s = &song->samples[editor.currSample];
 	if (s->length == 0)
 	{
 		invertRange();
@@ -1578,7 +1628,7 @@
 
 	assert(editor.currSample >= 0 && editor.currSample <= 30);
 
-	s = &modEntry->samples[editor.currSample];
+	s = &song->samples[editor.currSample];
 	if (s->length == 0)
 	{
 		invertRange();
@@ -1609,6 +1659,12 @@
 {
 	moduleSample_t *s;
 
+	if (editor.sampleZero)
+	{
+		statusNotSampleZero();
+		return;
+	}
+
 	assert(editor.currSample >= 0 && editor.currSample <= 30);
 
 	if (editor.markStartOfs == -1)
@@ -1623,10 +1679,10 @@
 		return;
 	}
 
-	s = &modEntry->samples[editor.currSample];
+	s = &song->samples[editor.currSample];
 	if (s->length == 0)
 	{
-		displayErrorMsg("SAMPLE IS EMPTY");
+		statusSampleIsEmpty();
 		return;
 	}
 
@@ -1638,7 +1694,7 @@
 		return;
 	}
 
-	memcpy(sampler.copyBuf, &modEntry->sampleData[s->offset+editor.markStartOfs], sampler.copyBufSize);
+	memcpy(sampler.copyBuf, &song->sampleData[s->offset+editor.markStartOfs], sampler.copyBufSize);
 }
 
 void samplerSamDelete(uint8_t cut)
@@ -1647,6 +1703,12 @@
 	int32_t val32, sampleLength, copyLength, markEnd, markStart;
 	moduleSample_t *s;
 
+	if (editor.sampleZero)
+	{
+		statusNotSampleZero();
+		return;
+	}
+
 	assert(editor.currSample >= 0 && editor.currSample <= 30);
 
 	if (editor.markStartOfs == -1)
@@ -1664,12 +1726,12 @@
 	if (cut)
 		samplerSamCopy();
 
-	s = &modEntry->samples[editor.currSample];
+	s = &song->samples[editor.currSample];
 
 	sampleLength = s->length;
 	if (sampleLength == 0)
 	{
-		displayErrorMsg("SAMPLE IS EMPTY");
+		statusSampleIsEmpty();
 		return;
 	}
 
@@ -1678,7 +1740,7 @@
 	// if whole sample is marked, wipe it
 	if (editor.markEndOfs-editor.markStartOfs >= sampleLength)
 	{
-		memset(&modEntry->sampleData[s->offset], 0, MAX_SAMPLE_LEN);
+		memset(&song->sampleData[s->offset], 0, MAX_SAMPLE_LEN);
 
 		invertRange();
 		editor.markStartOfs = -1;
@@ -1718,17 +1780,17 @@
 	}
 
 	// copy start part
-	memcpy(tmpBuf, &modEntry->sampleData[s->offset], editor.markStartOfs);
+	memcpy(tmpBuf, &song->sampleData[s->offset], editor.markStartOfs);
 
 	// copy end part
 	if (sampleLength-markEnd > 0)
-		memcpy(&tmpBuf[editor.markStartOfs], &modEntry->sampleData[s->offset+markEnd], sampleLength - markEnd);
+		memcpy(&tmpBuf[editor.markStartOfs], &song->sampleData[s->offset+markEnd], sampleLength - markEnd);
 
 	// nuke sample data and copy over the result
-	memcpy(&modEntry->sampleData[s->offset], tmpBuf, copyLength);
+	memcpy(&song->sampleData[s->offset], tmpBuf, copyLength);
 
 	if (copyLength < MAX_SAMPLE_LEN)
-		memset(&modEntry->sampleData[s->offset+copyLength], 0, MAX_SAMPLE_LEN - copyLength);
+		memset(&song->sampleData[s->offset+copyLength], 0, MAX_SAMPLE_LEN - copyLength);
 
 	free(tmpBuf);
 
@@ -1833,6 +1895,12 @@
 	uint32_t readPos;
 	moduleSample_t *s;
 
+	if (editor.sampleZero)
+	{
+		statusNotSampleZero();
+		return;
+	}
+
 	assert(editor.currSample >= 0 && editor.currSample <= 30);
 
 	if (sampler.copyBuf == NULL || sampler.copyBufSize == 0)
@@ -1841,7 +1909,7 @@
 		return;
 	}
 
-	s = &modEntry->samples[editor.currSample];
+	s = &song->samples[editor.currSample];
 	if (s->length > 0 && editor.markStartOfs == -1)
 	{
 		displayErrorMsg("SET CURSOR POS");
@@ -1872,7 +1940,7 @@
 	// copy start part
 	if (markStart > 0)
 	{
-		memcpy(&tmpBuf[readPos], &modEntry->sampleData[s->offset], markStart);
+		memcpy(&tmpBuf[readPos], &song->sampleData[s->offset], markStart);
 		readPos += markStart;
 	}
 
@@ -1885,7 +1953,7 @@
 		readPos += sampler.copyBufSize;
 
 		if (s->length-markStart > 0)
-			memcpy(&tmpBuf[readPos], &modEntry->sampleData[s->offset+markStart], s->length - markStart);
+			memcpy(&tmpBuf[readPos], &song->sampleData[s->offset+markStart], s->length - markStart);
 	}
 
 	int32_t newLength = (s->length + sampler.copyBufSize) & 0xFFFFFFFE;
@@ -1940,9 +2008,9 @@
 		}
 	}
 
-	memcpy(&modEntry->sampleData[s->offset], tmpBuf, s->length);
+	memcpy(&song->sampleData[s->offset], tmpBuf, s->length);
 	if (s->length < MAX_SAMPLE_LEN)
-		memset(&modEntry->sampleData[s->offset+s->length], 0, MAX_SAMPLE_LEN - s->length);
+		memset(&song->sampleData[s->offset+s->length], 0, MAX_SAMPLE_LEN - s->length);
 
 	free(tmpBuf);
 
@@ -1973,8 +2041,8 @@
 	assert(chn < AMIGA_VOICES);
 	assert(editor.currPlayNote <= 35);
 
-	s = &modEntry->samples[editor.currSample];
-	ch = &modEntry->channels[chn];
+	s = &song->samples[editor.currSample];
+	ch = &song->channels[chn];
 
 	ch->n_samplenum = editor.currSample;
 	ch->n_volume = s->volume;
@@ -1982,16 +2050,16 @@
 	
 	if (playWaveformFlag)
 	{
-		ch->n_start = &modEntry->sampleData[s->offset];
+		ch->n_start = &song->sampleData[s->offset];
 		ch->n_length = (s->loopStart > 0) ? (uint32_t)(s->loopStart + s->loopLength) / 2 : s->length / 2;
-		ch->n_loopstart = &modEntry->sampleData[s->offset + s->loopStart];
+		ch->n_loopstart = &song->sampleData[s->offset + s->loopStart];
 		ch->n_replen = s->loopLength / 2;
 	}
 	else
 	{
-		ch->n_start = &modEntry->sampleData[s->offset + startOffset];
+		ch->n_start = &song->sampleData[s->offset + startOffset];
 		ch->n_length = (uint16_t)((uint32_t)(endOffset - startOffset) >> 1);
-		ch->n_loopstart = &modEntry->sampleData[s->offset];
+		ch->n_loopstart = &song->sampleData[s->offset];
 		ch->n_replen = 1;
 	}
 
@@ -2066,7 +2134,7 @@
 
 	assert(editor.currSample >= 0 && editor.currSample <= 30);
 
-	s = &modEntry->samples[editor.currSample];
+	s = &song->samples[editor.currSample];
 	if (s->loopStart+s->loopLength > 2)
 	{
 		if (sampler.samDisplay > 0)
@@ -2119,7 +2187,7 @@
 {
 	int32_t tmpDisplay, tmpOffset;
 
-	if (modEntry->samples[editor.currSample].length == 0 || sampler.samDisplay <= 2)
+	if (song->samples[editor.currSample].length == 0 || sampler.samDisplay <= 2)
 		return;
 
 	if (step < 1)
@@ -2151,7 +2219,7 @@
 {
 	int32_t tmpDisplay, tmpOffset;
 
-	if (modEntry->samples[editor.currSample].length == 0 || sampler.samDisplay == sampler.samLength)
+	if (song->samples[editor.currSample].length == 0 || sampler.samDisplay == sampler.samLength)
 		return;
 
 	if (step < 1)
@@ -2205,7 +2273,7 @@
 
 	assert(editor.currSample >= 0 && editor.currSample <= 30);
 
-	s = &modEntry->samples[editor.currSample];
+	s = &song->samples[editor.currSample];
 	if (s->length == 0)
 	{
 		invertRange();
@@ -2226,10 +2294,10 @@
 
 	assert(editor.currSample >= 0 && editor.currSample <= 30);
 
-	s = &modEntry->samples[editor.currSample];
+	s = &song->samples[editor.currSample];
 	if (s->length == 0)
 	{
-		displayErrorMsg("SAMPLE IS EMPTY");
+		statusSampleIsEmpty();
 		return;
 	}
 
@@ -2403,8 +2471,14 @@
 	int32_t mx, my, tmp32, p, vl, tvl, r, rl, rvl, start, end;
 	moduleSample_t *s;
 
+	if (editor.sampleZero)
+	{
+		statusNotSampleZero();
+		return;
+	}
+
 	assert(editor.currSample >= 0 && editor.currSample <= 30);
-	s = &modEntry->samples[editor.currSample];
+	s = &song->samples[editor.currSample];
 
 	if (s->length == 0)
 	{
@@ -2461,7 +2535,7 @@
 		vl = tmp32;
 	}
 
-	ptr8 = &modEntry->sampleData[s->offset];
+	ptr8 = &song->sampleData[s->offset];
 
 	start = p;
 	if (start < 0)
@@ -2515,7 +2589,7 @@
 
 	if (!mouseButtonHeld)
 	{
-		if (mouse.y < 142)
+		if (!editor.sampleZero && mouse.y < 142)
 		{
 			if (mouse.x >= sampler.loopStartPos && mouse.x <= sampler.loopStartPos+3)
 			{
@@ -2538,7 +2612,7 @@
 
 	mouseX = CLAMP(mouse.x, 0, SCREEN_W+8); // allow some extra pixels outside of the screen
 
-	s = &modEntry->samples[editor.currSample];
+	s = &song->samples[editor.currSample];
 
 	if (ui.leftLoopPinMoving)
 	{
@@ -2582,7 +2656,7 @@
 		{
 			sampler.lastMouseX = mouseX;
 
-			s = &modEntry->samples[editor.currSample];
+			s = &song->samples[editor.currSample];
 
 			tmpPos = (scr2SmpPos(mouseX - 4) - s->loopStart) & 0xFFFFFFFE;
 			tmpPos = CLAMP(tmpPos, 2, MAX_SAMPLE_LEN);
@@ -2602,7 +2676,7 @@
 
 	if (!mouseButtonHeld)
 	{
-		if (mouseX < 3 || mouseX >= SCREEN_W)
+		if (mouseX < 0 || mouseX >= SCREEN_W)
 			return;
 
 		ui.sampleMarkingPos = (int16_t)mouseX;
@@ -2693,9 +2767,15 @@
 {
 	moduleSample_t *s;
 
+	if (editor.sampleZero)
+	{
+		statusNotSampleZero();
+		return;
+	}
+
 	assert(editor.currSample >= 0 && editor.currSample <= 30);
 
-	s = &modEntry->samples[editor.currSample];
+	s = &song->samples[editor.currSample];
 	if (s->length < 2)
 		return;
 
@@ -2792,7 +2872,7 @@
 
 	for (i = 0; i < AMIGA_VOICES; i++)
 	{
-		if (modEntry->channels[i].n_samplenum == editor.currSample && !editor.muted[i])
+		if (song->channels[i].n_samplenum == editor.currSample && !editor.muted[i])
 		{
 			pos = getSampleReadPos(i, editor.currSample);
 			if (pos >= 0)
--- a/src/pt2_scopes.c
+++ b/src/pt2_scopes.c
@@ -17,6 +17,7 @@
 #include "pt2_palette.h"
 #include "pt2_tables.h"
 #include "pt2_structs.h"
+#include "pt2_config.h"
 
 // this uses code that is not entirely thread safe, but I have never had any issues so far...
 
@@ -43,13 +44,13 @@
 
 	if (scopeExt[ch].active && pos >= 2)
 	{
-		s = &modEntry->samples[smpNum];
+		s = &song->samples[smpNum];
 
 		/* Get real sampling position regardless of where the scope data points to
 		** sc->data changes during loop, offset and so on, so this has to be done
 		** (sadly, because it's really hackish).
 		*/
-		pos = (int32_t)(&data[pos] - &modEntry->sampleData[s->offset]);
+		pos = (int32_t)(&data[pos] - &song->sampleData[s->offset]);
 		if (pos >= s->length)
 			return -1;
 
@@ -149,7 +150,7 @@
 		if (samplesToScan > 512) // don't waste cycles on reading a ton of samples
 			samplesToScan = 512;
 
-		volume = modEntry->channels[i].n_volume;
+		volume = song->channels[i].n_volume;
 
 		if (se->active && tmpScope.data != NULL && volume != 0 && tmpScope.length > 0)
 		{
@@ -227,7 +228,7 @@
 			tmpScope = *sc;
 			didSwapData = se->didSwapData;
 
-			volume = -modEntry->channels[i].n_volume; // invert volume
+			volume = -song->channels[i].n_volume; // invert volume
 
 			// render scope
 			if (se->active && tmpScope.data != NULL && volume != 0 && tmpScope.length > 0)
--- a/src/pt2_structs.h
+++ b/src/pt2_structs.h
@@ -32,6 +32,13 @@
 	uint32_t dwSMPTEFormat, dwSMPTEOffset, cSampleLoops, cbSamplerData;
 	sampleLoop_t loop;
 } samplerChunk_t;
+
+typedef struct mptExtraChunk_t
+{
+	uint32_t chunkID, chunkSize, flags;
+	uint16_t defaultPan, defaultVolume, globalVolume, reserved;
+	uint8_t vibratoType, vibratoSweep, vibratoDepth, vibratoRate;
+} mptExtraChunk_t;
 // -----------------------------------------
 
 typedef struct note_t
@@ -42,8 +49,8 @@
 
 typedef struct moduleHeader_t
 {
-	char moduleTitle[20 + 1];
-	uint16_t order[MOD_ORDERS], orderCount;
+	char name[20 + 1];
+	uint16_t order[MOD_ORDERS], numOrders;
 	uint16_t initialTempo; // used for STK/UST modules after module is loaded
 } moduleHeader_t;
 
@@ -71,14 +78,25 @@
 
 typedef struct module_t
 {
-	int8_t *sampleData, currRow, modified, row;
-	uint8_t currSpeed, moduleLoaded;
-	uint16_t currOrder, currPattern, currBPM;
-	uint32_t rowsCounter, rowsInTotal;
-	moduleHeader_t head;
+	bool loaded, modified;
+	int8_t *sampleData;
+
+	volatile uint8_t tick, speed;
+
+	int8_t row; // used for different things, so must not be internal to replayer
+
+	moduleHeader_t header;
 	moduleSample_t samples[MOD_SAMPLES];
 	moduleChannel_t channels[AMIGA_VOICES];
 	note_t *patterns[MAX_PATTERNS];
+
+	// for pattern viewer
+	int8_t currRow;
+	uint8_t currSpeed;
+	uint16_t currOrder, currPattern, currBPM;
+
+	// for MOD2WAV progress bar
+	uint32_t rowsCounter, rowsInTotal;
 } module_t;
 
 typedef struct keyb_t
@@ -125,7 +143,6 @@
 	volatile int8_t vuMeterVolumes[AMIGA_VOICES], spectrumVolumes[SPECTRUM_BAR_NUM];
 	volatile int8_t *sampleFromDisp, *sampleToDisp, *currSampleDisp, realVuMeterVolumes[AMIGA_VOICES];
 	volatile bool songPlaying, programRunning, isWAVRendering, isSMPRendering, smpRenderingDone;
-	volatile uint8_t modTick, modSpeed;
 	volatile uint16_t *quantizeValueDisp, *metroSpeedDisp, *metroChannelDisp, *sampleVolDisp;
 	volatile uint16_t *vol1Disp, *vol2Disp, *currEditPatternDisp, *currPosDisp, *currPatternDisp;
 	volatile uint16_t *currPosEdPattDisp, *currLengthDisp, *lpCutOffDisp, *hpCutOffDisp;
@@ -144,7 +161,7 @@
 
 	int8_t smpRedoFinetunes[MOD_SAMPLES], smpRedoVolumes[MOD_SAMPLES], multiModeNext[4], trackPattFlag;
 	int8_t *smpRedoBuffer[MOD_SAMPLES], *tempSample, currSample, recordMode, sampleFrom, sampleTo, autoInsSlot;
-	int8_t keypadSampleOffset, note1, note2, note3, note4, oldNote1, oldNote2, oldNote3, oldNote4;
+	int8_t hiLowInstr, note1, note2, note3, note4, oldNote1, oldNote2, oldNote3, oldNote4;
 	uint8_t playMode, currMode, tuningChan, tuningVol, errorMsgCounter, buffFromPos, buffToPos;
 	uint8_t blockFromPos, blockToPos, timingMode, f6Pos, f7Pos, f8Pos, f9Pos, f10Pos, keyOctave, pNoteFlag;
 	uint8_t tuningNote, resampleNote, initialTempo, initialSpeed, editMoveAdd;
@@ -240,4 +257,4 @@
 extern cursor_t cursor;
 extern ui_t ui;
 
-extern module_t *modEntry; // pt_main.c
+extern module_t *song; // pt_main.c
--- a/src/pt2_tables.c
+++ b/src/pt2_tables.c
@@ -1,15 +1,6 @@
-#include <SDL2/SDL.h>
-#include <stdio.h>
 #include <stdint.h>
+#include <stdbool.h>
 #include "pt2_mouse.h"
-
-uint32_t *aboutScreenBMP   = NULL, *clearDialogBMP     = NULL;
-uint32_t *diskOpScreenBMP  = NULL, *editOpModeCharsBMP = NULL, *mod2wavBMP         = NULL;
-uint32_t *editOpScreen1BMP = NULL, *editOpScreen2BMP   = NULL, *samplerVolumeBMP   = NULL;
-uint32_t *editOpScreen3BMP = NULL, *editOpScreen4BMP   = NULL, *spectrumVisualsBMP = NULL;
-uint32_t *muteButtonsBMP   = NULL, *posEdBMP           = NULL, *samplerFiltersBMP  = NULL;
-uint32_t *samplerScreenBMP = NULL, *pat2SmpDialogBMP   = NULL, *trackerFrameBMP    = NULL;
-uint32_t *yesNoDialogBMP   = NULL, *bigYesNoDialogBMP  = NULL;
 
 const char hexTable[16] =
 {
--- a/src/pt2_tables.h
+++ b/src/pt2_tables.h
@@ -16,62 +16,10 @@
 extern const int16_t periodTable[(37*16)+15];
 extern int8_t pNoteTable[32];
 
-// GFX
-extern uint32_t iconBMP[1024];
-extern const uint8_t mousePointerBMP[256];
-extern const uint8_t fontBMP[6096];
-
-// PACKED GFX
-extern const uint8_t aboutScreenPackedBMP[1684];
-extern const uint8_t clearDialogPackedBMP[525];
-extern const uint8_t diskOpScreenPackedBMP[1898];
-extern const uint8_t editOpModeCharsPackedBMP[88];
-extern const uint8_t editOpScreen1PackedBMP[1481];
-extern const uint8_t editOpScreen2PackedBMP[1502];
-extern const uint8_t editOpScreen3PackedBMP[1736];
-extern const uint8_t editOpScreen4PackedBMP[1713];
-extern const uint8_t mod2wavPackedBMP[607];
-extern const uint8_t muteButtonsPackedBMP[46];
-extern const uint8_t posEdPackedBMP[1375];
-extern const uint8_t samplerVolumePackedBMP[706];
-extern const uint8_t samplerFiltersPackedBMP[933];
-extern const uint8_t samplerScreenPackedBMP[3056];
-extern const uint8_t spectrumVisualsPackedBMP[2217];
-extern const uint8_t trackerFramePackedBMP[8486];
-extern const uint8_t yesNoDialogPackedBMP[476];
-extern const uint8_t bigYesNoDialogPackedBMP[472];
-extern const uint8_t pat2SmpDialogPackedBMP[520];
-
 // changable by config file
 extern uint16_t analyzerColors[36];
 extern uint16_t vuMeterColors[48];
 
-// these are filled/normalized on init, so no const
-extern uint32_t vuMeterBMP[480];
-extern uint32_t loopPinsBMP[512];
-extern uint32_t samplingPosBMP[64];
-extern uint32_t spectrumAnaBMP[36];
-extern uint32_t patternCursorBMP[154];
-extern uint32_t *editOpScreen1BMP;
-extern uint32_t *editOpScreen2BMP;
-extern uint32_t *editOpScreen3BMP;
-extern uint32_t *editOpScreen4BMP;
-extern uint32_t *yesNoDialogBMP;
-extern uint32_t *bigYesNoDialogBMP;
-extern uint32_t *spectrumVisualsBMP;
-extern uint32_t *posEdBMP;
-extern uint32_t *mod2wavBMP;
-extern uint32_t *diskOpScreenBMP;
-extern uint32_t *clearDialogBMP;
-extern uint32_t *samplerVolumeBMP;
-extern uint32_t *samplerFiltersBMP;
-extern uint32_t *samplerScreenBMP;
-extern uint32_t *trackerFrameBMP;
-extern uint32_t *aboutScreenBMP;
-extern uint32_t *muteButtonsBMP;
-extern uint32_t *editOpModeCharsBMP;
-extern uint32_t *pat2SmpDialogBMP;
-
 // button tables taken from the ptplay project + modified
 
 // MODIFY THESE EVERY TIME YOU REMOVE/ADD A BUTTON!
@@ -103,12 +51,3 @@
 extern const guiButton_t bEditOp3[];
 extern const guiButton_t bEditOp4[];
 extern const guiButton_t bSampler[];
-
-#define EDOP_MODE_BMP_A_OFS ((7 * 6) * 0)
-#define EDOP_MODE_BMP_C_OFS ((7 * 6) * 1)
-#define EDOP_MODE_BMP_H_OFS ((7 * 6) * 2)
-#define EDOP_MODE_BMP_N_OFS ((7 * 6) * 3)
-#define EDOP_MODE_BMP_O_OFS ((7 * 6) * 4)
-#define EDOP_MODE_BMP_P_OFS ((7 * 6) * 5)
-#define EDOP_MODE_BMP_S_OFS ((7 * 6) * 6)
-#define EDOP_MODE_BMP_T_OFS ((7 * 6) * 7)
--- a/src/pt2_textout.c
+++ b/src/pt2_textout.c
@@ -7,6 +7,7 @@
 #include "pt2_palette.h"
 #include "pt2_visuals.h"
 #include "pt2_structs.h"
+#include "pt2_bmp.h"
 
 void charOut(uint32_t xPos, uint32_t yPos, char ch, uint32_t color)
 {
@@ -32,38 +33,6 @@
 	}
 }
 
-void charOut2(uint32_t xPos, uint32_t yPos, char ch)
-{
-	const uint8_t *srcPtr;
-	uint32_t *dstPtr1, *dstPtr2;
-
-	if (ch == '\0' || ch == ' ')
-		return;
-
-	srcPtr = &fontBMP[(ch & 0x7F) << 3];
-	dstPtr1 = &video.frameBuffer[(yPos * SCREEN_W) + xPos];
-	dstPtr2 = dstPtr1 + (SCREEN_W+1);
-
-	const uint32_t color1 = video.palette[PAL_BORDER];
-	const uint32_t color2 = video.palette[PAL_GENBKG2];
-
-	for (int32_t y = 0; y < FONT_CHAR_H; y++)
-	{
-		for (int32_t x = 0; x < FONT_CHAR_W; x++)
-		{
-			if (srcPtr[x])
-			{
-				dstPtr2[x] = color2;
-				dstPtr1[x] = color1;
-			}
-		}
-
-		srcPtr += 127*FONT_CHAR_W;
-		dstPtr1 += SCREEN_W;
-		dstPtr2 += SCREEN_W;
-	}
-}
-
 void charOutBg(uint32_t xPos, uint32_t yPos, char ch, uint32_t fgColor, uint32_t bgColor)
 {
 	const uint8_t *srcPtr;
@@ -155,18 +124,6 @@
 	while (*text != '\0')
 	{
 		charOut(x, yPos, *text++, color);
-		x += FONT_CHAR_W;
-	}
-}
-
-void textOut2(uint32_t xPos, uint32_t yPos, const char *text)
-{
-	assert(text != NULL);
-
-	uint32_t x = xPos;
-	while (*text != '\0')
-	{
-		charOut2(x, yPos, *text++);
 		x += FONT_CHAR_W;
 	}
 }
--- a/src/pt2_textout.h
+++ b/src/pt2_textout.h
@@ -4,12 +4,10 @@
 #include <stdbool.h>
 
 void charOut(uint32_t xPos, uint32_t yPos, char ch, uint32_t color);
-void charOut2(uint32_t xPos, uint32_t yPos, char ch);
 void charOutBg(uint32_t xPos, uint32_t yPos, char ch, uint32_t fgColor, uint32_t bgColor);
 void charOutBig(uint32_t xPos, uint32_t yPos, char ch, uint32_t color);
 void charOutBigBg(uint32_t xPos, uint32_t yPos, char ch, uint32_t fgColor, uint32_t bgColor);
 void textOut(uint32_t xPos, uint32_t yPos, const char *text, uint32_t color);
-void textOut2(uint32_t xPos, uint32_t yPos, const char *text);
 void textOutTight(uint32_t xPos, uint32_t yPos, const char *text, uint32_t color);
 void textOutBg(uint32_t xPos, uint32_t yPos, const char *text, uint32_t fgColor, uint32_t bgColor);
 void textOutBig(uint32_t xPos, uint32_t yPos, const char *text, uint32_t color);
--- a/src/pt2_visuals.c
+++ b/src/pt2_visuals.c
@@ -26,17 +26,20 @@
 #include "pt2_helpers.h"
 #include "pt2_textout.h"
 #include "pt2_tables.h"
-#include "pt2_modloader.h"
-#include "pt2_sampleloader.h"
-#include "pt2_patternviewer.h"
+#include "pt2_module_loader.h"
+#include "pt2_module_saver.h"
+#include "pt2_sample_loader.h"
+#include "pt2_sample_saver.h"
+#include "pt2_pattern_viewer.h"
 #include "pt2_sampler.h"
 #include "pt2_diskop.h"
 #include "pt2_visuals.h"
-#include "pt2_helpers.h"
 #include "pt2_scopes.h"
 #include "pt2_edit.h"
 #include "pt2_pat2smp.h"
 #include "pt2_mod2wav.h"
+#include "pt2_config.h"
+#include "pt2_bmp.h"
 
 typedef struct sprite_t
 {
@@ -77,6 +80,20 @@
 	displayErrorMsg("OUT OF MEMORY !!!");
 }
 
+void statusSampleIsEmpty(void)
+{
+	displayErrorMsg("SAMPLE IS EMPTY");
+}
+
+void statusNotSampleZero(void)
+{
+	/* This rather confusing error message actually means that
+	** you can't load a sample to sample slot #0 (which isn't a
+	** real sample slot).
+	*/
+	displayErrorMsg("NOT SAMPLE 0 !");
+}
+
 void setupPerfFreq(void)
 {
 	uint64_t perfFreq64;
@@ -339,7 +356,7 @@
 	if (ui.diskOpScreenShown)
 		return;
 
-	currSample = &modEntry->samples[editor.currSample];
+	currSample = &song->samples[editor.currSample];
 
 	if (ui.updateSongPos)
 	{
@@ -441,7 +458,7 @@
 			textOut(88, 127, "MARK BLOCK", video.palette[PAL_GENTXT]);
 			charOut(192, 127, '-', video.palette[PAL_GENTXT]);
 
-			editor.blockToPos = modEntry->currRow;
+			editor.blockToPos = song->currRow;
 			if (editor.blockFromPos >= editor.blockToPos)
 			{
 				printTwoDecimals(176, 127, editor.blockToPos, video.palette[PAL_GENTXT]);
@@ -463,7 +480,7 @@
 	{
 		ui.updateSongBPM = false;
 		if (!ui.samplerScreenShown)
-			printThreeDecimalsBg(32, 123, modEntry->currBPM, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
+			printThreeDecimalsBg(32, 123, song->currBPM, video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
 	}
 
 	if (ui.updateCurrPattText)
@@ -542,7 +559,7 @@
 		ui.updateSongName = false;
 		for (x = 0; x < 20; x++)
 		{
-			tempChar = modEntry->head.moduleTitle[x];
+			tempChar = song->header.name[x];
 			if (tempChar == '\0')
 				tempChar = '_';
 
@@ -553,7 +570,7 @@
 	if (ui.updateCurrSampleName)
 	{
 		ui.updateCurrSampleName = false;
-		currSample = &modEntry->samples[editor.currSample];
+		currSample = &song->samples[editor.currSample];
 
 		for (x = 0; x < 22; x++)
 		{
@@ -575,13 +592,13 @@
 		// calculate module length
 		uint32_t totalSampleDataSize = 0;
 		for (i = 0; i < MOD_SAMPLES; i++)
-			totalSampleDataSize += modEntry->samples[i].length;
+			totalSampleDataSize += song->samples[i].length;
 
 		uint32_t totalPatterns = 0;
 		for (i = 0; i < MOD_ORDERS; i++)
 		{
-			if (modEntry->head.order[i] > totalPatterns)
-				totalPatterns = modEntry->head.order[i];
+			if (song->header.order[i] > totalPatterns)
+				totalPatterns = song->header.order[i];
 		}
 
 		uint32_t moduleSize = 2108 + (totalPatterns * 1024) + totalSampleDataSize;
@@ -618,7 +635,7 @@
 		return;
 
 	assert(editor.currSample >= 0 && editor.currSample <= 30);
-	s = &modEntry->samples[editor.currSample];
+	s = &song->samples[editor.currSample];
 
 	// update 9xx offset
 	if (mouse.y >= 138 && mouse.y <= 201 && mouse.x >= 3 && mouse.x <= 316)
@@ -899,9 +916,9 @@
 	{
 		bgPixel = video.palette[PAL_BACKGRD];
 
-		posEdPosition = modEntry->currOrder;
-		if (posEdPosition > modEntry->head.orderCount-1)
-			posEdPosition = modEntry->head.orderCount-1;
+		posEdPosition = song->currOrder;
+		if (posEdPosition > song->header.numOrders-1)
+			posEdPosition = song->header.numOrders-1;
 
 		// top five
 		for (y = 0; y < 5; y++)
@@ -909,7 +926,7 @@
 			if (posEdPosition-(5-y) >= 0)
 			{
 				printThreeDecimalsBg(128, 23+(y*6), posEdPosition-(5-y), video.palette[PAL_QADSCP], video.palette[PAL_BACKGRD]);
-				printTwoDecimalsBg(160, 23+(y*6), modEntry->head.order[posEdPosition-(5-y)], video.palette[PAL_QADSCP], video.palette[PAL_BACKGRD]);
+				printTwoDecimalsBg(160, 23+(y*6), song->header.order[posEdPosition-(5-y)], video.palette[PAL_QADSCP], video.palette[PAL_BACKGRD]);
 			}
 			else
 			{
@@ -931,10 +948,10 @@
 		// bottom six
 		for (y = 0; y < 6; y++)
 		{
-			if (posEdPosition+y < modEntry->head.orderCount-1)
+			if (posEdPosition+y < song->header.numOrders-1)
 			{
 				printThreeDecimalsBg(128, 59+(y*6), posEdPosition+(y+1), video.palette[PAL_QADSCP], video.palette[PAL_BACKGRD]);
-				printTwoDecimalsBg(160, 59+(y*6), modEntry->head.order[posEdPosition+(y+1)], video.palette[PAL_QADSCP], video.palette[PAL_BACKGRD]);
+				printTwoDecimalsBg(160, 59+(y*6), song->header.order[posEdPosition+(y+1)], video.palette[PAL_QADSCP], video.palette[PAL_BACKGRD]);
 			}
 			else
 			{
@@ -1449,7 +1466,7 @@
 			}
 
 			editor.isWAVRendering = false;
-			modSetTempo(modEntry->currBPM); // update BPM with normal audio output rate
+			modSetTempo(song->currBPM); // update BPM with normal audio output rate
 			displayMainScreen();
 		}
 		else
@@ -1456,7 +1473,7 @@
 		{
 			// render progress bar
 
-			percent = (uint8_t)((modEntry->rowsCounter * 100) / modEntry->rowsInTotal);
+			percent = (uint8_t)((song->rowsCounter * 100) / song->rowsInTotal);
 			if (percent > 100)
 				percent = 100;
 
@@ -1591,7 +1608,7 @@
 			textOutBg(168, 91, "    ", video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
 			charOut(198, 91,    ':', video.palette[PAL_GENBKG]);
 
-			if (modEntry->samples[editor.currSample].loopLength > 2 || modEntry->samples[editor.currSample].loopStart >= 2)
+			if (song->samples[editor.currSample].loopLength > 2 || song->samples[editor.currSample].loopStart >= 2)
 			{
 				textOut(168, 91, "LOOP", video.palette[PAL_GENTXT]);
 			}
@@ -1830,7 +1847,7 @@
 			for (i = 0; i < MOD_SAMPLES; i++)
 			{
 				editor.currSample = (int8_t)i;
-				if (modEntry->samples[i].length > 2)
+				if (song->samples[i].length > 2)
 					saveSample(DONT_CHECK_IF_FILE_EXIST, GIVE_NEW_FILENAME);
 			}
 			editor.currSample = oldSample;
@@ -1893,8 +1910,14 @@
 		{
 			restoreStatusAndMousePointer();
 
+			if (editor.sampleZero)
+			{
+				statusNotSampleZero();
+				break;
+			}
+
 			turnOffVoices();
-			s = &modEntry->samples[editor.currSample];
+			s = &song->samples[editor.currSample];
 
 			s->fineTune = 0;
 			s->volume = 0;
@@ -1903,7 +1926,7 @@
 			s->loopLength = 2;
 
 			memset(s->text, 0, sizeof (s->text));
-			memset(&modEntry->sampleData[(editor.currSample * MAX_SAMPLE_LEN)], 0, MAX_SAMPLE_LEN);
+			memset(&song->sampleData[(editor.currSample * MAX_SAMPLE_LEN)], 0, MAX_SAMPLE_LEN);
 
 			editor.samplePos = 0;
 			updateCurrSample();
@@ -1932,11 +1955,11 @@
 		{
 			memset(fileName, 0, sizeof (fileName));
 
-			if (modEntry->head.moduleTitle[0] != '\0')
+			if (song->header.name[0] != '\0')
 			{
 				for (i = 0; i < 20; i++)
 				{
-					fileName[i] = (char)tolower(modEntry->head.moduleTitle[i]);
+					fileName[i] = (char)tolower(song->header.name[i]);
 					if (fileName[i] == '\0') break;
 					sanitizeFilenameChar(&fileName[i]);
 				}
@@ -1956,11 +1979,11 @@
 		{
 			memset(fileName, 0, sizeof (fileName));
 
-			if (modEntry->head.moduleTitle[0] != '\0')
+			if (song->header.name[0] != '\0')
 			{
 				for (i = 0; i < 20; i++)
 				{
-					fileName[i] = (char)(tolower(modEntry->head.moduleTitle[i]));
+					fileName[i] = (char)(tolower(song->header.name[i]));
 					if (fileName[i] == '\0') break;
 					sanitizeFilenameChar(&fileName[i]);
 				}
@@ -2015,276 +2038,6 @@
 	}
 
 	removeAskDialog();
-}
-
-void createBitmaps(void)
-{
-	uint8_t r8, g8, b8, r8_2, g8_2, b8_2;
-	uint16_t pixel12;
-	uint32_t i, j, x, y, pixel24;
-
-	pixel24 = video.palette[PAL_PATCURSOR];
-	for (y = 0; y < 14; y++)
-	{
-		// top two rows have a lighter color
-		if (y < 2)
-		{
-			r8 = R24(pixel24);
-			g8 = G24(pixel24);
-			b8 = B24(pixel24);
-
-			if (r8 <= 0xFF-0x33)
-				r8 += 0x33;
-			else
-				r8 = 0xFF;
-
-			if (g8 <= 0xFF-0x33)
-				g8 += 0x33;
-			else
-				g8 = 0xFF;
-
-			if (b8 <= 0xFF-0x33)
-				b8 += 0x33;
-			else
-				b8 = 0xFF;
-
-			for (x = 0; x < 11; x++)
-				patternCursorBMP[(y * 11) + x] = RGB24(r8, g8, b8);
-		}
-
-		// sides (same color)
-		if (y >= 2 && y <= 12)
-		{
-			patternCursorBMP[(y * 11) + 0] = pixel24;
-
-			for (x = 1; x < 10; x++)
-				patternCursorBMP[(y * 11) + x] = video.palette[PAL_COLORKEY];
-
-			patternCursorBMP[(y * 11) + 10] = pixel24;
-		}
-
-		// bottom two rows have a darker color
-		if (y > 11)
-		{
-			r8 = R24(pixel24);
-			g8 = G24(pixel24);
-			b8 = B24(pixel24);
-
-			if (r8 >= 0x33)
-				r8 -= 0x33;
-			else
-				r8 = 0x00;
-
-			if (g8 >= 0x33)
-				g8 -= 0x33;
-			else
-				g8 = 0x00;
-
-			if (b8 >= 0x33)
-				b8 -= 0x33;
-			else
-				b8 = 0x00;
-
-			for (x = 0; x < 11; x++)
-				patternCursorBMP[(y * 11) + x] = RGB24(r8, g8, b8);
-		}
-	}
-
-	// create spectrum analyzer bar graphics
-	for (i = 0; i < 36; i++)
-		spectrumAnaBMP[i] = RGB12_to_RGB24(analyzerColors[35-i]);
-
-	// create VU-Meter bar graphics
-	for (i = 0; i < 48; i++)
-	{
-		pixel12 = vuMeterColors[47-i];
-
-		r8_2 = r8 = R12_to_R24(pixel12);
-		g8_2 = g8 = G12_to_G24(pixel12);
-		b8_2 = b8 = B12_to_B24(pixel12);
-
-		// brighter pixels on the left side
-
-		if (r8_2 <= 0xFF-0x33)
-			r8_2 += 0x33;
-		else
-			r8_2 = 0xFF;
-
-		if (g8_2 <= 0xFF-0x33)
-			g8_2 += 0x33;
-		else
-			g8_2 = 0xFF;
-
-		if (b8_2 <= 0xFF-0x33)
-			b8_2 += 0x33;
-		else
-			b8_2 = 0xFF;
-
-		pixel24 = RGB24(r8_2, g8_2, b8_2);
-
-		vuMeterBMP[(i * 10) + 0] = pixel24;
-		vuMeterBMP[(i * 10) + 1] = pixel24;
-
-		// main pixels
-		for (j = 2; j < 8; j++)
-			vuMeterBMP[(i * 10) + j] = RGB24(r8, g8, b8);
-
-		// darker pixels on the right side
-		r8_2 = r8;
-		g8_2 = g8;
-		b8_2 = b8;
-
-		if (r8_2 >= 0x33)
-			r8_2 -= 0x33;
-		else
-			r8_2 = 0x00;
-
-		if (g8_2 >= 0x33)
-			g8_2 -= 0x33;
-		else
-			g8_2 = 0x00;
-
-		if (b8_2 >= 0x33)
-			b8_2 -= 0x33;
-		else
-			b8_2 = 0x00;
-
-		pixel24 = RGB24(r8_2, g8_2, b8_2);
-
-		vuMeterBMP[(i * 10) + 8] = pixel24;
-		vuMeterBMP[(i * 10) + 9] = pixel24;
-	}
-}
-
-void freeBMPs(void)
-{
-	if (trackerFrameBMP != NULL) free(trackerFrameBMP);
-	if (samplerScreenBMP != NULL) free(samplerScreenBMP);
-	if (samplerVolumeBMP != NULL) free(samplerVolumeBMP);
-	if (samplerFiltersBMP != NULL) free(samplerFiltersBMP);
-	if (clearDialogBMP != NULL) free(clearDialogBMP);
-	if (diskOpScreenBMP != NULL) free(diskOpScreenBMP);
-	if (mod2wavBMP != NULL) free(mod2wavBMP);
-	if (posEdBMP != NULL) free(posEdBMP);
-	if (spectrumVisualsBMP != NULL) free(spectrumVisualsBMP);
-	if (yesNoDialogBMP != NULL) free(yesNoDialogBMP);
-	if (bigYesNoDialogBMP != NULL) free(bigYesNoDialogBMP);
-	if (pat2SmpDialogBMP != NULL) free(pat2SmpDialogBMP);
-	if (editOpScreen1BMP != NULL) free(editOpScreen1BMP);
-	if (editOpScreen2BMP != NULL) free(editOpScreen2BMP);
-	if (editOpScreen3BMP != NULL) free(editOpScreen3BMP);
-	if (editOpScreen4BMP != NULL) free(editOpScreen4BMP);
-	if (aboutScreenBMP != NULL) free(aboutScreenBMP);
-	if (muteButtonsBMP != NULL) free(muteButtonsBMP);
-	if (editOpModeCharsBMP != NULL) free(editOpModeCharsBMP);
-}
-
-uint32_t *unpackBMP(const uint8_t *src, uint32_t packedLen)
-{
-	const uint8_t *packSrc;
-	uint8_t *tmpBuffer, *packDst, byteIn;
-	int16_t count;
-	uint32_t *dst, decodedLength, i;
-
-	// RLE decode
-	decodedLength = (src[0] << 24) | (src[1] << 16) | (src[2] << 8) | src[3];
-
-	// 2-bit to 8-bit conversion
-	dst = (uint32_t *)malloc((decodedLength * 4) * sizeof (int32_t));
-	if (dst == NULL)
-		return NULL;
-
-	tmpBuffer = (uint8_t *)malloc(decodedLength + 512); // some margin is needed, the packer is buggy
-	if (tmpBuffer == NULL)
-	{
-		free(dst);
-		return NULL;
-	}
-
-	packSrc = src + 4;
-	packDst = tmpBuffer;
-
-	i = packedLen - 4;
-	while (i > 0)
-	{
-		byteIn = *packSrc++;
-		if (byteIn == 0xCC) // compactor code
-		{
-			count  = *packSrc++;
-			byteIn = *packSrc++;
-
-			while (count-- >= 0)
-				*packDst++ = byteIn;
-
-			i -= 2;
-		}
-		else
-		{
-			*packDst++ = byteIn;
-		}
-
-		i--;
-	}
-
-	for (i = 0; i < decodedLength; i++)
-	{
-		byteIn = (tmpBuffer[i] & 0xC0) >> 6;
-		assert(byteIn < PALETTE_NUM);
-		dst[(i * 4) + 0] = video.palette[byteIn];
-
-		byteIn = (tmpBuffer[i] & 0x30) >> 4;
-		assert(byteIn < PALETTE_NUM);
-		dst[(i * 4) + 1] = video.palette[byteIn];
-
-		byteIn = (tmpBuffer[i] & 0x0C) >> 2;
-		assert(byteIn < PALETTE_NUM);
-		dst[(i * 4) + 2] = video.palette[byteIn];
-
-		byteIn = (tmpBuffer[i] & 0x03) >> 0;
-		assert(byteIn < PALETTE_NUM);
-		dst[(i * 4) + 3] = video.palette[byteIn];
-	}
-
-	free(tmpBuffer);
-	return dst;
-}
-
-bool unpackBMPs(void)
-{
-	trackerFrameBMP = unpackBMP(trackerFramePackedBMP, sizeof (trackerFramePackedBMP));
-	samplerScreenBMP = unpackBMP(samplerScreenPackedBMP, sizeof (samplerScreenPackedBMP));
-	samplerVolumeBMP = unpackBMP(samplerVolumePackedBMP, sizeof (samplerVolumePackedBMP));
-	samplerFiltersBMP = unpackBMP(samplerFiltersPackedBMP, sizeof (samplerFiltersPackedBMP));
-	clearDialogBMP = unpackBMP(clearDialogPackedBMP, sizeof (clearDialogPackedBMP));
-	diskOpScreenBMP = unpackBMP(diskOpScreenPackedBMP, sizeof (diskOpScreenPackedBMP));
-	mod2wavBMP = unpackBMP(mod2wavPackedBMP, sizeof (mod2wavPackedBMP));
-	posEdBMP = unpackBMP(posEdPackedBMP, sizeof (posEdPackedBMP));
-	spectrumVisualsBMP = unpackBMP(spectrumVisualsPackedBMP, sizeof (spectrumVisualsPackedBMP));
-	yesNoDialogBMP = unpackBMP(yesNoDialogPackedBMP, sizeof (yesNoDialogPackedBMP));
-	bigYesNoDialogBMP = unpackBMP(bigYesNoDialogPackedBMP, sizeof (bigYesNoDialogPackedBMP));
-	pat2SmpDialogBMP = unpackBMP(pat2SmpDialogPackedBMP, sizeof (pat2SmpDialogPackedBMP));
-	editOpScreen1BMP = unpackBMP(editOpScreen1PackedBMP, sizeof (editOpScreen1PackedBMP));
-	editOpScreen2BMP = unpackBMP(editOpScreen2PackedBMP, sizeof (editOpScreen2PackedBMP));
-	editOpScreen3BMP = unpackBMP(editOpScreen3PackedBMP, sizeof (editOpScreen3PackedBMP));
-	editOpScreen4BMP = unpackBMP(editOpScreen4PackedBMP, sizeof (editOpScreen4PackedBMP));
-	aboutScreenBMP = unpackBMP(aboutScreenPackedBMP, sizeof (aboutScreenPackedBMP));
-	muteButtonsBMP = unpackBMP(muteButtonsPackedBMP, sizeof (muteButtonsPackedBMP));
-	editOpModeCharsBMP = unpackBMP(editOpModeCharsPackedBMP, sizeof (editOpModeCharsPackedBMP));
-
-	if (trackerFrameBMP    == NULL || samplerScreenBMP   == NULL || samplerVolumeBMP  == NULL ||
-		clearDialogBMP     == NULL || diskOpScreenBMP    == NULL || mod2wavBMP        == NULL ||
-		posEdBMP           == NULL || spectrumVisualsBMP == NULL || yesNoDialogBMP    == NULL ||
-		editOpScreen1BMP   == NULL || editOpScreen2BMP   == NULL || editOpScreen3BMP  == NULL ||
-		editOpScreen4BMP   == NULL || aboutScreenBMP     == NULL || muteButtonsBMP    == NULL ||
-		editOpModeCharsBMP == NULL || samplerFiltersBMP  == NULL || yesNoDialogBMP    == NULL ||
-		bigYesNoDialogBMP  == NULL)
-	{
-		showErrorMsgBox("Out of memory!");
-		return false; // BMPs are free'd in cleanUp()
-	}
-
-	createBitmaps();
-	return true;
 }
 
 void videoClose(void)
--- a/src/pt2_visuals.h
+++ b/src/pt2_visuals.h
@@ -20,11 +20,13 @@
 
 void statusAllRight(void);
 void statusOutOfMemory(void);
+void statusSampleIsEmpty(void);
+void statusNotSampleZero(void);
+
 void setupPerfFreq(void);
 void setupWaitVBL(void);
 void waitVBL(void);
 void resetAllScreens(void);
-void freeBMPs(void);
 void handleAskNo(void);
 void handleAskYes(void);
 bool setupVideo(void);
@@ -38,8 +40,7 @@
 void updateDiskOp(void);
 void toggleFullScreen(void);
 void videoClose(void);
-bool unpackBMPs(void);
-void createBitmaps(void);
+
 void displayMainScreen(void);
 void renderAskDialog(void);
 void renderBigAskDialog(void);
--- a/vs2019_project/pt2-clone/pt2-clone.vcxproj
+++ b/vs2019_project/pt2-clone/pt2-clone.vcxproj
@@ -273,6 +273,7 @@
   <ItemGroup>
     <ClInclude Include="..\..\src\pt2_audio.h" />
     <ClInclude Include="..\..\src\pt2_blep.h" />
+    <ClInclude Include="..\..\src\pt2_bmp.h" />
     <ClInclude Include="..\..\src\pt2_config.h" />
     <ClInclude Include="..\..\src\pt2_diskop.h" />
     <ClInclude Include="..\..\src\pt2_edit.h" />
@@ -280,13 +281,15 @@
     <ClInclude Include="..\..\src\pt2_helpers.h" />
     <ClInclude Include="..\..\src\pt2_keyboard.h" />
     <ClInclude Include="..\..\src\pt2_mod2wav.h" />
-    <ClInclude Include="..\..\src\pt2_modloader.h" />
+    <ClInclude Include="..\..\src\pt2_module_loader.h" />
+    <ClInclude Include="..\..\src\pt2_module_saver.h" />
     <ClInclude Include="..\..\src\pt2_mouse.h" />
     <ClInclude Include="..\..\src\pt2_palette.h" />
     <ClInclude Include="..\..\src\pt2_pat2smp.h" />
-    <ClInclude Include="..\..\src\pt2_patternviewer.h" />
-    <ClInclude Include="..\..\src\pt2_sampleloader.h" />
+    <ClInclude Include="..\..\src\pt2_pattern_viewer.h" />
+    <ClInclude Include="..\..\src\pt2_sample_loader.h" />
     <ClInclude Include="..\..\src\pt2_sampler.h" />
+    <ClInclude Include="..\..\src\pt2_sample_saver.h" />
     <ClInclude Include="..\..\src\pt2_scopes.h" />
     <ClInclude Include="..\..\src\pt2_structs.h" />
     <ClInclude Include="..\..\src\pt2_tables.h" />
@@ -317,6 +320,7 @@
       <EnableEnhancedInstructionSet Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">StreamingSIMDExtensions2</EnableEnhancedInstructionSet>
     </ClCompile>
     <ClCompile Include="..\..\src\pt2_blep.c" />
+    <ClCompile Include="..\..\src\pt2_bmp.c" />
     <ClCompile Include="..\..\src\pt2_config.c" />
     <ClCompile Include="..\..\src\pt2_diskop.c" />
     <ClCompile Include="..\..\src\pt2_edit.c" />
@@ -324,14 +328,16 @@
     <ClCompile Include="..\..\src\pt2_keyboard.c" />
     <ClCompile Include="..\..\src\pt2_main.c" />
     <ClCompile Include="..\..\src\pt2_mod2wav.c" />
-    <ClCompile Include="..\..\src\pt2_modloader.c" />
-    <ClCompile Include="..\..\src\pt2_modplayer.c" />
+    <ClCompile Include="..\..\src\pt2_module_loader.c" />
+    <ClCompile Include="..\..\src\pt2_replayer.c" />
+    <ClCompile Include="..\..\src\pt2_module_saver.c" />
     <ClCompile Include="..\..\src\pt2_mouse.c" />
     <ClCompile Include="..\..\src\pt2_palette.c" />
     <ClCompile Include="..\..\src\pt2_pat2smp.c" />
-    <ClCompile Include="..\..\src\pt2_patternviewer.c" />
-    <ClCompile Include="..\..\src\pt2_sampleloader.c" />
+    <ClCompile Include="..\..\src\pt2_pattern_viewer.c" />
+    <ClCompile Include="..\..\src\pt2_sample_loader.c" />
     <ClCompile Include="..\..\src\pt2_sampler.c" />
+    <ClCompile Include="..\..\src\pt2_sample_saver.c" />
     <ClCompile Include="..\..\src\pt2_scopes.c" />
     <ClCompile Include="..\..\src\pt2_structs.c" />
     <ClCompile Include="..\..\src\pt2_tables.c" />
--- a/vs2019_project/pt2-clone/pt2-clone.vcxproj.filters
+++ b/vs2019_project/pt2-clone/pt2-clone.vcxproj.filters
@@ -33,9 +33,6 @@
     <ClInclude Include="..\..\src\pt2_keyboard.h">
       <Filter>headers</Filter>
     </ClInclude>
-    <ClInclude Include="..\..\src\pt2_modloader.h">
-      <Filter>headers</Filter>
-    </ClInclude>
     <ClInclude Include="..\..\src\pt2_mouse.h">
       <Filter>headers</Filter>
     </ClInclude>
@@ -42,12 +39,6 @@
     <ClInclude Include="..\..\src\pt2_palette.h">
       <Filter>headers</Filter>
     </ClInclude>
-    <ClInclude Include="..\..\src\pt2_patternviewer.h">
-      <Filter>headers</Filter>
-    </ClInclude>
-    <ClInclude Include="..\..\src\pt2_sampleloader.h">
-      <Filter>headers</Filter>
-    </ClInclude>
     <ClInclude Include="..\..\src\pt2_sampler.h">
       <Filter>headers</Filter>
     </ClInclude>
@@ -75,6 +66,24 @@
     <ClInclude Include="..\..\src\pt2_structs.h">
       <Filter>headers</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\pt2_bmp.h">
+      <Filter>headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\pt2_sample_loader.h">
+      <Filter>headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\pt2_sample_saver.h">
+      <Filter>headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\pt2_module_loader.h">
+      <Filter>headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\pt2_pattern_viewer.h">
+      <Filter>headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\pt2_module_saver.h">
+      <Filter>headers</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="..\..\src\pt2_audio.c" />
@@ -85,12 +94,8 @@
     <ClCompile Include="..\..\src\pt2_helpers.c" />
     <ClCompile Include="..\..\src\pt2_keyboard.c" />
     <ClCompile Include="..\..\src\pt2_main.c" />
-    <ClCompile Include="..\..\src\pt2_modloader.c" />
-    <ClCompile Include="..\..\src\pt2_modplayer.c" />
     <ClCompile Include="..\..\src\pt2_mouse.c" />
     <ClCompile Include="..\..\src\pt2_palette.c" />
-    <ClCompile Include="..\..\src\pt2_patternviewer.c" />
-    <ClCompile Include="..\..\src\pt2_sampleloader.c" />
     <ClCompile Include="..\..\src\pt2_sampler.c" />
     <ClCompile Include="..\..\src\pt2_scopes.c" />
     <ClCompile Include="..\..\src\pt2_tables.c" />
@@ -154,6 +159,13 @@
     <ClCompile Include="..\..\src\pt2_mod2wav.c" />
     <ClCompile Include="..\..\src\pt2_pat2smp.c" />
     <ClCompile Include="..\..\src\pt2_structs.c" />
+    <ClCompile Include="..\..\src\pt2_bmp.c" />
+    <ClCompile Include="..\..\src\pt2_sample_saver.c" />
+    <ClCompile Include="..\..\src\pt2_sample_loader.c" />
+    <ClCompile Include="..\..\src\pt2_module_loader.c" />
+    <ClCompile Include="..\..\src\pt2_pattern_viewer.c" />
+    <ClCompile Include="..\..\src\pt2_module_saver.c" />
+    <ClCompile Include="..\..\src\pt2_replayer.c" />
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="..\..\src\pt2-clone.rc" />