shithub: pt2-clone

Download patch

ref: 9b62d4de7960879e6524f7bcaeb3c007654a8301
parent: d9fb87f3797258356992da59402a3561e4e1b955
author: Olav Sørensen <olav.sorensen@live.no>
date: Wed Feb 12 16:53:51 EST 2020

Pushed v1.05 code

- The audio dithering has been slightly improved. It was changed from 1.5-bit
  rectangular to 1-bit triangular.
- Bugfix: Don't load sample data after loop end when importing The Ultimate
  SoundTracker (STK/UST) modules. The data would be one-shot played in PT,
  which is not how it works in STK/UST. Fixes several modules...
- Bugfix: Reset tempo mode to CIA when loading a new module
- The module loader code has been cleaned up a little bit

--- a/make-linux.sh
+++ b/make-linux.sh
@@ -6,4 +6,4 @@
 gcc -DNDEBUG src/gfx/*.c src/*.c -lSDL2 -lm -Wall -Wno-unused-result -Wc++-compat -Wshadow -Winit-self -Wextra -Wunused -Wunreachable-code -Wredundant-decls -Wswitch-default -march=native -mtune=native -O3 -o release/other/pt2-clone
 rm src/gfx/*.o src/*.o &> /dev/null
 
-echo Done! The executable is in the folder named \'release/other\'.
\ No newline at end of file
+echo Done. The executable can be found in \'release/other\' if everything went well.
--- a/make-macos.sh
+++ b/make-macos.sh
@@ -13,5 +13,5 @@
     install_name_tool -change @rpath/SDL2.framework/Versions/A/SDL2 @executable_path/../Frameworks/SDL2.framework/Versions/A/SDL2 release/macos/pt2-clone-macos.app/Contents/MacOS/pt2-clone-macos
     
     rm src/gfx/*.o src/*.o &> /dev/null
-    echo Done! The executable is in the folder named \'release/macos\'.
+    echo Done. The executable can be found in \'release/macos\' if everything went well.
 fi
--- a/src/pt2_audio.c
+++ b/src/pt2_audio.c
@@ -16,7 +16,7 @@
 #else
 #include <unistd.h>
 #endif
-#include <math.h> // sqrt(),tan(),M_PI,round(),roundf()
+#include <math.h> // sqrt(),tan(),M_PI
 #include <fcntl.h>
 #include <sys/types.h>
 #include <sys/stat.h>
@@ -62,6 +62,7 @@
 static int32_t sampleCounter, maxSamplesToMix, randSeed = INITIAL_DITHER_SEED;
 static uint32_t oldScopeDelta;
 static double *dMixBufferL, *dMixBufferR, *dMixBufferLUnaligned, *dMixBufferRUnaligned, dOldVoiceDelta, dOldVoiceDeltaMul;
+static double dPrngStateL, dPrngStateR;
 static blep_t blep[AMIGA_VOICES], blepVol[AMIGA_VOICES];
 static lossyIntegrator_t filterLo, filterHi;
 static ledFilterCoeff_t filterLEDC;
@@ -89,7 +90,8 @@
 	ciaVal = (uint32_t)(1773447 / bpm); // yes, PT truncates here
 	dFreqMul = ciaVal * (1.0 / CIA_PAL_CLK);
 
-	return (uint16_t)((audioFreq * dFreqMul) + 0.5);
+	int32_t smpsPerTick = (int32_t)((audioFreq * dFreqMul) + 0.5);
+	return (uint16_t)smpsPerTick;
 }
 
 static void generateBpmTables(void)
@@ -321,7 +323,7 @@
 	clearLossyIntegrator(&filterHi);
 	clearLEDFilter(&filterLED);
 
-	resetDitherSeed();
+	resetAudioDithering();
 
 	editor.tuningFlag = false;
 }
@@ -596,15 +598,18 @@
 	}
 }
 
-void resetDitherSeed(void)
+void resetAudioDithering(void)
 {
 	randSeed = INITIAL_DITHER_SEED;
+	dPrngStateL = 0.0;
+	dPrngStateR = 0.0;
 }
 
-// Delphi/Pascal LCG Random() (without limit). Suitable for 32-bit random numbers
 static inline int32_t random32(void)
 {
-	randSeed = randSeed * 134775813 + 1;
+	// LCG random 32-bit generator (quite good and fast)
+	randSeed *= 134775813;
+	randSeed++;
 	return randSeed;
 }
 
@@ -611,7 +616,7 @@
 static inline void processMixedSamplesA1200(int32_t i, int16_t *out)
 {
 	int32_t smp32;
-	double dOut[2], dDither;
+	double dOut[2], dPrng;
 
 	dOut[0] = dMixBufferL[i];
 	dOut[1] = dMixBufferR[i];
@@ -626,19 +631,21 @@
 	lossyIntegratorHighPass(&filterHi, dOut, dOut);
 
 	// normalize and flip phase (A500/A1200 has an inverted audio signal)
-	dOut[0] *= -((INT16_MAX+1.0) / AMIGA_VOICES);
-	dOut[1] *= -((INT16_MAX+1.0) / AMIGA_VOICES);
+	dOut[0] *= -(INT16_MAX / AMIGA_VOICES);
+	dOut[1] *= -(INT16_MAX / AMIGA_VOICES);
 
-	// apply 0.5-bit dither
-	dDither = random32() * (0.5 / (INT32_MAX+1.0)); // -0.5..0.5
-	dOut[0] += dDither;
-	dDither = random32() * (0.5 / (INT32_MAX+1.0));
-	dOut[1] += dDither;
-
+	// left channel - 1-bit triangular dithering (high-pass filtered)
+	dPrng = random32() * (0.5 / INT32_MAX); // -0.5..0.5
+	dOut[0] = (dOut[0] + dPrng) - dPrngStateL;
+	dPrngStateL = dPrng;
 	smp32 = (int32_t)dOut[0];
 	CLAMP16(smp32);
 	out[0] = (int16_t)smp32;
 
+	// right channel - 1-bit triangular dithering (high-pass filtered)
+	dPrng = random32() * (0.5 / INT32_MAX); // -0.5..0.5
+	dOut[1] = (dOut[1] + dPrng) - dPrngStateR;
+	dPrngStateR = dPrng;
 	smp32 = (int32_t)dOut[1];
 	CLAMP16(smp32);
 	out[1] = (int16_t)smp32;
@@ -647,7 +654,7 @@
 static inline void processMixedSamplesA500(int32_t i, int16_t *out)
 {
 	int32_t smp32;
-	double dOut[2], dDither;
+	double dOut[2], dPrng;
 
 	dOut[0] = dMixBufferL[i];
 	dOut[1] = dMixBufferR[i];
@@ -662,20 +669,21 @@
 	// process high-pass filter
 	lossyIntegratorHighPass(&filterHi, dOut, dOut);
 
-	// normalize and flip phase (A500/A1200 has an inverted audio signal)
-	dOut[0] *= -((INT16_MAX+1.0) / AMIGA_VOICES);
-	dOut[1] *= -((INT16_MAX+1.0) / AMIGA_VOICES);
+	dOut[0] *= -(INT16_MAX / AMIGA_VOICES);
+	dOut[1] *= -(INT16_MAX / AMIGA_VOICES);
 
-	// apply 0.5-bit dither
-	dDither = random32() * (0.5 / (INT32_MAX+1.0)); // -0.5..0.5
-	dOut[0] += dDither;
-	dDither = random32() * (0.5 / (INT32_MAX+1.0));
-	dOut[1] += dDither;
-
+	// left channel - 1-bit triangular dithering (high-pass filtered)
+	dPrng = random32() * (0.5 / INT32_MAX); // -0.5..0.5
+	dOut[0] = (dOut[0] + dPrng) - dPrngStateL;
+	dPrngStateL = dPrng;
 	smp32 = (int32_t)dOut[0];
 	CLAMP16(smp32);
 	out[0] = (int16_t)smp32;
 
+	// right channel - 1-bit triangular dithering (high-pass filtered)
+	dPrng = random32() * (0.5 / INT32_MAX); // -0.5..0.5
+	dOut[1] = (dOut[1] + dPrng) - dPrngStateR;
+	dPrngStateR = dPrng;
 	smp32 = (int32_t)dOut[1];
 	CLAMP16(smp32);
 	out[1] = (int16_t)smp32;
--- a/src/pt2_audio.h
+++ b/src/pt2_audio.h
@@ -12,7 +12,7 @@
 } lossyIntegrator_t;
 
 void resetCachedMixerPeriod(void);
-void resetDitherSeed(void);
+void resetAudioDithering(void);
 void calcCoeffLossyIntegrator(double dSr, double dHz, lossyIntegrator_t *filter);
 void lossyIntegrator(lossyIntegrator_t *filter, double *dIn, double *dOut);
 void lossyIntegratorHighPass(lossyIntegrator_t *filter, double *dIn, double *dOut);
--- a/src/pt2_header.h
+++ b/src/pt2_header.h
@@ -13,7 +13,7 @@
 #include <stdint.h>
 #include "pt2_unicode.h"
 
-#define PROG_VER_STR "1.04"
+#define PROG_VER_STR "1.05"
 
 #ifdef _WIN32
 #define DIR_DELIMITER '\\'
@@ -83,19 +83,17 @@
 
 #define KEYB_REPEAT_DELAY 17
 
+// .MOD types
 enum
 {
-	FORMAT_MK, // ProTracker 1.x
-	FORMAT_MK2, // ProTracker 2.x (if tune has >64 patterns)
-	FORMAT_FLT4, // StarTrekker
-	FORMAT_1CHN,
-	FORMAT_2CHN, // FastTracker II
-	FORMAT_3CHN,
-	FORMAT_4CHN, // rare type, not sure what tracker it comes from
+	FORMAT_MK, // ProTracker or compatible
+	FORMAT_FLT, // Startrekker (4 channels)
+	FORMAT_FT2, // FT2 (or other trackers, multichannel)
 	FORMAT_STK, // The Ultimate SoundTracker (15 samples)
 	FORMAT_NT, // NoiseTracker
-	FORMAT_FEST, // NoiseTracker (special one)
-	FORMAT_UNKNOWN
+	FORMAT_HMNT, // His Master's NoiseTracker (special one)
+
+	FORMAT_UNKNOWN // may be The Ultimate Soundtracker (set to FORMAT_STK later)
 };
 
 enum
@@ -249,9 +247,8 @@
 typedef struct moduleHeader_t
 {
 	char moduleTitle[20 + 1];
-	uint8_t ticks, format, restartPos;
-	uint16_t order[MOD_ORDERS], orderCount, patternCount, tempo, initBPM;
-	uint32_t moduleSize, totalSampleSize;
+	uint16_t order[MOD_ORDERS], orderCount;
+	uint16_t initialTempo; // used for STK/UST modules after module is loaded
 } moduleHeader_t;
 
 typedef struct moduleSample_t
@@ -261,8 +258,8 @@
 	char text[22 + 1];
 	int8_t volume;
 	uint8_t fineTune;
-	uint16_t length, loopStart, loopLength, tmpLoopStart;
-	int32_t offset, realLength;
+	uint16_t length, loopStart, loopLength;
+	int32_t offset;
 } moduleSample_t;
 
 typedef struct moduleChannel_t
@@ -278,7 +275,7 @@
 
 typedef struct module_t
 {
-	int8_t *sampleData, *sampleDataUnaligned, currRow, modified, row;
+	int8_t *sampleData, currRow, modified, row;
 	uint8_t currSpeed, moduleLoaded;
 	uint16_t currOrder, currPattern, currBPM;
 	uint32_t rowsCounter, rowsInTotal;
@@ -438,7 +435,7 @@
 		uint16_t *numPtr16, tmpDisp16, *dstOffset, dstPos, textLength, editTextPos;
 		uint16_t dstOffsetEnd, lastSampleOffset;
 		int32_t askTempData, renderX, renderY, renderW, renderH, displayW, displayH;
-		uint32_t xScale, yScale;
+		int32_t xScale, yScale;
 		double dMouseXMul, dMouseYMul;
 		SDL_PixelFormat *pixelFormat;
 #ifdef _WIN32
--- a/src/pt2_helpers.h
+++ b/src/pt2_helpers.h
@@ -13,7 +13,6 @@
 
 #define ALIGN_PTR(p, x) (((uintptr_t)(p) + ((x)-1)) & ~((x)-1))
 #define MALLOC_PAD(size, pad) (malloc((size) + (pad)))
-#define CALLOC_PAD(size, pad) (calloc((size) + (pad), 1))
 
 #define SWAP16(value) \
 ( \
--- a/src/pt2_modloader.c
+++ b/src/pt2_modloader.c
@@ -32,7 +32,7 @@
 	bool _eof;
 	uint8_t *_ptr, *_base;
 	uint32_t _cnt, _bufsiz;
-} mem_t;
+} MEMFILE;
 
 static bool oldAutoPlay;
 static char oldFullPath[(PATH_MAX * 2) + 2];
@@ -41,11 +41,12 @@
 
 extern SDL_Window *window;
 
-static mem_t *mopen(const uint8_t *src, uint32_t length);
-static void mclose(mem_t **buf);
-static int32_t mgetc(mem_t *buf);
-static size_t mread(void *buffer, size_t size, size_t count, mem_t *buf);
-static void mseek(mem_t *buf, int32_t offset, int32_t whence);
+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)
@@ -167,23 +168,35 @@
 	return true;
 }
 
-static int8_t checkModType(const char *buf)
+#define IS_ID(s, b) !strncmp(s, b, 4)
+
+static uint8_t getModType(uint8_t *numChannels, const char *id)
 {
-	     if (!strncmp(buf, "M.K.", 4)) return FORMAT_MK;   // ProTracker v1.x, handled as ProTracker v2.x
-	else if (!strncmp(buf, "M!K!", 4)) return FORMAT_MK2;  // ProTracker v2.x (if >64 patterns)
-	else if (!strncmp(buf, "FLT4", 4)) return FORMAT_FLT4; // StarTrekker (4ch), handled as ProTracker v2.x
-	else if (!strncmp(buf, "1CHN", 4)) return FORMAT_1CHN; // handled as 4ch
-	else if (!strncmp(buf, "2CHN", 4)) return FORMAT_2CHN; // FastTracker II, handled as 4ch
-	else if (!strncmp(buf, "3CHN", 4)) return FORMAT_3CHN; // handled as 4ch
-	else if (!strncmp(buf, "4CHN", 4)) return FORMAT_4CHN; // rare type, not sure what tracker it comes from
-	else if (!strncmp(buf, "N.T.", 4)) return FORMAT_MK;   // NoiseTracker 1.0, handled as ProTracker v2.x
-	else if (!strncmp(buf, "M&K!", 4)) return FORMAT_FEST; // Special NoiseTracker format (used in music disks?)
-	else if (!strncmp(buf, "FEST", 4)) return FORMAT_FEST; // Special NoiseTracker format (used in music disks?)
-	else if (!strncmp(buf, "NSMS", 4)) return FORMAT_MK;   // OpenMPT Load_mod.cpp: "kingdomofpleasure.mod by bee hunter"
-	else if (!strncmp(buf, "LARD", 4)) return FORMAT_MK;   // OpenMPT Load_mod.cpp: "judgement_day_gvine.mod by 4-mat"
-	else if (!strncmp(buf, "PATT", 4)) return FORMAT_MK;   // OpenMPT Load_mod.cpp: "ProTracker 3.6"
+	*numChannels = 4;
 
-	return FORMAT_UNKNOWN; // may be The Ultimate SoundTracker, 15 samples
+	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
@@ -208,115 +221,115 @@
 	}
 }
 
+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 modSig[4], tmpChar;
+	char modID[4], tmpChar;
 	int8_t numSamples;
-	uint8_t ppCrunchData[4], bytes[4], *ppBuffer;
-	uint8_t *modBuffer, ch, row, pattern, channels;
-	uint16_t ciaPeriod;
-	int32_t i, loopStart, loopLength, loopOverflowVal;
-	uint32_t j, PP20, ppPackLen, ppUnpackLen;
-	FILE *fmodule;
-	module_t *newModule;
+	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;
-	mem_t *mod;
 
-	/* these flags are kinda dumb and inaccurate, but we
-	** don't aim for excellent STK import anyway. */
-	veryLateSTKVerFlag = false; // "DFJ SoundTracker III" nad later
+	veryLateSTKVerFlag = false; // "DFJ SoundTracker III" and later
 	lateSTKVerFlag = false; // "TJC SoundTracker II" and later
 	mightBeSTK = false;
 
-	mod = NULL;
-	ppBuffer = NULL;
+	m = NULL;
+	f = NULL;
 	modBuffer = NULL;
-	fmodule = NULL;
-	newModule = NULL;
 
-	newModule = (module_t *)calloc(1, sizeof (module_t));
-	if (newModule == NULL)
+	newMod = (module_t *)calloc(1, sizeof (module_t));
+	if (newMod == NULL)
 	{
 		statusOutOfMemory();
 		goto modLoadError;
 	}
 
-	fmodule = UNICHAR_FOPEN(fileName, "rb");
-	if (fmodule == NULL)
+	f = UNICHAR_FOPEN(fileName, "rb");
+	if (f == NULL)
 	{
 		displayErrorMsg("FILE I/O ERROR !");
 		goto modLoadError;
 	}
 
-	fseek(fmodule, 0, SEEK_END);
-	newModule->head.moduleSize = ftell(fmodule);
-	fseek(fmodule, 0, SEEK_SET);
+	fseek(f, 0, SEEK_END);
+	filesize = ftell(f);
+	rewind(f);
 
 	// check if mod is a powerpacker mod
-	fread(&PP20, 4, 1, fmodule);
-	if (PP20 == 0x30325850) // "PX20"
+	fread(&powerPackerID, 4, 1, f);
+	if (powerPackerID == 0x30325850) // "PX20"
 	{
-		displayErrorMsg("ENCRYPTED PPACK !");
+		displayErrorMsg("ENCRYPTED MOD !");
 		goto modLoadError;
 	}
-	else if (PP20 == 0x30325050) // "PP20"
+	else if (powerPackerID == 0x30325050) // "PP20"
 	{
-		ppPackLen = newModule->head.moduleSize;
-		if (ppPackLen & 3)
-		{
-			displayErrorMsg("POWERPACKER ERROR");
-			goto modLoadError;
-		}
-
-		fseek(fmodule, ppPackLen - 4, SEEK_SET);
-
-		ppCrunchData[0] = (uint8_t)fgetc(fmodule);
-		ppCrunchData[1] = (uint8_t)fgetc(fmodule);
-		ppCrunchData[2] = (uint8_t)fgetc(fmodule);
-		ppCrunchData[3] = (uint8_t)fgetc(fmodule);
-
-		ppUnpackLen = (ppCrunchData[0] << 16) | (ppCrunchData[1] << 8) | ppCrunchData[2];
-
-		// smallest and biggest possible .MOD
-		if (ppUnpackLen < 2108 || ppUnpackLen > 4195326)
-		{
-			displayErrorMsg("NOT A MOD FILE !");
-			goto modLoadError;
-		}
-
-		ppBuffer = (uint8_t *)malloc(ppPackLen);
-		if (ppBuffer == NULL)
-		{
-			statusOutOfMemory();
-			goto modLoadError;
-		}
-
-		modBuffer = (uint8_t *)malloc(ppUnpackLen);
+		modBuffer = unpackPPModule(f, &filesize);
 		if (modBuffer == NULL)
-		{
-			statusOutOfMemory();
-			goto modLoadError;
-		}
-
-		fseek(fmodule, 0, SEEK_SET);
-		fread(ppBuffer, 1, ppPackLen, fmodule);
-		fclose(fmodule);
-		ppdecrunch(ppBuffer + 8, modBuffer, ppBuffer + 4, ppPackLen - 12, ppUnpackLen, ppCrunchData[3]);
-		free(ppBuffer);
-		newModule->head.moduleSize = ppUnpackLen;
+			goto modLoadError; // error msg is set in unpackPPModule()
 	}
 	else
 	{
-		// smallest and biggest possible PT .MOD
-		if (newModule->head.moduleSize < 2108 || newModule->head.moduleSize > 4195326)
-		{
-			displayErrorMsg("NOT A MOD FILE !");
-			goto modLoadError;
-		}
-
-		modBuffer = (uint8_t *)malloc(newModule->head.moduleSize);
+		modBuffer = (uint8_t *)malloc(filesize);
 		if (modBuffer == NULL)
 		{
 			statusOutOfMemory();
@@ -323,61 +336,79 @@
 			goto modLoadError;
 		}
 
-		fseek(fmodule, 0, SEEK_SET);
-		fread(modBuffer, 1, newModule->head.moduleSize, fmodule);
-		fclose(fmodule);
+		fseek(f, 0, SEEK_SET);
+		fread(modBuffer, 1, filesize, f);
+		fclose(f);
 	}
 
-	mod = mopen(modBuffer, newModule->head.moduleSize);
-	if (mod == NULL)
+	// smallest and biggest possible PT .MOD
+	if (filesize < 2108 || filesize > 4195326)
 	{
-		displayErrorMsg("FILE I/O ERROR !");
+		displayErrorMsg("NOT A MOD FILE !");
 		goto modLoadError;
 	}
 
-	// check module tag
-	mseek(mod, 0x0438, SEEK_SET);
-	mread(modSig, 1, 4, mod);
+	// Use MEMFILE functions on module buffer (similar to FILE functions)
 
-	newModule->head.format = checkModType(modSig);
-	if (newModule->head.format == FORMAT_UNKNOWN)
-		mightBeSTK = true;
+	m = mopen(modBuffer, filesize);
+	if (m == NULL)
+	{
+		displayErrorMsg("FILE I/O ERROR !");
+		goto modLoadError;
+	}
 
-	     if (newModule->head.format == FORMAT_1CHN) channels = 1;
-	else if (newModule->head.format == FORMAT_2CHN) channels = 2;
-	else if (newModule->head.format == FORMAT_3CHN) channels = 3;
-	else channels = 4;
+	// check magic ID
+	memset(modID, 0, 4); // in case mread fails
+	mseek(m, 1080, SEEK_SET);
+	mread(modID, 1, 4, m);
 
-	mseek(mod, 0, SEEK_SET);
+	modFormat = getModType(&numChannels, modID);
+	if (numChannels == 0 || numChannels > AMIGA_VOICES)
+	{
+		displayErrorMsg("UNSUPPORTED MOD !");
+		goto modLoadError;
+	}
 
-	mread(newModule->head.moduleTitle, 1, 20, mod);
-	newModule->head.moduleTitle[20] = '\0';
+	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 = newModule->head.moduleTitle[i];
+		tmpChar = newMod->head.moduleTitle[i];
 		if ((tmpChar < ' ' || tmpChar > '~') && tmpChar != '\0')
 			tmpChar = ' ';
 
-		newModule->head.moduleTitle[i] = (char)tolower(tmpChar);
+		newMod->head.moduleTitle[i] = (char)tolower(tmpChar);
 	}
 
-	fixZeroesInString(newModule->head.moduleTitle, 20);
+	fixZeroesInString(newMod->head.moduleTitle, 20);
 
-	// read sample information
-	for (i = 0; i < MOD_SAMPLES; i++)
+	// read sample headers
+	s = newMod->samples;
+	for (i = 0; i < MOD_SAMPLES; i++, s++)
 	{
-		s = &newModule->samples[i];
+		if (mightBeSTK && i >= 15) // skip reading sample headers past sample slot 15 in STK/UST modules
+		{
+			s->loopLength = 2; // this be set though
+			continue;
+		}
 
-		if (mightBeSTK && i >= 15)
+		mread(s->text, 1, 22, m);
+		s->text[22] = '\0';
+
+		if (modFormat == FORMAT_HMNT)
 		{
-			s->loopLength = 2;
+			// most of "His Master's Noisetracker" songs have junk sample names, so let's wipe it.
+			memset(s->text, 0, 22);
 		}
 		else
 		{
-			mread(s->text, 1, 22, mod);
-			s->text[22] = '\0';
-
+			// convert illegal sample name characters to space
 			for (j = 0; j < 22; j++)
 			{
 				tmpChar = s->text[j];
@@ -388,165 +419,140 @@
 			}
 
 			fixZeroesInString(s->text, 22);
+		}
 
-			s->realLength = ((mgetc(mod) << 8) | mgetc(mod)) * 2;
-			if (s->realLength > MAX_SAMPLE_LEN)
-				s->length = MAX_SAMPLE_LEN;
-			else
-				s->length = (uint16_t)s->realLength;
+		s->length = ((mgetc(m) << 8) | mgetc(m)) * 2;
 
-			if (s->length > 9999)
-				lateSTKVerFlag = true; // Only used if mightBeSTK is set
+		/* 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 (newModule->head.format == FORMAT_FEST)
-				s->fineTune = (uint8_t)((-mgetc(mod) & 0x1F) / 2); // One more bit of precision, + inverted
-			else
-				s->fineTune = (uint8_t)mgetc(mod) & 0x0F;
+		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;
 
-			s->volume = (int8_t)mgetc(mod);
-			s->volume = CLAMP(s->volume, 0, 64);
+		if (mightBeSTK)
+			s->fineTune = 0; // this is high byte of volume in STK/UST (has no finetune), set to zero
 
-			loopStart = ((mgetc(mod) << 8) | mgetc(mod)) * 2;
-			loopLength = ((mgetc(mod) << 8) | mgetc(mod)) * 2;
+		s->volume = (int8_t)mgetc(m);
+		if ((uint8_t)s->volume > 64)
+			s->volume = 64;
 
-			if (loopLength < 2)
-				loopLength = 2; // fixes empty samples in .MODs saved from FT2
+		loopStart = ((mgetc(m) << 8) | mgetc(m)) * 2;
+		loopLength = ((mgetc(m) << 8) | mgetc(m)) * 2;
 
-			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;
-			}
+		if (loopLength < 2)
+			loopLength = 2; // fixes empty samples in .MODs saved from FT2
 
-			if (mightBeSTK)
-				s->loopStart /= 2;
-
-			// fix for poorly converted STK->PTMOD modules.
-			if (!mightBeSTK && s->loopLength > 2 && s->loopStart+s->loopLength > s->length)
-			{
-				if ((s->loopStart/2) + s->loopLength <= s->length)
-					s->loopStart /= 2;
-			}
-
-			if (mightBeSTK)
-			{
-				if (s->loopLength > 2 && s->loopStart < s->length)
-				{
-					s->tmpLoopStart = s->loopStart; // for sample data reading later on
-					s->length -= s->loopStart;
-					s->realLength -= s->loopStart;
-					s->loopStart = 0;
-				}
-
-				// no finetune in STK/UST
-				s->fineTune = 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 += loopOverflowVal; // this is safe, we're calloc()'ing 65535*(31+2) bytes
-				}
-				else
-				{
-					s->loopStart = 0;
-					s->loopLength = 2;
-				}
-			}
+		// 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;
 		}
-	}
-
-	// STK 2.5 had loopStart in words, not bytes. Convert if late version STK.
-	if (mightBeSTK && lateSTKVerFlag)
-	{
-		for (i = 0; i < 15; i++)
+		else
 		{
-			s = &newModule->samples[i];
-			if (s->loopStart > 2)
-			{
-				s->length -= s->tmpLoopStart;
-				s->tmpLoopStart *= 2;
-			}
+			s->loopStart = (uint16_t)loopStart;
+			s->loopLength = (uint16_t)loopLength;
 		}
-	}
 
-	newModule->head.orderCount = (uint8_t)mgetc(mod);
+		/* Ultimate SoundTracker before version 2.5 had loopStart in bytes, not words
+		** XXX: This has to be verified... It's possible that it was before that,
+		** and that this breaks some modules.
+		*/
+		if (mightBeSTK && !lateSTKVerFlag)
+			s->loopStart /= 2;
 
-	// fixes beatwave.mod (129 orders) and other weird MODs
-	if (newModule->head.orderCount > 128)
-	{
-		if (newModule->head.orderCount > 129)
+		// 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)
 		{
-			displayErrorMsg("NOT A MOD FILE !");
-			goto modLoadError;
+			if ((s->loopStart/2) + s->loopLength <= s->length)
+				s->loopStart /= 2;
 		}
+	}
 
-		newModule->head.orderCount = 128;
+	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 (newModule->head.orderCount == 0)
+	if (newMod->head.orderCount == 0)
 	{
 		displayErrorMsg("NOT A MOD FILE !");
 		goto modLoadError;
 	}
 
-	newModule->head.restartPos = (uint8_t)mgetc(mod);
-	if (mightBeSTK && (newModule->head.restartPos == 0 || newModule->head.restartPos > 220))
+	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 definitely a proper The Ultimate SoundTracker (STK) module. */
+		** then it's most likely a proper The Ultimate SoundTracker (STK/UST) module.
+		*/
+		modFormat = FORMAT_STK;
 
-		newModule->head.format = FORMAT_STK;
+		if (restartPos == 0)
+			restartPos = 120;
 
-		if (newModule->head.restartPos != 120) // 120 is a special case and means 50Hz (125BPM)
+		// 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 (newModule->head.restartPos > 239)
-				newModule->head.restartPos = 239;
+			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);
 
-			ciaPeriod = (240 - newModule->head.restartPos) * 122;
-			newModule->head.initBPM = (uint16_t)round(((double)CIA_PAL_CLK / ciaPeriod) * (125.0 / 50.0));
+			newMod->head.initialTempo = (uint16_t)BPM;
 		}
-
-		newModule->head.restartPos = 0;
 	}
 
+	// read orders and count number of patterns
+	numPatterns = 0;
 	for (i = 0; i < MOD_ORDERS; i++)
 	{
-		newModule->head.order[i] = (int16_t)mgetc(mod);
-		if (newModule->head.order[i] > newModule->head.patternCount)
-			newModule->head.patternCount = newModule->head.order[i];
+		newMod->head.order[i] = (int16_t)mgetc(m);
+		if (newMod->head.order[i] > numPatterns)
+			numPatterns = newMod->head.order[i];
 	}
+	numPatterns++;
 
-	if (++newModule->head.patternCount > MAX_PATTERNS)
+	if (numPatterns > MAX_PATTERNS)
 	{
 		displayErrorMsg("UNSUPPORTED MOD !");
 		goto modLoadError;
 	}
 
-	if (newModule->head.format != FORMAT_STK) // The Ultimate SoundTracker MODs doesn't have this tag
-		mseek(mod, 4, SEEK_CUR); // we already read/tested the tag earlier, skip it
+	// skip magic ID (The Ultimate SoundTracker MODs doesn't have it)
+	if (modFormat != FORMAT_STK)
+		mseek(m, 4, SEEK_CUR);
 
-	// init 100 patterns and load patternCount of patterns
-	for (pattern = 0; pattern < MAX_PATTERNS; pattern++)
+	// allocate 100 patterns
+	for (i = 0; i < MAX_PATTERNS; i++)
 	{
-		newModule->patterns[pattern] = (note_t *)calloc(MOD_ROWS * AMIGA_VOICES, sizeof (note_t));
-		if (newModule->patterns[pattern] == NULL)
+		newMod->patterns[i] = (note_t *)calloc(MOD_ROWS * AMIGA_VOICES, sizeof (note_t));
+		if (newMod->patterns[i] == NULL)
 		{
 			statusOutOfMemory();
 			goto modLoadError;
@@ -554,21 +560,21 @@
 	}
 
 	// load pattern data
-	for (pattern = 0; pattern < newModule->head.patternCount; pattern++)
+	for (i = 0; i < numPatterns; i++)
 	{
-		note = newModule->patterns[pattern];
-		for (row = 0; row < MOD_ROWS; row++)
+		note = newMod->patterns[i];
+		for (j = 0; j < MOD_ROWS; j++)
 		{
-			for (ch = 0; ch < channels; ch++)
+			for (k = 0; k < numChannels; k++, note++)
 			{
-				mread(bytes, 1, 4, mod);
+				mread(bytes, 1, 4, m);
 
 				note->period = ((bytes[0] & 0x0F) << 8) | bytes[1];
-				note->sample = (bytes[0] & 0xF0) | (bytes[2] >> 4); // don't (!) clamp, the player checks for invalid samples
+				note->sample = (bytes[0] & 0xF0) | (bytes[2] >> 4);
 				note->command = bytes[2] & 0x0F;
 				note->param = bytes[3];
 
-				if (mightBeSTK)
+				if (modFormat == FORMAT_STK)
 				{
 					if (note->command == 0xC || note->command == 0xD || note->command == 0xE)
 					{
@@ -583,39 +589,45 @@
 						veryLateSTKVerFlag = true;
 					}
 				}
-
-				note++;
 			}
 
-			if (channels < 4)
-				note += AMIGA_VOICES - channels;
+			if (numChannels < AMIGA_VOICES)
+				note += AMIGA_VOICES-numChannels;
 		}
 	}
 
-	/* TODO: Find out if song is FORMAT_NT through heuristics
-	** Only detected for FEST songs for now. */
-
-	// pattern command conversion
-	if (mightBeSTK || newModule->head.format == FORMAT_4CHN ||
-		newModule->head.format == FORMAT_NT || newModule->head.format == FORMAT_FEST)
+	// pattern command conversion for non-PT formats
+	if (modFormat == FORMAT_STK || modFormat == FORMAT_FT2 || modFormat == FORMAT_NT || modFormat == FORMAT_HMNT || modFormat == FORMAT_FLT)
 	{
-		for (pattern = 0; pattern < newModule->head.patternCount; pattern++)
+		for (i = 0; i < numPatterns; i++)
 		{
-			note = newModule->patterns[pattern];
-			for (j = 0; j < MOD_ROWS*4; j++)
+			note = newMod->patterns[i];
+			for (j = 0; j < MOD_ROWS*4; j++, note++)
 			{
-				if (newModule->head.format == FORMAT_NT || newModule->head.format == FORMAT_FEST)
+				if (modFormat == FORMAT_NT || modFormat == FORMAT_HMNT)
 				{
-					// any Dxx == D00 in N.T./FEST modules
+					// any Dxx == D00 in NT/HMNT
 					if (note->command == 0xD)
 						note->param = 0;
 
-					// effect F with param 0x00 does nothing in NT
+					// effect F with param 0x00 does nothing in NT/HMNT
 					if (note->command == 0xF && note->param == 0)
 						note->command = 0;
 				}
-				else if (mightBeSTK)
+				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)
@@ -661,85 +673,129 @@
 							}
 						}
 					}
-				}
-				else if (newModule->head.format == FORMAT_4CHN) // 4CHN != PT MOD
-				{
-					// remove E8x (pan) commands as these are Karplus-Strong in ProTracker
-					if (note->command == 0xE && (note->param >> 4) == 0x8)
-					{
-						note->command = 0;
-						note->param = 0;
-					}
 
-					// effect F with param 0x00 does nothing in these 4CHN formats
+					// effect F with param 0x00 does nothing in UST/STK (I think?)
 					if (note->command == 0xF && note->param == 0)
-					{
 						note->command = 0;
-						note->param = 0;
-					}
 				}
 
-				note++;
+				// 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;
+				}
 			}
 		}
 	}
 
-	// set static sample data pointers (sample data = one huge buffer internally)
-	for (i = 0; i < MOD_SAMPLES; i++)
-		newModule->samples[i].offset = MAX_SAMPLE_LEN * i;
-
-	// +2 sample slots for overflow safety (Paula and scopes)
-	newModule->sampleDataUnaligned = (int8_t *)CALLOC_PAD((MOD_SAMPLES + 2) * MAX_SAMPLE_LEN, 256);
-	if (newModule->sampleDataUnaligned == NULL)
+	// 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;
 	}
 
-	newModule->sampleData = (int8_t *)ALIGN_PTR(newModule->sampleDataUnaligned, 256);
+	// 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 = (newModule->head.format == FORMAT_STK) ? 15 : 31;
-	for (i = 0; i < numSamples; i++)
+	numSamples = (modFormat == FORMAT_STK) ? 15 : 31;
+	s = newMod->samples;
+	for (i = 0; i < numSamples; i++, s++)
 	{
-		s = &newModule->samples[i];
-		if (mightBeSTK && (s->loopLength > 2 && s->loopLength < s->length))
-			mseek(mod, s->tmpLoopStart, SEEK_CUR);
+		uint32_t bytesToSkip = 0;
 
-		mread(&newModule->sampleData[s->offset], 1, s->length, mod);
-		if (s->realLength > s->length)
-			mseek(mod, s->realLength - s->length, SEEK_CUR);
+		/* 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;
+		}
 
-		// fix beeping samples
-		if (s->length >= 2 && s->loopStart+s->loopLength <= 2)
+		/* 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)
 		{
-			newModule->sampleData[s->offset+0] = 0;
-			newModule->sampleData[s->offset+1] = 0;
+			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 += loopOverflowVal; // this is safe, we're allocating 65534 bytes per sample slot
+			}
+			else
+			{
+				s->loopStart = 0;
+				s->loopLength = 2;
+			}
+		}
 	}
 
-	mclose(&mod);
+	mclose(&m);
 	free(modBuffer);
 
 	for (i = 0; i < AMIGA_VOICES; i++)
-		newModule->channels[i].n_chanindex = i;
+		newMod->channels[i].n_chanindex = i;
 
-	return newModule;
+	return newMod;
 
 modLoadError:
-	if (mod != NULL) mclose(&mod);
-	if (modBuffer != NULL) free(modBuffer);
-	if (ppBuffer != NULL) free(ppBuffer);
+	if (m != NULL)
+		mclose(&m);
 
-	if (newModule != NULL)
+	if (modBuffer != NULL)
+		free(modBuffer);
+
+	if (newMod != NULL)
 	{
 		for (i = 0; i < MAX_PATTERNS; i++)
 		{
-			if (newModule->patterns[i] != NULL)
-				free(newModule->patterns[i]);
+			if (newMod->patterns[i] != NULL)
+				free(newMod->patterns[i]);
 		}
 
-		free(newModule);
+		free(newMod);
 	}
 
 	return NULL;
@@ -860,14 +916,14 @@
 	return modSave(fileName);
 }
 
-static mem_t *mopen(const uint8_t *src, uint32_t length)
+static MEMFILE *mopen(const uint8_t *src, uint32_t length)
 {
-	mem_t *b;
+	MEMFILE *b;
 
 	if (src == NULL || length == 0)
 		return NULL;
 
-	b = (mem_t *)malloc(sizeof (mem_t));
+	b = (MEMFILE *)malloc(sizeof (MEMFILE));
 	if (b == NULL)
 		return NULL;
 
@@ -880,7 +936,7 @@
 	return b;
 }
 
-static void mclose(mem_t **buf)
+static void mclose(MEMFILE **buf)
 {
 	if (*buf != NULL)
 	{
@@ -889,7 +945,7 @@
 	}
 }
 
-static int32_t mgetc(mem_t *buf)
+static int32_t mgetc(MEMFILE *buf)
 {
 	int32_t b;
 
@@ -911,7 +967,7 @@
 	return (int32_t)b;
 }
 
-static size_t mread(void *buffer, size_t size, size_t count, mem_t *buf)
+static size_t mread(void *buffer, size_t size, size_t count, MEMFILE *buf)
 {
 	int32_t pcnt;
 	size_t wrcnt;
@@ -936,10 +992,10 @@
 		buf->_eof = true;
 	}
 
-	return pcnt/size;
+	return pcnt / size;
 }
 
-static void mseek(mem_t *buf, int32_t offset, int32_t whence)
+static void mseek(MEMFILE *buf, int32_t offset, int32_t whence)
 {
 	if (buf == NULL)
 		return;
@@ -948,9 +1004,9 @@
 	{
 		switch (whence)
 		{
-			case SEEK_SET: buf->_ptr  = buf->_base + offset; break;
+			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;
+			case SEEK_END: buf->_ptr = buf->_base + buf->_bufsiz + offset; break;
 			default: break;
 		}
 
@@ -957,33 +1013,43 @@
 		buf->_eof = false;
 		if (buf->_ptr >= buf->_base+buf->_bufsiz)
 		{
-			buf->_ptr = buf->_base+buf->_bufsiz;
+			buf->_ptr = buf->_base + buf->_bufsiz;
 			buf->_eof = true;
 		}
 
-		buf->_cnt = (buf->_base+buf->_bufsiz) - buf->_ptr;
+		buf->_cnt = (buf->_base + buf->_bufsiz) - buf->_ptr;
 	}
 }
 
-/* Code taken from Heikki Orsila's amigadepack. Seems to have no license,
-** so I'll assume it fits into wtfpl (wtfpl.net). Heikki should contact me
-** if it shall not.
-** Modified by 8bitbubsy */
+static void mrewind(MEMFILE *buf)
+{
+	mseek(buf, 0, SEEK_SET);
+}
 
-#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;                        \
-  }                                         \
+/* 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;
@@ -1017,7 +1083,6 @@
 			while (todo--)
 			{
 				PP_READ_BITS(8, x);
-
 				if (out <= dst)
 					return false;
 
@@ -1030,7 +1095,6 @@
 		}
 
 		PP_READ_BITS(2, x);
-
 		offBits = offsetLens[x];
 		todo = x + 2;
 
@@ -1058,7 +1122,6 @@
 		while (todo--)
 		{
 			x = out[offset];
-
 			if (out <= dst)
 				return false;
 
@@ -1114,13 +1177,11 @@
 
 	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
 
-	if (modEntry->head.initBPM > 0)
-		modSetTempo(modEntry->head.initBPM);
-	else
-		modSetTempo(125);
-
 	updateCurrSample();
 	editor.samplePos = 0;
 	updateSamplePos();
@@ -1136,7 +1197,7 @@
 
 	filenameLen = (uint32_t)strlen(arg);
 
-	filenameU = (UNICHAR *)calloc((filenameLen + 2), sizeof (UNICHAR));
+	filenameU = (UNICHAR *)calloc(filenameLen + 2, sizeof (UNICHAR));
 	if (filenameU == NULL)
 	{
 		statusOutOfMemory();
@@ -1208,9 +1269,8 @@
 	UNICHAR *fullPathU;
 
 	// don't allow drag n' drop if the tracker is busy
-	if (editor.ui.pointerMode == POINTER_MODE_MSG1 ||
-		editor.diskop.isFilling || editor.isWAVRendering ||
-		editor.ui.samplerFiltersBoxShown || editor.ui.samplerVolBoxShown)
+	if (editor.ui.pointerMode == POINTER_MODE_MSG1 || editor.diskop.isFilling ||
+		editor.isWAVRendering || editor.ui.samplerFiltersBoxShown || editor.ui.samplerVolBoxShown)
 	{
 		return;
 	}
@@ -1222,7 +1282,7 @@
 		return;
 	}
 
-	fullPathU = (UNICHAR *)calloc((fullPathLen + 2), sizeof (UNICHAR));
+	fullPathU = (UNICHAR *)calloc(fullPathLen + 2, sizeof (UNICHAR));
 	if (fullPathU == NULL)
 	{
 		statusOutOfMemory();
@@ -1364,14 +1424,11 @@
 	}
 
 	// +2 sample slots for overflow safety (Paula and scopes)
-	newMod->sampleDataUnaligned = (int8_t *)CALLOC_PAD((MOD_SAMPLES + 2) * MAX_SAMPLE_LEN, 256);
-	if (newMod->sampleDataUnaligned == NULL)
+	newMod->sampleData = (int8_t *)calloc(MOD_SAMPLES + 2, MAX_SAMPLE_LEN);
+	if (newMod->sampleData == NULL)
 		goto oom;
 
-	newMod->sampleData = (int8_t *)ALIGN_PTR(newMod->sampleDataUnaligned, 256);
-
 	newMod->head.orderCount = 1;
-	newMod->head.patternCount = 1;
 
 	for (i = 0; i < MOD_SAMPLES; i++)
 	{
--- a/src/pt2_modplayer.c
+++ b/src/pt2_modplayer.c
@@ -322,7 +322,7 @@
 	{
 		editor.modTick = 0;
 
-		if ((editor.timingMode == TEMPO_MODE_VBLANK) || ((ch->n_cmd & 0xFF) < 32))
+		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
@@ -1354,7 +1354,6 @@
 		editor.swapChannelFlag = false;
 
 		modEntry->head.orderCount = 1;
-		modEntry->head.patternCount = 1;
 
 		for (i = 0; i < MAX_PATTERNS; i++)
 			memset(modEntry->patterns[i], 0, (MOD_ROWS * AMIGA_VOICES) * sizeof (note_t));
@@ -1447,10 +1446,10 @@
 			free(modEntry->patterns[i]);
 	}
 
-	if (modEntry->sampleDataUnaligned != NULL)
+	if (modEntry->sampleData != NULL)
 	{
 		clearPaulaAndScopes();
-		free(modEntry->sampleDataUnaligned);
+		free(modEntry->sampleData);
 	}
 
 	free(modEntry);
--- a/src/pt2_sampleloader.c
+++ b/src/pt2_sampleloader.c
@@ -7,7 +7,6 @@
 #include <stdlib.h>
 #include <stdint.h>
 #include <stdbool.h>
-#include <math.h> // round()/roundf()
 #include <ctype.h> // tolower()/toupper()
 #ifdef _WIN32
 #include <io.h>
@@ -847,7 +846,7 @@
 	if (sampleVolume > 65536)
 		sampleVolume = 65536;
 
-	sampleVolume = (int32_t)round(sampleVolume / 1024.0);
+	sampleVolume = (int32_t)((sampleVolume / 1024.0) + 0.5);
 	if (sampleVolume > 64)
 		sampleVolume = 64;
 
@@ -1079,7 +1078,7 @@
 	exp -= 16383;
 
 	dOut = ldexp(lo, -31 + exp) + ldexp(hi, -63 + exp);
-	return (int32_t)round(dOut);
+	return (int32_t)(dOut + 0.5);
 }
 
 bool loadAIFFSample(UNICHAR *fileName, char *entryName, int8_t forceDownSampling)
--- a/src/pt2_sampler.c
+++ b/src/pt2_sampler.c
@@ -7,7 +7,6 @@
 #include <stdlib.h>
 #include <stdint.h>
 #include <stdbool.h>
-#include <math.h> // roundf()
 #include "pt2_header.h"
 #include "pt2_helpers.h"
 #include "pt2_textout.h"
@@ -2101,19 +2100,19 @@
 
 void samplerZoomInMouseWheel(void)
 {
-	int32_t step = (int32_t)round(editor.sampler.samDisplay / 10.0);
+	int32_t step = (int32_t)((editor.sampler.samDisplay / 10.0f) + 0.5f);
 	samplerZoomIn(step, input.mouse.x);
 }
 
 void samplerZoomOutMouseWheel(void)
 {
-	int32_t step = (int32_t)round(editor.sampler.samDisplay / 10.0);
+	int32_t step = (int32_t)((editor.sampler.samDisplay / 10.0f) + 0.5f);
 	samplerZoomOut(step, input.mouse.x);
 }
 
 void samplerZoomOut2x(void)
 {
-	int32_t step= (int32_t)round(editor.sampler.samDisplay / 2.0);
+	int32_t step = (int32_t)((editor.sampler.samDisplay / 2.0f) + 0.5f);
 	samplerZoomOut(step, SCREEN_W / 2);
 }
 
@@ -2281,7 +2280,7 @@
 		if (tmp32 < 0)
 			tmp32 = 0;
 
-		tmp32 = (int32_t)round((tmp32 * editor.sampler.samLength) / 311.0);
+		tmp32 = (int32_t)(((tmp32 * editor.sampler.samLength) / 311.0) + 0.5);
 		if (tmp32+editor.sampler.samDisplay <= editor.sampler.samLength)
 		{
 			if (tmp32 == editor.sampler.samOffset)
--- a/src/pt2_scopes.c
+++ b/src/pt2_scopes.c
@@ -386,9 +386,7 @@
 	scopeTimeLen = (uint32_t)dInt;
 
 	// fractional part scaled to 0..2^32-1
-	dFrac *= UINT32_MAX + 1.0;
-	if (dFrac > (double)UINT32_MAX)
-		dFrac = (double)UINT32_MAX;
+	dFrac *= UINT32_MAX;
 	scopeTimeLenFrac = (uint32_t)(dFrac + 0.5);
 
 	scopeThread = SDL_CreateThread(scopeThreadFunc, NULL, NULL);
--- a/src/pt2_unicode.c
+++ b/src/pt2_unicode.c
@@ -17,7 +17,7 @@
 #ifdef _WIN32
 		if (ch >= 256)
 #else
-		if ((signed)ch < 0)
+		if ((uint8_t)ch > 127)
 #endif
 		{
 			*dstBuffer++ = '?';
--- a/src/pt2_visuals.c
+++ b/src/pt2_visuals.c
@@ -101,11 +101,7 @@
 	editor.vblankTimeLen = (uint32_t)dInt;
 
 	// fractional part scaled to 0..2^32-1
-
-	dFrac *= UINT32_MAX + 1.0;
-	if (dFrac > (double)UINT32_MAX)
-		dFrac = (double)UINT32_MAX;
-
+	dFrac *= UINT32_MAX;
 	editor.vblankTimeLenFrac = (uint32_t)(dFrac + 0.5);
 }
 
@@ -546,27 +542,26 @@
 		textOutBg(pixelBuffer, 264, 123, "      ", palette[PAL_GENBKG], palette[PAL_GENBKG]);
 
 		// calculate module length
-		modEntry->head.totalSampleSize = 0;
+		uint32_t totalSampleDataSize = 0;
 		for (i = 0; i < MOD_SAMPLES; i++)
-			modEntry->head.totalSampleSize += modEntry->samples[i].length;
+			totalSampleDataSize += modEntry->samples[i].length;
 
-		modEntry->head.patternCount = 0;
+		uint32_t totalPatterns = 0;
 		for (i = 0; i < MOD_ORDERS; i++)
 		{
-			if (modEntry->head.order[i] > modEntry->head.patternCount)
-				modEntry->head.patternCount = modEntry->head.order[i];
+			if (modEntry->head.order[i] > totalPatterns)
+				totalPatterns = modEntry->head.order[i];
 		}
 
-		modEntry->head.moduleSize = 2108 + modEntry->head.totalSampleSize + (modEntry->head.patternCount * 1024);
-
-		if (modEntry->head.moduleSize > 999999)
+		uint32_t moduleSize = 2108 + (totalPatterns * 1024) + totalSampleDataSize;
+		if (moduleSize > 999999)
 		{
 			charOut(pixelBuffer, 304, 123, 'K', palette[PAL_GENTXT]);
-			printFourDecimals(pixelBuffer, 272, 123, modEntry->head.moduleSize / 1000, palette[PAL_GENTXT]);
+			printFourDecimals(pixelBuffer, 272, 123, moduleSize / 1000, palette[PAL_GENTXT]);
 		}
 		else
 		{
-			printSixDecimals(pixelBuffer, 264, 123, modEntry->head.moduleSize, palette[PAL_GENTXT]);
+			printSixDecimals(pixelBuffer, 264, 123, moduleSize, palette[PAL_GENTXT]);
 		}
 	}
 
@@ -2755,8 +2750,8 @@
 	}
 
 	// for mouse cursor creation
-	editor.ui.xScale = (uint32_t)round(editor.ui.renderW / (double)SCREEN_W);
-	editor.ui.yScale = (uint32_t)round(editor.ui.renderH / (double)SCREEN_H);
+	editor.ui.xScale = (int32_t)((editor.ui.renderW / (double)SCREEN_W) + 0.5);
+	editor.ui.yScale = (int32_t)((editor.ui.renderH / (double)SCREEN_H) + 0.5);
 	createMouseCursors();
 }
 
--- a/vs2019_project/pt2-clone/pt2-clone.vcxproj
+++ b/vs2019_project/pt2-clone/pt2-clone.vcxproj
@@ -205,6 +205,7 @@
       <RuntimeTypeInfo>false</RuntimeTypeInfo>
       <OpenMPSupport>false</OpenMPSupport>
       <LanguageStandard>stdcpplatest</LanguageStandard>
+      <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
     </ClCompile>
     <Link>
       <SubSystem>Windows</SubSystem>
@@ -245,6 +246,7 @@
       <RuntimeTypeInfo>false</RuntimeTypeInfo>
       <OpenMPSupport>false</OpenMPSupport>
       <LanguageStandard>stdcpplatest</LanguageStandard>
+      <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
     </ClCompile>
     <Link>
       <SubSystem>Windows</SubSystem>