diff --git a/Machines/Utility/MachineForTarget.cpp b/Machines/Utility/MachineForTarget.cpp
new file mode 100644
index 000000000..9638f340b
--- /dev/null
+++ b/Machines/Utility/MachineForTarget.cpp
@@ -0,0 +1,56 @@
+//
+//  MachineForTarget.cpp
+//  Clock Signal
+//
+//  Created by Thomas Harte on 04/11/2017.
+//  Copyright © 2017 Thomas Harte. All rights reserved.
+//
+
+#include "MachineForTarget.hpp"
+
+#include "../AmstradCPC/AmstradCPC.hpp"
+#include "../Atari2600/Atari2600.hpp"
+#include "../Commodore/Vic-20/Vic20.hpp"
+#include "../Electron/Electron.hpp"
+#include "../Oric/Oric.hpp"
+#include "../ZX8081/ZX8081.hpp"
+
+namespace {
+
+template<typename T> class TypedDynamicMachine: public ::Machine::DynamicMachine {
+	public:
+		TypedDynamicMachine(T *machine) : machine_(machine) {}
+
+		ConfigurationTarget::Machine *configuration_target() {
+			return dynamic_cast<ConfigurationTarget::Machine *>(machine_.get());
+		}
+
+		CRTMachine::Machine *crt_machine() {
+			return dynamic_cast<CRTMachine::Machine *>(machine_.get());
+		}
+
+		JoystickMachine::Machine *joystick_machine() {
+			return dynamic_cast<JoystickMachine::Machine *>(machine_.get());
+		}
+
+		KeyboardMachine::Machine *keyboard_machine() {
+			return dynamic_cast<KeyboardMachine::Machine *>(machine_.get());
+		}
+
+	private:
+		std::unique_ptr<T> machine_;
+};
+
+}
+
+::Machine::DynamicMachine *::Machine::MachineForTarget(const StaticAnalyser::Target &target) {
+	switch(target.machine) {
+		case StaticAnalyser::Target::AmstradCPC:	return new TypedDynamicMachine<AmstradCPC::Machine>(AmstradCPC::Machine::AmstradCPC());
+//		case StaticAnalyser::Target::Atari2600:		return new TypedDynamicMachine(Atari2600::Machine::Atari2600());
+//		case StaticAnalyser::Target::Electron:		return new TypedDynamicMachine(Electron::Machine::Electron());
+//		case StaticAnalyser::Target::Oric:			return new TypedDynamicMachine(Oric::Machine::Oric());
+//		case StaticAnalyser::Target::Vic20:			return new TypedDynamicMachine(Commodore::Vic20::Machine::Vic20());
+//		case StaticAnalyser::Target::ZX8081:		return new TypedDynamicMachine(ZX8081::Machine::ZX8081(target));
+	default: return nullptr;
+	}
+}
diff --git a/Machines/Utility/MachineForTarget.hpp b/Machines/Utility/MachineForTarget.hpp
new file mode 100644
index 000000000..488382c35
--- /dev/null
+++ b/Machines/Utility/MachineForTarget.hpp
@@ -0,0 +1,41 @@
+//
+//  MachineForTarget.hpp
+//  Clock Signal
+//
+//  Created by Thomas Harte on 04/11/2017.
+//  Copyright © 2017 Thomas Harte. All rights reserved.
+//
+
+#ifndef MachineForTarget_hpp
+#define MachineForTarget_hpp
+
+#include "../../StaticAnalyser/StaticAnalyser.hpp"
+
+#include "../ConfigurationTarget.hpp"
+#include "../CRTMachine.hpp"
+#include "../JoystickMachine.hpp"
+#include "../KeyboardMachine.hpp"
+
+namespace Machine {
+
+/*!
+	Provides the structure for owning a machine and dynamically casting it as desired without knowledge of
+	the machine's parent class or, therefore, the need to establish a common one.
+*/
+struct DynamicMachine {
+	virtual ConfigurationTarget::Machine *configuration_target() = 0;
+	virtual CRTMachine::Machine *crt_machine() = 0;
+	virtual JoystickMachine::Machine *joystick_machine() = 0;
+	virtual KeyboardMachine::Machine *keyboard_machine() = 0;
+};
+
+/*!
+	Allocates an instance of DynamicMachine holding a machine that can
+	receive the supplied target. The machine has been allocated on the heap.
+	It is the caller's responsibility to delete the class when finished.
+*/
+DynamicMachine *MachineForTarget(const StaticAnalyser::Target &target);
+
+}
+
+#endif /* MachineForTarget_hpp */
diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj
index 575d34c4e..dbf307ba2 100644
--- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj	
+++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj	
@@ -74,6 +74,55 @@
 		4B055ABB1FAE86170060FFFF /* Oric.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805F91DCFF807003085B1 /* Oric.cpp */; };
 		4B055ABC1FAE86170060FFFF /* ZX8081.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBFBB6A1EE8401E00C01E7A /* ZX8081.cpp */; };
 		4B055ABD1FAE86530060FFFF /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B69FB451C4D950F00B5F0AA /* libz.tbd */; };
+		4B055AC11FAE98DC0060FFFF /* MachineForTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B055ABE1FAE98000060FFFF /* MachineForTarget.cpp */; };
+		4B055AC21FAE9AE30060FFFF /* KeyboardMachine.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B54C0BB1F8D8E790050900F /* KeyboardMachine.cpp */; };
+		4B055AC31FAE9AE80060FFFF /* AmstradCPC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B38F3461F2EC11D00D9235D /* AmstradCPC.cpp */; };
+		4B055AC41FAE9AE80060FFFF /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B54C0C11F8D91CD0050900F /* Keyboard.cpp */; };
+		4B055AC51FAE9AEE0060FFFF /* Atari2600.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D971C3A06EC00138695 /* Atari2600.cpp */; };
+		4B055AC61FAE9AEE0060FFFF /* Speaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA52641DF3472B007E74F2 /* Speaker.cpp */; };
+		4B055AC71FAE9AEE0060FFFF /* TIA.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE7C9161E3D397100A5496D /* TIA.cpp */; };
+		4B055AC81FAE9AFB0060FFFF /* C1540.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8334941F5E25B60097E338 /* C1540.cpp */; };
+		4B055AC91FAE9AFB0060FFFF /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B54C0C41F8D91D90050900F /* Keyboard.cpp */; };
+		4B055ACA1FAE9AFB0060FFFF /* Vic20.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4DC81F1D2C2425003C5BF8 /* Vic20.cpp */; };
+		4B055ACB1FAE9AFB0060FFFF /* SerialBus.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4DC8291D2C27A4003C5BF8 /* SerialBus.cpp */; };
+		4B055ACC1FAE9B030060FFFF /* Electron.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2E2D9B1C3A070400138695 /* Electron.cpp */; };
+		4B055ACD1FAE9B030060FFFF /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B54C0C61F8D91E50050900F /* Keyboard.cpp */; };
+		4B055ACE1FAE9B030060FFFF /* Plus3.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B30512E1D98ACC600B4FED8 /* Plus3.cpp */; };
+		4B055ACF1FAE9B030060FFFF /* Speaker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA52611DF339D7007E74F2 /* Speaker.cpp */; };
+		4B055AD01FAE9B030060FFFF /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA525D1DF33323007E74F2 /* Tape.cpp */; };
+		4B055AD11FAE9B030060FFFF /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7913CA1DFCD80E00175A82 /* Video.cpp */; };
+		4B055AD21FAE9B0B0060FFFF /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B54C0BD1F8D8F450050900F /* Keyboard.cpp */; };
+		4B055AD31FAE9B0B0060FFFF /* Microdisc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B5FADBE1DE3BF2B00AEC565 /* Microdisc.cpp */; };
+		4B055AD41FAE9B0B0060FFFF /* Oric.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF1FA21DADC3DD0039D2E7 /* Oric.cpp */; };
+		4B055AD51FAE9B0B0060FFFF /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2BFDB01DAEF5FF001A68B8 /* Video.cpp */; };
+		4B055AD61FAE9B130060FFFF /* MemoryFuzzer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2B3A481F9B8FA70062DABF /* MemoryFuzzer.cpp */; };
+		4B055AD71FAE9B180060FFFF /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B54C0CA1F8D92580050900F /* Keyboard.cpp */; };
+		4B055AD81FAE9B180060FFFF /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD3A3091EE755C800B5B501 /* Video.cpp */; };
+		4B055AD91FAE9B180060FFFF /* ZX8081.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B1497901EE4B5A800CE2596 /* ZX8081.cpp */; };
+		4B055ADA1FAE9B460060FFFF /* 1770.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD468F51D8DF41D0084958B /* 1770.cpp */; };
+		4B055ADB1FAE9B460060FFFF /* 6560.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9DF4D1D04691600F44158 /* 6560.cpp */; };
+		4B055ADC1FAE9B460060FFFF /* AY38910.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4A762E1DB1A3FA007AAE2E /* AY38910.cpp */; };
+		4B055ADD1FAE9B460060FFFF /* i8272.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBC951C1F368D83008F4C34 /* i8272.cpp */; };
+		4B055ADE1FAE9B4C0060FFFF /* 6522Base.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B83348B1F5DB99C0097E338 /* 6522Base.cpp */; };
+		4B055ADF1FAE9B4C0060FFFF /* IRQDelegatePortHandler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8334891F5DB94B0097E338 /* IRQDelegatePortHandler.cpp */; };
+		4B055AE01FAE9B660060FFFF /* CRT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0CCC421C62D0B3001CAC5F /* CRT.cpp */; };
+		4B055AE11FAE9B6F0060FFFF /* ArrayBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B5073051DDD3B9400C48FBD /* ArrayBuilder.cpp */; };
+		4B055AE21FAE9B6F0060FFFF /* CRTOpenGL.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF990A1C8FBA6F0075DAFB /* CRTOpenGL.cpp */; };
+		4B055AE31FAE9B6F0060FFFF /* TextureBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF99081C8FBA6F0075DAFB /* TextureBuilder.cpp */; };
+		4B055AE41FAE9B6F0060FFFF /* TextureTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF99121C8FBA6F0075DAFB /* TextureTarget.cpp */; };
+		4B055AE51FAE9B6F0060FFFF /* IntermediateShader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB142F1CD2CECE00BDB55C /* IntermediateShader.cpp */; };
+		4B055AE61FAE9B6F0060FFFF /* OutputShader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC3B7501CD1956900F86E85 /* OutputShader.cpp */; };
+		4B055AE71FAE9B6F0060FFFF /* Shader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC3B74D1CD194CC00F86E85 /* Shader.cpp */; };
+		4B055AE81FAE9B7B0060FFFF /* FIRFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */; };
+		4B055AE91FAE9B990060FFFF /* 6502Base.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6A4C951F58F09E00E3F787 /* 6502Base.cpp */; };
+		4B055AEA1FAE9B990060FFFF /* 6502Storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8334851F5DA3780097E338 /* 6502Storage.cpp */; };
+		4B055AEB1FAE9BA20060FFFF /* PartialMachineCycle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8334811F5D9FF70097E338 /* PartialMachineCycle.cpp */; };
+		4B055AEC1FAE9BA20060FFFF /* Z80Base.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B322E031F5A2E3C004EB04C /* Z80Base.cpp */; };
+		4B055AED1FAE9BA20060FFFF /* Z80Storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8334831F5DA0360097E338 /* Z80Storage.cpp */; };
+		4B055AEE1FAE9BBF0060FFFF /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B86E2591F8C628F006FAA45 /* Keyboard.cpp */; };
+		4B055AEF1FAE9BF00060FFFF /* Typer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2B3A471F9B8FA70062DABF /* Typer.cpp */; };
+		4B055AF11FAE9C160060FFFF /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BC76E6A1C98F43700E6EF73 /* Accelerate.framework */; };
+		4B055AF21FAE9C1C0060FFFF /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B055AF01FAE9C080060FFFF /* OpenGL.framework */; };
 		4B08A2751EE35D56008B7065 /* Z80InterruptTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B08A2741EE35D56008B7065 /* Z80InterruptTests.swift */; };
 		4B08A2781EE39306008B7065 /* TestMachine.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B08A2771EE39306008B7065 /* TestMachine.mm */; };
 		4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0CCC421C62D0B3001CAC5F /* CRT.cpp */; };
@@ -563,8 +612,11 @@
 		4B046DC31CFE651500E9E45E /* CRTMachine.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRTMachine.hpp; sourceTree = "<group>"; };
 		4B049CDC1DA3C82F00322067 /* BCDTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BCDTest.swift; sourceTree = "<group>"; };
 		4B055A6A1FAE763F0060FFFF /* Clock Signal Kiosk */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "Clock Signal Kiosk"; sourceTree = BUILT_PRODUCTS_DIR; };
-		4B055A771FAE78210060FFFF /* SDL2.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SDL2.framework; path = ../../../../Library/Frameworks/SDL2.framework; sourceTree = "<group>"; };
+		4B055A771FAE78210060FFFF /* SDL2.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SDL2.framework; path = ../../../../Library/Frameworks/SDL2.framework; sourceTree = SOURCE_ROOT; };
 		4B055A7C1FAE84A50060FFFF /* main.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = main.cpp; sourceTree = "<group>"; };
+		4B055ABE1FAE98000060FFFF /* MachineForTarget.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MachineForTarget.cpp; sourceTree = "<group>"; };
+		4B055ABF1FAE98000060FFFF /* MachineForTarget.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MachineForTarget.hpp; sourceTree = "<group>"; };
+		4B055AF01FAE9C080060FFFF /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; };
 		4B08A2741EE35D56008B7065 /* Z80InterruptTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Z80InterruptTests.swift; sourceTree = "<group>"; };
 		4B08A2761EE39306008B7065 /* TestMachine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TestMachine.h; sourceTree = "<group>"; };
 		4B08A2771EE39306008B7065 /* TestMachine.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TestMachine.mm; sourceTree = "<group>"; };
@@ -1218,6 +1270,8 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				4B055AF21FAE9C1C0060FFFF /* OpenGL.framework in Frameworks */,
+				4B055AF11FAE9C160060FFFF /* Accelerate.framework in Frameworks */,
 				4B055ABD1FAE86530060FFFF /* libz.tbd in Frameworks */,
 				4B055A7A1FAE78A00060FFFF /* SDL2.framework in Frameworks */,
 			);
@@ -1252,6 +1306,7 @@
 		4B055A761FAE78210060FFFF /* Frameworks */ = {
 			isa = PBXGroup;
 			children = (
+				4B055AF01FAE9C080060FFFF /* OpenGL.framework */,
 				4B055A771FAE78210060FFFF /* SDL2.framework */,
 			);
 			name = Frameworks;
@@ -1406,6 +1461,8 @@
 		4B2B3A461F9B8FA70062DABF /* Utility */ = {
 			isa = PBXGroup;
 			children = (
+				4B055ABE1FAE98000060FFFF /* MachineForTarget.cpp */,
+				4B055ABF1FAE98000060FFFF /* MachineForTarget.hpp */,
 				4B2B3A471F9B8FA70062DABF /* Typer.cpp */,
 				4B2B3A481F9B8FA70062DABF /* MemoryFuzzer.cpp */,
 				4B2B3A491F9B8FA70062DABF /* MemoryFuzzer.hpp */,
@@ -3050,24 +3107,41 @@
 			files = (
 				4B055AAA1FAE85F50060FFFF /* CPM.cpp in Sources */,
 				4B055A9A1FAE85CB0060FFFF /* MFMDiskController.cpp in Sources */,
+				4B055ACB1FAE9AFB0060FFFF /* SerialBus.cpp in Sources */,
 				4B055AA41FAE85E50060FFFF /* DigitalPhaseLockedLoop.cpp in Sources */,
+				4B055AE61FAE9B6F0060FFFF /* OutputShader.cpp in Sources */,
 				4B055A9B1FAE85DA0060FFFF /* AcornADF.cpp in Sources */,
+				4B055AD51FAE9B0B0060FFFF /* Video.cpp in Sources */,
 				4B055A811FAE853A0060FFFF /* Disk.cpp in Sources */,
+				4B055AE11FAE9B6F0060FFFF /* ArrayBuilder.cpp in Sources */,
 				4B055AA51FAE85EF0060FFFF /* Encoder.cpp in Sources */,
+				4B055AEA1FAE9B990060FFFF /* 6502Storage.cpp in Sources */,
 				4B055A8A1FAE855B0060FFFF /* Tape.cpp in Sources */,
 				4B055AA71FAE85EF0060FFFF /* SegmentParser.cpp in Sources */,
+				4B055AC11FAE98DC0060FFFF /* MachineForTarget.cpp in Sources */,
+				4B055AD81FAE9B180060FFFF /* Video.cpp in Sources */,
+				4B055AE51FAE9B6F0060FFFF /* IntermediateShader.cpp in Sources */,
 				4B055A851FAE85480060FFFF /* File.cpp in Sources */,
+				4B055AD31FAE9B0B0060FFFF /* Microdisc.cpp in Sources */,
 				4B055AB41FAE860F0060FFFF /* OricTAP.cpp in Sources */,
 				4B055AB71FAE860F0060FFFF /* TZX.cpp in Sources */,
 				4B055A8C1FAE85670060FFFF /* StaticAnalyser.cpp in Sources */,
+				4B055ADA1FAE9B460060FFFF /* 1770.cpp in Sources */,
+				4B055ADC1FAE9B460060FFFF /* AY38910.cpp in Sources */,
+				4B055AD71FAE9B180060FFFF /* Keyboard.cpp in Sources */,
 				4B055AB61FAE860F0060FFFF /* TapeUEF.cpp in Sources */,
 				4B055A9D1FAE85DA0060FFFF /* D64.cpp in Sources */,
 				4B055ABB1FAE86170060FFFF /* Oric.cpp in Sources */,
+				4B055AE81FAE9B7B0060FFFF /* FIRFilter.cpp in Sources */,
 				4B055A901FAE85A90060FFFF /* TimedEventLoop.cpp in Sources */,
 				4B055AAB1FAE85FD0060FFFF /* PCMPatchedTrack.cpp in Sources */,
+				4B055AC71FAE9AEE0060FFFF /* TIA.cpp in Sources */,
+				4B055AD21FAE9B0B0060FFFF /* Keyboard.cpp in Sources */,
 				4B055AA31FAE85DF0060FFFF /* ImplicitSectors.cpp in Sources */,
 				4B055AAE1FAE85FD0060FFFF /* TrackSerialiser.cpp in Sources */,
 				4B055A981FAE85C50060FFFF /* Drive.cpp in Sources */,
+				4B055AE21FAE9B6F0060FFFF /* CRTOpenGL.cpp in Sources */,
+				4B055AC31FAE9AE80060FFFF /* AmstradCPC.cpp in Sources */,
 				4B055A9E1FAE85DA0060FFFF /* G64.cpp in Sources */,
 				4B055AB81FAE860F0060FFFF /* ZX80O81P.cpp in Sources */,
 				4B055A8E1FAE85920060FFFF /* BestEffortUpdater.cpp in Sources */,
@@ -3075,41 +3149,71 @@
 				4B055A801FAE85350060FFFF /* StaticAnalyser.cpp in Sources */,
 				4B055AAC1FAE85FD0060FFFF /* PCMSegment.cpp in Sources */,
 				4B055AB31FAE860F0060FFFF /* CSW.cpp in Sources */,
+				4B055ACF1FAE9B030060FFFF /* Speaker.cpp in Sources */,
+				4B055AEE1FAE9BBF0060FFFF /* Keyboard.cpp in Sources */,
 				4B055A881FAE85530060FFFF /* Disassembler6502.cpp in Sources */,
+				4B055AED1FAE9BA20060FFFF /* Z80Storage.cpp in Sources */,
+				4B055AD11FAE9B030060FFFF /* Video.cpp in Sources */,
 				4B055AA21FAE85DA0060FFFF /* SSD.cpp in Sources */,
+				4B055ADD1FAE9B460060FFFF /* i8272.cpp in Sources */,
+				4B055AC51FAE9AEE0060FFFF /* Atari2600.cpp in Sources */,
 				4B055A9C1FAE85DA0060FFFF /* CPCDSK.cpp in Sources */,
+				4B055AE41FAE9B6F0060FFFF /* TextureTarget.cpp in Sources */,
 				4B055ABA1FAE86170060FFFF /* Commodore.cpp in Sources */,
 				4B055AA61FAE85EF0060FFFF /* Parser.cpp in Sources */,
+				4B055AE91FAE9B990060FFFF /* 6502Base.cpp in Sources */,
 				4B055A861FAE854C0060FFFF /* StaticAnalyser.cpp in Sources */,
+				4B055AEF1FAE9BF00060FFFF /* Typer.cpp in Sources */,
+				4B055ACA1FAE9AFB0060FFFF /* Vic20.cpp in Sources */,
 				4B055ABC1FAE86170060FFFF /* ZX8081.cpp in Sources */,
+				4B055AC91FAE9AFB0060FFFF /* Keyboard.cpp in Sources */,
 				4B055A991FAE85CB0060FFFF /* DiskController.cpp in Sources */,
+				4B055ACC1FAE9B030060FFFF /* Electron.cpp in Sources */,
 				4B055AB11FAE86070060FFFF /* Tape.cpp in Sources */,
 				4B055AA91FAE85EF0060FFFF /* CommodoreGCR.cpp in Sources */,
 				4B055A7F1FAE852F0060FFFF /* StaticAnalyser.cpp in Sources */,
+				4B055ADB1FAE9B460060FFFF /* 6560.cpp in Sources */,
 				4B055AA01FAE85DA0060FFFF /* MFMSectorDump.cpp in Sources */,
 				4B055AA11FAE85DA0060FFFF /* OricMFMDSK.cpp in Sources */,
 				4B055A951FAE85BB0060FFFF /* BitReverse.cpp in Sources */,
 				4B055A891FAE85580060FFFF /* StaticAnalyser.cpp in Sources */,
+				4B055ACE1FAE9B030060FFFF /* Plus3.cpp in Sources */,
 				4B055A8D1FAE85920060FFFF /* AsyncTaskQueue.cpp in Sources */,
+				4B055AC41FAE9AE80060FFFF /* Keyboard.cpp in Sources */,
 				4B055A941FAE85B50060FFFF /* CommodoreROM.cpp in Sources */,
 				4B055A971FAE85BB0060FFFF /* ZX8081.cpp in Sources */,
 				4B055AAD1FAE85FD0060FFFF /* PCMTrack.cpp in Sources */,
 				4B055A841FAE85450060FFFF /* Disk.cpp in Sources */,
 				4B055A831FAE85410060FFFF /* Tape.cpp in Sources */,
+				4B055AC61FAE9AEE0060FFFF /* Speaker.cpp in Sources */,
 				4B055AA81FAE85EF0060FFFF /* Shifter.cpp in Sources */,
+				4B055AC81FAE9AFB0060FFFF /* C1540.cpp in Sources */,
 				4B055A8F1FAE85A90060FFFF /* FileHolder.cpp in Sources */,
 				4B055A911FAE85B50060FFFF /* Cartridge.cpp in Sources */,
+				4B055ACD1FAE9B030060FFFF /* Keyboard.cpp in Sources */,
 				4B055A8B1FAE85670060FFFF /* StaticAnalyser.cpp in Sources */,
 				4B055AB21FAE860F0060FFFF /* CommodoreTAP.cpp in Sources */,
+				4B055ADF1FAE9B4C0060FFFF /* IRQDelegatePortHandler.cpp in Sources */,
 				4B055AB51FAE860F0060FFFF /* TapePRG.cpp in Sources */,
+				4B055AE01FAE9B660060FFFF /* CRT.cpp in Sources */,
+				4B055AD01FAE9B030060FFFF /* Tape.cpp in Sources */,
 				4B055A961FAE85BB0060FFFF /* Commodore.cpp in Sources */,
+				4B055ADE1FAE9B4C0060FFFF /* 6522Base.cpp in Sources */,
+				4B055AD41FAE9B0B0060FFFF /* Oric.cpp in Sources */,
 				4B055A821FAE853D0060FFFF /* StaticAnalyser.cpp in Sources */,
 				4B055A921FAE85B50060FFFF /* PRG.cpp in Sources */,
 				4B055AAF1FAE85FD0060FFFF /* UnformattedTrack.cpp in Sources */,
 				4B055A7E1FAE84AA0060FFFF /* main.cpp in Sources */,
 				4B055A9F1FAE85DA0060FFFF /* HFE.cpp in Sources */,
+				4B055AE71FAE9B6F0060FFFF /* Shader.cpp in Sources */,
+				4B055AEC1FAE9BA20060FFFF /* Z80Base.cpp in Sources */,
+				4B055AE31FAE9B6F0060FFFF /* TextureBuilder.cpp in Sources */,
 				4B055AB91FAE86170060FFFF /* Acorn.cpp in Sources */,
 				4B055A931FAE85B50060FFFF /* BinaryDump.cpp in Sources */,
+				4B055AD61FAE9B130060FFFF /* MemoryFuzzer.cpp in Sources */,
+				4B055AC21FAE9AE30060FFFF /* KeyboardMachine.cpp in Sources */,
+				4B055AD91FAE9B180060FFFF /* ZX8081.cpp in Sources */,
+				4B055AEB1FAE9BA20060FFFF /* PartialMachineCycle.cpp in Sources */,
 				4B055A871FAE854F0060FFFF /* Tape.cpp in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
diff --git a/OSBindings/SDL/main.cpp b/OSBindings/SDL/main.cpp
index e207105ca..9e3dda043 100644
--- a/OSBindings/SDL/main.cpp
+++ b/OSBindings/SDL/main.cpp
@@ -7,31 +7,84 @@
 //
 
 #include <iostream>
+#include <memory>
 #include <SDL2/SDL.h>
 
 #include "../../StaticAnalyser/StaticAnalyser.hpp"
+#include "../../Machines/Utility/MachineForTarget.hpp"
+
+#include "../../Machines/ConfigurationTarget.hpp"
+#include "../../Machines/CRTMachine.hpp"
+
+#include "../../Concurrency/BestEffortUpdater.hpp"
+
+namespace {
+
+struct CRTMachineDelegate: public CRTMachine::Machine::Delegate {
+
+	void machine_did_change_clock_rate(CRTMachine::Machine *machine) {
+		best_effort_updater->set_clock_rate(machine->get_clock_rate());
+	}
+
+	void machine_did_change_clock_is_unlimited(CRTMachine::Machine *machine) {
+	}
+
+	Concurrency::BestEffortUpdater *best_effort_updater;
+};
+
+struct BestEffortUpdaterDelegate: public Concurrency::BestEffortUpdater::Delegate {
+
+	void update(Concurrency::BestEffortUpdater *updater, int cycles, bool did_skip_previous_update) {
+		machine->crt_machine()->run_for(Cycles(cycles));
+	}
+
+	Machine::DynamicMachine *machine;
+};
+
+}
 
 int main(int argc, char *argv[]) {
 	SDL_Window *window = nullptr;
 
+	// Perform a sanity check on arguments.
 	if(argc < 2) {
 		std::cerr << "Usage: " << argv[0] << " [file]" << std::endl;
 		return -1;
 	}
-	
+
+	// Determine the machine for the supplied file.
 	std::list<StaticAnalyser::Target> targets = StaticAnalyser::GetTargets(argv[1]);
 	if(targets.empty()) {
 		std::cerr << "Cannot open " << argv[1] << std::endl;
 		return -1;
 	}
 
+	Concurrency::BestEffortUpdater updater;
+	BestEffortUpdaterDelegate best_effort_updater_delegate;
+	CRTMachineDelegate crt_delegate;
+
+	// Create and configure a machine.
+	std::unique_ptr<::Machine::DynamicMachine> machine(::Machine::MachineForTarget(targets.front()));
+	machine->configuration_target()->configure_as_target(targets.front());
+	
+	crt_delegate.best_effort_updater = &updater;
+	best_effort_updater_delegate.machine = machine.get();
+
+	machine->crt_machine()->set_delegate(&crt_delegate);
+	updater.set_delegate(&best_effort_updater_delegate);
+
+	// Attempt to set up video and audio.
 	if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) {
 		std::cerr << "SDL could not initialize! SDL_Error: " << SDL_GetError() << std::endl;
 		return -1;
 	}
 
+	// Ask for no depth buffer, a core profile and vsync-aligned rendering.
 	SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0);
 	SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
+	SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
+	SDL_GL_SetSwapInterval(1);
+
 	window = SDL_CreateWindow(	"Clock Signal",
 								SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
 								400, 300,
@@ -43,21 +96,38 @@ int main(int argc, char *argv[]) {
 		return -1;
 	}
 
+	SDL_GLContext gl_context = SDL_GL_CreateContext(window);
+	SDL_GL_MakeCurrent(window, gl_context);
+
+	// Setup output, assuming a CRT machine for now, and prepare a best-effort updater.
+	machine->crt_machine()->setup_output(4.0 / 3.0);
+
+	// Run the main event loop until the OS tells us to quit.
 	bool should_quit = false;
 	while(!should_quit) {
+		// Process all pending events.
 		SDL_Event event;
-		while (SDL_PollEvent(&event)) {
+		while(SDL_PollEvent(&event)) {
 			switch(event.type) {
+				default: std::cout << "Unhandled " << event.type << std::endl; break;
 				case SDL_QUIT:	should_quit = true;	break;
-				
+
 				case SDL_KEYDOWN:
 				break;
 				case SDL_KEYUP:
 				break;
 			}
 		}
+
+		// Display a new frame and wait for vsync.
+		updater.update();
+		int width, height;
+		SDL_GetWindowSize(window, &width, &height);
+		machine->crt_machine()->get_crt()->draw_frame(static_cast<unsigned int>(width), static_cast<unsigned int>(height), true);
+		SDL_GL_SwapWindow(window);
 	}
-	
+
+	// Clean up.
 	SDL_DestroyWindow( window );
 	SDL_Quit();