shithub: ft²

Download patch

ref: 380d39a7206417b80dc6468d44c8cc3f93c197d5
parent: 4dd5fb98c4325b4fb674bce5dec18652fdd5b22a
author: Olav Sørensen <olav.sorensen@live.no>
date: Tue Mar 4 13:31:55 EST 2025

New smp. ed. "effects" screen, code cleanup + more

--- a/src/ft2_about.c
+++ b/src/ft2_about.c
@@ -8,6 +8,7 @@
 #include "ft2_gfxdata.h"
 #include "ft2_pattern_ed.h" // exitPatternEditorExtended()
 #include "ft2_config.h"
+#include "ft2_random.h"
 
 #define OLD_NUM_STARS 1000
 #define NUM_STARS 1500
@@ -64,17 +65,6 @@
 static vector_t starPoints[NUM_STARS], starRotation;
 static matrix_t starMatrix;
 
-// exact Turbo Pascal Random() implementation
-static int32_t Random(int32_t limit)
-{
-	static uint32_t randSeed; // seed is 0 in Turbo Pascal unless Randomize() is called
-
-	randSeed *= 134775813;
-	randSeed++;
-
-	return ((int64_t)randSeed * limit) >> 32;
-}
-
 static uint32_t blendPixels(uint32_t pixelA, uint32_t pixelB, uint16_t alpha)
 {
 	const uint16_t invAlpha = alpha ^ 0xFFFF;
@@ -333,7 +323,7 @@
 	{
 		oldVector_t *s = oldStarPoints;
 
-		const int32_t type = Random(4);
+		const int32_t type = randoml(4);
 		switch (type)
 		{
 			// classic "space stars"
@@ -342,9 +332,9 @@
 				zSpeed = 309;
 				for (int32_t i = 0; i < OLD_NUM_STARS; i++, s++)
 				{
-					s->z = (int16_t)Random(0xFFFF) - 0x8000;
-					s->y = (int16_t)Random(0xFFFF) - 0x8000;
-					s->x = (int16_t)Random(0xFFFF) - 0x8000;
+					s->z = (int16_t)randoml(0xFFFF) - 0x8000;
+					s->y = (int16_t)randoml(0xFFFF) - 0x8000;
+					s->x = (int16_t)randoml(0xFFFF) - 0x8000;
 				}
 			}
 			break;
@@ -357,17 +347,17 @@
 				{
 					if (i < OLD_NUM_STARS/4)
 					{
-						s->z = (int16_t)Random(0xFFFF) - 0x8000;
-						s->y = (int16_t)Random(0xFFFF) - 0x8000;
-						s->x = (int16_t)Random(0xFFFF) - 0x8000;
+						s->z = (int16_t)randoml(0xFFFF) - 0x8000;
+						s->y = (int16_t)randoml(0xFFFF) - 0x8000;
+						s->x = (int16_t)randoml(0xFFFF) - 0x8000;
 					}
 					else
 					{
-						int32_t r = Random(30000);
-						int32_t n = Random(5);
-						int32_t w = ((2 * Random(2)) - 1) * Sqr(Random(1000));
+						int32_t r = randoml(30000);
+						int32_t n = randoml(5);
+						int32_t w = ((2 * randoml(2)) - 1) * Sqr(randoml(1000));
 						double ww = (((PI * 2.0) / 5.0) * n) + (r * (1.0 / 12000.0)) + (w * (1.0 / 3000000.0));
-						int32_t h = ((Sqr(r) / 30000) * (Random(10000) - 5000)) / 12000;
+						int32_t h = ((Sqr(r) / 30000) * (randoml(10000) - 5000)) / 12000;
 
 						s->x = (int16_t)(r * cos(ww));
 						s->y = (int16_t)(r * sin(ww));
@@ -384,8 +374,8 @@
 				zSpeed = 0;
 				for (int32_t i = 0; i < OLD_NUM_STARS; i++, s++)
 				{
-					int32_t r = (int32_t)round(sqrt(Random(500) * 500));
-					int32_t w = Random(3000);
+					int32_t r = (int32_t)round(sqrt(randoml(500) * 500));
+					int32_t w = randoml(3000);
 					double ww = ((w * 8) + r) * (1.0 / 16.0);
 
 					const int16_t z =  (int16_t)round(32767.0 * cos(w  * (2.0 * PI / 1024.0)));
@@ -421,9 +411,9 @@
 	vector_t *s = starPoints;
 	for (int32_t i = 0; i < NUM_STARS; i++, s++)
 	{
-		s->x = (float)((Random(INT32_MAX) - (INT32_MAX/2)) * (1.0 / INT32_MAX));
-		s->y = (float)((Random(INT32_MAX) - (INT32_MAX/2)) * (1.0 / INT32_MAX));
-		s->z = (float)((Random(INT32_MAX) - (INT32_MAX/2)) * (1.0 / INT32_MAX));
+		s->x = (float)((randoml(INT32_MAX) - (INT32_MAX/2)) * (1.0 / INT32_MAX));
+		s->y = (float)((randoml(INT32_MAX) - (INT32_MAX/2)) * (1.0 / INT32_MAX));
+		s->z = (float)((randoml(INT32_MAX) - (INT32_MAX/2)) * (1.0 / INT32_MAX));
 	}
 
 	sinp1 = 0;
--- a/src/ft2_checkboxes.c
+++ b/src/ft2_checkboxes.c
@@ -15,6 +15,7 @@
 #include "ft2_edit.h"
 #include "ft2_bmp.h"
 #include "ft2_wav_renderer.h"
+#include "ft2_smpfx.h"
 #include "ft2_structs.h"
 
 checkBox_t checkBoxes[NUM_CHECKBOXES] =
@@ -69,6 +70,10 @@
 	//x,   y,   w,   h,  funcOnUp
 	{   3, 112, 148, 12, cbInstMidiEnable },
 	{ 172, 112, 103, 12, cbInstMuteComputer },
+
+	// ------ SAMPLE EDITOR EFFECTS CHECKBOXES ------
+	//x,   y,   w,   h,  funcOnUp
+	{ 119, 384,  95, 12, cbSfxNormalization },
 
 	// ------ TRIM SCREEN CHECKBOXES ------
 	//x,   y,   w,   h,  funcOnUp
--- a/src/ft2_checkboxes.h
+++ b/src/ft2_checkboxes.h
@@ -42,6 +42,9 @@
 	CB_INST_EXT_MIDI,
 	CB_INST_EXT_MUTE,
 
+	// SAMPLE EDITOR EFFECTS
+	CB_SAMPFX_NORMALIZATION,
+
 	// TRIM
 	CB_TRIM_PATT,
 	CB_TRIM_INST,
--- a/src/ft2_edit.c
+++ b/src/ft2_edit.c
@@ -369,7 +369,7 @@
 			time = INT32_MAX;
 			for (i = 0; i < song.numChannels; i++)
 			{
-				if (editor.chnMode[i] && config.multiRecChn[i] && editor.keyOffTime[i] < time && editor.keyOnTab[i] == 0)
+				if (!editor.channelMuted[i] && config.multiRecChn[i] && editor.keyOffTime[i] < time && editor.keyOnTab[i] == 0)
 				{
 					c = i;
 					time = editor.keyOffTime[i];
--- a/src/ft2_header.h
+++ b/src/ft2_header.h
@@ -12,7 +12,7 @@
 #endif
 #include "ft2_replayer.h"
 
-#define PROG_VER_STR "1.94"
+#define PROG_VER_STR "1.95"
 
 // do NOT change these! It will only mess things up...
 
--- a/src/ft2_main.c
+++ b/src/ft2_main.c
@@ -35,6 +35,7 @@
 #include "ft2_bmp.h"
 #include "ft2_structs.h"
 #include "ft2_hpc.h"
+#include "ft2_smpfx.h"
 
 static void initializeVars(void);
 static void cleanUpAndExit(void); // never call this inside the main loop
@@ -362,6 +363,7 @@
 	freeSprites();
 	freeDiskOp();
 	clearCopyBuffer();
+	clearSampleUndo();
 	freeAudioDeviceSelectorBuffers();
 	windUpFTHelp();
 	freeTextBoxes();
--- a/src/ft2_midi.c
+++ b/src/ft2_midi.c
@@ -220,7 +220,7 @@
 		note_t *p = &pattern[editor.editPattern][editor.row * MAX_CHANNELS];
 		for (int32_t i = 0; i < song.numChannels; i++, p++)
 		{
-			if (config.multiRecChn[i] && editor.chnMode[i])
+			if (config.multiRecChn[i] && !editor.channelMuted[i])
 			{
 				if (!allocatePattern(editor.editPattern))
 					return;
--- a/src/ft2_module_loader.c
+++ b/src/ft2_module_loader.c
@@ -19,6 +19,7 @@
 #include "ft2_gui.h"
 #include "ft2_diskop.h"
 #include "ft2_sample_loader.h"
+#include "ft2_smpfx.h"
 #include "ft2_mouse.h"
 #include "ft2_midi.h"
 #include "ft2_events.h"
@@ -498,6 +499,7 @@
 	updateChanNums();
 	resetWavRenderer();
 	clearPattMark();
+	clearSampleUndo();
 	resetTrimSizes();
 	resetPlaybackTime();
 
--- a/src/ft2_pattern_draw.c
+++ b/src/ft2_pattern_draw.c
@@ -300,7 +300,7 @@
 static void drawChannelNumbering(uint16_t yPos)
 {
 	uint16_t xPos = 30;
-	int32_t ch = ui.channelOffset + 1;
+	uint8_t ch = ui.channelOffset + 1;
 
 	for (uint8_t i = 0; i < ui.numChannelsShown; i++)
 	{
@@ -310,8 +310,8 @@
 		}
 		else
 		{
-			charOutOutlined(xPos, yPos, PAL_MOUSEPT, chDecTab1[ch]);
-			charOutOutlined(xPos + (FONT1_CHAR_W + 1), yPos, PAL_MOUSEPT, chDecTab2[ch]);
+			charOutOutlined(xPos, yPos, PAL_MOUSEPT, '0' + (ch / 10));
+			charOutOutlined(xPos + (FONT1_CHAR_W + 1), yPos, PAL_MOUSEPT, '0' + (ch % 10));
 		}
 
 		ch++;
--- a/src/ft2_pattern_ed.c
+++ b/src/ft2_pattern_ed.c
@@ -613,6 +613,7 @@
 	ui._instEditorShown = ui.instEditorShown;
 	ui._instEditorExtShown = ui.instEditorExtShown;
 	ui._sampleEditorExtShown = ui.sampleEditorExtShown;
+	ui._sampleEditorEffectsShown = ui.sampleEditorEffectsShown;
 	ui._patternEditorShown = ui.patternEditorShown;
 	ui._sampleEditorShown = ui.sampleEditorShown;
 	ui._advEditShown= ui.advEditShown;
@@ -703,6 +704,7 @@
 	ui.instEditorShown = ui._instEditorShown;
 	ui.instEditorExtShown = ui._instEditorExtShown;
 	ui.sampleEditorExtShown = ui._sampleEditorExtShown;
+	ui.sampleEditorEffectsShown = ui._sampleEditorEffectsShown;
 	ui.patternEditorShown = ui._patternEditorShown;
 	ui.sampleEditorShown = ui._sampleEditorShown;
 	ui.advEditShown = ui._advEditShown;
--- a/src/ft2_pushbuttons.c
+++ b/src/ft2_pushbuttons.c
@@ -27,6 +27,7 @@
 #include "ft2_mouse.h"
 #include "ft2_edit.h"
 #include "ft2_sample_ed_features.h"
+#include "ft2_smpfx.h"
 #include "ft2_palette.h"
 #include "ft2_structs.h"
 #include "ft2_bmp.h"
@@ -218,7 +219,7 @@
 	{ 251, 382, 43, 16, 0, 0, "Paste",            NULL,    NULL,                  sampPaste },
 	{ 300, 348, 50, 16, 0, 0, "Crop",             NULL,    NULL,                  sampCrop },
 	{ 300, 365, 50, 16, 0, 0, "Volume",           NULL,    NULL,                  pbSampleVolume },
-	{ 300, 382, 50, 16, 0, 0, "X-Fade",           NULL,    NULL,                  sampXFade },
+	{ 300, 382, 50, 16, 0, 0, "Effects",          NULL,    NULL,                  pbEffects },
 	{ 430, 348, 54, 16, 0, 0, "Exit",             NULL,    NULL,                  exitSampleEditor },
 	{ 594, 348, 35, 13, 0, 0, "Clr S.",           NULL,    NULL,                  clearSample },
 	{ 594, 360, 35, 13, 0, 0, "Min.",             NULL,    NULL,                  sampMinimize },
@@ -227,6 +228,27 @@
 	{ 594, 385, 18, 13, 2, 4, ARROW_UP_STRING,    NULL,    sampReplenUp,          NULL },
 	{ 611, 385, 18, 13, 2, 4, ARROW_DOWN_STRING,  NULL,    sampReplenDown,        NULL },
 
+	// ------ SAMPLE EDITOR EFFECTS PUSHBUTTONS ------
+	//x,   y,   w,  h,  p, d, text #1,              text #2, funcOnDown,      funcOnUp
+	{  78, 350, 18, 13, 2, 2, ARROW_UP_STRING,      NULL,    pbSfxCyclesUp,   NULL },
+	{  95, 350, 18, 13, 2, 2, ARROW_DOWN_STRING,    NULL,    pbSfxCyclesDown, NULL },
+	{   3, 365, 54, 16, 0, 0, "Triangle",           NULL,    NULL,            pbSfxTriangle },
+	{  59, 365, 54, 16, 0, 0, "Saw",                NULL,    NULL,            pbSfxSaw },
+	{   3, 382, 54, 16, 0, 0, "Sine",               NULL,    NULL,            pbSfxSine },
+	{  59, 382, 54, 16, 0, 0, "Square",             NULL,    NULL,            pbSfxSquare },
+	{ 192, 350, 18, 13, 1, 2, ARROW_UP_STRING,      NULL,    pbSfxResoUp,     NULL },
+	{ 209, 350, 18, 13, 1, 2, ARROW_DOWN_STRING,    NULL,    pbSfxResoDown,   NULL },
+	{ 119, 365, 53, 16, 0, 0, "lp filter",          NULL,    NULL,            pbSfxLowPass },
+	{ 174, 365, 53, 16, 0, 0, "hp filter",          NULL,    NULL,            pbSfxHighPass },
+	{ 269, 350, 13, 13, 0, 0, "-",                  NULL,    NULL,            pbSfxSubBass },
+	{ 281, 350, 13, 13, 0, 0, "+",                  NULL,    NULL,            pbSfxAddBass },
+	{ 269, 367, 13, 13, 0, 0, "-",                  NULL,    NULL,            pbSfxSubTreble },
+	{ 281, 367, 13, 13, 0, 0, "+",                  NULL,    NULL,            pbSfxAddTreble },
+	{ 233, 382, 61, 16, 0, 0, "Amplitude",          NULL,    NULL,            pbSfxSetAmp },
+	{ 300, 348, 50, 16, 0, 0, "Undo",               NULL,    NULL,            pbSfxUndo },
+	{ 300, 365, 50, 16, 0, 0, "X-Fade",             NULL,    NULL,            sampXFade },
+	{ 300, 382, 50, 16, 0, 0, "Back...",            NULL,    NULL,            hideSampleEffectsScreen },
+
 	// ------ SAMPLE EDITOR EXTENSION PUSHBUTTONS ------
 	//x,   y,   w,  h,  p, d, text #1,     text #2, funcOnDown, funcOnUp
 	{   3, 138, 52, 16, 0, 0, "Clr. c.bf", NULL,    NULL,       clearCopyBuffer },
@@ -233,7 +255,7 @@
 	{  56, 138, 49, 16, 0, 0, "Sign",      NULL,    NULL,       sampleChangeSign },
 	{ 106, 138, 49, 16, 0, 0, "Echo",      NULL,    NULL,       pbSampleEcho },
 	{   3, 155, 52, 16, 0, 0, "Backw.",    NULL,    NULL,       sampleBackwards },
-	{  56, 155, 49, 16, 0, 0, "B. swap",    NULL,    NULL,      sampleByteSwap },
+	{  56, 155, 49, 16, 0, 0, "B. swap",   NULL,    NULL,       sampleByteSwap },
 	{ 106, 155, 49, 16, 0, 0, "Fix DC",    NULL,    NULL,       fixDC },
 	{ 161, 121, 60, 16, 0, 0, "Copy ins.", NULL,    NULL,       copyInstr },
 	{ 222, 121, 66, 16, 0, 0, "Copy smp.", NULL,    NULL,       copySmp },
--- a/src/ft2_pushbuttons.h
+++ b/src/ft2_pushbuttons.h
@@ -168,7 +168,7 @@
 	PB_SAMP_PASTE,
 	PB_SAMP_CROP,
 	PB_SAMP_VOLUME,
-	PB_SAMP_XFADE,
+	PB_SAMP_EFFECTS,
 	PB_SAMP_EXIT,
 	PB_SAMP_CLEAR,
 	PB_SAMP_MIN,
@@ -176,6 +176,26 @@
 	PB_SAMP_REPEAT_DOWN,
 	PB_SAMP_REPLEN_UP,
 	PB_SAMP_REPLEN_DOWN,
+
+	// SAMPLE EDITOR EFFECTS SCREEN
+	PB_SAMPFX_CYCLES_UP,
+	PB_SAMPFX_CYCLES_DOWN,
+	PB_SAMPFX_TRIANGLE,
+	PB_SAMPFX_SAW,
+	PB_SAMPFX_SINE,
+	PB_SAMPFX_SQUARE,
+	PB_SAMPFX_RESO_UP,
+	PB_SAMPFX_RESO_DOWN,
+	PB_SAMPFX_LOWPASS,
+	PB_SAMPFX_HIGHPASS,
+	PB_SAMPFX_SUB_BASS,
+	PB_SAMPFX_SUB_TREBLE,
+	PB_SAMPFX_ADD_BASS,
+	PB_SAMPFX_ADD_TREBLE,
+	PB_SAMPFX_SET_AMP,
+	PB_SAMPFX_UNDO,
+	PB_SAMPFX_XFADE,
+	PB_SAMPFX_BACK,
 
 	// SAMPLE EDITOR EXTENSION
 	PB_SAMP_EXT_CLEAR_COPYBUF,
--- /dev/null
+++ b/src/ft2_random.c
@@ -1,0 +1,25 @@
+#include <SDL2/SDL.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+static uint32_t randSeed;
+
+void randomize(void)
+{
+	randSeed = (uint32_t)SDL_GetTicks();
+}
+
+int32_t randoml(int32_t limit)
+{
+	randSeed *= 134775813;
+	randSeed++;
+	return ((int64_t)randSeed * (int32_t)limit) >> 32;
+}
+
+int32_t random32(void)
+{
+	randSeed *= 134775813;
+	randSeed++;
+
+	return randSeed;
+}
--- /dev/null
+++ b/src/ft2_random.h
@@ -1,0 +1,7 @@
+#pragma once
+
+#include <stdint.h>
+
+void randomize(void);
+int32_t randoml(int32_t limit);
+int32_t random32(void);
--- a/src/ft2_replayer.c
+++ b/src/ft2_replayer.c
@@ -42,7 +42,7 @@
 int8_t playMode = 0;
 bool songPlaying = false, audioPaused = false, musicPaused = false;
 volatile bool replayerBusy = false;
-const uint16_t *note2Period = NULL;
+const uint16_t *note2PeriodLUT = NULL;
 int16_t patternNumRows[MAX_PATTERNS];
 channel_t channel[MAX_CHANNELS];
 song_t song;
@@ -120,7 +120,7 @@
 		ch->outPan = 128;
 		ch->finalPan = 128;
 
-		ch->channelOff = !editor.chnMode[i]; // set channel mute flag from global mute flag
+		ch->channelOff = editor.channelMuted[i]; // set channel mute flag from global mute flag
 	}
 
 	if (audioWasntLocked)
@@ -274,7 +274,7 @@
 
 	const int32_t C4Period = (note << 4) + (((int8_t)s->finetune >> 3) + 16);
 
-	const int32_t period = audio.linearPeriodsFlag ? linearPeriods[C4Period] : amigaPeriods[C4Period];
+	const int32_t period = audio.linearPeriodsFlag ? linearPeriodLUT[C4Period] : amigaPeriodLUT[C4Period];
 	return dPeriod2Hz(period);
 }
 
@@ -285,9 +285,9 @@
 	audio.linearPeriodsFlag = linearPeriodsFlag;
 
 	if (audio.linearPeriodsFlag)
-		note2Period = linearPeriods;
+		note2PeriodLUT = linearPeriodLUT;
 	else
-		note2Period = amigaPeriods;
+		note2PeriodLUT = amigaPeriodLUT;
 
 	resumeAudio();
 
@@ -302,7 +302,7 @@
 		drawC4Rate();
 }
 
-static void resetVolumes(channel_t *ch)
+void resetVolumes(channel_t *ch)
 {
 	ch->realVol = ch->oldVol;
 	ch->outVol = ch->oldVol;
@@ -311,7 +311,7 @@
 	ch->status |= IS_Vol + IS_Pan + IS_QuickVol;
 }
 
-static void triggerInstrument(channel_t *ch)
+void triggerInstrument(channel_t *ch)
 {
 	if (!(ch->vibTremCtrl & 0x04)) ch->vibratoPos = 0;
 	if (!(ch->vibTremCtrl & 0x40)) ch->tremoloPos = 0;
@@ -430,8 +430,8 @@
 // for piano in Instr. Ed. (values outside 0..95 can happen)
 int32_t getPianoKey(uint16_t period, int8_t finetune, int8_t relativeNote)
 {
-	assert(note2Period != NULL);
-	if (period > note2Period[0])
+	assert(note2PeriodLUT != NULL);
+	if (period > note2PeriodLUT[0])
 		return -1; // outside left piano edge
 
 	finetune = ((int8_t)finetune >> 3) + 16; // -128..127 -> 0..31
@@ -447,7 +447,7 @@
 		if (lookUp < 0)
 			lookUp = 0;
 
-		if (period >= note2Period[lookUp])
+		if (period >= note2PeriodLUT[lookUp])
 			hiPeriod = (tmpPeriod - finetune) & ~15;
 		else
 			loPeriod = (tmpPeriod - finetune) & ~15;
@@ -456,7 +456,7 @@
 	return (loPeriod >> 4) - relativeNote;
 }
 
-static void triggerNote(uint8_t note, uint8_t efx, uint8_t efxData, channel_t *ch)
+void triggerNote(uint8_t note, uint8_t efx, uint8_t efxData, channel_t *ch)
 {
 	if (note == NOTE_OFF)
 	{
@@ -507,8 +507,8 @@
 	{
 		const uint16_t noteIndex = ((note-1) * 16) + (((int8_t)ch->finetune >> 3) + 16); // 0..1920
 
-		assert(note2Period != NULL);
-		ch->outPeriod = ch->realPeriod = note2Period[noteIndex];
+		assert(note2PeriodLUT != NULL);
+		ch->outPeriod = ch->realPeriod = note2PeriodLUT[noteIndex];
 	}
 
 	ch->status |= IS_Period + IS_Vol + IS_Pan + IS_Trigger + IS_QuickVol;
@@ -1237,8 +1237,8 @@
 			const uint16_t note = (((p->note-1) + ch->relativeNote) * 16) + (((int8_t)ch->finetune >> 3) + 16);
 			if (note < MAX_NOTES)
 			{
-				assert(note2Period != NULL);
-				ch->portamentoTargetPeriod = note2Period[note];
+				assert(note2PeriodLUT != NULL);
+				ch->portamentoTargetPeriod = note2PeriodLUT[note];
 
 				if (ch->portamentoTargetPeriod == ch->realPeriod)
 					ch->portamentoDirection = 0;
@@ -1366,7 +1366,7 @@
 	handleEffects_TickZero(ch);
 }
 
-static void updateVolPanAutoVib(channel_t *ch)
+void updateVolPanAutoVib(channel_t *ch)
 {
 	bool envInterpolateFlag, envDidInterpolate;
 	uint8_t envPos;
@@ -1708,7 +1708,7 @@
 		if (lookUp < 0)
 			lookUp = 0; // safety fix (C-0 w/ f.tune <= -65). This seems to result in 0 in FT2 (TODO: verify)
 
-		if (period >= note2Period[lookUp])
+		if (period >= note2PeriodLUT[lookUp])
 			hiPeriod = (tmpPeriod - fineTune) & ~15;
 		else
 			loPeriod = (tmpPeriod - fineTune) & ~15;
@@ -1718,7 +1718,7 @@
 	if (tmpPeriod >= (8*12*16+15)-1) // FT2 bug, should've been 10*12*16+16 (also notice the +2 difference)
 		tmpPeriod = (8*12*16+16)-1;
 
-	return note2Period[tmpPeriod];
+	return note2PeriodLUT[tmpPeriod];
 }
 
 static void doVibrato(channel_t *ch)
@@ -2804,7 +2804,7 @@
 
 	// unmute all channels (must be done before resetChannels() call)
 	for (int32_t i = 0; i < MAX_CHANNELS; i++)
-		editor.chnMode[i] = 1;
+		editor.channelMuted[i] = false;
 
 	resetChannels();
 
@@ -2814,7 +2814,7 @@
 	editor.speed = song.initialSpeed = song.speed = 6;
 	editor.globalVolume = song.globalVolume = 64;
 	audio.linearPeriodsFlag = true;
-	note2Period = linearPeriods;
+	note2PeriodLUT = linearPeriodLUT;
 
 	calcPanningTable();
 
--- a/src/ft2_replayer.h
+++ b/src/ft2_replayer.h
@@ -295,6 +295,8 @@
 double dPeriod2Hz(int32_t period);
 
 int32_t getPianoKey(uint16_t period, int8_t finetune, int8_t relativeNote); // for piano in Instr. Ed.
+void triggerNote(uint8_t note, uint8_t efx, uint8_t efxData, channel_t *ch);
+void updateVolPanAutoVib(channel_t *ch);
 
 bool allocateInstr(int16_t insNum);
 void freeInstr(int32_t insNum);
@@ -324,6 +326,8 @@
 void samp2Delta(int8_t *p, int32_t length, uint8_t smpFlags);
 void setPatternLen(uint16_t pattNum, int16_t numRows);
 void setLinearPeriods(bool linearPeriodsFlag);
+void resetVolumes(channel_t *ch);
+void triggerInstrument(channel_t *ch);
 void tickReplayer(void); // periodically called from audio callback
 void resetChannels(void);
 bool patternEmpty(uint16_t pattNum);
@@ -347,7 +351,7 @@
 extern int8_t playMode;
 extern bool songPlaying, audioPaused, musicPaused;
 extern volatile bool replayerBusy;
-extern const uint16_t *note2Period;
+extern const uint16_t *note2PeriodLUT;
 extern int16_t patternNumRows[MAX_PATTERNS];
 extern channel_t channel[MAX_CHANNELS];
 extern song_t song;
--- a/src/ft2_sample_ed.c
+++ b/src/ft2_sample_ed.c
@@ -27,7 +27,9 @@
 #include "ft2_diskop.h"
 #include "ft2_keyboard.h"
 #include "ft2_structs.h"
+#include "ft2_random.h"
 #include "ft2_replayer.h"
+#include "ft2_smpfx.h"
 #include "mixer/ft2_windowed_sinc.h" // SINC_TAPS, SINC_NEGATIVE_TAPS
 
 static const char sharpNote1Char[12] = { 'C', 'C', 'D', 'D', 'E', 'F', 'F', 'G', 'G', 'A', 'A', 'B' };
@@ -1436,8 +1438,7 @@
 
 void updateSampleEditorSample(void)
 {
-	smpEd_Rx1 = 0;
-	smpEd_Rx2 = 0;
+	smpEd_Rx1 = smpEd_Rx2 = 0;
 
 	smpEd_ScrPos = 0;
 	updateScrPos();
@@ -1493,28 +1494,31 @@
 
 	showRadioButtonGroup(RB_GROUP_SAMPLE_LOOP);
 
-	// draw sample play note
+	if (!ui.sampleEditorEffectsShown)
+	{
+		// draw sample play note
 
-	const uint32_t noteNr = editor.smpEd_NoteNr - 1;
+		const uint32_t noteNr = editor.smpEd_NoteNr - 1;
 
-	const uint32_t note   = noteNr % 12;
-	const uint32_t octave = noteNr / 12;
+		const uint32_t note   = noteNr % 12;
+		const uint32_t octave = noteNr / 12;
 
-	if (config.ptnAcc == 0)
-	{
-		noteChar1 = sharpNote1Char[note];
-		noteChar2 = sharpNote2Char[note];
+		if (config.ptnAcc == 0)
+		{
+			noteChar1 = sharpNote1Char[note];
+			noteChar2 = sharpNote2Char[note];
+		}
+		else
+		{
+			noteChar1 = flatNote1Char[note];
+			noteChar2 = flatNote2Char[note];
+		}
+
+		charOutBg(7,  369, PAL_FORGRND, PAL_BCKGRND, noteChar1);
+		charOutBg(15, 369, PAL_FORGRND, PAL_BCKGRND, noteChar2);
+		charOutBg(23, 369, PAL_FORGRND, PAL_BCKGRND, (char)('0' + octave));
 	}
-	else
-	{
-		noteChar1 = flatNote1Char[note];
-		noteChar2 = flatNote2Char[note];
-	}
 
-	charOutBg(7,  369, PAL_FORGRND, PAL_BCKGRND, noteChar1);
-	charOutBg(15, 369, PAL_FORGRND, PAL_BCKGRND, noteChar2);
-	charOutBg(23, 369, PAL_FORGRND, PAL_BCKGRND, (char)('0' + octave));
-
 	// draw sample display/length
 
 	hexOutBg(536, 350, PAL_FORGRND, PAL_DESKTOP, smpEd_ViewSize, 8);
@@ -2898,6 +2902,8 @@
 
 void hideSampleEditor(void)
 {
+	hideSampleEffectsScreen();
+
 	hidePushButton(PB_SAMP_SCROLL_LEFT);
 	hidePushButton(PB_SAMP_SCROLL_RIGHT);
 	hidePushButton(PB_SAMP_PNOTE_UP);
@@ -2917,7 +2923,7 @@
 	hidePushButton(PB_SAMP_PASTE);
 	hidePushButton(PB_SAMP_CROP);
 	hidePushButton(PB_SAMP_VOLUME);
-	hidePushButton(PB_SAMP_XFADE);
+	hidePushButton(PB_SAMP_EFFECTS);
 	hidePushButton(PB_SAMP_EXIT);
 	hidePushButton(PB_SAMP_CLEAR);
 	hidePushButton(PB_SAMP_MIN);
@@ -2995,7 +3001,7 @@
 	showPushButton(PB_SAMP_PASTE);
 	showPushButton(PB_SAMP_CROP);
 	showPushButton(PB_SAMP_VOLUME);
-	showPushButton(PB_SAMP_XFADE);
+	showPushButton(PB_SAMP_EFFECTS);
 	showPushButton(PB_SAMP_EXIT);
 	showPushButton(PB_SAMP_CLEAR);
 	showPushButton(PB_SAMP_MIN);
@@ -3015,6 +3021,9 @@
 
 	updateSampleEditor();
 	writeSample(true);
+
+	if (ui.sampleEditorEffectsShown)
+		pbEffects();
 }
 
 void toggleSampleEditor(void)
--- a/src/ft2_sample_ed.h
+++ b/src/ft2_sample_ed.h
@@ -18,7 +18,6 @@
 void setSmpDataPtr(sample_t *s, smpPtr_t *sp);
 void freeSmpDataPtr(smpPtr_t *sp);
 void freeSmpData(sample_t *s);
-
 bool cloneSample(sample_t *src, sample_t *dst);
 sample_t *getCurSample(void);
 void sanitizeSample(sample_t *s);
--- /dev/null
+++ b/src/ft2_smpfx.c
@@ -1,0 +1,1389 @@
+// for finding memory leaks in debug mode with Visual Studio
+#if defined _DEBUG && defined _MSC_VER
+#include <crtdbg.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <math.h>
+#include "ft2_header.h"
+#include "ft2_audio.h"
+#include "ft2_pattern_ed.h"
+#include "ft2_gui.h"
+#include "ft2_sample_ed.h"
+#include "ft2_structs.h"
+#include "ft2_replayer.h"
+
+#define RESONANCE_RANGE 99
+#define RESONANCE_MIN 0.01 /* prevent massive blow-up */
+
+enum
+{
+	REMOVE_SAMPLE_MARK = 0,
+	KEEP_SAMPLE_MARK   = 1
+};
+
+static struct
+{
+	bool filled, keepSampleMark;
+	uint8_t flags, undoInstr, undoSmp;
+	uint32_t length, loopStart, loopLength;
+	int8_t *smpData8;
+	int16_t *smpData16;
+} sampleUndo;
+
+typedef struct
+{
+	double a1, a2, a3, b1, b2;
+	double inTmp[2], outTmp[2];
+} resoFilter_t;
+
+enum
+{
+	FILTER_LOWPASS  = 0,
+	FILTER_HIGHPASS = 1
+};
+
+static bool normalization;
+static uint8_t lastFilterType;
+static int32_t lastLpCutoff = 2000, lastHpCutoff = 200, filterResonance, smpCycles = 1, lastWaveLength = 64, lastAmp = 75;
+
+void clearSampleUndo(void)
+{
+	if (sampleUndo.smpData8 != NULL)
+	{
+		free(sampleUndo.smpData8);
+		sampleUndo.smpData8 = NULL;
+	}
+
+	if (sampleUndo.smpData16 != NULL)
+	{
+		free(sampleUndo.smpData16);
+		sampleUndo.smpData16 = NULL;
+	}
+
+	sampleUndo.filled = false;
+	sampleUndo.keepSampleMark = false;
+}
+
+static void fillSampleUndo(bool keepSampleMark)
+{
+	sampleUndo.filled = false;
+
+	sample_t *s = getCurSample();
+	if (s != NULL && s->length > 0)
+	{
+		pauseAudio();
+		unfixSample(s);
+
+		clearSampleUndo();
+
+		sampleUndo.undoInstr = editor.curInstr;
+		sampleUndo.undoSmp = editor.curSmp;
+		sampleUndo.flags = s->flags;
+		sampleUndo.length = s->length;
+		sampleUndo.loopStart = s->loopStart;
+		sampleUndo.loopLength = s->loopLength;
+		sampleUndo.keepSampleMark = keepSampleMark;
+
+		if (s->flags & SAMPLE_16BIT)
+		{
+			sampleUndo.smpData16 = (int16_t *)malloc(s->length * sizeof (int16_t));
+			if (sampleUndo.smpData16 != NULL)
+			{
+				memcpy(sampleUndo.smpData16, s->dataPtr, s->length * sizeof (int16_t));
+				sampleUndo.filled = true;
+			}
+		}
+		else
+		{
+			sampleUndo.smpData8 = (int8_t *)malloc(s->length * sizeof (int8_t));
+			if (sampleUndo.smpData8 != NULL)
+			{
+				memcpy(sampleUndo.smpData8, s->dataPtr, s->length * sizeof (int8_t));
+				sampleUndo.filled = true;
+			}
+		}
+
+		fixSample(s);
+		resumeAudio();
+	}
+}
+
+static sample_t *setupNewSample(uint32_t length)
+{
+	pauseAudio();
+
+	if (instr[editor.curInstr] == NULL)
+		allocateInstr(editor.curInstr);
+
+	if (instr[editor.curInstr] == NULL)
+		goto Error;
+
+	sample_t *s = &instr[editor.curInstr]->smp[editor.curSmp];
+
+	if (!reallocateSmpData(s, length, true))
+		goto Error;
+
+	s->isFixed = false;
+	s->length = length;
+	s->loopLength = s->loopStart = 0;
+	s->flags = SAMPLE_16BIT;
+
+	resumeAudio();
+	return s;
+
+Error:
+	resumeAudio();
+	return NULL;
+}
+
+void cbSfxNormalization(void)
+{
+	normalization ^= 1;
+}
+
+static void drawSampleCycles(void)
+{
+	const int16_t x = 54;
+
+	fillRect(x, 352, 7*3, 8, PAL_DESKTOP);
+
+	char str[16];
+	sprintf(str, "%03d", smpCycles);
+	textOut(x, 352, PAL_FORGRND, str);
+}
+
+void pbSfxCyclesUp(void)
+{
+	if (smpCycles < 256)
+	{
+		smpCycles++;
+		drawSampleCycles();
+	}
+}
+
+void pbSfxCyclesDown(void)
+{
+	if (smpCycles > 1)
+	{
+		smpCycles--;
+		drawSampleCycles();
+	}
+}
+
+void pbSfxTriangle(void)
+{
+	char lengthStr[5+1];
+	memset(lengthStr, '\0', sizeof (lengthStr));
+	snprintf(lengthStr, sizeof (lengthStr), "%d", lastWaveLength);
+
+	if (inputBox(1, "Enter new waveform length:", lengthStr, sizeof (lengthStr)-1) != 1)
+		return;
+
+	if (lengthStr[0] == '\0')
+		return;
+
+	lastWaveLength = (int32_t)atoi(lengthStr);
+	if (lastWaveLength <= 1 || lastWaveLength > 65536)
+	{
+		okBox(0, "System message", "Illegal range! Allowed range is 2..65535", NULL);
+		return;
+	}
+
+	fillSampleUndo(REMOVE_SAMPLE_MARK);
+	
+	int32_t newLength = lastWaveLength * smpCycles;
+
+	pauseAudio();
+
+	sample_t *s = setupNewSample(newLength);
+	if (s == NULL)
+	{
+		resumeAudio();
+		okBox(0, "System message", "Not enough memory!", NULL);
+		return;
+	}
+
+	const double delta = 4.0 / lastWaveLength;
+	double phase = 0.0;
+
+	int16_t *ptr16 = (int16_t *)s->dataPtr;
+	for (int32_t i = 0; i < newLength; i++)
+	{
+		double t = phase;
+		if (t > 3.0)
+			t -= 4.0;
+		else if (t >= 1.0)
+			t = 2.0 - t;
+
+		*ptr16++ = (int16_t)(t * INT16_MAX);
+		phase = fmod(phase + delta, 4.0);
+	}
+
+	s->loopLength = newLength;
+	s->flags |= LOOP_FORWARD;
+	fixSample(s);
+	resumeAudio();
+
+	updateSampleEditorSample();
+}
+
+void pbSfxSaw(void)
+{
+	char lengthStr[5+1];
+	memset(lengthStr, '\0', sizeof (lengthStr));
+	snprintf(lengthStr, sizeof (lengthStr), "%d", lastWaveLength);
+
+	if (inputBox(1, "Enter new waveform length:", lengthStr, sizeof (lengthStr)-1) != 1)
+		return;
+
+	if (lengthStr[0] == '\0')
+		return;
+
+	lastWaveLength = (int32_t)atoi(lengthStr);
+	if (lastWaveLength <= 1 || lastWaveLength > 65536)
+	{
+		okBox(0, "System message", "Illegal range! Allowed range is 2..65535", NULL);
+		return;
+	}
+
+	fillSampleUndo(REMOVE_SAMPLE_MARK);
+
+	int32_t newLength = lastWaveLength * smpCycles;
+
+	pauseAudio();
+
+	sample_t *s = setupNewSample(newLength);
+	if (s == NULL)
+	{
+		resumeAudio();
+		okBox(0, "System message", "Not enough memory!", NULL);
+		return;
+	}
+
+	uint64_t point64 = 0;
+	uint64_t delta64 = ((uint64_t)(INT16_MAX*2) << 32ULL) / lastWaveLength;
+
+	int16_t *ptr16 = (int16_t *)s->dataPtr;
+	for (int32_t i = 0; i < newLength; i++)
+	{
+		*ptr16++ = (int16_t)(point64 >> 32);
+		point64 += delta64;
+	}
+
+	s->loopLength = newLength;
+	s->flags |= LOOP_FORWARD;
+	fixSample(s);
+	resumeAudio();
+
+	updateSampleEditorSample();
+}
+
+void pbSfxSine(void)
+{
+	char lengthStr[5+1];
+	memset(lengthStr, '\0', sizeof (lengthStr));
+	snprintf(lengthStr, sizeof (lengthStr), "%d", lastWaveLength);
+
+	if (inputBox(1, "Enter new waveform length:", lengthStr, sizeof (lengthStr)-1) != 1)
+		return;
+
+	if (lengthStr[0] == '\0')
+		return;
+
+	lastWaveLength = (int32_t)atoi(lengthStr);
+	if (lastWaveLength <= 1 || lastWaveLength > 65536)
+	{
+		okBox(0, "System message", "Illegal range! Allowed range is 2..65535", NULL);
+		return;
+	}
+
+	fillSampleUndo(REMOVE_SAMPLE_MARK);
+
+	int32_t newLength = lastWaveLength * smpCycles;
+
+	pauseAudio();
+
+	sample_t *s = setupNewSample(newLength);
+	if (s == NULL)
+	{
+		resumeAudio();
+		okBox(0, "System message", "Not enough memory!", NULL);
+		return;
+	}
+
+	const double delta = 2.0 * M_PI / lastWaveLength;
+	double phase = 0.0;
+
+	int16_t *ptr16 = (int16_t *)s->dataPtr;
+	for (int32_t i = 0; i < newLength; i++)
+	{
+		*ptr16++ = (int16_t)(INT16_MAX * sin(phase));
+		phase += delta;
+	}
+
+	s->loopLength = newLength;
+	s->flags |= LOOP_FORWARD;
+	fixSample(s);
+	resumeAudio();
+
+	updateSampleEditorSample();
+}
+
+void pbSfxSquare(void)
+{
+	char lengthStr[5+1];
+	memset(lengthStr, '\0', sizeof (lengthStr));
+	snprintf(lengthStr, sizeof (lengthStr), "%d", lastWaveLength);
+
+	if (inputBox(1, "Enter new waveform length:", lengthStr, sizeof (lengthStr)-1) != 1)
+		return;
+
+	if (lengthStr[0] == '\0')
+		return;
+
+	lastWaveLength = (int32_t)atoi(lengthStr);
+	if (lastWaveLength <= 1 || lastWaveLength > 65536)
+	{
+		okBox(0, "System message", "Illegal range! Allowed range is 2..65535", NULL);
+		return;
+	}
+
+	fillSampleUndo(REMOVE_SAMPLE_MARK);
+
+	uint32_t newLength = lastWaveLength * smpCycles;
+
+	pauseAudio();
+
+	sample_t *s = setupNewSample(newLength);
+	if (s == NULL)
+	{
+		resumeAudio();
+		okBox(0, "System message", "Not enough memory!", NULL);
+		return;
+	}
+
+	const uint32_t halfWaveLength = lastWaveLength / 2;
+
+	int16_t currValue = INT16_MAX;
+	uint32_t counter = 0;
+
+	int16_t *ptr16 = (int16_t *)s->dataPtr;
+	for (uint32_t i = 0; i < newLength; i++)
+	{
+		*ptr16++ = currValue;
+		if (++counter >= halfWaveLength)
+		{
+			counter = 0;
+			currValue = -currValue;
+		}
+	}
+
+	s->loopLength = newLength;
+	s->flags |= LOOP_FORWARD;
+	fixSample(s);
+	resumeAudio();
+
+	updateSampleEditorSample();
+}
+
+void drawFilterResonance(void)
+{
+	const int16_t x = 172;
+
+	fillRect(x, 352, 18, 12, PAL_DESKTOP);
+
+	if (filterResonance <= 0)
+	{
+		textOut(x, 352, PAL_FORGRND, "off");
+	}
+	else
+	{
+		char str[16];
+		sprintf(str, "%02d", filterResonance);
+		textOut(x+3, 352, PAL_FORGRND, str);
+	}
+}
+
+void pbSfxResoUp(void)
+{
+	if (filterResonance < RESONANCE_RANGE)
+	{
+		filterResonance++;
+		drawFilterResonance();
+	}
+}
+
+void pbSfxResoDown(void)
+{
+	if (filterResonance > 0)
+	{
+		filterResonance--;
+		drawFilterResonance();
+	}
+}
+
+#define CUTOFF_EPSILON (1E-4)
+
+static void setupResoLpFilter(sample_t *s, resoFilter_t *f, double cutoff, uint32_t resonance, bool absoluteCutoff)
+{
+	// 12dB/oct resonant low-pass filter
+
+	if (!absoluteCutoff)
+	{
+		const double sampleFreq = getSampleC4Rate(s);
+		if (cutoff >= sampleFreq/2.0)
+			cutoff = (sampleFreq/2.0) - CUTOFF_EPSILON;
+
+		cutoff /= sampleFreq;
+	}
+
+	double r = sqrt(2.0);
+	if (resonance > 0)
+	{
+		r = pow(10.0, (resonance * -24.0) / (RESONANCE_RANGE * 20.0));
+		if (r < RESONANCE_MIN)
+			r = RESONANCE_MIN;
+	}
+
+	const double c = 1.0 / tan(PI * cutoff);
+
+	f->a1 = 1.0 / (1.0 + r * c + c * c);
+	f->a2 = 2.0 * f->a1;
+	f->a3 = f->a1;
+	f->b1 = 2.0 * (1.0 - c*c) * f->a1;
+	f->b2 = (1.0 - r * c + c * c) * f->a1;
+
+	f->inTmp[0] = f->inTmp[1] = f->outTmp[0] = f->outTmp[1] = 0.0; // clear filter history
+}
+
+static void setupResoHpFilter(sample_t *s, resoFilter_t *f, double cutoff, uint32_t resonance, bool absoluteCutoff)
+{
+	// 12dB/oct resonant high-pass filter
+
+	if (!absoluteCutoff)
+	{
+		const double sampleFreq = getSampleC4Rate(s);
+		if (cutoff >= sampleFreq/2.0)
+			cutoff = (sampleFreq/2.0) - CUTOFF_EPSILON;
+
+		cutoff /= sampleFreq;
+	}
+
+	double r = sqrt(2.0);
+	if (resonance > 0)
+	{
+		r = pow(10.0, (resonance * -24.0) / (RESONANCE_RANGE * 20.0));
+		if (r < RESONANCE_MIN)
+			r = RESONANCE_MIN;
+	}
+
+	const double c = tan(PI * cutoff);
+
+	f->a1 = 1.0 / (1.0 + r * c + c * c);
+	f->a2 = -2.0 * f->a1;
+	f->a3 = f->a1;
+	f->b1 = 2.0 * (c*c - 1.0) * f->a1;
+	f->b2 = (1.0 - r * c + c * c) * f->a1;
+
+	f->inTmp[0] = f->inTmp[1] = f->outTmp[0] = f->outTmp[1] = 0.0; // clear filter history
+}
+
+static bool applyResoFilter(sample_t *s, resoFilter_t *f)
+{
+	int32_t x1, x2;
+	if (smpEd_Rx1 < smpEd_Rx2)
+	{
+		x1 = smpEd_Rx1;
+		x2 = smpEd_Rx2;
+
+		if (x2 > s->length)
+			x2 = s->length;
+
+		if (x1 < 0)
+			x1 = 0;
+
+		if (x2 <= x1)
+			return true;
+	}
+	else
+	{
+		// no mark, operate on whole sample
+		x1 = 0;
+		x2 = s->length;
+	}
+	
+	const int32_t len = x2 - x1;
+
+	if (!normalization)
+	{
+		pauseAudio();
+		unfixSample(s);
+
+		if (s->flags & SAMPLE_16BIT)
+		{
+			int16_t *ptr16 = (int16_t *)s->dataPtr + x1;
+			for (int32_t i = 0; i < len; i++)
+			{
+				double out = (f->a1*ptr16[i]) + (f->a2*f->inTmp[0]) + (f->a3*f->inTmp[1]) - (f->b1*f->outTmp[0]) - (f->b2*f->outTmp[1]);
+
+				f->inTmp[1] = f->inTmp[0];
+				f->inTmp[0] = ptr16[i];
+
+				f->outTmp[1] = f->outTmp[0];
+				f->outTmp[0] = out;
+
+				ptr16[i] = (int16_t)CLAMP(out, INT16_MIN, INT16_MAX);
+			}
+		}
+		else
+		{
+			int8_t *ptr8 = (int8_t *)s->dataPtr + x1;
+			for (int32_t i = 0; i < len; i++)
+			{
+				double out = (f->a1*ptr8[i]) + (f->a2*f->inTmp[0]) + (f->a3*f->inTmp[1]) - (f->b1*f->outTmp[0]) - (f->b2*f->outTmp[1]);
+
+				f->inTmp[1] = f->inTmp[0];
+				f->inTmp[0] = ptr8[i];
+
+				f->outTmp[1] = f->outTmp[0];
+				f->outTmp[0] = out;
+
+				ptr8[i] = (int8_t)CLAMP(out, INT8_MIN, INT8_MAX);
+			}
+		}
+
+		fixSample(s);
+		resumeAudio();
+	}
+	else // normalize peak, no clipping
+	{
+		double *dSmp = (double *)malloc(len * sizeof (double));
+		if (dSmp == NULL)
+		{
+			okBox(0, "System message", "Not enough memory!", NULL);
+			return false;
+		}
+
+		pauseAudio();
+		unfixSample(s);
+
+		if (s->flags & SAMPLE_16BIT)
+		{
+			int16_t *ptr16 = (int16_t *)s->dataPtr + x1;
+			for (int32_t i = 0; i < len; i++)
+				dSmp[i] = (double)ptr16[i];
+		}
+		else
+		{
+			int8_t *ptr8 = (int8_t *)s->dataPtr + x1;
+			for (int32_t i = 0; i < len; i++)
+				dSmp[i] = (double)ptr8[i];
+		}
+
+		double peak = 0.0;
+		for (int32_t i = 0; i < len; i++)
+		{
+			const double out = (f->a1*dSmp[i]) + (f->a2*f->inTmp[0]) + (f->a3*f->inTmp[1]) - (f->b1*f->outTmp[0]) - (f->b2*f->outTmp[1]);
+
+			f->inTmp[1] = f->inTmp[0];
+			f->inTmp[0] = dSmp[i];
+
+			f->outTmp[1] = f->outTmp[0];
+			f->outTmp[0] = out;
+
+			dSmp[i] = out;
+
+			const double outAbs = fabs(out);
+			if (outAbs > peak)
+				peak = outAbs;
+		}
+
+		if (s->flags & SAMPLE_16BIT)
+		{
+			const double scale = INT16_MAX / peak;
+
+			int16_t *ptr16 = (int16_t *)s->dataPtr + x1;
+			for (int32_t i = 0; i < len; i++)
+				ptr16[i] = (int16_t)(dSmp[i] * scale);
+		}
+		else
+		{
+			const double scale = INT8_MAX / peak;
+
+			int8_t *ptr8 = (int8_t *)s->dataPtr + x1;
+			for (int32_t i = 0; i < len; i++)
+				ptr8[i] = (int8_t)(dSmp[i] * scale);
+		}
+
+		free(dSmp);
+
+		fixSample(s);
+		resumeAudio();
+	}
+
+	return true;
+}
+
+void pbSfxLowPass(void)
+{
+	resoFilter_t f;
+
+	sample_t *s = getCurSample();
+	if (s == NULL || s->dataPtr == NULL)
+		return;
+
+	char lengthStr[5+1];
+	memset(lengthStr, '\0', sizeof (lengthStr));
+	snprintf(lengthStr, sizeof (lengthStr), "%d", lastLpCutoff);
+
+	lastFilterType = FILTER_LOWPASS;
+	if (inputBox(6, "Enter low-pass filter cutoff (in Hz):", lengthStr, sizeof (lengthStr)-1) != 1)
+		return;
+
+	if (lengthStr[0] == '\0')
+		return;
+
+	lastLpCutoff = (int32_t)atoi(lengthStr);
+	if (lastLpCutoff < 1 || lastLpCutoff > 99999)
+	{
+		okBox(0, "System message", "Illegal range! Allowed range is 1..99999", NULL);
+		return;
+	}
+
+	setupResoLpFilter(s, &f, lastLpCutoff, filterResonance, false);
+	fillSampleUndo(KEEP_SAMPLE_MARK);
+	applyResoFilter(s, &f);
+	writeSample(true);
+}
+
+void pbSfxHighPass(void)
+{
+	resoFilter_t f;
+
+	sample_t *s = getCurSample();
+	if (s == NULL || s->dataPtr == NULL)
+		return;
+
+	char lengthStr[5+1];
+	memset(lengthStr, '\0', sizeof (lengthStr));
+	snprintf(lengthStr, sizeof (lengthStr), "%d", lastHpCutoff);
+
+	lastFilterType = FILTER_HIGHPASS;
+	if (inputBox(6, "Enter high-pass filter cutoff (in Hz):", lengthStr, sizeof (lengthStr)-1) != 1)
+		return;
+
+	if (lengthStr[0] == '\0')
+		return;
+
+	lastHpCutoff = (int32_t)atoi(lengthStr);
+	if (lastHpCutoff < 1 || lastHpCutoff > 99999)
+	{
+		okBox(0, "System message", "Illegal range! Allowed range is 1..99999", NULL);
+		return;
+	}
+
+	setupResoHpFilter(s, &f, lastHpCutoff, filterResonance, false);
+	fillSampleUndo(KEEP_SAMPLE_MARK);
+	applyResoFilter(s, &f);
+	writeSample(true);
+}
+
+void sfxPreviewFilter(uint32_t cutoff)
+{
+	sample_t oldSample;
+	resoFilter_t f;
+
+	sample_t *s = getCurSample();
+	if (s == NULL || s->dataPtr == NULL || s->length == 0 || cutoff < 1 || cutoff > 99999)
+		return;
+
+	int32_t x1, x2;
+	if (smpEd_Rx1 < smpEd_Rx2)
+	{
+		x1 = smpEd_Rx1;
+		x2 = smpEd_Rx2;
+
+		if (x2 > s->length)
+			x2 = s->length;
+
+		if (x1 < 0)
+			x1 = 0;
+
+		if (x2 <= x1)
+			return;
+	}
+	else
+	{
+		// no mark, operate on whole sample
+		x1 = 0;
+		x2 = s->length;
+	}
+
+	const int32_t len = x2 - x1;
+
+	pauseAudio();
+	unfixSample(s);
+	memcpy(&oldSample, s, sizeof (sample_t));
+
+	if (lastFilterType == FILTER_LOWPASS)
+		setupResoLpFilter(s, &f, cutoff, filterResonance, false);
+	else
+		setupResoHpFilter(s, &f, cutoff, filterResonance, false);
+
+	// prepare new sample
+	int8_t *sampleData;
+	if (s->flags & SAMPLE_16BIT)
+	{
+		sampleData = (int8_t *)malloc((len * sizeof (int16_t)) + SAMPLE_PAD_LENGTH);
+		if (sampleData == NULL)
+			goto Error;
+
+		memcpy(sampleData + SMP_DAT_OFFSET, (int16_t *)s->dataPtr + x1, len * sizeof (int16_t));
+	}
+	else
+	{
+		sampleData = (int8_t *)malloc((len * sizeof (int8_t)) + SAMPLE_PAD_LENGTH);
+		if (sampleData == NULL)
+			goto Error;
+
+		memcpy(sampleData + SMP_DAT_OFFSET, (int8_t *)s->dataPtr + x1, len * sizeof (int8_t));
+	}
+
+	s->origDataPtr = sampleData;
+	s->length = len;
+	s->dataPtr = s->origDataPtr + SMP_DAT_OFFSET;
+	s->loopStart = s->loopLength = 0;
+	fixSample(s);
+
+	const int32_t oldX1 = smpEd_Rx1;
+	const int32_t oldX2 = smpEd_Rx2;
+	smpEd_Rx1 = smpEd_Rx2 = 0;
+	applyResoFilter(s, &f);
+	smpEd_Rx1 = oldX1;
+	smpEd_Rx2 = oldX2;
+
+	// set up preview sample on channel 0
+	channel_t *ch = &channel[0];
+	uint8_t note = editor.smpEd_NoteNr;
+	ch->smpNum = editor.curSmp;
+	ch->instrNum = editor.curInstr;
+	ch->copyOfInstrAndNote = (ch->instrNum << 8) | note;
+	ch->efx = 0;
+	ch->smpStartPos = 0;
+	resumeAudio();
+	triggerNote(note, 0, 0, ch);
+	resetVolumes(ch);
+	triggerInstrument(ch);
+	ch->realVol = ch->outVol = ch->oldVol = 64;
+	updateVolPanAutoVib(ch);
+
+	while (ch->status & IS_Trigger); // wait for sample to latch in mixer
+	SDL_Delay(1500); // wait 1.5 seconds
+
+	// we're done, stop voice and free temporary data
+	pauseAudio();
+	free(sampleData);
+
+Error:
+	// set back old sample
+	memcpy(s, &oldSample, sizeof (sample_t));
+	fixSample(s);
+	resumeAudio();
+}
+
+void pbSfxSubBass(void)
+{
+	resoFilter_t f;
+
+	sample_t *s = getCurSample();
+	if (s == NULL || s->dataPtr == NULL)
+		return;
+
+	setupResoHpFilter(s, &f, 0.001, 0, true);
+	fillSampleUndo(KEEP_SAMPLE_MARK);
+	applyResoFilter(s, &f);
+	writeSample(true);
+}
+
+void pbSfxAddBass(void)
+{
+	resoFilter_t f;
+
+	sample_t *s = getCurSample();
+	if (s == NULL || s->dataPtr == NULL)
+		return;
+
+	int32_t x1, x2;
+	if (smpEd_Rx1 < smpEd_Rx2)
+	{
+		x1 = smpEd_Rx1;
+		x2 = smpEd_Rx2;
+
+		if (x2 > s->length)
+			x2 = s->length;
+
+		if (x1 < 0)
+			x1 = 0;
+
+		if (x2 <= x1)
+			return;
+	}
+	else
+	{
+		// no mark, operate on whole sample
+		x1 = 0;
+		x2 = s->length;
+	}
+	
+	const int32_t len = x2 - x1;
+
+	setupResoLpFilter(s, &f, 0.015, 0, true);
+
+	double *dSmp = (double *)malloc(len * sizeof (double));
+	if (dSmp == NULL)
+	{
+		okBox(0, "System message", "Not enough memory!", NULL);
+		return;
+	}
+
+	fillSampleUndo(KEEP_SAMPLE_MARK);
+
+	pauseAudio();
+	unfixSample(s);
+
+	if (s->flags & SAMPLE_16BIT)
+	{
+		int16_t *ptr16 = (int16_t *)s->dataPtr + x1;
+		for (int32_t i = 0; i < len; i++)
+			dSmp[i] = (double)ptr16[i];
+	}
+	else
+	{
+		int8_t *ptr8 = (int8_t *)s->dataPtr + x1;
+		for (int32_t i = 0; i < len; i++)
+			dSmp[i] = (double)ptr8[i];
+	}
+
+	if (!normalization)
+	{
+		for (int32_t i = 0; i < len; i++)
+		{
+			double out = (f.a1*dSmp[i]) + (f.a2*f.inTmp[0]) + (f.a3*f.inTmp[1]) - (f.b1*f.outTmp[0]) - (f.b2*f.outTmp[1]);
+
+			f.inTmp[1] = f.inTmp[0];
+			f.inTmp[0] = dSmp[i];
+
+			f.outTmp[1] = f.outTmp[0];
+			f.outTmp[0] = out;
+
+			dSmp[i] = out;
+		}
+
+		if (s->flags & SAMPLE_16BIT)
+		{
+			int16_t *ptr16 = (int16_t *)s->dataPtr + x1;
+			for (int32_t i = 0; i < len; i++)
+			{
+				double out = ptr16[i] + (dSmp[i] * 0.25);
+				out = CLAMP(out, INT16_MIN, INT16_MAX);
+
+				ptr16[i] = (int16_t)out;
+			}
+		}
+		else
+		{
+			int8_t *ptr8 = (int8_t *)s->dataPtr + x1;
+			for (int32_t i = 0; i < len; i++)
+			{
+				double out = ptr8[i] + (dSmp[i] * 0.25);
+				out = CLAMP(out, INT8_MIN, INT8_MAX);
+
+				ptr8[i] = (int8_t)out;
+			}
+		}
+	}
+	else
+	{
+		if (s->flags & SAMPLE_16BIT)
+		{
+			int16_t *ptr16 = (int16_t *)s->dataPtr + x1;
+
+			double peak = 0.0;
+			for (int32_t i = 0; i < len; i++)
+			{
+				double out = (f.a1*dSmp[i]) + (f.a2*f.inTmp[0]) + (f.a3*f.inTmp[1]) - (f.b1*f.outTmp[0]) - (f.b2*f.outTmp[1]);
+
+				f.inTmp[1] = f.inTmp[0];
+				f.inTmp[0] = dSmp[i];
+
+				f.outTmp[1] = f.outTmp[0];
+				f.outTmp[0] = out;
+
+				dSmp[i] = out;
+				double bass = ptr16[i] + (out * 0.25);
+
+				const double outAbs = fabs(bass);
+				if (outAbs > peak)
+					peak = outAbs;
+			}
+
+			double scale = INT16_MAX / peak;
+			for (int32_t i = 0; i < len; i++)
+				ptr16[i] = (int16_t)((ptr16[i] + (dSmp[i] * 0.25)) * scale);
+		}
+		else
+		{
+			int8_t *ptr8 = (int8_t *)s->dataPtr + x1;
+
+			double peak = 0.0;
+			for (int32_t i = 0; i < len; i++)
+			{
+				double out = (f.a1*dSmp[i]) + (f.a2*f.inTmp[0]) + (f.a3*f.inTmp[1]) - (f.b1*f.outTmp[0]) - (f.b2*f.outTmp[1]);
+
+				f.inTmp[1] = f.inTmp[0];
+				f.inTmp[0] = dSmp[i];
+
+				f.outTmp[1] = f.outTmp[0];
+				f.outTmp[0] = out;
+
+				dSmp[i] = out;
+				double bass = ptr8[i] + (out * 0.25);
+
+				const double outAbs = fabs(bass);
+				if (outAbs > peak)
+					peak = outAbs;
+			}
+
+			double scale = INT8_MAX / peak;
+			for (int32_t i = 0; i < len; i++)
+				ptr8[i] = (int8_t)((ptr8[i] + (dSmp[i] * 0.25)) * scale);
+		}
+	}
+
+	free(dSmp);
+
+	fixSample(s);
+	resumeAudio();
+
+	writeSample(true);
+}
+
+void pbSfxSubTreble(void)
+{
+	resoFilter_t f;
+
+	sample_t *s = getCurSample();
+	if (s == NULL || s->dataPtr == NULL)
+		return;
+
+	setupResoLpFilter(s, &f, 0.33, 0, true);
+	fillSampleUndo(KEEP_SAMPLE_MARK);
+	applyResoFilter(s, &f);
+	writeSample(true);
+}
+
+void pbSfxAddTreble(void)
+{
+	resoFilter_t f;
+
+	sample_t *s = getCurSample();
+	if (s == NULL || s->dataPtr == NULL)
+		return;
+
+	int32_t x1, x2;
+	if (smpEd_Rx1 < smpEd_Rx2)
+	{
+		x1 = smpEd_Rx1;
+		x2 = smpEd_Rx2;
+
+		if (x2 > s->length)
+			x2 = s->length;
+
+		if (x1 < 0)
+			x1 = 0;
+
+		if (x2 <= x1)
+			return;
+	}
+	else
+	{
+		// no mark, operate on whole sample
+		x1 = 0;
+		x2 = s->length;
+	}
+	
+	const int32_t len = x2 - x1;
+
+	setupResoHpFilter(s, &f, 0.27, 0, true);
+
+	double *dSmp = (double *)malloc(len * sizeof (double));
+	if (dSmp == NULL)
+	{
+		okBox(0, "System message", "Not enough memory!", NULL);
+		return;
+	}
+
+	fillSampleUndo(KEEP_SAMPLE_MARK);
+
+	pauseAudio();
+	unfixSample(s);
+
+	if (s->flags & SAMPLE_16BIT)
+	{
+		int16_t *ptr16 = (int16_t *)s->dataPtr + x1;
+		for (int32_t i = 0; i < len; i++)
+			dSmp[i] = (double)ptr16[i];
+	}
+	else
+	{
+		int8_t *ptr8 = (int8_t *)s->dataPtr + x1;
+		for (int32_t i = 0; i < len; i++)
+			dSmp[i] = (double)ptr8[i];
+	}
+
+	if (!normalization)
+	{
+		for (int32_t i = 0; i < len; i++)
+		{
+			double out = (f.a1*dSmp[i]) + (f.a2*f.inTmp[0]) + (f.a3*f.inTmp[1]) - (f.b1*f.outTmp[0]) - (f.b2*f.outTmp[1]);
+
+			f.inTmp[1] = f.inTmp[0];
+			f.inTmp[0] = dSmp[i];
+
+			f.outTmp[1] = f.outTmp[0];
+			f.outTmp[0] = out;
+
+			dSmp[i] = out;
+		}
+
+		if (s->flags & SAMPLE_16BIT)
+		{
+			int16_t *ptr16 = (int16_t *)s->dataPtr + x1;
+			for (int32_t i = 0; i < len; i++)
+			{
+				double out = ptr16[i] - (dSmp[i] * 0.25);
+				out = CLAMP(out, INT16_MIN, INT16_MAX);
+
+				ptr16[i] = (int16_t)out;
+			}
+		}
+		else
+		{
+			int8_t *ptr8 = (int8_t *)s->dataPtr + x1;
+			for (int32_t i = 0; i < len; i++)
+			{
+				double out = ptr8[i] - (dSmp[i] * 0.25);
+				out = CLAMP(out, INT8_MIN, INT8_MAX);
+
+				ptr8[i] = (int8_t)out;
+			}
+		}
+	}
+	else
+	{
+		if (s->flags & SAMPLE_16BIT)
+		{
+			int16_t *ptr16 = (int16_t *)s->dataPtr + x1;
+
+			double peak = 0.0;
+			for (int32_t i = 0; i < len; i++)
+			{
+				double out = (f.a1*dSmp[i]) + (f.a2*f.inTmp[0]) + (f.a3*f.inTmp[1]) - (f.b1*f.outTmp[0]) - (f.b2*f.outTmp[1]);
+
+				f.inTmp[1] = f.inTmp[0];
+				f.inTmp[0] = dSmp[i];
+
+				f.outTmp[1] = f.outTmp[0];
+				f.outTmp[0] = out;
+
+				dSmp[i] = out;
+				double treble = ptr16[i] - (out * 0.25);
+
+				const double outAbs = fabs(treble);
+				if (outAbs > peak)
+					peak = outAbs;
+			}
+
+			double scale = INT16_MAX / peak;
+			for (int32_t i = 0; i < len; i++)
+				ptr16[i] = (int16_t)((ptr16[i] - (dSmp[i] * 0.25)) * scale);
+		}
+		else
+		{
+			int8_t *ptr8 = (int8_t *)s->dataPtr + x1;
+
+			double peak = 0.0;
+			for (int32_t i = 0; i < len; i++)
+			{
+				double out = (f.a1*dSmp[i]) + (f.a2*f.inTmp[0]) + (f.a3*f.inTmp[1]) - (f.b1*f.outTmp[0]) - (f.b2*f.outTmp[1]);
+
+				f.inTmp[1] = f.inTmp[0];
+				f.inTmp[0] = dSmp[i];
+
+				f.outTmp[1] = f.outTmp[0];
+				f.outTmp[0] = out;
+
+				dSmp[i] = out;
+				double treble = ptr8[i] - (out * 0.25);
+
+				const double outAbs = fabs(treble);
+				if (outAbs > peak)
+					peak = outAbs;
+			}
+
+			double scale = INT8_MAX / peak;
+			for (int32_t i = 0; i < len; i++)
+				ptr8[i] = (int8_t)((ptr8[i] - (dSmp[i] * 0.25)) * scale);
+		}
+	}
+
+	free(dSmp);
+
+	fixSample(s);
+	resumeAudio();
+
+	writeSample(true);
+}
+
+void pbSfxSetAmp(void)
+{
+	sample_t *s = getCurSample();
+	if (s == NULL || s->dataPtr == NULL)
+		return;
+
+	int32_t x1, x2;
+	if (smpEd_Rx1 < smpEd_Rx2)
+	{
+		x1 = smpEd_Rx1;
+		x2 = smpEd_Rx2;
+
+		if (x2 > s->length)
+			x2 = s->length;
+
+		if (x1 < 0)
+			x1 = 0;
+
+		if (x2 <= x1)
+			return;
+	}
+	else
+	{
+		// no mark, operate on whole sample
+		x1 = 0;
+		x2 = s->length;
+	}
+	
+	const int32_t len = x2 - x1;
+
+	char ampStr[3+1];
+	memset(ampStr, '\0', sizeof (ampStr));
+	snprintf(ampStr, sizeof (ampStr), "%d", lastAmp);
+
+	if (inputBox(1, "Change sample amplitude (in percentage, 0..999):", ampStr, sizeof (ampStr)-1) != 1)
+		return;
+
+	if (ampStr[0] == '\0')
+		return;
+
+	lastAmp = (int32_t)atoi(ampStr);
+
+	fillSampleUndo(KEEP_SAMPLE_MARK);
+
+	pauseAudio();
+	unfixSample(s);
+
+	const int32_t mul = (int32_t)round((1 << 22UL) * (lastAmp / 100.0));
+
+	if (s->flags & SAMPLE_16BIT)
+	{
+		int16_t *ptr16 = (int16_t *)s->dataPtr + x1;
+		for (int32_t i = 0; i < len; i++)
+		{
+			int32_t sample = ((int64_t)ptr16[i] * (int32_t)mul) >> 22;
+			sample = CLAMP(sample, INT16_MIN, INT16_MAX);
+			ptr16[i] = (int16_t)sample;
+		}
+	}
+	else
+	{
+		int8_t *ptr8 = (int8_t *)s->dataPtr + x1;
+		for (int32_t i = 0; i < len; i++)
+		{
+			int32_t sample = ((int64_t)ptr8[i] * (int32_t)mul) >> 22;
+			sample = CLAMP(sample, INT8_MIN, INT8_MAX);
+			ptr8[i] = (int8_t)sample;
+		}
+	}
+
+	fixSample(s);
+	resumeAudio();
+
+	writeSample(true);
+}
+
+void pbSfxUndo(void)
+{
+	if (!sampleUndo.filled || sampleUndo.undoInstr != editor.curInstr || sampleUndo.undoSmp != editor.curSmp)
+		return;
+
+	sample_t *s = getCurSample();
+	if (s == NULL || s->dataPtr == NULL)
+		return;
+
+	pauseAudio();
+
+	freeSmpData(s);
+	s->flags = sampleUndo.flags;
+	s->length = sampleUndo.length;
+	s->loopStart = sampleUndo.loopStart;
+	s->loopLength = sampleUndo.loopLength;
+
+	if (allocateSmpData(s, s->length, !!(s->flags & SAMPLE_16BIT)))
+	{
+		if (s->flags & SAMPLE_16BIT)
+			memcpy(s->dataPtr, sampleUndo.smpData16, s->length * sizeof (int16_t));
+		else
+			memcpy(s->dataPtr, sampleUndo.smpData8, s->length * sizeof (int8_t));
+
+		fixSample(s);
+		resumeAudio();
+	}
+	else
+	{
+		resumeAudio();
+		okBox(0, "System message", "Not enough memory!", NULL);
+	}
+
+	int32_t oldRx1 = smpEd_Rx1;
+	int32_t oldRx2 = smpEd_Rx2;
+
+	updateSampleEditorSample();
+
+	if (sampleUndo.keepSampleMark && oldRx1 < oldRx2)
+	{
+		smpEd_Rx1 = oldRx1;
+		smpEd_Rx2 = oldRx2;
+		writeSample(false); // redraw sample mark only
+	}
+
+	sampleUndo.keepSampleMark = false;
+	sampleUndo.filled = false;
+}
+
+void hideSampleEffectsScreen(void)
+{
+	ui.sampleEditorEffectsShown = false;
+
+	hideCheckBox(CB_SAMPFX_NORMALIZATION);
+	hidePushButton(PB_SAMPFX_CYCLES_UP);
+	hidePushButton(PB_SAMPFX_CYCLES_DOWN);
+	hidePushButton(PB_SAMPFX_TRIANGLE);
+	hidePushButton(PB_SAMPFX_SAW);
+	hidePushButton(PB_SAMPFX_SINE);
+	hidePushButton(PB_SAMPFX_SQUARE);
+	hidePushButton(PB_SAMPFX_RESO_UP);
+	hidePushButton(PB_SAMPFX_RESO_DOWN);
+	hidePushButton(PB_SAMPFX_LOWPASS);
+	hidePushButton(PB_SAMPFX_HIGHPASS);
+	hidePushButton(PB_SAMPFX_SUB_BASS);
+	hidePushButton(PB_SAMPFX_SUB_TREBLE);
+	hidePushButton(PB_SAMPFX_ADD_BASS);
+	hidePushButton(PB_SAMPFX_ADD_TREBLE);
+	hidePushButton(PB_SAMPFX_SET_AMP);
+	hidePushButton(PB_SAMPFX_UNDO);
+	hidePushButton(PB_SAMPFX_XFADE);
+	hidePushButton(PB_SAMPFX_BACK);
+
+	drawFramework(0,   346, 115, 54, FRAMEWORK_TYPE1);
+	drawFramework(115, 346, 133, 54, FRAMEWORK_TYPE1);
+	drawFramework(248, 346,  49, 54, FRAMEWORK_TYPE1);
+	drawFramework(297, 346,  56, 54, FRAMEWORK_TYPE1);
+
+	showPushButton(PB_SAMP_PNOTE_UP);
+	showPushButton(PB_SAMP_PNOTE_DOWN);
+	showPushButton(PB_SAMP_STOP);
+	showPushButton(PB_SAMP_PWAVE);
+	showPushButton(PB_SAMP_PRANGE);
+	showPushButton(PB_SAMP_PDISPLAY);
+	showPushButton(PB_SAMP_SHOW_RANGE);
+	showPushButton(PB_SAMP_RANGE_ALL);
+	showPushButton(PB_SAMP_CLR_RANGE);
+	showPushButton(PB_SAMP_ZOOM_OUT);
+	showPushButton(PB_SAMP_SHOW_ALL);
+	showPushButton(PB_SAMP_SAVE_RNG);
+	showPushButton(PB_SAMP_CUT);
+	showPushButton(PB_SAMP_COPY);
+	showPushButton(PB_SAMP_PASTE);
+	showPushButton(PB_SAMP_CROP);
+	showPushButton(PB_SAMP_VOLUME);
+	showPushButton(PB_SAMP_EFFECTS);
+
+	drawFramework(2, 366,  34, 15, FRAMEWORK_TYPE2);
+	textOutShadow(5, 352, PAL_FORGRND, PAL_DSKTOP2, "Play:");
+	updateSampleEditor();
+}
+
+void pbEffects(void)
+{
+	hidePushButton(PB_SAMP_PNOTE_UP);
+	hidePushButton(PB_SAMP_PNOTE_DOWN);
+	hidePushButton(PB_SAMP_STOP);
+	hidePushButton(PB_SAMP_PWAVE);
+	hidePushButton(PB_SAMP_PRANGE);
+	hidePushButton(PB_SAMP_PDISPLAY);
+	hidePushButton(PB_SAMP_SHOW_RANGE);
+	hidePushButton(PB_SAMP_RANGE_ALL);
+	hidePushButton(PB_SAMP_CLR_RANGE);
+	hidePushButton(PB_SAMP_ZOOM_OUT);
+	hidePushButton(PB_SAMP_SHOW_ALL);
+	hidePushButton(PB_SAMP_SAVE_RNG);
+	hidePushButton(PB_SAMP_CUT);
+	hidePushButton(PB_SAMP_COPY);
+	hidePushButton(PB_SAMP_PASTE);
+	hidePushButton(PB_SAMP_CROP);
+	hidePushButton(PB_SAMP_VOLUME);
+	hidePushButton(PB_SAMP_EFFECTS);
+
+	drawFramework(0,   346, 116, 54, FRAMEWORK_TYPE1);
+	drawFramework(116, 346, 114, 54, FRAMEWORK_TYPE1);
+	drawFramework(230, 346,  67, 54, FRAMEWORK_TYPE1);
+	drawFramework(297, 346,  56, 54, FRAMEWORK_TYPE1);
+
+	checkBoxes[CB_SAMPFX_NORMALIZATION].checked = normalization ? true : false;
+	showCheckBox(CB_SAMPFX_NORMALIZATION);
+	showPushButton(PB_SAMPFX_CYCLES_UP);
+	showPushButton(PB_SAMPFX_CYCLES_DOWN);
+	showPushButton(PB_SAMPFX_TRIANGLE);
+	showPushButton(PB_SAMPFX_SAW);
+	showPushButton(PB_SAMPFX_SINE);
+	showPushButton(PB_SAMPFX_SQUARE);
+	showPushButton(PB_SAMPFX_RESO_UP);
+	showPushButton(PB_SAMPFX_RESO_DOWN);
+	showPushButton(PB_SAMPFX_LOWPASS);
+	showPushButton(PB_SAMPFX_HIGHPASS);
+	showPushButton(PB_SAMPFX_SUB_BASS);
+	showPushButton(PB_SAMPFX_SUB_TREBLE);
+	showPushButton(PB_SAMPFX_ADD_BASS);
+	showPushButton(PB_SAMPFX_ADD_TREBLE);
+	showPushButton(PB_SAMPFX_SET_AMP);
+	showPushButton(PB_SAMPFX_UNDO);
+	showPushButton(PB_SAMPFX_XFADE);
+	showPushButton(PB_SAMPFX_BACK);
+
+	textOutShadow(4,   352, PAL_FORGRND, PAL_DSKTOP2, "Cycles:");
+	drawSampleCycles();
+
+	textOutShadow(121, 352, PAL_FORGRND, PAL_DSKTOP2, "Reson.:");
+	drawFilterResonance();
+
+	textOutShadow(135, 386, PAL_FORGRND, PAL_DSKTOP2, "Normalization");
+
+	textOutShadow(235, 352, PAL_FORGRND, PAL_DSKTOP2, "Bass");
+	textOutShadow(235, 369, PAL_FORGRND, PAL_DSKTOP2, "Treb.");
+
+	ui.sampleEditorEffectsShown = true;
+}
--- /dev/null
+++ b/src/ft2_smpfx.h
@@ -1,0 +1,26 @@
+#pragma once
+
+#include <stdint.h>
+#include "ft2_header.h"
+
+void clearSampleUndo(void);
+void cbSfxNormalization(void);
+void pbSfxCyclesUp(void);
+void pbSfxCyclesDown(void);
+void pbSfxTriangle(void);
+void pbSfxSaw(void);
+void pbSfxSine(void);
+void pbSfxSquare(void);
+void pbSfxResoUp(void);
+void pbSfxResoDown(void);
+void pbSfxLowPass(void);
+void pbSfxHighPass(void);
+void sfxPreviewFilter(uint32_t cutof);
+void pbSfxSubBass(void);
+void pbSfxSubTreble(void);
+void pbSfxAddBass(void);
+void pbSfxAddTreble(void);
+void pbSfxSetAmp(void);
+void pbSfxUndo(void);
+void hideSampleEffectsScreen(void);
+void pbEffects(void);
--- a/src/ft2_structs.h
+++ b/src/ft2_structs.h
@@ -25,7 +25,7 @@
 
 	bool autoPlayOnDrop, trimThreadWasDone, throwExit, editTextFlag;
 	bool copyMaskEnable, diskOpReadOnOpen, samplingAudioFlag, editSampleFlag;
-	bool instrBankSwapped, chnMode[MAX_CHANNELS], NI_Play;
+	bool instrBankSwapped, channelMuted[MAX_CHANNELS], NI_Play;
 
 	uint8_t curPlayInstr, curPlaySmp, curSmpChannel, currPanEnvPoint, currVolEnvPoint;
 	uint8_t copyMask[5], pasteMask[5], transpMask[5], smpEd_NoteNr, instrBankOffset, sampleBankOffset;
@@ -56,7 +56,7 @@
 	uint8_t oldTopLeftScreen;
 
 	// bottom screens
-	bool patternEditorShown, instEditorShown, sampleEditorShown, pattChanScrollShown;
+	bool patternEditorShown, instEditorShown, sampleEditorShown, sampleEditorEffectsShown, pattChanScrollShown;
 	bool leftLoopPinMoving, rightLoopPinMoving;
 	bool drawReplayerPianoFlag, drawPianoFlag, updatePatternEditor;
 	uint8_t channelOffset, numChannelsShown, maxVisibleChannels;
@@ -66,7 +66,7 @@
 	// backup flag for when entering/exiting extended pattern editor (TODO: this is lame and shouldn't be hardcoded)
 	bool _aboutScreenShown, _helpScreenShown, _configScreenShown, _diskOpShown;
 	bool _nibblesShown, _transposeShown, _instEditorShown;
-	bool _instEditorExtShown, _sampleEditorExtShown, _patternEditorShown;
+	bool _instEditorExtShown, _sampleEditorExtShown, _sampleEditorEffectsShown, _patternEditorShown;
 	bool _sampleEditorShown, _advEditShown, _wavRendererShown, _trimScreenShown;
 	// -------------------------------------------------------------------------
 } ui_t;
--- a/src/ft2_sysreqs.c
+++ b/src/ft2_sysreqs.c
@@ -10,6 +10,7 @@
 #include "ft2_sysreqs.h"
 #include "ft2_structs.h"
 #include "ft2_events.h"
+#include "ft2_smpfx.h"
 
 #define SYSTEM_REQUEST_H 67
 #define SYSTEM_REQUEST_Y 249
@@ -21,7 +22,7 @@
 int16_t (*loaderSysReq)(int16_t, const char *, const char *, void (*)(void));
 // ----------------
 
-#define NUM_SYSREQ_TYPES 6
+#define NUM_SYSREQ_TYPES 7
 
 static char *buttonText[NUM_SYSREQ_TYPES][5] =
 {
@@ -33,7 +34,8 @@
 	// custom dialogs
 	{ "All", "Song", "Instruments", "Cancel", "" },   // "song clear" dialog
 	{ "Read left", "Read right", "Convert", "", "" }, // "stereo sample loader" dialog
-	{ "Mono", "Stereo", "Cancel", "","" }             // "audio sampling" dialog
+	{ "Mono", "Stereo", "Cancel", "","" },            // "audio sampling" dialog
+	{ "OK", "Preview", "Cancel", "","" }              // sample editor effects filters
 };
 
 static SDL_Keycode shortCut[NUM_SYSREQ_TYPES][5] =
@@ -47,6 +49,7 @@
 	{ SDLK_a, SDLK_s, SDLK_i, SDLK_c, 0 }, // "song clear" dialog
 	{ SDLK_l, SDLK_r, SDLK_c, 0,      0 }, // "stereo sample loader" dialog 
 	{ SDLK_m, SDLK_s, SDLK_c, 0,      0 }, // "audio sampling" dialog
+	{ SDLK_o, SDLK_p, SDLK_c, 0,      0 }  // sample editor effects filters
 };
 
 typedef struct quitType_t
@@ -191,7 +194,10 @@
 	SDL_Event inputEvent;
 
 	if (editor.editTextFlag)
+	{
 		exitTextEditing();
+		keyb.ignoreCurrKeyUp = false; // don't handle key-up kludge here
+	}
 
 	// revert "delete/rename" mouse modes (disk op.)
 	if (mouse.mode != MOUSE_MODE_NORMAL)
@@ -396,7 +402,10 @@
 	SDL_Event inputEvent;
 
 	if (editor.editTextFlag)
+	{
 		exitTextEditing();
+		keyb.ignoreCurrKeyUp = false; // don't handle key-up kludge here
+	}
 
 	// revert "delete/rename" mouse modes (disk op.)
 	if (mouse.mode != MOUSE_MODE_NORMAL)
@@ -553,10 +562,19 @@
 					{
 						if (shortCut[1][i] == inputEvent.key.keysym.sym)
 						{
-							returnVal = i + 1;
-							ui.sysReqShown = false;
-							keyb.ignoreCurrKeyUp = true; // don't handle key up event for any keys that were pressed
-							break;
+							if (type == 6 && returnVal == 2)
+							{
+								// special case for filters in sample editor "effects"
+								if (edText[0] != '\0')
+									sfxPreviewFilter(atoi(edText));
+							}
+							else
+							{
+								returnVal = i + 1;
+								ui.sysReqShown = false;
+								keyb.ignoreCurrKeyUp = true; // don't handle key up event for any keys that were pressed
+								break;
+							}
 						}
 					}
 				}
@@ -567,7 +585,17 @@
 				{
 					returnVal = testPushButtonMouseRelease(false) + 1;
 					if (returnVal > 0)
-						ui.sysReqShown = false;
+					{
+						if (type == 6 && returnVal == 2)
+						{
+							if (edText[0] != '\0')
+								sfxPreviewFilter(atoi(edText)); // special case for filters in sample editor "effects"
+						}
+						else
+						{
+							ui.sysReqShown = false;
+						}
+					}
 
 					mouse.lastUsedObjectID = OBJECT_ID_NONE;
 					mouse.lastUsedObjectType = OBJECT_NONE;
--- a/src/ft2_tables.c
+++ b/src/ft2_tables.c
@@ -79,7 +79,7 @@
 	  53,   50,   47,   45,   42,   40,   37,   35,   33,   31,   30,   28
 };
 
-const uint16_t linearPeriods[1936] = // bit-exact to FT2 table
+const uint16_t linearPeriodLUT[1936] = // bit-exact to FT2 table
 {
 	7744, 7740, 7736, 7732, 7728, 7724, 7720, 7716, 7712, 7708, 7704, 7700, 7696, 7692, 7688, 7684,
 	7680, 7676, 7672, 7668, 7664, 7660, 7656, 7652, 7648, 7644, 7640, 7636, 7632, 7628, 7624, 7620,
@@ -204,7 +204,7 @@
 	  64,   60,   56,   52,   48,   44,   40,   36,   32,   28,   24,   20,   16,   12,    8,    4
 };
 
-const uint16_t amigaPeriods[1936] = // bit-exact to FT2 table
+const uint16_t amigaPeriodLUT[1936] = // bit-exact to FT2 table
 {
 	29024, 28912, 28800, 28704, 28608, 28496, 28384, 28288, 28192, 28096, 28000, 27888, 27776, 27680, 27584, 27488,
 	27392, 27296, 27200, 27104, 27008, 26912, 26816, 26720, 26624, 26528, 26432, 26336, 26240, 26144, 26048, 25952,
@@ -710,23 +710,6 @@
 	24,  8,  8,  8,  8,  8,  8,  8, //  6 columns visible
 	24,  4,  4,  4,  4,  4,  4,  4, //  8 columns visible
 	24,  4,  4,  4,  4,  4,  4,  4  // 12 columns visible
-};
-
-// these two are for channel numbering on pattern data/scopes
-const char chDecTab1[MAX_CHANNELS+1] = 
-{
-	'0', '0', '0', '0', '0', '0', '0', '0', '0', '0',
-	'1', '1', '1', '1', '1', '1', '1', '1', '1', '1',
-	'2', '2', '2', '2', '2', '2', '2', '2', '2', '2',
-	'3', '3', '3'
-};
-
-const char chDecTab2[MAX_CHANNELS+1] = 
-{
-	'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
-	'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
-	'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
-	'0', '1', '2'
 };
 
 const SDL_Keycode key2VolTab[16] = 
--- a/src/ft2_tables.h
+++ b/src/ft2_tables.h
@@ -16,8 +16,8 @@
 extern const int8_t autoVibSineTab[256];
 extern const uint8_t vibratoTab[32];
 extern const uint16_t modPeriods[8 * 12];
-extern const uint16_t linearPeriods[1936];
-extern const uint16_t amigaPeriods[1936];
+extern const uint16_t linearPeriodLUT[1936];
+extern const uint16_t amigaPeriodLUT[1936];
 
 extern const char *dec2StrTab[100];
 extern const char *dec3StrTab[256];
@@ -37,8 +37,6 @@
 extern const markCoord_t markCoordTable[2][2][2];
 extern const uint8_t pattCursorXTab[2 * 4 * 8];
 extern const uint8_t pattCursorWTab[2 * 4 * 8];
-extern const char chDecTab1[MAX_CHANNELS+1];
-extern const char chDecTab2[MAX_CHANNELS+1];
 extern const SDL_Keycode key2VolTab[16];
 extern const SDL_Keycode key2EfxTab[36];
 extern const SDL_Keycode key2HexTab[16];
--- a/src/ft2_textboxes.c
+++ b/src/ft2_textboxes.c
@@ -722,7 +722,10 @@
 
 	// if we were editing text and we clicked outside of a text box, exit text editing
 	if (editor.editTextFlag)
+	{
 		exitTextEditing();
+		keyb.ignoreCurrKeyUp = false; // if we exited with mouse, don't handle key-up kludge
+	}
 
 	return false;
 }
--- a/src/scopes/ft2_scopes.c
+++ b/src/scopes/ft2_scopes.c
@@ -66,11 +66,11 @@
 }
 
 // toggle mute
-static void setChannel(int32_t chNr, bool on)
+static void setChannelMute(int32_t chNr, bool off)
 {
 	channel_t *ch = &channel[chNr];
 
-	ch->channelOff = !on;
+	ch->channelOff = off;
 	if (ch->channelOff)
 	{
 		ch->efx = 0;
@@ -104,8 +104,8 @@
 		}
 		else
 		{
-			charOutOutlined(scopeXOffs, scopeYOffs, PAL_MOUSEPT, chDecTab1[chNr]);
-			charOutOutlined(scopeXOffs + 7, scopeYOffs, PAL_MOUSEPT, chDecTab2[chNr]);
+			charOutOutlined(scopeXOffs, scopeYOffs, PAL_MOUSEPT, '0' + (chNr / 10));
+			charOutOutlined(scopeXOffs + 7, scopeYOffs, PAL_MOUSEPT, '0' + (chNr % 10));
 		}
 	}
 	else
@@ -116,8 +116,8 @@
 		}
 		else
 		{
-			charOut(scopeXOffs, scopeYOffs, PAL_MOUSEPT, chDecTab1[chNr]);
-			charOut(scopeXOffs + 7, scopeYOffs, PAL_MOUSEPT, chDecTab2[chNr]);
+			charOut(scopeXOffs, scopeYOffs, PAL_MOUSEPT, '0' + (chNr / 10));
+			charOut(scopeXOffs + 7, scopeYOffs, PAL_MOUSEPT, '0' + (chNr % 10));
 		}
 	}
 }
@@ -157,7 +157,7 @@
 	drawFramework(x, y, scopeLen + 2, 38, FRAMEWORK_TYPE2);
 
 	// draw mute graphics if channel is muted
-	if (!editor.chnMode[i])
+	if (editor.channelMuted[i])
 	{
 		const uint16_t muteGfxLen = scopeMuteBMP_Widths[chanLookup];
 		const uint16_t muteGfxX = x + ((scopeLen - muteGfxLen) >> 1);
@@ -191,7 +191,7 @@
 		bool test = false;
 		for (i = 0; i < song.numChannels; i++)
 		{
-			if (i != chn && !editor.chnMode[i])
+			if (i != chn && editor.channelMuted[i])
 				test = true;
 		}
 
@@ -198,21 +198,21 @@
 		if (test)
 		{
 			for (i = 0; i < song.numChannels; i++)
-				editor.chnMode[i] = true;
+				editor.channelMuted[i] = false;
 		}
 		else
 		{
 			for (i = 0; i < song.numChannels; i++)
-				editor.chnMode[i] = (i == chn);
+				editor.channelMuted[i] = !(i == chn);
 		}
 	}
 	else if (m)
 	{
-		editor.chnMode[chn] ^= 1;
+		editor.channelMuted[chn] ^= 1;
 	}
 	else
 	{
-		if (editor.chnMode[chn])
+		if (!editor.channelMuted[chn])
 		{
 			config.multiRecChn[chn] ^= 1;
 		}
@@ -219,13 +219,13 @@
 		else
 		{
 			config.multiRecChn[chn] = true;
-			editor.chnMode[chn] = true;
+			editor.channelMuted[chn] = false;
 			m = true;
 		}
 	}
 
 	for (i = 0; i < song.numChannels; i++)
-		setChannel(i, editor.chnMode[i]);
+		setChannelMute(i, editor.channelMuted[i]);
 
 	if (m2)
 	{
@@ -421,7 +421,7 @@
 		}
 
 		const uint16_t scopeDrawLen = scopeLens[i];
-		if (!editor.chnMode[i]) // scope muted (mute graphics blit()'ed elsewhere)
+		if (editor.channelMuted[i]) // scope muted (mute graphics blit()'ed elsewhere)
 		{
 			scopeXOffs += scopeDrawLen+3; // align x to next scope
 			continue;
--- a/vs2019_project/ft2-clone/ft2-clone.vcxproj
+++ b/vs2019_project/ft2-clone/ft2-clone.vcxproj
@@ -319,6 +319,7 @@
     <ClCompile Include="..\..\src\ft2_pattern_draw.c" />
     <ClCompile Include="..\..\src\ft2_pushbuttons.c" />
     <ClCompile Include="..\..\src\ft2_radiobuttons.c" />
+    <ClCompile Include="..\..\src\ft2_random.c" />
     <ClCompile Include="..\..\src\ft2_sample_ed_features.c" />
     <ClCompile Include="..\..\src\ft2_sampling.c" />
     <ClCompile Include="..\..\src\ft2_replayer.c" />
@@ -326,6 +327,7 @@
     <ClCompile Include="..\..\src\ft2_sample_loader.c" />
     <ClCompile Include="..\..\src\ft2_sample_saver.c" />
     <ClCompile Include="..\..\src\ft2_scrollbars.c" />
+    <ClCompile Include="..\..\src\ft2_smpfx.c" />
     <ClCompile Include="..\..\src\ft2_structs.c" />
     <ClCompile Include="..\..\src\ft2_sysreqs.c" />
     <ClCompile Include="..\..\src\ft2_tables.c" />
@@ -417,6 +419,7 @@
     <ClInclude Include="..\..\src\ft2_pattern_draw.h" />
     <ClInclude Include="..\..\src\ft2_pushbuttons.h" />
     <ClInclude Include="..\..\src\ft2_radiobuttons.h" />
+    <ClInclude Include="..\..\src\ft2_random.h" />
     <ClInclude Include="..\..\src\ft2_sample_ed_features.h" />
     <ClInclude Include="..\..\src\ft2_sampling.h" />
     <ClInclude Include="..\..\src\ft2_replayer.h" />
@@ -425,6 +428,7 @@
     <ClInclude Include="..\..\src\ft2_sample_saver.h" />
     <ClInclude Include="..\..\src\ft2_scopedraw.h" />
     <ClInclude Include="..\..\src\ft2_scrollbars.h" />
+    <ClInclude Include="..\..\src\ft2_smpfx.h" />
     <ClInclude Include="..\..\src\ft2_structs.h" />
     <ClInclude Include="..\..\src\ft2_sysreqs.h" />
     <ClInclude Include="..\..\src\ft2_tables.h" />
--- a/vs2019_project/ft2-clone/ft2-clone.vcxproj.filters
+++ b/vs2019_project/ft2-clone/ft2-clone.vcxproj.filters
@@ -176,6 +176,8 @@
     <ClCompile Include="..\..\src\mixer\ft2_quadratic_spline.c">
       <Filter>mixer</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\src\ft2_random.c" />
+    <ClCompile Include="..\..\src\ft2_smpfx.c" />
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="..\..\src\rtmidi\RtMidi.h">
@@ -336,6 +338,12 @@
     </ClInclude>
     <ClInclude Include="..\..\src\mixer\ft2_quadratic_spline.h">
       <Filter>mixer</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\ft2_random.h">
+      <Filter>headers</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\ft2_smpfx.h">
+      <Filter>headers</Filter>
     </ClInclude>
   </ItemGroup>
   <ItemGroup>
--