shithub: pt2-clone

Download patch

ref: 7e106b7505fbd4682ba11b7854c92f15dc5ea44e
parent: 02664d922f41be9050be714cedb677bc010ae903
author: Olav Sørensen <olav.sorensen@live.no>
date: Fri Sep 18 16:38:57 EDT 2020

Pushed v1.23 code

- PAT2SMP now has higher quality sound. Especially noticable in LO mode.
- PAT2SMP LO mode now uses 20864Hz (E-3 finetune 0) instead of 22168Hz
  (F-3 finetune +1) for slightly more room, and still has better quality
  than before because of the improvement above!
- The optional 2x downsample during sample load is now of better quality.
  Or it should be, at least...
- Fixed some bugs in the "sampling" screen (SAMPLER screen -> Sample)
- The sampling feature now takes up less CPU time while downsampling. Also
  fixed a small error in the sinc resampling interpolation.
- Don't block GUI input and don't show red mouse pointer for a while every time
  you cancel a requester. No idea why PT did this in the first place, what a
  workflow limiter!
- Bugfix: Don't reset playback time counter on "play pattern"
- protracker.ini: "A500LOWPASSFILTER" has been replaced with "FILTERMODEL"
  (A1200 or A500). Removed NORMALIZESAMPLING and SAMPLELOWPASS (deprecated).
- Some other minor changes not worth of a mention
- Code cleanup

--- a/.gitignore
+++ b/.gitignore
@@ -22,3 +22,5 @@
 *.db-wal
 *.db-shm
 pt_pal_editor/vs2019_project/pt_pal_editor/Debug/pt_pal_editor.vcxproj.FileListAbsolute.txt
+vs2019_project/pt2-clone/Release/pt2-clone-win64.exe.recipe
+*.recipe
--- a/release/help.txt
+++ b/release/help.txt
@@ -11,6 +11,9 @@
  * Is there a way to run the program in fullscreen mode?
  - Press F11. On some keyboards/configurations you may need to press fn+F11.
  
+ * How can I toggle the audio type between Amiga 500 and Amiga 1200?
+ - Press F12. You can also change "FILTERMODEL" in protracker.ini.
+ 
  * Alt+F5/ALT+F5 (copy/paste) doesn't work!
  - Windows: you need to make sure those keybinding are disabled in
    'GeForce Experience' if you have that software installed.
@@ -170,10 +173,10 @@
  
  - Quality mode: -
  HI: 28604Hz - Play the new sample at note A-3 (finetune +4)
- LO: 22168Hz - Play the new sample at note F-3 (finetune +1)
+ LO: 20864Hz - Play the new sample at note E-3 (finetune  0)
   
  HI has the highest possible quality, but can only fit 2.29 seconds of audio.
- LO is worse in quality, but can fit slightly more at 2.95 seconds.
+ LO is worse in quality, but can fit slightly more at 3.14 seconds.
  
  You should use this feature in sequences to fit as much rows as you need.
  F.ex. start from row 0 to row 23, then make another sample from row 24 to 47,
--- a/release/macos/protracker.ini
+++ b/release/macos/protracker.ini
@@ -220,39 +220,16 @@
 ;
 SAMPLINGFREQ=44100
 
-; Normalize sampled audio before converting to 8-bit
-;        Syntax: TRUE or FALSE
-; Default value: TRUE
-;       Comment: This one is for the audio sampling feature in the SAMPLER
-;         screen. If it's set to TRUE, it will normalize the gain before it
-;         converts the sample to 8-bit in the end. This will preserve as much
-;         amplitude information as possible to lower quantization noise.
+; Filter model (Amiga model)
+;        Syntax: A500 or A1200
+; Default value: A1200
+;       Comment: Selects what kind of Amiga to simulate (lp/hp filters).
+;         A1200 has sharper sound but more aliasing, while A500 has more
+;         filtered (muddy) sound but less aliasing. The filter model can
+;         also be toggled by pressing F12 in the program.
 ;
-NORMALIZESAMPLING=TRUE
+FILTERMODEL=A1200
 
-; Audio buffer size
-;        Syntax: Number, in samples
-; Default value: 1024
-;       Comment: Ranges from 128 to 8192. Should be a number that is 2^n
-;          (128, 256, 512, 1024, 2048, 4096, 8192, ...). The number you input
-;          isn't necessarily the actual value the audio API decides to use.
-;          Lower means less audio latency but possible audio issues, higher
-;          means more audio latency but less chance for issues.
-;
-BUFFERSIZE=1024
-
-; Amiga 500 low-pass filter (not the "LED" filter)
-;        Syntax: TRUE or FALSE
-; Default value: FALSE
-;       Comment: Enabling this will simulate the ~4421Hz 6dB/oct RC low-pass
-;         filter present in almost all Amigas. This will make the sound a bit
-;         muddier. On Amiga 1200, the cut-off is ~34kHz (sharper sound). This
-;         can also be toggled in the tracker by pressing F12. This must not be
-;         confused with the "LED" filter which can be toggled with the pattern
-;         command E0x.
-;
-A500LOWPASSFILTER=FALSE
-
 ; Stereo separation
 ;        Syntax: 0 to 100 (percent)
 ; Default value: 20 (good value for headphones)
@@ -261,18 +238,15 @@
 ;
 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.
+; Audio buffer size
+;        Syntax: Number, in unit of samples (not bytes)
+; Default value: 1024
+;       Comment: Ranges from 128 to 8192. Should be a number that is 2^n
+;          (128, 256, 512, 1024, 2048, 4096, 8192, ...). The number you input
+;          isn't necessarily the actual value the audio API decides to use.
+;          Lower means less audio latency but possible audio issues, higher
+;          means more audio latency but less chance for issues.
 ;
-SAMPLELOWPASS=TRUE
+BUFFERSIZE=1024
 
 ; End of config file
\ No newline at end of file
--- a/release/other/protracker.ini
+++ b/release/other/protracker.ini
@@ -220,39 +220,16 @@
 ;
 SAMPLINGFREQ=44100
 
-; Normalize sampled audio before converting to 8-bit
-;        Syntax: TRUE or FALSE
-; Default value: TRUE
-;       Comment: This one is for the audio sampling feature in the SAMPLER
-;         screen. If it's set to TRUE, it will normalize the gain before it
-;         converts the sample to 8-bit in the end. This will preserve as much
-;         amplitude information as possible to lower quantization noise.
+; Filter model (Amiga model)
+;        Syntax: A500 or A1200
+; Default value: A1200
+;       Comment: Selects what kind of Amiga to simulate (lp/hp filters).
+;         A1200 has sharper sound but more aliasing, while A500 has more
+;         filtered (muddy) sound but less aliasing. The filter model can
+;         also be toggled by pressing F12 in the program.
 ;
-NORMALIZESAMPLING=TRUE
+FILTERMODEL=A1200
 
-; Audio buffer size
-;        Syntax: Number, in samples
-; Default value: 1024
-;       Comment: Ranges from 128 to 8192. Should be a number that is 2^n
-;          (128, 256, 512, 1024, 2048, 4096, 8192, ...). The number you input
-;          isn't necessarily the actual value the audio API decides to use.
-;          Lower means less audio latency but possible audio issues, higher
-;          means more audio latency but less chance for issues.
-;
-BUFFERSIZE=1024
-
-; Amiga 500 low-pass filter (not the "LED" filter)
-;        Syntax: TRUE or FALSE
-; Default value: FALSE
-;       Comment: Enabling this will simulate the ~4421Hz 6dB/oct RC low-pass
-;         filter present in almost all Amigas. This will make the sound a bit
-;         muddier. On Amiga 1200, the cut-off is ~34kHz (sharper sound). This
-;         can also be toggled in the tracker by pressing F12. This must not be
-;         confused with the "LED" filter which can be toggled with the pattern
-;         command E0x.
-;
-A500LOWPASSFILTER=FALSE
-
 ; Stereo separation
 ;        Syntax: 0 to 100 (percent)
 ; Default value: 20 (good value for headphones)
@@ -261,18 +238,15 @@
 ;
 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.
+; Audio buffer size
+;        Syntax: Number, in unit of samples (not bytes)
+; Default value: 1024
+;       Comment: Ranges from 128 to 8192. Should be a number that is 2^n
+;          (128, 256, 512, 1024, 2048, 4096, 8192, ...). The number you input
+;          isn't necessarily the actual value the audio API decides to use.
+;          Lower means less audio latency but possible audio issues, higher
+;          means more audio latency but less chance for issues.
 ;
-SAMPLELOWPASS=TRUE
+BUFFERSIZE=1024
 
 ; End of config file
\ No newline at end of file
--- a/release/win32/protracker.ini
+++ b/release/win32/protracker.ini
@@ -220,39 +220,16 @@
 ;
 SAMPLINGFREQ=44100
 
-; Normalize sampled audio before converting to 8-bit
-;        Syntax: TRUE or FALSE
-; Default value: TRUE
-;       Comment: This one is for the audio sampling feature in the SAMPLER
-;         screen. If it's set to TRUE, it will normalize the gain before it
-;         converts the sample to 8-bit in the end. This will preserve as much
-;         amplitude information as possible to lower quantization noise.
+; Filter model (Amiga model)
+;        Syntax: A500 or A1200
+; Default value: A1200
+;       Comment: Selects what kind of Amiga to simulate (lp/hp filters).
+;         A1200 has sharper sound but more aliasing, while A500 has more
+;         filtered (muddy) sound but less aliasing. The filter model can
+;         also be toggled by pressing F12 in the program.
 ;
-NORMALIZESAMPLING=TRUE
+FILTERMODEL=A1200
 
-; Audio buffer size
-;        Syntax: Number, in samples
-; Default value: 1024
-;       Comment: Ranges from 128 to 8192. Should be a number that is 2^n
-;          (128, 256, 512, 1024, 2048, 4096, 8192, ...). The number you input
-;          isn't necessarily the actual value the audio API decides to use.
-;          Lower means less audio latency but possible audio issues, higher
-;          means more audio latency but less chance for issues.
-;
-BUFFERSIZE=1024
-
-; Amiga 500 low-pass filter (not the "LED" filter)
-;        Syntax: TRUE or FALSE
-; Default value: FALSE
-;       Comment: Enabling this will simulate the ~4421Hz 6dB/oct RC low-pass
-;         filter present in almost all Amigas. This will make the sound a bit
-;         muddier. On Amiga 1200, the cut-off is ~34kHz (sharper sound). This
-;         can also be toggled in the tracker by pressing F12. This must not be
-;         confused with the "LED" filter which can be toggled with the pattern
-;         command E0x.
-;
-A500LOWPASSFILTER=FALSE
-
 ; Stereo separation
 ;        Syntax: 0 to 100 (percent)
 ; Default value: 20 (good value for headphones)
@@ -261,18 +238,15 @@
 ;
 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.
+; Audio buffer size
+;        Syntax: Number, in unit of samples (not bytes)
+; Default value: 1024
+;       Comment: Ranges from 128 to 8192. Should be a number that is 2^n
+;          (128, 256, 512, 1024, 2048, 4096, 8192, ...). The number you input
+;          isn't necessarily the actual value the audio API decides to use.
+;          Lower means less audio latency but possible audio issues, higher
+;          means more audio latency but less chance for issues.
 ;
-SAMPLELOWPASS=TRUE
+BUFFERSIZE=1024
 
 ; End of config file
\ No newline at end of file
--- a/release/win64/protracker.ini
+++ b/release/win64/protracker.ini
@@ -220,39 +220,16 @@
 ;
 SAMPLINGFREQ=44100
 
-; Normalize sampled audio before converting to 8-bit
-;        Syntax: TRUE or FALSE
-; Default value: TRUE
-;       Comment: This one is for the audio sampling feature in the SAMPLER
-;         screen. If it's set to TRUE, it will normalize the gain before it
-;         converts the sample to 8-bit in the end. This will preserve as much
-;         amplitude information as possible to lower quantization noise.
+; Filter model (Amiga model)
+;        Syntax: A500 or A1200
+; Default value: A1200
+;       Comment: Selects what kind of Amiga to simulate (lp/hp filters).
+;         A1200 has sharper sound but more aliasing, while A500 has more
+;         filtered (muddy) sound but less aliasing. The filter model can
+;         also be toggled by pressing F12 in the program.
 ;
-NORMALIZESAMPLING=TRUE
+FILTERMODEL=A1200
 
-; Audio buffer size
-;        Syntax: Number, in samples
-; Default value: 1024
-;       Comment: Ranges from 128 to 8192. Should be a number that is 2^n
-;          (128, 256, 512, 1024, 2048, 4096, 8192, ...). The number you input
-;          isn't necessarily the actual value the audio API decides to use.
-;          Lower means less audio latency but possible audio issues, higher
-;          means more audio latency but less chance for issues.
-;
-BUFFERSIZE=1024
-
-; Amiga 500 low-pass filter (not the "LED" filter)
-;        Syntax: TRUE or FALSE
-; Default value: FALSE
-;       Comment: Enabling this will simulate the ~4421Hz 6dB/oct RC low-pass
-;         filter present in almost all Amigas. This will make the sound a bit
-;         muddier. On Amiga 1200, the cut-off is ~34kHz (sharper sound). This
-;         can also be toggled in the tracker by pressing F12. This must not be
-;         confused with the "LED" filter which can be toggled with the pattern
-;         command E0x.
-;
-A500LOWPASSFILTER=FALSE
-
 ; Stereo separation
 ;        Syntax: 0 to 100 (percent)
 ; Default value: 20 (good value for headphones)
@@ -261,18 +238,15 @@
 ;
 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.
+; Audio buffer size
+;        Syntax: Number, in unit of samples (not bytes)
+; Default value: 1024
+;       Comment: Ranges from 128 to 8192. Should be a number that is 2^n
+;          (128, 256, 512, 1024, 2048, 4096, 8192, ...). The number you input
+;          isn't necessarily the actual value the audio API decides to use.
+;          Lower means less audio latency but possible audio issues, higher
+;          means more audio latency but less chance for issues.
 ;
-SAMPLELOWPASS=TRUE
+BUFFERSIZE=1024
 
 ; End of config file
\ No newline at end of file
--- a/src/pt2_audio.c
+++ b/src/pt2_audio.c
@@ -15,7 +15,7 @@
 #else
 #include <unistd.h>
 #endif
-#include <math.h> // sqrt(),tan(),M_PI
+#include <math.h> // sqrt(),tan()
 #include <fcntl.h>
 #include <sys/types.h>
 #include <sys/stat.h>
@@ -26,7 +26,6 @@
 #include "pt2_blep.h"
 #include "pt2_config.h"
 #include "pt2_tables.h"
-#include "pt2_palette.h"
 #include "pt2_textout.h"
 #include "pt2_visuals.h"
 #include "pt2_scopes.h"
@@ -34,19 +33,15 @@
 #include "pt2_pat2smp.h"
 #include "pt2_sync.h"
 #include "pt2_structs.h"
+#include "pt2_rcfilter.h"
+#include "pt2_ledfilter.h"
 
 #define INITIAL_DITHER_SEED 0x12345000
 
-typedef struct ledFilter_t
-{
-	double buffer[4];
-	double c, ci, feedback, bg, cg, c2;
-} ledFilter_t;
-
-static volatile int8_t filterFlags;
+static volatile bool ledFilterEnabled;
+static volatile uint8_t filterModel;
 static int8_t defStereoSep;
 static bool amigaPanFlag;
-static uint16_t ch1Pan, ch2Pan, ch3Pan, ch4Pan;
 static int32_t oldPeriod = -1, randSeed = INITIAL_DITHER_SEED;
 static uint32_t audLatencyPerfValInt, audLatencyPerfValFrac;
 static uint64_t tickTime64, tickTime64Frac;
@@ -53,7 +48,7 @@
 static double *dMixBufferL, *dMixBufferR, *dMixBufferLUnaligned, *dMixBufferRUnaligned, dOldVoiceDelta, dOldVoiceDeltaMul;
 static double dPrngStateL, dPrngStateR;
 static blep_t blep[AMIGA_VOICES], blepVol[AMIGA_VOICES];
-static rcFilter_t filterLoA500, filterLoA1200, filterHi;
+static rcFilter_t filterLoA500, filterLoA1200, filterHiA500, filterHiA1200;
 static ledFilter_t filterLED;
 static SDL_AudioDeviceID dev;
 
@@ -66,52 +61,6 @@
 
 bool intMusic(void); // defined in pt_modplayer.c
 
-static void calcAudioLatencyVars(int32_t audioBufferSize, int32_t audioFreq)
-{
-	double dInt, dFrac;
-
-	if (audioFreq == 0)
-		return;
-
-	const double dAudioLatencySecs = audioBufferSize / (double)audioFreq;
-
-	dFrac = modf(dAudioLatencySecs * editor.dPerfFreq, &dInt);
-
-	// integer part
-	audLatencyPerfValInt = (int32_t)dInt;
-
-	// fractional part (scaled to 0..2^32-1)
-	dFrac *= UINT32_MAX+1.0;
-	audLatencyPerfValFrac = (uint32_t)dFrac;
-}
-
-void setSyncTickTimeLen(uint32_t timeLen, uint32_t timeLenFrac)
-{
-	tickTimeLen = timeLen;
-	tickTimeLenFrac = timeLenFrac;
-}
-
-static void generateBpmTables(void)
-{
-	for (int32_t i = 32; i <= 255; i++)
-	{
-		const double dBpmHz = i / 2.5;
-
-		audio.bpmTab[i-32] = audio.outputRate / dBpmHz;
-		audio.bpmTab28kHz[i-32] = PAT2SMP_HI_FREQ / dBpmHz; // PAT2SMP hi quality
-		audio.bpmTab22kHz[i-32] = PAT2SMP_LO_FREQ / dBpmHz; // PAT2SMP low quality
-		audio.bpmTabMod2Wav[i-32] = MOD2WAV_FREQ / dBpmHz; // MOD2WAV
-	}
-}
-
-static void clearLEDFilterState(void)
-{
-	filterLED.buffer[0] = 0.0; // left channel
-	filterLED.buffer[1] = 0.0;
-	filterLED.buffer[2] = 0.0; // right channel
-	filterLED.buffer[3] = 0.0;
-}
-
 void setLEDFilter(bool state, bool doLockAudio)
 {
 	const bool audioWasntLocked = !audio.locked;
@@ -118,16 +67,10 @@
 	if (doLockAudio && audioWasntLocked)
 		lockAudio();
 
+	clearLEDFilterState(&filterLED);
+
 	editor.useLEDFilter = state;
-	if (editor.useLEDFilter)
-	{
-		clearLEDFilterState();
-		filterFlags |= FILTER_LED_ENABLED;
-	}
-	else
-	{
-		filterFlags &= ~FILTER_LED_ENABLED;
-	}
+	ledFilterEnabled = editor.useLEDFilter;
 
 	if (doLockAudio && audioWasntLocked)
 		unlockAudio();
@@ -139,161 +82,53 @@
 	if (audioWasntLocked)
 		lockAudio();
 
+	clearLEDFilterState(&filterLED);
+
 	editor.useLEDFilter ^= 1;
-	if (editor.useLEDFilter)
-	{
-		clearLEDFilterState();
-		filterFlags |= FILTER_LED_ENABLED;
-	}
-	else
-	{
-		filterFlags &= ~FILTER_LED_ENABLED;
-	}
+	ledFilterEnabled = editor.useLEDFilter;
 
 	if (audioWasntLocked)
 		unlockAudio();
 }
 
-/* Imperfect "LED" filter implementation. This may be further improved in the future.
-** Based upon ideas posted by mystran @ the kvraudio.com forum.
-**
-** This filter may not function correctly used outside the fixed-cutoff context here!
-*/
-
-static double sigmoid(double x, double coefficient)
+static void calcAudioLatencyVars(int32_t audioBufferSize, int32_t audioFreq)
 {
-	/* Coefficient from:
-	**   0.0 to  inf (linear)
-	**  -1.0 to -inf (linear)
-	*/
-	return x / (x + coefficient) * (coefficient + 1.0);
-}
+	double dInt, dFrac;
 
-static void calcLEDFilterCoeffs(const double sr, const double hz, const double fb, ledFilter_t *filter)
-{
-	/* tan() may produce NaN or other bad results in some cases!
-	** It appears to work correctly with these specific coefficients.
-	*/
-	const double c = (hz < (sr / 2.0)) ? tan((M_PI * hz) / sr) : 1.0;
-	const double g = 1.0 / (1.0 + c);
+	if (audioFreq == 0)
+		return;
 
-	// dirty compensation
-	const double s = 0.5;
-	const double t = 0.5;
-	const double ic = c > t ? 1.0 / ((1.0 - s*t) + s*c) : 1.0;
-	const double cg = c * g;
-	const double fbg = 1.0 / (1.0 + fb * cg*cg);
+	const double dAudioLatencySecs = audioBufferSize / (double)audioFreq;
 
-	filter->c = c;
-	filter->ci = g;
-	filter->feedback = 2.0 * sigmoid(fb, 0.5);
-	filter->bg = fbg * filter->feedback * ic;
-	filter->cg = cg;
-	filter->c2 = c * 2.0;
-}
+	dFrac = modf(dAudioLatencySecs * editor.dPerfFreq, &dInt);
 
-static inline void LEDFilter(ledFilter_t *f, const double *in, double *out)
-{
-	const double in_1 = DENORMAL_OFFSET;
-	const double in_2 = DENORMAL_OFFSET;
+	// integer part
+	audLatencyPerfValInt = (int32_t)dInt;
 
-	const double c = f->c;
-	const double g = f->ci;
-	const double cg = f->cg;
-	const double bg = f->bg;
-	const double c2 = f->c2;
-
-	double *v = f->buffer;
-
-	// left channel
-	const double estimate_L = in_2 + g*(v[1] + c*(in_1 + g*(v[0] + c*in[0])));
-	const double y0_L = v[0]*g + in[0]*cg + in_1 + estimate_L * bg;
-	const double y1_L = v[1]*g + y0_L*cg + in_2;
-
-	v[0] += c2 * (in[0] - y0_L);
-	v[1] += c2 * (y0_L - y1_L);
-	out[0] = y1_L;
-
-	// right channel
-	const double estimate_R = in_2 + g*(v[3] + c*(in_1 + g*(v[2] + c*in[1])));
-	const double y0_R = v[2]*g + in[1]*cg + in_1 + estimate_R * bg;
-	const double y1_R = v[3]*g + y0_R*cg + in_2;
-
-	v[2] += c2 * (in[1] - y0_R);
-	v[3] += c2 * (y0_R - y1_R);
-	out[1] = y1_R;
+	// fractional part (scaled to 0..2^32-1)
+	dFrac *= UINT32_MAX+1.0;
+	audLatencyPerfValFrac = (uint32_t)dFrac;
 }
 
-void calcRCFilterCoeffs(double dSr, double dHz, rcFilter_t *f)
+void setSyncTickTimeLen(uint32_t timeLen, uint32_t timeLenFrac)
 {
-	const double c = (dHz < (dSr / 2.0)) ? tan((M_PI * dHz) / dSr) : 1.0;
-	f->c = c;
-	f->c2 = f->c * 2.0;
-	f->g = 1.0 / (1.0 + f->c);
-	f->cg = f->c * f->g;
+	tickTimeLen = timeLen;
+	tickTimeLenFrac = timeLenFrac;
 }
 
-void clearRCFilterState(rcFilter_t *f)
+static void generateBpmTables(void)
 {
-	f->buffer[0] = 0.0; // left channel
-	f->buffer[1] = 0.0; // right channel
-}
+	for (int32_t i = 32; i <= 255; i++)
+	{
+		const double dBpmHz = i / 2.5;
 
-// aciddose: input 0 is resistor side of capacitor (low-pass), input 1 is reference side (high-pass)
-static inline double getLowpassOutput(rcFilter_t *f, const double input_0, const double input_1, const double buffer)
-{
-	double dOutput = DENORMAL_OFFSET;
-
-	dOutput += buffer * f->g + input_0 * f->cg + input_1 * (1.0 - f->cg);
-
-	return dOutput;
+		audio.bpmTab[i-32] = audio.outputRate / dBpmHz;
+		audio.bpmTab28kHz[i-32] = PAT2SMP_HI_FREQ / dBpmHz; // PAT2SMP hi quality
+		audio.bpmTab22kHz[i-32] = PAT2SMP_LO_FREQ / dBpmHz; // PAT2SMP low quality
+		audio.bpmTabMod2Wav[i-32] = MOD2WAV_FREQ / dBpmHz; // MOD2WAV
+	}
 }
 
-void RCLowPassFilter(rcFilter_t *f, const double *in, double *out)
-{
-	double output;
-
-	// left channel RC low-pass
-	output = getLowpassOutput(f, in[0], 0.0, f->buffer[0]);
-	f->buffer[0] += (in[0] - output) * f->c2;
-	out[0] = output;
-
-	// right channel RC low-pass
-	output = getLowpassOutput(f, in[1], 0.0, f->buffer[1]);
-	f->buffer[1] += (in[1] - output) * f->c2;
-	out[1] = output;
-}
-
-void RCHighPassFilter(rcFilter_t *f, const double *in, double *out)
-{
-	double low[2];
-
-	RCLowPassFilter(f, in, low);
-
-	out[0] = in[0] - low[0]; // left channel high-pass
-	out[1] = in[1] - low[1]; // right channel high-pass
-}
-
-/* These two are used for the filters in the SAMPLER screen, and
-** also the 2x downsampling when loading samples whose frequency
-** is above 22kHz.
-*/
-
-void RCLowPassFilterMono(rcFilter_t *f, const double in, double *out)
-{
-	double output = getLowpassOutput(f, in, 0.0, f->buffer[0]);
-	f->buffer[0] += (in - output) * f->c2;
-	*out = output;
-}
-
-void RCHighPassFilterMono(rcFilter_t *f, const double in, double *out)
-{
-	double low;
-
-	RCLowPassFilterMono(f, in, &low);
-	*out = in - low; // high-pass
-}
-
 void lockAudio(void)
 {
 	if (dev != 0)
@@ -331,36 +166,6 @@
 	}
 }
 
-/* aciddose: these sin/cos approximations both use a 0..1
-** parameter range and have 'normalized' (1/2 = 0db) coeffs
-**
-** the coeffs are for LERP(x, x * x, 0.224) * sqrt(2)
-** max_error is minimized with 0.224 = 0.0013012886
-*/
-static double sinApx(double x)
-{
-	x = x * (2.0 - x);
-	return x * 1.09742972 + x * x * 0.31678383;
-}
-
-static double cosApx(double x)
-{
-	x = (1.0 - x) * (1.0 + x);
-	return x * 1.09742972 + x * x * 0.31678383;
-}
-
-static void mixerSetVoicePan(uint8_t ch, uint16_t pan) // pan = 0..256
-{
-	/* aciddose: proper 'normalized' equal-power panning is (assuming pan left to right):
-	** L = cos(p * pi * 1/2) * sqrt(2);
-	** R = sin(p * pi * 1/2) * sqrt(2);
-	*/
-	const double dPan = pan * (1.0 / 256.0); // 0.0..1.0
-
-	paula[ch].dPanL = cosApx(dPan);
-	paula[ch].dPanR = sinApx(dPan);
-}
-
 void mixerKillVoice(int32_t ch)
 {
 	const bool audioWasntLocked = !audio.locked;
@@ -397,8 +202,9 @@
 
 	clearRCFilterState(&filterLoA500);
 	clearRCFilterState(&filterLoA1200);
-	clearRCFilterState(&filterHi);
-	clearLEDFilterState();
+	clearRCFilterState(&filterHiA500);
+	clearRCFilterState(&filterHiA1200);
+	clearLEDFilterState(&filterLED);
 
 	resetAudioDithering();
 
@@ -572,7 +378,7 @@
 	}
 }
 
-void toggleA500Filters(void)
+void toggleFilterModel(void)
 {
 	const bool audioWasntLocked = !audio.locked;
 	if (audioWasntLocked)
@@ -580,19 +386,15 @@
 
 	clearRCFilterState(&filterLoA500);
 	clearRCFilterState(&filterLoA1200);
-	clearRCFilterState(&filterHi);
-	clearLEDFilterState();
+	clearRCFilterState(&filterHiA500);
+	clearRCFilterState(&filterHiA1200);
+	clearLEDFilterState(&filterLED);
 
-	if (filterFlags & FILTER_A500)
-	{
-		filterFlags &= ~FILTER_A500;
-		displayMsg("LP FILTER: A1200");
-	}
+	filterModel ^= 1;
+	if (filterModel == FILTERMODEL_A500)
+		displayMsg("AUDIO: AMIGA 500");
 	else
-	{
-		filterFlags |= FILTER_A500;
-		displayMsg("LP FILTER: A500");
-	}
+		displayMsg("AUDIO: AMIGA 1200");
 
 	if (audioWasntLocked)
 		unlockAudio();
@@ -669,77 +471,6 @@
 	}
 }
 
-void mixChannelsMultiStep(int32_t numSamples) // for PAT2SMP
-{
-	double dSmp, dVol;
-	blep_t *bSmp, *bVol;
-	paulaVoice_t *v;
-
-	memset(dMixBufferL, 0, numSamples * sizeof (double));
-	memset(dMixBufferR, 0, numSamples * sizeof (double));
-
-	v = paula;
-	bSmp = blep;
-	bVol = blepVol;
-
-	for (int32_t i = 0; i < AMIGA_VOICES; i++, v++, bSmp++, bVol++)
-	{
-		if (!v->active || v->data == NULL)
-			continue;
-
-		for (int32_t j = 0; j < numSamples; j++)
-		{
-			assert(v->data != NULL);
-			dSmp = v->data[v->pos] * (1.0 / 128.0);
-			dVol = v->dVolume;
-
-			if (dSmp != bSmp->dLastValue)
-			{
-				if (v->dLastDelta > v->dLastPhase)
-				{
-					// div->mul trick: v->dLastDeltaMul is 1.0 / v->dLastDelta
-					blepAdd(bSmp, v->dLastPhase * v->dLastDeltaMul, bSmp->dLastValue - dSmp);
-				}
-
-				bSmp->dLastValue = dSmp;
-			}
-
-			if (dVol != bVol->dLastValue)
-			{
-				blepVolAdd(bVol, bVol->dLastValue - dVol);
-				bVol->dLastValue = dVol;
-			}
-
-			if (bSmp->samplesLeft > 0) dSmp = blepRun(bSmp, dSmp);
-			if (bVol->samplesLeft > 0) dVol = blepRun(bVol, dVol);
-
-			dSmp *= dVol;
-
-			dMixBufferL[j] += dSmp * v->dPanL;
-			dMixBufferR[j] += dSmp * v->dPanR;
-
-			v->dPhase += v->dDelta;
-			while (v->dPhase >= 1.0) // deltas can be >= 1.0 here
-			{
-				v->dPhase -= 1.0;
-
-				v->dLastPhase = v->dPhase;
-				v->dLastDelta = v->dDelta;
-				v->dLastDeltaMul = v->dDeltaMul;
-
-				if (++v->pos >= v->length)
-				{
-					v->pos = 0;
-
-					// re-fetch new Paula register values now
-					v->length = v->newLength;
-					v->data = v->newData;
-				}
-			}
-		}
-	}
-}
-
 void resetAudioDithering(void)
 {
 	randSeed = INITIAL_DITHER_SEED;
@@ -755,7 +486,7 @@
 	return randSeed;
 }
 
-static inline void processMixedSamplesA1200(int32_t i, int16_t *out)
+static inline void processMixedSamples(int32_t i, int16_t *out)
 {
 	int32_t smp32;
 	double dOut[2], dPrng;
@@ -763,57 +494,33 @@
 	dOut[0] = dMixBufferL[i];
 	dOut[1] = dMixBufferR[i];
 
-	if (audio.outputRate >= 96000) // cutoff is too high for 44.1kHz/48kHz
+	if (filterModel == FILTERMODEL_A500)
 	{
-		// process low-pass filter
-		RCLowPassFilter(&filterLoA1200, dOut, dOut);
-	}
+		// A500 low-pass RC filter
+		RCLowPassFilterStereo(&filterLoA500, dOut, dOut);
 
-	// process high-pass filter
-	RCHighPassFilter(&filterHi, dOut, dOut);
+		// "LED" Sallen-Key filter
+		if (ledFilterEnabled)
+			LEDFilter(&filterLED, dOut, dOut);
 
-	// normalize and flip phase (A500/A1200 has an inverted audio signal)
-	dOut[0] *= -INT16_MAX / (double)AMIGA_VOICES;
-	dOut[1] *= -INT16_MAX / (double)AMIGA_VOICES;
+		// A500 high-pass RC filter
+		RCHighPassFilterStereo(&filterHiA500, dOut, dOut);
+	}
+	else
+	{
+		// A1200 low-pass RC filter
+		if (audio.outputRate >= 96000) // cutoff is too high for 44.1kHz/48kHz
+			RCLowPassFilterStereo(&filterLoA1200, dOut, dOut);
 
-	// left channel - 1-bit triangular dithering (high-pass filtered)
-	dPrng = random32() * (0.5 / INT32_MAX); // -0.5..0.5
-	dOut[0] = (dOut[0] + dPrng) - dPrngStateL;
-	dPrngStateL = dPrng;
-	smp32 = (int32_t)dOut[0];
-	CLAMP16(smp32);
-	out[0] = (int16_t)smp32;
+		// "LED" Sallen-Key filter
+		if (ledFilterEnabled)
+			LEDFilter(&filterLED, dOut, dOut);
 
-	// right channel - 1-bit triangular dithering (high-pass filtered)
-	dPrng = random32() * (0.5 / INT32_MAX); // -0.5..0.5
-	dOut[1] = (dOut[1] + dPrng) - dPrngStateR;
-	dPrngStateR = dPrng;
-	smp32 = (int32_t)dOut[1];
-	CLAMP16(smp32);
-	out[1] = (int16_t)smp32;
-}
-
-static inline void processMixedSamplesA1200LED(int32_t i, int16_t *out)
-{
-	int32_t smp32;
-	double dOut[2], dPrng;
-
-	dOut[0] = dMixBufferL[i];
-	dOut[1] = dMixBufferR[i];
-
-	if (audio.outputRate >= 96000) // cutoff is too high for 44.1kHz/48kHz
-	{
-		// process low-pass filter
-		RCLowPassFilter(&filterLoA1200, dOut, dOut);
+		// A1200 high-pass RC filter
+		RCHighPassFilterStereo(&filterHiA1200, dOut, dOut);
 	}
 
-	// process "LED" filter
-	LEDFilter(&filterLED, dOut, dOut);
-
-	// process high-pass filter
-	RCHighPassFilter(&filterHi, dOut, dOut);
-
-	// normalize and flip phase (A500/A1200 has an inverted audio signal)
+	// normalize and flip phase (A500/A1200 has a phase-inverted audio signal)
 	dOut[0] *= -INT16_MAX / (double)AMIGA_VOICES;
 	dOut[1] *= -INT16_MAX / (double)AMIGA_VOICES;
 
@@ -834,99 +541,9 @@
 	out[1] = (int16_t)smp32;
 }
 
-static inline void processMixedSamplesA500(int32_t i, int16_t *out)
-{
-	int32_t smp32;
-	double dOut[2], dPrng;
-
-	dOut[0] = dMixBufferL[i];
-	dOut[1] = dMixBufferR[i];
-
-	// process low-pass filter
-	RCLowPassFilter(&filterLoA500, dOut, dOut);
-
-	// process high-pass filter
-	RCHighPassFilter(&filterHi, dOut, dOut);
-
-	dOut[0] *= -INT16_MAX / (double)AMIGA_VOICES;
-	dOut[1] *= -INT16_MAX / (double)AMIGA_VOICES;
-
-	// left channel - 1-bit triangular dithering (high-pass filtered)
-	dPrng = random32() * (0.5 / INT32_MAX); // -0.5..0.5
-	dOut[0] = (dOut[0] + dPrng) - dPrngStateL;
-	dPrngStateL = dPrng;
-	smp32 = (int32_t)dOut[0];
-	CLAMP16(smp32);
-	out[0] = (int16_t)smp32;
-
-	// right channel - 1-bit triangular dithering (high-pass filtered)
-	dPrng = random32() * (0.5 / INT32_MAX); // -0.5..0.5
-	dOut[1] = (dOut[1] + dPrng) - dPrngStateR;
-	dPrngStateR = dPrng;
-	smp32 = (int32_t)dOut[1];
-	CLAMP16(smp32);
-	out[1] = (int16_t)smp32;
-}
-
-static inline void processMixedSamplesA500LED(int32_t i, int16_t *out)
-{
-	int32_t smp32;
-	double dOut[2], dPrng;
-
-	dOut[0] = dMixBufferL[i];
-	dOut[1] = dMixBufferR[i];
-
-	// process low-pass filter
-	RCLowPassFilter(&filterLoA500, dOut, dOut);
-
-	// process "LED" filter
-	LEDFilter(&filterLED, dOut, dOut);
-
-	// process high-pass filter
-	RCHighPassFilter(&filterHi, dOut, dOut);
-
-	dOut[0] *= -INT16_MAX / (double)AMIGA_VOICES;
-	dOut[1] *= -INT16_MAX / (double)AMIGA_VOICES;
-
-	// left channel - 1-bit triangular dithering (high-pass filtered)
-	dPrng = random32() * (0.5 / INT32_MAX); // -0.5..0.5
-	dOut[0] = (dOut[0] + dPrng) - dPrngStateL;
-	dPrngStateL = dPrng;
-	smp32 = (int32_t)dOut[0];
-	CLAMP16(smp32);
-	out[0] = (int16_t)smp32;
-
-	// right channel - 1-bit triangular dithering (high-pass filtered)
-	dPrng = random32() * (0.5 / INT32_MAX); // -0.5..0.5
-	dOut[1] = (dOut[1] + dPrng) - dPrngStateR;
-	dPrngStateR = dPrng;
-	smp32 = (int32_t)dOut[1];
-	CLAMP16(smp32);
-	out[1] = (int16_t)smp32;
-}
-
-static inline void processMixedSamplesRaw(int32_t i, int16_t *out) // for PAT2SMP
-{
-	int32_t smp32;
-	double dOut[2];
-
-	dOut[0] = dMixBufferL[i];
-	dOut[1] = dMixBufferR[i];
-
-	// normalize (don't flip the phase this time)
-	dOut[0] *= INT16_MAX / (double)AMIGA_VOICES;
-	dOut[1] *= INT16_MAX / (double)AMIGA_VOICES;
-
-	dOut[0] = (dOut[0] + dOut[1]) * 0.5; // mix to mono
-
-	smp32 = (int32_t)dOut[0];
-	CLAMP16(smp32);
-	*out = (int16_t)smp32;
-}
-
 void outputAudio(int16_t *target, int32_t numSamples)
 {
-	int16_t *outStream, out[2];
+	int16_t out[2];
 	int32_t i;
 
 	if (editor.isSMPRendering)
@@ -934,20 +551,17 @@
 		// render to sample (PAT2SMP)
 
 		int32_t samplesTodo = numSamples;
-		if (editor.pat2SmpPos+samplesTodo > MAX_SAMPLE_LEN)
-			samplesTodo = MAX_SAMPLE_LEN-editor.pat2SmpPos;
+		if (editor.pat2SmpPos+samplesTodo > MAX_SAMPLE_LEN*2)
+			samplesTodo = (MAX_SAMPLE_LEN*2)-editor.pat2SmpPos;
 
-		mixChannelsMultiStep(samplesTodo);
+		mixChannels(samplesTodo);
 
-		outStream = &editor.pat2SmpBuf[editor.pat2SmpPos];
+		double *dOutStream = &editor.dPat2SmpBuf[editor.pat2SmpPos];
 		for (i = 0; i < samplesTodo; i++)
-		{
-			processMixedSamplesRaw(i, out);
-			outStream[i] = out[0];
-		}
+			dOutStream[i] = dMixBufferL[i] + dMixBufferR[i]; // normalized to -128..127 later
 
 		editor.pat2SmpPos += samplesTodo;
-		if (editor.pat2SmpPos >= MAX_SAMPLE_LEN)
+		if (editor.pat2SmpPos >= MAX_SAMPLE_LEN*2)
 		{
 			editor.smpRenderingDone = true;
 			updateWindowTitle(MOD_IS_MODIFIED);
@@ -959,53 +573,13 @@
 
 		mixChannels(numSamples);
 
-		outStream = target;
-		if (filterFlags & FILTER_A500)
+		int16_t *outStream = target;
+		for (i = 0; i < numSamples; i++)
 		{
-			// Amiga 500 filter model
-
-			if (filterFlags & FILTER_LED_ENABLED)
-			{
-				for (i = 0; i < numSamples; i++)
-				{
-					processMixedSamplesA500LED(i, out);
-					*outStream++ = out[0];
-					*outStream++ = out[1];
-				}
-			}
-			else
-			{
-				for (i = 0; i < numSamples; i++)
-				{
-					processMixedSamplesA500(i, out);
-					*outStream++ = out[0];
-					*outStream++ = out[1];
-				}
-			}
+			processMixedSamples(i, out);
+			*outStream++ = out[0];
+			*outStream++ = out[1];
 		}
-		else
-		{
-			// Amiga 1200 filter model
-
-			if (filterFlags & FILTER_LED_ENABLED)
-			{
-				for (i = 0; i < numSamples; i++)
-				{
-					processMixedSamplesA1200LED(i, out);
-					*outStream++ = out[0];
-					*outStream++ = out[1];
-				}
-			}
-			else
-			{
-				for (i = 0; i < numSamples; i++)
-				{
-					processMixedSamplesA1200(i, out);
-					*outStream++ = out[0];
-					*outStream++ = out[1];
-				}
-			}
-		}
 	}
 }
 
@@ -1147,61 +721,73 @@
 	** - RC 6dB/oct low-pass: R=680 ohm, C=6800pF (f=34419.321Hz)
 	** - Sallen-key low-pass ("LED"): Same as A500 (f=3090.532Hz)
 	** - RC 6dB/oct high-pass: R=1390 ohm (1000+390), C=22uF (f=5.204Hz)
-	**
-	** Correct values for A600 (a600_schematics.pdf):
-	** - RC 6dB/oct low-pass: Same as A500 (f=4420.970Hz)
-	** - Sallen-key low-pass ("LED"): Same as A500 (f=3090.532Hz)
-	** - RC 6dB/oct high-pass: Same as A1200 (f=5.204Hz)
 	*/
 
 	double R, C, R1, R2, C1, C2, fc, fb;
+	const double pi = 4.0 * atan(1.0); // M_PI can not be trusted
 
+	// A500 1-pole (6db/oct) static RC low-pass filter:
+	R = 360.0; // R321 (360 ohm resistor)
+	C = 1e-7;  // C321 (0.1uF capacitor)
+	fc = 1.0 / (2.0 * pi * R * C);
+	calcRCFilterCoeffs(audio.outputRate, fc, &filterLoA500);
+
+	// A1200 1-pole (6db/oct) static RC low-pass filter:
 	if (audio.outputRate >= 96000) // cutoff is too high for 44.1kHz/48kHz
 	{
-		// A1200 1-pole (6db/oct) static RC low-pass filter:
 		R = 680.0;  // R321 (680 ohm resistor)
 		C = 6.8e-9; // C321 (6800pf capacitor)
-		fc = 1.0 / (2.0 * M_PI * R * C);
+		fc = 1.0 / (2.0 * pi * R * C);
 		calcRCFilterCoeffs(audio.outputRate, fc, &filterLoA1200);
 	}
 
-	// A500 1-pole (6db/oct) static RC low-pass filter:
-	R = 360.0; // R321 (360 ohm resistor)
-	C = 1e-7;  // C321 (0.1uF capacitor)
-	fc = 1.0 / (2.0 * M_PI * R * C);
-	calcRCFilterCoeffs(audio.outputRate, fc, &filterLoA500);
-
-	// Sallen-Key filter ("LED"):
+	// Sallen-Key filter ("LED" filter, same RC values on A500 and A1200):
 	R1 = 10000.0; // R322 (10K ohm resistor)
 	R2 = 10000.0; // R323 (10K ohm resistor)
 	C1 = 6.8e-9;  // C322 (6800pF capacitor)
 	C2 = 3.9e-9;  // C323 (3900pF capacitor)
-	fc = 1.0 / (2.0 * M_PI * sqrt(R1 * R2 * C1 * C2));
+	fc = 1.0 / (2.0 * pi * sqrt(R1 * R2 * C1 * C2));
 	fb = 0.125; // Fb = 0.125 : Q ~= 1/sqrt(2)
 	calcLEDFilterCoeffs(audio.outputRate, fc, fb, &filterLED);
 
+	// A500 1-pole (6db/oct) static RC high-pass filter:
+	R = 1390.0; // R324 (1K ohm resistor) + R325 (390 ohm resistor)
+	C = 2.233e-5; // C334 (22uF capacitor) + C335 (0.33�F capacitor)
+	fc = 1.0 / (2.0 * pi * R * C);
+	calcRCFilterCoeffs(audio.outputRate, fc, &filterHiA500);
+
 	// A1200 1-pole (6db/oct) static RC high-pass filter:
 	R = 1390.0; // R324 (1K ohm resistor) + R325 (390 ohm resistor)
 	C = 2.2e-5; // C334 (22uF capacitor)
-	fc = 1.0 / (2.0 * M_PI * R * C);
-	calcRCFilterCoeffs(audio.outputRate, fc, &filterHi);
+	fc = 1.0 / (2.0 * pi * R * C);
+	calcRCFilterCoeffs(audio.outputRate, fc, &filterHiA1200);
 }
 
-void mixerCalcVoicePans(uint8_t stereoSeparation)
+static void setVoicePan(int32_t ch, double pan) // pan = 0.0 .. 1.0
 {
-	const uint8_t scaledPanPos = (stereoSeparation * 128) / 100;
+	// constant power panning
 
-	ch1Pan = 128 - scaledPanPos;
-	ch2Pan = 128 + scaledPanPos;
-	ch3Pan = 128 + scaledPanPos;
-	ch4Pan = 128 - scaledPanPos;
+	const double pi = 4.0 * atan(1.0); // M_PI can not be trusted
 
-	mixerSetVoicePan(0, ch1Pan);
-	mixerSetVoicePan(1, ch2Pan);
-	mixerSetVoicePan(2, ch3Pan);
-	mixerSetVoicePan(3, ch4Pan);
+	paula[ch].dPanL = cos(pan * pi * 0.5) * sqrt(2.0);
+	paula[ch].dPanR = sin(pan * pi * 0.5) * sqrt(2.0);
 }
 
+void mixerCalcVoicePans(uint8_t stereoSeparation) // 0..100 (percentage)
+{
+	assert(stereoSeparation <= 100);
+
+	const double panMid = 0.5;
+
+	const double panR = panMid + (stereoSeparation / (100.0 * 2.0));
+	const double panL = 1.0 - panR;
+
+	setVoicePan(0, panL);
+	setVoicePan(1, panR);
+	setVoicePan(2, panR);
+	setVoicePan(3, panL);
+}
+
 bool setupAudio(void)
 {
 	SDL_AudioSpec want, have;
@@ -1238,16 +824,12 @@
 
 	generateBpmTables();
 
-	/* If the audio output rate is lower than MOD2WAV_FREQ, we need
-	** to allocate slightly more space so that MOD2WAV rendering
-	** won't overflow.
-	*/
+	const int32_t lowestBPM = 32;
+	const int32_t pat2SmpMaxSamples = (int32_t)ceil(audio.bpmTab22kHz[lowestBPM-32]);
+	const int32_t mod2WavMaxSamples = (int32_t)ceil(audio.bpmTabMod2Wav[lowestBPM-32]);
+	const int32_t renderMaxSamples = (int32_t)ceil(audio.bpmTab[lowestBPM-32]);
 
-	int32_t maxSamplesToMix;
-	if (MOD2WAV_FREQ > audio.outputRate)
-		maxSamplesToMix = (int32_t)ceil(audio.bpmTabMod2Wav[32-32]); // BPM 32
-	else
-		maxSamplesToMix = (int32_t)ceil(audio.bpmTab[32-32]); // BPM 32
+	const int32_t maxSamplesToMix = MAX(pat2SmpMaxSamples, MAX(mod2WavMaxSamples, renderMaxSamples));
 
 	dMixBufferLUnaligned = (double *)MALLOC_PAD(maxSamplesToMix * sizeof (double), 256);
 	dMixBufferRUnaligned = (double *)MALLOC_PAD(maxSamplesToMix * sizeof (double), 256);
@@ -1264,7 +846,8 @@
 	mixerCalcVoicePans(config.stereoSeparation);
 	defStereoSep = config.stereoSeparation;
 
-	filterFlags = config.a500LowPassFilter ? FILTER_A500 : 0;
+	filterModel = config.filterModel;
+	ledFilterEnabled = false;
 	calculateFilterCoeffs();
 
 	audio.dSamplesPerTick = audio.bpmTab[125-32]; // BPM 125
@@ -1335,97 +918,104 @@
 		unlockAudio();
 }
 
-void normalize32bitSigned(int32_t *sampleData, uint32_t sampleLength)
+uint16_t get16BitPeak(int16_t *sampleData, uint32_t sampleLength)
 {
-	int32_t sample, sampleVolPeak;
-	uint32_t i;
-	double dGain;
-
-	sampleVolPeak = 0;
-	for (i = 0; i < sampleLength; i++)
+	uint16_t samplePeak = 0;
+	for (uint32_t i = 0; i < sampleLength; i++)
 	{
-		sample = ABS(sampleData[i]);
-		if (sampleVolPeak < sample)
-			sampleVolPeak = sample;
+		uint16_t sample = ABS(sampleData[i]);
+		if (samplePeak < sample)
+			samplePeak = sample;
 	}
 
-	if (sampleVolPeak >= INT32_MAX)
-		return; // sample is already normalized
+	return samplePeak;
+}
 
-	// prevent division by zero!
-	if (sampleVolPeak <= 0)
-		sampleVolPeak = 1;
-
-	dGain = (double)INT32_MAX / sampleVolPeak;
-	for (i = 0; i < sampleLength; i++)
+uint32_t get32BitPeak(int32_t *sampleData, uint32_t sampleLength)
+{
+	uint32_t samplePeak = 0;
+	for (uint32_t i = 0; i < sampleLength; i++)
 	{
-		sample = (int32_t)(sampleData[i] * dGain);
-		sampleData[i] = (int32_t)sample;
+		uint32_t sample = ABS(sampleData[i]);
+		if (samplePeak < sample)
+			samplePeak = sample;
 	}
+
+	return samplePeak;
 }
 
-void normalize16bitSigned(int16_t *sampleData, uint32_t sampleLength)
+float getFloatPeak(float *fSampleData, uint32_t sampleLength)
 {
-	uint32_t i;
-	int32_t sample, sampleVolPeak, gain;
+	float fSamplePeak = 0.0f;
+	for (uint32_t i = 0; i < sampleLength; i++)
+	{
+		const float fSample = fabsf(fSampleData[i]);
+		if (fSamplePeak < fSample)
+			fSamplePeak = fSample;
+	}
 
-	sampleVolPeak = 0;
-	for (i = 0; i < sampleLength; i++)
+	return fSamplePeak;
+}
+
+double getDoublePeak(double *dSampleData, uint32_t sampleLength)
+{
+	double dSamplePeak = 0.0;
+	for (uint32_t i = 0; i < sampleLength; i++)
 	{
-		sample = ABS(sampleData[i]);
-		if (sampleVolPeak < sample)
-			sampleVolPeak = sample;
+		const double dSample = fabs(dSampleData[i]);
+		if (dSamplePeak < dSample)
+			dSamplePeak = dSample;
 	}
 
-	if (sampleVolPeak >= INT16_MAX)
-		return; // sample is already normalized
+	return dSamplePeak;
+}
 
-	if (sampleVolPeak < 1)
+void normalize16BitTo8Bit(int16_t *sampleData, uint32_t sampleLength)
+{
+	const uint16_t samplePeak = get16BitPeak(sampleData, sampleLength);
+	if (samplePeak == 0 || samplePeak >= INT16_MAX)
 		return;
 
-	gain = (INT16_MAX * 65536) / sampleVolPeak;
-	for (i = 0; i < sampleLength; i++)
-		sampleData[i] = (int16_t)((sampleData[i] * gain) >> 16);
+	const double dGain = (double)INT16_MAX / samplePeak;
+	for (uint32_t i = 0; i < sampleLength; i++)
+	{
+		const int32_t sample = (const int32_t)(sampleData[i] * dGain);
+		sampleData[i] = (int16_t)sample;
+	}
 }
 
-void normalize8bitFloatSigned(float *fSampleData, uint32_t sampleLength)
+void normalize32BitTo8Bit(int32_t *sampleData, uint32_t sampleLength)
 {
-	uint32_t i;
-	float fSample, fSampleVolPeak, fGain;
+	const uint32_t samplePeak = get32BitPeak(sampleData, sampleLength);
+	if (samplePeak == 0 || samplePeak >= INT32_MAX)
+		return;
 
-	fSampleVolPeak = 0.0f;
-	for (i = 0; i < sampleLength; i++)
+	const double dGain = (double)INT32_MAX / samplePeak;
+	for (uint32_t i = 0; i < sampleLength; i++)
 	{
-		fSample = fabsf(fSampleData[i]);
-		if (fSampleVolPeak < fSample)
-			fSampleVolPeak = fSample;
+		const int32_t sample = (const int32_t)(sampleData[i] * dGain);
+		sampleData[i] = (int32_t)sample;
 	}
+}
 
-	if (fSampleVolPeak <= 0.0f)
+void normalizeFloatTo8Bit(float *fSampleData, uint32_t sampleLength)
+{
+	const float fSamplePeak = getFloatPeak(fSampleData, sampleLength);
+	if (fSamplePeak <= 0.0f)
 		return;
 
-	fGain = INT8_MAX / fSampleVolPeak;
-	for (i = 0; i < sampleLength; i++)
+	const float fGain = INT8_MAX / fSamplePeak;
+	for (uint32_t i = 0; i < sampleLength; i++)
 		fSampleData[i] *= fGain;
 }
 
-void normalize8bitDoubleSigned(double *dSampleData, uint32_t sampleLength)
+void normalizeDoubleTo8Bit(double *dSampleData, uint32_t sampleLength)
 {
-	uint32_t i;
-	double dSample, dSampleVolPeak, dGain;
-
-	dSampleVolPeak = 0.0;
-	for (i = 0; i < sampleLength; i++)
-	{
-		dSample = fabs(dSampleData[i]);
-		if (dSampleVolPeak < dSample)
-			dSampleVolPeak = dSample;
-	}
-
-	if (dSampleVolPeak <= 0.0)
+	const double dSamplePeak = getDoublePeak(dSampleData, sampleLength);
+	if (dSamplePeak <= 0.0)
 		return;
 
-	dGain = INT8_MAX / dSampleVolPeak;
-	for (i = 0; i < sampleLength; i++)
+	const double dGain = INT8_MAX / dSamplePeak;
+	for (uint32_t i = 0; i < sampleLength; i++)
 		dSampleData[i] *= dGain;
 }
--- a/src/pt2_audio.h
+++ b/src/pt2_audio.h
@@ -4,15 +4,6 @@
 #include <stdbool.h>
 #include "pt2_header.h" // AMIGA_VOICES
 
-// adding this prevents denormalized numbers, which is slow
-#define DENORMAL_OFFSET 1e-15
-
-typedef struct rcFilter_t
-{
-	double buffer[2];
-	double c, c2, g, cg;
-} rcFilter_t;
-
 typedef struct audio_t
 {
 	volatile bool locked, isSampling;
@@ -51,20 +42,20 @@
 void setSyncTickTimeLen(uint32_t timeLen, uint32_t timeLenFrac);
 void resetCachedMixerPeriod(void);
 void resetAudioDithering(void);
-void calcRCFilterCoeffs(const double sr, const double hz, rcFilter_t *f);
-void clearRCFilterState(rcFilter_t *f);
-void RCLowPassFilter(rcFilter_t *f, const double *in, double *out);
-void RCHighPassFilter(rcFilter_t *f, const double *in, double *out);
-void RCLowPassFilterMono(rcFilter_t *f, const double in, double *out);
-void RCHighPassFilterMono(rcFilter_t *f, const double in, double *out);
-void normalize32bitSigned(int32_t *sampleData, uint32_t sampleLength);
-void normalize16bitSigned(int16_t *sampleData, uint32_t sampleLength);
-void normalize8bitFloatSigned(float *fSampleData, uint32_t sampleLength);
-void normalize8bitDoubleSigned(double *dSampleData, uint32_t sampleLength);
+
+uint16_t get16BitPeak(int16_t *sampleData, uint32_t sampleLength);
+uint32_t get32BitPeak(int32_t *sampleData, uint32_t sampleLength);
+float getFloatPeak(float *fSampleData, uint32_t sampleLength);
+double getDoublePeak(double *dSampleData, uint32_t sampleLength);
+void normalize16BitTo8Bit(int16_t *sampleData, uint32_t sampleLength);
+void normalize32BitTo8Bit(int32_t *sampleData, uint32_t sampleLength);
+void normalizeFloatTo8Bit(float *fSampleData, uint32_t sampleLength);
+void normalizeDoubleTo8Bit(double *dSampleData, uint32_t sampleLength);
+
 void setLEDFilter(bool state, bool doLockAudio);
 void toggleLEDFilter(void);
 void toggleAmigaPanMode(void);
-void toggleA500Filters(void);
+void toggleFilterModel(void);
 void paulaStopDMA(int32_t ch);
 void paulaStartDMA(int32_t ch);
 void paulaSetPeriod(int32_t ch, uint16_t period);
--- a/src/pt2_blep.c
+++ b/src/pt2_blep.c
@@ -1,7 +1,6 @@
 // these BLEP routines were coded by aciddose
 
 #include <stdint.h>
-#include <stdbool.h>
 #include <assert.h>
 #include "pt2_blep.h"
 
--- /dev/null
+++ b/src/pt2_chordmaker.c
@@ -1,0 +1,594 @@
+// for finding memory leaks in debug mode with Visual Studio 
+#if defined _DEBUG && defined _MSC_VER
+#include <crtdbg.h>
+#endif
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include "pt2_header.h"
+#include "pt2_mouse.h"
+#include "pt2_textout.h"
+#include "pt2_visuals.h"
+#include "pt2_structs.h"
+#include "pt2_helpers.h"
+#include "pt2_tables.h"
+#include "pt2_sampler.h"
+#include "pt2_audio.h"
+#include "pt2_blep.h"
+#include "pt2_downsamplers2x.h"
+
+#define MAX_NOTES 4
+
+typedef struct sampleMixer_t
+{
+	bool active;
+	int32_t length, pos;
+	double dDelta, dPhase, dLastPhase;
+} sampleMixer_t;
+
+static void sortNotes(void)
+{
+	for (int32_t i = 0; i < 3; i++)
+	{
+		if (editor.note2 == 36)
+		{
+			editor.note2 = editor.note3;
+			editor.note3 = editor.note4;
+			editor.note4 = 36;
+		}
+	}
+
+	for (int32_t i = 0; i < 3; i++)
+	{
+		if (editor.note3 == 36)
+		{
+			editor.note3 = editor.note4;
+			editor.note4 = 36;
+		}
+	}
+}
+
+static void removeDuplicateNotes(void)
+{
+	if (editor.note4 == editor.note3) editor.note4 = 36;
+	if (editor.note4 == editor.note2) editor.note4 = 36;
+	if (editor.note3 == editor.note2) editor.note3 = 36;
+}
+
+static void setupMixVoice(sampleMixer_t *v, int32_t length, double dDelta)
+{
+	v->dDelta = dDelta;
+	v->length = (int32_t)ceil(length / dDelta);
+
+	if (v->length > 0)
+		v->active = true;
+}
+
+// this has 2x oversampling for BLEP to function properly with all pitches
+void mixChordSample(void)
+{
+	bool smpLoopFlag;
+	char smpText[22+1];
+	int8_t *smpData, sameNotes, smpVolume;
+	uint8_t smpFinetune, finetune;
+	int32_t i, smpLoopStart, smpLoopLength, smpEnd;
+	sampleMixer_t mixCh[MAX_NOTES];
+	moduleSample_t *s;
+	blep_t blep[MAX_NOTES];
+
+	if (editor.sampleZero)
+	{
+		statusNotSampleZero();
+		return;
+	}
+
+	if (editor.note1 == 36)
+	{
+		displayErrorMsg("NO BASENOTE!");
+		return;
+	}
+
+	s = &song->samples[editor.currSample];
+	if (s->length == 0)
+	{
+		statusSampleIsEmpty();
+		return;
+	}
+
+	// check if all notes are the same (illegal)
+	sameNotes = true;
+	if (editor.note2 != 36 && editor.note2 != editor.note1) sameNotes = false; else editor.note2 = 36;
+	if (editor.note3 != 36 && editor.note3 != editor.note1) sameNotes = false; else editor.note3 = 36;
+	if (editor.note4 != 36 && editor.note4 != editor.note1) sameNotes = false; else editor.note4 = 36;
+
+	if (sameNotes)
+	{
+		displayErrorMsg("ONLY ONE NOTE!");
+		return;
+	}
+
+	sortNotes();
+	removeDuplicateNotes();
+
+	ui.updateNote1Text = true;
+	ui.updateNote2Text = true;
+	ui.updateNote3Text = true;
+	ui.updateNote4Text = true;
+
+	// setup some variables
+	smpLoopStart = s->loopStart;
+	smpLoopLength = s->loopLength;
+	smpLoopFlag = (smpLoopStart + smpLoopLength) > 2;
+	smpEnd = smpLoopFlag ? (smpLoopStart + smpLoopLength) : s->length;
+	smpData = &song->sampleData[s->offset];
+
+	if (editor.newOldFlag == 0)
+	{
+		// find a free sample slot for the new sample
+
+		for (i = 0; i < MOD_SAMPLES; i++)
+		{
+			if (song->samples[i].length == 0)
+				break;
+		}
+
+		if (i == MOD_SAMPLES)
+		{
+			displayErrorMsg("NO EMPTY SAMPLE!");
+			return;
+		}
+
+		smpFinetune = s->fineTune;
+		smpVolume = s->volume;
+		memcpy(smpText, s->text, sizeof (smpText));
+
+		s = &song->samples[i];
+		s->fineTune = smpFinetune;
+		s->volume = smpVolume;
+
+		memcpy(s->text, smpText, sizeof (smpText));
+		editor.currSample = (int8_t)i;
+	}
+
+	double *dMixData = (double *)calloc(MAX_SAMPLE_LEN*2, sizeof (double));
+	if (dMixData == NULL)
+	{
+		statusOutOfMemory();
+		return;
+	}
+
+	s->length = smpLoopFlag ? MAX_SAMPLE_LEN : editor.chordLength; // if sample loops, set max length
+	s->loopLength = 2;
+	s->loopStart = 0;
+	s->text[21] = '!'; // chord sample indicator
+	s->text[22] = '\0';
+
+	memset(mixCh, 0, sizeof (mixCh));
+
+	// setup mixing lengths and deltas
+
+	finetune = s->fineTune & 0xF;
+	const double dOutputHz = ((double)PAULA_PAL_CLK / periodTable[24]) * 2.0;
+
+	const double dClk = PAULA_PAL_CLK / dOutputHz;
+	if (editor.note1 < 36) setupMixVoice(&mixCh[0], smpEnd, dClk / periodTable[(finetune * 37) + editor.note1]);
+	if (editor.note2 < 36) setupMixVoice(&mixCh[1], smpEnd, dClk / periodTable[(finetune * 37) + editor.note2]);
+	if (editor.note3 < 36) setupMixVoice(&mixCh[2], smpEnd, dClk / periodTable[(finetune * 37) + editor.note3]);
+	if (editor.note4 < 36) setupMixVoice(&mixCh[3], smpEnd, dClk / periodTable[(finetune * 37) + editor.note4]);
+
+	// start mixing
+	memset(blep, 0, sizeof (blep));
+	turnOffVoices();
+
+	sampleMixer_t *v = mixCh;
+	blep_t *bSmp = blep;
+
+	for (i = 0; i < MAX_NOTES; i++, v++, bSmp++)
+	{
+		if (!v->active || v->dDelta == 0.0)
+			continue;
+
+		for (int32_t j = 0; j < MAX_SAMPLE_LEN*2; j++)
+		{
+			double dSmp = smpData[v->pos] * (1.0 / 128.0);
+
+			if (dSmp != bSmp->dLastValue)
+			{
+				if (v->dDelta > v->dLastPhase)
+					blepAdd(bSmp, v->dLastPhase / v->dDelta, bSmp->dLastValue - dSmp);
+
+				bSmp->dLastValue = dSmp;
+			}
+
+			if (bSmp->samplesLeft > 0) dSmp = blepRun(bSmp, dSmp);
+
+			dMixData[j] += dSmp;
+
+			v->dPhase += v->dDelta;
+			if (v->dPhase >= 1.0)
+			{
+				v->dPhase -= 1.0;
+				v->dLastPhase = v->dPhase;
+
+				if (++v->pos >= smpEnd)
+				{
+					if (smpLoopFlag)
+					{
+						do
+						{
+							v->pos -= smpLoopLength;
+						}
+						while (v->pos >= smpEnd);
+					}
+					else break; // we should insert an ending blep here, but I lost that code ages ago...
+				}
+			}
+		}
+	}
+
+	// downsample oversampled buffer, normalize and quantize to 8-bit
+
+	downsample2xDouble(dMixData, s->length * 2);
+
+	double dAmp = 1.0;
+	const double dPeak = getDoublePeak(dMixData, s->length);
+	if (dPeak > 0.0)
+		dAmp = INT8_MAX / dPeak;
+
+	int8_t *smpPtr = &song->sampleData[s->offset];
+	for (i = 0; i < s->length; i++)
+	{
+		const int32_t smp = (const int32_t)round(dMixData[i] * dAmp);
+		assert(smp >= -128 && smp <= 127); // shouldn't happen according to dAmp (but just in case)
+		smpPtr[i] = (int8_t)smp;
+	}
+
+	free(dMixData);
+
+	// clear unused sample data (if sample is not full already)
+	if (s->length < MAX_SAMPLE_LEN)
+		memset(&song->sampleData[s->offset + s->length], 0, MAX_SAMPLE_LEN - s->length);
+
+	// we're done
+
+	editor.samplePos = 0;
+	fixSampleBeep(s);
+	updateCurrSample();
+
+	updateWindowTitle(MOD_IS_MODIFIED);
+}
+
+static void backupChord(void)
+{
+	editor.oldNote1 = editor.note1;
+	editor.oldNote2 = editor.note2;
+	editor.oldNote3 = editor.note3;
+	editor.oldNote4 = editor.note4;
+}
+
+void recalcChordLength(void)
+{
+	int8_t note;
+	int32_t len;
+	moduleSample_t *s;
+
+	s = &song->samples[editor.currSample];
+
+	if (editor.chordLengthMin)
+	{
+		note = MAX(MAX((editor.note1 == 36) ? -1 : editor.note1,
+		               (editor.note2 == 36) ? -1 : editor.note2),
+		           MAX((editor.note3 == 36) ? -1 : editor.note3,
+		               (editor.note4 == 36) ? -1 : editor.note4));
+	}
+	else
+	{
+		note = MIN(MIN(editor.note1, editor.note2), MIN(editor.note3, editor.note4));
+	}
+
+	if (note < 0 || note > 35)
+	{
+		editor.chordLength = 0;
+	}
+	else
+	{
+		len = (s->length * periodTable[(37 * s->fineTune) + note]) / periodTable[24];
+		if (len > MAX_SAMPLE_LEN)
+			len = MAX_SAMPLE_LEN;
+
+		editor.chordLength = len & 0xFFFE;
+	}
+
+	if (ui.editOpScreenShown && ui.editOpScreen == 3)
+		ui.updateLengthText = true;
+}
+
+void resetChord(void)
+{
+	editor.note1 = 36;
+	editor.note2 = 36;
+	editor.note3 = 36;
+	editor.note4 = 36;
+
+	editor.chordLengthMin = false;
+
+	ui.updateNote1Text = true;
+	ui.updateNote2Text = true;
+	ui.updateNote3Text = true;
+	ui.updateNote4Text = true;
+
+	recalcChordLength();
+}
+
+void undoChord(void)
+{
+	editor.note1 = editor.oldNote1;
+	editor.note2 = editor.oldNote2;
+	editor.note3 = editor.oldNote3;
+	editor.note4 = editor.oldNote4;
+
+	ui.updateNote1Text = true;
+	ui.updateNote2Text = true;
+	ui.updateNote3Text = true;
+	ui.updateNote4Text = true;
+
+	recalcChordLength();
+}
+
+void toggleChordLength(void)
+{
+	if (song->samples[editor.currSample].loopLength == 2 && song->samples[editor.currSample].loopStart == 0)
+	{
+		editor.chordLengthMin = mouse.rightButtonPressed ? true : false;
+		recalcChordLength();
+	}
+}
+
+void setChordMajor(void)
+{
+	if (editor.note1 == 36)
+	{
+		displayErrorMsg("NO BASENOTE!");
+		return;
+	}
+
+	backupChord();
+
+	editor.note2 = editor.note1 + 4;
+	editor.note3 = editor.note1 + 7;
+
+	if (editor.note2 >= 36) editor.note2 -= 12;
+	if (editor.note3 >= 36) editor.note3 -= 12;
+
+	editor.note4 = 36;
+
+	ui.updateNote2Text = true;
+	ui.updateNote3Text = true;
+	ui.updateNote4Text = true;
+
+	recalcChordLength();
+}
+
+void setChordMinor(void)
+{
+	if (editor.note1 == 36)
+	{
+		displayErrorMsg("NO BASENOTE!");
+		return;
+	}
+
+	backupChord();
+
+	editor.note2 = editor.note1 + 3;
+	editor.note3 = editor.note1 + 7;
+
+	if (editor.note2 >= 36) editor.note2 -= 12;
+	if (editor.note3 >= 36) editor.note3 -= 12;
+
+	editor.note4 = 36;
+
+	ui.updateNote2Text = true;
+	ui.updateNote3Text = true;
+	ui.updateNote4Text = true;
+
+	recalcChordLength();
+}
+
+void setChordSus4(void)
+{
+	if (editor.note1 == 36)
+	{
+		displayErrorMsg("NO BASENOTE!");
+		return;
+	}
+
+	backupChord();
+
+	editor.note2 = editor.note1 + 5;
+	editor.note3 = editor.note1 + 7;
+
+	if (editor.note2 >= 36) editor.note2 -= 12;
+	if (editor.note3 >= 36) editor.note3 -= 12;
+
+	editor.note4 = 36;
+
+	ui.updateNote2Text = true;
+	ui.updateNote3Text = true;
+	ui.updateNote4Text = true;
+
+	recalcChordLength();
+}
+
+void setChordMajor6(void)
+{
+	if (editor.note1 == 36)
+	{
+		displayErrorMsg("NO BASENOTE!");
+		return;
+	}
+
+	backupChord();
+
+	editor.note2 = editor.note1 + 4;
+	editor.note3 = editor.note1 + 7;
+	editor.note4 = editor.note1 + 9;
+
+	if (editor.note2 >= 36) editor.note2 -= 12;
+	if (editor.note3 >= 36) editor.note3 -= 12;
+	if (editor.note4 >= 36) editor.note4 -= 12;
+
+	ui.updateNote2Text = true;
+	ui.updateNote3Text = true;
+	ui.updateNote4Text = true;
+
+	recalcChordLength();
+}
+
+void setChordMinor6(void)
+{
+	if (editor.note1 == 36)
+	{
+		displayErrorMsg("NO BASENOTE!");
+		return;
+	}
+
+	backupChord();
+
+	editor.note2 = editor.note1 + 3;
+	editor.note3 = editor.note1 + 7;
+	editor.note4 = editor.note1 + 9;
+
+	if (editor.note2 >= 36) editor.note2 -= 12;
+	if (editor.note3 >= 36) editor.note3 -= 12;
+	if (editor.note4 >= 36) editor.note4 -= 12;
+
+	ui.updateNote2Text = true;
+	ui.updateNote3Text = true;
+	ui.updateNote4Text = true;
+
+	recalcChordLength();
+}
+
+void setChordMajor7(void)
+{
+	if (editor.note1 == 36)
+	{
+		displayErrorMsg("NO BASENOTE!");
+		return;
+	}
+
+	backupChord();
+
+	editor.note2 = editor.note1 + 4;
+	editor.note3 = editor.note1 + 7;
+	editor.note4 = editor.note1 + 11;
+
+	if (editor.note2 >= 36) editor.note2 -= 12;
+	if (editor.note3 >= 36) editor.note3 -= 12;
+	if (editor.note4 >= 36) editor.note4 -= 12;
+
+	ui.updateNote2Text = true;
+	ui.updateNote3Text = true;
+	ui.updateNote4Text = true;
+
+	recalcChordLength();
+}
+
+void setChordMinor7(void)
+{
+	if (editor.note1 == 36)
+	{
+		displayErrorMsg("NO BASENOTE!");
+		return;
+	}
+
+	backupChord();
+
+	editor.note2 = editor.note1 + 3;
+	editor.note3 = editor.note1 + 7;
+	editor.note4 = editor.note1 + 10;
+
+	if (editor.note2 >= 36) editor.note2 -= 12;
+	if (editor.note3 >= 36) editor.note3 -= 12;
+	if (editor.note4 >= 36) editor.note4 -= 12;
+
+	ui.updateNote2Text = true;
+	ui.updateNote3Text = true;
+	ui.updateNote4Text = true;
+
+	recalcChordLength();
+}
+
+void selectChordNote1(void)
+{
+	if (mouse.rightButtonPressed)
+	{
+		editor.note1 = 36;
+	}
+	else
+	{
+		ui.changingChordNote = 1;
+		setStatusMessage("SELECT NOTE", NO_CARRY);
+		pointerSetMode(POINTER_MODE_MSG1, NO_CARRY);
+	}
+
+	ui.updateNote1Text = true;
+}
+
+void selectChordNote2(void)
+{
+	if (mouse.rightButtonPressed)
+	{
+		editor.note2 = 36;
+	}
+	else
+	{
+		ui.changingChordNote = 2;
+		setStatusMessage("SELECT NOTE", NO_CARRY);
+		pointerSetMode(POINTER_MODE_MSG1, NO_CARRY);
+	}
+
+	ui.updateNote2Text = true;
+}
+
+void selectChordNote3(void)
+{
+	if (mouse.rightButtonPressed)
+	{
+		editor.note3 = 36;
+	}
+	else
+	{
+		ui.changingChordNote = 3;
+		setStatusMessage("SELECT NOTE", NO_CARRY);
+		pointerSetMode(POINTER_MODE_MSG1, NO_CARRY);
+	}
+
+	ui.updateNote3Text = true;
+}
+
+void selectChordNote4(void)
+{
+	if (mouse.rightButtonPressed)
+	{
+		editor.note4 = 36;
+	}
+	else
+	{
+		ui.changingChordNote = 4;
+		setStatusMessage("SELECT NOTE", NO_CARRY);
+		pointerSetMode(POINTER_MODE_MSG1, NO_CARRY);
+	}
+
+	ui.updateNote4Text = true;
+}
+
+void makeChord(void)
+{
+	ui.askScreenShown = true;
+	ui.askScreenType = ASK_MAKE_CHORD;
+	pointerSetMode(POINTER_MODE_MSG1, NO_CARRY);
+	setStatusMessage("MAKE CHORD?", NO_CARRY);
+	renderAskDialog();
+}
--- /dev/null
+++ b/src/pt2_chordmaker.h
@@ -1,0 +1,18 @@
+#pragma once
+
+void recalcChordLength(void);
+void resetChord(void);
+void undoChord(void);
+void toggleChordLength(void);
+void setChordMajor(void);
+void setChordMinor(void);
+void setChordSus4(void);
+void setChordMajor6(void);
+void setChordMinor6(void);
+void setChordMajor7(void);
+void setChordMinor7(void);
+void selectChordNote1(void);
+void selectChordNote2(void);
+void selectChordNote3(void);
+void selectChordNote4(void);
+void makeChord(void);
--- a/src/pt2_config.c
+++ b/src/pt2_config.c
@@ -45,8 +45,8 @@
 	config.fullScreenStretch = false;
 	config.pattDots = false;
 	config.waveformCenterLine = true;
-	config.a500LowPassFilter = false;
-	config.soundFrequency = 96000;
+	config.filterModel = FILTERMODEL_A1200;
+	config.soundFrequency = 48000;
 	config.rememberPlayMode = false;
 	config.stereoSeparation = 20;
 	config.videoScaleFactor = 2;
@@ -61,12 +61,10 @@
 	config.autoCloseDiskOp = true;
 	config.vsyncOff = false;
 	config.hwMouse = true;
-	config.sampleLowpass = true;
 	config.startInFullscreen = false;
 	config.pixelFilter = PIXELFILTER_NEAREST;
 	config.integerScaling = true;
 	config.audioInputFrequency = 44100;
-	config.normalizeSampling = true;
 
 #ifndef _WIN32
 	getcwd(oldCwd, PATH_MAX);
@@ -201,13 +199,6 @@
 			else if (!_strnicmp(&configLine[8], "FALSE", 5)) config.hwMouse = false;
 		}
 
-		// SAMPLELOWPASS
-		else if (!_strnicmp(configLine, "SAMPLELOWPASS=", 14))
-		{
-			     if (!_strnicmp(&configLine[14], "TRUE",  4)) config.sampleLowpass = true;
-			else if (!_strnicmp(&configLine[14], "FALSE", 5)) config.sampleLowpass = false;
-		}
-
 		// VSYNCOFF
 		else if (!_strnicmp(configLine, "VSYNCOFF=", 9))
 		{
@@ -375,18 +366,25 @@
 			}
 		}
 
-		// A500LOWPASSFILTER
+		// FILTERMODEL
+		else if (!_strnicmp(configLine, "FILTERMODEL=", 12))
+		{
+			     if (!_strnicmp(&configLine[12], "A500",  4)) config.filterModel = FILTERMODEL_A500;
+			else if (!_strnicmp(&configLine[12], "A1200", 5)) config.filterModel = FILTERMODEL_A1200;
+		}
+
+		// A500LOWPASSFILTER (deprecated, same as A4000LOWPASSFILTER)
 		else if (!_strnicmp(configLine, "A500LOWPASSFILTER=", 18))
 		{
-			     if (!_strnicmp(&configLine[18], "TRUE",  4)) config.a500LowPassFilter = true;
-			else if (!_strnicmp(&configLine[18], "FALSE", 5)) config.a500LowPassFilter = false;
+			     if (!_strnicmp(&configLine[18], "TRUE",  4)) config.filterModel = FILTERMODEL_A500;
+			else if (!_strnicmp(&configLine[18], "FALSE", 5)) config.filterModel = FILTERMODEL_A1200;
 		}
 
-		// A4000LOWPASSFILTER (deprecated, same as A500LOWPASSFILTER)
+		// A4000LOWPASSFILTER (deprecated)
 		else if (!_strnicmp(configLine, "A4000LOWPASSFILTER=", 19))
 		{
-			     if (!_strnicmp(&configLine[19], "TRUE",  4)) config.a500LowPassFilter = true;
-			else if (!_strnicmp(&configLine[19], "FALSE", 5)) config.a500LowPassFilter = false;
+			     if (!_strnicmp(&configLine[19], "TRUE",  4)) config.filterModel = FILTERMODEL_A500;
+			else if (!_strnicmp(&configLine[19], "FALSE", 5)) config.filterModel = FILTERMODEL_A1200;
 		}
 
 		// SAMPLINGFREQ
@@ -397,13 +395,6 @@
 				const int32_t num = atoi(&configLine[13]);
 				config.audioInputFrequency = CLAMP(num, 44100, 192000);
 			}
-		}
-
-		// NORMALIZESAMPLING
-		else if (!_strnicmp(configLine, "NORMALIZESAMPLING=", 18))
-		{
-			     if (!_strnicmp(&configLine[18], "TRUE",  4)) config.normalizeSampling = true;
-			else if (!_strnicmp(&configLine[18], "FALSE", 5)) config.normalizeSampling = false;
 		}
 
 		// FREQUENCY
--- a/src/pt2_config.h
+++ b/src/pt2_config.h
@@ -13,11 +13,11 @@
 typedef struct config_t
 {
 	char *defModulesDir, *defSamplesDir;
-	bool waveformCenterLine, pattDots, a500LowPassFilter, compoMode, autoCloseDiskOp, hideDiskOpDates, hwMouse;
+	bool waveformCenterLine, pattDots, compoMode, autoCloseDiskOp, hideDiskOpDates, hwMouse;
 	bool transDel, fullScreenStretch, vsyncOff, modDot, blankZeroFlag, realVuMeters, rememberPlayMode;
-	bool sampleLowpass, startInFullscreen, integerScaling, normalizeSampling;
+	bool startInFullscreen, integerScaling;
 	int8_t stereoSeparation, videoScaleFactor, accidental;
-	uint8_t pixelFilter;
+	uint8_t pixelFilter, filterModel;
 	uint16_t quantizeValue;
 	uint32_t soundFrequency, soundBufferSize, audioInputFrequency;
 } config_t;
--- a/src/pt2_diskop.c
+++ b/src/pt2_diskop.c
@@ -27,7 +27,6 @@
 #include "pt2_textout.h"
 #include "pt2_diskop.h"
 #include "pt2_tables.h"
-#include "pt2_palette.h"
 #include "pt2_module_loader.h"
 #include "pt2_audio.h"
 #include "pt2_sampler.h"
--- /dev/null
+++ b/src/pt2_downsample2x.c
@@ -1,0 +1,215 @@
+// for finding memory leaks in debug mode with Visual Studio 
+#if defined _DEBUG && defined _MSC_VER
+#include <crtdbg.h>
+#endif
+
+#include <stdint.h>
+#include <stdbool.h>
+#include "pt2_helpers.h" // CLAMP
+
+static double state[2];
+
+/*
+** - all-pass halfband filters (2x downsample) -
+*/
+
+static double f(const double in, double *b, const double c)
+{
+	const double x = (in - *b) * c;
+	const double out = *b + x;
+	*b = in + x;
+
+	return out;
+}
+
+static double d2x(const double *input, double *b)
+{
+	return (f(input[0], &b[0], 0.150634765625) + f(input[1], &b[1], -0.3925628662109375)) * 0.5;
+}
+
+// Warning: These can exceed original range because of undershoot/overshoot!
+
+void downsample2xDouble(double *buffer, int32_t originalLength)
+{
+	state[0] = state[1] = 0.0;
+
+	const double *input = buffer;
+	const int32_t length = originalLength / 2;
+	for (int32_t i = 0; i < length; i++, input += 2)
+		buffer[i] = d2x(input, state);
+}
+
+void downsample2xFloat(float *buffer, int32_t originalLength)
+{
+	double in[2];
+
+	state[0] = state[1] = 0.0;
+
+	const float *input = buffer;
+	const int32_t length = originalLength / 2;
+	for (int32_t i = 0; i < length; i++, input += 2)
+	{
+		in[0] = input[0];
+		in[1] = input[1];
+
+		buffer[i] = (float)d2x(in, state);
+	}
+}
+
+// Warning: These are slow and use normalization to prevent clipping from undershoot/overshoot!
+
+bool downsample2x8BitU(uint8_t *buffer, int32_t originalLength)
+{
+	state[0] = state[1] = 0.0;
+
+	double *dBuffer = (double *)malloc(originalLength * sizeof (double));
+	if (dBuffer == NULL)
+		return false;
+
+	for (int32_t i = 0; i < originalLength; i++)
+		dBuffer[i] = (buffer[i] - 128) * (1.0 / (INT8_MAX+1.0));
+
+	const double *input = dBuffer;
+	double dPeak = 0.0;
+
+	const int32_t length = originalLength / 2;
+	for (int32_t i = 0; i < length; i++, input += 2)
+	{
+		double dOut = d2x(input, state);
+		dBuffer[i] = dOut;
+
+		dOut = ABS(dOut);
+		if (dOut > dPeak)
+			dPeak = dOut;
+	}
+
+	// normalize
+
+	double dAmp = 1.0;
+	if (dPeak > 0.0)
+		dAmp = INT8_MAX / dPeak;
+
+	for (int32_t i = 0; i < length; i++)
+		buffer[i] = (uint8_t)round(dBuffer[i] * dAmp) + 128;
+
+	free(dBuffer);
+
+	return true;
+}
+
+bool downsample2x8Bit(int8_t *buffer, int32_t originalLength)
+{
+	state[0] = state[1] = 0.0;
+
+	double *dBuffer = (double *)malloc(originalLength * sizeof (double));
+	if (dBuffer == NULL)
+		return false;
+
+	for (int32_t i = 0; i < originalLength; i++)
+		dBuffer[i] = buffer[i] * (1.0 / (INT8_MAX+1.0));
+
+	const double *input = dBuffer;
+	double dPeak = 0.0;
+
+	const int32_t length = originalLength / 2;
+	for (int32_t i = 0; i < length; i++, input += 2)
+	{
+		double dOut = d2x(input, state);
+		dBuffer[i] = dOut;
+
+		dOut = ABS(dOut);
+		if (dOut > dPeak)
+			dPeak = dOut;
+	}
+
+	// normalize
+
+	double dAmp = 1.0;
+	if (dPeak > 0.0)
+		dAmp = INT8_MAX / dPeak;
+
+	for (int32_t i = 0; i < length; i++)
+		buffer[i] = (int8_t)round(dBuffer[i] * dAmp);
+
+	free(dBuffer);
+
+	return true;
+}
+
+bool downsample2x16Bit(int16_t *buffer, int32_t originalLength)
+{
+	state[0] = state[1] = 0.0;
+
+	double *dBuffer = (double *)malloc(originalLength * sizeof (double));
+	if (dBuffer == NULL)
+		return false;
+
+	for (int32_t i = 0; i < originalLength; i++)
+		dBuffer[i] = buffer[i] * (1.0 / (INT16_MAX+1.0));
+
+	const double *input = dBuffer;
+	double dPeak = 0.0;
+
+	const int32_t length = originalLength / 2;
+	for (int32_t i = 0; i < length; i++, input += 2)
+	{
+		double dOut = d2x(input, state);
+		dBuffer[i] = dOut;
+
+		dOut = ABS(dOut);
+		if (dOut > dPeak)
+			dPeak = dOut;
+	}
+
+	// normalize
+
+	double dAmp = 1.0;
+	if (dPeak > 0.0)
+		dAmp = INT16_MAX / dPeak;
+
+	for (int32_t i = 0; i < length; i++)
+		buffer[i] = (int16_t)round(dBuffer[i] * dAmp);
+
+	free(dBuffer);
+
+	return true;
+}
+
+bool downsample2x32Bit(int32_t *buffer, int32_t originalLength)
+{
+	state[0] = state[1] = 0.0;
+
+	double *dBuffer = (double *)malloc(originalLength * sizeof (double));
+	if (dBuffer == NULL)
+		return false;
+
+	for (int32_t i = 0; i < originalLength; i++)
+		dBuffer[i] = buffer[i] * (1.0 / (INT32_MAX+1.0));
+
+	const double *input = dBuffer;
+	double dPeak = 0.0;
+
+	const int32_t length = originalLength / 2;
+	for (int32_t i = 0; i < length; i++, input += 2)
+	{
+		double dOut = d2x(input, state);
+		dBuffer[i] = dOut;
+
+		dOut = ABS(dOut);
+		if (dOut > dPeak)
+			dPeak = dOut;
+	}
+
+	// normalize
+
+	double dAmp = 1.0;
+	if (dPeak > 0.0)
+		dAmp = INT32_MAX / dPeak;
+
+	for (int32_t i = 0; i < length; i++)
+		buffer[i] = (int32_t)round(dBuffer[i] * dAmp);
+
+	free(dBuffer);
+
+	return true;
+}
--- /dev/null
+++ b/src/pt2_downsamplers2x.h
@@ -1,0 +1,17 @@
+#pragma once
+
+#include <stdint.h>
+
+// all-pass halfband filters
+
+// Warning: These can exceed -1.0 .. 1.0 because of undershoot/overshoot!
+
+void downsample2xFloat(float *buffer, int32_t originalLength);
+void downsample2xDouble(double *buffer, int32_t originalLength);
+
+// Warning: These are slow and use normalization to prevent clipping from undershoot/overshoot!
+
+void downsample2x8Bit(int8_t *buffer, int32_t originalLength);
+void downsample2x8BitU(uint8_t *buffer, int32_t originalLength);
+void downsample2x16Bit(int16_t *buffer, int32_t originalLength);
+void downsample2x32Bit(int32_t *buffer, int32_t originalLength);
--- a/src/pt2_edit.c
+++ b/src/pt2_edit.c
@@ -18,7 +18,6 @@
 #include "pt2_textout.h"
 #include "pt2_tables.h"
 #include "pt2_audio.h"
-#include "pt2_palette.h"
 #include "pt2_diskop.h"
 #include "pt2_mouse.h"
 #include "pt2_sampler.h"
@@ -29,6 +28,7 @@
 #include "pt2_config.h"
 #include "pt2_audio.h"
 #include "pt2_sync.h"
+#include "pt2_chordmaker.h"
 
 const int8_t scancode2NoteLo[52] = // "USB usage page standard" order
 {
--- a/src/pt2_header.h
+++ b/src/pt2_header.h
@@ -14,7 +14,7 @@
 #include "pt2_unicode.h"
 #include "pt2_palette.h"
 
-#define PROG_VER_STR "1.22"
+#define PROG_VER_STR "1.23"
 
 #ifdef _WIN32
 #define DIR_DELIMITER '\\'
@@ -103,7 +103,8 @@
 	TEMPFLAG_START = 1,
 	TEMPFLAG_DELAY = 2,
 
-	FILTER_A500 = 1,
+	FILTERMODEL_A1200 = 0,
+	FILTERMODEL_A500 = 1,
 	FILTER_LED_ENABLED = 2,
 
 	NO_CARRY = 0,
--- a/src/pt2_helpers.c
+++ b/src/pt2_helpers.c
@@ -18,7 +18,6 @@
 #include "pt2_helpers.h"
 #include "pt2_header.h"
 #include "pt2_tables.h"
-#include "pt2_palette.h"
 #include "pt2_structs.h"
 #include "pt2_config.h"
 
@@ -161,45 +160,4 @@
 	}
 
 	 SDL_SetWindowTitle(video.window, titleTemp);
-}
-
-void recalcChordLength(void)
-{
-	int8_t note;
-	int32_t len;
-	moduleSample_t *s;
-
-	s = &song->samples[editor.currSample];
-
-	if (editor.chordLengthMin)
-	{
-		note = MAX(MAX((editor.note1 == 36) ? -1 : editor.note1,
-		               (editor.note2 == 36) ? -1 : editor.note2),
-		           MAX((editor.note3 == 36) ? -1 : editor.note3,
-		               (editor.note4 == 36) ? -1 : editor.note4));
-	}
-	else
-	{
-		note = MIN(MIN(editor.note1, editor.note2), MIN(editor.note3, editor.note4));
-	}
-
-	if (note < 0 || note > 35)
-	{
-		editor.chordLength = 0;
-	}
-	else
-	{
-		assert(editor.tuningNote < 36);
-		if (editor.tuningNote < 36)
-		{
-			len = (s->length * periodTable[(37 * s->fineTune) + note]) / periodTable[editor.tuningNote];
-			if (len > MAX_SAMPLE_LEN)
-				len = MAX_SAMPLE_LEN;
-
-			editor.chordLength = len & 0xFFFE;
-		}
-	}
-
-	if (ui.editOpScreenShown && ui.editOpScreen == 3)
-		ui.updateLengthText = true;
 }
--- a/src/pt2_helpers.h
+++ b/src/pt2_helpers.h
@@ -64,4 +64,3 @@
 bool sampleNameIsEmpty(char *name);
 bool moduleNameIsEmpty(char *name);
 void updateWindowTitle(bool modified);
-void recalcChordLength(void);
--- a/src/pt2_keyboard.c
+++ b/src/pt2_keyboard.c
@@ -16,7 +16,6 @@
 #include "pt2_header.h"
 #include "pt2_helpers.h"
 #include "pt2_visuals.h"
-#include "pt2_palette.h"
 #include "pt2_diskop.h"
 #include "pt2_edit.h"
 #include "pt2_sampler.h"
@@ -29,6 +28,7 @@
 #include "pt2_unicode.h"
 #include "pt2_config.h"
 #include "pt2_sampling.h"
+#include "pt2_chordmaker.h"
 
 #if defined _WIN32 && !defined _DEBUG
 extern bool windowsKeyIsDown;
@@ -718,7 +718,7 @@
 			}
 			else
 			{
-				toggleA500Filters();
+				toggleFilterModel();
 			}
 		}
 		break;
@@ -3959,12 +3959,6 @@
 
 				pointerSetPreviousMode();
 				setPrevStatusMessage();
-
-				editor.errorMsgActive = true;
-				editor.errorMsgBlock = true;
-				editor.errorMsgCounter = 0;
-
-				setErrPointer();
 			}
 			break;
 
--- /dev/null
+++ b/src/pt2_ledfilter.c
@@ -1,0 +1,87 @@
+#include <stdint.h>
+#include <math.h>
+#include "pt2_rcfilter.h" // DENORMAL_OFFSET definition
+#include "pt2_ledfilter.h"
+
+/* Imperfect Amiga "LED" filter implementation. This may be further improved in the future.
+** Based upon ideas posted by mystran @ the kvraudio.com forum.
+**
+** This filter may not function correctly used outside the fixed-cutoff context here!
+*/
+
+void clearLEDFilterState(ledFilter_t *filterLED)
+{
+	filterLED->buffer[0] = 0.0; // left channel
+	filterLED->buffer[1] = 0.0;
+	filterLED->buffer[2] = 0.0; // right channel
+	filterLED->buffer[3] = 0.0;
+}
+
+static double sigmoid(double x, double coefficient)
+{
+	/* aciddose:
+	** Coefficient from:
+	**   0.0 to  inf (linear)
+	**  -1.0 to -inf (linear)
+	*/
+	return x / (x + coefficient) * (coefficient + 1.0);
+}
+
+void calcLEDFilterCoeffs(const double sr, const double hz, const double fb, ledFilter_t *filter)
+{
+	/* aciddose:
+	** tan() may produce NaN or other bad results in some cases!
+	** It appears to work correctly with these specific coefficients.
+	*/
+
+	const double pi = 4.0 * atan(1.0); // M_PI can not be trusted
+
+	const double c = (hz < (sr / 2.0)) ? tan((pi * hz) / sr) : 1.0;
+	const double g = 1.0 / (1.0 + c);
+
+	// aciddose: dirty compensation
+	const double s = 0.5;
+	const double t = 0.5;
+	const double ic = c > t ? 1.0 / ((1.0 - s*t) + s*c) : 1.0;
+	const double cg = c * g;
+	const double fbg = 1.0 / (1.0 + fb * cg*cg);
+
+	filter->c = c;
+	filter->ci = g;
+	filter->feedback = 2.0 * sigmoid(fb, 0.5);
+	filter->bg = fbg * filter->feedback * ic;
+	filter->cg = cg;
+	filter->c2 = c * 2.0;
+}
+
+void LEDFilter(ledFilter_t *f, const double *in, double *out)
+{
+	const double in_1 = DENORMAL_OFFSET;
+	const double in_2 = DENORMAL_OFFSET;
+
+	const double c = f->c;
+	const double g = f->ci;
+	const double cg = f->cg;
+	const double bg = f->bg;
+	const double c2 = f->c2;
+
+	double *v = f->buffer;
+
+	// left channel
+	const double estimate_L = in_2 + g*(v[1] + c*(in_1 + g*(v[0] + c*in[0])));
+	const double y0_L = v[0]*g + in[0]*cg + in_1 + estimate_L * bg;
+	const double y1_L = v[1]*g + y0_L*cg + in_2;
+
+	v[0] += c2 * (in[0] - y0_L);
+	v[1] += c2 * (y0_L - y1_L);
+	out[0] = y1_L;
+
+	// right channel
+	const double estimate_R = in_2 + g*(v[3] + c*(in_1 + g*(v[2] + c*in[1])));
+	const double y0_R = v[2]*g + in[1]*cg + in_1 + estimate_R * bg;
+	const double y1_R = v[3]*g + y0_R*cg + in_2;
+
+	v[2] += c2 * (in[1] - y0_R);
+	v[3] += c2 * (y0_R - y1_R);
+	out[1] = y1_R;
+}
--- /dev/null
+++ b/src/pt2_ledfilter.h
@@ -1,0 +1,14 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+
+typedef struct ledFilter_t
+{
+	double buffer[4];
+	double c, ci, feedback, bg, cg, c2;
+} ledFilter_t;
+
+void clearLEDFilterState(ledFilter_t *filterLED);
+void calcLEDFilterCoeffs(const double sr, const double hz, const double fb, ledFilter_t *filter);
+void LEDFilter(ledFilter_t *f, const double *in, double *out);
--- a/src/pt2_main.c
+++ b/src/pt2_main.c
@@ -19,7 +19,6 @@
 #include <sys/stat.h>
 #include "pt2_header.h"
 #include "pt2_helpers.h"
-#include "pt2_palette.h"
 #include "pt2_keyboard.h"
 #include "pt2_textout.h"
 #include "pt2_mouse.h"
@@ -428,7 +427,7 @@
 
 			if (!ui.askScreenShown && ui.introScreenShown)
 			{
-				if (!ui.clearScreenShown && !ui.diskOpScreenShown)
+				if (!ui.clearScreenShown && !ui.diskOpScreenShown && !editor.errorMsgActive)
 					statusAllRight();
 
 				ui.introScreenShown = false;
@@ -509,7 +508,7 @@
 	editor.oldNote4 = 36;
 	editor.tuningVol = 32;
 	editor.sampleVol = 100;
-	editor.tuningNote = 24;
+	editor.tuningNote = 24; // C-3
 	editor.metroSpeed = 4;
 	editor.editMoveAdd = 1;
 	editor.initialTempo = 125;
@@ -648,39 +647,37 @@
 #ifdef _WIN32
 static void disableWasapi(void)
 {
-	const char *audioDriver;
-	int32_t i, numAudioDrivers;
-
 	// disable problematic WASAPI SDL2 audio driver on Windows (causes clicks/pops sometimes...)
 
-	numAudioDrivers = SDL_GetNumAudioDrivers();
-	for (i = 0; i < numAudioDrivers; i++)
+	const int32_t numAudioDrivers = SDL_GetNumAudioDrivers();
+	if (numAudioDrivers <= 1)
+		return;
+
+	// look for directsound and enable it if found
+	for (int32_t i = 0; i < numAudioDrivers; i++)
 	{
-		audioDriver = SDL_GetAudioDriver(i);
+		const char *audioDriver = SDL_GetAudioDriver(i);
 		if (audioDriver != NULL && strcmp("directsound", audioDriver) == 0)
 		{
 			SDL_setenv("SDL_AUDIODRIVER", "directsound", true);
 			audio.rescanAudioDevicesSupported = false;
-			break;
+			return;
 		}
 	}
 
-	if (i == numAudioDrivers)
+	// directsound is not available, try winmm
+	for (int32_t i = 0; i < numAudioDrivers; i++)
 	{
-		// directsound is not available, try winmm
-		for (i = 0; i < numAudioDrivers; i++)
+		const char *audioDriver = SDL_GetAudioDriver(i);
+		if (audioDriver != NULL && strcmp("winmm", audioDriver) == 0)
 		{
-			audioDriver = SDL_GetAudioDriver(i);
-			if (audioDriver != NULL && strcmp("winmm", audioDriver) == 0)
-			{
-				SDL_setenv("SDL_AUDIODRIVER", "winmm", true);
-				audio.rescanAudioDevicesSupported = false;
-				break;
-			}
+			SDL_setenv("SDL_AUDIODRIVER", "winmm", true);
+			audio.rescanAudioDevicesSupported = false;
+			return;
 		}
 	}
 
-	// maybe we didn't find directsound or winmm, let's use wasapi after all then...
+	// we didn't find directsound or winmm, let's use wasapi after all...
 }
 
 static void makeSureDirIsProgramDir(void)
--- a/src/pt2_mouse.c
+++ b/src/pt2_mouse.c
@@ -12,7 +12,6 @@
 #include "pt2_header.h"
 #include "pt2_mouse.h"
 #include "pt2_helpers.h"
-#include "pt2_palette.h"
 #include "pt2_diskop.h"
 #include "pt2_sampler.h"
 #include "pt2_module_loader.h"
@@ -26,6 +25,7 @@
 #include "pt2_config.h"
 #include "pt2_bmp.h"
 #include "pt2_sampling.h"
+#include "pt2_chordmaker.h"
 
 /* TODO: Move irrelevant routines outta here! Disgusting design!
 ** Keep in mind that this was programmed in my early programming days...
@@ -2164,12 +2164,6 @@
 		ui.clearScreenShown = false;
 		setPrevStatusMessage();
 		pointerSetPreviousMode();
-
-		editor.errorMsgActive = true;
-		editor.errorMsgBlock = true;
-		editor.errorMsgCounter = 0;
-
-		setErrPointer();
 		removeClearScreen();
 		return true;
 	}
@@ -2379,7 +2373,7 @@
 
 	if (editor.errorMsgActive)
 	{
-		if (++editor.errorMsgCounter >= (uint8_t)(VBLANK_HZ/1.15))
+		if (++editor.errorMsgCounter >= 55)
 		{
 			editor.errorMsgCounter = 0;
 
@@ -3268,340 +3262,31 @@
 		case PTB_EO_VOL_DOWN: edVolDownButton(); break;
 		// ----------------------------------------------------------
 
-		// Edit Op. Screen #4
+		// Edit Op. Screen #4 (chord maker)
 
-		case PTB_EO_DOCHORD:
-		{
-			ui.askScreenShown = true;
-			ui.askScreenType = ASK_MAKE_CHORD;
-			pointerSetMode(POINTER_MODE_MSG1, NO_CARRY);
-			setStatusMessage("MAKE CHORD?", NO_CARRY);
-			renderAskDialog();
-		}
-		break;
-
-		case PTB_EO_MAJOR:
-		{
-			if (editor.note1 == 36)
-			{
-				displayErrorMsg("NO BASENOTE!");
-				break;
-			}
-
-			editor.oldNote1 = editor.note1;
-			editor.oldNote2 = editor.note2;
-			editor.oldNote3 = editor.note3;
-			editor.oldNote4 = editor.note4;
-
-			editor.note2 = editor.note1 + 4;
-			editor.note3 = editor.note1 + 7;
-
-			if (editor.note2 >= 36) editor.note2 -= 12;
-			if (editor.note3 >= 36) editor.note3 -= 12;
-
-			editor.note4 = 36;
-
-			ui.updateNote2Text = true;
-			ui.updateNote3Text = true;
-			ui.updateNote4Text = true;
-
-			recalcChordLength();
-		}
-		break;
-
-		case PTB_EO_MAJOR7:
-		{
-			if (editor.note1 == 36)
-			{
-				displayErrorMsg("NO BASENOTE!");
-				break;
-			}
-
-			editor.oldNote1 = editor.note1;
-			editor.oldNote2 = editor.note2;
-			editor.oldNote3 = editor.note3;
-			editor.oldNote4 = editor.note4;
-
-			editor.note2 = editor.note1 + 4;
-			editor.note3 = editor.note1 + 7;
-			editor.note4 = editor.note1 + 11;
-
-			if (editor.note2 >= 36) editor.note2 -= 12;
-			if (editor.note3 >= 36) editor.note3 -= 12;
-			if (editor.note4 >= 36) editor.note4 -= 12;
-
-			ui.updateNote2Text = true;
-			ui.updateNote3Text = true;
-			ui.updateNote4Text = true;
-
-			recalcChordLength();
-		}
-		break;
-
-		case PTB_EO_NOTE1:
-		{
-			if (mouse.rightButtonPressed)
-			{
-				editor.note1 = 36;
-			}
-			else
-			{
-				ui.changingChordNote = 1;
-				setStatusMessage("SELECT NOTE", NO_CARRY);
-				pointerSetMode(POINTER_MODE_MSG1, NO_CARRY);
-			}
-			ui.updateNote1Text = true;
-		}
-		break;
-
+		case PTB_EO_DOCHORD: makeChord(); break;
+		case PTB_EO_NOTE1: selectChordNote1(); break;
+		case PTB_EO_NOTE2: selectChordNote2(); break;
+		case PTB_EO_NOTE3: selectChordNote3(); break;
+		case PTB_EO_NOTE4: selectChordNote4(); break;
 		case PTB_EO_NOTE1_UP: edNote1UpButton(); break;
 		case PTB_EO_NOTE1_DOWN: edNote1DownButton(); break;
-
-		case PTB_EO_NOTE2:
-		{
-			if (mouse.rightButtonPressed)
-			{
-				editor.note2 = 36;
-			}
-			else
-			{
-				ui.changingChordNote = 2;
-				setStatusMessage("SELECT NOTE", NO_CARRY);
-				pointerSetMode(POINTER_MODE_MSG1, NO_CARRY);
-			}
-			ui.updateNote2Text = true;
-		}
-		break;
-
 		case PTB_EO_NOTE2_UP: edNote2UpButton(); break;
 		case PTB_EO_NOTE2_DOWN: edNote2DownButton(); break;
-
-		case PTB_EO_NOTE3:
-		{
-			if (mouse.rightButtonPressed)
-			{
-				editor.note3 = 36;
-			}
-			else
-			{
-				ui.changingChordNote = 3;
-				setStatusMessage("SELECT NOTE", NO_CARRY);
-				pointerSetMode(POINTER_MODE_MSG1, NO_CARRY);
-			}
-			ui.updateNote3Text = true;
-		}
-		break;
-
 		case PTB_EO_NOTE3_UP: edNote3UpButton(); break;
 		case PTB_EO_NOTE3_DOWN: edNote3DownButton(); break;
-
-		case PTB_EO_NOTE4:
-		{
-			if (mouse.rightButtonPressed)
-			{
-				editor.note4 = 36;
-			}
-			else
-			{
-				ui.changingChordNote = 4;
-				setStatusMessage("SELECT NOTE", NO_CARRY);
-				pointerSetMode(POINTER_MODE_MSG1, NO_CARRY);
-			}
-			ui.updateNote4Text = true;
-		}
-		break;
-
 		case PTB_EO_NOTE4_UP: edNote4UpButton(); break;
 		case PTB_EO_NOTE4_DOWN: edNote4DownButton(); break;
-
-		case PTB_EO_RESET:
-		{
-			editor.note1 = 36;
-			editor.note2 = 36;
-			editor.note3 = 36;
-			editor.note4 = 36;
-
-			editor.chordLengthMin = false;
-
-			ui.updateNote1Text = true;
-			ui.updateNote2Text = true;
-			ui.updateNote3Text = true;
-			ui.updateNote4Text = true;
-
-			recalcChordLength();
-		}
-		break;
-
-		case PTB_EO_MINOR:
-		{
-			if (editor.note1 == 36)
-			{
-				displayErrorMsg("NO BASENOTE!");
-				break;
-			}
-
-			editor.oldNote1 = editor.note1;
-			editor.oldNote2 = editor.note2;
-			editor.oldNote3 = editor.note3;
-			editor.oldNote4 = editor.note4;
-
-			editor.note2 = editor.note1 + 3;
-			editor.note3 = editor.note1 + 7;
-
-			if (editor.note2 >= 36) editor.note2 -= 12;
-			if (editor.note3 >= 36) editor.note3 -= 12;
-
-			editor.note4 = 36;
-
-			ui.updateNote2Text = true;
-			ui.updateNote3Text = true;
-			ui.updateNote4Text = true;
-
-			recalcChordLength();
-		}
-		break;
-
-		case PTB_EO_MINOR7:
-		{
-			if (editor.note1 == 36)
-			{
-				displayErrorMsg("NO BASENOTE!");
-				break;
-			}
-
-			editor.oldNote1 = editor.note1;
-			editor.oldNote2 = editor.note2;
-			editor.oldNote3 = editor.note3;
-			editor.oldNote4 = editor.note4;
-
-			editor.note2 = editor.note1 + 3;
-			editor.note3 = editor.note1 + 7;
-			editor.note4 = editor.note1 + 10;
-
-			if (editor.note2 >= 36) editor.note2 -= 12;
-			if (editor.note3 >= 36) editor.note3 -= 12;
-			if (editor.note4 >= 36) editor.note4 -= 12;
-
-			ui.updateNote2Text = true;
-			ui.updateNote3Text = true;
-			ui.updateNote4Text = true;
-
-			recalcChordLength();
-		}
-		break;
-
-		case PTB_EO_UNDO:
-		{
-			editor.note1 = editor.oldNote1;
-			editor.note2 = editor.oldNote2;
-			editor.note3 = editor.oldNote3;
-			editor.note4 = editor.oldNote4;
-
-			ui.updateNote1Text = true;
-			ui.updateNote2Text = true;
-			ui.updateNote3Text = true;
-			ui.updateNote4Text = true;
-
-			recalcChordLength();
-		}
-		break;
-
-		case PTB_EO_SUS4:
-		{
-			if (editor.note1 == 36)
-			{
-				displayErrorMsg("NO BASENOTE!");
-				break;
-			}
-
-			editor.oldNote1 = editor.note1;
-			editor.oldNote2 = editor.note2;
-			editor.oldNote3 = editor.note3;
-			editor.oldNote4 = editor.note4;
-
-			editor.note2 = editor.note1 + 5;
-			editor.note3 = editor.note1 + 7;
-
-			if (editor.note2 >= 36) editor.note2 -= 12;
-			if (editor.note3 >= 36) editor.note3 -= 12;
-
-			editor.note4 = 36;
-
-			ui.updateNote2Text = true;
-			ui.updateNote3Text = true;
-			ui.updateNote4Text = true;
-
-			recalcChordLength();
-		}
-		break;
-
-		case PTB_EO_MAJOR6:
-		{
-			if (editor.note1 == 36)
-			{
-				displayErrorMsg("NO BASENOTE!");
-				break;
-			}
-
-			editor.oldNote1 = editor.note1;
-			editor.oldNote2 = editor.note2;
-			editor.oldNote3 = editor.note3;
-			editor.oldNote4 = editor.note4;
-
-			editor.note2 = editor.note1 + 4;
-			editor.note3 = editor.note1 + 7;
-			editor.note4 = editor.note1 + 9;
-
-			if (editor.note2 >= 36) editor.note2 -= 12;
-			if (editor.note3 >= 36) editor.note3 -= 12;
-			if (editor.note4 >= 36) editor.note4 -= 12;
-
-			ui.updateNote2Text = true;
-			ui.updateNote3Text = true;
-			ui.updateNote4Text = true;
-
-			recalcChordLength();
-		}
-		break;
-
-		case PTB_EO_LENGTH:
-		{
-			if (song->samples[editor.currSample].loopLength == 2 && song->samples[editor.currSample].loopStart == 0)
-			{
-				editor.chordLengthMin = mouse.rightButtonPressed ? true : false;
-				recalcChordLength();
-			}
-		}
-		break;
-
-		case PTB_EO_MINOR6:
-		{
-			if (editor.note1 == 36)
-			{
-				displayErrorMsg("NO BASENOTE!");
-				break;
-			}
-
-			editor.oldNote1 = editor.note1;
-			editor.oldNote2 = editor.note2;
-			editor.oldNote3 = editor.note3;
-			editor.oldNote4 = editor.note4;
-
-			editor.note2 = editor.note1 + 3;
-			editor.note3 = editor.note1 + 7;
-			editor.note4 = editor.note1 + 9;
-
-			if (editor.note2 >= 36) editor.note2 -= 12;
-			if (editor.note3 >= 36) editor.note3 -= 12;
-			if (editor.note4 >= 36) editor.note4 -= 12;
-
-			ui.updateNote2Text = true;
-			ui.updateNote3Text = true;
-			ui.updateNote4Text = true;
-
-			recalcChordLength();
-		}
-		break;
+		case PTB_EO_RESET: resetChord(); break;
+		case PTB_EO_UNDO: undoChord(); break;
+		case PTB_EO_LENGTH: toggleChordLength(); break;
+		case PTB_EO_MAJOR: setChordMajor(); break;
+		case PTB_EO_MINOR: setChordMinor(); break;
+		case PTB_EO_SUS4: setChordSus4(); break;
+		case PTB_EO_MAJOR7: setChordMajor7(); break;
+		case PTB_EO_MINOR7: setChordMinor7(); break;
+		case PTB_EO_MAJOR6: setChordMajor6(); break;
+		case PTB_EO_MINOR6: setChordMinor6(); break;
 		// ----------------------------------------------------------
 
 		case PTB_ABOUT:
@@ -4646,10 +4331,6 @@
 			removeClearScreen();
 			setPrevStatusMessage();
 			pointerSetPreviousMode();
-			editor.errorMsgActive = true;
-			editor.errorMsgBlock = true;
-			editor.errorMsgCounter = 0;
-			setErrPointer();
 		}
 		break;
 
--- a/src/pt2_pat2smp.c
+++ b/src/pt2_pat2smp.c
@@ -12,6 +12,9 @@
 #include "pt2_audio.h"
 #include "pt2_sampler.h"
 #include "pt2_textout.h"
+#include "pt2_rcfilter.h"
+#include "pt2_pat2smp.h"
+#include "pt2_downsamplers2x.h"
 
 bool intMusic(void); // pt_modplayer.c
 void storeTempVariables(void); // pt_modplayer.c
@@ -28,8 +31,8 @@
 		return;
 	}
 
-	editor.pat2SmpBuf = (int16_t *)malloc(MAX_SAMPLE_LEN * sizeof (int16_t));
-	if (editor.pat2SmpBuf == NULL)
+	editor.dPat2SmpBuf = (double *)malloc((MAX_SAMPLE_LEN*2) * sizeof (double));
+	if (editor.dPat2SmpBuf == NULL)
 	{
 		statusOutOfMemory();
 		return;
@@ -69,36 +72,54 @@
 	editor.isSMPRendering = false;
 	resetSong();
 
+	int32_t renderLength = editor.pat2SmpPos;
+
+	s = &song->samples[editor.currSample];
+
 	// set back old row
 	song->currRow = song->row = oldRow;
 
-	normalize16bitSigned(editor.pat2SmpBuf, MIN(editor.pat2SmpPos, MAX_SAMPLE_LEN));
+	// downsample oversampled buffer, normalize and quantize to 8-bit
 
-	s = &song->samples[editor.currSample];
+	downsample2xDouble(editor.dPat2SmpBuf, renderLength);
+	renderLength /= 2;
 
-	// quantize to 8-bit
-	for (int32_t i = 0; i < editor.pat2SmpPos; i++)
-		song->sampleData[s->offset+i] = editor.pat2SmpBuf[i] >> 8;
+	double dAmp = 1.0;
+	const double dPeak = getDoublePeak(editor.dPat2SmpBuf, renderLength);
+	if (dPeak > 0.0)
+		dAmp = INT8_MAX / dPeak;
 
-	// clear the rest of the sample
-	if (editor.pat2SmpPos < MAX_SAMPLE_LEN)
-		memset(&song->sampleData[s->offset+editor.pat2SmpPos], 0, MAX_SAMPLE_LEN - editor.pat2SmpPos);
+	double dVol = 64.0 * dPeak;
+	if (dVol > 64.0)
+		dVol = 64.0;
 
-	free(editor.pat2SmpBuf);
+	int8_t *smpPtr = &song->sampleData[s->offset];
+	for (int32_t i = 0; i < renderLength; i++)
+	{
+		const int32_t smp = (const int32_t)round(editor.dPat2SmpBuf[i] * dAmp);
+		assert(smp >= -128 && smp <= 127); // shouldn't happen according to dAmp (but just in case)
+		smpPtr[i] = (int8_t)smp;
+	}
 
+	free(editor.dPat2SmpBuf);
+	
+	// clear the rest of the sample (if not full)
+	if (renderLength < MAX_SAMPLE_LEN)
+		memset(&song->sampleData[s->offset+renderLength], 0, MAX_SAMPLE_LEN - renderLength);
+
 	if (editor.pat2SmpHQ)
 	{
-		strcpy(s->text, "pat2smp (a-3 tune:+4)");
+		strcpy(s->text, "pat2smp(a-3 ftune:+4)");
 		s->fineTune = 4;
 	}
 	else
 	{
-		strcpy(s->text, "pat2smp (f-3 tune:+1)");
-		s->fineTune = 1;
+		strcpy(s->text, "pat2smp(e-3 ftune: 0)");
+		s->fineTune = 0;
 	}
 
-	s->length = (uint16_t)editor.pat2SmpPos;
-	s->volume = 64;
+	s->length = (uint16_t)renderLength;
+	s->volume = (int8_t)round(dVol);
 	s->loopStart = 0;
 	s->loopLength = 2;
 
--- a/src/pt2_pat2smp.h
+++ b/src/pt2_pat2smp.h
@@ -2,10 +2,12 @@
 
 #include "pt2_header.h"
 
+// we do 2x oversampling for BLEP synthesis to work right on all ProTracker pitches
+
 #define PAT2SMP_HI_PERIOD 124 /* A-3 finetune +4, 28603.99Hz */
-#define PAT2SMP_LO_PERIOD 160 /* F-3 finetune +1, 22168.09Hz */
+#define PAT2SMP_LO_PERIOD 170 /* E-3 finetune  0, 20864.08Hz */
 
-#define PAT2SMP_HI_FREQ (PAULA_PAL_CLK / (double)PAT2SMP_HI_PERIOD)
-#define PAT2SMP_LO_FREQ (PAULA_PAL_CLK / (double)PAT2SMP_LO_PERIOD)
+#define PAT2SMP_HI_FREQ (PAULA_PAL_CLK / (PAT2SMP_HI_PERIOD / 2.0))
+#define PAT2SMP_LO_FREQ (PAULA_PAL_CLK / (PAT2SMP_LO_PERIOD / 2.0))
 
 void doPat2Smp(void);
--- a/src/pt2_pattern_viewer.c
+++ b/src/pt2_pattern_viewer.c
@@ -1,7 +1,6 @@
 #include <stdint.h>
 #include <stdbool.h>
 #include "pt2_header.h"
-#include "pt2_palette.h"
 #include "pt2_tables.h"
 #include "pt2_textout.h"
 #include "pt2_structs.h"
--- /dev/null
+++ b/src/pt2_rcfilter.c
@@ -1,0 +1,70 @@
+#include <stdint.h>
+#include <math.h>
+#include "pt2_rcfilter.h"
+
+void calcRCFilterCoeffs(double dSr, double dHz, rcFilter_t *f)
+{
+	const double pi = 4.0 * atan(1.0); // M_PI can not be trusted
+
+	const double c = (dHz < (dSr / 2.0)) ? tan((pi * dHz) / dSr) : 1.0;
+	f->c = c;
+	f->c2 = f->c * 2.0;
+	f->g = 1.0 / (1.0 + f->c);
+	f->cg = f->c * f->g;
+}
+
+void clearRCFilterState(rcFilter_t *f)
+{
+	f->buffer[0] = 0.0; // left channel
+	f->buffer[1] = 0.0; // right channel
+}
+
+// aciddose: input 0 is resistor side of capacitor (low-pass), input 1 is reference side (high-pass)
+static inline double getLowpassOutput(rcFilter_t *f, const double input_0, const double input_1, const double buffer)
+{
+	double dOutput = DENORMAL_OFFSET;
+
+	dOutput += buffer * f->g + input_0 * f->cg + input_1 * (1.0 - f->cg);
+
+	return dOutput;
+}
+
+void RCLowPassFilterStereo(rcFilter_t *f, const double *in, double *out)
+{
+	double output;
+
+	// left channel RC low-pass
+	output = getLowpassOutput(f, in[0], 0.0, f->buffer[0]);
+	f->buffer[0] += (in[0] - output) * f->c2;
+	out[0] = output;
+
+	// right channel RC low-pass
+	output = getLowpassOutput(f, in[1], 0.0, f->buffer[1]);
+	f->buffer[1] += (in[1] - output) * f->c2;
+	out[1] = output;
+}
+
+void RCHighPassFilterStereo(rcFilter_t *f, const double *in, double *out)
+{
+	double low[2];
+
+	RCLowPassFilterStereo(f, in, low);
+
+	out[0] = in[0] - low[0]; // left channel high-pass
+	out[1] = in[1] - low[1]; // right channel high-pass
+}
+
+void RCLowPassFilter(rcFilter_t *f, const double in, double *out)
+{
+	double output = getLowpassOutput(f, in, 0.0, f->buffer[0]);
+	f->buffer[0] += (in - output) * f->c2;
+	*out = output;
+}
+
+void RCHighPassFilter(rcFilter_t *f, const double in, double *out)
+{
+	double low;
+
+	RCLowPassFilter(f, in, &low);
+	*out = in - low; // high-pass
+}
--- /dev/null
+++ b/src/pt2_rcfilter.h
@@ -1,0 +1,21 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+
+// adding this prevents denormalized numbers, which is slow
+#define DENORMAL_OFFSET 1e-15
+
+typedef struct rcFilter_t
+{
+	double buffer[2];
+	double c, c2, g, cg;
+} rcFilter_t;
+
+void calcRCFilterCoeffs(const double sr, const double hz, rcFilter_t *f);
+void clearRCFilterState(rcFilter_t *f);
+void RCLowPassFilterStereo(rcFilter_t *f, const double *in, double *out);
+void RCHighPassFilterStereo(rcFilter_t *f, const double *in, double *out);
+void RCLowPassFilter(rcFilter_t *f, const double in, double *out);
+void RCHighPassFilter(rcFilter_t *f, const double in, double *out);
+
--- a/src/pt2_replayer.c
+++ b/src/pt2_replayer.c
@@ -1,4 +1,4 @@
-// C port of ProTracker 2.3D's replayer by 8bitbubsy, slightly modified.
+// C port of ProTracker 2.3D's replayer (with some modifications, but still accurate)
 
 // for finding memory leaks in debug mode with Visual Studio 
 #if defined _DEBUG && defined _MSC_VER
@@ -13,7 +13,6 @@
 #include "pt2_header.h"
 #include "pt2_audio.h"
 #include "pt2_helpers.h"
-#include "pt2_palette.h"
 #include "pt2_tables.h"
 #include "pt2_module_loader.h"
 #include "pt2_config.h"
@@ -37,7 +36,7 @@
 
 int8_t *allocMemForAllSamples(void)
 {
-	/* Allocate memoru for all sample data blocks.
+	/* Allocate memory for all sample data blocks.
 	**
 	** We need three extra sample slots:
 	** The 1st is extra safety padding since setting a Paula length of 0
--- a/src/pt2_sample_loader.c
+++ b/src/pt2_sample_loader.c
@@ -18,13 +18,8 @@
 #include "pt2_unicode.h"
 #include "pt2_config.h"
 #include "pt2_sampling.h"
+#include "pt2_downsamplers2x.h"
 
-/* TODO: Get a low-pass filter with a steeper slope!
-** A 6db/oct filter may not be very suitable for filtering out frequencies above nyquist,
-** before 2x downsampling.
-*/
-#define DOWNSAMPLE_CUTOFF_FACTOR 4.0
-
 enum
 {
 	WAV_FORMAT_PCM = 0x0001,
@@ -31,165 +26,37 @@
 	WAV_FORMAT_IEEE_FLOAT = 0x0003
 };
 
-static bool loadedFileWasAIFF;
+enum
+{
+	SAMPLE_IFF = 0,
+	SAMPLE_AIFF = 1,
+	SAMPLE_WAV = 2
+};
 
+
+static int8_t loadedSampleType;
+
 static bool loadWAVSample(UNICHAR *fileName, char *entryName, int8_t forceDownSampling);
-static bool loadIFFSample(UNICHAR *fileName, char *entryName);
+static bool loadIFFSample(UNICHAR *fileName, char *entryName, int8_t forceDownSampling);
 static bool loadRAWSample(UNICHAR *fileName, char *entryName);
 static bool loadAIFFSample(UNICHAR *fileName, char *entryName, int8_t forceDownSampling);
 
-static bool lowPassSample8Bit(int8_t *buffer, int32_t length, int32_t sampleFrequency, double cutoff)
+void extLoadWAVOrAIFFSampleCallback(bool downsample)
 {
-	rcFilter_t filter;
-
-	if (buffer == NULL || length == 0 || cutoff == 0.0)
-		return false;
-
-	calcRCFilterCoeffs(sampleFrequency, cutoff, &filter);
-	clearRCFilterState(&filter);
-
-	for (int32_t i = 0; i < length; i++)
+	switch (loadedSampleType)
 	{
-		int32_t sample;
-		double dSample;
-
-		RCLowPassFilterMono(&filter, buffer[i], &dSample);
-		sample = (int32_t)dSample;
-
-		buffer[i] = (int8_t)CLAMP(sample, INT8_MIN, INT8_MAX);
+		case SAMPLE_IFF:   loadIFFSample(editor.fileNameTmpU, editor.entryNameTmp, downsample); break;
+		case SAMPLE_AIFF: loadAIFFSample(editor.fileNameTmpU, editor.entryNameTmp, downsample); break;
+		case SAMPLE_WAV:   loadWAVSample(editor.fileNameTmpU, editor.entryNameTmp, downsample); break;
+		default: break;
 	}
-	
-	return true;
 }
 
-static bool lowPassSample8BitUnsigned(uint8_t *buffer, int32_t length, int32_t sampleFrequency, double cutoff)
-{
-	rcFilter_t filter;
-
-	if (buffer == NULL || length == 0 || cutoff == 0.0)
-		return false;
-
-	calcRCFilterCoeffs(sampleFrequency, cutoff, &filter);
-	clearRCFilterState(&filter);
-
-	for (int32_t i = 0; i < length; i++)
-	{
-		int32_t sample;
-		double dSample;
-
-		RCLowPassFilterMono(&filter, buffer[i] - 128, &dSample);
-		sample = (int32_t)dSample;
-
-		sample = CLAMP(sample, INT8_MIN, INT8_MAX);
-		buffer[i] = (uint8_t)(sample + 128);
-	}
-
-	return true;
-}
-
-static bool lowPassSample16Bit(int16_t *buffer, int32_t length, int32_t sampleFrequency, double cutoff)
-{
-	rcFilter_t filter;
-
-	if (buffer == NULL || length == 0 || cutoff == 0.0)
-		return false;
-
-	calcRCFilterCoeffs(sampleFrequency, cutoff, &filter);
-	clearRCFilterState(&filter);
-
-	for (int32_t i = 0; i < length; i++)
-	{
-		int32_t sample;
-		double dSample;
-
-		RCLowPassFilterMono(&filter, buffer[i], &dSample);
-		sample = (int32_t)dSample;
-
-		buffer[i] = (int16_t)CLAMP(sample, INT16_MIN, INT16_MAX);
-	}
-
-	return true;
-}
-
-static bool lowPassSample32Bit(int32_t *buffer, int32_t length, int32_t sampleFrequency, double cutoff)
-{
-	rcFilter_t filter;
-
-	if (buffer == NULL || length == 0 || cutoff == 0.0)
-		return false;
-
-	calcRCFilterCoeffs(sampleFrequency, cutoff, &filter);
-	clearRCFilterState(&filter);
-
-	for (int32_t i = 0; i < length; i++)
-	{
-		int64_t sample;
-		double dSample;
-
-		RCLowPassFilterMono(&filter, buffer[i], &dSample);
-		sample = (int32_t)dSample;
-
-		buffer[i] = (int32_t)CLAMP(sample, INT32_MIN, INT32_MAX);
-	}
-
-	return true;
-}
-
-static bool lowPassSampleFloat(float *buffer, int32_t length, int32_t sampleFrequency, double cutoff)
-{
-	rcFilter_t filter;
-
-	if (buffer == NULL || length == 0 || cutoff == 0.0)
-		return false;
-
-	calcRCFilterCoeffs(sampleFrequency, cutoff, &filter);
-	clearRCFilterState(&filter);
-
-	for (int32_t i = 0; i < length; i++)
-	{
-		double dSample;
-
-		RCLowPassFilterMono(&filter, buffer[i], &dSample);
-		buffer[i] = (float)dSample;
-	}
-
-	return true;
-}
-
-static bool lowPassSampleDouble(double *buffer, int32_t length, int32_t sampleFrequency, double cutoff)
-{
-	rcFilter_t filter;
-
-	if (buffer == NULL || length == 0 || cutoff == 0.0)
-		return false;
-
-	calcRCFilterCoeffs(sampleFrequency, cutoff, &filter);
-	clearRCFilterState(&filter);
-
-	for (int32_t i = 0; i < length; i++)
-	{
-		double dSample;
-		RCLowPassFilterMono(&filter, buffer[i], &dSample);
-
-		buffer[i] = dSample;
-	}
-
-	return true;
-}
-
-void extLoadWAVOrAIFFSampleCallback(bool downsample)
-{
-	if (loadedFileWasAIFF)
-		loadAIFFSample(editor.fileNameTmpU, editor.entryNameTmp, downsample);
-	else
-		loadWAVSample(editor.fileNameTmpU, editor.entryNameTmp, downsample);
-}
-
 bool loadWAVSample(UNICHAR *fileName, char *entryName, int8_t forceDownSampling)
 {
 	bool wavSampleNameFound;
 	uint8_t *audioDataU8;
-	int16_t *audioDataS16, tempVol, smp16;
+	int16_t *audioDataS16, tempVol;
 	uint16_t audioFormat, numChannels, bitsPerSample;
 	int32_t *audioDataS32, smp32;
 	uint32_t *audioDataU32, i, nameLen, chunkID, chunkSize;
@@ -196,12 +63,12 @@
 	uint32_t sampleLength, sampleRate, filesize, loopFlags;
 	uint32_t loopStart, loopEnd, dataPtr, dataLen, fmtPtr, endOfChunk, bytesRead;
 	uint32_t fmtLen, inamPtr, inamLen, smplPtr, smplLen, xtraPtr, xtraLen;
-	float *fAudioDataFloat, fSmp;
-	double *dAudioDataDouble, dSmp;
+	float *fAudioDataFloat;
+	double *dAudioDataDouble;
 	FILE *f;
 	moduleSample_t *s;
 
-	loadedFileWasAIFF = false;
+	loadedSampleType = SAMPLE_WAV;
 
 	// zero out chunk pointers and lengths
 	fmtPtr  = 0; fmtLen = 0;
@@ -389,6 +256,8 @@
 	// ---- READ SAMPLE DATA ----
 	fseek(f, dataPtr, SEEK_SET);
 
+	int8_t *smpPtr = &song->sampleData[editor.currSample * MAX_SAMPLE_LEN];
+
 	if (bitsPerSample == 8) // 8-BIT INTEGER SAMPLE
 	{
 		if (sampleLength > MAX_SAMPLE_LEN*4)
@@ -417,21 +286,17 @@
 			sampleLength >>= 1;
 			for (i = 0; i < sampleLength-1; i++) // add right channel to left channel
 			{
-				smp16 = (audioDataU8[(i << 1) + 0] - 128) + (audioDataU8[(i << 1) + 1] - 128);
-				smp16 = 128 + (smp16 >> 1);
-				audioDataU8[i] = (uint8_t)smp16;
+				smp32 = (audioDataU8[(i << 1) + 0] - 128) + (audioDataU8[(i << 1) + 1] - 128);
+				smp32 = 128 + (smp32 >> 1);
+				audioDataU8[i] = (uint8_t)smp32;
 			}
 		}
 
-		// 2x downsampling - remove every other sample (if needed)
+		// 2x downsampling
 		if (forceDownSampling)
 		{
-			if (config.sampleLowpass)
-				lowPassSample8BitUnsigned(audioDataU8, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
-
+			downsample2x8BitU(audioDataU8, sampleLength);
 			sampleLength >>= 1;
-			for (i = 1; i < sampleLength; i++)
-				audioDataU8[i] = audioDataU8[i << 1];
 		}
 
 		if (sampleLength > MAX_SAMPLE_LEN)
@@ -438,13 +303,8 @@
 			sampleLength = MAX_SAMPLE_LEN;
 
 		turnOffVoices();
-		for (i = 0; i < MAX_SAMPLE_LEN; i++)
-		{
-			if (i <= (sampleLength & 0xFFFFFFFE))
-				song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = audioDataU8[i] - 128;
-			else
-				song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample
-		}
+		for (i = 0; i < sampleLength; i++)
+			smpPtr[i] = audioDataU8[i] - 128;
 
 		free(audioDataU8);
 	}
@@ -476,35 +336,37 @@
 		{
 			sampleLength >>= 1;
 			for (i = 0; i < sampleLength-1; i++) // add right channel to left channel
-			{
-				smp32 = (audioDataS16[(i << 1) + 0] + audioDataS16[(i << 1) + 1]) >> 1;
-				audioDataS16[i] = (int16_t)smp32;
-			}
+				audioDataS16[i] = (audioDataS16[(i << 1) + 0] + audioDataS16[(i << 1) + 1]) >> 1;;
 		}
 
-		// 2x downsampling - remove every other sample (if needed)
+		// 2x downsampling
 		if (forceDownSampling)
 		{
-			if (config.sampleLowpass)
-				lowPassSample16Bit(audioDataS16, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
-
+			downsample2x16Bit(audioDataS16, sampleLength);
 			sampleLength >>= 1;
-			for (i = 1; i < sampleLength; i++)
-				audioDataS16[i] = audioDataS16[i << 1];
 		}
 
 		if (sampleLength > MAX_SAMPLE_LEN)
 			sampleLength = MAX_SAMPLE_LEN;
 
-		normalize16bitSigned(audioDataS16, sampleLength);
+		double dAmp = 1.0;
+		if (forceDownSampling) // we already normalized
+		{
+			dAmp = INT8_MAX / (double)INT16_MAX;
+		}
+		else
+		{
+			const double dPeak = get16BitPeak(audioDataS16, sampleLength);
+			if (dPeak > 0.0)
+				dAmp = INT8_MAX / dPeak;
+		}
 
 		turnOffVoices();
-		for (i = 0; i < MAX_SAMPLE_LEN; i++)
+		for (i = 0; i < sampleLength; i++)
 		{
-			if (i <= (sampleLength & 0xFFFFFFFE))
-				song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = audioDataS16[i] >> 8;
-			else
-				song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample
+			smp32 = (int32_t)round(audioDataS16[i] * dAmp);
+			assert(smp32 >= -128 && smp32 <= 127); // shouldn't happen according to dAmp (but just in case)
+			smpPtr[i] = (int8_t)smp32;
 		}
 
 		free(audioDataS16);
@@ -543,29 +405,34 @@
 			}
 		}
 
-		// 2x downsampling - remove every other sample (if needed)
+		// 2x downsampling
 		if (forceDownSampling)
 		{
-			if (config.sampleLowpass)
-				lowPassSample32Bit(audioDataS32, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
-
+			downsample2x32Bit(audioDataS32, sampleLength);
 			sampleLength >>= 1;
-			for (i = 1; i < sampleLength; i++)
-				audioDataS32[i] = audioDataS32[i << 1];
 		}
 
 		if (sampleLength > MAX_SAMPLE_LEN)
 			sampleLength = MAX_SAMPLE_LEN;
 
-		normalize32bitSigned(audioDataS32, sampleLength);
+		double dAmp = 1.0;
+		if (forceDownSampling) // we already normalized
+		{
+			dAmp = INT8_MAX / (double)INT32_MAX;
+		}
+		else
+		{
+			const double dPeak = get32BitPeak(audioDataS32, sampleLength);
+			if (dPeak > 0.0)
+				dAmp = INT8_MAX / dPeak;
+		}
 
 		turnOffVoices();
-		for (i = 0; i < MAX_SAMPLE_LEN; i++)
+		for (i = 0; i < sampleLength; i++)
 		{
-			if (i <= (sampleLength & 0xFFFFFFFE))
-				song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = (int8_t)(audioDataS32[i] >> 24);
-			else
-				song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample
+			smp32 = (int32_t)round(audioDataS32[i] * dAmp);
+			assert(smp32 >= -128 && smp32 <= 127); // shouldn't happen according to dAmp (but just in case)
+			smpPtr[i] = (int8_t)smp32;
 		}
 
 		free(audioDataS32);
@@ -604,29 +471,34 @@
 			}
 		}
 
-		// 2x downsampling - remove every other sample (if needed)
+		// 2x downsampling
 		if (forceDownSampling)
 		{
-			if (config.sampleLowpass)
-				lowPassSample32Bit(audioDataS32, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
-
+			downsample2x32Bit(audioDataS32, sampleLength);
 			sampleLength >>= 1;
-			for (i = 1; i < sampleLength; i++)
-				audioDataS32[i] = audioDataS32[i << 1];
 		}
 
 		if (sampleLength > MAX_SAMPLE_LEN)
 			sampleLength = MAX_SAMPLE_LEN;
 
-		normalize32bitSigned(audioDataS32, sampleLength);
+		double dAmp = 1.0;
+		if (forceDownSampling) // we already normalized
+		{
+			dAmp = INT8_MAX / (double)INT32_MAX;
+		}
+		else
+		{
+			const double dPeak = get32BitPeak(audioDataS32, sampleLength);
+			if (dPeak > 0.0)
+				dAmp = INT8_MAX / dPeak;
+		}
 
 		turnOffVoices();
-		for (i = 0; i < MAX_SAMPLE_LEN; i++)
+		for (i = 0; i < sampleLength; i++)
 		{
-			if (i <= (sampleLength & 0xFFFFFFFE))
-				song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = (int8_t)(audioDataS32[i] >> 24);
-			else
-				song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample
+			smp32 = (int32_t)round(audioDataS32[i] * dAmp);
+			assert(smp32 >= -128 && smp32 <= 127); // shouldn't happen according to dAmp (but just in case)
+			smpPtr[i] = (int8_t)smp32;
 		}
 
 		free(audioDataS32);
@@ -661,41 +533,30 @@
 		{
 			sampleLength >>= 1;
 			for (i = 0; i < sampleLength-1; i++) // add right channel to left channel
-			{
-				fSmp = (fAudioDataFloat[(i * 2) + 0] + fAudioDataFloat[(i * 2) + 1]) * 0.5f;
-				fAudioDataFloat[i] = fSmp;
-			}
+				fAudioDataFloat[i] = (fAudioDataFloat[(i * 2) + 0] + fAudioDataFloat[(i * 2) + 1]) * 0.5f;
 		}
 
-		// 2x downsampling - remove every other sample (if needed)
+		// 2x downsampling
 		if (forceDownSampling)
 		{
-			if (config.sampleLowpass)
-				lowPassSampleFloat(fAudioDataFloat, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
-
+			downsample2xFloat(fAudioDataFloat, sampleLength);
 			sampleLength >>= 1;
-			for (i = 1; i < sampleLength; i++)
-				fAudioDataFloat[i] = fAudioDataFloat[i << 1];
 		}
 
 		if (sampleLength > MAX_SAMPLE_LEN)
 			sampleLength = MAX_SAMPLE_LEN;
 
-		normalize8bitFloatSigned(fAudioDataFloat, sampleLength);
+		float fAmp = 1.0f;
+		const float fPeak = getFloatPeak(fAudioDataFloat, sampleLength);
+		if (fPeak > 0.0f)
+			fAmp = INT8_MAX / fPeak;
 
 		turnOffVoices();
-		for (i = 0; i < MAX_SAMPLE_LEN; i++)
+		for (i = 0; i < sampleLength; i++)
 		{
-			if (i <= (sampleLength & 0xFFFFFFFE))
-			{
-				smp32 = (int32_t)fAudioDataFloat[i];
-				CLAMP8(smp32);
-				song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = (int8_t)smp32;
-			}
-			else
-			{
-				song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample
-			}
+			smp32 = (int32_t)roundf(fAudioDataFloat[i] * fAmp);
+			assert(smp32 >= -128 && smp32 <= 127); // shouldn't happen according to dAmp (but just in case)
+			smpPtr[i] = (int8_t)smp32;
 		}
 
 		free(audioDataU32);
@@ -730,46 +591,38 @@
 		{
 			sampleLength >>= 1;
 			for (i = 0; i < sampleLength-1; i++) // add right channel to left channel
-			{
-				dSmp = (dAudioDataDouble[(i * 2) + 0] + dAudioDataDouble[(i * 2) + 1]) * 0.5;
-				dAudioDataDouble[i] = dSmp;
-			}
+				dAudioDataDouble[i] = (dAudioDataDouble[(i * 2) + 0] + dAudioDataDouble[(i * 2) + 1]) * 0.5;
 		}
 
-		// 2x downsampling - remove every other sample (if needed)
+		// 2x downsampling
 		if (forceDownSampling)
 		{
-			if (config.sampleLowpass)
-				lowPassSampleDouble(dAudioDataDouble, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
-
+			downsample2xDouble(dAudioDataDouble, sampleLength);
 			sampleLength >>= 1;
-			for (i = 1; i < sampleLength; i++)
-				dAudioDataDouble[i] = dAudioDataDouble[i << 1];
 		}
 
 		if (sampleLength > MAX_SAMPLE_LEN)
 			sampleLength = MAX_SAMPLE_LEN;
 
-		normalize8bitDoubleSigned(dAudioDataDouble, sampleLength);
+		double dAmp = 1.0;
+		const double dPeak = getDoublePeak(dAudioDataDouble, sampleLength);
+		if (dPeak > 0.0)
+			dAmp = INT8_MAX / dPeak;
 
 		turnOffVoices();
-		for (i = 0; i < MAX_SAMPLE_LEN; i++)
+		for (i = 0; i < sampleLength; i++)
 		{
-			if (i <= (sampleLength & 0xFFFFFFFE))
-			{
-				smp32 = (int32_t)dAudioDataDouble[i];
-				CLAMP8(smp32);
-				song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = (int8_t)smp32;
-			}
-			else
-			{
-				song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample
-			}
+			smp32 = (int32_t)round(dAudioDataDouble[i] * dAmp);
+			assert(smp32 >= -128 && smp32 <= 127); // shouldn't happen according to dAmp (but just in case)
+			smpPtr[i] = (int8_t)smp32;
 		}
 
 		free(audioDataU32);
 	}
 
+	if (sampleLength < MAX_SAMPLE_LEN) // clear rest of sample data
+		memset(&song->sampleData[s->offset + sampleLength], 0, MAX_SAMPLE_LEN - sampleLength);
+
 	// set sample length
 	if (sampleLength & 1)
 	{
@@ -894,13 +747,14 @@
 	return true;
 }
 
-bool loadIFFSample(UNICHAR *fileName, char *entryName)
+bool loadIFFSample(UNICHAR *fileName, char *entryName, int8_t forceDownSampling)
 {
 	bool nameFound, is16Bit;
 	char tmpCharBuf[23];
 	int8_t *sampleData;
-	int16_t sample16, *ptr16;
-	int32_t filesize;
+	int16_t *ptr16;
+	int32_t filesize, smp32;
+	uint16_t sampleRate;
 	uint32_t i, sampleLength, sampleLoopStart, sampleLoopLength;
 	uint32_t sampleVolume, blockName, blockSize;
 	uint32_t vhdrPtr, vhdrLen, bodyPtr, bodyLen, namePtr, nameLen;
@@ -907,6 +761,8 @@
 	FILE *f;
 	moduleSample_t *s;
 
+	loadedSampleType = SAMPLE_IFF;
+
 	s = &song->samples[editor.currSample];
 
 	vhdrPtr = 0; vhdrLen = 0;
@@ -913,6 +769,15 @@
 	bodyPtr = 0; bodyLen = 0;
 	namePtr = 0; nameLen = 0;
 
+	if (forceDownSampling == -1)
+	{
+		// these two *must* be fully wiped, for outputting reasons
+		memset(editor.fileNameTmpU, 0, PATH_MAX);
+		memset(editor.entryNameTmp, 0, PATH_MAX);
+		UNICHAR_STRCPY(editor.fileNameTmpU, fileName);
+		strcpy(editor.entryNameTmp, entryName);
+	}
+
 	f = UNICHAR_FOPEN(fileName, "rb");
 	if (f == NULL)
 	{
@@ -991,9 +856,10 @@
 	fseek(f, vhdrPtr, SEEK_SET);
 	fread(&sampleLoopStart,  4, 1, f); sampleLoopStart  = SWAP32(sampleLoopStart);
 	fread(&sampleLoopLength, 4, 1, f); sampleLoopLength = SWAP32(sampleLoopLength);
+	fseek(f, 4, SEEK_CUR);
+	fread(&sampleRate, 2, 1, f); sampleRate = SWAP16(sampleRate);
+	fseek(f, 1, SEEK_CUR);
 
-	fseek(f, 4 + 2 + 1, SEEK_CUR);
-
 	if (fgetc(f) != 0) // sample type
 	{
 		fclose(f);
@@ -1010,16 +876,6 @@
 		sampleVolume = 64;
 
 	sampleLength = bodyLen;
-	if (is16Bit)
-	{
-		if (sampleLength > MAX_SAMPLE_LEN*2)
-			sampleLength = MAX_SAMPLE_LEN*2;
-	}
-	else
-	{
-		if (sampleLength > MAX_SAMPLE_LEN)
-			sampleLength = MAX_SAMPLE_LEN;
-	}
 
 	if (sampleLength == 0)
 	{
@@ -1028,6 +884,30 @@
 		return false;
 	}
 
+	if (sampleRate > 22050)
+	{
+		if (forceDownSampling == -1)
+		{
+			showDownsampleAskDialog();
+			fclose(f);
+			return true;
+		}
+	}
+	else
+	{
+		forceDownSampling = false;
+	}
+
+	uint32_t maxSampleLength = MAX_SAMPLE_LEN;
+	if (is16Bit)
+		maxSampleLength *= 2;
+
+	if (forceDownSampling)
+		maxSampleLength *= 2;
+
+	if (sampleLength > maxSampleLength)
+		sampleLength = maxSampleLength;
+
 	sampleData = (int8_t *)malloc(sampleLength);
 	if (sampleData == NULL)
 	{
@@ -1043,13 +923,82 @@
 		sampleLoopLength >>= 1;
 	}
 
-	sampleLength &= 0xFFFFFFFE;
+	if (forceDownSampling)
+	{
+		sampleLoopStart >>= 1;
+		sampleLoopLength >>= 1;
+	}
+
+	turnOffVoices();
+
+	fseek(f, bodyPtr, SEEK_SET);
+	if (is16Bit) // FT2-specific 16SV format (little-endian samples)
+	{
+		fread(sampleData, 1, sampleLength << 1, f);
+		ptr16 = (int16_t *)sampleData;
+
+		// 2x downsampling
+		if (forceDownSampling)
+		{
+			downsample2x16Bit(ptr16, sampleLength);
+			sampleLength >>= 1;
+		}
+
+		if (sampleLength > MAX_SAMPLE_LEN)
+			sampleLength = MAX_SAMPLE_LEN;
+
+		double dAmp = 1.0;
+		if (forceDownSampling) // we already normalized
+		{
+			dAmp = INT8_MAX / (double)INT16_MAX;
+		}
+		else
+		{
+			const double dPeak = get16BitPeak(ptr16, sampleLength);
+			if (dPeak > 0.0)
+				dAmp = INT8_MAX / dPeak;
+		}
+
+		int8_t *smpPtr = &song->sampleData[s->offset];
+		for (i = 0; i < sampleLength; i++)
+		{
+			smp32 = (int32_t)round(ptr16[i] * dAmp);
+			assert(smp32 >= -128 && smp32 <= 127); // shouldn't happen according to dAmp (but just in case)
+			smpPtr[i] = (int8_t)smp32;
+		}
+	}
+	else
+	{
+		fread(sampleData, 1, sampleLength, f);
+
+		// 2x downsampling
+		if (forceDownSampling)
+		{
+			downsample2x8Bit(sampleData, sampleLength);
+			sampleLength >>= 1;
+		}
+
+		if (sampleLength > MAX_SAMPLE_LEN)
+			sampleLength = MAX_SAMPLE_LEN;
+
+		memcpy(&song->sampleData[s->offset], sampleData, sampleLength);
+	}
+
+	free(sampleData);
+
+	if (sampleLength < MAX_SAMPLE_LEN) // clear rest of sample data
+		memset(&song->sampleData[s->offset + sampleLength], 0, MAX_SAMPLE_LEN - sampleLength);
+
+	// set sample length
+	if (sampleLength & 1)
+	{
+		if (++sampleLength > MAX_SAMPLE_LEN)
+			sampleLength = MAX_SAMPLE_LEN;
+	}
+
 	sampleLoopStart &= 0xFFFFFFFE;
 	sampleLoopLength &= 0xFFFFFFFE;
 
-	if (sampleLength > MAX_SAMPLE_LEN)
-		sampleLength = MAX_SAMPLE_LEN;
-
 	if (sampleLoopLength < 2)
 	{
 		sampleLoopStart = 0;
@@ -1056,9 +1005,9 @@
 		sampleLoopLength = 2;
 	}
 
-	if (sampleLoopStart >= MAX_SAMPLE_LEN || sampleLoopLength > MAX_SAMPLE_LEN)
+	if (sampleLoopStart >= sampleLength || sampleLoopLength > sampleLength)
 	{
-		sampleLoopStart= 0;
+		sampleLoopStart = 0;
 		sampleLoopLength = 2;
 	}
 
@@ -1074,31 +1023,6 @@
 		sampleLoopLength = 2;
 	}
 
-	turnOffVoices();
-
-	fseek(f, bodyPtr, SEEK_SET);
-	if (is16Bit) // FT2 specific 16SV format (little-endian samples)
-	{
-		fread(sampleData, 1, sampleLength << 1, f);
-
-		ptr16 = (int16_t *)sampleData;
-		for (i = 0; i < sampleLength; i++)
-		{
-			sample16 = ptr16[i];
-			song->sampleData[s->offset+i] = sample16 >> 8;
-		}
-	}
-	else
-	{
-		fread(sampleData, 1, sampleLength, f);
-		memcpy(&song->sampleData[s->offset], sampleData, sampleLength);
-	}
-
-	if (sampleLength < MAX_SAMPLE_LEN) // clear rest of sample data
-		memset(&song->sampleData[s->offset + sampleLength], 0, MAX_SAMPLE_LEN - sampleLength);
-
-	free(sampleData);
-
 	// set sample attributes
 	s->volume = (int8_t)sampleVolume;
 	s->fineTune = 0;
@@ -1253,7 +1177,7 @@
 	char compType[4];
 	int8_t *audioDataS8;
 	uint8_t *audioDataU8, sampleRateBytes[10];
-	int16_t *audioDataS16, smp16;
+	int16_t *audioDataS16;
 	uint16_t bitDepth, numChannels;
 	int32_t filesize, *audioDataS32, smp32;
 	uint32_t nameLen, i, offset, sampleRate, sampleLength, blockName, blockSize;
@@ -1262,7 +1186,7 @@
 	moduleSample_t *s;
 
 	unsigned8bit = false;
-	loadedFileWasAIFF = true;
+	loadedSampleType = SAMPLE_AIFF;
 
 	if (forceDownSampling == -1)
 	{
@@ -1412,6 +1336,8 @@
 		forceDownSampling = false;
 	}
 
+	int8_t *smpPtr = &song->sampleData[editor.currSample * MAX_SAMPLE_LEN];
+
 	if (bitDepth == 8) // 8-BIT INTEGER SAMPLE
 	{
 		if (sampleLength > MAX_SAMPLE_LEN*4)
@@ -1445,21 +1371,14 @@
 		{
 			sampleLength >>= 1;
 			for (i = 0; i < sampleLength-1; i++) // add right channel to left channel
-			{
-				smp16 = (audioDataS8[(i * 2) + 0] + audioDataS8[(i * 2) + 1]) >> 1;
-				audioDataS8[i] = (uint8_t)smp16;
-			}
+				audioDataS8[i] = (audioDataS8[(i * 2) + 0] + audioDataS8[(i * 2) + 1]) >> 1;;
 		}
 
-		// 2x downsampling - remove every other sample (if needed)
+		// 2x downsampling
 		if (forceDownSampling)
 		{
-			if (config.sampleLowpass)
-				lowPassSample8Bit(audioDataS8, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
-
+			downsample2x8Bit(audioDataS8, sampleLength);
 			sampleLength >>= 1;
-			for (i = 1; i < sampleLength; i++)
-				audioDataS8[i] = audioDataS8[i << 1];
 		}
 
 		if (sampleLength > MAX_SAMPLE_LEN)
@@ -1466,13 +1385,8 @@
 			sampleLength = MAX_SAMPLE_LEN;
 
 		turnOffVoices();
-		for (i = 0; i < MAX_SAMPLE_LEN; i++)
-		{
-			if (i <= (sampleLength & 0xFFFFFFFE))
-				song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = audioDataS8[i];
-			else
-				song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample
-		}
+		for (i = 0; i < sampleLength; i++)
+			smpPtr[i] = audioDataS8[i];
 
 		free(audioDataS8);
 	}
@@ -1508,35 +1422,37 @@
 		{
 			sampleLength >>= 1;
 			for (i = 0; i < sampleLength-1; i++) // add right channel to left channel
-			{
-				smp32 = (audioDataS16[(i << 1) + 0] + audioDataS16[(i << 1) + 1]) >> 1;
-				audioDataS16[i] = (int16_t)(smp32);
-			}
+				audioDataS16[i] = (audioDataS16[(i << 1) + 0] + audioDataS16[(i << 1) + 1]) >> 1;
 		}
 
-		// 2x downsampling - remove every other sample (if needed)
+		// 2x downsampling
 		if (forceDownSampling)
 		{
-			if (config.sampleLowpass)
-				lowPassSample16Bit(audioDataS16, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
-
+			downsample2x16Bit(audioDataS16, sampleLength);
 			sampleLength >>= 1;
-			for (i = 1; i < sampleLength; i++)
-				audioDataS16[i] = audioDataS16[i << 1];
 		}
 
 		if (sampleLength > MAX_SAMPLE_LEN)
 			sampleLength = MAX_SAMPLE_LEN;
 
-		normalize16bitSigned(audioDataS16, sampleLength);
+		double dAmp = 1.0;
+		if (forceDownSampling) // we already normalized
+		{
+			dAmp = INT8_MAX / (double)INT16_MAX;
+		}
+		else
+		{
+			const double dPeak = get16BitPeak(audioDataS16, sampleLength);
+			if (dPeak > 0.0)
+				dAmp = INT8_MAX / dPeak;
+		}
 
 		turnOffVoices();
-		for (i = 0; i < MAX_SAMPLE_LEN; i++)
+		for (i = 0; i < sampleLength; i++)
 		{
-			if (i <= (sampleLength & 0xFFFFFFFE))
-				song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = audioDataS16[i] >> 8;
-			else
-				song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample
+			smp32 = (int32_t)round(audioDataS16[i] * dAmp);
+			assert(smp32 >= -128 && smp32 <= 127); // shouldn't happen according to dAmp (but just in case)
+			smpPtr[i] = (int8_t)smp32;
 		}
 
 		free(audioDataS16);
@@ -1583,29 +1499,33 @@
 			}
 		}
 
-		// 2x downsampling - remove every other sample (if needed)
+		// 2x downsampling
 		if (forceDownSampling)
 		{
-			if (config.sampleLowpass)
-				lowPassSample32Bit(audioDataS32, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
-
+			downsample2x32Bit(audioDataS32, sampleLength);
 			sampleLength >>= 1;
-			for (i = 1; i < sampleLength; i++)
-				audioDataS32[i] = audioDataS32[i << 1];
 		}
 
 		if (sampleLength > MAX_SAMPLE_LEN)
 			sampleLength = MAX_SAMPLE_LEN;
 
-		normalize32bitSigned(audioDataS32, sampleLength);
+		double dAmp = 1.0;
+		if (forceDownSampling) // we already normalized
+		{
+			dAmp = INT8_MAX / (double)INT32_MAX;
+		}
+		else
+		{
+			const double dPeak = get32BitPeak(audioDataS32, sampleLength);
+			if (dPeak > 0.0)
+				dAmp = INT8_MAX / dPeak;
+		}
 
 		turnOffVoices();
-		for (i = 0; i < MAX_SAMPLE_LEN; i++)
+		for (i = 0; i < sampleLength; i++)
 		{
-			if (i <= (sampleLength & 0xFFFFFFFE))
-				song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = (int8_t)(audioDataS32[i] >> 24);
-			else
-				song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample
+			smp32 = (int32_t)round(audioDataS32[i] * dAmp);
+			smpPtr[i] = (int8_t)smp32;
 		}
 
 		free(audioDataS32);
@@ -1648,34 +1568,41 @@
 			}
 		}
 
-		// 2x downsampling - remove every other sample (if needed)
+		// 2x downsampling
 		if (forceDownSampling)
 		{
-			if (config.sampleLowpass)
-				lowPassSample32Bit(audioDataS32, sampleLength, sampleRate, sampleRate / DOWNSAMPLE_CUTOFF_FACTOR);
-
+			downsample2x32Bit(audioDataS32, sampleLength);
 			sampleLength >>= 1;
-			for (i = 1; i < sampleLength; i++)
-				audioDataS32[i] = audioDataS32[i << 1];
 		}
 
 		if (sampleLength > MAX_SAMPLE_LEN)
 			sampleLength = MAX_SAMPLE_LEN;
 
-		normalize32bitSigned(audioDataS32, sampleLength);
+		double dAmp = 1.0;
+		if (forceDownSampling) // we already normalized
+		{
+			dAmp = INT8_MAX / (double)INT32_MAX;
+		}
+		else
+		{
+			const double dPeak = get32BitPeak(audioDataS32, sampleLength);
+			if (dPeak > 0.0)
+				dAmp = INT8_MAX / dPeak;
+		}
 
 		turnOffVoices();
-		for (i = 0; i < MAX_SAMPLE_LEN; i++)
+		for (i = 0; i < sampleLength; i++)
 		{
-			if (i <= (sampleLength & 0xFFFFFFFE))
-				song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = (int8_t)(audioDataS32[i] >> 24);
-			else
-				song->sampleData[(editor.currSample * MAX_SAMPLE_LEN) + i] = 0; // clear rest of sample
+			smp32 = (int32_t)round(audioDataS32[i] * dAmp);
+			smpPtr[i] = (int8_t)smp32;
 		}
 
 		free(audioDataS32);
 	}
 
+	if (sampleLength < MAX_SAMPLE_LEN) // clear rest of sample data
+		memset(&song->sampleData[s->offset + sampleLength], 0, MAX_SAMPLE_LEN - sampleLength);
+
 	// set sample length
 	if (sampleLength & 1)
 	{
@@ -1749,6 +1676,16 @@
 	{
 		fread(&ID, 4, 1, f);
 
+		/* Reject FLAC files, since they are not supported.
+		** This is a dumb test since any sample *could* start
+		** with "fLaC", but in 99.999% of cases it won't happen.
+		*/
+		if (ID == 0x43614C66) // "fLaC"
+		{
+			displayErrorMsg("NOT SUPPORTED !");
+			return false;
+		}
+
 		// check if it's actually a WAV sample
 		if (ID == 0x46464952) // "RIFF"
 		{
@@ -1770,7 +1707,7 @@
 			if (ID == 0x58565338 || ID == 0x56533631) // "8SVX" (normal) and "16SV" (FT2 sample)
 			{
 				fclose(f);
-				return loadIFFSample(fileName, entryName);
+				return loadIFFSample(fileName, entryName, -1);
 			}
 
 			// check if it's an AIFF sample
--- a/src/pt2_sampler.c
+++ b/src/pt2_sampler.c
@@ -11,7 +11,6 @@
 #include "pt2_helpers.h"
 #include "pt2_textout.h"
 #include "pt2_audio.h"
-#include "pt2_palette.h"
 #include "pt2_tables.h"
 #include "pt2_visuals.h"
 #include "pt2_blep.h"
@@ -22,6 +21,8 @@
 #include "pt2_config.h"
 #include "pt2_bmp.h"
 #include "pt2_sync.h"
+#include "pt2_rcfilter.h"
+#include "pt2_chordmaker.h"
 
 #define CENTER_LINE_COLOR 0x303030
 #define MARK_COLOR_1 0x666666 /* inverted background */
@@ -31,12 +32,6 @@
 #define SAMPLE_AREA_Y_CENTER 169
 #define SAMPLE_AREA_HEIGHT 64
 
-typedef struct sampleMixer_t
-{
-	int32_t length, pos;
-	uint32_t posFrac, delta;
-} sampleMixer_t;
-
 sampler_t sampler; // globalized
 
 static int32_t samOffsetScaled, lastDrawX, lastDrawY;
@@ -312,7 +307,7 @@
 	if (sampler.samDisplay == 0)
 		return 0;
 
-	uint32_t roundingBias = (uint32_t)sampler.samDisplay >> 1;
+	const uint32_t roundingBias = (const uint32_t)sampler.samDisplay >> 1;
 
 	pos = (((uint32_t)pos * SAMPLE_AREA_WIDTH) + roundingBias) / (uint32_t)sampler.samDisplay; // rounded
 	pos -= samOffsetScaled;
@@ -465,7 +460,7 @@
 	for (y = 0; y < 64; y++)
 	{
 		for (x = 0; x < rangeLen; x++)
-			dstPtr[x] = waveInvertTable[((dstPtr[x] >> 24) & 7) ^ 4]; // It's magic! ptr[x]>>24 = wave/invert color number
+			dstPtr[x] = waveInvertTable[((dstPtr[x] >> 24) & 7) ^ 4]; // ptr[x]>>24 = wave/invert color number
 
 		dstPtr += SCREEN_W;
 	}
@@ -523,7 +518,7 @@
 
 void highPassSample(int32_t cutOff)
 {
-	int32_t smp32, i, from, to;
+	int32_t i, from, to;
 	double *dSampleData, dBaseFreq, dCutOff;
 	moduleSample_t *s;
 	rcFilter_t filterHi;
@@ -589,25 +584,31 @@
 
 	calcRCFilterCoeffs(dBaseFreq, dCutOff, &filterHi);
 
-	// copy over sample data to double buffer
-	for (i = 0; i < s->length; i++)
-		dSampleData[i] = song->sampleData[s->offset+i];
-
 	clearRCFilterState(&filterHi);
 	if (to <= s->length)
 	{
+		const int8_t *smpPtr = &song->sampleData[s->offset];
 		for (i = from; i < to; i++)
-			RCHighPassFilterMono(&filterHi, dSampleData[i], &dSampleData[i]);
+		{
+			double dSmp = smpPtr[i];
+			RCHighPassFilter(&filterHi, dSmp, &dSampleData[i]);
+		}
 	}
 
+	double dAmp = 1.0;
 	if (editor.normalizeFiltersFlag)
-		normalize8bitDoubleSigned(dSampleData, s->length);
+	{
+		const double dPeak = getDoublePeak(dSampleData, s->length);
+		if (dPeak > 0.0)
+			dAmp = INT8_MAX / dPeak;
+	}
 
+	int8_t *smpPtr = &song->sampleData[s->offset];
 	for (i = from; i < to; i++)
 	{
-		smp32 = (int32_t)dSampleData[i];
-		CLAMP8(smp32);
-		song->sampleData[s->offset + i] = (int8_t)smp32;
+		int16_t smp16 = (int16_t)round(dSampleData[i] * dAmp);
+		CLAMP8(smp16);
+		smpPtr[i] = (int8_t)smp16;
 	}
 
 	free(dSampleData);
@@ -619,7 +620,7 @@
 
 void lowPassSample(int32_t cutOff)
 {
-	int32_t smp32, i, from, to;
+	int32_t i, from, to;
 	double *dSampleData, dBaseFreq, dCutOff;
 	moduleSample_t *s;
 	rcFilter_t filterLo;
@@ -692,18 +693,29 @@
 	clearRCFilterState(&filterLo);
 	if (to <= s->length)
 	{
+		const int8_t *smpPtr = &song->sampleData[s->offset];
 		for (i = from; i < to; i++)
-			RCLowPassFilterMono(&filterLo, dSampleData[i], &dSampleData[i]);
+		{ 
+			double dSmp = smpPtr[i];
+			RCLowPassFilter(&filterLo, dSmp, &dSampleData[i]);
+		}
 	}
 
+	double dAmp = 1.0;
+
 	if (editor.normalizeFiltersFlag)
-		normalize8bitDoubleSigned(dSampleData, s->length);
+	{
+		const double dPeak = getDoublePeak(dSampleData, s->length);
+		if (dPeak > 0.0)
+			dAmp = INT8_MAX / dPeak;
+	}
 
+	int8_t *smpPtr = &song->sampleData[s->offset];
 	for (i = from; i < to; i++)
 	{
-		smp32 = (int32_t)dSampleData[i];
-		CLAMP8(smp32);
-		song->sampleData[s->offset + i] = (int8_t)smp32;
+		int16_t smp16 = (int16_t)round(dSampleData[i] * dAmp);
+		CLAMP8(smp16);
+		smpPtr[i] = (int8_t)smp16;
 	}
 
 	free(dSampleData);
@@ -888,20 +900,6 @@
 	updateWindowTitle(MOD_IS_MODIFIED);
 }
 
-#define INTRP_QUADRATIC_TAPS 3
-#define INTRP8_QUADRATIC(s1, s2, s3, f) /* output: -32768..32767 (+ spline overshoot) */ \
-{ \
-	int32_t s4, frac = (f) >> 1; \
-	\
-	s2 <<= 8; \
-	s4 = ((s1 + s3) << (8 - 1)) - s2; \
-	s4 = ((s4 * frac) >> 16) + s2; \
-	s3 = (s1 + s3) << (8 - 1); \
-	s1 <<= 8; \
-	s3 = (s1 + s3) >> 1; \
-	s1 += ((s4 - s3) * frac) >> 14; \
-} \
-
 #define INTRP_LINEAR_TAPS 2
 #define INTRP8_LINEAR(s1, s2, f) /* output: -127..128 */ \
 	s2 -= s1; \
@@ -911,237 +909,6 @@
 	s1 += s2; \
 	s1 >>= 8; \
 
-void mixChordSample(void)
-{
-	bool smpLoopFlag;
-	char smpText[22 + 1];
-	int8_t *smpData, sameNotes, smpVolume;
-	uint8_t smpFinetune, finetune;
-	int32_t channels, samples[INTRP_QUADRATIC_TAPS], *mixData, i, j, k, pos, smpLoopStart, smpLoopLength, smpEnd;
-	sampleMixer_t mixCh[4], *v;
-	moduleSample_t *s;
-
-	if (editor.sampleZero)
-	{
-		statusNotSampleZero();
-		return;
-	}
-
-	assert(editor.currSample >= 0 && editor.currSample <= 30);
-	assert(editor.tuningNote <= 35);
-
-	if (editor.note1 == 36)
-	{
-		displayErrorMsg("NO BASENOTE!");
-		return;
-	}
-
-	if (song->samples[editor.currSample].length == 0)
-	{
-		statusSampleIsEmpty();
-		return;
-	}
-
-	// check if all notes are the same (illegal)
-	sameNotes = true;
-	if ((editor.note2 != 36) && (editor.note2 != editor.note1)) sameNotes = false; else editor.note2 = 36;
-	if ((editor.note3 != 36) && (editor.note3 != editor.note1)) sameNotes = false; else editor.note3 = 36;
-	if ((editor.note4 != 36) && (editor.note4 != editor.note1)) sameNotes = false; else editor.note4 = 36;
-
-	if (sameNotes)
-	{
-		displayErrorMsg("ONLY ONE NOTE!");
-		return;
-	}
-
-	// sort the notes
-
-	for (i = 0; i < 3; i++)
-	{
-		if (editor.note2 == 36)
-		{
-			editor.note2 = editor.note3;
-			editor.note3 = editor.note4;
-			editor.note4 = 36;
-		}
-	}
-
-	for (i = 0; i < 3; i++)
-	{
-		if (editor.note3 == 36)
-		{
-			editor.note3 = editor.note4;
-			editor.note4 = 36;
-		}
-	}
-
-	// remove eventual note duplicates
-	if (editor.note4 == editor.note3) editor.note4 = 36;
-	if (editor.note4 == editor.note2) editor.note4 = 36;
-	if (editor.note3 == editor.note2) editor.note3 = 36;
-
-	ui.updateNote1Text = true;
-	ui.updateNote2Text = true;
-	ui.updateNote3Text = true;
-	ui.updateNote4Text = true;
-
-	// setup some variables
-
-	smpLoopStart = song->samples[editor.currSample].loopStart;
-	smpLoopLength = song->samples[editor.currSample].loopLength;
-	smpLoopFlag = (smpLoopStart + smpLoopLength) > 2;
-	smpEnd = smpLoopFlag ? (smpLoopStart + smpLoopLength) : song->samples[editor.currSample].length;
-	smpData = &song->sampleData[song->samples[editor.currSample].offset];
-
-	if (editor.newOldFlag == 0)
-	{
-		// find a free sample slot for the new sample
-
-		for (i = 0; i < MOD_SAMPLES; i++)
-		{
-			if (song->samples[i].length == 0)
-				break;
-		}
-
-		if (i == MOD_SAMPLES)
-		{
-			displayErrorMsg("NO EMPTY SAMPLE!");
-			return;
-		}
-
-		smpFinetune = song->samples[editor.currSample].fineTune;
-		smpVolume = song->samples[editor.currSample].volume;
-		memcpy(smpText, song->samples[editor.currSample].text, sizeof (smpText));
-
-		s = &song->samples[i];
-		s->fineTune = smpFinetune;
-		s->volume = smpVolume;
-
-		memcpy(s->text, smpText, sizeof (smpText));
-		editor.currSample = (int8_t)i;
-	}
-	else
-	{
-		// overwrite current sample
-		s = &song->samples[editor.currSample];
-	}
-
-	mixData = (int32_t *)calloc(MAX_SAMPLE_LEN, sizeof (int32_t));
-	if (mixData == NULL)
-	{
-		statusOutOfMemory();
-		return;
-	}
-
-	s->length = smpLoopFlag ? MAX_SAMPLE_LEN : editor.chordLength; // if sample loops, set max length
-	s->loopLength = 2;
-	s->loopStart = 0;
-	s->text[21] = '!'; // chord sample indicator
-	s->text[22] = '\0';
-
-	memset(mixCh, 0, sizeof (mixCh));
-
-	// setup mixing lengths and deltas
-
-	finetune = s->fineTune & 0xF;
-	channels = 0;
-
-	if (editor.note1 < 36)
-	{
-		mixCh[0].delta = (periodTable[editor.tuningNote] << 16) / (periodTable[(finetune * 37) + editor.note1]);
-		mixCh[0].length = (smpEnd * periodTable[(finetune * 37) + editor.note1]) / periodTable[editor.tuningNote];
-		channels++;
-	}
-
-	if (editor.note2 < 36)
-	{
-		mixCh[1].delta = (periodTable[editor.tuningNote] << 16) / (periodTable[(finetune * 37) + editor.note2]);
-		mixCh[1].length = (smpEnd * periodTable[(finetune * 37) + editor.note2]) / periodTable[editor.tuningNote];
-		channels++;
-	}
-
-	if (editor.note3 < 36)
-	{
-		mixCh[2].delta = (periodTable[editor.tuningNote] << 16) / (periodTable[(finetune * 37) + editor.note3]);
-		mixCh[2].length = (smpEnd * periodTable[(finetune * 37) + editor.note3]) / periodTable[editor.tuningNote];
-		channels++;
-	}
-
-	if (editor.note4 < 36)
-	{
-		mixCh[3].delta = (periodTable[editor.tuningNote] << 16) / (periodTable[(finetune * 37) + editor.note4]);
-		mixCh[3].length = (smpEnd * periodTable[(finetune * 37) + editor.note4]) / periodTable[editor.tuningNote];
-		channels++;
-	}
-
-	// start mixing
-
-	turnOffVoices();
-	for (i = 0; i < channels; i++)
-	{
-		v = &mixCh[i];
-		if (v->length <= 0)
-			continue; // mix active channels only
-
-		for (j = 0; j < MAX_SAMPLE_LEN; j++) // don't mix more than we can handle in a sample slot
-		{
-			// collect samples for interpolation
-			for (k = 0; k < INTRP_QUADRATIC_TAPS; k++)
-			{
-				pos = v->pos + k;
-				if (smpLoopFlag)
-				{
-					while (pos >= smpEnd)
-						pos -= smpLoopLength;
-
-					samples[k] = smpData[pos];
-				}
-				else
-				{
-					if (pos >= smpEnd)
-						samples[k] = 0;
-					else
-						samples[k] = smpData[pos];
-				}
-			}
-
-			INTRP8_QUADRATIC(samples[0], samples[1], samples[2], v->posFrac);
-			mixData[j] += samples[0];
-
-			v->posFrac += v->delta;
-			if (v->posFrac > 0xFFFF)
-			{
-				v->pos += v->posFrac >> 16;
-				v->posFrac &= 0xFFFF;
-
-				if (smpLoopFlag)
-				{
-					while (v->pos >= smpEnd)
-						v->pos -= smpLoopLength;
-				}
-			}
-		}
-	}
-
-	normalize32bitSigned(mixData, s->length);
-
-	// normalize gain and quantize to 8-bit
-	for (i = 0; i < s->length; i++)
-		song->sampleData[s->offset + i] = (int8_t)(mixData[i] >> 24);
-
-	if (s->length < MAX_SAMPLE_LEN)
-		memset(&song->sampleData[s->offset + s->length], 0, MAX_SAMPLE_LEN - s->length);
-
-	// we're done
-
-	free(mixData);
-
-	editor.samplePos = 0;
-	fixSampleBeep(s);
-	updateCurrSample();
-
-	updateWindowTitle(MOD_IS_MODIFIED);
-}
 
 void samplerResample(void)
 {
--- a/src/pt2_sampling.c
+++ b/src/pt2_sampling.c
@@ -23,7 +23,6 @@
 #include "pt2_audio.h"
 #include "pt2_tables.h"
 #include "pt2_config.h"
-#include "pt2_sinc.h"
 #include "pt2_sampling.h"
 
 enum
@@ -36,13 +35,13 @@
 // this may change after opening the audio input device
 #define SAMPLING_BUFFER_SIZE 1024
 
-#define FRAC_BITS 24
-#define FRAC_SCALE (1L << 24)
-#define FRAC_MASK (FRAC_SCALE-1)
+// after several tests, these values yields a good trade-off between quality and compute time
+#define SINC_TAPS 64
+#define SINC_TAPS_BITS 6 /* log2(SINC_TAPS) */
+#define SINC_PHASES 4096
 
 #define SAMPLE_PREVIEW_WITDH 194
 #define SAMPLE_PREVIEW_HEIGHT 38
-
 #define MAX_INPUT_DEVICES 99
 #define VISIBLE_LIST_ENTRIES 4
 
@@ -50,14 +49,100 @@
 static bool audioDevOpen;
 static char *audioInputDevs[MAX_INPUT_DEVICES];
 static uint8_t samplingNote = 33, samplingFinetune = 4; // period 124, max safe period for PAL Paula
-static int16_t displayBuffer[SAMPLING_BUFFER_SIZE], *bufferOrig, *buffer;
+static int16_t displayBuffer[SAMPLING_BUFFER_SIZE];
 static int32_t samplingMode = SAMPLE_MIX, inputFrequency, roundedOutputFrequency;
 static int32_t numAudioInputDevs, audioInputDevListOffset, selectedDev;
 static int32_t bytesSampled, maxSamplingLength, inputBufferSize;
-static float fOutputFrequency;
-static double dOutputFrequency;
+static double dOutputFrequency, *dSincTable, *dSamplingBuffer, *dSamplingBufferOrig;
 static SDL_AudioDeviceID recordDev;
 
+/*
+** ----------------------------------------------------------------------------------
+** Sinc code taken from the OpenMPT project (has a similar BSD license), and modified
+** ----------------------------------------------------------------------------------
+*/
+
+static double Izero(double y) // Compute Bessel function Izero(y) using a series approximation
+{
+	double s = 1.0, ds = 1.0, d = 0.0;
+
+	do
+	{
+		d = d + 2.0;
+		ds = ds * (y * y) / (d * d);
+		s = s + ds;
+	}
+	while (ds > 1E-7 * s);
+
+	return s;
+}
+
+static bool initSincTable(double cutoff)
+{
+	if (cutoff > 0.999)
+		cutoff = 0.999;
+
+	dSincTable = (double *)malloc(SINC_TAPS * SINC_PHASES * sizeof (double));
+	if (dSincTable == NULL)
+		return false;
+
+	const double beta = 9.6377; // this value can maybe be tweaked (we do downsampling only)
+	const double izeroBeta = Izero(beta);
+	const double kPi = 4.0 * atan(1.0) * cutoff;
+
+#define MID_TAP ((SINC_TAPS/2)*SINC_PHASES)
+
+	for (int32_t i = 0; i < SINC_TAPS*SINC_PHASES; i++)
+	{
+		double fsinc;
+		int32_t ix = (SINC_TAPS-1) - (i & (SINC_TAPS-1));
+
+		ix = (ix * SINC_PHASES) + (i >> SINC_TAPS_BITS);
+		if (ix == MID_TAP)
+		{
+			fsinc = 1.0;
+		}
+		else
+		{
+			const double x = (ix - MID_TAP) * (1.0 / SINC_PHASES);
+			const double xPi = x * kPi;
+
+			const double xMul = 1.0 / ((SINC_TAPS/2) * (SINC_TAPS/2));
+			fsinc = sin(xPi) * Izero(beta * sqrt(1.0 - x * x * xMul)) / (izeroBeta * xPi); // Kaiser window
+		}
+
+		dSincTable[i] = fsinc * cutoff;
+	}
+
+	return true;
+}
+
+static void freeSincTable(void)
+{
+	if (dSincTable != NULL)
+	{
+		free(dSincTable);
+		dSincTable = NULL;
+	}
+}
+
+static double sinc(const double *dSmpData, const double dPhase)
+{
+	const int32_t phase = (int32_t)(dPhase * SINC_PHASES);
+	const double *dSincLUT = &dSincTable[phase << SINC_TAPS_BITS];
+
+	double dSmp = 0.0;
+	for (int32_t i = 0; i < SINC_TAPS; i++)
+		dSmp += dSmpData[i] * dSincLUT[i];
+
+	return dSmp;
+}
+
+/*
+** ----------------------------------------------------------------------------------
+** ----------------------------------------------------------------------------------
+*/
+
 static void listAudioDevices(void);
 
 static void updateOutputFrequency(void)
@@ -70,8 +155,7 @@
 		period = 113;
 
 	dOutputFrequency = (double)PAULA_PAL_CLK / period;
-	fOutputFrequency = (float)dOutputFrequency;
-	roundedOutputFrequency = (int32_t)(fOutputFrequency + 0.5f);
+	roundedOutputFrequency = (int32_t)(dOutputFrequency + 0.5);
 }
 
 static void SDLCALL samplingCallback(void *userdata, Uint8 *stream, int len)
@@ -116,22 +200,22 @@
 		const int16_t *L = (int16_t *)stream;
 		const int16_t *R = ((int16_t *)stream) + 1;
 
-		int16_t *dst16 = &buffer[bytesSampled];
+		double *dSmp = &dSamplingBuffer[bytesSampled];
 
 		if (samplingMode == SAMPLE_LEFT)
 		{
 			for (int32_t i = 0; i < len; i++)
-				dst16[i] = L[i << 1];
+				dSmp[i] = L[i << 1] * (1.0 / 32768.0);
 		}
 		else if (samplingMode == SAMPLE_RIGHT)
 		{
 			for (int32_t i = 0; i < len; i++)
-				dst16[i] = R[i << 1];
+				dSmp[i] = R[i << 1] * (1.0 / 32768.0);
 		}
 		else
 		{
 			for (int32_t i = 0; i < len; i++)
-				dst16[i] = (L[i << 1] + R[i << 1]) >> 1;
+				dSmp[i] = (L[i << 1] + R[i << 1]) * (1.0 / (32768.0 * 2.0));
 		}
 
 		bytesSampled += len;
@@ -153,6 +237,7 @@
 		SDL_CloseAudioDevice(recordDev);
 		recordDev = 0;
 	}
+	callbackBusy = false;
 }
 
 static void startInputAudio(void)
@@ -187,12 +272,6 @@
 	SDL_PauseAudioDevice(recordDev, false);
 }
 
-static void changeStatusText(const char *text)
-{
-	fillRect(88, 127, 17*FONT_CHAR_W, FONT_CHAR_H, video.palette[PAL_GENBKG]);
-	textOut(88, 127, text, video.palette[PAL_GENTXT]);
-}
-
 static void selectAudioDevice(int32_t dev)
 {
 	if (dev < 0)
@@ -480,14 +559,18 @@
 
 	maxSamplingLength = (int32_t)(ceil((65534.0*inputFrequency) / dOutputFrequency)) + 1;
 	
-	bufferOrig = (int16_t *)calloc(SINC_TAPS + maxSamplingLength + SINC_TAPS, sizeof (int16_t));
-	if (bufferOrig == NULL)
+	int32_t allocLen = (SINC_TAPS/2) + maxSamplingLength + (SINC_TAPS/2);
+	dSamplingBufferOrig = (double *)malloc(allocLen * sizeof (double));
+	if (dSamplingBufferOrig == NULL)
 	{
 		statusOutOfMemory();
 		return;
 	}
-	buffer = bufferOrig + SINC_TAPS; // allow negative look-up for sinc taps
+	dSamplingBuffer = dSamplingBufferOrig + (SINC_TAPS/2); // allow negative look-up for sinc taps
 
+	// clear tap area
+	memset(dSamplingBufferOrig, 0, (SINC_TAPS/2) * sizeof (double));
+
 	bytesSampled = 0;
 	audio.isSampling = true;
 	samplingEnded = false;
@@ -498,8 +581,11 @@
 	setStatusMessage("SAMPLING ...", NO_CARRY);
 }
 
-static uint16_t downsampleSamplingBuffer(void)
+static int32_t downsampleSamplingBuffer(void)
 {
+	// clear tap area
+	memset(&dSamplingBuffer[bytesSampled], 0, (SINC_TAPS/2) * sizeof (double));
+
 	const int32_t readLength = bytesSampled;
 	const double dRatio = dOutputFrequency / inputFrequency;
 	
@@ -507,102 +593,67 @@
 	if (writeLength > MAX_SAMPLE_LEN)
 		writeLength = MAX_SAMPLE_LEN;
 
-	//config.normalizeSampling = false;
-
-	double *dBuffer = NULL;
-	if (config.normalizeSampling)
+	double *dBuffer = (double *)malloc(writeLength * sizeof (double));
+	if (dBuffer == NULL)
 	{
-		dBuffer = (double *)malloc(writeLength * sizeof (double));
-		if (dBuffer == NULL)
-		{
-			statusOutOfMemory();
-			return 0;
-		}
+		statusOutOfMemory();
+		return -1;
 	}
 
-	const double dCutoff = dRatio * 0.97; // slightly below nyquist
-	if (!initSinc(dCutoff))
+	if (!initSincTable(dRatio))
 	{
-		if (config.normalizeSampling)
-			free(dBuffer);
-
+		free(dBuffer);
 		statusOutOfMemory();
-		return 0;
+		return -1;
 	}
 
-	changeStatusText("DOWNSAMPLING ...");
-	flipFrame();
-
 	// downsample
 
 	int8_t *output = &song->sampleData[song->samples[editor.currSample].offset];
-
 	const double dDelta = inputFrequency / dOutputFrequency;
-	int16_t *smpPtr = &buffer[-((SINC_TAPS/2)-1)]; // pre-centered (this is safe, look at how bufferOrig is alloc'd)
 
-	double dFrac = 0.0;
-	if (config.normalizeSampling)
+	// pre-centered (this is safe, look at how fSamplingBufferOrig is alloc'd)
+	const double *dSmpPtr = &dSamplingBuffer[-((SINC_TAPS/2)-1)];
+
+	double dPhase = 0.0;
+	double dPeakAmp = 0.0;
+	for (int32_t i = 0; i < writeLength; i++)
 	{
-		double dPeakAmp = 0.0;
-		for (int32_t i = 0; i < writeLength; i++) // up to 65534 bytes
-		{
-			double dSmp = sinc(smpPtr, dFrac);
+		double dSmp = sinc(dSmpPtr, dPhase);
+		dBuffer[i] = dSmp;
 
-			dFrac += dDelta;
-			int32_t wholeSamples = (int32_t)dFrac;
-			dFrac -= wholeSamples;
-			smpPtr += wholeSamples;
+		dSmp = fabs(dSmp);
+		if (dSmp > dPeakAmp)
+			dPeakAmp = dSmp;
 
-			const double dAbsSmp = fabs(dSmp);
-			if (dAbsSmp > dPeakAmp)
-				dPeakAmp = dAbsSmp;
+		dPhase += dDelta;
+		const int32_t wholeSamples = (const int32_t)dPhase;
+		dPhase -= wholeSamples;
+		dSmpPtr += wholeSamples;
+	}
 
-			dBuffer[i] = dSmp;
-		}
+	freeSincTable();
 
-		// normalize
+	// normalize
 
-		double dAmp = INT8_MAX / dPeakAmp;
+	double dAmp = INT8_MAX / dPeakAmp;
 
-		/* If we have to amplify THIS much, it would mean that the gain was extremely low.
-		** We don't want to amplify a ton of noise, so keep it quantized to zero (silence).
-		*/
-		const double dAmp_dB = 20.0*log10(dAmp);
-		if (dAmp_dB > 40.0)
-			dAmp = 0.0;
+	/* If we have to amplify THIS much, it would mean that the gain was extremely low.
+	** We don't want the result to be 99% noise, so keep it quantized to zero (silence).
+	*/
+	const double dAmp_dB = 20.0 * log10(dAmp / 128.0);
+	if (dAmp_dB > 50.0)
+		dAmp = 0.0;
 
-		for (int32_t i = 0; i < writeLength; i++)
-		{
-			/* To round the sample is probably incorrect, but it aliases audibly
-			** less after sampling a 1kHz sine wave, so I'll stick with it for now.
-			** Also just a note: Dithering is not very suitable for samples being
-			** played at lower pitches, hence why I don't dithering.
-			*/
-			const double dSmp = dBuffer[i] * dAmp;
-			int32_t smp32 = (int32_t)round(dSmp);
-			output[i] = (int8_t)CLAMP(smp32, -128, 127);
-		}
-	}
-	else
+	for (int32_t i = 0; i < writeLength; i++)
 	{
-		for (int32_t i = 0; i < writeLength; i++) // up to 65534 bytes
-		{
-			const double dSmp = sinc(smpPtr, dFrac);
-			int32_t smp32 = (int32_t)round(dSmp);
-			output[i] = (int8_t)CLAMP(smp32, -128, 127);
-
-			dFrac += dDelta;
-			int32_t wholeSamples = (int32_t)dFrac;
-			dFrac -= wholeSamples;
-			smpPtr += wholeSamples;
-		}
+		const int32_t smp32 = (const int32_t)round(dBuffer[i] * dAmp);
+		assert(smp32 >= -128 && smp32 <= 127); // shouldn't happen according to dAmp (but just in case)
+		output[i] = (int8_t)smp32;
 	}
-	freeSinc();
 
-	if (config.normalizeSampling)
-		free(dBuffer);
-
-	return (uint16_t)writeLength;
+	free(dBuffer);
+	return writeLength;
 }
 
 void stopSampling(void)
@@ -611,9 +662,15 @@
 	audio.isSampling = false;
 
 	int32_t newLength = downsampleSamplingBuffer();
-	if (newLength == 0)
+	if (newLength == -1)
 		return; // out of memory
 
+	if (dSamplingBufferOrig != NULL)
+	{
+		free(dSamplingBufferOrig);
+		dSamplingBufferOrig = NULL;
+	}
+
 	moduleSample_t *s = &song->samples[editor.currSample];
 	s->length = (uint16_t)newLength;
 	s->fineTune = samplingFinetune;
@@ -622,15 +679,8 @@
 	s->volume = 64;
 	fixSampleBeep(s);
 
-	if (bufferOrig != NULL)
-	{
-		free(bufferOrig);
-		bufferOrig = NULL;
-	}
-
 	pointerSetMode(POINTER_MODE_IDLE, DO_CARRY);
-	displayMsg("SAMPLING DONE !");
-	setMsgPointer();
+	statusAllRight();
 
 	showCurrSample();
 }
@@ -733,15 +783,30 @@
 	if (!mouse.leftButtonPressed)
 		return;
 
-	if (audio.isSampling)
-		stopSampling();
-
 	mouse.lastSamplingButton = -1;
 	mouse.repeatCounter = 0;
 
+	if (audio.isSampling)
+	{
+		stopSampling();
+		return;
+	}
+
 	// check buttons
 	const int32_t mx = mouse.x;
 	const int32_t my = mouse.y;
+
+	if (mx >= 182 && mx <= 243 && my >= 0 && my <= 10) // STOP (main UI)
+	{
+		turnOffVoices();
+	}
+
+	if (mx >= 6 && mx <= 25 && my >= 124 && my <= 133) // EXIT (main UI)
+	{
+		ui.samplingBoxShown = false;
+		removeSamplingBox();
+		exitFromSam();
+	}
 
 	if (mx >= 98 && mx <= 108 && my >= 44 && my <= 54) // SAMPLE UP (main UI)
 	{
--- a/src/pt2_scopes.c
+++ b/src/pt2_scopes.c
@@ -1,8 +1,3 @@
-// for finding memory leaks in debug mode with Visual Studio 
-#if defined _DEBUG && defined _MSC_VER
-#include <crtdbg.h>
-#endif
-
 #include <stdint.h>
 #include <stdbool.h>
 #include <math.h> // modf()
@@ -13,8 +8,6 @@
 #include "pt2_helpers.h"
 #include "pt2_visuals.h"
 #include "pt2_scopes.h"
-#include "pt2_sampler.h"
-#include "pt2_palette.h"
 #include "pt2_tables.h"
 #include "pt2_structs.h"
 #include "pt2_config.h"
@@ -25,7 +18,7 @@
 static int32_t oldPeriod = -1;
 static uint32_t scopeTimeLen, scopeTimeLenFrac;
 static uint64_t timeNext64, timeNext64Frac;
-static float fOldScopeDelta;
+static double dOldScopeDelta;
 static SDL_Thread *scopeThread;
 
 scope_t scope[AMIGA_VOICES]; // global
@@ -33,6 +26,7 @@
 void resetCachedScopePeriod(void)
 {
 	oldPeriod = -1;
+	dOldScopeDelta = 0.0;
 }
 
 // this is quite hackish, but fixes sample swapping issues
@@ -105,11 +99,11 @@
 	if (period != oldPeriod)
 	{
 		oldPeriod = period;
-		const float fPeriodToScopeDeltaDiv = PAULA_PAL_CLK / (float)SCOPE_HZ;
-		fOldScopeDelta = fPeriodToScopeDeltaDiv / period;
+		const double dPeriodToScopeDeltaDiv = PAULA_PAL_CLK / (double)SCOPE_HZ;
+		dOldScopeDelta = dPeriodToScopeDeltaDiv / period;
 	}
 
-	scope[ch].fDelta = fOldScopeDelta;
+	scope[ch].dDelta = dOldScopeDelta;
 }
 
 void scopeTrigger(int32_t ch)
@@ -125,7 +119,7 @@
 	if (newLength < 2)
 		newLength = 2; // for safety
 
-	tempState.fPhase = 0.0f;
+	tempState.dPhase = 0.0;
 	tempState.pos = 0;
 	tempState.data = newData;
 	tempState.length = newLength;
@@ -156,10 +150,10 @@
 		if (!tempState.active)
 			continue; // scope is not active
 
-		tempState.fPhase += tempState.fDelta;
+		tempState.dPhase += tempState.dDelta;
 
-		const int32_t wholeSamples = (int32_t)tempState.fPhase;
-		tempState.fPhase -= wholeSamples;
+		const int32_t wholeSamples = (int32_t)tempState.dPhase;
+		tempState.dPhase -= wholeSamples;
 		tempState.pos += wholeSamples;
 
 		if (tempState.pos >= tempState.length)
@@ -208,7 +202,8 @@
 		if (!tmpScope.active || tmpScope.data == NULL || tmpScope.volume == 0 || tmpScope.length == 0)
 			continue;
 
-		int32_t samplesToScan = (int32_t)tmpScope.fDelta; // amount of integer samples getting skipped every frame
+		// amount of integer samples getting skipped every frame
+		const int32_t samplesToScan = (const int32_t)tmpScope.dDelta;
 		if (samplesToScan <= 0)
 			continue;
 
@@ -237,11 +232,11 @@
 			}
 		}
 
-		float fAvgAmplitude = runningAmplitude / (float)samplesToScan;
+		double dAvgAmplitude = runningAmplitude / (double)samplesToScan;
 
-		fAvgAmplitude *= 96.0f / (128.0f * 64.0f); // normalize
+		dAvgAmplitude *= 96.0 / (128.0 * 64.0); // normalize
 
-		int32_t vuHeight = (int32_t)(fAvgAmplitude + 0.5f); // rounded
+		int32_t vuHeight = (int32_t)(dAvgAmplitude + 0.5); // rounded
 		if (vuHeight > 48) // max VU-meter height
 			vuHeight = 48;
 
--- a/src/pt2_scopes.h
+++ b/src/pt2_scopes.h
@@ -5,10 +5,6 @@
 #include "pt2_header.h"
 #include "pt2_structs.h"
 
-#define SCOPE_FRAC_BITS 16
-#define SCOPE_FRAC_SCALE (1UL << 16)
-#define SCOPE_FRAC_MASK (SCOPE_FRAC_SCALE-1)
-
 typedef struct scope_t
 {
 	const int8_t *data;
@@ -16,7 +12,7 @@
 	uint8_t volume;
 	int32_t length, pos;
 
-	float fDelta, fPhase;
+	double dDelta, dPhase;
 	const int8_t *newData;
 	int32_t newLength;
 } scope_t;
--- a/src/pt2_sinc.c
+++ /dev/null
@@ -1,95 +1,0 @@
-/* These routines are heavily based upon code from 
-** the OpenMPT project (Tables.cpp), which has a
-** similar license.
-**
-** This code is not very readable, as I tried to
-** make it as optimized as I could. The reason I don't
-** make one big pre-calculated table is because I want
-** *many* taps while preserving fractional precision.
-**
-** There might also be some errors in how I wrote this,
-** but so far it sounds okay to my ears.
-** Let me know if I did some crucials mistakes here!
-*/
-
-#include <stdlib.h>
-#include <stdint.h>
-#include <stdbool.h>
-#include <assert.h>
-#include <math.h>
-#include "pt2_sinc.h"
-
-#define CENTER_TAP ((SINC_TAPS / 2) - 1)
-
-static double *dWindowLUT, dKPi;
-
-// Compute Bessel function Izero(y) using a series approximation
-static double Izero(double y)
-{
-	double s = 1.0, ds = 1.0, d = 0.0;
-
-	do
-	{
-		d = d + 2.0;
-		ds = ds * (y * y) / (d * d);
-		s = s + ds;
-	}
-	while (ds > 1E-7 * s);
-
-	return s;
-}
-
-bool initSinc(double dCutoff) // dCutoff = 0.0 .. 0.999
-{
-	assert(SINC_TAPS > 0);
-	if (dCutoff > 0.999)
-		dCutoff = 0.999;
-
-	const double dBeta = 9.6377;
-	
-	dKPi = M_PI * dCutoff;
-
-	// generate window table
-
-	dWindowLUT = (double *)malloc(SINC_TAPS * sizeof (double));
-	if (dWindowLUT == NULL)
-		return false;
-
-	const double dMul1 = 1.0 / ((SINC_TAPS/2) * (SINC_TAPS/2));
-	const double dMul2 = (1.0 / Izero(dBeta)) * dCutoff;
-
-	double dX = CENTER_TAP;
-	for (int32_t i = 0; i < SINC_TAPS; i++)
-	{
-		dWindowLUT[i] = Izero(dBeta * sqrt(1.0 - dX * dX * dMul1)) * dMul2; // Kaiser window
-		dX -= 1.0;
-	}
-
-	return true;
-}
-
-void freeSinc(void)
-{
-	if (dWindowLUT != NULL)
-	{
-		free(dWindowLUT);
-		dWindowLUT = NULL;
-	}
-}
-
-double sinc(int16_t *smpPtr16, double dFrac)
-{
-	double dSmp = 0.0;
-	double dX = (CENTER_TAP + dFrac) * dKPi;
-
-	for (int32_t i = 0; i < SINC_TAPS; i++)
-	{
-		const double dSinc = (sin(dX) / dX) * dWindowLUT[i]; // if only I could replace this div with a mul...
-		dSmp += smpPtr16[i] * dSinc;
-		dX -= dKPi;
-	}
-
-	dSmp *= 1.0 / (SINC_TAPS / 2); // normalize (XXX: This is probably not how to do it?)
-
-	return dSmp;
-}
--- a/src/pt2_sinc.h
+++ /dev/null
@@ -1,14 +1,0 @@
-#pragma once
-
-#include <stdint.h>
-#include <stdbool.h>
-
-#if defined __amd64__ || defined __i386__ || defined _WIN64 || defined _WIN32
-#define SINC_TAPS 512
-#else
-#define SINC_TAPS 128
-#endif
-
-bool initSinc(double dCutoff); // 0.0 .. 0.999
-double sinc(int16_t *smpPtr16, double dFrac);
-void freeSinc(void);
--- a/src/pt2_structs.h
+++ b/src/pt2_structs.h
@@ -173,7 +173,7 @@
 	uint8_t blockFromPos, blockToPos, timingMode, f6Pos, f7Pos, f8Pos, f9Pos, f10Pos, keyOctave, pNoteFlag;
 	uint8_t tuningNote, resampleNote, initialTempo, initialSpeed, editMoveAdd;
 
-	int16_t *pat2SmpBuf, modulateSpeed;
+	int16_t modulateSpeed;
 	uint16_t metroSpeed, metroChannel, sampleVol, samplePos, chordLength;
 	uint16_t effectMacros[10], oldTempo, currPlayNote, vol1, vol2, lpCutOff, hpCutOff;
 	uint16_t smpRedoLoopStarts[MOD_SAMPLES], smpRedoLoopLengths[MOD_SAMPLES], smpRedoLengths[MOD_SAMPLES];
@@ -180,7 +180,7 @@
 	int32_t modulatePos, modulateOffset, markStartOfs, markEndOfs, pat2SmpPos;
 	uint32_t vblankTimeLen, vblankTimeLenFrac;
 	uint64_t musicTime64;
-	double dPerfFreq, dPerfFreqMulMicro;
+	double dPerfFreq, dPerfFreqMulMicro, *dPat2SmpBuf;
 	note_t trackBuffer[MOD_ROWS], cmdsBuffer[MOD_ROWS], blockBuffer[MOD_ROWS];
 	note_t patternBuffer[MOD_ROWS * AMIGA_VOICES], undoBuffer[MOD_ROWS * AMIGA_VOICES];
 	SDL_Thread *mod2WavThread, *pat2SmpThread;
--- a/src/pt2_textout.c
+++ b/src/pt2_textout.c
@@ -4,7 +4,6 @@
 #include "pt2_header.h"
 #include "pt2_helpers.h"
 #include "pt2_tables.h"
-#include "pt2_palette.h"
 #include "pt2_visuals.h"
 #include "pt2_structs.h"
 #include "pt2_bmp.h"
--- a/src/pt2_visuals.c
+++ b/src/pt2_visuals.c
@@ -23,7 +23,6 @@
 #include "pt2_keyboard.h"
 #include "pt2_mouse.h"
 #include "pt2_audio.h"
-#include "pt2_palette.h"
 #include "pt2_helpers.h"
 #include "pt2_textout.h"
 #include "pt2_tables.h"
@@ -42,6 +41,7 @@
 #include "pt2_config.h"
 #include "pt2_bmp.h"
 #include "pt2_sampling.h"
+#include "pt2_chordmaker.h"
 
 typedef struct sprite_t
 {
@@ -185,6 +185,12 @@
 	displayErrorMsg("SAMPLE IS EMPTY");
 }
 
+void changeStatusText(const char *text)
+{
+	fillRect(88, 127, 17*FONT_CHAR_W, FONT_CHAR_H, video.palette[PAL_GENBKG]);
+	textOut(88, 127, text, video.palette[PAL_GENTXT]);
+}
+
 void statusNotSampleZero(void)
 {
 	/* This rather confusing error message actually means that
@@ -1620,13 +1626,7 @@
 		break;
 
 		default:
-		{
 			restoreStatusAndMousePointer();
-			editor.errorMsgActive = true;
-			editor.errorMsgBlock = true;
-			editor.errorMsgCounter = 0;
-			setErrPointer();
-		}
 		break;
 	}
 
--- a/src/pt2_visuals.h
+++ b/src/pt2_visuals.h
@@ -33,6 +33,7 @@
 void statusOutOfMemory(void);
 void statusSampleIsEmpty(void);
 void statusNotSampleZero(void);
+void changeStatusText(const char *text);
 
 void setupPerfFreq(void);
 void setupWaitVBL(void);
--- a/vs2019_project/pt2-clone/protracker.ini
+++ b/vs2019_project/pt2-clone/protracker.ini
@@ -220,39 +220,16 @@
 ;
 SAMPLINGFREQ=44100
 
-; Normalize sampled audio before converting to 8-bit
-;        Syntax: TRUE or FALSE
-; Default value: TRUE
-;       Comment: This one is for the audio sampling feature in the SAMPLER
-;         screen. If it's set to TRUE, it will normalize the gain before it
-;         converts the sample to 8-bit in the end. This will preserve as much
-;         amplitude information as possible to lower quantization noise.
+; Filter model (Amiga model)
+;        Syntax: A500 or A1200
+; Default value: A1200
+;       Comment: Selects what kind of Amiga to simulate (lp/hp filters).
+;         A1200 has sharper sound but more aliasing, while A500 has more
+;         filtered (muddy) sound but less aliasing. The filter model can
+;         also be toggled by pressing F12 in the program.
 ;
-NORMALIZESAMPLING=TRUE
+FILTERMODEL=A1200
 
-; Audio buffer size
-;        Syntax: Number, in samples
-; Default value: 1024
-;       Comment: Ranges from 128 to 8192. Should be a number that is 2^n
-;          (128, 256, 512, 1024, 2048, 4096, 8192, ...). The number you input
-;          isn't necessarily the actual value the audio API decides to use.
-;          Lower means less audio latency but possible audio issues, higher
-;          means more audio latency but less chance for issues.
-;
-BUFFERSIZE=1024
-
-; Amiga 500 low-pass filter (not the "LED" filter)
-;        Syntax: TRUE or FALSE
-; Default value: FALSE
-;       Comment: Enabling this will simulate the ~4421Hz 6dB/oct RC low-pass
-;         filter present in almost all Amigas. This will make the sound a bit
-;         muddier. On Amiga 1200, the cut-off is ~34kHz (sharper sound). This
-;         can also be toggled in the tracker by pressing F12. This must not be
-;         confused with the "LED" filter which can be toggled with the pattern
-;         command E0x.
-;
-A500LOWPASSFILTER=FALSE
-
 ; Stereo separation
 ;        Syntax: 0 to 100 (percent)
 ; Default value: 20 (good value for headphones)
@@ -261,18 +238,15 @@
 ;
 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.
+; Audio buffer size
+;        Syntax: Number, in unit of samples (not bytes)
+; Default value: 1024
+;       Comment: Ranges from 128 to 8192. Should be a number that is 2^n
+;          (128, 256, 512, 1024, 2048, 4096, 8192, ...). The number you input
+;          isn't necessarily the actual value the audio API decides to use.
+;          Lower means less audio latency but possible audio issues, higher
+;          means more audio latency but less chance for issues.
 ;
-SAMPLELOWPASS=TRUE
+BUFFERSIZE=1024
 
 ; 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
@@ -88,7 +88,7 @@
     <ClCompile>
       <WarningLevel>Level4</WarningLevel>
       <Optimization>MaxSpeed</Optimization>
-      <PreprocessorDefinitions>NDEBUG;WIN32;_CRT_SECURE_NO_WARNINGS;_USE_MATH_DEFINES;HAVE_M_PI</PreprocessorDefinitions>
+      <PreprocessorDefinitions>NDEBUG;WIN32;_CRT_SECURE_NO_WARNINGS</PreprocessorDefinitions>
       <EnableEnhancedInstructionSet>StreamingSIMDExtensions2</EnableEnhancedInstructionSet>
       <MultiProcessorCompilation>true</MultiProcessorCompilation>
       <IntrinsicFunctions>true</IntrinsicFunctions>
@@ -99,12 +99,12 @@
       <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
       <FloatingPointModel>Fast</FloatingPointModel>
       <DebugInformationFormat>None</DebugInformationFormat>
-      <InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion>
       <OmitFramePointers>true</OmitFramePointers>
       <CompileAsWinRT>false</CompileAsWinRT>
       <BufferSecurityCheck>false</BufferSecurityCheck>
       <TreatWChar_tAsBuiltInType>false</TreatWChar_tAsBuiltInType>
       <RuntimeTypeInfo>false</RuntimeTypeInfo>
+      <InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion>
     </ClCompile>
     <Link>
       <SubSystem>Windows</SubSystem>
@@ -135,7 +135,7 @@
     <ClCompile>
       <WarningLevel>Level4</WarningLevel>
       <Optimization>MaxSpeed</Optimization>
-      <PreprocessorDefinitions>NDEBUG;WIN32;_CRT_SECURE_NO_WARNINGS;_USE_MATH_DEFINES;HAVE_M_PI</PreprocessorDefinitions>
+      <PreprocessorDefinitions>NDEBUG;WIN32;_CRT_SECURE_NO_WARNINGS</PreprocessorDefinitions>
       <MultiProcessorCompilation>true</MultiProcessorCompilation>
       <IntrinsicFunctions>true</IntrinsicFunctions>
       <StringPooling>true</StringPooling>
@@ -145,11 +145,11 @@
       <FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
       <FloatingPointModel>Fast</FloatingPointModel>
       <DebugInformationFormat>None</DebugInformationFormat>
-      <InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion>
       <OmitFramePointers>true</OmitFramePointers>
       <BufferSecurityCheck>false</BufferSecurityCheck>
       <RuntimeTypeInfo>false</RuntimeTypeInfo>
       <TreatWChar_tAsBuiltInType>false</TreatWChar_tAsBuiltInType>
+      <InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion>
     </ClCompile>
     <Link>
       <SubSystem>Windows</SubSystem>
@@ -177,7 +177,7 @@
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
     <ClCompile>
       <WarningLevel>Level4</WarningLevel>
-      <PreprocessorDefinitions>_CRTDBG_MAP_ALLOC;DEBUG;_DEBUG;WIN32;_CRT_SECURE_NO_WARNINGS;_USE_MATH_DEFINES;HAVE_M_PI</PreprocessorDefinitions>
+      <PreprocessorDefinitions>_CRTDBG_MAP_ALLOC;DEBUG;_DEBUG;WIN32;_CRT_SECURE_NO_WARNINGS</PreprocessorDefinitions>
       <FloatingPointModel>Fast</FloatingPointModel>
       <EnableEnhancedInstructionSet>StreamingSIMDExtensions2</EnableEnhancedInstructionSet>
       <TreatWChar_tAsBuiltInType>false</TreatWChar_tAsBuiltInType>
@@ -205,7 +205,7 @@
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
     <ClCompile>
       <WarningLevel>Level4</WarningLevel>
-      <PreprocessorDefinitions>_CRTDBG_MAP_ALLOC;DEBUG;_DEBUG;WIN32;_CRT_SECURE_NO_WARNINGS;_USE_MATH_DEFINES;HAVE_M_PI</PreprocessorDefinitions>
+      <PreprocessorDefinitions>_CRTDBG_MAP_ALLOC;DEBUG;_DEBUG;WIN32;_CRT_SECURE_NO_WARNINGS</PreprocessorDefinitions>
       <FloatingPointModel>Fast</FloatingPointModel>
       <OmitFramePointers>false</OmitFramePointers>
       <TreatWChar_tAsBuiltInType>false</TreatWChar_tAsBuiltInType>
@@ -242,6 +242,7 @@
     <ClInclude Include="..\..\src\pt2_audio.h" />
     <ClInclude Include="..\..\src\pt2_blep.h" />
     <ClInclude Include="..\..\src\pt2_bmp.h" />
+    <ClInclude Include="..\..\src\pt2_chordmaker.h" />
     <ClInclude Include="..\..\src\pt2_config.h" />
     <ClInclude Include="..\..\src\pt2_diskop.h" />
     <ClInclude Include="..\..\src\pt2_edit.h" />
@@ -248,6 +249,7 @@
     <ClInclude Include="..\..\src\pt2_header.h" />
     <ClInclude Include="..\..\src\pt2_helpers.h" />
     <ClInclude Include="..\..\src\pt2_keyboard.h" />
+    <ClInclude Include="..\..\src\pt2_ledfilter.h" />
     <ClInclude Include="..\..\src\pt2_mod2wav.h" />
     <ClInclude Include="..\..\src\pt2_module_loader.h" />
     <ClInclude Include="..\..\src\pt2_module_saver.h" />
@@ -255,12 +257,13 @@
     <ClInclude Include="..\..\src\pt2_palette.h" />
     <ClInclude Include="..\..\src\pt2_pat2smp.h" />
     <ClInclude Include="..\..\src\pt2_pattern_viewer.h" />
+    <ClInclude Include="..\..\src\pt2_rcfilter.h" />
+    <ClInclude Include="..\..\src\pt2_downsamplers2x.h" />
     <ClInclude Include="..\..\src\pt2_sample_loader.h" />
     <ClInclude Include="..\..\src\pt2_sampler.h" />
     <ClInclude Include="..\..\src\pt2_sample_saver.h" />
     <ClInclude Include="..\..\src\pt2_sampling.h" />
     <ClInclude Include="..\..\src\pt2_scopes.h" />
-    <ClInclude Include="..\..\src\pt2_sinc.h" />
     <ClInclude Include="..\..\src\pt2_structs.h" />
     <ClInclude Include="..\..\src\pt2_sync.h" />
     <ClInclude Include="..\..\src\pt2_tables.h" />
@@ -292,14 +295,17 @@
     </ClCompile>
     <ClCompile Include="..\..\src\pt2_blep.c" />
     <ClCompile Include="..\..\src\pt2_bmp.c" />
+    <ClCompile Include="..\..\src\pt2_chordmaker.c" />
     <ClCompile Include="..\..\src\pt2_config.c" />
     <ClCompile Include="..\..\src\pt2_diskop.c" />
     <ClCompile Include="..\..\src\pt2_edit.c" />
     <ClCompile Include="..\..\src\pt2_helpers.c" />
     <ClCompile Include="..\..\src\pt2_keyboard.c" />
+    <ClCompile Include="..\..\src\pt2_ledfilter.c" />
     <ClCompile Include="..\..\src\pt2_main.c" />
     <ClCompile Include="..\..\src\pt2_mod2wav.c" />
     <ClCompile Include="..\..\src\pt2_module_loader.c" />
+    <ClCompile Include="..\..\src\pt2_rcfilter.c" />
     <ClCompile Include="..\..\src\pt2_replayer.c" />
     <ClCompile Include="..\..\src\pt2_module_saver.c" />
     <ClCompile Include="..\..\src\pt2_mouse.c" />
@@ -306,12 +312,12 @@
     <ClCompile Include="..\..\src\pt2_palette.c" />
     <ClCompile Include="..\..\src\pt2_pat2smp.c" />
     <ClCompile Include="..\..\src\pt2_pattern_viewer.c" />
+    <ClCompile Include="..\..\src\pt2_downsample2x.c" />
     <ClCompile Include="..\..\src\pt2_sample_loader.c" />
     <ClCompile Include="..\..\src\pt2_sampler.c" />
     <ClCompile Include="..\..\src\pt2_sample_saver.c" />
     <ClCompile Include="..\..\src\pt2_sampling.c" />
     <ClCompile Include="..\..\src\pt2_scopes.c" />
-    <ClCompile Include="..\..\src\pt2_sinc.c" />
     <ClCompile Include="..\..\src\pt2_structs.c" />
     <ClCompile Include="..\..\src\pt2_sync.c" />
     <ClCompile Include="..\..\src\pt2_tables.c" />
--- a/vs2019_project/pt2-clone/pt2-clone.vcxproj.filters
+++ b/vs2019_project/pt2-clone/pt2-clone.vcxproj.filters
@@ -90,9 +90,18 @@
     <ClInclude Include="..\..\src\pt2_sync.h">
       <Filter>headers</Filter>
     </ClInclude>
-    <ClInclude Include="..\..\src\pt2_sinc.h">
+    <ClInclude Include="..\..\src\pt2_rcfilter.h">
       <Filter>headers</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\src\pt2_ledfilter.h">
+      <Filter>headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\pt2_chordmaker.h">
+      <Filter>headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\pt2_downsamplers2x.h">
+      <Filter>headers</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="..\..\src\pt2_audio.c" />
@@ -177,7 +186,10 @@
     <ClCompile Include="..\..\src\pt2_replayer.c" />
     <ClCompile Include="..\..\src\pt2_sampling.c" />
     <ClCompile Include="..\..\src\pt2_sync.c" />
-    <ClCompile Include="..\..\src\pt2_sinc.c" />
+    <ClCompile Include="..\..\src\pt2_rcfilter.c" />
+    <ClCompile Include="..\..\src\pt2_ledfilter.c" />
+    <ClCompile Include="..\..\src\pt2_chordmaker.c" />
+    <ClCompile Include="..\..\src\pt2_downsample2x.c" />
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="..\..\src\pt2-clone.rc" />