shithub: ft2-clone

Download patch

ref: fe46cb47df86f1e740dce81e820ade70031902db
parent: 8eb89a1cfaf049ba6d97626ae4e749fb1c328770
author: Olav Sørensen <olav.sorensen@live.no>
date: Thu Jan 30 12:06:07 EST 2020

Pushed v1.07 code

- Bugfix: After deleting the very last vol/pan envelope point, the currently
  selected point wouldn't be properly set. This is actually a behavior/bug
  from real FT2, but I wanted to fix it anyway.
- Bugfix: Attempting to add a vol/pan envelope point to a completely empty
  envelope would mess things up. Empty envelopes in an allocated instrument
  shouldn't happen, but it happens when loading certain non-FT2 XMs.
- Bugfix: The envelope plotter could display garbage on envelopes with tick
  offsets above 324. Now it just cuts off at the end instead. Also yes, such
  envelopes can be made! OpenMPT, f.ex., has no 0..324 limit for envelope ticks
  in XM mode.
- Bugfix: A couple of system request dialogs had the wrong button captions.
  (Yes/No instead of OK/Cancel).
- When pressing Esc. and the song is unmodified/saved, you'll now get the
  classic joke quit dialogs from FT2 asking if you really want to quit.
- Some minor optimizations and minor fixups. Nothing to write home about...
- Windows 32-bit: This version now requires your CPU to have the SSE2
  instruction set. Intel CPUs from around 2000 (AMD around 2003) and later
  have it. In other words, it's not worth my time trying to make the clone
  run on such old machines!

--- a/.gitignore
+++ b/.gitignore
@@ -15,3 +15,5 @@
 vs2019_project/ft2-clone/Release/ft2-clone.vcxproj.FileListAbsolute.txt
 vs2019_project/ft2-clone/x64/Debug/ft2-clone.vcxproj.FileListAbsolute.txt
 *.cod
+vs2019_project/ft2-clone/Debug/ft2-clone.vcxproj.FileListAbsolute.txt
+*.opendb
--- a/src/ft2_about.c
+++ b/src/ft2_about.c
@@ -7,7 +7,7 @@
 
 // ported from original FT2 code
 
-#define NUM_STARS 650
+#define NUM_STARS 512
 #define ABOUT_SCREEN_W 626
 #define ABOUT_SCREEN_H 167
 #define FT2_LOGO_W 449
@@ -231,6 +231,8 @@
 
 void showAboutScreen(void) // called once when About screen is opened
 {
+#define TEXT_BORDER_COL 0x2E2E2E
+
 	const char *infoString = "Clone by Olav \"8bitbubsy\" S\025rensen - https://16-bits.org";
 	char verText[32];
 	uint16_t x, y;
@@ -246,16 +248,18 @@
 	showPushButton(PB_EXIT_ABOUT);
 
 	blit32(91, 31, ft2Logo, FT2_LOGO_W, FT2_LOGO_H);
-    blit(146, 113, aboutText, ABOUT_TEXT_W, ABOUT_TEXT_H);
+	blit(146, 113, aboutText, ABOUT_TEXT_W, ABOUT_TEXT_H);
 
+	setCustomPalColor(TEXT_BORDER_COL); // sets PAL_CUSTOM
+
 	x = 5 + (SCREEN_W - textWidth(infoString)) / 2;
 	y = 147;
-	textOutBorder(x, y, PAL_FORGRND, PAL_BUTTON2, infoString);
+	textOutBorder(x, y, PAL_FORGRND, PAL_CUSTOM, infoString);
 
 	sprintf(verText, "v%s (compiled on %s)", PROG_VER_STR, __DATE__);
 	x = ((3 + ABOUT_SCREEN_W) - textWidth(verText)) / 2;
 	y = (3 + ABOUT_SCREEN_H) - ((FONT1_CHAR_H - 2) + 3);
-	textOutBorder(x, y, PAL_FORGRND, PAL_BUTTON2, verText);
+	textOutBorder(x, y, PAL_FORGRND, PAL_CUSTOM, verText);
 
 	aboutInit();
 
--- a/src/ft2_audio.c
+++ b/src/ft2_audio.c
@@ -20,7 +20,7 @@
 
 static int8_t pmpCountDiv, pmpChannels = 2;
 static uint16_t smpBuffSize;
-static int32_t masterVol, amp, oldAudioFreq, speedVal, pmpLeft, randSeed = INITIAL_DITHER_SEED;
+static int32_t masterVol, masterAmp, oldAudioFreq, speedVal, pmpLeft, randSeed = INITIAL_DITHER_SEED;
 static uint32_t tickTimeLen, tickTimeLenFrac, oldSFrq, oldSFrqRev = 0xFFFFFFFF;
 static float fAudioAmpMul;
 static voice_t voice[MAX_VOICES * 2];
@@ -124,9 +124,9 @@
 
 	// calculate channel amp
 
-	if (amp != ampFactor)
+	if (masterAmp != ampFactor)
 	{
-		amp = ampFactor;
+		masterAmp = ampFactor;
 
 		// make all channels update volume because of amp change
 		for (uint32_t i = 0; i < song.antChn; i++)
@@ -164,10 +164,18 @@
 		// number of samples per tick -> tick length for performance counter
 		dFrac = modf(speedVal * audio.dSpeedValMul, &dInt);
 
-		// integer part
-		tickTimeLen = (uint32_t)dInt;
+		/* - integer part -
+		** Cast to int32_t so that the compiler will use fast SSE2 float->int instructions.
+		** This result won't be above 2^31, so this is safe.
+		*/
+		tickTimeLen = (int32_t)dInt;
 
-		// fractional part (scaled to 0..2^32-1)
+		/* - fractional part (scaled to 0..2^32-1) -
+		** We have to resort to slow float->int conversion here because the result
+		** can be above 2^31. In 64-bit mode, this will still use a fast SSE2 instruction.
+		** Anyhow, this function won't be called that many times a second in worst case,
+		** so it's not a big issue at all.
+		*/
 		dFrac *= UINT32_MAX;
 		tickTimeLenFrac = (uint32_t)(dFrac + 0.5);
 	}
@@ -194,7 +202,7 @@
 
 	v = &voice[i];
 
-	volL = v->SVol * amp; // 0..2047 * 1..32 = 0..65504
+	volL = v->SVol * masterAmp; // 0..2047 * 1..32 = 0..65504
 
 	// (0..65504 * 0..65536) >> 4 = 0..268304384
 	volR = ((uint32_t)volL * panningTab[v->SPan]) >> 4;
@@ -331,7 +339,6 @@
 void mix_UpdateChannelVolPanFrq(void)
 {
 	uint8_t status;
-	uint16_t vol;
 	stmTyp *ch;
 	voice_t *v;
 
@@ -347,14 +354,8 @@
 
 			// volume change
 			if (status & IS_Vol)
-			{
-				vol = ch->finalVol;
-				if (vol > 0) // yes, FT2 does this!
-					vol--;
+				v->SVol = ch->finalVol;
 
-				v->SVol = vol;
-			}
-
 			// panning change
 			if (status & IS_Pan)
 				v->SPan = ch->finalPan;
@@ -1063,28 +1064,35 @@
 {
 	const uint32_t sampleSize = sizeof (int32_t);
 
-	audio.mixBufferL = (int32_t *)malloc(MAX_WAV_RENDER_SAMPLES_PER_TICK * sampleSize);
-	audio.mixBufferR = (int32_t *)malloc(MAX_WAV_RENDER_SAMPLES_PER_TICK * sampleSize);
+	audio.mixBufferLUnaligned = (int32_t *)MALLOC_PAD(MAX_WAV_RENDER_SAMPLES_PER_TICK * sampleSize, 256);
+	audio.mixBufferRUnaligned = (int32_t *)MALLOC_PAD(MAX_WAV_RENDER_SAMPLES_PER_TICK * sampleSize, 256);
 
-	if (audio.mixBufferL == NULL || audio.mixBufferR == NULL)
+	if (audio.mixBufferLUnaligned == NULL || audio.mixBufferRUnaligned == NULL)
 		return false;
 
+	// make aligned main pointers
+	audio.mixBufferL = (int32_t *)ALIGN_PTR(audio.mixBufferLUnaligned, 256);
+	audio.mixBufferR = (int32_t *)ALIGN_PTR(audio.mixBufferRUnaligned, 256);
+
 	return true;
 }
 
 static void freeAudioBuffers(void)
 {
-	if (audio.mixBufferL != NULL)
+	if (audio.mixBufferLUnaligned != NULL)
 	{
-		free(audio.mixBufferL);
-		audio.mixBufferL = NULL;
+		free(audio.mixBufferLUnaligned);
+		audio.mixBufferLUnaligned = NULL;
 	}
 
-	if (audio.mixBufferR != NULL)
+	if (audio.mixBufferRUnaligned != NULL)
 	{
-		free(audio.mixBufferR);
-		audio.mixBufferR = NULL;
+		free(audio.mixBufferRUnaligned);
+		audio.mixBufferRUnaligned = NULL;
 	}
+
+	audio.mixBufferL = NULL;
+	audio.mixBufferR = NULL;
 }
 
 void updateSendAudSamplesRoutine(bool lockMixer)
--- a/src/ft2_audio.h
+++ b/src/ft2_audio.h
@@ -26,7 +26,7 @@
 	volatile bool locked, resetSyncTickTimeFlag, volumeRampingFlag, interpolationFlag;
 	bool linearFreqTable, rescanAudioDevicesSupported;
 	int32_t inputDeviceNum, outputDeviceNum, lastWorkingAudioFreq, lastWorkingAudioBits;
-	int32_t quickVolSizeVal, *mixBufferL, *mixBufferR;
+	int32_t quickVolSizeVal, *mixBufferL, *mixBufferR, *mixBufferLUnaligned, *mixBufferRUnaligned;
 	uint32_t freq;
 	uint32_t audLatencyPerfValInt, audLatencyPerfValFrac;
 	uint64_t tickTime64, tickTime64Frac;
--- a/src/ft2_config.c
+++ b/src/ft2_config.c
@@ -29,6 +29,7 @@
 #include "ft2_midi.h"
 #include "ft2_gfxdata.h"
 #include "ft2_palette.h"
+#include "ft2_pattern_draw.h"
 
 // defined at the bottom of this file
 extern const uint8_t defConfigData[CONFIG_FILE_SIZE];
@@ -190,6 +191,7 @@
 	changeBadgeType(config.id_TritonProd);
 	editor.ui.maxVisibleChannels = (uint8_t)(2 + ((config.ptnMaxChannels + 1) * 2));
 	setPal16(palTable[config.cfg_StdPalNr], true);
+	updatePattFontPtrs();
 
 	unlockMixerCallback();
 }
@@ -813,8 +815,10 @@
 	uncheckRadioButtonGroup(RB_GROUP_CONFIG_SOUND_BUFF_SIZE);
 
 	tmpID = RB_CONFIG_SBS_1024;
-	     if (config.specialFlags & BUFFSIZE_512)  tmpID = RB_CONFIG_SBS_512;
-	else if (config.specialFlags & BUFFSIZE_2048) tmpID = RB_CONFIG_SBS_2048;
+	if (config.specialFlags & BUFFSIZE_512)
+		tmpID = RB_CONFIG_SBS_512;
+	else if (config.specialFlags & BUFFSIZE_2048)
+		tmpID = RB_CONFIG_SBS_2048;
 
 	radioButtons[tmpID].state = RADIOBUTTON_CHECKED;
 
@@ -1873,6 +1877,7 @@
 {
 	config.ptnFont = PATT_FONT_CAPITALS;
 	checkRadioButton(RB_CONFIG_FONT_CAPITALS);
+	updatePattFontPtrs();
 	redrawPatternEditor();
 }
 
@@ -1880,6 +1885,7 @@
 {
 	config.ptnFont = PATT_FONT_LOWERCASE;
 	checkRadioButton(RB_CONFIG_FONT_LOWERCASE);
+	updatePattFontPtrs();
 	redrawPatternEditor();
 }
 
@@ -1887,6 +1893,7 @@
 {
 	config.ptnFont = PATT_FONT_FUTURE;
 	checkRadioButton(RB_CONFIG_FONT_FUTURE);
+	updatePattFontPtrs();
 	redrawPatternEditor();
 }
 
@@ -1894,6 +1901,7 @@
 {
 	config.ptnFont = PATT_FONT_BOLD;
 	checkRadioButton(RB_CONFIG_FONT_BOLD);
+	updatePattFontPtrs();
 	redrawPatternEditor();
 }
 
--- a/src/ft2_diskop.c
+++ b/src/ft2_diskop.c
@@ -1089,7 +1089,7 @@
 				// in case of UTF8 -> CP437 encoding failure, there can be question marks. Remove them...
 				removeQuestionmarksFromString(FReq_NameTemp);
 
-				if (inputBox(2, dirEntry->isDir ? "Enter new directory name:" : "Enter new filename:", FReq_NameTemp, PATH_MAX - 1) == 1)
+				if (inputBox(1, dirEntry->isDir ? "Enter new directory name:" : "Enter new filename:", FReq_NameTemp, PATH_MAX - 1) == 1)
 				{
 					if ((FReq_NameTemp == NULL) || (FReq_NameTemp[0] == '\0'))
 					{
--- a/src/ft2_edit.c
+++ b/src/ft2_edit.c
@@ -1858,7 +1858,7 @@
 	uint8_t err;
 
 	sprintf(volstr, "%0.2f,%0.2f", dVolScaleFK1, dVolScaleFK2);
-	if (inputBox(2, msg, volstr, sizeof (volstr) - 1) != 1)
+	if (inputBox(1, msg, volstr, sizeof (volstr) - 1) != 1)
 		return false;
 
 	err = false;
--- a/src/ft2_gui.c
+++ b/src/ft2_gui.c
@@ -734,22 +734,22 @@
 
 void blit32(uint16_t xPos, uint16_t yPos, const uint32_t* srcPtr, uint16_t w, uint16_t h)
 {
-    uint32_t* dstPtr;
+	uint32_t* dstPtr;
 
-    assert(srcPtr != NULL && xPos < SCREEN_W && yPos < SCREEN_H && (xPos + w) <= SCREEN_W && (yPos + h) <= SCREEN_H);
+	assert(srcPtr != NULL && xPos < SCREEN_W && yPos < SCREEN_H && (xPos + w) <= SCREEN_W && (yPos + h) <= SCREEN_H);
 
-    dstPtr = &video.frameBuffer[(yPos * SCREEN_W) + xPos];
-    for (uint32_t y = 0; y < h; y++)
-    {
-        for (uint32_t x = 0; x < w; x++)
-        {
-            if (srcPtr[x] != 0x00FF00)
-                dstPtr[x] = srcPtr[x] | 0xFF000000; // most significant 8 bits = palette number. 0xFF because no true palette
-        }
+	dstPtr = &video.frameBuffer[(yPos * SCREEN_W) + xPos];
+	for (uint32_t y = 0; y < h; y++)
+	{
+		for (uint32_t x = 0; x < w; x++)
+		{
+			if (srcPtr[x] != 0x00FF00)
+				dstPtr[x] = srcPtr[x] | 0xFF000000; // most significant 8 bits = palette number. 0xFF because no true palette
+		}
 
-        srcPtr += w;
-        dstPtr += SCREEN_W;
-    }
+		srcPtr += w;
+		dstPtr += SCREEN_W;
+	}
 }
 
 void blit(uint16_t xPos, uint16_t yPos, const uint8_t *srcPtr, uint16_t w, uint16_t h)
--- a/src/ft2_header.h
+++ b/src/ft2_header.h
@@ -12,7 +12,7 @@
 #endif
 #include "ft2_replayer.h"
 
-#define PROG_VER_STR "1.06"
+#define PROG_VER_STR "1.07"
 
 // do NOT change these! It will only mess things up...
 
@@ -54,6 +54,9 @@
 
 // fast 32-bit -> 16-bit clamp
 #define CLAMP16(i) if ((int16_t)(i) != i) i = 0x7FFF ^ (i >> 31)
+
+#define ALIGN_PTR(p, x) (((uintptr_t)(p) + ((x)-1)) & ~((x)-1))
+#define MALLOC_PAD(size, pad) (malloc((size) + (pad)))
 
 #define SWAP16(value) \
 ( \
--- a/src/ft2_inst_ed.c
+++ b/src/ft2_inst_ed.c
@@ -788,24 +788,28 @@
 
 void volEnvAdd(void)
 {
-	int16_t i;
+	int16_t i, ant;
 	instrTyp *ins = instr[editor.curInstr];
 
-	if (ins == NULL || editor.curInstr == 0 || ins->envVPAnt >= 12)
+	ant = ins->envVPAnt;
+	if (ins == NULL || editor.curInstr == 0 || ant >= 12)
 		return;
 
 	i = (int16_t)editor.currVolEnvPoint;
+	if (i < 0 || i >= ant)
+	{
+		i = ant-1;
+		if (i < 0)
+			i = 0;
+	}
 
-	if (i < 0 || i >= ins->envVPAnt)
-		i = ins->envVPAnt - 1;
-
-	if (i < ins->envVPAnt-1 && ins->envVP[i+1][0]-ins->envVP[i][0] < 2)
+	if (i < ant-1 && ins->envVP[i+1][0]-ins->envVP[i][0] < 2)
 		return;
 
 	if (ins->envVP[i][0] >= 323)
 		return;
 
-	for (int16_t j = ins->envVPAnt; j > i; j--)
+	for (int16_t j = ant; j > i; j--)
 	{
 		ins->envVP[j][0] = ins->envVP[j-1][0];
 		ins->envVP[j][1] = ins->envVP[j-1][1];
@@ -815,7 +819,7 @@
 	if (ins->envVRepS > i) { ins->envVRepS++; drawVolEnvRepS(); }
 	if (ins->envVRepE > i) { ins->envVRepE++; drawVolEnvRepE(); }
 
-	if (i < ins->envVPAnt-1)
+	if (i < ant-1)
 	{
 		ins->envVP[i+1][0] = (ins->envVP[i][0] + ins->envVP[i+2][0]) / 2;
 		ins->envVP[i+1][1] = (ins->envVP[i][1] + ins->envVP[i+2][1]) / 2;
@@ -840,7 +844,6 @@
 	uint8_t drawSust, drawRepS, drawRepE;
 	int16_t i;
 	instrTyp *ins = instr[editor.curInstr];
-
 	if (ins == NULL || editor.curInstr == 0 || ins->envVPAnt <= 2)
 		return;
 
@@ -873,6 +876,11 @@
 	if (drawRepS) drawVolEnvRepS();
 	if (drawRepE) drawVolEnvRepE();
 
+	if (ins->envVPAnt == 0)
+		editor.currVolEnvPoint = 0;
+	else if (editor.currVolEnvPoint >= ins->envVPAnt)
+		editor.currVolEnvPoint = ins->envVPAnt-1;
+
 	updateVolEnv = true;
 	setSongModifiedFlag();
 }
@@ -880,7 +888,6 @@
 void volEnvSusUp(void)
 {
 	instrTyp *ins = instr[editor.curInstr];
-
 	if (ins == NULL || editor.curInstr == 0)
 		return;
 
@@ -896,7 +903,6 @@
 void volEnvSusDown(void)
 {
 	instrTyp *ins = instr[editor.curInstr];
-
 	if (ins == NULL || editor.curInstr == 0)
 		return;
 
@@ -912,7 +918,6 @@
 void volEnvRepSUp(void)
 {
 	instrTyp *ins = instr[editor.curInstr];
-
 	if (ins == NULL || editor.curInstr == 0)
 		return;
 
@@ -928,7 +933,6 @@
 void volEnvRepSDown(void)
 {
 	instrTyp *ins = instr[editor.curInstr];
-
 	if (ins == NULL || editor.curInstr == 0)
 		return;
 
@@ -944,7 +948,6 @@
 void volEnvRepEUp(void)
 {
 	instrTyp *ins = instr[editor.curInstr];
-
 	if (ins == NULL || editor.curInstr == 0)
 		return;
 
@@ -960,7 +963,6 @@
 void volEnvRepEDown(void)
 {
 	instrTyp *ins = instr[editor.curInstr];
-
 	if (ins == NULL || editor.curInstr == 0)
 		return;
 
@@ -975,24 +977,28 @@
 
 void panEnvAdd(void)
 {
-	int16_t i;
+	int16_t i, ant;
 	instrTyp *ins = instr[editor.curInstr];
 
-	if (ins == NULL || editor.curInstr == 0 || ins->envPPAnt >= 12)
+	ant = ins->envPPAnt;
+	if (ins == NULL || editor.curInstr == 0 || ant >= 12)
 		return;
 
 	i = (int16_t)editor.currPanEnvPoint;
+	if (i < 0 || i >= ant)
+	{
+		i = ant-1;
+		if (i < 0)
+			i = 0;
+	}
 
-	if (i < 0 || i >= ins->envPPAnt)
-		i = ins->envPPAnt - 1;
-
-	if (i < ins->envPPAnt-1 && ins->envPP[i+1][0]-ins->envPP[i][0] < 2)
+	if (i < ant-1 && ins->envPP[i+1][0]-ins->envPP[i][0] < 2)
 		return;
 
 	if (ins->envPP[i][0] >= 323)
 		return;
 
-	for (int16_t j = ins->envPPAnt; j > i; j--)
+	for (int16_t j = ant; j > i; j--)
 	{
 		ins->envPP[j][0] = ins->envPP[j-1][0];
 		ins->envPP[j][1] = ins->envPP[j-1][1];
@@ -1002,7 +1008,7 @@
 	if (ins->envPRepS > i) { ins->envPRepS++; drawPanEnvRepS(); }
 	if (ins->envPRepE > i) { ins->envPRepE++; drawPanEnvRepE(); }
 
-	if (i < ins->envPPAnt-1)
+	if (i < ant-1)
 	{
 		ins->envPP[i+1][0] = (ins->envPP[i][0] + ins->envPP[i+2][0]) / 2;
 		ins->envPP[i+1][1] = (ins->envPP[i][1] + ins->envPP[i+2][1]) / 2;
@@ -1027,7 +1033,6 @@
 	uint8_t drawSust, drawRepS, drawRepE;
 	int16_t i;
 	instrTyp *ins = instr[editor.curInstr];
-
 	if (ins == NULL || editor.curInstr == 0 || ins->envPPAnt <= 2)
 		return;
 
@@ -1060,6 +1065,11 @@
 	if (drawRepS) drawPanEnvRepS();
 	if (drawRepE) drawPanEnvRepE();
 
+	if (ins->envPPAnt == 0)
+		editor.currPanEnvPoint = 0;
+	else if (editor.currPanEnvPoint >= ins->envPPAnt)
+		editor.currPanEnvPoint = ins->envPPAnt-1;
+
 	updatePanEnv = true;
 	setSongModifiedFlag();
 }
@@ -1067,7 +1077,6 @@
 void panEnvSusUp(void)
 {
 	instrTyp *ins = instr[editor.curInstr];
-
 	if (ins == NULL || editor.curInstr == 0)
 		return;
 
@@ -1083,7 +1092,6 @@
 void panEnvSusDown(void)
 {
 	instrTyp *ins = instr[editor.curInstr];
-
 	if (ins == NULL || editor.curInstr == 0)
 		return;
 
@@ -1099,7 +1107,6 @@
 void panEnvRepSUp(void)
 {
 	instrTyp *ins = instr[editor.curInstr];
-
 	if (ins == NULL || editor.curInstr == 0)
 		return;
 
@@ -1115,7 +1122,6 @@
 void panEnvRepSDown(void)
 {
 	instrTyp *ins = instr[editor.curInstr];
-
 	if (ins == NULL || editor.curInstr == 0)
 		return;
 
@@ -1131,7 +1137,6 @@
 void panEnvRepEUp(void)
 {
 	instrTyp *ins = instr[editor.curInstr];
-
 	if (ins == NULL || editor.curInstr == 0)
 		return;
 
@@ -1147,7 +1152,6 @@
 void panEnvRepEDown(void)
 {
 	instrTyp *ins = instr[editor.curInstr];
-
 	if (ins == NULL || editor.curInstr == 0)
 		return;
 
@@ -1975,7 +1979,7 @@
 
 	// draw center line on pan envelope
 	if (nr == 1)
-		envelopeLine(nr, 8, 33, 335, 33, PAL_BLCKMRK);
+		envelopeLine(nr, 8, 33, 332, 33, PAL_BLCKMRK);
 
 	if (ins == NULL)
 		return;
@@ -2035,38 +2039,48 @@
 	// draw envelope
 	for (i = 0; i < nd; i++)
 	{
-		x = curEnvP[i][0]; x = CLAMP(x, 0, 340);
-		y = curEnvP[i][1]; y = CLAMP(y, 0,  64);
+		x = curEnvP[i][0];
+		y = curEnvP[i][1];
 
-		envelopeDot(nr, 7 + x, 64 - y);
+		x = CLAMP(x, 0, 324);
+		
+		if (nr == 0)
+			y = CLAMP(y, 0, 64);
+		else
+			y = CLAMP(y, 0, 63);
 
-		// draw "envelope selected" data
-		if (i == selected)
+		if ((uint16_t)curEnvP[i][0] <= 324)
 		{
-			envelopeLine(nr, 5  + x, 64 - y, 5  + x, 66 - y, PAL_BLCKTXT);
-			envelopeLine(nr, 11 + x, 64 - y, 11 + x, 66 - y, PAL_BLCKTXT);
-			envelopePixel(nr, 5, 65 - y, PAL_BLCKTXT);
-			envelopePixel(nr, 8 + x, 65, PAL_BLCKTXT);
-		}
+			envelopeDot(nr, 7 + x, 64 - y);
 
-		// draw loop start marker
-		if (i == ls)
-		{
-			envelopeLine(nr, x + 6, 1, x + 10, 1, PAL_PATTEXT);
-			envelopeLine(nr, x + 7, 2, x +  9, 2, PAL_PATTEXT);
-			envelopeVertLine(nr, x + 8, 1, PAL_PATTEXT);
-		}
+			// draw "envelope selected" data
+			if (i == selected)
+			{
+				envelopeLine(nr, 5  + x, 64 - y, 5  + x, 66 - y, PAL_BLCKTXT);
+				envelopeLine(nr, 11 + x, 64 - y, 11 + x, 66 - y, PAL_BLCKTXT);
+				envelopePixel(nr, 5, 65 - y, PAL_BLCKTXT);
+				envelopePixel(nr, 8 + x, 65, PAL_BLCKTXT);
+			}
 
-		// draw sustain marker
-		if (i == sp)
-			envelopeVertLine(nr, x + 8, 1, PAL_BLCKTXT);
+			// draw loop start marker
+			if (i == ls)
+			{
+				envelopeLine(nr, x + 6, 1, x + 10, 1, PAL_PATTEXT);
+				envelopeLine(nr, x + 7, 2, x +  9, 2, PAL_PATTEXT);
+				envelopeVertLine(nr, x + 8, 1, PAL_PATTEXT);
+			}
 
-		// draw loop end marker
-		if (i == le)
-		{
-			envelopeLine(nr, x + 6, 65, x + 10, 65, PAL_PATTEXT);
-			envelopeLine(nr, x + 7, 64, x +  9, 64, PAL_PATTEXT);
-			envelopeVertLine(nr, x + 8, 1, PAL_PATTEXT);
+			// draw sustain marker
+			if (i == sp)
+				envelopeVertLine(nr, x + 8, 1, PAL_BLCKTXT);
+
+			// draw loop end marker
+			if (i == le)
+			{
+				envelopeLine(nr, x + 6, 65, x + 10, 65, PAL_PATTEXT);
+				envelopeLine(nr, x + 7, 64, x +  9, 64, PAL_PATTEXT);
+				envelopeVertLine(nr, x + 8, 1, PAL_PATTEXT);
+			}
 		}
 
 		// draw envelope line
@@ -2465,7 +2479,7 @@
 			if (editor.currVolEnvPoint == ant-1)
 			{
 				minX = ins->envVP[editor.currVolEnvPoint-1][0] + 1;
-				maxX = 325;
+				maxX = 324;
 			}
 			else
 			{
@@ -2473,6 +2487,9 @@
 				maxX = ins->envVP[editor.currVolEnvPoint+1][0] - 1;
 			}
 
+			minX = CLAMP(minX, 0, 324);
+			maxX = CLAMP(maxX, 0, 324);
+
 			ins->envVP[editor.currVolEnvPoint][0] = (int16_t)(CLAMP(mx, minX, maxX));
 			updateVolEnv = true;
 
@@ -2561,7 +2578,7 @@
 			if (editor.currPanEnvPoint == ant-1)
 			{
 				minX = ins->envPP[editor.currPanEnvPoint-1][0] + 1;
-				maxX = 325;
+				maxX = 324;
 			}
 			else
 			{
@@ -2569,6 +2586,9 @@
 				maxX = ins->envPP[editor.currPanEnvPoint+1][0] - 1;
 			}
 
+			minX = CLAMP(minX, 0, 324);
+			maxX = CLAMP(maxX, 0, 324);
+
 			ins->envPP[editor.currPanEnvPoint][0] = (int16_t)(CLAMP(mx, minX, maxX));
 			updatePanEnv = true;
 
@@ -3102,7 +3122,7 @@
 				goto loadDone;
 			}
 
-			// sanitize stuff for malicious instruments
+			// sanitize stuff for broken/unsupported instruments
 			ih.midiProgram = CLAMP(ih.midiProgram, 0, 127);
 			ih.midiBend = CLAMP(ih.midiBend, 0, 36);
 
@@ -3127,6 +3147,15 @@
 			if (ih.envPRepS > 11) ih.envPRepS = 11;
 			if (ih.envPRepE > 11) ih.envPRepE = 11;
 			if (ih.envPSust > 11) ih.envPSust = 11;
+
+			for (int16_t i = 0; i < 12; i++)
+			{
+				if ((uint16_t)ih.envVP[i][0] > 32767) ih.envVP[i][0] = 32767;
+				if ((uint16_t)ih.envPP[i][0] > 32767) ih.envPP[i][0] = 32767;
+				if ((uint16_t)ih.envVP[i][1] > 64) ih.envVP[i][1] = 64;
+				if ((uint16_t)ih.envPP[i][1] > 63) ih.envPP[i][1] = 63;
+			}
+
 			// ----------------------------------------
 
 			memcpy(instr[editor.curInstr]->ta, ih.ta, INSTR_SIZE);
--- a/src/ft2_keyboard.c
+++ b/src/ft2_keyboard.c
@@ -107,8 +107,10 @@
 
 	if (editor.ui.sysReqShown)
 	{
-		     if (keycode == SDLK_RETURN) editor.ui.sysReqEnterPressed = true;
-		else if (keycode == SDLK_ESCAPE) editor.ui.sysReqShown = false;
+		if (keycode == SDLK_RETURN)
+			editor.ui.sysReqEnterPressed = true;
+		else if (keycode == SDLK_ESCAPE)
+			editor.ui.sysReqShown = false;
 
 		return;
 	}
@@ -858,7 +860,7 @@
 				video.showFPSCounter ^= 1;
 				if (!video.showFPSCounter)
 				{
-					if (editor.ui.extended) // Mr. Kludge, my only friend
+					if (editor.ui.extended) // yet another kludge...
 						exitPatternEditorExtended();
 
 					showTopScreen(false);
@@ -1025,15 +1027,19 @@
 		case SDLK_v:
 			if (keyb.leftAltPressed)
 			{
-				     if (editor.ui.sampleEditorShown) sampPaste();
-				else if (!editor.ui.instEditorShown)  scaleFadeVolumeBlock();
+				if (editor.ui.sampleEditorShown)
+					sampPaste();
+				else if (!editor.ui.instEditorShown)
+					scaleFadeVolumeBlock();
 
 				return true;
 			}
 			else if (keyb.leftCtrlPressed || keyb.leftCommandPressed)
 			{
-				     if (editor.ui.sampleEditorShown) sampPaste();
-				else if (!editor.ui.instEditorShown)  scaleFadeVolumePattern();
+				if (editor.ui.sampleEditorShown)
+					sampPaste();
+				else if (!editor.ui.instEditorShown)
+					scaleFadeVolumePattern();
 
 				return true;
 			}
--- a/src/ft2_main.c
+++ b/src/ft2_main.c
@@ -103,6 +103,13 @@
 		return 0;
 	}
 
+	if (!cpu.hasSSE2)
+	{
+		showErrorMsgBox("Your computer's processor doesn't have the SSE2 instruction set\n" \
+		                "which is needed for this program to run. Sorry!");
+		return 0;
+	}
+
 	setupWin32Usleep();
 	disableWasapi(); // disable problematic WASAPI SDL2 audio driver on Windows (causes clicks/pops sometimes...)
 #endif
@@ -192,6 +199,7 @@
 #ifdef _WIN32 // on Windows we show the window at this point
 	SDL_ShowWindow(video.window);
 #endif
+
 	if (config.windowFlags & START_IN_FULLSCR)
 	{
 		video.fullscreen = true;
--- a/src/ft2_module_loader.c
+++ b/src/ft2_module_loader.c
@@ -1810,8 +1810,6 @@
 
 void loadMusic(UNICHAR *filenameU)
 {
-	int32_t i;
-
 	if (musicIsLoading)
 		return;
 
@@ -1831,7 +1829,7 @@
 	// prevent stuck instrument names from previous module
 	memset(&songTmp, 0, sizeof (songTmp));
 
-	for (i = 0; i < MAX_PATTERNS; i++)
+	for (uint32_t i = 0; i < MAX_PATTERNS; i++)
 		pattLensTmp[i] = 64;
 
 	thread = SDL_CreateThread(loadMusicThread, NULL, NULL);
@@ -1846,6 +1844,35 @@
 	SDL_DetachThread(thread);
 }
 
+bool loadMusicUnthreaded(UNICHAR *filenameU) // for development testing
+{
+	if (editor.tmpFilenameU == NULL)
+		return false;
+
+	// clear deprecated pointers from possible last loading session (super important)
+	memset(pattTmp,  0, sizeof (pattTmp));
+	memset(instrTmp, 0, sizeof (instrTmp));
+
+	// prevent stuck instrument names from previous module
+	memset(&songTmp, 0, sizeof (songTmp));
+
+	for (uint32_t i = 0; i < MAX_PATTERNS; i++)
+		pattLensTmp[i] = 64;
+
+	UNICHAR_STRCPY(editor.tmpFilenameU, filenameU);
+
+	loadMusicThread(NULL);
+	editor.loadMusicEvent = EVENT_NONE;
+
+	if (moduleLoaded)
+	{
+		setupLoadedModule();
+		return true;
+	}
+
+	return false;
+}
+
 static void freeTmpModule(void)
 {
 	uint16_t i;
@@ -1925,7 +1952,7 @@
 			if (!allocateTmpInstr(i))
 				return false;
 
-			// sanitize stuff for malicious instruments
+			// sanitize stuff for broken/unsupported instruments
 			ih.midiProgram = CLAMP(ih.midiProgram, 0, 127);
 			ih.midiBend = CLAMP(ih.midiBend, 0, 36);
 
@@ -1941,20 +1968,29 @@
 				if (ih.ta[j] > 15)
 					ih.ta[j] = 15;
 			}
+
+			if (ih.envVPAnt > 12) ih.envVPAnt = 12;
+			if (ih.envVRepS > 11) ih.envVRepS = 11;
+			if (ih.envVRepE > 11) ih.envVRepE = 11;
+			if (ih.envVSust > 11) ih.envVSust = 11;
+			if (ih.envPPAnt > 12) ih.envPPAnt = 12;
+			if (ih.envPRepS > 11) ih.envPRepS = 11;
+			if (ih.envPRepE > 11) ih.envPRepE = 11;
+			if (ih.envPSust > 11) ih.envPSust = 11;
+
+			for (j = 0; j < 12; j++)
+			{
+				if ((uint16_t)ih.envVP[j][0] > 32767) ih.envVP[j][0] = 32767;
+				if ((uint16_t)ih.envPP[j][0] > 32767) ih.envPP[j][0] = 32767;
+				if ((uint16_t)ih.envVP[j][1] > 64) ih.envVP[j][1] = 64;
+				if ((uint16_t)ih.envPP[j][1] > 63) ih.envPP[j][1] = 63;
+				
+			}
 			// ----------------------------------------
 
 			// copy over final instrument data from temp buffer
 			memcpy(instrTmp[i], ih.ta, INSTR_SIZE);
 			instrTmp[i]->antSamp = ih.antSamp;
-
-			if (instrTmp[i]->envVPAnt > 12) instrTmp[i]->envVPAnt = 12;
-			if (instrTmp[i]->envVRepS > 11) instrTmp[i]->envVRepS = 11;
-			if (instrTmp[i]->envVRepE > 11) instrTmp[i]->envVRepE = 11;
-			if (instrTmp[i]->envVSust > 11) instrTmp[i]->envVSust = 11;
-			if (instrTmp[i]->envPPAnt > 12) instrTmp[i]->envPPAnt = 12;
-			if (instrTmp[i]->envPRepS > 11) instrTmp[i]->envPRepS = 11;
-			if (instrTmp[i]->envPRepE > 11) instrTmp[i]->envPRepE = 11;
-			if (instrTmp[i]->envPSust > 11) instrTmp[i]->envPSust = 11;
 		}
 
 		if (fread(ih.samp, ih.antSamp * sizeof (sampleHeaderTyp), 1, f) != 1)
@@ -2501,7 +2537,7 @@
 			SDL_RestoreWindow(video.window);
 			SDL_RaiseWindow(video.window);
 
-			if (okBox(2, "System request", "You have unsaved changes in your song. Load new song and lose all changes?") != 1)
+			if (!askUnsavedChanges(ASK_TYPE_LOAD_SONG))
 			{
 				free(fullPathU);
 				return;
--- a/src/ft2_module_loader.h
+++ b/src/ft2_module_loader.h
@@ -5,6 +5,7 @@
 #include "ft2_unicode.h"
 
 void loadMusic(UNICHAR *filenameU);
+bool loadMusicUnthreaded(UNICHAR *filenameU); // for development testing
 bool handleModuleLoadFromArg(int argc, char **argv);
 void loadDroppedFile(char *fullPathUTF8, bool songModifiedCheck);
 void handleLoadMusicEvents(void);
--- a/src/ft2_mouse.c
+++ b/src/ft2_mouse.c
@@ -667,19 +667,29 @@
 		return;
 	}
 
-	     if (mouseButton == SDL_BUTTON_LEFT)  mouse.leftButtonPressed = true;
-	else if (mouseButton == SDL_BUTTON_RIGHT) mouse.rightButtonPressed = true;
-
-	mouse.leftButtonReleased = false;
-	mouse.rightButtonReleased = false;
-
 	// mouse 0,0 = open exit dialog
-	if (mouse.x == 0 && mouse.y == 0 && quitBox(false) == 1)
+	if (mouse.x == 0 && mouse.y == 0)
 	{
-		editor.throwExit = true;
+		if (quitBox(false) == 1)
+			editor.throwExit = true;
+
+		// release button presses from okBox()
+		mouse.leftButtonPressed = false;
+		mouse.rightButtonPressed = false;
+		mouse.leftButtonReleased = false;
+		mouse.rightButtonReleased = false;
+
 		return;
 	}
 
+	if (mouseButton == SDL_BUTTON_LEFT)
+		mouse.leftButtonPressed = true;
+	else if (mouseButton == SDL_BUTTON_RIGHT)
+		mouse.rightButtonPressed = true;
+
+	mouse.leftButtonReleased = false;
+	mouse.rightButtonReleased = false;
+
 	// don't do mouse down testing here if we already are using an object
 	if (mouse.lastUsedObjectType != OBJECT_NONE)
 		return;
@@ -762,8 +772,8 @@
 
 void updateMouseScaling(void)
 {
-	video.dMouseXMul = (double)SCREEN_W / video.renderW;
-	video.dMouseYMul = (double)SCREEN_H / video.renderH;
+	if (video.renderW > 0.0) video.dMouseXMul = (double)SCREEN_W / video.renderW;
+	if (video.renderH > 0.0) video.dMouseYMul = (double)SCREEN_H / video.renderH;
 }
 
 void readMouseXY(void)
@@ -818,8 +828,8 @@
 	if (my < 0) mx = 0;
 
 	// multiply coords by video upscaling factors (don't round)
-	mx = (uint32_t)(mx * video.dMouseXMul);
-	my = (uint32_t)(my * video.dMouseYMul);
+	mx = (int32_t)(mx * video.dMouseXMul);
+	my = (int32_t)(my * video.dMouseYMul);
 
 	if (mx >= SCREEN_W) mx = SCREEN_W - 1;
 	if (my >= SCREEN_H) my = SCREEN_H - 1;
--- a/src/ft2_mouse.h
+++ b/src/ft2_mouse.h
@@ -32,7 +32,6 @@
 
 void freeMouseCursors(void);
 bool createMouseCursors(void);
-
 void setMousePosToCenter(void);
 void setMouseShape(int16_t shape);
 void setMouseMode(uint8_t mode);
--- a/src/ft2_palette.c
+++ b/src/ft2_palette.c
@@ -18,6 +18,11 @@
 	{66, 62}, {68, 57}, {46, 57}, {57, 55}, {62, 57}, {52, 57}
 };
 
+void setCustomPalColor(uint32_t color)
+{
+	video.palette[PAL_CUSTOM] = (PAL_CUSTOM << 24) | color;
+}
+
 void setPal16(pal16 *p, bool redrawScreen)
 {
 #define LOOP_PIN_COL_SUB 118
--- a/src/ft2_palette.h
+++ b/src/ft2_palette.h
@@ -38,6 +38,9 @@
 	PAL_TEXTMRK = 17,
 	PAL_BOXSLCT = 18,
 
+	// modifiable with setCustomPalColor()
+	PAL_CUSTOM = 19,
+
 	PAL_NUM
 };
 
@@ -48,6 +51,8 @@
 
 extern uint8_t cfg_ColorNr;
 extern pal16 palTable[12][16];
+
+void setCustomPalColor(uint32_t color);
 
 uint8_t palMax(int32_t c);
 void setPal16(pal16 *p, bool redrawScreen);
--- a/src/ft2_pattern_draw.c
+++ b/src/ft2_pattern_draw.c
@@ -12,6 +12,9 @@
 #include "ft2_gui.h"
 #include "ft2_video.h"
 
+static tonTyp emptyPattern[MAX_VOICES * MAX_PATT_LEN];
+
+static const uint8_t *font4Ptr, *font5Ptr;
 static const uint8_t vol2charTab1[16] = { 39, 0, 1, 2, 3, 4, 36, 52, 53, 54, 28, 31, 25, 58, 59, 22 };
 static const uint8_t vol2charTab2[16] = { 42, 0, 1, 2, 3, 4, 36, 37, 38, 39, 28, 31, 25, 40, 41, 22 };
 static const uint8_t columnModeTab[12] = { 0, 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 3 };
@@ -28,29 +31,229 @@
 static const uint16_t flatNote1Char_big[12] = { 12*16, 13*16, 13*16, 14*16, 14*16, 15*16, 16*16, 16*16, 10*16, 10*16, 11*16, 11*16 };
 static const uint16_t flatNote2Char_big[12] = { 36*16, 38*16, 36*16, 38*16, 36*16, 36*16, 38*16, 36*16, 38*16, 36*16, 38*16, 36*16 };
 
-static tonTyp emptyNote;
+static const uint8_t noteTab1[96] =
+{
+	0,1,2,3,4,5,6,7,8,9,10,11,
+	0,1,2,3,4,5,6,7,8,9,10,11,
+	0,1,2,3,4,5,6,7,8,9,10,11,
+	0,1,2,3,4,5,6,7,8,9,10,11,
+	0,1,2,3,4,5,6,7,8,9,10,11,
+	0,1,2,3,4,5,6,7,8,9,10,11,
+	0,1,2,3,4,5,6,7,8,9,10,11,
+	0,1,2,3,4,5,6,7,8,9,10,11
+};
 
+static const uint8_t noteTab2[96] =
+{
+	0,0,0,0,0,0,0,0,0,0,0,0,
+	1,1,1,1,1,1,1,1,1,1,1,1,
+	2,2,2,2,2,2,2,2,2,2,2,2,
+	3,3,3,3,3,3,3,3,3,3,3,3,
+	4,4,4,4,4,4,4,4,4,4,4,4,
+	5,5,5,5,5,5,5,5,5,5,5,5,
+	6,6,6,6,6,6,6,6,6,6,6,6,
+	7,7,7,7,7,7,7,7,7,7,7,7
+};
+
+static const uint8_t hex2Dec[256] =
+{
+	0,1,2,3,4,5,6,7,8,9,
+	16,17,18,19,20,21,22,23,24,25,
+	32,33,34,35,36,37,38,39,40,41,
+	48,49,50,51,52,53,54,55,56,57,
+	64,65,66,67,68,69,70,71,72,73,
+	80,81,82,83,84,85,86,87,88,89,
+	96,97,98,99,100,101,102,103,104,105,
+	112,113,114,115,116,117,118,119,120,121,
+	128,129,130,131,132,133,134,135,136,137,
+	144,145,146,147,148,149,150,151,152,153,
+
+	0,1,2,3,4,5,6,7,8,9,
+	16,17,18,19,20,21,22,23,24,25,
+	32,33,34,35,36,37,38,39,40,41,
+	48,49,50,51,52,53,54,55,56,57,
+	64,65,66,67,68,69,70,71,72,73,
+	80,81,82,83,84,85,86,87,88,89,
+	96,97,98,99,100,101,102,103,104,105,
+	112,113,114,115,116,117,118,119,120,121,
+	128,129,130,131,132,133,134,135,136,137,
+	144,145,146,147,148,149,150,151,152,153,
+
+	0,1,2,3,4,5,6,7,8,9,
+	16,17,18,19,20,21,22,23,24,25,
+	32,33,34,35,36,37,38,39,40,41,
+	48,49,50,51,52,53,54,55,56,57,
+	64,65,66,67,68,69,70,71,72,73,
+	80,81,82,83,84,85
+};
+
+static const pattCoord_t pattCoordTable[2][2][2] =
+{
+	// no pattern stretch
+	{
+		// no pattern channel scroll
+		{
+			{ 176, 292, 177, 283, 293, 13, 13 }, // normal pattern editor
+			{  56, 228,  57, 219, 229, 20, 21 }, // extended pattern editor
+		},
+
+		// pattern channel scroll
+		{
+			{ 176, 285, 177, 276, 286, 12, 12 }, // normal pattern editor
+			{  56, 221,  57, 212, 222, 19, 20 }, // extended pattern editor
+		}
+	},
+
+	// pattern stretch
+	{
+		// no pattern channel scroll
+		{
+			{ 177, 286, 178, 277, 288,  9, 10 }, // normal pattern editor
+			{  56, 232,  58, 223, 234, 15, 15 }, // extended pattern editor
+		},
+
+		// pattern channel scroll
+		{
+			{  176, 285, 177, 276, 286,  9,  9 }, // normal pattern editor
+			{   56, 220,  57, 211, 221, 14, 15 }, // extended pattern editor
+		},
+	}
+};
+
+static const pattCoord2_t pattCoord2Table[2][2][2] =
+{
+	// no pattern stretch
+	{
+		// no pattern channel scroll
+		{
+			{ 175, 291, 107, 107 }, //   normal pattern editor
+			{  55, 227, 163, 171 }, // extended pattern editor
+		},
+
+		// pattern channel scroll
+		{
+			{ 175, 284, 100, 100 }, //   normal pattern editor
+			{  55, 220, 156, 164 }, // extended pattern editor
+		}
+	},
+
+	// pattern stretch
+	{
+		// no pattern channel scroll
+		{
+			{ 175, 285, 101, 113 }, //   normal pattern editor
+			{  55, 231, 167, 167 }, // extended pattern editor
+		},
+
+		// pattern channel scroll
+		{
+			{ 175, 284, 100, 100 }, //   normal pattern editor
+			{  55, 219, 155, 165 }, // extended pattern editor
+		},
+	}
+};
+
+static const markCoord_t markCoordTable[2][2][2] =
+{
+	// no pattern stretch
+	{
+		// no pattern channel scroll
+		{
+			{ 177, 281, 293 }, //   normal pattern editor
+			{  57, 217, 229 }, // extended pattern editor
+		},
+
+		// pattern channel scroll
+		{
+			{ 177, 274, 286 }, //   normal pattern editor
+			{  57, 210, 222 }, // extended pattern editor
+		}
+	},
+
+	// pattern stretch
+	{
+		// no pattern channel scroll
+		{
+			{ 176, 275, 286 }, //   normal pattern editor
+			{  56, 221, 232 }, // extended pattern editor
+		},
+
+		// pattern channel scroll
+		{
+			{ 175, 274, 284 }, //   normal pattern editor
+			{  55, 209, 219 }, // extended pattern editor
+		},
+	}
+};
+
+static const uint8_t pattCursorXTab[2 * 4 * 8] =
+{
+	// no volume column shown
+	32, 88, 104, 0, 0, 120, 136, 152, //  4 columns visible
+	32, 80,  88, 0, 0,  96, 104, 112, //  6 columns visible
+	32, 56,  64, 0, 0,  72,  80,  88, //  8 columns visible
+	32, 52,  56, 0, 0,  60,  64,  68, // 12 columns visible
+
+	// volume column shown
+	32, 96, 104, 120, 128, 144, 152, 160, //  4 columns visible
+	32, 56,  64,  80,  88,  96, 104, 112, //  6 columns visible
+	32, 60,  64,  72,  76,  84,  88,  92, //  8 columns visible
+	32, 60,  64,  72,  76,  84,  88,  92, // 12 columns visible
+};
+
+static const uint8_t pattCursorWTab[2 * 4 * 8] =
+{
+	// no volume column shown
+	48, 16, 16, 0, 0, 16, 16, 16, //  4 columns visible
+	48,  8,  8, 0, 0,  8,  8,  8, //  6 columns visible
+	24,  8,  8, 0, 0,  8,  8,  8, //  8 columns visible
+	16,  4,  4, 0, 0,  4,  4,  4, // 12 columns visible
+
+	// volume column shown
+	48,  8,  8,  8,  8,  8,  8,  8, //  4 columns visible
+	24,  8,  8,  8,  8,  8,  8,  8, //  6 columns visible
+	24,  4,  4,  4,  4,  4,  4,  4, //  8 columns visible
+	24,  4,  4,  4,  4,  4,  4,  4  // 12 columns visible
+};
+
+// global tables
+
+const char chDecTab1[MAX_VOICES+1] = 
+{
+	'0', '0', '0', '0', '0', '0', '0', '0', '0', '0',
+	'1', '1', '1', '1', '1', '1', '1', '1', '1', '1',
+	'2', '2', '2', '2', '2', '2', '2', '2', '2', '2',
+	'3', '3', '3'
+};
+
+const char chDecTab2[MAX_VOICES+1] = 
+{
+	'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+	'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+	'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+	'0', '1', '2'
+};
+
 // ft2_pattern_ed.c
 extern const uint16_t chanWidths[6];
 
-// defined at the bottom of this file
-extern const pattCoord_t pattCoordTable[2][2][2];
-extern const pattCoord2_t pattCoord2Table[2][2][2];
-extern const markCoord_t markCoordTable[2][2][2];
-extern const uint8_t pattCursorXTab[2 * 4 * 8];
-extern const uint8_t pattCursorWTab[2 * 4 * 8];
+static void pattCharOut(uint32_t xPos, uint32_t yPos, uint8_t chr, uint8_t fontType, uint32_t color);
+static void drawEmptyNoteSmall(uint32_t xPos, uint32_t yPos, uint32_t color);
+static void drawKeyOffSmall(uint32_t xPos, uint32_t yPos, uint32_t color);
+static void drawNoteSmall(uint32_t xPos, uint32_t yPos, int32_t ton, uint32_t color);
+static void drawEmptyNoteMedium(uint32_t xPos, uint32_t yPos, uint32_t color);
+static void drawKeyOffMedium(uint32_t xPos, uint32_t yPos, uint32_t color);
+static void drawNoteMedium(uint32_t xPos, uint32_t yPos, int32_t ton, uint32_t color);
+static void drawEmptyNoteBig(uint32_t xPos, uint32_t yPos, uint32_t color);
+static void drawKeyOffBig(uint32_t xPos, uint32_t yPos, uint32_t color);
+static void drawNoteBig(uint32_t xPos, uint32_t yPos, int32_t ton, uint32_t color);
 
-static void rowNumOut(uint32_t yPos, uint8_t paletteIndex, uint8_t rowChar1, uint8_t rowChar2);
-static void pattCharOut(uint32_t xPos, uint32_t yPos, uint8_t paletteIndex, uint8_t chr, uint8_t fontType);
-static void drawEmptyNoteSmall(uint16_t x, uint16_t y, uint8_t paletteIndex);
-static void drawKeyOffSmall(uint16_t x, uint16_t y, uint8_t paletteIndex);
-static void drawNoteSmall(uint16_t x, uint16_t y, uint8_t paletteIndex, int16_t ton);
-static void drawEmptyNoteMedium(uint16_t x, uint16_t y, uint8_t paletteIndex);
-static void drawKeyOffMedium(uint16_t x, uint16_t y, uint8_t paletteIndex);
-static void drawNoteMedium(uint16_t x, uint16_t y, uint8_t paletteIndex, int16_t ton);
-static void drawEmptyNoteBig(uint16_t xPos, uint16_t yPos, uint8_t paletteIndex);
-static void drawKeyOffBig(uint16_t xPos, uint16_t yPos, uint8_t paletteIndex);
-static void drawNoteBig(uint16_t xPos, uint16_t yPos, uint8_t paletteIndex, int16_t ton);
+void updatePattFontPtrs(void)
+{
+	//config.ptnFont is pre-clamped and safe
+	font4Ptr = &font4Data[config.ptnFont * (FONT4_WIDTH * FONT4_CHAR_H)];
+	font5Ptr = &font5Data[config.ptnFont * (FONT5_WIDTH * FONT5_CHAR_H)];
+}
 
 void drawPatternBorders(void)
 {
@@ -141,11 +344,11 @@
 			drawFramework(xOffs, pattCoord->upperRowsY, chanWidth, pattCoord->upperRowsH, FRAMEWORK_TYPE2); // top part
 			drawFramework(xOffs, pattCoord->lowerRowsY, chanWidth, pattCoord->lowerRowsH, FRAMEWORK_TYPE2); // bottom part
 
-			xOffs += (chanWidth + 1);
+			xOffs += chanWidth+1;
 		}
 
-		vLine(xOffs - 1, pattCoord->upperRowsY, pattCoord->upperRowsH, PAL_DESKTOP);
-		vLine(xOffs - 1, pattCoord->lowerRowsY, pattCoord->lowerRowsH + 1, PAL_DESKTOP);
+		vLine(xOffs-1, pattCoord->upperRowsY, pattCoord->upperRowsH, PAL_DESKTOP);
+		vLine(xOffs-1, pattCoord->lowerRowsY, pattCoord->lowerRowsH+1, PAL_DESKTOP);
 	}
 	else
 	{
@@ -203,11 +406,9 @@
 	}
 }
 
-static void writePatternBlockMark(int16_t currRow, uint16_t rowHeight, const pattCoord_t *pattCoord)
+static void writePatternBlockMark(int32_t currRow, uint32_t rowHeight, const pattCoord_t *pattCoord)
 {
-	uint8_t startCh, endCh;
-	int16_t startRow, endRow, x1, x2, y1, y2;
-	uint16_t pattYStart, pattYEnd;
+	int32_t startCh, endCh, startRow, endRow, x1, x2, y1, y2, pattYStart, pattYEnd;
 	uint32_t w, h, *ptr32;
 	const markCoord_t *markCoord;
 
@@ -311,53 +512,79 @@
 
 static void drawChannelNumbering(uint16_t yPos)
 {
-	uint8_t chNum;
-	uint16_t xPos;
+#define CH_NUM_XPOS 29
 
-	xPos = 29;
+	uint16_t xPos = CH_NUM_XPOS;
+	int32_t ch = editor.ui.channelOffset + 1;
+
 	for (uint8_t i = 0; i < editor.ui.numChannelsShown; i++)
 	{
-		chNum = editor.ui.channelOffset + i + 1;
-		if (chNum < 10)
+		if (ch < 10)
 		{
-			charOutOutlined(xPos, yPos, PAL_MOUSEPT, '0' + chNum);
+			charOutOutlined(xPos, yPos, PAL_MOUSEPT, '0' + (char)ch);
 		}
 		else
 		{
-			charOutOutlined(xPos, yPos, PAL_MOUSEPT, '0' + (chNum / 10));
-			charOutOutlined(xPos + 9, yPos, PAL_MOUSEPT, '0' + (chNum % 10));
+			charOutOutlined(xPos, yPos, PAL_MOUSEPT, chDecTab1[ch]);
+			charOutOutlined(xPos + (FONT1_CHAR_W + 1), yPos, PAL_MOUSEPT, chDecTab2[ch]);
 		}
 
+		ch++;
 		xPos += editor.ui.patternChannelWidth;
 	}
 }
 
-static void drawRowNum(uint16_t yPos, uint16_t row, bool middleRowFlag)
+static void drawRowNums(int32_t yPos, uint8_t row, bool selectedRowFlag)
 {
-	uint8_t pal;
+#define LEFT_ROW_XPOS 8
+#define RIGHT_ROW_XPOS 608
 
+	const uint8_t *src1Ptr, *src2Ptr;
+	uint32_t *dst1Ptr, *dst2Ptr, pixVal;
+
 	// set color based on some conditions
-	if (middleRowFlag)
-		pal = PAL_FORGRND;
-	else if ((row & 3) == 0 && config.ptnLineLight)
-		pal = PAL_BLCKTXT;
+	if (selectedRowFlag)
+		pixVal = video.palette[PAL_FORGRND];
+	else if (config.ptnLineLight && !(row & 3))
+		pixVal = video.palette[PAL_BLCKTXT];
 	else
-		pal = PAL_PATTEXT;
+		pixVal = video.palette[PAL_PATTEXT];
 
-	if (config.ptnHex)
+	if (!config.ptnHex)
+		row = hex2Dec[row];
+
+	src1Ptr = &font4Ptr[(row   >> 4) * FONT4_CHAR_W];
+	src2Ptr = &font4Ptr[(row & 0x0F) * FONT4_CHAR_W];
+	dst1Ptr = &video.frameBuffer[(yPos * SCREEN_W) + LEFT_ROW_XPOS];
+	dst2Ptr = dst1Ptr + (RIGHT_ROW_XPOS - LEFT_ROW_XPOS);
+
+	for (uint32_t y = 0; y < FONT4_CHAR_H; y++)
 	{
-		rowNumOut(yPos, pal, (uint8_t)(row >> 4), row & 0x0F);
+		for (uint32_t x1 = 0; x1 < FONT4_CHAR_W; x1++)
+		{
+			if (src1Ptr[x1])
+			{
+				dst1Ptr[x1] = pixVal; // left side
+				dst2Ptr[x1] = pixVal; // right side
+			}
+
+			if (src2Ptr[x1])
+			{
+				dst1Ptr[FONT4_CHAR_W + x1] = pixVal; // left side
+				dst2Ptr[FONT4_CHAR_W + x1] = pixVal; // right side
+			}
+		}
+
+		src1Ptr += FONT4_WIDTH;
+		src2Ptr += FONT4_WIDTH;
+		dst1Ptr += SCREEN_W;
+		dst2Ptr += SCREEN_W;
 	}
-	else
-	{
-		row %= 100;
-		rowNumOut(yPos, pal, (uint8_t)(row / 10), row % 10);
-	}
 }
 
 // DRAWING ROUTINES (WITH VOLUME COLUMN)
 
-static void showNoteNum(uint8_t pal, uint16_t xPos, uint16_t yPos, int16_t ton)
+static void showNoteNum(uint32_t xPos, uint32_t yPos, int16_t ton, uint32_t color)
 {
 	xPos += 3;
 
@@ -366,24 +593,24 @@
 	if (editor.ui.numChannelsShown <= 4)
 	{
 		if (ton <= 0 || ton > 97)
-			drawEmptyNoteBig(xPos, yPos, pal);
+			drawEmptyNoteBig(xPos, yPos, color);
 		else if (ton == 97)
-			drawKeyOffBig(xPos, yPos, pal);
+			drawKeyOffBig(xPos, yPos, color);
 		else
-			drawNoteBig(xPos, yPos, pal, ton);
+			drawNoteBig(xPos, yPos, ton, color);
 	}
 	else
 	{
 		if (ton <= 0 || ton > 97)
-			drawEmptyNoteMedium(xPos, yPos, pal);
+			drawEmptyNoteMedium(xPos, yPos, color);
 		else if (ton == 97)
-			drawKeyOffMedium(xPos, yPos, pal);
+			drawKeyOffMedium(xPos, yPos, color);
 		else
-			drawNoteMedium(xPos, yPos, pal, ton);
+			drawNoteMedium(xPos, yPos, ton, color);
 	}
 }
 
-static void showInstrNum(uint8_t pal, uint16_t xPos, uint16_t yPos, uint8_t ins)
+static void showInstrNum(uint32_t xPos, uint32_t yPos, uint8_t ins, uint32_t color)
 {
 	uint8_t chr1, chr2, charW, fontType;
 
@@ -408,8 +635,8 @@
 
 	if (config.ptnInstrZero)
 	{
-		pattCharOut(xPos,         yPos, pal, ins >> 4,   fontType);
-		pattCharOut(xPos + charW, yPos, pal, ins & 0x0F, fontType);
+		pattCharOut(xPos,         yPos, ins >> 4,   fontType, color);
+		pattCharOut(xPos + charW, yPos, ins & 0x0F, fontType, color);
 	}
 	else
 	{
@@ -417,14 +644,14 @@
 		chr2 = ins & 0x0F;
 
 		if (chr1 > 0)
-			pattCharOut(xPos, yPos, pal, chr1, fontType);
+			pattCharOut(xPos, yPos, chr1, fontType, color);
 
 		if (chr1 > 0 || chr2 > 0)
-			pattCharOut(xPos + charW, yPos, pal, chr2, fontType);
+			pattCharOut(xPos + charW, yPos, chr2, fontType, color);
 	}
 }
 
-static void showVolEfx(uint8_t pal, uint16_t xPos, uint16_t yPos, uint8_t vol)
+static void showVolEfx(uint32_t xPos, uint32_t yPos, uint8_t vol, uint32_t color)
 {
 	uint8_t char1, char2, fontType, charW;
 
@@ -465,11 +692,11 @@
 			char2 = vol & 0x0F;
 	}
 
-	pattCharOut(xPos,         yPos, pal, char1, fontType);
-	pattCharOut(xPos + charW, yPos, pal, char2, fontType);
+	pattCharOut(xPos,         yPos, char1, fontType, color);
+	pattCharOut(xPos + charW, yPos, char2, fontType, color);
 }
 
-static void showEfx(uint8_t pal, uint16_t xPos, uint16_t yPos, uint8_t effTyp, uint8_t eff)
+static void showEfx(uint32_t xPos, uint32_t yPos, uint8_t effTyp, uint8_t eff, uint32_t color)
 {
 	uint8_t fontType, charW;
 
@@ -492,14 +719,14 @@
 		xPos += 55;
 	}
 
-	pattCharOut(xPos,               yPos, pal, effTyp,     fontType);
-	pattCharOut(xPos +  charW,      yPos, pal, eff >> 4,   fontType);
-	pattCharOut(xPos + (charW * 2), yPos, pal, eff & 0x0F, fontType);
+	pattCharOut(xPos,               yPos, effTyp,     fontType, color);
+	pattCharOut(xPos +  charW,      yPos, eff >> 4,   fontType, color);
+	pattCharOut(xPos + (charW * 2), yPos, eff & 0x0F, fontType, color);
 }
 
 // DRAWING ROUTINES (WITHOUT VOLUME COLUMN)
 
-static void showNoteNumNoVolColumn(uint8_t pal, uint16_t xPos, uint16_t yPos, int16_t ton)
+static void showNoteNumNoVolColumn(uint32_t xPos, uint32_t yPos, int16_t ton, uint32_t color)
 {
 	xPos += 3;
 
@@ -508,33 +735,33 @@
 	if (editor.ui.numChannelsShown <= 6)
 	{
 		if (ton <= 0 || ton > 97)
-			drawEmptyNoteBig(xPos, yPos, pal);
+			drawEmptyNoteBig(xPos, yPos, color);
 		else if (ton == 97)
-			drawKeyOffBig(xPos, yPos, pal);
+			drawKeyOffBig(xPos, yPos, color);
 		else
-			drawNoteBig(xPos, yPos, pal, ton);
+			drawNoteBig(xPos, yPos, ton, color);
 	}
 	else if (editor.ui.numChannelsShown <= 8)
 	{
 		if (ton <= 0 || ton > 97)
-			drawEmptyNoteMedium(xPos, yPos, pal);
+			drawEmptyNoteMedium(xPos, yPos, color);
 		else if (ton == 97)
-			drawKeyOffMedium(xPos, yPos, pal);
+			drawKeyOffMedium(xPos, yPos, color);
 		else
-			drawNoteMedium(xPos, yPos, pal, ton);
+			drawNoteMedium(xPos, yPos, ton, color);
 	}
 	else
 	{
 		if (ton <= 0 || ton > 97)
-			drawEmptyNoteSmall(xPos, yPos, pal);
+			drawEmptyNoteSmall(xPos, yPos, color);
 		else if (ton == 97)
-			drawKeyOffSmall(xPos, yPos, pal);
+			drawKeyOffSmall(xPos, yPos, color);
 		else
-			drawNoteSmall(xPos, yPos, pal, ton);
+			drawNoteSmall(xPos, yPos, ton, color);
 	}
 }
 
-static void showInstrNumNoVolColumn(uint8_t pal, uint16_t xPos, uint16_t yPos, uint8_t ins)
+static void showInstrNumNoVolColumn(uint32_t xPos, uint32_t yPos, uint8_t ins, uint32_t color)
 {
 	uint8_t chr1, chr2, charW, fontType;
 
@@ -565,8 +792,8 @@
 
 	if (config.ptnInstrZero)
 	{
-		pattCharOut(xPos,         yPos, pal, ins >> 4,   fontType);
-		pattCharOut(xPos + charW, yPos, pal, ins & 0x0F, fontType);
+		pattCharOut(xPos,         yPos, ins >> 4,   fontType, color);
+		pattCharOut(xPos + charW, yPos, ins & 0x0F, fontType, color);
 	}
 	else
 	{
@@ -574,23 +801,23 @@
 		chr2 = ins & 0x0F;
 
 		if (chr1 > 0)
-			pattCharOut(xPos, yPos, pal, chr1, fontType);
+			pattCharOut(xPos, yPos, chr1, fontType, color);
 
 		if (chr1 > 0 || chr2 > 0)
-			pattCharOut(xPos + charW, yPos, pal, chr2, fontType);
+			pattCharOut(xPos + charW, yPos, chr2, fontType, color);
 	}
 }
 
-static void showNoVolEfx(uint8_t pal, uint16_t xPos, uint16_t yPos, uint8_t vol)
+static void showNoVolEfx(uint32_t xPos, uint32_t yPos, uint8_t vol, uint32_t color)
 {
 	// make compiler happy
-	(void)pal;
 	(void)xPos;
 	(void)yPos;
 	(void)vol;
+	(void)color;
 }
 
-static void showEfxNoVolColumn(uint8_t pal, uint16_t xPos, uint16_t yPos, uint8_t effTyp, uint8_t eff)
+static void showEfxNoVolColumn(uint32_t xPos, uint32_t yPos, uint8_t effTyp, uint8_t eff, uint32_t color)
 {
 	uint8_t charW, fontType;
 
@@ -619,65 +846,32 @@
 		xPos += 31;
 	}
 
-	pattCharOut(xPos,               yPos, pal, effTyp,     fontType);
-	pattCharOut(xPos +  charW,      yPos, pal, eff >> 4,   fontType);
-	pattCharOut(xPos + (charW * 2), yPos, pal, eff & 0x0F, fontType);
+	pattCharOut(xPos,               yPos, effTyp,     fontType, color);
+	pattCharOut(xPos +  charW,      yPos, eff >> 4,   fontType, color);
+	pattCharOut(xPos + (charW * 2), yPos, eff & 0x0F, fontType, color);
 }
 
-static void drawRowNumbers(const pattCoord_t *pattCoord, int16_t currRow, uint16_t rowHeight, uint16_t upperRowsYEnd, uint16_t pattLen)
+void writePattern(int32_t currRow, int32_t pattern)
 {
-	int16_t j, rowYPos, numRows;
-
-	// upper rows
-	numRows = currRow;
-	if (numRows > 0)
-	{
-		if (numRows > pattCoord->numUpperRows)
-			numRows = pattCoord->numUpperRows;
-
-		rowYPos = upperRowsYEnd;
-		for (j = 0; j < numRows; j++)
-		{
-			drawRowNum(rowYPos, currRow - j - 1, false);
-			rowYPos -= rowHeight;
-		}
-	}
-
-	// current row
-	drawRowNum(pattCoord->midRowTextY, currRow, true);
-
-	// lower rows
-	numRows = (pattLen - 1) - currRow;
-	if (numRows > 0)
-	{
-		if (numRows > pattCoord->numLowerRows)
-			numRows = pattCoord->numLowerRows;
-
-		rowYPos = pattCoord->lowerRowsTextY;
-		for (j = 0; j < numRows; j++)
-		{
-			drawRowNum(rowYPos, currRow + j + 1, false);
-			rowYPos += rowHeight;
-		}
-	}
-}
-
-void writePattern(int16_t currRow, int16_t pattern)
-{
-	uint8_t chans, chNum;
-	int16_t numRows, j;
-	uint16_t pattLen, xPos, rowYPos, rowHeight, chanWidth, upperRowsYEnd;
+	int32_t row, rowsOnScreen, numRows, afterCurrRow, numChannels;
+	int32_t textY, midRowTextY, lowerRowsTextY, xPos, xWidth;
+	uint32_t rowHeight, chanWidth, chans, noteTextColors[2], color;
 	tonTyp *note, *pattPtr;
 	const pattCoord_t *pattCoord;
-	void (*drawNote)(uint8_t, uint16_t, uint16_t, int16_t);
-	void (*drawInst)(uint8_t, uint16_t, uint16_t, uint8_t);
-	void (*drawVolEfx)(uint8_t, uint16_t, uint16_t, uint8_t);
-	void (*drawEfx)(uint8_t, uint16_t, uint16_t, uint8_t, uint8_t);
+	void (*drawNote)(uint32_t, uint32_t, int16_t, uint32_t);
+	void (*drawInst)(uint32_t, uint32_t, uint8_t, uint32_t);
+	void (*drawVolEfx)(uint32_t, uint32_t, uint8_t, uint32_t);
+	void (*drawEfx)(uint32_t, uint32_t, uint8_t, uint8_t, uint32_t);
 
-	/* We're too lazy to carefully erase things as needed, just render
-	** the whole pattern framework first (fast enough on modern PCs) */
+	/* Draw pattern framework every time (erasing existing content).
+	** FT2 doesn't do this. This is quite lazy and consumes more CPU
+	** time than needed (overlapped drawing), but it makes the pattern
+	** mark/cursor drawing MUCH simpler to implement...
+	*/
 	drawPatternBorders();
 
+	// setup variables
+
 	chans = editor.ui.numChannelsShown;
 	if (chans > editor.ui.maxVisibleChannels)
 		chans = editor.ui.maxVisibleChannels;
@@ -686,18 +880,26 @@
 
 	// get channel width
 	chanWidth = chanWidths[(chans / 2) - 1];
-	editor.ui.patternChannelWidth = chanWidth + 3;
+	editor.ui.patternChannelWidth = (uint16_t)(chanWidth + 3);
 
 	// get heights/pos/rows depending on configuration
-	pattCoord = &pattCoordTable[config.ptnUnpressed][editor.ui.pattChanScrollShown][editor.ui.extended];
 	rowHeight = config.ptnUnpressed ? 11 : 8;
-	upperRowsYEnd = pattCoord->upperRowsTextY + ((pattCoord->numUpperRows - 1) * rowHeight);
+	pattCoord = &pattCoordTable[config.ptnUnpressed][editor.ui.pattChanScrollShown][editor.ui.extended];
+	midRowTextY = pattCoord->midRowTextY;
+	lowerRowsTextY = pattCoord->lowerRowsTextY;
+	row = currRow - pattCoord->numUpperRows;
+	rowsOnScreen = pattCoord->numUpperRows + 1 + pattCoord->numLowerRows;
+	textY = pattCoord->upperRowsTextY;
 
+	afterCurrRow = currRow + 1;
+	numChannels = editor.ui.numChannelsShown;
 	pattPtr = patt[pattern];
-	pattLen = pattLens[pattern];
+	numRows = pattLens[pattern];
+	noteTextColors[0] = video.palette[PAL_PATTEXT]; // not selected
+	noteTextColors[1] = video.palette[PAL_FORGRND]; // selected
 
-	// draw row numbers
-	drawRowNumbers(pattCoord, currRow, rowHeight, upperRowsYEnd, pattLen);
+	if (pattPtr != NULL)
+		pattPtr += editor.ui.channelOffset;
 
 	// set up function pointers for drawing
 	if (config.ptnS3M)
@@ -716,91 +918,55 @@
 	}
 
 	// draw pattern data
-
-	xPos = 29;
-	for (uint8_t i = 0; i < editor.ui.numChannelsShown; i++)
+	for (int32_t i = 0; i < rowsOnScreen; i++)
 	{
-		chNum = editor.ui.channelOffset + i;
-
-		// upper rows
-		numRows = currRow;
-		if (numRows > 0)
+		if (row >= 0)
 		{
-			if (numRows > pattCoord->numUpperRows)
-				numRows = pattCoord->numUpperRows;
+			bool selectedRowFlag = row == currRow;
 
-			rowYPos = upperRowsYEnd;
-
+			drawRowNums(textY, (uint8_t)row, selectedRowFlag);
+	
 			if (pattPtr == NULL)
-				note = &emptyNote;
+				note = emptyPattern;
 			else
-				note = &pattPtr[((currRow - 1) * MAX_VOICES) + chNum];
+				note = &pattPtr[(uint32_t)row * MAX_VOICES];
 
-			for (j = 0; j < numRows; j++)
+			xPos = 29;
+			xWidth = editor.ui.patternChannelWidth;
+
+			color = noteTextColors[selectedRowFlag];
+			for (int32_t j = 0; j < numChannels; j++)
 			{
-				drawNote(PAL_PATTEXT, xPos, rowYPos, note->ton);
-				drawInst(PAL_PATTEXT, xPos, rowYPos, note->instr);
-				drawVolEfx(PAL_PATTEXT, xPos, rowYPos, note->vol);
-				drawEfx(PAL_PATTEXT, xPos, rowYPos, note->effTyp, note->eff);
+				drawNote(xPos, textY, note->ton, color);
+				drawInst(xPos, textY, note->instr, color);
+				drawVolEfx(xPos, textY, note->vol, color);
+				drawEfx(xPos, textY, note->effTyp, note->eff, color);
 
-				if (pattPtr != NULL)
-					note -= MAX_VOICES;
-
-				rowYPos -= rowHeight;
+				xPos += xWidth;
+				note++;
 			}
 		}
 
-		// current row
+		// next row
+		if (++row >= numRows)
+			break;
 
-		if (pattPtr == NULL)
-			note = &emptyNote;
+		// adjust textY position
+		if (row == currRow)
+			textY = midRowTextY;
+		else if (row == afterCurrRow)
+			textY = lowerRowsTextY;
 		else
-			note = &pattPtr[(currRow * MAX_VOICES) + chNum];
-
-		rowYPos = pattCoord->midRowTextY;
-
-		drawNote(PAL_FORGRND, xPos, rowYPos, note->ton);
-		drawInst(PAL_FORGRND, xPos, rowYPos, note->instr);
-		drawVolEfx(PAL_FORGRND, xPos, rowYPos, note->vol);
-		drawEfx(PAL_FORGRND, xPos, rowYPos, note->effTyp, note->eff);
-
-		// lower rows
-		numRows = (pattLen - 1) - currRow;
-		if (numRows > 0)
-		{
-			if (numRows > pattCoord->numLowerRows)
-				numRows = pattCoord->numLowerRows;
-
-			rowYPos = pattCoord->lowerRowsTextY;
-
-			if (pattPtr == NULL)
-				note = &emptyNote;
-			else
-				note = &pattPtr[((currRow + 1) * MAX_VOICES) + chNum];
-
-			for (j = 0; j < numRows; j++)
-			{
-				drawNote(PAL_PATTEXT, xPos, rowYPos, note->ton);
-				drawInst(PAL_PATTEXT, xPos, rowYPos, note->instr);
-				drawVolEfx(PAL_PATTEXT, xPos, rowYPos, note->vol);
-				drawEfx(PAL_PATTEXT, xPos, rowYPos, note->effTyp, note->eff);
-
-				if (pattPtr != NULL)
-					note += MAX_VOICES;
-
-				rowYPos += rowHeight;
-			}
-		}
-
-		xPos += editor.ui.patternChannelWidth;
+			textY += rowHeight;
 	}
 
 	writeCursor();
 
-	if (pattMark.markY1 != pattMark.markY2) // do we have a pattern mark?
+	// draw pattern marking (if anything is marked)
+	if (pattMark.markY1 != pattMark.markY2)
 		writePatternBlockMark(currRow, rowHeight, pattCoord);
 
-	// channel numbers must be drawn in the very end
+	// channel numbers must be drawn lastly
 	if (config.ptnChnNumbers)
 		drawChannelNumbering(pattCoord->upperRowsTextY);
 }
@@ -807,15 +973,13 @@
 
 // ========== OPTIMIZED CHARACTER DRAWING ROUTINES FOR PATTERN EDITOR ==========
 
-void pattTwoHexOut(uint32_t xPos, uint32_t yPos, uint8_t paletteIndex, uint8_t val)
+void pattTwoHexOut(uint32_t xPos, uint32_t yPos, uint8_t val, uint32_t color)
 {
 	const uint8_t *ch1Ptr, *ch2Ptr;
-	uint32_t *dstPtr, pixVal, offset;
+	uint32_t *dstPtr;
 
-	pixVal = video.palette[paletteIndex];
-	offset = config.ptnFont * (FONT4_WIDTH * FONT4_CHAR_H);
-	ch1Ptr = &font4Data[((val   >> 4) * FONT4_CHAR_W) + offset];
-	ch2Ptr = &font4Data[((val & 0x0F) * FONT4_CHAR_W) + offset];
+	ch1Ptr = &font4Ptr[(val   >> 4) * FONT4_CHAR_W];
+	ch2Ptr = &font4Ptr[(val & 0x0F) * FONT4_CHAR_W];
 	dstPtr = &video.frameBuffer[(yPos * SCREEN_W) + xPos];
 
 	for (uint32_t y = 0; y < FONT4_CHAR_H; y++)
@@ -822,8 +986,8 @@
 	{
 		for (uint32_t x = 0; x < FONT4_CHAR_W; x++)
 		{
-			if (ch1Ptr[x]) dstPtr[x] = pixVal;
-			if (ch2Ptr[x]) dstPtr[FONT4_CHAR_W + x] = pixVal;
+			if (ch1Ptr[x]) dstPtr[x] = color;
+			if (ch2Ptr[x]) dstPtr[FONT4_CHAR_W + x] = color;
 		}
 
 		ch1Ptr += FONT4_WIDTH;
@@ -832,46 +996,11 @@
 	}
 }
 
-static void rowNumOut(uint32_t yPos, uint8_t paletteIndex, uint8_t rowChar1, uint8_t rowChar2)
+static void pattCharOut(uint32_t xPos, uint32_t yPos, uint8_t chr, uint8_t fontType, uint32_t color)
 {
-	const uint8_t *ch1Ptr, *ch2Ptr;
-	uint32_t *dstPtr, pixVal, offset;
-
-	pixVal = video.palette[paletteIndex];
-	offset = config.ptnFont * (FONT4_WIDTH * FONT4_CHAR_H);
-	ch1Ptr = &font4Data[(rowChar1 * FONT4_CHAR_W) + offset];
-	ch2Ptr = &font4Data[(rowChar2 * FONT4_CHAR_W) + offset];
-	dstPtr = &video.frameBuffer[(yPos * SCREEN_W) + 8];
-
-	for (uint32_t y = 0; y < FONT4_CHAR_H; y++)
-	{
-		for (uint32_t x = 0; x < FONT4_CHAR_W; x++)
-		{
-			if (ch1Ptr[x])
-			{
-				dstPtr[x] = pixVal; // left side
-				dstPtr[600 + x] = pixVal; // right side
-			}
-
-			if (ch2Ptr[x])
-			{
-				dstPtr[ FONT4_CHAR_W + x] = pixVal; // left side
-				dstPtr[(600 + FONT4_CHAR_W) + x] = pixVal; // right side
-			}
-		}
-
-		ch1Ptr += FONT4_WIDTH;
-		ch2Ptr += FONT4_WIDTH;
-		dstPtr += SCREEN_W;
-	}
-}
-
-static void pattCharOut(uint32_t xPos, uint32_t yPos, uint8_t paletteIndex, uint8_t chr, uint8_t fontType)
-{
 	const uint8_t *srcPtr;
-	uint32_t x, y, *dstPtr, pixVal;
+	uint32_t x, y, *dstPtr;
 
-	pixVal = video.palette[paletteIndex];
 	dstPtr = &video.frameBuffer[(yPos * SCREEN_W) + xPos];
 
 	if (fontType == FONT_TYPE3)
@@ -882,7 +1011,7 @@
 			for (x = 0; x < FONT3_CHAR_W; x++)
 			{
 				if (srcPtr[x])
-					dstPtr[x] = pixVal;
+					dstPtr[x] = color;
 			}
 
 			srcPtr += FONT3_WIDTH;
@@ -891,13 +1020,13 @@
 	}
 	else if (fontType == FONT_TYPE4)
 	{
-		srcPtr = &font4Data[(chr * FONT4_CHAR_W) + (config.ptnFont * (FONT4_WIDTH * FONT4_CHAR_H))];
+		srcPtr = &font4Ptr[chr * FONT4_CHAR_W];
 		for (y = 0; y < FONT4_CHAR_H; y++)
 		{
 			for (x = 0; x < FONT4_CHAR_W; x++)
 			{
 				if (srcPtr[x])
-					dstPtr[x] = pixVal;
+					dstPtr[x] = color;
 			}
 
 			srcPtr += FONT4_WIDTH;
@@ -906,13 +1035,13 @@
 	}
 	else if (fontType == FONT_TYPE5)
 	{
-		srcPtr = &font5Data[(chr * FONT5_CHAR_W) + (config.ptnFont * (FONT5_WIDTH * FONT5_CHAR_H))];
+		srcPtr = &font5Ptr[chr * FONT5_CHAR_W];
 		for (y = 0; y < FONT5_CHAR_H; y++)
 		{
 			for (x = 0; x < FONT5_CHAR_W; x++)
 			{
 				if (srcPtr[x])
-					dstPtr[x] = pixVal;
+					dstPtr[x] = color;
 			}
 
 			srcPtr += FONT5_WIDTH;
@@ -927,7 +1056,7 @@
 			for (x = 0; x < FONT7_CHAR_W; x++)
 			{
 				if (srcPtr[x])
-					dstPtr[x] = pixVal;
+					dstPtr[x] = color;
 			}
 
 			srcPtr += FONT7_WIDTH;
@@ -936,12 +1065,11 @@
 	}
 }
 
-static void drawEmptyNoteSmall(uint16_t xPos, uint16_t yPos, uint8_t paletteIndex)
+static void drawEmptyNoteSmall(uint32_t xPos, uint32_t yPos, uint32_t color)
 {
 	const uint8_t *srcPtr;
-	uint32_t *dstPtr, pixVal;
+	uint32_t *dstPtr;
 
-	pixVal = video.palette[paletteIndex];
 	srcPtr = &font7Data[18 * FONT7_CHAR_W];
 	dstPtr = &video.frameBuffer[(yPos * SCREEN_W) + xPos];
 
@@ -950,7 +1078,7 @@
 		for (uint32_t x = 0; x < FONT7_CHAR_W*3; x++)
 		{
 			if (srcPtr[x])
-				dstPtr[x] = pixVal;
+				dstPtr[x] = color;
 		}
 
 		srcPtr += FONT7_WIDTH;
@@ -958,12 +1086,11 @@
 	}
 }
 
-static void drawKeyOffSmall(uint16_t xPos, uint16_t yPos, uint8_t paletteIndex)
+static void drawKeyOffSmall(uint32_t xPos, uint32_t yPos, uint32_t color)
 {
 	const uint8_t *srcPtr;
-	uint32_t *dstPtr, pixVal;
+	uint32_t *dstPtr;
 
-	pixVal = video.palette[paletteIndex];
 	srcPtr = &font7Data[21 * FONT7_CHAR_W];
 	dstPtr = &video.frameBuffer[(yPos * SCREEN_W) + (xPos + 2)];
 
@@ -972,7 +1099,7 @@
 		for (uint32_t x = 0; x < FONT7_CHAR_W*2; x++)
 		{
 			if (srcPtr[x])
-				dstPtr[x] = pixVal;
+				dstPtr[x] = color;
 		}
 
 		srcPtr += FONT7_WIDTH;
@@ -980,18 +1107,18 @@
 	}
 }
 
-static void drawNoteSmall(uint16_t xPos, uint16_t yPos, uint8_t paletteIndex, int16_t ton)
+static void drawNoteSmall(uint32_t xPos, uint32_t yPos, int32_t ton, uint32_t color)
 {
 	const uint8_t *ch1Ptr, *ch2Ptr, *ch3Ptr;
 	uint8_t note;
-	uint32_t *dstPtr, pixVal, char1, char2, char3;
+	uint32_t *dstPtr, char1, char2, char3;
 
 	assert(ton >= 1 && ton <= 97);
 
 	ton--;
 
-	note  =  ton % 12;
-	char3 = (ton / 12) * FONT7_CHAR_W;
+	note = noteTab1[ton];
+	char3 = noteTab2[ton] * FONT7_CHAR_W;
 
 	if (config.ptnAcc == 0)
 	{
@@ -1004,7 +1131,6 @@
 		char2 = flatNote2Char_small[note];
 	}
 
-	pixVal = video.palette[paletteIndex];
 	ch1Ptr = &font7Data[char1];
 	ch2Ptr = &font7Data[char2];
 	ch3Ptr = &font7Data[char3];
@@ -1014,9 +1140,9 @@
 	{
 		for (uint32_t x = 0; x < FONT7_CHAR_W; x++)
 		{
-			if (ch1Ptr[x]) dstPtr[ (FONT7_CHAR_W * 0)      + x] = pixVal;
-			if (ch2Ptr[x]) dstPtr[ (FONT7_CHAR_W * 1)      + x] = pixVal;
-			if (ch3Ptr[x]) dstPtr[((FONT7_CHAR_W * 2) - 2) + x] = pixVal;
+			if (ch1Ptr[x]) dstPtr[ (FONT7_CHAR_W * 0)      + x] = color;
+			if (ch2Ptr[x]) dstPtr[ (FONT7_CHAR_W * 1)      + x] = color;
+			if (ch3Ptr[x]) dstPtr[((FONT7_CHAR_W * 2) - 2) + x] = color;
 		}
 
 		ch1Ptr += FONT7_WIDTH;
@@ -1026,14 +1152,13 @@
 	}
 }
 
-static void drawEmptyNoteMedium(uint16_t xPos, uint16_t yPos, uint8_t paletteIndex)
+static void drawEmptyNoteMedium(uint32_t xPos, uint32_t yPos, uint32_t color)
 {
 	const uint8_t *srcPtr;
-	uint32_t *dstPtr, pixVal;
+	uint32_t *dstPtr;
 
-	pixVal = video.palette[paletteIndex];
 	dstPtr = &video.frameBuffer[(yPos * SCREEN_W) + xPos];
-	srcPtr = &font4Data[(43 * FONT4_CHAR_W) + (config.ptnFont * (FONT4_WIDTH * FONT4_CHAR_H))];
+	srcPtr = &font4Ptr[43 * FONT4_CHAR_W];
 
 	for (uint32_t y = 0; y < FONT4_CHAR_H; y++)
 	{
@@ -1040,7 +1165,7 @@
 		for (uint32_t x = 0; x < FONT4_CHAR_W*3; x++)
 		{
 			if (srcPtr[x])
-				dstPtr[x] = pixVal;
+				dstPtr[x] = color;
 		}
 
 		srcPtr += FONT4_WIDTH;
@@ -1048,13 +1173,12 @@
 	}
 }
 
-static void drawKeyOffMedium(uint16_t xPos, uint16_t yPos, uint8_t paletteIndex)
+static void drawKeyOffMedium(uint32_t xPos, uint32_t yPos, uint32_t color)
 {
 	const uint8_t *srcPtr;
-	uint32_t *dstPtr, pixVal;
+	uint32_t *dstPtr;
 
-	pixVal = video.palette[paletteIndex];
-	srcPtr = &font4Data[(40 * FONT4_CHAR_W) + (config.ptnFont * (FONT4_WIDTH * FONT4_CHAR_H))];
+	srcPtr = &font4Ptr[40 * FONT4_CHAR_W];
 	dstPtr = &video.frameBuffer[(yPos * SCREEN_W) + xPos];
 
 	for (uint32_t y = 0; y < FONT4_CHAR_H; y++)
@@ -1062,7 +1186,7 @@
 		for (uint32_t x = 0; x < FONT4_CHAR_W*3; x++)
 		{
 			if (srcPtr[x])
-				dstPtr[x] = pixVal;
+				dstPtr[x] = color;
 		}
 
 		srcPtr += FONT4_WIDTH;
@@ -1070,16 +1194,15 @@
 	}
 }
 
-static void drawNoteMedium(uint16_t xPos, uint16_t yPos, uint8_t paletteIndex, int16_t ton)
+static void drawNoteMedium(uint32_t xPos, uint32_t yPos, int32_t ton, uint32_t color)
 {
 	const uint8_t *ch1Ptr, *ch2Ptr, *ch3Ptr;
-	uint8_t note;
-	uint32_t *dstPtr, pixVal, fontOffset, char1, char2, char3;
+	uint32_t note, *dstPtr, char1, char2, char3;
 
 	ton--;
 
-	note  =  ton % 12;
-	char3 = (ton / 12) * FONT4_CHAR_W;
+	note = noteTab1[ton];
+	char3 = noteTab2[ton] * FONT4_CHAR_W;
 
 	if (config.ptnAcc == 0)
 	{
@@ -1092,11 +1215,9 @@
 		char2 = flatNote2Char_med[note];
 	}
 
-	pixVal = video.palette[paletteIndex];
-	fontOffset = config.ptnFont * (FONT4_WIDTH * FONT4_CHAR_H);
-	ch1Ptr = &font4Data[char1 + fontOffset];
-	ch2Ptr = &font4Data[char2 + fontOffset];
-	ch3Ptr = &font4Data[char3 + fontOffset];
+	ch1Ptr = &font4Ptr[char1];
+	ch2Ptr = &font4Ptr[char2];
+	ch3Ptr = &font4Ptr[char3];
 	dstPtr = &video.frameBuffer[(yPos * SCREEN_W) + xPos];
 
 	for (uint32_t y = 0; y < FONT4_CHAR_H; y++)
@@ -1103,9 +1224,9 @@
 	{
 		for (uint32_t x = 0; x < FONT4_CHAR_W; x++)
 		{
-			if (ch1Ptr[x]) dstPtr[(FONT4_CHAR_W * 0) + x] = pixVal;
-			if (ch2Ptr[x]) dstPtr[(FONT4_CHAR_W * 1) + x] = pixVal;
-			if (ch3Ptr[x]) dstPtr[(FONT4_CHAR_W * 2) + x] = pixVal;
+			if (ch1Ptr[x]) dstPtr[(FONT4_CHAR_W * 0) + x] = color;
+			if (ch2Ptr[x]) dstPtr[(FONT4_CHAR_W * 1) + x] = color;
+			if (ch3Ptr[x]) dstPtr[(FONT4_CHAR_W * 2) + x] = color;
 		}
 
 		ch1Ptr += FONT4_WIDTH;
@@ -1115,13 +1236,12 @@
 	}
 }
 
-static void drawEmptyNoteBig(uint16_t xPos, uint16_t yPos, uint8_t paletteIndex)
+static void drawEmptyNoteBig(uint32_t xPos, uint32_t yPos, uint32_t color)
 {
 	const uint8_t *srcPtr;
-	uint32_t *dstPtr, pixVal;
+	uint32_t *dstPtr;
 
-	pixVal = video.palette[paletteIndex];
-	srcPtr = &font4Data[(67 * FONT4_CHAR_W) + (config.ptnFont * (FONT4_WIDTH * FONT4_CHAR_H))];
+	srcPtr = &font4Ptr[67 * FONT4_CHAR_W];
 	dstPtr = &video.frameBuffer[(yPos * SCREEN_W) + xPos];
 
 	for (uint32_t y = 0; y < FONT4_CHAR_H; y++)
@@ -1129,7 +1249,7 @@
 		for (uint32_t x = 0; x < FONT4_CHAR_W*6; x++)
 		{
 			if (srcPtr[x])
-				dstPtr[x] = pixVal;
+				dstPtr[x] = color;
 		}
 
 		srcPtr += FONT4_WIDTH;
@@ -1137,13 +1257,12 @@
 	}
 }
 
-static void drawKeyOffBig(uint16_t xPos, uint16_t yPos, uint8_t paletteIndex)
+static void drawKeyOffBig(uint32_t xPos, uint32_t yPos, uint32_t color)
 {
 	const uint8_t *srcPtr;
-	uint32_t *dstPtr, pixVal;
+	uint32_t *dstPtr;
 
-	pixVal = video.palette[paletteIndex];
-	srcPtr = &font4Data[(61 * FONT4_CHAR_W) + (config.ptnFont * (FONT4_WIDTH * FONT4_CHAR_H))];
+	srcPtr = &font4Data[61 * FONT4_CHAR_W];
 	dstPtr = &video.frameBuffer[(yPos * SCREEN_W) + xPos];
 
 	for (uint32_t y = 0; y < FONT4_CHAR_H; y++)
@@ -1151,7 +1270,7 @@
 		for (uint32_t x = 0; x < FONT4_CHAR_W*6; x++)
 		{
 			if (srcPtr[x])
-				dstPtr[x] = pixVal;
+				dstPtr[x] = color;
 		}
 
 		srcPtr += FONT4_WIDTH;
@@ -1159,16 +1278,16 @@
 	}
 }
 
-static void drawNoteBig(uint16_t xPos, uint16_t yPos, uint8_t paletteIndex, int16_t ton)
+static void drawNoteBig(uint32_t xPos, uint32_t yPos, int32_t ton, uint32_t color)
 {
 	const uint8_t *ch1Ptr, *ch2Ptr, *ch3Ptr;
 	uint8_t note;
-	uint32_t *dstPtr, pixVal, fontOffset, char1, char2, char3;
+	uint32_t *dstPtr, char1, char2, char3;
 
 	ton--;
 
-	note  =  ton % 12;
-	char3 = (ton / 12) * FONT5_CHAR_W;
+	note = noteTab1[ton];
+	char3 = noteTab2[ton] * FONT5_CHAR_W;
 
 	if (config.ptnAcc == 0)
 	{
@@ -1181,11 +1300,9 @@
 		char2 = flatNote2Char_big[note];
 	}
 
-	pixVal = video.palette[paletteIndex];
-	fontOffset = config.ptnFont * (FONT5_WIDTH * FONT5_CHAR_H);
-	ch1Ptr = &font5Data[char1 + fontOffset];
-	ch2Ptr = &font5Data[char2 + fontOffset];
-	ch3Ptr = &font5Data[char3 + fontOffset];
+	ch1Ptr = &font5Ptr[char1];
+	ch2Ptr = &font5Ptr[char2];
+	ch3Ptr = &font5Ptr[char3];
 	dstPtr = &video.frameBuffer[(yPos * SCREEN_W) + xPos];
 
 	for (uint32_t y = 0; y < FONT5_CHAR_H; y++)
@@ -1192,9 +1309,9 @@
 	{
 		for (uint32_t x = 0; x < FONT5_CHAR_W; x++)
 		{
-			if (ch1Ptr[x]) dstPtr[(FONT5_CHAR_W * 0) + x] = pixVal;
-			if (ch2Ptr[x]) dstPtr[(FONT5_CHAR_W * 1) + x] = pixVal;
-			if (ch3Ptr[x]) dstPtr[(FONT5_CHAR_W * 2) + x] = pixVal;
+			if (ch1Ptr[x]) dstPtr[(FONT5_CHAR_W * 0) + x] = color;
+			if (ch2Ptr[x]) dstPtr[(FONT5_CHAR_W * 1) + x] = color;
+			if (ch3Ptr[x]) dstPtr[(FONT5_CHAR_W * 2) + x] = color;
 		}
 
 		ch1Ptr += FONT5_WIDTH;
@@ -1203,145 +1320,3 @@
 		dstPtr += SCREEN_W;
 	}
 }
-
-const pattCoord_t pattCoordTable[2][2][2] =
-{
-	/*
-		uint16_t upperRowsY, lowerRowsY;
-		uint16_t upperRowsTextY, midRowTextY, lowerRowsTextY;
-		uint16_t numUpperRows, numLowerRows;
-	*/
-
-	// no pattern stretch
-	{
-		// no pattern channel scroll
-		{ 
-			{ 176, 292, 177, 283, 293, 13, 13 }, // normal pattern editor
-			{  56, 228,  57, 219, 229, 20, 21 }, // extended pattern editor
-		},
-
-		// pattern channel scroll
-		{
-			{ 176, 285, 177, 276, 286, 12, 12 }, // normal pattern editor
-			{  56, 221,  57, 212, 222, 19, 20 }, // extended pattern editor
-		}
-	},
-
-	// pattern stretch
-	{
-		// no pattern channel scroll
-		{
-			{ 177, 286, 178, 277, 288,  9, 10 }, // normal pattern editor
-			{  56, 232,  58, 223, 234, 15, 15 }, // extended pattern editor
-		},
-
-		// pattern channel scroll
-		{
-			{  176, 285, 177, 276, 286,  9,  9 }, // normal pattern editor
-			{   56, 220,  57, 211, 221, 14, 15 }, // extended pattern editor
-		},
-	}
-};
-
-const pattCoord2_t pattCoord2Table[2][2][2] =
-{
-	/*
-		uint16_t upperRowsY, lowerRowsY;
-		uint16_t upperRowsH, lowerRowsH;
-	*/
-
-	// no pattern stretch
-	{
-		// no pattern channel scroll
-		{
-			{ 175, 291, 107, 107 }, //   normal pattern editor
-			{  55, 227, 163, 171 }, // extended pattern editor
-		},
-
-		// pattern channel scroll
-		{
-			{ 175, 284, 100, 100 }, //   normal pattern editor
-			{  55, 220, 156, 164 }, // extended pattern editor
-		}
-	},
-
-	// pattern stretch
-	{
-		// no pattern channel scroll
-		{
-			{ 175, 285, 101, 113 }, //   normal pattern editor
-			{  55, 231, 167, 167 }, // extended pattern editor
-		},
-
-		// pattern channel scroll
-		{
-			{ 175, 284, 100, 100 }, //   normal pattern editor
-			{  55, 219, 155, 165 }, // extended pattern editor
-		},
-	}
-};
-
-const markCoord_t markCoordTable[2][2][2] =
-{
-	// uint16_t upperRowsY, midRowY, lowerRowsY;
-
-	// no pattern stretch
-	{
-		// no pattern channel scroll
-		{
-			{ 177, 281, 293 }, //   normal pattern editor
-			{  57, 217, 229 }, // extended pattern editor
-		},
-
-		// pattern channel scroll
-		{
-			{ 177, 274, 286 }, //   normal pattern editor
-			{  57, 210, 222 }, // extended pattern editor
-		}
-	},
-
-	// pattern stretch
-	{
-		// no pattern channel scroll
-		{
-			{ 176, 275, 286 }, //   normal pattern editor
-			{  56, 221, 232 }, // extended pattern editor
-		},
-
-		// pattern channel scroll
-		{
-			{ 175, 274, 284 }, //   normal pattern editor
-			{  55, 209, 219 }, // extended pattern editor
-		},
-	}
-};
-
-const uint8_t pattCursorXTab[2 * 4 * 8] =
-{
-	// no volume column shown
-	32, 88, 104, 0, 0, 120, 136, 152, //  4 columns visible
-	32, 80,  88, 0, 0,  96, 104, 112, //  6 columns visible
-	32, 56,  64, 0, 0,  72,  80,  88, //  8 columns visible
-	32, 52,  56, 0, 0,  60,  64,  68, // 12 columns visible
-
-	// volume column shown
-	32, 96, 104, 120, 128, 144, 152, 160, //  4 columns visible
-	32, 56,  64,  80,  88,  96, 104, 112, //  6 columns visible
-	32, 60,  64,  72,  76,  84,  88,  92, //  8 columns visible
-	32, 60,  64,  72,  76,  84,  88,  92, // 12 columns visible
-};
-
-const uint8_t pattCursorWTab[2 * 4 * 8] =
-{
-	// no volume column shown
-	48, 16, 16, 0, 0, 16, 16, 16, //  4 columns visible
-	48,  8,  8, 0, 0,  8,  8,  8, //  6 columns visible
-	24,  8,  8, 0, 0,  8,  8,  8, //  8 columns visible
-	16,  4,  4, 0, 0,  4,  4,  4, // 12 columns visible
-
-	// volume column shown
-	48,  8,  8,  8,  8,  8,  8,  8, //  4 columns visible
-	24,  8,  8,  8,  8,  8,  8,  8, //  6 columns visible
-	24,  4,  4,  4,  4,  4,  4,  4, //  8 columns visible
-	24,  4,  4,  4,  4,  4,  4,  4  // 12 columns visible
-};
--- a/src/ft2_pattern_draw.h
+++ b/src/ft2_pattern_draw.h
@@ -2,6 +2,7 @@
 
 #include <stdint.h>
 
+void updatePattFontPtrs(void);
 void drawPatternBorders(void);
-void writePattern(int16_t currRow, int16_t pattern);
-void pattTwoHexOut(uint32_t xPos, uint32_t yPos, uint8_t paletteIndex, uint8_t val);
+void writePattern(int32_t currRow, int32_t pattern);
+void pattTwoHexOut(uint32_t xPos, uint32_t yPos, uint8_t val, uint32_t color);
--- a/src/ft2_pattern_ed.c
+++ b/src/ft2_pattern_ed.c
@@ -21,6 +21,8 @@
 #include "ft2_mouse.h"
 #include "ft2_video.h"
 
+#include "ft2_module_loader.h"
+
 // for pattern marking w/ keyboard
 static int8_t lastChMark;
 static int16_t lastRowMark;
@@ -657,7 +659,13 @@
 	drawFramework(2,  2, 51, 20, FRAMEWORK_TYPE2);
 	drawFramework(2, 31, 51, 20, FRAMEWORK_TYPE2);
 
+	// force updating of end/page/length when showing scrollbar
+	scrollBars[SB_POS_ED].oldEnd = 0xFFFFFFFF;
+	scrollBars[SB_POS_ED].oldPage = 0xFFFFFFFF;
+	scrollBars[SB_POS_ED].oldPos = 0xFFFFFFFF;
+
 	showScrollBar(SB_POS_ED);
+
 	showPushButton(PB_POSED_POS_UP);
 	showPushButton(PB_POSED_POS_DOWN);
 	showPushButton(PB_POSED_INS);
@@ -2063,7 +2071,11 @@
 {
 	uint8_t y;
 	int16_t entry;
+	uint32_t color1, color2;
 
+	color1 = video.palette[PAL_PATTEXT];
+	color2 = video.palette[PAL_FORGRND];
+
 	if (songPos >= song.len)
 		songPos = song.len - 1;
 
@@ -2092,13 +2104,13 @@
 
 		if (editor.ui.extended)
 		{
-			pattTwoHexOut(8,  4 + (y * 9), PAL_PATTEXT, (uint8_t)entry);
-			pattTwoHexOut(32, 4 + (y * 9), PAL_PATTEXT, song.songTab[entry]);
+			pattTwoHexOut(8,  4 + (y * 9), (uint8_t)entry, color1);
+			pattTwoHexOut(32, 4 + (y * 9), song.songTab[entry], color1);
 		}
 		else
 		{
-			pattTwoHexOut(8,  4 + (y * 8), PAL_PATTEXT, (uint8_t)entry);
-			pattTwoHexOut(32, 4 + (y * 8), PAL_PATTEXT, song.songTab[entry]);
+			pattTwoHexOut(8,  4 + (y * 8), (uint8_t)entry, color1);
+			pattTwoHexOut(32, 4 + (y * 8), song.songTab[entry], color1);
 		}
 	}
 
@@ -2107,13 +2119,13 @@
 	// middle
 	if (editor.ui.extended)
 	{
-		pattTwoHexOut(8,  23, PAL_FORGRND, (uint8_t)songPos);
-		pattTwoHexOut(32, 23, PAL_FORGRND, song.songTab[songPos]);
+		pattTwoHexOut(8,  23, (uint8_t)songPos, color2);
+		pattTwoHexOut(32, 23, song.songTab[songPos], color2);
 	}
 	else
 	{
-		pattTwoHexOut(8,  22, PAL_FORGRND, (uint8_t)songPos);
-		pattTwoHexOut(32, 22, PAL_FORGRND, song.songTab[songPos]);
+		pattTwoHexOut(8,  22, (uint8_t)songPos, color2);
+		pattTwoHexOut(32, 22, song.songTab[songPos], color2);
 	}
 
 	// bottom two
@@ -2125,13 +2137,13 @@
 
 		if (editor.ui.extended)
 		{
-			pattTwoHexOut(8,  33 + (y * 9), PAL_PATTEXT, (uint8_t)entry);
-			pattTwoHexOut(32, 33 + (y * 9), PAL_PATTEXT, song.songTab[entry]);
+			pattTwoHexOut(8,  33 + (y * 9), (uint8_t)entry, color1);
+			pattTwoHexOut(32, 33 + (y * 9), song.songTab[entry], color1);
 		}
 		else
 		{
-			pattTwoHexOut(8,  32 + (y * 8), PAL_PATTEXT, (uint8_t)entry);
-			pattTwoHexOut(32, 32 + (y * 8), PAL_PATTEXT, song.songTab[entry]);
+			pattTwoHexOut(8,  32 + (y * 8), (uint8_t)entry, color1);
+			pattTwoHexOut(32, 32 + (y * 8), song.songTab[entry], color1);
 		}
 	}
 }
@@ -2256,7 +2268,7 @@
 	char str[2 + 1];
 	uint32_t a, MI_TimeH, MI_TimeM, MI_TimeS;
 
-	a = ((song.musicTime / 256) * 5) / 512;
+	a = ((song.musicTime >> 8) * 5) >> 9;
 	MI_TimeH = a / 3600;
 	a -= (MI_TimeH * 3600);
 	MI_TimeM = a / 60;
@@ -2803,6 +2815,7 @@
 
 void resetChannelOffset(void)
 {
+	editor.ui.pattChanScrollShown = song.antChn > getMaxVisibleChannels();
 	editor.cursor.object = CURSOR_NOTE;
 	editor.cursor.ch = 0;
 	setScrollBarPos(SB_CHAN_SCROLL, 0, true);
--- a/src/ft2_pattern_ed.h
+++ b/src/ft2_pattern_ed.h
@@ -74,7 +74,6 @@
 void cursorRight(void);
 void chanLeft(void);
 void chanRight(void);
-void writePattern(int16_t currRow, int16_t pattern);
 void showPatternEditor(void);
 void updateInstrumentSwitcher(void);
 void hidePatternEditor(void);
--- a/src/ft2_replayer.c
+++ b/src/ft2_replayer.c
@@ -422,7 +422,7 @@
 	if (ton > 96) // non-FT2 security (should never happen because I clamp in the patt loaders now)
 		ton = 96;
 
-	smp = ins->ta[ton-1] & 0x0F;
+	smp = ins->ta[ton-1] & 0xF;
 	ch->sampleNr = smp;
 
 	s = &ins->samp[smp];
@@ -1208,9 +1208,9 @@
 	uint8_t envPos;
 	int16_t autoVibVal, panTmp;
 	uint16_t autoVibAmp, tmpPeriod, envVal;
-	int32_t vol, tmp32;
+	int32_t tmp32;
+	uint32_t vol;
 	instrTyp *ins;
-	double dVol;
 
 	ins = ch->instrSeg;
 
@@ -1257,7 +1257,7 @@
 						{
 							envPos = ins->envVRepS;
 							ch->envVCnt = ins->envVP[envPos][0];
-							ch->envVAmp = (ins->envVP[envPos][1] & 0xFF) << 8;
+							ch->envVAmp = ins->envVP[envPos][1] << 8;
 						}
 					}
 
@@ -1284,7 +1284,7 @@
 						ch->envVIPValue = 0;
 						if (ins->envVP[envPos][0] > ins->envVP[envPos-1][0])
 						{
-							ch->envVIPValue = ((ins->envVP[envPos][1] - ins->envVP[envPos-1][1]) & 0xFF) << 8;
+							ch->envVIPValue = (ins->envVP[envPos][1] - ins->envVP[envPos-1][1]) << 8;
 							ch->envVIPValue /= (ins->envVP[envPos][0] - ins->envVP[envPos-1][0]);
 
 							envVal = ch->envVAmp;
@@ -1303,10 +1303,10 @@
 				ch->envVAmp += ch->envVIPValue;
 
 				envVal = ch->envVAmp;
-				if ((envVal >> 8) > 0x40)
+				if (envVal > 64*256)
 				{
-					if ((envVal >> 8) > (0x40+0xC0)/2)
-						envVal = 16384;
+					if (envVal > 128*256)
+						envVal = 64*256;
 					else
 						envVal = 0;
 
@@ -1314,41 +1314,36 @@
 				}
 			}
 
-			/* old integer method with low precision (FT2 way)
+			/* original FT2 method with lower precision (finalVol = 0..256)
 			envVal >>= 8;
 			ch->finalVol = (song.globVol * (((envVal * ch->outVol) * ch->fadeOutAmp) >> (16 + 2))) >> 7;
 			*/
 
-			/* calculate with four times more precision (+ rounding).
-			** also, env. range is now 0..16384 instead of being shifted to 0..64. */
+			/* calculate with four times more precision (finalVol = 0..2048)
+			** Also, vol envelope range is now 0..16384 instead of being shifted to 0..64
+			*/
 
-			dVol = song.globVol * ch->outVol * ch->fadeOutAmp;
-			dVol *= envVal; // we need a float mul because it would overflow 32-bit integer
-			dVol *= 1.0 / ((64.0 * 64.0 * 32768.0 * 16384.0) / 2048.0); // 0..2048 (real FT2 is 0..256)
+			uint32_t vol1 = song.globVol * ch->outVol * ch->fadeOutAmp; // 0..64 * 0..64 * 0..32768 = 0..134217728
+			uint32_t vol2 = envVal << 2; // 0..16384 * 2^2 = 0..65536
 
-			vol = (int32_t)(dVol + 0.5);
-			if (vol > 2048)
-				vol = 2048;
+			vol = ((uint64_t)vol1 * vol2) >> 32; // 0..2048
 
-			ch->finalVol = (uint16_t)vol;
 			ch->status |= IS_Vol;
 		}
 		else
 		{
-			/* old integer method with low precision (FT2 way)
+			/* original FT2 method with lower precision (finalVol = 0..256)
 			ch->finalVol = (song.globVol * (((ch->outVol << 4) * ch->fadeOutAmp) >> 16)) >> 7;
 			*/
 
-			// calculate with four times more precision (+ rounding)
-			dVol = song.globVol * ch->outVol * ch->fadeOutAmp;
-			dVol *= 1.0 / ((64.0 * 64.0 * 32768.0) / 2048.0); // 0..2048 (real FT2 is 0..256)
+			// calculate with four times more precision (finalVol = 0..2048)
+			vol = (song.globVol * ch->outVol * ch->fadeOutAmp) >> 16; // 0..64 * 0..64 * 0..32768 = 0..2048
+		}
 
-			vol = (int32_t)(dVol + 0.5);
-			if (vol > 2048)
-				vol = 2048;
+		if (vol > 2047)
+			vol = 2047; // range is now 0..2047 to prevent MUL overflow when voice volume is calculated
 
-			ch->finalVol = (uint16_t)vol;
-		}
+		ch->finalVol = (uint16_t)vol;
 	}
 	else
 	{
@@ -1365,7 +1360,7 @@
 
 		if (++ch->envPCnt == ins->envPP[envPos][0])
 		{
-			ch->envPAmp = (ins->envPP[envPos][1] & 0xFF) << 8;
+			ch->envPAmp = ins->envPP[envPos][1] << 8;
 
 			envPos++;
 			if (ins->envPTyp & 4)
@@ -1379,7 +1374,7 @@
 						envPos = ins->envPRepS;
 
 						ch->envPCnt = ins->envPP[envPos][0];
-						ch->envPAmp = (ins->envPP[envPos][1] & 0xFF) << 8;
+						ch->envPAmp = ins->envPP[envPos][1] << 8;
 					}
 				}
 
@@ -1406,7 +1401,7 @@
 					ch->envPIPValue = 0;
 					if (ins->envPP[envPos][0] > ins->envPP[envPos-1][0])
 					{
-						ch->envPIPValue = ((ins->envPP[envPos][1] - ins->envPP[envPos-1][1]) & 0xFF) << 8;
+						ch->envPIPValue = (ins->envPP[envPos][1] - ins->envPP[envPos-1][1]) << 8;
 						ch->envPIPValue /= (ins->envPP[envPos][0] - ins->envPP[envPos-1][0]);
 
 						envVal = ch->envPAmp;
@@ -1425,10 +1420,10 @@
 			ch->envPAmp += ch->envPIPValue;
 
 			envVal = ch->envPAmp;
-			if ((envVal >> 8) > 0x40)
+			if (envVal > 64*256)
 			{
-				if ((envVal >> 8) > (0x40+0xC0)/2)
-					envVal = 16384;
+				if (envVal > 128*256)
+					envVal = 64*256;
 				else
 					envVal = 0;
 
@@ -1441,9 +1436,9 @@
 			panTmp = 0 - panTmp;
 		panTmp += 128;
 
-		envVal -= (32 * 256);
+		envVal -= 32*256;
 
-		ch->finalPan = ch->outPan + ((((int16_t)envVal * (panTmp << 3)) >> 16) & 0xFF);
+		ch->finalPan = ch->outPan + (uint8_t)(((int16_t)envVal * panTmp) >> 13);
 		ch->status |= IS_Pan;
 	}
 	else
@@ -2589,7 +2584,7 @@
 		}
 	}
 
-	editor.ui.pattChanScrollShown = (song.antChn > getMaxVisibleChannels());
+	editor.ui.pattChanScrollShown = song.antChn > getMaxVisibleChannels();
 
 	if (editor.ui.patternEditorShown)
 	{
--- a/src/ft2_scopes.c
+++ b/src/ft2_scopes.c
@@ -86,6 +86,10 @@
 	/* 32 ch */ {15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15}
 };
 
+// ft2_pattern_draw.c
+extern const char chDecTab1[MAX_VOICES+1];
+extern char chDecTab2[MAX_VOICES+1];
+
 void resetOldScopeRates(void)
 {
 	oldVoiceDelta = 0;
@@ -175,8 +179,8 @@
 		}
 		else
 		{
-			charOutOutlined(scopeXOffs, scopeYOffs, PAL_MOUSEPT, '0' + (channel / 10));
-			charOutOutlined(scopeXOffs + 7, scopeYOffs, PAL_MOUSEPT, '0' + (channel % 10));
+			charOutOutlined(scopeXOffs, scopeYOffs, PAL_MOUSEPT, chDecTab1[channel]);
+			charOutOutlined(scopeXOffs + 7, scopeYOffs, PAL_MOUSEPT, chDecTab2[channel]);
 		}
 	}
 	else
@@ -187,8 +191,8 @@
 		}
 		else
 		{
-			charOut(scopeXOffs, scopeYOffs, PAL_MOUSEPT, '0' + (channel / 10));
-			charOut(scopeXOffs + 7, scopeYOffs, PAL_MOUSEPT, '0' + (channel % 10));
+			charOut(scopeXOffs, scopeYOffs, PAL_MOUSEPT, chDecTab1[channel]);
+			charOut(scopeXOffs + 7, scopeYOffs, PAL_MOUSEPT, chDecTab2[channel]);
 		}
 	}
 }
@@ -597,7 +601,7 @@
 			if (ch->voiceDelta != oldVoiceDelta)
 			{
 				oldVoiceDelta = ch->voiceDelta;
-				oldSFrq = (uint32_t)((oldVoiceDelta * audio.dScopeFreqMul) + 0.5); // rounded
+				oldSFrq = (int32_t)((oldVoiceDelta * audio.dScopeFreqMul) + 0.5); // rounded
 			}
 
 			sc->SFrq = oldSFrq;
--- a/src/ft2_scrollbars.c
+++ b/src/ft2_scrollbars.c
@@ -355,6 +355,10 @@
 	assert(scrollBarID < NUM_SCROLLBARS);
 	scrollBar = &scrollBars[scrollBarID];
 
+	if (scrollBar->oldPos == pos)
+		return;
+	scrollBar->oldPos = pos;
+
 	if (scrollBar->page == 0)
 	{
 		scrollBar->pos = 0;
@@ -405,8 +409,12 @@
 	if (end < 1)
 		end = 1;
 
-	scrollBar->end = end;
+	if (scrollBar->oldEnd == end)
+		return;
+	scrollBar->oldEnd = end;
 
+	scrollBar->end = end;
+	
 	setPos = false;
 	if (scrollBar->pos >= end)
 	{
@@ -439,6 +447,10 @@
 	if (pageLength < 1)
 		pageLength = 1;
 
+	if (scrollBar->oldPage == pageLength)
+		return;
+	scrollBar->oldPage = pageLength;
+
 	scrollBar->page = pageLength;
 	if (scrollBar->end > 0)
 	{
@@ -618,6 +630,13 @@
 
 void initializeScrollBars(void)
 {
+	for (int32_t i = 0; i < NUM_SCROLLBARS; i++)
+	{
+		scrollBars[i].oldEnd = 0xFFFFFFFF;
+		scrollBars[i].oldPage = 0xFFFFFFFF;
+		scrollBars[i].oldPos = 0xFFFFFFFF;
+	}
+
 	// pattern editor
 	setScrollBarPageLength(SB_CHAN_SCROLL, 8);
 	setScrollBarEnd(SB_CHAN_SCROLL, 8);
--- a/src/ft2_scrollbars.h
+++ b/src/ft2_scrollbars.h
@@ -75,6 +75,7 @@
 	bool visible;
 	uint8_t state;
 	uint32_t pos, page, end;
+	uint32_t oldPos, oldPage, oldEnd;
 	uint16_t thumbX, thumbY, thumbW, thumbH; 
 } scrollBar_t;
 
--- a/src/ft2_sysreqs.c
+++ b/src/ft2_sysreqs.c
@@ -41,12 +41,41 @@
 	{ SDLK_m, SDLK_s, SDLK_c, 0,      0 },
 };
 
+typedef struct quitType_t
+{
+	const char *text;
+	uint8_t typ;
+} quitType_t;
+
+#define QUIT_MESSAGES 16
+
+// 8bitbubsy: Removed the MS-DOS ones...
+static quitType_t quitMessage[QUIT_MESSAGES] =
+{
+	{ "Do you really want to quit?", 2 },
+	{ "Musicians, press >Cancel<.  Lamers, press >OK<", 1 },
+	{ "Tired already?", 2 },
+	{ "Dost thou wish to leave with such hasty abandon?", 2 },
+	{ "So, you think you can quit this easily, huh?", 2 },
+	{ "Hey, what is the matter? You are not quiting now, are you?", 2 },
+	{ "Rome was not built in one day! Quit really?", 2 },
+	{ "For Work and Worry, press YES.  For Delectation and Demos, press NO.", 2 },
+	{ "Did you really press the right key?", 2 },
+	{ "You are a lamer, aren't you? Press >OK< to confirm.", 1 },
+	{ "Hope ya did some good. Press >OK< to quit.", 1 },
+	{ "Quit? Only for a good reason you are allowed to press >OK<.", 1 },
+	{ "Are we at the end of a Fasttracker round?", 2 },
+	{ "Are you just another boring user?", 2 },
+	{ "Hope you're doing the compulsory \"Exit ceremony\" before pressing >OK<.", 1 },
+	{ "Fasttracker...", 3 }
+};
+
 static void drawWindow(uint16_t w)
 {
 	const uint16_t h = SYSTEM_REQUEST_H;
 	uint16_t x, y;
 
-	x = (SCREEN_W / 2) - (w / 2);
+	x = (SCREEN_W - w) / 2;
 	y = editor.ui.extended ? 91 : SYSTEM_REQUEST_Y;
 
 	// main fill
@@ -85,8 +114,10 @@
 		return false;
 	}
 
-	     if (mouseButton == SDL_BUTTON_LEFT) mouse.leftButtonPressed = true;
-	else if (mouseButton == SDL_BUTTON_RIGHT) mouse.rightButtonPressed = true;
+	if (mouseButton == SDL_BUTTON_LEFT)
+		mouse.leftButtonPressed = true;
+	else if (mouseButton == SDL_BUTTON_RIGHT)
+		mouse.rightButtonPressed = true;
 
 	// don't do mouse down testing here if we already are using an object
 	if (mouse.lastUsedObjectType != OBJECT_NONE)
@@ -105,8 +136,10 @@
 
 static bool mouseButtonUpLogic(uint8_t mouseButton)
 {
-	     if (mouseButton == SDL_BUTTON_LEFT) mouse.leftButtonPressed = false;
-	else if (mouseButton == SDL_BUTTON_RIGHT) mouse.rightButtonPressed = false;
+	if (mouseButton == SDL_BUTTON_LEFT)
+		mouse.leftButtonPressed = false;
+	else if (mouseButton == SDL_BUTTON_RIGHT)
+		mouse.rightButtonPressed = false;
 
 	editor.textCursorBlinkCounter = 0;
 
@@ -118,13 +151,12 @@
 }
 
 // WARNING: This routine must ONLY be called from the main input/video thread!
-int16_t okBox(int16_t typ, char *headline, char *text)
+int16_t okBox(int16_t typ, const char *headline, const char *text)
 {
 #define PUSHBUTTON_W 80
 
 	int16_t returnVal, oldLastUsedObjectID, oldLastUsedObjectType;
 	uint16_t x, y, i, tlen, hlen, wlen, tx, knp, headlineX, textX;
-	const uint16_t mid = SCREEN_W / 2;
 	SDL_Event inputEvent;
 	pushButton_t *p;
 	checkBox_t *c;
@@ -172,9 +204,9 @@
 	if (wlen > 600)
 		wlen = 600;
 
-	headlineX = mid - (hlen / 2);
-	textX = mid - (tlen / 2);
-	x = mid - (wlen / 2);
+	headlineX = (SCREEN_W - hlen) / 2;
+	textX = (SCREEN_W - tlen) / 2;
+	x = (SCREEN_W - wlen) / 2;
 
 	// the box y position differs in extended pattern editor mode
 	y = editor.ui.extended ? SYSTEM_REQUEST_Y_EXT : SYSTEM_REQUEST_Y;
@@ -184,7 +216,7 @@
 	{
 		p = &pushButtons[i];
 
-		p->x = (mid - (tx / 2)) + (i * 100);
+		p->x = ((SCREEN_W - tx) / 2) + (i * 100);
 		p->y = y + 42;
 		p->w = PUSHBUTTON_W;
 		p->h = 16;
@@ -231,8 +263,10 @@
 
 		if (mouse.leftButtonPressed || mouse.rightButtonPressed)
 		{
-			     if (mouse.lastUsedObjectType == OBJECT_PUSHBUTTON) handlePushButtonsWhileMouseDown();
-			else if (mouse.lastUsedObjectType == OBJECT_CHECKBOX) handleCheckBoxesWhileMouseDown();
+			if (mouse.lastUsedObjectType == OBJECT_PUSHBUTTON)
+				handlePushButtonsWhileMouseDown();
+			else if (mouse.lastUsedObjectType == OBJECT_CHECKBOX)
+				handleCheckBoxesWhileMouseDown();
 		}
 
 		while (SDL_PollEvent(&inputEvent))
@@ -330,7 +364,7 @@
 ** - This routine must ONLY be called from the main input/video thread!!
 ** - edText must be null-terminated
 */
-int16_t inputBox(int16_t typ, char *headline, char *edText, uint16_t maxStrLen)
+int16_t inputBox(int16_t typ, const char *headline, char *edText, uint16_t maxStrLen)
 {
 #define PUSHBUTTON_W 80
 #define TEXTBOX_W 250
@@ -338,7 +372,6 @@
 	char *inputText;
 	int16_t returnVal, oldLastUsedObjectID, oldLastUsedObjectType;
 	uint16_t y, wlen, tx, knp, headlineX, i;
-	const uint16_t mid = SCREEN_W / 2;
 	SDL_Event inputEvent;
 	pushButton_t *p;
 	textBox_t *t;
@@ -389,7 +422,7 @@
 	mouseAnimOff();
 
 	wlen = textWidth(headline);
-	headlineX = mid - (wlen / 2);
+	headlineX = (SCREEN_W - wlen) / 2;
 
 	// count number of buttons
 	knp = 0;
@@ -412,7 +445,7 @@
 	y = editor.ui.extended ? SYSTEM_REQUEST_Y_EXT : SYSTEM_REQUEST_Y;
 
 	// set further text box settings
-	t->x = mid - (TEXTBOX_W / 2);
+	t->x = (SCREEN_W - TEXTBOX_W) / 2;
 	t->y = y + 24;
 	t->visible = true;
 
@@ -423,7 +456,7 @@
 
 		p->w = PUSHBUTTON_W;
 		p->h = 16;
-		p->x = (mid - (tx / 2)) + (i * 100);
+		p->x = ((SCREEN_W - tx) / 2) + (i * 100);
 		p->y = y + 42;
 		p->caption = buttonText[typ][i];
 		p->visible = true;
@@ -452,8 +485,10 @@
 
 		if (mouse.leftButtonPressed || mouse.rightButtonPressed)
 		{
-			     if (mouse.lastUsedObjectType == OBJECT_PUSHBUTTON) handlePushButtonsWhileMouseDown();
-			else if (mouse.lastUsedObjectType == OBJECT_TEXTBOX) handleTextBoxWhileMouseDown();
+			if (mouse.lastUsedObjectType == OBJECT_PUSHBUTTON)
+				handlePushButtonsWhileMouseDown();
+			else if (mouse.lastUsedObjectType == OBJECT_TEXTBOX)
+				handleTextBoxWhileMouseDown();
 		}
 
 		while (SDL_PollEvent(&inputEvent))
@@ -575,7 +610,7 @@
 }
 
 // WARNING: This routine must NOT be called from the main input/video thread!
-int16_t okBoxThreadSafe(int16_t typ, char *headline, char *text)
+int16_t okBoxThreadSafe(int16_t typ, const char *headline, const char *text)
 {
 	// block multiple calls before they are completed (for safety)
 	while (okBoxData.active)
@@ -592,10 +627,34 @@
 	return okBoxData.returnData;
 }
 
-int16_t quitBox(bool skipQuitMsg)
+static bool askQuit_RandomMsg(void)
 {
-	char *text;
+	uint8_t msg = rand() % QUIT_MESSAGES;
+	int16_t button = okBox(quitMessage[msg].typ, "System request", quitMessage[msg].text);
 
+	return (button == 1) ? true : false;
+}
+
+bool askUnsavedChanges(uint8_t type)
+{
+	int16_t button;
+	
+	if (type == ASK_TYPE_QUIT)
+	{
+		button = okBox(2, "System request",
+			"You have unsaved changes in your song. Do you still want to quit and lose ALL changes?");
+	}
+	else
+	{
+		button = okBox(2, "System request",
+			"You have unsaved changes in your song. Load new song and lose ALL changes?");
+	}
+
+	return (button == 1) ? true : false;
+}
+
+int16_t quitBox(bool skipQuitMsg)
+{
 	if (editor.ui.sysReqShown)
 		return 0;
 
@@ -603,9 +662,7 @@
 		return 1;
 
 	if (song.isModified)
-		text = "You have unsaved changes in your song. Do you still want to quit and lose ALL changes?";
-	else
-		text = "Do you really want to quit?";
+		return askUnsavedChanges(ASK_TYPE_QUIT);
 
-	return okBox(2, "System request", text);
+	return askQuit_RandomMsg();
 }
--- a/src/ft2_sysreqs.h
+++ b/src/ft2_sysreqs.h
@@ -3,10 +3,11 @@
 #include <stdint.h>
 #include <stdbool.h>
 
-int16_t okBoxThreadSafe(int16_t typ, char *headline, char *text);
-int16_t okBox(int16_t typ, char *headline, char *text);
+int16_t okBoxThreadSafe(int16_t typ, const char *headline, const char *text);
+int16_t okBox(int16_t typ, const char *headline, const char *text);
 int16_t quitBox(bool skipQuitMsg);
-int16_t inputBox(int16_t typ, char *headline, char *edText, uint16_t maxStrLen);
+int16_t inputBox(int16_t typ, const char *headline, char *edText, uint16_t maxStrLen);
+bool askUnsavedChanges(uint8_t type);
 
 // for thread-safe version of okBox()
 struct
@@ -13,5 +14,11 @@
 {
 	volatile bool active;
 	int16_t typ, returnData;
-	char *headline, *text;
+	const char *headline, *text;
 } okBoxData;
+
+enum
+{
+	ASK_TYPE_QUIT = 0,
+	ASK_TYPE_LOAD_SONG = 1,
+};
--- a/src/ft2_video.c
+++ b/src/ft2_video.c
@@ -23,6 +23,7 @@
 #include "ft2_mouse.h"
 #include "ft2_scopes.h"
 #include "ft2_pattern_ed.h"
+#include "ft2_pattern_draw.h"
 #include "ft2_sample_ed.h"
 #include "ft2_nibbles.h"
 #include "ft2_inst_ed.h"
@@ -33,13 +34,6 @@
 #include "ft2_module_loader.h"
 #include "ft2_midi.h"
 
-// for FPS counter
-#define FPS_SCAN_FRAMES 60
-#define FPS_RENDER_W 280
-#define FPS_RENDER_H (((FONT1_CHAR_H + 1) * 8) + 1)
-#define FPS_RENDER_X 2
-#define FPS_RENDER_Y 2
-
 static const uint8_t textCursorData[12] =
 {
 	PAL_FORGRND, PAL_FORGRND, PAL_FORGRND,
@@ -49,10 +43,21 @@
 };
 
 static bool songIsModified;
-static char buf[1024], wndTitle[128 + PATH_MAX];
-static uint64_t frameStartTime, timeNext64, timeNext64Frac;
+static char wndTitle[128 + PATH_MAX];
+static uint64_t timeNext64, timeNext64Frac;
 static sprite_t sprites[SPRITE_NUM];
+
+// for FPS counter
+#define FPS_SCAN_FRAMES 60
+#define FPS_RENDER_W 280
+#define FPS_RENDER_H (((FONT1_CHAR_H + 1) * 8) + 1)
+#define FPS_RENDER_X 2
+#define FPS_RENDER_Y 2
+
+static char fpsTextBuf[1024];
+static uint64_t frameStartTime;
 static double dRunningFPS, dFrameTime, dAvgFPS;
+// ------------------
 
 static void drawReplayerData(void);
 
@@ -59,7 +64,7 @@
 void resetFPSCounter(void)
 {
 	editor.framesPassed = 0;
-	buf[0] = '\0';
+	fpsTextBuf[0] = '\0';
 	dRunningFPS = VBLANK_HZ;
 	dFrameTime = 1000.0 / VBLANK_HZ;
 }
@@ -106,7 +111,7 @@
 	if (dAudLatency < 0.0 || dAudLatency > 999999999.9999)
 		dAudLatency = 999999999.9999; // prevent number from overflowing text box
 
-	sprintf(buf, "Frames per second: %.4f\n" \
+	sprintf(fpsTextBuf, "Frames per second: %.4f\n" \
 	             "Monitor refresh rate: %.1fHz (+/-)\n" \
 	             "59..61Hz GPU VSync used: %s\n" \
 	             "Audio frequency: %.1fkHz (expected %.1fkHz)\n" \
@@ -126,7 +131,7 @@
 	xPos = FPS_RENDER_X + 3;
 	yPos = FPS_RENDER_Y + 3;
 
-	textPtr = buf;
+	textPtr = fpsTextBuf;
 	while (*textPtr != '\0')
 	{
 		ch = *textPtr++;
@@ -210,7 +215,7 @@
 	SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", strBuf, video.window);
 }
 
-void updateRenderSizeVars(void)
+static void updateRenderSizeVars(void)
 {
 	int32_t di;
 #ifdef __APPLE__
@@ -754,11 +759,13 @@
 		video.window = NULL;
 	}
 
-	if (video.frameBuffer != NULL)
+	if (video.frameBufferUnaligned != NULL)
 	{
-		free(video.frameBuffer);
-		video.frameBuffer = NULL;
+		free(video.frameBufferUnaligned);
+		video.frameBufferUnaligned = NULL;
 	}
+
+	video.frameBuffer = NULL;
 }
 
 void setWindowSizeFromConfig(bool updateRenderer)
@@ -854,7 +861,7 @@
 	video.texture = SDL_CreateTexture(video.renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, SCREEN_W, SCREEN_H);
 	if (video.texture == NULL)
 	{
-		showErrorMsgBox("Couldn't create a %dx%d GPU texture:\n%s\n\nIs your GPU (+ driver) too old?", SCREEN_W, SCREEN_H, SDL_GetError());
+		showErrorMsgBox("Couldn't create a %dx%d GPU texture:\n\"%s\"\n\nIs your GPU (+ driver) too old?", SCREEN_W, SCREEN_H, SDL_GetError());
 		return false;
 	}
 
@@ -930,7 +937,7 @@
 
 		if (video.renderer == NULL)
 		{
-			showErrorMsgBox("Couldn't create SDL renderer:\n%s\n\nIs your GPU (+ driver) too old?",
+			showErrorMsgBox("Couldn't create SDL renderer:\n\"%s\"\n\nIs your GPU (+ driver) too old?",
 				SDL_GetError());
 			return false;
 		}
@@ -946,19 +953,22 @@
 
 	if (!recreateTexture())
 	{
-		showErrorMsgBox("Couldn't create a %dx%d GPU texture:\n%s\n\nIs your GPU (+ driver) too old?",
+		showErrorMsgBox("Couldn't create a %dx%d GPU texture:\n\"%s\"\n\nIs your GPU (+ driver) too old?",
 			SCREEN_W, SCREEN_H, SDL_GetError());
 		return false;
 	}
 
 	// framebuffer used by SDL (for texture)
-	video.frameBuffer = (uint32_t *)malloc(SCREEN_W * SCREEN_H * sizeof (int32_t));
-	if (video.frameBuffer == NULL)
+	video.frameBufferUnaligned = (uint32_t *)MALLOC_PAD(SCREEN_W * SCREEN_H * sizeof (int32_t), 256);
+	if (video.frameBufferUnaligned == NULL)
 	{
 		showErrorMsgBox("Not enough memory!");
 		return false;
 	}
 
+	// we want an aligned pointer
+	video.frameBuffer = (uint32_t *)ALIGN_PTR(video.frameBufferUnaligned, 256);
+
 	if (!setupSprites())
 		return false;
 
@@ -1033,8 +1043,10 @@
 				if (!editor.ui.diskOpShown)
 					drawPlaybackTime();
 
-				     if (editor.ui.sampleEditorExtShown) handleSampleEditorExtRedrawing();
-				else if (editor.ui.scopesShown) drawScopes();
+				if (editor.ui.sampleEditorExtShown)
+					handleSampleEditorExtRedrawing();
+				else if (editor.ui.scopesShown)
+					drawScopes();
 			}
 		}
 	}
@@ -1041,8 +1053,10 @@
 
 	drawReplayerData();
 
-	     if (editor.ui.instEditorShown) handleInstEditorRedrawing();
-	else if (editor.ui.sampleEditorShown) handleSamplerRedrawing();
+	if (editor.ui.instEditorShown)
+		handleInstEditorRedrawing();
+	else if (editor.ui.sampleEditorShown)
+		handleSamplerRedrawing();
 
 	// blink text edit cursor
 	if (editor.editTextFlag && mouse.lastEditBox != -1)
--- a/src/ft2_video.h
+++ b/src/ft2_video.h
@@ -18,16 +18,18 @@
 
 struct video_t
 {
-	uint8_t upscaleFactor;
-	bool fullscreen, vsync60HzPresent, showFPSCounter;
-	int32_t renderX, renderY, renderW, renderH, displayW, displayH;
-	uint32_t *frameBuffer, palette[PAL_NUM], vblankTimeLen, vblankTimeLenFrac;
+	bool fullscreen, showFPSCounter;
 	uint32_t xScale, yScale;
-	double dMonitorRefreshRate, dMouseXMul, dMouseYMul;
+	uint32_t *frameBuffer, palette[PAL_NUM], vblankTimeLen, vblankTimeLenFrac;
 #ifdef _WIN32
 	HWND hWnd;
 #endif
 	SDL_Window *window;
+	double dMonitorRefreshRate, dMouseXMul, dMouseYMul;
+	uint8_t upscaleFactor;
+	bool vsync60HzPresent;
+	int32_t renderX, renderY, renderW, renderH, displayW, displayH;
+	uint32_t *frameBufferUnaligned;
 	SDL_Renderer *renderer;
 	SDL_Texture *texture;
 	SDL_Surface *iconSurface;
@@ -48,7 +50,6 @@
 void flipFrame(void);
 void showErrorMsgBox(const char *fmt, ...);
 void updateWindowTitle(bool forceUpdate);
-void setPalettePreset(int16_t palettePreset);
 void handleScopesFromChQueue(chSyncData_t *chSyncData, uint8_t *scopeUpdateStatus);
 bool setupWindow(void);
 bool setupRenderer(void);
@@ -67,8 +68,8 @@
 void handleRedrawing(void);
 void enterFullscreen(void);
 void leaveFullScreen(void);
-void toggleFullScreen(void);
 void setWindowSizeFromConfig(bool updateRenderer);
 bool recreateTexture(void);
+void toggleFullScreen(void);
 void setupWaitVBL(void);
 void waitVBL(void);
binary files /dev/null b/src/gfxdata/bmp/patternfonts.bmp differ
--- a/vs2019_project/ft2-clone/ft2-clone.vcxproj
+++ b/vs2019_project/ft2-clone/ft2-clone.vcxproj
@@ -116,8 +116,9 @@
       <OpenMPSupport>false</OpenMPSupport>
       <InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion>
       <BufferSecurityCheck>false</BufferSecurityCheck>
-      <EnableEnhancedInstructionSet>StreamingSIMDExtensions</EnableEnhancedInstructionSet>
+      <EnableEnhancedInstructionSet>StreamingSIMDExtensions2</EnableEnhancedInstructionSet>
       <DebugInformationFormat>None</DebugInformationFormat>
+      <LanguageStandard>stdcpplatest</LanguageStandard>
     </ClCompile>
     <Link>
       <AdditionalLibraryDirectories>
@@ -174,6 +175,7 @@
       <InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion>
       <BufferSecurityCheck>false</BufferSecurityCheck>
       <DebugInformationFormat>None</DebugInformationFormat>
+      <LanguageStandard>stdcpplatest</LanguageStandard>
     </ClCompile>
     <Link>
       <AdditionalLibraryDirectories>
@@ -223,12 +225,13 @@
       <PreprocessorDefinitions>__WINDOWS_MM__;_CRTDBG_MAP_ALLOC;DEBUG;_DEBUG;WIN32;_CRT_SECURE_NO_WARNINGS;_USE_MATH_DEFINES;HAVE_M_PI;HAS_MIDI</PreprocessorDefinitions>
       <ExceptionHandling>false</ExceptionHandling>
       <FloatingPointModel>Fast</FloatingPointModel>
-      <EnableEnhancedInstructionSet>StreamingSIMDExtensions</EnableEnhancedInstructionSet>
+      <EnableEnhancedInstructionSet>StreamingSIMDExtensions2</EnableEnhancedInstructionSet>
       <ControlFlowGuard>false</ControlFlowGuard>
       <EnableParallelCodeGeneration>false</EnableParallelCodeGeneration>
       <CreateHotpatchableImage>false</CreateHotpatchableImage>
       <RuntimeTypeInfo>false</RuntimeTypeInfo>
       <OpenMPSupport>false</OpenMPSupport>
+      <LanguageStandard>stdcpplatest</LanguageStandard>
     </ClCompile>
     <Link>
       <AdditionalLibraryDirectories>
@@ -276,6 +279,7 @@
       <CreateHotpatchableImage>false</CreateHotpatchableImage>
       <RuntimeTypeInfo>false</RuntimeTypeInfo>
       <OpenMPSupport>false</OpenMPSupport>
+      <LanguageStandard>stdcpplatest</LanguageStandard>
     </ClCompile>
     <Link>
       <AdditionalLibraryDirectories>