shithub: ft²

Download patch

ref: 8eb89a1cfaf049ba6d97626ae4e749fb1c328770
parent: 506d1b78eb878807fbc889cad6c14de881d7f063
author: Olav Sørensen <olav.sorensen@live.no>
date: Wed Jan 15 08:38:25 EST 2020

Pushed v1.06 code

- Bugfix: Scopes were not doing backwards sampling correctly on pingpong loops.
  This would also affect the sample playback line in Smp. Ed. It was especially
  noticable on very low sampling rates (note).
- For devs: Added HAS_MIDI compiler pre-processor flag. If not defined, MIDI
  will not be used in the clone. Handy for situations where rtmidi and/or
  libstdc++ can't be used/compiled.

--- /dev/null
+++ b/make-linux-nomidi.sh
@@ -1,0 +1,14 @@
+#!/bin/bash
+
+rm release/other/ft2-clone &> /dev/null
+echo Compiling \(with no MIDI functionality\), please wait patiently...
+
+# If you're compiling for *SLOW* devices, try adding -DLERPMIX right after gcc
+# This will activate 2-tap linear interpolation mixing (blurrier sound) instead
+# of 3-tap quadratic interpolation mixing (sharper sound)
+
+gcc -DNDEBUG src/gfxdata/*.c src/*.c -lSDL2 -lm -Wshadow -Winit-self -Wall -Wno-missing-field-initializers -Wno-unused-result -Wno-strict-aliasing -Wextra -Wunused -Wunreachable-code -Wswitch-default -march=native -mtune=native -O3 -o release/other/ft2-clone
+
+rm src/gfxdata/*.o src/*.o &> /dev/null
+
+echo Done! The executable is in the folder named \'release/other\'.
--- a/make-linux.sh
+++ b/make-linux.sh
@@ -7,7 +7,7 @@
 # This will activate 2-tap linear interpolation mixing (blurrier sound) instead
 # of 3-tap quadratic interpolation mixing (sharper sound)
 
-gcc -DNDEBUG -D__LINUX_ALSA__ src/rtmidi/*.cpp src/gfxdata/*.c src/*.c -lSDL2 -lpthread -lasound -lstdc++ -lm -Wshadow -Winit-self -Wall -Wno-missing-field-initializers -Wno-unused-result -Wno-strict-aliasing -Wextra -Wunused -Wunreachable-code -Wswitch-default -march=native -mtune=native -O3 -o release/other/ft2-clone
+gcc -DNDEBUG -DHAS_MIDI -D__LINUX_ALSA__ src/rtmidi/*.cpp src/gfxdata/*.c src/*.c -lSDL2 -lpthread -lasound -lstdc++ -lm -Wshadow -Winit-self -Wall -Wno-missing-field-initializers -Wno-unused-result -Wno-strict-aliasing -Wextra -Wunused -Wunreachable-code -Wswitch-default -march=native -mtune=native -O3 -o release/other/ft2-clone
 
 rm src/rtmidi/*.o src/gfxdata/*.o src/*.o &> /dev/null
 
--- a/make-macos.sh
+++ b/make-macos.sh
@@ -8,7 +8,7 @@
     
     rm release/macos/ft2-clone-macos.app/Contents/MacOS/ft2-clone-macos &> /dev/null
     
-    clang -mmacosx-version-min=10.7 -arch x86_64 -mmmx -mfpmath=sse -msse2 -I/Library/Frameworks/SDL2.framework/Headers -F/Library/Frameworks -g0 -DNDEBUG -D__MACOSX_CORE__ -stdlib=libc++ src/rtmidi/*.cpp src/gfxdata/*.c src/*.c -O3 /usr/lib/libiconv.dylib -lm -Winit-self -Wno-deprecated -Wextra -Wunused -mno-ms-bitfields -Wno-missing-field-initializers -Wswitch-default -framework SDL2 -framework CoreMidi -framework CoreAudio -framework Cocoa -lpthread -lm -lstdc++ -o release/macos/ft2-clone-macos.app/Contents/MacOS/ft2-clone-macos
+    clang -mmacosx-version-min=10.7 -arch x86_64 -mmmx -mfpmath=sse -msse2 -I/Library/Frameworks/SDL2.framework/Headers -F/Library/Frameworks -g0 -DNDEBUG -DHAS_MIDI -D__MACOSX_CORE__ -stdlib=libc++ src/rtmidi/*.cpp src/gfxdata/*.c src/*.c -O3 /usr/lib/libiconv.dylib -lm -Winit-self -Wno-deprecated -Wextra -Wunused -mno-ms-bitfields -Wno-missing-field-initializers -Wswitch-default -framework SDL2 -framework CoreMidi -framework CoreAudio -framework Cocoa -lpthread -lm -lstdc++ -o release/macos/ft2-clone-macos.app/Contents/MacOS/ft2-clone-macos
     strip release/macos/ft2-clone-macos.app/Contents/MacOS/ft2-clone-macos
     install_name_tool -change @rpath/SDL2.framework/Versions/A/SDL2 @executable_path/../Frameworks/SDL2.framework/Versions/A/SDL2 release/macos/ft2-clone-macos.app/Contents/MacOS/ft2-clone-macos
     
--- a/src/ft2_audio.c
+++ b/src/ft2_audio.c
@@ -168,10 +168,8 @@
 		tickTimeLen = (uint32_t)dInt;
 
 		// fractional part (scaled to 0..2^32-1)
-		dFrac *= UINT32_MAX + 1.0;
-		if (dFrac > (double)UINT32_MAX)
-			dFrac = (double)UINT32_MAX;
-		tickTimeLenFrac = (uint32_t)dFrac;
+		dFrac *= UINT32_MAX;
+		tickTimeLenFrac = (uint32_t)(dFrac + 0.5);
 	}
 }
 
@@ -1038,9 +1036,8 @@
 			chQueuePush(chSyncData);
 
 			audio.tickTime64 += tickTimeLen;
-
 			audio.tickTime64Frac += tickTimeLenFrac;
-			if (audio.tickTime64Frac >= (1ULL << 32))
+			if (audio.tickTime64Frac > 0xFFFFFFFF)
 			{
 				audio.tickTime64Frac &= 0xFFFFFFFF;
 				audio.tickTime64++;
@@ -1055,7 +1052,7 @@
 			b = pmpLeft;
 
 		mixAudio(stream, b, pmpChannels);
-		stream += (b * pmpCountDiv);
+		stream += b * pmpCountDiv;
 
 		a -= b;
 		pmpLeft -= b;
@@ -1133,14 +1130,15 @@
 
 static void calcAudioLatencyVars(uint16_t haveSamples, int32_t haveFreq)
 {
-	double dAudioLatencySecs, dInt, dFrac;
+	double dHaveFreq, dAudioLatencySecs, dInt, dFrac;
 
-	if (haveFreq == 0)
+	dHaveFreq = haveFreq;
+	if (dHaveFreq == 0.0)
 		return; // panic!
 
-	dAudioLatencySecs = haveSamples / (double)haveFreq;
+	dAudioLatencySecs = haveSamples / dHaveFreq;
 
-	// dear SDL2, haveSamples and haveFreq better not be bogus values...
+	// XXX: haveSamples and haveFreq better not be bogus values...
 	dFrac = modf(dAudioLatencySecs * editor.dPerfFreq, &dInt);
 
 	// integer part
@@ -1147,10 +1145,8 @@
 	audio.audLatencyPerfValInt = (uint32_t)dInt;
 
 	// fractional part (scaled to 0..2^32-1)
-	dFrac *= UINT32_MAX + 1.0;
-	if (dFrac > (double)UINT32_MAX)
-		dFrac = (double)UINT32_MAX;
-	audio.audLatencyPerfValFrac = (uint32_t)round(dFrac);
+	dFrac *= UINT32_MAX;
+	audio.audLatencyPerfValFrac = (uint32_t)(dFrac + 0.5);
 
 	audio.dAudioLatencyMs = dAudioLatencySecs * 1000.0;
 }
@@ -1200,8 +1196,10 @@
 	// get audio buffer size from config special flags
 
 	configAudioBufSize = 1024;
-	     if (config.specialFlags & BUFFSIZE_512)  configAudioBufSize = 512;
-	else if (config.specialFlags & BUFFSIZE_2048) configAudioBufSize = 2048;
+	if (config.specialFlags & BUFFSIZE_512)
+		configAudioBufSize = 512;
+	else if (config.specialFlags & BUFFSIZE_2048)
+		configAudioBufSize = 2048;
 
 	audio.wantFreq = config.audioFreq;
 	audio.wantSamples = configAudioBufSize;
@@ -1211,8 +1209,8 @@
 	memset(&want, 0, sizeof (want));
 
 	// these three may change after opening a device, but our mixer is dealing with it
-	want.freq     = config.audioFreq;
-	want.format   = (config.specialFlags & BITDEPTH_24) ? AUDIO_F32 : AUDIO_S16;
+	want.freq = config.audioFreq;
+	want.format = (config.specialFlags & BITDEPTH_24) ? AUDIO_F32 : AUDIO_S16;
 	want.channels = 2;
 	// -------------------------------------------------------------------------------
 	want.callback = mixCallback;
--- a/src/ft2_config.c
+++ b/src/ft2_config.c
@@ -269,6 +269,7 @@
 	audio.currOutputDevice = getAudioOutputDeviceFromConfig();
 	audio.currInputDevice = getAudioInputDeviceFromConfig();
 
+#ifdef HAS_MIDI
 	if (midi.initThreadDone)
 	{
 		setMidiInputDeviceFromConfig();
@@ -275,6 +276,7 @@
 		if (editor.ui.configScreenShown && editor.currConfigScreen == CONFIG_SCREEN_MIDI_INPUT)
 			drawMidiInputList();
 	}
+#endif
 
 	if (editor.configFileLocation == NULL)
 	{
@@ -388,7 +390,10 @@
 	}
 
 	saveAudioDevicesToConfig(audio.currOutputDevice, audio.currInputDevice);
+
+#ifdef HAS_MIDI
 	saveMidiInputDeviceToConfig();
+#endif
 
 	out = UNICHAR_FOPEN(editor.configFileLocation, "wb");
 	if (out == NULL)
@@ -791,7 +796,9 @@
 		case CONFIG_SCREEN_IO_DEVICES:    tmpID = RB_CONFIG_IO_DEVICES;    break;
 		case CONFIG_SCREEN_LAYOUT:        tmpID = RB_CONFIG_LAYOUT;        break;
 		case CONFIG_SCREEN_MISCELLANEOUS: tmpID = RB_CONFIG_MISCELLANEOUS; break;
+#ifdef HAS_MIDI
 		case CONFIG_SCREEN_MIDI_INPUT:    tmpID = RB_CONFIG_MIDI_INPUT;    break;
+#endif
 	}
 	radioButtons[tmpID].state = RADIOBUTTON_CHECKED;
 
@@ -1012,7 +1019,11 @@
 	checkBoxes[CB_CONF_QUANTIZATION].checked = config.recQuant;
 	checkBoxes[CB_CONF_CHANGE_PATTLEN_INS_DEL].checked = config.recTrueInsert;
 	checkBoxes[CB_CONF_MIDI_ALLOW_PC].checked = config.recMIDIAllowPC;
+#ifdef HAS_MIDI
 	checkBoxes[CB_CONF_MIDI_ENABLE].checked = midi.enable;
+#else
+	checkBoxes[CB_CONF_MIDI_ENABLE].checked = false;
+#endif
 	checkBoxes[CB_CONF_MIDI_REC_ALL].checked = config.recMIDIAllChn;
 	checkBoxes[CB_CONF_MIDI_REC_TRANS].checked = config.recMIDITransp;
 	checkBoxes[CB_CONF_MIDI_REC_VELOC].checked = config.recMIDIVelocity;
@@ -1097,8 +1108,9 @@
 	textOutShadow(22,  20, PAL_FORGRND, PAL_DSKTOP2, "I/O devices");
 	textOutShadow(22,  36, PAL_FORGRND, PAL_DSKTOP2, "Layout");
 	textOutShadow(22,  52, PAL_FORGRND, PAL_DSKTOP2, "Miscellaneous");
+#ifdef HAS_MIDI
 	textOutShadow(22,  68, PAL_FORGRND, PAL_DSKTOP2, "MIDI input");
-
+#endif
 	textOutShadow(19,  92, PAL_FORGRND, PAL_DSKTOP2, "Auto save");
 
 	switch (editor.currConfigScreen)
@@ -1377,13 +1389,13 @@
 
 			blitFast(517, 51, midiLogo, 103, 55);
 
+#ifdef HAS_MIDI
 			showPushButton(PB_CONFIG_MIDI_INPUT_DOWN);
 			showPushButton(PB_CONFIG_MIDI_INPUT_UP);
-
 			rescanMidiInputDevices();
 			drawMidiInputList();
-
 			showScrollBar(SB_MIDI_INPUT_SCROLL);
+#endif
 		}
 		break;
 	}
@@ -1490,10 +1502,12 @@
 	hideTextBox(TB_CONF_DEF_TRACKS_DIR);
 	hideScrollBar(SB_MIDI_SENS);
 
+#ifdef HAS_MIDI
 	// CONFIG MIDI
 	hidePushButton(PB_CONFIG_MIDI_INPUT_DOWN);
 	hidePushButton(PB_CONFIG_MIDI_INPUT_UP);
 	hideScrollBar(SB_MIDI_INPUT_SCROLL);
+#endif
 
 	editor.ui.configScreenShown = false;
 }
@@ -1543,6 +1557,7 @@
 	showConfigScreen();
 }
 
+#ifdef HAS_MIDI
 void rbConfigMidiInput(void)
 {
 	checkRadioButton(RB_CONFIG_MIDI_INPUT);
@@ -1551,6 +1566,7 @@
 	hideConfigScreen();
 	showConfigScreen();
 }
+#endif
 
 void rbConfigSbs512(void)
 {
@@ -2022,7 +2038,14 @@
 
 void cbMIDIEnable(void)
 {
+#ifdef HAS_MIDI
 	midi.enable ^= 1;
+#else
+	checkBoxes[CB_CONF_MIDI_ENABLE].checked = false;
+	drawCheckBox(CB_CONF_MIDI_ENABLE);
+
+	okBox(0, "System message", "This program was not compiled with MIDI functionality!");
+#endif
 }
 
 void cbMIDIRecTransp(void)
--- a/src/ft2_config.h
+++ b/src/ft2_config.h
@@ -197,7 +197,9 @@
 void rbConfigIODevices(void);
 void rbConfigLayout(void);
 void rbConfigMiscellaneous(void);
+#ifdef HAS_MIDI
 void rbConfigMidiInput(void);
+#endif
 void rbConfigSbs512(void);
 void rbConfigSbs1024(void);
 void rbConfigSbs2048(void);
--- a/src/ft2_edit.c
+++ b/src/ft2_edit.c
@@ -438,7 +438,13 @@
 		editor.keyOnTab[c] = note;
 
 		if (pattpos >= oldpattpos) // non-FT2 fix: only do this if we didn't quantize to next row
+		{
+#ifdef HAS_MIDI
 			playTone(c, editor.curInstr, note, vol, midi.currMIDIVibDepth, midi.currMIDIPitch);
+#else
+			playTone(c, editor.curInstr, note, vol, 0, 0);
+#endif
+		}
 
 		if (editmode || recmode)
 		{
@@ -490,7 +496,13 @@
 		editor.keyOffTime[c] = ++editor.keyOffNr;
 
 		if (pattpos >= oldpattpos) // non-FT2 fix: only do this if we didn't quantize to next row
+		{
+#ifdef HAS_MIDI
 			playTone(c, editor.curInstr, 97, vol, midi.currMIDIVibDepth, midi.currMIDIPitch);
+#else
+			playTone(c, editor.curInstr, 97, vol, 0, 0);
+#endif
+		}
 
 		if (config.recRelease && recmode)
 		{
--- a/src/ft2_events.c
+++ b/src/ft2_events.c
@@ -105,6 +105,7 @@
 
 void handleEvents(void)
 {
+#ifdef HAS_MIDI
 	// called after MIDI has been initialized
 	if (midi.rescanDevicesFlag)
 	{
@@ -114,6 +115,7 @@
 		if (editor.ui.configScreenShown && editor.currConfigScreen == CONFIG_SCREEN_MIDI_INPUT)
 			drawMidiInputList();
 	}
+#endif
 
 	if (editor.trimThreadWasDone)
 	{
@@ -396,7 +398,6 @@
 static void handleInput(void)
 {
 	char *inputText;
-	uint8_t vibDepth;
 	uint32_t eventType;
 	SDL_Event event;
 	SDL_Keycode key;
@@ -472,8 +473,10 @@
 		}
 		else if (event.type == SDL_MOUSEWHEEL)
 		{
-			     if (event.wheel.y > 0) mouseWheelHandler(MOUSE_WHEEL_UP);
-			else if (event.wheel.y < 0) mouseWheelHandler(MOUSE_WHEEL_DOWN);
+			if (event.wheel.y > 0)
+				mouseWheelHandler(MOUSE_WHEEL_UP);
+			else if (event.wheel.y < 0)
+				mouseWheelHandler(MOUSE_WHEEL_DOWN);
 		}
 		else if (event.type == SDL_DROPFILE)
 		{
@@ -527,8 +530,10 @@
 			editor.programRunning = false;
 	}
 
+#ifdef HAS_MIDI
 	// MIDI vibrato
-	vibDepth = (midi.currMIDIVibDepth >> 9) & 0x0F;
+	uint8_t vibDepth = (midi.currMIDIVibDepth >> 9) & 0x0F;
 	if (vibDepth > 0)
 		recordMIDIEffect(0x04, 0xA0 | vibDepth);
+#endif
 }
--- a/src/ft2_header.h
+++ b/src/ft2_header.h
@@ -12,7 +12,7 @@
 #endif
 #include "ft2_replayer.h"
 
-#define PROG_VER_STR "1.05"
+#define PROG_VER_STR "1.06"
 
 // do NOT change these! It will only mess things up...
 
--- a/src/ft2_keyboard.c
+++ b/src/ft2_keyboard.c
@@ -1186,6 +1186,7 @@
 
 				return true;
 			}
+#ifdef HAS_MIDI
 			else if (keyb.leftCtrlPressed)
 			{
 				editor.currConfigScreen = 3;
@@ -1194,6 +1195,7 @@
 
 				return true;
 			}
+#endif
 			break;
 
 		case SDLK_5:
--- a/src/ft2_main.c
+++ b/src/ft2_main.c
@@ -32,7 +32,9 @@
 #include "ft2_midi.h"
 #include "ft2_events.h"
 
+#ifdef HAS_MIDI
 static SDL_Thread *initMidiThread;
+#endif
 
 static void setupPerfFreq(void);
 static void initializeVars(void);
@@ -196,6 +198,7 @@
 		enterFullscreen();
 	}
 
+#ifdef HAS_MIDI
 	// set up MIDI input (in a thread because it can take quite a while on f.ex. macOS)
 	initMidiThread = SDL_CreateThread(initMidiFunc, NULL, NULL);
 	if (initMidiThread == NULL)
@@ -205,6 +208,7 @@
 		return 1;
 	}
 	SDL_DetachThread(initMidiThread); // don't wait for this thread, let it clean up when done
+#endif
 
 	setupWaitVBL();
 	handleModuleLoadFromArg(argc, argv);
@@ -278,22 +282,26 @@
 	editor.ptnJumpPos[3] = 0x30;
 
 	editor.copyMaskEnable = true;
-	memset(editor.copyMask,  1, sizeof (editor.copyMask));
+	memset(editor.copyMask, 1, sizeof (editor.copyMask));
 	memset(editor.pasteMask, 1, sizeof (editor.pasteMask));
 
+#ifdef HAS_MIDI
 	midi.enable = true;
+#endif
 
 	editor.diskOpReadOnOpen = true;
-	editor.programRunning   = true;
+	editor.programRunning = true;
 }
 
 static void cleanUpAndExit(void) // never call this inside the main loop!
 {
+#ifdef HAS_MIDI
 	if (midi.closeMidiOnExit)
 	{
 		closeMidiInDevice();
 		freeMidiIn();
 	}
+#endif
 
 	closeAudio();
 	closeReplayer();
@@ -302,16 +310,20 @@
 	freeDiskOp();
 	clearCopyBuffer();
 	freeAudioDeviceSelectorBuffers();
+#ifdef HAS_MIDI
 	freeMidiInputDeviceList();
+#endif
 	windUpFTHelp();
 	freeTextBoxes();
 	freeMouseCursors();
 
+#ifdef HAS_MIDI
 	if (midi.inputDeviceName != NULL)
 	{
 		free(midi.inputDeviceName);
 		midi.inputDeviceName = NULL;
 	}
+#endif
 
 	if (editor.audioDevConfigFileLocation != NULL)
 	{
@@ -392,10 +404,8 @@
 	video.vblankTimeLen = (uint32_t)dInt;
 
 	// fractional part scaled to 0..2^32-1
-	dFrac *= UINT32_MAX + 1.0;
-	if (dFrac > (double)UINT32_MAX)
-		dFrac = (double)UINT32_MAX;
-	video.vblankTimeLenFrac = (uint32_t)round(dFrac);
+	dFrac *= UINT32_MAX;
+	video.vblankTimeLenFrac = (uint32_t)(dFrac + 0.5);
 }
 
 #ifdef _WIN32
--- a/src/ft2_midi.c
+++ b/src/ft2_midi.c
@@ -1,3 +1,5 @@
+#ifdef HAS_MIDI
+
 // for finding memory leaks in debug mode with Visual Studio
 #if defined _DEBUG && defined _MSC_VER
 #include <crtdbg.h>
@@ -28,7 +30,7 @@
 
 static inline void midiInSetChannel(uint8_t status)
 {
-	recMIDIValidChn = (config.recMIDIAllChn || (status & 0x0F) == config.recMIDIChn -1);
+	recMIDIValidChn = (config.recMIDIAllChn || (status & 0xF) == config.recMIDIChn-1);
 }
 
 static inline void midiInKeyAction(int8_t m, uint8_t mv)
@@ -518,3 +520,7 @@
 
 	return true;
 }
+
+#else
+typedef int make_iso_compilers_happy; // kludge: prevent warning about empty .c file if HAS_MIDI is not defined
+#endif
--- a/src/ft2_midi.h
+++ b/src/ft2_midi.h
@@ -1,5 +1,7 @@
 #pragma once
 
+#ifdef HAS_MIDI
+
 #include <stdint.h>
 #include <stdbool.h>
 #include <SDL2/SDL.h>
@@ -32,3 +34,5 @@
 void sbMidiInputSetPos(uint32_t pos);
 bool testMidiInputDeviceListMouseDown(void);
 int32_t SDLCALL initMidiFunc(void *ptr);
+
+#endif
--- a/src/ft2_module_loader.c
+++ b/src/ft2_module_loader.c
@@ -2278,8 +2278,11 @@
 
 	editor.currVolEnvPoint = 0;
 	editor.currPanEnvPoint = 0;
+
+#ifdef HAS_MIDI
 	midi.currMIDIVibDepth = 0;
 	midi.currMIDIPitch = 0;
+#endif
 
 	memset(editor.keyOnTab, 0, sizeof (editor.keyOnTab));
 
--- a/src/ft2_mouse.c
+++ b/src/ft2_mouse.c
@@ -477,6 +477,7 @@
 						directionUp ? scrollAudInputDevListUp() : scrollAudInputDevListDown();
 				}
 			}
+#ifdef HAS_MIDI
 			else if (editor.currConfigScreen == CONFIG_SCREEN_MIDI_INPUT)
 			{
 				// midi input device selector
@@ -483,6 +484,7 @@
 				if (mouse.x >= 110 && mouse.x <= 503 && mouse.y <= 173)
 					directionUp ? scrollMidiInputDevListUp() : scrollMidiInputDevListDown();
 			}
+#endif
 		}
 
 		if (!editor.ui.aboutScreenShown  && !editor.ui.helpScreenShown &&
@@ -707,15 +709,18 @@
 	if (editor.ui.sysReqShown)
 		return;
 
-	if (testInstrVolEnvMouseDown(false))    return;
-	if (testInstrPanEnvMouseDown(false))    return;
-	if (testDiskOpMouseDown(false))         return;
-	if (testPianoKeysMouseDown(false))      return;
-	if (testSamplerDataMouseDown())         return;
-	if (testPatternDataMouseDown())         return;
-	if (testScopesMouseDown())              return;
-	if (testAudioDeviceListsMouseDown())    return;
+	if (testInstrVolEnvMouseDown(false)) return;
+	if (testInstrPanEnvMouseDown(false)) return;
+	if (testDiskOpMouseDown(false))      return;
+	if (testPianoKeysMouseDown(false))   return;
+	if (testSamplerDataMouseDown())      return;
+	if (testPatternDataMouseDown())      return;
+	if (testScopesMouseDown())           return;
+	if (testAudioDeviceListsMouseDown()) return;
+
+#ifdef HAS_MIDI
 	if (testMidiInputDeviceListMouseDown()) return;
+#endif
 }
 
 void handleLastGUIObjectDown(void)
--- a/src/ft2_palette.c
+++ b/src/ft2_palette.c
@@ -20,7 +20,7 @@
 
 void setPal16(pal16 *p, bool redrawScreen)
 {
-#define LOOP_PIN_COL_SUB 106
+#define LOOP_PIN_COL_SUB 118
 #define TEXT_MARK_COLOR 0x0078D7
 #define BOX_SELECT_COLOR 0x7F7F7F
 
--- a/src/ft2_pushbuttons.c
+++ b/src/ft2_pushbuttons.c
@@ -345,10 +345,12 @@
 	{ 556, 158, 22, 13, 1, 4, ARROW_LEFT_STRING,              NULL,    configMIDISensDown,  NULL },
 	{ 607, 158, 22, 13, 1, 4, ARROW_RIGHT_STRING,             NULL,    configMIDISensUp,    NULL },
 
+#ifdef HAS_MIDI
 	// ------ CONFIG MIDI PUSHBUTTONS ------
 	//x,   y,   w,  h,  p, d, text #1,           text #2, funcOnDown,                 funcOnUp
 	{ 483,   2, 18, 13, 1, 4, ARROW_UP_STRING,   NULL,    scrollMidiInputDevListUp,   NULL },
 	{ 483, 158, 18, 13, 1, 4, ARROW_DOWN_STRING, NULL,    scrollMidiInputDevListDown, NULL },
+#endif
 
 	// ------ DISK OP. PUSHBUTTONS ------
 	//x,   y,   w,  h,  p, d, text #1,           text #2, funcOnDown,       funcOnUp
--- a/src/ft2_pushbuttons.h
+++ b/src/ft2_pushbuttons.h
@@ -290,9 +290,11 @@
 	PB_CONFIG_MIDISENS_DOWN,
 	PB_CONFIG_MIDISENS_UP,
 
+#ifdef HAS_MIDI
 	// CONFIG MIDI
 	PB_CONFIG_MIDI_INPUT_DOWN,
 	PB_CONFIG_MIDI_INPUT_UP,
+#endif
 
 	// DISK OP.
 	PB_DISKOP_SAVE,
--- a/src/ft2_radiobuttons.c
+++ b/src/ft2_radiobuttons.c
@@ -65,7 +65,10 @@
 	{ 4, 19, 87, RB_GROUP_CONFIG_SELECT, rbConfigIODevices },
 	{ 4, 35, 59, RB_GROUP_CONFIG_SELECT, rbConfigLayout },
 	{ 4, 51, 99, RB_GROUP_CONFIG_SELECT, rbConfigMiscellaneous },
+
+#ifdef HAS_MIDI
 	{ 4, 67, 74, RB_GROUP_CONFIG_SELECT, rbConfigMidiInput },
+#endif
 
 	// ------ CONFIG AUDIO ------
 
--- a/src/ft2_radiobuttons.h
+++ b/src/ft2_radiobuttons.h
@@ -38,7 +38,10 @@
 	RB_CONFIG_IO_DEVICES,
 	RB_CONFIG_LAYOUT,
 	RB_CONFIG_MISCELLANEOUS,
+
+#ifdef HAS_MIDI
 	RB_CONFIG_MIDI_INPUT,
+#endif
 
 	// CONFIG AUDIO
 
--- a/src/ft2_replayer.c
+++ b/src/ft2_replayer.c
@@ -1452,7 +1452,11 @@
 	}
 
 	// *** AUTO VIBRATO ***
+#ifdef HAS_MIDI
 	if (ch->midiVibDepth > 0 || ins->vibDepth > 0)
+#else
+	if (ins->vibDepth > 0)
+#endif
 	{
 		if (ch->eVibSweep > 0)
 		{
@@ -1474,12 +1478,13 @@
 			autoVibAmp = ch->eVibAmp;
 		}
 
+#ifdef HAS_MIDI
 		// non-FT2 hack to make modulation wheel work when auto vibrato rate is zero
 		if (ch->midiVibDepth > 0 && ins->vibRate == 0)
 			ins->vibRate = 0x20;
 
 		autoVibAmp += ch->midiVibDepth;
-
+#endif
 		ch->eVibPos += ins->vibRate;
 
 		     if (ins->vibTyp == 1) autoVibVal = (ch->eVibPos > 127) ? 64 : -64; // square
@@ -1500,11 +1505,14 @@
 	else
 	{
 		ch->finalPeriod = ch->outPeriod;
+
+#ifdef HAS_MIDI
 		if (midi.enable)
 		{
 			ch->finalPeriod -= ch->midiPitch;
 			ch->status |= IS_Period;
 		}
+#endif
 	}
 }
 
@@ -2879,8 +2887,10 @@
 	if (songWasPlaying)
 		editor.pattPos = song.pattPos;
 
+#ifdef HAS_MIDI
 	midi.currMIDIVibDepth = 0;
 	midi.currMIDIPitch = 0;
+#endif
 
 	memset(editor.keyOnTab, 0, sizeof (editor.keyOnTab));
 
--- a/src/ft2_sample_ed_features.c
+++ b/src/ft2_sample_ed_features.c
@@ -1,8 +1,9 @@
 /* This file contains the routines for the following sample editor functions:
-** - Resampler
-** - Echo
-** - Mix
-** - Volume */
+ * - Resampler
+ * - Echo
+ * - Mix
+ * - Volume
+ */
 
 // for finding memory leaks in debug mode with Visual Studio
 #if defined _DEBUG && defined _MSC_VER
--- /dev/null
+++ b/src/ft2_scopedraw.c
@@ -1,0 +1,364 @@
+#include "ft2_scopes.h"
+#include "ft2_scopedraw.h"
+#include "ft2_video.h"
+
+/* ----------------------------------------------------------------------- */
+/*                          SCOPE DRAWING MACROS                           */
+/* ----------------------------------------------------------------------- */
+
+#define SCOPE_REGS \
+	int32_t sample; \
+	int32_t scopeDrawPos = s->SPos; \
+	int32_t scopeDrawFrac = 0; \
+	uint32_t scopePixelColor = video.palette[PAL_PATTEXT]; \
+	uint32_t len = x + w; \
+
+#define SCOPE_REGS_PINGPONG \
+	int32_t sample; \
+	int32_t scopeDrawPos = s->SPos; \
+	int32_t scopeDrawFrac = 0; \
+	int32_t drawPosDir = s->SPosDir; \
+	uint32_t scopePixelColor = video.palette[PAL_PATTEXT]; \
+	uint32_t len = x + w; \
+
+#define LINED_SCOPE_REGS \
+	int32_t sample; \
+	int32_t y1, y2; \
+	int32_t scopeDrawPos = s->SPos; \
+	int32_t scopeDrawFrac = 0; \
+	uint32_t len = (x + w) - 1; \
+
+#define LINED_SCOPE_REGS_PINGPONG \
+	int32_t sample; \
+	int32_t y1, y2; \
+	int32_t scopeDrawPos = s->SPos; \
+	int32_t scopeDrawFrac = 0; \
+	int32_t drawPosDir = s->SPosDir; \
+	uint32_t len = (x + w) - 1; \
+
+#define SCOPE_GET_SMP8 \
+	if (!s->active) \
+	{ \
+		sample = 0; \
+	} \
+	else \
+	{ \
+		assert(scopeDrawPos >= 0 && scopeDrawPos < s->SLen); \
+		sample = (s->sampleData8[scopeDrawPos] * s->SVol) >> 8; \
+	} \
+
+#define SCOPE_GET_SMP16 \
+	if (!s->active) \
+	{ \
+		sample = 0; \
+	} \
+	else \
+	{ \
+		assert(scopeDrawPos >= 0 && scopeDrawPos < s->SLen); \
+		sample = (int8_t)((s->sampleData16[scopeDrawPos] * s->SVol) >> 16); \
+	} \
+
+#define SCOPE_UPDATE_DRAWPOS \
+	scopeDrawFrac += s->SFrq >> 6; \
+	scopeDrawPos += scopeDrawFrac >> 16; \
+	scopeDrawFrac &= 0xFFFF; \
+
+#define SCOPE_UPDATE_DRAWPOS_PINGPONG \
+	scopeDrawFrac += s->SFrq >> 6; \
+	scopeDrawPos += (scopeDrawFrac >> 16) * drawPosDir; \
+	scopeDrawFrac &= 0xFFFF; \
+
+#define SCOPE_DRAW_SMP \
+	video.frameBuffer[((lineY - sample) * SCREEN_W) + x] = scopePixelColor;
+
+#define LINED_SCOPE_PREPARE_SMP8 \
+	SCOPE_GET_SMP8 \
+	y1 = lineY - sample; \
+	SCOPE_UPDATE_DRAWPOS
+
+#define LINED_SCOPE_PREPARE_SMP16 \
+	SCOPE_GET_SMP16 \
+	y1 = lineY - sample; \
+	SCOPE_UPDATE_DRAWPOS
+
+#define LINED_SCOPE_DRAW_SMP \
+	y2 = lineY - sample; \
+	scopeLine(x, y1, y2); \
+	y1 = y2; \
+
+#define SCOPE_HANDLE_POS_NO_LOOP \
+	if (scopeDrawPos >= s->SLen) \
+		s->active = false; \
+
+#define SCOPE_HANDLE_POS_LOOP \
+	if (scopeDrawPos >= s->SLen) \
+	{ \
+		if (s->SRepL < 2) \
+			scopeDrawPos = s->SRepS; \
+		else \
+			scopeDrawPos = s->SRepS + ((scopeDrawPos - s->SLen) % s->SRepL); \
+		\
+		assert(scopeDrawPos >= s->SRepS && scopeDrawPos < s->SLen); \
+	} \
+
+#define SCOPE_HANDLE_POS_PINGPONG \
+	if (drawPosDir == -1 && scopeDrawPos < s->SRepS) \
+	{ \
+		drawPosDir = 1; /* change direction to forwards */ \
+		\
+		if (s->SRepL < 2) \
+			scopeDrawPos = s->SRepS; \
+		else \
+			scopeDrawPos = s->SRepS + ((s->SRepS - scopeDrawPos - 1) % s->SRepL); \
+		\
+		assert(scopeDrawPos >= s->SRepS && scopeDrawPos < s->SLen); \
+	} \
+	else if (scopeDrawPos >= s->SLen) \
+	{ \
+		drawPosDir = -1; /* change direction to backwards */ \
+		\
+		if (s->SRepL < 2) \
+			scopeDrawPos = s->SLen - 1; \
+		else \
+			scopeDrawPos = (s->SLen - 1) - ((scopeDrawPos - s->SLen) % s->SRepL); \
+		\
+		assert(scopeDrawPos >= s->SRepS && scopeDrawPos < s->SLen); \
+	} \
+	assert(scopeDrawPos >= 0); \
+
+static void scopeLine(int32_t x1, int32_t y1, int32_t y2)
+{
+	int32_t pitch, d, sy, dy;
+	uint32_t ay, pixVal, *dst32;
+
+	dy = y2 - y1;
+	ay = ABS(dy);
+	sy = SGN(dy);
+
+	pixVal = video.palette[PAL_PATTEXT];
+	pitch = sy * SCREEN_W;
+
+	dst32 = &video.frameBuffer[(y1 * SCREEN_W) + x1];
+	*dst32 = pixVal;
+
+	if (ay <= 1)
+	{
+		if (ay != 0)
+			dst32 += pitch;
+
+		*++dst32 = pixVal;
+		return;
+	}
+
+	d = 2 - ay;
+
+	ay *= 2;
+	while (y1 != y2)
+	{
+		if (d >= 0)
+		{
+			d -= ay;
+			dst32++;
+		}
+
+		y1 += sy;
+		d += 2;
+
+		dst32 += pitch;
+		*dst32 = pixVal;
+	}
+}
+
+/* ----------------------------------------------------------------------- */
+/*                         SCOPE DRAWING ROUTINES                          */
+/* ----------------------------------------------------------------------- */
+
+static void scopeDrawNoLoop_8bit(scope_t *s, uint32_t x, uint32_t lineY, uint32_t w)
+{
+	SCOPE_REGS
+
+	for (; x < len; x++)
+	{
+		SCOPE_GET_SMP8
+		SCOPE_DRAW_SMP
+		SCOPE_UPDATE_DRAWPOS
+		SCOPE_HANDLE_POS_NO_LOOP
+	}
+}
+
+static void scopeDrawLoop_8bit(scope_t *s, uint32_t x, uint32_t lineY, uint32_t w)
+{
+	SCOPE_REGS
+
+	for (; x < len; x++)
+	{
+		SCOPE_GET_SMP8
+		SCOPE_DRAW_SMP
+		SCOPE_UPDATE_DRAWPOS
+		SCOPE_HANDLE_POS_LOOP
+	}
+}
+
+static void scopeDrawPingPong_8bit(scope_t *s, uint32_t x, uint32_t lineY, uint32_t w)
+{
+	SCOPE_REGS_PINGPONG
+
+	for (; x < len; x++)
+	{
+		SCOPE_GET_SMP8
+		SCOPE_DRAW_SMP
+		SCOPE_UPDATE_DRAWPOS_PINGPONG
+		SCOPE_HANDLE_POS_PINGPONG
+	}
+}
+
+static void scopeDrawNoLoop_16bit(scope_t *s, uint32_t x, uint32_t lineY, uint32_t w)
+{
+	SCOPE_REGS
+
+	for (; x < len; x++)
+	{
+		SCOPE_GET_SMP16
+		SCOPE_DRAW_SMP
+		SCOPE_UPDATE_DRAWPOS
+		SCOPE_HANDLE_POS_NO_LOOP
+	}
+}
+
+static void scopeDrawLoop_16bit(scope_t *s, uint32_t x, uint32_t lineY, uint32_t w)
+{
+	SCOPE_REGS
+
+	for (; x < len; x++)
+	{
+		SCOPE_GET_SMP16
+		SCOPE_DRAW_SMP
+		SCOPE_UPDATE_DRAWPOS
+		SCOPE_HANDLE_POS_LOOP
+	}
+}
+
+static void scopeDrawPingPong_16bit(scope_t *s, uint32_t x, uint32_t lineY, uint32_t w)
+{
+	SCOPE_REGS_PINGPONG
+
+	for (; x < len; x++)
+	{
+		SCOPE_GET_SMP16
+		SCOPE_DRAW_SMP
+		SCOPE_UPDATE_DRAWPOS_PINGPONG
+		SCOPE_HANDLE_POS_PINGPONG
+	}
+}
+
+/* ----------------------------------------------------------------------- */
+/*                      LINED SCOPE DRAWING ROUTINES                       */
+/* ----------------------------------------------------------------------- */
+
+static void linedScopeDrawNoLoop_8bit(scope_t *s, uint32_t x, uint32_t lineY, uint32_t w)
+{
+	LINED_SCOPE_REGS
+	LINED_SCOPE_PREPARE_SMP8
+	SCOPE_HANDLE_POS_NO_LOOP
+
+	for (; x < len; x++)
+	{
+		SCOPE_GET_SMP8
+		LINED_SCOPE_DRAW_SMP
+		SCOPE_UPDATE_DRAWPOS
+		SCOPE_HANDLE_POS_NO_LOOP
+	}
+}
+
+static void linedScopeDrawLoop_8bit(scope_t *s, uint32_t x, uint32_t lineY, uint32_t w)
+{
+	LINED_SCOPE_REGS
+	LINED_SCOPE_PREPARE_SMP8
+	SCOPE_HANDLE_POS_LOOP
+
+	for (; x < len; x++)
+	{
+		SCOPE_GET_SMP8
+		LINED_SCOPE_DRAW_SMP
+		SCOPE_UPDATE_DRAWPOS
+		SCOPE_HANDLE_POS_LOOP
+	}
+}
+
+static void linedScopeDrawPingPong_8bit(scope_t *s, uint32_t x, uint32_t lineY, uint32_t w)
+{
+	LINED_SCOPE_REGS_PINGPONG
+	LINED_SCOPE_PREPARE_SMP8
+	SCOPE_HANDLE_POS_PINGPONG
+
+	for (; x < len; x++)
+	{
+		SCOPE_GET_SMP8
+		LINED_SCOPE_DRAW_SMP
+		SCOPE_UPDATE_DRAWPOS_PINGPONG
+		SCOPE_HANDLE_POS_PINGPONG
+	}
+}
+
+static void linedScopeDrawNoLoop_16bit(scope_t *s, uint32_t x, uint32_t lineY, uint32_t w)
+{
+	LINED_SCOPE_REGS
+	LINED_SCOPE_PREPARE_SMP16
+	SCOPE_HANDLE_POS_NO_LOOP
+
+	for (; x < len; x++)
+	{
+		SCOPE_GET_SMP16
+		LINED_SCOPE_DRAW_SMP
+		SCOPE_UPDATE_DRAWPOS
+		SCOPE_HANDLE_POS_NO_LOOP
+	}
+}
+
+static void linedScopeDrawLoop_16bit(scope_t *s, uint32_t x, uint32_t lineY, uint32_t w)
+{
+	LINED_SCOPE_REGS
+	LINED_SCOPE_PREPARE_SMP16
+	SCOPE_HANDLE_POS_LOOP
+
+	for (; x < len; x++)
+	{
+		SCOPE_GET_SMP16
+		LINED_SCOPE_DRAW_SMP
+		SCOPE_UPDATE_DRAWPOS
+		SCOPE_HANDLE_POS_LOOP
+	}
+}
+
+static void linedScopeDrawPingPong_16bit(scope_t *s, uint32_t x, uint32_t lineY, uint32_t w)
+{
+	LINED_SCOPE_REGS_PINGPONG
+	LINED_SCOPE_PREPARE_SMP16
+	SCOPE_HANDLE_POS_PINGPONG
+
+	for (; x < len; x++)
+	{
+		SCOPE_GET_SMP16
+		LINED_SCOPE_DRAW_SMP
+		SCOPE_UPDATE_DRAWPOS_PINGPONG
+		SCOPE_HANDLE_POS_PINGPONG
+	}
+}
+
+// -----------------------------------------------------------------------
+
+const scopeDrawRoutine scopeDrawRoutineTable[12] =
+{
+	(scopeDrawRoutine)scopeDrawNoLoop_8bit,
+	(scopeDrawRoutine)scopeDrawLoop_8bit,
+	(scopeDrawRoutine)scopeDrawPingPong_8bit,
+	(scopeDrawRoutine)scopeDrawNoLoop_16bit,
+	(scopeDrawRoutine)scopeDrawLoop_16bit,
+	(scopeDrawRoutine)scopeDrawPingPong_16bit,
+	(scopeDrawRoutine)linedScopeDrawNoLoop_8bit,
+	(scopeDrawRoutine)linedScopeDrawLoop_8bit,
+	(scopeDrawRoutine)linedScopeDrawPingPong_8bit,
+	(scopeDrawRoutine)linedScopeDrawNoLoop_16bit,
+	(scopeDrawRoutine)linedScopeDrawLoop_16bit,
+	(scopeDrawRoutine)linedScopeDrawPingPong_16bit
+};
--- /dev/null
+++ b/src/ft2_scopedraw.h
@@ -1,0 +1,8 @@
+#pragma once
+
+#include <stdint.h>
+#include "ft2_scopes.h"
+
+typedef void (*scopeDrawRoutine)(scope_t *, uint32_t, uint32_t, uint32_t);
+
+extern const scopeDrawRoutine scopeDrawRoutineTable[12]; // ft2_scopedraw.c
--- a/src/ft2_scopes.c
+++ b/src/ft2_scopes.c
@@ -19,6 +19,7 @@
 #include "ft2_scopes.h"
 #include "ft2_mouse.h"
 #include "ft2_video.h"
+#include "ft2_scopedraw.h"
 
 enum
 {
@@ -37,19 +38,6 @@
 	int32_t len, repS, repL, playOffset;
 } scopeState_t;
 
-// actual scope data
-typedef struct scope_t
-{
-	volatile bool active;
-	const int8_t *sampleData8;
-	const int16_t *sampleData16;
-	int8_t SVol;
-	bool wasCleared, sample16Bit;
-	uint8_t loopType;
-	int32_t SRepS, SRepL, SLen, SPos;
-	uint32_t SFrq, SPosDec, posXOR;
-} scope_t;
-
 static volatile bool scopesUpdatingFlag, scopesDisplayingFlag;
 static uint32_t oldVoiceDelta, oldSFrq, scopeTimeLen, scopeTimeLenFrac;
 static uint64_t timeNext64, timeNext64Frac;
@@ -408,7 +396,7 @@
 	tempState.sample16Bit = sampleIs16Bit;
 	tempState.loopType = loopType;
 
-	tempState.posXOR = 0; // forwards
+	tempState.SPosDir = 1; // forwards
 	tempState.SLen = (loopType > 0) ? (loopBegin + loopLength) : length;
 	tempState.SRepS = loopBegin;
 	tempState.SRepL = loopLength;
@@ -454,14 +442,14 @@
 		// scope position update
 
 		tempState.SPosDec += tempState.SFrq;
-		tempState.SPos += ((tempState.SPosDec >> 16) ^ tempState.posXOR);
+		tempState.SPos += ((tempState.SPosDec >> 16) * tempState.SPosDir);
 		tempState.SPosDec &= 0xFFFF;
 
 		// handle loop wrapping or sample end
 
-		if (tempState.posXOR == 0xFFFFFFFF && tempState.SPos < tempState.SRepS) // sampling backwards (definitely pingpong loop)
+		if (tempState.SPosDir == -1 && tempState.SPos < tempState.SRepS) // sampling backwards (definitely pingpong loop)
 		{
-			tempState.posXOR = 0; // change direction to forwards
+			tempState.SPosDir = 1; // change direction to forwards
 
 			if (tempState.SRepL < 2)
 				tempState.SPos = tempState.SRepS;
@@ -488,7 +476,7 @@
 			}
 			else // pingpong loop
 			{
-				tempState.posXOR = 0xFFFFFFFF; // change direction to backwards
+				tempState.SPosDir = -1; // change direction to backwards
 				tempState.SPos = (tempState.SLen - 1) - loopOverflowVal;
 				assert(tempState.SPos >= tempState.SRepS && tempState.SPos < tempState.SLen);
 			}
@@ -500,118 +488,11 @@
 	scopesUpdatingFlag = false;
 }
 
-static void scopeLine(int16_t x1, int16_t y1, int16_t y2)
-{
-	int16_t d, sy, dy;
-	uint16_t ay;
-	int32_t pitch;
-	uint32_t pixVal, *dst32;
-
-	dy = y2 - y1;
-	ay = ABS(dy);
-	sy = SGN(dy);
-
-	pixVal = video.palette[PAL_PATTEXT];
-	pitch = sy * SCREEN_W;
-
-	dst32 = &video.frameBuffer[(y1 * SCREEN_W) + x1];
-	*dst32 = pixVal;
-
-	if (ay <= 1)
-	{
-		if (ay != 0)
-			dst32 += pitch;
-
-		*++dst32 = pixVal;
-		return;
-	}
-
-	d = 2 - ay;
-
-	ay *= 2;
-	while (y1 != y2)
-	{
-		if (d >= 0)
-		{
-			d -= ay;
-			dst32++;
-		}
-
-		y1 += sy;
-		d  += 2;
-
-		 dst32 += pitch;
-		*dst32  = pixVal;
-	}
-}
-
-static inline int8_t getScaledScopeSample8(scope_t *sc, int32_t drawPos)
-{
-	if (!sc->active)
-		return 0;
-
-	assert(drawPos >= 0 && drawPos < sc->SLen);
-	return (sc->sampleData8[drawPos] * sc->SVol) >> 8;
-}
-
-static inline int8_t getScaledScopeSample16(scope_t *sc, int32_t drawPos)
-{
-	if (!sc->active)
-		return 0;
-
-	assert(drawPos >= 0 && drawPos < sc->SLen);
-	return (int8_t)((sc->sampleData16[drawPos] * sc->SVol) >> 16);
-}
-
-#define SCOPE_UPDATE_DRAWPOS \
-	scopeDrawFrac += s.SFrq >> 6; \
-	scopeDrawPos += ((scopeDrawFrac >> 16) ^ drawPosXOR); \
-	scopeDrawFrac &= 0xFFFF; \
-	\
-	if (drawPosXOR == 0xFFFFFFFF && scopeDrawPos < s.SRepS) /* sampling backwards (definitely pingpong loop) */ \
-	{ \
-		drawPosXOR = 0; /* change direction to forwards */ \
-		\
-		if (s.SRepL < 2) \
-			scopeDrawPos = s.SRepS; \
-		else \
-			scopeDrawPos = s.SRepS + ((s.SRepS - scopeDrawPos - 1) % s.SRepL); \
-		\
-		assert(scopeDrawPos >= s.SRepS && scopeDrawPos < s.SLen); \
-	} \
-	else if (scopeDrawPos >= s.SLen) \
-	{ \
-		if (s.SRepL < 2) \
-			loopOverflowVal = 0; \
-		else \
-			loopOverflowVal = (scopeDrawPos - s.SLen) % s.SRepL; \
-		\
-		if (s.loopType == LOOP_NONE) \
-		{ \
-			s.active = false; \
-		} \
-		else if (s.loopType == LOOP_FORWARD) \
-		{ \
-			scopeDrawPos = s.SRepS + loopOverflowVal; \
-			assert(scopeDrawPos >= s.SRepS && scopeDrawPos < s.SLen); \
-		} \
-		else /* pingpong loop */ \
-		{ \
-			drawPosXOR = 0xFFFFFFFF; /* change direction to backwards */ \
-			scopeDrawPos = (s.SLen - 1) - loopOverflowVal; \
-			assert(scopeDrawPos >= s.SRepS && scopeDrawPos < s.SLen); \
-		} \
-		\
-	} \
-	assert(scopeDrawPos >= 0); \
-
 void drawScopes(void)
 {
-	int16_t y1, y2, sample, scopeLineY;
+	int16_t scopeLineY;
 	const uint16_t *scopeLens;
-	uint16_t chansPerRow, x16, scopeXOffs, scopeYOffs, scopeDrawLen;
-	int32_t scopeDrawPos, loopOverflowVal;
-	uint32_t x, len, drawPosXOR, scopeDrawFrac, scopePixelColor;
+	uint16_t chansPerRow, scopeXOffs, scopeYOffs, scopeDrawLen;
 	volatile scope_t *sc;
 	scope_t s;
 
@@ -651,80 +532,9 @@
 			// clear scope background
 			clearRect(scopeXOffs, scopeYOffs, scopeDrawLen, SCOPE_HEIGHT);
 
-			scopeDrawPos = s.SPos;
-			scopeDrawFrac = 0;
-			drawPosXOR = s.posXOR;
-
-			// draw current scope
-			if (config.specialFlags & LINED_SCOPES)
-			{
-				// LINE SCOPE
-
-				if (s.sample16Bit)
-				{
-					y1 = scopeLineY - getScaledScopeSample16(&s, scopeDrawPos);
-					SCOPE_UPDATE_DRAWPOS
-
-					x16 = scopeXOffs;
-					len = scopeXOffs + (scopeDrawLen - 1);
-
-					for (; x16 < len; x16++)
-					{
-						y2 = scopeLineY - getScaledScopeSample16(&s, scopeDrawPos);
-						scopeLine(x16, y1, y2);
-						y1 = y2;
-
-						SCOPE_UPDATE_DRAWPOS
-					}
-				}
-				else
-				{
-					y1 = scopeLineY - getScaledScopeSample8(&s, scopeDrawPos);
-					SCOPE_UPDATE_DRAWPOS
-
-					x16 = scopeXOffs;
-					len = scopeXOffs + (scopeDrawLen - 1);
-
-					for (; x16 < len; x16++)
-					{
-						y2 = scopeLineY - getScaledScopeSample8(&s, scopeDrawPos);
-						scopeLine(x16, y1, y2);
-						y1 = y2;
-
-						SCOPE_UPDATE_DRAWPOS
-					}
-				}
-			}
-			else
-			{
-				// PIXEL SCOPE
-
-				scopePixelColor = video.palette[PAL_PATTEXT];
-
-				x = scopeXOffs;
-				len = scopeXOffs + scopeDrawLen;
-
-				if (s.sample16Bit)
-				{
-					for (; x < len; x++)
-					{
-						sample = getScaledScopeSample16(&s, scopeDrawPos);
-						video.frameBuffer[((scopeLineY - sample) * SCREEN_W) + x] = scopePixelColor;
-
-						SCOPE_UPDATE_DRAWPOS
-					}
-				}
-				else
-				{
-					for (; x < len; x++)
-					{
-						sample = getScaledScopeSample8(&s, scopeDrawPos);
-						video.frameBuffer[((scopeLineY - sample) * SCREEN_W) + x] = scopePixelColor;
-
-						SCOPE_UPDATE_DRAWPOS
-					}
-				}
-			}
+			// draw scope
+			bool linedScopes = !!(config.specialFlags & LINED_SCOPES);
+			scopeDrawRoutineTable[(linedScopes * 6) + (s.sample16Bit * 3) + s.loopType](&s, scopeXOffs, scopeLineY, scopeDrawLen);
 		}
 		else
 		{
@@ -850,12 +660,11 @@
 
 		// update next tick time
 		timeNext64 += scopeTimeLen;
-
 		timeNext64Frac += scopeTimeLenFrac;
-		if (timeNext64Frac >= (1ULL << 32))
+		if (timeNext64Frac > 0xFFFFFFFF)
 		{
-			timeNext64++;
 			timeNext64Frac &= 0xFFFFFFFF;
+			timeNext64++;
 		}
 	}
 
@@ -872,11 +681,9 @@
 	// integer part
 	scopeTimeLen = (uint32_t)dInt;
 
-	// fractional part scaled to 0..2^32-1
-	dFrac *= UINT32_MAX + 1.0;
-	if (dFrac > (double)UINT32_MAX)
-		dFrac = (double)UINT32_MAX;
-	scopeTimeLenFrac = (uint32_t)round(dFrac);
+	// fractional part (scaled to 0..2^32-1)
+	dFrac *= UINT32_MAX;
+	scopeTimeLenFrac = (uint32_t)(dFrac + 0.5);
 
 	scopeThread = SDL_CreateThread(scopeThreadFunc, NULL, NULL);
 	if (scopeThread == NULL)
--- a/src/ft2_scopes.h
+++ b/src/ft2_scopes.h
@@ -13,6 +13,19 @@
 void drawScopeFramework(void);
 bool initScopes(void);
 
+// actual scope data
+typedef struct scope_t
+{
+	volatile bool active;
+	const int8_t *sampleData8;
+	const int16_t *sampleData16;
+	int8_t SVol;
+	bool wasCleared, sample16Bit;
+	uint8_t loopType;
+	int32_t SPosDir, SRepS, SRepL, SLen, SPos;
+	uint32_t SFrq, SPosDec;
+} scope_t;
+
 typedef struct lastChInstr_t
 {
 	uint8_t sampleNr, instrNr;
--- a/src/ft2_scrollbars.c
+++ b/src/ft2_scrollbars.c
@@ -90,9 +90,11 @@
 	//x,   y,   w,  h,  type,                 style                   funcOnDown
 	{ 578, 158, 29, 13, SCROLLBAR_HORIZONTAL, SCROLLBAR_THUMB_NOFLAT, sbMIDISens },
 
+#ifdef HAS_MIDI
 	// ------ CONFIG MIDI SCROLLBARS ------
 	//x,   y,  w,  h,   type,               style                 funcOnDown
 	{ 483, 15, 18, 143, SCROLLBAR_VERTICAL, SCROLLBAR_THUMB_FLAT, sbMidiInputSetPos },
+#endif
 
 	// ------ DISK OP. SCROLLBARS ------
 	//x,   y,  w,  h,   type,               style                 funcOnDown
@@ -651,8 +653,11 @@
 	setScrollBarEnd(SB_AUDIO_OUTPUT_SCROLL, 1);
 	setScrollBarPageLength(SB_AUDIO_INPUT_SCROLL, 4);
 	setScrollBarEnd(SB_AUDIO_INPUT_SCROLL, 1);
+
+#ifdef HAS_MIDI
 	setScrollBarPageLength(SB_MIDI_INPUT_SCROLL, 15);
 	setScrollBarEnd(SB_MIDI_INPUT_SCROLL, 1);
+#endif
 
 	// disk op.
 	setScrollBarPageLength(SB_DISKOP_LIST, DISKOP_ENTRY_NUM);
--- a/src/ft2_scrollbars.h
+++ b/src/ft2_scrollbars.h
@@ -45,8 +45,10 @@
 	// Config Miscellaneous
 	SB_MIDI_SENS,
 
+#ifdef HAS_MIDI
 	// Config Midi
 	SB_MIDI_INPUT_SCROLL,
+#endif
 
 	// Disk Op.
 	SB_DISKOP_LIST,
--- a/src/ft2_video.c
+++ b/src/ft2_video.c
@@ -76,9 +76,6 @@
 	uint16_t xPos, yPos;
 	double dRefreshRate, dAudLatency;
 
-	if (!video.showFPSCounter)
-		return;
-
 	if (editor.framesPassed >= FPS_SCAN_FRAMES && (editor.framesPassed % FPS_SCAN_FRAMES) == 0)
 	{
 		dAvgFPS = dRunningFPS * (1.0 / FPS_SCAN_FRAMES);
@@ -163,7 +160,10 @@
 	uint32_t windowFlags = SDL_GetWindowFlags(video.window);
 
 	renderSprites();
-	drawFPSCounter();
+
+	if (video.showFPSCounter)
+		drawFPSCounter();
+
 	SDL_UpdateTexture(video.texture, NULL, video.frameBuffer, SCREEN_W * sizeof (int32_t));
 	SDL_RenderClear(video.renderer);
 	SDL_RenderCopy(video.renderer, video.texture, NULL, NULL);
@@ -363,7 +363,7 @@
 	// setup refresh buffer (used to clear sprites after each frame)
 	for (uint32_t i = 0; i < SPRITE_NUM; i++)
 	{
-		sprites[i].refreshBuffer = (uint32_t *)malloc((sprites[i].w * sprites[i].h) * sizeof (int32_t));
+		sprites[i].refreshBuffer = (uint32_t *)malloc(sprites[i].w * sprites[i].h * sizeof (int32_t));
 		if (sprites[i].refreshBuffer == NULL)
 			return false;
 	}
@@ -423,7 +423,7 @@
 	uint32_t *dst32;
 	sprite_t *s;
 
-	for (i = (SPRITE_NUM - 1); i >= 0; i--) // erasing must be done in reverse order
+	for (i = SPRITE_NUM-1; i >= 0; i--) // erasing must be done in reverse order
 	{
 		s = &sprites[i];
 		if (s->x >= SCREEN_W) // sprite is hidden, don't erase
@@ -725,11 +725,9 @@
 	}
 
 	// update next frame time
-
 	timeNext64 += video.vblankTimeLen;
-
 	timeNext64Frac += video.vblankTimeLenFrac;
-	if (timeNext64Frac >= (1ULL << 32))
+	if (timeNext64Frac > 0xFFFFFFFF)
 	{
 		timeNext64Frac &= 0xFFFFFFFF;
 		timeNext64++;
--- a/vs2019_project/ft2-clone/ft2-clone.vcxproj
+++ b/vs2019_project/ft2-clone/ft2-clone.vcxproj
@@ -94,7 +94,7 @@
       <Optimization>MaxSpeed</Optimization>
       <AdditionalIncludeDirectories>
       </AdditionalIncludeDirectories>
-      <PreprocessorDefinitions>__WINDOWS_MM__;NDEBUG;WIN32;_CRT_SECURE_NO_WARNINGS;_USE_MATH_DEFINES;HAVE_M_PI</PreprocessorDefinitions>
+      <PreprocessorDefinitions>__WINDOWS_MM__;NDEBUG;WIN32;_CRT_SECURE_NO_WARNINGS;_USE_MATH_DEFINES;HAVE_M_PI;HAS_MIDI</PreprocessorDefinitions>
       <MultiProcessorCompilation>true</MultiProcessorCompilation>
       <CompileAsManaged>false</CompileAsManaged>
       <IntrinsicFunctions>true</IntrinsicFunctions>
@@ -151,7 +151,7 @@
       <Optimization>MaxSpeed</Optimization>
       <AdditionalIncludeDirectories>
       </AdditionalIncludeDirectories>
-      <PreprocessorDefinitions>__WINDOWS_MM__;NDEBUG;WIN32;_CRT_SECURE_NO_WARNINGS;_USE_MATH_DEFINES;HAVE_M_PI</PreprocessorDefinitions>
+      <PreprocessorDefinitions>__WINDOWS_MM__;NDEBUG;WIN32;_CRT_SECURE_NO_WARNINGS;_USE_MATH_DEFINES;HAVE_M_PI;HAS_MIDI</PreprocessorDefinitions>
       <MultiProcessorCompilation>true</MultiProcessorCompilation>
       <CompileAsManaged>false</CompileAsManaged>
       <IntrinsicFunctions>true</IntrinsicFunctions>
@@ -220,7 +220,7 @@
       <WarningLevel>Level4</WarningLevel>
       <AdditionalIncludeDirectories>
       </AdditionalIncludeDirectories>
-      <PreprocessorDefinitions>__WINDOWS_MM__;_CRTDBG_MAP_ALLOC;DEBUG;_DEBUG;WIN32;_CRT_SECURE_NO_WARNINGS;_USE_MATH_DEFINES;HAVE_M_PI</PreprocessorDefinitions>
+      <PreprocessorDefinitions>__WINDOWS_MM__;_CRTDBG_MAP_ALLOC;DEBUG;_DEBUG;WIN32;_CRT_SECURE_NO_WARNINGS;_USE_MATH_DEFINES;HAVE_M_PI;HAS_MIDI</PreprocessorDefinitions>
       <ExceptionHandling>false</ExceptionHandling>
       <FloatingPointModel>Fast</FloatingPointModel>
       <EnableEnhancedInstructionSet>StreamingSIMDExtensions</EnableEnhancedInstructionSet>
@@ -265,7 +265,7 @@
       <WarningLevel>Level4</WarningLevel>
       <AdditionalIncludeDirectories>
       </AdditionalIncludeDirectories>
-      <PreprocessorDefinitions>__WINDOWS_MM__;_CRTDBG_MAP_ALLOC;DEBUG;_DEBUG;WIN32;_CRT_SECURE_NO_WARNINGS;_USE_MATH_DEFINES;HAVE_M_PI</PreprocessorDefinitions>
+      <PreprocessorDefinitions>__WINDOWS_MM__;_CRTDBG_MAP_ALLOC;DEBUG;_DEBUG;WIN32;_CRT_SECURE_NO_WARNINGS;_USE_MATH_DEFINES;HAVE_M_PI;HAS_MIDI</PreprocessorDefinitions>
       <ExceptionHandling>false</ExceptionHandling>
       <FloatingPointModel>Fast</FloatingPointModel>
       <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
@@ -351,6 +351,7 @@
     <ClCompile Include="..\..\src\ft2_sample_ed.c" />
     <ClCompile Include="..\..\src\ft2_sample_loader.c" />
     <ClCompile Include="..\..\src\ft2_sample_saver.c" />
+    <ClCompile Include="..\..\src\ft2_scopedraw.c" />
     <ClCompile Include="..\..\src\ft2_scopes.c" />
     <ClCompile Include="..\..\src\ft2_scrollbars.c" />
     <ClCompile Include="..\..\src\ft2_sysreqs.c" />
@@ -417,6 +418,7 @@
     <ClInclude Include="..\..\src\ft2_sample_ed.h" />
     <ClInclude Include="..\..\src\ft2_sample_loader.h" />
     <ClInclude Include="..\..\src\ft2_sample_saver.h" />
+    <ClInclude Include="..\..\src\ft2_scopedraw.h" />
     <ClInclude Include="..\..\src\ft2_scopes.h" />
     <ClInclude Include="..\..\src\ft2_scrollbars.h" />
     <ClInclude Include="..\..\src\ft2_sysreqs.h" />
--- a/vs2019_project/ft2-clone/ft2-clone.vcxproj.filters
+++ b/vs2019_project/ft2-clone/ft2-clone.vcxproj.filters
@@ -69,6 +69,7 @@
       <Filter>rtmidi</Filter>
     </ClCompile>
     <ClCompile Include="..\..\src\ft2_palette.c" />
+    <ClCompile Include="..\..\src\ft2_scopedraw.c" />
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="..\..\src\ft2_audio.h">
@@ -196,6 +197,9 @@
     </ClInclude>
     <ClInclude Include="..\..\src\rtmidi\rtmidi_c.h">
       <Filter>rtmidi</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\src\ft2_scopedraw.h">
+      <Filter>headers</Filter>
     </ClInclude>
   </ItemGroup>
   <ItemGroup>