shithub: pt2-clone

Download patch

ref: 7442f4167c59bc041ba46fd0b0fee592a5de282f
parent: 9fca319758c1194a953ad582a9c55e0a0f873ee4
author: Olav Sørensen <[email protected]>
date: Sun Apr 19 14:10:48 EDT 2020

Pushed v1.10 code

- Bugfix: note(s) displayed in SAMPLER (resample note) and Edit Op. screen #4
  were wrong.
- After a long talk with aciddose I have found out that I did several things
  wrong in the Amiga filtering. I have now removed the filter cutoff tweaking,
  gotten a slightly improved low-pass/high-pass routine, and also replaced the
  naive "LED" filter implementation with another one that is slightly more
  accurate (but still not perfect).

--- a/release/macos/protracker.ini
+++ b/release/macos/protracker.ini
@@ -180,10 +180,9 @@
 ; Audio output frequency
 ;        Syntax: Number, in hertz
 ; Default value: 48000
-;       Comment: Ranges from 32000 to 96000.
+;       Comment: Ranges from 44100 to 192000.
 ;         Also sets the playback frequency for WAVs made with MOD2WAV.
-;         Note to coders: Don't allow lower numbers than 32000, it will
-;           break the BLEP synthesis.
+;
 FREQUENCY=48000
 
 ; Audio buffer size
--- a/release/other/protracker.ini
+++ b/release/other/protracker.ini
@@ -180,10 +180,9 @@
 ; Audio output frequency
 ;        Syntax: Number, in hertz
 ; Default value: 48000
-;       Comment: Ranges from 32000 to 96000.
+;       Comment: Ranges from 44100 to 192000.
 ;         Also sets the playback frequency for WAVs made with MOD2WAV.
-;         Note to coders: Don't allow lower numbers than 32000, it will
-;           break the BLEP synthesis.
+;
 FREQUENCY=48000
 
 ; Audio buffer size
--- a/release/win32/protracker.ini
+++ b/release/win32/protracker.ini
@@ -180,10 +180,9 @@
 ; Audio output frequency
 ;        Syntax: Number, in hertz
 ; Default value: 48000
-;       Comment: Ranges from 32000 to 96000.
+;       Comment: Ranges from 44100 to 192000.
 ;         Also sets the playback frequency for WAVs made with MOD2WAV.
-;         Note to coders: Don't allow lower numbers than 32000, it will
-;           break the BLEP synthesis.
+;
 FREQUENCY=48000
 
 ; Audio buffer size
--- a/release/win64/protracker.ini
+++ b/release/win64/protracker.ini
@@ -180,10 +180,9 @@
 ; Audio output frequency
 ;        Syntax: Number, in hertz
 ; Default value: 48000
-;       Comment: Ranges from 32000 to 96000.
+;       Comment: Ranges from 44100 to 192000.
 ;         Also sets the playback frequency for WAVs made with MOD2WAV.
-;         Note to coders: Don't allow lower numbers than 32000, it will
-;           break the BLEP synthesis.
+;
 FREQUENCY=48000
 
 ; Audio buffer size
--- a/src/pt2_audio.c
+++ b/src/pt2_audio.c
@@ -1,5 +1,4 @@
-/* The "LED" filter and BLEP routines were coded by aciddose.
-** Low-pass filter is based on https://bel.fi/alankila/modguide/interpolate.txt */
+// the audio filters and BLEP synthesis were coded by aciddose
 
 // for finding memory leaks in debug mode with Visual Studio 
 #if defined _DEBUG && defined _MSC_VER
@@ -36,14 +35,10 @@
 
 typedef struct ledFilter_t
 {
-	double dLed[4];
+	double buffer[4];
+	double c, ci, feedback, bg, cg, c2;
 } ledFilter_t;
 
-typedef struct ledFilterCoeff_t
-{
-	double dLed, dLedFb;
-} ledFilterCoeff_t;
-
 typedef struct voice_t
 {
 	volatile bool active;
@@ -56,13 +51,12 @@
 static int8_t defStereoSep;
 static bool amigaPanFlag, wavRenderingDone;
 static uint16_t ch1Pan, ch2Pan, ch3Pan, ch4Pan;
-static int32_t oldPeriod, sampleCounter, maxSamplesToMix, randSeed = INITIAL_DITHER_SEED;
+static int32_t oldPeriod, sampleCounter, randSeed = INITIAL_DITHER_SEED;
 static uint32_t oldScopeDelta;
 static double *dMixBufferL, *dMixBufferR, *dMixBufferLUnaligned, *dMixBufferRUnaligned, dOldVoiceDelta, dOldVoiceDeltaMul;
 static double dPrngStateL, dPrngStateR;
 static blep_t blep[AMIGA_VOICES], blepVol[AMIGA_VOICES];
-static lossyIntegrator_t filterLo, filterHi;
-static ledFilterCoeff_t filterLEDC;
+static rcFilter_t filterLo, filterHi;
 static ledFilter_t filterLED;
 static paulaVoice_t paula[AMIGA_VOICES];
 static SDL_AudioDeviceID dev;
@@ -77,16 +71,13 @@
 
 static uint16_t bpm2SmpsPerTick(uint32_t bpm, uint32_t audioFreq)
 {
-	uint32_t ciaVal;
-	double dFreqMul;
-
 	if (bpm == 0)
 		return 0;
 
-	ciaVal = (uint32_t)(1773447 / bpm); // yes, PT truncates here
-	dFreqMul = ciaVal * (1.0 / CIA_PAL_CLK);
+	const uint32_t ciaVal = (uint32_t)(1773447 / bpm); // yes, PT truncates here
+	const double dCiaHz = (double)CIA_PAL_CLK / ciaVal;
 
-	int32_t smpsPerTick = (int32_t)((audioFreq * dFreqMul) + 0.5);
+	int32_t smpsPerTick = (int32_t)((audioFreq / dCiaHz) + 0.5);
 	return (uint16_t)smpsPerTick;
 }
 
@@ -94,133 +85,184 @@
 {
 	for (uint32_t i = 32; i <= 255; i++)
 	{
-		audio.bpmTab[i-32] = bpm2SmpsPerTick(i, audio.audioFreq);
+		audio.bpmTab[i-32] = bpm2SmpsPerTick(i, audio.outputRate);
 		audio.bpmTab28kHz[i-32] = bpm2SmpsPerTick(i, 28836);
 		audio.bpmTab22kHz[i-32] = bpm2SmpsPerTick(i, 22168);
 	}
 }
 
+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)
 {
 	editor.useLEDFilter = state;
-
 	if (editor.useLEDFilter)
-		filterFlags |=  FILTER_LED_ENABLED;
+	{
+		clearLEDFilterState();
+		filterFlags |= FILTER_LED_ENABLED;
+	}
 	else
+	{
 		filterFlags &= ~FILTER_LED_ENABLED;
+	}
 }
 
 void toggleLEDFilter(void)
 {
 	editor.useLEDFilter ^= 1;
-
 	if (editor.useLEDFilter)
-		filterFlags |=  FILTER_LED_ENABLED;
+	{
+		clearLEDFilterState();
+		filterFlags |= FILTER_LED_ENABLED;
+	}
 	else
+	{
 		filterFlags &= ~FILTER_LED_ENABLED;
+	}
 }
 
-static void calcCoeffLED(double dSr, double dHz, ledFilterCoeff_t *filter)
+/* 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 double dFb = 0.125;
+	/* Coefficient from:
+	**   0.0 to  inf (linear)
+	**  -1.0 to -inf (linear)
+	*/
+	return x / (x + coefficient) * (coefficient + 1.0);
+}
 
-#ifndef NO_FILTER_FINETUNING
-	/* 8bitbubsy: makes the filter curve sound (and look) much closer to the real deal.
-	** This has been tested against both an A500 and A1200 (real units).
+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.
 	*/
-	dFb *= 0.62;
-#endif
+	const double c = (hz < (sr / 2.0)) ? tan((M_PI * hz) / sr) : 1.0;
+	const double g = 1.0 / (1.0 + c);
 
-	if (dHz < dSr/2.0)
-		filter->dLed = ((2.0 * M_PI) * dHz) / dSr;
-	else
-		filter->dLed = 1.0;
+	// 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->dLedFb = dFb + (dFb / (1.0 - filter->dLed)); // Q ~= 1/sqrt(2) (Butterworth)
+	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 calcCoeffLossyIntegrator(double dSr, double dHz, lossyIntegrator_t *filter)
+static inline void LEDFilter(ledFilter_t *f, const double *in, double *out)
 {
-	double dOmega = ((2.0 * M_PI) * dHz) / dSr;
-	filter->b0 = 1.0 / (1.0 + (1.0 / dOmega));
-	filter->b1 = 1.0 - filter->b0;
+	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;
 }
 
-static void clearLossyIntegrator(lossyIntegrator_t *filter)
+void calcRCFilterCoeffs(double dSr, double dHz, rcFilter_t *f)
 {
-	filter->dBuffer[0] = 0.0; // L
-	filter->dBuffer[1] = 0.0; // R
+	f->c = tan((M_PI * dHz) / dSr);
+	f->c2 = f->c * 2.0;
+	f->g = 1.0 / (1.0 + f->c);
+	f->cg = f->c * f->g;
 }
 
-static void clearLEDFilter(ledFilter_t *filter)
+void clearRCFilterState(rcFilter_t *f)
 {
-	filter->dLed[0] = 0.0; // L
-	filter->dLed[1] = 0.0;
-	filter->dLed[2] = 0.0; // R
-	filter->dLed[3] = 0.0;
+	f->buffer[0] = 0.0; // left channel
+	f->buffer[1] = 0.0; // right channel
 }
 
-static inline void lossyIntegratorLED(ledFilterCoeff_t filterC, ledFilter_t *filter, double *dIn, double *dOut)
+// 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)
 {
-	// left channel "LED" filter
-	filter->dLed[0] += filterC.dLed * (dIn[0] - filter->dLed[0])
-		+ filterC.dLedFb * (filter->dLed[0] - filter->dLed[1]) + DENORMAL_OFFSET;
-	filter->dLed[1] += filterC.dLed * (filter->dLed[0] - filter->dLed[1]) + DENORMAL_OFFSET;
-	dOut[0] = filter->dLed[1];
-
-	// right channel "LED" filter
-	filter->dLed[2] += filterC.dLed * (dIn[1] - filter->dLed[2])
-		+ filterC.dLedFb * (filter->dLed[2] - filter->dLed[3]) + DENORMAL_OFFSET;
-	filter->dLed[3] += filterC.dLed * (filter->dLed[2] - filter->dLed[3]) + DENORMAL_OFFSET;
-	dOut[1] = filter->dLed[3];
+	return buffer * f->g + input_0 * f->cg + input_1 * (1.0 - f->cg);
 }
 
-void lossyIntegrator(lossyIntegrator_t *filter, double *dIn, double *dOut)
+void RCLowPassFilter(rcFilter_t *f, const double *in, double *out)
 {
-	/* Low-pass filter implementation taken from:
-	** https://bel.fi/alankila/modguide/interpolate.txt
-	**
-	** This implementation has a less smooth cutoff curve compared to the old one, so it's
-	** maybe not the best. However, I stick to this one because it has a higher gain
-	** at the end of the curve (closer to real tested Amiga 500). It also sounds much closer when
-	** comparing whitenoise on an A500.
-	*/
+	double output;
 
-	// left channel low-pass
-	filter->dBuffer[0] = (filter->b0 * dIn[0]) + (filter->b1 * filter->dBuffer[0]) + DENORMAL_OFFSET;
-	dOut[0] = filter->dBuffer[0];
+	// 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 low-pass
-	filter->dBuffer[1] = (filter->b0 * dIn[1]) + (filter->b1 * filter->dBuffer[1]) + DENORMAL_OFFSET;
-	dOut[1] = filter->dBuffer[1];
+	// 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 lossyIntegratorMono(lossyIntegrator_t *filter, double dIn, double *dOut)
+void RCHighPassFilter(rcFilter_t *f, const double *in, double *out)
 {
-	filter->dBuffer[0] = (filter->b0 * dIn) + (filter->b1 * filter->dBuffer[0]) + DENORMAL_OFFSET;
-	*dOut = filter->dBuffer[0];
-}
+	double low[2];
 
-void lossyIntegratorHighPass(lossyIntegrator_t *filter, double *dIn, double *dOut)
-{
-	double dLow[2];
+	RCLowPassFilter(f, in, low);
 
-	lossyIntegrator(filter, dIn, dLow);
+	out[0] = in[0] - low[0]; // left channel high-pass
+	out[1] = in[1] - low[1]; // right channel high-pass
+}
 
-	dOut[0] = dIn[0] - dLow[0]; // left channel high-pass
-	dOut[1] = dIn[1] - dLow[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 lossyIntegratorHighPassMono(lossyIntegrator_t *filter, double dIn, double *dOut)
+void RCHighPassFilterMono(rcFilter_t *f, const double in, double *out)
 {
-	double dLow;
+	double low;
 
-	lossyIntegratorMono(filter, dIn, &dLow);
-
-	*dOut = dIn - dLow;
+	RCLowPassFilterMono(f, in, &low);
+	*out = in - low; // high-pass
 }
 
-/* adejr/aciddose: these sin/cos approximations both use a 0..1
+/* 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)
@@ -275,7 +317,7 @@
 {
 	double dPan;
 
-	/* proper 'normalized' equal-power panning is (assuming pan left to right):
+	/* 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);
 	*/
@@ -318,9 +360,9 @@
 	for (uint8_t i = 0; i < AMIGA_VOICES; i++)
 		mixerKillVoice(i);
 
-	clearLossyIntegrator(&filterLo);
-	clearLossyIntegrator(&filterHi);
-	clearLEDFilter(&filterLED);
+	clearRCFilterState(&filterLo);
+	clearRCFilterState(&filterHi);
+	clearLEDFilterState();
 
 	resetAudioDithering();
 
@@ -346,7 +388,7 @@
 	v = &paula[ch];
 
 	if (period == 0)
-		realPeriod = 65536; // confirmed behavior on real Amiga
+		realPeriod = 1+65535; // confirmed behavior on real Amiga
 	else if (period < 113)
 		realPeriod = 113; // confirmed behavior on real Amiga
 	else
@@ -494,9 +536,9 @@
 void toggleA500Filters(void)
 {
 	lockAudio();
-	clearLossyIntegrator(&filterLo);
-	clearLossyIntegrator(&filterHi);
-	clearLEDFilter(&filterLED);
+	clearRCFilterState(&filterLo);
+	clearRCFilterState(&filterHi);
+	clearLEDFilterState();
 	unlockAudio();
 
 	if (filterFlags & FILTER_A500)
@@ -676,10 +718,10 @@
 	dOut[0] = dMixBufferL[i];
 	dOut[1] = dMixBufferR[i];
 
-	// don't process any low-pass filter since the cut-off is around 28-31kHz on A1200
+	// don't process any low-pass filter since the cut-off is around 34kHz on A1200
 
 	// process high-pass filter
-	lossyIntegratorHighPass(&filterHi, dOut, dOut);
+	RCHighPassFilter(&filterHi, dOut, dOut);
 
 	// normalize and flip phase (A500/A1200 has an inverted audio signal)
 	dOut[0] *= -(INT16_MAX / AMIGA_VOICES);
@@ -710,13 +752,13 @@
 	dOut[0] = dMixBufferL[i];
 	dOut[1] = dMixBufferR[i];
 
-	// don't process any low-pass filter since the cut-off is around 28-31kHz on A1200
+	// don't process any low-pass filter since the cut-off is around 34kHz on A1200
 
 	// process "LED" filter
-	lossyIntegratorLED(filterLEDC, &filterLED, dOut, dOut);
+	LEDFilter(&filterLED, dOut, dOut);
 
 	// process high-pass filter
-	lossyIntegratorHighPass(&filterHi, dOut, dOut);
+	RCHighPassFilter(&filterHi, dOut, dOut);
 
 	// normalize and flip phase (A500/A1200 has an inverted audio signal)
 	dOut[0] *= -(INT16_MAX / AMIGA_VOICES);
@@ -748,14 +790,10 @@
 	dOut[1] = dMixBufferR[i];
 
 	// process low-pass filter
-	lossyIntegrator(&filterLo, dOut, dOut);
+	RCLowPassFilter(&filterLo, dOut, dOut);
 
-	// process "LED" filter
-	if (filterFlags & FILTER_LED_ENABLED)
-		lossyIntegratorLED(filterLEDC, &filterLED, dOut, dOut);
-
 	// process high-pass filter
-	lossyIntegratorHighPass(&filterHi, dOut, dOut);
+	RCHighPassFilter(&filterHi, dOut, dOut);
 
 	dOut[0] *= -(INT16_MAX / AMIGA_VOICES);
 	dOut[1] *= -(INT16_MAX / AMIGA_VOICES);
@@ -786,13 +824,13 @@
 	dOut[1] = dMixBufferR[i];
 
 	// process low-pass filter
-	lossyIntegrator(&filterLo, dOut, dOut);
+	RCLowPassFilter(&filterLo, dOut, dOut);
 
 	// process "LED" filter
-	lossyIntegratorLED(filterLEDC, &filterLED, dOut, dOut);
+	LEDFilter(&filterLED, dOut, dOut);
 
 	// process high-pass filter
-	lossyIntegratorHighPass(&filterHi, dOut, dOut);
+	RCHighPassFilter(&filterHi, dOut, dOut);
 
 	dOut[0] *= -(INT16_MAX / AMIGA_VOICES);
 	dOut[1] *= -(INT16_MAX / AMIGA_VOICES);
@@ -899,8 +937,6 @@
 	int16_t *out;
 	int32_t sampleBlock, samplesTodo;
 
-	(void)userdata;
-
 	if (audio.forceMixerOff) // during MOD2WAV
 	{
 		memset(stream, 0, len);
@@ -929,12 +965,12 @@
 			sampleCounter = samplesPerTick;
 		}
 	}
+
+	(void)userdata;
 }
 
 static void calculateFilterCoeffs(void)
 {
-	double dCutOffHz;
-
 	/* Amiga 500 filter emulation, by aciddose
 	**
 	** First comes a static low-pass 6dB formed by the supply current
@@ -971,34 +1007,28 @@
 	** Under spice simulation the circuit yields -3dB = 5.2Hz.
 	*/
 
-	// Amiga 500 rev6 RC low-pass filter:
-	const double dLp_R = 360.0; // R321 - 360 ohm resistor
-	const double dLp_C = 1e-7;  // C321 - 0.1uF capacitor
-	dCutOffHz = 1.0 / ((2.0 * M_PI) * dLp_R * dLp_C); // ~4420.97Hz
-#ifndef NO_FILTER_FINETUNING
-	dCutOffHz += 580.0; // 8bitbubsy: finetuning to better match A500 low-pass testing
-#endif
-	calcCoeffLossyIntegrator(audio.dAudioFreq, dCutOffHz, &filterLo);
+	double R, C, R1, R2, C1, C2, fc, fb;
 
-	// Amiga Sallen-Key "LED" filter:
-	const double dLed_R1 = 10000.0; // R322 - 10K ohm resistor
-	const double dLed_R2 = 10000.0; // R323 - 10K ohm resistor
-	const double dLed_C1 = 6.8e-9;  // C322 - 6800pF capacitor
-	const double dLed_C2 = 3.9e-9;  // C323 - 3900pF capacitor
-	dCutOffHz = 1.0 / ((2.0 * M_PI) * sqrt(dLed_R1 * dLed_R2 * dLed_C1 * dLed_C2)); // ~3090.53Hz
-#ifndef NO_FILTER_FINETUNING
-	dCutOffHz -= 300.0; // 8bitbubsy: finetuning to better match A500 & A1200 "LED" filter testing
-#endif
-	calcCoeffLED(audio.dAudioFreq, dCutOffHz, &filterLEDC);
+	// A500 one-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); // ~4420.97Hz
+	calcRCFilterCoeffs(audio.outputRate, fc, &filterLo);
 
-	// Amiga RC high-pass filter:
-	const double dHp_R = 1000.0 + 390.0; // R324 - 1K ohm resistor + R325 - 390 ohm resistor
-	const double dHp_C = 2.2e-5; // C334 - 22uF capacitor
-	dCutOffHz = 1.0 / ((2.0 * M_PI) * dHp_R * dHp_C); // ~5.20Hz
-#ifndef NO_FILTER_FINETUNING
-	dCutOffHz += 1.5; // 8bitbubsy: finetuning to better match A500 & A1200 high-pass testing
-#endif
-	calcCoeffLossyIntegrator(audio.dAudioFreq, dCutOffHz, &filterHi);
+	// A500/A1200 Sallen-Key filter ("LED"):
+	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)); // ~3090.53Hz
+	fb = 0.125; // Fb = 0.125 : Q ~= 1/sqrt(2) (Butterworth)
+	calcLEDFilterCoeffs(audio.outputRate, fc, fb, &filterLED);
+
+	// A500/A1200 one-pole 6db/oct static RC high-pass filter:
+	R = 1000.0 + 390.0;  // R324 (1K ohm resistor) + R325 (390 ohm resistor)
+	C = 2.2e-5;          // C334 (22uF capacitor) (+ C324 (0.33uF capacitor) if A500)
+	fc = 1.0 / (2.0 * M_PI * R * C); // ~5.20Hz
+	calcRCFilterCoeffs(audio.outputRate, fc, &filterHi);
 }
 
 void mixerCalcVoicePans(uint8_t stereoSeparation)
@@ -1021,11 +1051,11 @@
 	SDL_AudioSpec want, have;
 
 	want.freq = config.soundFrequency;
+	want.samples = config.soundBufferSize;
 	want.format = AUDIO_S16;
 	want.channels = 2;
 	want.callback = audioCallback;
 	want.userdata = NULL;
-	want.samples = config.soundBufferSize;
 
 	dev = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0);
 	if (dev == 0)
@@ -1034,9 +1064,9 @@
 		return false;
 	}
 
-	if (have.freq < 32000) // lower than this is not safe for one-step mixer w/ BLEP
+	if (have.freq < 32000) // lower than this is not safe for the BLEP synthesis in the mixer
 	{
-		showErrorMsgBox("Unable to open audio: The audio output rate couldn't be used!");
+		showErrorMsgBox("Unable to open audio: An audio rate below 32kHz can't be used!");
 		return false;
 	}
 
@@ -1046,13 +1076,17 @@
 		return false;
 	}
 
-	maxSamplesToMix = (int32_t)ceil((have.freq * 2.5) / 32.0);
+	audio.outputRate = have.freq;
+	audio.audioBufferSize = have.samples;
+	audio.dPeriodToDeltaDiv = (double)PAULA_PAL_CLK / audio.outputRate;
 
+	generateBpmTables();
+	const int32_t maxSamplesToMix = audio.bpmTab[0]; // BPM 32
+
 	dMixBufferLUnaligned = (double *)MALLOC_PAD(maxSamplesToMix * sizeof (double), 256);
 	dMixBufferRUnaligned = (double *)MALLOC_PAD(maxSamplesToMix * sizeof (double), 256);
+	editor.mod2WavBuffer = (int16_t *)malloc(maxSamplesToMix * sizeof (int16_t));
 
-	editor.mod2WavBuffer = (int16_t *)malloc(sizeof (int16_t) * maxSamplesToMix);
-
 	if (dMixBufferLUnaligned == NULL || dMixBufferRUnaligned == NULL || editor.mod2WavBuffer == NULL)
 	{
 		showErrorMsgBox("Out of memory!");
@@ -1062,19 +1096,11 @@
 	dMixBufferL = (double *)ALIGN_PTR(dMixBufferLUnaligned, 256);
 	dMixBufferR = (double *)ALIGN_PTR(dMixBufferRUnaligned, 256);
 
-	audio.audioBufferSize = have.samples;
-	config.soundFrequency = have.freq;
-	audio.audioFreq = config.soundFrequency;
-	audio.dAudioFreq = (double)config.soundFrequency;
-	audio.dPeriodToDeltaDiv = PAULA_PAL_CLK / audio.dAudioFreq;
-
 	mixerCalcVoicePans(config.stereoSeparation);
 	defStereoSep = config.stereoSeparation;
 
 	filterFlags = config.a500LowPassFilter ? FILTER_A500 : 0;
-
 	calculateFilterCoeffs();
-	generateBpmTables();
 
 	samplesPerTick = 0;
 	sampleCounter = 0;
@@ -1149,8 +1175,6 @@
 	while (smpCounter > 0)
 	{
 		samplesToMix = smpCounter;
-		if (samplesToMix > maxSamplesToMix)
-			samplesToMix = maxSamplesToMix;
 
 		outputAudio(outStream, samplesToMix);
 		outStream += (uint32_t)samplesToMix * 2;
@@ -1210,7 +1234,7 @@
 	wavHeader.subchunk1Size = 16;
 	wavHeader.audioFormat = 1;
 	wavHeader.numChannels = 2;
-	wavHeader.sampleRate = audio.audioFreq;
+	wavHeader.sampleRate = audio.outputRate;
 	wavHeader.bitsPerSample = 16;
 	wavHeader.byteRate = wavHeader.sampleRate * wavHeader.numChannels * (wavHeader.bitsPerSample / 8);
 	wavHeader.blockAlign = wavHeader.numChannels * (wavHeader.bitsPerSample / 8);
@@ -1353,30 +1377,23 @@
 				{
 					n_pattpos[ch] = modRow;
 				}
-				else
+				else if (n_loopcount[ch] == 0)
 				{
-					// this is so ugly
-					if (n_loopcount[ch] == 0)
-					{
-						n_loopcount[ch] = pos;
+					n_loopcount[ch] = pos;
 
-						pBreakPosition = n_pattpos[ch];
-						pBreakFlag = true;
+					pBreakPosition = n_pattpos[ch];
+					pBreakFlag = true;
 
-						for (pos = pBreakPosition; pos <= modRow; pos++)
-							editor.rowVisitTable[(modOrder * MOD_ROWS) + pos] = false;
-					}
-					else
-					{
-						if (--n_loopcount[ch])
-						{
-							pBreakPosition = n_pattpos[ch];
-							pBreakFlag = true;
+					for (pos = pBreakPosition; pos <= modRow; pos++)
+						editor.rowVisitTable[(modOrder * MOD_ROWS) + pos] = false;
+				}
+				else if (--n_loopcount[ch])
+				{
+					pBreakPosition = n_pattpos[ch];
+					pBreakFlag = true;
 
-							for (pos = pBreakPosition; pos <= modRow; pos++)
-								editor.rowVisitTable[(modOrder * MOD_ROWS) + pos] = false;
-						}
-					}
+					for (pos = pBreakPosition; pos <= modRow; pos++)
+						editor.rowVisitTable[(modOrder * MOD_ROWS) + pos] = false;
 				}
 			}
 		}
--- a/src/pt2_audio.h
+++ b/src/pt2_audio.h
@@ -6,20 +6,23 @@
 #include <stdint.h>
 #include <stdbool.h>
 
+// adding this forces the FPU to enter slow mode
 #define DENORMAL_OFFSET 1e-10
 
-typedef struct lossyIntegrator_t
+typedef struct rcFilter_t
 {
-	double dBuffer[2], b0, b1;
-} lossyIntegrator_t;
+	double buffer[2];
+	double c, c2, g, cg;
+} rcFilter_t;
 
 void resetCachedMixerPeriod(void);
 void resetAudioDithering(void);
-void calcCoeffLossyIntegrator(double dSr, double dHz, lossyIntegrator_t *filter);
-void lossyIntegrator(lossyIntegrator_t *filter, double *dIn, double *dOut);
-void lossyIntegratorMono(lossyIntegrator_t *filter, double dIn, double *dOut);
-void lossyIntegratorHighPass(lossyIntegrator_t *filter, double *dIn, double *dOut);
-void lossyIntegratorHighPassMono(lossyIntegrator_t *filter, double dIn, double *dOut);
+void 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);
--- a/src/pt2_config.c
+++ b/src/pt2_config.c
@@ -59,7 +59,7 @@
 	config.soundBufferSize = 1024;
 	config.autoCloseDiskOp = true;
 	config.vsyncOff = false;
-	config.hwMouse = false;
+	config.hwMouse = true;
 	config.sampleLowpass = true;
 
 #ifndef _WIN32
--- a/src/pt2_edit.c
+++ b/src/pt2_edit.c
@@ -1380,7 +1380,7 @@
 
 void trackOctaUp(bool sampleAllFlag, uint8_t from, uint8_t to)
 {
-	bool noteDeleted;
+	bool noteDeleted, noteChanged;
 	uint8_t j;
 	note_t *noteSrc;
 
@@ -1391,6 +1391,8 @@
 		to = j;
 	}
 
+	noteChanged = false;
+
 	saveUndo();
 	for (uint8_t i = from; i <= to; i++)
 	{
@@ -1401,6 +1403,8 @@
 
 		if (noteSrc->period)
 		{
+			uint16_t oldPeriod = noteSrc->period;
+
 			// period -> note
 			for (j = 0; j < 36; j++)
 			{
@@ -1422,11 +1426,17 @@
 
 			if (!noteDeleted)
 				noteSrc->period = periodTable[j];
+
+			if (noteSrc->period != oldPeriod)
+				noteChanged = true;
 		}
 	}
 
-	updateWindowTitle(MOD_IS_MODIFIED);
-	editor.ui.updatePatternData = true;
+	if (noteChanged)
+	{
+		updateWindowTitle(MOD_IS_MODIFIED);
+		editor.ui.updatePatternData = true;
+	}
 }
 
 void trackOctaDown(bool sampleAllFlag, uint8_t from, uint8_t to)
--- a/src/pt2_filters.c
+++ /dev/null
@@ -1,146 +1,0 @@
-/* These are second variants of low-pass/high-pass filters that are better than
-** the ones used in the main audio mixer. The reason we use a different ones for
-** the main audio mixer is because it makes it sound closer to real Amigas.
-**
-** These ones are used for low-pass filtering when loading samples w/ 2x downsampling.
-*/
-
-#include <stdio.h>
-#include <stdbool.h>
-#include <math.h>
-#include "pt2_audio.h" // DENORMAL_OFFSET constant
-#include "pt2_helpers.h"
-
-typedef struct filterState_t
-{
-	double dBuffer, b0, b1;
-} filterState_t;
-
-static void calcFilterCoeffs(double dSr, double dHz, filterState_t *filter)
-{
-	filter->b0 = tan((M_PI * dHz) / dSr);
-	filter->b1 = 1.0 / (1.0 + filter->b0);
-}
-
-static double doLowpass(filterState_t *filter, double dIn)
-{
-	double dOutput;
-
-	dOutput = (filter->b0 * dIn + filter->dBuffer) * filter->b1;
-	filter->dBuffer = filter->b0 * (dIn - dOutput) + dOutput + DENORMAL_OFFSET;
-
-	return dOutput;
-}
-
-bool lowPassSample8Bit(int8_t *buffer, int32_t length, int32_t sampleFrequency, double cutoff)
-{
-	filterState_t filter;
-
-	if (buffer == NULL || length == 0 || cutoff == 0.0)
-		return false;
-
-	calcFilterCoeffs(sampleFrequency, cutoff, &filter);
-
-	filter.dBuffer = 0.0;
-	for (int32_t i = 0; i < length; i++)
-	{
-		int32_t sample;
-		sample = (int32_t)doLowpass(&filter, buffer[i]);
-		buffer[i] = (int8_t)CLAMP(sample, INT8_MIN, INT8_MAX);
-	}
-	
-	return true;
-}
-
-bool lowPassSample8BitUnsigned(uint8_t *buffer, int32_t length, int32_t sampleFrequency, double cutoff)
-{
-	filterState_t filter;
-
-	if (buffer == NULL || length == 0 || cutoff == 0.0)
-		return false;
-
-	calcFilterCoeffs(sampleFrequency, cutoff, &filter);
-
-	filter.dBuffer = 0.0;
-	for (int32_t i = 0; i < length; i++)
-	{
-		int32_t sample;
-		sample = (int32_t)doLowpass(&filter, buffer[i] - 128);
-		sample = CLAMP(sample, INT8_MIN, INT8_MAX);
-		buffer[i] = (uint8_t)(sample + 128);
-	}
-
-	return true;
-}
-
-bool lowPassSample16Bit(int16_t *buffer, int32_t length, int32_t sampleFrequency, double cutoff)
-{
-	filterState_t filter;
-
-	if (buffer == NULL || length == 0 || cutoff == 0.0)
-		return false;
-
-	calcFilterCoeffs(sampleFrequency, cutoff, &filter);
-
-	filter.dBuffer = 0.0;
-	for (int32_t i = 0; i < length; i++)
-	{
-		int32_t sample;
-		sample = (int32_t)doLowpass(&filter, buffer[i]);
-		buffer[i] = (int16_t)CLAMP(sample, INT16_MIN, INT16_MAX);
-	}
-
-	return true;
-}
-
-bool lowPassSample32Bit(int32_t *buffer, int32_t length, int32_t sampleFrequency, double cutoff)
-{
-	filterState_t filter;
-
-	if (buffer == NULL || length == 0 || cutoff == 0.0)
-		return false;
-
-	calcFilterCoeffs(sampleFrequency, cutoff, &filter);
-
-	filter.dBuffer = 0.0;
-	for (int32_t i = 0; i < length; i++)
-	{
-		int64_t sample;
-		sample = (int64_t)doLowpass(&filter, buffer[i]);
-		buffer[i] = (int32_t)CLAMP(sample, INT32_MIN, INT32_MAX);
-	}
-
-	return true;
-}
-
-bool lowPassSampleFloat(float *buffer, int32_t length, int32_t sampleFrequency, double cutoff)
-{
-	filterState_t filter;
-
-	if (buffer == NULL || length == 0 || cutoff == 0.0)
-		return false;
-
-	calcFilterCoeffs(sampleFrequency, cutoff, &filter);
-
-	filter.dBuffer = 0.0;
-	for (int32_t i = 0; i < length; i++)
-		buffer[i] = (float)doLowpass(&filter, buffer[i]);
-
-	return true;
-}
-
-bool lowPassSampleDouble(double *buffer, int32_t length, int32_t sampleFrequency, double cutoff)
-{
-	filterState_t filter;
-
-	if (buffer == NULL || length == 0 || cutoff == 0.0)
-		return false;
-
-	calcFilterCoeffs(sampleFrequency, cutoff, &filter);
-	
-	filter.dBuffer = 0.0;
-	for (int32_t i = 0; i < length; i++)
-		buffer[i] = doLowpass(&filter, buffer[i]);
-
-	return true;
-}
--- a/src/pt2_filters.h
+++ /dev/null
@@ -1,31 +1,0 @@
-/* These are second variants of low-pass/high-pass filters that are better than
-** the ones used in the main audio mixer. The reason we use a different one for
-** the main audio mixer is because it makes it sound closer to real Amigas.
-**
-** These ones are used for filtering samples when loading samples, or with the
-** FILTERS toolbox in the Sample Editor.
-*/
-
-#pragma once
-
-#include <stdio.h>
-#include <stdbool.h>
-
-/* 8bitbubsy: Before we downsample a loaded WAV/AIFF (>22kHz) sample by 2x, we low-pass
-** filter it.
-**
-*** I think this value ought to be 4.0 (nyquist freq. / 2), but it cuts off too much in
-** my opinion! The improvement is only noticable on samples that has quite a bit of high
-** frequencies in them to begin with.
-**
-** This is probably not how to do it, so if someone with a bit more knowledge can do this
-** in a proper way without using an external resampler library, that would be neato!
-*/
-#define DOWNSAMPLE_CUTOFF_FACTOR 4.0
-
-bool lowPassSample8Bit(int8_t *buffer, int32_t length, int32_t sampleFrequency, double cutoff);
-bool lowPassSample8BitUnsigned(uint8_t *buffer, int32_t length, int32_t sampleFrequency, double cutoff);
-bool lowPassSample16Bit(int16_t *buffer, int32_t length, int32_t sampleFrequency, double cutoff);
-bool lowPassSample32Bit(int32_t *buffer, int32_t length, int32_t sampleFrequency, double cutoff);
-bool lowPassSampleFloat(float *buffer, int32_t length, int32_t sampleFrequency, double cutoff);
-bool lowPassSampleDouble(double *buffer, int32_t length, int32_t sampleFrequency, double cutoff);
--- a/src/pt2_header.h
+++ b/src/pt2_header.h
@@ -14,7 +14,7 @@
 #include "pt2_unicode.h"
 #include "pt2_palette.h"
 
-#define PROG_VER_STR "1.09"
+#define PROG_VER_STR "1.10"
 
 #ifdef _WIN32
 #define DIR_DELIMITER '\\'
@@ -292,8 +292,8 @@
 	volatile bool locked;
 	bool forceMixerOff;
 	uint16_t bpmTab[256-32], bpmTab28kHz[256-32], bpmTab22kHz[256-32];
-	uint32_t audioFreq, audioBufferSize;
-	double dAudioFreq, dPeriodToDeltaDiv;
+	uint32_t outputRate, audioBufferSize;
+	double dPeriodToDeltaDiv;
 } audio;
 
 struct keyb_t
--- a/src/pt2_sampleloader.c
+++ b/src/pt2_sampleloader.c
@@ -25,8 +25,9 @@
 #include "pt2_visuals.h"
 #include "pt2_helpers.h"
 #include "pt2_unicode.h"
-#include "pt2_filters.h"
 
+#define DOWNSAMPLE_CUTOFF_FACTOR 4.0
+
 enum
 {
 	WAV_FORMAT_PCM = 0x0001,
@@ -39,6 +40,145 @@
 static bool loadAIFFSample(UNICHAR *fileName, char *entryName, int8_t forceDownSampling);
 
 static bool loadedFileWasAIFF;
+
+static bool lowPassSample8Bit(int8_t *buffer, int32_t length, int32_t sampleFrequency, double cutoff)
+{
+	rcFilter_t filter;
+
+	if (buffer == NULL || length == 0 || cutoff == 0.0)
+		return false;
+
+	calcRCFilterCoeffs(sampleFrequency, cutoff, &filter);
+	clearRCFilterState(&filter);
+
+	for (int32_t i = 0; i < length; i++)
+	{
+		int32_t sample;
+		double dSample;
+
+		RCLowPassFilterMono(&filter, buffer[i], &dSample);
+		sample = (int32_t)dSample;
+
+		buffer[i] = (int8_t)CLAMP(sample, INT8_MIN, INT8_MAX);
+	}
+	
+	return true;
+}
+
+static bool lowPassSample8BitUnsigned(uint8_t *buffer, int32_t length, int32_t sampleFrequency, double cutoff)
+{
+	rcFilter_t filter;
+
+	if (buffer == NULL || length == 0 || cutoff == 0.0)
+		return false;
+
+	calcRCFilterCoeffs(sampleFrequency, cutoff, &filter);
+	clearRCFilterState(&filter);
+
+	for (int32_t i = 0; i < length; i++)
+	{
+		int32_t sample;
+		double dSample;
+
+		RCLowPassFilterMono(&filter, buffer[i] - 128, &dSample);
+		sample = (int32_t)dSample;
+
+		sample = CLAMP(sample, INT8_MIN, INT8_MAX);
+		buffer[i] = (uint8_t)(sample + 128);
+	}
+
+	return true;
+}
+
+static bool lowPassSample16Bit(int16_t *buffer, int32_t length, int32_t sampleFrequency, double cutoff)
+{
+	rcFilter_t filter;
+
+	if (buffer == NULL || length == 0 || cutoff == 0.0)
+		return false;
+
+	calcRCFilterCoeffs(sampleFrequency, cutoff, &filter);
+	clearRCFilterState(&filter);
+
+	for (int32_t i = 0; i < length; i++)
+	{
+		int32_t sample;
+		double dSample;
+
+		RCLowPassFilterMono(&filter, buffer[i], &dSample);
+		sample = (int32_t)dSample;
+
+		buffer[i] = (int16_t)CLAMP(sample, INT16_MIN, INT16_MAX);
+	}
+
+	return true;
+}
+
+static bool lowPassSample32Bit(int32_t *buffer, int32_t length, int32_t sampleFrequency, double cutoff)
+{
+	rcFilter_t filter;
+
+	if (buffer == NULL || length == 0 || cutoff == 0.0)
+		return false;
+
+	calcRCFilterCoeffs(sampleFrequency, cutoff, &filter);
+	clearRCFilterState(&filter);
+
+	for (int32_t i = 0; i < length; i++)
+	{
+		int64_t sample;
+		double dSample;
+
+		RCLowPassFilterMono(&filter, buffer[i], &dSample);
+		sample = (int32_t)dSample;
+
+		buffer[i] = (int32_t)CLAMP(sample, INT32_MIN, INT32_MAX);
+	}
+
+	return true;
+}
+
+static bool lowPassSampleFloat(float *buffer, int32_t length, int32_t sampleFrequency, double cutoff)
+{
+	rcFilter_t filter;
+
+	if (buffer == NULL || length == 0 || cutoff == 0.0)
+		return false;
+
+	calcRCFilterCoeffs(sampleFrequency, cutoff, &filter);
+	clearRCFilterState(&filter);
+
+	for (int32_t i = 0; i < length; i++)
+	{
+		double dSample;
+
+		RCLowPassFilterMono(&filter, buffer[i], &dSample);
+		buffer[i] = (float)dSample;
+	}
+
+	return true;
+}
+
+static bool lowPassSampleDouble(double *buffer, int32_t length, int32_t sampleFrequency, double cutoff)
+{
+	rcFilter_t filter;
+
+	if (buffer == NULL || length == 0 || cutoff == 0.0)
+		return false;
+
+	calcRCFilterCoeffs(sampleFrequency, cutoff, &filter);
+	clearRCFilterState(&filter);
+
+	for (int32_t i = 0; i < length; i++)
+	{
+		double dSample;
+		RCLowPassFilterMono(&filter, buffer[i], &dSample);
+
+		buffer[i] = dSample;
+	}
+
+	return true;
+}
 
 void extLoadWAVOrAIFFSampleCallback(bool downsample)
 {
--- a/src/pt2_sampler.c
+++ b/src/pt2_sampler.c
@@ -468,7 +468,7 @@
 	int32_t smp32, i, from, to;
 	double *dSampleData, dBaseFreq, dCutOff;
 	moduleSample_t *s;
-	lossyIntegrator_t filterHi;
+	rcFilter_t filterHi;
 
 	assert(editor.currSample >= 0 && editor.currSample <= 30);
 
@@ -523,17 +523,17 @@
 		editor.hpCutOff = (uint16_t)dCutOff;
 	}
 
-	calcCoeffLossyIntegrator(dBaseFreq, dCutOff, &filterHi);
+	calcRCFilterCoeffs(dBaseFreq, dCutOff, &filterHi);
 
 	// copy over sample data to double buffer
 	for (i = 0; i < s->length; i++)
 		dSampleData[i] = modEntry->sampleData[s->offset+i];
 
-	filterHi.dBuffer[0] = 0.0;
+	clearRCFilterState(&filterHi);
 	if (to <= s->length)
 	{
 		for (i = from; i < to; i++)
-			lossyIntegratorHighPassMono(&filterHi, dSampleData[i], &dSampleData[i]);
+			RCHighPassFilterMono(&filterHi, dSampleData[i], &dSampleData[i]);
 	}
 
 	if (editor.normalizeFiltersFlag)
@@ -558,7 +558,7 @@
 	int32_t smp32, i, from, to;
 	double *dSampleData, dBaseFreq, dCutOff;
 	moduleSample_t *s;
-	lossyIntegrator_t filterLo;
+	rcFilter_t filterLo;
 
 	assert(editor.currSample >= 0 && editor.currSample <= 30);
 
@@ -613,17 +613,17 @@
 		editor.lpCutOff = (uint16_t)dCutOff;
 	}
 
-	calcCoeffLossyIntegrator(dBaseFreq, dCutOff, &filterLo);
+	calcRCFilterCoeffs(dBaseFreq, dCutOff, &filterLo);
 
 	// copy over sample data to double buffer
 	for (i = 0; i < s->length; i++)
 		dSampleData[i] = modEntry->sampleData[s->offset+i];
 
-	filterLo.dBuffer[0] = 0.0;
+	clearRCFilterState(&filterLo);
 	if (to <= s->length)
 	{
 		for (i = from; i < to; i++)
-			lossyIntegratorMono(&filterLo, dSampleData[i], &dSampleData[i]);
+			RCLowPassFilterMono(&filterLo, dSampleData[i], &dSampleData[i]);
 	}
 
 	if (editor.normalizeFiltersFlag)
--- a/src/pt2_scopes.c
+++ b/src/pt2_scopes.c
@@ -386,7 +386,10 @@
 
 	// fractional part scaled to 0..2^32-1
 	dFrac *= UINT32_MAX;
-	scopeTimeLenFrac = (uint32_t)(dFrac + 0.5);
+	dFrac += 0.5;
+	if (dFrac > UINT32_MAX)
+		dFrac = UINT32_MAX;
+	scopeTimeLenFrac = (uint32_t)dFrac;
 
 	scopeThread = SDL_CreateThread(scopeThreadFunc, NULL, NULL);
 	if (scopeThread == NULL)
--- a/src/pt2_visuals.c
+++ b/src/pt2_visuals.c
@@ -94,7 +94,10 @@
 
 	// fractional part scaled to 0..2^32-1
 	dFrac *= UINT32_MAX;
-	editor.vblankTimeLenFrac = (uint32_t)(dFrac + 0.5);
+	dFrac += 0.5;
+	if (dFrac > UINT32_MAX)
+		dFrac = UINT32_MAX;
+	editor.vblankTimeLenFrac = (uint32_t)dFrac;
 }
 
 void setupWaitVBL(void)
@@ -653,7 +656,7 @@
 		{
 			assert(editor.resampleNote < 36);
 			textOutBg(288, 236,
-				config.accidental ? noteNames2[editor.resampleNote] : noteNames1[editor.resampleNote],
+				config.accidental ? noteNames2[2+editor.resampleNote] : noteNames1[2+editor.resampleNote],
 				video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
 		}
 	}
@@ -1604,7 +1607,7 @@
 			if (editor.note1 > 35)
 				textOutBg(256, 58, "---", video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
 			else
-				textOutBg(256, 58, config.accidental ? noteNames2[editor.note1] : noteNames1[editor.note1],
+				textOutBg(256, 58, config.accidental ? noteNames2[2+editor.note1] : noteNames1[2+editor.note1],
 					video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
 		}
 
@@ -1614,7 +1617,7 @@
 			if (editor.note2 > 35)
 				textOutBg(256, 69, "---", video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
 			else
-				textOutBg(256, 69, config.accidental ? noteNames2[editor.note2] : noteNames1[editor.note2],
+				textOutBg(256, 69, config.accidental ? noteNames2[2+editor.note2] : noteNames1[2+editor.note2],
 					video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
 		}
 
@@ -1624,7 +1627,7 @@
 			if (editor.note3 > 35)
 				textOutBg(256, 80, "---", video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
 			else
-				textOutBg(256, 80, config.accidental ? noteNames2[editor.note3] : noteNames1[editor.note3],
+				textOutBg(256, 80, config.accidental ? noteNames2[2+editor.note3] : noteNames1[2+editor.note3],
 					video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
 		}
 			
@@ -1634,7 +1637,7 @@
 			if (editor.note4 > 35)
 				textOutBg(256, 91, "---", video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
 			else
-				textOutBg(256, 91, config.accidental ? noteNames2[editor.note4] : noteNames1[editor.note4],
+				textOutBg(256, 91, config.accidental ? noteNames2[2+editor.note4] : noteNames1[2+editor.note4],
 					video.palette[PAL_GENTXT], video.palette[PAL_GENBKG]);
 		}
 	}
--- a/vs2019_project/pt2-clone/protracker.ini
+++ b/vs2019_project/pt2-clone/protracker.ini
@@ -180,10 +180,9 @@
 ; Audio output frequency
 ;        Syntax: Number, in hertz
 ; Default value: 48000
-;       Comment: Ranges from 32000 to 96000.
+;       Comment: Ranges from 44100 to 192000.
 ;         Also sets the playback frequency for WAVs made with MOD2WAV.
-;         Note to coders: Don't allow lower numbers than 32000, it will
-;           break the BLEP synthesis.
+;
 FREQUENCY=48000
 
 ; Audio buffer size
--- a/vs2019_project/pt2-clone/pt2-clone.vcxproj
+++ b/vs2019_project/pt2-clone/pt2-clone.vcxproj
@@ -287,7 +287,6 @@
     <ClInclude Include="..\..\src\pt2_config.h" />
     <ClInclude Include="..\..\src\pt2_diskop.h" />
     <ClInclude Include="..\..\src\pt2_edit.h" />
-    <ClInclude Include="..\..\src\pt2_filters.h" />
     <ClInclude Include="..\..\src\pt2_header.h" />
     <ClInclude Include="..\..\src\pt2_helpers.h" />
     <ClInclude Include="..\..\src\pt2_keyboard.h" />
@@ -329,7 +328,6 @@
     <ClCompile Include="..\..\src\pt2_config.c" />
     <ClCompile Include="..\..\src\pt2_diskop.c" />
     <ClCompile Include="..\..\src\pt2_edit.c" />
-    <ClCompile Include="..\..\src\pt2_filters.c" />
     <ClCompile Include="..\..\src\pt2_helpers.c" />
     <ClCompile Include="..\..\src\pt2_keyboard.c" />
     <ClCompile Include="..\..\src\pt2_main.c" />
--- a/vs2019_project/pt2-clone/pt2-clone.vcxproj.filters
+++ b/vs2019_project/pt2-clone/pt2-clone.vcxproj.filters
@@ -66,9 +66,6 @@
     <ClInclude Include="..\..\src\pt2_visuals.h">
       <Filter>headers</Filter>
     </ClInclude>
-    <ClInclude Include="..\..\src\pt2_filters.h">
-      <Filter>headers</Filter>
-    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="..\..\src\pt2_audio.c" />
@@ -145,7 +142,6 @@
     <ClCompile Include="..\..\src\gfx\pt2_gfx_yes_no_dialog.c">
       <Filter>gfx</Filter>
     </ClCompile>
-    <ClCompile Include="..\..\src\pt2_filters.c" />
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="..\..\src\pt2-clone.rc" />