shithub: pt2-clone

Download patch

ref: a1d153b3ea809d6032483d629d0681bb4743e786
parent: 37069678c8a822cf43703ae627cdaa9768431e15
author: Olav Sørensen <[email protected]>
date: Sun Feb 16 15:03:49 EST 2020

Pushed v1.06 code

- Fix: Don't cut off voices when changing the sample length from the GUI
- When loading a WAV/AIFF sample that has a frequency above 22kHz, show a big
  ask dialog with more details about what is being asked. Some users got
  confused about the old "2X DOWNSAMPLING ?" status text that appeared when
  loading some samples.
- When selecting "Yes" to downsampling a sample before loading it, apply a
  low-pass filter first to get rid of some potential aliasing after
  downsampling. This can now be turned off in protracker.ini (SAMPLELOWPASS).
  I recommend leaving this on, as it might remove quite a bit of aliasing in
  samples with a lot of high frequencies, like hi-hats and cymbals. However,
  some sharpness might get lost. If you need more sharpness, try the "BOOST"
  button in "Edit Op." screen #3. The low-pass filter is only applied to samples
  that are going to get 2x downsampled during load. You'll get a dialog where
  you click yes/no on samples that has a frequency higher than 22kHz.
- Only filter forwards (not backwards as well) when filtering samples in the
  FILTERS toolbox in the sample editor. This makes the selected cutoff more
  correct.
- The sample marking now looks slightly nicer (different color on the center
  line).

--- a/release/macos/protracker.ini
+++ b/release/macos/protracker.ini
@@ -221,4 +221,18 @@
 ;
 STEREOSEPARATION=20
 
+
+; Low-pass samples before getting 2x downsampled during loading (if requested)
+;        Syntax: TRUE or FALSE
+; Default value: TRUE
+;       Comment: Set to false if you want slightly sharper sound when loading
+;         samples that are 2x downsampled (if requested).
+;         Keep in mind that you might get more aliasing in the sound if you
+;         disable this, and certain sounds with a lot of treble might sound
+;         weird (f.ex. hi-hats and cymbals). If the sample you load have a
+;         frequency below 22kHz, it will never be downsampled (and thus this
+;         setting changes nothing). I recommend keeping this set to TRUE.
+;
+SAMPLELOWPASS=TRUE
+
 ; End of config file
\ No newline at end of file
--- a/release/other/protracker.ini
+++ b/release/other/protracker.ini
@@ -221,4 +221,18 @@
 ;
 STEREOSEPARATION=20
 
+
+; Low-pass samples before getting 2x downsampled during loading (if requested)
+;        Syntax: TRUE or FALSE
+; Default value: TRUE
+;       Comment: Set to false if you want slightly sharper sound when loading
+;         samples that are 2x downsampled (if requested).
+;         Keep in mind that you might get more aliasing in the sound if you
+;         disable this, and certain sounds with a lot of treble might sound
+;         weird (f.ex. hi-hats and cymbals). If the sample you load have a
+;         frequency below 22kHz, it will never be downsampled (and thus this
+;         setting changes nothing). I recommend keeping this set to TRUE.
+;
+SAMPLELOWPASS=TRUE
+
 ; End of config file
\ No newline at end of file
--- a/release/win32/protracker.ini
+++ b/release/win32/protracker.ini
@@ -221,4 +221,18 @@
 ;
 STEREOSEPARATION=20
 
+
+; Low-pass samples before getting 2x downsampled during loading (if requested)
+;        Syntax: TRUE or FALSE
+; Default value: TRUE
+;       Comment: Set to false if you want slightly sharper sound when loading
+;         samples that are 2x downsampled (if requested).
+;         Keep in mind that you might get more aliasing in the sound if you
+;         disable this, and certain sounds with a lot of treble might sound
+;         weird (f.ex. hi-hats and cymbals). If the sample you load have a
+;         frequency below 22kHz, it will never be downsampled (and thus this
+;         setting changes nothing). I recommend keeping this set to TRUE.
+;
+SAMPLELOWPASS=TRUE
+
 ; End of config file
\ No newline at end of file
--- a/release/win64/protracker.ini
+++ b/release/win64/protracker.ini
@@ -221,4 +221,18 @@
 ;
 STEREOSEPARATION=20
 
+
+; Low-pass samples before getting 2x downsampled during loading (if requested)
+;        Syntax: TRUE or FALSE
+; Default value: TRUE
+;       Comment: Set to false if you want slightly sharper sound when loading
+;         samples that are 2x downsampled (if requested).
+;         Keep in mind that you might get more aliasing in the sound if you
+;         disable this, and certain sounds with a lot of treble might sound
+;         weird (f.ex. hi-hats and cymbals). If the sample you load have a
+;         frequency below 22kHz, it will never be downsampled (and thus this
+;         setting changes nothing). I recommend keeping this set to TRUE.
+;
+SAMPLELOWPASS=TRUE
+
 ; End of config file
\ No newline at end of file
binary files /dev/null b/src/gfx/bmp/bigyesnodialog.bmp differ
--- a/src/gfx/pt2_gfx_yes_no_dialog.c
+++ b/src/gfx/pt2_gfx_yes_no_dialog.c
@@ -35,3 +35,34 @@
 	0x5A,0xCC,0x17,0xAA,0xAF,0x5A,0xCC,0x17,0xAA,0xAF,0x5A,0xCC,0x17,0xAA,0xAF,0x5A,
 	0xCC,0x17,0xAA,0xAF,0x6F,0xCC,0x18,0xFF,0xBF,0xCC,0x18,0xFF
 };
+
+// Final unpack length: 11000
+// Decoded length: 2750 (first four bytes of buffer)
+const uint8_t bigYesNoDialogPackedBMP[472] =
+{
+	0x00,0x00,0x0A,0xBE,0xCC,0x30,0x55,0x56,0xCC,0x30,0x55,0x5B,0x5A,0xCC,0x2F,0xAA,0xAF,0x5A,0xCC,0x2F,
+	0xAA,0xAF,0x5A,0xCC,0x2F,0xAA,0xAF,0x5A,0xCC,0x2F,0xAA,0xAF,0x5A,0xCC,0x2F,0xAA,0xAF,0x5A,0xCC,0x2F,
+	0xAA,0xAF,0x5A,0xCC,0x2F,0xAA,0xAF,0x5A,0xCC,0x2F,0xAA,0xAF,0x5A,0xCC,0x2F,0xAA,0xAF,0x5A,0xCC,0x2F,
+	0xAA,0xAF,0x5A,0xCC,0x2F,0xAA,0xAF,0x5A,0xCC,0x2F,0xAA,0xAF,0x5A,0xCC,0x2F,0xAA,0xAF,0x5A,0xCC,0x2F,
+	0xAA,0xAF,0x5A,0xCC,0x2F,0xAA,0xAF,0x5A,0xCC,0x2F,0xAA,0xAF,0x5A,0xCC,0x2F,0xAA,0xAF,0x5A,0xCC,0x2F,
+	0xAA,0xAF,0x5A,0xCC,0x2F,0xAA,0xAF,0x5A,0xCC,0x2F,0xAA,0xAF,0x5A,0xCC,0x2F,0xAA,0xAF,0x5A,0xCC,0x2F,
+	0xAA,0xAF,0x5A,0xCC,0x2F,0xAA,0xAF,0x5A,0xCC,0x2F,0xAA,0xAF,0x5A,0xCC,0x2F,0xAA,0xAF,0x5A,0xCC,0x2F,
+	0xAA,0xAF,0x5A,0xCC,0x2F,0xAA,0xAF,0x5A,0xCC,0x2F,0xAA,0xAF,0x5A,0xCC,0x2F,0xAA,0xAF,0x5A,0xCC,0x2F,
+	0xAA,0xAF,0x5A,0xCC,0x2F,0xAA,0xAF,0x5A,0xCC,0x2F,0xAA,0xAF,0x5A,0xCC,0x2F,0xAA,0xAF,0x5A,0xCC,0x2F,
+	0xAA,0xAF,0x5A,0xCC,0x2F,0xAA,0xAF,0x5A,0xCC,0x2F,0xAA,0xAF,0x5A,0xCC,0x2F,0xAA,0xAF,0x5A,0xCC,0x0C,
+	0xAA,0xAB,0xCC,0x05,0xFF,0xCC,0x08,0xAA,0xAF,0xCC,0x03,0xFF,0xCC,0x0D,0xAA,0xAF,0x5A,0xCC,0x0C,0xAA,
+	0xAB,0xCC,0x05,0xAA,0x6A,0xCC,0x07,0xAA,0xAE,0xCC,0x03,0xAA,0x6A,0xCC,0x0C,0xAA,0xAF,0x5A,0xCC,0x0C,
+	0xAA,0xAB,0xCC,0x05,0xAA,0x6A,0xCC,0x07,0xAA,0xAE,0xCC,0x03,0xAA,0x6A,0xCC,0x0C,0xAA,0xAF,0x5A,0xCC,
+	0x0C,0xAA,0xAB,0xA5,0xA5,0x95,0x56,0x95,0x5A,0x6A,0xCC,0x07,0xAA,0xAE,0x96,0x96,0x95,0x6A,0x6A,0xCC,
+	0x0C,0xAA,0xAF,0x5A,0xCC,0x0C,0xAA,0xAB,0xA5,0xE5,0xD7,0xFF,0x5F,0xFE,0x6A,0xCC,0x07,0xAA,0xAE,0x95,
+	0x97,0x5F,0x5A,0x6A,0xCC,0x0C,0xAA,0xAF,0x5A,0xCC,0x0C,0xAA,0xAB,0xA9,0x57,0xD5,0x5A,0x95,0x6A,0x6A,
+	0xCC,0x07,0xAA,0xAE,0x95,0x57,0x5E,0x5E,0x6A,0xCC,0x0C,0xAA,0xAF,0x5A,0xCC,0x0C,0xAA,0xAB,0xAA,0x5F,
+	0x97,0xFE,0xAF,0x5A,0x6A,0xCC,0x07,0xAA,0xAE,0x97,0x57,0x5E,0x5E,0x6A,0xCC,0x0C,0xAA,0xAF,0x5A,0xCC,
+	0x0C,0xAA,0xAB,0xAA,0x5E,0x95,0x56,0x55,0x7E,0x6A,0xCC,0x07,0xAA,0xAE,0x97,0x97,0x95,0x7E,0x6A,0xCC,
+	0x0C,0xAA,0xAF,0x5A,0xCC,0x0C,0xAA,0xAB,0xAA,0xBE,0xAF,0xFF,0xBF,0xFA,0x6A,0xCC,0x07,0xAA,0xAE,0xAF,
+	0xAF,0xAF,0xFA,0x6A,0xCC,0x0C,0xAA,0xAF,0x5A,0xCC,0x0C,0xAA,0xAB,0xCC,0x05,0xAA,0x6A,0xCC,0x07,0xAA,
+	0xAE,0xCC,0x03,0xAA,0x6A,0xCC,0x0C,0xAA,0xAF,0x5A,0xCC,0x0D,0xAA,0xCC,0x05,0x55,0x6A,0xCC,0x07,0xAA,
+	0xA9,0xCC,0x03,0x55,0x6A,0xCC,0x0C,0xAA,0xAF,0x5A,0xCC,0x2F,0xAA,0xAF,0x5A,0xCC,0x2F,0xAA,0xAF,0x5A,
+	0xCC,0x2F,0xAA,0xAF,0x6F,0xCC,0x30,0xFF,0xBF,0xCC,0x30,0xFF
+};
+
--- a/src/pt2_audio.c
+++ b/src/pt2_audio.c
@@ -34,8 +34,6 @@
 
 #define INITIAL_DITHER_SEED 0x12345000
 
-#define DENORMAL_OFFSET 1e-10
-
 typedef struct ledFilter_t
 {
 	double dLed[4];
@@ -197,6 +195,12 @@
 	dOut[1] = filter->dBuffer[1];
 }
 
+void lossyIntegratorMono(lossyIntegrator_t *filter, double dIn, double *dOut)
+{
+	filter->dBuffer[0] = (filter->b0 * dIn) + (filter->b1 * filter->dBuffer[0]) + DENORMAL_OFFSET;
+	*dOut = filter->dBuffer[0];
+}
+
 void lossyIntegratorHighPass(lossyIntegrator_t *filter, double *dIn, double *dOut)
 {
 	double dLow[2];
@@ -205,6 +209,15 @@
 
 	dOut[0] = dIn[0] - dLow[0]; // left channel high-pass
 	dOut[1] = dIn[1] - dLow[1]; // right channel high-pass
+}
+
+void lossyIntegratorHighPassMono(lossyIntegrator_t *filter, double dIn, double *dOut)
+{
+	double dLow;
+
+	lossyIntegratorMono(filter, dIn, &dLow);
+
+	*dOut = dIn - dLow;
 }
 
 /* adejr/aciddose: these sin/cos approximations both use a 0..1
--- a/src/pt2_audio.h
+++ b/src/pt2_audio.h
@@ -6,6 +6,8 @@
 #include <stdint.h>
 #include <stdbool.h>
 
+#define DENORMAL_OFFSET 1e-10
+
 typedef struct lossyIntegrator_t
 {
 	double dBuffer[2], b0, b1;
@@ -15,7 +17,9 @@
 void resetAudioDithering(void);
 void calcCoeffLossyIntegrator(double dSr, double dHz, lossyIntegrator_t *filter);
 void lossyIntegrator(lossyIntegrator_t *filter, double *dIn, double *dOut);
+void lossyIntegratorMono(lossyIntegrator_t *filter, double dIn, double *dOut);
 void lossyIntegratorHighPass(lossyIntegrator_t *filter, double *dIn, double *dOut);
+void lossyIntegratorHighPassMono(lossyIntegrator_t *filter, double dIn, double *dOut);
 void normalize32bitSigned(int32_t *sampleData, uint32_t sampleLength);
 void normalize16bitSigned(int16_t *sampleData, uint32_t sampleLength);
 void normalize8bitFloatSigned(float *fSampleData, uint32_t sampleLength);
--- a/src/pt2_config.c
+++ b/src/pt2_config.c
@@ -21,6 +21,7 @@
 #include "pt2_diskop.h"
 #include "pt2_config.h"
 #include "pt2_textout.h"
+#include "pt2_sampler.h"
 
 #ifndef _WIN32
 static char oldCwd[PATH_MAX];
@@ -59,6 +60,7 @@
 	ptConfig.autoCloseDiskOp = true;
 	ptConfig.vsyncOff = false;
 	ptConfig.hwMouse = false;
+	ptConfig.sampleLowpass = true;
 
 #ifndef _WIN32
 	getcwd(oldCwd, PATH_MAX);
@@ -138,6 +140,9 @@
 #ifndef _WIN32
 	chdir(oldCwd);
 #endif
+
+	// use palette for generating sample data mark (invert) table
+	createSampleMarkTable();
 }
 
 static bool loadProTrackerDotIni(FILE *f)
@@ -188,6 +193,13 @@
 		{
 			     if (!_strnicmp(&configLine[8], "TRUE",  4)) ptConfig.hwMouse = true;
 			else if (!_strnicmp(&configLine[8], "FALSE", 5)) ptConfig.hwMouse = false;
+		}
+
+		// SAMPLELOWPASS
+		else if (!_strnicmp(configLine, "SAMPLELOWPASS=", 14))
+		{
+			     if (!_strnicmp(&configLine[14], "TRUE",  4)) ptConfig.sampleLowpass = true;
+			else if (!_strnicmp(&configLine[14], "FALSE", 5)) ptConfig.sampleLowpass = false;
 		}
 
 		// VSYNCOFF
--- a/src/pt2_config.h
+++ b/src/pt2_config.h
@@ -8,6 +8,7 @@
 	char *defModulesDir, *defSamplesDir;
 	bool dottedCenterFlag, pattDots, a500LowPassFilter, compoMode, autoCloseDiskOp, hideDiskOpDates, hwMouse;
 	bool transDel, fullScreenStretch, vsyncOff, modDot, blankZeroFlag, realVuMeters, rememberPlayMode;
+	bool sampleLowpass;
 	int8_t stereoSeparation, videoScaleFactor, accidental;
 	uint16_t quantizeValue;
 	uint32_t soundFrequency, soundBufferSize;
--- /dev/null
+++ b/src/pt2_filters.c
@@ -1,0 +1,146 @@
+/* These are second variants of low-pass/high-pass filters that are better than
+** the ones used in the main audio mixer. The reason we use a different ones for
+** the main audio mixer is because it makes it sound closer to real Amigas.
+**
+** These ones are used for low-pass filtering when loading samples w/ 2x downsampling.
+*/
+
+#include <stdio.h>
+#include <stdbool.h>
+#include <math.h>
+#include "pt2_audio.h" // DENORMAL_OFFSET constant
+#include "pt2_helpers.h"
+
+typedef struct filterState_t
+{
+	double dBuffer, b0, b1;
+} filterState_t;
+
+static void calcFilterCoeffs(double dSr, double dHz, filterState_t *filter)
+{
+	filter->b0 = tan((M_PI * dHz) / dSr);
+	filter->b1 = 1.0 / (1.0 + filter->b0);
+}
+
+static double doLowpass(filterState_t *filter, double dIn)
+{
+	double dOutput;
+
+	dOutput = (filter->b0 * dIn + filter->dBuffer) * filter->b1;
+	filter->dBuffer = filter->b0 * (dIn - dOutput) + dOutput + DENORMAL_OFFSET;
+
+	return dOutput;
+}
+
+bool lowPassSample8Bit(int8_t *buffer, int32_t length, int32_t sampleFrequency, double cutoff)
+{
+	filterState_t filter;
+
+	if (buffer == NULL || length == 0 || cutoff == 0.0)
+		return false;
+
+	calcFilterCoeffs(sampleFrequency, cutoff, &filter);
+
+	filter.dBuffer = 0.0;
+	for (int32_t i = 0; i < length; i++)
+	{
+		int32_t sample;
+		sample = (int32_t)doLowpass(&filter, buffer[i]);
+		buffer[i] = (int8_t)CLAMP(sample, INT8_MIN, INT8_MAX);
+	}
+	
+	return true;
+}
+
+bool lowPassSample8BitUnsigned(uint8_t *buffer, int32_t length, int32_t sampleFrequency, double cutoff)
+{
+	filterState_t filter;
+
+	if (buffer == NULL || length == 0 || cutoff == 0.0)
+		return false;
+
+	calcFilterCoeffs(sampleFrequency, cutoff, &filter);
+
+	filter.dBuffer = 0.0;
+	for (int32_t i = 0; i < length; i++)
+	{
+		int32_t sample;
+		sample = (int32_t)doLowpass(&filter, buffer[i] - 128);
+		sample = CLAMP(sample, INT8_MIN, INT8_MAX);
+		buffer[i] = (uint8_t)(sample + 128);
+	}
+		
+	return true;
+}
+
+bool lowPassSample16Bit(int16_t *buffer, int32_t length, int32_t sampleFrequency, double cutoff)
+{
+	filterState_t filter;
+
+	if (buffer == NULL || length == 0 || cutoff == 0.0)
+		return false;
+
+	calcFilterCoeffs(sampleFrequency, cutoff, &filter);
+
+	filter.dBuffer = 0.0;
+	for (int32_t i = 0; i < length; i++)
+	{
+		int32_t sample;
+		sample = (int32_t)doLowpass(&filter, buffer[i]);
+		buffer[i] = (int16_t)CLAMP(sample, INT16_MIN, INT16_MAX);
+	}
+
+	return true;
+}
+
+bool lowPassSample32Bit(int32_t *buffer, int32_t length, int32_t sampleFrequency, double cutoff)
+{
+	filterState_t filter;
+
+	if (buffer == NULL || length == 0 || cutoff == 0.0)
+		return false;
+
+	calcFilterCoeffs(sampleFrequency, cutoff, &filter);
+
+	filter.dBuffer = 0.0;
+	for (int32_t i = 0; i < length; i++)
+	{
+		int64_t sample;
+		sample = (int64_t)doLowpass(&filter, buffer[i]);
+		buffer[i] = (int32_t)CLAMP(sample, INT32_MIN, INT32_MAX);
+	}
+
+	return true;
+}
+
+bool lowPassSampleFloat(float *buffer, int32_t length, int32_t sampleFrequency, double cutoff)
+{
+	filterState_t filter;
+
+	if (buffer == NULL || length == 0 || cutoff == 0.0)
+		return false;
+
+	calcFilterCoeffs(sampleFrequency, cutoff, &filter);
+
+	filter.dBuffer = 0.0;
+	for (int32_t i = 0; i < length; i++)
+		buffer[i] = (float)doLowpass(&filter, buffer[i]);
+		
+	return true;
+}
+
+bool lowPassSampleDouble(double *buffer, int32_t length, int32_t sampleFrequency, double cutoff)
+{
+	filterState_t filter;
+
+	if (buffer == NULL || length == 0 || cutoff == 0.0)
+		return false;
+
+	calcFilterCoeffs(sampleFrequency, cutoff, &filter);
+	
+	filter.dBuffer = 0.0;
+	for (int32_t i = 0; i < length; i++)
+		buffer[i] = doLowpass(&filter, buffer[i]);
+		
+	return true;
+}
--- /dev/null
+++ b/src/pt2_filters.h
@@ -1,0 +1,31 @@
+/* These are second variants of low-pass/high-pass filters that are better than
+** the ones used in the main audio mixer. The reason we use a different one for
+** the main audio mixer is because it makes it sound closer to real Amigas.
+**
+** These ones are used for filtering samples when loading samples, or with the
+** FILTERS toolbox in the Sample Editor.
+*/
+
+#pragma once
+
+#include <stdio.h>
+#include <stdbool.h>
+
+/* 8bitbubsy: Before we downsample a loaded WAV/AIFF (>22kHz) sample by 2x, we low-pass
+** filter it.
+**
+*** I think this value ought to be 4.0 (nyquist freq. / 2), but it cuts off too much in
+** my opinion! The improvement is only noticable on samples that has quite a bit of high
+** frequencies in them to begin with.
+**
+** This is probably not how to do it, so if someone with a bit more knowledge can do this
+** in a proper way without using an external resampler library, that would be neato!
+*/
+#define DOWNSAMPLE_CUTOFF_FACTOR 4.0
+
+bool lowPassSample8Bit(int8_t *buffer, int32_t length, int32_t sampleFrequency, double cutoff);
+bool lowPassSample8BitUnsigned(uint8_t *buffer, int32_t length, int32_t sampleFrequency, double cutoff);
+bool lowPassSample16Bit(int16_t *buffer, int32_t length, int32_t sampleFrequency, double cutoff);
+bool lowPassSample32Bit(int32_t *buffer, int32_t length, int32_t sampleFrequency, double cutoff);
+bool lowPassSampleFloat(float *buffer, int32_t length, int32_t sampleFrequency, double cutoff);
+bool lowPassSampleDouble(double *buffer, int32_t length, int32_t sampleFrequency, double cutoff);
--- a/src/pt2_header.h
+++ b/src/pt2_header.h
@@ -13,7 +13,7 @@
 #include <stdint.h>
 #include "pt2_unicode.h"
 
-#define PROG_VER_STR "1.05"
+#define PROG_VER_STR "1.06"
 
 #ifdef _WIN32
 #define DIR_DELIMITER '\\'
@@ -193,7 +193,7 @@
 	ASK_MOD2WAV_OVERWRITE = 5,
 	ASK_SAVEMOD_OVERWRITE = 6,
 	ASK_SAVESMP_OVERWRITE = 7,
-	ASK_DOWNSAMPLING = 8,
+	ASK_LOAD_DOWNSAMPLE = 8,
 	ASK_RESAMPLE = 9,
 	ASK_KILL_SAMPLE = 10,
 	ASK_UPSAMPLE = 11,
--- a/src/pt2_mouse.c
+++ b/src/pt2_mouse.c
@@ -771,8 +771,6 @@
 	if (modEntry->samples[editor.currSample].length == MAX_SAMPLE_LEN)
 		return;
 
-	turnOffVoices();
-
 	val = modEntry->samples[editor.currSample].length;
 	if (input.mouse.rightButtonPressed)
 	{
@@ -813,8 +811,6 @@
 			return;
 	}
 
-	turnOffVoices();
-
 	val = modEntry->samples[editor.currSample].length;
 	if (input.mouse.rightButtonPressed)
 	{
@@ -834,13 +830,14 @@
 	if (val < 0)
 		val = 0;
 
-	s->length = val;
 	if (s->loopStart+s->loopLength > 2)
 	{
-		if (s->length < s->loopStart+s->loopLength)
-			s->length = s->loopStart+s->loopLength;
+		if (val < s->loopStart+s->loopLength)
+			val = s->loopStart+s->loopLength;
 	}
 
+	s->length = val;
+
 	editor.ui.updateCurrSampleLength = true;
 }
 
@@ -2183,6 +2180,34 @@
 	{
 		handleSamplerFiltersBox();
 		return true;
+	}
+
+	// "downsample before loading sample" ask dialog
+	if (editor.ui.askScreenShown && editor.ui.askScreenType == ASK_LOAD_DOWNSAMPLE)
+	{
+		if (input.mouse.y >= 83 && input.mouse.y <= 93)
+		{
+			if (input.mouse.x >= 179 && input.mouse.x <= 204)
+			{
+				// YES button
+				editor.ui.askScreenShown = false;
+				editor.ui.answerNo = false;
+				editor.ui.answerYes = true;
+				handleAskYes();
+				return true;
+			}
+			else if (input.mouse.x >= 242 && input.mouse.x <= 260)
+			{
+				// NO button
+				editor.ui.askScreenShown = false;
+				editor.ui.answerNo = true;
+				editor.ui.answerYes = false;
+				handleAskNo();
+				return true;
+			}
+		}
+
+		return false;
 	}
 
 	// cancel note input gadgets with left/right mouse button
--- a/src/pt2_sampleloader.c
+++ b/src/pt2_sampleloader.c
@@ -25,6 +25,7 @@
 #include "pt2_visuals.h"
 #include "pt2_helpers.h"
 #include "pt2_unicode.h"
+#include "pt2_filters.h"
 
 enum
 {
@@ -177,7 +178,7 @@
 			default: break;
 		}
 
-		bytesRead += (chunkSize + (chunkSize & 1));
+		bytesRead += chunkSize + (chunkSize & 1);
 		fseek(f, endOfChunk, SEEK_SET);
 	}
 
@@ -238,11 +239,7 @@
 	{
 		if (forceDownSampling == -1)
 		{
-			editor.ui.askScreenShown = true;
-			editor.ui.askScreenType = ASK_DOWNSAMPLING;
-			pointerSetMode(POINTER_MODE_MSG1, NO_CARRY);
-			setStatusMessage("2X DOWNSAMPLING ?", NO_CARRY);
-			renderAskDialog();
+			showDownsampleAskDialog();
 			fclose(f);
 			return true;
 		}
@@ -292,6 +289,9 @@
 		// 2x downsampling - remove every other sample (if needed)
 		if (forceDownSampling)
 		{
+			if (ptConfig.sampleLowpass)
+				lowPassSample8BitUnsigned(audioDataU8, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
+
 			sampleLength /= 2;
 			for (i = 1; i < sampleLength; i++)
 				audioDataU8[i] = audioDataU8[i * 2];
@@ -348,6 +348,9 @@
 		// 2x downsampling - remove every other sample (if needed)
 		if (forceDownSampling)
 		{
+			if (ptConfig.sampleLowpass)
+				lowPassSample16Bit(audioDataS16, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
+
 			sampleLength /= 2;
 			for (i = 1; i < sampleLength; i++)
 				audioDataS16[i] = audioDataS16[i * 2];
@@ -407,6 +410,9 @@
 		// 2x downsampling - remove every other sample (if needed)
 		if (forceDownSampling)
 		{
+			if (ptConfig.sampleLowpass)
+				lowPassSample32Bit(audioDataS32, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
+
 			sampleLength /= 2;
 			for (i = 1; i < sampleLength; i++)
 				audioDataS32[i] = audioDataS32[i * 2];
@@ -467,6 +473,9 @@
 		// 2x downsampling - remove every other sample (if needed)
 		if (forceDownSampling)
 		{
+			if (ptConfig.sampleLowpass)
+				lowPassSample32Bit(audioDataS32, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
+
 			sampleLength /= 2;
 			for (i = 1; i < sampleLength; i++)
 				audioDataS32[i] = audioDataS32[i * 2];
@@ -527,6 +536,9 @@
 		// 2x downsampling - remove every other sample (if needed)
 		if (forceDownSampling)
 		{
+			if (ptConfig.sampleLowpass)
+				lowPassSampleFloat(fAudioDataFloat, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
+
 			sampleLength /= 2;
 			for (i = 1; i < sampleLength; i++)
 				fAudioDataFloat[i] = fAudioDataFloat[i * 2];
@@ -593,6 +605,9 @@
 		// 2x downsampling - remove every other sample (if needed)
 		if (forceDownSampling)
 		{
+			if (ptConfig.sampleLowpass)
+				lowPassSampleDouble(dAudioDataDouble, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
+
 			sampleLength /= 2;
 			for (i = 1; i < sampleLength; i++)
 				dAudioDataDouble[i] = dAudioDataDouble[i * 2];
@@ -1234,11 +1249,7 @@
 	{
 		if (forceDownSampling == -1)
 		{
-			editor.ui.askScreenShown = true;
-			editor.ui.askScreenType = ASK_DOWNSAMPLING;
-			pointerSetMode(POINTER_MODE_MSG1, NO_CARRY);
-			setStatusMessage("2X DOWNSAMPLING ?", NO_CARRY);
-			renderAskDialog();
+			showDownsampleAskDialog();
 			fclose(f);
 			return true;
 		}
@@ -1284,6 +1295,9 @@
 		// 2x downsampling - remove every other sample (if needed)
 		if (forceDownSampling)
 		{
+			if (ptConfig.sampleLowpass)
+				lowPassSample8Bit(audioDataS8, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
+
 			sampleLength /= 2;
 			for (i = 1; i < sampleLength; i++)
 				audioDataS8[i] = audioDataS8[i * 2];
@@ -1344,6 +1358,9 @@
 		// 2x downsampling - remove every other sample (if needed)
 		if (forceDownSampling)
 		{
+			if (ptConfig.sampleLowpass)
+				lowPassSample16Bit(audioDataS16, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
+
 			sampleLength /= 2;
 			for (i = 1; i < sampleLength; i++)
 				audioDataS16[i] = audioDataS16[i * 2];
@@ -1411,6 +1428,9 @@
 		// 2x downsampling - remove every other sample (if needed)
 		if (forceDownSampling)
 		{
+			if (ptConfig.sampleLowpass)
+				lowPassSample32Bit(audioDataS32, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
+
 			sampleLength /= 2;
 			for (i = 1; i < sampleLength; i++)
 				audioDataS32[i] = audioDataS32[i * 2];
@@ -1475,6 +1495,9 @@
 		// 2x downsampling - remove every other sample (if needed)
 		if (forceDownSampling)
 		{
+			if (ptConfig.sampleLowpass)
+				lowPassSample32Bit(audioDataS32, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
+
 			sampleLength /= 2;
 			for (i = 1; i < sampleLength; i++)
 				audioDataS32[i] = audioDataS32[i * 2];
--- a/src/pt2_sampler.c
+++ b/src/pt2_sampler.c
@@ -18,6 +18,11 @@
 #include "pt2_mouse.h"
 #include "pt2_scopes.h"
 
+#define CENTER_LINE_COLOR 0x303030
+#define MARK_COLOR_1 0x666666 /* inverted background */
+#define MARK_COLOR_2 0xCCCCCC /* inverted waveform */
+#define MARK_COLOR_3 0x7D7D7D /* inverted center line */
+
 #define SAMPLE_AREA_Y_CENTER 169
 #define SAMPLE_AREA_HEIGHT 64
 
@@ -28,6 +33,7 @@
 } sampleMixer_t;
 
 static int32_t samOffsetScaled;
+static uint32_t waveInvertTable[8];
 
 static const int8_t tuneToneData[32] = // Tuning Tone (Sine Wave)
 {
@@ -41,12 +47,26 @@
 
 void setLoopSprites(void);
 
+void createSampleMarkTable(void)
+{
+	// used for invertRange()  (sample data marking)
+
+	waveInvertTable[0] = 0x00000000 | palette[PAL_BACKGRD];
+	waveInvertTable[1] = 0x01000000 | palette[PAL_QADSCP];
+	waveInvertTable[2] = 0x02000000 | CENTER_LINE_COLOR;
+	waveInvertTable[3] = 0x03000000; // spacer, not used
+	waveInvertTable[4] = 0x04000000 | MARK_COLOR_1;
+	waveInvertTable[5] = 0x05000000 | MARK_COLOR_2;
+	waveInvertTable[6] = 0x06000000 | MARK_COLOR_3;
+	waveInvertTable[7] = 0x07000000; // spacer, not used
+}
+
 static void updateSamOffset(void)
 {
 	if (editor.sampler.samDisplay == 0)
 		samOffsetScaled = 0;
 	else
-		samOffsetScaled = (editor.sampler.samOffset * SAMPLE_AREA_WIDTH) / editor.sampler.samDisplay;
+		samOffsetScaled = (editor.sampler.samOffset * SAMPLE_AREA_WIDTH) / editor.sampler.samDisplay; // truncate here
 }
 
 void fixSampleBeep(moduleSample_t *s)
@@ -86,9 +106,10 @@
 	}
 }
 
-static void line(uint32_t *frameBuffer, int16_t line_x1, int16_t line_x2, int16_t line_y1, int16_t line_y2)
+static void sampleLine(uint32_t *frameBuffer, int16_t line_x1, int16_t line_x2, int16_t line_y1, int16_t line_y2)
 {
 	int16_t d, x, y, ax, ay, sx, sy, dx, dy;
+	uint32_t color = 0x01000000 | palette[PAL_QADSCP];
 
 	assert(line_x1 >= 0 || line_x2 >= 0 || line_x1 < SCREEN_W || line_x2 < SCREEN_W);
 	assert(line_y1 >= 0 || line_y2 >= 0 || line_y1 < SCREEN_H || line_y2 < SCREEN_H);
@@ -109,7 +130,7 @@
 		{
 			assert(y >= 0 || x >= 0 || y < SCREEN_H || x < SCREEN_W);
 
-			frameBuffer[(y * SCREEN_W) + x] = palette[PAL_QADSCP];
+			frameBuffer[(y * SCREEN_W) + x] = color;
 
 			if (x == line_x2)
 				break;
@@ -131,7 +152,7 @@
 		{
 			assert(y >= 0 || x >= 0 || y < SCREEN_H || x < SCREEN_W);
 
-			frameBuffer[(y * SCREEN_W) + x] = palette[PAL_QADSCP];
+			frameBuffer[(y * SCREEN_W) + x] = color;
 
 			if (y == line_y2)
 				break;
@@ -150,21 +171,20 @@
 
 static void setDragBar(void)
 {
-	int32_t pos32;
+	int32_t pos;
 	uint32_t *dstPtr, pixel, bgPixel;
-	double dPos;
 
 	if (editor.sampler.samLength > 0 && editor.sampler.samDisplay != editor.sampler.samLength)
 	{
+		int32_t roundingBias = (uint32_t)editor.sampler.samLength / 2;
+
 		// update drag bar coordinates
-		dPos = (editor.sampler.samOffset * 311.0) / editor.sampler.samLength;
-		pos32 = (int32_t)(dPos + 0.5);
-		editor.sampler.dragStart = 4 + (uint16_t)pos32;
+		pos = ((editor.sampler.samOffset * 311) + roundingBias) / editor.sampler.samLength;
+		editor.sampler.dragStart = pos + 4;
 		editor.sampler.dragStart = CLAMP(editor.sampler.dragStart, 4, 315);
 
-		dPos = ((editor.sampler.samDisplay + editor.sampler.samOffset) * 311.0) / editor.sampler.samLength;
-		pos32 = (int32_t)(dPos + 0.5);
-		editor.sampler.dragEnd = 5 + (uint16_t)pos32;
+		pos = (((editor.sampler.samDisplay + editor.sampler.samOffset) * 311) + roundingBias) / editor.sampler.samLength;
+		editor.sampler.dragEnd = pos + 5;
 		editor.sampler.dragEnd = CLAMP(editor.sampler.dragEnd, 5, 316);
 
 		if (editor.sampler.dragStart > editor.sampler.dragEnd-1)
@@ -222,13 +242,12 @@
 
 int32_t smpPos2Scr(int32_t pos) // sample pos -> screen x pos
 {
-	double dPos;
-
 	if (editor.sampler.samDisplay == 0)
 		return 0;
 
-	dPos = (pos * (double)SAMPLE_AREA_WIDTH) / editor.sampler.samDisplay;
-	pos = (int32_t)(dPos + 0.5);
+	uint32_t roundingBias = (uint32_t)editor.sampler.samDisplay >> 1;
+
+	pos = (((uint32_t)pos * SAMPLE_AREA_WIDTH) + roundingBias) / (uint32_t)editor.sampler.samDisplay; // rounded
 	pos -= samOffsetScaled;
 
 	return pos;
@@ -243,7 +262,7 @@
 		x = 0;
 
 	x += samOffsetScaled;
-	x  = (x * editor.sampler.samDisplay) / SAMPLE_AREA_WIDTH;
+	x = (uint32_t)(x * editor.sampler.samDisplay) / SAMPLE_AREA_WIDTH; // truncate here
 
 	return x;
 }
@@ -291,7 +310,11 @@
 
 	// display center line
 	if (ptConfig.dottedCenterFlag)
-		memset(&pixelBuffer[(SAMPLE_AREA_Y_CENTER * SCREEN_W) + 3], 0x373737, SAMPLE_AREA_WIDTH * sizeof (int32_t));
+	{
+		dstPtr = &pixelBuffer[(SAMPLE_AREA_Y_CENTER * SCREEN_W) + 3];
+		for (x = 0; x < SAMPLE_AREA_WIDTH; x++)
+			dstPtr[x] = 0x02000000 | CENTER_LINE_COLOR;
+	}
 
 	// render sample data
 	if (editor.sampler.samDisplay >= 0 && editor.sampler.samDisplay <= MAX_SAMPLE_LEN)
@@ -304,7 +327,7 @@
 			for (x = 1; x < SAMPLE_AREA_WIDTH; x++)
 			{
 				y2 = SAMPLE_AREA_Y_CENTER - getScaledSample(scr2SmpPos(x));
-				line(pixelBuffer, x + 2, x + 3, y1, y2);
+				sampleLine(pixelBuffer, x + 2, x + 3, y1, y2);
 				y1 = y2;
 			}
 		}
@@ -332,11 +355,11 @@
 
 				if (x > 0)
 				{
-					if (min > oldMax) line(pixelBuffer, x + 2, x + 3, oldMax, min);
-					if (max < oldMin) line(pixelBuffer, x + 2, x + 3, oldMin, max);
+					if (min > oldMax) sampleLine(pixelBuffer, x + 2, x + 3, oldMax, min);
+					if (max < oldMin) sampleLine(pixelBuffer, x + 2, x + 3, oldMin, max);
 				}
 
-				line(pixelBuffer, x + 3, x + 3, max, min);
+				sampleLine(pixelBuffer, x + 3, x + 3, max, min);
 
 				oldMin = min;
 				oldMax = max;
@@ -356,8 +379,8 @@
 
 void invertRange(void)
 {
-	int32_t x, y, rangeLen, dstPitch, start, end;
-	uint32_t *dstPtr, pixel1, pixel2;
+	int32_t x, y, rangeLen, start, end;
+	uint32_t *dstPtr;
 
 	if (editor.markStartOfs == -1)
 		return; // no marking
@@ -366,34 +389,22 @@
 	end = smpPos2Scr(editor.markEndOfs);
 
 	if (editor.sampler.samDisplay < editor.sampler.samLength && (start >= SAMPLE_AREA_WIDTH || end < 0))
-		return; // range is outside of view (passed it by scrolling)
+		return; // range is outside of view
 
-	start = CLAMP(start, 0, SAMPLE_AREA_WIDTH - 1);
-	end = CLAMP(end, 0, SAMPLE_AREA_WIDTH - 1);
+	start = CLAMP(start, 0, SAMPLE_AREA_WIDTH-1);
+	end = CLAMP(end, 0, SAMPLE_AREA_WIDTH-1);
 
 	rangeLen = (end + 1) - start;
 	if (rangeLen < 1)
 		rangeLen = 1;
 
-	dstPtr = &pixelBuffer[(138 * SCREEN_W) + (3 + start)];
-	dstPitch = SCREEN_W - rangeLen;
-	pixel1 = palette[PAL_BACKGRD];
-	pixel2 = palette[PAL_QADSCP];
-
+	dstPtr = &pixelBuffer[(138 * SCREEN_W) + (start + 3)];
 	for (y = 0; y < 64; y++)
 	{
 		for (x = 0; x < rangeLen; x++)
-		{
-			// this is stupid...
-			     if (*dstPtr == pixel1) *dstPtr = 0x666666;
-			else if (*dstPtr == 0x666666) *dstPtr = pixel1;
-			else if (*dstPtr == 0xCCCCCC) *dstPtr = pixel2;
-			else if (*dstPtr == pixel2) *dstPtr = 0xCCCCCC;
+			dstPtr[x] = waveInvertTable[((dstPtr[x] >> 24) & 7) ^ 4]; // It's magic! ptr[x]>>24 = wave/invert color number
 
-			dstPtr++;
-		}
-
-		dstPtr += dstPitch;
+		dstPtr += SCREEN_W;
 	}
 }
 
@@ -454,7 +465,7 @@
 void highPassSample(int32_t cutOff)
 {
 	int32_t smp32, i, from, to;
-	double *dSampleData, dBaseFreq, dCutOff, dIn[2], dOut[2];
+	double *dSampleData, dBaseFreq, dCutOff;
 	moduleSample_t *s;
 	lossyIntegrator_t filterHi;
 
@@ -517,30 +528,13 @@
 	for (i = 0; i < s->length; i++)
 		dSampleData[i] = modEntry->sampleData[s->offset+i];
 
-	// filter forwards
 	filterHi.dBuffer[0] = 0.0;
 	if (to <= s->length)
 	{
 		for (i = from; i < to; i++)
-		{
-			dIn[0] = dSampleData[i];
-			lossyIntegratorHighPass(&filterHi, dIn, dOut);
-			dSampleData[i] = dOut[0];
-		}
+			lossyIntegratorHighPassMono(&filterHi, dSampleData[i], &dSampleData[i]);
 	}
 
-	// filter backwards
-	filterHi.dBuffer[0] = 0.0;
-	if (to <= s->length)
-	{
-		for (i = to-1; i >= from; i--)
-		{
-			dIn[0] = dSampleData[i];
-			lossyIntegratorHighPass(&filterHi, dIn, dOut);
-			dSampleData[i] = dOut[0];
-		}
-	}
-
 	if (editor.normalizeFiltersFlag)
 		normalize8bitDoubleSigned(dSampleData, s->length);
 
@@ -561,7 +555,7 @@
 void lowPassSample(int32_t cutOff)
 {
 	int32_t smp32, i, from, to;
-	double *dSampleData, dBaseFreq, dCutOff, dIn[2], dOut[2];
+	double *dSampleData, dBaseFreq, dCutOff;
 	moduleSample_t *s;
 	lossyIntegrator_t filterLo;
 
@@ -624,30 +618,13 @@
 	for (i = 0; i < s->length; i++)
 		dSampleData[i] = modEntry->sampleData[s->offset+i];
 
-	// filter forwards
 	filterLo.dBuffer[0] = 0.0;
 	if (to <= s->length)
 	{
 		for (i = from; i < to; i++)
-		{
-			dIn[0] = dSampleData[i];
-			lossyIntegrator(&filterLo, dIn, dOut);
-			dSampleData[i] = dOut[0];
-		}
+			lossyIntegratorMono(&filterLo, dSampleData[i], &dSampleData[i]);
 	}
 
-	// filter backwards
-	filterLo.dBuffer[0] = 0.0;
-	if (to <= s->length)
-	{
-		for (i = to-1; i >= from; i--)
-		{
-			dIn[0] = dSampleData[i];
-			lossyIntegrator(&filterLo, dIn, dOut);
-			dSampleData[i] = dOut[0];
-		}
-	}
-
 	if (editor.normalizeFiltersFlag)
 		normalize8bitDoubleSigned(dSampleData, s->length);
 
@@ -1491,7 +1468,7 @@
 	}
 	else
 	{
-		middlePos = editor.sampler.samOffset + (int32_t)((editor.sampler.samDisplay / 2.0) + 0.5);
+		middlePos = editor.sampler.samOffset + ((editor.sampler.samDisplay + 1) / 2);
 
 		invertRange();
 		if (input.keyb.shiftPressed && editor.markStartOfs != -1)
@@ -2047,8 +2024,10 @@
 	if (tmpDisplay < 2)
 		tmpDisplay = 2;
 
-	step += (((x - (SCREEN_W / 2)) * step) / (SCREEN_W / 2));
+	const int32_t roundingBias = SCREEN_W / 4;
 
+	step += (((x - (SCREEN_W / 2)) * step) + roundingBias) / (SCREEN_W / 2);
+
 	tmpOffset = editor.sampler.samOffset + step;
 	if (tmpOffset < 0)
 		tmpOffset = 0;
@@ -2081,8 +2060,10 @@
 	}
 	else
 	{
-		step += (((x - (SCREEN_W / 2)) * step) / (SCREEN_W / 2));
+		const int32_t roundingBias = SCREEN_W / 4;
 
+		step += (((x - (SCREEN_W / 2)) * step) + roundingBias) / (SCREEN_W / 2);
+
 		tmpOffset = editor.sampler.samOffset - step;
 		if (tmpOffset < 0)
 			tmpOffset = 0;
@@ -2100,20 +2081,17 @@
 
 void samplerZoomInMouseWheel(void)
 {
-	int32_t step = (int32_t)((editor.sampler.samDisplay / 10.0f) + 0.5f);
-	samplerZoomIn(step, input.mouse.x);
+	samplerZoomIn((editor.sampler.samDisplay + 5) / 10, input.mouse.x);
 }
 
 void samplerZoomOutMouseWheel(void)
 {
-	int32_t step = (int32_t)((editor.sampler.samDisplay / 10.0f) + 0.5f);
-	samplerZoomOut(step, input.mouse.x);
+	samplerZoomOut((editor.sampler.samDisplay + 5) / 10, input.mouse.x);
 }
 
 void samplerZoomOut2x(void)
 {
-	int32_t step = (int32_t)((editor.sampler.samDisplay / 2.0f) + 0.5f);
-	samplerZoomOut(step, SCREEN_W / 2);
+	samplerZoomOut((editor.sampler.samDisplay + 1) / 2, SCREEN_W / 2);
 }
 
 void samplerRangeAll(void)
@@ -2200,13 +2178,13 @@
 
 			if (editor.ui.forceVolDrag == 1)
 			{
-				editor.vol1 = (int16_t)((mouseX * 200) / 60);
+				editor.vol1 = (int16_t)(((mouseX * 200) + (60/2)) / 60); // rounded
 				editor.ui.updateVolFromText = true;
 				showVolFromSlider();
 			}
 			else if (editor.ui.forceVolDrag == 2)
 			{
-				editor.vol2 = (int16_t)((mouseX * 200) / 60);
+				editor.vol2 = (int16_t)(((mouseX * 200) + (60/2)) / 60); // rounded
 				editor.ui.updateVolToText = true;
 				showVolToSlider();
 			}
@@ -2280,7 +2258,7 @@
 		if (tmp32 < 0)
 			tmp32 = 0;
 
-		tmp32 = (int32_t)(((tmp32 * editor.sampler.samLength) / 311.0) + 0.5);
+		tmp32 = (int32_t)(((tmp32 * editor.sampler.samLength) + (311/2)) / 311); // rounded
 		if (tmp32+editor.sampler.samDisplay <= editor.sampler.samLength)
 		{
 			if (tmp32 == editor.sampler.samOffset)
--- a/src/pt2_sampler.h
+++ b/src/pt2_sampler.h
@@ -3,6 +3,7 @@
 #include <stdint.h>
 #include <stdbool.h>
 
+void createSampleMarkTable(void);
 int32_t smpPos2Scr(int32_t pos);
 int32_t scr2SmpPos(int32_t x);
 void fixSampleBeep(moduleSample_t *s);
--- a/src/pt2_tables.c
+++ b/src/pt2_tables.c
@@ -9,7 +9,7 @@
 uint32_t *editOpScreen3BMP = NULL, *editOpScreen4BMP   = NULL, *spectrumVisualsBMP = NULL;
 uint32_t *muteButtonsBMP   = NULL, *posEdBMP           = NULL, *samplerFiltersBMP  = NULL;
 uint32_t *samplerScreenBMP = NULL, *pat2SmpDialogBMP   = NULL, *trackerFrameBMP    = NULL;
-uint32_t *yesNoDialogBMP   = NULL;
+uint32_t *yesNoDialogBMP   = NULL, *bigYesNoDialogBMP  = NULL;
 
 const uint32_t cursorColors[6][3] =
 {
--- a/src/pt2_tables.h
+++ b/src/pt2_tables.h
@@ -36,6 +36,7 @@
 extern const uint8_t spectrumVisualsPackedBMP[2217];
 extern const uint8_t trackerFramePackedBMP[8486];
 extern const uint8_t yesNoDialogPackedBMP[476];
+extern const uint8_t bigYesNoDialogPackedBMP[472];
 extern const uint8_t pat2SmpDialogPackedBMP[520];
 
 // changable by config file
@@ -53,6 +54,7 @@
 extern uint32_t *editOpScreen3BMP;
 extern uint32_t *editOpScreen4BMP;
 extern uint32_t *yesNoDialogBMP;
+extern uint32_t *bigYesNoDialogBMP;
 extern uint32_t *spectrumVisualsBMP;
 extern uint32_t *posEdBMP;
 extern uint32_t *mod2wavBMP;
--- a/src/pt2_visuals.c
+++ b/src/pt2_visuals.c
@@ -214,6 +214,42 @@
 	}
 }
 
+void renderBigAskDialog(void)
+{
+	const uint32_t *srcPtr;
+	uint32_t *dstPtr;
+
+	editor.ui.disablePosEd = true;
+	editor.ui.disableVisualizer = true;
+
+	// render custom big ask dialog
+
+	srcPtr = bigYesNoDialogBMP;
+	dstPtr = &pixelBuffer[(44 * SCREEN_W) + 120];
+
+	for (uint32_t y = 0; y < 55; y++)
+	{
+		memcpy(dstPtr, srcPtr, 200 * sizeof (int32_t));
+
+		srcPtr += 200;
+		dstPtr += SCREEN_W;
+	}
+}
+
+void showDownsampleAskDialog(void)
+{
+	editor.ui.askScreenShown = true;
+	editor.ui.askScreenType = ASK_LOAD_DOWNSAMPLE;
+	pointerSetMode(POINTER_MODE_MSG1, NO_CARRY);
+	setStatusMessage("PLEASE SELECT", NO_CARRY);
+	renderBigAskDialog();
+
+	textOutTight(pixelBuffer, 133, 49, "THE SAMPLE'S FREQUENCY IS", palette[PAL_BACKGRD]);
+	textOutTight(pixelBuffer, 178, 57, "ABOVE 22KHZ.", palette[PAL_BACKGRD]);
+	textOutTight(pixelBuffer, 133, 65, "DO YOU WANT TO DOWNSAMPLE", palette[PAL_BACKGRD]);
+	textOutTight(pixelBuffer, 156, 73, "BEFORE LOADING IT?", palette[PAL_BACKGRD]);
+}
+
 static void fillFromVuMetersBgBuffer(void)
 {
 	const uint32_t *srcPtr;
@@ -594,7 +630,7 @@
 	{
 		if (!editor.ui.samplerVolBoxShown && !editor.ui.samplerFiltersBoxShown && s->length > 0)
 		{
-			tmpSampleOffset = (scr2SmpPos(input.mouse.x - 3) + 128) >> 8;
+			tmpSampleOffset = (scr2SmpPos(input.mouse.x-3) + (1 << 7)) >> 8; // rounded
 			tmpSampleOffset = 0x900 + CLAMP(tmpSampleOffset, 0x00, 0xFF);
 
 			if (tmpSampleOffset != editor.ui.lastSampleOffset)
@@ -674,7 +710,7 @@
 {
 	uint32_t *dstPtr, pixel, bgPixel, sliderStart, sliderEnd;
 
-	sliderStart = (editor.vol1 * 3) / 10;
+	sliderStart = ((editor.vol1 * 3) + 5) / 10;
 	sliderEnd  = sliderStart + 4;
 	pixel = palette[PAL_QADSCP];
 	bgPixel = palette[PAL_BACKGRD];
@@ -698,7 +734,7 @@
 {
 	uint32_t *dstPtr, pixel, bgPixel, sliderStart, sliderEnd;
 
-	sliderStart = (editor.vol2 * 3) / 10;
+	sliderStart = ((editor.vol2 * 3) + 5) / 10;
 	sliderEnd = sliderStart + 4;
 	pixel = palette[PAL_QADSCP];
 	bgPixel = palette[PAL_BACKGRD];
@@ -1069,6 +1105,7 @@
 		pixel = palette[PAL_GENBKG2];
 		for (uint32_t x = 0; x < 7; x++)
 			dstPtr[x] = pixel;
+
 		// no need to clear the second row of pixels
 
 		editor.ui.updatePosEd = true;
@@ -1148,7 +1185,7 @@
 				if (y < tmpVol)
 					pixel = srcPtr[y];
 
-				for (uint32_t  x = 0; x < SPECTRUM_BAR_WIDTH; x++)
+				for (uint32_t x = 0; x < SPECTRUM_BAR_WIDTH; x++)
 					dstPtr[x] = pixel;
 
 				dstPtr += SCREEN_W;
@@ -1223,7 +1260,7 @@
 	// draw version string
 
 	sprintf(verString, "v%s", PROG_VER_STR);
-	verStringX = 260 + (63 - ((uint32_t)strlen(verString) * (FONT_CHAR_W - 1))) / 2;
+	verStringX = 260 + (((63 - ((uint32_t)strlen(verString) * (FONT_CHAR_W - 1))) + 1) / 2);
 	textOutTight(pixelBuffer, verStringX, 67, verString, palette[PAL_GENBKG2]);
 }
 
@@ -1393,7 +1430,7 @@
 			if (percent > 100)
 				percent = 100;
 
-			barLength = (percent * 180) / 100;
+			barLength = ((percent * 180) + 50) / 100;
 			dstPtr = &pixelBuffer[(42 * SCREEN_W) + 70];
 			pixel = palette[PAL_GENBKG2];
 			bgPixel = palette[PAL_BORDER];
@@ -1401,8 +1438,14 @@
 			for (int32_t y = 0; y < 11; y++)
 			{
 				for (int32_t x = 0; x < 180; x++)
-					dstPtr[x] = (x < barLength) ? pixel : bgPixel;
+				{
+					uint32_t color = bgPixel;
+					if (x < barLength)
+						color = pixel;
 
+					dstPtr[x] = color;
+				}
+
 				dstPtr += SCREEN_W;
 			}
 
@@ -1690,7 +1733,7 @@
 		}
 		break;
 
-		case ASK_DOWNSAMPLING:
+		case ASK_LOAD_DOWNSAMPLE:
 		{
 			restoreStatusAndMousePointer();
 			extLoadWAVOrAIFFSampleCallback(DONT_DOWNSAMPLE);
@@ -2021,7 +2064,7 @@
 		}
 		break;
 
-		case ASK_DOWNSAMPLING:
+		case ASK_LOAD_DOWNSAMPLE:
 		{
 			// for WAV and AIFF sample loader
 			restoreStatusAndMousePointer();
@@ -2273,6 +2316,7 @@
 	if (posEdBMP != NULL) free(posEdBMP);
 	if (spectrumVisualsBMP != NULL) free(spectrumVisualsBMP);
 	if (yesNoDialogBMP != NULL) free(yesNoDialogBMP);
+	if (bigYesNoDialogBMP != NULL) free(bigYesNoDialogBMP);
 	if (pat2SmpDialogBMP != NULL) free(pat2SmpDialogBMP);
 	if (editOpScreen1BMP != NULL) free(editOpScreen1BMP);
 	if (editOpScreen2BMP != NULL) free(editOpScreen2BMP);
@@ -2366,6 +2410,7 @@
 	posEdBMP = unpackBMP(posEdPackedBMP, sizeof (posEdPackedBMP));
 	spectrumVisualsBMP = unpackBMP(spectrumVisualsPackedBMP, sizeof (spectrumVisualsPackedBMP));
 	yesNoDialogBMP = unpackBMP(yesNoDialogPackedBMP, sizeof (yesNoDialogPackedBMP));
+	bigYesNoDialogBMP = unpackBMP(bigYesNoDialogPackedBMP, sizeof (bigYesNoDialogPackedBMP));
 	pat2SmpDialogBMP = unpackBMP(pat2SmpDialogPackedBMP, sizeof (pat2SmpDialogPackedBMP));
 	editOpScreen1BMP = unpackBMP(editOpScreen1PackedBMP, sizeof (editOpScreen1PackedBMP));
 	editOpScreen2BMP = unpackBMP(editOpScreen2PackedBMP, sizeof (editOpScreen2PackedBMP));
@@ -2383,7 +2428,7 @@
 		editOpScreen1BMP   == NULL || editOpScreen2BMP   == NULL || editOpScreen3BMP  == NULL ||
 		editOpScreen4BMP   == NULL || aboutScreenBMP     == NULL || muteButtonsBMP    == NULL ||
 		editOpModeCharsBMP == NULL || arrowBMP           == NULL || samplerFiltersBMP == NULL ||
-		yesNoDialogBMP     == NULL)
+		yesNoDialogBMP     == NULL || bigYesNoDialogBMP  == NULL)
 	{
 		showErrorMsgBox("Out of memory!");
 		return false; // BMPs are free'd in cleanUp()
--- a/src/pt2_visuals.h
+++ b/src/pt2_visuals.h
@@ -42,6 +42,8 @@
 void createBitmaps(void);
 void displayMainScreen(void);
 void renderAskDialog(void);
+void renderBigAskDialog(void);
+void showDownsampleAskDialog(void);
 void renderPosEdScreen(void);
 void renderDiskOpScreen(void);
 void renderMuteButtons(void);
--- a/vs2019_project/pt2-clone/protracker.ini
+++ b/vs2019_project/pt2-clone/protracker.ini
@@ -221,4 +221,18 @@
 ;
 STEREOSEPARATION=20
 
+
+; Low-pass samples before getting 2x downsampled during loading (if requested)
+;        Syntax: TRUE or FALSE
+; Default value: TRUE
+;       Comment: Set to false if you want slightly sharper sound when loading
+;         samples that are 2x downsampled (if requested).
+;         Keep in mind that you might get more aliasing in the sound if you
+;         disable this, and certain sounds with a lot of treble might sound
+;         weird (f.ex. hi-hats and cymbals). If the sample you load have a
+;         frequency below 22kHz, it will never be downsampled (and thus this
+;         setting changes nothing). I recommend keeping this set to TRUE.
+;
+SAMPLELOWPASS=TRUE
+
 ; End of config file
\ No newline at end of file
--- a/vs2019_project/pt2-clone/pt2-clone.vcxproj
+++ b/vs2019_project/pt2-clone/pt2-clone.vcxproj
@@ -284,6 +284,7 @@
     <ClInclude Include="..\..\src\pt2_config.h" />
     <ClInclude Include="..\..\src\pt2_diskop.h" />
     <ClInclude Include="..\..\src\pt2_edit.h" />
+    <ClInclude Include="..\..\src\pt2_filters.h" />
     <ClInclude Include="..\..\src\pt2_header.h" />
     <ClInclude Include="..\..\src\pt2_helpers.h" />
     <ClInclude Include="..\..\src\pt2_keyboard.h" />
@@ -326,6 +327,7 @@
     <ClCompile Include="..\..\src\pt2_config.c" />
     <ClCompile Include="..\..\src\pt2_diskop.c" />
     <ClCompile Include="..\..\src\pt2_edit.c" />
+    <ClCompile Include="..\..\src\pt2_filters.c" />
     <ClCompile Include="..\..\src\pt2_helpers.c" />
     <ClCompile Include="..\..\src\pt2_keyboard.c" />
     <ClCompile Include="..\..\src\pt2_main.c" />
--- a/vs2019_project/pt2-clone/pt2-clone.vcxproj.filters
+++ b/vs2019_project/pt2-clone/pt2-clone.vcxproj.filters
@@ -66,6 +66,9 @@
     <ClInclude Include="..\..\src\pt2_visuals.h">
       <Filter>headers</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\pt2_filters.h">
+      <Filter>headers</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="..\..\src\pt2_audio.c" />
@@ -145,6 +148,7 @@
     <ClCompile Include="..\..\src\gfx\pt2_gfx_yes_no_dialog.c">
       <Filter>gfx</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\src\pt2_filters.c" />
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="..\..\src\pt2-clone.rc" />