[hamradio-commits] [cubicsdr] 01/03: New upstream version 0.2.0+dfsg

Andreas E. Bombe aeb at moszumanska.debian.org
Mon Dec 26 02:40:33 UTC 2016


This is an automated email from the git hooks/post-receive script.

aeb pushed a commit to branch master
in repository cubicsdr.

commit ac88baf7bc5c0dd392c7dd9a4ddc24f74d52514d
Author: Andreas Bombe <aeb at debian.org>
Date:   Mon Nov 21 00:38:30 2016 +0100

    New upstream version 0.2.0+dfsg
---
 .gitignore                                       |    3 +
 CMakeLists.txt                                   |  953 ++++
 LICENSE                                          |  340 ++
 README.md                                        |   51 +
 cmake/CubicSDR.desktop.in                        |    9 +
 cmake/CubicSDRInfo.plist.in                      |   40 +
 cmake/Modules/FindJack.cmake                     |   50 +
 cmake/Modules/FindLiquid.cmake                   |   22 +
 cmake/Modules/Findhamlib.cmake                   |   77 +
 cmake/cmake_uninstall.cmake.in                   |   22 +
 cmake/code_sign.sh.in                            |   11 +
 cmake/dmg_sign.sh.in                             |    2 +
 cubicsdr.rc                                      |    2 +
 external/cubicvr2/math/aabb.h                    |  110 +
 external/cubicvr2/math/cubic_math.cpp            |   27 +
 external/cubicvr2/math/cubic_math.h              |   35 +
 external/cubicvr2/math/cubic_types.h             |   46 +
 external/cubicvr2/math/frustum.h                 |  167 +
 external/cubicvr2/math/mat3.h                    |   86 +
 external/cubicvr2/math/mat4.h                    |  328 ++
 external/cubicvr2/math/plane.h                   |   32 +
 external/cubicvr2/math/quaternion.h              |  100 +
 external/cubicvr2/math/sphere.h                  |   34 +
 external/cubicvr2/math/transform.h               |  175 +
 external/cubicvr2/math/triangle.h                |   40 +
 external/cubicvr2/math/vec2.h                    |   93 +
 external/cubicvr2/math/vec3.h                    |  172 +
 external/cubicvr2/math/vec4.h                    |   73 +
 external/lodepng/lodepng.cpp                     | 6224 ++++++++++++++++++++++
 external/lodepng/lodepng.h                       | 1759 ++++++
 font/vera_sans_mono12.bmfc                       |   57 +
 font/vera_sans_mono12.fnt                        |  258 +
 font/vera_sans_mono12_0.png                      |  Bin 0 -> 2929 bytes
 font/vera_sans_mono16.bmfc                       |   57 +
 font/vera_sans_mono16.fnt                        |  258 +
 font/vera_sans_mono16_0.png                      |  Bin 0 -> 8182 bytes
 font/vera_sans_mono18.bmfc                       |   57 +
 font/vera_sans_mono18.fnt                        |  258 +
 font/vera_sans_mono18_0.png                      |  Bin 0 -> 15503 bytes
 font/vera_sans_mono24.bmfc                       |   57 +
 font/vera_sans_mono24.fnt                        |  258 +
 font/vera_sans_mono24_0.png                      |  Bin 0 -> 22092 bytes
 font/vera_sans_mono27.bmfc                       |   57 +
 font/vera_sans_mono27.fnt                        |  258 +
 font/vera_sans_mono27_0.png                      |  Bin 0 -> 24814 bytes
 font/vera_sans_mono32.bmfc                       |   57 +
 font/vera_sans_mono32.fnt                        |  258 +
 font/vera_sans_mono32_0.png                      |  Bin 0 -> 21426 bytes
 font/vera_sans_mono36.bmfc                       |   57 +
 font/vera_sans_mono36.fnt                        |  258 +
 font/vera_sans_mono36_0.png                      |  Bin 0 -> 35493 bytes
 font/vera_sans_mono48.bmfc                       |   57 +
 font/vera_sans_mono48.fnt                        |  258 +
 font/vera_sans_mono48_0.png                      |  Bin 0 -> 43724 bytes
 font/vera_sans_mono64.bmfc                       |   57 +
 font/vera_sans_mono64.fnt                        |  258 +
 font/vera_sans_mono64_0.png                      |  Bin 0 -> 62952 bytes
 font/vera_sans_mono72.bmfc                       |   57 +
 font/vera_sans_mono72.fnt                        |  258 +
 font/vera_sans_mono72_0.png                      |  Bin 0 -> 75412 bytes
 font/vera_sans_mono96.bmfc                       |   57 +
 font/vera_sans_mono96.fnt                        |  258 +
 font/vera_sans_mono96_0.png                      |  Bin 0 -> 100585 bytes
 icon/CubicSDR.icns                               |  Bin 0 -> 411946 bytes
 icon/CubicSDR.ico                                |  Bin 0 -> 110701 bytes
 icon/NSIS_Header.bmp                             |  Bin 0 -> 9742 bytes
 src/AppConfig.cpp                                |  783 +++
 src/AppConfig.h                                  |  167 +
 src/AppFrame.cpp                                 | 2231 ++++++++
 src/AppFrame.h                                   |  199 +
 src/CubicSDR.cpp                                 |  970 ++++
 src/CubicSDR.h                                   |  247 +
 src/CubicSDR.png                                 |  Bin 0 -> 65132 bytes
 src/CubicSDR.xpm                                 |  518 ++
 src/CubicSDRDefs.h                               |   40 +
 src/DemodLabelDialog.cpp                         |   97 +
 src/DemodLabelDialog.h                           |   29 +
 src/FrequencyDialog.cpp                          |  243 +
 src/FrequencyDialog.h                            |   41 +
 src/IOThread.cpp                                 |  129 +
 src/IOThread.h                                   |  230 +
 src/ModemProperties.cpp                          |  316 ++
 src/ModemProperties.h                            |   57 +
 src/audio/AudioThread.cpp                        |  534 ++
 src/audio/AudioThread.h                          |  110 +
 src/demod/DemodDefs.h                            |  101 +
 src/demod/DemodulatorInstance.cpp                |  551 ++
 src/demod/DemodulatorInstance.h                  |  159 +
 src/demod/DemodulatorMgr.cpp                     |  370 ++
 src/demod/DemodulatorMgr.h                       |   82 +
 src/demod/DemodulatorPreThread.cpp               |  401 ++
 src/demod/DemodulatorPreThread.h                 |   82 +
 src/demod/DemodulatorThread.cpp                  |  412 ++
 src/demod/DemodulatorThread.h                    |   69 +
 src/demod/DemodulatorWorkerThread.cpp            |  113 +
 src/demod/DemodulatorWorkerThread.h              |   98 +
 src/forms/DigitalConsole/DigitalConsole.cpp      |  138 +
 src/forms/DigitalConsole/DigitalConsole.fbp      |  485 ++
 src/forms/DigitalConsole/DigitalConsole.h        |   61 +
 src/forms/DigitalConsole/DigitalConsoleFrame.cpp |   73 +
 src/forms/DigitalConsole/DigitalConsoleFrame.h   |   57 +
 src/forms/SDRDevices/SDRDeviceAdd.cpp            |   59 +
 src/forms/SDRDevices/SDRDeviceAdd.fbp            |  681 +++
 src/forms/SDRDevices/SDRDeviceAdd.h              |   21 +
 src/forms/SDRDevices/SDRDeviceAddForm.cpp        |   83 +
 src/forms/SDRDevices/SDRDeviceAddForm.h          |   56 +
 src/forms/SDRDevices/SDRDevices.cpp              |  500 ++
 src/forms/SDRDevices/SDRDevices.fbp              | 1033 ++++
 src/forms/SDRDevices/SDRDevices.h                |   48 +
 src/forms/SDRDevices/SDRDevicesForm.cpp          |  115 +
 src/forms/SDRDevices/SDRDevicesForm.h            |   77 +
 src/modules/modem/Modem.cpp                      |  119 +
 src/modules/modem/Modem.h                        |  162 +
 src/modules/modem/ModemAnalog.cpp                |   98 +
 src/modules/modem/ModemAnalog.h                  |   34 +
 src/modules/modem/ModemDigital.cpp               |   82 +
 src/modules/modem/ModemDigital.h                 |   61 +
 src/modules/modem/analog/ModemAM.cpp             |   39 +
 src/modules/modem/analog/ModemAM.h               |   20 +
 src/modules/modem/analog/ModemDSB.cpp            |   39 +
 src/modules/modem/analog/ModemDSB.h              |   20 +
 src/modules/modem/analog/ModemFM.cpp             |   36 +
 src/modules/modem/analog/ModemFM.h               |   20 +
 src/modules/modem/analog/ModemFMStereo.cpp       |  283 +
 src/modules/modem/analog/ModemFMStereo.h         |   58 +
 src/modules/modem/analog/ModemIQ.cpp             |   56 +
 src/modules/modem/analog/ModemIQ.h               |   24 +
 src/modules/modem/analog/ModemLSB.cpp            |   77 +
 src/modules/modem/analog/ModemLSB.h              |   28 +
 src/modules/modem/analog/ModemNBFM.cpp           |   36 +
 src/modules/modem/analog/ModemNBFM.h             |   20 +
 src/modules/modem/analog/ModemUSB.cpp            |   78 +
 src/modules/modem/analog/ModemUSB.h              |   27 +
 src/modules/modem/digital/ModemAPSK.cpp          |  109 +
 src/modules/modem/digital/ModemAPSK.h            |   30 +
 src/modules/modem/digital/ModemASK.cpp           |  113 +
 src/modules/modem/digital/ModemASK.h             |   31 +
 src/modules/modem/digital/ModemBPSK.cpp          |   29 +
 src/modules/modem/digital/ModemBPSK.h            |   17 +
 src/modules/modem/digital/ModemDPSK.cpp          |  114 +
 src/modules/modem/digital/ModemDPSK.h            |   32 +
 src/modules/modem/digital/ModemFSK.cpp           |  143 +
 src/modules/modem/digital/ModemFSK.h             |   40 +
 src/modules/modem/digital/ModemGMSK.cpp          |  133 +
 src/modules/modem/digital/ModemGMSK.h            |   41 +
 src/modules/modem/digital/ModemOOK.cpp           |   36 +
 src/modules/modem/digital/ModemOOK.h             |   19 +
 src/modules/modem/digital/ModemPSK.cpp           |  115 +
 src/modules/modem/digital/ModemPSK.h             |   32 +
 src/modules/modem/digital/ModemQAM.cpp           |  107 +
 src/modules/modem/digital/ModemQAM.h             |   32 +
 src/modules/modem/digital/ModemQPSK.cpp          |   29 +
 src/modules/modem/digital/ModemQPSK.h            |   17 +
 src/modules/modem/digital/ModemSQAM.cpp          |   78 +
 src/modules/modem/digital/ModemSQAM.h            |   25 +
 src/modules/modem/digital/ModemST.cpp            |   29 +
 src/modules/modem/digital/ModemST.h              |   18 +
 src/panel/MeterPanel.cpp                         |  209 +
 src/panel/MeterPanel.h                           |   38 +
 src/panel/ScopePanel.cpp                         |  112 +
 src/panel/ScopePanel.h                           |   24 +
 src/panel/SpectrumPanel.cpp                      |  292 +
 src/panel/SpectrumPanel.h                        |   44 +
 src/panel/WaterfallPanel.cpp                     |  211 +
 src/panel/WaterfallPanel.h                       |   32 +
 src/process/FFTDataDistributor.cpp               |   98 +
 src/process/FFTDataDistributor.h                 |   24 +
 src/process/FFTVisualDataThread.cpp              |   68 +
 src/process/FFTVisualDataThread.h                |   25 +
 src/process/ScopeVisualProcessor.cpp             |  216 +
 src/process/ScopeVisualProcessor.h               |   50 +
 src/process/SpectrumVisualDataThread.cpp         |   26 +
 src/process/SpectrumVisualDataThread.h           |   16 +
 src/process/SpectrumVisualProcessor.cpp          |  613 +++
 src/process/SpectrumVisualProcessor.h            |   97 +
 src/process/VisualProcessor.cpp                  |    1 +
 src/process/VisualProcessor.h                    |  150 +
 src/rig/RigThread.cpp                            |  184 +
 src/rig/RigThread.h                              |   58 +
 src/sdr/SDRDeviceInfo.cpp                        |  215 +
 src/sdr/SDRDeviceInfo.h                          |   97 +
 src/sdr/SDREnumerator.cpp                        |  413 ++
 src/sdr/SDREnumerator.h                          |   48 +
 src/sdr/SDRPostThread.cpp                        |  492 ++
 src/sdr/SDRPostThread.h                          |   65 +
 src/sdr/SDRThread.cpp                            |  314 ++
 src/sdr/SDRThread.h                              |   73 +
 src/sdr/SoapySDRThread.cpp                       |  601 +++
 src/sdr/SoapySDRThread.h                         |  121 +
 src/ui/GLPanel.cpp                               |  464 ++
 src/ui/GLPanel.h                                 |  119 +
 src/ui/UITestCanvas.cpp                          |   82 +
 src/ui/UITestCanvas.h                            |   35 +
 src/ui/UITestContext.cpp                         |   72 +
 src/ui/UITestContext.h                           |   24 +
 src/util/DataTree.cpp                            | 1793 +++++++
 src/util/DataTree.h                              |  362 ++
 src/util/GLExt.cpp                               |  100 +
 src/util/GLExt.h                                 |   24 +
 src/util/GLFont.cpp                              |  931 ++++
 src/util/GLFont.h                                |  207 +
 src/util/Gradient.cpp                            |   75 +
 src/util/Gradient.h                              |   33 +
 src/util/MouseTracker.cpp                        |  181 +
 src/util/MouseTracker.h                          |   50 +
 src/util/ThreadQueue.cpp                         |    1 +
 src/util/ThreadQueue.h                           |  301 ++
 src/util/Timer.cpp                               |  175 +
 src/util/Timer.h                                 |  164 +
 src/visual/ColorTheme.cpp                        |  265 +
 src/visual/ColorTheme.h                          |  121 +
 src/visual/GainCanvas.cpp                        |  220 +
 src/visual/GainCanvas.h                          |   53 +
 src/visual/InteractiveCanvas.cpp                 |  193 +
 src/visual/InteractiveCanvas.h                   |   65 +
 src/visual/MeterCanvas.cpp                       |  188 +
 src/visual/MeterCanvas.h                         |   61 +
 src/visual/MeterContext.cpp                      |   51 +
 src/visual/MeterContext.h                        |   19 +
 src/visual/ModeSelectorCanvas.cpp                |  221 +
 src/visual/ModeSelectorCanvas.h                  |   75 +
 src/visual/ModeSelectorContext.cpp               |   71 +
 src/visual/ModeSelectorContext.h                 |   17 +
 src/visual/PrimaryGLContext.cpp                  |  524 ++
 src/visual/PrimaryGLContext.h                    |   36 +
 src/visual/ScopeCanvas.cpp                       |  274 +
 src/visual/ScopeCanvas.h                         |   61 +
 src/visual/ScopeContext.cpp                      |   68 +
 src/visual/ScopeContext.h                        |   21 +
 src/visual/SpectrumCanvas.cpp                    |  299 ++
 src/visual/SpectrumCanvas.h                      |   61 +
 src/visual/TuningCanvas.cpp                      |  442 ++
 src/visual/TuningCanvas.h                        |   78 +
 src/visual/TuningContext.cpp                     |  196 +
 src/visual/TuningContext.h                       |   28 +
 src/visual/WaterfallCanvas.cpp                   |  931 ++++
 src/visual/WaterfallCanvas.h                     |   87 +
 237 files changed, 45146 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0bbd302
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+build/
+cmake_build/
+dist/
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..a817a1d
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,953 @@
+cmake_minimum_required (VERSION 2.8)
+
+project (CubicSDR)
+
+list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules/")
+
+SET(CUBICSDR_VERSION_MAJOR "0")
+SET(CUBICSDR_VERSION_MINOR "2")
+SET(CUBICSDR_VERSION_PATCH "0")
+SET(CUBICSDR_VERSION_REL "")
+SET(CUBICSDR_VERSION "${CUBICSDR_VERSION_MAJOR}.${CUBICSDR_VERSION_MINOR}.${CUBICSDR_VERSION_PATCH}${CUBICSDR_VERSION_REL}")
+
+SET(CPACK_PACKAGE_VERSION "${CUBICSDR_VERSION_MAJOR}.${CUBICSDR_VERSION_MINOR}.${CUBICSDR_VERSION_PATCH}")
+SET(CPACK_PACKAGE_VERSION_MAJOR ${CUBICSDR_VERSION_MAJOR})
+SET(CPACK_PACKAGE_VERSION_MINOR ${CUBICSDR_VERSION_MINOR})
+SET(CPACK_PACKAGE_VERSION_PATCH ${CUBICSDR_VERSION_PATCH})
+
+SET (VERSION_SUFFIX "" CACHE STRING "Add custom version suffix to CubicSDR application title.")
+
+ADD_DEFINITIONS(
+    -DCUBICSDR_VERSION="${CUBICSDR_VERSION}${VERSION_SUFFIX}"
+)
+
+SET (ENABLE_DIGITAL_LAB OFF CACHE BOOL "Enable 'Digital Lab' testing features.")
+IF(ENABLE_DIGITAL_LAB)
+ADD_DEFINITIONS(
+    -DENABLE_DIGITAL_LAB=1
+)
+IF(MSVC)
+	SET (ENABLE_LIQUID_EXPERIMENTAL OFF CACHE BOOL "Enable experimental liquid-dsp features (requires latest liquid-dsp installed)")
+ELSE()
+	SET (ENABLE_LIQUID_EXPERIMENTAL ON CACHE BOOL "Enable experimental liquid-dsp features (requires latest liquid-dsp installed)")
+ENDIF()
+IF(ENABLE_LIQUID_EXPERIMENTAL)
+ADD_DEFINITIONS(
+    -DENABLE_LIQUID_EXPERIMENTAL=1
+)
+ENDIF()
+ENDIF()
+
+set(USE_HAMLIB OFF CACHE BOOL "Support hamlib for radio control functions.")
+
+if (USE_HAMLIB)
+    find_package(hamlib REQUIRED)
+    
+    if (NOT HAMLIB_FOUND)
+        message(FATAL_ERROR "hamlib development files not found...")
+    endif ()
+    
+    include_directories(${HAMLIB_INCLUDE_DIR})
+    link_libraries(${HAMLIB_LIBRARY})
+
+	ADD_DEFINITIONS(-DUSE_HAMLIB)	    
+endif ()
+
+macro(configure_files srcDir destDir globStr)
+    message(STATUS "Copying ${srcDir}/${globStr} to directory ${destDir}")
+    make_directory(${destDir})
+
+    file(GLOB templateFiles RELATIVE ${srcDir} ${srcDir}/${globStr})
+    foreach(templateFile ${templateFiles})
+        set(srcTemplatePath ${srcDir}/${templateFile})
+        message(STATUS "Configuring file ${templateFile}")
+        if(NOT IS_DIRECTORY ${srcTemplatePath})
+            configure_file(
+                    ${srcTemplatePath}
+                    ${destDir}/${templateFile}
+                    COPYONLY)
+        endif(NOT IS_DIRECTORY ${srcTemplatePath})
+    endforeach(templateFile)
+endmacro(configure_files)
+
+macro(configure_files_recurse srcDir destDir)
+    message(STATUS "Configuring directory ${destDir}")
+    make_directory(${destDir})
+
+    file(GLOB_RECURSE templateFiles RELATIVE ${srcDir} ${srcDir}/*)
+    foreach(templateFile ${templateFiles})
+        set(srcTemplatePath ${srcDir}/${templateFile})
+        message(STATUS "Configuring file ${templateFile}")
+        if(NOT IS_DIRECTORY ${srcTemplatePath})
+            configure_file(
+                    ${srcTemplatePath}
+                    ${destDir}/${templateFile}
+                    COPYONLY)
+        endif(NOT IS_DIRECTORY ${srcTemplatePath})
+    endforeach(templateFile)
+endmacro(configure_files_recurse)
+
+if( CMAKE_SIZEOF_VOID_P EQUAL 8 )
+    MESSAGE( "64 bit compiler detected" )
+    SET( EX_PLATFORM 64 )
+    SET( EX_PLATFORM_NAME "x64" )
+else( CMAKE_SIZEOF_VOID_P EQUAL 8 ) 
+    MESSAGE( "32 bit compiler detected" )
+    SET( EX_PLATFORM 32 )
+    SET( EX_PLATFORM_NAME "x86" )
+endif( CMAKE_SIZEOF_VOID_P EQUAL 8 )
+
+
+SET( CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/${EX_PLATFORM_NAME})
+SET( CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${PROJECT_BINARY_DIR}/${EX_PLATFORM_NAME})
+SET( CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${PROJECT_BINARY_DIR}/${EX_PLATFORM_NAME})
+
+IF (MSVC) 
+	include_directories ("${PROJECT_SOURCE_DIR}/external/wglext")
+	SET(LIQUID_INCLUDES "${PROJECT_SOURCE_DIR}/external/liquid-dsp/include/" CACHE STRING "Liquid-DSP include directory")
+	SET(LIQUID_LIBRARIES "${PROJECT_SOURCE_DIR}/external/liquid-dsp/msvc/${EX_PLATFORM}/libliquid.lib" CACHE STRING "Liquid-DSP Library")
+	SET(LIQUID_DLL "${PROJECT_SOURCE_DIR}/external/liquid-dsp/msvc/${EX_PLATFORM}/libliquid.dll" CACHE STRING "Liquid-DSP DLL")
+	SET(HAMLIB_DLLS "${PROJECT_SOURCE_DIR}/external/hamlib/${EX_PLATFORM}/libhamlib-2.dll;${PROJECT_SOURCE_DIR}/external/hamlib/${EX_PLATFORM}/libwinpthread-1.dll" CACHE STRING "HAMLIB DLLS")
+ELSE (MSVC)
+	ADD_DEFINITIONS(
+		-std=c++0x 
+		-pthread
+	)
+ENDIF(MSVC)
+
+find_package(OpenGL REQUIRED)
+find_package(Liquid REQUIRED)
+include_directories(${LIQUID_INCLUDES})
+SET(OTHER_LIBRARIES ${OTHER_LIBRARIES} ${LIQUID_LIBRARIES})
+
+find_package(wxWidgets COMPONENTS gl core propgrid adv base REQUIRED)
+set(wxWidgets_CONFIGURATION mswu)
+include(${wxWidgets_USE_FILE})
+
+find_package(SoapySDR "0.4.0" NO_MODULE REQUIRED)
+include_directories(${SOAPY_SDR_INCLUDE_DIR})
+SET(OTHER_LIBRARIES ${SOAPY_SDR_LIBRARY} ${OTHER_LIBRARIES})
+ADD_DEFINITIONS(
+	-DUSE_SOAPY_SDR=1
+)       
+
+IF (WIN32)
+	set(wxWidgets_USE_STATIC ON)
+
+	set(BUILD_INSTALLER OFF CACHE BOOL "Build Installer")
+	
+	# Audio device selection is not mandatory, dummy audio device is used if none are compiled in.
+	# Can also compile support for more than one simultaneously.
+	set(USE_AUDIO_DS ON CACHE BOOL "Include support for DirectSound")
+	set(USE_AUDIO_WASAPI OFF CACHE BOOL "Include support for WASAPI Audio")
+	# TODO:
+	# set(USE_AUDIO_ASIO OFF CACHE BOOL "Include support for ASIO Audio")
+
+	# WASAPI
+	IF(USE_AUDIO_WASAPI)
+		ADD_DEFINITIONS(-D__WINDOWS_WASAPI__)	
+		IF (NOT MSVC)	
+			SET(OTHER_LIBRARIES ${OTHER_LIBRARIES} -luuid -lksuser)
+		ENDIF(NOT MSVC)
+	ENDIF(USE_AUDIO_WASAPI)
+
+	# DirectSound
+	IF (USE_AUDIO_DS)	
+		ADD_DEFINITIONS(-D__WINDOWS_DS__)	
+		IF (MSVC)	
+			SET(OTHER_LIBRARIES ${OTHER_LIBRARIES} dsound.lib)
+		ELSE (MSVC)
+			SET(OTHER_LIBRARIES ${OTHER_LIBRARIES} -ldsound)
+		ENDIF (MSVC)
+	ENDIF(USE_AUDIO_DS)    
+  
+	SET(USE_MINGW_PATCH OFF CACHE BOOL "Add some missing functions when compiling against mingw liquid-dsp.")
+	IF (USE_MINGW_PATCH) 
+		SET(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} legacy_stdio_definitions.lib libgcc.a")
+		ADD_DEFINITIONS(
+			-DMINGW_PATCH=1
+		)
+		SET (GCC_LINKDIR "" CACHE STRING "")
+		IF (GCC_LINKDIR)
+			link_directories("${GCC_LINKDIR}")
+		ENDIF()
+	ENDIF()
+ENDIF (WIN32)
+
+IF (UNIX AND NOT APPLE)
+    set(BUILD_DEB OFF CACHE BOOL "Build DEB")
+
+  
+    SET(USE_AUDIO_PULSE ON CACHE BOOL "Use Pulse Audio")
+    SET(USE_AUDIO_JACK OFF CACHE BOOL "Use Jack Audio")
+    SET(USE_AUDIO_ALSA OFF CACHE BOOL "Use ALSA Audio")
+    SET(USE_AUDIO_OSS OFF CACHE BOOL "Use OSS Audio")
+  
+    SET(LIQUID_LIB liquid)
+    SET(OTHER_LIBRARIES ${OTHER_LIBRARIES} dl)
+
+IF(USE_AUDIO_PULSE)
+   SET (OTHER_LIBRARIES ${OTHER_LIBRARIES} pulse-simple pulse)
+   ADD_DEFINITIONS(
+        -D__LINUX_PULSE__
+   )
+ENDIF(USE_AUDIO_PULSE)
+
+IF(USE_AUDIO_JACK)
+   find_package(Jack)
+   SET (OTHER_LIBRARIES ${OTHER_LIBRARIES} ${JACK_LIBRARIES})
+   ADD_DEFINITIONS(
+        -D__UNIX_JACK__
+   )
+   include_directories(${JACK_INCLUDE_DIRS})
+ENDIF(USE_AUDIO_JACK)
+
+IF(USE_AUDIO_ALSA)
+   SET (OTHER_LIBRARIES ${OTHER_LIBRARIES} asound)
+   set(ALSA_INCLUDE_DIR "/usr/include" CACHE FILEPATH "ALSA include path")
+   include_directories(${ALSA_INCLUDE_DIR})
+   set(ALSA_LIB_DIR "/usr/lib" CACHE FILEPATH "ALSA lib path")
+   link_directories(${ALSA_LIB_DIR})
+   ADD_DEFINITIONS(
+       -D__LINUX_ALSA__
+   )
+ENDIF(USE_AUDIO_ALSA)
+
+IF(USE_AUDIO_OSS)
+   SET (OTHER_LIBRARIES ${OTHER_LIBRARIES} oss)
+   ADD_DEFINITIONS(
+        -D__LINUX_OSS__
+   )
+ENDIF(USE_AUDIO_OSS)
+ENDIF(UNIX AND NOT APPLE)
+
+IF (APPLE)
+    SET(CMAKE_OSX_DEPLOYMENT_TARGET, "10.9")
+  
+    SET(LIQUID_LIB liquid)
+    link_directories(/usr/local/lib)
+    link_directories(/opt/local/lib)
+
+    ADD_DEFINITIONS(
+      -D__MACOSX_CORE__
+    )    
+
+   FIND_LIBRARY(COREAUDIO_LIBRARY CoreAudio)
+   SET (OTHER_LIBRARIES ${COREAUDIO_LIBRARY} ${OTHER_LIBRARIES})
+   set(BUNDLE_APP OFF CACHE BOOL "Bundle Application")
+
+ENDIF (APPLE)
+
+
+SET (cubicsdr_sources
+	src/CubicSDR.cpp
+	src/AppFrame.cpp
+	src/AppConfig.cpp
+	src/FrequencyDialog.cpp
+    src/DemodLabelDialog.cpp
+    src/IOThread.cpp
+    src/ModemProperties.cpp
+	src/sdr/SDRDeviceInfo.cpp
+	src/sdr/SDRPostThread.cpp
+	src/sdr/SDREnumerator.cpp
+	src/sdr/SoapySDRThread.h
+	src/demod/DemodulatorPreThread.cpp
+	src/demod/DemodulatorThread.cpp
+	src/demod/DemodulatorWorkerThread.cpp
+	src/demod/DemodulatorInstance.cpp
+	src/demod/DemodulatorMgr.cpp
+    src/modules/modem/Modem.cpp
+    src/modules/modem/ModemAnalog.cpp
+    src/modules/modem/ModemDigital.cpp
+    src/modules/modem/analog/ModemAM.cpp
+    src/modules/modem/analog/ModemDSB.cpp
+    src/modules/modem/analog/ModemFM.cpp
+    src/modules/modem/analog/ModemNBFM.cpp
+    src/modules/modem/analog/ModemFMStereo.cpp
+    src/modules/modem/analog/ModemIQ.cpp
+    src/modules/modem/analog/ModemLSB.cpp
+    src/modules/modem/analog/ModemUSB.cpp
+	src/audio/AudioThread.cpp
+	src/util/Gradient.cpp
+	src/util/Timer.cpp
+	src/util/MouseTracker.cpp
+	src/util/GLExt.cpp
+	src/util/GLFont.cpp
+	src/util/DataTree.cpp
+    src/panel/ScopePanel.cpp
+    src/panel/SpectrumPanel.cpp
+    src/panel/WaterfallPanel.cpp
+    src/panel/MeterPanel.cpp
+    src/panel/MeterPanel.h
+	src/visual/ColorTheme.cpp
+	src/visual/PrimaryGLContext.cpp
+	src/visual/InteractiveCanvas.cpp
+	src/visual/MeterCanvas.cpp
+	src/visual/MeterContext.cpp
+	src/visual/TuningCanvas.cpp
+	src/visual/TuningContext.cpp
+	src/visual/ModeSelectorCanvas.cpp
+	src/visual/ModeSelectorContext.cpp
+	src/visual/ScopeCanvas.cpp
+	src/visual/ScopeContext.cpp
+	src/visual/SpectrumCanvas.cpp
+	src/visual/WaterfallCanvas.cpp
+    src/visual/GainCanvas.cpp
+	src/process/VisualProcessor.cpp
+	src/process/ScopeVisualProcessor.cpp
+	src/process/SpectrumVisualProcessor.cpp
+	src/process/FFTVisualDataThread.cpp
+	src/process/FFTDataDistributor.cpp
+    src/process/SpectrumVisualDataThread.cpp
+	src/ui/GLPanel.cpp
+    src/forms/SDRDevices/SDRDevices.cpp
+    src/forms/SDRDevices/SDRDevicesForm.cpp
+    src/forms/SDRDevices/SDRDeviceAdd.cpp
+    src/forms/SDRDevices/SDRDeviceAddForm.cpp
+	external/rtaudio/RtAudio.cpp
+	external/lodepng/lodepng.cpp
+	external/tinyxml/tinyxml.cpp
+	external/tinyxml/tinystr.cpp
+	external/tinyxml/tinyxmlparser.cpp
+	external/tinyxml/tinyxmlerror.cpp
+	external/cubicvr2/math/cubic_math.cpp
+)
+
+IF(ENABLE_DIGITAL_LAB)
+    SET (cubicsdr_sources
+        ${cubicsdr_sources}
+        src/forms/DigitalConsole/DigitalConsole.cpp
+        src/forms/DigitalConsole/DigitalConsoleFrame.cpp
+        src/modules/modem/digital/ModemASK.cpp
+        src/modules/modem/digital/ModemAPSK.cpp
+        src/modules/modem/digital/ModemBPSK.cpp
+        src/modules/modem/digital/ModemDPSK.cpp
+        src/modules/modem/digital/ModemGMSK.cpp
+        src/modules/modem/digital/ModemPSK.cpp
+        src/modules/modem/digital/ModemOOK.cpp
+        src/modules/modem/digital/ModemST.cpp
+        src/modules/modem/digital/ModemSQAM.cpp
+        src/modules/modem/digital/ModemQAM.cpp
+        src/modules/modem/digital/ModemQPSK.cpp
+    )
+	IF(ENABLE_LIQUID_EXPERIMENTAL)
+    SET (cubicsdr_sources
+        ${cubicsdr_sources}
+        src/modules/modem/digital/ModemFSK.cpp
+    )
+	ENDIF()
+ENDIF()
+
+SET (cubicsdr_headers
+	src/CubicSDRDefs.h
+	src/CubicSDR.h
+	src/AppFrame.h
+	src/AppConfig.h
+	src/FrequencyDialog.h
+    src/DemodLabelDialog.h
+    src/IOThread.h
+    src/ModemProperties.h
+	src/sdr/SDRDeviceInfo.h
+	src/sdr/SDRPostThread.h
+	src/sdr/SDREnumerator.h
+	src/sdr/SoapySDRThread.cpp
+	src/demod/DemodulatorPreThread.h
+	src/demod/DemodulatorThread.h
+	src/demod/DemodulatorWorkerThread.h
+	src/demod/DemodulatorInstance.h
+	src/demod/DemodulatorMgr.h
+	src/demod/DemodDefs.h
+    src/modules/modem/Modem.h
+    src/modules/modem/ModemAnalog.h
+    src/modules/modem/ModemDigital.h
+    src/modules/modem/analog/ModemAM.h
+    src/modules/modem/analog/ModemDSB.h
+    src/modules/modem/analog/ModemFM.h
+    src/modules/modem/analog/ModemNBFM.h
+    src/modules/modem/analog/ModemFMStereo.h
+    src/modules/modem/analog/ModemIQ.h
+    src/modules/modem/analog/ModemLSB.h
+    src/modules/modem/analog/ModemUSB.h
+	src/audio/AudioThread.h
+	src/util/Gradient.h
+	src/util/Timer.h
+	src/util/ThreadQueue.h
+	src/util/MouseTracker.h
+	src/util/GLExt.h
+	src/util/GLFont.h
+	src/util/DataTree.h
+    src/panel/ScopePanel.h
+    src/panel/SpectrumPanel.h
+    src/panel/WaterfallPanel.h
+	src/visual/ColorTheme.h
+	src/visual/PrimaryGLContext.h
+	src/visual/InteractiveCanvas.h
+	src/visual/MeterCanvas.h
+	src/visual/MeterContext.h
+	src/visual/TuningCanvas.h
+	src/visual/TuningContext.h
+	src/visual/ModeSelectorCanvas.h
+	src/visual/ModeSelectorContext.h
+	src/visual/ScopeCanvas.h
+	src/visual/ScopeContext.h
+	src/visual/SpectrumCanvas.h
+	src/visual/WaterfallCanvas.h
+    src/visual/GainCanvas.h
+	src/process/VisualProcessor.h
+	src/process/ScopeVisualProcessor.h
+	src/process/SpectrumVisualProcessor.h
+	src/process/FFTVisualDataThread.h
+	src/process/FFTDataDistributor.h
+    src/process/SpectrumVisualDataThread.h
+	src/ui/GLPanel.h
+	src/ui/UITestCanvas.cpp
+	src/ui/UITestCanvas.h
+	src/ui/UITestContext.cpp
+	src/ui/UITestContext.h    
+    src/forms/SDRDevices/SDRDevices.h
+    src/forms/SDRDevices/SDRDevicesForm.h
+    src/forms/SDRDevices/SDRDeviceAdd.h
+    src/forms/SDRDevices/SDRDeviceAddForm.h
+	external/rtaudio/RtAudio.h
+	external/lodepng/lodepng.h
+	external/tinyxml/tinyxml.h
+	external/tinyxml/tinystr.h
+	external/cubicvr2/math/aabb.h
+	external/cubicvr2/math/cubic_math.h
+	external/cubicvr2/math/cubic_types.h
+	external/cubicvr2/math/frustum.h
+	external/cubicvr2/math/mat3.h
+	external/cubicvr2/math/mat4.h
+	external/cubicvr2/math/plane.h
+	external/cubicvr2/math/quaternion.h
+	external/cubicvr2/math/sphere.h
+	external/cubicvr2/math/transform.h
+	external/cubicvr2/math/triangle.h
+	external/cubicvr2/math/vec2.h
+	external/cubicvr2/math/vec3.h
+	external/cubicvr2/math/vec4.h
+)
+
+IF(ENABLE_DIGITAL_LAB)
+SET (cubicsdr_headers
+    ${cubicsdr_headers}
+    src/forms/DigitalConsole/DigitalConsole.h
+    src/forms/DigitalConsole/DigitalConsoleFrame.h
+    src/modules/modem/digital/ModemASK.h
+    src/modules/modem/digital/ModemAPSK.h
+    src/modules/modem/digital/ModemBPSK.h
+    src/modules/modem/digital/ModemDPSK.h
+    src/modules/modem/digital/ModemGMSK.h
+    src/modules/modem/digital/ModemPSK.h
+    src/modules/modem/digital/ModemOOK.h
+    src/modules/modem/digital/ModemST.h
+    src/modules/modem/digital/ModemSQAM.h
+    src/modules/modem/digital/ModemQAM.h
+    src/modules/modem/digital/ModemQPSK.h
+)    
+IF(ENABLE_LIQUID_EXPERIMENTAL)
+    SET (cubicsdr_sources
+        ${cubicsdr_sources}
+        src/modules/modem/digital/ModemFSK.h
+    )
+ENDIF()
+ENDIF()
+
+
+IF (USE_HAMLIB)
+    SET (cubicsdr_headers
+        ${cubicsdr_headers}
+        src/rig/RigThread.h
+    )    
+    SET (cubicsdr_sources
+        ${cubicsdr_sources}
+        src/rig/RigThread.cpp
+    )
+ENDIF()
+
+
+SET (CUBICSDR_FONTS 
+    ${PROJECT_SOURCE_DIR}/font/vera_sans_mono12.fnt
+    ${PROJECT_SOURCE_DIR}/font/vera_sans_mono16.fnt
+    ${PROJECT_SOURCE_DIR}/font/vera_sans_mono18.fnt
+    ${PROJECT_SOURCE_DIR}/font/vera_sans_mono24.fnt
+    ${PROJECT_SOURCE_DIR}/font/vera_sans_mono27.fnt
+    ${PROJECT_SOURCE_DIR}/font/vera_sans_mono32.fnt
+    ${PROJECT_SOURCE_DIR}/font/vera_sans_mono36.fnt
+    ${PROJECT_SOURCE_DIR}/font/vera_sans_mono48.fnt
+    ${PROJECT_SOURCE_DIR}/font/vera_sans_mono64.fnt
+    ${PROJECT_SOURCE_DIR}/font/vera_sans_mono72.fnt
+    ${PROJECT_SOURCE_DIR}/font/vera_sans_mono96.fnt
+    ${PROJECT_SOURCE_DIR}/font/vera_sans_mono12_0.png
+    ${PROJECT_SOURCE_DIR}/font/vera_sans_mono16_0.png
+    ${PROJECT_SOURCE_DIR}/font/vera_sans_mono18_0.png
+    ${PROJECT_SOURCE_DIR}/font/vera_sans_mono24_0.png
+    ${PROJECT_SOURCE_DIR}/font/vera_sans_mono27_0.png
+    ${PROJECT_SOURCE_DIR}/font/vera_sans_mono32_0.png
+    ${PROJECT_SOURCE_DIR}/font/vera_sans_mono36_0.png
+    ${PROJECT_SOURCE_DIR}/font/vera_sans_mono48_0.png
+    ${PROJECT_SOURCE_DIR}/font/vera_sans_mono64_0.png
+    ${PROJECT_SOURCE_DIR}/font/vera_sans_mono72_0.png
+    ${PROJECT_SOURCE_DIR}/font/vera_sans_mono96_0.png
+)
+
+set(REG_EXT "[^/]*([.]cpp|[.]c|[.]h|[.]hpp)$")
+
+SOURCE_GROUP("Base" REGULAR_EXPRESSION "src/${REG_EXT}")
+SOURCE_GROUP("Forms\\SDRDevices" REGULAR_EXPRESSION "src/forms/SDRDevices/${REG_EXT}")
+SOURCE_GROUP("SDR" REGULAR_EXPRESSION "src/sdr/${REG_EXT}")
+IF(USE_HAMLIB)
+    SOURCE_GROUP("Rig" REGULAR_EXPRESSION "src/rig/${REG_EXT}")    
+ENDIF()
+SOURCE_GROUP("Demodulator" REGULAR_EXPRESSION "src/demod/${REG_EXT}")
+SOURCE_GROUP("Modem" REGULAR_EXPRESSION "src/modules/modem/${REG_EXT}")
+SOURCE_GROUP("Modem\\Analog" REGULAR_EXPRESSION "src/modules/modem/analog/${REG_EXT}")
+IF(ENABLE_DIGITAL_LAB)
+SOURCE_GROUP("Modem\\Digital" REGULAR_EXPRESSION "src/modules/modem/digital/${REG_EXT}")
+SOURCE_GROUP("Forms\\DigitalConsole" REGULAR_EXPRESSION "src/forms/DigitalConsole/${REG_EXT}")
+ENDIF()
+SOURCE_GROUP("Audio" REGULAR_EXPRESSION "src/audio/${REG_EXT}")
+SOURCE_GROUP("Utility" REGULAR_EXPRESSION "src/util/${REG_EXT}")
+SOURCE_GROUP("Visual" REGULAR_EXPRESSION "src/visual/${REG_EXT}")
+SOURCE_GROUP("Panel" REGULAR_EXPRESSION "src/panel/${REG_EXT}")
+SOURCE_GROUP("Process" REGULAR_EXPRESSION "src/process/${REG_EXT}")
+SOURCE_GROUP("UI" REGULAR_EXPRESSION "src/ui/${REG_EXT}")
+SOURCE_GROUP("_ext-RTAudio" REGULAR_EXPRESSION "external/rtaudio/.*${REG_EXT}")
+SOURCE_GROUP("_ext-LodePNG" REGULAR_EXPRESSION "external/lodepng/.*${REG_EXT}")
+SOURCE_GROUP("_ext-TinyXML" REGULAR_EXPRESSION "external/tinyxml/.*${REG_EXT}")
+SOURCE_GROUP("_ext-CubicVR2" REGULAR_EXPRESSION "external/cubicvr2/.*${REG_EXT}")
+
+include_directories ( 
+	${PROJECT_SOURCE_DIR}/src/forms/SDRDevices
+	${PROJECT_SOURCE_DIR}/src/forms/DigitalConsole
+	${PROJECT_SOURCE_DIR}/src/sdr 
+	${PROJECT_SOURCE_DIR}/src/demod
+	${PROJECT_SOURCE_DIR}/src/modules
+	${PROJECT_SOURCE_DIR}/src/modules/modem
+	${PROJECT_SOURCE_DIR}/src/modules/modem/digital
+	${PROJECT_SOURCE_DIR}/src/modules/modem/analog
+	${PROJECT_SOURCE_DIR}/src/audio
+	${PROJECT_SOURCE_DIR}/src/util
+	${PROJECT_SOURCE_DIR}/src/panel
+	${PROJECT_SOURCE_DIR}/src/visual
+	${PROJECT_SOURCE_DIR}/src/process
+	${PROJECT_SOURCE_DIR}/src/ui
+	${PROJECT_SOURCE_DIR}/src/rig
+	${PROJECT_SOURCE_DIR}/src
+	${PROJECT_SOURCE_DIR}/external/rtaudio
+	${PROJECT_SOURCE_DIR}/external/lodepng 
+	${PROJECT_SOURCE_DIR}/external/tinyxml
+	${PROJECT_SOURCE_DIR}/external/cubicvr2/math
+)
+
+set(RES_FILES "")
+if(MINGW OR MSVC)
+ set(RES_FILES ${PROJECT_SOURCE_DIR}/cubicsdr.rc)
+ set(CMAKE_RC_COMPILER_INIT windres)
+ ENABLE_LANGUAGE(RC)
+ IF(EX_PLATFORM EQUAL 64)
+	SET(RC_TARGET "pe-x86-64")
+ ELSE(EX_PLATFORM EQUAL 64)
+	SET(RC_TARGET "pe-i386")
+ ENDIF(EX_PLATFORM EQUAL 64)
+	
+ SET(CMAKE_RC_COMPILE_OBJECT "<CMAKE_RC_COMPILER> -O coff <DEFINES> -i <SOURCE> -o <OBJECT>")
+endif(MINGW OR MSVC)
+
+IF (NOT BUNDLE_APP)
+    configure_files(${PROJECT_SOURCE_DIR}/font ${CMAKE_BINARY_DIR}/${EX_PLATFORM_NAME}/fonts "*.fnt")
+    configure_files(${PROJECT_SOURCE_DIR}/font ${CMAKE_BINARY_DIR}/${EX_PLATFORM_NAME}/fonts "*.png")
+    configure_files(${PROJECT_SOURCE_DIR}/icon ${CMAKE_BINARY_DIR}/${EX_PLATFORM_NAME} CubicSDR.ico)
+	IF(MSVC)	
+		configure_files(${PROJECT_SOURCE_DIR}/external/liquid-dsp/msvc/${EX_PLATFORM}/ ${CMAKE_BINARY_DIR}/${EX_PLATFORM_NAME} "*.dll")
+	ENDIF()
+    add_executable(CubicSDR ${cubicsdr_sources} ${cubicsdr_headers} ${RES_FILES})
+    target_link_libraries(CubicSDR ${LIQUID_LIB} ${wxWidgets_LIBRARIES} ${OPENGL_LIBRARIES} ${OTHER_LIBRARIES})
+ENDIF (NOT BUNDLE_APP)
+
+IF (MSVC)
+  set_target_properties(CubicSDR PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:WINDOWS")
+  set_target_properties(CubicSDR PROPERTIES COMPILE_DEFINITIONS_DEBUG "_WINDOWS")
+  set_target_properties(CubicSDR PROPERTIES LINK_FLAGS_RELWITHDEBINFO "/SUBSYSTEM:WINDOWS")
+  set_target_properties(CubicSDR PROPERTIES COMPILE_DEFINITIONS_RELWITHDEBINFO "_WINDOWS")
+  set_target_properties(CubicSDR PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS")
+  set_target_properties(CubicSDR PROPERTIES COMPILE_DEFINITIONS_RELEASE "_WINDOWS")
+  set_target_properties(CubicSDR PROPERTIES LINK_FLAGS_MINSIZEREL "/SUBSYSTEM:WINDOWS")
+  set_target_properties(CubicSDR PROPERTIES COMPILE_DEFINITIONS_MINSIZEREL "_WINDOWS")
+  set(CMAKE_CREATE_WIN32_EXE "/SUBSYSTEM:WINDOWS /ENTRY:\"mainCRTStartup\"")
+ENDIF(MSVC)
+
+IF (APPLE)
+    ADD_DEFINITIONS(
+        -DHAVE_TYPE_TRAITS=1
+        -mmacosx-version-min=10.9
+    )
+ENDIF(APPLE)
+
+IF (APPLE AND BUNDLE_APP)
+    PROJECT(CubicSDR)
+    set(CPACK_BINARY_DRAGNDROP ON)
+ 
+    SET(MACOSX_BUNDLE_BUNDLE_NAME CubicSDR)
+    SET(CPACK_APPLE_BUNDLE_ID "com.cubicproductions.cubicsdr")    
+    set(CUBICSDR_CODE_SIGN OFF CACHE BOOL "Code Signing")
+    
+    set(BUNDLE_SOAPY_MODS OFF CACHE BOOL "Bundle local SoapySDR modules")
+    set(BUNDLE_MIR_SDR OFF CACHE BOOL "Bundle mir_sdr for personal use only -- do not distribute.")
+    
+    IF (BUNDLE_SOAPY_MODS)
+    	ADD_DEFINITIONS(
+    		-DBUNDLE_SOAPY_MODS=1
+    	)
+	    set(BUNDLED_MODS_ONLY OFF CACHE BOOL "Use bundled mods only")
+		IF (BUNDLED_MODS_ONLY)
+		ADD_DEFINITIONS(
+    		-DBUNDLED_MODS_ONLY=1
+    	)
+		ENDIF()
+    ENDIF()
+    
+    ADD_DEFINITIONS(
+		-std=c++0x 
+		-pthread
+        -D_OSX_APP_
+    )   
+      
+    ADD_EXECUTABLE(CubicSDR 
+        MACOSX_BUNDLE 
+        ${cubicsdr_sources}
+        ${cubicsdr_headers} 
+        ${CUBICSDR_FONTS}
+        ${PROJECT_SOURCE_DIR}/icon/CubicSDR.icns
+    ) 
+    
+    SET_SOURCE_FILES_PROPERTIES(
+        ${PROJECT_SOURCE_DIR}/icon/CubicSDR.icns
+        PROPERTIES
+        MACOSX_PACKAGE_LOCATION Resources
+    )
+
+    SET_SOURCE_FILES_PROPERTIES(
+        ${CUBICSDR_FONTS}
+        PROPERTIES
+        MACOSX_PACKAGE_LOCATION Resources/fonts
+    )
+
+    target_link_libraries(CubicSDR ${LIQUID_LIB} ${wxWidgets_LIBRARIES} ${OPENGL_LIBRARIES} ${OTHER_LIBRARIES})
+    SET_TARGET_PROPERTIES(CubicSDR PROPERTIES
+        MACOSX_BUNDLE TRUE
+        MACOSX_BUNDLE_INFO_STRING "CubicSDR Open-Source Software-Defined Radio Application"
+        MACOSX_BUNDLE_BUNDLE_NAME "CubicSDR"
+        MACOSX_BUNDLE_BUNDLE_VERSION "${CUBICSDR_VERSION}"
+        MACOSX_BUNDLE_LONG_VERSION_STRING "${CUBICSDR_VERSION_MAJOR}.${CUBICSDR_VERSION_MINOR}.${CUBICSDR_VERSION_PATCH}.${CUBICSDR_VERSION_REL}"
+        MACOSX_BUNDLE_SHORT_VERSION_STRING "${CUBICSDR_VERSION_MAJOR}.${CUBICSDR_VERSION_MINOR}.${CUBICSDR_VERSION_PATCH}"
+        MACOSX_BUNDLE_GUI_IDENTIFIER "com.cubicproductions.cubicsdr"
+        MACOSX_BUNDLE_ICON_FILE "CubicSDR.icns"
+        MACOSX_BUNDLE_COPYRIGHT "Copyright 2015 Charles J. Cliffe. All Rights Reserved."
+    )
+    
+    SET(APPS "${CMAKE_BINARY_DIR}/${EX_PLATFORM_NAME}/CubicSDR.app")
+    # SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH FALSE)
+    # SET(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE)
+        
+    IF (BUNDLE_SOAPY_MODS)
+
+    message(STATUS "SOAPY_ROOT: ${SOAPY_SDR_ROOT}")
+    file(GLOB SOAPY_MODS ${SOAPY_SDR_ROOT}/lib/SoapySDR/modules/*.so)
+    
+    FOREACH(SOAPY_MOD_FILE ${SOAPY_MODS})
+            INSTALL( FILES "${SOAPY_MOD_FILE}"
+                DESTINATION "${APPS}/Contents/MacOS/modules"
+                COMPONENT Runtime
+            )
+    ENDFOREACH()
+        
+    ENDIF(BUNDLE_SOAPY_MODS)
+
+    SET_TARGET_PROPERTIES(CubicSDR PROPERTIES MACOSX_BUNDLE_INFO_PLIST "${PROJECT_SOURCE_DIR}/cmake/CubicSDRInfo.plist.in")
+
+    INSTALL(CODE "
+       SET(BU_COPY_FULL_FRAMEWORK_CONTENTS ON)
+       include(BundleUtilities)
+       fixup_bundle(\"${APPS}\"   \"\"   \"/usr/local/lib\")
+       " COMPONENT Runtime)
+
+    IF (BUNDLE_SOAPY_MODS)
+       FOREACH(SOAPY_MOD_FILE ${SOAPY_MODS})
+           GET_FILENAME_COMPONENT(SOAPY_MOD_NAME ${SOAPY_MOD_FILE} NAME)
+           IF(NOT BUNDLE_MIR_SDR) # prevent inclusion of libmirsdrapi-rsp.so
+           IF(${SOAPY_MOD_NAME} STREQUAL "libsdrPlaySupport.so")
+               message(STATUS "Excluding libsdrPlaySupport.so")
+               CONTINUE()
+           ELSE()
+               message(STATUS "Bundling ${SOAPY_MOD_NAME} from ${SOAPY_MOD_FILE}")
+           ENDIF()
+           ENDIF()
+           INSTALL(CODE "
+               fixup_bundle(\"${APPS}\"   \"${APPS}/Contents/MacOS/modules/${SOAPY_MOD_NAME}\"   \"/usr/local/lib\")
+              " COMPONENT Runtime)
+       ENDFOREACH()
+   ENDIF(BUNDLE_SOAPY_MODS)
+   
+   IF (CUBICSDR_CODE_SIGN)
+       SET (CUBICSDR_CERT "3rd Party Mac Developer Application: [Name]" CACHE STRING "Code signing certificate name.")
+       MESSAGE(STATUS "Code Signing Enabled: ${CUBICSDR_CERT}")        
+  
+       CONFIGURE_FILE(
+         "${PROJECT_SOURCE_DIR}/cmake/code_sign.sh.in"
+         "${CMAKE_CURRENT_BINARY_DIR}/code_sign.sh"
+        )
+        CONFIGURE_FILE(
+          "${PROJECT_SOURCE_DIR}/cmake/dmg_sign.sh.in"
+          "${CMAKE_CURRENT_BINARY_DIR}/dmg_sign.sh"
+         )
+  
+        ADD_CUSTOM_COMMAND(
+            TARGET CubicSDR
+            COMMAND chmod ARGS +x ${CMAKE_CURRENT_BINARY_DIR}/code_sign.sh ${CMAKE_CURRENT_BINARY_DIR}/dmg_sign.sh
+        )
+
+        INSTALL(CODE "
+            execute_process(
+                COMMAND ${CMAKE_COMMAND} -E echo \"Signing code..\"
+                COMMAND \"${CMAKE_CURRENT_BINARY_DIR}/code_sign.sh\"
+            )"
+        COMPONENT Runtime)
+  
+        ADD_CUSTOM_COMMAND(
+            TARGET CubicSDR
+            POST_BUILD
+            COMMAND ${CMAKE_CPACK_COMMAND}
+            COMMAND ${CMAKE_COMMAND} -E echo "Signing package.."
+            COMMAND "${CMAKE_CURRENT_BINARY_DIR}/dmg_sign.sh" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+        )
+   ENDIF(CUBICSDR_CODE_SIGN)
+   
+   
+   INSTALL(CODE "
+       VERIFY_APP(\"${APPS}\")
+       " COMPONENT Runtime)
+
+   INSTALL(TARGETS CubicSDR
+       BUNDLE DESTINATION . COMPONENT Runtime
+   )       
+
+   include(CPack)
+ENDIF()
+IF(APPLE AND NOT BUNDLE_APP)
+    IF (NOT CMAKE_INSTALL_PREFIX)
+        SET(CMAKE_INSTALL_PREFIX "/usr/")
+    ENDIF()
+	ADD_DEFINITIONS(
+	    -DRES_FOLDER="${CMAKE_INSTALL_PREFIX}/share/cubicsdr/"
+	)	
+
+    set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,-z,relro")
+    set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,relro")
+
+	INSTALL(TARGETS CubicSDR DESTINATION bin)
+        install(FILES 
+            ${PROJECT_SOURCE_DIR}/src/CubicSDR.png
+	    DESTINATION share/cubicsdr)
+
+        install(FILES 
+            ${CUBICSDR_FONTS}
+	    DESTINATION share/cubicsdr/fonts)
+
+    CONFIGURE_FILE(
+      "${PROJECT_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in"
+      "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake"
+       @ONLY IMMEDIATE)
+    ADD_CUSTOM_TARGET(uninstall
+      "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake") 
+          
+ENDIF ()
+
+
+IF (WIN32 AND NOT BUILD_INSTALLER)
+    ADD_DEFINITIONS(
+        -DRES_FOLDER="../share/cubicsdr/"
+    )
+	
+    INSTALL(TARGETS CubicSDR DESTINATION bin)
+	INSTALL(FILES 		
+		${LIQUID_DLL}
+	DESTINATION bin)
+
+	IF(USE_HAMLIB)
+		FOREACH(HAMLIB_DLL ${HAMLIB_DLLS})
+			message(STATUS "Copying Hamlib DLL: ${HAMLIB_DLL}")
+            INSTALL( FILES "${HAMLIB_DLL}"
+                DESTINATION bin
+            )
+		ENDFOREACH()
+	ENDIF()
+
+    INSTALL(FILES 
+		${PROJECT_SOURCE_DIR}/src/CubicSDR.png
+	 DESTINATION share/cubicsdr)
+
+    INSTALL(FILES 
+		${CUBICSDR_FONTS}
+	 DESTINATION share/cubicsdr/fonts)
+ENDIF()
+
+IF (WIN32 AND BUILD_INSTALLER)
+    set(BUNDLE_SOAPY_MODS OFF CACHE BOOL "Bundle local SoapySDR modules")
+
+	set(CPACK_GENERATOR NSIS)
+	set(CPACK_PACKAGE_NAME "CubicSDR")
+	set(CPACK_PACKAGE_VENDOR "cubicsdr.com")
+	set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "CubicSDR ${CUBICSDR_VERSION} Installer")
+	set(CPACK_PACKAGE_INSTALL_DIRECTORY "CubicSDR")
+	SET(CPACK_NSIS_INSTALLED_ICON_NAME "CubicSDR.ico")
+	SET(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE") 
+	set(CPACK_PACKAGE_ICON "${PROJECT_SOURCE_DIR}/icon\\\\NSIS_Header.bmp")
+	IF(EX_PLATFORM EQUAL 64)
+		SET(CPACK_NSIS_INSTALL_ROOT "$PROGRAMFILES64")
+		SET(CPACK_NSIS_PACKAGE_NAME "${CPACK_PACKAGE_INSTALL_DIRECTORY}")
+		SET(CPACK_PACKAGE_INSTALL_REGISTRY_KEY "${CPACK_PACKAGE_NAME} ${CPACK_PACKAGE_VERSION}")
+		set(CMAKE_CL_64 TRUE)	# This gets around a bug in the CPack installer name generation for MinGW 64-bit since 2.8
+	ELSE(EX_PLATFORM EQUAL 64)
+		SET(CPACK_NSIS_INSTALL_ROOT "$PROGRAMFILES")
+		SET(CPACK_NSIS_PACKAGE_NAME "${CPACK_PACKAGE_INSTALL_DIRECTORY} (x86)")
+		SET(CPACK_PACKAGE_INSTALL_REGISTRY_KEY "${CPACK_PACKAGE_NAME} ${CPACK_PACKAGE_VERSION} (x86)")
+		set(CMAKE_CL_64 FALSE)
+	ENDIF(EX_PLATFORM EQUAL 64)
+	
+	set(CPACK_NSIS_EXECUTABLES_DIRECTORY ".") 
+	install(TARGETS CubicSDR RUNTIME DESTINATION .)
+
+	install(FILES 
+        ${PROJECT_SOURCE_DIR}/icon/CubicSDR.ico
+		${LIQUID_DLL}
+		DESTINATION .)
+
+	install(FILES 
+        ${CUBICSDR_FONTS}
+		DESTINATION fonts)
+		
+	IF(USE_HAMLIB)
+		FOREACH(HAMLIB_DLL ${HAMLIB_DLLS})
+			message(STATUS "Copying Hamlib DLL: ${HAMLIB_DLL}")
+            INSTALL( FILES 
+				${HAMLIB_DLL}
+			DESTINATION .)
+		ENDFOREACH()
+	ENDIF()
+
+	IF (BUNDLE_SOAPY_MODS)
+		ADD_DEFINITIONS(
+			-DBUNDLE_SOAPY_MODS=1
+		)
+	    set(BUNDLED_MODS_ONLY OFF CACHE BOOL "Use bundled mods only")
+		IF (BUNDLED_MODS_ONLY)
+		ADD_DEFINITIONS(
+    		-DBUNDLED_MODS_ONLY=1
+    	)
+		ENDIF()
+		
+		file(GLOB SOAPY_BINS ${SOAPY_SDR_ROOT}/bin/*.dll)
+		file(GLOB SOAPY_MODS ${SOAPY_SDR_ROOT}/lib/SoapySDR/modules/*.dll)
+		message(STATUS "SOAPY_BINS: ${SOAPY_BINS}")
+		message(STATUS "SOAPY_MODS: ${SOAPY_MODS}")
+		install(FILES ${SOAPY_BINS} DESTINATION .)
+		install(FILES ${SOAPY_MODS} DESTINATION modules)
+	ENDIF(BUNDLE_SOAPY_MODS)
+	
+	IF(MSVC AND EX_PLATFORM EQUAL 32)
+	install(FILES 
+		${PROJECT_SOURCE_DIR}/external/msvc/${EX_PLATFORM_NAME}/libgcc_s_dw2-1.dll
+		DESTINATION .)
+	ENDIF(MSVC AND EX_PLATFORM EQUAL 32)
+
+		set(CPACK_PACKAGE_EXECUTABLES CubicSDR "CubicSDR")
+
+	IF (MSVC)
+		install(PROGRAMS ${CMAKE_CURRENT_SOURCE_DIR}/external/msvc/${EX_PLATFORM_NAME}/vc_redist.${EX_PLATFORM_NAME}.exe DESTINATION vc_redist)
+		set(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "ExecWait '\\\"$INSTDIR\\\\vc_redist\\\\vc_redist.${EX_PLATFORM_NAME}.exe\\\" /q:a'")
+	ENDIF (MSVC)
+		  
+		  
+	INCLUDE(CPack)	
+ENDIF (WIN32 AND BUILD_INSTALLER)
+
+
+IF (UNIX AND NOT APPLE AND BUILD_DEB) 
+    set(CPACK_GENERATOR DEB)
+	set(CPACK_PACKAGE_NAME "CubicSDR")
+	SET(CPACK_DEBIAN_PACKAGE_DEPENDS " libwxgtk3.0-0, libpulse0")
+ 	SET(CPACK_DEBIAN_PACKAGE_MAINTAINER "Charles J. Cliffe <cj at cubicproductions.com>")
+	SET(CPACK_DEBIAN_PACKAGE_DESCRIPTION "CubicSDR Software Defined Radio application v${CUBICSDR_VERSION}")
+	SET(CPACK_DEBIAN_PACKAGE_SECTION "comm")
+        SET(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/LICENSE")
+	SET(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${EX_PLATFORM_NAME}")
+
+    IF (NOT CMAKE_INSTALL_PREFIX)
+        SET(CMAKE_INSTALL_PREFIX "/usr/")
+    ENDIF()
+	ADD_DEFINITIONS(
+	    -DRES_FOLDER="${CMAKE_INSTALL_PREFIX}/share/cubicsdr/"
+	    -D_FORTIFY_SOURCE=2
+	)	
+    	
+    set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,-z,relro")
+    set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,relro")
+
+	CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/external/deb/deb_post.sh.in" 
+			"${CMAKE_CURRENT_BINARY_DIR}/deb_post.sh" @ONLY IMMEDIATE)
+
+	CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/cmake/CubicSDR.desktop.in"
+			"${CMAKE_CURRENT_BINARY_DIR}/CubicSDR.desktop" @ONLY IMMEDIATE)
+
+	INSTALL(TARGETS CubicSDR DESTINATION bin)
+        install(FILES 
+            ${PROJECT_SOURCE_DIR}/src/CubicSDR.png
+	    DESTINATION share/cubicsdr)
+
+        install(FILES 
+            ${CUBICSDR_FONTS}
+	    DESTINATION share/cubicsdr/fonts)
+        
+    INSTALL(FILES "${CMAKE_CURRENT_BINARY_DIR}/CubicSDR.desktop" 
+        DESTINATION share/applications)
+        
+	INCLUDE(CPack)
+ENDIF()
+IF(UNIX AND NOT APPLE AND NOT BUILD_DEB)
+    IF (NOT CMAKE_INSTALL_PREFIX)
+        SET(CMAKE_INSTALL_PREFIX "/usr/")
+    ENDIF()
+	ADD_DEFINITIONS(
+	    -DRES_FOLDER="${CMAKE_INSTALL_PREFIX}/share/cubicsdr/"
+	)	
+
+    set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,-z,relro")
+    set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,relro")
+ 
+	CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/cmake/CubicSDR.desktop.in"
+			"${CMAKE_CURRENT_BINARY_DIR}/CubicSDR.desktop" @ONLY IMMEDIATE)
+
+	INSTALL(TARGETS CubicSDR DESTINATION bin)
+ 
+    INSTALL(FILES 
+            ${PROJECT_SOURCE_DIR}/src/CubicSDR.png
+		    DESTINATION share/cubicsdr)
+
+    INSTALL(FILES 
+            ${CUBICSDR_FONTS}
+		    DESTINATION share/cubicsdr/fonts)
+
+    INSTALL(FILES "${CMAKE_CURRENT_BINARY_DIR}/CubicSDR.desktop"
+        	DESTINATION share/applications)
+        
+    CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/external/deb/deb_post.sh.in" 
+			"${CMAKE_CURRENT_BINARY_DIR}/deb_post.sh" @ONLY IMMEDIATE)
+    CONFIGURE_FILE(
+      "${PROJECT_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in"
+      "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake"
+       @ONLY IMMEDIATE)
+    ADD_CUSTOM_TARGET(uninstall
+      "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake") 
+
+ENDIF()
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..00eec89
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,340 @@
+GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    CubicSDR
+    Copyright (C) 2014  Charles J. Cliffe
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  {signature of Ty Coon}, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..e931b94
--- /dev/null
+++ b/README.md
@@ -0,0 +1,51 @@
+CubicSDR
+========
+
+Cross-Platform Software-Defined Radio Application
+
+- Please see the [CubicSDR GitHub Wiki](https://github.com/cjcliffe/CubicSDR/wiki) for build instructions.
+- Manual is available (work-in-progress) at [cubicsdr.readthedocs.io](http://cubicsdr.readthedocs.io).
+- See also the current [CubicSDR Releases](https://github.com/cjcliffe/CubicSDR/releases) page for available binaries.
+
+Utilizes: 
+--------
+  - liquid-dsp (http://liquidsdr.org/ -- https://github.com/jgaeddert/liquid-dsp)
+  - SoapySDR (http://www.pothosware.com/ -- https://github.com/pothosware/SoapySDR)
+  - RtAudio (http://www.music.mcgill.ca/~gary/rtaudio/ -- http://github.com/thestk/rtaudio/)
+  - LodePNG (http://lodev.org/lodepng/)
+  - BMFont (http://www.angelcode.com/ -- http://www.angelcode.com/products/bmfont/)
+  - Bitstream Vera font (http://en.wikipedia.org/wiki/Bitstream_Vera)
+  - OpenGL (https://www.opengl.org/)
+  - wxWidgets (https://www.wxwidgets.org/)
+  - CMake (http://www.cmake.org/)
+
+Optional Libs:
+--------
+  - FFTW3 (can be compiled into liquid-dsp if desired) (http://www.fftw.org/ -- https://github.com/FFTW/fftw3)
+  - hamlib (https://sourceforge.net/p/hamlib/wiki/Hamlib/ -- https://sourceforge.net/p/hamlib/code/ci/master/tree/)
+
+Features and Status:
+--------------------
+  - Please see the issues on GitHub or visit https://github.com/cjcliffe/CubicSDR/wiki/CubicSDR-Roadmap-and-Ideas for more information.
+  - A manual is in development at https://github.com/cjcliffe/CubicSDR/issues/248 if you would like to contribute.
+
+Recommended minimum requirements:
+--------------------
+  - Multi-core processor system with at least 1GB RAM.
+  - Graphics card with at least 128MB video memory and OpenGL 3.x or ES 2.0 support.
+  - OSX 10.9+ for Mac binary releases.
+  - Windows 7+ for 64 or 32-bit Windows binary releases.
+  - Linux and other embedded distribution support yet to be indexed, known to at least work on Debian 8+ and Ubuntu 14+.
+  - Raspberry Pi2 support is being experimented with; earlier versions of CubicSDR known to work with Banana Pi.
+
+Target Platforms:
+----------------
+  - [x] OSX
+  - [x] Windows
+  - [x] Linux
+  - [ ] HTML5
+
+
+License:
+-------
+  - GPL
diff --git a/cmake/CubicSDR.desktop.in b/cmake/CubicSDR.desktop.in
new file mode 100644
index 0000000..6d33ae4
--- /dev/null
+++ b/cmake/CubicSDR.desktop.in
@@ -0,0 +1,9 @@
+[Desktop Entry]
+Type=Application
+Exec=CubicSDR %u
+Icon=@CMAKE_INSTALL_PREFIX@/share/cubicsdr/CubicSDR.png
+Terminal=false
+Name=CubicSDR
+GenericName=CubicSDR
+Comment=Software-Defined Radio Application
+Categories=Science;HamRadio;DataVisualization;
diff --git a/cmake/CubicSDRInfo.plist.in b/cmake/CubicSDRInfo.plist.in
new file mode 100644
index 0000000..c3658f4
--- /dev/null
+++ b/cmake/CubicSDRInfo.plist.in
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>English</string>
+	<key>CFBundleExecutable</key>
+	<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
+	<key>CFBundleGetInfoString</key>
+	<string>${MACOSX_BUNDLE_INFO_STRING}</string>
+	<key>CFBundleIconFile</key>
+	<string>${MACOSX_BUNDLE_ICON_FILE}</string>
+	<key>CFBundleIdentifier</key>
+	<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleLongVersionString</key>
+	<string>${MACOSX_BUNDLE_LONG_VERSION_STRING}</string>
+	<key>CFBundleName</key>
+	<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
+	<key>CFBundlePackageType</key>
+	<string>APPL</string>
+	<key>CFBundleShortVersionString</key>
+	<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
+	<key>CFBundleSignature</key>
+	<string>????</string>
+	<key>CFBundleVersion</key>
+	<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
+	<key>CSResourcesFileMapped</key>
+	<true/>
+	<key>LSRequiresCarbon</key>
+	<true/>
+	<key>NSHumanReadableCopyright</key>
+	<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
+    <key>NSHighResolutionCapable</key>
+    <true/>
+    <key>NSSupportsAutomaticGraphicsSwitching</key>
+    <true/>
+</dict>
+</plist>
diff --git a/cmake/Modules/FindJack.cmake b/cmake/Modules/FindJack.cmake
new file mode 100644
index 0000000..6d93087
--- /dev/null
+++ b/cmake/Modules/FindJack.cmake
@@ -0,0 +1,50 @@
+# Try to find JACK
+# This will define the following variables:
+#
+#  JACK_FOUND - Whether Jack was found.
+#  JACK_INCLUDE_DIRS - Jack include directories.
+#  JACK_LIBRARIES - Jack libraries.
+
+include(FindPackageHandleStandardArgs)
+
+if(JACK_LIBRARIES AND JACK_INCLUDE_DIRS)
+
+  # in cache already
+  set(JACK_FOUND TRUE)
+
+else()
+
+  find_package(PkgConfig)
+  if(PKG_CONFIG_FOUND)
+    pkg_check_modules(_JACK jack)
+  endif(PKG_CONFIG_FOUND)
+
+  find_path(JACK_INCLUDE_DIR
+    NAMES
+      jack/jack.h
+    PATHS
+      ${_JACK_INCLUDEDIR}
+  )
+  
+  find_library(JACK_LIBRARY
+    NAMES
+      jack
+    PATHS
+      ${_JACK_LIBDIR}
+  )
+
+  set(JACK_INCLUDE_DIRS
+    ${JACK_INCLUDE_DIR}
+  )
+
+  set(JACK_LIBRARIES
+    ${JACK_LIBRARY}
+  )
+
+  find_package_handle_standard_args(Jack DEFAULT_MSG JACK_LIBRARIES JACK_INCLUDE_DIRS)
+
+  # show the JACK_INCLUDE_DIRS and JACK_LIBRARIES variables only in the advanced view
+  mark_as_advanced(JACK_INCLUDE_DIR JACK_LIBRARY JACK_INCLUDE_DIRS JACK_LIBRARIES)
+
+endif()
+
diff --git a/cmake/Modules/FindLiquid.cmake b/cmake/Modules/FindLiquid.cmake
new file mode 100644
index 0000000..5ce9d0d
--- /dev/null
+++ b/cmake/Modules/FindLiquid.cmake
@@ -0,0 +1,22 @@
+# - Find LIQUID
+# Find the native LIQUID includes and library
+#
+#  LIQUID_INCLUDES    - where to find LIQUID.h
+#  LIQUID_LIBRARIES   - List of libraries when using LIQUID.
+#  LIQUID_FOUND       - True if LIQUID found.
+
+if (LIQUID_INCLUDES)
+  # Already in cache, be silent
+  set (LIQUID_FIND_QUIETLY TRUE)
+endif (LIQUID_INCLUDES)
+
+find_path (LIQUID_INCLUDES liquid/liquid.h)
+
+find_library (LIQUID_LIBRARIES NAMES liquid)
+
+# handle the QUIETLY and REQUIRED arguments and set LIQUID_FOUND to TRUE if
+# all listed variables are TRUE
+include (FindPackageHandleStandardArgs)
+find_package_handle_standard_args (LIQUID DEFAULT_MSG LIQUID_LIBRARIES LIQUID_INCLUDES)
+
+#mark_as_advanced (LIQUID_LIBRARIES LIQUID_INCLUDES)
diff --git a/cmake/Modules/Findhamlib.cmake b/cmake/Modules/Findhamlib.cmake
new file mode 100644
index 0000000..0e90d1b
--- /dev/null
+++ b/cmake/Modules/Findhamlib.cmake
@@ -0,0 +1,77 @@
+# - Try to find Hamlib
+# Author: George L. Emigh - AB4BD
+# 
+# Change Log: Charles J. Cliffe <cj at cubicproductions.com>
+#  Updates: 
+#       Jan 2015 - Add /opt/ paths for OSX MacPorts
+#                - Fix HAMLIB_INCLUDE_DIR absolute search
+#                - Add static lib support
+#  TODO: 
+#       Windows support
+#
+# HAMLIB_FOUND - system has Hamlib
+# HAMLIB_LIBRARY - location of the library for hamlib
+# HAMLIB_INCLUDE_DIR - location of the include files for hamlib
+
+set(HAMLIB_FOUND FALSE)
+
+find_path(HAMLIB_INCLUDE_DIR
+	NAMES hamlib/rig.h
+	PATHS
+		/usr/include
+		/usr/local/include
+		/opt/local/include
+)
+
+find_library(HAMLIB_LIBRARY
+	NAMES hamlib
+	PATHS
+		/usr/lib64/hamlib
+		/usr/lib/hamlib
+		/usr/lib64
+		/usr/lib
+		/usr/local/lib64/hamlib
+		/usr/local/lib/hamlib
+		/usr/local/lib64
+		/usr/local/lib
+		/opt/local/lib
+		/opt/local/lib/hamlib
+)
+
+find_library(HAMLIB_STATIC_LIBRARY
+	NAMES libhamlib.a
+	PATHS
+		/usr/lib64/hamlib
+		/usr/lib/hamlib
+		/usr/lib64
+		/usr/lib
+		/usr/local/lib64/hamlib
+		/usr/local/lib/hamlib
+		/usr/local/lib64
+		/usr/local/lib
+		/opt/local/lib
+		/opt/local/lib/hamlib
+)
+
+GET_FILENAME_COMPONENT(HAMLIB_LIB_FOLDER ${HAMLIB_STATIC_LIBRARY} DIRECTORY)
+
+file(GLOB HAMLIB_STATIC_MODS ${HAMLIB_LIB_FOLDER}/hamlib-*.a)
+
+if(HAMLIB_INCLUDE_DIR AND HAMLIB_LIBRARY)
+	set(HAMLIB_FOUND TRUE)
+    # message(STATUS "Hamlib version: ${VERSION}")
+	message(STATUS "Found hamlib library at: ${HAMLIB_LIBRARY}")
+	message(STATUS "Found hamlib static library at: ${HAMLIB_STATIC_LIBRARY}")
+	message(STATUS "Found hamlib static modules: ${HAMLIB_STATIC_MODS}")
+	message(STATUS "Found hamlib include directory at: ${HAMLIB_INCLUDE_DIR}")
+endif(HAMLIB_INCLUDE_DIR AND HAMLIB_LIBRARY)
+
+IF(NOT HAMLIB_FOUND)
+  IF(NOT HAMLIB_FIND_QUIETLY)
+    MESSAGE(STATUS "HAMLIB was not found.")
+  ELSE(NOT HAMLIB_FIND_QUIETLY)
+    IF(HAMLIB_FIND_REQUIRED)
+      MESSAGE(FATAL_ERROR "HAMLIB was not found.")
+    ENDIF(HAMLIB_FIND_REQUIRED)
+  ENDIF(NOT HAMLIB_FIND_QUIETLY)
+ENDIF(NOT HAMLIB_FOUND)
\ No newline at end of file
diff --git a/cmake/cmake_uninstall.cmake.in b/cmake/cmake_uninstall.cmake.in
new file mode 100644
index 0000000..9da54f5
--- /dev/null
+++ b/cmake/cmake_uninstall.cmake.in
@@ -0,0 +1,22 @@
+IF(NOT EXISTS "${CMAKE_CURRENT_BINARY_DIR}/install_manifest.txt")
+  MESSAGE(FATAL_ERROR "Cannot find install manifest: '${CMAKE_CURRENT_BINARY_DIR}/install_manifest.txt'")
+ENDIF(NOT EXISTS "${CMAKE_CURRENT_BINARY_DIR}/install_manifest.txt")
+
+FILE(READ "${CMAKE_CURRENT_BINARY_DIR}/install_manifest.txt" files)
+STRING(REGEX REPLACE " " ";" files "${files}")
+STRING(REGEX REPLACE "\n" ";" files "${files}")
+FOREACH(file ${files})
+  MESSAGE(STATUS "Uninstalling '$ENV{DESTDIR}${file}'")
+  IF(EXISTS "$ENV{DESTDIR}${file}")
+    EXEC_PROGRAM(
+      "${CMAKE_COMMAND}" ARGS "-E remove '$ENV{DESTDIR}${file}'"
+      OUTPUT_VARIABLE rm_out
+      RETURN_VALUE rm_retval
+      )
+    IF(NOT "${rm_retval}" STREQUAL 0)
+      MESSAGE(FATAL_ERROR "Problem when removing '$ENV{DESTDIR}${file}'")
+    ENDIF(NOT "${rm_retval}" STREQUAL 0)
+  ELSE(EXISTS "$ENV{DESTDIR}${file}")
+    MESSAGE(STATUS "File '$ENV{DESTDIR}${file}' does not exist.")
+  ENDIF(EXISTS "$ENV{DESTDIR}${file}")
+ENDFOREACH(file)
\ No newline at end of file
diff --git a/cmake/code_sign.sh.in b/cmake/code_sign.sh.in
new file mode 100755
index 0000000..8b78396
--- /dev/null
+++ b/cmake/code_sign.sh.in
@@ -0,0 +1,11 @@
+#!/bin/bash
+for f in ${APPS}/Contents/MacOS/*.dylib
+do
+    /usr/bin/codesign --force --verify --verbose --sign "${CUBICSDR_CERT}" $f
+done
+for f in ${APPS}/Contents/MacOS/modules/*.so
+do
+    /usr/bin/codesign --force --verify --verbose --sign "${CUBICSDR_CERT}" $f
+done
+
+/usr/bin/codesign --force --verify --verbose --sign "${CUBICSDR_CERT}" ${APPS}
diff --git a/cmake/dmg_sign.sh.in b/cmake/dmg_sign.sh.in
new file mode 100755
index 0000000..19f9404
--- /dev/null
+++ b/cmake/dmg_sign.sh.in
@@ -0,0 +1,2 @@
+#!/bin/bash
+/usr/bin/codesign --force --verify --verbose --sign "${CUBICSDR_CERT}" CubicSDR-${CPACK_PACKAGE_VERSION}-Darwin.dmg
diff --git a/cubicsdr.rc b/cubicsdr.rc
new file mode 100644
index 0000000..38c3ee2
--- /dev/null
+++ b/cubicsdr.rc
@@ -0,0 +1,2 @@
+id ICON "icon/CubicSDR.ico"
+frame_icon ICON "icon/CubicSDR.ico"
\ No newline at end of file
diff --git a/external/cubicvr2/math/aabb.h b/external/cubicvr2/math/aabb.h
new file mode 100644
index 0000000..bcf34ba
--- /dev/null
+++ b/external/cubicvr2/math/aabb.h
@@ -0,0 +1,110 @@
+//
+//  aabb.h
+//  CubicVR2
+//
+//  Created by Charles J. Cliffe on 2013-02-22.
+//  Copyright (c) 2013 Charles J. Cliffe. All rights reserved.
+//
+
+#ifndef __CubicVR2__aabb__
+#define __CubicVR2__aabb__
+
+#include <iostream>
+#include "vec3.h"
+
+namespace CubicVR {
+
+    enum aabb_enum { AABB_DISJOINT, AABB_A_INSIDE_B, AABB_B_INSIDE_A, AABB_INTERSECT };
+
+    struct aabb {
+        vec3 min, max;
+        
+        aabb(vec3 min_in, vec3 max_in) {
+            min=min_in;
+            max=max_in;
+        }
+
+        aabb() {
+            min=max=vec3(0,0,0);
+        }
+
+        aabb engulf(aabb aabb, vec3 point) {
+            if (aabb.min[0] > point[0]) {
+                aabb.min[0] = point[0];
+            }
+            if (aabb.min[1] > point[1]) {
+                aabb.min[1] = point[1];
+            }
+            if (aabb.min[2] > point[2]) {
+                aabb.min[2] = point[2];
+            }
+            if (aabb.max[0] < point[0]) {
+                aabb.max[0] = point[0];
+            }
+            if (aabb.max[1] < point[1]) {
+                aabb.max[1] = point[1];
+            }
+            if (aabb.max[2] < point[2]) {
+                aabb.max[2] = point[2];
+            }
+            return aabb;
+        };
+        
+        static aabb reset(aabb aabb, vec3 point=vec3(0.0f,0.0f,0.0f)) {
+            
+            aabb.min[0] = point[0];
+            aabb.min[1] = point[1];
+            aabb.min[2] = point[2];
+            aabb.max[0] = point[0];
+            aabb.max[1] = point[1];
+            aabb.max[2] = point[2];
+            
+            return aabb;
+        };
+        
+        static vec3 size(aabb aabb) {
+            __float x = aabb.min[0] < aabb.max[0] ? aabb.max[0] - aabb.min[0] : aabb.min[0] - aabb.max[0];
+            __float y = aabb.min[1] < aabb.max[1] ? aabb.max[1] - aabb.min[1] : aabb.min[1] - aabb.max[1];
+            __float z = aabb.min[2] < aabb.max[2] ? aabb.max[2] - aabb.min[2] : aabb.min[2] - aabb.max[2];
+            return vec3(x,y,z);
+        };
+        /**
+         Returns positive integer if intersect between A and B, 0 otherwise.
+         For more detailed intersect result check value:
+         CubicVR.enums.aabb.INTERSECT if AABBs intersect
+         CubicVR.enums.aabb.A_INSIDE_B if boxA is inside boxB
+         CubicVR.enums.aabb.B_INSIDE_A if boxB is inside boxA
+         CubicVR.enums.aabb.DISJOINT if AABBs are disjoint (do not intersect)
+         */
+        aabb_enum intersects(aabb boxA, aabb boxB) {
+            // Disjoint
+            if( boxA.min[0] > boxB.max[0] || boxA.max[0] < boxB.min[0] ){
+                return AABB_DISJOINT;
+            }
+            if( boxA.min[1] > boxB.max[1] || boxA.max[1] < boxB.min[1] ){
+                return AABB_DISJOINT;
+            }
+            if( boxA.min[2] > boxB.max[2] || boxA.max[2] < boxB.min[2] ){
+                return AABB_DISJOINT;
+            }
+            
+            // boxA is inside boxB.
+            if( boxA.min[0] >= boxB.min[0] && boxA.max[0] <= boxB.max[0] &&
+               boxA.min[1] >= boxB.min[1] && boxA.max[1] <= boxB.max[1] &&
+               boxA.min[2] >= boxB.min[2] && boxA.max[2] <= boxB.max[2]) {
+                return AABB_A_INSIDE_B;
+            }
+            // boxB is inside boxA.
+            if( boxB.min[0] >= boxA.min[0] && boxB.max[0] <= boxA.max[0] &&
+               boxB.min[1] >= boxA.min[1] && boxB.max[1] <= boxA.max[1] &&
+               boxB.min[2] >= boxA.min[2] && boxB.max[2] <= boxA.max[2]) {
+                return AABB_B_INSIDE_A;
+            }
+            
+            // Otherwise AABB's intersect.
+            return AABB_INTERSECT;
+        }
+    };
+};
+
+#endif /* defined(__CubicVR2__aabb__) */
diff --git a/external/cubicvr2/math/cubic_math.cpp b/external/cubicvr2/math/cubic_math.cpp
new file mode 100644
index 0000000..45470ff
--- /dev/null
+++ b/external/cubicvr2/math/cubic_math.cpp
@@ -0,0 +1,27 @@
+//
+//  math.cpp
+//  CubicVR2
+//
+//  Created by Charles J. Cliffe on 2013-02-22.
+//  Copyright (c) 2013 Charles J. Cliffe. All rights reserved.
+//
+
+#include "cubic_math.h"
+
+namespace CubicVR {
+    std::ostream& operator<<(std::ostream &strm, const vec4 &v) {
+        return strm << "{" << v.x << ", " << v.y << ", " << v.z << ", " << v.w << "}";
+    }
+    std::ostream& operator<<(std::ostream &strm, const vec3 &v) {
+        return strm << "{" << v.x << ", " << v.y << ", " << v.z << "}";
+    }
+    std::ostream& operator<<(std::ostream &strm, const vec2 &v) {
+        return strm << "{" << v.x << ", " << v.y << "}";
+    }
+    std::ostream& operator<<(std::ostream &strm, const mat4 &m) {
+        return strm << "{ " << m[0] << ", " << m[1] << ", " << m[2] << ", " << m[3] << endl
+        << "  " << m[4] << ", " << m[5] << ", " << m[6] << ", " << m[7] << endl
+        << "  " << m[8] << ", " << m[9] << ", " << m[10] << ", " << m[11] << endl
+        << "  " << m[12] << ", " << m[13] << ", " << m[14] << ", " << m[15] << " }" << endl;
+    }
+}
diff --git a/external/cubicvr2/math/cubic_math.h b/external/cubicvr2/math/cubic_math.h
new file mode 100644
index 0000000..7f74264
--- /dev/null
+++ b/external/cubicvr2/math/cubic_math.h
@@ -0,0 +1,35 @@
+//
+//  math.h
+//  CubicVR2
+//
+//  Created by Charles J. Cliffe on 2013-02-22.
+//  Copyright (c) 2013 Charles J. Cliffe. All rights reserved.
+//
+
+#ifndef __CubicVR2__math__
+#define __CubicVR2__math__
+
+#include <iostream>
+
+#include "aabb.h"
+#include "mat3.h"
+#include "mat4.h"
+#include "quaternion.h"
+#include "transform.h"
+#include "triangle.h"
+#include "vec2.h"
+#include "vec3.h"
+#include "vec4.h"
+#include "plane.h"
+#include "sphere.h"
+#include "frustum.h"
+
+namespace CubicVR {
+    std::ostream& operator<<(std::ostream &strm, const vec4 &v);
+    std::ostream& operator<<(std::ostream &strm, const vec3 &v);
+    std::ostream& operator<<(std::ostream &strm, const vec2 &v);
+    std::ostream& operator<<(std::ostream &strm, const mat4 &m);
+}
+
+
+#endif /* defined(__CubicVR2__math__) */
diff --git a/external/cubicvr2/math/cubic_types.h b/external/cubicvr2/math/cubic_types.h
new file mode 100644
index 0000000..060fc6f
--- /dev/null
+++ b/external/cubicvr2/math/cubic_types.h
@@ -0,0 +1,46 @@
+//
+//  types.h
+//  CubicVR2
+//
+//  Created by Charles J. Cliffe on 2013-02-21.
+//  Copyright (c) 2013 Charles J. Cliffe. All rights reserved.
+//
+
+#ifndef CubicVR2_types_h
+#define CubicVR2_types_h
+
+namespace CubicVR {
+    
+    typedef double __float64;
+    typedef float __float32;
+    
+    typedef __float32 __float;
+    
+    #define COMBINE(x,y) x ## y
+    #define floatSG(c, x,y) \
+        __float COMBINE(get,x)() { return y; } \
+        c & COMBINE(set,x)(__float value) { y = value; return *this; }
+    #define intSG(c, x,y) \
+        int COMBINE(get,x)() { return y; } \
+        c & COMBINE(set,x)(int value) { y = value; return *this; }
+    #define uintSG(c, x,y) \
+        unsigned int COMBINE(get,x)() { return y; } \
+        c & COMBINE(set,x)(unsigned int value) { y = value; return *this; }
+    #define boolSG(c,x,y) \
+        bool COMBINE(get,x)() { return y; } \
+        c & COMBINE(set,x)(bool value) { y = value; return *this; }
+    #define stringSG(c,x,y) \
+        string COMBINE(get,x)() { return y; } \
+        c & COMBINE(set,x)(string value) { y = value; return *this; }
+    #define isBoolSG(c,x,y) \
+        bool COMBINE(is,x)() { return y; } \
+        c & COMBINE(set,x)(bool value) { y = value; return *this; }
+
+}
+
+#include <cmath>
+#ifndef M_PI
+    #define M_PI 3.14159265358979323846
+#endif
+
+#endif
diff --git a/external/cubicvr2/math/frustum.h b/external/cubicvr2/math/frustum.h
new file mode 100644
index 0000000..2d983fe
--- /dev/null
+++ b/external/cubicvr2/math/frustum.h
@@ -0,0 +1,167 @@
+//
+//  frustum.h
+//  CubicVR2
+//
+//  Created by Charles J. Cliffe on 2013-02-22.
+//  Copyright (c) 2013 Charles J. Cliffe. All rights reserved.
+//
+
+#ifndef CubicVR2_frustum_h
+#define CubicVR2_frustum_h
+
+#include <vector>
+#include "cubic_types.h"
+#include "mat4.h"
+#include "vec3.h"
+#include "vec4.h"
+#include "plane.h"
+
+namespace CubicVR {
+    
+    enum frustum_plane { PLANE_LEFT, PLANE_RIGHT, PLANE_TOP, PLANE_BOTTOM, PLANE_NEAR, PLANE_FAR };
+    
+    struct frustum {
+        std::vector<vec4> planes;
+        vec4 sphere;
+        
+        frustum() {
+            planes.resize(6);
+            for (int i = 0; i < 6; ++i) {
+                planes[i] = vec4(0, 0, 0, 0);
+            } //for
+        } //Frustum::Constructor
+        
+        void extract(vec3 position, mat4 mvMatrix, mat4 pMatrix) {
+            mat4 comboMatrix = mat4::multiply(pMatrix, mvMatrix, true);
+            
+            // Left clipping plane
+            planes[PLANE_LEFT][0] = comboMatrix[3] + comboMatrix[0];
+            planes[PLANE_LEFT][1] = comboMatrix[7] + comboMatrix[4];
+            planes[PLANE_LEFT][2] = comboMatrix[11] + comboMatrix[8];
+            planes[PLANE_LEFT][3] = comboMatrix[15] + comboMatrix[12];
+            
+            // Right clipping plane
+            planes[PLANE_RIGHT][0] = comboMatrix[3] - comboMatrix[0];
+            planes[PLANE_RIGHT][1] = comboMatrix[7] - comboMatrix[4];
+            planes[PLANE_RIGHT][2] = comboMatrix[11] - comboMatrix[8];
+            planes[PLANE_RIGHT][3] = comboMatrix[15] - comboMatrix[12];
+            
+            // Top clipping plane
+            planes[PLANE_TOP][0] = comboMatrix[3] - comboMatrix[1];
+            planes[PLANE_TOP][1] = comboMatrix[7] - comboMatrix[5];
+            planes[PLANE_TOP][2] = comboMatrix[11] - comboMatrix[9];
+            planes[PLANE_TOP][3] = comboMatrix[15] - comboMatrix[13];
+            
+            // Bottom clipping plane
+            planes[PLANE_BOTTOM][0] = comboMatrix[3] + comboMatrix[1];
+            planes[PLANE_BOTTOM][1] = comboMatrix[7] + comboMatrix[5];
+            planes[PLANE_BOTTOM][2] = comboMatrix[11] + comboMatrix[9];
+            planes[PLANE_BOTTOM][3] = comboMatrix[15] + comboMatrix[13];
+            
+            // Near clipping plane
+            planes[PLANE_NEAR][0] = comboMatrix[3] + comboMatrix[2];
+            planes[PLANE_NEAR][1] = comboMatrix[7] + comboMatrix[6];
+            planes[PLANE_NEAR][2] = comboMatrix[11] + comboMatrix[10];
+            planes[PLANE_NEAR][3] = comboMatrix[15] + comboMatrix[14];
+            
+            // Far clipping plane
+            planes[PLANE_FAR][0] = comboMatrix[3] - comboMatrix[2];
+            planes[PLANE_FAR][1] = comboMatrix[7] - comboMatrix[6];
+            planes[PLANE_FAR][2] = comboMatrix[11] - comboMatrix[10];
+            planes[PLANE_FAR][3] = comboMatrix[15] - comboMatrix[14];
+            
+            for (unsigned int i = 0; i < 6; ++i) {
+                planes[i] = vec4::normalize(planes[i]);
+            }
+            
+            //Sphere
+            __float fov = 1 / pMatrix[5];
+            __float znear = -planes[PLANE_NEAR][3];
+            __float zfar = planes[PLANE_FAR][3];
+            __float view_length = zfar - znear;
+            __float height = view_length * fov;
+            __float width = height;
+            
+            vec3 P(0, 0, znear + view_length * 0.5f);
+            vec3 Q(width, height, znear + view_length);
+            vec3 diff = vec3::subtract(P, Q);
+            __float diff_mag = vec3::length(diff);
+            
+            vec3 look_v = vec3(comboMatrix[3], comboMatrix[9], comboMatrix[10]);
+            __float look_mag = vec3::length(look_v);
+            look_v = vec3::multiply(look_v, 1 / look_mag);
+            
+            vec3 pos = vec3(position[0], position[1], position[2]);
+            pos = vec3::add(pos, vec3::multiply(look_v, view_length * 0.5f));
+            pos = vec3::add(pos, vec3::multiply(look_v, 1));
+            
+            sphere = vec4(pos[0], pos[1], pos[2], diff_mag);
+            
+        }; //Frustum::extract
+        
+        int contains_sphere(vec4 sphere) {
+            
+            for (unsigned int i = 0; i < 6; ++i) {
+                vec4 &p = planes[i];
+                vec3 normal = vec3(p[0], p[1], p[2]);
+                __float distance = vec3::dot(normal, vec3(sphere[0],sphere[1],sphere[2])) + p[3];
+                
+                //OUT
+                if (distance < -sphere[3]) {
+                    return -1;
+                }
+                
+                //INTERSECT
+                if (fabs(distance) < sphere[3]) {
+                    return 0;
+                }
+                
+            } //for
+            //IN
+            return 1;
+        }; //Frustum::contains_sphere
+        
+        int contains_box(aabb bbox) {
+            int total_in = 0;
+            
+            vec3 points[8];
+            
+            points[0] = bbox.min;
+            points[1] = vec3(bbox.min[0], bbox.min[1], bbox.max[2]);
+            points[2] = vec3(bbox.min[0], bbox.max[1], bbox.min[2]);
+            points[3] = vec3(bbox.min[0], bbox.max[1], bbox.max[2]);
+            points[4] = vec3(bbox.max[0], bbox.min[1], bbox.min[2]);
+            points[5] = vec3(bbox.max[0], bbox.min[1], bbox.max[2]);
+            points[6] = vec3(bbox.max[0], bbox.max[1], bbox.min[2]);
+            points[7] = bbox.max;
+            
+            for (unsigned int i = 0; i < 6; ++i) {
+                unsigned int in_count = 8;
+                unsigned int point_in = 1;
+                
+                for (unsigned int j = 0; j < 8; ++j) {
+                    if (plane::classifyPoint(planes[i], points[j]) == -1) {
+                        point_in = 0;
+                        --in_count;
+                    } //if
+                } //for j
+                
+                //OUT
+                if (in_count == 0) {
+                    return -1;
+                }
+                
+                total_in += point_in;
+            } //for i
+            //IN
+            if (total_in == 6) {
+                return 1;
+            }
+            
+            return 0;
+        }; //Frustum::contains_box
+        
+    };
+}
+
+#endif
diff --git a/external/cubicvr2/math/mat3.h b/external/cubicvr2/math/mat3.h
new file mode 100644
index 0000000..8f5ab22
--- /dev/null
+++ b/external/cubicvr2/math/mat3.h
@@ -0,0 +1,86 @@
+//
+//  mat3.h
+//  CubicVR2
+//
+//  Created by Charles J. Cliffe on 2013-02-22.
+//  Copyright (c) 2013 Charles J. Cliffe. All rights reserved.
+//
+
+#ifndef __CubicVR2__mat3__
+#define __CubicVR2__mat3__
+
+#include <iostream>
+#include "vec3.h"
+#include <cstring>
+
+namespace CubicVR {
+
+    #define mat3SG(c,x,y) \
+        mat3 COMBINE(get,x)() { return y; } \
+        c & COMBINE(set,x)(mat3 value) { y = value; return *this; }
+
+    struct mat3 {
+        __float a,b,c,d,e,f,g,h,i;
+
+        //        __float  operator [] (unsigned i) const { return ((__float *)this)[i]; }
+#ifndef _WIN32
+        __float& operator [] (unsigned i)       { return ((__float *)this)[i]; }
+#endif
+        operator __float*() const { return (__float *)this; }
+        
+        mat3(__float ai,__float bi,__float ci,__float di,__float ei,__float fi,__float gi,__float hi,__float ii) {
+            a = ai; b = bi; c = ci; d = di; e = ei; f = fi; g = gi; h = hi; i = ii;
+        };
+        
+        mat3() { memset((__float *)this, 0, sizeof(mat3)); }
+        //        mat3 operator* (mat4 m) { return mat3::multiply(*this,m); };
+        //        void operator*= (mat4 m) { *this = mat3::multiply(*this,m); };
+ 
+        
+        static mat3 identity() {
+            return mat3(1.0f, 0.0f, 0.0f,
+                        0.0f, 1.0f, 0.0f,
+                        0.0f, 0.0f, 1.0f);
+        }
+
+        static void transpose_inline(mat3 &mat) {
+            __float a01 = mat[1], a02 = mat[2], a12 = mat[5];
+            
+            mat[1] = mat[3];
+            mat[2] = mat[6];
+            mat[3] = a01;
+            mat[5] = mat[7];
+            mat[6] = a02;
+            mat[7] = a12;
+        };
+        
+        static mat3 transpose(mat3 mat_in) {
+            __float a01 = mat_in[1], a02 = mat_in[2], a12 = mat_in[5];
+            
+            mat3 mat;
+            
+            mat[1] = mat_in[3];
+            mat[2] = mat_in[6];
+            mat[3] = a01;
+            mat[5] = mat_in[7];
+            mat[6] = a02;
+            mat[7] = a12;
+            
+            return mat;
+        };
+        
+        static vec3 multiply(mat3 m1, vec3 m2) {
+            vec3 mOut;
+            
+            mOut[0] = m2[0] * m1[0] + m2[3] * m1[1] + m2[6] * m1[2] ;
+            mOut[1] = m2[1] * m1[0] + m2[4] * m1[1] + m2[7] * m1[2] ;
+            mOut[2] = m2[2] * m1[0] + m2[5] * m1[1] + m2[8] * m1[2];
+            
+            return mOut;
+        };
+    };
+    
+    
+}
+
+#endif /* defined(__CubicVR2__mat3__) */
diff --git a/external/cubicvr2/math/mat4.h b/external/cubicvr2/math/mat4.h
new file mode 100644
index 0000000..2007535
--- /dev/null
+++ b/external/cubicvr2/math/mat4.h
@@ -0,0 +1,328 @@
+//
+//  mat4.h
+//  CubicVR2
+//
+//  Created by Charles J. Cliffe on 2013-02-21.
+//  Copyright (c) 2013 Charles J. Cliffe. All rights reserved.
+//
+
+#ifndef __CubicVR2__mat4__
+#define __CubicVR2__mat4__
+
+#include <iostream>
+#include "cubic_types.h"
+#include "vec3.h"
+#include "vec4.h"
+#include "mat3.h"
+#include <cmath>
+
+namespace CubicVR {
+    using namespace std;
+    #define mat4SG(c,x,y) \
+        mat4 COMBINE(get,x)() { return y; } \
+        c & COMBINE(set,x)(mat4 value) { y = value; return *this; }
+    struct mat4 {
+        __float a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p;
+
+        //        __float  operator [] (unsigned i) const { return ((__float *)this)[i]; }
+#ifndef _WIN32
+        __float& operator [] (unsigned i)       { return ((__float *)this)[i]; }
+#endif
+
+        operator __float*() const { return (__float *)this; }
+        mat4(__float ai,__float bi,__float ci,__float di,__float ei,__float fi,__float gi,__float hi,__float ii,__float ji,__float ki,__float li,__float mi,__float ni,__float oi,__float pi) {
+            a = ai; b = bi; c = ci; d = di; e = ei; f = fi; g = gi; h = hi; i = ii; j = ji; k = ki; l = li; m = mi; n = ni; o = oi; p = pi;
+        }
+        mat4() { memset(this,0,sizeof(mat4)); }
+        mat4 operator* (mat4 m) { return mat4::multiply(*this, m, true); };
+        void operator*= (mat4 m) { *this = mat4::multiply(*this, m, true); };
+//        mat4 &operator= (const mat4 &m) { memcpy(this,(__float *)m,sizeof(__float)*16); return *this; };
+        
+        static mat4 identity() {
+            return mat4(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f);
+        }
+        
+        static mat4 multiply(mat4 mLeft, mat4 mRight, bool /* updated */) {
+            mat4 mOut;
+            
+            mOut[0] = mLeft[0] * mRight[0] + mLeft[4] * mRight[1] + mLeft[8] * mRight[2] + mLeft[12] * mRight[3];
+            mOut[1] = mLeft[1] * mRight[0] + mLeft[5] * mRight[1] + mLeft[9] * mRight[2] + mLeft[13] * mRight[3];
+            mOut[2] = mLeft[2] * mRight[0] + mLeft[6] * mRight[1] + mLeft[10] * mRight[2] + mLeft[14] * mRight[3];
+            mOut[3] = mLeft[3] * mRight[0] + mLeft[7] * mRight[1] + mLeft[11] * mRight[2] + mLeft[15] * mRight[3];
+            mOut[4] = mLeft[0] * mRight[4] + mLeft[4] * mRight[5] + mLeft[8] * mRight[6] + mLeft[12] * mRight[7];
+            mOut[5] = mLeft[1] * mRight[4] + mLeft[5] * mRight[5] + mLeft[9] * mRight[6] + mLeft[13] * mRight[7];
+            mOut[6] = mLeft[2] * mRight[4] + mLeft[6] * mRight[5] + mLeft[10] * mRight[6] + mLeft[14] * mRight[7];
+            mOut[7] = mLeft[3] * mRight[4] + mLeft[7] * mRight[5] + mLeft[11] * mRight[6] + mLeft[15] * mRight[7];
+            mOut[8] = mLeft[0] * mRight[8] + mLeft[4] * mRight[9] + mLeft[8] * mRight[10] + mLeft[12] * mRight[11];
+            mOut[9] = mLeft[1] * mRight[8] + mLeft[5] * mRight[9] + mLeft[9] * mRight[10] + mLeft[13] * mRight[11];
+            mOut[10] = mLeft[2] * mRight[8] + mLeft[6] * mRight[9] + mLeft[10] * mRight[10] + mLeft[14] * mRight[11];
+            mOut[11] = mLeft[3] * mRight[8] + mLeft[7] * mRight[9] + mLeft[11] * mRight[10] + mLeft[15] * mRight[11];
+            mOut[12] = mLeft[0] * mRight[12] + mLeft[4] * mRight[13] + mLeft[8] * mRight[14] + mLeft[12] * mRight[15];
+            mOut[13] = mLeft[1] * mRight[12] + mLeft[5] * mRight[13] + mLeft[9] * mRight[14] + mLeft[13] * mRight[15];
+            mOut[14] = mLeft[2] * mRight[12] + mLeft[6] * mRight[13] + mLeft[10] * mRight[14] + mLeft[14] * mRight[15];
+            mOut[15] = mLeft[3] * mRight[12] + mLeft[7] * mRight[13] + mLeft[11] * mRight[14] + mLeft[15] * mRight[15];
+            
+            return mOut;
+        };
+        
+        static vec3 multiply(mat4 m1, vec3 m2, bool /* updated */) {
+            vec3 mOut;
+            
+            mOut[0] = m1[0] * m2[0] + m1[4] * m2[1] + m1[8] * m2[2] + m1[12];
+            mOut[1] = m1[1] * m2[0] + m1[5] * m2[1] + m1[9] * m2[2] + m1[13];
+            mOut[2] = m1[2] * m2[0] + m1[6] * m2[1] + m1[10] * m2[2] + m1[14];
+            
+            return mOut;
+        }
+        static mat4 frustum(__float left, __float right, __float bottom, __float top, __float zNear, __float zFar) {
+            __float A = (right + left) / (right - left);
+            __float B = (top + bottom) / (top - bottom);
+            __float C = - (zFar + zNear) / (zFar - zNear);
+            __float D = - (-2.0f * zFar * zNear) / (zFar - zNear);
+            
+            
+            return mat4((2.0f * zNear) / (right - left), 0, A, 0,
+                        0, (2.0f * zNear) / (top - bottom), B, 0,
+                        0, 0, C, D,
+                        0, 0, -1, 0);
+        };
+        static mat4 perspective(__float fovy, __float aspect, __float zNear, __float zFar) {
+            __float yFac = tan(fovy * (float)M_PI / 360.0f);
+            __float xFac = yFac * aspect;
+            
+            return mat4::frustum(-xFac, xFac, -yFac, yFac, zNear, zFar);
+
+        };
+        static mat4 ortho(__float left,__float right,__float bottom,__float top,__float znear,__float zfar) {
+            return mat4(2.0f / (right - left), 0, 0, 0, 0, 2.0f / (top - bottom), 0, 0, 0, 0, -2.0f / (zfar - znear), 0, -(left + right) / (right - left), -(top + bottom) / (top - bottom), -(zfar + znear) / (zfar - znear), 1);
+        };
+        static __float determinant(mat4 m) {
+            
+            __float a0 = m[0] * m[5] - m[1] * m[4];
+            __float a1 = m[0] * m[6] - m[2] * m[4];
+            __float a2 = m[0] * m[7] - m[3] * m[4];
+            __float a3 = m[1] * m[6] - m[2] * m[5];
+            __float a4 = m[1] * m[7] - m[3] * m[5];
+            __float a5 = m[2] * m[7] - m[3] * m[6];
+            __float b0 = m[8] * m[13] - m[9] * m[12];
+            __float b1 = m[8] * m[14] - m[10] * m[12];
+            __float b2 = m[8] * m[15] - m[11] * m[12];
+            __float b3 = m[9] * m[14] - m[10] * m[13];
+            __float b4 = m[9] * m[15] - m[11] * m[13];
+            __float b5 = m[10] * m[15] - m[11] * m[14];
+            
+            __float det = a0 * b5 - a1 * b4 + a2 * b3 + a3 * b2 - a4 * b1 + a5 * b0;
+            
+            return det;
+        };
+        //        coFactor: function (m, n, out) {
+        //            // .. todo..
+        //        },
+        
+        static mat4 transpose(mat4 m) {
+            return mat4(m[0], m[4], m[8], m[12], m[1], m[5], m[9], m[13], m[2], m[6], m[10], m[14], m[3], m[7], m[11], m[15]);
+        };
+        
+        static mat3 inverse_mat3(mat4 mat) {
+            mat3 dest;
+
+            __float a00 = mat[0], a01 = mat[1], a02 = mat[2],
+            a10 = mat[4], a11 = mat[5], a12 = mat[6],
+            a20 = mat[8], a21 = mat[9], a22 = mat[10];
+
+            __float b01 = a22*a11-a12*a21,
+            b11 = -a22*a10+a12*a20,
+            b21 = a21*a10-a11*a20;
+
+            __float d = a00*b01 + a01*b11 + a02*b21;
+            if (!d) { return dest; }
+            __float id = 1/d;
+
+            dest[0] = b01*id;
+            dest[1] = (-a22*a01 + a02*a21)*id;
+            dest[2] = (a12*a01 - a02*a11)*id;
+            dest[3] = b11*id;
+            dest[4] = (a22*a00 - a02*a20)*id;
+            dest[5] = (-a12*a00 + a02*a10)*id;
+            dest[6] = b21*id;
+            dest[7] = (-a21*a00 + a01*a20)*id;
+            dest[8] = (a11*a00 - a01*a10)*id;
+
+            return dest;
+        };
+        
+        static mat4 inverse(mat4 m) {
+            mat4 m_inv;
+            
+            __float a0 = m[0] * m[5] - m[1] * m[4];
+            __float a1 = m[0] * m[6] - m[2] * m[4];
+            __float a2 = m[0] * m[7] - m[3] * m[4];
+            __float a3 = m[1] * m[6] - m[2] * m[5];
+            __float a4 = m[1] * m[7] - m[3] * m[5];
+            __float a5 = m[2] * m[7] - m[3] * m[6];
+            __float b0 = m[8] * m[13] - m[9] * m[12];
+            __float b1 = m[8] * m[14] - m[10] * m[12];
+            __float b2 = m[8] * m[15] - m[11] * m[12];
+            __float b3 = m[9] * m[14] - m[10] * m[13];
+            __float b4 = m[9] * m[15] - m[11] * m[13];
+            __float b5 = m[10] * m[15] - m[11] * m[14];
+            
+            __float determinant = a0 * b5 - a1 * b4 + a2 * b3 + a3 * b2 - a4 * b1 + a5 * b0;
+            
+            if (determinant != 0) {
+                m_inv[0] = 0 + m[5] * b5 - m[6] * b4 + m[7] * b3;
+                m_inv[4] = 0 - m[4] * b5 + m[6] * b2 - m[7] * b1;
+                m_inv[8] = 0 + m[4] * b4 - m[5] * b2 + m[7] * b0;
+                m_inv[12] = 0 - m[4] * b3 + m[5] * b1 - m[6] * b0;
+                m_inv[1] = 0 - m[1] * b5 + m[2] * b4 - m[3] * b3;
+                m_inv[5] = 0 + m[0] * b5 - m[2] * b2 + m[3] * b1;
+                m_inv[9] = 0 - m[0] * b4 + m[1] * b2 - m[3] * b0;
+                m_inv[13] = 0 + m[0] * b3 - m[1] * b1 + m[2] * b0;
+                m_inv[2] = 0 + m[13] * a5 - m[14] * a4 + m[15] * a3;
+                m_inv[6] = 0 - m[12] * a5 + m[14] * a2 - m[15] * a1;
+                m_inv[10] = 0 + m[12] * a4 - m[13] * a2 + m[15] * a0;
+                m_inv[14] = 0 - m[12] * a3 + m[13] * a1 - m[14] * a0;
+                m_inv[3] = 0 - m[9] * a5 + m[10] * a4 - m[11] * a3;
+                m_inv[7] = 0 + m[8] * a5 - m[10] * a2 + m[11] * a1;
+                m_inv[11] = 0 - m[8] * a4 + m[9] * a2 - m[11] * a0;
+                m_inv[15] = 0 + m[8] * a3 - m[9] * a1 + m[10] * a0;
+                
+                __float inverse_det = 1.0f / determinant;
+                
+                m_inv[0] *= inverse_det;
+                m_inv[1] *= inverse_det;
+                m_inv[2] *= inverse_det;
+                m_inv[3] *= inverse_det;
+                m_inv[4] *= inverse_det;
+                m_inv[5] *= inverse_det;
+                m_inv[6] *= inverse_det;
+                m_inv[7] *= inverse_det;
+                m_inv[8] *= inverse_det;
+                m_inv[9] *= inverse_det;
+                m_inv[10] *= inverse_det;
+                m_inv[11] *= inverse_det;
+                m_inv[12] *= inverse_det;
+                m_inv[13] *= inverse_det;
+                m_inv[14] *= inverse_det;
+                m_inv[15] *= inverse_det;
+                
+                return m_inv;
+            }
+            
+            return mat4::identity();
+        };
+        
+        static mat4 translate(__float x, __float y, __float z) {
+            mat4 m = mat4(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, x,   y,   z, 1.0f);
+            
+            return m;
+        };
+        
+        static mat4 rotateAxis(__float r, __float x, __float y, __float z) {   // rotate r about axis x,y,z
+            __float sAng = sinf(r*((float)M_PI/180.0f));
+            __float cAng = cosf(r*((float)M_PI/180.0f));
+            
+            return mat4( cAng+(x*x)*(1.0f-cAng), x*y*(1.0f-cAng) - z*sAng, x*z*(1.0f-cAng) + y*sAng, 0,
+                y*x*(1.0f-cAng)+z*sAng, cAng + y*y*(1.0f-cAng), y*z*(1.0f-cAng)-x*sAng, 0,
+                z*x*(1.0f-cAng)-y*sAng, z*y*(1.0f-cAng)+x*sAng, cAng+(z*z)*(1.0f-cAng), 0,
+                0, 0, 0, 1 );
+        };
+        
+        static mat4 rotate(__float x, __float y, __float z) {   // rotate each axis, angles x, y, z in turn
+            __float  sAng,cAng;
+            mat4 mOut = mat4(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f);
+            
+            if (z!=0) {
+                sAng = sinf(z*((float)M_PI/180.0f));
+                cAng = cosf(z*((float)M_PI/180.0f));
+                
+                mOut *= mat4(cAng, sAng, 0.0f, 0.0f, -sAng, cAng, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f);
+            }
+            
+            if (y!=0) {
+                sAng = sinf(y*((float)M_PI/180.0f));
+                cAng = cosf(y*((float)M_PI/180.0f));
+                
+                mOut *= mat4(cAng, 0.0f, -sAng, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, sAng, 0.0f, cAng, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f);
+            }
+            
+            if (x!=0) {
+                sAng = sinf(x*((float)M_PI/180.0f));
+                cAng = cosf(x*((float)M_PI/180.0f));
+                
+                mOut *= mat4(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, cAng, sAng, 0.0f, 0.0f, -sAng, cAng, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f);
+            }
+            
+            return mOut;
+        };
+        
+        static mat4 scale(__float x, __float y, __float z) {
+            return mat4(x, 0.0f, 0.0f, 0.0f, 0.0f, y, 0.0f, 0.0f, 0.0f, 0.0f, z, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f);
+        };
+        
+        static mat4 transform(vec3 position, vec3 rotation, vec3 scale) {
+            mat4 m = mat4::identity();
+            
+            if (position!=NULL) {
+                m *= mat4::translate(position[0],position[1],position[2]);
+            }
+            if (rotation!=NULL) {
+                if (!(rotation[0] == 0 && rotation[1] == 0 && rotation[2] == 0)) {
+                    m *= mat4::rotate(rotation[0],rotation[1],rotation[2]);
+                }
+            }
+            if (scale!=NULL) {
+                if (!(scale[0] == 1 && scale[1] == 1 && scale[2] == 1)) {
+                    m *= mat4::scale(scale[0],scale[1],scale[2]);
+                }
+            }
+            
+            return m;
+        };
+        static vec4 vec4_multiply(vec4 m1, mat4 m2) {
+            vec4 mOut;
+
+            mOut[0] = m2[0] * m1[0] + m2[4] * m1[1] + m2[8] * m1[2] + m2[12] * m1[3];
+            mOut[1] = m2[1] * m1[0] + m2[5] * m1[1] + m2[9] * m1[2] + m2[13] * m1[3];
+            mOut[2] = m2[2] * m1[0] + m2[6] * m1[1] + m2[10] * m1[2] + m2[14] * m1[3];
+            mOut[3] = m2[3] * m1[0] + m2[7] * m1[1] + m2[11] * m1[2] + m2[15] * m1[3];
+
+            return mOut;
+        };
+        static mat4 lookat(__float eyex, __float eyey, __float eyez, __float centerx, __float centery, __float centerz, __float upx, __float upy, __float upz) {
+            vec3 forward, side, up;
+            
+            forward[0] = centerx - eyex;
+            forward[1] = centery - eyey;
+            forward[2] = centerz - eyez;
+            
+            up[0] = upx;
+            up[1] = upy;
+            up[2] = upz;
+            
+            forward = vec3::normalize(forward);
+            
+            /* Side = forward x up */
+            side = vec3::cross(forward, up);
+            side = vec3::normalize(side);
+            
+            /* Recompute up as: up = side x forward */
+            up = vec3::cross(side, forward);
+            
+            return mat4::translate(-eyex,-eyey,-eyez) * mat4( side[0], up[0], -forward[0], 0, side[1], up[1], -forward[1], 0, side[2], up[2], -forward[2], 0, 0, 0, 0, 1);
+        };
+        
+        static vec3 unProject(mat4 pMatrix, mat4 mvMatrix, float width, float height, float winx, float winy, float /* winz */) {
+            vec4 p(((winx / width) * 2.0f) - 1.0, -(((winy / height) * 2.0f) - 1.0), 1.0, 1.0);
+            
+            vec4 invp = mat4::vec4_multiply(mat4::vec4_multiply(p, mat4::inverse(pMatrix)), mat4::inverse(mvMatrix));
+            
+            vec3 result(invp[0] / invp[3], invp[1] / invp[3], invp[2] / invp[3]);
+            
+            return result;
+        };
+    };
+  }
+
+
+#endif /* defined(__CubicVR2__mat4__) */
diff --git a/external/cubicvr2/math/plane.h b/external/cubicvr2/math/plane.h
new file mode 100644
index 0000000..4db2c2c
--- /dev/null
+++ b/external/cubicvr2/math/plane.h
@@ -0,0 +1,32 @@
+//
+//  plane.h
+//  CubicVR2
+//
+//  Created by Charles J. Cliffe on 2013-02-22.
+//  Copyright (c) 2013 Charles J. Cliffe. All rights reserved.
+//
+
+#ifndef CubicVR2_plane_h
+#define CubicVR2_plane_h
+
+#include "vec4.h"
+#include "vec3.h"
+
+namespace CubicVR {
+    
+    struct plane : vec4 {
+        static int classifyPoint(vec4 plane, vec3 pt) {
+            __float dist = (plane[0] * pt[0]) + (plane[1] * pt[1]) + (plane[2] * pt[2]) + (plane[3]);
+            if (dist < 0) {
+                return -1;
+            }
+            else if (dist > 0) {
+                return 1;
+            }
+            return 0;
+        };
+    };
+    
+}
+
+#endif
diff --git a/external/cubicvr2/math/quaternion.h b/external/cubicvr2/math/quaternion.h
new file mode 100644
index 0000000..41d33db
--- /dev/null
+++ b/external/cubicvr2/math/quaternion.h
@@ -0,0 +1,100 @@
+//
+//  quaternion.h
+//  CubicVR2
+//
+//  Created by Charles J. Cliffe on 2013-02-22.
+//  Copyright (c) 2013 Charles J. Cliffe. All rights reserved.
+//
+
+#ifndef __CubicVR2__quaternion__
+#define __CubicVR2__quaternion__
+
+#include <iostream>
+#include "vec4.h"
+#include "mat4.h"
+
+namespace CubicVR {
+    
+    struct quaternion : vec4 {
+        
+        static vec4 fromMatrix(mat4 mat) {
+            __float t = 1 + mat[0] + mat[5] + mat[10];
+            __float S,X,Y,Z,W;
+            
+            if ( t > 0.00000001 ) {
+                S = sqrtf(t) * 2;
+                X = ( mat[9] - mat[6] ) / S;
+                Y = ( mat[2] - mat[8] ) / S;
+                Z = ( mat[4] - mat[1] ) / S;
+                W = 0.25f * S;
+            } else {
+                if ( mat[0] > mat[5] && mat[0] > mat[10] )  {	// Column 0:
+                    S  = sqrtf( 1.0f + mat[0] - mat[5] - mat[10] ) * 2.0f;
+                    X = 0.25f * S;
+                    Y = (mat[4] + mat[1] ) / S;
+                    Z = (mat[2] + mat[8] ) / S;
+                    W = (mat[9] - mat[6] ) / S;
+                } else if ( mat[5] > mat[10] ) {			// Column 1:
+                    S  = sqrtf( 1.0f + mat[5] - mat[0] - mat[10] ) * 2.0f;
+                    X = (mat[4] + mat[1] ) / S;
+                    Y = 0.25f * S;
+                    Z = (mat[9] + mat[6] ) / S;
+                    W = (mat[2] - mat[8] ) / S;
+                } else {						// Column 2:
+                    S  = sqrtf( 1.0f + mat[10] - mat[0] - mat[5] ) * 2.0f;
+                    X = (mat[2] + mat[8] ) / S;
+                    Y = (mat[9] + mat[6] ) / S;
+                    Z = 0.25f * S;
+                    W = (mat[4] - mat[1] ) / S;
+                }
+            }
+            
+            return vec4(X,Y,Z,W);
+        };
+        
+        static vec4 fromEuler(__float bank, __float heading, __float pitch) { // x,y,z
+            __float c1 = cosf(((float)M_PI / 180.0f) * heading / 2.0f);
+            __float s1 = sinf(((float)M_PI / 180.0f) * heading / 2.0f);
+            __float c2 = cosf(((float)M_PI / 180.0f) * pitch / 2.0f);
+            __float s2 = sinf(((float)M_PI / 180.0f) * pitch / 2.0f);
+            __float c3 = cosf(((float)M_PI / 180.0f) * bank / 2.0f);
+            __float s3 = sinf(((float)M_PI / 180.0f) * bank / 2.0f);
+            __float c1c2 = c1 * c2;
+            __float s1s2 = s1 * s2;
+            
+            vec4 mOut;
+            
+            mOut[0] = c1c2 * c3 - s1s2 * s3;
+            mOut[1] = c1c2 * s3 + s1s2 * c3;
+            mOut[2] = s1 * c2 * c3 + c1 * s2 * s3;
+            mOut[3] = c1 * s2 * c3 - s1 * c2 * s3;
+            
+            return mOut;
+        };
+        
+        static vec3 toEuler(vec4 q) {
+            __float sqx = q[0] * q[0];
+            __float sqy = q[1] * q[1];
+            __float sqz = q[2] * q[2];
+            __float sqw = q[3] * q[3];
+            
+            __float x = (180.0f / (float)M_PI) * ((atan2f(2.0f * (q[1] * q[2] + q[0] * q[3]), (-sqx - sqy + sqz + sqw))));
+            __float y = (180.0f / (float)M_PI) * ((asinf(-2.0f * (q[0] * q[2] - q[1] * q[3]))));
+            __float z = (180.0f / (float)M_PI) * ((atan2f(2.0f * (q[0] * q[1] + q[2] * q[3]), (sqx - sqy - sqz + sqw))));
+            
+            return vec3(x, y, z);
+        };
+        
+        static vec4 multiply(vec4 q1, vec4 q2) {
+            __float x = q1[0] * q2[3] + q1[3] * q2[0] + q1[1] * q2[2] - q1[2] * q2[1];
+            __float y = q1[1] * q2[3] + q1[3] * q2[1] + q1[2] * q2[0] - q1[0] * q2[2];
+            __float z = q1[2] * q2[3] + q1[3] * q2[2] + q1[0] * q2[1] - q1[1] * q2[0];
+            __float w = q1[3] * q2[3] - q1[0] * q2[0] - q1[1] * q2[1] - q1[2] * q2[2];
+            
+            return vec4(x,y,z,w);
+        };
+        
+    };
+}
+
+#endif /* defined(__CubicVR2__quaternion__) */
diff --git a/external/cubicvr2/math/sphere.h b/external/cubicvr2/math/sphere.h
new file mode 100644
index 0000000..8e33530
--- /dev/null
+++ b/external/cubicvr2/math/sphere.h
@@ -0,0 +1,34 @@
+//
+//  sphere.h
+//  CubicVR2
+//
+//  Created by Charles J. Cliffe on 2013-02-22.
+//  Copyright (c) 2013 Charles J. Cliffe. All rights reserved.
+//
+
+#ifndef CubicVR2_sphere_h
+#define CubicVR2_sphere_h
+
+#include "vec3.h"
+#include "vec4.h"
+
+namespace CubicVR {
+    
+    struct sphere {
+        bool intersects(vec4 sphere, vec4 other) {
+            vec3 spherePos(sphere[0], sphere[1], sphere[2]);
+            vec3 otherPos(other[0], other[1], other[2]);
+            vec3 diff = vec3::subtract(spherePos, otherPos);
+            
+            __float mag = sqrtf(diff[0] * diff[0] + diff[1] * diff[1] + diff[2] * diff[2]);
+            __float sum_radii = sphere[3] + other[3];
+            
+            if (mag * mag < sum_radii * sum_radii) {
+                return true;
+            }
+            return false;
+        }
+    };
+}
+
+#endif
diff --git a/external/cubicvr2/math/transform.h b/external/cubicvr2/math/transform.h
new file mode 100644
index 0000000..bd6ddde
--- /dev/null
+++ b/external/cubicvr2/math/transform.h
@@ -0,0 +1,175 @@
+//
+//  Transform.h
+//  CubicVR2
+//
+//  Created by Charles J. Cliffe on 2013-02-22.
+//  Copyright (c) 2013 Charles J. Cliffe. All rights reserved.
+//
+
+#ifndef __CubicVR2__Transform__
+#define __CubicVR2__Transform__
+
+#include <iostream>
+#include "cubic_types.h"
+#include "mat4.h"
+#include "vec3.h"
+#include <vector>
+
+namespace CubicVR {
+    
+    class transform {
+        std::vector<mat4> m_stack;
+        std::vector<mat4> m_cache;
+        int c_stack;
+        int valid;
+        mat4 result;
+        
+        transform() {
+            c_stack = 0;
+            valid = false;
+            result = mat4::identity();
+        };
+        
+        transform(mat4 init_mat) {
+            clearStack(init_mat);
+        };
+        
+        void setIdentity() {
+            m_stack[c_stack] = mat4(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f);
+            if (valid == c_stack && c_stack) {
+                valid--;
+            }
+        }
+        
+        mat4 getIdentity() {
+            return mat4(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f);
+        }
+        
+        void invalidate() {
+            valid = 0;
+            result = mat4::identity();
+        }
+        
+        mat4 getResult() {
+            if (!c_stack) {
+                return m_stack[0];
+            }
+            
+            mat4 m = getIdentity();
+            
+            if (valid > c_stack-1) valid = c_stack-1;
+            
+            for (int i = valid; i < c_stack+1; i++) {
+                m = mat4::multiply(m, m_stack[i], true);
+                m_cache[i] = m;
+            }
+            
+            valid = c_stack-1;
+            
+            result = m_cache[c_stack];
+            
+            return result;
+        }
+        
+        void pushMatrix(mat4 m) {
+            c_stack++;
+            m_stack[c_stack] = (m ? m : getIdentity());
+        }
+        
+        void popMatrix() {
+            if (c_stack == 0) {
+                return;
+            }
+            c_stack--;
+        }
+        
+        void clearStack(mat4 init_mat) {
+            m_stack.clear();
+            m_cache.clear();
+            c_stack = 0;
+            valid = 0;
+            delete result;
+            result = mat4::identity();
+            
+            if (init_mat != NULL) {
+                m_stack[0] = init_mat;
+            } else {
+                setIdentity();
+            }
+        }
+        
+        void translate(__float x, __float y, __float z) {
+            mat4 m = getIdentity();
+            
+            m[12] = x;
+            m[13] = y;
+            m[14] = z;
+            
+            m_stack[c_stack] = mat4::multiply(m, m_stack[c_stack], true);
+            if (valid == c_stack && c_stack) {
+                valid--;
+            }
+        }
+        
+        void scale(__float x, __float y, __float z) {
+            mat4 m = getIdentity();
+            
+            m[0] = x;
+            m[5] = y;
+            m[10] = z;
+            
+            m_stack[c_stack] = mat4::multiply(m, m_stack[c_stack], true);
+            if (valid == c_stack && c_stack) {
+                valid--;
+            }
+        }
+        
+        void rotate(__float ang, __float x, __float y, __float z) {
+            __float sAng, cAng;
+            
+            if (x || y || z) {
+                sAng = sin(-ang * ((float)M_PI / 180.0f));
+                cAng = cos(-ang * ((float)M_PI / 180.0f));
+            }
+            
+            if (x) {
+                mat4 X_ROT = getIdentity();
+                
+                X_ROT[5] = cAng * x;
+                X_ROT[9] = sAng * x;
+                X_ROT[6] = -sAng * x;
+                X_ROT[10] = cAng * x;
+                
+                m_stack[c_stack] = mat4::multiply(m_stack[c_stack], X_ROT, true);
+            }
+            
+            if (y) {
+                mat4 Y_ROT = getIdentity();
+                
+                Y_ROT[0] = cAng * y;
+                Y_ROT[8] = -sAng * y;
+                Y_ROT[2] = sAng * y;
+                Y_ROT[10] = cAng * y;
+                
+                m_stack[c_stack] = mat4::multiply(m_stack[c_stack], Y_ROT, true);
+            }
+            
+            if (z) {
+                mat4 Z_ROT = getIdentity();
+                
+                Z_ROT[0] = cAng * z;
+                Z_ROT[4] = sAng * z;
+                Z_ROT[1] = -sAng * z;
+                Z_ROT[5] = cAng * z;
+                
+                m_stack[c_stack] = mat4::multiply(m_stack[c_stack], Z_ROT, true);
+            }
+            
+            if (valid == c_stack && c_stack) {
+                valid--;
+            }
+        };
+    };
+}
+
+#endif /* defined(__CubicVR2__Transform__) */
diff --git a/external/cubicvr2/math/triangle.h b/external/cubicvr2/math/triangle.h
new file mode 100644
index 0000000..147380d
--- /dev/null
+++ b/external/cubicvr2/math/triangle.h
@@ -0,0 +1,40 @@
+//
+//  triangle.h
+//  CubicVR2
+//
+//  Created by Charles J. Cliffe on 2013-02-22.
+//  Copyright (c) 2013 Charles J. Cliffe. All rights reserved.
+//
+
+#ifndef __CubicVR2__triangle__
+#define __CubicVR2__triangle__
+
+#include <iostream>
+#include "vec3.h"
+
+namespace CubicVR {
+    
+    struct triangle {
+        static vec3 normal(vec3 pt1, vec3 pt2, vec3 pt3) {
+            
+            __float v10 = pt1[0] - pt2[0];
+            __float v11 = pt1[1] - pt2[1];
+            __float v12 = pt1[2] - pt2[2];
+            __float v20 = pt2[0] - pt3[0];
+            __float v21 = pt2[1] - pt3[1];
+            __float v22 = pt2[2] - pt3[2];
+            
+            vec3 mOut;
+            
+            mOut[0] = v11 * v22 - v12 * v21;
+            mOut[1] = v12 * v20 - v10 * v22;
+            mOut[2] = v10 * v21 - v11 * v20;
+            
+            return mOut;
+        };
+    };
+    
+}
+
+
+#endif /* defined(__CubicVR2__triangle__) */
diff --git a/external/cubicvr2/math/vec2.h b/external/cubicvr2/math/vec2.h
new file mode 100644
index 0000000..24011c7
--- /dev/null
+++ b/external/cubicvr2/math/vec2.h
@@ -0,0 +1,93 @@
+//
+//  vec2.h
+//  CubicVR2
+//
+//  Created by Charles J. Cliffe on 2013-02-22.
+//  Copyright (c) 2013 Charles J. Cliffe. All rights reserved.
+//
+
+#ifndef __CubicVR2__vec2__
+#define __CubicVR2__vec2__
+
+#include <iostream>
+#include <cmath>
+#include "cubic_types.h"
+
+namespace CubicVR {
+    #define vec2SG(c,x,y) \
+        vec2 COMBINE(get,x)() { return y; } \
+        c & COMBINE(set,x)(vec2 value) { y = value; return *this; }
+
+    struct vec2 {
+        __float x, y;
+    public:
+        __float& u() { return x; }
+        __float& v() { return y; }
+        
+//        __float  operator [] (unsigned i) const { return ((__float *)this)[i]; }
+#ifndef _WIN32
+		__float& operator [] (unsigned i)       { return ((__float *)this)[i]; }
+#endif        
+        vec2 (__float xi,__float yi) { x = xi; y = yi;  }
+        vec2 () { x = y = 0.0f; }
+        
+        operator __float*() const { return (__float *)this; }
+        
+        vec2 operator*(__float v) { return vec2( x*v, y*v ); }
+        //    vec2 operator*(vec2 v) { return vec2::cross(*this,v); }
+        vec2 operator+(vec2 v) { return vec2::add(*this,v); }
+        vec2 operator-(vec2 v) { return vec2::subtract(*this,v); }
+        
+        
+        static bool equal(vec2 a, vec2 b, __float epsilon = 0.00000001) {
+            return (fabs(a[0] - b[0]) < epsilon && fabs(a[1] - b[1]) < epsilon);
+        };
+        
+        static bool onLine(vec2 a, vec2 b,vec2 c) {
+            __float minx = (a[0]<b[0])?a[0]:b[0];
+            __float miny = (a[1]<b[1])?a[1]:b[1];
+            __float maxx = (a[0]>b[0])?a[0]:b[0];
+            __float maxy = (a[1]>b[1])?a[1]:b[1];
+            
+            if ((minx <= c[0] && c[0] <= maxx) && (miny <= c[1] && c[1] <= maxy)) {
+                return true;
+            } else {
+                return false;
+            }
+        };
+        
+        static vec2 lineIntersect(vec2 a1, vec2 a2, vec2 b1, vec2 b2) {
+            __float x1 = a1[0], y1 = a1[1], x2 = a2[0], y2 = a2[1];
+            __float x3 = b1[0], y3 = b1[1], x4 = b2[0], y4 = b2[1];
+            
+            __float d = ((x1-x2) * (y3-y4)) - ((y1-y2) * (x3-x4));
+            if (d == 0) return vec2(INFINITY,INFINITY);
+            
+            __float xi = (((x3-x4) * ((x1*y2)-(y1*x2))) - ((x1-x2) *((x3*y4)-(y3*x4))))/d;
+            __float yi = (((y3-y4) * ((x1*y2)-(y1*x2))) - ((y1-y2) *((x3*y4)-(y3*x4))))/d;
+            
+            return vec2( xi,yi );
+        };
+        
+        static vec2 add(vec2 a,vec2 b) {
+            return vec2(a[0]+b[0],a[1]+b[1]);
+        };
+        
+        static vec2 subtract(vec2 a, vec2 b) {
+            return vec2(a[0]-b[0],a[1]-b[1]);
+        };
+        
+        static __float length(vec2 a,vec2 b) {
+            vec2 s(a[0]-b[0],a[1]-b[1]);
+            
+            return sqrtf(s[0]*s[0]+s[1]*s[1]);
+        };
+        
+        static __float length(vec2 a) {
+            return sqrtf(a[0]*a[0]+a[1]*a[1]);
+        };
+        
+    };
+    
+}
+#endif /* defined(__CubicVR2__vec2__) */
diff --git a/external/cubicvr2/math/vec3.h b/external/cubicvr2/math/vec3.h
new file mode 100644
index 0000000..220bf60
--- /dev/null
+++ b/external/cubicvr2/math/vec3.h
@@ -0,0 +1,172 @@
+//
+//  vec3.h
+//  CubicVR2
+//
+//  Created by Charles J. Cliffe on 2013-02-21.
+//  Copyright (c) 2013 Charles J. Cliffe. All rights reserved.
+//
+
+#ifndef __CubicVR2__vec3__
+#define __CubicVR2__vec3__
+
+#include <iostream>
+#include "cubic_types.h"
+#include <cmath>
+
+namespace CubicVR {
+    
+#define vec3SG(c,x,y) \
+    vec3 COMBINE(get,x)() { return y; } \
+    c & COMBINE(set,x)(vec3 value) { y = value; return *this; }
+    
+
+    struct vec3 {
+        __float x,y,z;
+
+        operator __float*() const { return (__float *)this; }
+
+        __float& r() { return x; }
+        __float& g() { return y; }
+        __float& b() { return z; }
+        
+#ifndef _WIN32
+        __float& operator [] (unsigned i)       { return ((__float *)this)[i]; }
+#endif
+        vec3 (__float xi,__float yi,__float zi) { x = xi; y = yi; z = zi; }
+        vec3 () { x = y = z = 0.0f; }
+        
+        
+        vec3 operator*(__float v) { return vec3(x*v, y*v, z*v); }
+        vec3 operator*(vec3 v) { return vec3::cross(*this,v); }
+        vec3 operator+(vec3 v) { return vec3::add(*this,v); }
+        vec3 operator-(vec3 v) { return vec3::subtract(*this,v); }
+        
+        
+        static __float length(vec3 pta, vec3 ptb) {
+            __float a,b,c;
+            a = ptb[0]-pta[0];
+            b = ptb[1]-pta[1];
+            c = ptb[2]-pta[2];
+            return sqrtf((a*a) + (b*b) + (c*c));
+        };
+        static __float length(vec3 pta) {
+            __float a,b,c;
+            a = pta[0];
+            b = pta[1];
+            c = pta[2];
+            return sqrtf((a*a) + (b*b) + (c*c));
+        };
+        static vec3 normalize(vec3 pt) {
+            __float a = pt[0], b = pt[1], c = pt[2],
+            d = sqrtf((a*a) + (b*b) + (c*c));
+            if (d) {
+                pt[0] = pt[0]/d;
+                pt[1] = pt[1]/d;
+                pt[2] = pt[2]/d;
+                
+                return pt;
+            }
+            
+            pt = vec3(0.0f,0.0f,0.0f);
+            
+            return pt;
+        };
+        static __float dot(vec3 v1, vec3 v2) {
+            return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2];
+        };
+        static __float angle(vec3 v1, vec3 v2) {
+            __float a = acosf((v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]) / (sqrtf(v1[0] * v1[0] + v1[1] * v1[1] + v1[2] * v1[2]) * sqrtf(v2[0] * v2[0] + v2[1] * v2[1] + v2[2] * v2[2])));
+            
+            return a;
+        };
+        static vec3 cross(vec3 vectA, vec3 vectB) {
+            return vec3(
+                vectA[1] * vectB[2] - vectB[1] * vectA[2], vectA[2] * vectB[0] - vectB[2] * vectA[0], vectA[0] * vectB[1] - vectB[0] * vectA[1]
+            );
+        };
+        static vec3 multiply(vec3 vectA, __float constB) {
+            return vec3(vectA[0] * constB, vectA[1] * constB, vectA[2] * constB);
+        };
+        static vec3 add(vec3 vectA, vec3 vectB) {
+            return vec3(vectA[0] + vectB[0], vectA[1] + vectB[1], vectA[2] + vectB[2]);
+        };
+        
+        static vec3 subtract(vec3 vectA, vec3 vectB) {
+            return vec3(vectA[0] - vectB[0], vectA[1] - vectB[1], vectA[2] - vectB[2]);
+        };
+        
+        static bool equal(vec3 a, vec3 b, __float epsilon = 0.0000001f) {
+            return ((fabs(a[0] - b[0]) < epsilon) && (fabs(a[1] - b[1]) < epsilon) && (fabs(a[2] - b[2]) < epsilon));
+        };
+        
+        static vec3 moveViewRelative(vec3 position, vec3 target, __float xdelta, __float zdelta) {
+            __float ang = atan2f(zdelta, xdelta);
+            __float cam_ang = atan2f(target[2] - position[2], target[0] - position[0]);
+            __float mag = sqrtf(xdelta * xdelta + zdelta * zdelta);
+            
+            __float move_ang = cam_ang + ang + (float)M_PI/2.0f;
+            
+            //        if (typeof(alt_source) === 'object') {
+            //            return [alt_source[0] + mag * Math.cos(move_ang), alt_source[1], alt_source[2] + mag * Math.sin(move_ang)];
+            //        }
+            
+            return vec3(position[0] + mag * cosf(move_ang), position[1], position[2] + mag * sinf(move_ang));
+        };
+        
+        static vec3 trackTarget(vec3 position, vec3 target, __float trackingSpeed, __float safeDistance) {
+            vec3 camv = vec3::subtract(target, position);
+            vec3 dist = camv;
+            __float fdist = vec3::length(dist);
+            vec3 motionv = camv;
+            
+            motionv = vec3::normalize(motionv);
+            motionv = vec3::multiply(motionv, trackingSpeed * (1.0f / (1.0f / (fdist - safeDistance))));
+            
+            vec3 ret_pos;
+            
+            if (fdist > safeDistance) {
+                ret_pos = vec3::add(position, motionv);
+            } else if (fdist < safeDistance) {
+                motionv = camv;
+                motionv = vec3::normalize(motionv);
+                motionv = vec3::multiply(motionv, trackingSpeed * (1.0f / (1.0f / (fabsf(fdist - safeDistance)))));
+                ret_pos = vec3::subtract(position, motionv);
+            } else {
+                ret_pos = vec3(position[0], position[1] + motionv[2], position[2]);
+            }
+            
+            return ret_pos;
+        };
+        
+        static vec3 getClosestTo(vec3 ptA, vec3 ptB, vec3 ptTest) {
+            vec3 S, T, U;
+            
+            S = vec3::subtract(ptB, ptA);
+            T = vec3::subtract(ptTest, ptA);
+            U = vec3::add(vec3::multiply(S, vec3::dot(S, T) / vec3::dot(S, S)), ptA);
+            
+            return U;
+        };
+        
+        //        linePlaneIntersect: function(normal, point_on_plane, segment_start, segment_end)
+        //        {
+        //            // form a plane from normal and point_on_plane and test segment start->end to find intersect point
+        //            var denom,mu;
+        //
+        //            var d = - normal[0] * point_on_plane[0] - normal[1] * point_on_plane[1] - normal[2] * point_on_plane[2];
+        //
+        //            // calculate position where the plane intersects the segment
+        //            denom = normal[0] * (segment_end[0] - segment_start[0]) + normal[1] * (segment_end[1] - segment_start[1]) + normal[2] * (segment_end[2] - segment_start[2]);
+        //            if (Math.fabs(denom) < 0.001) return false;
+        //
+        //            mu = - (d + normal[0] * segment_start[0] + normal[1] * segment_start[1] + normal[2] * segment_start[2]) / denom;
+        //            return [
+        //                    (segment_start[0] + mu * (segment_end[0] - segment_start[0])),
+        //                    (segment_start[1] + mu * (segment_end[1] - segment_start[1])),
+        //                    (segment_start[2] + mu * (segment_end[2] - segment_start[2]))
+        //                    ];
+        //        }
+    };
+    
+}
+#endif /* defined(__CubicVR2__vec3__) */
diff --git a/external/cubicvr2/math/vec4.h b/external/cubicvr2/math/vec4.h
new file mode 100644
index 0000000..ad795c1
--- /dev/null
+++ b/external/cubicvr2/math/vec4.h
@@ -0,0 +1,73 @@
+//
+//  vec4.h
+//  CubicVR2
+//
+//  Created by Charles J. Cliffe on 2013-02-22.
+//  Copyright (c) 2013 Charles J. Cliffe. All rights reserved.
+//
+
+#ifndef __CubicVR2__vec4__
+#define __CubicVR2__vec4__
+
+#include <iostream>
+#include "cubic_types.h"
+#include <cmath>
+
+namespace CubicVR {
+
+#define vec4SG(c,x,y) \
+    vec3 COMBINE(get,x)() { return y; } \
+    c & COMBINE(set,x)(vec3 value) { y = value; return *this; }
+
+    struct vec4 {
+        __float x,y,z,w;
+    public:
+        __float& r() { return x; }
+        __float& g() { return y; }
+        __float& b() { return z; }
+        __float& a() { return w; }
+        
+//        __float  operator [] (unsigned i) const { return ((__float *)this)[i]; }
+#ifndef _WIN32
+        __float& operator [] (unsigned i)       { return ((__float *)this)[i]; }
+#endif
+
+        vec4 (__float xi,__float yi,__float zi,__float wi) { x = xi; y = yi; z = zi; w = wi; }
+        vec4 () { x = y = z = w =  0.0f; }
+        
+        operator __float*() const { return (__float *)this; }
+        
+        vec4 operator*(__float v) { return vec4(x*v, y*v, z*v, w*v); }
+//        vec4 operator*(vec4 v) { return vec4::cross(*this,v); }
+//        vec4 operator+(vec4 v) { return vec4::add(*this,v); }
+//        vec4 operator-(vec4 v) { return vec4::subtract(*this,v); }
+
+        static __float length(vec4 a, vec4 b) {
+            __float v[4] = {a[0]-b[0],a[1]-b[1],a[2]-b[2],a[3]-b[3]};
+            return sqrtf(v[0] * v[0] + v[1] * v[1] + v[2] * v[2] + v[3] * v[3]);
+        };
+
+        static __float length(vec4 v) {
+            return sqrtf(v[0] * v[0] + v[1] * v[1] + v[2] * v[2] + v[3] * v[3]);
+        };
+        
+        static vec4 normalize(vec4 v) {
+            __float n = sqrtf(vec4::length(v));
+            
+            v[0] /= n;
+            v[1] /= n;
+            v[2] /= n;
+            v[3] /= n;
+            
+            return v;
+        };
+        
+        static __float dot(vec4 v1, vec4 v2) {
+            return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2] + v1[3] * v2[3];
+        };
+        
+    };
+
+}
+
+#endif /* defined(__CubicVR2__vec4__) */
diff --git a/external/lodepng/lodepng.cpp b/external/lodepng/lodepng.cpp
new file mode 100644
index 0000000..8c78758
--- /dev/null
+++ b/external/lodepng/lodepng.cpp
@@ -0,0 +1,6224 @@
+/*
+LodePNG version 20160501
+
+Copyright (c) 2005-2016 Lode Vandevenne
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any damages
+arising from the use of this software.
+
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+
+    1. The origin of this software must not be misrepresented; you must not
+    claim that you wrote the original software. If you use this software
+    in a product, an acknowledgment in the product documentation would be
+    appreciated but is not required.
+
+    2. Altered source versions must be plainly marked as such, and must not be
+    misrepresented as being the original software.
+
+    3. This notice may not be removed or altered from any source
+    distribution.
+*/
+
+/*
+The manual and changelog are in the header file "lodepng.h"
+Rename this file to lodepng.cpp to use it for C++, or to lodepng.c to use it for C.
+*/
+
+#include "lodepng.h"
+
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1310) /*Visual Studio: A few warning types are not desired here.*/
+#pragma warning( disable : 4244 ) /*implicit conversions: not warned by gcc -Wall -Wextra and requires too much casts*/
+#pragma warning( disable : 4996 ) /*VS does not like fopen, but fopen_s is not standard C so unusable here*/
+#endif /*_MSC_VER */
+
+const char* LODEPNG_VERSION_STRING = "20160501";
+
+/*
+This source file is built up in the following large parts. The code sections
+with the "LODEPNG_COMPILE_" #defines divide this up further in an intermixed way.
+-Tools for C and common code for PNG and Zlib
+-C Code for Zlib (huffman, deflate, ...)
+-C Code for PNG (file format chunks, adam7, PNG filters, color conversions, ...)
+-The C++ wrapper around all of the above
+*/
+
+/*The malloc, realloc and free functions defined here with "lodepng_" in front
+of the name, so that you can easily change them to others related to your
+platform if needed. Everything else in the code calls these. Pass
+-DLODEPNG_NO_COMPILE_ALLOCATORS to the compiler, or comment out
+#define LODEPNG_COMPILE_ALLOCATORS in the header, to disable the ones here and
+define them in your own project's source files without needing to change
+lodepng source code. Don't forget to remove "static" if you copypaste them
+from here.*/
+
+#ifdef LODEPNG_COMPILE_ALLOCATORS
+static void* lodepng_malloc(size_t size)
+{
+  return malloc(size);
+}
+
+static void* lodepng_realloc(void* ptr, size_t new_size)
+{
+  return realloc(ptr, new_size);
+}
+
+static void lodepng_free(void* ptr)
+{
+  free(ptr);
+}
+#else /*LODEPNG_COMPILE_ALLOCATORS*/
+void* lodepng_malloc(size_t size);
+void* lodepng_realloc(void* ptr, size_t new_size);
+void lodepng_free(void* ptr);
+#endif /*LODEPNG_COMPILE_ALLOCATORS*/
+
+/* ////////////////////////////////////////////////////////////////////////// */
+/* ////////////////////////////////////////////////////////////////////////// */
+/* // Tools for C, and common code for PNG and Zlib.                       // */
+/* ////////////////////////////////////////////////////////////////////////// */
+/* ////////////////////////////////////////////////////////////////////////// */
+
+/*
+Often in case of an error a value is assigned to a variable and then it breaks
+out of a loop (to go to the cleanup phase of a function). This macro does that.
+It makes the error handling code shorter and more readable.
+
+Example: if(!uivector_resizev(&frequencies_ll, 286, 0)) ERROR_BREAK(83);
+*/
+#define CERROR_BREAK(errorvar, code)\
+{\
+  errorvar = code;\
+  break;\
+}
+
+/*version of CERROR_BREAK that assumes the common case where the error variable is named "error"*/
+#define ERROR_BREAK(code) CERROR_BREAK(error, code)
+
+/*Set error var to the error code, and return it.*/
+#define CERROR_RETURN_ERROR(errorvar, code)\
+{\
+  errorvar = code;\
+  return code;\
+}
+
+/*Try the code, if it returns error, also return the error.*/
+#define CERROR_TRY_RETURN(call)\
+{\
+  unsigned error = call;\
+  if(error) return error;\
+}
+
+/*Set error var to the error code, and return from the void function.*/
+#define CERROR_RETURN(errorvar, code)\
+{\
+  errorvar = code;\
+  return;\
+}
+
+/*
+About uivector, ucvector and string:
+-All of them wrap dynamic arrays or text strings in a similar way.
+-LodePNG was originally written in C++. The vectors replace the std::vectors that were used in the C++ version.
+-The string tools are made to avoid problems with compilers that declare things like strncat as deprecated.
+-They're not used in the interface, only internally in this file as static functions.
+-As with many other structs in this file, the init and cleanup functions serve as ctor and dtor.
+*/
+
+#ifdef LODEPNG_COMPILE_ZLIB
+/*dynamic vector of unsigned ints*/
+typedef struct uivector
+{
+  unsigned* data;
+  size_t size; /*size in number of unsigned longs*/
+  size_t allocsize; /*allocated size in bytes*/
+} uivector;
+
+static void uivector_cleanup(void* p)
+{
+  ((uivector*)p)->size = ((uivector*)p)->allocsize = 0;
+  lodepng_free(((uivector*)p)->data);
+  ((uivector*)p)->data = NULL;
+}
+
+/*returns 1 if success, 0 if failure ==> nothing done*/
+static unsigned uivector_reserve(uivector* p, size_t allocsize)
+{
+  if(allocsize > p->allocsize)
+  {
+    size_t newsize = (allocsize > p->allocsize * 2) ? allocsize : (allocsize * 3 / 2);
+    void* data = lodepng_realloc(p->data, newsize);
+    if(data)
+    {
+      p->allocsize = newsize;
+      p->data = (unsigned*)data;
+    }
+    else return 0; /*error: not enough memory*/
+  }
+  return 1;
+}
+
+/*returns 1 if success, 0 if failure ==> nothing done*/
+static unsigned uivector_resize(uivector* p, size_t size)
+{
+  if(!uivector_reserve(p, size * sizeof(unsigned))) return 0;
+  p->size = size;
+  return 1; /*success*/
+}
+
+/*resize and give all new elements the value*/
+static unsigned uivector_resizev(uivector* p, size_t size, unsigned value)
+{
+  size_t oldsize = p->size, i;
+  if(!uivector_resize(p, size)) return 0;
+  for(i = oldsize; i < size; ++i) p->data[i] = value;
+  return 1;
+}
+
+static void uivector_init(uivector* p)
+{
+  p->data = NULL;
+  p->size = p->allocsize = 0;
+}
+
+#ifdef LODEPNG_COMPILE_ENCODER
+/*returns 1 if success, 0 if failure ==> nothing done*/
+static unsigned uivector_push_back(uivector* p, unsigned c)
+{
+  if(!uivector_resize(p, p->size + 1)) return 0;
+  p->data[p->size - 1] = c;
+  return 1;
+}
+#endif /*LODEPNG_COMPILE_ENCODER*/
+#endif /*LODEPNG_COMPILE_ZLIB*/
+
+/* /////////////////////////////////////////////////////////////////////////// */
+
+/*dynamic vector of unsigned chars*/
+typedef struct ucvector
+{
+  unsigned char* data;
+  size_t size; /*used size*/
+  size_t allocsize; /*allocated size*/
+} ucvector;
+
+/*returns 1 if success, 0 if failure ==> nothing done*/
+static unsigned ucvector_reserve(ucvector* p, size_t allocsize)
+{
+  if(allocsize > p->allocsize)
+  {
+    size_t newsize = (allocsize > p->allocsize * 2) ? allocsize : (allocsize * 3 / 2);
+    void* data = lodepng_realloc(p->data, newsize);
+    if(data)
+    {
+      p->allocsize = newsize;
+      p->data = (unsigned char*)data;
+    }
+    else return 0; /*error: not enough memory*/
+  }
+  return 1;
+}
+
+/*returns 1 if success, 0 if failure ==> nothing done*/
+static unsigned ucvector_resize(ucvector* p, size_t size)
+{
+  if(!ucvector_reserve(p, size * sizeof(unsigned char))) return 0;
+  p->size = size;
+  return 1; /*success*/
+}
+
+#ifdef LODEPNG_COMPILE_PNG
+
+static void ucvector_cleanup(void* p)
+{
+  ((ucvector*)p)->size = ((ucvector*)p)->allocsize = 0;
+  lodepng_free(((ucvector*)p)->data);
+  ((ucvector*)p)->data = NULL;
+}
+
+static void ucvector_init(ucvector* p)
+{
+  p->data = NULL;
+  p->size = p->allocsize = 0;
+}
+#endif /*LODEPNG_COMPILE_PNG*/
+
+#ifdef LODEPNG_COMPILE_ZLIB
+/*you can both convert from vector to buffer&size and vica versa. If you use
+init_buffer to take over a buffer and size, it is not needed to use cleanup*/
+static void ucvector_init_buffer(ucvector* p, unsigned char* buffer, size_t size)
+{
+  p->data = buffer;
+  p->allocsize = p->size = size;
+}
+#endif /*LODEPNG_COMPILE_ZLIB*/
+
+#if (defined(LODEPNG_COMPILE_PNG) && defined(LODEPNG_COMPILE_ANCILLARY_CHUNKS)) || defined(LODEPNG_COMPILE_ENCODER)
+/*returns 1 if success, 0 if failure ==> nothing done*/
+static unsigned ucvector_push_back(ucvector* p, unsigned char c)
+{
+  if(!ucvector_resize(p, p->size + 1)) return 0;
+  p->data[p->size - 1] = c;
+  return 1;
+}
+#endif /*defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_ENCODER)*/
+
+
+/* ////////////////////////////////////////////////////////////////////////// */
+
+#ifdef LODEPNG_COMPILE_PNG
+#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS
+/*returns 1 if success, 0 if failure ==> nothing done*/
+static unsigned string_resize(char** out, size_t size)
+{
+  char* data = (char*)lodepng_realloc(*out, size + 1);
+  if(data)
+  {
+    data[size] = 0; /*null termination char*/
+    *out = data;
+  }
+  return data != 0;
+}
+
+/*init a {char*, size_t} pair for use as string*/
+static void string_init(char** out)
+{
+  *out = NULL;
+  string_resize(out, 0);
+}
+
+/*free the above pair again*/
+static void string_cleanup(char** out)
+{
+  lodepng_free(*out);
+  *out = NULL;
+}
+
+static void string_set(char** out, const char* in)
+{
+  size_t insize = strlen(in), i;
+  if(string_resize(out, insize))
+  {
+    for(i = 0; i != insize; ++i)
+    {
+      (*out)[i] = in[i];
+    }
+  }
+}
+#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/
+#endif /*LODEPNG_COMPILE_PNG*/
+
+/* ////////////////////////////////////////////////////////////////////////// */
+
+unsigned lodepng_read32bitInt(const unsigned char* buffer)
+{
+  return (unsigned)((buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3]);
+}
+
+#if defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_ENCODER)
+/*buffer must have at least 4 allocated bytes available*/
+static void lodepng_set32bitInt(unsigned char* buffer, unsigned value)
+{
+  buffer[0] = (unsigned char)((value >> 24) & 0xff);
+  buffer[1] = (unsigned char)((value >> 16) & 0xff);
+  buffer[2] = (unsigned char)((value >>  8) & 0xff);
+  buffer[3] = (unsigned char)((value      ) & 0xff);
+}
+#endif /*defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_ENCODER)*/
+
+#ifdef LODEPNG_COMPILE_ENCODER
+static void lodepng_add32bitInt(ucvector* buffer, unsigned value)
+{
+  ucvector_resize(buffer, buffer->size + 4); /*todo: give error if resize failed*/
+  lodepng_set32bitInt(&buffer->data[buffer->size - 4], value);
+}
+#endif /*LODEPNG_COMPILE_ENCODER*/
+
+/* ////////////////////////////////////////////////////////////////////////// */
+/* / File IO                                                                / */
+/* ////////////////////////////////////////////////////////////////////////// */
+
+#ifdef LODEPNG_COMPILE_DISK
+
+/* returns negative value on error. This should be pure C compatible, so no fstat. */
+static long lodepng_filesize(const char* filename)
+{
+  FILE* file;
+  long size;
+  file = fopen(filename, "rb");
+  if(!file) return -1;
+
+  if(fseek(file, 0, SEEK_END) != 0)
+  {
+    fclose(file);
+    return -1;
+  }
+
+  size = ftell(file);
+  /* It may give LONG_MAX as directory size, this is invalid for us. */
+  if(size == LONG_MAX) size = -1;
+
+  fclose(file);
+  return size;
+}
+
+/* load file into buffer that already has the correct allocated size. Returns error code.*/
+static unsigned lodepng_buffer_file(unsigned char* out, size_t size, const char* filename)
+{
+  FILE* file;
+  size_t readsize;
+  file = fopen(filename, "rb");
+  if(!file) return 78;
+
+  readsize = fread(out, 1, size, file);
+  fclose(file);
+
+  if (readsize != size) return 78;
+  return 0;
+}
+
+unsigned lodepng_load_file(unsigned char** out, size_t* outsize, const char* filename)
+{
+  long size = lodepng_filesize(filename);
+  if (size < 0) return 78;
+  *outsize = (size_t)size;
+
+  *out = (unsigned char*)lodepng_malloc((size_t)size);
+  if(!(*out) && size > 0) return 83; /*the above malloc failed*/
+
+  return lodepng_buffer_file(*out, (size_t)size, filename);
+}
+
+/*write given buffer to the file, overwriting the file, it doesn't append to it.*/
+unsigned lodepng_save_file(const unsigned char* buffer, size_t buffersize, const char* filename)
+{
+  FILE* file;
+  file = fopen(filename, "wb" );
+  if(!file) return 79;
+  fwrite((char*)buffer , 1 , buffersize, file);
+  fclose(file);
+  return 0;
+}
+
+#endif /*LODEPNG_COMPILE_DISK*/
+
+/* ////////////////////////////////////////////////////////////////////////// */
+/* ////////////////////////////////////////////////////////////////////////// */
+/* // End of common code and tools. Begin of Zlib related code.            // */
+/* ////////////////////////////////////////////////////////////////////////// */
+/* ////////////////////////////////////////////////////////////////////////// */
+
+#ifdef LODEPNG_COMPILE_ZLIB
+#ifdef LODEPNG_COMPILE_ENCODER
+/*TODO: this ignores potential out of memory errors*/
+#define addBitToStream(/*size_t**/ bitpointer, /*ucvector**/ bitstream, /*unsigned char*/ bit)\
+{\
+  /*add a new byte at the end*/\
+  if(((*bitpointer) & 7) == 0) ucvector_push_back(bitstream, (unsigned char)0);\
+  /*earlier bit of huffman code is in a lesser significant bit of an earlier byte*/\
+  (bitstream->data[bitstream->size - 1]) |= (bit << ((*bitpointer) & 0x7));\
+  ++(*bitpointer);\
+}
+
+static void addBitsToStream(size_t* bitpointer, ucvector* bitstream, unsigned value, size_t nbits)
+{
+  size_t i;
+  for(i = 0; i != nbits; ++i) addBitToStream(bitpointer, bitstream, (unsigned char)((value >> i) & 1));
+}
+
+static void addBitsToStreamReversed(size_t* bitpointer, ucvector* bitstream, unsigned value, size_t nbits)
+{
+  size_t i;
+  for(i = 0; i != nbits; ++i) addBitToStream(bitpointer, bitstream, (unsigned char)((value >> (nbits - 1 - i)) & 1));
+}
+#endif /*LODEPNG_COMPILE_ENCODER*/
+
+#ifdef LODEPNG_COMPILE_DECODER
+
+#define READBIT(bitpointer, bitstream) ((bitstream[bitpointer >> 3] >> (bitpointer & 0x7)) & (unsigned char)1)
+
+static unsigned char readBitFromStream(size_t* bitpointer, const unsigned char* bitstream)
+{
+  unsigned char result = (unsigned char)(READBIT(*bitpointer, bitstream));
+  ++(*bitpointer);
+  return result;
+}
+
+static unsigned readBitsFromStream(size_t* bitpointer, const unsigned char* bitstream, size_t nbits)
+{
+  unsigned result = 0, i;
+  for(i = 0; i != nbits; ++i)
+  {
+    result += ((unsigned)READBIT(*bitpointer, bitstream)) << i;
+    ++(*bitpointer);
+  }
+  return result;
+}
+#endif /*LODEPNG_COMPILE_DECODER*/
+
+/* ////////////////////////////////////////////////////////////////////////// */
+/* / Deflate - Huffman                                                      / */
+/* ////////////////////////////////////////////////////////////////////////// */
+
+#define FIRST_LENGTH_CODE_INDEX 257
+#define LAST_LENGTH_CODE_INDEX 285
+/*256 literals, the end code, some length codes, and 2 unused codes*/
+#define NUM_DEFLATE_CODE_SYMBOLS 288
+/*the distance codes have their own symbols, 30 used, 2 unused*/
+#define NUM_DISTANCE_SYMBOLS 32
+/*the code length codes. 0-15: code lengths, 16: copy previous 3-6 times, 17: 3-10 zeros, 18: 11-138 zeros*/
+#define NUM_CODE_LENGTH_CODES 19
+
+/*the base lengths represented by codes 257-285*/
+static const unsigned LENGTHBASE[29]
+  = {3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59,
+     67, 83, 99, 115, 131, 163, 195, 227, 258};
+
+/*the extra bits used by codes 257-285 (added to base length)*/
+static const unsigned LENGTHEXTRA[29]
+  = {0, 0, 0, 0, 0, 0, 0,  0,  1,  1,  1,  1,  2,  2,  2,  2,  3,  3,  3,  3,
+      4,  4,  4,   4,   5,   5,   5,   5,   0};
+
+/*the base backwards distances (the bits of distance codes appear after length codes and use their own huffman tree)*/
+static const unsigned DISTANCEBASE[30]
+  = {1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513,
+     769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577};
+
+/*the extra bits of backwards distances (added to base)*/
+static const unsigned DISTANCEEXTRA[30]
+  = {0, 0, 0, 0, 1, 1, 2,  2,  3,  3,  4,  4,  5,  5,   6,   6,   7,   7,   8,
+       8,    9,    9,   10,   10,   11,   11,   12,    12,    13,    13};
+
+/*the order in which "code length alphabet code lengths" are stored, out of this
+the huffman tree of the dynamic huffman tree lengths is generated*/
+static const unsigned CLCL_ORDER[NUM_CODE_LENGTH_CODES]
+  = {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15};
+
+/* ////////////////////////////////////////////////////////////////////////// */
+
+/*
+Huffman tree struct, containing multiple representations of the tree
+*/
+typedef struct HuffmanTree
+{
+  unsigned* tree2d;
+  unsigned* tree1d;
+  unsigned* lengths; /*the lengths of the codes of the 1d-tree*/
+  unsigned maxbitlen; /*maximum number of bits a single code can get*/
+  unsigned numcodes; /*number of symbols in the alphabet = number of codes*/
+} HuffmanTree;
+
+/*function used for debug purposes to draw the tree in ascii art with C++*/
+/*
+static void HuffmanTree_draw(HuffmanTree* tree)
+{
+  std::cout << "tree. length: " << tree->numcodes << " maxbitlen: " << tree->maxbitlen << std::endl;
+  for(size_t i = 0; i != tree->tree1d.size; ++i)
+  {
+    if(tree->lengths.data[i])
+      std::cout << i << " " << tree->tree1d.data[i] << " " << tree->lengths.data[i] << std::endl;
+  }
+  std::cout << std::endl;
+}*/
+
+static void HuffmanTree_init(HuffmanTree* tree)
+{
+  tree->tree2d = 0;
+  tree->tree1d = 0;
+  tree->lengths = 0;
+}
+
+static void HuffmanTree_cleanup(HuffmanTree* tree)
+{
+  lodepng_free(tree->tree2d);
+  lodepng_free(tree->tree1d);
+  lodepng_free(tree->lengths);
+}
+
+/*the tree representation used by the decoder. return value is error*/
+static unsigned HuffmanTree_make2DTree(HuffmanTree* tree)
+{
+  unsigned nodefilled = 0; /*up to which node it is filled*/
+  unsigned treepos = 0; /*position in the tree (1 of the numcodes columns)*/
+  unsigned n, i;
+
+  tree->tree2d = (unsigned*)lodepng_malloc(tree->numcodes * 2 * sizeof(unsigned));
+  if(!tree->tree2d) return 83; /*alloc fail*/
+
+  /*
+  convert tree1d[] to tree2d[][]. In the 2D array, a value of 32767 means
+  uninited, a value >= numcodes is an address to another bit, a value < numcodes
+  is a code. The 2 rows are the 2 possible bit values (0 or 1), there are as
+  many columns as codes - 1.
+  A good huffman tree has N * 2 - 1 nodes, of which N - 1 are internal nodes.
+  Here, the internal nodes are stored (what their 0 and 1 option point to).
+  There is only memory for such good tree currently, if there are more nodes
+  (due to too long length codes), error 55 will happen
+  */
+  for(n = 0; n < tree->numcodes * 2; ++n)
+  {
+    tree->tree2d[n] = 32767; /*32767 here means the tree2d isn't filled there yet*/
+  }
+
+  for(n = 0; n < tree->numcodes; ++n) /*the codes*/
+  {
+    for(i = 0; i != tree->lengths[n]; ++i) /*the bits for this code*/
+    {
+      unsigned char bit = (unsigned char)((tree->tree1d[n] >> (tree->lengths[n] - i - 1)) & 1);
+      /*oversubscribed, see comment in lodepng_error_text*/
+      if(treepos > 2147483647 || treepos + 2 > tree->numcodes) return 55;
+      if(tree->tree2d[2 * treepos + bit] == 32767) /*not yet filled in*/
+      {
+        if(i + 1 == tree->lengths[n]) /*last bit*/
+        {
+          tree->tree2d[2 * treepos + bit] = n; /*put the current code in it*/
+          treepos = 0;
+        }
+        else
+        {
+          /*put address of the next step in here, first that address has to be found of course
+          (it's just nodefilled + 1)...*/
+          ++nodefilled;
+          /*addresses encoded with numcodes added to it*/
+          tree->tree2d[2 * treepos + bit] = nodefilled + tree->numcodes;
+          treepos = nodefilled;
+        }
+      }
+      else treepos = tree->tree2d[2 * treepos + bit] - tree->numcodes;
+    }
+  }
+
+  for(n = 0; n < tree->numcodes * 2; ++n)
+  {
+    if(tree->tree2d[n] == 32767) tree->tree2d[n] = 0; /*remove possible remaining 32767's*/
+  }
+
+  return 0;
+}
+
+/*
+Second step for the ...makeFromLengths and ...makeFromFrequencies functions.
+numcodes, lengths and maxbitlen must already be filled in correctly. return
+value is error.
+*/
+static unsigned HuffmanTree_makeFromLengths2(HuffmanTree* tree)
+{
+  uivector blcount;
+  uivector nextcode;
+  unsigned error = 0;
+  unsigned bits, n;
+
+  uivector_init(&blcount);
+  uivector_init(&nextcode);
+
+  tree->tree1d = (unsigned*)lodepng_malloc(tree->numcodes * sizeof(unsigned));
+  if(!tree->tree1d) error = 83; /*alloc fail*/
+
+  if(!uivector_resizev(&blcount, tree->maxbitlen + 1, 0)
+  || !uivector_resizev(&nextcode, tree->maxbitlen + 1, 0))
+    error = 83; /*alloc fail*/
+
+  if(!error)
+  {
+    /*step 1: count number of instances of each code length*/
+    for(bits = 0; bits != tree->numcodes; ++bits) ++blcount.data[tree->lengths[bits]];
+    /*step 2: generate the nextcode values*/
+    for(bits = 1; bits <= tree->maxbitlen; ++bits)
+    {
+      nextcode.data[bits] = (nextcode.data[bits - 1] + blcount.data[bits - 1]) << 1;
+    }
+    /*step 3: generate all the codes*/
+    for(n = 0; n != tree->numcodes; ++n)
+    {
+      if(tree->lengths[n] != 0) tree->tree1d[n] = nextcode.data[tree->lengths[n]]++;
+    }
+  }
+
+  uivector_cleanup(&blcount);
+  uivector_cleanup(&nextcode);
+
+  if(!error) return HuffmanTree_make2DTree(tree);
+  else return error;
+}
+
+/*
+given the code lengths (as stored in the PNG file), generate the tree as defined
+by Deflate. maxbitlen is the maximum bits that a code in the tree can have.
+return value is error.
+*/
+static unsigned HuffmanTree_makeFromLengths(HuffmanTree* tree, const unsigned* bitlen,
+                                            size_t numcodes, unsigned maxbitlen)
+{
+  unsigned i;
+  tree->lengths = (unsigned*)lodepng_malloc(numcodes * sizeof(unsigned));
+  if(!tree->lengths) return 83; /*alloc fail*/
+  for(i = 0; i != numcodes; ++i) tree->lengths[i] = bitlen[i];
+  tree->numcodes = (unsigned)numcodes; /*number of symbols*/
+  tree->maxbitlen = maxbitlen;
+  return HuffmanTree_makeFromLengths2(tree);
+}
+
+#ifdef LODEPNG_COMPILE_ENCODER
+
+/*BPM: Boundary Package Merge, see "A Fast and Space-Economical Algorithm for Length-Limited Coding",
+Jyrki Katajainen, Alistair Moffat, Andrew Turpin, 1995.*/
+
+/*chain node for boundary package merge*/
+typedef struct BPMNode
+{
+  int weight; /*the sum of all weights in this chain*/
+  unsigned index; /*index of this leaf node (called "count" in the paper)*/
+  struct BPMNode* tail; /*the next nodes in this chain (null if last)*/
+  int in_use;
+} BPMNode;
+
+/*lists of chains*/
+typedef struct BPMLists
+{
+  /*memory pool*/
+  unsigned memsize;
+  BPMNode* memory;
+  unsigned numfree;
+  unsigned nextfree;
+  BPMNode** freelist;
+  /*two heads of lookahead chains per list*/
+  unsigned listsize;
+  BPMNode** chains0;
+  BPMNode** chains1;
+} BPMLists;
+
+/*creates a new chain node with the given parameters, from the memory in the lists */
+static BPMNode* bpmnode_create(BPMLists* lists, int weight, unsigned index, BPMNode* tail)
+{
+  unsigned i;
+  BPMNode* result;
+
+  /*memory full, so garbage collect*/
+  if(lists->nextfree >= lists->numfree)
+  {
+    /*mark only those that are in use*/
+    for(i = 0; i != lists->memsize; ++i) lists->memory[i].in_use = 0;
+    for(i = 0; i != lists->listsize; ++i)
+    {
+      BPMNode* node;
+      for(node = lists->chains0[i]; node != 0; node = node->tail) node->in_use = 1;
+      for(node = lists->chains1[i]; node != 0; node = node->tail) node->in_use = 1;
+    }
+    /*collect those that are free*/
+    lists->numfree = 0;
+    for(i = 0; i != lists->memsize; ++i)
+    {
+      if(!lists->memory[i].in_use) lists->freelist[lists->numfree++] = &lists->memory[i];
+    }
+    lists->nextfree = 0;
+  }
+
+  result = lists->freelist[lists->nextfree++];
+  result->weight = weight;
+  result->index = index;
+  result->tail = tail;
+  return result;
+}
+
+/*sort the leaves with stable mergesort*/
+static void bpmnode_sort(BPMNode* leaves, size_t num)
+{
+  BPMNode* mem = (BPMNode*)lodepng_malloc(sizeof(*leaves) * num);
+  size_t width, counter = 0;
+  for(width = 1; width < num; width *= 2)
+  {
+    BPMNode* a = (counter & 1) ? mem : leaves;
+    BPMNode* b = (counter & 1) ? leaves : mem;
+    size_t p;
+    for(p = 0; p < num; p += 2 * width)
+    {
+      size_t q = (p + width > num) ? num : (p + width);
+      size_t r = (p + 2 * width > num) ? num : (p + 2 * width);
+      size_t i = p, j = q, k;
+      for(k = p; k < r; k++)
+      {
+        if(i < q && (j >= r || a[i].weight <= a[j].weight)) b[k] = a[i++];
+        else b[k] = a[j++];
+      }
+    }
+    counter++;
+  }
+  if(counter & 1) memcpy(leaves, mem, sizeof(*leaves) * num);
+  lodepng_free(mem);
+}
+
+/*Boundary Package Merge step, numpresent is the amount of leaves, and c is the current chain.*/
+static void boundaryPM(BPMLists* lists, BPMNode* leaves, size_t numpresent, int c, int num)
+{
+  unsigned lastindex = lists->chains1[c]->index;
+
+  if(c == 0)
+  {
+    if(lastindex >= numpresent) return;
+    lists->chains0[c] = lists->chains1[c];
+    lists->chains1[c] = bpmnode_create(lists, leaves[lastindex].weight, lastindex + 1, 0);
+  }
+  else
+  {
+    /*sum of the weights of the head nodes of the previous lookahead chains.*/
+    int sum = lists->chains0[c - 1]->weight + lists->chains1[c - 1]->weight;
+    lists->chains0[c] = lists->chains1[c];
+    if(lastindex < numpresent && sum > leaves[lastindex].weight)
+    {
+      lists->chains1[c] = bpmnode_create(lists, leaves[lastindex].weight, lastindex + 1, lists->chains1[c]->tail);
+      return;
+    }
+    lists->chains1[c] = bpmnode_create(lists, sum, lastindex, lists->chains1[c - 1]);
+    /*in the end we are only interested in the chain of the last list, so no
+    need to recurse if we're at the last one (this gives measurable speedup)*/
+    if(num + 1 < (int)(2 * numpresent - 2))
+    {
+      boundaryPM(lists, leaves, numpresent, c - 1, num);
+      boundaryPM(lists, leaves, numpresent, c - 1, num);
+    }
+  }
+}
+
+unsigned lodepng_huffman_code_lengths(unsigned* lengths, const unsigned* frequencies,
+                                      size_t numcodes, unsigned maxbitlen)
+{
+  unsigned error = 0;
+  unsigned i;
+  size_t numpresent = 0; /*number of symbols with non-zero frequency*/
+  BPMNode* leaves; /*the symbols, only those with > 0 frequency*/
+
+  if(numcodes == 0) return 80; /*error: a tree of 0 symbols is not supposed to be made*/
+  if((1u << maxbitlen) < numcodes) return 80; /*error: represent all symbols*/
+
+  leaves = (BPMNode*)lodepng_malloc(numcodes * sizeof(*leaves));
+  if(!leaves) return 83; /*alloc fail*/
+
+  for(i = 0; i != numcodes; ++i)
+  {
+    if(frequencies[i] > 0)
+    {
+      leaves[numpresent].weight = (int)frequencies[i];
+      leaves[numpresent].index = i;
+      ++numpresent;
+    }
+  }
+
+  for(i = 0; i != numcodes; ++i) lengths[i] = 0;
+
+  /*ensure at least two present symbols. There should be at least one symbol
+  according to RFC 1951 section 3.2.7. Some decoders incorrectly require two. To
+  make these work as well ensure there are at least two symbols. The
+  Package-Merge code below also doesn't work correctly if there's only one
+  symbol, it'd give it the theoritical 0 bits but in practice zlib wants 1 bit*/
+  if(numpresent == 0)
+  {
+    lengths[0] = lengths[1] = 1; /*note that for RFC 1951 section 3.2.7, only lengths[0] = 1 is needed*/
+  }
+  else if(numpresent == 1)
+  {
+    lengths[leaves[0].index] = 1;
+    lengths[leaves[0].index == 0 ? 1 : 0] = 1;
+  }
+  else
+  {
+    BPMLists lists;
+    BPMNode* node;
+
+    bpmnode_sort(leaves, numpresent);
+
+    lists.listsize = maxbitlen;
+    lists.memsize = 2 * maxbitlen * (maxbitlen + 1);
+    lists.nextfree = 0;
+    lists.numfree = lists.memsize;
+    lists.memory = (BPMNode*)lodepng_malloc(lists.memsize * sizeof(*lists.memory));
+    lists.freelist = (BPMNode**)lodepng_malloc(lists.memsize * sizeof(BPMNode*));
+    lists.chains0 = (BPMNode**)lodepng_malloc(lists.listsize * sizeof(BPMNode*));
+    lists.chains1 = (BPMNode**)lodepng_malloc(lists.listsize * sizeof(BPMNode*));
+    if(!lists.memory || !lists.freelist || !lists.chains0 || !lists.chains1) error = 83; /*alloc fail*/
+
+    if(!error)
+    {
+      for(i = 0; i != lists.memsize; ++i) lists.freelist[i] = &lists.memory[i];
+
+      bpmnode_create(&lists, leaves[0].weight, 1, 0);
+      bpmnode_create(&lists, leaves[1].weight, 2, 0);
+
+      for(i = 0; i != lists.listsize; ++i)
+      {
+        lists.chains0[i] = &lists.memory[0];
+        lists.chains1[i] = &lists.memory[1];
+      }
+
+      /*each boundaryPM call adds one chain to the last list, and we need 2 * numpresent - 2 chains.*/
+      for(i = 2; i != 2 * numpresent - 2; ++i) boundaryPM(&lists, leaves, numpresent, (int)maxbitlen - 1, (int)i);
+
+      for(node = lists.chains1[maxbitlen - 1]; node; node = node->tail)
+      {
+        for(i = 0; i != node->index; ++i) ++lengths[leaves[i].index];
+      }
+    }
+
+    lodepng_free(lists.memory);
+    lodepng_free(lists.freelist);
+    lodepng_free(lists.chains0);
+    lodepng_free(lists.chains1);
+  }
+
+  lodepng_free(leaves);
+  return error;
+}
+
+/*Create the Huffman tree given the symbol frequencies*/
+static unsigned HuffmanTree_makeFromFrequencies(HuffmanTree* tree, const unsigned* frequencies,
+                                                size_t mincodes, size_t numcodes, unsigned maxbitlen)
+{
+  unsigned error = 0;
+  while(!frequencies[numcodes - 1] && numcodes > mincodes) --numcodes; /*trim zeroes*/
+  tree->maxbitlen = maxbitlen;
+  tree->numcodes = (unsigned)numcodes; /*number of symbols*/
+  tree->lengths = (unsigned*)lodepng_realloc(tree->lengths, numcodes * sizeof(unsigned));
+  if(!tree->lengths) return 83; /*alloc fail*/
+  /*initialize all lengths to 0*/
+  memset(tree->lengths, 0, numcodes * sizeof(unsigned));
+
+  error = lodepng_huffman_code_lengths(tree->lengths, frequencies, numcodes, maxbitlen);
+  if(!error) error = HuffmanTree_makeFromLengths2(tree);
+  return error;
+}
+
+static unsigned HuffmanTree_getCode(const HuffmanTree* tree, unsigned index)
+{
+  return tree->tree1d[index];
+}
+
+static unsigned HuffmanTree_getLength(const HuffmanTree* tree, unsigned index)
+{
+  return tree->lengths[index];
+}
+#endif /*LODEPNG_COMPILE_ENCODER*/
+
+/*get the literal and length code tree of a deflated block with fixed tree, as per the deflate specification*/
+static unsigned generateFixedLitLenTree(HuffmanTree* tree)
+{
+  unsigned i, error = 0;
+  unsigned* bitlen = (unsigned*)lodepng_malloc(NUM_DEFLATE_CODE_SYMBOLS * sizeof(unsigned));
+  if(!bitlen) return 83; /*alloc fail*/
+
+  /*288 possible codes: 0-255=literals, 256=endcode, 257-285=lengthcodes, 286-287=unused*/
+  for(i =   0; i <= 143; ++i) bitlen[i] = 8;
+  for(i = 144; i <= 255; ++i) bitlen[i] = 9;
+  for(i = 256; i <= 279; ++i) bitlen[i] = 7;
+  for(i = 280; i <= 287; ++i) bitlen[i] = 8;
+
+  error = HuffmanTree_makeFromLengths(tree, bitlen, NUM_DEFLATE_CODE_SYMBOLS, 15);
+
+  lodepng_free(bitlen);
+  return error;
+}
+
+/*get the distance code tree of a deflated block with fixed tree, as specified in the deflate specification*/
+static unsigned generateFixedDistanceTree(HuffmanTree* tree)
+{
+  unsigned i, error = 0;
+  unsigned* bitlen = (unsigned*)lodepng_malloc(NUM_DISTANCE_SYMBOLS * sizeof(unsigned));
+  if(!bitlen) return 83; /*alloc fail*/
+
+  /*there are 32 distance codes, but 30-31 are unused*/
+  for(i = 0; i != NUM_DISTANCE_SYMBOLS; ++i) bitlen[i] = 5;
+  error = HuffmanTree_makeFromLengths(tree, bitlen, NUM_DISTANCE_SYMBOLS, 15);
+
+  lodepng_free(bitlen);
+  return error;
+}
+
+#ifdef LODEPNG_COMPILE_DECODER
+
+/*
+returns the code, or (unsigned)(-1) if error happened
+inbitlength is the length of the complete buffer, in bits (so its byte length times 8)
+*/
+static unsigned huffmanDecodeSymbol(const unsigned char* in, size_t* bp,
+                                    const HuffmanTree* codetree, size_t inbitlength)
+{
+  unsigned treepos = 0, ct;
+  for(;;)
+  {
+    if(*bp >= inbitlength) return (unsigned)(-1); /*error: end of input memory reached without endcode*/
+    /*
+    decode the symbol from the tree. The "readBitFromStream" code is inlined in
+    the expression below because this is the biggest bottleneck while decoding
+    */
+    ct = codetree->tree2d[(treepos << 1) + READBIT(*bp, in)];
+    ++(*bp);
+    if(ct < codetree->numcodes) return ct; /*the symbol is decoded, return it*/
+    else treepos = ct - codetree->numcodes; /*symbol not yet decoded, instead move tree position*/
+
+    if(treepos >= codetree->numcodes) return (unsigned)(-1); /*error: it appeared outside the codetree*/
+  }
+}
+#endif /*LODEPNG_COMPILE_DECODER*/
+
+#ifdef LODEPNG_COMPILE_DECODER
+
+/* ////////////////////////////////////////////////////////////////////////// */
+/* / Inflator (Decompressor)                                                / */
+/* ////////////////////////////////////////////////////////////////////////// */
+
+/*get the tree of a deflated block with fixed tree, as specified in the deflate specification*/
+static void getTreeInflateFixed(HuffmanTree* tree_ll, HuffmanTree* tree_d)
+{
+  /*TODO: check for out of memory errors*/
+  generateFixedLitLenTree(tree_ll);
+  generateFixedDistanceTree(tree_d);
+}
+
+/*get the tree of a deflated block with dynamic tree, the tree itself is also Huffman compressed with a known tree*/
+static unsigned getTreeInflateDynamic(HuffmanTree* tree_ll, HuffmanTree* tree_d,
+                                      const unsigned char* in, size_t* bp, size_t inlength)
+{
+  /*make sure that length values that aren't filled in will be 0, or a wrong tree will be generated*/
+  unsigned error = 0;
+  unsigned n, HLIT, HDIST, HCLEN, i;
+  size_t inbitlength = inlength * 8;
+
+  /*see comments in deflateDynamic for explanation of the context and these variables, it is analogous*/
+  unsigned* bitlen_ll = 0; /*lit,len code lengths*/
+  unsigned* bitlen_d = 0; /*dist code lengths*/
+  /*code length code lengths ("clcl"), the bit lengths of the huffman tree used to compress bitlen_ll and bitlen_d*/
+  unsigned* bitlen_cl = 0;
+  HuffmanTree tree_cl; /*the code tree for code length codes (the huffman tree for compressed huffman trees)*/
+
+  if((*bp) + 14 > (inlength << 3)) return 49; /*error: the bit pointer is or will go past the memory*/
+
+  /*number of literal/length codes + 257. Unlike the spec, the value 257 is added to it here already*/
+  HLIT =  readBitsFromStream(bp, in, 5) + 257;
+  /*number of distance codes. Unlike the spec, the value 1 is added to it here already*/
+  HDIST = readBitsFromStream(bp, in, 5) + 1;
+  /*number of code length codes. Unlike the spec, the value 4 is added to it here already*/
+  HCLEN = readBitsFromStream(bp, in, 4) + 4;
+
+  if((*bp) + HCLEN * 3 > (inlength << 3)) return 50; /*error: the bit pointer is or will go past the memory*/
+
+  HuffmanTree_init(&tree_cl);
+
+  while(!error)
+  {
+    /*read the code length codes out of 3 * (amount of code length codes) bits*/
+
+    bitlen_cl = (unsigned*)lodepng_malloc(NUM_CODE_LENGTH_CODES * sizeof(unsigned));
+    if(!bitlen_cl) ERROR_BREAK(83 /*alloc fail*/);
+
+    for(i = 0; i != NUM_CODE_LENGTH_CODES; ++i)
+    {
+      if(i < HCLEN) bitlen_cl[CLCL_ORDER[i]] = readBitsFromStream(bp, in, 3);
+      else bitlen_cl[CLCL_ORDER[i]] = 0; /*if not, it must stay 0*/
+    }
+
+    error = HuffmanTree_makeFromLengths(&tree_cl, bitlen_cl, NUM_CODE_LENGTH_CODES, 7);
+    if(error) break;
+
+    /*now we can use this tree to read the lengths for the tree that this function will return*/
+    bitlen_ll = (unsigned*)lodepng_malloc(NUM_DEFLATE_CODE_SYMBOLS * sizeof(unsigned));
+    bitlen_d = (unsigned*)lodepng_malloc(NUM_DISTANCE_SYMBOLS * sizeof(unsigned));
+    if(!bitlen_ll || !bitlen_d) ERROR_BREAK(83 /*alloc fail*/);
+    for(i = 0; i != NUM_DEFLATE_CODE_SYMBOLS; ++i) bitlen_ll[i] = 0;
+    for(i = 0; i != NUM_DISTANCE_SYMBOLS; ++i) bitlen_d[i] = 0;
+
+    /*i is the current symbol we're reading in the part that contains the code lengths of lit/len and dist codes*/
+    i = 0;
+    while(i < HLIT + HDIST)
+    {
+      unsigned code = huffmanDecodeSymbol(in, bp, &tree_cl, inbitlength);
+      if(code <= 15) /*a length code*/
+      {
+        if(i < HLIT) bitlen_ll[i] = code;
+        else bitlen_d[i - HLIT] = code;
+        ++i;
+      }
+      else if(code == 16) /*repeat previous*/
+      {
+        unsigned replength = 3; /*read in the 2 bits that indicate repeat length (3-6)*/
+        unsigned value; /*set value to the previous code*/
+
+        if(i == 0) ERROR_BREAK(54); /*can't repeat previous if i is 0*/
+
+        if((*bp + 2) > inbitlength) ERROR_BREAK(50); /*error, bit pointer jumps past memory*/
+        replength += readBitsFromStream(bp, in, 2);
+
+        if(i < HLIT + 1) value = bitlen_ll[i - 1];
+        else value = bitlen_d[i - HLIT - 1];
+        /*repeat this value in the next lengths*/
+        for(n = 0; n < replength; ++n)
+        {
+          if(i >= HLIT + HDIST) ERROR_BREAK(13); /*error: i is larger than the amount of codes*/
+          if(i < HLIT) bitlen_ll[i] = value;
+          else bitlen_d[i - HLIT] = value;
+          ++i;
+        }
+      }
+      else if(code == 17) /*repeat "0" 3-10 times*/
+      {
+        unsigned replength = 3; /*read in the bits that indicate repeat length*/
+        if((*bp + 3) > inbitlength) ERROR_BREAK(50); /*error, bit pointer jumps past memory*/
+        replength += readBitsFromStream(bp, in, 3);
+
+        /*repeat this value in the next lengths*/
+        for(n = 0; n < replength; ++n)
+        {
+          if(i >= HLIT + HDIST) ERROR_BREAK(14); /*error: i is larger than the amount of codes*/
+
+          if(i < HLIT) bitlen_ll[i] = 0;
+          else bitlen_d[i - HLIT] = 0;
+          ++i;
+        }
+      }
+      else if(code == 18) /*repeat "0" 11-138 times*/
+      {
+        unsigned replength = 11; /*read in the bits that indicate repeat length*/
+        if((*bp + 7) > inbitlength) ERROR_BREAK(50); /*error, bit pointer jumps past memory*/
+        replength += readBitsFromStream(bp, in, 7);
+
+        /*repeat this value in the next lengths*/
+        for(n = 0; n < replength; ++n)
+        {
+          if(i >= HLIT + HDIST) ERROR_BREAK(15); /*error: i is larger than the amount of codes*/
+
+          if(i < HLIT) bitlen_ll[i] = 0;
+          else bitlen_d[i - HLIT] = 0;
+          ++i;
+        }
+      }
+      else /*if(code == (unsigned)(-1))*/ /*huffmanDecodeSymbol returns (unsigned)(-1) in case of error*/
+      {
+        if(code == (unsigned)(-1))
+        {
+          /*return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol
+          (10=no endcode, 11=wrong jump outside of tree)*/
+          error = (*bp) > inbitlength ? 10 : 11;
+        }
+        else error = 16; /*unexisting code, this can never happen*/
+        break;
+      }
+    }
+    if(error) break;
+
+    if(bitlen_ll[256] == 0) ERROR_BREAK(64); /*the length of the end code 256 must be larger than 0*/
+
+    /*now we've finally got HLIT and HDIST, so generate the code trees, and the function is done*/
+    error = HuffmanTree_makeFromLengths(tree_ll, bitlen_ll, NUM_DEFLATE_CODE_SYMBOLS, 15);
+    if(error) break;
+    error = HuffmanTree_makeFromLengths(tree_d, bitlen_d, NUM_DISTANCE_SYMBOLS, 15);
+
+    break; /*end of error-while*/
+  }
+
+  lodepng_free(bitlen_cl);
+  lodepng_free(bitlen_ll);
+  lodepng_free(bitlen_d);
+  HuffmanTree_cleanup(&tree_cl);
+
+  return error;
+}
+
+/*inflate a block with dynamic of fixed Huffman tree*/
+static unsigned inflateHuffmanBlock(ucvector* out, const unsigned char* in, size_t* bp,
+                                    size_t* pos, size_t inlength, unsigned btype)
+{
+  unsigned error = 0;
+  HuffmanTree tree_ll; /*the huffman tree for literal and length codes*/
+  HuffmanTree tree_d; /*the huffman tree for distance codes*/
+  size_t inbitlength = inlength * 8;
+
+  HuffmanTree_init(&tree_ll);
+  HuffmanTree_init(&tree_d);
+
+  if(btype == 1) getTreeInflateFixed(&tree_ll, &tree_d);
+  else if(btype == 2) error = getTreeInflateDynamic(&tree_ll, &tree_d, in, bp, inlength);
+
+  while(!error) /*decode all symbols until end reached, breaks at end code*/
+  {
+    /*code_ll is literal, length or end code*/
+    unsigned code_ll = huffmanDecodeSymbol(in, bp, &tree_ll, inbitlength);
+    if(code_ll <= 255) /*literal symbol*/
+    {
+      /*ucvector_push_back would do the same, but for some reason the two lines below run 10% faster*/
+      if(!ucvector_resize(out, (*pos) + 1)) ERROR_BREAK(83 /*alloc fail*/);
+      out->data[*pos] = (unsigned char)code_ll;
+      ++(*pos);
+    }
+    else if(code_ll >= FIRST_LENGTH_CODE_INDEX && code_ll <= LAST_LENGTH_CODE_INDEX) /*length code*/
+    {
+      unsigned code_d, distance;
+      unsigned numextrabits_l, numextrabits_d; /*extra bits for length and distance*/
+      size_t start, forward, backward, length;
+
+      /*part 1: get length base*/
+      length = LENGTHBASE[code_ll - FIRST_LENGTH_CODE_INDEX];
+
+      /*part 2: get extra bits and add the value of that to length*/
+      numextrabits_l = LENGTHEXTRA[code_ll - FIRST_LENGTH_CODE_INDEX];
+      if((*bp + numextrabits_l) > inbitlength) ERROR_BREAK(51); /*error, bit pointer will jump past memory*/
+      length += readBitsFromStream(bp, in, numextrabits_l);
+
+      /*part 3: get distance code*/
+      code_d = huffmanDecodeSymbol(in, bp, &tree_d, inbitlength);
+      if(code_d > 29)
+      {
+        if(code_ll == (unsigned)(-1)) /*huffmanDecodeSymbol returns (unsigned)(-1) in case of error*/
+        {
+          /*return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol
+          (10=no endcode, 11=wrong jump outside of tree)*/
+          error = (*bp) > inlength * 8 ? 10 : 11;
+        }
+        else error = 18; /*error: invalid distance code (30-31 are never used)*/
+        break;
+      }
+      distance = DISTANCEBASE[code_d];
+
+      /*part 4: get extra bits from distance*/
+      numextrabits_d = DISTANCEEXTRA[code_d];
+      if((*bp + numextrabits_d) > inbitlength) ERROR_BREAK(51); /*error, bit pointer will jump past memory*/
+      distance += readBitsFromStream(bp, in, numextrabits_d);
+
+      /*part 5: fill in all the out[n] values based on the length and dist*/
+      start = (*pos);
+      if(distance > start) ERROR_BREAK(52); /*too long backward distance*/
+      backward = start - distance;
+
+      if(!ucvector_resize(out, (*pos) + length)) ERROR_BREAK(83 /*alloc fail*/);
+      if (distance < length) {
+        for(forward = 0; forward < length; ++forward)
+        {
+          out->data[(*pos)++] = out->data[backward++];
+        }
+      } else {
+        memcpy(out->data + *pos, out->data + backward, length);
+        *pos += length;
+      }
+    }
+    else if(code_ll == 256)
+    {
+      break; /*end code, break the loop*/
+    }
+    else /*if(code == (unsigned)(-1))*/ /*huffmanDecodeSymbol returns (unsigned)(-1) in case of error*/
+    {
+      /*return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol
+      (10=no endcode, 11=wrong jump outside of tree)*/
+      error = ((*bp) > inlength * 8) ? 10 : 11;
+      break;
+    }
+  }
+
+  HuffmanTree_cleanup(&tree_ll);
+  HuffmanTree_cleanup(&tree_d);
+
+  return error;
+}
+
+static unsigned inflateNoCompression(ucvector* out, const unsigned char* in, size_t* bp, size_t* pos, size_t inlength)
+{
+  size_t p;
+  unsigned LEN, NLEN, n, error = 0;
+
+  /*go to first boundary of byte*/
+  while(((*bp) & 0x7) != 0) ++(*bp);
+  p = (*bp) / 8; /*byte position*/
+
+  /*read LEN (2 bytes) and NLEN (2 bytes)*/
+  if(p + 4 >= inlength) return 52; /*error, bit pointer will jump past memory*/
+  LEN = in[p] + 256u * in[p + 1]; p += 2;
+  NLEN = in[p] + 256u * in[p + 1]; p += 2;
+
+  /*check if 16-bit NLEN is really the one's complement of LEN*/
+  if(LEN + NLEN != 65535) return 21; /*error: NLEN is not one's complement of LEN*/
+
+  if(!ucvector_resize(out, (*pos) + LEN)) return 83; /*alloc fail*/
+
+  /*read the literal data: LEN bytes are now stored in the out buffer*/
+  if(p + LEN > inlength) return 23; /*error: reading outside of in buffer*/
+  for(n = 0; n < LEN; ++n) out->data[(*pos)++] = in[p++];
+
+  (*bp) = p * 8;
+
+  return error;
+}
+
+static unsigned lodepng_inflatev(ucvector* out,
+                                 const unsigned char* in, size_t insize,
+                                 const LodePNGDecompressSettings* settings)
+{
+  /*bit pointer in the "in" data, current byte is bp >> 3, current bit is bp & 0x7 (from lsb to msb of the byte)*/
+  size_t bp = 0;
+  unsigned BFINAL = 0;
+  size_t pos = 0; /*byte position in the out buffer*/
+  unsigned error = 0;
+
+  (void)settings;
+
+  while(!BFINAL)
+  {
+    unsigned BTYPE;
+    if(bp + 2 >= insize * 8) return 52; /*error, bit pointer will jump past memory*/
+    BFINAL = readBitFromStream(&bp, in);
+    BTYPE = 1u * readBitFromStream(&bp, in);
+    BTYPE += 2u * readBitFromStream(&bp, in);
+
+    if(BTYPE == 3) return 20; /*error: invalid BTYPE*/
+    else if(BTYPE == 0) error = inflateNoCompression(out, in, &bp, &pos, insize); /*no compression*/
+    else error = inflateHuffmanBlock(out, in, &bp, &pos, insize, BTYPE); /*compression, BTYPE 01 or 10*/
+
+    if(error) return error;
+  }
+
+  return error;
+}
+
+unsigned lodepng_inflate(unsigned char** out, size_t* outsize,
+                         const unsigned char* in, size_t insize,
+                         const LodePNGDecompressSettings* settings)
+{
+  unsigned error;
+  ucvector v;
+  ucvector_init_buffer(&v, *out, *outsize);
+  error = lodepng_inflatev(&v, in, insize, settings);
+  *out = v.data;
+  *outsize = v.size;
+  return error;
+}
+
+static unsigned inflate(unsigned char** out, size_t* outsize,
+                        const unsigned char* in, size_t insize,
+                        const LodePNGDecompressSettings* settings)
+{
+  if(settings->custom_inflate)
+  {
+    return settings->custom_inflate(out, outsize, in, insize, settings);
+  }
+  else
+  {
+    return lodepng_inflate(out, outsize, in, insize, settings);
+  }
+}
+
+#endif /*LODEPNG_COMPILE_DECODER*/
+
+#ifdef LODEPNG_COMPILE_ENCODER
+
+/* ////////////////////////////////////////////////////////////////////////// */
+/* / Deflator (Compressor)                                                  / */
+/* ////////////////////////////////////////////////////////////////////////// */
+
+static const size_t MAX_SUPPORTED_DEFLATE_LENGTH = 258;
+
+/*bitlen is the size in bits of the code*/
+static void addHuffmanSymbol(size_t* bp, ucvector* compressed, unsigned code, unsigned bitlen)
+{
+  addBitsToStreamReversed(bp, compressed, code, bitlen);
+}
+
+/*search the index in the array, that has the largest value smaller than or equal to the given value,
+given array must be sorted (if no value is smaller, it returns the size of the given array)*/
+static size_t searchCodeIndex(const unsigned* array, size_t array_size, size_t value)
+{
+  /*binary search (only small gain over linear). TODO: use CPU log2 instruction for getting symbols instead*/
+  size_t left = 1;
+  size_t right = array_size - 1;
+
+  while(left <= right) {
+    size_t mid = (left + right) >> 1;
+    if (array[mid] >= value) right = mid - 1;
+    else left = mid + 1;
+  }
+  if(left >= array_size || array[left] > value) left--;
+  return left;
+}
+
+static void addLengthDistance(uivector* values, size_t length, size_t distance)
+{
+  /*values in encoded vector are those used by deflate:
+  0-255: literal bytes
+  256: end
+  257-285: length/distance pair (length code, followed by extra length bits, distance code, extra distance bits)
+  286-287: invalid*/
+
+  unsigned length_code = (unsigned)searchCodeIndex(LENGTHBASE, 29, length);
+  unsigned extra_length = (unsigned)(length - LENGTHBASE[length_code]);
+  unsigned dist_code = (unsigned)searchCodeIndex(DISTANCEBASE, 30, distance);
+  unsigned extra_distance = (unsigned)(distance - DISTANCEBASE[dist_code]);
+
+  uivector_push_back(values, length_code + FIRST_LENGTH_CODE_INDEX);
+  uivector_push_back(values, extra_length);
+  uivector_push_back(values, dist_code);
+  uivector_push_back(values, extra_distance);
+}
+
+/*3 bytes of data get encoded into two bytes. The hash cannot use more than 3
+bytes as input because 3 is the minimum match length for deflate*/
+static const unsigned HASH_NUM_VALUES = 65536;
+static const unsigned HASH_BIT_MASK = 65535; /*HASH_NUM_VALUES - 1, but C90 does not like that as initializer*/
+
+typedef struct Hash
+{
+  int* head; /*hash value to head circular pos - can be outdated if went around window*/
+  /*circular pos to prev circular pos*/
+  unsigned short* chain;
+  int* val; /*circular pos to hash value*/
+
+  /*TODO: do this not only for zeros but for any repeated byte. However for PNG
+  it's always going to be the zeros that dominate, so not important for PNG*/
+  int* headz; /*similar to head, but for chainz*/
+  unsigned short* chainz; /*those with same amount of zeros*/
+  unsigned short* zeros; /*length of zeros streak, used as a second hash chain*/
+} Hash;
+
+static unsigned hash_init(Hash* hash, unsigned windowsize)
+{
+  unsigned i;
+  hash->head = (int*)lodepng_malloc(sizeof(int) * HASH_NUM_VALUES);
+  hash->val = (int*)lodepng_malloc(sizeof(int) * windowsize);
+  hash->chain = (unsigned short*)lodepng_malloc(sizeof(unsigned short) * windowsize);
+
+  hash->zeros = (unsigned short*)lodepng_malloc(sizeof(unsigned short) * windowsize);
+  hash->headz = (int*)lodepng_malloc(sizeof(int) * (MAX_SUPPORTED_DEFLATE_LENGTH + 1));
+  hash->chainz = (unsigned short*)lodepng_malloc(sizeof(unsigned short) * windowsize);
+
+  if(!hash->head || !hash->chain || !hash->val  || !hash->headz|| !hash->chainz || !hash->zeros)
+  {
+    return 83; /*alloc fail*/
+  }
+
+  /*initialize hash table*/
+  for(i = 0; i != HASH_NUM_VALUES; ++i) hash->head[i] = -1;
+  for(i = 0; i != windowsize; ++i) hash->val[i] = -1;
+  for(i = 0; i != windowsize; ++i) hash->chain[i] = i; /*same value as index indicates uninitialized*/
+
+  for(i = 0; i <= MAX_SUPPORTED_DEFLATE_LENGTH; ++i) hash->headz[i] = -1;
+  for(i = 0; i != windowsize; ++i) hash->chainz[i] = i; /*same value as index indicates uninitialized*/
+
+  return 0;
+}
+
+static void hash_cleanup(Hash* hash)
+{
+  lodepng_free(hash->head);
+  lodepng_free(hash->val);
+  lodepng_free(hash->chain);
+
+  lodepng_free(hash->zeros);
+  lodepng_free(hash->headz);
+  lodepng_free(hash->chainz);
+}
+
+
+
+static unsigned getHash(const unsigned char* data, size_t size, size_t pos)
+{
+  unsigned result = 0;
+  if(pos + 2 < size)
+  {
+    /*A simple shift and xor hash is used. Since the data of PNGs is dominated
+    by zeroes due to the filters, a better hash does not have a significant
+    effect on speed in traversing the chain, and causes more time spend on
+    calculating the hash.*/
+    result ^= (unsigned)(data[pos + 0] << 0u);
+    result ^= (unsigned)(data[pos + 1] << 4u);
+    result ^= (unsigned)(data[pos + 2] << 8u);
+  } else {
+    size_t amount, i;
+    if(pos >= size) return 0;
+    amount = size - pos;
+    for(i = 0; i != amount; ++i) result ^= (unsigned)(data[pos + i] << (i * 8u));
+  }
+  return result & HASH_BIT_MASK;
+}
+
+static unsigned countZeros(const unsigned char* data, size_t size, size_t pos)
+{
+  const unsigned char* start = data + pos;
+  const unsigned char* end = start + MAX_SUPPORTED_DEFLATE_LENGTH;
+  if(end > data + size) end = data + size;
+  data = start;
+  while(data != end && *data == 0) ++data;
+  /*subtracting two addresses returned as 32-bit number (max value is MAX_SUPPORTED_DEFLATE_LENGTH)*/
+  return (unsigned)(data - start);
+}
+
+/*wpos = pos & (windowsize - 1)*/
+static void updateHashChain(Hash* hash, size_t wpos, unsigned hashval, unsigned short numzeros)
+{
+  hash->val[wpos] = (int)hashval;
+  if(hash->head[hashval] != -1) hash->chain[wpos] = hash->head[hashval];
+  hash->head[hashval] = wpos;
+
+  hash->zeros[wpos] = numzeros;
+  if(hash->headz[numzeros] != -1) hash->chainz[wpos] = hash->headz[numzeros];
+  hash->headz[numzeros] = wpos;
+}
+
+/*
+LZ77-encode the data. Return value is error code. The input are raw bytes, the output
+is in the form of unsigned integers with codes representing for example literal bytes, or
+length/distance pairs.
+It uses a hash table technique to let it encode faster. When doing LZ77 encoding, a
+sliding window (of windowsize) is used, and all past bytes in that window can be used as
+the "dictionary". A brute force search through all possible distances would be slow, and
+this hash technique is one out of several ways to speed this up.
+*/
+static unsigned encodeLZ77(uivector* out, Hash* hash,
+                           const unsigned char* in, size_t inpos, size_t insize, unsigned windowsize,
+                           unsigned minmatch, unsigned nicematch, unsigned lazymatching)
+{
+  size_t pos;
+  unsigned i, error = 0;
+  /*for large window lengths, assume the user wants no compression loss. Otherwise, max hash chain length speedup.*/
+  unsigned maxchainlength = windowsize >= 8192 ? windowsize : windowsize / 8;
+  unsigned maxlazymatch = windowsize >= 8192 ? MAX_SUPPORTED_DEFLATE_LENGTH : 64;
+
+  unsigned usezeros = 1; /*not sure if setting it to false for windowsize < 8192 is better or worse*/
+  unsigned numzeros = 0;
+
+  unsigned offset; /*the offset represents the distance in LZ77 terminology*/
+  unsigned length;
+  unsigned lazy = 0;
+  unsigned lazylength = 0, lazyoffset = 0;
+  unsigned hashval;
+  unsigned current_offset, current_length;
+  unsigned prev_offset;
+  const unsigned char *lastptr, *foreptr, *backptr;
+  unsigned hashpos;
+
+  if(windowsize == 0 || windowsize > 32768) return 60; /*error: windowsize smaller/larger than allowed*/
+  if((windowsize & (windowsize - 1)) != 0) return 90; /*error: must be power of two*/
+
+  if(nicematch > MAX_SUPPORTED_DEFLATE_LENGTH) nicematch = MAX_SUPPORTED_DEFLATE_LENGTH;
+
+  for(pos = inpos; pos < insize; ++pos)
+  {
+    size_t wpos = pos & (windowsize - 1); /*position for in 'circular' hash buffers*/
+    unsigned chainlength = 0;
+
+    hashval = getHash(in, insize, pos);
+
+    if(usezeros && hashval == 0)
+    {
+      if(numzeros == 0) numzeros = countZeros(in, insize, pos);
+      else if(pos + numzeros > insize || in[pos + numzeros - 1] != 0) --numzeros;
+    }
+    else
+    {
+      numzeros = 0;
+    }
+
+    updateHashChain(hash, wpos, hashval, numzeros);
+
+    /*the length and offset found for the current position*/
+    length = 0;
+    offset = 0;
+
+    hashpos = hash->chain[wpos];
+
+    lastptr = &in[insize < pos + MAX_SUPPORTED_DEFLATE_LENGTH ? insize : pos + MAX_SUPPORTED_DEFLATE_LENGTH];
+
+    /*search for the longest string*/
+    prev_offset = 0;
+    for(;;)
+    {
+      if(chainlength++ >= maxchainlength) break;
+      current_offset = hashpos <= wpos ? wpos - hashpos : wpos - hashpos + windowsize;
+
+      if(current_offset < prev_offset) break; /*stop when went completely around the circular buffer*/
+      prev_offset = current_offset;
+      if(current_offset > 0)
+      {
+        /*test the next characters*/
+        foreptr = &in[pos];
+        backptr = &in[pos - current_offset];
+
+        /*common case in PNGs is lots of zeros. Quickly skip over them as a speedup*/
+        if(numzeros >= 3)
+        {
+          unsigned skip = hash->zeros[hashpos];
+          if(skip > numzeros) skip = numzeros;
+          backptr += skip;
+          foreptr += skip;
+        }
+
+        while(foreptr != lastptr && *backptr == *foreptr) /*maximum supported length by deflate is max length*/
+        {
+          ++backptr;
+          ++foreptr;
+        }
+        current_length = (unsigned)(foreptr - &in[pos]);
+
+        if(current_length > length)
+        {
+          length = current_length; /*the longest length*/
+          offset = current_offset; /*the offset that is related to this longest length*/
+          /*jump out once a length of max length is found (speed gain). This also jumps
+          out if length is MAX_SUPPORTED_DEFLATE_LENGTH*/
+          if(current_length >= nicematch) break;
+        }
+      }
+
+      if(hashpos == hash->chain[hashpos]) break;
+
+      if(numzeros >= 3 && length > numzeros)
+      {
+        hashpos = hash->chainz[hashpos];
+        if(hash->zeros[hashpos] != numzeros) break;
+      }
+      else
+      {
+        hashpos = hash->chain[hashpos];
+        /*outdated hash value, happens if particular value was not encountered in whole last window*/
+        if(hash->val[hashpos] != (int)hashval) break;
+      }
+    }
+
+    if(lazymatching)
+    {
+      if(!lazy && length >= 3 && length <= maxlazymatch && length < MAX_SUPPORTED_DEFLATE_LENGTH)
+      {
+        lazy = 1;
+        lazylength = length;
+        lazyoffset = offset;
+        continue; /*try the next byte*/
+      }
+      if(lazy)
+      {
+        lazy = 0;
+        if(pos == 0) ERROR_BREAK(81);
+        if(length > lazylength + 1)
+        {
+          /*push the previous character as literal*/
+          if(!uivector_push_back(out, in[pos - 1])) ERROR_BREAK(83 /*alloc fail*/);
+        }
+        else
+        {
+          length = lazylength;
+          offset = lazyoffset;
+          hash->head[hashval] = -1; /*the same hashchain update will be done, this ensures no wrong alteration*/
+          hash->headz[numzeros] = -1; /*idem*/
+          --pos;
+        }
+      }
+    }
+    if(length >= 3 && offset > windowsize) ERROR_BREAK(86 /*too big (or overflown negative) offset*/);
+
+    /*encode it as length/distance pair or literal value*/
+    if(length < 3) /*only lengths of 3 or higher are supported as length/distance pair*/
+    {
+      if(!uivector_push_back(out, in[pos])) ERROR_BREAK(83 /*alloc fail*/);
+    }
+    else if(length < minmatch || (length == 3 && offset > 4096))
+    {
+      /*compensate for the fact that longer offsets have more extra bits, a
+      length of only 3 may be not worth it then*/
+      if(!uivector_push_back(out, in[pos])) ERROR_BREAK(83 /*alloc fail*/);
+    }
+    else
+    {
+      addLengthDistance(out, length, offset);
+      for(i = 1; i < length; ++i)
+      {
+        ++pos;
+        wpos = pos & (windowsize - 1);
+        hashval = getHash(in, insize, pos);
+        if(usezeros && hashval == 0)
+        {
+          if(numzeros == 0) numzeros = countZeros(in, insize, pos);
+          else if(pos + numzeros > insize || in[pos + numzeros - 1] != 0) --numzeros;
+        }
+        else
+        {
+          numzeros = 0;
+        }
+        updateHashChain(hash, wpos, hashval, numzeros);
+      }
+    }
+  } /*end of the loop through each character of input*/
+
+  return error;
+}
+
+/* /////////////////////////////////////////////////////////////////////////// */
+
+static unsigned deflateNoCompression(ucvector* out, const unsigned char* data, size_t datasize)
+{
+  /*non compressed deflate block data: 1 bit BFINAL,2 bits BTYPE,(5 bits): it jumps to start of next byte,
+  2 bytes LEN, 2 bytes NLEN, LEN bytes literal DATA*/
+
+  size_t i, j, numdeflateblocks = (datasize + 65534) / 65535;
+  unsigned datapos = 0;
+  for(i = 0; i != numdeflateblocks; ++i)
+  {
+    unsigned BFINAL, BTYPE, LEN, NLEN;
+    unsigned char firstbyte;
+
+    BFINAL = (i == numdeflateblocks - 1);
+    BTYPE = 0;
+
+    firstbyte = (unsigned char)(BFINAL + ((BTYPE & 1) << 1) + ((BTYPE & 2) << 1));
+    ucvector_push_back(out, firstbyte);
+
+    LEN = 65535;
+    if(datasize - datapos < 65535) LEN = (unsigned)datasize - datapos;
+    NLEN = 65535 - LEN;
+
+    ucvector_push_back(out, (unsigned char)(LEN & 255));
+    ucvector_push_back(out, (unsigned char)(LEN >> 8));
+    ucvector_push_back(out, (unsigned char)(NLEN & 255));
+    ucvector_push_back(out, (unsigned char)(NLEN >> 8));
+
+    /*Decompressed data*/
+    for(j = 0; j < 65535 && datapos < datasize; ++j)
+    {
+      ucvector_push_back(out, data[datapos++]);
+    }
+  }
+
+  return 0;
+}
+
+/*
+write the lz77-encoded data, which has lit, len and dist codes, to compressed stream using huffman trees.
+tree_ll: the tree for lit and len codes.
+tree_d: the tree for distance codes.
+*/
+static void writeLZ77data(size_t* bp, ucvector* out, const uivector* lz77_encoded,
+                          const HuffmanTree* tree_ll, const HuffmanTree* tree_d)
+{
+  size_t i = 0;
+  for(i = 0; i != lz77_encoded->size; ++i)
+  {
+    unsigned val = lz77_encoded->data[i];
+    addHuffmanSymbol(bp, out, HuffmanTree_getCode(tree_ll, val), HuffmanTree_getLength(tree_ll, val));
+    if(val > 256) /*for a length code, 3 more things have to be added*/
+    {
+      unsigned length_index = val - FIRST_LENGTH_CODE_INDEX;
+      unsigned n_length_extra_bits = LENGTHEXTRA[length_index];
+      unsigned length_extra_bits = lz77_encoded->data[++i];
+
+      unsigned distance_code = lz77_encoded->data[++i];
+
+      unsigned distance_index = distance_code;
+      unsigned n_distance_extra_bits = DISTANCEEXTRA[distance_index];
+      unsigned distance_extra_bits = lz77_encoded->data[++i];
+
+      addBitsToStream(bp, out, length_extra_bits, n_length_extra_bits);
+      addHuffmanSymbol(bp, out, HuffmanTree_getCode(tree_d, distance_code),
+                       HuffmanTree_getLength(tree_d, distance_code));
+      addBitsToStream(bp, out, distance_extra_bits, n_distance_extra_bits);
+    }
+  }
+}
+
+/*Deflate for a block of type "dynamic", that is, with freely, optimally, created huffman trees*/
+static unsigned deflateDynamic(ucvector* out, size_t* bp, Hash* hash,
+                               const unsigned char* data, size_t datapos, size_t dataend,
+                               const LodePNGCompressSettings* settings, unsigned final)
+{
+  unsigned error = 0;
+
+  /*
+  A block is compressed as follows: The PNG data is lz77 encoded, resulting in
+  literal bytes and length/distance pairs. This is then huffman compressed with
+  two huffman trees. One huffman tree is used for the lit and len values ("ll"),
+  another huffman tree is used for the dist values ("d"). These two trees are
+  stored using their code lengths, and to compress even more these code lengths
+  are also run-length encoded and huffman compressed. This gives a huffman tree
+  of code lengths "cl". The code lenghts used to describe this third tree are
+  the code length code lengths ("clcl").
+  */
+
+  /*The lz77 encoded data, represented with integers since there will also be length and distance codes in it*/
+  uivector lz77_encoded;
+  HuffmanTree tree_ll; /*tree for lit,len values*/
+  HuffmanTree tree_d; /*tree for distance codes*/
+  HuffmanTree tree_cl; /*tree for encoding the code lengths representing tree_ll and tree_d*/
+  uivector frequencies_ll; /*frequency of lit,len codes*/
+  uivector frequencies_d; /*frequency of dist codes*/
+  uivector frequencies_cl; /*frequency of code length codes*/
+  uivector bitlen_lld; /*lit,len,dist code lenghts (int bits), literally (without repeat codes).*/
+  uivector bitlen_lld_e; /*bitlen_lld encoded with repeat codes (this is a rudemtary run length compression)*/
+  /*bitlen_cl is the code length code lengths ("clcl"). The bit lengths of codes to represent tree_cl
+  (these are written as is in the file, it would be crazy to compress these using yet another huffman
+  tree that needs to be represented by yet another set of code lengths)*/
+  uivector bitlen_cl;
+  size_t datasize = dataend - datapos;
+
+  /*
+  Due to the huffman compression of huffman tree representations ("two levels"), there are some anologies:
+  bitlen_lld is to tree_cl what data is to tree_ll and tree_d.
+  bitlen_lld_e is to bitlen_lld what lz77_encoded is to data.
+  bitlen_cl is to bitlen_lld_e what bitlen_lld is to lz77_encoded.
+  */
+
+  unsigned BFINAL = final;
+  size_t numcodes_ll, numcodes_d, i;
+  unsigned HLIT, HDIST, HCLEN;
+
+  uivector_init(&lz77_encoded);
+  HuffmanTree_init(&tree_ll);
+  HuffmanTree_init(&tree_d);
+  HuffmanTree_init(&tree_cl);
+  uivector_init(&frequencies_ll);
+  uivector_init(&frequencies_d);
+  uivector_init(&frequencies_cl);
+  uivector_init(&bitlen_lld);
+  uivector_init(&bitlen_lld_e);
+  uivector_init(&bitlen_cl);
+
+  /*This while loop never loops due to a break at the end, it is here to
+  allow breaking out of it to the cleanup phase on error conditions.*/
+  while(!error)
+  {
+    if(settings->use_lz77)
+    {
+      error = encodeLZ77(&lz77_encoded, hash, data, datapos, dataend, settings->windowsize,
+                         settings->minmatch, settings->nicematch, settings->lazymatching);
+      if(error) break;
+    }
+    else
+    {
+      if(!uivector_resize(&lz77_encoded, datasize)) ERROR_BREAK(83 /*alloc fail*/);
+      for(i = datapos; i < dataend; ++i) lz77_encoded.data[i - datapos] = data[i]; /*no LZ77, but still will be Huffman compressed*/
+    }
+
+    if(!uivector_resizev(&frequencies_ll, 286, 0)) ERROR_BREAK(83 /*alloc fail*/);
+    if(!uivector_resizev(&frequencies_d, 30, 0)) ERROR_BREAK(83 /*alloc fail*/);
+
+    /*Count the frequencies of lit, len and dist codes*/
+    for(i = 0; i != lz77_encoded.size; ++i)
+    {
+      unsigned symbol = lz77_encoded.data[i];
+      ++frequencies_ll.data[symbol];
+      if(symbol > 256)
+      {
+        unsigned dist = lz77_encoded.data[i + 2];
+        ++frequencies_d.data[dist];
+        i += 3;
+      }
+    }
+    frequencies_ll.data[256] = 1; /*there will be exactly 1 end code, at the end of the block*/
+
+    /*Make both huffman trees, one for the lit and len codes, one for the dist codes*/
+    error = HuffmanTree_makeFromFrequencies(&tree_ll, frequencies_ll.data, 257, frequencies_ll.size, 15);
+    if(error) break;
+    /*2, not 1, is chosen for mincodes: some buggy PNG decoders require at least 2 symbols in the dist tree*/
+    error = HuffmanTree_makeFromFrequencies(&tree_d, frequencies_d.data, 2, frequencies_d.size, 15);
+    if(error) break;
+
+    numcodes_ll = tree_ll.numcodes; if(numcodes_ll > 286) numcodes_ll = 286;
+    numcodes_d = tree_d.numcodes; if(numcodes_d > 30) numcodes_d = 30;
+    /*store the code lengths of both generated trees in bitlen_lld*/
+    for(i = 0; i != numcodes_ll; ++i) uivector_push_back(&bitlen_lld, HuffmanTree_getLength(&tree_ll, (unsigned)i));
+    for(i = 0; i != numcodes_d; ++i) uivector_push_back(&bitlen_lld, HuffmanTree_getLength(&tree_d, (unsigned)i));
+
+    /*run-length compress bitlen_ldd into bitlen_lld_e by using repeat codes 16 (copy length 3-6 times),
+    17 (3-10 zeroes), 18 (11-138 zeroes)*/
+    for(i = 0; i != (unsigned)bitlen_lld.size; ++i)
+    {
+      unsigned j = 0; /*amount of repititions*/
+      while(i + j + 1 < (unsigned)bitlen_lld.size && bitlen_lld.data[i + j + 1] == bitlen_lld.data[i]) ++j;
+
+      if(bitlen_lld.data[i] == 0 && j >= 2) /*repeat code for zeroes*/
+      {
+        ++j; /*include the first zero*/
+        if(j <= 10) /*repeat code 17 supports max 10 zeroes*/
+        {
+          uivector_push_back(&bitlen_lld_e, 17);
+          uivector_push_back(&bitlen_lld_e, j - 3);
+        }
+        else /*repeat code 18 supports max 138 zeroes*/
+        {
+          if(j > 138) j = 138;
+          uivector_push_back(&bitlen_lld_e, 18);
+          uivector_push_back(&bitlen_lld_e, j - 11);
+        }
+        i += (j - 1);
+      }
+      else if(j >= 3) /*repeat code for value other than zero*/
+      {
+        size_t k;
+        unsigned num = j / 6, rest = j % 6;
+        uivector_push_back(&bitlen_lld_e, bitlen_lld.data[i]);
+        for(k = 0; k < num; ++k)
+        {
+          uivector_push_back(&bitlen_lld_e, 16);
+          uivector_push_back(&bitlen_lld_e, 6 - 3);
+        }
+        if(rest >= 3)
+        {
+          uivector_push_back(&bitlen_lld_e, 16);
+          uivector_push_back(&bitlen_lld_e, rest - 3);
+        }
+        else j -= rest;
+        i += j;
+      }
+      else /*too short to benefit from repeat code*/
+      {
+        uivector_push_back(&bitlen_lld_e, bitlen_lld.data[i]);
+      }
+    }
+
+    /*generate tree_cl, the huffmantree of huffmantrees*/
+
+    if(!uivector_resizev(&frequencies_cl, NUM_CODE_LENGTH_CODES, 0)) ERROR_BREAK(83 /*alloc fail*/);
+    for(i = 0; i != bitlen_lld_e.size; ++i)
+    {
+      ++frequencies_cl.data[bitlen_lld_e.data[i]];
+      /*after a repeat code come the bits that specify the number of repetitions,
+      those don't need to be in the frequencies_cl calculation*/
+      if(bitlen_lld_e.data[i] >= 16) ++i;
+    }
+
+    error = HuffmanTree_makeFromFrequencies(&tree_cl, frequencies_cl.data,
+                                            frequencies_cl.size, frequencies_cl.size, 7);
+    if(error) break;
+
+    if(!uivector_resize(&bitlen_cl, tree_cl.numcodes)) ERROR_BREAK(83 /*alloc fail*/);
+    for(i = 0; i != tree_cl.numcodes; ++i)
+    {
+      /*lenghts of code length tree is in the order as specified by deflate*/
+      bitlen_cl.data[i] = HuffmanTree_getLength(&tree_cl, CLCL_ORDER[i]);
+    }
+    while(bitlen_cl.data[bitlen_cl.size - 1] == 0 && bitlen_cl.size > 4)
+    {
+      /*remove zeros at the end, but minimum size must be 4*/
+      if(!uivector_resize(&bitlen_cl, bitlen_cl.size - 1)) ERROR_BREAK(83 /*alloc fail*/);
+    }
+    if(error) break;
+
+    /*
+    Write everything into the output
+
+    After the BFINAL and BTYPE, the dynamic block consists out of the following:
+    - 5 bits HLIT, 5 bits HDIST, 4 bits HCLEN
+    - (HCLEN+4)*3 bits code lengths of code length alphabet
+    - HLIT + 257 code lenghts of lit/length alphabet (encoded using the code length
+      alphabet, + possible repetition codes 16, 17, 18)
+    - HDIST + 1 code lengths of distance alphabet (encoded using the code length
+      alphabet, + possible repetition codes 16, 17, 18)
+    - compressed data
+    - 256 (end code)
+    */
+
+    /*Write block type*/
+    addBitToStream(bp, out, BFINAL);
+    addBitToStream(bp, out, 0); /*first bit of BTYPE "dynamic"*/
+    addBitToStream(bp, out, 1); /*second bit of BTYPE "dynamic"*/
+
+    /*write the HLIT, HDIST and HCLEN values*/
+    HLIT = (unsigned)(numcodes_ll - 257);
+    HDIST = (unsigned)(numcodes_d - 1);
+    HCLEN = (unsigned)bitlen_cl.size - 4;
+    /*trim zeroes for HCLEN. HLIT and HDIST were already trimmed at tree creation*/
+    while(!bitlen_cl.data[HCLEN + 4 - 1] && HCLEN > 0) --HCLEN;
+    addBitsToStream(bp, out, HLIT, 5);
+    addBitsToStream(bp, out, HDIST, 5);
+    addBitsToStream(bp, out, HCLEN, 4);
+
+    /*write the code lenghts of the code length alphabet*/
+    for(i = 0; i != HCLEN + 4; ++i) addBitsToStream(bp, out, bitlen_cl.data[i], 3);
+
+    /*write the lenghts of the lit/len AND the dist alphabet*/
+    for(i = 0; i != bitlen_lld_e.size; ++i)
+    {
+      addHuffmanSymbol(bp, out, HuffmanTree_getCode(&tree_cl, bitlen_lld_e.data[i]),
+                       HuffmanTree_getLength(&tree_cl, bitlen_lld_e.data[i]));
+      /*extra bits of repeat codes*/
+      if(bitlen_lld_e.data[i] == 16) addBitsToStream(bp, out, bitlen_lld_e.data[++i], 2);
+      else if(bitlen_lld_e.data[i] == 17) addBitsToStream(bp, out, bitlen_lld_e.data[++i], 3);
+      else if(bitlen_lld_e.data[i] == 18) addBitsToStream(bp, out, bitlen_lld_e.data[++i], 7);
+    }
+
+    /*write the compressed data symbols*/
+    writeLZ77data(bp, out, &lz77_encoded, &tree_ll, &tree_d);
+    /*error: the length of the end code 256 must be larger than 0*/
+    if(HuffmanTree_getLength(&tree_ll, 256) == 0) ERROR_BREAK(64);
+
+    /*write the end code*/
+    addHuffmanSymbol(bp, out, HuffmanTree_getCode(&tree_ll, 256), HuffmanTree_getLength(&tree_ll, 256));
+
+    break; /*end of error-while*/
+  }
+
+  /*cleanup*/
+  uivector_cleanup(&lz77_encoded);
+  HuffmanTree_cleanup(&tree_ll);
+  HuffmanTree_cleanup(&tree_d);
+  HuffmanTree_cleanup(&tree_cl);
+  uivector_cleanup(&frequencies_ll);
+  uivector_cleanup(&frequencies_d);
+  uivector_cleanup(&frequencies_cl);
+  uivector_cleanup(&bitlen_lld_e);
+  uivector_cleanup(&bitlen_lld);
+  uivector_cleanup(&bitlen_cl);
+
+  return error;
+}
+
+static unsigned deflateFixed(ucvector* out, size_t* bp, Hash* hash,
+                             const unsigned char* data,
+                             size_t datapos, size_t dataend,
+                             const LodePNGCompressSettings* settings, unsigned final)
+{
+  HuffmanTree tree_ll; /*tree for literal values and length codes*/
+  HuffmanTree tree_d; /*tree for distance codes*/
+
+  unsigned BFINAL = final;
+  unsigned error = 0;
+  size_t i;
+
+  HuffmanTree_init(&tree_ll);
+  HuffmanTree_init(&tree_d);
+
+  generateFixedLitLenTree(&tree_ll);
+  generateFixedDistanceTree(&tree_d);
+
+  addBitToStream(bp, out, BFINAL);
+  addBitToStream(bp, out, 1); /*first bit of BTYPE*/
+  addBitToStream(bp, out, 0); /*second bit of BTYPE*/
+
+  if(settings->use_lz77) /*LZ77 encoded*/
+  {
+    uivector lz77_encoded;
+    uivector_init(&lz77_encoded);
+    error = encodeLZ77(&lz77_encoded, hash, data, datapos, dataend, settings->windowsize,
+                       settings->minmatch, settings->nicematch, settings->lazymatching);
+    if(!error) writeLZ77data(bp, out, &lz77_encoded, &tree_ll, &tree_d);
+    uivector_cleanup(&lz77_encoded);
+  }
+  else /*no LZ77, but still will be Huffman compressed*/
+  {
+    for(i = datapos; i < dataend; ++i)
+    {
+      addHuffmanSymbol(bp, out, HuffmanTree_getCode(&tree_ll, data[i]), HuffmanTree_getLength(&tree_ll, data[i]));
+    }
+  }
+  /*add END code*/
+  if(!error) addHuffmanSymbol(bp, out, HuffmanTree_getCode(&tree_ll, 256), HuffmanTree_getLength(&tree_ll, 256));
+
+  /*cleanup*/
+  HuffmanTree_cleanup(&tree_ll);
+  HuffmanTree_cleanup(&tree_d);
+
+  return error;
+}
+
+static unsigned lodepng_deflatev(ucvector* out, const unsigned char* in, size_t insize,
+                                 const LodePNGCompressSettings* settings)
+{
+  unsigned error = 0;
+  size_t i, blocksize, numdeflateblocks;
+  size_t bp = 0; /*the bit pointer*/
+  Hash hash;
+
+  if(settings->btype > 2) return 61;
+  else if(settings->btype == 0) return deflateNoCompression(out, in, insize);
+  else if(settings->btype == 1) blocksize = insize;
+  else /*if(settings->btype == 2)*/
+  {
+    /*on PNGs, deflate blocks of 65-262k seem to give most dense encoding*/
+    blocksize = insize / 8 + 8;
+    if(blocksize < 65536) blocksize = 65536;
+    if(blocksize > 262144) blocksize = 262144;
+  }
+
+  numdeflateblocks = (insize + blocksize - 1) / blocksize;
+  if(numdeflateblocks == 0) numdeflateblocks = 1;
+
+  error = hash_init(&hash, settings->windowsize);
+  if(error) return error;
+
+  for(i = 0; i != numdeflateblocks && !error; ++i)
+  {
+    unsigned final = (i == numdeflateblocks - 1);
+    size_t start = i * blocksize;
+    size_t end = start + blocksize;
+    if(end > insize) end = insize;
+
+    if(settings->btype == 1) error = deflateFixed(out, &bp, &hash, in, start, end, settings, final);
+    else if(settings->btype == 2) error = deflateDynamic(out, &bp, &hash, in, start, end, settings, final);
+  }
+
+  hash_cleanup(&hash);
+
+  return error;
+}
+
+unsigned lodepng_deflate(unsigned char** out, size_t* outsize,
+                         const unsigned char* in, size_t insize,
+                         const LodePNGCompressSettings* settings)
+{
+  unsigned error;
+  ucvector v;
+  ucvector_init_buffer(&v, *out, *outsize);
+  error = lodepng_deflatev(&v, in, insize, settings);
+  *out = v.data;
+  *outsize = v.size;
+  return error;
+}
+
+static unsigned deflate(unsigned char** out, size_t* outsize,
+                        const unsigned char* in, size_t insize,
+                        const LodePNGCompressSettings* settings)
+{
+  if(settings->custom_deflate)
+  {
+    return settings->custom_deflate(out, outsize, in, insize, settings);
+  }
+  else
+  {
+    return lodepng_deflate(out, outsize, in, insize, settings);
+  }
+}
+
+#endif /*LODEPNG_COMPILE_DECODER*/
+
+/* ////////////////////////////////////////////////////////////////////////// */
+/* / Adler32                                                                  */
+/* ////////////////////////////////////////////////////////////////////////// */
+
+static unsigned update_adler32(unsigned adler, const unsigned char* data, unsigned len)
+{
+   unsigned s1 = adler & 0xffff;
+   unsigned s2 = (adler >> 16) & 0xffff;
+
+  while(len > 0)
+  {
+    /*at least 5550 sums can be done before the sums overflow, saving a lot of module divisions*/
+    unsigned amount = len > 5550 ? 5550 : len;
+    len -= amount;
+    while(amount > 0)
+    {
+      s1 += (*data++);
+      s2 += s1;
+      --amount;
+    }
+    s1 %= 65521;
+    s2 %= 65521;
+  }
+
+  return (s2 << 16) | s1;
+}
+
+/*Return the adler32 of the bytes data[0..len-1]*/
+static unsigned adler32(const unsigned char* data, unsigned len)
+{
+  return update_adler32(1L, data, len);
+}
+
+/* ////////////////////////////////////////////////////////////////////////// */
+/* / Zlib                                                                   / */
+/* ////////////////////////////////////////////////////////////////////////// */
+
+#ifdef LODEPNG_COMPILE_DECODER
+
+unsigned lodepng_zlib_decompress(unsigned char** out, size_t* outsize, const unsigned char* in,
+                                 size_t insize, const LodePNGDecompressSettings* settings)
+{
+  unsigned error = 0;
+  unsigned CM, CINFO, FDICT;
+
+  if(insize < 2) return 53; /*error, size of zlib data too small*/
+  /*read information from zlib header*/
+  if((in[0] * 256 + in[1]) % 31 != 0)
+  {
+    /*error: 256 * in[0] + in[1] must be a multiple of 31, the FCHECK value is supposed to be made that way*/
+    return 24;
+  }
+
+  CM = in[0] & 15;
+  CINFO = (in[0] >> 4) & 15;
+  /*FCHECK = in[1] & 31;*/ /*FCHECK is already tested above*/
+  FDICT = (in[1] >> 5) & 1;
+  /*FLEVEL = (in[1] >> 6) & 3;*/ /*FLEVEL is not used here*/
+
+  if(CM != 8 || CINFO > 7)
+  {
+    /*error: only compression method 8: inflate with sliding window of 32k is supported by the PNG spec*/
+    return 25;
+  }
+  if(FDICT != 0)
+  {
+    /*error: the specification of PNG says about the zlib stream:
+      "The additional flags shall not specify a preset dictionary."*/
+    return 26;
+  }
+
+  error = inflate(out, outsize, in + 2, insize - 2, settings);
+  if(error) return error;
+
+  if(!settings->ignore_adler32)
+  {
+    unsigned ADLER32 = lodepng_read32bitInt(&in[insize - 4]);
+    unsigned checksum = adler32(*out, (unsigned)(*outsize));
+    if(checksum != ADLER32) return 58; /*error, adler checksum not correct, data must be corrupted*/
+  }
+
+  return 0; /*no error*/
+}
+
+static unsigned zlib_decompress(unsigned char** out, size_t* outsize, const unsigned char* in,
+                                size_t insize, const LodePNGDecompressSettings* settings)
+{
+  if(settings->custom_zlib)
+  {
+    return settings->custom_zlib(out, outsize, in, insize, settings);
+  }
+  else
+  {
+    return lodepng_zlib_decompress(out, outsize, in, insize, settings);
+  }
+}
+
+#endif /*LODEPNG_COMPILE_DECODER*/
+
+#ifdef LODEPNG_COMPILE_ENCODER
+
+unsigned lodepng_zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in,
+                               size_t insize, const LodePNGCompressSettings* settings)
+{
+  /*initially, *out must be NULL and outsize 0, if you just give some random *out
+  that's pointing to a non allocated buffer, this'll crash*/
+  ucvector outv;
+  size_t i;
+  unsigned error;
+  unsigned char* deflatedata = 0;
+  size_t deflatesize = 0;
+
+  /*zlib data: 1 byte CMF (CM+CINFO), 1 byte FLG, deflate data, 4 byte ADLER32 checksum of the Decompressed data*/
+  unsigned CMF = 120; /*0b01111000: CM 8, CINFO 7. With CINFO 7, any window size up to 32768 can be used.*/
+  unsigned FLEVEL = 0;
+  unsigned FDICT = 0;
+  unsigned CMFFLG = 256 * CMF + FDICT * 32 + FLEVEL * 64;
+  unsigned FCHECK = 31 - CMFFLG % 31;
+  CMFFLG += FCHECK;
+
+  /*ucvector-controlled version of the output buffer, for dynamic array*/
+  ucvector_init_buffer(&outv, *out, *outsize);
+
+  ucvector_push_back(&outv, (unsigned char)(CMFFLG >> 8));
+  ucvector_push_back(&outv, (unsigned char)(CMFFLG & 255));
+
+  error = deflate(&deflatedata, &deflatesize, in, insize, settings);
+
+  if(!error)
+  {
+    unsigned ADLER32 = adler32(in, (unsigned)insize);
+    for(i = 0; i != deflatesize; ++i) ucvector_push_back(&outv, deflatedata[i]);
+    lodepng_free(deflatedata);
+    lodepng_add32bitInt(&outv, ADLER32);
+  }
+
+  *out = outv.data;
+  *outsize = outv.size;
+
+  return error;
+}
+
+/* compress using the default or custom zlib function */
+static unsigned zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in,
+                              size_t insize, const LodePNGCompressSettings* settings)
+{
+  if(settings->custom_zlib)
+  {
+    return settings->custom_zlib(out, outsize, in, insize, settings);
+  }
+  else
+  {
+    return lodepng_zlib_compress(out, outsize, in, insize, settings);
+  }
+}
+
+#endif /*LODEPNG_COMPILE_ENCODER*/
+
+#else /*no LODEPNG_COMPILE_ZLIB*/
+
+#ifdef LODEPNG_COMPILE_DECODER
+static unsigned zlib_decompress(unsigned char** out, size_t* outsize, const unsigned char* in,
+                                size_t insize, const LodePNGDecompressSettings* settings)
+{
+  if(!settings->custom_zlib) return 87; /*no custom zlib function provided */
+  return settings->custom_zlib(out, outsize, in, insize, settings);
+}
+#endif /*LODEPNG_COMPILE_DECODER*/
+#ifdef LODEPNG_COMPILE_ENCODER
+static unsigned zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in,
+                              size_t insize, const LodePNGCompressSettings* settings)
+{
+  if(!settings->custom_zlib) return 87; /*no custom zlib function provided */
+  return settings->custom_zlib(out, outsize, in, insize, settings);
+}
+#endif /*LODEPNG_COMPILE_ENCODER*/
+
+#endif /*LODEPNG_COMPILE_ZLIB*/
+
+/* ////////////////////////////////////////////////////////////////////////// */
+
+#ifdef LODEPNG_COMPILE_ENCODER
+
+/*this is a good tradeoff between speed and compression ratio*/
+#define DEFAULT_WINDOWSIZE 2048
+
+void lodepng_compress_settings_init(LodePNGCompressSettings* settings)
+{
+  /*compress with dynamic huffman tree (not in the mathematical sense, just not the predefined one)*/
+  settings->btype = 2;
+  settings->use_lz77 = 1;
+  settings->windowsize = DEFAULT_WINDOWSIZE;
+  settings->minmatch = 3;
+  settings->nicematch = 128;
+  settings->lazymatching = 1;
+
+  settings->custom_zlib = 0;
+  settings->custom_deflate = 0;
+  settings->custom_context = 0;
+}
+
+const LodePNGCompressSettings lodepng_default_compress_settings = {2, 1, DEFAULT_WINDOWSIZE, 3, 128, 1, 0, 0, 0};
+
+
+#endif /*LODEPNG_COMPILE_ENCODER*/
+
+#ifdef LODEPNG_COMPILE_DECODER
+
+void lodepng_decompress_settings_init(LodePNGDecompressSettings* settings)
+{
+  settings->ignore_adler32 = 0;
+
+  settings->custom_zlib = 0;
+  settings->custom_inflate = 0;
+  settings->custom_context = 0;
+}
+
+const LodePNGDecompressSettings lodepng_default_decompress_settings = {0, 0, 0, 0};
+
+#endif /*LODEPNG_COMPILE_DECODER*/
+
+/* ////////////////////////////////////////////////////////////////////////// */
+/* ////////////////////////////////////////////////////////////////////////// */
+/* // End of Zlib related code. Begin of PNG related code.                 // */
+/* ////////////////////////////////////////////////////////////////////////// */
+/* ////////////////////////////////////////////////////////////////////////// */
+
+#ifdef LODEPNG_COMPILE_PNG
+
+/* ////////////////////////////////////////////////////////////////////////// */
+/* / CRC32                                                                  / */
+/* ////////////////////////////////////////////////////////////////////////// */
+
+
+#ifndef LODEPNG_NO_COMPILE_CRC
+/* CRC polynomial: 0xedb88320 */
+static unsigned lodepng_crc32_table[256] = {
+           0u, 1996959894u, 3993919788u, 2567524794u,  124634137u, 1886057615u, 3915621685u, 2657392035u,
+   249268274u, 2044508324u, 3772115230u, 2547177864u,  162941995u, 2125561021u, 3887607047u, 2428444049u,
+   498536548u, 1789927666u, 4089016648u, 2227061214u,  450548861u, 1843258603u, 4107580753u, 2211677639u,
+   325883990u, 1684777152u, 4251122042u, 2321926636u,  335633487u, 1661365465u, 4195302755u, 2366115317u,
+   997073096u, 1281953886u, 3579855332u, 2724688242u, 1006888145u, 1258607687u, 3524101629u, 2768942443u,
+   901097722u, 1119000684u, 3686517206u, 2898065728u,  853044451u, 1172266101u, 3705015759u, 2882616665u,
+   651767980u, 1373503546u, 3369554304u, 3218104598u,  565507253u, 1454621731u, 3485111705u, 3099436303u,
+   671266974u, 1594198024u, 3322730930u, 2970347812u,  795835527u, 1483230225u, 3244367275u, 3060149565u,
+  1994146192u,   31158534u, 2563907772u, 4023717930u, 1907459465u,  112637215u, 2680153253u, 3904427059u,
+  2013776290u,  251722036u, 2517215374u, 3775830040u, 2137656763u,  141376813u, 2439277719u, 3865271297u,
+  1802195444u,  476864866u, 2238001368u, 4066508878u, 1812370925u,  453092731u, 2181625025u, 4111451223u,
+  1706088902u,  314042704u, 2344532202u, 4240017532u, 1658658271u,  366619977u, 2362670323u, 4224994405u,
+  1303535960u,  984961486u, 2747007092u, 3569037538u, 1256170817u, 1037604311u, 2765210733u, 3554079995u,
+  1131014506u,  879679996u, 2909243462u, 3663771856u, 1141124467u,  855842277u, 2852801631u, 3708648649u,
+  1342533948u,  654459306u, 3188396048u, 3373015174u, 1466479909u,  544179635u, 3110523913u, 3462522015u,
+  1591671054u,  702138776u, 2966460450u, 3352799412u, 1504918807u,  783551873u, 3082640443u, 3233442989u,
+  3988292384u, 2596254646u,   62317068u, 1957810842u, 3939845945u, 2647816111u,   81470997u, 1943803523u,
+  3814918930u, 2489596804u,  225274430u, 2053790376u, 3826175755u, 2466906013u,  167816743u, 2097651377u,
+  4027552580u, 2265490386u,  503444072u, 1762050814u, 4150417245u, 2154129355u,  426522225u, 1852507879u,
+  4275313526u, 2312317920u,  282753626u, 1742555852u, 4189708143u, 2394877945u,  397917763u, 1622183637u,
+  3604390888u, 2714866558u,  953729732u, 1340076626u, 3518719985u, 2797360999u, 1068828381u, 1219638859u,
+  3624741850u, 2936675148u,  906185462u, 1090812512u, 3747672003u, 2825379669u,  829329135u, 1181335161u,
+  3412177804u, 3160834842u,  628085408u, 1382605366u, 3423369109u, 3138078467u,  570562233u, 1426400815u,
+  3317316542u, 2998733608u,  733239954u, 1555261956u, 3268935591u, 3050360625u,  752459403u, 1541320221u,
+  2607071920u, 3965973030u, 1969922972u,   40735498u, 2617837225u, 3943577151u, 1913087877u,   83908371u,
+  2512341634u, 3803740692u, 2075208622u,  213261112u, 2463272603u, 3855990285u, 2094854071u,  198958881u,
+  2262029012u, 4057260610u, 1759359992u,  534414190u, 2176718541u, 4139329115u, 1873836001u,  414664567u,
+  2282248934u, 4279200368u, 1711684554u,  285281116u, 2405801727u, 4167216745u, 1634467795u,  376229701u,
+  2685067896u, 3608007406u, 1308918612u,  956543938u, 2808555105u, 3495958263u, 1231636301u, 1047427035u,
+  2932959818u, 3654703836u, 1088359270u,  936918000u, 2847714899u, 3736837829u, 1202900863u,  817233897u,
+  3183342108u, 3401237130u, 1404277552u,  615818150u, 3134207493u, 3453421203u, 1423857449u,  601450431u,
+  3009837614u, 3294710456u, 1567103746u,  711928724u, 3020668471u, 3272380065u, 1510334235u,  755167117u
+};
+
+/*Return the CRC of the bytes buf[0..len-1].*/
+unsigned lodepng_crc32(const unsigned char* data, size_t length)
+{
+  unsigned r = 0xffffffffu;
+  size_t i;
+  for(i = 0; i < length; ++i)
+  {
+    r = lodepng_crc32_table[(r ^ data[i]) & 0xff] ^ (r >> 8);
+  }
+  return r ^ 0xffffffffu;
+}
+#else /* !LODEPNG_NO_COMPILE_CRC */
+unsigned lodepng_crc32(const unsigned char* data, size_t length);
+#endif /* !LODEPNG_NO_COMPILE_CRC */
+
+/* ////////////////////////////////////////////////////////////////////////// */
+/* / Reading and writing single bits and bytes from/to stream for LodePNG   / */
+/* ////////////////////////////////////////////////////////////////////////// */
+
+static unsigned char readBitFromReversedStream(size_t* bitpointer, const unsigned char* bitstream)
+{
+  unsigned char result = (unsigned char)((bitstream[(*bitpointer) >> 3] >> (7 - ((*bitpointer) & 0x7))) & 1);
+  ++(*bitpointer);
+  return result;
+}
+
+static unsigned readBitsFromReversedStream(size_t* bitpointer, const unsigned char* bitstream, size_t nbits)
+{
+  unsigned result = 0;
+  size_t i;
+  for(i = 0 ; i < nbits; ++i)
+  {
+    result <<= 1;
+    result |= (unsigned)readBitFromReversedStream(bitpointer, bitstream);
+  }
+  return result;
+}
+
+#ifdef LODEPNG_COMPILE_DECODER
+static void setBitOfReversedStream0(size_t* bitpointer, unsigned char* bitstream, unsigned char bit)
+{
+  /*the current bit in bitstream must be 0 for this to work*/
+  if(bit)
+  {
+    /*earlier bit of huffman code is in a lesser significant bit of an earlier byte*/
+    bitstream[(*bitpointer) >> 3] |= (bit << (7 - ((*bitpointer) & 0x7)));
+  }
+  ++(*bitpointer);
+}
+#endif /*LODEPNG_COMPILE_DECODER*/
+
+static void setBitOfReversedStream(size_t* bitpointer, unsigned char* bitstream, unsigned char bit)
+{
+  /*the current bit in bitstream may be 0 or 1 for this to work*/
+  if(bit == 0) bitstream[(*bitpointer) >> 3] &=  (unsigned char)(~(1 << (7 - ((*bitpointer) & 0x7))));
+  else         bitstream[(*bitpointer) >> 3] |=  (1 << (7 - ((*bitpointer) & 0x7)));
+  ++(*bitpointer);
+}
+
+/* ////////////////////////////////////////////////////////////////////////// */
+/* / PNG chunks                                                             / */
+/* ////////////////////////////////////////////////////////////////////////// */
+
+unsigned lodepng_chunk_length(const unsigned char* chunk)
+{
+  return lodepng_read32bitInt(&chunk[0]);
+}
+
+void lodepng_chunk_type(char type[5], const unsigned char* chunk)
+{
+  unsigned i;
+  for(i = 0; i != 4; ++i) type[i] = (char)chunk[4 + i];
+  type[4] = 0; /*null termination char*/
+}
+
+unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, const char* type)
+{
+  if(strlen(type) != 4) return 0;
+  return (chunk[4] == type[0] && chunk[5] == type[1] && chunk[6] == type[2] && chunk[7] == type[3]);
+}
+
+unsigned char lodepng_chunk_ancillary(const unsigned char* chunk)
+{
+  return((chunk[4] & 32) != 0);
+}
+
+unsigned char lodepng_chunk_private(const unsigned char* chunk)
+{
+  return((chunk[6] & 32) != 0);
+}
+
+unsigned char lodepng_chunk_safetocopy(const unsigned char* chunk)
+{
+  return((chunk[7] & 32) != 0);
+}
+
+unsigned char* lodepng_chunk_data(unsigned char* chunk)
+{
+  return &chunk[8];
+}
+
+const unsigned char* lodepng_chunk_data_const(const unsigned char* chunk)
+{
+  return &chunk[8];
+}
+
+unsigned lodepng_chunk_check_crc(const unsigned char* chunk)
+{
+  unsigned length = lodepng_chunk_length(chunk);
+  unsigned CRC = lodepng_read32bitInt(&chunk[length + 8]);
+  /*the CRC is taken of the data and the 4 chunk type letters, not the length*/
+  unsigned checksum = lodepng_crc32(&chunk[4], length + 4);
+  if(CRC != checksum) return 1;
+  else return 0;
+}
+
+void lodepng_chunk_generate_crc(unsigned char* chunk)
+{
+  unsigned length = lodepng_chunk_length(chunk);
+  unsigned CRC = lodepng_crc32(&chunk[4], length + 4);
+  lodepng_set32bitInt(chunk + 8 + length, CRC);
+}
+
+unsigned char* lodepng_chunk_next(unsigned char* chunk)
+{
+  unsigned total_chunk_length = lodepng_chunk_length(chunk) + 12;
+  return &chunk[total_chunk_length];
+}
+
+const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk)
+{
+  unsigned total_chunk_length = lodepng_chunk_length(chunk) + 12;
+  return &chunk[total_chunk_length];
+}
+
+unsigned lodepng_chunk_append(unsigned char** out, size_t* outlength, const unsigned char* chunk)
+{
+  unsigned i;
+  unsigned total_chunk_length = lodepng_chunk_length(chunk) + 12;
+  unsigned char *chunk_start, *new_buffer;
+  size_t new_length = (*outlength) + total_chunk_length;
+  if(new_length < total_chunk_length || new_length < (*outlength)) return 77; /*integer overflow happened*/
+
+  new_buffer = (unsigned char*)lodepng_realloc(*out, new_length);
+  if(!new_buffer) return 83; /*alloc fail*/
+  (*out) = new_buffer;
+  (*outlength) = new_length;
+  chunk_start = &(*out)[new_length - total_chunk_length];
+
+  for(i = 0; i != total_chunk_length; ++i) chunk_start[i] = chunk[i];
+
+  return 0;
+}
+
+unsigned lodepng_chunk_create(unsigned char** out, size_t* outlength, unsigned length,
+                              const char* type, const unsigned char* data)
+{
+  unsigned i;
+  unsigned char *chunk, *new_buffer;
+  size_t new_length = (*outlength) + length + 12;
+  if(new_length < length + 12 || new_length < (*outlength)) return 77; /*integer overflow happened*/
+  new_buffer = (unsigned char*)lodepng_realloc(*out, new_length);
+  if(!new_buffer) return 83; /*alloc fail*/
+  (*out) = new_buffer;
+  (*outlength) = new_length;
+  chunk = &(*out)[(*outlength) - length - 12];
+
+  /*1: length*/
+  lodepng_set32bitInt(chunk, (unsigned)length);
+
+  /*2: chunk name (4 letters)*/
+  chunk[4] = (unsigned char)type[0];
+  chunk[5] = (unsigned char)type[1];
+  chunk[6] = (unsigned char)type[2];
+  chunk[7] = (unsigned char)type[3];
+
+  /*3: the data*/
+  for(i = 0; i != length; ++i) chunk[8 + i] = data[i];
+
+  /*4: CRC (of the chunkname characters and the data)*/
+  lodepng_chunk_generate_crc(chunk);
+
+  return 0;
+}
+
+/* ////////////////////////////////////////////////////////////////////////// */
+/* / Color types and such                                                   / */
+/* ////////////////////////////////////////////////////////////////////////// */
+
+/*return type is a LodePNG error code*/
+static unsigned checkColorValidity(LodePNGColorType colortype, unsigned bd) /*bd = bitdepth*/
+{
+  switch(colortype)
+  {
+    case 0: if(!(bd == 1 || bd == 2 || bd == 4 || bd == 8 || bd == 16)) return 37; break; /*grey*/
+    case 2: if(!(                                 bd == 8 || bd == 16)) return 37; break; /*RGB*/
+    case 3: if(!(bd == 1 || bd == 2 || bd == 4 || bd == 8            )) return 37; break; /*palette*/
+    case 4: if(!(                                 bd == 8 || bd == 16)) return 37; break; /*grey + alpha*/
+    case 6: if(!(                                 bd == 8 || bd == 16)) return 37; break; /*RGBA*/
+    default: return 31;
+  }
+  return 0; /*allowed color type / bits combination*/
+}
+
+static unsigned getNumColorChannels(LodePNGColorType colortype)
+{
+  switch(colortype)
+  {
+    case 0: return 1; /*grey*/
+    case 2: return 3; /*RGB*/
+    case 3: return 1; /*palette*/
+    case 4: return 2; /*grey + alpha*/
+    case 6: return 4; /*RGBA*/
+  }
+  return 0; /*unexisting color type*/
+}
+
+static unsigned lodepng_get_bpp_lct(LodePNGColorType colortype, unsigned bitdepth)
+{
+  /*bits per pixel is amount of channels * bits per channel*/
+  return getNumColorChannels(colortype) * bitdepth;
+}
+
+/* ////////////////////////////////////////////////////////////////////////// */
+
+void lodepng_color_mode_init(LodePNGColorMode* info)
+{
+  info->key_defined = 0;
+  info->key_r = info->key_g = info->key_b = 0;
+  info->colortype = LCT_RGBA;
+  info->bitdepth = 8;
+  info->palette = 0;
+  info->palettesize = 0;
+}
+
+void lodepng_color_mode_cleanup(LodePNGColorMode* info)
+{
+  lodepng_palette_clear(info);
+}
+
+unsigned lodepng_color_mode_copy(LodePNGColorMode* dest, const LodePNGColorMode* source)
+{
+  size_t i;
+  lodepng_color_mode_cleanup(dest);
+  *dest = *source;
+  if(source->palette)
+  {
+    dest->palette = (unsigned char*)lodepng_malloc(1024);
+    if(!dest->palette && source->palettesize) return 83; /*alloc fail*/
+    for(i = 0; i != source->palettesize * 4; ++i) dest->palette[i] = source->palette[i];
+  }
+  return 0;
+}
+
+static int lodepng_color_mode_equal(const LodePNGColorMode* a, const LodePNGColorMode* b)
+{
+  size_t i;
+  if(a->colortype != b->colortype) return 0;
+  if(a->bitdepth != b->bitdepth) return 0;
+  if(a->key_defined != b->key_defined) return 0;
+  if(a->key_defined)
+  {
+    if(a->key_r != b->key_r) return 0;
+    if(a->key_g != b->key_g) return 0;
+    if(a->key_b != b->key_b) return 0;
+  }
+  /*if one of the palette sizes is 0, then we consider it to be the same as the
+  other: it means that e.g. the palette was not given by the user and should be
+  considered the same as the palette inside the PNG.*/
+  if(1/*a->palettesize != 0 && b->palettesize != 0*/) {
+    if(a->palettesize != b->palettesize) return 0;
+    for(i = 0; i != a->palettesize * 4; ++i)
+    {
+      if(a->palette[i] != b->palette[i]) return 0;
+    }
+  }
+  return 1;
+}
+
+void lodepng_palette_clear(LodePNGColorMode* info)
+{
+  if(info->palette) lodepng_free(info->palette);
+  info->palette = 0;
+  info->palettesize = 0;
+}
+
+unsigned lodepng_palette_add(LodePNGColorMode* info,
+                             unsigned char r, unsigned char g, unsigned char b, unsigned char a)
+{
+  unsigned char* data;
+  /*the same resize technique as C++ std::vectors is used, and here it's made so that for a palette with
+  the max of 256 colors, it'll have the exact alloc size*/
+  if(!info->palette) /*allocate palette if empty*/
+  {
+    /*room for 256 colors with 4 bytes each*/
+    data = (unsigned char*)lodepng_realloc(info->palette, 1024);
+    if(!data) return 83; /*alloc fail*/
+    else info->palette = data;
+  }
+  info->palette[4 * info->palettesize + 0] = r;
+  info->palette[4 * info->palettesize + 1] = g;
+  info->palette[4 * info->palettesize + 2] = b;
+  info->palette[4 * info->palettesize + 3] = a;
+  ++info->palettesize;
+  return 0;
+}
+
+unsigned lodepng_get_bpp(const LodePNGColorMode* info)
+{
+  /*calculate bits per pixel out of colortype and bitdepth*/
+  return lodepng_get_bpp_lct(info->colortype, info->bitdepth);
+}
+
+unsigned lodepng_get_channels(const LodePNGColorMode* info)
+{
+  return getNumColorChannels(info->colortype);
+}
+
+unsigned lodepng_is_greyscale_type(const LodePNGColorMode* info)
+{
+  return info->colortype == LCT_GREY || info->colortype == LCT_GREY_ALPHA;
+}
+
+unsigned lodepng_is_alpha_type(const LodePNGColorMode* info)
+{
+  return (info->colortype & 4) != 0; /*4 or 6*/
+}
+
+unsigned lodepng_is_palette_type(const LodePNGColorMode* info)
+{
+  return info->colortype == LCT_PALETTE;
+}
+
+unsigned lodepng_has_palette_alpha(const LodePNGColorMode* info)
+{
+  size_t i;
+  for(i = 0; i != info->palettesize; ++i)
+  {
+    if(info->palette[i * 4 + 3] < 255) return 1;
+  }
+  return 0;
+}
+
+unsigned lodepng_can_have_alpha(const LodePNGColorMode* info)
+{
+  return info->key_defined
+      || lodepng_is_alpha_type(info)
+      || lodepng_has_palette_alpha(info);
+}
+
+size_t lodepng_get_raw_size(unsigned w, unsigned h, const LodePNGColorMode* color)
+{
+  /*will not overflow for any color type if roughly w * h < 268435455*/
+  size_t bpp = lodepng_get_bpp(color);
+  size_t n = w * h;
+  return ((n / 8) * bpp) + ((n & 7) * bpp + 7) / 8;
+}
+
+size_t lodepng_get_raw_size_lct(unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth)
+{
+  /*will not overflow for any color type if roughly w * h < 268435455*/
+  size_t bpp = lodepng_get_bpp_lct(colortype, bitdepth);
+  size_t n = w * h;
+  return ((n / 8) * bpp) + ((n & 7) * bpp + 7) / 8;
+}
+
+
+#ifdef LODEPNG_COMPILE_PNG
+#ifdef LODEPNG_COMPILE_DECODER
+/*in an idat chunk, each scanline is a multiple of 8 bits, unlike the lodepng output buffer*/
+static size_t lodepng_get_raw_size_idat(unsigned w, unsigned h, const LodePNGColorMode* color)
+{
+  /*will not overflow for any color type if roughly w * h < 268435455*/
+  size_t bpp = lodepng_get_bpp(color);
+  size_t line = ((w / 8) * bpp) + ((w & 7) * bpp + 7) / 8;
+  return h * line;
+}
+#endif /*LODEPNG_COMPILE_DECODER*/
+#endif /*LODEPNG_COMPILE_PNG*/
+
+#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS
+
+static void LodePNGUnknownChunks_init(LodePNGInfo* info)
+{
+  unsigned i;
+  for(i = 0; i != 3; ++i) info->unknown_chunks_data[i] = 0;
+  for(i = 0; i != 3; ++i) info->unknown_chunks_size[i] = 0;
+}
+
+static void LodePNGUnknownChunks_cleanup(LodePNGInfo* info)
+{
+  unsigned i;
+  for(i = 0; i != 3; ++i) lodepng_free(info->unknown_chunks_data[i]);
+}
+
+static unsigned LodePNGUnknownChunks_copy(LodePNGInfo* dest, const LodePNGInfo* src)
+{
+  unsigned i;
+
+  LodePNGUnknownChunks_cleanup(dest);
+
+  for(i = 0; i != 3; ++i)
+  {
+    size_t j;
+    dest->unknown_chunks_size[i] = src->unknown_chunks_size[i];
+    dest->unknown_chunks_data[i] = (unsigned char*)lodepng_malloc(src->unknown_chunks_size[i]);
+    if(!dest->unknown_chunks_data[i] && dest->unknown_chunks_size[i]) return 83; /*alloc fail*/
+    for(j = 0; j < src->unknown_chunks_size[i]; ++j)
+    {
+      dest->unknown_chunks_data[i][j] = src->unknown_chunks_data[i][j];
+    }
+  }
+
+  return 0;
+}
+
+/******************************************************************************/
+
+static void LodePNGText_init(LodePNGInfo* info)
+{
+  info->text_num = 0;
+  info->text_keys = NULL;
+  info->text_strings = NULL;
+}
+
+static void LodePNGText_cleanup(LodePNGInfo* info)
+{
+  size_t i;
+  for(i = 0; i != info->text_num; ++i)
+  {
+    string_cleanup(&info->text_keys[i]);
+    string_cleanup(&info->text_strings[i]);
+  }
+  lodepng_free(info->text_keys);
+  lodepng_free(info->text_strings);
+}
+
+static unsigned LodePNGText_copy(LodePNGInfo* dest, const LodePNGInfo* source)
+{
+  size_t i = 0;
+  dest->text_keys = 0;
+  dest->text_strings = 0;
+  dest->text_num = 0;
+  for(i = 0; i != source->text_num; ++i)
+  {
+    CERROR_TRY_RETURN(lodepng_add_text(dest, source->text_keys[i], source->text_strings[i]));
+  }
+  return 0;
+}
+
+void lodepng_clear_text(LodePNGInfo* info)
+{
+  LodePNGText_cleanup(info);
+}
+
+unsigned lodepng_add_text(LodePNGInfo* info, const char* key, const char* str)
+{
+  char** new_keys = (char**)(lodepng_realloc(info->text_keys, sizeof(char*) * (info->text_num + 1)));
+  char** new_strings = (char**)(lodepng_realloc(info->text_strings, sizeof(char*) * (info->text_num + 1)));
+  if(!new_keys || !new_strings)
+  {
+    lodepng_free(new_keys);
+    lodepng_free(new_strings);
+    return 83; /*alloc fail*/
+  }
+
+  ++info->text_num;
+  info->text_keys = new_keys;
+  info->text_strings = new_strings;
+
+  string_init(&info->text_keys[info->text_num - 1]);
+  string_set(&info->text_keys[info->text_num - 1], key);
+
+  string_init(&info->text_strings[info->text_num - 1]);
+  string_set(&info->text_strings[info->text_num - 1], str);
+
+  return 0;
+}
+
+/******************************************************************************/
+
+static void LodePNGIText_init(LodePNGInfo* info)
+{
+  info->itext_num = 0;
+  info->itext_keys = NULL;
+  info->itext_langtags = NULL;
+  info->itext_transkeys = NULL;
+  info->itext_strings = NULL;
+}
+
+static void LodePNGIText_cleanup(LodePNGInfo* info)
+{
+  size_t i;
+  for(i = 0; i != info->itext_num; ++i)
+  {
+    string_cleanup(&info->itext_keys[i]);
+    string_cleanup(&info->itext_langtags[i]);
+    string_cleanup(&info->itext_transkeys[i]);
+    string_cleanup(&info->itext_strings[i]);
+  }
+  lodepng_free(info->itext_keys);
+  lodepng_free(info->itext_langtags);
+  lodepng_free(info->itext_transkeys);
+  lodepng_free(info->itext_strings);
+}
+
+static unsigned LodePNGIText_copy(LodePNGInfo* dest, const LodePNGInfo* source)
+{
+  size_t i = 0;
+  dest->itext_keys = 0;
+  dest->itext_langtags = 0;
+  dest->itext_transkeys = 0;
+  dest->itext_strings = 0;
+  dest->itext_num = 0;
+  for(i = 0; i != source->itext_num; ++i)
+  {
+    CERROR_TRY_RETURN(lodepng_add_itext(dest, source->itext_keys[i], source->itext_langtags[i],
+                                        source->itext_transkeys[i], source->itext_strings[i]));
+  }
+  return 0;
+}
+
+void lodepng_clear_itext(LodePNGInfo* info)
+{
+  LodePNGIText_cleanup(info);
+}
+
+unsigned lodepng_add_itext(LodePNGInfo* info, const char* key, const char* langtag,
+                           const char* transkey, const char* str)
+{
+  char** new_keys = (char**)(lodepng_realloc(info->itext_keys, sizeof(char*) * (info->itext_num + 1)));
+  char** new_langtags = (char**)(lodepng_realloc(info->itext_langtags, sizeof(char*) * (info->itext_num + 1)));
+  char** new_transkeys = (char**)(lodepng_realloc(info->itext_transkeys, sizeof(char*) * (info->itext_num + 1)));
+  char** new_strings = (char**)(lodepng_realloc(info->itext_strings, sizeof(char*) * (info->itext_num + 1)));
+  if(!new_keys || !new_langtags || !new_transkeys || !new_strings)
+  {
+    lodepng_free(new_keys);
+    lodepng_free(new_langtags);
+    lodepng_free(new_transkeys);
+    lodepng_free(new_strings);
+    return 83; /*alloc fail*/
+  }
+
+  ++info->itext_num;
+  info->itext_keys = new_keys;
+  info->itext_langtags = new_langtags;
+  info->itext_transkeys = new_transkeys;
+  info->itext_strings = new_strings;
+
+  string_init(&info->itext_keys[info->itext_num - 1]);
+  string_set(&info->itext_keys[info->itext_num - 1], key);
+
+  string_init(&info->itext_langtags[info->itext_num - 1]);
+  string_set(&info->itext_langtags[info->itext_num - 1], langtag);
+
+  string_init(&info->itext_transkeys[info->itext_num - 1]);
+  string_set(&info->itext_transkeys[info->itext_num - 1], transkey);
+
+  string_init(&info->itext_strings[info->itext_num - 1]);
+  string_set(&info->itext_strings[info->itext_num - 1], str);
+
+  return 0;
+}
+#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/
+
+void lodepng_info_init(LodePNGInfo* info)
+{
+  lodepng_color_mode_init(&info->color);
+  info->interlace_method = 0;
+  info->compression_method = 0;
+  info->filter_method = 0;
+#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS
+  info->background_defined = 0;
+  info->background_r = info->background_g = info->background_b = 0;
+
+  LodePNGText_init(info);
+  LodePNGIText_init(info);
+
+  info->time_defined = 0;
+  info->phys_defined = 0;
+
+  LodePNGUnknownChunks_init(info);
+#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/
+}
+
+void lodepng_info_cleanup(LodePNGInfo* info)
+{
+  lodepng_color_mode_cleanup(&info->color);
+#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS
+  LodePNGText_cleanup(info);
+  LodePNGIText_cleanup(info);
+
+  LodePNGUnknownChunks_cleanup(info);
+#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/
+}
+
+unsigned lodepng_info_copy(LodePNGInfo* dest, const LodePNGInfo* source)
+{
+  lodepng_info_cleanup(dest);
+  *dest = *source;
+  lodepng_color_mode_init(&dest->color);
+  CERROR_TRY_RETURN(lodepng_color_mode_copy(&dest->color, &source->color));
+
+#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS
+  CERROR_TRY_RETURN(LodePNGText_copy(dest, source));
+  CERROR_TRY_RETURN(LodePNGIText_copy(dest, source));
+
+  LodePNGUnknownChunks_init(dest);
+  CERROR_TRY_RETURN(LodePNGUnknownChunks_copy(dest, source));
+#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/
+  return 0;
+}
+
+void lodepng_info_swap(LodePNGInfo* a, LodePNGInfo* b)
+{
+  LodePNGInfo temp = *a;
+  *a = *b;
+  *b = temp;
+}
+
+/* ////////////////////////////////////////////////////////////////////////// */
+
+/*index: bitgroup index, bits: bitgroup size(1, 2 or 4), in: bitgroup value, out: octet array to add bits to*/
+static void addColorBits(unsigned char* out, size_t index, unsigned bits, unsigned in)
+{
+  unsigned m = bits == 1 ? 7 : bits == 2 ? 3 : 1; /*8 / bits - 1*/
+  /*p = the partial index in the byte, e.g. with 4 palettebits it is 0 for first half or 1 for second half*/
+  unsigned p = index & m;
+  in &= (1u << bits) - 1u; /*filter out any other bits of the input value*/
+  in = in << (bits * (m - p));
+  if(p == 0) out[index * bits / 8] = in;
+  else out[index * bits / 8] |= in;
+}
+
+typedef struct ColorTree ColorTree;
+
+/*
+One node of a color tree
+This is the data structure used to count the number of unique colors and to get a palette
+index for a color. It's like an octree, but because the alpha channel is used too, each
+node has 16 instead of 8 children.
+*/
+struct ColorTree
+{
+  ColorTree* children[16]; /*up to 16 pointers to ColorTree of next level*/
+  int index; /*the payload. Only has a meaningful value if this is in the last level*/
+};
+
+static void color_tree_init(ColorTree* tree)
+{
+  int i;
+  for(i = 0; i != 16; ++i) tree->children[i] = 0;
+  tree->index = -1;
+}
+
+static void color_tree_cleanup(ColorTree* tree)
+{
+  int i;
+  for(i = 0; i != 16; ++i)
+  {
+    if(tree->children[i])
+    {
+      color_tree_cleanup(tree->children[i]);
+      lodepng_free(tree->children[i]);
+    }
+  }
+}
+
+/*returns -1 if color not present, its index otherwise*/
+static int color_tree_get(ColorTree* tree, unsigned char r, unsigned char g, unsigned char b, unsigned char a)
+{
+  int bit = 0;
+  for(bit = 0; bit < 8; ++bit)
+  {
+    int i = 8 * ((r >> bit) & 1) + 4 * ((g >> bit) & 1) + 2 * ((b >> bit) & 1) + 1 * ((a >> bit) & 1);
+    if(!tree->children[i]) return -1;
+    else tree = tree->children[i];
+  }
+  return tree ? tree->index : -1;
+}
+
+#ifdef LODEPNG_COMPILE_ENCODER
+static int color_tree_has(ColorTree* tree, unsigned char r, unsigned char g, unsigned char b, unsigned char a)
+{
+  return color_tree_get(tree, r, g, b, a) >= 0;
+}
+#endif /*LODEPNG_COMPILE_ENCODER*/
+
+/*color is not allowed to already exist.
+Index should be >= 0 (it's signed to be compatible with using -1 for "doesn't exist")*/
+static void color_tree_add(ColorTree* tree,
+                           unsigned char r, unsigned char g, unsigned char b, unsigned char a, unsigned index)
+{
+  int bit;
+  for(bit = 0; bit < 8; ++bit)
+  {
+    int i = 8 * ((r >> bit) & 1) + 4 * ((g >> bit) & 1) + 2 * ((b >> bit) & 1) + 1 * ((a >> bit) & 1);
+    if(!tree->children[i])
+    {
+      tree->children[i] = (ColorTree*)lodepng_malloc(sizeof(ColorTree));
+      color_tree_init(tree->children[i]);
+    }
+    tree = tree->children[i];
+  }
+  tree->index = (int)index;
+}
+
+/*put a pixel, given its RGBA color, into image of any color type*/
+static unsigned rgba8ToPixel(unsigned char* out, size_t i,
+                             const LodePNGColorMode* mode, ColorTree* tree /*for palette*/,
+                             unsigned char r, unsigned char g, unsigned char b, unsigned char a)
+{
+  if(mode->colortype == LCT_GREY)
+  {
+    unsigned char grey = r; /*((unsigned short)r + g + b) / 3*/;
+    if(mode->bitdepth == 8) out[i] = grey;
+    else if(mode->bitdepth == 16) out[i * 2 + 0] = out[i * 2 + 1] = grey;
+    else
+    {
+      /*take the most significant bits of grey*/
+      grey = (grey >> (8 - mode->bitdepth)) & ((1 << mode->bitdepth) - 1);
+      addColorBits(out, i, mode->bitdepth, grey);
+    }
+  }
+  else if(mode->colortype == LCT_RGB)
+  {
+    if(mode->bitdepth == 8)
+    {
+      out[i * 3 + 0] = r;
+      out[i * 3 + 1] = g;
+      out[i * 3 + 2] = b;
+    }
+    else
+    {
+      out[i * 6 + 0] = out[i * 6 + 1] = r;
+      out[i * 6 + 2] = out[i * 6 + 3] = g;
+      out[i * 6 + 4] = out[i * 6 + 5] = b;
+    }
+  }
+  else if(mode->colortype == LCT_PALETTE)
+  {
+    int index = color_tree_get(tree, r, g, b, a);
+    if(index < 0) return 82; /*color not in palette*/
+    if(mode->bitdepth == 8) out[i] = index;
+    else addColorBits(out, i, mode->bitdepth, (unsigned)index);
+  }
+  else if(mode->colortype == LCT_GREY_ALPHA)
+  {
+    unsigned char grey = r; /*((unsigned short)r + g + b) / 3*/;
+    if(mode->bitdepth == 8)
+    {
+      out[i * 2 + 0] = grey;
+      out[i * 2 + 1] = a;
+    }
+    else if(mode->bitdepth == 16)
+    {
+      out[i * 4 + 0] = out[i * 4 + 1] = grey;
+      out[i * 4 + 2] = out[i * 4 + 3] = a;
+    }
+  }
+  else if(mode->colortype == LCT_RGBA)
+  {
+    if(mode->bitdepth == 8)
+    {
+      out[i * 4 + 0] = r;
+      out[i * 4 + 1] = g;
+      out[i * 4 + 2] = b;
+      out[i * 4 + 3] = a;
+    }
+    else
+    {
+      out[i * 8 + 0] = out[i * 8 + 1] = r;
+      out[i * 8 + 2] = out[i * 8 + 3] = g;
+      out[i * 8 + 4] = out[i * 8 + 5] = b;
+      out[i * 8 + 6] = out[i * 8 + 7] = a;
+    }
+  }
+
+  return 0; /*no error*/
+}
+
+/*put a pixel, given its RGBA16 color, into image of any color 16-bitdepth type*/
+static void rgba16ToPixel(unsigned char* out, size_t i,
+                         const LodePNGColorMode* mode,
+                         unsigned short r, unsigned short g, unsigned short b, unsigned short a)
+{
+  if(mode->colortype == LCT_GREY)
+  {
+    unsigned short grey = r; /*((unsigned)r + g + b) / 3*/;
+    out[i * 2 + 0] = (grey >> 8) & 255;
+    out[i * 2 + 1] = grey & 255;
+  }
+  else if(mode->colortype == LCT_RGB)
+  {
+    out[i * 6 + 0] = (r >> 8) & 255;
+    out[i * 6 + 1] = r & 255;
+    out[i * 6 + 2] = (g >> 8) & 255;
+    out[i * 6 + 3] = g & 255;
+    out[i * 6 + 4] = (b >> 8) & 255;
+    out[i * 6 + 5] = b & 255;
+  }
+  else if(mode->colortype == LCT_GREY_ALPHA)
+  {
+    unsigned short grey = r; /*((unsigned)r + g + b) / 3*/;
+    out[i * 4 + 0] = (grey >> 8) & 255;
+    out[i * 4 + 1] = grey & 255;
+    out[i * 4 + 2] = (a >> 8) & 255;
+    out[i * 4 + 3] = a & 255;
+  }
+  else if(mode->colortype == LCT_RGBA)
+  {
+    out[i * 8 + 0] = (r >> 8) & 255;
+    out[i * 8 + 1] = r & 255;
+    out[i * 8 + 2] = (g >> 8) & 255;
+    out[i * 8 + 3] = g & 255;
+    out[i * 8 + 4] = (b >> 8) & 255;
+    out[i * 8 + 5] = b & 255;
+    out[i * 8 + 6] = (a >> 8) & 255;
+    out[i * 8 + 7] = a & 255;
+  }
+}
+
+/*Get RGBA8 color of pixel with index i (y * width + x) from the raw image with given color type.*/
+static void getPixelColorRGBA8(unsigned char* r, unsigned char* g,
+                               unsigned char* b, unsigned char* a,
+                               const unsigned char* in, size_t i,
+                               const LodePNGColorMode* mode)
+{
+  if(mode->colortype == LCT_GREY)
+  {
+    if(mode->bitdepth == 8)
+    {
+      *r = *g = *b = in[i];
+      if(mode->key_defined && *r == mode->key_r) *a = 0;
+      else *a = 255;
+    }
+    else if(mode->bitdepth == 16)
+    {
+      *r = *g = *b = in[i * 2 + 0];
+      if(mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r) *a = 0;
+      else *a = 255;
+    }
+    else
+    {
+      unsigned highest = ((1U << mode->bitdepth) - 1U); /*highest possible value for this bit depth*/
+      size_t j = i * mode->bitdepth;
+      unsigned value = readBitsFromReversedStream(&j, in, mode->bitdepth);
+      *r = *g = *b = (value * 255) / highest;
+      if(mode->key_defined && value == mode->key_r) *a = 0;
+      else *a = 255;
+    }
+  }
+  else if(mode->colortype == LCT_RGB)
+  {
+    if(mode->bitdepth == 8)
+    {
+      *r = in[i * 3 + 0]; *g = in[i * 3 + 1]; *b = in[i * 3 + 2];
+      if(mode->key_defined && *r == mode->key_r && *g == mode->key_g && *b == mode->key_b) *a = 0;
+      else *a = 255;
+    }
+    else
+    {
+      *r = in[i * 6 + 0];
+      *g = in[i * 6 + 2];
+      *b = in[i * 6 + 4];
+      if(mode->key_defined && 256U * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r
+         && 256U * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g
+         && 256U * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b) *a = 0;
+      else *a = 255;
+    }
+  }
+  else if(mode->colortype == LCT_PALETTE)
+  {
+    unsigned index;
+    if(mode->bitdepth == 8) index = in[i];
+    else
+    {
+      size_t j = i * mode->bitdepth;
+      index = readBitsFromReversedStream(&j, in, mode->bitdepth);
+    }
+
+    if(index >= mode->palettesize)
+    {
+      /*This is an error according to the PNG spec, but common PNG decoders make it black instead.
+      Done here too, slightly faster due to no error handling needed.*/
+      *r = *g = *b = 0;
+      *a = 255;
+    }
+    else
+    {
+      *r = mode->palette[index * 4 + 0];
+      *g = mode->palette[index * 4 + 1];
+      *b = mode->palette[index * 4 + 2];
+      *a = mode->palette[index * 4 + 3];
+    }
+  }
+  else if(mode->colortype == LCT_GREY_ALPHA)
+  {
+    if(mode->bitdepth == 8)
+    {
+      *r = *g = *b = in[i * 2 + 0];
+      *a = in[i * 2 + 1];
+    }
+    else
+    {
+      *r = *g = *b = in[i * 4 + 0];
+      *a = in[i * 4 + 2];
+    }
+  }
+  else if(mode->colortype == LCT_RGBA)
+  {
+    if(mode->bitdepth == 8)
+    {
+      *r = in[i * 4 + 0];
+      *g = in[i * 4 + 1];
+      *b = in[i * 4 + 2];
+      *a = in[i * 4 + 3];
+    }
+    else
+    {
+      *r = in[i * 8 + 0];
+      *g = in[i * 8 + 2];
+      *b = in[i * 8 + 4];
+      *a = in[i * 8 + 6];
+    }
+  }
+}
+
+/*Similar to getPixelColorRGBA8, but with all the for loops inside of the color
+mode test cases, optimized to convert the colors much faster, when converting
+to RGBA or RGB with 8 bit per cannel. buffer must be RGBA or RGB output with
+enough memory, if has_alpha is true the output is RGBA. mode has the color mode
+of the input buffer.*/
+static void getPixelColorsRGBA8(unsigned char* buffer, size_t numpixels,
+                                unsigned has_alpha, const unsigned char* in,
+                                const LodePNGColorMode* mode)
+{
+  unsigned num_channels = has_alpha ? 4 : 3;
+  size_t i;
+  if(mode->colortype == LCT_GREY)
+  {
+    if(mode->bitdepth == 8)
+    {
+      for(i = 0; i != numpixels; ++i, buffer += num_channels)
+      {
+        buffer[0] = buffer[1] = buffer[2] = in[i];
+        if(has_alpha) buffer[3] = mode->key_defined && in[i] == mode->key_r ? 0 : 255;
+      }
+    }
+    else if(mode->bitdepth == 16)
+    {
+      for(i = 0; i != numpixels; ++i, buffer += num_channels)
+      {
+        buffer[0] = buffer[1] = buffer[2] = in[i * 2];
+        if(has_alpha) buffer[3] = mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r ? 0 : 255;
+      }
+    }
+    else
+    {
+      unsigned highest = ((1U << mode->bitdepth) - 1U); /*highest possible value for this bit depth*/
+      size_t j = 0;
+      for(i = 0; i != numpixels; ++i, buffer += num_channels)
+      {
+        unsigned value = readBitsFromReversedStream(&j, in, mode->bitdepth);
+        buffer[0] = buffer[1] = buffer[2] = (value * 255) / highest;
+        if(has_alpha) buffer[3] = mode->key_defined && value == mode->key_r ? 0 : 255;
+      }
+    }
+  }
+  else if(mode->colortype == LCT_RGB)
+  {
+    if(mode->bitdepth == 8)
+    {
+      for(i = 0; i != numpixels; ++i, buffer += num_channels)
+      {
+        buffer[0] = in[i * 3 + 0];
+        buffer[1] = in[i * 3 + 1];
+        buffer[2] = in[i * 3 + 2];
+        if(has_alpha) buffer[3] = mode->key_defined && buffer[0] == mode->key_r
+           && buffer[1]== mode->key_g && buffer[2] == mode->key_b ? 0 : 255;
+      }
+    }
+    else
+    {
+      for(i = 0; i != numpixels; ++i, buffer += num_channels)
+      {
+        buffer[0] = in[i * 6 + 0];
+        buffer[1] = in[i * 6 + 2];
+        buffer[2] = in[i * 6 + 4];
+        if(has_alpha) buffer[3] = mode->key_defined
+           && 256U * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r
+           && 256U * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g
+           && 256U * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b ? 0 : 255;
+      }
+    }
+  }
+  else if(mode->colortype == LCT_PALETTE)
+  {
+    unsigned index;
+    size_t j = 0;
+    for(i = 0; i != numpixels; ++i, buffer += num_channels)
+    {
+      if(mode->bitdepth == 8) index = in[i];
+      else index = readBitsFromReversedStream(&j, in, mode->bitdepth);
+
+      if(index >= mode->palettesize)
+      {
+        /*This is an error according to the PNG spec, but most PNG decoders make it black instead.
+        Done here too, slightly faster due to no error handling needed.*/
+        buffer[0] = buffer[1] = buffer[2] = 0;
+        if(has_alpha) buffer[3] = 255;
+      }
+      else
+      {
+        buffer[0] = mode->palette[index * 4 + 0];
+        buffer[1] = mode->palette[index * 4 + 1];
+        buffer[2] = mode->palette[index * 4 + 2];
+        if(has_alpha) buffer[3] = mode->palette[index * 4 + 3];
+      }
+    }
+  }
+  else if(mode->colortype == LCT_GREY_ALPHA)
+  {
+    if(mode->bitdepth == 8)
+    {
+      for(i = 0; i != numpixels; ++i, buffer += num_channels)
+      {
+        buffer[0] = buffer[1] = buffer[2] = in[i * 2 + 0];
+        if(has_alpha) buffer[3] = in[i * 2 + 1];
+      }
+    }
+    else
+    {
+      for(i = 0; i != numpixels; ++i, buffer += num_channels)
+      {
+        buffer[0] = buffer[1] = buffer[2] = in[i * 4 + 0];
+        if(has_alpha) buffer[3] = in[i * 4 + 2];
+      }
+    }
+  }
+  else if(mode->colortype == LCT_RGBA)
+  {
+    if(mode->bitdepth == 8)
+    {
+      for(i = 0; i != numpixels; ++i, buffer += num_channels)
+      {
+        buffer[0] = in[i * 4 + 0];
+        buffer[1] = in[i * 4 + 1];
+        buffer[2] = in[i * 4 + 2];
+        if(has_alpha) buffer[3] = in[i * 4 + 3];
+      }
+    }
+    else
+    {
+      for(i = 0; i != numpixels; ++i, buffer += num_channels)
+      {
+        buffer[0] = in[i * 8 + 0];
+        buffer[1] = in[i * 8 + 2];
+        buffer[2] = in[i * 8 + 4];
+        if(has_alpha) buffer[3] = in[i * 8 + 6];
+      }
+    }
+  }
+}
+
+/*Get RGBA16 color of pixel with index i (y * width + x) from the raw image with
+given color type, but the given color type must be 16-bit itself.*/
+static void getPixelColorRGBA16(unsigned short* r, unsigned short* g, unsigned short* b, unsigned short* a,
+                                const unsigned char* in, size_t i, const LodePNGColorMode* mode)
+{
+  if(mode->colortype == LCT_GREY)
+  {
+    *r = *g = *b = 256 * in[i * 2 + 0] + in[i * 2 + 1];
+    if(mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r) *a = 0;
+    else *a = 65535;
+  }
+  else if(mode->colortype == LCT_RGB)
+  {
+    *r = 256u * in[i * 6 + 0] + in[i * 6 + 1];
+    *g = 256u * in[i * 6 + 2] + in[i * 6 + 3];
+    *b = 256u * in[i * 6 + 4] + in[i * 6 + 5];
+    if(mode->key_defined
+       && 256u * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r
+       && 256u * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g
+       && 256u * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b) *a = 0;
+    else *a = 65535;
+  }
+  else if(mode->colortype == LCT_GREY_ALPHA)
+  {
+    *r = *g = *b = 256u * in[i * 4 + 0] + in[i * 4 + 1];
+    *a = 256u * in[i * 4 + 2] + in[i * 4 + 3];
+  }
+  else if(mode->colortype == LCT_RGBA)
+  {
+    *r = 256u * in[i * 8 + 0] + in[i * 8 + 1];
+    *g = 256u * in[i * 8 + 2] + in[i * 8 + 3];
+    *b = 256u * in[i * 8 + 4] + in[i * 8 + 5];
+    *a = 256u * in[i * 8 + 6] + in[i * 8 + 7];
+  }
+}
+
+unsigned lodepng_convert(unsigned char* out, const unsigned char* in,
+                         const LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in,
+                         unsigned w, unsigned h)
+{
+  size_t i;
+  ColorTree tree;
+  size_t numpixels = w * h;
+
+  if(lodepng_color_mode_equal(mode_out, mode_in))
+  {
+    size_t numbytes = lodepng_get_raw_size(w, h, mode_in);
+    for(i = 0; i != numbytes; ++i) out[i] = in[i];
+    return 0;
+  }
+
+  if(mode_out->colortype == LCT_PALETTE)
+  {
+    size_t palettesize = mode_out->palettesize;
+    const unsigned char* palette = mode_out->palette;
+    size_t palsize = 1u << mode_out->bitdepth;
+    /*if the user specified output palette but did not give the values, assume
+    they want the values of the input color type (assuming that one is palette).
+    Note that we never create a new palette ourselves.*/
+    if(palettesize == 0)
+    {
+      palettesize = mode_in->palettesize;
+      palette = mode_in->palette;
+    }
+    if(palettesize < palsize) palsize = palettesize;
+    color_tree_init(&tree);
+    for(i = 0; i != palsize; ++i)
+    {
+      const unsigned char* p = &palette[i * 4];
+      color_tree_add(&tree, p[0], p[1], p[2], p[3], i);
+    }
+  }
+
+  if(mode_in->bitdepth == 16 && mode_out->bitdepth == 16)
+  {
+    for(i = 0; i != numpixels; ++i)
+    {
+      unsigned short r = 0, g = 0, b = 0, a = 0;
+      getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in);
+      rgba16ToPixel(out, i, mode_out, r, g, b, a);
+    }
+  }
+  else if(mode_out->bitdepth == 8 && mode_out->colortype == LCT_RGBA)
+  {
+    getPixelColorsRGBA8(out, numpixels, 1, in, mode_in);
+  }
+  else if(mode_out->bitdepth == 8 && mode_out->colortype == LCT_RGB)
+  {
+    getPixelColorsRGBA8(out, numpixels, 0, in, mode_in);
+  }
+  else
+  {
+    unsigned char r = 0, g = 0, b = 0, a = 0;
+    for(i = 0; i != numpixels; ++i)
+    {
+      getPixelColorRGBA8(&r, &g, &b, &a, in, i, mode_in);
+      CERROR_TRY_RETURN(rgba8ToPixel(out, i, mode_out, &tree, r, g, b, a));
+    }
+  }
+
+  if(mode_out->colortype == LCT_PALETTE)
+  {
+    color_tree_cleanup(&tree);
+  }
+
+  return 0; /*no error*/
+}
+
+#ifdef LODEPNG_COMPILE_ENCODER
+
+void lodepng_color_profile_init(LodePNGColorProfile* profile)
+{
+  profile->colored = 0;
+  profile->key = 0;
+  profile->alpha = 0;
+  profile->key_r = profile->key_g = profile->key_b = 0;
+  profile->numcolors = 0;
+  profile->bits = 1;
+}
+
+/*function used for debug purposes with C++*/
+/*void printColorProfile(LodePNGColorProfile* p)
+{
+  std::cout << "colored: " << (int)p->colored << ", ";
+  std::cout << "key: " << (int)p->key << ", ";
+  std::cout << "key_r: " << (int)p->key_r << ", ";
+  std::cout << "key_g: " << (int)p->key_g << ", ";
+  std::cout << "key_b: " << (int)p->key_b << ", ";
+  std::cout << "alpha: " << (int)p->alpha << ", ";
+  std::cout << "numcolors: " << (int)p->numcolors << ", ";
+  std::cout << "bits: " << (int)p->bits << std::endl;
+}*/
+
+/*Returns how many bits needed to represent given value (max 8 bit)*/
+static unsigned getValueRequiredBits(unsigned char value)
+{
+  if(value == 0 || value == 255) return 1;
+  /*The scaling of 2-bit and 4-bit values uses multiples of 85 and 17*/
+  if(value % 17 == 0) return value % 85 == 0 ? 2 : 4;
+  return 8;
+}
+
+/*profile must already have been inited with mode.
+It's ok to set some parameters of profile to done already.*/
+unsigned lodepng_get_color_profile(LodePNGColorProfile* profile,
+                                   const unsigned char* in, unsigned w, unsigned h,
+                                   const LodePNGColorMode* mode)
+{
+  unsigned error = 0;
+  size_t i;
+  ColorTree tree;
+  size_t numpixels = w * h;
+
+  unsigned colored_done = lodepng_is_greyscale_type(mode) ? 1 : 0;
+  unsigned alpha_done = lodepng_can_have_alpha(mode) ? 0 : 1;
+  unsigned numcolors_done = 0;
+  unsigned bpp = lodepng_get_bpp(mode);
+  unsigned bits_done = bpp == 1 ? 1 : 0;
+  unsigned maxnumcolors = 257;
+  unsigned sixteen = 0;
+  if(bpp <= 8) maxnumcolors = bpp == 1 ? 2 : (bpp == 2 ? 4 : (bpp == 4 ? 16 : 256));
+
+  color_tree_init(&tree);
+
+  /*Check if the 16-bit input is truly 16-bit*/
+  if(mode->bitdepth == 16)
+  {
+    unsigned short r, g, b, a;
+    for(i = 0; i != numpixels; ++i)
+    {
+      getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode);
+      if((r & 255) != ((r >> 8) & 255) || (g & 255) != ((g >> 8) & 255) ||
+         (b & 255) != ((b >> 8) & 255) || (a & 255) != ((a >> 8) & 255)) /*first and second byte differ*/
+      {
+        sixteen = 1;
+        break;
+      }
+    }
+  }
+
+  if(sixteen)
+  {
+    unsigned short r = 0, g = 0, b = 0, a = 0;
+    profile->bits = 16;
+    bits_done = numcolors_done = 1; /*counting colors no longer useful, palette doesn't support 16-bit*/
+
+    for(i = 0; i != numpixels; ++i)
+    {
+      getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode);
+
+      if(!colored_done && (r != g || r != b))
+      {
+        profile->colored = 1;
+        colored_done = 1;
+      }
+
+      if(!alpha_done)
+      {
+        unsigned matchkey = (r == profile->key_r && g == profile->key_g && b == profile->key_b);
+        if(a != 65535 && (a != 0 || (profile->key && !matchkey)))
+        {
+          profile->alpha = 1;
+          alpha_done = 1;
+          if(profile->bits < 8) profile->bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/
+        }
+        else if(a == 0 && !profile->alpha && !profile->key)
+        {
+          profile->key = 1;
+          profile->key_r = r;
+          profile->key_g = g;
+          profile->key_b = b;
+        }
+        else if(a == 65535 && profile->key && matchkey)
+        {
+          /* Color key cannot be used if an opaque pixel also has that RGB color. */
+          profile->alpha = 1;
+          alpha_done = 1;
+        }
+      }
+      if(alpha_done && numcolors_done && colored_done && bits_done) break;
+    }
+
+    if(profile->key && !profile->alpha)
+    {
+      for(i = 0; i != numpixels; ++i)
+      {
+        getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode);
+        if(a != 0 && r == profile->key_r && g == profile->key_g && b == profile->key_b)
+        {
+          /* Color key cannot be used if an opaque pixel also has that RGB color. */
+          profile->alpha = 1;
+          alpha_done = 1;
+        }
+      }
+    }
+  }
+  else /* < 16-bit */
+  {
+    unsigned char r = 0, g = 0, b = 0, a = 0;
+    for(i = 0; i != numpixels; ++i)
+    {
+      getPixelColorRGBA8(&r, &g, &b, &a, in, i, mode);
+
+      if(!bits_done && profile->bits < 8)
+      {
+        /*only r is checked, < 8 bits is only relevant for greyscale*/
+        unsigned bits = getValueRequiredBits(r);
+        if(bits > profile->bits) profile->bits = bits;
+      }
+      bits_done = (profile->bits >= bpp);
+
+      if(!colored_done && (r != g || r != b))
+      {
+        profile->colored = 1;
+        colored_done = 1;
+        if(profile->bits < 8) profile->bits = 8; /*PNG has no colored modes with less than 8-bit per channel*/
+      }
+
+      if(!alpha_done)
+      {
+        unsigned matchkey = (r == profile->key_r && g == profile->key_g && b == profile->key_b);
+        if(a != 255 && (a != 0 || (profile->key && !matchkey)))
+        {
+          profile->alpha = 1;
+          alpha_done = 1;
+          if(profile->bits < 8) profile->bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/
+        }
+        else if(a == 0 && !profile->alpha && !profile->key)
+        {
+          profile->key = 1;
+          profile->key_r = r;
+          profile->key_g = g;
+          profile->key_b = b;
+        }
+        else if(a == 255 && profile->key && matchkey)
+        {
+          /* Color key cannot be used if an opaque pixel also has that RGB color. */
+          profile->alpha = 1;
+          alpha_done = 1;
+          if(profile->bits < 8) profile->bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/
+        }
+      }
+
+      if(!numcolors_done)
+      {
+        if(!color_tree_has(&tree, r, g, b, a))
+        {
+          color_tree_add(&tree, r, g, b, a, profile->numcolors);
+          if(profile->numcolors < 256)
+          {
+            unsigned char* p = profile->palette;
+            unsigned n = profile->numcolors;
+            p[n * 4 + 0] = r;
+            p[n * 4 + 1] = g;
+            p[n * 4 + 2] = b;
+            p[n * 4 + 3] = a;
+          }
+          ++profile->numcolors;
+          numcolors_done = profile->numcolors >= maxnumcolors;
+        }
+      }
+
+      if(alpha_done && numcolors_done && colored_done && bits_done) break;
+    }
+
+    if(profile->key && !profile->alpha)
+    {
+      for(i = 0; i != numpixels; ++i)
+      {
+        getPixelColorRGBA8(&r, &g, &b, &a, in, i, mode);
+        if(a != 0 && r == profile->key_r && g == profile->key_g && b == profile->key_b)
+        {
+          /* Color key cannot be used if an opaque pixel also has that RGB color. */
+          profile->alpha = 1;
+          alpha_done = 1;
+        }
+      }
+    }
+
+    /*make the profile's key always 16-bit for consistency - repeat each byte twice*/
+    profile->key_r += (profile->key_r << 8);
+    profile->key_g += (profile->key_g << 8);
+    profile->key_b += (profile->key_b << 8);
+  }
+
+  color_tree_cleanup(&tree);
+  return error;
+}
+
+/*Automatically chooses color type that gives smallest amount of bits in the
+output image, e.g. grey if there are only greyscale pixels, palette if there
+are less than 256 colors, ...
+Updates values of mode with a potentially smaller color model. mode_out should
+contain the user chosen color model, but will be overwritten with the new chosen one.*/
+unsigned lodepng_auto_choose_color(LodePNGColorMode* mode_out,
+                                   const unsigned char* image, unsigned w, unsigned h,
+                                   const LodePNGColorMode* mode_in)
+{
+  LodePNGColorProfile prof;
+  unsigned error = 0;
+  unsigned i, n, palettebits, grey_ok, palette_ok;
+
+  lodepng_color_profile_init(&prof);
+  error = lodepng_get_color_profile(&prof, image, w, h, mode_in);
+  if(error) return error;
+  mode_out->key_defined = 0;
+
+  if(prof.key && w * h <= 16)
+  {
+    prof.alpha = 1; /*too few pixels to justify tRNS chunk overhead*/
+    if(prof.bits < 8) prof.bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/
+  }
+  grey_ok = !prof.colored && !prof.alpha; /*grey without alpha, with potentially low bits*/
+  n = prof.numcolors;
+  palettebits = n <= 2 ? 1 : (n <= 4 ? 2 : (n <= 16 ? 4 : 8));
+  palette_ok = n <= 256 && (n * 2 < w * h) && prof.bits <= 8;
+  if(w * h < n * 2) palette_ok = 0; /*don't add palette overhead if image has only a few pixels*/
+  if(grey_ok && prof.bits <= palettebits) palette_ok = 0; /*grey is less overhead*/
+
+  if(palette_ok)
+  {
+    unsigned char* p = prof.palette;
+    lodepng_palette_clear(mode_out); /*remove potential earlier palette*/
+    for(i = 0; i != prof.numcolors; ++i)
+    {
+      error = lodepng_palette_add(mode_out, p[i * 4 + 0], p[i * 4 + 1], p[i * 4 + 2], p[i * 4 + 3]);
+      if(error) break;
+    }
+
+    mode_out->colortype = LCT_PALETTE;
+    mode_out->bitdepth = palettebits;
+
+    if(mode_in->colortype == LCT_PALETTE && mode_in->palettesize >= mode_out->palettesize
+        && mode_in->bitdepth == mode_out->bitdepth)
+    {
+      /*If input should have same palette colors, keep original to preserve its order and prevent conversion*/
+      lodepng_color_mode_cleanup(mode_out);
+      lodepng_color_mode_copy(mode_out, mode_in);
+    }
+  }
+  else /*8-bit or 16-bit per channel*/
+  {
+    mode_out->bitdepth = prof.bits;
+    mode_out->colortype = prof.alpha ? (prof.colored ? LCT_RGBA : LCT_GREY_ALPHA)
+                                     : (prof.colored ? LCT_RGB : LCT_GREY);
+
+    if(prof.key && !prof.alpha)
+    {
+      unsigned mask = (1u << mode_out->bitdepth) - 1u; /*profile always uses 16-bit, mask converts it*/
+      mode_out->key_r = prof.key_r & mask;
+      mode_out->key_g = prof.key_g & mask;
+      mode_out->key_b = prof.key_b & mask;
+      mode_out->key_defined = 1;
+    }
+  }
+
+  return error;
+}
+
+#endif /* #ifdef LODEPNG_COMPILE_ENCODER */
+
+/*
+Paeth predicter, used by PNG filter type 4
+The parameters are of type short, but should come from unsigned chars, the shorts
+are only needed to make the paeth calculation correct.
+*/
+static unsigned char paethPredictor(short a, short b, short c)
+{
+  short pa = abs(b - c);
+  short pb = abs(a - c);
+  short pc = abs(a + b - c - c);
+
+  if(pc < pa && pc < pb) return (unsigned char)c;
+  else if(pb < pa) return (unsigned char)b;
+  else return (unsigned char)a;
+}
+
+/*shared values used by multiple Adam7 related functions*/
+
+static const unsigned ADAM7_IX[7] = { 0, 4, 0, 2, 0, 1, 0 }; /*x start values*/
+static const unsigned ADAM7_IY[7] = { 0, 0, 4, 0, 2, 0, 1 }; /*y start values*/
+static const unsigned ADAM7_DX[7] = { 8, 8, 4, 4, 2, 2, 1 }; /*x delta values*/
+static const unsigned ADAM7_DY[7] = { 8, 8, 8, 4, 4, 2, 2 }; /*y delta values*/
+
+/*
+Outputs various dimensions and positions in the image related to the Adam7 reduced images.
+passw: output containing the width of the 7 passes
+passh: output containing the height of the 7 passes
+filter_passstart: output containing the index of the start and end of each
+ reduced image with filter bytes
+padded_passstart output containing the index of the start and end of each
+ reduced image when without filter bytes but with padded scanlines
+passstart: output containing the index of the start and end of each reduced
+ image without padding between scanlines, but still padding between the images
+w, h: width and height of non-interlaced image
+bpp: bits per pixel
+"padded" is only relevant if bpp is less than 8 and a scanline or image does not
+ end at a full byte
+*/
+static void Adam7_getpassvalues(unsigned passw[7], unsigned passh[7], size_t filter_passstart[8],
+                                size_t padded_passstart[8], size_t passstart[8], unsigned w, unsigned h, unsigned bpp)
+{
+  /*the passstart values have 8 values: the 8th one indicates the byte after the end of the 7th (= last) pass*/
+  unsigned i;
+
+  /*calculate width and height in pixels of each pass*/
+  for(i = 0; i != 7; ++i)
+  {
+    passw[i] = (w + ADAM7_DX[i] - ADAM7_IX[i] - 1) / ADAM7_DX[i];
+    passh[i] = (h + ADAM7_DY[i] - ADAM7_IY[i] - 1) / ADAM7_DY[i];
+    if(passw[i] == 0) passh[i] = 0;
+    if(passh[i] == 0) passw[i] = 0;
+  }
+
+  filter_passstart[0] = padded_passstart[0] = passstart[0] = 0;
+  for(i = 0; i != 7; ++i)
+  {
+    /*if passw[i] is 0, it's 0 bytes, not 1 (no filtertype-byte)*/
+    filter_passstart[i + 1] = filter_passstart[i]
+                            + ((passw[i] && passh[i]) ? passh[i] * (1 + (passw[i] * bpp + 7) / 8) : 0);
+    /*bits padded if needed to fill full byte at end of each scanline*/
+    padded_passstart[i + 1] = padded_passstart[i] + passh[i] * ((passw[i] * bpp + 7) / 8);
+    /*only padded at end of reduced image*/
+    passstart[i + 1] = passstart[i] + (passh[i] * passw[i] * bpp + 7) / 8;
+  }
+}
+
+#ifdef LODEPNG_COMPILE_DECODER
+
+/* ////////////////////////////////////////////////////////////////////////// */
+/* / PNG Decoder                                                            / */
+/* ////////////////////////////////////////////////////////////////////////// */
+
+/*read the information from the header and store it in the LodePNGInfo. return value is error*/
+unsigned lodepng_inspect(unsigned* w, unsigned* h, LodePNGState* state,
+                         const unsigned char* in, size_t insize)
+{
+  LodePNGInfo* info = &state->info_png;
+  if(insize == 0 || in == 0)
+  {
+    CERROR_RETURN_ERROR(state->error, 48); /*error: the given data is empty*/
+  }
+  if(insize < 33)
+  {
+    CERROR_RETURN_ERROR(state->error, 27); /*error: the data length is smaller than the length of a PNG header*/
+  }
+
+  /*when decoding a new PNG image, make sure all parameters created after previous decoding are reset*/
+  lodepng_info_cleanup(info);
+  lodepng_info_init(info);
+
+  if(in[0] != 137 || in[1] != 80 || in[2] != 78 || in[3] != 71
+     || in[4] != 13 || in[5] != 10 || in[6] != 26 || in[7] != 10)
+  {
+    CERROR_RETURN_ERROR(state->error, 28); /*error: the first 8 bytes are not the correct PNG signature*/
+  }
+  if(lodepng_chunk_length(in + 8) != 13)
+  {
+    CERROR_RETURN_ERROR(state->error, 94); /*error: header size must be 13 bytes*/
+  }
+  if(!lodepng_chunk_type_equals(in + 8, "IHDR"))
+  {
+    CERROR_RETURN_ERROR(state->error, 29); /*error: it doesn't start with a IHDR chunk!*/
+  }
+
+  /*read the values given in the header*/
+  *w = lodepng_read32bitInt(&in[16]);
+  *h = lodepng_read32bitInt(&in[20]);
+  info->color.bitdepth = in[24];
+  info->color.colortype = (LodePNGColorType)in[25];
+  info->compression_method = in[26];
+  info->filter_method = in[27];
+  info->interlace_method = in[28];
+
+  if(*w == 0 || *h == 0)
+  {
+    CERROR_RETURN_ERROR(state->error, 93);
+  }
+
+  if(!state->decoder.ignore_crc)
+  {
+    unsigned CRC = lodepng_read32bitInt(&in[29]);
+    unsigned checksum = lodepng_crc32(&in[12], 17);
+    if(CRC != checksum)
+    {
+      CERROR_RETURN_ERROR(state->error, 57); /*invalid CRC*/
+    }
+  }
+
+  /*error: only compression method 0 is allowed in the specification*/
+  if(info->compression_method != 0) CERROR_RETURN_ERROR(state->error, 32);
+  /*error: only filter method 0 is allowed in the specification*/
+  if(info->filter_method != 0) CERROR_RETURN_ERROR(state->error, 33);
+  /*error: only interlace methods 0 and 1 exist in the specification*/
+  if(info->interlace_method > 1) CERROR_RETURN_ERROR(state->error, 34);
+
+  state->error = checkColorValidity(info->color.colortype, info->color.bitdepth);
+  return state->error;
+}
+
+static unsigned unfilterScanline(unsigned char* recon, const unsigned char* scanline, const unsigned char* precon,
+                                 size_t bytewidth, unsigned char filterType, size_t length)
+{
+  /*
+  For PNG filter method 0
+  unfilter a PNG image scanline by scanline. when the pixels are smaller than 1 byte,
+  the filter works byte per byte (bytewidth = 1)
+  precon is the previous unfiltered scanline, recon the result, scanline the current one
+  the incoming scanlines do NOT include the filtertype byte, that one is given in the parameter filterType instead
+  recon and scanline MAY be the same memory address! precon must be disjoint.
+  */
+
+  size_t i;
+  switch(filterType)
+  {
+    case 0:
+      for(i = 0; i != length; ++i) recon[i] = scanline[i];
+      break;
+    case 1:
+      for(i = 0; i != bytewidth; ++i) recon[i] = scanline[i];
+      for(i = bytewidth; i < length; ++i) recon[i] = scanline[i] + recon[i - bytewidth];
+      break;
+    case 2:
+      if(precon)
+      {
+        for(i = 0; i != length; ++i) recon[i] = scanline[i] + precon[i];
+      }
+      else
+      {
+        for(i = 0; i != length; ++i) recon[i] = scanline[i];
+      }
+      break;
+    case 3:
+      if(precon)
+      {
+        for(i = 0; i != bytewidth; ++i) recon[i] = scanline[i] + (precon[i] >> 1);
+        for(i = bytewidth; i < length; ++i) recon[i] = scanline[i] + ((recon[i - bytewidth] + precon[i]) >> 1);
+      }
+      else
+      {
+        for(i = 0; i != bytewidth; ++i) recon[i] = scanline[i];
+        for(i = bytewidth; i < length; ++i) recon[i] = scanline[i] + (recon[i - bytewidth] >> 1);
+      }
+      break;
+    case 4:
+      if(precon)
+      {
+        for(i = 0; i != bytewidth; ++i)
+        {
+          recon[i] = (scanline[i] + precon[i]); /*paethPredictor(0, precon[i], 0) is always precon[i]*/
+        }
+        for(i = bytewidth; i < length; ++i)
+        {
+          recon[i] = (scanline[i] + paethPredictor(recon[i - bytewidth], precon[i], precon[i - bytewidth]));
+        }
+      }
+      else
+      {
+        for(i = 0; i != bytewidth; ++i)
+        {
+          recon[i] = scanline[i];
+        }
+        for(i = bytewidth; i < length; ++i)
+        {
+          /*paethPredictor(recon[i - bytewidth], 0, 0) is always recon[i - bytewidth]*/
+          recon[i] = (scanline[i] + recon[i - bytewidth]);
+        }
+      }
+      break;
+    default: return 36; /*error: unexisting filter type given*/
+  }
+  return 0;
+}
+
+static unsigned unfilter(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp)
+{
+  /*
+  For PNG filter method 0
+  this function unfilters a single image (e.g. without interlacing this is called once, with Adam7 seven times)
+  out must have enough bytes allocated already, in must have the scanlines + 1 filtertype byte per scanline
+  w and h are image dimensions or dimensions of reduced image, bpp is bits per pixel
+  in and out are allowed to be the same memory address (but aren't the same size since in has the extra filter bytes)
+  */
+
+  unsigned y;
+  unsigned char* prevline = 0;
+
+  /*bytewidth is used for filtering, is 1 when bpp < 8, number of bytes per pixel otherwise*/
+  size_t bytewidth = (bpp + 7) / 8;
+  size_t linebytes = (w * bpp + 7) / 8;
+
+  for(y = 0; y < h; ++y)
+  {
+    size_t outindex = linebytes * y;
+    size_t inindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/
+    unsigned char filterType = in[inindex];
+
+    CERROR_TRY_RETURN(unfilterScanline(&out[outindex], &in[inindex + 1], prevline, bytewidth, filterType, linebytes));
+
+    prevline = &out[outindex];
+  }
+
+  return 0;
+}
+
+/*
+in: Adam7 interlaced image, with no padding bits between scanlines, but between
+ reduced images so that each reduced image starts at a byte.
+out: the same pixels, but re-ordered so that they're now a non-interlaced image with size w*h
+bpp: bits per pixel
+out has the following size in bits: w * h * bpp.
+in is possibly bigger due to padding bits between reduced images.
+out must be big enough AND must be 0 everywhere if bpp < 8 in the current implementation
+(because that's likely a little bit faster)
+NOTE: comments about padding bits are only relevant if bpp < 8
+*/
+static void Adam7_deinterlace(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp)
+{
+  unsigned passw[7], passh[7];
+  size_t filter_passstart[8], padded_passstart[8], passstart[8];
+  unsigned i;
+
+  Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp);
+
+  if(bpp >= 8)
+  {
+    for(i = 0; i != 7; ++i)
+    {
+      unsigned x, y, b;
+      size_t bytewidth = bpp / 8;
+      for(y = 0; y < passh[i]; ++y)
+      for(x = 0; x < passw[i]; ++x)
+      {
+        size_t pixelinstart = passstart[i] + (y * passw[i] + x) * bytewidth;
+        size_t pixeloutstart = ((ADAM7_IY[i] + y * ADAM7_DY[i]) * w + ADAM7_IX[i] + x * ADAM7_DX[i]) * bytewidth;
+        for(b = 0; b < bytewidth; ++b)
+        {
+          out[pixeloutstart + b] = in[pixelinstart + b];
+        }
+      }
+    }
+  }
+  else /*bpp < 8: Adam7 with pixels < 8 bit is a bit trickier: with bit pointers*/
+  {
+    for(i = 0; i != 7; ++i)
+    {
+      unsigned x, y, b;
+      unsigned ilinebits = bpp * passw[i];
+      unsigned olinebits = bpp * w;
+      size_t obp, ibp; /*bit pointers (for out and in buffer)*/
+      for(y = 0; y < passh[i]; ++y)
+      for(x = 0; x < passw[i]; ++x)
+      {
+        ibp = (8 * passstart[i]) + (y * ilinebits + x * bpp);
+        obp = (ADAM7_IY[i] + y * ADAM7_DY[i]) * olinebits + (ADAM7_IX[i] + x * ADAM7_DX[i]) * bpp;
+        for(b = 0; b < bpp; ++b)
+        {
+          unsigned char bit = readBitFromReversedStream(&ibp, in);
+          /*note that this function assumes the out buffer is completely 0, use setBitOfReversedStream otherwise*/
+          setBitOfReversedStream0(&obp, out, bit);
+        }
+      }
+    }
+  }
+}
+
+static void removePaddingBits(unsigned char* out, const unsigned char* in,
+                              size_t olinebits, size_t ilinebits, unsigned h)
+{
+  /*
+  After filtering there are still padding bits if scanlines have non multiple of 8 bit amounts. They need
+  to be removed (except at last scanline of (Adam7-reduced) image) before working with pure image buffers
+  for the Adam7 code, the color convert code and the output to the user.
+  in and out are allowed to be the same buffer, in may also be higher but still overlapping; in must
+  have >= ilinebits*h bits, out must have >= olinebits*h bits, olinebits must be <= ilinebits
+  also used to move bits after earlier such operations happened, e.g. in a sequence of reduced images from Adam7
+  only useful if (ilinebits - olinebits) is a value in the range 1..7
+  */
+  unsigned y;
+  size_t diff = ilinebits - olinebits;
+  size_t ibp = 0, obp = 0; /*input and output bit pointers*/
+  for(y = 0; y < h; ++y)
+  {
+    size_t x;
+    for(x = 0; x < olinebits; ++x)
+    {
+      unsigned char bit = readBitFromReversedStream(&ibp, in);
+      setBitOfReversedStream(&obp, out, bit);
+    }
+    ibp += diff;
+  }
+}
+
+/*out must be buffer big enough to contain full image, and in must contain the full decompressed data from
+the IDAT chunks (with filter index bytes and possible padding bits)
+return value is error*/
+static unsigned postProcessScanlines(unsigned char* out, unsigned char* in,
+                                     unsigned w, unsigned h, const LodePNGInfo* info_png)
+{
+  /*
+  This function converts the filtered-padded-interlaced data into pure 2D image buffer with the PNG's colortype.
+  Steps:
+  *) if no Adam7: 1) unfilter 2) remove padding bits (= posible extra bits per scanline if bpp < 8)
+  *) if adam7: 1) 7x unfilter 2) 7x remove padding bits 3) Adam7_deinterlace
+  NOTE: the in buffer will be overwritten with intermediate data!
+  */
+  unsigned bpp = lodepng_get_bpp(&info_png->color);
+  if(bpp == 0) return 31; /*error: invalid colortype*/
+
+  if(info_png->interlace_method == 0)
+  {
+    if(bpp < 8 && w * bpp != ((w * bpp + 7) / 8) * 8)
+    {
+      CERROR_TRY_RETURN(unfilter(in, in, w, h, bpp));
+      removePaddingBits(out, in, w * bpp, ((w * bpp + 7) / 8) * 8, h);
+    }
+    /*we can immediately filter into the out buffer, no other steps needed*/
+    else CERROR_TRY_RETURN(unfilter(out, in, w, h, bpp));
+  }
+  else /*interlace_method is 1 (Adam7)*/
+  {
+    unsigned passw[7], passh[7]; size_t filter_passstart[8], padded_passstart[8], passstart[8];
+    unsigned i;
+
+    Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp);
+
+    for(i = 0; i != 7; ++i)
+    {
+      CERROR_TRY_RETURN(unfilter(&in[padded_passstart[i]], &in[filter_passstart[i]], passw[i], passh[i], bpp));
+      /*TODO: possible efficiency improvement: if in this reduced image the bits fit nicely in 1 scanline,
+      move bytes instead of bits or move not at all*/
+      if(bpp < 8)
+      {
+        /*remove padding bits in scanlines; after this there still may be padding
+        bits between the different reduced images: each reduced image still starts nicely at a byte*/
+        removePaddingBits(&in[passstart[i]], &in[padded_passstart[i]], passw[i] * bpp,
+                          ((passw[i] * bpp + 7) / 8) * 8, passh[i]);
+      }
+    }
+
+    Adam7_deinterlace(out, in, w, h, bpp);
+  }
+
+  return 0;
+}
+
+static unsigned readChunk_PLTE(LodePNGColorMode* color, const unsigned char* data, size_t chunkLength)
+{
+  unsigned pos = 0, i;
+  if(color->palette) lodepng_free(color->palette);
+  color->palettesize = chunkLength / 3;
+  color->palette = (unsigned char*)lodepng_malloc(4 * color->palettesize);
+  if(!color->palette && color->palettesize)
+  {
+    color->palettesize = 0;
+    return 83; /*alloc fail*/
+  }
+  if(color->palettesize > 256) return 38; /*error: palette too big*/
+
+  for(i = 0; i != color->palettesize; ++i)
+  {
+    color->palette[4 * i + 0] = data[pos++]; /*R*/
+    color->palette[4 * i + 1] = data[pos++]; /*G*/
+    color->palette[4 * i + 2] = data[pos++]; /*B*/
+    color->palette[4 * i + 3] = 255; /*alpha*/
+  }
+
+  return 0; /* OK */
+}
+
+static unsigned readChunk_tRNS(LodePNGColorMode* color, const unsigned char* data, size_t chunkLength)
+{
+  unsigned i;
+  if(color->colortype == LCT_PALETTE)
+  {
+    /*error: more alpha values given than there are palette entries*/
+    if(chunkLength > color->palettesize) return 38;
+
+    for(i = 0; i != chunkLength; ++i) color->palette[4 * i + 3] = data[i];
+  }
+  else if(color->colortype == LCT_GREY)
+  {
+    /*error: this chunk must be 2 bytes for greyscale image*/
+    if(chunkLength != 2) return 30;
+
+    color->key_defined = 1;
+    color->key_r = color->key_g = color->key_b = 256u * data[0] + data[1];
+  }
+  else if(color->colortype == LCT_RGB)
+  {
+    /*error: this chunk must be 6 bytes for RGB image*/
+    if(chunkLength != 6) return 41;
+
+    color->key_defined = 1;
+    color->key_r = 256u * data[0] + data[1];
+    color->key_g = 256u * data[2] + data[3];
+    color->key_b = 256u * data[4] + data[5];
+  }
+  else return 42; /*error: tRNS chunk not allowed for other color models*/
+
+  return 0; /* OK */
+}
+
+
+#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS
+/*background color chunk (bKGD)*/
+static unsigned readChunk_bKGD(LodePNGInfo* info, const unsigned char* data, size_t chunkLength)
+{
+  if(info->color.colortype == LCT_PALETTE)
+  {
+    /*error: this chunk must be 1 byte for indexed color image*/
+    if(chunkLength != 1) return 43;
+
+    info->background_defined = 1;
+    info->background_r = info->background_g = info->background_b = data[0];
+  }
+  else if(info->color.colortype == LCT_GREY || info->color.colortype == LCT_GREY_ALPHA)
+  {
+    /*error: this chunk must be 2 bytes for greyscale image*/
+    if(chunkLength != 2) return 44;
+
+    info->background_defined = 1;
+    info->background_r = info->background_g = info->background_b = 256u * data[0] + data[1];
+  }
+  else if(info->color.colortype == LCT_RGB || info->color.colortype == LCT_RGBA)
+  {
+    /*error: this chunk must be 6 bytes for greyscale image*/
+    if(chunkLength != 6) return 45;
+
+    info->background_defined = 1;
+    info->background_r = 256u * data[0] + data[1];
+    info->background_g = 256u * data[2] + data[3];
+    info->background_b = 256u * data[4] + data[5];
+  }
+
+  return 0; /* OK */
+}
+
+/*text chunk (tEXt)*/
+static unsigned readChunk_tEXt(LodePNGInfo* info, const unsigned char* data, size_t chunkLength)
+{
+  unsigned error = 0;
+  char *key = 0, *str = 0;
+  unsigned i;
+
+  while(!error) /*not really a while loop, only used to break on error*/
+  {
+    unsigned length, string2_begin;
+
+    length = 0;
+    while(length < chunkLength && data[length] != 0) ++length;
+    /*even though it's not allowed by the standard, no error is thrown if
+    there's no null termination char, if the text is empty*/
+    if(length < 1 || length > 79) CERROR_BREAK(error, 89); /*keyword too short or long*/
+
+    key = (char*)lodepng_malloc(length + 1);
+    if(!key) CERROR_BREAK(error, 83); /*alloc fail*/
+
+    key[length] = 0;
+    for(i = 0; i != length; ++i) key[i] = (char)data[i];
+
+    string2_begin = length + 1; /*skip keyword null terminator*/
+
+    length = chunkLength < string2_begin ? 0 : chunkLength - string2_begin;
+    str = (char*)lodepng_malloc(length + 1);
+    if(!str) CERROR_BREAK(error, 83); /*alloc fail*/
+
+    str[length] = 0;
+    for(i = 0; i != length; ++i) str[i] = (char)data[string2_begin + i];
+
+    error = lodepng_add_text(info, key, str);
+
+    break;
+  }
+
+  lodepng_free(key);
+  lodepng_free(str);
+
+  return error;
+}
+
+/*compressed text chunk (zTXt)*/
+static unsigned readChunk_zTXt(LodePNGInfo* info, const LodePNGDecompressSettings* zlibsettings,
+                               const unsigned char* data, size_t chunkLength)
+{
+  unsigned error = 0;
+  unsigned i;
+
+  unsigned length, string2_begin;
+  char *key = 0;
+  ucvector decoded;
+
+  ucvector_init(&decoded);
+
+  while(!error) /*not really a while loop, only used to break on error*/
+  {
+    for(length = 0; length < chunkLength && data[length] != 0; ++length) ;
+    if(length + 2 >= chunkLength) CERROR_BREAK(error, 75); /*no null termination, corrupt?*/
+    if(length < 1 || length > 79) CERROR_BREAK(error, 89); /*keyword too short or long*/
+
+    key = (char*)lodepng_malloc(length + 1);
+    if(!key) CERROR_BREAK(error, 83); /*alloc fail*/
+
+    key[length] = 0;
+    for(i = 0; i != length; ++i) key[i] = (char)data[i];
+
+    if(data[length + 1] != 0) CERROR_BREAK(error, 72); /*the 0 byte indicating compression must be 0*/
+
+    string2_begin = length + 2;
+    if(string2_begin > chunkLength) CERROR_BREAK(error, 75); /*no null termination, corrupt?*/
+
+    length = chunkLength - string2_begin;
+    /*will fail if zlib error, e.g. if length is too small*/
+    error = zlib_decompress(&decoded.data, &decoded.size,
+                            (unsigned char*)(&data[string2_begin]),
+                            length, zlibsettings);
+    if(error) break;
+    ucvector_push_back(&decoded, 0);
+
+    error = lodepng_add_text(info, key, (char*)decoded.data);
+
+    break;
+  }
+
+  lodepng_free(key);
+  ucvector_cleanup(&decoded);
+
+  return error;
+}
+
+/*international text chunk (iTXt)*/
+static unsigned readChunk_iTXt(LodePNGInfo* info, const LodePNGDecompressSettings* zlibsettings,
+                               const unsigned char* data, size_t chunkLength)
+{
+  unsigned error = 0;
+  unsigned i;
+
+  unsigned length, begin, compressed;
+  char *key = 0, *langtag = 0, *transkey = 0;
+  ucvector decoded;
+  ucvector_init(&decoded);
+
+  while(!error) /*not really a while loop, only used to break on error*/
+  {
+    /*Quick check if the chunk length isn't too small. Even without check
+    it'd still fail with other error checks below if it's too short. This just gives a different error code.*/
+    if(chunkLength < 5) CERROR_BREAK(error, 30); /*iTXt chunk too short*/
+
+    /*read the key*/
+    for(length = 0; length < chunkLength && data[length] != 0; ++length) ;
+    if(length + 3 >= chunkLength) CERROR_BREAK(error, 75); /*no null termination char, corrupt?*/
+    if(length < 1 || length > 79) CERROR_BREAK(error, 89); /*keyword too short or long*/
+
+    key = (char*)lodepng_malloc(length + 1);
+    if(!key) CERROR_BREAK(error, 83); /*alloc fail*/
+
+    key[length] = 0;
+    for(i = 0; i != length; ++i) key[i] = (char)data[i];
+
+    /*read the compression method*/
+    compressed = data[length + 1];
+    if(data[length + 2] != 0) CERROR_BREAK(error, 72); /*the 0 byte indicating compression must be 0*/
+
+    /*even though it's not allowed by the standard, no error is thrown if
+    there's no null termination char, if the text is empty for the next 3 texts*/
+
+    /*read the langtag*/
+    begin = length + 3;
+    length = 0;
+    for(i = begin; i < chunkLength && data[i] != 0; ++i) ++length;
+
+    langtag = (char*)lodepng_malloc(length + 1);
+    if(!langtag) CERROR_BREAK(error, 83); /*alloc fail*/
+
+    langtag[length] = 0;
+    for(i = 0; i != length; ++i) langtag[i] = (char)data[begin + i];
+
+    /*read the transkey*/
+    begin += length + 1;
+    length = 0;
+    for(i = begin; i < chunkLength && data[i] != 0; ++i) ++length;
+
+    transkey = (char*)lodepng_malloc(length + 1);
+    if(!transkey) CERROR_BREAK(error, 83); /*alloc fail*/
+
+    transkey[length] = 0;
+    for(i = 0; i != length; ++i) transkey[i] = (char)data[begin + i];
+
+    /*read the actual text*/
+    begin += length + 1;
+
+    length = chunkLength < begin ? 0 : chunkLength - begin;
+
+    if(compressed)
+    {
+      /*will fail if zlib error, e.g. if length is too small*/
+      error = zlib_decompress(&decoded.data, &decoded.size,
+                              (unsigned char*)(&data[begin]),
+                              length, zlibsettings);
+      if(error) break;
+      if(decoded.allocsize < decoded.size) decoded.allocsize = decoded.size;
+      ucvector_push_back(&decoded, 0);
+    }
+    else
+    {
+      if(!ucvector_resize(&decoded, length + 1)) CERROR_BREAK(error, 83 /*alloc fail*/);
+
+      decoded.data[length] = 0;
+      for(i = 0; i != length; ++i) decoded.data[i] = data[begin + i];
+    }
+
+    error = lodepng_add_itext(info, key, langtag, transkey, (char*)decoded.data);
+
+    break;
+  }
+
+  lodepng_free(key);
+  lodepng_free(langtag);
+  lodepng_free(transkey);
+  ucvector_cleanup(&decoded);
+
+  return error;
+}
+
+static unsigned readChunk_tIME(LodePNGInfo* info, const unsigned char* data, size_t chunkLength)
+{
+  if(chunkLength != 7) return 73; /*invalid tIME chunk size*/
+
+  info->time_defined = 1;
+  info->time.year = 256u * data[0] + data[1];
+  info->time.month = data[2];
+  info->time.day = data[3];
+  info->time.hour = data[4];
+  info->time.minute = data[5];
+  info->time.second = data[6];
+
+  return 0; /* OK */
+}
+
+static unsigned readChunk_pHYs(LodePNGInfo* info, const unsigned char* data, size_t chunkLength)
+{
+  if(chunkLength != 9) return 74; /*invalid pHYs chunk size*/
+
+  info->phys_defined = 1;
+  info->phys_x = 16777216u * data[0] + 65536u * data[1] + 256u * data[2] + data[3];
+  info->phys_y = 16777216u * data[4] + 65536u * data[5] + 256u * data[6] + data[7];
+  info->phys_unit = data[8];
+
+  return 0; /* OK */
+}
+#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/
+
+/*read a PNG, the result will be in the same color type as the PNG (hence "generic")*/
+static void decodeGeneric(unsigned char** out, unsigned* w, unsigned* h,
+                          LodePNGState* state,
+                          const unsigned char* in, size_t insize)
+{
+  unsigned char IEND = 0;
+  const unsigned char* chunk;
+  size_t i;
+  ucvector idat; /*the data from idat chunks*/
+  ucvector scanlines;
+  size_t predict;
+  size_t numpixels;
+  size_t outsize = 0;
+
+  /*for unknown chunk order*/
+  unsigned unknown = 0;
+#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS
+  unsigned critical_pos = 1; /*1 = after IHDR, 2 = after PLTE, 3 = after IDAT*/
+#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/
+
+  /*provide some proper output values if error will happen*/
+  *out = 0;
+
+  state->error = lodepng_inspect(w, h, state, in, insize); /*reads header and resets other parameters in state->info_png*/
+  if(state->error) return;
+
+  numpixels = *w * *h;
+
+  /*multiplication overflow*/
+  if(*h != 0 && numpixels / *h != *w) CERROR_RETURN(state->error, 92);
+  /*multiplication overflow possible further below. Allows up to 2^31-1 pixel
+  bytes with 16-bit RGBA, the rest is room for filter bytes.*/
+  if(numpixels > 268435455) CERROR_RETURN(state->error, 92);
+
+  ucvector_init(&idat);
+  chunk = &in[33]; /*first byte of the first chunk after the header*/
+
+  /*loop through the chunks, ignoring unknown chunks and stopping at IEND chunk.
+  IDAT data is put at the start of the in buffer*/
+  while(!IEND && !state->error)
+  {
+    unsigned chunkLength;
+    const unsigned char* data; /*the data in the chunk*/
+
+    /*error: size of the in buffer too small to contain next chunk*/
+    if((size_t)((chunk - in) + 12) > insize || chunk < in) CERROR_BREAK(state->error, 30);
+
+    /*length of the data of the chunk, excluding the length bytes, chunk type and CRC bytes*/
+    chunkLength = lodepng_chunk_length(chunk);
+    /*error: chunk length larger than the max PNG chunk size*/
+    if(chunkLength > 2147483647) CERROR_BREAK(state->error, 63);
+
+    if((size_t)((chunk - in) + chunkLength + 12) > insize || (chunk + chunkLength + 12) < in)
+    {
+      CERROR_BREAK(state->error, 64); /*error: size of the in buffer too small to contain next chunk*/
+    }
+
+    data = lodepng_chunk_data_const(chunk);
+
+    /*IDAT chunk, containing compressed image data*/
+    if(lodepng_chunk_type_equals(chunk, "IDAT"))
+    {
+      size_t oldsize = idat.size;
+      if(!ucvector_resize(&idat, oldsize + chunkLength)) CERROR_BREAK(state->error, 83 /*alloc fail*/);
+      for(i = 0; i != chunkLength; ++i) idat.data[oldsize + i] = data[i];
+#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS
+      critical_pos = 3;
+#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/
+    }
+    /*IEND chunk*/
+    else if(lodepng_chunk_type_equals(chunk, "IEND"))
+    {
+      IEND = 1;
+    }
+    /*palette chunk (PLTE)*/
+    else if(lodepng_chunk_type_equals(chunk, "PLTE"))
+    {
+      state->error = readChunk_PLTE(&state->info_png.color, data, chunkLength);
+      if(state->error) break;
+#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS
+      critical_pos = 2;
+#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/
+    }
+    /*palette transparency chunk (tRNS)*/
+    else if(lodepng_chunk_type_equals(chunk, "tRNS"))
+    {
+      state->error = readChunk_tRNS(&state->info_png.color, data, chunkLength);
+      if(state->error) break;
+    }
+#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS
+    /*background color chunk (bKGD)*/
+    else if(lodepng_chunk_type_equals(chunk, "bKGD"))
+    {
+      state->error = readChunk_bKGD(&state->info_png, data, chunkLength);
+      if(state->error) break;
+    }
+    /*text chunk (tEXt)*/
+    else if(lodepng_chunk_type_equals(chunk, "tEXt"))
+    {
+      if(state->decoder.read_text_chunks)
+      {
+        state->error = readChunk_tEXt(&state->info_png, data, chunkLength);
+        if(state->error) break;
+      }
+    }
+    /*compressed text chunk (zTXt)*/
+    else if(lodepng_chunk_type_equals(chunk, "zTXt"))
+    {
+      if(state->decoder.read_text_chunks)
+      {
+        state->error = readChunk_zTXt(&state->info_png, &state->decoder.zlibsettings, data, chunkLength);
+        if(state->error) break;
+      }
+    }
+    /*international text chunk (iTXt)*/
+    else if(lodepng_chunk_type_equals(chunk, "iTXt"))
+    {
+      if(state->decoder.read_text_chunks)
+      {
+        state->error = readChunk_iTXt(&state->info_png, &state->decoder.zlibsettings, data, chunkLength);
+        if(state->error) break;
+      }
+    }
+    else if(lodepng_chunk_type_equals(chunk, "tIME"))
+    {
+      state->error = readChunk_tIME(&state->info_png, data, chunkLength);
+      if(state->error) break;
+    }
+    else if(lodepng_chunk_type_equals(chunk, "pHYs"))
+    {
+      state->error = readChunk_pHYs(&state->info_png, data, chunkLength);
+      if(state->error) break;
+    }
+#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/
+    else /*it's not an implemented chunk type, so ignore it: skip over the data*/
+    {
+      /*error: unknown critical chunk (5th bit of first byte of chunk type is 0)*/
+      if(!lodepng_chunk_ancillary(chunk)) CERROR_BREAK(state->error, 69);
+
+      unknown = 1;
+#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS
+      if(state->decoder.remember_unknown_chunks)
+      {
+        state->error = lodepng_chunk_append(&state->info_png.unknown_chunks_data[critical_pos - 1],
+                                            &state->info_png.unknown_chunks_size[critical_pos - 1], chunk);
+        if(state->error) break;
+      }
+#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/
+    }
+
+    if(!state->decoder.ignore_crc && !unknown) /*check CRC if wanted, only on known chunk types*/
+    {
+      if(lodepng_chunk_check_crc(chunk)) CERROR_BREAK(state->error, 57); /*invalid CRC*/
+    }
+
+    if(!IEND) chunk = lodepng_chunk_next_const(chunk);
+  }
+
+  ucvector_init(&scanlines);
+  /*predict output size, to allocate exact size for output buffer to avoid more dynamic allocation.
+  If the decompressed size does not match the prediction, the image must be corrupt.*/
+  if(state->info_png.interlace_method == 0)
+  {
+    /*The extra *h is added because this are the filter bytes every scanline starts with*/
+    predict = lodepng_get_raw_size_idat(*w, *h, &state->info_png.color) + *h;
+  }
+  else
+  {
+    /*Adam-7 interlaced: predicted size is the sum of the 7 sub-images sizes*/
+    const LodePNGColorMode* color = &state->info_png.color;
+    predict = 0;
+    predict += lodepng_get_raw_size_idat((*w + 7) >> 3, (*h + 7) >> 3, color) + ((*h + 7) >> 3);
+    if(*w > 4) predict += lodepng_get_raw_size_idat((*w + 3) >> 3, (*h + 7) >> 3, color) + ((*h + 7) >> 3);
+    predict += lodepng_get_raw_size_idat((*w + 3) >> 2, (*h + 3) >> 3, color) + ((*h + 3) >> 3);
+    if(*w > 2) predict += lodepng_get_raw_size_idat((*w + 1) >> 2, (*h + 3) >> 2, color) + ((*h + 3) >> 2);
+    predict += lodepng_get_raw_size_idat((*w + 1) >> 1, (*h + 1) >> 2, color) + ((*h + 1) >> 2);
+    if(*w > 1) predict += lodepng_get_raw_size_idat((*w + 0) >> 1, (*h + 1) >> 1, color) + ((*h + 1) >> 1);
+    predict += lodepng_get_raw_size_idat((*w + 0), (*h + 0) >> 1, color) + ((*h + 0) >> 1);
+  }
+  if(!state->error && !ucvector_reserve(&scanlines, predict)) state->error = 83; /*alloc fail*/
+  if(!state->error)
+  {
+    state->error = zlib_decompress(&scanlines.data, &scanlines.size, idat.data,
+                                   idat.size, &state->decoder.zlibsettings);
+    if(!state->error && scanlines.size != predict) state->error = 91; /*decompressed size doesn't match prediction*/
+  }
+  ucvector_cleanup(&idat);
+
+  if(!state->error)
+  {
+    outsize = lodepng_get_raw_size(*w, *h, &state->info_png.color);
+    *out = (unsigned char*)lodepng_malloc(outsize);
+    if(!*out) state->error = 83; /*alloc fail*/
+  }
+  if(!state->error)
+  {
+    for(i = 0; i < outsize; i++) (*out)[i] = 0;
+    state->error = postProcessScanlines(*out, scanlines.data, *w, *h, &state->info_png);
+  }
+  ucvector_cleanup(&scanlines);
+}
+
+unsigned lodepng_decode(unsigned char** out, unsigned* w, unsigned* h,
+                        LodePNGState* state,
+                        const unsigned char* in, size_t insize)
+{
+  *out = 0;
+  decodeGeneric(out, w, h, state, in, insize);
+  if(state->error) return state->error;
+  if(!state->decoder.color_convert || lodepng_color_mode_equal(&state->info_raw, &state->info_png.color))
+  {
+    /*same color type, no copying or converting of data needed*/
+    /*store the info_png color settings on the info_raw so that the info_raw still reflects what colortype
+    the raw image has to the end user*/
+    if(!state->decoder.color_convert)
+    {
+      state->error = lodepng_color_mode_copy(&state->info_raw, &state->info_png.color);
+      if(state->error) return state->error;
+    }
+  }
+  else
+  {
+    /*color conversion needed; sort of copy of the data*/
+    unsigned char* data = *out;
+    size_t outsize;
+
+    /*TODO: check if this works according to the statement in the documentation: "The converter can convert
+    from greyscale input color type, to 8-bit greyscale or greyscale with alpha"*/
+    if(!(state->info_raw.colortype == LCT_RGB || state->info_raw.colortype == LCT_RGBA)
+       && !(state->info_raw.bitdepth == 8))
+    {
+      return 56; /*unsupported color mode conversion*/
+    }
+
+    outsize = lodepng_get_raw_size(*w, *h, &state->info_raw);
+    *out = (unsigned char*)lodepng_malloc(outsize);
+    if(!(*out))
+    {
+      state->error = 83; /*alloc fail*/
+    }
+    else state->error = lodepng_convert(*out, data, &state->info_raw,
+                                        &state->info_png.color, *w, *h);
+    lodepng_free(data);
+  }
+  return state->error;
+}
+
+unsigned lodepng_decode_memory(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in,
+                               size_t insize, LodePNGColorType colortype, unsigned bitdepth)
+{
+  unsigned error;
+  LodePNGState state;
+  lodepng_state_init(&state);
+  state.info_raw.colortype = colortype;
+  state.info_raw.bitdepth = bitdepth;
+  error = lodepng_decode(out, w, h, &state, in, insize);
+  lodepng_state_cleanup(&state);
+  return error;
+}
+
+unsigned lodepng_decode32(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, size_t insize)
+{
+  return lodepng_decode_memory(out, w, h, in, insize, LCT_RGBA, 8);
+}
+
+unsigned lodepng_decode24(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, size_t insize)
+{
+  return lodepng_decode_memory(out, w, h, in, insize, LCT_RGB, 8);
+}
+
+#ifdef LODEPNG_COMPILE_DISK
+unsigned lodepng_decode_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename,
+                             LodePNGColorType colortype, unsigned bitdepth)
+{
+  unsigned char* buffer = 0;
+  size_t buffersize;
+  unsigned error;
+  error = lodepng_load_file(&buffer, &buffersize, filename);
+  if(!error) error = lodepng_decode_memory(out, w, h, buffer, buffersize, colortype, bitdepth);
+  lodepng_free(buffer);
+  return error;
+}
+
+unsigned lodepng_decode32_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename)
+{
+  return lodepng_decode_file(out, w, h, filename, LCT_RGBA, 8);
+}
+
+unsigned lodepng_decode24_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename)
+{
+  return lodepng_decode_file(out, w, h, filename, LCT_RGB, 8);
+}
+#endif /*LODEPNG_COMPILE_DISK*/
+
+void lodepng_decoder_settings_init(LodePNGDecoderSettings* settings)
+{
+  settings->color_convert = 1;
+#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS
+  settings->read_text_chunks = 1;
+  settings->remember_unknown_chunks = 0;
+#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/
+  settings->ignore_crc = 0;
+  lodepng_decompress_settings_init(&settings->zlibsettings);
+}
+
+#endif /*LODEPNG_COMPILE_DECODER*/
+
+#if defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER)
+
+void lodepng_state_init(LodePNGState* state)
+{
+#ifdef LODEPNG_COMPILE_DECODER
+  lodepng_decoder_settings_init(&state->decoder);
+#endif /*LODEPNG_COMPILE_DECODER*/
+#ifdef LODEPNG_COMPILE_ENCODER
+  lodepng_encoder_settings_init(&state->encoder);
+#endif /*LODEPNG_COMPILE_ENCODER*/
+  lodepng_color_mode_init(&state->info_raw);
+  lodepng_info_init(&state->info_png);
+  state->error = 1;
+}
+
+void lodepng_state_cleanup(LodePNGState* state)
+{
+  lodepng_color_mode_cleanup(&state->info_raw);
+  lodepng_info_cleanup(&state->info_png);
+}
+
+void lodepng_state_copy(LodePNGState* dest, const LodePNGState* source)
+{
+  lodepng_state_cleanup(dest);
+  *dest = *source;
+  lodepng_color_mode_init(&dest->info_raw);
+  lodepng_info_init(&dest->info_png);
+  dest->error = lodepng_color_mode_copy(&dest->info_raw, &source->info_raw); if(dest->error) return;
+  dest->error = lodepng_info_copy(&dest->info_png, &source->info_png); if(dest->error) return;
+}
+
+#endif /* defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) */
+
+#ifdef LODEPNG_COMPILE_ENCODER
+
+/* ////////////////////////////////////////////////////////////////////////// */
+/* / PNG Encoder                                                            / */
+/* ////////////////////////////////////////////////////////////////////////// */
+
+/*chunkName must be string of 4 characters*/
+static unsigned addChunk(ucvector* out, const char* chunkName, const unsigned char* data, size_t length)
+{
+  CERROR_TRY_RETURN(lodepng_chunk_create(&out->data, &out->size, (unsigned)length, chunkName, data));
+  out->allocsize = out->size; /*fix the allocsize again*/
+  return 0;
+}
+
+static void writeSignature(ucvector* out)
+{
+  /*8 bytes PNG signature, aka the magic bytes*/
+  ucvector_push_back(out, 137);
+  ucvector_push_back(out, 80);
+  ucvector_push_back(out, 78);
+  ucvector_push_back(out, 71);
+  ucvector_push_back(out, 13);
+  ucvector_push_back(out, 10);
+  ucvector_push_back(out, 26);
+  ucvector_push_back(out, 10);
+}
+
+static unsigned addChunk_IHDR(ucvector* out, unsigned w, unsigned h,
+                              LodePNGColorType colortype, unsigned bitdepth, unsigned interlace_method)
+{
+  unsigned error = 0;
+  ucvector header;
+  ucvector_init(&header);
+
+  lodepng_add32bitInt(&header, w); /*width*/
+  lodepng_add32bitInt(&header, h); /*height*/
+  ucvector_push_back(&header, (unsigned char)bitdepth); /*bit depth*/
+  ucvector_push_back(&header, (unsigned char)colortype); /*color type*/
+  ucvector_push_back(&header, 0); /*compression method*/
+  ucvector_push_back(&header, 0); /*filter method*/
+  ucvector_push_back(&header, interlace_method); /*interlace method*/
+
+  error = addChunk(out, "IHDR", header.data, header.size);
+  ucvector_cleanup(&header);
+
+  return error;
+}
+
+static unsigned addChunk_PLTE(ucvector* out, const LodePNGColorMode* info)
+{
+  unsigned error = 0;
+  size_t i;
+  ucvector PLTE;
+  ucvector_init(&PLTE);
+  for(i = 0; i != info->palettesize * 4; ++i)
+  {
+    /*add all channels except alpha channel*/
+    if(i % 4 != 3) ucvector_push_back(&PLTE, info->palette[i]);
+  }
+  error = addChunk(out, "PLTE", PLTE.data, PLTE.size);
+  ucvector_cleanup(&PLTE);
+
+  return error;
+}
+
+static unsigned addChunk_tRNS(ucvector* out, const LodePNGColorMode* info)
+{
+  unsigned error = 0;
+  size_t i;
+  ucvector tRNS;
+  ucvector_init(&tRNS);
+  if(info->colortype == LCT_PALETTE)
+  {
+    size_t amount = info->palettesize;
+    /*the tail of palette values that all have 255 as alpha, does not have to be encoded*/
+    for(i = info->palettesize; i != 0; --i)
+    {
+      if(info->palette[4 * (i - 1) + 3] == 255) --amount;
+      else break;
+    }
+    /*add only alpha channel*/
+    for(i = 0; i != amount; ++i) ucvector_push_back(&tRNS, info->palette[4 * i + 3]);
+  }
+  else if(info->colortype == LCT_GREY)
+  {
+    if(info->key_defined)
+    {
+      ucvector_push_back(&tRNS, (unsigned char)(info->key_r >> 8));
+      ucvector_push_back(&tRNS, (unsigned char)(info->key_r & 255));
+    }
+  }
+  else if(info->colortype == LCT_RGB)
+  {
+    if(info->key_defined)
+    {
+      ucvector_push_back(&tRNS, (unsigned char)(info->key_r >> 8));
+      ucvector_push_back(&tRNS, (unsigned char)(info->key_r & 255));
+      ucvector_push_back(&tRNS, (unsigned char)(info->key_g >> 8));
+      ucvector_push_back(&tRNS, (unsigned char)(info->key_g & 255));
+      ucvector_push_back(&tRNS, (unsigned char)(info->key_b >> 8));
+      ucvector_push_back(&tRNS, (unsigned char)(info->key_b & 255));
+    }
+  }
+
+  error = addChunk(out, "tRNS", tRNS.data, tRNS.size);
+  ucvector_cleanup(&tRNS);
+
+  return error;
+}
+
+static unsigned addChunk_IDAT(ucvector* out, const unsigned char* data, size_t datasize,
+                              LodePNGCompressSettings* zlibsettings)
+{
+  ucvector zlibdata;
+  unsigned error = 0;
+
+  /*compress with the Zlib compressor*/
+  ucvector_init(&zlibdata);
+  error = zlib_compress(&zlibdata.data, &zlibdata.size, data, datasize, zlibsettings);
+  if(!error) error = addChunk(out, "IDAT", zlibdata.data, zlibdata.size);
+  ucvector_cleanup(&zlibdata);
+
+  return error;
+}
+
+static unsigned addChunk_IEND(ucvector* out)
+{
+  unsigned error = 0;
+  error = addChunk(out, "IEND", 0, 0);
+  return error;
+}
+
+#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS
+
+static unsigned addChunk_tEXt(ucvector* out, const char* keyword, const char* textstring)
+{
+  unsigned error = 0;
+  size_t i;
+  ucvector text;
+  ucvector_init(&text);
+  for(i = 0; keyword[i] != 0; ++i) ucvector_push_back(&text, (unsigned char)keyword[i]);
+  if(i < 1 || i > 79) return 89; /*error: invalid keyword size*/
+  ucvector_push_back(&text, 0); /*0 termination char*/
+  for(i = 0; textstring[i] != 0; ++i) ucvector_push_back(&text, (unsigned char)textstring[i]);
+  error = addChunk(out, "tEXt", text.data, text.size);
+  ucvector_cleanup(&text);
+
+  return error;
+}
+
+static unsigned addChunk_zTXt(ucvector* out, const char* keyword, const char* textstring,
+                              LodePNGCompressSettings* zlibsettings)
+{
+  unsigned error = 0;
+  ucvector data, compressed;
+  size_t i, textsize = strlen(textstring);
+
+  ucvector_init(&data);
+  ucvector_init(&compressed);
+  for(i = 0; keyword[i] != 0; ++i) ucvector_push_back(&data, (unsigned char)keyword[i]);
+  if(i < 1 || i > 79) return 89; /*error: invalid keyword size*/
+  ucvector_push_back(&data, 0); /*0 termination char*/
+  ucvector_push_back(&data, 0); /*compression method: 0*/
+
+  error = zlib_compress(&compressed.data, &compressed.size,
+                        (unsigned char*)textstring, textsize, zlibsettings);
+  if(!error)
+  {
+    for(i = 0; i != compressed.size; ++i) ucvector_push_back(&data, compressed.data[i]);
+    error = addChunk(out, "zTXt", data.data, data.size);
+  }
+
+  ucvector_cleanup(&compressed);
+  ucvector_cleanup(&data);
+  return error;
+}
+
+static unsigned addChunk_iTXt(ucvector* out, unsigned compressed, const char* keyword, const char* langtag,
+                              const char* transkey, const char* textstring, LodePNGCompressSettings* zlibsettings)
+{
+  unsigned error = 0;
+  ucvector data;
+  size_t i, textsize = strlen(textstring);
+
+  ucvector_init(&data);
+
+  for(i = 0; keyword[i] != 0; ++i) ucvector_push_back(&data, (unsigned char)keyword[i]);
+  if(i < 1 || i > 79) return 89; /*error: invalid keyword size*/
+  ucvector_push_back(&data, 0); /*null termination char*/
+  ucvector_push_back(&data, compressed ? 1 : 0); /*compression flag*/
+  ucvector_push_back(&data, 0); /*compression method*/
+  for(i = 0; langtag[i] != 0; ++i) ucvector_push_back(&data, (unsigned char)langtag[i]);
+  ucvector_push_back(&data, 0); /*null termination char*/
+  for(i = 0; transkey[i] != 0; ++i) ucvector_push_back(&data, (unsigned char)transkey[i]);
+  ucvector_push_back(&data, 0); /*null termination char*/
+
+  if(compressed)
+  {
+    ucvector compressed_data;
+    ucvector_init(&compressed_data);
+    error = zlib_compress(&compressed_data.data, &compressed_data.size,
+                          (unsigned char*)textstring, textsize, zlibsettings);
+    if(!error)
+    {
+      for(i = 0; i != compressed_data.size; ++i) ucvector_push_back(&data, compressed_data.data[i]);
+    }
+    ucvector_cleanup(&compressed_data);
+  }
+  else /*not compressed*/
+  {
+    for(i = 0; textstring[i] != 0; ++i) ucvector_push_back(&data, (unsigned char)textstring[i]);
+  }
+
+  if(!error) error = addChunk(out, "iTXt", data.data, data.size);
+  ucvector_cleanup(&data);
+  return error;
+}
+
+static unsigned addChunk_bKGD(ucvector* out, const LodePNGInfo* info)
+{
+  unsigned error = 0;
+  ucvector bKGD;
+  ucvector_init(&bKGD);
+  if(info->color.colortype == LCT_GREY || info->color.colortype == LCT_GREY_ALPHA)
+  {
+    ucvector_push_back(&bKGD, (unsigned char)(info->background_r >> 8));
+    ucvector_push_back(&bKGD, (unsigned char)(info->background_r & 255));
+  }
+  else if(info->color.colortype == LCT_RGB || info->color.colortype == LCT_RGBA)
+  {
+    ucvector_push_back(&bKGD, (unsigned char)(info->background_r >> 8));
+    ucvector_push_back(&bKGD, (unsigned char)(info->background_r & 255));
+    ucvector_push_back(&bKGD, (unsigned char)(info->background_g >> 8));
+    ucvector_push_back(&bKGD, (unsigned char)(info->background_g & 255));
+    ucvector_push_back(&bKGD, (unsigned char)(info->background_b >> 8));
+    ucvector_push_back(&bKGD, (unsigned char)(info->background_b & 255));
+  }
+  else if(info->color.colortype == LCT_PALETTE)
+  {
+    ucvector_push_back(&bKGD, (unsigned char)(info->background_r & 255)); /*palette index*/
+  }
+
+  error = addChunk(out, "bKGD", bKGD.data, bKGD.size);
+  ucvector_cleanup(&bKGD);
+
+  return error;
+}
+
+static unsigned addChunk_tIME(ucvector* out, const LodePNGTime* time)
+{
+  unsigned error = 0;
+  unsigned char* data = (unsigned char*)lodepng_malloc(7);
+  if(!data) return 83; /*alloc fail*/
+  data[0] = (unsigned char)(time->year >> 8);
+  data[1] = (unsigned char)(time->year & 255);
+  data[2] = (unsigned char)time->month;
+  data[3] = (unsigned char)time->day;
+  data[4] = (unsigned char)time->hour;
+  data[5] = (unsigned char)time->minute;
+  data[6] = (unsigned char)time->second;
+  error = addChunk(out, "tIME", data, 7);
+  lodepng_free(data);
+  return error;
+}
+
+static unsigned addChunk_pHYs(ucvector* out, const LodePNGInfo* info)
+{
+  unsigned error = 0;
+  ucvector data;
+  ucvector_init(&data);
+
+  lodepng_add32bitInt(&data, info->phys_x);
+  lodepng_add32bitInt(&data, info->phys_y);
+  ucvector_push_back(&data, info->phys_unit);
+
+  error = addChunk(out, "pHYs", data.data, data.size);
+  ucvector_cleanup(&data);
+
+  return error;
+}
+
+#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/
+
+static void filterScanline(unsigned char* out, const unsigned char* scanline, const unsigned char* prevline,
+                           size_t length, size_t bytewidth, unsigned char filterType)
+{
+  size_t i;
+  switch(filterType)
+  {
+    case 0: /*None*/
+      for(i = 0; i != length; ++i) out[i] = scanline[i];
+      break;
+    case 1: /*Sub*/
+      for(i = 0; i != bytewidth; ++i) out[i] = scanline[i];
+      for(i = bytewidth; i < length; ++i) out[i] = scanline[i] - scanline[i - bytewidth];
+      break;
+    case 2: /*Up*/
+      if(prevline)
+      {
+        for(i = 0; i != length; ++i) out[i] = scanline[i] - prevline[i];
+      }
+      else
+      {
+        for(i = 0; i != length; ++i) out[i] = scanline[i];
+      }
+      break;
+    case 3: /*Average*/
+      if(prevline)
+      {
+        for(i = 0; i != bytewidth; ++i) out[i] = scanline[i] - (prevline[i] >> 1);
+        for(i = bytewidth; i < length; ++i) out[i] = scanline[i] - ((scanline[i - bytewidth] + prevline[i]) >> 1);
+      }
+      else
+      {
+        for(i = 0; i != bytewidth; ++i) out[i] = scanline[i];
+        for(i = bytewidth; i < length; ++i) out[i] = scanline[i] - (scanline[i - bytewidth] >> 1);
+      }
+      break;
+    case 4: /*Paeth*/
+      if(prevline)
+      {
+        /*paethPredictor(0, prevline[i], 0) is always prevline[i]*/
+        for(i = 0; i != bytewidth; ++i) out[i] = (scanline[i] - prevline[i]);
+        for(i = bytewidth; i < length; ++i)
+        {
+          out[i] = (scanline[i] - paethPredictor(scanline[i - bytewidth], prevline[i], prevline[i - bytewidth]));
+        }
+      }
+      else
+      {
+        for(i = 0; i != bytewidth; ++i) out[i] = scanline[i];
+        /*paethPredictor(scanline[i - bytewidth], 0, 0) is always scanline[i - bytewidth]*/
+        for(i = bytewidth; i < length; ++i) out[i] = (scanline[i] - scanline[i - bytewidth]);
+      }
+      break;
+    default: return; /*unexisting filter type given*/
+  }
+}
+
+/* log2 approximation. A slight bit faster than std::log. */
+static float flog2(float f)
+{
+  float result = 0;
+  while(f > 32) { result += 4; f /= 16; }
+  while(f > 2) { ++result; f /= 2; }
+  return result + 1.442695f * (f * f * f / 3 - 3 * f * f / 2 + 3 * f - 1.83333f);
+}
+
+static unsigned filter(unsigned char* out, const unsigned char* in, unsigned w, unsigned h,
+                       const LodePNGColorMode* info, const LodePNGEncoderSettings* settings)
+{
+  /*
+  For PNG filter method 0
+  out must be a buffer with as size: h + (w * h * bpp + 7) / 8, because there are
+  the scanlines with 1 extra byte per scanline
+  */
+
+  unsigned bpp = lodepng_get_bpp(info);
+  /*the width of a scanline in bytes, not including the filter type*/
+  size_t linebytes = (w * bpp + 7) / 8;
+  /*bytewidth is used for filtering, is 1 when bpp < 8, number of bytes per pixel otherwise*/
+  size_t bytewidth = (bpp + 7) / 8;
+  const unsigned char* prevline = 0;
+  unsigned x, y;
+  unsigned error = 0;
+  LodePNGFilterStrategy strategy = settings->filter_strategy;
+
+  /*
+  There is a heuristic called the minimum sum of absolute differences heuristic, suggested by the PNG standard:
+   *  If the image type is Palette, or the bit depth is smaller than 8, then do not filter the image (i.e.
+      use fixed filtering, with the filter None).
+   * (The other case) If the image type is Grayscale or RGB (with or without Alpha), and the bit depth is
+     not smaller than 8, then use adaptive filtering heuristic as follows: independently for each row, apply
+     all five filters and select the filter that produces the smallest sum of absolute values per row.
+  This heuristic is used if filter strategy is LFS_MINSUM and filter_palette_zero is true.
+
+  If filter_palette_zero is true and filter_strategy is not LFS_MINSUM, the above heuristic is followed,
+  but for "the other case", whatever strategy filter_strategy is set to instead of the minimum sum
+  heuristic is used.
+  */
+  if(settings->filter_palette_zero &&
+     (info->colortype == LCT_PALETTE || info->bitdepth < 8)) strategy = LFS_ZERO;
+
+  if(bpp == 0) return 31; /*error: invalid color type*/
+
+  if(strategy == LFS_ZERO)
+  {
+    for(y = 0; y != h; ++y)
+    {
+      size_t outindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/
+      size_t inindex = linebytes * y;
+      out[outindex] = 0; /*filter type byte*/
+      filterScanline(&out[outindex + 1], &in[inindex], prevline, linebytes, bytewidth, 0);
+      prevline = &in[inindex];
+    }
+  }
+  else if(strategy == LFS_MINSUM)
+  {
+    /*adaptive filtering*/
+    size_t sum[5];
+    unsigned char* attempt[5]; /*five filtering attempts, one for each filter type*/
+    size_t smallest = 0;
+    unsigned char type, bestType = 0;
+
+    for(type = 0; type != 5; ++type)
+    {
+      attempt[type] = (unsigned char*)lodepng_malloc(linebytes);
+      if(!attempt[type]) return 83; /*alloc fail*/
+    }
+
+    if(!error)
+    {
+      for(y = 0; y != h; ++y)
+      {
+        /*try the 5 filter types*/
+        for(type = 0; type != 5; ++type)
+        {
+          filterScanline(attempt[type], &in[y * linebytes], prevline, linebytes, bytewidth, type);
+
+          /*calculate the sum of the result*/
+          sum[type] = 0;
+          if(type == 0)
+          {
+            for(x = 0; x != linebytes; ++x) sum[type] += (unsigned char)(attempt[type][x]);
+          }
+          else
+          {
+            for(x = 0; x != linebytes; ++x)
+            {
+              /*For differences, each byte should be treated as signed, values above 127 are negative
+              (converted to signed char). Filtertype 0 isn't a difference though, so use unsigned there.
+              This means filtertype 0 is almost never chosen, but that is justified.*/
+              unsigned char s = attempt[type][x];
+              sum[type] += s < 128 ? s : (255U - s);
+            }
+          }
+
+          /*check if this is smallest sum (or if type == 0 it's the first case so always store the values)*/
+          if(type == 0 || sum[type] < smallest)
+          {
+            bestType = type;
+            smallest = sum[type];
+          }
+        }
+
+        prevline = &in[y * linebytes];
+
+        /*now fill the out values*/
+        out[y * (linebytes + 1)] = bestType; /*the first byte of a scanline will be the filter type*/
+        for(x = 0; x != linebytes; ++x) out[y * (linebytes + 1) + 1 + x] = attempt[bestType][x];
+      }
+    }
+
+    for(type = 0; type != 5; ++type) lodepng_free(attempt[type]);
+  }
+  else if(strategy == LFS_ENTROPY)
+  {
+    float sum[5];
+    unsigned char* attempt[5]; /*five filtering attempts, one for each filter type*/
+    float smallest = 0;
+    unsigned type, bestType = 0;
+    unsigned count[256];
+
+    for(type = 0; type != 5; ++type)
+    {
+      attempt[type] = (unsigned char*)lodepng_malloc(linebytes);
+      if(!attempt[type]) return 83; /*alloc fail*/
+    }
+
+    for(y = 0; y != h; ++y)
+    {
+      /*try the 5 filter types*/
+      for(type = 0; type != 5; ++type)
+      {
+        filterScanline(attempt[type], &in[y * linebytes], prevline, linebytes, bytewidth, type);
+        for(x = 0; x != 256; ++x) count[x] = 0;
+        for(x = 0; x != linebytes; ++x) ++count[attempt[type][x]];
+        ++count[type]; /*the filter type itself is part of the scanline*/
+        sum[type] = 0;
+        for(x = 0; x != 256; ++x)
+        {
+          float p = count[x] / (float)(linebytes + 1);
+          sum[type] += count[x] == 0 ? 0 : flog2(1 / p) * p;
+        }
+        /*check if this is smallest sum (or if type == 0 it's the first case so always store the values)*/
+        if(type == 0 || sum[type] < smallest)
+        {
+          bestType = type;
+          smallest = sum[type];
+        }
+      }
+
+      prevline = &in[y * linebytes];
+
+      /*now fill the out values*/
+      out[y * (linebytes + 1)] = bestType; /*the first byte of a scanline will be the filter type*/
+      for(x = 0; x != linebytes; ++x) out[y * (linebytes + 1) + 1 + x] = attempt[bestType][x];
+    }
+
+    for(type = 0; type != 5; ++type) lodepng_free(attempt[type]);
+  }
+  else if(strategy == LFS_PREDEFINED)
+  {
+    for(y = 0; y != h; ++y)
+    {
+      size_t outindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/
+      size_t inindex = linebytes * y;
+      unsigned char type = settings->predefined_filters[y];
+      out[outindex] = type; /*filter type byte*/
+      filterScanline(&out[outindex + 1], &in[inindex], prevline, linebytes, bytewidth, type);
+      prevline = &in[inindex];
+    }
+  }
+  else if(strategy == LFS_BRUTE_FORCE)
+  {
+    /*brute force filter chooser.
+    deflate the scanline after every filter attempt to see which one deflates best.
+    This is very slow and gives only slightly smaller, sometimes even larger, result*/
+    size_t size[5];
+    unsigned char* attempt[5]; /*five filtering attempts, one for each filter type*/
+    size_t smallest = 0;
+    unsigned type = 0, bestType = 0;
+    unsigned char* dummy;
+    LodePNGCompressSettings zlibsettings = settings->zlibsettings;
+    /*use fixed tree on the attempts so that the tree is not adapted to the filtertype on purpose,
+    to simulate the true case where the tree is the same for the whole image. Sometimes it gives
+    better result with dynamic tree anyway. Using the fixed tree sometimes gives worse, but in rare
+    cases better compression. It does make this a bit less slow, so it's worth doing this.*/
+    zlibsettings.btype = 1;
+    /*a custom encoder likely doesn't read the btype setting and is optimized for complete PNG
+    images only, so disable it*/
+    zlibsettings.custom_zlib = 0;
+    zlibsettings.custom_deflate = 0;
+    for(type = 0; type != 5; ++type)
+    {
+      attempt[type] = (unsigned char*)lodepng_malloc(linebytes);
+      if(!attempt[type]) return 83; /*alloc fail*/
+    }
+    for(y = 0; y != h; ++y) /*try the 5 filter types*/
+    {
+      for(type = 0; type != 5; ++type)
+      {
+        unsigned testsize = linebytes;
+        /*if(testsize > 8) testsize /= 8;*/ /*it already works good enough by testing a part of the row*/
+
+        filterScanline(attempt[type], &in[y * linebytes], prevline, linebytes, bytewidth, type);
+        size[type] = 0;
+        dummy = 0;
+        zlib_compress(&dummy, &size[type], attempt[type], testsize, &zlibsettings);
+        lodepng_free(dummy);
+        /*check if this is smallest size (or if type == 0 it's the first case so always store the values)*/
+        if(type == 0 || size[type] < smallest)
+        {
+          bestType = type;
+          smallest = size[type];
+        }
+      }
+      prevline = &in[y * linebytes];
+      out[y * (linebytes + 1)] = bestType; /*the first byte of a scanline will be the filter type*/
+      for(x = 0; x != linebytes; ++x) out[y * (linebytes + 1) + 1 + x] = attempt[bestType][x];
+    }
+    for(type = 0; type != 5; ++type) lodepng_free(attempt[type]);
+  }
+  else return 88; /* unknown filter strategy */
+
+  return error;
+}
+
+static void addPaddingBits(unsigned char* out, const unsigned char* in,
+                           size_t olinebits, size_t ilinebits, unsigned h)
+{
+  /*The opposite of the removePaddingBits function
+  olinebits must be >= ilinebits*/
+  unsigned y;
+  size_t diff = olinebits - ilinebits;
+  size_t obp = 0, ibp = 0; /*bit pointers*/
+  for(y = 0; y != h; ++y)
+  {
+    size_t x;
+    for(x = 0; x < ilinebits; ++x)
+    {
+      unsigned char bit = readBitFromReversedStream(&ibp, in);
+      setBitOfReversedStream(&obp, out, bit);
+    }
+    /*obp += diff; --> no, fill in some value in the padding bits too, to avoid
+    "Use of uninitialised value of size ###" warning from valgrind*/
+    for(x = 0; x != diff; ++x) setBitOfReversedStream(&obp, out, 0);
+  }
+}
+
+/*
+in: non-interlaced image with size w*h
+out: the same pixels, but re-ordered according to PNG's Adam7 interlacing, with
+ no padding bits between scanlines, but between reduced images so that each
+ reduced image starts at a byte.
+bpp: bits per pixel
+there are no padding bits, not between scanlines, not between reduced images
+in has the following size in bits: w * h * bpp.
+out is possibly bigger due to padding bits between reduced images
+NOTE: comments about padding bits are only relevant if bpp < 8
+*/
+static void Adam7_interlace(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp)
+{
+  unsigned passw[7], passh[7];
+  size_t filter_passstart[8], padded_passstart[8], passstart[8];
+  unsigned i;
+
+  Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp);
+
+  if(bpp >= 8)
+  {
+    for(i = 0; i != 7; ++i)
+    {
+      unsigned x, y, b;
+      size_t bytewidth = bpp / 8;
+      for(y = 0; y < passh[i]; ++y)
+      for(x = 0; x < passw[i]; ++x)
+      {
+        size_t pixelinstart = ((ADAM7_IY[i] + y * ADAM7_DY[i]) * w + ADAM7_IX[i] + x * ADAM7_DX[i]) * bytewidth;
+        size_t pixeloutstart = passstart[i] + (y * passw[i] + x) * bytewidth;
+        for(b = 0; b < bytewidth; ++b)
+        {
+          out[pixeloutstart + b] = in[pixelinstart + b];
+        }
+      }
+    }
+  }
+  else /*bpp < 8: Adam7 with pixels < 8 bit is a bit trickier: with bit pointers*/
+  {
+    for(i = 0; i != 7; ++i)
+    {
+      unsigned x, y, b;
+      unsigned ilinebits = bpp * passw[i];
+      unsigned olinebits = bpp * w;
+      size_t obp, ibp; /*bit pointers (for out and in buffer)*/
+      for(y = 0; y < passh[i]; ++y)
+      for(x = 0; x < passw[i]; ++x)
+      {
+        ibp = (ADAM7_IY[i] + y * ADAM7_DY[i]) * olinebits + (ADAM7_IX[i] + x * ADAM7_DX[i]) * bpp;
+        obp = (8 * passstart[i]) + (y * ilinebits + x * bpp);
+        for(b = 0; b < bpp; ++b)
+        {
+          unsigned char bit = readBitFromReversedStream(&ibp, in);
+          setBitOfReversedStream(&obp, out, bit);
+        }
+      }
+    }
+  }
+}
+
+/*out must be buffer big enough to contain uncompressed IDAT chunk data, and in must contain the full image.
+return value is error**/
+static unsigned preProcessScanlines(unsigned char** out, size_t* outsize, const unsigned char* in,
+                                    unsigned w, unsigned h,
+                                    const LodePNGInfo* info_png, const LodePNGEncoderSettings* settings)
+{
+  /*
+  This function converts the pure 2D image with the PNG's colortype, into filtered-padded-interlaced data. Steps:
+  *) if no Adam7: 1) add padding bits (= posible extra bits per scanline if bpp < 8) 2) filter
+  *) if adam7: 1) Adam7_interlace 2) 7x add padding bits 3) 7x filter
+  */
+  unsigned bpp = lodepng_get_bpp(&info_png->color);
+  unsigned error = 0;
+
+  if(info_png->interlace_method == 0)
+  {
+    *outsize = h + (h * ((w * bpp + 7) / 8)); /*image size plus an extra byte per scanline + possible padding bits*/
+    *out = (unsigned char*)lodepng_malloc(*outsize);
+    if(!(*out) && (*outsize)) error = 83; /*alloc fail*/
+
+    if(!error)
+    {
+      /*non multiple of 8 bits per scanline, padding bits needed per scanline*/
+      if(bpp < 8 && w * bpp != ((w * bpp + 7) / 8) * 8)
+      {
+        unsigned char* padded = (unsigned char*)lodepng_malloc(h * ((w * bpp + 7) / 8));
+        if(!padded) error = 83; /*alloc fail*/
+        if(!error)
+        {
+          addPaddingBits(padded, in, ((w * bpp + 7) / 8) * 8, w * bpp, h);
+          error = filter(*out, padded, w, h, &info_png->color, settings);
+        }
+        lodepng_free(padded);
+      }
+      else
+      {
+        /*we can immediately filter into the out buffer, no other steps needed*/
+        error = filter(*out, in, w, h, &info_png->color, settings);
+      }
+    }
+  }
+  else /*interlace_method is 1 (Adam7)*/
+  {
+    unsigned passw[7], passh[7];
+    size_t filter_passstart[8], padded_passstart[8], passstart[8];
+    unsigned char* adam7;
+
+    Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp);
+
+    *outsize = filter_passstart[7]; /*image size plus an extra byte per scanline + possible padding bits*/
+    *out = (unsigned char*)lodepng_malloc(*outsize);
+    if(!(*out)) error = 83; /*alloc fail*/
+
+    adam7 = (unsigned char*)lodepng_malloc(passstart[7]);
+    if(!adam7 && passstart[7]) error = 83; /*alloc fail*/
+
+    if(!error)
+    {
+      unsigned i;
+
+      Adam7_interlace(adam7, in, w, h, bpp);
+      for(i = 0; i != 7; ++i)
+      {
+        if(bpp < 8)
+        {
+          unsigned char* padded = (unsigned char*)lodepng_malloc(padded_passstart[i + 1] - padded_passstart[i]);
+          if(!padded) ERROR_BREAK(83); /*alloc fail*/
+          addPaddingBits(padded, &adam7[passstart[i]],
+                         ((passw[i] * bpp + 7) / 8) * 8, passw[i] * bpp, passh[i]);
+          error = filter(&(*out)[filter_passstart[i]], padded,
+                         passw[i], passh[i], &info_png->color, settings);
+          lodepng_free(padded);
+        }
+        else
+        {
+          error = filter(&(*out)[filter_passstart[i]], &adam7[padded_passstart[i]],
+                         passw[i], passh[i], &info_png->color, settings);
+        }
+
+        if(error) break;
+      }
+    }
+
+    lodepng_free(adam7);
+  }
+
+  return error;
+}
+
+/*
+palette must have 4 * palettesize bytes allocated, and given in format RGBARGBARGBARGBA...
+returns 0 if the palette is opaque,
+returns 1 if the palette has a single color with alpha 0 ==> color key
+returns 2 if the palette is semi-translucent.
+*/
+static unsigned getPaletteTranslucency(const unsigned char* palette, size_t palettesize)
+{
+  size_t i;
+  unsigned key = 0;
+  unsigned r = 0, g = 0, b = 0; /*the value of the color with alpha 0, so long as color keying is possible*/
+  for(i = 0; i != palettesize; ++i)
+  {
+    if(!key && palette[4 * i + 3] == 0)
+    {
+      r = palette[4 * i + 0]; g = palette[4 * i + 1]; b = palette[4 * i + 2];
+      key = 1;
+      i = (size_t)(-1); /*restart from beginning, to detect earlier opaque colors with key's value*/
+    }
+    else if(palette[4 * i + 3] != 255) return 2;
+    /*when key, no opaque RGB may have key's RGB*/
+    else if(key && r == palette[i * 4 + 0] && g == palette[i * 4 + 1] && b == palette[i * 4 + 2]) return 2;
+  }
+  return key;
+}
+
+#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS
+static unsigned addUnknownChunks(ucvector* out, unsigned char* data, size_t datasize)
+{
+  unsigned char* inchunk = data;
+  while((size_t)(inchunk - data) < datasize)
+  {
+    CERROR_TRY_RETURN(lodepng_chunk_append(&out->data, &out->size, inchunk));
+    out->allocsize = out->size; /*fix the allocsize again*/
+    inchunk = lodepng_chunk_next(inchunk);
+  }
+  return 0;
+}
+#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/
+
+unsigned lodepng_encode(unsigned char** out, size_t* outsize,
+                        const unsigned char* image, unsigned w, unsigned h,
+                        LodePNGState* state)
+{
+  LodePNGInfo info;
+  ucvector outv;
+  unsigned char* data = 0; /*uncompressed version of the IDAT chunk data*/
+  size_t datasize = 0;
+
+  /*provide some proper output values if error will happen*/
+  *out = 0;
+  *outsize = 0;
+  state->error = 0;
+
+  lodepng_info_init(&info);
+  lodepng_info_copy(&info, &state->info_png);
+
+  if((info.color.colortype == LCT_PALETTE || state->encoder.force_palette)
+      && (info.color.palettesize == 0 || info.color.palettesize > 256))
+  {
+    state->error = 68; /*invalid palette size, it is only allowed to be 1-256*/
+    return state->error;
+  }
+
+  if(state->encoder.auto_convert)
+  {
+    state->error = lodepng_auto_choose_color(&info.color, image, w, h, &state->info_raw);
+  }
+  if(state->error) return state->error;
+
+  if(state->encoder.zlibsettings.btype > 2)
+  {
+    CERROR_RETURN_ERROR(state->error, 61); /*error: unexisting btype*/
+  }
+  if(state->info_png.interlace_method > 1)
+  {
+    CERROR_RETURN_ERROR(state->error, 71); /*error: unexisting interlace mode*/
+  }
+
+  state->error = checkColorValidity(info.color.colortype, info.color.bitdepth);
+  if(state->error) return state->error; /*error: unexisting color type given*/
+  state->error = checkColorValidity(state->info_raw.colortype, state->info_raw.bitdepth);
+  if(state->error) return state->error; /*error: unexisting color type given*/
+
+  if(!lodepng_color_mode_equal(&state->info_raw, &info.color))
+  {
+    unsigned char* converted;
+    size_t size = (w * h * (size_t)lodepng_get_bpp(&info.color) + 7) / 8;
+
+    converted = (unsigned char*)lodepng_malloc(size);
+    if(!converted && size) state->error = 83; /*alloc fail*/
+    if(!state->error)
+    {
+      state->error = lodepng_convert(converted, image, &info.color, &state->info_raw, w, h);
+    }
+    if(!state->error) preProcessScanlines(&data, &datasize, converted, w, h, &info, &state->encoder);
+    lodepng_free(converted);
+  }
+  else preProcessScanlines(&data, &datasize, image, w, h, &info, &state->encoder);
+
+  ucvector_init(&outv);
+  while(!state->error) /*while only executed once, to break on error*/
+  {
+#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS
+    size_t i;
+#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/
+    /*write signature and chunks*/
+    writeSignature(&outv);
+    /*IHDR*/
+    addChunk_IHDR(&outv, w, h, info.color.colortype, info.color.bitdepth, info.interlace_method);
+#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS
+    /*unknown chunks between IHDR and PLTE*/
+    if(info.unknown_chunks_data[0])
+    {
+      state->error = addUnknownChunks(&outv, info.unknown_chunks_data[0], info.unknown_chunks_size[0]);
+      if(state->error) break;
+    }
+#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/
+    /*PLTE*/
+    if(info.color.colortype == LCT_PALETTE)
+    {
+      addChunk_PLTE(&outv, &info.color);
+    }
+    if(state->encoder.force_palette && (info.color.colortype == LCT_RGB || info.color.colortype == LCT_RGBA))
+    {
+      addChunk_PLTE(&outv, &info.color);
+    }
+    /*tRNS*/
+    if(info.color.colortype == LCT_PALETTE && getPaletteTranslucency(info.color.palette, info.color.palettesize) != 0)
+    {
+      addChunk_tRNS(&outv, &info.color);
+    }
+    if((info.color.colortype == LCT_GREY || info.color.colortype == LCT_RGB) && info.color.key_defined)
+    {
+      addChunk_tRNS(&outv, &info.color);
+    }
+#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS
+    /*bKGD (must come between PLTE and the IDAt chunks*/
+    if(info.background_defined) addChunk_bKGD(&outv, &info);
+    /*pHYs (must come before the IDAT chunks)*/
+    if(info.phys_defined) addChunk_pHYs(&outv, &info);
+
+    /*unknown chunks between PLTE and IDAT*/
+    if(info.unknown_chunks_data[1])
+    {
+      state->error = addUnknownChunks(&outv, info.unknown_chunks_data[1], info.unknown_chunks_size[1]);
+      if(state->error) break;
+    }
+#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/
+    /*IDAT (multiple IDAT chunks must be consecutive)*/
+    state->error = addChunk_IDAT(&outv, data, datasize, &state->encoder.zlibsettings);
+    if(state->error) break;
+#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS
+    /*tIME*/
+    if(info.time_defined) addChunk_tIME(&outv, &info.time);
+    /*tEXt and/or zTXt*/
+    for(i = 0; i != info.text_num; ++i)
+    {
+      if(strlen(info.text_keys[i]) > 79)
+      {
+        state->error = 66; /*text chunk too large*/
+        break;
+      }
+      if(strlen(info.text_keys[i]) < 1)
+      {
+        state->error = 67; /*text chunk too small*/
+        break;
+      }
+      if(state->encoder.text_compression)
+      {
+        addChunk_zTXt(&outv, info.text_keys[i], info.text_strings[i], &state->encoder.zlibsettings);
+      }
+      else
+      {
+        addChunk_tEXt(&outv, info.text_keys[i], info.text_strings[i]);
+      }
+    }
+    /*LodePNG version id in text chunk*/
+    if(state->encoder.add_id)
+    {
+      unsigned alread_added_id_text = 0;
+      for(i = 0; i != info.text_num; ++i)
+      {
+        if(!strcmp(info.text_keys[i], "LodePNG"))
+        {
+          alread_added_id_text = 1;
+          break;
+        }
+      }
+      if(alread_added_id_text == 0)
+      {
+        addChunk_tEXt(&outv, "LodePNG", LODEPNG_VERSION_STRING); /*it's shorter as tEXt than as zTXt chunk*/
+      }
+    }
+    /*iTXt*/
+    for(i = 0; i != info.itext_num; ++i)
+    {
+      if(strlen(info.itext_keys[i]) > 79)
+      {
+        state->error = 66; /*text chunk too large*/
+        break;
+      }
+      if(strlen(info.itext_keys[i]) < 1)
+      {
+        state->error = 67; /*text chunk too small*/
+        break;
+      }
+      addChunk_iTXt(&outv, state->encoder.text_compression,
+                    info.itext_keys[i], info.itext_langtags[i], info.itext_transkeys[i], info.itext_strings[i],
+                    &state->encoder.zlibsettings);
+    }
+
+    /*unknown chunks between IDAT and IEND*/
+    if(info.unknown_chunks_data[2])
+    {
+      state->error = addUnknownChunks(&outv, info.unknown_chunks_data[2], info.unknown_chunks_size[2]);
+      if(state->error) break;
+    }
+#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/
+    addChunk_IEND(&outv);
+
+    break; /*this isn't really a while loop; no error happened so break out now!*/
+  }
+
+  lodepng_info_cleanup(&info);
+  lodepng_free(data);
+  /*instead of cleaning the vector up, give it to the output*/
+  *out = outv.data;
+  *outsize = outv.size;
+
+  return state->error;
+}
+
+unsigned lodepng_encode_memory(unsigned char** out, size_t* outsize, const unsigned char* image,
+                               unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth)
+{
+  unsigned error;
+  LodePNGState state;
+  lodepng_state_init(&state);
+  state.info_raw.colortype = colortype;
+  state.info_raw.bitdepth = bitdepth;
+  state.info_png.color.colortype = colortype;
+  state.info_png.color.bitdepth = bitdepth;
+  lodepng_encode(out, outsize, image, w, h, &state);
+  error = state.error;
+  lodepng_state_cleanup(&state);
+  return error;
+}
+
+unsigned lodepng_encode32(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h)
+{
+  return lodepng_encode_memory(out, outsize, image, w, h, LCT_RGBA, 8);
+}
+
+unsigned lodepng_encode24(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h)
+{
+  return lodepng_encode_memory(out, outsize, image, w, h, LCT_RGB, 8);
+}
+
+#ifdef LODEPNG_COMPILE_DISK
+unsigned lodepng_encode_file(const char* filename, const unsigned char* image, unsigned w, unsigned h,
+                             LodePNGColorType colortype, unsigned bitdepth)
+{
+  unsigned char* buffer;
+  size_t buffersize;
+  unsigned error = lodepng_encode_memory(&buffer, &buffersize, image, w, h, colortype, bitdepth);
+  if(!error) error = lodepng_save_file(buffer, buffersize, filename);
+  lodepng_free(buffer);
+  return error;
+}
+
+unsigned lodepng_encode32_file(const char* filename, const unsigned char* image, unsigned w, unsigned h)
+{
+  return lodepng_encode_file(filename, image, w, h, LCT_RGBA, 8);
+}
+
+unsigned lodepng_encode24_file(const char* filename, const unsigned char* image, unsigned w, unsigned h)
+{
+  return lodepng_encode_file(filename, image, w, h, LCT_RGB, 8);
+}
+#endif /*LODEPNG_COMPILE_DISK*/
+
+void lodepng_encoder_settings_init(LodePNGEncoderSettings* settings)
+{
+  lodepng_compress_settings_init(&settings->zlibsettings);
+  settings->filter_palette_zero = 1;
+  settings->filter_strategy = LFS_MINSUM;
+  settings->auto_convert = 1;
+  settings->force_palette = 0;
+  settings->predefined_filters = 0;
+#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS
+  settings->add_id = 0;
+  settings->text_compression = 1;
+#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/
+}
+
+#endif /*LODEPNG_COMPILE_ENCODER*/
+#endif /*LODEPNG_COMPILE_PNG*/
+
+#ifdef LODEPNG_COMPILE_ERROR_TEXT
+/*
+This returns the description of a numerical error code in English. This is also
+the documentation of all the error codes.
+*/
+const char* lodepng_error_text(unsigned code)
+{
+  switch(code)
+  {
+    case 0: return "no error, everything went ok";
+    case 1: return "nothing done yet"; /*the Encoder/Decoder has done nothing yet, error checking makes no sense yet*/
+    case 10: return "end of input memory reached without huffman end code"; /*while huffman decoding*/
+    case 11: return "error in code tree made it jump outside of huffman tree"; /*while huffman decoding*/
+    case 13: return "problem while processing dynamic deflate block";
+    case 14: return "problem while processing dynamic deflate block";
+    case 15: return "problem while processing dynamic deflate block";
+    case 16: return "unexisting code while processing dynamic deflate block";
+    case 17: return "end of out buffer memory reached while inflating";
+    case 18: return "invalid distance code while inflating";
+    case 19: return "end of out buffer memory reached while inflating";
+    case 20: return "invalid deflate block BTYPE encountered while decoding";
+    case 21: return "NLEN is not ones complement of LEN in a deflate block";
+     /*end of out buffer memory reached while inflating:
+     This can happen if the inflated deflate data is longer than the amount of bytes required to fill up
+     all the pixels of the image, given the color depth and image dimensions. Something that doesn't
+     happen in a normal, well encoded, PNG image.*/
+    case 22: return "end of out buffer memory reached while inflating";
+    case 23: return "end of in buffer memory reached while inflating";
+    case 24: return "invalid FCHECK in zlib header";
+    case 25: return "invalid compression method in zlib header";
+    case 26: return "FDICT encountered in zlib header while it's not used for PNG";
+    case 27: return "PNG file is smaller than a PNG header";
+    /*Checks the magic file header, the first 8 bytes of the PNG file*/
+    case 28: return "incorrect PNG signature, it's no PNG or corrupted";
+    case 29: return "first chunk is not the header chunk";
+    case 30: return "chunk length too large, chunk broken off at end of file";
+    case 31: return "illegal PNG color type or bpp";
+    case 32: return "illegal PNG compression method";
+    case 33: return "illegal PNG filter method";
+    case 34: return "illegal PNG interlace method";
+    case 35: return "chunk length of a chunk is too large or the chunk too small";
+    case 36: return "illegal PNG filter type encountered";
+    case 37: return "illegal bit depth for this color type given";
+    case 38: return "the palette is too big"; /*more than 256 colors*/
+    case 39: return "more palette alpha values given in tRNS chunk than there are colors in the palette";
+    case 40: return "tRNS chunk has wrong size for greyscale image";
+    case 41: return "tRNS chunk has wrong size for RGB image";
+    case 42: return "tRNS chunk appeared while it was not allowed for this color type";
+    case 43: return "bKGD chunk has wrong size for palette image";
+    case 44: return "bKGD chunk has wrong size for greyscale image";
+    case 45: return "bKGD chunk has wrong size for RGB image";
+    case 48: return "empty input buffer given to decoder. Maybe caused by non-existing file?";
+    case 49: return "jumped past memory while generating dynamic huffman tree";
+    case 50: return "jumped past memory while generating dynamic huffman tree";
+    case 51: return "jumped past memory while inflating huffman block";
+    case 52: return "jumped past memory while inflating";
+    case 53: return "size of zlib data too small";
+    case 54: return "repeat symbol in tree while there was no value symbol yet";
+    /*jumped past tree while generating huffman tree, this could be when the
+    tree will have more leaves than symbols after generating it out of the
+    given lenghts. They call this an oversubscribed dynamic bit lengths tree in zlib.*/
+    case 55: return "jumped past tree while generating huffman tree";
+    case 56: return "given output image colortype or bitdepth not supported for color conversion";
+    case 57: return "invalid CRC encountered (checking CRC can be disabled)";
+    case 58: return "invalid ADLER32 encountered (checking ADLER32 can be disabled)";
+    case 59: return "requested color conversion not supported";
+    case 60: return "invalid window size given in the settings of the encoder (must be 0-32768)";
+    case 61: return "invalid BTYPE given in the settings of the encoder (only 0, 1 and 2 are allowed)";
+    /*LodePNG leaves the choice of RGB to greyscale conversion formula to the user.*/
+    case 62: return "conversion from color to greyscale not supported";
+    case 63: return "length of a chunk too long, max allowed for PNG is 2147483647 bytes per chunk"; /*(2^31-1)*/
+    /*this would result in the inability of a deflated block to ever contain an end code. It must be at least 1.*/
+    case 64: return "the length of the END symbol 256 in the Huffman tree is 0";
+    case 66: return "the length of a text chunk keyword given to the encoder is longer than the maximum of 79 bytes";
+    case 67: return "the length of a text chunk keyword given to the encoder is smaller than the minimum of 1 byte";
+    case 68: return "tried to encode a PLTE chunk with a palette that has less than 1 or more than 256 colors";
+    case 69: return "unknown chunk type with 'critical' flag encountered by the decoder";
+    case 71: return "unexisting interlace mode given to encoder (must be 0 or 1)";
+    case 72: return "while decoding, unexisting compression method encountering in zTXt or iTXt chunk (it must be 0)";
+    case 73: return "invalid tIME chunk size";
+    case 74: return "invalid pHYs chunk size";
+    /*length could be wrong, or data chopped off*/
+    case 75: return "no null termination char found while decoding text chunk";
+    case 76: return "iTXt chunk too short to contain required bytes";
+    case 77: return "integer overflow in buffer size";
+    case 78: return "failed to open file for reading"; /*file doesn't exist or couldn't be opened for reading*/
+    case 79: return "failed to open file for writing";
+    case 80: return "tried creating a tree of 0 symbols";
+    case 81: return "lazy matching at pos 0 is impossible";
+    case 82: return "color conversion to palette requested while a color isn't in palette";
+    case 83: return "memory allocation failed";
+    case 84: return "given image too small to contain all pixels to be encoded";
+    case 86: return "impossible offset in lz77 encoding (internal bug)";
+    case 87: return "must provide custom zlib function pointer if LODEPNG_COMPILE_ZLIB is not defined";
+    case 88: return "invalid filter strategy given for LodePNGEncoderSettings.filter_strategy";
+    case 89: return "text chunk keyword too short or long: must have size 1-79";
+    /*the windowsize in the LodePNGCompressSettings. Requiring POT(==> & instead of %) makes encoding 12% faster.*/
+    case 90: return "windowsize must be a power of two";
+    case 91: return "invalid decompressed idat size";
+    case 92: return "too many pixels, not supported";
+    case 93: return "zero width or height is invalid";
+    case 94: return "header chunk must have a size of 13 bytes";
+  }
+  return "unknown error code";
+}
+#endif /*LODEPNG_COMPILE_ERROR_TEXT*/
+
+/* ////////////////////////////////////////////////////////////////////////// */
+/* ////////////////////////////////////////////////////////////////////////// */
+/* // C++ Wrapper                                                          // */
+/* ////////////////////////////////////////////////////////////////////////// */
+/* ////////////////////////////////////////////////////////////////////////// */
+
+#ifdef LODEPNG_COMPILE_CPP
+namespace lodepng
+{
+
+#ifdef LODEPNG_COMPILE_DISK
+unsigned load_file(std::vector<unsigned char>& buffer, const std::string& filename)
+{
+  long size = lodepng_filesize(filename.c_str());
+  if(size < 0) return 78;
+  buffer.resize((size_t)size);
+  return size == 0 ? 0 : lodepng_buffer_file(&buffer[0], (size_t)size, filename.c_str());
+}
+
+/*write given buffer to the file, overwriting the file, it doesn't append to it.*/
+unsigned save_file(const std::vector<unsigned char>& buffer, const std::string& filename)
+{
+  return lodepng_save_file(buffer.empty() ? 0 : &buffer[0], buffer.size(), filename.c_str());
+}
+#endif /* LODEPNG_COMPILE_DISK */
+
+#ifdef LODEPNG_COMPILE_ZLIB
+#ifdef LODEPNG_COMPILE_DECODER
+unsigned decompress(std::vector<unsigned char>& out, const unsigned char* in, size_t insize,
+                    const LodePNGDecompressSettings& settings)
+{
+  unsigned char* buffer = 0;
+  size_t buffersize = 0;
+  unsigned error = zlib_decompress(&buffer, &buffersize, in, insize, &settings);
+  if(buffer)
+  {
+    out.insert(out.end(), &buffer[0], &buffer[buffersize]);
+    lodepng_free(buffer);
+  }
+  return error;
+}
+
+unsigned decompress(std::vector<unsigned char>& out, const std::vector<unsigned char>& in,
+                    const LodePNGDecompressSettings& settings)
+{
+  return decompress(out, in.empty() ? 0 : &in[0], in.size(), settings);
+}
+#endif /* LODEPNG_COMPILE_DECODER */
+
+#ifdef LODEPNG_COMPILE_ENCODER
+unsigned compress(std::vector<unsigned char>& out, const unsigned char* in, size_t insize,
+                  const LodePNGCompressSettings& settings)
+{
+  unsigned char* buffer = 0;
+  size_t buffersize = 0;
+  unsigned error = zlib_compress(&buffer, &buffersize, in, insize, &settings);
+  if(buffer)
+  {
+    out.insert(out.end(), &buffer[0], &buffer[buffersize]);
+    lodepng_free(buffer);
+  }
+  return error;
+}
+
+unsigned compress(std::vector<unsigned char>& out, const std::vector<unsigned char>& in,
+                  const LodePNGCompressSettings& settings)
+{
+  return compress(out, in.empty() ? 0 : &in[0], in.size(), settings);
+}
+#endif /* LODEPNG_COMPILE_ENCODER */
+#endif /* LODEPNG_COMPILE_ZLIB */
+
+
+#ifdef LODEPNG_COMPILE_PNG
+
+State::State()
+{
+  lodepng_state_init(this);
+}
+
+State::State(const State& other)
+{
+  lodepng_state_init(this);
+  lodepng_state_copy(this, &other);
+}
+
+State::~State()
+{
+  lodepng_state_cleanup(this);
+}
+
+State& State::operator=(const State& other)
+{
+  lodepng_state_copy(this, &other);
+  return *this;
+}
+
+#ifdef LODEPNG_COMPILE_DECODER
+
+unsigned decode(std::vector<unsigned char>& out, unsigned& w, unsigned& h, const unsigned char* in,
+                size_t insize, LodePNGColorType colortype, unsigned bitdepth)
+{
+  unsigned char* buffer;
+  unsigned error = lodepng_decode_memory(&buffer, &w, &h, in, insize, colortype, bitdepth);
+  if(buffer && !error)
+  {
+    State state;
+    state.info_raw.colortype = colortype;
+    state.info_raw.bitdepth = bitdepth;
+    size_t buffersize = lodepng_get_raw_size(w, h, &state.info_raw);
+    out.insert(out.end(), &buffer[0], &buffer[buffersize]);
+    lodepng_free(buffer);
+  }
+  return error;
+}
+
+unsigned decode(std::vector<unsigned char>& out, unsigned& w, unsigned& h,
+                const std::vector<unsigned char>& in, LodePNGColorType colortype, unsigned bitdepth)
+{
+  return decode(out, w, h, in.empty() ? 0 : &in[0], (unsigned)in.size(), colortype, bitdepth);
+}
+
+unsigned decode(std::vector<unsigned char>& out, unsigned& w, unsigned& h,
+                State& state,
+                const unsigned char* in, size_t insize)
+{
+  unsigned char* buffer = NULL;
+  unsigned error = lodepng_decode(&buffer, &w, &h, &state, in, insize);
+  if(buffer && !error)
+  {
+    size_t buffersize = lodepng_get_raw_size(w, h, &state.info_raw);
+    out.insert(out.end(), &buffer[0], &buffer[buffersize]);
+  }
+  lodepng_free(buffer);
+  return error;
+}
+
+unsigned decode(std::vector<unsigned char>& out, unsigned& w, unsigned& h,
+                State& state,
+                const std::vector<unsigned char>& in)
+{
+  return decode(out, w, h, state, in.empty() ? 0 : &in[0], in.size());
+}
+
+#ifdef LODEPNG_COMPILE_DISK
+unsigned decode(std::vector<unsigned char>& out, unsigned& w, unsigned& h, const std::string& filename,
+                LodePNGColorType colortype, unsigned bitdepth)
+{
+  std::vector<unsigned char> buffer;
+  unsigned error = load_file(buffer, filename);
+  if(error) return error;
+  return decode(out, w, h, buffer, colortype, bitdepth);
+}
+#endif /* LODEPNG_COMPILE_DECODER */
+#endif /* LODEPNG_COMPILE_DISK */
+
+#ifdef LODEPNG_COMPILE_ENCODER
+unsigned encode(std::vector<unsigned char>& out, const unsigned char* in, unsigned w, unsigned h,
+                LodePNGColorType colortype, unsigned bitdepth)
+{
+  unsigned char* buffer;
+  size_t buffersize;
+  unsigned error = lodepng_encode_memory(&buffer, &buffersize, in, w, h, colortype, bitdepth);
+  if(buffer)
+  {
+    out.insert(out.end(), &buffer[0], &buffer[buffersize]);
+    lodepng_free(buffer);
+  }
+  return error;
+}
+
+unsigned encode(std::vector<unsigned char>& out,
+                const std::vector<unsigned char>& in, unsigned w, unsigned h,
+                LodePNGColorType colortype, unsigned bitdepth)
+{
+  if(lodepng_get_raw_size_lct(w, h, colortype, bitdepth) > in.size()) return 84;
+  return encode(out, in.empty() ? 0 : &in[0], w, h, colortype, bitdepth);
+}
+
+unsigned encode(std::vector<unsigned char>& out,
+                const unsigned char* in, unsigned w, unsigned h,
+                State& state)
+{
+  unsigned char* buffer;
+  size_t buffersize;
+  unsigned error = lodepng_encode(&buffer, &buffersize, in, w, h, &state);
+  if(buffer)
+  {
+    out.insert(out.end(), &buffer[0], &buffer[buffersize]);
+    lodepng_free(buffer);
+  }
+  return error;
+}
+
+unsigned encode(std::vector<unsigned char>& out,
+                const std::vector<unsigned char>& in, unsigned w, unsigned h,
+                State& state)
+{
+  if(lodepng_get_raw_size(w, h, &state.info_raw) > in.size()) return 84;
+  return encode(out, in.empty() ? 0 : &in[0], w, h, state);
+}
+
+#ifdef LODEPNG_COMPILE_DISK
+unsigned encode(const std::string& filename,
+                const unsigned char* in, unsigned w, unsigned h,
+                LodePNGColorType colortype, unsigned bitdepth)
+{
+  std::vector<unsigned char> buffer;
+  unsigned error = encode(buffer, in, w, h, colortype, bitdepth);
+  if(!error) error = save_file(buffer, filename);
+  return error;
+}
+
+unsigned encode(const std::string& filename,
+                const std::vector<unsigned char>& in, unsigned w, unsigned h,
+                LodePNGColorType colortype, unsigned bitdepth)
+{
+  if(lodepng_get_raw_size_lct(w, h, colortype, bitdepth) > in.size()) return 84;
+  return encode(filename, in.empty() ? 0 : &in[0], w, h, colortype, bitdepth);
+}
+#endif /* LODEPNG_COMPILE_DISK */
+#endif /* LODEPNG_COMPILE_ENCODER */
+#endif /* LODEPNG_COMPILE_PNG */
+} /* namespace lodepng */
+#endif /*LODEPNG_COMPILE_CPP*/
diff --git a/external/lodepng/lodepng.h b/external/lodepng/lodepng.h
new file mode 100644
index 0000000..94e8195
--- /dev/null
+++ b/external/lodepng/lodepng.h
@@ -0,0 +1,1759 @@
+/*
+LodePNG version 20160501
+
+Copyright (c) 2005-2016 Lode Vandevenne
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any damages
+arising from the use of this software.
+
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+
+    1. The origin of this software must not be misrepresented; you must not
+    claim that you wrote the original software. If you use this software
+    in a product, an acknowledgment in the product documentation would be
+    appreciated but is not required.
+
+    2. Altered source versions must be plainly marked as such, and must not be
+    misrepresented as being the original software.
+
+    3. This notice may not be removed or altered from any source
+    distribution.
+*/
+
+#ifndef LODEPNG_H
+#define LODEPNG_H
+
+#include <string.h> /*for size_t*/
+
+extern const char* LODEPNG_VERSION_STRING;
+
+/*
+The following #defines are used to create code sections. They can be disabled
+to disable code sections, which can give faster compile time and smaller binary.
+The "NO_COMPILE" defines are designed to be used to pass as defines to the
+compiler command to disable them without modifying this header, e.g.
+-DLODEPNG_NO_COMPILE_ZLIB for gcc.
+In addition to those below, you can also define LODEPNG_NO_COMPILE_CRC to
+allow implementing a custom lodepng_crc32.
+*/
+/*deflate & zlib. If disabled, you must specify alternative zlib functions in
+the custom_zlib field of the compress and decompress settings*/
+#ifndef LODEPNG_NO_COMPILE_ZLIB
+#define LODEPNG_COMPILE_ZLIB
+#endif
+/*png encoder and png decoder*/
+#ifndef LODEPNG_NO_COMPILE_PNG
+#define LODEPNG_COMPILE_PNG
+#endif
+/*deflate&zlib decoder and png decoder*/
+#ifndef LODEPNG_NO_COMPILE_DECODER
+#define LODEPNG_COMPILE_DECODER
+#endif
+/*deflate&zlib encoder and png encoder*/
+#ifndef LODEPNG_NO_COMPILE_ENCODER
+#define LODEPNG_COMPILE_ENCODER
+#endif
+/*the optional built in harddisk file loading and saving functions*/
+#ifndef LODEPNG_NO_COMPILE_DISK
+#define LODEPNG_COMPILE_DISK
+#endif
+/*support for chunks other than IHDR, IDAT, PLTE, tRNS, IEND: ancillary and unknown chunks*/
+#ifndef LODEPNG_NO_COMPILE_ANCILLARY_CHUNKS
+#define LODEPNG_COMPILE_ANCILLARY_CHUNKS
+#endif
+/*ability to convert error numerical codes to English text string*/
+#ifndef LODEPNG_NO_COMPILE_ERROR_TEXT
+#define LODEPNG_COMPILE_ERROR_TEXT
+#endif
+/*Compile the default allocators (C's free, malloc and realloc). If you disable this,
+you can define the functions lodepng_free, lodepng_malloc and lodepng_realloc in your
+source files with custom allocators.*/
+#ifndef LODEPNG_NO_COMPILE_ALLOCATORS
+#define LODEPNG_COMPILE_ALLOCATORS
+#endif
+/*compile the C++ version (you can disable the C++ wrapper here even when compiling for C++)*/
+#ifdef __cplusplus
+#ifndef LODEPNG_NO_COMPILE_CPP
+#define LODEPNG_COMPILE_CPP
+#endif
+#endif
+
+#ifdef LODEPNG_COMPILE_CPP
+#include <vector>
+#include <string>
+#endif /*LODEPNG_COMPILE_CPP*/
+
+#ifdef LODEPNG_COMPILE_PNG
+/*The PNG color types (also used for raw).*/
+typedef enum LodePNGColorType
+{
+  LCT_GREY = 0, /*greyscale: 1,2,4,8,16 bit*/
+  LCT_RGB = 2, /*RGB: 8,16 bit*/
+  LCT_PALETTE = 3, /*palette: 1,2,4,8 bit*/
+  LCT_GREY_ALPHA = 4, /*greyscale with alpha: 8,16 bit*/
+  LCT_RGBA = 6 /*RGB with alpha: 8,16 bit*/
+} LodePNGColorType;
+
+#ifdef LODEPNG_COMPILE_DECODER
+/*
+Converts PNG data in memory to raw pixel data.
+out: Output parameter. Pointer to buffer that will contain the raw pixel data.
+     After decoding, its size is w * h * (bytes per pixel) bytes larger than
+     initially. Bytes per pixel depends on colortype and bitdepth.
+     Must be freed after usage with free(*out).
+     Note: for 16-bit per channel colors, uses big endian format like PNG does.
+w: Output parameter. Pointer to width of pixel data.
+h: Output parameter. Pointer to height of pixel data.
+in: Memory buffer with the PNG file.
+insize: size of the in buffer.
+colortype: the desired color type for the raw output image. See explanation on PNG color types.
+bitdepth: the desired bit depth for the raw output image. See explanation on PNG color types.
+Return value: LodePNG error code (0 means no error).
+*/
+unsigned lodepng_decode_memory(unsigned char** out, unsigned* w, unsigned* h,
+                               const unsigned char* in, size_t insize,
+                               LodePNGColorType colortype, unsigned bitdepth);
+
+/*Same as lodepng_decode_memory, but always decodes to 32-bit RGBA raw image*/
+unsigned lodepng_decode32(unsigned char** out, unsigned* w, unsigned* h,
+                          const unsigned char* in, size_t insize);
+
+/*Same as lodepng_decode_memory, but always decodes to 24-bit RGB raw image*/
+unsigned lodepng_decode24(unsigned char** out, unsigned* w, unsigned* h,
+                          const unsigned char* in, size_t insize);
+
+#ifdef LODEPNG_COMPILE_DISK
+/*
+Load PNG from disk, from file with given name.
+Same as the other decode functions, but instead takes a filename as input.
+*/
+unsigned lodepng_decode_file(unsigned char** out, unsigned* w, unsigned* h,
+                             const char* filename,
+                             LodePNGColorType colortype, unsigned bitdepth);
+
+/*Same as lodepng_decode_file, but always decodes to 32-bit RGBA raw image.*/
+unsigned lodepng_decode32_file(unsigned char** out, unsigned* w, unsigned* h,
+                               const char* filename);
+
+/*Same as lodepng_decode_file, but always decodes to 24-bit RGB raw image.*/
+unsigned lodepng_decode24_file(unsigned char** out, unsigned* w, unsigned* h,
+                               const char* filename);
+#endif /*LODEPNG_COMPILE_DISK*/
+#endif /*LODEPNG_COMPILE_DECODER*/
+
+
+#ifdef LODEPNG_COMPILE_ENCODER
+/*
+Converts raw pixel data into a PNG image in memory. The colortype and bitdepth
+  of the output PNG image cannot be chosen, they are automatically determined
+  by the colortype, bitdepth and content of the input pixel data.
+  Note: for 16-bit per channel colors, needs big endian format like PNG does.
+out: Output parameter. Pointer to buffer that will contain the PNG image data.
+     Must be freed after usage with free(*out).
+outsize: Output parameter. Pointer to the size in bytes of the out buffer.
+image: The raw pixel data to encode. The size of this buffer should be
+       w * h * (bytes per pixel), bytes per pixel depends on colortype and bitdepth.
+w: width of the raw pixel data in pixels.
+h: height of the raw pixel data in pixels.
+colortype: the color type of the raw input image. See explanation on PNG color types.
+bitdepth: the bit depth of the raw input image. See explanation on PNG color types.
+Return value: LodePNG error code (0 means no error).
+*/
+unsigned lodepng_encode_memory(unsigned char** out, size_t* outsize,
+                               const unsigned char* image, unsigned w, unsigned h,
+                               LodePNGColorType colortype, unsigned bitdepth);
+
+/*Same as lodepng_encode_memory, but always encodes from 32-bit RGBA raw image.*/
+unsigned lodepng_encode32(unsigned char** out, size_t* outsize,
+                          const unsigned char* image, unsigned w, unsigned h);
+
+/*Same as lodepng_encode_memory, but always encodes from 24-bit RGB raw image.*/
+unsigned lodepng_encode24(unsigned char** out, size_t* outsize,
+                          const unsigned char* image, unsigned w, unsigned h);
+
+#ifdef LODEPNG_COMPILE_DISK
+/*
+Converts raw pixel data into a PNG file on disk.
+Same as the other encode functions, but instead takes a filename as output.
+NOTE: This overwrites existing files without warning!
+*/
+unsigned lodepng_encode_file(const char* filename,
+                             const unsigned char* image, unsigned w, unsigned h,
+                             LodePNGColorType colortype, unsigned bitdepth);
+
+/*Same as lodepng_encode_file, but always encodes from 32-bit RGBA raw image.*/
+unsigned lodepng_encode32_file(const char* filename,
+                               const unsigned char* image, unsigned w, unsigned h);
+
+/*Same as lodepng_encode_file, but always encodes from 24-bit RGB raw image.*/
+unsigned lodepng_encode24_file(const char* filename,
+                               const unsigned char* image, unsigned w, unsigned h);
+#endif /*LODEPNG_COMPILE_DISK*/
+#endif /*LODEPNG_COMPILE_ENCODER*/
+
+
+#ifdef LODEPNG_COMPILE_CPP
+namespace lodepng
+{
+#ifdef LODEPNG_COMPILE_DECODER
+/*Same as lodepng_decode_memory, but decodes to an std::vector. The colortype
+is the format to output the pixels to. Default is RGBA 8-bit per channel.*/
+unsigned decode(std::vector<unsigned char>& out, unsigned& w, unsigned& h,
+                const unsigned char* in, size_t insize,
+                LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8);
+unsigned decode(std::vector<unsigned char>& out, unsigned& w, unsigned& h,
+                const std::vector<unsigned char>& in,
+                LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8);
+#ifdef LODEPNG_COMPILE_DISK
+/*
+Converts PNG file from disk to raw pixel data in memory.
+Same as the other decode functions, but instead takes a filename as input.
+*/
+unsigned decode(std::vector<unsigned char>& out, unsigned& w, unsigned& h,
+                const std::string& filename,
+                LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8);
+#endif /* LODEPNG_COMPILE_DISK */
+#endif /* LODEPNG_COMPILE_DECODER */
+
+#ifdef LODEPNG_COMPILE_ENCODER
+/*Same as lodepng_encode_memory, but encodes to an std::vector. colortype
+is that of the raw input data. The output PNG color type will be auto chosen.*/
+unsigned encode(std::vector<unsigned char>& out,
+                const unsigned char* in, unsigned w, unsigned h,
+                LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8);
+unsigned encode(std::vector<unsigned char>& out,
+                const std::vector<unsigned char>& in, unsigned w, unsigned h,
+                LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8);
+#ifdef LODEPNG_COMPILE_DISK
+/*
+Converts 32-bit RGBA raw pixel data into a PNG file on disk.
+Same as the other encode functions, but instead takes a filename as output.
+NOTE: This overwrites existing files without warning!
+*/
+unsigned encode(const std::string& filename,
+                const unsigned char* in, unsigned w, unsigned h,
+                LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8);
+unsigned encode(const std::string& filename,
+                const std::vector<unsigned char>& in, unsigned w, unsigned h,
+                LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8);
+#endif /* LODEPNG_COMPILE_DISK */
+#endif /* LODEPNG_COMPILE_ENCODER */
+} /* namespace lodepng */
+#endif /*LODEPNG_COMPILE_CPP*/
+#endif /*LODEPNG_COMPILE_PNG*/
+
+#ifdef LODEPNG_COMPILE_ERROR_TEXT
+/*Returns an English description of the numerical error code.*/
+const char* lodepng_error_text(unsigned code);
+#endif /*LODEPNG_COMPILE_ERROR_TEXT*/
+
+#ifdef LODEPNG_COMPILE_DECODER
+/*Settings for zlib decompression*/
+typedef struct LodePNGDecompressSettings LodePNGDecompressSettings;
+struct LodePNGDecompressSettings
+{
+  unsigned ignore_adler32; /*if 1, continue and don't give an error message if the Adler32 checksum is corrupted*/
+
+  /*use custom zlib decoder instead of built in one (default: null)*/
+  unsigned (*custom_zlib)(unsigned char**, size_t*,
+                          const unsigned char*, size_t,
+                          const LodePNGDecompressSettings*);
+  /*use custom deflate decoder instead of built in one (default: null)
+  if custom_zlib is used, custom_deflate is ignored since only the built in
+  zlib function will call custom_deflate*/
+  unsigned (*custom_inflate)(unsigned char**, size_t*,
+                             const unsigned char*, size_t,
+                             const LodePNGDecompressSettings*);
+
+  const void* custom_context; /*optional custom settings for custom functions*/
+};
+
+extern const LodePNGDecompressSettings lodepng_default_decompress_settings;
+void lodepng_decompress_settings_init(LodePNGDecompressSettings* settings);
+#endif /*LODEPNG_COMPILE_DECODER*/
+
+#ifdef LODEPNG_COMPILE_ENCODER
+/*
+Settings for zlib compression. Tweaking these settings tweaks the balance
+between speed and compression ratio.
+*/
+typedef struct LodePNGCompressSettings LodePNGCompressSettings;
+struct LodePNGCompressSettings /*deflate = compress*/
+{
+  /*LZ77 related settings*/
+  unsigned btype; /*the block type for LZ (0, 1, 2 or 3, see zlib standard). Should be 2 for proper compression.*/
+  unsigned use_lz77; /*whether or not to use LZ77. Should be 1 for proper compression.*/
+  unsigned windowsize; /*must be a power of two <= 32768. higher compresses more but is slower. Default value: 2048.*/
+  unsigned minmatch; /*mininum lz77 length. 3 is normally best, 6 can be better for some PNGs. Default: 0*/
+  unsigned nicematch; /*stop searching if >= this length found. Set to 258 for best compression. Default: 128*/
+  unsigned lazymatching; /*use lazy matching: better compression but a bit slower. Default: true*/
+
+  /*use custom zlib encoder instead of built in one (default: null)*/
+  unsigned (*custom_zlib)(unsigned char**, size_t*,
+                          const unsigned char*, size_t,
+                          const LodePNGCompressSettings*);
+  /*use custom deflate encoder instead of built in one (default: null)
+  if custom_zlib is used, custom_deflate is ignored since only the built in
+  zlib function will call custom_deflate*/
+  unsigned (*custom_deflate)(unsigned char**, size_t*,
+                             const unsigned char*, size_t,
+                             const LodePNGCompressSettings*);
+
+  const void* custom_context; /*optional custom settings for custom functions*/
+};
+
+extern const LodePNGCompressSettings lodepng_default_compress_settings;
+void lodepng_compress_settings_init(LodePNGCompressSettings* settings);
+#endif /*LODEPNG_COMPILE_ENCODER*/
+
+#ifdef LODEPNG_COMPILE_PNG
+/*
+Color mode of an image. Contains all information required to decode the pixel
+bits to RGBA colors. This information is the same as used in the PNG file
+format, and is used both for PNG and raw image data in LodePNG.
+*/
+typedef struct LodePNGColorMode
+{
+  /*header (IHDR)*/
+  LodePNGColorType colortype; /*color type, see PNG standard or documentation further in this header file*/
+  unsigned bitdepth;  /*bits per sample, see PNG standard or documentation further in this header file*/
+
+  /*
+  palette (PLTE and tRNS)
+
+  Dynamically allocated with the colors of the palette, including alpha.
+  When encoding a PNG, to store your colors in the palette of the LodePNGColorMode, first use
+  lodepng_palette_clear, then for each color use lodepng_palette_add.
+  If you encode an image without alpha with palette, don't forget to put value 255 in each A byte of the palette.
+
+  When decoding, by default you can ignore this palette, since LodePNG already
+  fills the palette colors in the pixels of the raw RGBA output.
+
+  The palette is only supported for color type 3.
+  */
+  unsigned char* palette; /*palette in RGBARGBA... order. When allocated, must be either 0, or have size 1024*/
+  size_t palettesize; /*palette size in number of colors (amount of bytes is 4 * palettesize)*/
+
+  /*
+  transparent color key (tRNS)
+
+  This color uses the same bit depth as the bitdepth value in this struct, which can be 1-bit to 16-bit.
+  For greyscale PNGs, r, g and b will all 3 be set to the same.
+
+  When decoding, by default you can ignore this information, since LodePNG sets
+  pixels with this key to transparent already in the raw RGBA output.
+
+  The color key is only supported for color types 0 and 2.
+  */
+  unsigned key_defined; /*is a transparent color key given? 0 = false, 1 = true*/
+  unsigned key_r;       /*red/greyscale component of color key*/
+  unsigned key_g;       /*green component of color key*/
+  unsigned key_b;       /*blue component of color key*/
+} LodePNGColorMode;
+
+/*init, cleanup and copy functions to use with this struct*/
+void lodepng_color_mode_init(LodePNGColorMode* info);
+void lodepng_color_mode_cleanup(LodePNGColorMode* info);
+/*return value is error code (0 means no error)*/
+unsigned lodepng_color_mode_copy(LodePNGColorMode* dest, const LodePNGColorMode* source);
+
+void lodepng_palette_clear(LodePNGColorMode* info);
+/*add 1 color to the palette*/
+unsigned lodepng_palette_add(LodePNGColorMode* info,
+                             unsigned char r, unsigned char g, unsigned char b, unsigned char a);
+
+/*get the total amount of bits per pixel, based on colortype and bitdepth in the struct*/
+unsigned lodepng_get_bpp(const LodePNGColorMode* info);
+/*get the amount of color channels used, based on colortype in the struct.
+If a palette is used, it counts as 1 channel.*/
+unsigned lodepng_get_channels(const LodePNGColorMode* info);
+/*is it a greyscale type? (only colortype 0 or 4)*/
+unsigned lodepng_is_greyscale_type(const LodePNGColorMode* info);
+/*has it got an alpha channel? (only colortype 2 or 6)*/
+unsigned lodepng_is_alpha_type(const LodePNGColorMode* info);
+/*has it got a palette? (only colortype 3)*/
+unsigned lodepng_is_palette_type(const LodePNGColorMode* info);
+/*only returns true if there is a palette and there is a value in the palette with alpha < 255.
+Loops through the palette to check this.*/
+unsigned lodepng_has_palette_alpha(const LodePNGColorMode* info);
+/*
+Check if the given color info indicates the possibility of having non-opaque pixels in the PNG image.
+Returns true if the image can have translucent or invisible pixels (it still be opaque if it doesn't use such pixels).
+Returns false if the image can only have opaque pixels.
+In detail, it returns true only if it's a color type with alpha, or has a palette with non-opaque values,
+or if "key_defined" is true.
+*/
+unsigned lodepng_can_have_alpha(const LodePNGColorMode* info);
+/*Returns the byte size of a raw image buffer with given width, height and color mode*/
+size_t lodepng_get_raw_size(unsigned w, unsigned h, const LodePNGColorMode* color);
+
+#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS
+/*The information of a Time chunk in PNG.*/
+typedef struct LodePNGTime
+{
+  unsigned year;    /*2 bytes used (0-65535)*/
+  unsigned month;   /*1-12*/
+  unsigned day;     /*1-31*/
+  unsigned hour;    /*0-23*/
+  unsigned minute;  /*0-59*/
+  unsigned second;  /*0-60 (to allow for leap seconds)*/
+} LodePNGTime;
+#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/
+
+/*Information about the PNG image, except pixels, width and height.*/
+typedef struct LodePNGInfo
+{
+  /*header (IHDR), palette (PLTE) and transparency (tRNS) chunks*/
+  unsigned compression_method;/*compression method of the original file. Always 0.*/
+  unsigned filter_method;     /*filter method of the original file*/
+  unsigned interlace_method;  /*interlace method of the original file*/
+  LodePNGColorMode color;     /*color type and bits, palette and transparency of the PNG file*/
+
+#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS
+  /*
+  suggested background color chunk (bKGD)
+  This color uses the same color mode as the PNG (except alpha channel), which can be 1-bit to 16-bit.
+
+  For greyscale PNGs, r, g and b will all 3 be set to the same. When encoding
+  the encoder writes the red one. For palette PNGs: When decoding, the RGB value
+  will be stored, not a palette index. But when encoding, specify the index of
+  the palette in background_r, the other two are then ignored.
+
+  The decoder does not use this background color to edit the color of pixels.
+  */
+  unsigned background_defined; /*is a suggested background color given?*/
+  unsigned background_r;       /*red component of suggested background color*/
+  unsigned background_g;       /*green component of suggested background color*/
+  unsigned background_b;       /*blue component of suggested background color*/
+
+  /*
+  non-international text chunks (tEXt and zTXt)
+
+  The char** arrays each contain num strings. The actual messages are in
+  text_strings, while text_keys are keywords that give a short description what
+  the actual text represents, e.g. Title, Author, Description, or anything else.
+
+  A keyword is minimum 1 character and maximum 79 characters long. It's
+  discouraged to use a single line length longer than 79 characters for texts.
+
+  Don't allocate these text buffers yourself. Use the init/cleanup functions
+  correctly and use lodepng_add_text and lodepng_clear_text.
+  */
+  size_t text_num; /*the amount of texts in these char** buffers (there may be more texts in itext)*/
+  char** text_keys; /*the keyword of a text chunk (e.g. "Comment")*/
+  char** text_strings; /*the actual text*/
+
+  /*
+  international text chunks (iTXt)
+  Similar to the non-international text chunks, but with additional strings
+  "langtags" and "transkeys".
+  */
+  size_t itext_num; /*the amount of international texts in this PNG*/
+  char** itext_keys; /*the English keyword of the text chunk (e.g. "Comment")*/
+  char** itext_langtags; /*language tag for this text's language, ISO/IEC 646 string, e.g. ISO 639 language tag*/
+  char** itext_transkeys; /*keyword translated to the international language - UTF-8 string*/
+  char** itext_strings; /*the actual international text - UTF-8 string*/
+
+  /*time chunk (tIME)*/
+  unsigned time_defined; /*set to 1 to make the encoder generate a tIME chunk*/
+  LodePNGTime time;
+
+  /*phys chunk (pHYs)*/
+  unsigned phys_defined; /*if 0, there is no pHYs chunk and the values below are undefined, if 1 else there is one*/
+  unsigned phys_x; /*pixels per unit in x direction*/
+  unsigned phys_y; /*pixels per unit in y direction*/
+  unsigned phys_unit; /*may be 0 (unknown unit) or 1 (metre)*/
+
+  /*
+  unknown chunks
+  There are 3 buffers, one for each position in the PNG where unknown chunks can appear
+  each buffer contains all unknown chunks for that position consecutively
+  The 3 buffers are the unknown chunks between certain critical chunks:
+  0: IHDR-PLTE, 1: PLTE-IDAT, 2: IDAT-IEND
+  Do not allocate or traverse this data yourself. Use the chunk traversing functions declared
+  later, such as lodepng_chunk_next and lodepng_chunk_append, to read/write this struct.
+  */
+  unsigned char* unknown_chunks_data[3];
+  size_t unknown_chunks_size[3]; /*size in bytes of the unknown chunks, given for protection*/
+#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/
+} LodePNGInfo;
+
+/*init, cleanup and copy functions to use with this struct*/
+void lodepng_info_init(LodePNGInfo* info);
+void lodepng_info_cleanup(LodePNGInfo* info);
+/*return value is error code (0 means no error)*/
+unsigned lodepng_info_copy(LodePNGInfo* dest, const LodePNGInfo* source);
+
+#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS
+void lodepng_clear_text(LodePNGInfo* info); /*use this to clear the texts again after you filled them in*/
+unsigned lodepng_add_text(LodePNGInfo* info, const char* key, const char* str); /*push back both texts at once*/
+
+void lodepng_clear_itext(LodePNGInfo* info); /*use this to clear the itexts again after you filled them in*/
+unsigned lodepng_add_itext(LodePNGInfo* info, const char* key, const char* langtag,
+                           const char* transkey, const char* str); /*push back the 4 texts of 1 chunk at once*/
+#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/
+
+/*
+Converts raw buffer from one color type to another color type, based on
+LodePNGColorMode structs to describe the input and output color type.
+See the reference manual at the end of this header file to see which color conversions are supported.
+return value = LodePNG error code (0 if all went ok, an error if the conversion isn't supported)
+The out buffer must have size (w * h * bpp + 7) / 8, where bpp is the bits per pixel
+of the output color type (lodepng_get_bpp).
+For < 8 bpp images, there should not be padding bits at the end of scanlines.
+For 16-bit per channel colors, uses big endian format like PNG does.
+Return value is LodePNG error code
+*/
+unsigned lodepng_convert(unsigned char* out, const unsigned char* in,
+                         const LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in,
+                         unsigned w, unsigned h);
+
+#ifdef LODEPNG_COMPILE_DECODER
+/*
+Settings for the decoder. This contains settings for the PNG and the Zlib
+decoder, but not the Info settings from the Info structs.
+*/
+typedef struct LodePNGDecoderSettings
+{
+  LodePNGDecompressSettings zlibsettings; /*in here is the setting to ignore Adler32 checksums*/
+
+  unsigned ignore_crc; /*ignore CRC checksums*/
+
+  unsigned color_convert; /*whether to convert the PNG to the color type you want. Default: yes*/
+
+#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS
+  unsigned read_text_chunks; /*if false but remember_unknown_chunks is true, they're stored in the unknown chunks*/
+  /*store all bytes from unknown chunks in the LodePNGInfo (off by default, useful for a png editor)*/
+  unsigned remember_unknown_chunks;
+#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/
+} LodePNGDecoderSettings;
+
+void lodepng_decoder_settings_init(LodePNGDecoderSettings* settings);
+#endif /*LODEPNG_COMPILE_DECODER*/
+
+#ifdef LODEPNG_COMPILE_ENCODER
+/*automatically use color type with less bits per pixel if losslessly possible. Default: AUTO*/
+typedef enum LodePNGFilterStrategy
+{
+  /*every filter at zero*/
+  LFS_ZERO,
+  /*Use filter that gives minimum sum, as described in the official PNG filter heuristic.*/
+  LFS_MINSUM,
+  /*Use the filter type that gives smallest Shannon entropy for this scanline. Depending
+  on the image, this is better or worse than minsum.*/
+  LFS_ENTROPY,
+  /*
+  Brute-force-search PNG filters by compressing each filter for each scanline.
+  Experimental, very slow, and only rarely gives better compression than MINSUM.
+  */
+  LFS_BRUTE_FORCE,
+  /*use predefined_filters buffer: you specify the filter type for each scanline*/
+  LFS_PREDEFINED
+} LodePNGFilterStrategy;
+
+/*Gives characteristics about the colors of the image, which helps decide which color model to use for encoding.
+Used internally by default if "auto_convert" is enabled. Public because it's useful for custom algorithms.*/
+typedef struct LodePNGColorProfile
+{
+  unsigned colored; /*not greyscale*/
+  unsigned key; /*if true, image is not opaque. Only if true and alpha is false, color key is possible.*/
+  unsigned short key_r; /*these values are always in 16-bit bitdepth in the profile*/
+  unsigned short key_g;
+  unsigned short key_b;
+  unsigned alpha; /*alpha channel or alpha palette required*/
+  unsigned numcolors; /*amount of colors, up to 257. Not valid if bits == 16.*/
+  unsigned char palette[1024]; /*Remembers up to the first 256 RGBA colors, in no particular order*/
+  unsigned bits; /*bits per channel (not for palette). 1,2 or 4 for greyscale only. 16 if 16-bit per channel required.*/
+} LodePNGColorProfile;
+
+void lodepng_color_profile_init(LodePNGColorProfile* profile);
+
+/*Get a LodePNGColorProfile of the image.*/
+unsigned lodepng_get_color_profile(LodePNGColorProfile* profile,
+                                   const unsigned char* image, unsigned w, unsigned h,
+                                   const LodePNGColorMode* mode_in);
+/*The function LodePNG uses internally to decide the PNG color with auto_convert.
+Chooses an optimal color model, e.g. grey if only grey pixels, palette if < 256 colors, ...*/
+unsigned lodepng_auto_choose_color(LodePNGColorMode* mode_out,
+                                   const unsigned char* image, unsigned w, unsigned h,
+                                   const LodePNGColorMode* mode_in);
+
+/*Settings for the encoder.*/
+typedef struct LodePNGEncoderSettings
+{
+  LodePNGCompressSettings zlibsettings; /*settings for the zlib encoder, such as window size, ...*/
+
+  unsigned auto_convert; /*automatically choose output PNG color type. Default: true*/
+
+  /*If true, follows the official PNG heuristic: if the PNG uses a palette or lower than
+  8 bit depth, set all filters to zero. Otherwise use the filter_strategy. Note that to
+  completely follow the official PNG heuristic, filter_palette_zero must be true and
+  filter_strategy must be LFS_MINSUM*/
+  unsigned filter_palette_zero;
+  /*Which filter strategy to use when not using zeroes due to filter_palette_zero.
+  Set filter_palette_zero to 0 to ensure always using your chosen strategy. Default: LFS_MINSUM*/
+  LodePNGFilterStrategy filter_strategy;
+  /*used if filter_strategy is LFS_PREDEFINED. In that case, this must point to a buffer with
+  the same length as the amount of scanlines in the image, and each value must <= 5. You
+  have to cleanup this buffer, LodePNG will never free it. Don't forget that filter_palette_zero
+  must be set to 0 to ensure this is also used on palette or low bitdepth images.*/
+  const unsigned char* predefined_filters;
+
+  /*force creating a PLTE chunk if colortype is 2 or 6 (= a suggested palette).
+  If colortype is 3, PLTE is _always_ created.*/
+  unsigned force_palette;
+#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS
+  /*add LodePNG identifier and version as a text chunk, for debugging*/
+  unsigned add_id;
+  /*encode text chunks as zTXt chunks instead of tEXt chunks, and use compression in iTXt chunks*/
+  unsigned text_compression;
+#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/
+} LodePNGEncoderSettings;
+
+void lodepng_encoder_settings_init(LodePNGEncoderSettings* settings);
+#endif /*LODEPNG_COMPILE_ENCODER*/
+
+
+#if defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER)
+/*The settings, state and information for extended encoding and decoding.*/
+typedef struct LodePNGState
+{
+#ifdef LODEPNG_COMPILE_DECODER
+  LodePNGDecoderSettings decoder; /*the decoding settings*/
+#endif /*LODEPNG_COMPILE_DECODER*/
+#ifdef LODEPNG_COMPILE_ENCODER
+  LodePNGEncoderSettings encoder; /*the encoding settings*/
+#endif /*LODEPNG_COMPILE_ENCODER*/
+  LodePNGColorMode info_raw; /*specifies the format in which you would like to get the raw pixel buffer*/
+  LodePNGInfo info_png; /*info of the PNG image obtained after decoding*/
+  unsigned error;
+#ifdef LODEPNG_COMPILE_CPP
+  /* For the lodepng::State subclass. */
+  virtual ~LodePNGState(){}
+#endif
+} LodePNGState;
+
+/*init, cleanup and copy functions to use with this struct*/
+void lodepng_state_init(LodePNGState* state);
+void lodepng_state_cleanup(LodePNGState* state);
+void lodepng_state_copy(LodePNGState* dest, const LodePNGState* source);
+#endif /* defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) */
+
+#ifdef LODEPNG_COMPILE_DECODER
+/*
+Same as lodepng_decode_memory, but uses a LodePNGState to allow custom settings and
+getting much more information about the PNG image and color mode.
+*/
+unsigned lodepng_decode(unsigned char** out, unsigned* w, unsigned* h,
+                        LodePNGState* state,
+                        const unsigned char* in, size_t insize);
+
+/*
+Read the PNG header, but not the actual data. This returns only the information
+that is in the header chunk of the PNG, such as width, height and color type. The
+information is placed in the info_png field of the LodePNGState.
+*/
+unsigned lodepng_inspect(unsigned* w, unsigned* h,
+                         LodePNGState* state,
+                         const unsigned char* in, size_t insize);
+#endif /*LODEPNG_COMPILE_DECODER*/
+
+
+#ifdef LODEPNG_COMPILE_ENCODER
+/*This function allocates the out buffer with standard malloc and stores the size in *outsize.*/
+unsigned lodepng_encode(unsigned char** out, size_t* outsize,
+                        const unsigned char* image, unsigned w, unsigned h,
+                        LodePNGState* state);
+#endif /*LODEPNG_COMPILE_ENCODER*/
+
+/*
+The lodepng_chunk functions are normally not needed, except to traverse the
+unknown chunks stored in the LodePNGInfo struct, or add new ones to it.
+It also allows traversing the chunks of an encoded PNG file yourself.
+
+PNG standard chunk naming conventions:
+First byte: uppercase = critical, lowercase = ancillary
+Second byte: uppercase = public, lowercase = private
+Third byte: must be uppercase
+Fourth byte: uppercase = unsafe to copy, lowercase = safe to copy
+*/
+
+/*
+Gets the length of the data of the chunk. Total chunk length has 12 bytes more.
+There must be at least 4 bytes to read from. If the result value is too large,
+it may be corrupt data.
+*/
+unsigned lodepng_chunk_length(const unsigned char* chunk);
+
+/*puts the 4-byte type in null terminated string*/
+void lodepng_chunk_type(char type[5], const unsigned char* chunk);
+
+/*check if the type is the given type*/
+unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, const char* type);
+
+/*0: it's one of the critical chunk types, 1: it's an ancillary chunk (see PNG standard)*/
+unsigned char lodepng_chunk_ancillary(const unsigned char* chunk);
+
+/*0: public, 1: private (see PNG standard)*/
+unsigned char lodepng_chunk_private(const unsigned char* chunk);
+
+/*0: the chunk is unsafe to copy, 1: the chunk is safe to copy (see PNG standard)*/
+unsigned char lodepng_chunk_safetocopy(const unsigned char* chunk);
+
+/*get pointer to the data of the chunk, where the input points to the header of the chunk*/
+unsigned char* lodepng_chunk_data(unsigned char* chunk);
+const unsigned char* lodepng_chunk_data_const(const unsigned char* chunk);
+
+/*returns 0 if the crc is correct, 1 if it's incorrect (0 for OK as usual!)*/
+unsigned lodepng_chunk_check_crc(const unsigned char* chunk);
+
+/*generates the correct CRC from the data and puts it in the last 4 bytes of the chunk*/
+void lodepng_chunk_generate_crc(unsigned char* chunk);
+
+/*iterate to next chunks. don't use on IEND chunk, as there is no next chunk then*/
+unsigned char* lodepng_chunk_next(unsigned char* chunk);
+const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk);
+
+/*
+Appends chunk to the data in out. The given chunk should already have its chunk header.
+The out variable and outlength are updated to reflect the new reallocated buffer.
+Returns error code (0 if it went ok)
+*/
+unsigned lodepng_chunk_append(unsigned char** out, size_t* outlength, const unsigned char* chunk);
+
+/*
+Appends new chunk to out. The chunk to append is given by giving its length, type
+and data separately. The type is a 4-letter string.
+The out variable and outlength are updated to reflect the new reallocated buffer.
+Returne error code (0 if it went ok)
+*/
+unsigned lodepng_chunk_create(unsigned char** out, size_t* outlength, unsigned length,
+                              const char* type, const unsigned char* data);
+
+
+/*Calculate CRC32 of buffer*/
+unsigned lodepng_crc32(const unsigned char* buf, size_t len);
+#endif /*LODEPNG_COMPILE_PNG*/
+
+
+#ifdef LODEPNG_COMPILE_ZLIB
+/*
+This zlib part can be used independently to zlib compress and decompress a
+buffer. It cannot be used to create gzip files however, and it only supports the
+part of zlib that is required for PNG, it does not support dictionaries.
+*/
+
+#ifdef LODEPNG_COMPILE_DECODER
+/*Inflate a buffer. Inflate is the decompression step of deflate. Out buffer must be freed after use.*/
+unsigned lodepng_inflate(unsigned char** out, size_t* outsize,
+                         const unsigned char* in, size_t insize,
+                         const LodePNGDecompressSettings* settings);
+
+/*
+Decompresses Zlib data. Reallocates the out buffer and appends the data. The
+data must be according to the zlib specification.
+Either, *out must be NULL and *outsize must be 0, or, *out must be a valid
+buffer and *outsize its size in bytes. out must be freed by user after usage.
+*/
+unsigned lodepng_zlib_decompress(unsigned char** out, size_t* outsize,
+                                 const unsigned char* in, size_t insize,
+                                 const LodePNGDecompressSettings* settings);
+#endif /*LODEPNG_COMPILE_DECODER*/
+
+#ifdef LODEPNG_COMPILE_ENCODER
+/*
+Compresses data with Zlib. Reallocates the out buffer and appends the data.
+Zlib adds a small header and trailer around the deflate data.
+The data is output in the format of the zlib specification.
+Either, *out must be NULL and *outsize must be 0, or, *out must be a valid
+buffer and *outsize its size in bytes. out must be freed by user after usage.
+*/
+unsigned lodepng_zlib_compress(unsigned char** out, size_t* outsize,
+                               const unsigned char* in, size_t insize,
+                               const LodePNGCompressSettings* settings);
+
+/*
+Find length-limited Huffman code for given frequencies. This function is in the
+public interface only for tests, it's used internally by lodepng_deflate.
+*/
+unsigned lodepng_huffman_code_lengths(unsigned* lengths, const unsigned* frequencies,
+                                      size_t numcodes, unsigned maxbitlen);
+
+/*Compress a buffer with deflate. See RFC 1951. Out buffer must be freed after use.*/
+unsigned lodepng_deflate(unsigned char** out, size_t* outsize,
+                         const unsigned char* in, size_t insize,
+                         const LodePNGCompressSettings* settings);
+
+#endif /*LODEPNG_COMPILE_ENCODER*/
+#endif /*LODEPNG_COMPILE_ZLIB*/
+
+#ifdef LODEPNG_COMPILE_DISK
+/*
+Load a file from disk into buffer. The function allocates the out buffer, and
+after usage you should free it.
+out: output parameter, contains pointer to loaded buffer.
+outsize: output parameter, size of the allocated out buffer
+filename: the path to the file to load
+return value: error code (0 means ok)
+*/
+unsigned lodepng_load_file(unsigned char** out, size_t* outsize, const char* filename);
+
+/*
+Save a file from buffer to disk. Warning, if it exists, this function overwrites
+the file without warning!
+buffer: the buffer to write
+buffersize: size of the buffer to write
+filename: the path to the file to save to
+return value: error code (0 means ok)
+*/
+unsigned lodepng_save_file(const unsigned char* buffer, size_t buffersize, const char* filename);
+#endif /*LODEPNG_COMPILE_DISK*/
+
+#ifdef LODEPNG_COMPILE_CPP
+/* The LodePNG C++ wrapper uses std::vectors instead of manually allocated memory buffers. */
+namespace lodepng
+{
+#ifdef LODEPNG_COMPILE_PNG
+class State : public LodePNGState
+{
+  public:
+    State();
+    State(const State& other);
+    virtual ~State();
+    State& operator=(const State& other);
+};
+
+#ifdef LODEPNG_COMPILE_DECODER
+/* Same as other lodepng::decode, but using a State for more settings and information. */
+unsigned decode(std::vector<unsigned char>& out, unsigned& w, unsigned& h,
+                State& state,
+                const unsigned char* in, size_t insize);
+unsigned decode(std::vector<unsigned char>& out, unsigned& w, unsigned& h,
+                State& state,
+                const std::vector<unsigned char>& in);
+#endif /*LODEPNG_COMPILE_DECODER*/
+
+#ifdef LODEPNG_COMPILE_ENCODER
+/* Same as other lodepng::encode, but using a State for more settings and information. */
+unsigned encode(std::vector<unsigned char>& out,
+                const unsigned char* in, unsigned w, unsigned h,
+                State& state);
+unsigned encode(std::vector<unsigned char>& out,
+                const std::vector<unsigned char>& in, unsigned w, unsigned h,
+                State& state);
+#endif /*LODEPNG_COMPILE_ENCODER*/
+
+#ifdef LODEPNG_COMPILE_DISK
+/*
+Load a file from disk into an std::vector.
+return value: error code (0 means ok)
+*/
+unsigned load_file(std::vector<unsigned char>& buffer, const std::string& filename);
+
+/*
+Save the binary data in an std::vector to a file on disk. The file is overwritten
+without warning.
+*/
+unsigned save_file(const std::vector<unsigned char>& buffer, const std::string& filename);
+#endif /* LODEPNG_COMPILE_DISK */
+#endif /* LODEPNG_COMPILE_PNG */
+
+#ifdef LODEPNG_COMPILE_ZLIB
+#ifdef LODEPNG_COMPILE_DECODER
+/* Zlib-decompress an unsigned char buffer */
+unsigned decompress(std::vector<unsigned char>& out, const unsigned char* in, size_t insize,
+                    const LodePNGDecompressSettings& settings = lodepng_default_decompress_settings);
+
+/* Zlib-decompress an std::vector */
+unsigned decompress(std::vector<unsigned char>& out, const std::vector<unsigned char>& in,
+                    const LodePNGDecompressSettings& settings = lodepng_default_decompress_settings);
+#endif /* LODEPNG_COMPILE_DECODER */
+
+#ifdef LODEPNG_COMPILE_ENCODER
+/* Zlib-compress an unsigned char buffer */
+unsigned compress(std::vector<unsigned char>& out, const unsigned char* in, size_t insize,
+                  const LodePNGCompressSettings& settings = lodepng_default_compress_settings);
+
+/* Zlib-compress an std::vector */
+unsigned compress(std::vector<unsigned char>& out, const std::vector<unsigned char>& in,
+                  const LodePNGCompressSettings& settings = lodepng_default_compress_settings);
+#endif /* LODEPNG_COMPILE_ENCODER */
+#endif /* LODEPNG_COMPILE_ZLIB */
+} /* namespace lodepng */
+#endif /*LODEPNG_COMPILE_CPP*/
+
+/*
+TODO:
+[.] test if there are no memory leaks or security exploits - done a lot but needs to be checked often
+[.] check compatibility with various compilers  - done but needs to be redone for every newer version
+[X] converting color to 16-bit per channel types
+[ ] read all public PNG chunk types (but never let the color profile and gamma ones touch RGB values)
+[ ] make sure encoder generates no chunks with size > (2^31)-1
+[ ] partial decoding (stream processing)
+[X] let the "isFullyOpaque" function check color keys and transparent palettes too
+[X] better name for the variables "codes", "codesD", "codelengthcodes", "clcl" and "lldl"
+[ ] don't stop decoding on errors like 69, 57, 58 (make warnings)
+[ ] let the C++ wrapper catch exceptions coming from the standard library and return LodePNG error codes
+[ ] allow user to provide custom color conversion functions, e.g. for premultiplied alpha, padding bits or not, ...
+[ ] allow user to give data (void*) to custom allocator
+*/
+
+#endif /*LODEPNG_H inclusion guard*/
+
+/*
+LodePNG Documentation
+---------------------
+
+0. table of contents
+--------------------
+
+  1. about
+   1.1. supported features
+   1.2. features not supported
+  2. C and C++ version
+  3. security
+  4. decoding
+  5. encoding
+  6. color conversions
+    6.1. PNG color types
+    6.2. color conversions
+    6.3. padding bits
+    6.4. A note about 16-bits per channel and endianness
+  7. error values
+  8. chunks and PNG editing
+  9. compiler support
+  10. examples
+   10.1. decoder C++ example
+   10.2. decoder C example
+  11. state settings reference
+  12. changes
+  13. contact information
+
+
+1. about
+--------
+
+PNG is a file format to store raster images losslessly with good compression,
+supporting different color types and alpha channel.
+
+LodePNG is a PNG codec according to the Portable Network Graphics (PNG)
+Specification (Second Edition) - W3C Recommendation 10 November 2003.
+
+The specifications used are:
+
+*) Portable Network Graphics (PNG) Specification (Second Edition):
+     http://www.w3.org/TR/2003/REC-PNG-20031110
+*) RFC 1950 ZLIB Compressed Data Format version 3.3:
+     http://www.gzip.org/zlib/rfc-zlib.html
+*) RFC 1951 DEFLATE Compressed Data Format Specification ver 1.3:
+     http://www.gzip.org/zlib/rfc-deflate.html
+
+The most recent version of LodePNG can currently be found at
+http://lodev.org/lodepng/
+
+LodePNG works both in C (ISO C90) and C++, with a C++ wrapper that adds
+extra functionality.
+
+LodePNG exists out of two files:
+-lodepng.h: the header file for both C and C++
+-lodepng.c(pp): give it the name lodepng.c or lodepng.cpp (or .cc) depending on your usage
+
+If you want to start using LodePNG right away without reading this doc, get the
+examples from the LodePNG website to see how to use it in code, or check the
+smaller examples in chapter 13 here.
+
+LodePNG is simple but only supports the basic requirements. To achieve
+simplicity, the following design choices were made: There are no dependencies
+on any external library. There are functions to decode and encode a PNG with
+a single function call, and extended versions of these functions taking a
+LodePNGState struct allowing to specify or get more information. By default
+the colors of the raw image are always RGB or RGBA, no matter what color type
+the PNG file uses. To read and write files, there are simple functions to
+convert the files to/from buffers in memory.
+
+This all makes LodePNG suitable for loading textures in games, demos and small
+programs, ... It's less suitable for full fledged image editors, loading PNGs
+over network (it requires all the image data to be available before decoding can
+begin), life-critical systems, ...
+
+1.1. supported features
+-----------------------
+
+The following features are supported by the decoder:
+
+*) decoding of PNGs with any color type, bit depth and interlace mode, to a 24- or 32-bit color raw image,
+   or the same color type as the PNG
+*) encoding of PNGs, from any raw image to 24- or 32-bit color, or the same color type as the raw image
+*) Adam7 interlace and deinterlace for any color type
+*) loading the image from harddisk or decoding it from a buffer from other sources than harddisk
+*) support for alpha channels, including RGBA color model, translucent palettes and color keying
+*) zlib decompression (inflate)
+*) zlib compression (deflate)
+*) CRC32 and ADLER32 checksums
+*) handling of unknown chunks, allowing making a PNG editor that stores custom and unknown chunks.
+*) the following chunks are supported (generated/interpreted) by both encoder and decoder:
+    IHDR: header information
+    PLTE: color palette
+    IDAT: pixel data
+    IEND: the final chunk
+    tRNS: transparency for palettized images
+    tEXt: textual information
+    zTXt: compressed textual information
+    iTXt: international textual information
+    bKGD: suggested background color
+    pHYs: physical dimensions
+    tIME: modification time
+
+1.2. features not supported
+---------------------------
+
+The following features are _not_ supported:
+
+*) some features needed to make a conformant PNG-Editor might be still missing.
+*) partial loading/stream processing. All data must be available and is processed in one call.
+*) The following public chunks are not supported but treated as unknown chunks by LodePNG
+    cHRM, gAMA, iCCP, sRGB, sBIT, hIST, sPLT
+   Some of these are not supported on purpose: LodePNG wants to provide the RGB values
+   stored in the pixels, not values modified by system dependent gamma or color models.
+
+
+2. C and C++ version
+--------------------
+
+The C version uses buffers allocated with alloc that you need to free()
+yourself. You need to use init and cleanup functions for each struct whenever
+using a struct from the C version to avoid exploits and memory leaks.
+
+The C++ version has extra functions with std::vectors in the interface and the
+lodepng::State class which is a LodePNGState with constructor and destructor.
+
+These files work without modification for both C and C++ compilers because all
+the additional C++ code is in "#ifdef __cplusplus" blocks that make C-compilers
+ignore it, and the C code is made to compile both with strict ISO C90 and C++.
+
+To use the C++ version, you need to rename the source file to lodepng.cpp
+(instead of lodepng.c), and compile it with a C++ compiler.
+
+To use the C version, you need to rename the source file to lodepng.c (instead
+of lodepng.cpp), and compile it with a C compiler.
+
+
+3. Security
+-----------
+
+Even if carefully designed, it's always possible that LodePNG contains possible
+exploits. If you discover one, please let me know, and it will be fixed.
+
+When using LodePNG, care has to be taken with the C version of LodePNG, as well
+as the C-style structs when working with C++. The following conventions are used
+for all C-style structs:
+
+-if a struct has a corresponding init function, always call the init function when making a new one
+-if a struct has a corresponding cleanup function, call it before the struct disappears to avoid memory leaks
+-if a struct has a corresponding copy function, use the copy function instead of "=".
+ The destination must also be inited already.
+
+
+4. Decoding
+-----------
+
+Decoding converts a PNG compressed image to a raw pixel buffer.
+
+Most documentation on using the decoder is at its declarations in the header
+above. For C, simple decoding can be done with functions such as
+lodepng_decode32, and more advanced decoding can be done with the struct
+LodePNGState and lodepng_decode. For C++, all decoding can be done with the
+various lodepng::decode functions, and lodepng::State can be used for advanced
+features.
+
+When using the LodePNGState, it uses the following fields for decoding:
+*) LodePNGInfo info_png: it stores extra information about the PNG (the input) in here
+*) LodePNGColorMode info_raw: here you can say what color mode of the raw image (the output) you want to get
+*) LodePNGDecoderSettings decoder: you can specify a few extra settings for the decoder to use
+
+LodePNGInfo info_png
+--------------------
+
+After decoding, this contains extra information of the PNG image, except the actual
+pixels, width and height because these are already gotten directly from the decoder
+functions.
+
+It contains for example the original color type of the PNG image, text comments,
+suggested background color, etc... More details about the LodePNGInfo struct are
+at its declaration documentation.
+
+LodePNGColorMode info_raw
+-------------------------
+
+When decoding, here you can specify which color type you want
+the resulting raw image to be. If this is different from the colortype of the
+PNG, then the decoder will automatically convert the result. This conversion
+always works, except if you want it to convert a color PNG to greyscale or to
+a palette with missing colors.
+
+By default, 32-bit color is used for the result.
+
+LodePNGDecoderSettings decoder
+------------------------------
+
+The settings can be used to ignore the errors created by invalid CRC and Adler32
+chunks, and to disable the decoding of tEXt chunks.
+
+There's also a setting color_convert, true by default. If false, no conversion
+is done, the resulting data will be as it was in the PNG (after decompression)
+and you'll have to puzzle the colors of the pixels together yourself using the
+color type information in the LodePNGInfo.
+
+
+5. Encoding
+-----------
+
+Encoding converts a raw pixel buffer to a PNG compressed image.
+
+Most documentation on using the encoder is at its declarations in the header
+above. For C, simple encoding can be done with functions such as
+lodepng_encode32, and more advanced decoding can be done with the struct
+LodePNGState and lodepng_encode. For C++, all encoding can be done with the
+various lodepng::encode functions, and lodepng::State can be used for advanced
+features.
+
+Like the decoder, the encoder can also give errors. However it gives less errors
+since the encoder input is trusted, the decoder input (a PNG image that could
+be forged by anyone) is not trusted.
+
+When using the LodePNGState, it uses the following fields for encoding:
+*) LodePNGInfo info_png: here you specify how you want the PNG (the output) to be.
+*) LodePNGColorMode info_raw: here you say what color type of the raw image (the input) has
+*) LodePNGEncoderSettings encoder: you can specify a few settings for the encoder to use
+
+LodePNGInfo info_png
+--------------------
+
+When encoding, you use this the opposite way as when decoding: for encoding,
+you fill in the values you want the PNG to have before encoding. By default it's
+not needed to specify a color type for the PNG since it's automatically chosen,
+but it's possible to choose it yourself given the right settings.
+
+The encoder will not always exactly match the LodePNGInfo struct you give,
+it tries as close as possible. Some things are ignored by the encoder. The
+encoder uses, for example, the following settings from it when applicable:
+colortype and bitdepth, text chunks, time chunk, the color key, the palette, the
+background color, the interlace method, unknown chunks, ...
+
+When encoding to a PNG with colortype 3, the encoder will generate a PLTE chunk.
+If the palette contains any colors for which the alpha channel is not 255 (so
+there are translucent colors in the palette), it'll add a tRNS chunk.
+
+LodePNGColorMode info_raw
+-------------------------
+
+You specify the color type of the raw image that you give to the input here,
+including a possible transparent color key and palette you happen to be using in
+your raw image data.
+
+By default, 32-bit color is assumed, meaning your input has to be in RGBA
+format with 4 bytes (unsigned chars) per pixel.
+
+LodePNGEncoderSettings encoder
+------------------------------
+
+The following settings are supported (some are in sub-structs):
+*) auto_convert: when this option is enabled, the encoder will
+automatically choose the smallest possible color mode (including color key) that
+can encode the colors of all pixels without information loss.
+*) btype: the block type for LZ77. 0 = uncompressed, 1 = fixed huffman tree,
+   2 = dynamic huffman tree (best compression). Should be 2 for proper
+   compression.
+*) use_lz77: whether or not to use LZ77 for compressed block types. Should be
+   true for proper compression.
+*) windowsize: the window size used by the LZ77 encoder (1 - 32768). Has value
+   2048 by default, but can be set to 32768 for better, but slow, compression.
+*) force_palette: if colortype is 2 or 6, you can make the encoder write a PLTE
+   chunk if force_palette is true. This can used as suggested palette to convert
+   to by viewers that don't support more than 256 colors (if those still exist)
+*) add_id: add text chunk "Encoder: LodePNG <version>" to the image.
+*) text_compression: default 1. If 1, it'll store texts as zTXt instead of tEXt chunks.
+  zTXt chunks use zlib compression on the text. This gives a smaller result on
+  large texts but a larger result on small texts (such as a single program name).
+  It's all tEXt or all zTXt though, there's no separate setting per text yet.
+
+
+6. color conversions
+--------------------
+
+An important thing to note about LodePNG, is that the color type of the PNG, and
+the color type of the raw image, are completely independent. By default, when
+you decode a PNG, you get the result as a raw image in the color type you want,
+no matter whether the PNG was encoded with a palette, greyscale or RGBA color.
+And if you encode an image, by default LodePNG will automatically choose the PNG
+color type that gives good compression based on the values of colors and amount
+of colors in the image. It can be configured to let you control it instead as
+well, though.
+
+To be able to do this, LodePNG does conversions from one color mode to another.
+It can convert from almost any color type to any other color type, except the
+following conversions: RGB to greyscale is not supported, and converting to a
+palette when the palette doesn't have a required color is not supported. This is
+not supported on purpose: this is information loss which requires a color
+reduction algorithm that is beyong the scope of a PNG encoder (yes, RGB to grey
+is easy, but there are multiple ways if you want to give some channels more
+weight).
+
+By default, when decoding, you get the raw image in 32-bit RGBA or 24-bit RGB
+color, no matter what color type the PNG has. And by default when encoding,
+LodePNG automatically picks the best color model for the output PNG, and expects
+the input image to be 32-bit RGBA or 24-bit RGB. So, unless you want to control
+the color format of the images yourself, you can skip this chapter.
+
+6.1. PNG color types
+--------------------
+
+A PNG image can have many color types, ranging from 1-bit color to 64-bit color,
+as well as palettized color modes. After the zlib decompression and unfiltering
+in the PNG image is done, the raw pixel data will have that color type and thus
+a certain amount of bits per pixel. If you want the output raw image after
+decoding to have another color type, a conversion is done by LodePNG.
+
+The PNG specification gives the following color types:
+
+0: greyscale, bit depths 1, 2, 4, 8, 16
+2: RGB, bit depths 8 and 16
+3: palette, bit depths 1, 2, 4 and 8
+4: greyscale with alpha, bit depths 8 and 16
+6: RGBA, bit depths 8 and 16
+
+Bit depth is the amount of bits per pixel per color channel. So the total amount
+of bits per pixel is: amount of channels * bitdepth.
+
+6.2. color conversions
+----------------------
+
+As explained in the sections about the encoder and decoder, you can specify
+color types and bit depths in info_png and info_raw to change the default
+behaviour.
+
+If, when decoding, you want the raw image to be something else than the default,
+you need to set the color type and bit depth you want in the LodePNGColorMode,
+or the parameters colortype and bitdepth of the simple decoding function.
+
+If, when encoding, you use another color type than the default in the raw input
+image, you need to specify its color type and bit depth in the LodePNGColorMode
+of the raw image, or use the parameters colortype and bitdepth of the simple
+encoding function.
+
+If, when encoding, you don't want LodePNG to choose the output PNG color type
+but control it yourself, you need to set auto_convert in the encoder settings
+to false, and specify the color type you want in the LodePNGInfo of the
+encoder (including palette: it can generate a palette if auto_convert is true,
+otherwise not).
+
+If the input and output color type differ (whether user chosen or auto chosen),
+LodePNG will do a color conversion, which follows the rules below, and may
+sometimes result in an error.
+
+To avoid some confusion:
+-the decoder converts from PNG to raw image
+-the encoder converts from raw image to PNG
+-the colortype and bitdepth in LodePNGColorMode info_raw, are those of the raw image
+-the colortype and bitdepth in the color field of LodePNGInfo info_png, are those of the PNG
+-when encoding, the color type in LodePNGInfo is ignored if auto_convert
+ is enabled, it is automatically generated instead
+-when decoding, the color type in LodePNGInfo is set by the decoder to that of the original
+ PNG image, but it can be ignored since the raw image has the color type you requested instead
+-if the color type of the LodePNGColorMode and PNG image aren't the same, a conversion
+ between the color types is done if the color types are supported. If it is not
+ supported, an error is returned. If the types are the same, no conversion is done.
+-even though some conversions aren't supported, LodePNG supports loading PNGs from any
+ colortype and saving PNGs to any colortype, sometimes it just requires preparing
+ the raw image correctly before encoding.
+-both encoder and decoder use the same color converter.
+
+Non supported color conversions:
+-color to greyscale: no error is thrown, but the result will look ugly because
+only the red channel is taken
+-anything to palette when that palette does not have that color in it: in this
+case an error is thrown
+
+Supported color conversions:
+-anything to 8-bit RGB, 8-bit RGBA, 16-bit RGB, 16-bit RGBA
+-any grey or grey+alpha, to grey or grey+alpha
+-anything to a palette, as long as the palette has the requested colors in it
+-removing alpha channel
+-higher to smaller bitdepth, and vice versa
+
+If you want no color conversion to be done (e.g. for speed or control):
+-In the encoder, you can make it save a PNG with any color type by giving the
+raw color mode and LodePNGInfo the same color mode, and setting auto_convert to
+false.
+-In the decoder, you can make it store the pixel data in the same color type
+as the PNG has, by setting the color_convert setting to false. Settings in
+info_raw are then ignored.
+
+The function lodepng_convert does the color conversion. It is available in the
+interface but normally isn't needed since the encoder and decoder already call
+it.
+
+6.3. padding bits
+-----------------
+
+In the PNG file format, if a less than 8-bit per pixel color type is used and the scanlines
+have a bit amount that isn't a multiple of 8, then padding bits are used so that each
+scanline starts at a fresh byte. But that is NOT true for the LodePNG raw input and output.
+The raw input image you give to the encoder, and the raw output image you get from the decoder
+will NOT have these padding bits, e.g. in the case of a 1-bit image with a width
+of 7 pixels, the first pixel of the second scanline will the the 8th bit of the first byte,
+not the first bit of a new byte.
+
+6.4. A note about 16-bits per channel and endianness
+----------------------------------------------------
+
+LodePNG uses unsigned char arrays for 16-bit per channel colors too, just like
+for any other color format. The 16-bit values are stored in big endian (most
+significant byte first) in these arrays. This is the opposite order of the
+little endian used by x86 CPU's.
+
+LodePNG always uses big endian because the PNG file format does so internally.
+Conversions to other formats than PNG uses internally are not supported by
+LodePNG on purpose, there are myriads of formats, including endianness of 16-bit
+colors, the order in which you store R, G, B and A, and so on. Supporting and
+converting to/from all that is outside the scope of LodePNG.
+
+This may mean that, depending on your use case, you may want to convert the big
+endian output of LodePNG to little endian with a for loop. This is certainly not
+always needed, many applications and libraries support big endian 16-bit colors
+anyway, but it means you cannot simply cast the unsigned char* buffer to an
+unsigned short* buffer on x86 CPUs.
+
+
+7. error values
+---------------
+
+All functions in LodePNG that return an error code, return 0 if everything went
+OK, or a non-zero code if there was an error.
+
+The meaning of the LodePNG error values can be retrieved with the function
+lodepng_error_text: given the numerical error code, it returns a description
+of the error in English as a string.
+
+Check the implementation of lodepng_error_text to see the meaning of each code.
+
+
+8. chunks and PNG editing
+-------------------------
+
+If you want to add extra chunks to a PNG you encode, or use LodePNG for a PNG
+editor that should follow the rules about handling of unknown chunks, or if your
+program is able to read other types of chunks than the ones handled by LodePNG,
+then that's possible with the chunk functions of LodePNG.
+
+A PNG chunk has the following layout:
+
+4 bytes length
+4 bytes type name
+length bytes data
+4 bytes CRC
+
+8.1. iterating through chunks
+-----------------------------
+
+If you have a buffer containing the PNG image data, then the first chunk (the
+IHDR chunk) starts at byte number 8 of that buffer. The first 8 bytes are the
+signature of the PNG and are not part of a chunk. But if you start at byte 8
+then you have a chunk, and can check the following things of it.
+
+NOTE: none of these functions check for memory buffer boundaries. To avoid
+exploits, always make sure the buffer contains all the data of the chunks.
+When using lodepng_chunk_next, make sure the returned value is within the
+allocated memory.
+
+unsigned lodepng_chunk_length(const unsigned char* chunk):
+
+Get the length of the chunk's data. The total chunk length is this length + 12.
+
+void lodepng_chunk_type(char type[5], const unsigned char* chunk):
+unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, const char* type):
+
+Get the type of the chunk or compare if it's a certain type
+
+unsigned char lodepng_chunk_critical(const unsigned char* chunk):
+unsigned char lodepng_chunk_private(const unsigned char* chunk):
+unsigned char lodepng_chunk_safetocopy(const unsigned char* chunk):
+
+Check if the chunk is critical in the PNG standard (only IHDR, PLTE, IDAT and IEND are).
+Check if the chunk is private (public chunks are part of the standard, private ones not).
+Check if the chunk is safe to copy. If it's not, then, when modifying data in a critical
+chunk, unsafe to copy chunks of the old image may NOT be saved in the new one if your
+program doesn't handle that type of unknown chunk.
+
+unsigned char* lodepng_chunk_data(unsigned char* chunk):
+const unsigned char* lodepng_chunk_data_const(const unsigned char* chunk):
+
+Get a pointer to the start of the data of the chunk.
+
+unsigned lodepng_chunk_check_crc(const unsigned char* chunk):
+void lodepng_chunk_generate_crc(unsigned char* chunk):
+
+Check if the crc is correct or generate a correct one.
+
+unsigned char* lodepng_chunk_next(unsigned char* chunk):
+const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk):
+
+Iterate to the next chunk. This works if you have a buffer with consecutive chunks. Note that these
+functions do no boundary checking of the allocated data whatsoever, so make sure there is enough
+data available in the buffer to be able to go to the next chunk.
+
+unsigned lodepng_chunk_append(unsigned char** out, size_t* outlength, const unsigned char* chunk):
+unsigned lodepng_chunk_create(unsigned char** out, size_t* outlength, unsigned length,
+                              const char* type, const unsigned char* data):
+
+These functions are used to create new chunks that are appended to the data in *out that has
+length *outlength. The append function appends an existing chunk to the new data. The create
+function creates a new chunk with the given parameters and appends it. Type is the 4-letter
+name of the chunk.
+
+8.2. chunks in info_png
+-----------------------
+
+The LodePNGInfo struct contains fields with the unknown chunk in it. It has 3
+buffers (each with size) to contain 3 types of unknown chunks:
+the ones that come before the PLTE chunk, the ones that come between the PLTE
+and the IDAT chunks, and the ones that come after the IDAT chunks.
+It's necessary to make the distionction between these 3 cases because the PNG
+standard forces to keep the ordering of unknown chunks compared to the critical
+chunks, but does not force any other ordering rules.
+
+info_png.unknown_chunks_data[0] is the chunks before PLTE
+info_png.unknown_chunks_data[1] is the chunks after PLTE, before IDAT
+info_png.unknown_chunks_data[2] is the chunks after IDAT
+
+The chunks in these 3 buffers can be iterated through and read by using the same
+way described in the previous subchapter.
+
+When using the decoder to decode a PNG, you can make it store all unknown chunks
+if you set the option settings.remember_unknown_chunks to 1. By default, this
+option is off (0).
+
+The encoder will always encode unknown chunks that are stored in the info_png.
+If you need it to add a particular chunk that isn't known by LodePNG, you can
+use lodepng_chunk_append or lodepng_chunk_create to the chunk data in
+info_png.unknown_chunks_data[x].
+
+Chunks that are known by LodePNG should not be added in that way. E.g. to make
+LodePNG add a bKGD chunk, set background_defined to true and add the correct
+parameters there instead.
+
+
+9. compiler support
+-------------------
+
+No libraries other than the current standard C library are needed to compile
+LodePNG. For the C++ version, only the standard C++ library is needed on top.
+Add the files lodepng.c(pp) and lodepng.h to your project, include
+lodepng.h where needed, and your program can read/write PNG files.
+
+It is compatible with C90 and up, and C++03 and up.
+
+If performance is important, use optimization when compiling! For both the
+encoder and decoder, this makes a large difference.
+
+Make sure that LodePNG is compiled with the same compiler of the same version
+and with the same settings as the rest of the program, or the interfaces with
+std::vectors and std::strings in C++ can be incompatible.
+
+CHAR_BITS must be 8 or higher, because LodePNG uses unsigned chars for octets.
+
+*) gcc and g++
+
+LodePNG is developed in gcc so this compiler is natively supported. It gives no
+warnings with compiler options "-Wall -Wextra -pedantic -ansi", with gcc and g++
+version 4.7.1 on Linux, 32-bit and 64-bit.
+
+*) Clang
+
+Fully supported and warning-free.
+
+*) Mingw
+
+The Mingw compiler (a port of gcc for Windows) should be fully supported by
+LodePNG.
+
+*) Visual Studio and Visual C++ Express Edition
+
+LodePNG should be warning-free with warning level W4. Two warnings were disabled
+with pragmas though: warning 4244 about implicit conversions, and warning 4996
+where it wants to use a non-standard function fopen_s instead of the standard C
+fopen.
+
+Visual Studio may want "stdafx.h" files to be included in each source file and
+give an error "unexpected end of file while looking for precompiled header".
+This is not standard C++ and will not be added to the stock LodePNG. You can
+disable it for lodepng.cpp only by right clicking it, Properties, C/C++,
+Precompiled Headers, and set it to Not Using Precompiled Headers there.
+
+NOTE: Modern versions of VS should be fully supported, but old versions, e.g.
+VS6, are not guaranteed to work.
+
+*) Compilers on Macintosh
+
+LodePNG has been reported to work both with gcc and LLVM for Macintosh, both for
+C and C++.
+
+*) Other Compilers
+
+If you encounter problems on any compilers, feel free to let me know and I may
+try to fix it if the compiler is modern and standards complient.
+
+
+10. examples
+------------
+
+This decoder example shows the most basic usage of LodePNG. More complex
+examples can be found on the LodePNG website.
+
+10.1. decoder C++ example
+-------------------------
+
+#include "lodepng.h"
+#include <iostream>
+
+int main(int argc, char *argv[])
+{
+  const char* filename = argc > 1 ? argv[1] : "test.png";
+
+  //load and decode
+  std::vector<unsigned char> image;
+  unsigned width, height;
+  unsigned error = lodepng::decode(image, width, height, filename);
+
+  //if there's an error, display it
+  if(error) std::cout << "decoder error " << error << ": " << lodepng_error_text(error) << std::endl;
+
+  //the pixels are now in the vector "image", 4 bytes per pixel, ordered RGBARGBA..., use it as texture, draw it, ...
+}
+
+10.2. decoder C example
+-----------------------
+
+#include "lodepng.h"
+
+int main(int argc, char *argv[])
+{
+  unsigned error;
+  unsigned char* image;
+  size_t width, height;
+  const char* filename = argc > 1 ? argv[1] : "test.png";
+
+  error = lodepng_decode32_file(&image, &width, &height, filename);
+
+  if(error) printf("decoder error %u: %s\n", error, lodepng_error_text(error));
+
+  / * use image here * /
+
+  free(image);
+  return 0;
+}
+
+11. state settings reference
+----------------------------
+
+A quick reference of some settings to set on the LodePNGState
+
+For decoding:
+
+state.decoder.zlibsettings.ignore_adler32: ignore ADLER32 checksums
+state.decoder.zlibsettings.custom_...: use custom inflate function
+state.decoder.ignore_crc: ignore CRC checksums
+state.decoder.color_convert: convert internal PNG color to chosen one
+state.decoder.read_text_chunks: whether to read in text metadata chunks
+state.decoder.remember_unknown_chunks: whether to read in unknown chunks
+state.info_raw.colortype: desired color type for decoded image
+state.info_raw.bitdepth: desired bit depth for decoded image
+state.info_raw....: more color settings, see struct LodePNGColorMode
+state.info_png....: no settings for decoder but ouput, see struct LodePNGInfo
+
+For encoding:
+
+state.encoder.zlibsettings.btype: disable compression by setting it to 0
+state.encoder.zlibsettings.use_lz77: use LZ77 in compression
+state.encoder.zlibsettings.windowsize: tweak LZ77 windowsize
+state.encoder.zlibsettings.minmatch: tweak min LZ77 length to match
+state.encoder.zlibsettings.nicematch: tweak LZ77 match where to stop searching
+state.encoder.zlibsettings.lazymatching: try one more LZ77 matching
+state.encoder.zlibsettings.custom_...: use custom deflate function
+state.encoder.auto_convert: choose optimal PNG color type, if 0 uses info_png
+state.encoder.filter_palette_zero: PNG filter strategy for palette
+state.encoder.filter_strategy: PNG filter strategy to encode with
+state.encoder.force_palette: add palette even if not encoding to one
+state.encoder.add_id: add LodePNG identifier and version as a text chunk
+state.encoder.text_compression: use compressed text chunks for metadata
+state.info_raw.colortype: color type of raw input image you provide
+state.info_raw.bitdepth: bit depth of raw input image you provide
+state.info_raw: more color settings, see struct LodePNGColorMode
+state.info_png.color.colortype: desired color type if auto_convert is false
+state.info_png.color.bitdepth: desired bit depth if auto_convert is false
+state.info_png.color....: more color settings, see struct LodePNGColorMode
+state.info_png....: more PNG related settings, see struct LodePNGInfo
+
+
+12. changes
+-----------
+
+The version number of LodePNG is the date of the change given in the format
+yyyymmdd.
+
+Some changes aren't backwards compatible. Those are indicated with a (!)
+symbol.
+
+*) 18 apr 2016: Changed qsort to custom stable sort (for platforms w/o qsort).
+*) 09 apr 2016: Fixed colorkey usage detection, and better file loading (within
+   the limits of pure C90).
+*) 08 dec 2015: Made load_file function return error if file can't be opened.
+*) 24 okt 2015: Bugfix with decoding to palette output.
+*) 18 apr 2015: Boundary PM instead of just package-merge for faster encoding.
+*) 23 aug 2014: Reduced needless memory usage of decoder.
+*) 28 jun 2014: Removed fix_png setting, always support palette OOB for
+    simplicity. Made ColorProfile public.
+*) 09 jun 2014: Faster encoder by fixing hash bug and more zeros optimization.
+*) 22 dec 2013: Power of two windowsize required for optimization.
+*) 15 apr 2013: Fixed bug with LAC_ALPHA and color key.
+*) 25 mar 2013: Added an optional feature to ignore some PNG errors (fix_png).
+*) 11 mar 2013 (!): Bugfix with custom free. Changed from "my" to "lodepng_"
+    prefix for the custom allocators and made it possible with a new #define to
+    use custom ones in your project without needing to change lodepng's code.
+*) 28 jan 2013: Bugfix with color key.
+*) 27 okt 2012: Tweaks in text chunk keyword length error handling.
+*) 8 okt 2012 (!): Added new filter strategy (entropy) and new auto color mode.
+    (no palette). Better deflate tree encoding. New compression tweak settings.
+    Faster color conversions while decoding. Some internal cleanups.
+*) 23 sep 2012: Reduced warnings in Visual Studio a little bit.
+*) 1 sep 2012 (!): Removed #define's for giving custom (de)compression functions
+    and made it work with function pointers instead.
+*) 23 jun 2012: Added more filter strategies. Made it easier to use custom alloc
+    and free functions and toggle #defines from compiler flags. Small fixes.
+*) 6 may 2012 (!): Made plugging in custom zlib/deflate functions more flexible.
+*) 22 apr 2012 (!): Made interface more consistent, renaming a lot. Removed
+    redundant C++ codec classes. Reduced amount of structs. Everything changed,
+    but it is cleaner now imho and functionality remains the same. Also fixed
+    several bugs and shrunk the implementation code. Made new samples.
+*) 6 nov 2011 (!): By default, the encoder now automatically chooses the best
+    PNG color model and bit depth, based on the amount and type of colors of the
+    raw image. For this, autoLeaveOutAlphaChannel replaced by auto_choose_color.
+*) 9 okt 2011: simpler hash chain implementation for the encoder.
+*) 8 sep 2011: lz77 encoder lazy matching instead of greedy matching.
+*) 23 aug 2011: tweaked the zlib compression parameters after benchmarking.
+    A bug with the PNG filtertype heuristic was fixed, so that it chooses much
+    better ones (it's quite significant). A setting to do an experimental, slow,
+    brute force search for PNG filter types is added.
+*) 17 aug 2011 (!): changed some C zlib related function names.
+*) 16 aug 2011: made the code less wide (max 120 characters per line).
+*) 17 apr 2011: code cleanup. Bugfixes. Convert low to 16-bit per sample colors.
+*) 21 feb 2011: fixed compiling for C90. Fixed compiling with sections disabled.
+*) 11 dec 2010: encoding is made faster, based on suggestion by Peter Eastman
+    to optimize long sequences of zeros.
+*) 13 nov 2010: added LodePNG_InfoColor_hasPaletteAlpha and
+    LodePNG_InfoColor_canHaveAlpha functions for convenience.
+*) 7 nov 2010: added LodePNG_error_text function to get error code description.
+*) 30 okt 2010: made decoding slightly faster
+*) 26 okt 2010: (!) changed some C function and struct names (more consistent).
+     Reorganized the documentation and the declaration order in the header.
+*) 08 aug 2010: only changed some comments and external samples.
+*) 05 jul 2010: fixed bug thanks to warnings in the new gcc version.
+*) 14 mar 2010: fixed bug where too much memory was allocated for char buffers.
+*) 02 sep 2008: fixed bug where it could create empty tree that linux apps could
+    read by ignoring the problem but windows apps couldn't.
+*) 06 jun 2008: added more error checks for out of memory cases.
+*) 26 apr 2008: added a few more checks here and there to ensure more safety.
+*) 06 mar 2008: crash with encoding of strings fixed
+*) 02 feb 2008: support for international text chunks added (iTXt)
+*) 23 jan 2008: small cleanups, and #defines to divide code in sections
+*) 20 jan 2008: support for unknown chunks allowing using LodePNG for an editor.
+*) 18 jan 2008: support for tIME and pHYs chunks added to encoder and decoder.
+*) 17 jan 2008: ability to encode and decode compressed zTXt chunks added
+    Also various fixes, such as in the deflate and the padding bits code.
+*) 13 jan 2008: Added ability to encode Adam7-interlaced images. Improved
+    filtering code of encoder.
+*) 07 jan 2008: (!) changed LodePNG to use ISO C90 instead of C++. A
+    C++ wrapper around this provides an interface almost identical to before.
+    Having LodePNG be pure ISO C90 makes it more portable. The C and C++ code
+    are together in these files but it works both for C and C++ compilers.
+*) 29 dec 2007: (!) changed most integer types to unsigned int + other tweaks
+*) 30 aug 2007: bug fixed which makes this Borland C++ compatible
+*) 09 aug 2007: some VS2005 warnings removed again
+*) 21 jul 2007: deflate code placed in new namespace separate from zlib code
+*) 08 jun 2007: fixed bug with 2- and 4-bit color, and small interlaced images
+*) 04 jun 2007: improved support for Visual Studio 2005: crash with accessing
+    invalid std::vector element [0] fixed, and level 3 and 4 warnings removed
+*) 02 jun 2007: made the encoder add a tag with version by default
+*) 27 may 2007: zlib and png code separated (but still in the same file),
+    simple encoder/decoder functions added for more simple usage cases
+*) 19 may 2007: minor fixes, some code cleaning, new error added (error 69),
+    moved some examples from here to lodepng_examples.cpp
+*) 12 may 2007: palette decoding bug fixed
+*) 24 apr 2007: changed the license from BSD to the zlib license
+*) 11 mar 2007: very simple addition: ability to encode bKGD chunks.
+*) 04 mar 2007: (!) tEXt chunk related fixes, and support for encoding
+    palettized PNG images. Plus little interface change with palette and texts.
+*) 03 mar 2007: Made it encode dynamic Huffman shorter with repeat codes.
+    Fixed a bug where the end code of a block had length 0 in the Huffman tree.
+*) 26 feb 2007: Huffman compression with dynamic trees (BTYPE 2) now implemented
+    and supported by the encoder, resulting in smaller PNGs at the output.
+*) 27 jan 2007: Made the Adler-32 test faster so that a timewaste is gone.
+*) 24 jan 2007: gave encoder an error interface. Added color conversion from any
+    greyscale type to 8-bit greyscale with or without alpha.
+*) 21 jan 2007: (!) Totally changed the interface. It allows more color types
+    to convert to and is more uniform. See the manual for how it works now.
+*) 07 jan 2007: Some cleanup & fixes, and a few changes over the last days:
+    encode/decode custom tEXt chunks, separate classes for zlib & deflate, and
+    at last made the decoder give errors for incorrect Adler32 or Crc.
+*) 01 jan 2007: Fixed bug with encoding PNGs with less than 8 bits per channel.
+*) 29 dec 2006: Added support for encoding images without alpha channel, and
+    cleaned out code as well as making certain parts faster.
+*) 28 dec 2006: Added "Settings" to the encoder.
+*) 26 dec 2006: The encoder now does LZ77 encoding and produces much smaller files now.
+    Removed some code duplication in the decoder. Fixed little bug in an example.
+*) 09 dec 2006: (!) Placed output parameters of public functions as first parameter.
+    Fixed a bug of the decoder with 16-bit per color.
+*) 15 okt 2006: Changed documentation structure
+*) 09 okt 2006: Encoder class added. It encodes a valid PNG image from the
+    given image buffer, however for now it's not compressed.
+*) 08 sep 2006: (!) Changed to interface with a Decoder class
+*) 30 jul 2006: (!) LodePNG_InfoPng , width and height are now retrieved in different
+    way. Renamed decodePNG to decodePNGGeneric.
+*) 29 jul 2006: (!) Changed the interface: image info is now returned as a
+    struct of type LodePNG::LodePNG_Info, instead of a vector, which was a bit clumsy.
+*) 28 jul 2006: Cleaned the code and added new error checks.
+    Corrected terminology "deflate" into "inflate".
+*) 23 jun 2006: Added SDL example in the documentation in the header, this
+    example allows easy debugging by displaying the PNG and its transparency.
+*) 22 jun 2006: (!) Changed way to obtain error value. Added
+    loadFile function for convenience. Made decodePNG32 faster.
+*) 21 jun 2006: (!) Changed type of info vector to unsigned.
+    Changed position of palette in info vector. Fixed an important bug that
+    happened on PNGs with an uncompressed block.
+*) 16 jun 2006: Internally changed unsigned into unsigned where
+    needed, and performed some optimizations.
+*) 07 jun 2006: (!) Renamed functions to decodePNG and placed them
+    in LodePNG namespace. Changed the order of the parameters. Rewrote the
+    documentation in the header. Renamed files to lodepng.cpp and lodepng.h
+*) 22 apr 2006: Optimized and improved some code
+*) 07 sep 2005: (!) Changed to std::vector interface
+*) 12 aug 2005: Initial release (C++, decoder only)
+
+
+13. contact information
+-----------------------
+
+Feel free to contact me with suggestions, problems, comments, ... concerning
+LodePNG. If you encounter a PNG image that doesn't work properly with this
+decoder, feel free to send it and I'll use it to find and fix the problem.
+
+My email address is (puzzle the account and domain together with an @ symbol):
+Domain: gmail dot com.
+Account: lode dot vandevenne.
+
+
+Copyright (c) 2005-2016 Lode Vandevenne
+*/
diff --git a/font/vera_sans_mono12.bmfc b/font/vera_sans_mono12.bmfc
new file mode 100644
index 0000000..de66698
--- /dev/null
+++ b/font/vera_sans_mono12.bmfc
@@ -0,0 +1,57 @@
+# AngelCode Bitmap Font Generator configuration file
+fileVersion=1
+
+# font settings
+fontName=Bitstream Vera Sans Mono
+fontFile=VeraMono.ttf
+charSet=0
+fontSize=12
+aa=1
+scaleH=100
+useSmoothing=0
+isBold=0
+isItalic=0
+useUnicode=1
+disableBoxChars=1
+outputInvalidCharGlyph=0
+dontIncludeKerningPairs=0
+useHinting=1
+renderFromOutline=0
+useClearType=0
+
+# character alignment
+paddingDown=0
+paddingUp=0
+paddingRight=0
+paddingLeft=0
+spacingHoriz=1
+spacingVert=1
+useFixedHeight=1
+forceZero=0
+
+# output file
+outWidth=256
+outHeight=128
+outBitDepth=32
+fontDescFormat=0
+fourChnlPacked=0
+textureFormat=png
+textureCompression=0
+alphaChnl=1
+redChnl=0
+greenChnl=0
+blueChnl=0
+invA=0
+invR=0
+invG=0
+invB=0
+
+# outline
+outlineThickness=0
+
+# selected chars
+chars=32-126,160-255,262-263,268-269,273,286-287,304-305,321-322,338-339,350-353,376,381-382,402,710-711
+chars=728-733,937,960,8211-8212,8216-8218,8220-8222,8224-8226,8230,8240,8249-8250,8364,8482,8706,8710
+chars=8719,8721-8722,8725,8729-8730,8734,8747,8776,8800,8804-8805,9674
+
+# imported icon images
diff --git a/font/vera_sans_mono12.fnt b/font/vera_sans_mono12.fnt
new file mode 100644
index 0000000..5f6c2e7
--- /dev/null
+++ b/font/vera_sans_mono12.fnt
@@ -0,0 +1,258 @@
+info face="Bitstream Vera Sans Mono" size=12 bold=0 italic=0 charset="" unicode=1 stretchH=100 smooth=0 aa=1 padding=0,0,0,0 spacing=1,1 outline=0
+common lineHeight=12 base=10 scaleW=256 scaleH=128 pages=1 packed=0 alphaChnl=1 redChnl=0 greenChnl=0 blueChnl=0
+page id=0 file="vera_sans_mono12_0.png"
+chars count=254
+char id=32   x=252   y=0     width=3     height=12    xoffset=-1    yoffset=0     xadvance=6     page=0  chnl=15
+char id=33   x=100   y=65    width=1     height=12    xoffset=3     yoffset=0     xadvance=6     page=0  chnl=15
+char id=34   x=24    y=65    width=3     height=12    xoffset=2     yoffset=0     xadvance=6     page=0  chnl=15
+char id=35   x=21    y=0     width=6     height=12    xoffset=0     yoffset=0     xadvance=6     page=0  chnl=15
+char id=36   x=186   y=0     width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=37   x=35    y=0     width=6     height=12    xoffset=0     yoffset=0     xadvance=6     page=0  chnl=15
+char id=38   x=192   y=0     width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=39   x=102   y=65    width=1     height=12    xoffset=3     yoffset=0     xadvance=6     page=0  chnl=15
+char id=40   x=80    y=65    width=2     height=12    xoffset=2     yoffset=0     xadvance=6     page=0  chnl=15
+char id=41   x=74    y=65    width=2     height=12    xoffset=2     yoffset=0     xadvance=6     page=0  chnl=15
+char id=42   x=216   y=0     width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=43   x=222   y=0     width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=44   x=104   y=65    width=1     height=12    xoffset=2     yoffset=0     xadvance=6     page=0  chnl=15
+char id=45   x=36    y=65    width=3     height=12    xoffset=2     yoffset=0     xadvance=6     page=0  chnl=15
+char id=46   x=106   y=65    width=1     height=12    xoffset=2     yoffset=0     xadvance=6     page=0  chnl=15
+char id=47   x=246   y=0     width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=48   x=0     y=13    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=49   x=6     y=13    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=50   x=12    y=13    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=51   x=18    y=13    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=52   x=24    y=13    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=53   x=30    y=13    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=54   x=36    y=13    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=55   x=42    y=13    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=56   x=48    y=13    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=57   x=54    y=13    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=58   x=90    y=65    width=1     height=12    xoffset=2     yoffset=0     xadvance=6     page=0  chnl=15
+char id=59   x=86    y=65    width=1     height=12    xoffset=2     yoffset=0     xadvance=6     page=0  chnl=15
+char id=60   x=72    y=13    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=61   x=78    y=13    width=5     height=12    xoffset=0     yoffset=0     xadvance=6     page=0  chnl=15
+char id=62   x=84    y=13    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=63   x=218   y=52    width=4     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=64   x=96    y=13    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=65   x=102   y=13    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=66   x=108   y=13    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=67   x=114   y=13    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=68   x=120   y=13    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=69   x=126   y=13    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=70   x=132   y=13    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=71   x=138   y=13    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=72   x=144   y=13    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=73   x=150   y=13    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=74   x=188   y=52    width=4     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=75   x=162   y=13    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=76   x=168   y=13    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=77   x=174   y=13    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=78   x=180   y=13    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=79   x=186   y=13    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=80   x=192   y=13    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=81   x=198   y=13    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=82   x=42    y=0     width=6     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=83   x=204   y=13    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=84   x=210   y=13    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=85   x=216   y=13    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=86   x=222   y=13    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=87   x=77    y=0     width=6     height=12    xoffset=0     yoffset=0     xadvance=6     page=0  chnl=15
+char id=88   x=228   y=13    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=89   x=234   y=13    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=90   x=240   y=13    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=91   x=71    y=65    width=2     height=12    xoffset=2     yoffset=0     xadvance=6     page=0  chnl=15
+char id=92   x=0     y=26    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=93   x=56    y=65    width=2     height=12    xoffset=2     yoffset=0     xadvance=6     page=0  chnl=15
+char id=94   x=168   y=0     width=5     height=12    xoffset=0     yoffset=0     xadvance=6     page=0  chnl=15
+char id=95   x=18    y=26    width=5     height=12    xoffset=0     yoffset=0     xadvance=6     page=0  chnl=15
+char id=96   x=83    y=65    width=2     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=97   x=30    y=26    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=98   x=36    y=26    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=99   x=213   y=52    width=4     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=100  x=48    y=26    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=101  x=54    y=26    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=102  x=138   y=52    width=4     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=103  x=66    y=26    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=104  x=72    y=26    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=105  x=78    y=26    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=106  x=252   y=13    width=3     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=107  x=90    y=26    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=108  x=96    y=26    width=5     height=12    xoffset=0     yoffset=0     xadvance=6     page=0  chnl=15
+char id=109  x=102   y=26    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=110  x=108   y=26    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=111  x=114   y=26    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=112  x=120   y=26    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=113  x=126   y=26    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=114  x=163   y=52    width=4     height=12    xoffset=2     yoffset=0     xadvance=6     page=0  chnl=15
+char id=115  x=138   y=26    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=116  x=168   y=52    width=4     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=117  x=150   y=26    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=118  x=156   y=26    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=119  x=162   y=26    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=120  x=168   y=26    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=121  x=174   y=26    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=122  x=180   y=26    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=123  x=178   y=52    width=4     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=124  x=92    y=65    width=1     height=12    xoffset=3     yoffset=0     xadvance=6     page=0  chnl=15
+char id=125  x=203   y=52    width=4     height=12    xoffset=2     yoffset=0     xadvance=6     page=0  chnl=15
+char id=126  x=204   y=26    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=160  x=252   y=26    width=3     height=12    xoffset=-1    yoffset=0     xadvance=6     page=0  chnl=15
+char id=161  x=94    y=65    width=1     height=12    xoffset=3     yoffset=0     xadvance=6     page=0  chnl=15
+char id=162  x=143   y=52    width=4     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=163  x=228   y=26    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=164  x=234   y=26    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=165  x=240   y=26    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=166  x=98    y=65    width=1     height=12    xoffset=3     yoffset=0     xadvance=6     page=0  chnl=15
+char id=167  x=148   y=52    width=4     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=168  x=28    y=65    width=3     height=12    xoffset=2     yoffset=0     xadvance=6     page=0  chnl=15
+char id=169  x=140   y=0     width=6     height=12    xoffset=0     yoffset=0     xadvance=6     page=0  chnl=15
+char id=170  x=233   y=52    width=4     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=171  x=158   y=52    width=4     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=172  x=12    y=39    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=173  x=20    y=65    width=3     height=12    xoffset=2     yoffset=0     xadvance=6     page=0  chnl=15
+char id=174  x=7     y=0     width=6     height=12    xoffset=0     yoffset=0     xadvance=6     page=0  chnl=15
+char id=175  x=32    y=65    width=3     height=12    xoffset=2     yoffset=0     xadvance=6     page=0  chnl=15
+char id=176  x=16    y=65    width=3     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=177  x=18    y=39    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=178  x=252   y=52    width=3     height=12    xoffset=2     yoffset=0     xadvance=6     page=0  chnl=15
+char id=179  x=248   y=52    width=3     height=12    xoffset=2     yoffset=0     xadvance=6     page=0  chnl=15
+char id=180  x=77    y=65    width=2     height=12    xoffset=2     yoffset=0     xadvance=6     page=0  chnl=15
+char id=181  x=84    y=0     width=6     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=182  x=24    y=39    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=183  x=88    y=65    width=1     height=12    xoffset=2     yoffset=0     xadvance=6     page=0  chnl=15
+char id=184  x=59    y=65    width=2     height=12    xoffset=2     yoffset=0     xadvance=6     page=0  chnl=15
+char id=185  x=8     y=65    width=3     height=12    xoffset=2     yoffset=0     xadvance=6     page=0  chnl=15
+char id=186  x=243   y=52    width=4     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=187  x=228   y=52    width=4     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=188  x=133   y=0     width=6     height=12    xoffset=0     yoffset=0     xadvance=6     page=0  chnl=15
+char id=189  x=161   y=0     width=6     height=12    xoffset=0     yoffset=0     xadvance=6     page=0  chnl=15
+char id=190  x=49    y=0     width=6     height=12    xoffset=0     yoffset=0     xadvance=6     page=0  chnl=15
+char id=191  x=238   y=52    width=4     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=192  x=216   y=26    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=193  x=36    y=39    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=194  x=42    y=39    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=195  x=48    y=39    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=196  x=54    y=39    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=197  x=60    y=39    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=198  x=91    y=0     width=6     height=12    xoffset=0     yoffset=0     xadvance=6     page=0  chnl=15
+char id=199  x=66    y=39    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=200  x=72    y=39    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=201  x=78    y=39    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=202  x=84    y=39    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=203  x=90    y=39    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=204  x=96    y=39    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=205  x=102   y=39    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=206  x=108   y=39    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=207  x=114   y=39    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=208  x=126   y=0     width=6     height=12    xoffset=0     yoffset=0     xadvance=6     page=0  chnl=15
+char id=209  x=120   y=39    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=210  x=126   y=39    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=211  x=132   y=39    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=212  x=138   y=39    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=213  x=144   y=39    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=214  x=150   y=39    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=215  x=156   y=39    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=216  x=119   y=0     width=6     height=12    xoffset=0     yoffset=0     xadvance=6     page=0  chnl=15
+char id=217  x=162   y=39    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=218  x=168   y=39    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=219  x=174   y=39    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=220  x=180   y=39    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=221  x=186   y=39    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=222  x=192   y=39    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=223  x=90    y=13    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=224  x=198   y=39    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=225  x=204   y=39    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=226  x=210   y=39    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=227  x=216   y=39    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=228  x=222   y=39    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=229  x=228   y=39    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=230  x=234   y=39    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=231  x=193   y=52    width=4     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=232  x=240   y=39    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=233  x=246   y=39    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=234  x=0     y=52    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=235  x=6     y=52    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=236  x=12    y=52    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=237  x=18    y=52    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=238  x=24    y=52    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=239  x=30    y=52    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=240  x=36    y=52    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=241  x=42    y=52    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=242  x=48    y=52    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=243  x=54    y=52    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=244  x=60    y=52    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=245  x=66    y=52    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=246  x=72    y=52    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=247  x=78    y=52    width=5     height=12    xoffset=0     yoffset=0     xadvance=6     page=0  chnl=15
+char id=248  x=84    y=52    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=249  x=90    y=52    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=250  x=96    y=52    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=251  x=102   y=52    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=252  x=108   y=52    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=253  x=114   y=52    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=254  x=120   y=52    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=255  x=126   y=52    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=262  x=30    y=39    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=263  x=198   y=52    width=4     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=268  x=132   y=52    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=269  x=223   y=52    width=4     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=273  x=105   y=0     width=6     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=286  x=174   y=0     width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=287  x=180   y=0     width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=304  x=198   y=0     width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=305  x=204   y=0     width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=321  x=98    y=0     width=6     height=12    xoffset=0     yoffset=0     xadvance=6     page=0  chnl=15
+char id=322  x=208   y=52    width=4     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=338  x=210   y=0     width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=339  x=228   y=0     width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=350  x=234   y=0     width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=351  x=240   y=0     width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=352  x=60    y=13    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=353  x=66    y=13    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=376  x=156   y=13    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=381  x=246   y=13    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=382  x=6     y=26    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=402  x=70    y=0     width=6     height=12    xoffset=0     yoffset=0     xadvance=6     page=0  chnl=15
+char id=710  x=48    y=65    width=3     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=711  x=52    y=65    width=3     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=728  x=40    y=65    width=3     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=729  x=96    y=65    width=1     height=12    xoffset=3     yoffset=0     xadvance=6     page=0  chnl=15
+char id=730  x=44    y=65    width=3     height=12    xoffset=2     yoffset=0     xadvance=6     page=0  chnl=15
+char id=731  x=62    y=65    width=2     height=12    xoffset=2     yoffset=0     xadvance=6     page=0  chnl=15
+char id=732  x=183   y=52    width=4     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=733  x=173   y=52    width=4     height=12    xoffset=2     yoffset=0     xadvance=6     page=0  chnl=15
+char id=937  x=12    y=26    width=5     height=12    xoffset=0     yoffset=0     xadvance=6     page=0  chnl=15
+char id=960  x=63    y=0     width=6     height=12    xoffset=0     yoffset=0     xadvance=6     page=0  chnl=15
+char id=8211 x=112   y=0     width=6     height=12    xoffset=0     yoffset=0     xadvance=6     page=0  chnl=15
+char id=8212 x=56    y=0     width=6     height=12    xoffset=0     yoffset=0     xadvance=6     page=0  chnl=15
+char id=8216 x=108   y=65    width=1     height=12    xoffset=2     yoffset=0     xadvance=6     page=0  chnl=15
+char id=8217 x=110   y=65    width=1     height=12    xoffset=3     yoffset=0     xadvance=6     page=0  chnl=15
+char id=8218 x=112   y=65    width=1     height=12    xoffset=2     yoffset=0     xadvance=6     page=0  chnl=15
+char id=8220 x=0     y=65    width=3     height=12    xoffset=2     yoffset=0     xadvance=6     page=0  chnl=15
+char id=8221 x=252   y=39    width=3     height=12    xoffset=2     yoffset=0     xadvance=6     page=0  chnl=15
+char id=8222 x=4     y=65    width=3     height=12    xoffset=2     yoffset=0     xadvance=6     page=0  chnl=15
+char id=8224 x=24    y=26    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=8225 x=42    y=26    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=8226 x=12    y=65    width=3     height=12    xoffset=2     yoffset=0     xadvance=6     page=0  chnl=15
+char id=8230 x=60    y=26    width=5     height=12    xoffset=0     yoffset=0     xadvance=6     page=0  chnl=15
+char id=8240 x=28    y=0     width=6     height=12    xoffset=0     yoffset=0     xadvance=6     page=0  chnl=15
+char id=8249 x=65    y=65    width=2     height=12    xoffset=2     yoffset=0     xadvance=6     page=0  chnl=15
+char id=8250 x=68    y=65    width=2     height=12    xoffset=2     yoffset=0     xadvance=6     page=0  chnl=15
+char id=8364 x=84    y=26    width=5     height=12    xoffset=0     yoffset=0     xadvance=6     page=0  chnl=15
+char id=8482 x=14    y=0     width=6     height=12    xoffset=0     yoffset=0     xadvance=6     page=0  chnl=15
+char id=8706 x=153   y=52    width=4     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=8710 x=0     y=0     width=6     height=12    xoffset=0     yoffset=0     xadvance=6     page=0  chnl=15
+char id=8719 x=132   y=26    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=8721 x=144   y=26    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=8722 x=186   y=26    width=5     height=12    xoffset=0     yoffset=0     xadvance=6     page=0  chnl=15
+char id=8725 x=192   y=26    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=8729 x=114   y=65    width=1     height=12    xoffset=2     yoffset=0     xadvance=6     page=0  chnl=15
+char id=8730 x=154   y=0     width=6     height=12    xoffset=0     yoffset=0     xadvance=6     page=0  chnl=15
+char id=8734 x=147   y=0     width=6     height=12    xoffset=0     yoffset=0     xadvance=6     page=0  chnl=15
+char id=8747 x=198   y=26    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=8776 x=210   y=26    width=5     height=12    xoffset=0     yoffset=0     xadvance=6     page=0  chnl=15
+char id=8800 x=222   y=26    width=5     height=12    xoffset=0     yoffset=0     xadvance=6     page=0  chnl=15
+char id=8804 x=246   y=26    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=8805 x=0     y=39    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
+char id=9674 x=6     y=39    width=5     height=12    xoffset=1     yoffset=0     xadvance=6     page=0  chnl=15
diff --git a/font/vera_sans_mono12_0.png b/font/vera_sans_mono12_0.png
new file mode 100644
index 0000000..59154da
Binary files /dev/null and b/font/vera_sans_mono12_0.png differ
diff --git a/font/vera_sans_mono16.bmfc b/font/vera_sans_mono16.bmfc
new file mode 100644
index 0000000..7ebda40
--- /dev/null
+++ b/font/vera_sans_mono16.bmfc
@@ -0,0 +1,57 @@
+# AngelCode Bitmap Font Generator configuration file
+fileVersion=1
+
+# font settings
+fontName=Bitstream Vera Sans Mono
+fontFile=VeraMono.ttf
+charSet=0
+fontSize=16
+aa=1
+scaleH=100
+useSmoothing=1
+isBold=0
+isItalic=0
+useUnicode=1
+disableBoxChars=1
+outputInvalidCharGlyph=0
+dontIncludeKerningPairs=0
+useHinting=1
+renderFromOutline=0
+useClearType=0
+
+# character alignment
+paddingDown=0
+paddingUp=0
+paddingRight=0
+paddingLeft=0
+spacingHoriz=1
+spacingVert=1
+useFixedHeight=1
+forceZero=0
+
+# output file
+outWidth=256
+outHeight=128
+outBitDepth=32
+fontDescFormat=0
+fourChnlPacked=0
+textureFormat=png
+textureCompression=0
+alphaChnl=1
+redChnl=0
+greenChnl=0
+blueChnl=0
+invA=0
+invR=0
+invG=0
+invB=0
+
+# outline
+outlineThickness=0
+
+# selected chars
+chars=32-126,160-255,262-263,268-269,273,286-287,304-305,321-322,338-339,350-353,376,381-382,402,710-711
+chars=728-733,937,960,8211-8212,8216-8218,8220-8222,8224-8226,8230,8240,8249-8250,8364,8482,8706,8710
+chars=8719,8721-8722,8725,8729-8730,8734,8747,8776,8800,8804-8805,9674
+
+# imported icon images
diff --git a/font/vera_sans_mono16.fnt b/font/vera_sans_mono16.fnt
new file mode 100644
index 0000000..0e1464b
--- /dev/null
+++ b/font/vera_sans_mono16.fnt
@@ -0,0 +1,258 @@
+info face="Bitstream Vera Sans Mono" size=16 bold=0 italic=0 charset="" unicode=1 stretchH=100 smooth=1 aa=1 padding=0,0,0,0 spacing=1,1 outline=0
+common lineHeight=16 base=13 scaleW=256 scaleH=128 pages=1 packed=0 alphaChnl=1 redChnl=0 greenChnl=0 blueChnl=0
+page id=0 file="vera_sans_mono16_0.png"
+chars count=254
+char id=32   x=212   y=102   width=3     height=16    xoffset=-1    yoffset=0     xadvance=8     page=0  chnl=15
+char id=33   x=254   y=0     width=1     height=16    xoffset=4     yoffset=0     xadvance=8     page=0  chnl=15
+char id=34   x=204   y=102   width=3     height=16    xoffset=3     yoffset=0     xadvance=8     page=0  chnl=15
+char id=35   x=68    y=0     width=8     height=16    xoffset=0     yoffset=0     xadvance=8     page=0  chnl=15
+char id=36   x=88    y=34    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=37   x=86    y=0     width=8     height=16    xoffset=0     yoffset=0     xadvance=8     page=0  chnl=15
+char id=38   x=168   y=34    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=39   x=252   y=102   width=1     height=16    xoffset=4     yoffset=0     xadvance=8     page=0  chnl=15
+char id=40   x=224   y=102   width=3     height=16    xoffset=3     yoffset=0     xadvance=8     page=0  chnl=15
+char id=41   x=216   y=102   width=3     height=16    xoffset=2     yoffset=0     xadvance=8     page=0  chnl=15
+char id=42   x=40    y=51    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=43   x=64    y=51    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=44   x=192   y=102   width=3     height=16    xoffset=2     yoffset=0     xadvance=8     page=0  chnl=15
+char id=45   x=111   y=102   width=4     height=16    xoffset=2     yoffset=0     xadvance=8     page=0  chnl=15
+char id=46   x=232   y=102   width=2     height=16    xoffset=3     yoffset=0     xadvance=8     page=0  chnl=15
+char id=47   x=241   y=68    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=48   x=144   y=51    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=49   x=42    y=102   width=5     height=16    xoffset=2     yoffset=0     xadvance=8     page=0  chnl=15
+char id=50   x=176   y=51    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=51   x=192   y=51    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=52   x=200   y=51    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=53   x=216   y=51    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=54   x=224   y=51    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=55   x=230   y=0     width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=56   x=238   y=0     width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=57   x=246   y=0     width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=58   x=235   y=102   width=2     height=16    xoffset=3     yoffset=0     xadvance=8     page=0  chnl=15
+char id=59   x=160   y=102   width=3     height=16    xoffset=2     yoffset=0     xadvance=8     page=0  chnl=15
+char id=60   x=16    y=17    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=61   x=24    y=17    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=62   x=32    y=17    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=63   x=18    y=102   width=5     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=64   x=48    y=17    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=65   x=56    y=17    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=66   x=64    y=17    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=67   x=72    y=17    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=68   x=80    y=17    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=69   x=88    y=17    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=70   x=96    y=17    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=71   x=104   y=17    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=72   x=112   y=17    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=73   x=6     y=102   width=5     height=16    xoffset=2     yoffset=0     xadvance=8     page=0  chnl=15
+char id=74   x=248   y=68    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=75   x=136   y=17    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=76   x=144   y=17    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=77   x=152   y=17    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=78   x=160   y=17    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=79   x=168   y=17    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=80   x=176   y=17    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=81   x=184   y=17    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=82   x=77    y=0     width=8     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=83   x=192   y=17    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=84   x=200   y=17    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=85   x=208   y=17    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=86   x=216   y=17    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=87   x=0     y=0     width=10    height=16    xoffset=-1    yoffset=0     xadvance=8     page=0  chnl=15
+char id=88   x=224   y=17    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=89   x=232   y=17    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=90   x=240   y=17    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=91   x=228   y=102   width=3     height=16    xoffset=3     yoffset=0     xadvance=8     page=0  chnl=15
+char id=92   x=0     y=85    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=93   x=220   y=102   width=3     height=16    xoffset=2     yoffset=0     xadvance=8     page=0  chnl=15
+char id=94   x=48    y=34    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=95   x=24    y=34    width=7     height=16    xoffset=0     yoffset=0     xadvance=8     page=0  chnl=15
+char id=96   x=208   y=102   width=3     height=16    xoffset=2     yoffset=0     xadvance=8     page=0  chnl=15
+char id=97   x=14    y=85    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=98   x=28    y=85    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=99   x=42    y=85    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=100  x=49    y=85    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=101  x=63    y=85    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=102  x=70    y=85    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=103  x=77    y=85    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=104  x=84    y=85    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=105  x=104   y=34    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=106  x=106   y=102   width=4     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=107  x=112   y=85    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=108  x=128   y=34    width=7     height=16    xoffset=0     yoffset=0     xadvance=8     page=0  chnl=15
+char id=109  x=136   y=34    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=110  x=119   y=85    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=111  x=126   y=85    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=112  x=133   y=85    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=113  x=140   y=85    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=114  x=48    y=102   width=5     height=16    xoffset=2     yoffset=0     xadvance=8     page=0  chnl=15
+char id=115  x=147   y=85    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=116  x=161   y=85    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=117  x=182   y=85    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=118  x=196   y=85    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=119  x=140   y=0     width=8     height=16    xoffset=0     yoffset=0     xadvance=8     page=0  chnl=15
+char id=120  x=203   y=85    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=121  x=210   y=85    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=122  x=217   y=85    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=123  x=84    y=102   width=5     height=16    xoffset=2     yoffset=0     xadvance=8     page=0  chnl=15
+char id=124  x=250   y=102   width=1     height=16    xoffset=4     yoffset=0     xadvance=8     page=0  chnl=15
+char id=125  x=36    y=102   width=5     height=16    xoffset=2     yoffset=0     xadvance=8     page=0  chnl=15
+char id=126  x=16    y=51    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=160  x=184   y=102   width=3     height=16    xoffset=-1    yoffset=0     xadvance=8     page=0  chnl=15
+char id=161  x=244   y=102   width=1     height=16    xoffset=4     yoffset=0     xadvance=8     page=0  chnl=15
+char id=162  x=224   y=85    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=163  x=48    y=51    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=164  x=56    y=51    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=165  x=136   y=51    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=166  x=246   y=102   width=1     height=16    xoffset=4     yoffset=0     xadvance=8     page=0  chnl=15
+char id=167  x=231   y=85    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=168  x=172   y=102   width=3     height=16    xoffset=3     yoffset=0     xadvance=8     page=0  chnl=15
+char id=169  x=95    y=0     width=8     height=16    xoffset=0     yoffset=0     xadvance=8     page=0  chnl=15
+char id=170  x=60    y=102   width=5     height=16    xoffset=2     yoffset=0     xadvance=8     page=0  chnl=15
+char id=171  x=238   y=85    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=172  x=72    y=34    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=173  x=96    y=102   width=4     height=16    xoffset=2     yoffset=0     xadvance=8     page=0  chnl=15
+char id=174  x=122   y=0     width=8     height=16    xoffset=0     yoffset=0     xadvance=8     page=0  chnl=15
+char id=175  x=136   y=102   width=4     height=16    xoffset=2     yoffset=0     xadvance=8     page=0  chnl=15
+char id=176  x=131   y=102   width=4     height=16    xoffset=2     yoffset=0     xadvance=8     page=0  chnl=15
+char id=177  x=144   y=34    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=178  x=126   y=102   width=4     height=16    xoffset=2     yoffset=0     xadvance=8     page=0  chnl=15
+char id=179  x=121   y=102   width=4     height=16    xoffset=2     yoffset=0     xadvance=8     page=0  chnl=15
+char id=180  x=200   y=102   width=3     height=16    xoffset=3     yoffset=0     xadvance=8     page=0  chnl=15
+char id=181  x=184   y=34    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=182  x=192   y=34    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=183  x=238   y=102   width=2     height=16    xoffset=3     yoffset=0     xadvance=8     page=0  chnl=15
+char id=184  x=196   y=102   width=3     height=16    xoffset=3     yoffset=0     xadvance=8     page=0  chnl=15
+char id=185  x=252   y=85    width=3     height=16    xoffset=3     yoffset=0     xadvance=8     page=0  chnl=15
+char id=186  x=30    y=102   width=5     height=16    xoffset=2     yoffset=0     xadvance=8     page=0  chnl=15
+char id=187  x=245   y=85    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=188  x=149   y=0     width=8     height=16    xoffset=0     yoffset=0     xadvance=8     page=0  chnl=15
+char id=189  x=0     y=34    width=7     height=16    xoffset=0     yoffset=0     xadvance=8     page=0  chnl=15
+char id=190  x=113   y=0     width=8     height=16    xoffset=0     yoffset=0     xadvance=8     page=0  chnl=15
+char id=191  x=54    y=102   width=5     height=16    xoffset=2     yoffset=0     xadvance=8     page=0  chnl=15
+char id=192  x=32    y=51    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=193  x=240   y=34    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=194  x=232   y=34    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=195  x=224   y=34    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=196  x=208   y=34    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=197  x=200   y=34    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=198  x=131   y=0     width=8     height=16    xoffset=0     yoffset=0     xadvance=8     page=0  chnl=15
+char id=199  x=160   y=34    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=200  x=152   y=34    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=201  x=120   y=34    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=202  x=96    y=34    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=203  x=80    y=34    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=204  x=90    y=102   width=5     height=16    xoffset=2     yoffset=0     xadvance=8     page=0  chnl=15
+char id=205  x=78    y=102   width=5     height=16    xoffset=2     yoffset=0     xadvance=8     page=0  chnl=15
+char id=206  x=24    y=102   width=5     height=16    xoffset=2     yoffset=0     xadvance=8     page=0  chnl=15
+char id=207  x=0     y=102   width=5     height=16    xoffset=2     yoffset=0     xadvance=8     page=0  chnl=15
+char id=208  x=104   y=0     width=8     height=16    xoffset=0     yoffset=0     xadvance=8     page=0  chnl=15
+char id=209  x=40    y=34    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=210  x=128   y=17    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=211  x=24    y=51    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=212  x=8     y=51    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=213  x=0     y=51    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=214  x=248   y=34    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=215  x=31    y=68    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=216  x=11    y=0     width=9     height=16    xoffset=0     yoffset=0     xadvance=8     page=0  chnl=15
+char id=217  x=112   y=34    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=218  x=32    y=34    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=219  x=16    y=34    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=220  x=8     y=34    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=221  x=248   y=17    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=222  x=120   y=17    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=223  x=24    y=68    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=224  x=38    y=68    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=225  x=45    y=68    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=226  x=52    y=68    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=227  x=59    y=68    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=228  x=66    y=68    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=229  x=73    y=68    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=230  x=80    y=68    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=231  x=87    y=68    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=232  x=94    y=68    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=233  x=101   y=68    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=234  x=108   y=68    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=235  x=115   y=68    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=236  x=8     y=17    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=237  x=0     y=17    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=238  x=168   y=51    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=239  x=120   y=51    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=240  x=122   y=68    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=241  x=129   y=68    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=242  x=136   y=68    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=243  x=143   y=68    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=244  x=150   y=68    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=245  x=157   y=68    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=246  x=164   y=68    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=247  x=50    y=0     width=8     height=16    xoffset=0     yoffset=0     xadvance=8     page=0  chnl=15
+char id=248  x=41    y=0     width=8     height=16    xoffset=0     yoffset=0     xadvance=8     page=0  chnl=15
+char id=249  x=171   y=68    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=250  x=178   y=68    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=251  x=185   y=68    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=252  x=192   y=68    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=253  x=199   y=68    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=254  x=206   y=68    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=255  x=213   y=68    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=262  x=40    y=17    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=263  x=220   y=68    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=268  x=216   y=34    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=269  x=227   y=68    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=273  x=176   y=34    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=286  x=64    y=34    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=287  x=234   y=68    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=304  x=12    y=102   width=5     height=16    xoffset=2     yoffset=0     xadvance=8     page=0  chnl=15
+char id=305  x=56    y=34    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=321  x=158   y=0     width=8     height=16    xoffset=0     yoffset=0     xadvance=8     page=0  chnl=15
+char id=322  x=72    y=51    width=7     height=16    xoffset=0     yoffset=0     xadvance=8     page=0  chnl=15
+char id=338  x=80    y=51    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=339  x=7     y=85    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=350  x=88    y=51    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=351  x=21    y=85    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=352  x=96    y=51    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=353  x=35    y=85    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=376  x=104   y=51    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=381  x=112   y=51    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=382  x=56    y=85    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=402  x=167   y=0     width=8     height=16    xoffset=0     yoffset=0     xadvance=8     page=0  chnl=15
+char id=710  x=141   y=102   width=4     height=16    xoffset=2     yoffset=0     xadvance=8     page=0  chnl=15
+char id=711  x=146   y=102   width=4     height=16    xoffset=2     yoffset=0     xadvance=8     page=0  chnl=15
+char id=728  x=151   y=102   width=4     height=16    xoffset=2     yoffset=0     xadvance=8     page=0  chnl=15
+char id=729  x=248   y=102   width=1     height=16    xoffset=4     yoffset=0     xadvance=8     page=0  chnl=15
+char id=730  x=101   y=102   width=4     height=16    xoffset=2     yoffset=0     xadvance=8     page=0  chnl=15
+char id=731  x=164   y=102   width=3     height=16    xoffset=3     yoffset=0     xadvance=8     page=0  chnl=15
+char id=732  x=66    y=102   width=5     height=16    xoffset=2     yoffset=0     xadvance=8     page=0  chnl=15
+char id=733  x=72    y=102   width=5     height=16    xoffset=2     yoffset=0     xadvance=8     page=0  chnl=15
+char id=937  x=128   y=51    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=960  x=176   y=0     width=8     height=16    xoffset=0     yoffset=0     xadvance=8     page=0  chnl=15
+char id=8211 x=59    y=0     width=8     height=16    xoffset=0     yoffset=0     xadvance=8     page=0  chnl=15
+char id=8212 x=185   y=0     width=8     height=16    xoffset=0     yoffset=0     xadvance=8     page=0  chnl=15
+char id=8216 x=156   y=102   width=3     height=16    xoffset=3     yoffset=0     xadvance=8     page=0  chnl=15
+char id=8217 x=168   y=102   width=3     height=16    xoffset=3     yoffset=0     xadvance=8     page=0  chnl=15
+char id=8218 x=188   y=102   width=3     height=16    xoffset=2     yoffset=0     xadvance=8     page=0  chnl=15
+char id=8220 x=91    y=85    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=8221 x=98    y=85    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=8222 x=105   y=85    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=8224 x=152   y=51    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=8225 x=160   y=51    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=8226 x=116   y=102   width=4     height=16    xoffset=2     yoffset=0     xadvance=8     page=0  chnl=15
+char id=8230 x=194   y=0     width=8     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=8240 x=21    y=0     width=9     height=16    xoffset=0     yoffset=0     xadvance=8     page=0  chnl=15
+char id=8249 x=176   y=102   width=3     height=16    xoffset=2     yoffset=0     xadvance=8     page=0  chnl=15
+char id=8250 x=180   y=102   width=3     height=16    xoffset=3     yoffset=0     xadvance=8     page=0  chnl=15
+char id=8364 x=184   y=51    width=7     height=16    xoffset=0     yoffset=0     xadvance=8     page=0  chnl=15
+char id=8482 x=203   y=0     width=8     height=16    xoffset=0     yoffset=0     xadvance=8     page=0  chnl=15
+char id=8706 x=154   y=85    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=8710 x=31    y=0     width=9     height=16    xoffset=0     yoffset=0     xadvance=8     page=0  chnl=15
+char id=8719 x=168   y=85    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=8721 x=175   y=85    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=8722 x=208   y=51    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=8725 x=189   y=85    width=6     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=8729 x=241   y=102   width=2     height=16    xoffset=3     yoffset=0     xadvance=8     page=0  chnl=15
+char id=8730 x=212   y=0     width=8     height=16    xoffset=0     yoffset=0     xadvance=8     page=0  chnl=15
+char id=8734 x=221   y=0     width=8     height=16    xoffset=0     yoffset=0     xadvance=8     page=0  chnl=15
+char id=8747 x=232   y=51    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=8776 x=240   y=51    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=8800 x=248   y=51    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=8804 x=0     y=68    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=8805 x=8     y=68    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
+char id=9674 x=16    y=68    width=7     height=16    xoffset=1     yoffset=0     xadvance=8     page=0  chnl=15
diff --git a/font/vera_sans_mono16_0.png b/font/vera_sans_mono16_0.png
new file mode 100644
index 0000000..3ed47ed
Binary files /dev/null and b/font/vera_sans_mono16_0.png differ
diff --git a/font/vera_sans_mono18.bmfc b/font/vera_sans_mono18.bmfc
new file mode 100644
index 0000000..888f506
--- /dev/null
+++ b/font/vera_sans_mono18.bmfc
@@ -0,0 +1,57 @@
+# AngelCode Bitmap Font Generator configuration file
+fileVersion=1
+
+# font settings
+fontName=Bitstream Vera Sans Mono
+fontFile=VeraMono.ttf
+charSet=0
+fontSize=18
+aa=1
+scaleH=100
+useSmoothing=1
+isBold=0
+isItalic=0
+useUnicode=1
+disableBoxChars=1
+outputInvalidCharGlyph=0
+dontIncludeKerningPairs=0
+useHinting=1
+renderFromOutline=0
+useClearType=1
+
+# character alignment
+paddingDown=0
+paddingUp=0
+paddingRight=0
+paddingLeft=0
+spacingHoriz=1
+spacingVert=1
+useFixedHeight=1
+forceZero=0
+
+# output file
+outWidth=256
+outHeight=256
+outBitDepth=32
+fontDescFormat=0
+fourChnlPacked=0
+textureFormat=png
+textureCompression=0
+alphaChnl=1
+redChnl=0
+greenChnl=0
+blueChnl=0
+invA=0
+invR=0
+invG=0
+invB=0
+
+# outline
+outlineThickness=0
+
+# selected chars
+chars=32-126,160-255,262-263,268-269,273,286-287,304-305,321-322,338-339,350-353,376,381-382,402,710-711
+chars=728-733,937,960,8211-8212,8216-8218,8220-8222,8224-8226,8230,8240,8249-8250,8364,8482,8706,8710
+chars=8719,8721-8722,8725,8729-8730,8734,8747,8776,8800,8804-8805,9674
+
+# imported icon images
diff --git a/font/vera_sans_mono18.fnt b/font/vera_sans_mono18.fnt
new file mode 100644
index 0000000..5c01559
--- /dev/null
+++ b/font/vera_sans_mono18.fnt
@@ -0,0 +1,258 @@
+info face="Bitstream Vera Sans Mono" size=18 bold=0 italic=0 charset="" unicode=1 stretchH=100 smooth=1 aa=1 padding=0,0,0,0 spacing=1,1 outline=0
+common lineHeight=18 base=14 scaleW=256 scaleH=256 pages=1 packed=0 alphaChnl=1 redChnl=0 greenChnl=0 blueChnl=0
+page id=0 file="vera_sans_mono18_0.png"
+chars count=254
+char id=32   x=219   y=152   width=3     height=18    xoffset=-1    yoffset=0     xadvance=9     page=0  chnl=15
+char id=33   x=252   y=19    width=3     height=18    xoffset=3     yoffset=0     xadvance=9     page=0  chnl=15
+char id=34   x=100   y=152   width=5     height=18    xoffset=2     yoffset=0     xadvance=9     page=0  chnl=15
+char id=35   x=36    y=0     width=11    height=18    xoffset=-1    yoffset=0     xadvance=9     page=0  chnl=15
+char id=36   x=58    y=114   width=8     height=18    xoffset=1     yoffset=0     xadvance=9     page=0  chnl=15
+char id=37   x=0     y=0     width=11    height=18    xoffset=-1    yoffset=0     xadvance=9     page=0  chnl=15
+char id=38   x=0     y=19    width=10    height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=39   x=239   y=152   width=3     height=18    xoffset=3     yoffset=0     xadvance=9     page=0  chnl=15
+char id=40   x=250   y=38    width=5     height=18    xoffset=2     yoffset=0     xadvance=9     page=0  chnl=15
+char id=41   x=250   y=57    width=5     height=18    xoffset=2     yoffset=0     xadvance=9     page=0  chnl=15
+char id=42   x=94    y=114   width=8     height=18    xoffset=1     yoffset=0     xadvance=9     page=0  chnl=15
+char id=43   x=212   y=19    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=44   x=189   y=152   width=4     height=18    xoffset=2     yoffset=0     xadvance=9     page=0  chnl=15
+char id=45   x=250   y=76    width=5     height=18    xoffset=2     yoffset=0     xadvance=9     page=0  chnl=15
+char id=46   x=235   y=152   width=3     height=18    xoffset=3     yoffset=0     xadvance=9     page=0  chnl=15
+char id=47   x=130   y=114   width=8     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=48   x=10    y=38    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=49   x=139   y=114   width=8     height=18    xoffset=1     yoffset=0     xadvance=9     page=0  chnl=15
+char id=50   x=30    y=38    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=51   x=40    y=38    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=52   x=50    y=38    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=53   x=60    y=38    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=54   x=70    y=38    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=55   x=80    y=38    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=56   x=90    y=38    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=57   x=100   y=38    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=58   x=223   y=152   width=3     height=18    xoffset=3     yoffset=0     xadvance=9     page=0  chnl=15
+char id=59   x=184   y=152   width=4     height=18    xoffset=2     yoffset=0     xadvance=9     page=0  chnl=15
+char id=60   x=130   y=38    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=61   x=140   y=38    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=62   x=150   y=38    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=63   x=64    y=152   width=7     height=18    xoffset=1     yoffset=0     xadvance=9     page=0  chnl=15
+char id=64   x=110   y=19    width=10    height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=65   x=121   y=19    width=10    height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=66   x=170   y=38    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=67   x=180   y=38    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=68   x=190   y=38    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=69   x=200   y=38    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=70   x=166   y=114   width=8     height=18    xoffset=1     yoffset=0     xadvance=9     page=0  chnl=15
+char id=71   x=220   y=38    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=72   x=230   y=38    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=73   x=162   y=133   width=7     height=18    xoffset=1     yoffset=0     xadvance=9     page=0  chnl=15
+char id=74   x=184   y=114   width=8     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=75   x=10    y=57    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=76   x=193   y=114   width=8     height=18    xoffset=1     yoffset=0     xadvance=9     page=0  chnl=15
+char id=77   x=30    y=57    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=78   x=40    y=57    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=79   x=50    y=57    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=80   x=60    y=57    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=81   x=70    y=57    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=82   x=221   y=0     width=10    height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=83   x=80    y=57    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=84   x=90    y=57    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=85   x=100   y=57    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=86   x=11    y=19    width=10    height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=87   x=108   y=0     width=11    height=18    xoffset=-1    yoffset=0     xadvance=9     page=0  chnl=15
+char id=88   x=33    y=19    width=10    height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=89   x=110   y=57    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=90   x=120   y=57    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=91   x=194   y=152   width=4     height=18    xoffset=3     yoffset=0     xadvance=9     page=0  chnl=15
+char id=92   x=211   y=114   width=8     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=93   x=214   y=152   width=4     height=18    xoffset=2     yoffset=0     xadvance=9     page=0  chnl=15
+char id=94   x=132   y=19    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=95   x=170   y=57    width=9     height=18    xoffset=-1    yoffset=0     xadvance=9     page=0  chnl=15
+char id=96   x=250   y=133   width=5     height=18    xoffset=1     yoffset=0     xadvance=9     page=0  chnl=15
+char id=97   x=190   y=57    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=98   x=200   y=57    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=99   x=247   y=114   width=8     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=100  x=220   y=57    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=101  x=230   y=57    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=102  x=0     y=133   width=8     height=18    xoffset=1     yoffset=0     xadvance=9     page=0  chnl=15
+char id=103  x=0     y=76    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=104  x=9     y=133   width=8     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=105  x=40    y=114   width=8     height=18    xoffset=1     yoffset=0     xadvance=9     page=0  chnl=15
+char id=106  x=72    y=152   width=6     height=18    xoffset=1     yoffset=0     xadvance=9     page=0  chnl=15
+char id=107  x=36    y=133   width=8     height=18    xoffset=1     yoffset=0     xadvance=9     page=0  chnl=15
+char id=108  x=45    y=133   width=8     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=109  x=199   y=0     width=10    height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=110  x=54    y=133   width=8     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=111  x=70    y=76    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=112  x=80    y=76    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=113  x=90    y=76    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=114  x=202   y=133   width=7     height=18    xoffset=2     yoffset=0     xadvance=9     page=0  chnl=15
+char id=115  x=72    y=133   width=8     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=116  x=81    y=133   width=8     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=117  x=90    y=133   width=8     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=118  x=140   y=76    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=119  x=132   y=0     width=11    height=18    xoffset=-1    yoffset=0     xadvance=9     page=0  chnl=15
+char id=120  x=210   y=0     width=10    height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=121  x=150   y=76    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=122  x=99    y=133   width=8     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=123  x=0     y=152   width=7     height=18    xoffset=1     yoffset=0     xadvance=9     page=0  chnl=15
+char id=124  x=251   y=152   width=3     height=18    xoffset=3     yoffset=0     xadvance=9     page=0  chnl=15
+char id=125  x=16    y=152   width=7     height=18    xoffset=1     yoffset=0     xadvance=9     page=0  chnl=15
+char id=126  x=200   y=76    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=160  x=0     y=171   width=3     height=18    xoffset=-1    yoffset=0     xadvance=9     page=0  chnl=15
+char id=161  x=4     y=171   width=3     height=18    xoffset=3     yoffset=0     xadvance=9     page=0  chnl=15
+char id=162  x=238   y=114   width=8     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=163  x=220   y=76    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=164  x=27    y=133   width=8     height=18    xoffset=1     yoffset=0     xadvance=9     page=0  chnl=15
+char id=165  x=230   y=76    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=166  x=231   y=152   width=3     height=18    xoffset=3     yoffset=0     xadvance=9     page=0  chnl=15
+char id=167  x=170   y=133   width=7     height=18    xoffset=1     yoffset=0     xadvance=9     page=0  chnl=15
+char id=168  x=118   y=152   width=5     height=18    xoffset=2     yoffset=0     xadvance=9     page=0  chnl=15
+char id=169  x=84    y=0     width=11    height=18    xoffset=-1    yoffset=0     xadvance=9     page=0  chnl=15
+char id=170  x=8     y=152   width=7     height=18    xoffset=1     yoffset=0     xadvance=9     page=0  chnl=15
+char id=171  x=135   y=133   width=8     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=172  x=240   y=76    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=173  x=250   y=95    width=5     height=18    xoffset=2     yoffset=0     xadvance=9     page=0  chnl=15
+char id=174  x=120   y=0     width=11    height=18    xoffset=-1    yoffset=0     xadvance=9     page=0  chnl=15
+char id=175  x=154   y=152   width=5     height=18    xoffset=2     yoffset=0     xadvance=9     page=0  chnl=15
+char id=176  x=32    y=152   width=7     height=18    xoffset=1     yoffset=0     xadvance=9     page=0  chnl=15
+char id=177  x=0     y=95    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=178  x=106   y=152   width=5     height=18    xoffset=2     yoffset=0     xadvance=9     page=0  chnl=15
+char id=179  x=124   y=152   width=5     height=18    xoffset=2     yoffset=0     xadvance=9     page=0  chnl=15
+char id=180  x=148   y=152   width=5     height=18    xoffset=3     yoffset=0     xadvance=9     page=0  chnl=15
+char id=181  x=10    y=95    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=182  x=20    y=95    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=183  x=243   y=152   width=3     height=18    xoffset=3     yoffset=0     xadvance=9     page=0  chnl=15
+char id=184  x=178   y=152   width=5     height=18    xoffset=2     yoffset=0     xadvance=9     page=0  chnl=15
+char id=185  x=86    y=152   width=6     height=18    xoffset=2     yoffset=0     xadvance=9     page=0  chnl=15
+char id=186  x=24    y=152   width=7     height=18    xoffset=1     yoffset=0     xadvance=9     page=0  chnl=15
+char id=187  x=63    y=133   width=8     height=18    xoffset=1     yoffset=0     xadvance=9     page=0  chnl=15
+char id=188  x=30    y=95    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=189  x=40    y=95    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=190  x=50    y=95    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=191  x=242   y=133   width=7     height=18    xoffset=1     yoffset=0     xadvance=9     page=0  chnl=15
+char id=192  x=155   y=0     width=10    height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=193  x=144   y=0     width=10    height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=194  x=99    y=19    width=10    height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=195  x=232   y=0     width=10    height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=196  x=55    y=19    width=10    height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=197  x=188   y=0     width=10    height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=198  x=166   y=0     width=10    height=18    xoffset=-1    yoffset=0     xadvance=9     page=0  chnl=15
+char id=199  x=70    y=95    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=200  x=80    y=95    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=201  x=90    y=95    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=202  x=100   y=95    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=203  x=110   y=95    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=204  x=48    y=152   width=7     height=18    xoffset=1     yoffset=0     xadvance=9     page=0  chnl=15
+char id=205  x=226   y=133   width=7     height=18    xoffset=1     yoffset=0     xadvance=9     page=0  chnl=15
+char id=206  x=194   y=133   width=7     height=18    xoffset=1     yoffset=0     xadvance=9     page=0  chnl=15
+char id=207  x=218   y=133   width=7     height=18    xoffset=1     yoffset=0     xadvance=9     page=0  chnl=15
+char id=208  x=77    y=19    width=10    height=18    xoffset=-1    yoffset=0     xadvance=9     page=0  chnl=15
+char id=209  x=120   y=95    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=210  x=130   y=95    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=211  x=140   y=95    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=212  x=150   y=95    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=213  x=160   y=95    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=214  x=170   y=95    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=215  x=157   y=114   width=8     height=18    xoffset=1     yoffset=0     xadvance=9     page=0  chnl=15
+char id=216  x=22    y=19    width=10    height=18    xoffset=-1    yoffset=0     xadvance=9     page=0  chnl=15
+char id=217  x=180   y=95    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=218  x=190   y=95    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=219  x=200   y=95    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=220  x=210   y=95    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=221  x=220   y=95    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=222  x=230   y=95    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=223  x=160   y=38    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=224  x=240   y=95    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=225  x=0     y=114   width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=226  x=10    y=114   width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=227  x=20    y=114   width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=228  x=30    y=114   width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=229  x=142   y=19    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=230  x=243   y=0     width=10    height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=231  x=76    y=114   width=8     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=232  x=152   y=19    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=233  x=162   y=19    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=234  x=172   y=19    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=235  x=182   y=19    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=236  x=67    y=114   width=8     height=18    xoffset=1     yoffset=0     xadvance=9     page=0  chnl=15
+char id=237  x=112   y=114   width=8     height=18    xoffset=1     yoffset=0     xadvance=9     page=0  chnl=15
+char id=238  x=175   y=114   width=8     height=18    xoffset=1     yoffset=0     xadvance=9     page=0  chnl=15
+char id=239  x=202   y=114   width=8     height=18    xoffset=1     yoffset=0     xadvance=9     page=0  chnl=15
+char id=240  x=192   y=19    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=241  x=18    y=133   width=8     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=242  x=202   y=19    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=243  x=222   y=19    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=244  x=232   y=19    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=245  x=242   y=19    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=246  x=0     y=38    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=247  x=20    y=38    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=248  x=110   y=38    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=249  x=117   y=133   width=8     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=250  x=126   y=133   width=8     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=251  x=144   y=133   width=8     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=252  x=153   y=133   width=8     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=253  x=120   y=38    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=254  x=210   y=38    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=255  x=240   y=38    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=262  x=60    y=95    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=263  x=49    y=114   width=8     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=268  x=0     y=57    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=269  x=85    y=114   width=8     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=273  x=88    y=19    width=10    height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=286  x=20    y=57    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=287  x=130   y=57    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=304  x=56    y=152   width=7     height=18    xoffset=1     yoffset=0     xadvance=9     page=0  chnl=15
+char id=305  x=103   y=114   width=8     height=18    xoffset=1     yoffset=0     xadvance=9     page=0  chnl=15
+char id=321  x=66    y=19    width=10    height=18    xoffset=-1    yoffset=0     xadvance=9     page=0  chnl=15
+char id=322  x=220   y=114   width=8     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=338  x=44    y=19    width=10    height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=339  x=96    y=0     width=11    height=18    xoffset=-1    yoffset=0     xadvance=9     page=0  chnl=15
+char id=350  x=140   y=57    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=351  x=229   y=114   width=8     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=352  x=150   y=57    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=353  x=121   y=114   width=8     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=376  x=160   y=57    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=381  x=180   y=57    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=382  x=148   y=114   width=8     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=402  x=72    y=0     width=11    height=18    xoffset=-1    yoffset=0     xadvance=9     page=0  chnl=15
+char id=710  x=136   y=152   width=5     height=18    xoffset=2     yoffset=0     xadvance=9     page=0  chnl=15
+char id=711  x=142   y=152   width=5     height=18    xoffset=2     yoffset=0     xadvance=9     page=0  chnl=15
+char id=728  x=93    y=152   width=6     height=18    xoffset=2     yoffset=0     xadvance=9     page=0  chnl=15
+char id=729  x=247   y=152   width=3     height=18    xoffset=3     yoffset=0     xadvance=9     page=0  chnl=15
+char id=730  x=160   y=152   width=5     height=18    xoffset=2     yoffset=0     xadvance=9     page=0  chnl=15
+char id=731  x=166   y=152   width=5     height=18    xoffset=2     yoffset=0     xadvance=9     page=0  chnl=15
+char id=732  x=40    y=152   width=7     height=18    xoffset=1     yoffset=0     xadvance=9     page=0  chnl=15
+char id=733  x=79    y=152   width=6     height=18    xoffset=2     yoffset=0     xadvance=9     page=0  chnl=15
+char id=937  x=210   y=57    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=960  x=240   y=57    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=8211 x=12    y=0     width=11    height=18    xoffset=-1    yoffset=0     xadvance=9     page=0  chnl=15
+char id=8212 x=60    y=0     width=11    height=18    xoffset=-1    yoffset=0     xadvance=9     page=0  chnl=15
+char id=8216 x=199   y=152   width=4     height=18    xoffset=3     yoffset=0     xadvance=9     page=0  chnl=15
+char id=8217 x=204   y=152   width=4     height=18    xoffset=3     yoffset=0     xadvance=9     page=0  chnl=15
+char id=8218 x=209   y=152   width=4     height=18    xoffset=2     yoffset=0     xadvance=9     page=0  chnl=15
+char id=8220 x=178   y=133   width=7     height=18    xoffset=1     yoffset=0     xadvance=9     page=0  chnl=15
+char id=8221 x=186   y=133   width=7     height=18    xoffset=1     yoffset=0     xadvance=9     page=0  chnl=15
+char id=8222 x=210   y=133   width=7     height=18    xoffset=1     yoffset=0     xadvance=9     page=0  chnl=15
+char id=8224 x=10    y=76    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=8225 x=20    y=76    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=8226 x=130   y=152   width=5     height=18    xoffset=2     yoffset=0     xadvance=9     page=0  chnl=15
+char id=8230 x=30    y=76    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=8240 x=48    y=0     width=11    height=18    xoffset=-1    yoffset=0     xadvance=9     page=0  chnl=15
+char id=8249 x=172   y=152   width=5     height=18    xoffset=2     yoffset=0     xadvance=9     page=0  chnl=15
+char id=8250 x=112   y=152   width=5     height=18    xoffset=2     yoffset=0     xadvance=9     page=0  chnl=15
+char id=8364 x=40    y=76    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=8482 x=177   y=0     width=10    height=18    xoffset=-1    yoffset=0     xadvance=9     page=0  chnl=15
+char id=8706 x=234   y=133   width=7     height=18    xoffset=1     yoffset=0     xadvance=9     page=0  chnl=15
+char id=8710 x=24    y=0     width=11    height=18    xoffset=-1    yoffset=0     xadvance=9     page=0  chnl=15
+char id=8719 x=50    y=76    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=8721 x=60    y=76    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=8722 x=100   y=76    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=8725 x=108   y=133   width=8     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=8729 x=227   y=152   width=3     height=18    xoffset=3     yoffset=0     xadvance=9     page=0  chnl=15
+char id=8730 x=110   y=76    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=8734 x=120   y=76    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=8747 x=130   y=76    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=8776 x=160   y=76    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=8800 x=170   y=76    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=8804 x=180   y=76    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=8805 x=190   y=76    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
+char id=9674 x=210   y=76    width=9     height=18    xoffset=0     yoffset=0     xadvance=9     page=0  chnl=15
diff --git a/font/vera_sans_mono18_0.png b/font/vera_sans_mono18_0.png
new file mode 100644
index 0000000..865a108
Binary files /dev/null and b/font/vera_sans_mono18_0.png differ
diff --git a/font/vera_sans_mono24.bmfc b/font/vera_sans_mono24.bmfc
new file mode 100644
index 0000000..37c9557
--- /dev/null
+++ b/font/vera_sans_mono24.bmfc
@@ -0,0 +1,57 @@
+# AngelCode Bitmap Font Generator configuration file
+fileVersion=1
+
+# font settings
+fontName=Bitstream Vera Sans Mono
+fontFile=VeraMono.ttf
+charSet=0
+fontSize=24
+aa=1
+scaleH=100
+useSmoothing=1
+isBold=0
+isItalic=0
+useUnicode=1
+disableBoxChars=1
+outputInvalidCharGlyph=0
+dontIncludeKerningPairs=0
+useHinting=1
+renderFromOutline=0
+useClearType=1
+
+# character alignment
+paddingDown=0
+paddingUp=0
+paddingRight=0
+paddingLeft=0
+spacingHoriz=1
+spacingVert=1
+useFixedHeight=1
+forceZero=0
+
+# output file
+outWidth=512
+outHeight=256
+outBitDepth=32
+fontDescFormat=0
+fourChnlPacked=0
+textureFormat=png
+textureCompression=0
+alphaChnl=1
+redChnl=0
+greenChnl=0
+blueChnl=0
+invA=0
+invR=0
+invG=0
+invB=0
+
+# outline
+outlineThickness=0
+
+# selected chars
+chars=32-126,160-255,262-263,268-269,273,286-287,304-305,321-322,338-339,350-353,376,381-382,402,710-711
+chars=728-733,937,960,8211-8212,8216-8218,8220-8222,8224-8226,8230,8240,8249-8250,8364,8482,8706,8710
+chars=8719,8721-8722,8725,8729-8730,8734,8747,8776,8800,8804-8805,9674
+
+# imported icon images
diff --git a/font/vera_sans_mono24.fnt b/font/vera_sans_mono24.fnt
new file mode 100644
index 0000000..d556f3a
--- /dev/null
+++ b/font/vera_sans_mono24.fnt
@@ -0,0 +1,258 @@
+info face="Bitstream Vera Sans Mono" size=24 bold=0 italic=0 charset="" unicode=1 stretchH=100 smooth=1 aa=1 padding=0,0,0,0 spacing=1,1 outline=0
+common lineHeight=24 base=19 scaleW=512 scaleH=256 pages=1 packed=0 alphaChnl=1 redChnl=0 greenChnl=0 blueChnl=0
+page id=0 file="vera_sans_mono24_0.png"
+chars count=254
+char id=32   x=369   y=125   width=3     height=24    xoffset=-1    yoffset=0     xadvance=13    page=0  chnl=15
+char id=33   x=373   y=125   width=3     height=24    xoffset=5     yoffset=0     xadvance=13    page=0  chnl=15
+char id=34   x=226   y=125   width=7     height=24    xoffset=3     yoffset=0     xadvance=13    page=0  chnl=15
+char id=35   x=45    y=0     width=14    height=24    xoffset=-1    yoffset=0     xadvance=13    page=0  chnl=15
+char id=36   x=492   y=75    width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=37   x=238   y=0     width=13    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=38   x=252   y=0     width=13    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=39   x=385   y=125   width=3     height=24    xoffset=5     yoffset=0     xadvance=13    page=0  chnl=15
+char id=40   x=249   y=125   width=6     height=24    xoffset=4     yoffset=0     xadvance=13    page=0  chnl=15
+char id=41   x=242   y=125   width=6     height=24    xoffset=3     yoffset=0     xadvance=13    page=0  chnl=15
+char id=42   x=0     y=100   width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=43   x=322   y=0     width=13    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=44   x=311   y=125   width=5     height=24    xoffset=4     yoffset=0     xadvance=13    page=0  chnl=15
+char id=45   x=504   y=100   width=7     height=24    xoffset=3     yoffset=0     xadvance=13    page=0  chnl=15
+char id=46   x=341   y=125   width=4     height=24    xoffset=4     yoffset=0     xadvance=13    page=0  chnl=15
+char id=47   x=36    y=100   width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=48   x=48    y=100   width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=49   x=44    y=125   width=10    height=24    xoffset=2     yoffset=0     xadvance=13    page=0  chnl=15
+char id=50   x=60    y=100   width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=51   x=72    y=100   width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=52   x=386   y=25    width=12    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=53   x=84    y=100   width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=54   x=120   y=100   width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=55   x=144   y=100   width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=56   x=156   y=100   width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=57   x=180   y=100   width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=58   x=346   y=125   width=4     height=24    xoffset=4     yoffset=0     xadvance=13    page=0  chnl=15
+char id=59   x=317   y=125   width=5     height=24    xoffset=4     yoffset=0     xadvance=13    page=0  chnl=15
+char id=60   x=28    y=25    width=13    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=61   x=42    y=25    width=13    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=62   x=56    y=25    width=13    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=63   x=33    y=125   width=10    height=24    xoffset=2     yoffset=0     xadvance=13    page=0  chnl=15
+char id=64   x=84    y=25    width=13    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=65   x=98    y=25    width=13    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=66   x=464   y=25    width=12    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=67   x=477   y=25    width=12    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=68   x=490   y=25    width=12    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=69   x=0     y=50    width=12    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=70   x=482   y=100   width=10    height=24    xoffset=2     yoffset=0     xadvance=13    page=0  chnl=15
+char id=71   x=13    y=50    width=12    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=72   x=364   y=50    width=12    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=73   x=350   y=100   width=10    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=74   x=192   y=100   width=11    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=75   x=224   y=25    width=13    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=76   x=204   y=100   width=11    height=24    xoffset=2     yoffset=0     xadvance=13    page=0  chnl=15
+char id=77   x=299   y=50    width=12    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=78   x=247   y=50    width=12    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=79   x=195   y=50    width=12    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=80   x=39    y=50    width=12    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=81   x=351   y=50    width=12    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=82   x=238   y=25    width=13    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=83   x=416   y=50    width=12    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=84   x=252   y=25    width=13    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=85   x=442   y=50    width=12    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=86   x=377   y=50    width=12    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=87   x=180   y=0     width=14    height=24    xoffset=-1    yoffset=0     xadvance=13    page=0  chnl=15
+char id=88   x=280   y=25    width=13    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=89   x=294   y=25    width=13    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=90   x=156   y=50    width=12    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=91   x=284   y=125   width=6     height=24    xoffset=4     yoffset=0     xadvance=13    page=0  chnl=15
+char id=92   x=216   y=100   width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=93   x=256   y=125   width=6     height=24    xoffset=3     yoffset=0     xadvance=13    page=0  chnl=15
+char id=94   x=0     y=25    width=13    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=95   x=117   y=50    width=12    height=24    xoffset=-1    yoffset=0     xadvance=13    page=0  chnl=15
+char id=96   x=291   y=125   width=6     height=24    xoffset=2     yoffset=0     xadvance=13    page=0  chnl=15
+char id=97   x=468   y=50    width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=98   x=480   y=50    width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=99   x=339   y=100   width=10    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=100  x=492   y=50    width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=101  x=0     y=75    width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=102  x=12    y=75    width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=103  x=24    y=75    width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=104  x=405   y=100   width=10    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=105  x=36    y=75    width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=106  x=175   y=125   width=8     height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=107  x=48    y=75    width=11    height=24    xoffset=2     yoffset=0     xadvance=13    page=0  chnl=15
+char id=108  x=60    y=75    width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=109  x=65    y=50    width=12    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=110  x=284   y=100   width=10    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=111  x=72    y=75    width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=112  x=84    y=75    width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=113  x=96    y=75    width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=114  x=77    y=125   width=10    height=24    xoffset=3     yoffset=0     xadvance=13    page=0  chnl=15
+char id=115  x=55    y=125   width=10    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=116  x=108   y=75    width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=117  x=66    y=125   width=10    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=118  x=182   y=50    width=12    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=119  x=120   y=0     width=14    height=24    xoffset=-1    yoffset=0     xadvance=13    page=0  chnl=15
+char id=120  x=208   y=50    width=12    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=121  x=221   y=50    width=12    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=122  x=493   y=100   width=10    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=123  x=22    y=125   width=10    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=124  x=365   y=125   width=3     height=24    xoffset=5     yoffset=0     xadvance=13    page=0  chnl=15
+char id=125  x=328   y=100   width=10    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=126  x=224   y=0     width=13    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=160  x=377   y=125   width=3     height=24    xoffset=-1    yoffset=0     xadvance=13    page=0  chnl=15
+char id=161  x=361   y=125   width=3     height=24    xoffset=5     yoffset=0     xadvance=13    page=0  chnl=15
+char id=162  x=120   y=75    width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=163  x=132   y=75    width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=164  x=144   y=75    width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=165  x=448   y=0     width=13    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=166  x=381   y=125   width=3     height=24    xoffset=5     yoffset=0     xadvance=13    page=0  chnl=15
+char id=167  x=295   y=100   width=10    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=168  x=504   y=0     width=7     height=24    xoffset=3     yoffset=0     xadvance=13    page=0  chnl=15
+char id=169  x=60    y=0     width=14    height=24    xoffset=-1    yoffset=0     xadvance=13    page=0  chnl=15
+char id=170  x=88    y=125   width=9     height=24    xoffset=2     yoffset=0     xadvance=13    page=0  chnl=15
+char id=171  x=156   y=75    width=11    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=172  x=266   y=0     width=13    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=173  x=234   y=125   width=7     height=24    xoffset=3     yoffset=0     xadvance=13    page=0  chnl=15
+char id=174  x=165   y=0     width=14    height=24    xoffset=-1    yoffset=0     xadvance=13    page=0  chnl=15
+char id=175  x=193   y=125   width=8     height=24    xoffset=2     yoffset=0     xadvance=13    page=0  chnl=15
+char id=176  x=503   y=25    width=8     height=24    xoffset=2     yoffset=0     xadvance=13    page=0  chnl=15
+char id=177  x=308   y=0     width=13    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=178  x=504   y=50    width=7     height=24    xoffset=3     yoffset=0     xadvance=13    page=0  chnl=15
+char id=179  x=504   y=75    width=7     height=24    xoffset=3     yoffset=0     xadvance=13    page=0  chnl=15
+char id=180  x=298   y=125   width=6     height=24    xoffset=4     yoffset=0     xadvance=13    page=0  chnl=15
+char id=181  x=168   y=75    width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=182  x=91    y=50    width=12    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=183  x=351   y=125   width=4     height=24    xoffset=4     yoffset=0     xadvance=13    page=0  chnl=15
+char id=184  x=277   y=125   width=6     height=24    xoffset=3     yoffset=0     xadvance=13    page=0  chnl=15
+char id=185  x=218   y=125   width=7     height=24    xoffset=3     yoffset=0     xadvance=13    page=0  chnl=15
+char id=186  x=98    y=125   width=9     height=24    xoffset=2     yoffset=0     xadvance=13    page=0  chnl=15
+char id=187  x=180   y=75    width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=188  x=373   y=25    width=12    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=189  x=325   y=50    width=12    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=190  x=52    y=50    width=12    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=191  x=0     y=125   width=10    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=192  x=364   y=0     width=13    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=193  x=406   y=0     width=13    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=194  x=378   y=0     width=13    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=195  x=462   y=0     width=13    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=196  x=434   y=0     width=13    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=197  x=420   y=0     width=13    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=198  x=135   y=0     width=14    height=24    xoffset=-1    yoffset=0     xadvance=13    page=0  chnl=15
+char id=199  x=455   y=50    width=12    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=200  x=429   y=50    width=12    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=201  x=403   y=50    width=12    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=202  x=390   y=50    width=12    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=203  x=312   y=50    width=12    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=204  x=438   y=100   width=10    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=205  x=460   y=100   width=10    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=206  x=240   y=100   width=10    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=207  x=251   y=100   width=10    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=208  x=210   y=25    width=13    height=24    xoffset=-1    yoffset=0     xadvance=13    page=0  chnl=15
+char id=209  x=425   y=25    width=12    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=210  x=412   y=25    width=12    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=211  x=399   y=25    width=12    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=212  x=334   y=25    width=12    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=213  x=273   y=50    width=12    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=214  x=260   y=50    width=12    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=215  x=204   y=75    width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=216  x=105   y=0     width=14    height=24    xoffset=-1    yoffset=0     xadvance=13    page=0  chnl=15
+char id=217  x=234   y=50    width=12    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=218  x=169   y=50    width=12    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=219  x=143   y=50    width=12    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=220  x=130   y=50    width=12    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=221  x=392   y=0     width=13    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=222  x=26    y=50    width=12    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=223  x=192   y=75    width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=224  x=216   y=75    width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=225  x=228   y=75    width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=226  x=240   y=75    width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=227  x=252   y=75    width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=228  x=264   y=75    width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=229  x=276   y=75    width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=230  x=336   y=0     width=13    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=231  x=427   y=100   width=10    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=232  x=288   y=75    width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=233  x=300   y=75    width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=234  x=312   y=75    width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=235  x=324   y=75    width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=236  x=336   y=75    width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=237  x=348   y=75    width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=238  x=360   y=75    width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=239  x=372   y=75    width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=240  x=384   y=75    width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=241  x=273   y=100   width=10    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=242  x=396   y=75    width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=243  x=408   y=75    width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=244  x=420   y=75    width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=245  x=432   y=75    width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=246  x=444   y=75    width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=247  x=196   y=25    width=13    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=248  x=294   y=0     width=13    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=249  x=361   y=100   width=10    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=250  x=372   y=100   width=10    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=251  x=383   y=100   width=10    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=252  x=394   y=100   width=10    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=253  x=104   y=50    width=12    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=254  x=456   y=75    width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=255  x=78    y=50    width=12    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=262  x=451   y=25    width=12    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=263  x=449   y=100   width=10    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=268  x=286   y=50    width=12    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=269  x=471   y=100   width=10    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=273  x=168   y=25    width=13    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=286  x=438   y=25    width=12    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=287  x=468   y=75    width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=304  x=11    y=125   width=10    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=305  x=480   y=75    width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=321  x=195   y=0     width=14    height=24    xoffset=-1    yoffset=0     xadvance=13    page=0  chnl=15
+char id=322  x=360   y=25    width=12    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=338  x=350   y=0     width=13    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=339  x=0     y=0     width=14    height=24    xoffset=-1    yoffset=0     xadvance=13    page=0  chnl=15
+char id=350  x=347   y=25    width=12    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=351  x=262   y=100   width=10    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=352  x=321   y=25    width=12    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=353  x=306   y=100   width=10    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=376  x=280   y=0     width=13    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=381  x=308   y=25    width=12    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=382  x=416   y=100   width=10    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=402  x=30    y=0     width=14    height=24    xoffset=-1    yoffset=0     xadvance=13    page=0  chnl=15
+char id=710  x=184   y=125   width=8     height=24    xoffset=2     yoffset=0     xadvance=13    page=0  chnl=15
+char id=711  x=148   y=125   width=8     height=24    xoffset=2     yoffset=0     xadvance=13    page=0  chnl=15
+char id=728  x=157   y=125   width=8     height=24    xoffset=3     yoffset=0     xadvance=13    page=0  chnl=15
+char id=729  x=389   y=125   width=3     height=24    xoffset=5     yoffset=0     xadvance=13    page=0  chnl=15
+char id=730  x=210   y=125   width=7     height=24    xoffset=3     yoffset=0     xadvance=13    page=0  chnl=15
+char id=731  x=305   y=125   width=5     height=24    xoffset=4     yoffset=0     xadvance=13    page=0  chnl=15
+char id=732  x=138   y=125   width=9     height=24    xoffset=2     yoffset=0     xadvance=13    page=0  chnl=15
+char id=733  x=166   y=125   width=8     height=24    xoffset=3     yoffset=0     xadvance=13    page=0  chnl=15
+char id=937  x=210   y=0     width=13    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=960  x=266   y=25    width=13    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=8211 x=15    y=0     width=14    height=24    xoffset=-1    yoffset=0     xadvance=13    page=0  chnl=15
+char id=8212 x=90    y=0     width=14    height=24    xoffset=-1    yoffset=0     xadvance=13    page=0  chnl=15
+char id=8216 x=329   y=125   width=5     height=24    xoffset=4     yoffset=0     xadvance=13    page=0  chnl=15
+char id=8217 x=323   y=125   width=5     height=24    xoffset=4     yoffset=0     xadvance=13    page=0  chnl=15
+char id=8218 x=335   y=125   width=5     height=24    xoffset=4     yoffset=0     xadvance=13    page=0  chnl=15
+char id=8220 x=108   y=125   width=9     height=24    xoffset=2     yoffset=0     xadvance=13    page=0  chnl=15
+char id=8221 x=118   y=125   width=9     height=24    xoffset=2     yoffset=0     xadvance=13    page=0  chnl=15
+char id=8222 x=128   y=125   width=9     height=24    xoffset=2     yoffset=0     xadvance=13    page=0  chnl=15
+char id=8224 x=12    y=100   width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=8225 x=24    y=100   width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=8226 x=202   y=125   width=7     height=24    xoffset=3     yoffset=0     xadvance=13    page=0  chnl=15
+char id=8230 x=476   y=0     width=13    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=8240 x=75    y=0     width=14    height=24    xoffset=-1    yoffset=0     xadvance=13    page=0  chnl=15
+char id=8249 x=263   y=125   width=6     height=24    xoffset=3     yoffset=0     xadvance=13    page=0  chnl=15
+char id=8250 x=270   y=125   width=6     height=24    xoffset=4     yoffset=0     xadvance=13    page=0  chnl=15
+char id=8364 x=338   y=50    width=12    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=8482 x=490   y=0     width=13    height=24    xoffset=-1    yoffset=0     xadvance=13    page=0  chnl=15
+char id=8706 x=317   y=100   width=10    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=8710 x=150   y=0     width=14    height=24    xoffset=-1    yoffset=0     xadvance=13    page=0  chnl=15
+char id=8719 x=96    y=100   width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=8721 x=108   y=100   width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=8722 x=14    y=25    width=13    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=8725 x=132   y=100   width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=8729 x=356   y=125   width=4     height=24    xoffset=4     yoffset=0     xadvance=13    page=0  chnl=15
+char id=8730 x=70    y=25    width=13    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=8734 x=112   y=25    width=13    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=8747 x=168   y=100   width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
+char id=8776 x=126   y=25    width=13    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=8800 x=140   y=25    width=13    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=8804 x=154   y=25    width=13    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=8805 x=182   y=25    width=13    height=24    xoffset=0     yoffset=0     xadvance=13    page=0  chnl=15
+char id=9674 x=228   y=100   width=11    height=24    xoffset=1     yoffset=0     xadvance=13    page=0  chnl=15
diff --git a/font/vera_sans_mono24_0.png b/font/vera_sans_mono24_0.png
new file mode 100644
index 0000000..ec4778f
Binary files /dev/null and b/font/vera_sans_mono24_0.png differ
diff --git a/font/vera_sans_mono27.bmfc b/font/vera_sans_mono27.bmfc
new file mode 100644
index 0000000..c99c6a3
--- /dev/null
+++ b/font/vera_sans_mono27.bmfc
@@ -0,0 +1,57 @@
+# AngelCode Bitmap Font Generator configuration file
+fileVersion=1
+
+# font settings
+fontName=Bitstream Vera Sans Mono
+fontFile=VeraMoBd.ttf
+charSet=0
+fontSize=27
+aa=1
+scaleH=100
+useSmoothing=1
+isBold=0
+isItalic=0
+useUnicode=1
+disableBoxChars=1
+outputInvalidCharGlyph=0
+dontIncludeKerningPairs=0
+useHinting=1
+renderFromOutline=0
+useClearType=1
+
+# character alignment
+paddingDown=0
+paddingUp=0
+paddingRight=0
+paddingLeft=0
+spacingHoriz=1
+spacingVert=1
+useFixedHeight=1
+forceZero=0
+
+# output file
+outWidth=512
+outHeight=256
+outBitDepth=32
+fontDescFormat=0
+fourChnlPacked=0
+textureFormat=png
+textureCompression=0
+alphaChnl=1
+redChnl=0
+greenChnl=0
+blueChnl=0
+invA=0
+invR=0
+invG=0
+invB=0
+
+# outline
+outlineThickness=0
+
+# selected chars
+chars=32-126,160-255,262-263,268-269,273,286-287,304-305,321-322,338-339,350-353,376,381-382,402,710-711
+chars=728-733,937,960,8211-8212,8216-8218,8220-8222,8224-8226,8230,8240,8249-8250,8364,8482,8706,8710
+chars=8719,8721-8722,8725,8729-8730,8734,8747,8776,8800,8804-8805,9674
+
+# imported icon images
diff --git a/font/vera_sans_mono27.fnt b/font/vera_sans_mono27.fnt
new file mode 100644
index 0000000..d1d4b43
--- /dev/null
+++ b/font/vera_sans_mono27.fnt
@@ -0,0 +1,258 @@
+info face="Bitstream Vera Sans Mono" size=27 bold=0 italic=0 charset="" unicode=1 stretchH=100 smooth=1 aa=1 padding=0,0,0,0 spacing=1,1 outline=0
+common lineHeight=27 base=22 scaleW=512 scaleH=256 pages=1 packed=0 alphaChnl=1 redChnl=0 greenChnl=0 blueChnl=0
+page id=0 file="vera_sans_mono27_0.png"
+chars count=254
+char id=32   x=245   y=168   width=3     height=27    xoffset=-1    yoffset=0     xadvance=14    page=0  chnl=15
+char id=33   x=224   y=168   width=5     height=27    xoffset=5     yoffset=0     xadvance=14    page=0  chnl=15
+char id=34   x=468   y=140   width=10    height=27    xoffset=2     yoffset=0     xadvance=14    page=0  chnl=15
+char id=35   x=17    y=0     width=16    height=27    xoffset=-1    yoffset=0     xadvance=14    page=0  chnl=15
+char id=36   x=52    y=112   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=37   x=471   y=0     width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=38   x=218   y=0     width=15    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=39   x=507   y=112   width=4     height=27    xoffset=5     yoffset=0     xadvance=14    page=0  chnl=15
+char id=40   x=91    y=168   width=7     height=27    xoffset=4     yoffset=0     xadvance=14    page=0  chnl=15
+char id=41   x=75    y=168   width=7     height=27    xoffset=3     yoffset=0     xadvance=14    page=0  chnl=15
+char id=42   x=65    y=112   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=43   x=45    y=28    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=44   x=176   y=168   width=6     height=27    xoffset=4     yoffset=0     xadvance=14    page=0  chnl=15
+char id=45   x=48    y=168   width=8     height=27    xoffset=3     yoffset=0     xadvance=14    page=0  chnl=15
+char id=46   x=162   y=168   width=6     height=27    xoffset=4     yoffset=0     xadvance=14    page=0  chnl=15
+char id=47   x=78    y=112   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=48   x=91    y=112   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=49   x=416   y=56    width=13    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=50   x=104   y=112   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=51   x=117   y=112   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=52   x=130   y=112   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=53   x=143   y=112   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=54   x=156   y=112   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=55   x=208   y=112   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=56   x=221   y=112   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=57   x=234   y=112   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=58   x=155   y=168   width=6     height=27    xoffset=4     yoffset=0     xadvance=14    page=0  chnl=15
+char id=59   x=197   y=168   width=6     height=27    xoffset=4     yoffset=0     xadvance=14    page=0  chnl=15
+char id=60   x=336   y=84    width=13    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=61   x=225   y=28    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=62   x=308   y=84    width=13    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=63   x=378   y=140   width=11    height=27    xoffset=2     yoffset=0     xadvance=14    page=0  chnl=15
+char id=64   x=170   y=0     width=15    height=27    xoffset=-1    yoffset=0     xadvance=14    page=0  chnl=15
+char id=65   x=255   y=28    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=66   x=266   y=84    width=13    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=67   x=260   y=112   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=68   x=196   y=84    width=13    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=69   x=273   y=112   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=70   x=182   y=84    width=13    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=71   x=140   y=84    width=13    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=72   x=286   y=112   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=73   x=299   y=112   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=74   x=84    y=84    width=13    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=75   x=70    y=84    width=13    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=76   x=312   y=112   width=12    height=27    xoffset=2     yoffset=0     xadvance=14    page=0  chnl=15
+char id=77   x=330   y=28    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=78   x=351   y=112   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=79   x=360   y=28    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=80   x=364   y=112   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=81   x=375   y=28    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=82   x=472   y=56    width=13    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=83   x=377   y=112   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=84   x=405   y=28    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=85   x=14    y=84    width=13    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=86   x=420   y=28    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=87   x=85    y=0     width=16    height=27    xoffset=-1    yoffset=0     xadvance=14    page=0  chnl=15
+char id=88   x=435   y=28    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=89   x=330   y=0     width=15    height=27    xoffset=-1    yoffset=0     xadvance=14    page=0  chnl=15
+char id=90   x=168   y=84    width=13    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=91   x=83    y=168   width=7     height=27    xoffset=4     yoffset=0     xadvance=14    page=0  chnl=15
+char id=92   x=390   y=112   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=93   x=123   y=168   width=7     height=27    xoffset=3     yoffset=0     xadvance=14    page=0  chnl=15
+char id=94   x=120   y=28    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=95   x=34    y=0     width=16    height=27    xoffset=-1    yoffset=0     xadvance=14    page=0  chnl=15
+char id=96   x=139   y=168   width=7     height=27    xoffset=2     yoffset=0     xadvance=14    page=0  chnl=15
+char id=97   x=0     y=56    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=98   x=390   y=28    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=99   x=354   y=140   width=11    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=100  x=45    y=56    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=101  x=60    y=56    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=102  x=403   y=112   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=103  x=75    y=56    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=104  x=294   y=140   width=11    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=105  x=458   y=56    width=13    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=106  x=501   y=140   width=9     height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=107  x=364   y=84    width=13    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=108  x=406   y=84    width=13    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=109  x=120   y=56    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=110  x=318   y=140   width=11    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=111  x=150   y=56    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=112  x=165   y=56    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=113  x=180   y=56    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=114  x=416   y=112   width=12    height=27    xoffset=2     yoffset=0     xadvance=14    page=0  chnl=15
+char id=115  x=306   y=140   width=11    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=116  x=0     y=84    width=13    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=117  x=500   y=56    width=11    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=118  x=429   y=112   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=119  x=0     y=0     width=16    height=27    xoffset=-1    yoffset=0     xadvance=14    page=0  chnl=15
+char id=120  x=238   y=84    width=13    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=121  x=252   y=84    width=13    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=122  x=322   y=84    width=13    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=123  x=0     y=140   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=124  x=235   y=168   width=4     height=27    xoffset=5     yoffset=0     xadvance=14    page=0  chnl=15
+char id=125  x=13    y=140   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=126  x=430   y=56    width=13    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=160  x=249   y=168   width=3     height=27    xoffset=-1    yoffset=0     xadvance=14    page=0  chnl=15
+char id=161  x=218   y=168   width=5     height=27    xoffset=5     yoffset=0     xadvance=14    page=0  chnl=15
+char id=162  x=26    y=140   width=12    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=163  x=39    y=140   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=164  x=65    y=140   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=165  x=346   y=0     width=15    height=27    xoffset=-1    yoffset=0     xadvance=14    page=0  chnl=15
+char id=166  x=240   y=168   width=4     height=27    xoffset=5     yoffset=0     xadvance=14    page=0  chnl=15
+char id=167  x=104   y=140   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=168  x=57    y=168   width=8     height=27    xoffset=3     yoffset=0     xadvance=14    page=0  chnl=15
+char id=169  x=119   y=0     width=16    height=27    xoffset=-1    yoffset=0     xadvance=14    page=0  chnl=15
+char id=170  x=501   y=0     width=10    height=27    xoffset=2     yoffset=0     xadvance=14    page=0  chnl=15
+char id=171  x=234   y=140   width=11    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=172  x=180   y=28    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=173  x=66    y=168   width=8     height=27    xoffset=3     yoffset=0     xadvance=14    page=0  chnl=15
+char id=174  x=102   y=0     width=16    height=27    xoffset=-1    yoffset=0     xadvance=14    page=0  chnl=15
+char id=175  x=39    y=168   width=8     height=27    xoffset=3     yoffset=0     xadvance=14    page=0  chnl=15
+char id=176  x=20    y=168   width=9     height=27    xoffset=2     yoffset=0     xadvance=14    page=0  chnl=15
+char id=177  x=315   y=28    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=178  x=10    y=168   width=9     height=27    xoffset=2     yoffset=0     xadvance=14    page=0  chnl=15
+char id=179  x=402   y=140   width=10    height=27    xoffset=2     yoffset=0     xadvance=14    page=0  chnl=15
+char id=180  x=131   y=168   width=7     height=27    xoffset=5     yoffset=0     xadvance=14    page=0  chnl=15
+char id=181  x=486   y=56    width=13    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=182  x=112   y=84    width=13    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=183  x=183   y=168   width=6     height=27    xoffset=4     yoffset=0     xadvance=14    page=0  chnl=15
+char id=184  x=147   y=168   width=7     height=27    xoffset=3     yoffset=0     xadvance=14    page=0  chnl=15
+char id=185  x=502   y=84    width=9     height=27    xoffset=3     yoffset=0     xadvance=14    page=0  chnl=15
+char id=186  x=424   y=140   width=10    height=27    xoffset=2     yoffset=0     xadvance=14    page=0  chnl=15
+char id=187  x=130   y=140   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=188  x=105   y=28    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=189  x=240   y=28    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=190  x=150   y=28    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=191  x=435   y=140   width=10    height=27    xoffset=2     yoffset=0     xadvance=14    page=0  chnl=15
+char id=192  x=456   y=0     width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=193  x=30    y=28    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=194  x=225   y=56    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=195  x=210   y=56    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=196  x=195   y=56    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=197  x=15    y=56    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=198  x=378   y=0     width=15    height=27    xoffset=-1    yoffset=0     xadvance=14    page=0  chnl=15
+char id=199  x=156   y=140   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=200  x=169   y=140   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=201  x=182   y=140   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=202  x=195   y=140   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=203  x=208   y=140   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=204  x=476   y=84    width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=205  x=489   y=84    width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=206  x=0     y=112   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=207  x=13    y=112   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=208  x=234   y=0     width=15    height=27    xoffset=-1    yoffset=0     xadvance=14    page=0  chnl=15
+char id=209  x=26    y=112   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=210  x=345   y=28    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=211  x=300   y=28    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=212  x=285   y=28    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=213  x=270   y=28    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=214  x=165   y=28    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=215  x=350   y=84    width=13    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=216  x=394   y=0     width=15    height=27    xoffset=-1    yoffset=0     xadvance=14    page=0  chnl=15
+char id=217  x=392   y=84    width=13    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=218  x=444   y=56    width=13    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=219  x=378   y=84    width=13    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=220  x=42    y=84    width=13    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=221  x=266   y=0     width=15    height=27    xoffset=-1    yoffset=0     xadvance=14    page=0  chnl=15
+char id=222  x=39    y=112   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=223  x=280   y=84    width=13    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=224  x=135   y=56    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=225  x=105   y=56    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=226  x=90    y=56    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=227  x=30    y=56    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=228  x=495   y=28    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=229  x=480   y=28    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=230  x=465   y=28    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=231  x=246   y=140   width=11    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=232  x=450   y=28    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=233  x=210   y=28    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=234  x=195   y=28    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=235  x=90    y=28    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=236  x=402   y=56    width=13    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=237  x=388   y=56    width=13    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=238  x=374   y=56    width=13    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=239  x=360   y=56    width=13    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=240  x=486   y=0     width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=241  x=390   y=140   width=11    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=242  x=441   y=0     width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=243  x=426   y=0     width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=244  x=240   y=56    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=245  x=255   y=56    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=246  x=270   y=56    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=247  x=285   y=56    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=248  x=345   y=56    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=249  x=366   y=140   width=11    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=250  x=282   y=140   width=11    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=251  x=270   y=140   width=11    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=252  x=258   y=140   width=11    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=253  x=28    y=84    width=13    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=254  x=300   y=56    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=255  x=56    y=84    width=13    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=262  x=143   y=140   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=263  x=169   y=112   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=268  x=182   y=112   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=269  x=195   y=112   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=273  x=410   y=0     width=15    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=286  x=98    y=84    width=13    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=287  x=315   y=56    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=304  x=247   y=112   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=305  x=126   y=84    width=13    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=321  x=314   y=0     width=15    height=27    xoffset=-1    yoffset=0     xadvance=14    page=0  chnl=15
+char id=322  x=154   y=84    width=13    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=338  x=330   y=56    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=339  x=298   y=0     width=15    height=27    xoffset=-1    yoffset=0     xadvance=14    page=0  chnl=15
+char id=350  x=325   y=112   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=351  x=330   y=140   width=11    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=352  x=338   y=112   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=353  x=342   y=140   width=11    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=376  x=282   y=0     width=15    height=27    xoffset=-1    yoffset=0     xadvance=14    page=0  chnl=15
+char id=381  x=210   y=84    width=13    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=382  x=224   y=84    width=13    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=402  x=186   y=0     width=15    height=27    xoffset=-1    yoffset=0     xadvance=14    page=0  chnl=15
+char id=710  x=446   y=140   width=10    height=27    xoffset=2     yoffset=0     xadvance=14    page=0  chnl=15
+char id=711  x=457   y=140   width=10    height=27    xoffset=2     yoffset=0     xadvance=14    page=0  chnl=15
+char id=728  x=0     y=168   width=9     height=27    xoffset=3     yoffset=0     xadvance=14    page=0  chnl=15
+char id=729  x=230   y=168   width=4     height=27    xoffset=5     yoffset=0     xadvance=14    page=0  chnl=15
+char id=730  x=30    y=168   width=8     height=27    xoffset=3     yoffset=0     xadvance=14    page=0  chnl=15
+char id=731  x=115   y=168   width=7     height=27    xoffset=4     yoffset=0     xadvance=14    page=0  chnl=15
+char id=732  x=479   y=140   width=10    height=27    xoffset=2     yoffset=0     xadvance=14    page=0  chnl=15
+char id=733  x=490   y=140   width=10    height=27    xoffset=3     yoffset=0     xadvance=14    page=0  chnl=15
+char id=937  x=0     y=28    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=960  x=250   y=0     width=15    height=27    xoffset=-1    yoffset=0     xadvance=14    page=0  chnl=15
+char id=8211 x=153   y=0     width=16    height=27    xoffset=-1    yoffset=0     xadvance=14    page=0  chnl=15
+char id=8212 x=68    y=0     width=16    height=27    xoffset=-1    yoffset=0     xadvance=14    page=0  chnl=15
+char id=8216 x=169   y=168   width=6     height=27    xoffset=4     yoffset=0     xadvance=14    page=0  chnl=15
+char id=8217 x=204   y=168   width=6     height=27    xoffset=4     yoffset=0     xadvance=14    page=0  chnl=15
+char id=8218 x=211   y=168   width=6     height=27    xoffset=4     yoffset=0     xadvance=14    page=0  chnl=15
+char id=8220 x=442   y=112   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=8221 x=455   y=112   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=8222 x=468   y=112   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=8224 x=481   y=112   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=8225 x=494   y=112   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=8226 x=413   y=140   width=10    height=27    xoffset=2     yoffset=0     xadvance=14    page=0  chnl=15
+char id=8230 x=294   y=84    width=13    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=8240 x=51    y=0     width=16    height=27    xoffset=-1    yoffset=0     xadvance=14    page=0  chnl=15
+char id=8249 x=99    y=168   width=7     height=27    xoffset=3     yoffset=0     xadvance=14    page=0  chnl=15
+char id=8250 x=107   y=168   width=7     height=27    xoffset=4     yoffset=0     xadvance=14    page=0  chnl=15
+char id=8364 x=15    y=28    width=14    height=27    xoffset=-1    yoffset=0     xadvance=14    page=0  chnl=15
+char id=8482 x=202   y=0     width=15    height=27    xoffset=-1    yoffset=0     xadvance=14    page=0  chnl=15
+char id=8706 x=52    y=140   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=8710 x=136   y=0     width=16    height=27    xoffset=-1    yoffset=0     xadvance=14    page=0  chnl=15
+char id=8719 x=78    y=140   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=8721 x=91    y=140   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=8722 x=60    y=28    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=8725 x=117   y=140   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
+char id=8729 x=190   y=168   width=6     height=27    xoffset=4     yoffset=0     xadvance=14    page=0  chnl=15
+char id=8730 x=75    y=28    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=8734 x=362   y=0     width=15    height=27    xoffset=-1    yoffset=0     xadvance=14    page=0  chnl=15
+char id=8747 x=135   y=28    width=14    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=8776 x=420   y=84    width=13    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=8800 x=434   y=84    width=13    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=8804 x=448   y=84    width=13    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=8805 x=462   y=84    width=13    height=27    xoffset=0     yoffset=0     xadvance=14    page=0  chnl=15
+char id=9674 x=221   y=140   width=12    height=27    xoffset=1     yoffset=0     xadvance=14    page=0  chnl=15
diff --git a/font/vera_sans_mono27_0.png b/font/vera_sans_mono27_0.png
new file mode 100644
index 0000000..9287af1
Binary files /dev/null and b/font/vera_sans_mono27_0.png differ
diff --git a/font/vera_sans_mono32.bmfc b/font/vera_sans_mono32.bmfc
new file mode 100644
index 0000000..1463895
--- /dev/null
+++ b/font/vera_sans_mono32.bmfc
@@ -0,0 +1,57 @@
+# AngelCode Bitmap Font Generator configuration file
+fileVersion=1
+
+# font settings
+fontName=Bitstream Vera Sans Mono
+fontFile=VeraMono.ttf
+charSet=0
+fontSize=32
+aa=1
+scaleH=100
+useSmoothing=1
+isBold=0
+isItalic=0
+useUnicode=1
+disableBoxChars=1
+outputInvalidCharGlyph=0
+dontIncludeKerningPairs=0
+useHinting=1
+renderFromOutline=0
+useClearType=0
+
+# character alignment
+paddingDown=0
+paddingUp=0
+paddingRight=0
+paddingLeft=0
+spacingHoriz=1
+spacingVert=1
+useFixedHeight=1
+forceZero=0
+
+# output file
+outWidth=512
+outHeight=256
+outBitDepth=32
+fontDescFormat=0
+fourChnlPacked=0
+textureFormat=png
+textureCompression=0
+alphaChnl=1
+redChnl=0
+greenChnl=0
+blueChnl=0
+invA=0
+invR=0
+invG=0
+invB=0
+
+# outline
+outlineThickness=0
+
+# selected chars
+chars=32-126,160-255,262-263,268-269,273,286-287,304-305,321-322,338-339,350-353,376,381-382,402,710-711
+chars=728-733,937,960,8211-8212,8216-8218,8220-8222,8224-8226,8230,8240,8249-8250,8364,8482,8706,8710
+chars=8719,8721-8722,8725,8729-8730,8734,8747,8776,8800,8804-8805,9674
+
+# imported icon images
diff --git a/font/vera_sans_mono32.fnt b/font/vera_sans_mono32.fnt
new file mode 100644
index 0000000..906863f
--- /dev/null
+++ b/font/vera_sans_mono32.fnt
@@ -0,0 +1,258 @@
+info face="Bitstream Vera Sans Mono" size=32 bold=0 italic=0 charset="" unicode=1 stretchH=100 smooth=1 aa=1 padding=0,0,0,0 spacing=1,1 outline=0
+common lineHeight=32 base=26 scaleW=512 scaleH=256 pages=1 packed=0 alphaChnl=1 redChnl=0 greenChnl=0 blueChnl=0
+page id=0 file="vera_sans_mono32_0.png"
+chars count=254
+char id=32   x=267   y=198   width=3     height=32    xoffset=-1    yoffset=0     xadvance=17    page=0  chnl=15
+char id=33   x=247   y=198   width=3     height=32    xoffset=7     yoffset=0     xadvance=17    page=0  chnl=15
+char id=34   x=504   y=99    width=7     height=32    xoffset=5     yoffset=0     xadvance=17    page=0  chnl=15
+char id=35   x=54    y=0     width=17    height=32    xoffset=0     yoffset=0     xadvance=17    page=0  chnl=15
+char id=36   x=0     y=99    width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=37   x=90    y=0     width=17    height=32    xoffset=0     yoffset=0     xadvance=17    page=0  chnl=15
+char id=38   x=304   y=0     width=16    height=32    xoffset=1     yoffset=0     xadvance=17    page=0  chnl=15
+char id=39   x=275   y=198   width=2     height=32    xoffset=7     yoffset=0     xadvance=17    page=0  chnl=15
+char id=40   x=177   y=198   width=6     height=32    xoffset=6     yoffset=0     xadvance=17    page=0  chnl=15
+char id=41   x=219   y=198   width=6     height=32    xoffset=5     yoffset=0     xadvance=17    page=0  chnl=15
+char id=42   x=130   y=165   width=12    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=43   x=360   y=66    width=14    height=32    xoffset=1     yoffset=0     xadvance=17    page=0  chnl=15
+char id=44   x=242   y=198   width=4     height=32    xoffset=6     yoffset=0     xadvance=17    page=0  chnl=15
+char id=45   x=161   y=198   width=7     height=32    xoffset=5     yoffset=0     xadvance=17    page=0  chnl=15
+char id=46   x=271   y=198   width=3     height=32    xoffset=7     yoffset=0     xadvance=17    page=0  chnl=15
+char id=47   x=0     y=66    width=14    height=32    xoffset=1     yoffset=0     xadvance=17    page=0  chnl=15
+char id=48   x=42    y=99    width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=49   x=143   y=165   width=12    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=50   x=15    y=66    width=14    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=51   x=56    y=99    width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=52   x=90    y=66    width=14    height=32    xoffset=1     yoffset=0     xadvance=17    page=0  chnl=15
+char id=53   x=70    y=99    width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=54   x=84    y=99    width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=55   x=98    y=99    width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=56   x=112   y=99    width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=57   x=126   y=99    width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=58   x=259   y=198   width=3     height=32    xoffset=7     yoffset=0     xadvance=17    page=0  chnl=15
+char id=59   x=232   y=198   width=4     height=32    xoffset=6     yoffset=0     xadvance=17    page=0  chnl=15
+char id=60   x=195   y=66    width=14    height=32    xoffset=1     yoffset=0     xadvance=17    page=0  chnl=15
+char id=61   x=210   y=66    width=14    height=32    xoffset=1     yoffset=0     xadvance=17    page=0  chnl=15
+char id=62   x=225   y=66    width=14    height=32    xoffset=1     yoffset=0     xadvance=17    page=0  chnl=15
+char id=63   x=319   y=165   width=11    height=32    xoffset=4     yoffset=0     xadvance=17    page=0  chnl=15
+char id=64   x=406   y=0     width=16    height=32    xoffset=1     yoffset=0     xadvance=17    page=0  chnl=15
+char id=65   x=423   y=0     width=16    height=32    xoffset=1     yoffset=0     xadvance=17    page=0  chnl=15
+char id=66   x=140   y=99    width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=67   x=154   y=99    width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=68   x=168   y=99    width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=69   x=182   y=165   width=12    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=70   x=433   y=132   width=12    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=71   x=270   y=66    width=14    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=72   x=182   y=99    width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=73   x=367   y=165   width=11    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=74   x=446   y=132   width=12    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=75   x=246   y=33    width=15    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=76   x=196   y=99    width=13    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=77   x=345   y=66    width=14    height=32    xoffset=1     yoffset=0     xadvance=17    page=0  chnl=15
+char id=78   x=210   y=99    width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=79   x=390   y=66    width=14    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=80   x=224   y=99    width=13    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=81   x=435   y=66    width=14    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=82   x=214   y=33    width=15    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=83   x=238   y=99    width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=84   x=166   y=33    width=15    height=32    xoffset=1     yoffset=0     xadvance=17    page=0  chnl=15
+char id=85   x=252   y=99    width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=86   x=230   y=33    width=15    height=32    xoffset=1     yoffset=0     xadvance=17    page=0  chnl=15
+char id=87   x=72    y=0     width=17    height=32    xoffset=0     yoffset=0     xadvance=17    page=0  chnl=15
+char id=88   x=457   y=0     width=16    height=32    xoffset=1     yoffset=0     xadvance=17    page=0  chnl=15
+char id=89   x=198   y=0     width=17    height=32    xoffset=0     yoffset=0     xadvance=17    page=0  chnl=15
+char id=90   x=462   y=33    width=14    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=91   x=184   y=198   width=6     height=32    xoffset=6     yoffset=0     xadvance=17    page=0  chnl=15
+char id=92   x=492   y=33    width=14    height=32    xoffset=1     yoffset=0     xadvance=17    page=0  chnl=15
+char id=93   x=212   y=198   width=6     height=32    xoffset=5     yoffset=0     xadvance=17    page=0  chnl=15
+char id=94   x=134   y=33    width=15    height=32    xoffset=1     yoffset=0     xadvance=17    page=0  chnl=15
+char id=95   x=30    y=66    width=14    height=32    xoffset=0     yoffset=0     xadvance=17    page=0  chnl=15
+char id=96   x=169   y=198   width=7     height=32    xoffset=4     yoffset=0     xadvance=17    page=0  chnl=15
+char id=97   x=266   y=99    width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=98   x=280   y=99    width=13    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=99   x=472   y=132   width=12    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=100  x=294   y=99    width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=101  x=120   y=66    width=14    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=102  x=91    y=165   width=12    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=103  x=308   y=99    width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=104  x=459   y=132   width=12    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=105  x=322   y=99    width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=106  x=81    y=198   width=9     height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=107  x=336   y=99    width=13    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=108  x=350   y=99    width=13    height=32    xoffset=1     yoffset=0     xadvance=17    page=0  chnl=15
+char id=109  x=182   y=33    width=15    height=32    xoffset=1     yoffset=0     xadvance=17    page=0  chnl=15
+char id=110  x=52    y=165   width=12    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=111  x=364   y=99    width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=112  x=378   y=99    width=13    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=113  x=392   y=99    width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=114  x=259   y=165   width=11    height=32    xoffset=5     yoffset=0     xadvance=17    page=0  chnl=15
+char id=115  x=427   y=165   width=11    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=116  x=406   y=99    width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=117  x=39    y=165   width=12    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=118  x=375   y=66    width=14    height=32    xoffset=1     yoffset=0     xadvance=17    page=0  chnl=15
+char id=119  x=18    y=0     width=17    height=32    xoffset=0     yoffset=0     xadvance=17    page=0  chnl=15
+char id=120  x=310   y=33    width=15    height=32    xoffset=1     yoffset=0     xadvance=17    page=0  chnl=15
+char id=121  x=420   y=66    width=14    height=32    xoffset=1     yoffset=0     xadvance=17    page=0  chnl=15
+char id=122  x=415   y=165   width=11    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=123  x=403   y=165   width=11    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=124  x=281   y=198   width=2     height=32    xoffset=7     yoffset=0     xadvance=17    page=0  chnl=15
+char id=125  x=247   y=165   width=11    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=126  x=342   y=33    width=14    height=32    xoffset=1     yoffset=0     xadvance=17    page=0  chnl=15
+char id=160  x=251   y=198   width=3     height=32    xoffset=-1    yoffset=0     xadvance=17    page=0  chnl=15
+char id=161  x=508   y=66    width=3     height=32    xoffset=7     yoffset=0     xadvance=17    page=0  chnl=15
+char id=162  x=26    y=165   width=12    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=163  x=420   y=99    width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=164  x=331   y=165   width=11    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=165  x=252   y=0     width=17    height=32    xoffset=0     yoffset=0     xadvance=17    page=0  chnl=15
+char id=166  x=278   y=198   width=2     height=32    xoffset=7     yoffset=0     xadvance=17    page=0  chnl=15
+char id=167  x=307   y=165   width=11    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=168  x=11    y=198   width=9     height=32    xoffset=4     yoffset=0     xadvance=17    page=0  chnl=15
+char id=169  x=162   y=0     width=17    height=32    xoffset=0     yoffset=0     xadvance=17    page=0  chnl=15
+char id=170  x=496   y=165   width=10    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=171  x=169   y=165   width=12    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=172  x=255   y=66    width=14    height=32    xoffset=1     yoffset=0     xadvance=17    page=0  chnl=15
+char id=173  x=153   y=198   width=7     height=32    xoffset=5     yoffset=0     xadvance=17    page=0  chnl=15
+char id=174  x=0     y=0     width=17    height=32    xoffset=0     yoffset=0     xadvance=17    page=0  chnl=15
+char id=175  x=136   y=198   width=8     height=32    xoffset=4     yoffset=0     xadvance=17    page=0  chnl=15
+char id=176  x=31    y=198   width=9     height=32    xoffset=4     yoffset=0     xadvance=17    page=0  chnl=15
+char id=177  x=357   y=33    width=14    height=32    xoffset=1     yoffset=0     xadvance=17    page=0  chnl=15
+char id=178  x=91    y=198   width=8     height=32    xoffset=4     yoffset=0     xadvance=17    page=0  chnl=15
+char id=179  x=118   y=198   width=8     height=32    xoffset=4     yoffset=0     xadvance=17    page=0  chnl=15
+char id=180  x=145   y=198   width=7     height=32    xoffset=7     yoffset=0     xadvance=17    page=0  chnl=15
+char id=181  x=105   y=66    width=14    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=182  x=434   y=99    width=13    height=32    xoffset=1     yoffset=0     xadvance=17    page=0  chnl=15
+char id=183  x=255   y=198   width=3     height=32    xoffset=7     yoffset=0     xadvance=17    page=0  chnl=15
+char id=184  x=205   y=198   width=6     height=32    xoffset=5     yoffset=0     xadvance=17    page=0  chnl=15
+char id=185  x=100   y=198   width=8     height=32    xoffset=5     yoffset=0     xadvance=17    page=0  chnl=15
+char id=186  x=463   y=165   width=10    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=187  x=65    y=165   width=12    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=188  x=491   y=0     width=16    height=32    xoffset=0     yoffset=0     xadvance=17    page=0  chnl=15
+char id=189  x=262   y=33    width=15    height=32    xoffset=0     yoffset=0     xadvance=17    page=0  chnl=15
+char id=190  x=389   y=0     width=16    height=32    xoffset=0     yoffset=0     xadvance=17    page=0  chnl=15
+char id=191  x=451   y=165   width=11    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=192  x=321   y=0     width=16    height=32    xoffset=1     yoffset=0     xadvance=17    page=0  chnl=15
+char id=193  x=0     y=33    width=16    height=32    xoffset=1     yoffset=0     xadvance=17    page=0  chnl=15
+char id=194  x=355   y=0     width=16    height=32    xoffset=1     yoffset=0     xadvance=17    page=0  chnl=15
+char id=195  x=85    y=33    width=16    height=32    xoffset=1     yoffset=0     xadvance=17    page=0  chnl=15
+char id=196  x=51    y=33    width=16    height=32    xoffset=1     yoffset=0     xadvance=17    page=0  chnl=15
+char id=197  x=34    y=33    width=16    height=32    xoffset=1     yoffset=0     xadvance=17    page=0  chnl=15
+char id=198  x=17    y=33    width=16    height=32    xoffset=0     yoffset=0     xadvance=17    page=0  chnl=15
+char id=199  x=462   y=99    width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=200  x=117   y=165   width=12    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=201  x=78    y=165   width=12    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=202  x=0     y=165   width=12    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=203  x=156   y=165   width=12    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=204  x=283   y=165   width=11    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=205  x=379   y=165   width=11    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=206  x=391   y=165   width=11    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=207  x=343   y=165   width=11    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=208  x=150   y=33    width=15    height=32    xoffset=0     yoffset=0     xadvance=17    page=0  chnl=15
+char id=209  x=476   y=99    width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=210  x=60    y=66    width=14    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=211  x=387   y=33    width=14    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=212  x=405   y=66    width=14    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=213  x=447   y=33    width=14    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=214  x=285   y=66    width=14    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=215  x=490   y=99    width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=216  x=180   y=0     width=17    height=32    xoffset=0     yoffset=0     xadvance=17    page=0  chnl=15
+char id=217  x=0     y=132   width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=218  x=14    y=132   width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=219  x=28    y=132   width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=220  x=42    y=132   width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=221  x=36    y=0     width=17    height=32    xoffset=0     yoffset=0     xadvance=17    page=0  chnl=15
+char id=222  x=56    y=132   width=13    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=223  x=448   y=99    width=13    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=224  x=70    y=132   width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=225  x=84    y=132   width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=226  x=98    y=132   width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=227  x=112   y=132   width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=228  x=126   y=132   width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=229  x=140   y=132   width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=230  x=338   y=0     width=16    height=32    xoffset=1     yoffset=0     xadvance=17    page=0  chnl=15
+char id=231  x=485   y=132   width=12    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=232  x=150   y=66    width=14    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=233  x=135   y=66    width=14    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=234  x=75    y=66    width=14    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=235  x=372   y=33    width=14    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=236  x=154   y=132   width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=237  x=168   y=132   width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=238  x=182   y=132   width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=239  x=196   y=132   width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=240  x=210   y=132   width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=241  x=104   y=165   width=12    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=242  x=224   y=132   width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=243  x=238   y=132   width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=244  x=252   y=132   width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=245  x=266   y=132   width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=246  x=280   y=132   width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=247  x=118   y=33    width=15    height=32    xoffset=1     yoffset=0     xadvance=17    page=0  chnl=15
+char id=248  x=102   y=33    width=15    height=32    xoffset=1     yoffset=0     xadvance=17    page=0  chnl=15
+char id=249  x=208   y=165   width=12    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=250  x=221   y=165   width=12    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=251  x=234   y=165   width=12    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=252  x=420   y=132   width=12    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=253  x=465   y=66    width=14    height=32    xoffset=1     yoffset=0     xadvance=17    page=0  chnl=15
+char id=254  x=294   y=132   width=13    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=255  x=450   y=66    width=14    height=32    xoffset=1     yoffset=0     xadvance=17    page=0  chnl=15
+char id=262  x=28    y=99    width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=263  x=498   y=132   width=12    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=268  x=308   y=132   width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=269  x=13    y=165   width=12    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=273  x=287   y=0     width=16    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=286  x=330   y=66    width=14    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=287  x=322   y=132   width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=304  x=439   y=165   width=11    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=305  x=336   y=132   width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=321  x=270   y=0     width=16    height=32    xoffset=0     yoffset=0     xadvance=17    page=0  chnl=15
+char id=322  x=350   y=132   width=13    height=32    xoffset=1     yoffset=0     xadvance=17    page=0  chnl=15
+char id=338  x=68    y=33    width=16    height=32    xoffset=1     yoffset=0     xadvance=17    page=0  chnl=15
+char id=339  x=440   y=0     width=16    height=32    xoffset=0     yoffset=0     xadvance=17    page=0  chnl=15
+char id=350  x=364   y=132   width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=351  x=271   y=165   width=11    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=352  x=378   y=132   width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=353  x=355   y=165   width=11    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=376  x=144   y=0     width=17    height=32    xoffset=0     yoffset=0     xadvance=17    page=0  chnl=15
+char id=381  x=300   y=66    width=14    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=382  x=295   y=165   width=11    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=402  x=474   y=0     width=16    height=32    xoffset=0     yoffset=0     xadvance=17    page=0  chnl=15
+char id=710  x=21    y=198   width=9     height=32    xoffset=4     yoffset=0     xadvance=17    page=0  chnl=15
+char id=711  x=41    y=198   width=9     height=32    xoffset=4     yoffset=0     xadvance=17    page=0  chnl=15
+char id=728  x=51    y=198   width=9     height=32    xoffset=4     yoffset=0     xadvance=17    page=0  chnl=15
+char id=729  x=508   y=0     width=3     height=32    xoffset=7     yoffset=0     xadvance=17    page=0  chnl=15
+char id=730  x=127   y=198   width=8     height=32    xoffset=5     yoffset=0     xadvance=17    page=0  chnl=15
+char id=731  x=226   y=198   width=5     height=32    xoffset=6     yoffset=0     xadvance=17    page=0  chnl=15
+char id=732  x=61    y=198   width=9     height=32    xoffset=4     yoffset=0     xadvance=17    page=0  chnl=15
+char id=733  x=71    y=198   width=9     height=32    xoffset=5     yoffset=0     xadvance=17    page=0  chnl=15
+char id=937  x=45    y=66    width=14    height=32    xoffset=1     yoffset=0     xadvance=17    page=0  chnl=15
+char id=960  x=278   y=33    width=15    height=32    xoffset=1     yoffset=0     xadvance=17    page=0  chnl=15
+char id=8211 x=108   y=0     width=17    height=32    xoffset=0     yoffset=0     xadvance=17    page=0  chnl=15
+char id=8212 x=216   y=0     width=17    height=32    xoffset=0     yoffset=0     xadvance=17    page=0  chnl=15
+char id=8216 x=507   y=165   width=4     height=32    xoffset=6     yoffset=0     xadvance=17    page=0  chnl=15
+char id=8217 x=507   y=33    width=4     height=32    xoffset=7     yoffset=0     xadvance=17    page=0  chnl=15
+char id=8218 x=237   y=198   width=4     height=32    xoffset=6     yoffset=0     xadvance=17    page=0  chnl=15
+char id=8220 x=474   y=165   width=10    height=32    xoffset=4     yoffset=0     xadvance=17    page=0  chnl=15
+char id=8221 x=485   y=165   width=10    height=32    xoffset=4     yoffset=0     xadvance=17    page=0  chnl=15
+char id=8222 x=0     y=198   width=10    height=32    xoffset=4     yoffset=0     xadvance=17    page=0  chnl=15
+char id=8224 x=392   y=132   width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=8225 x=406   y=132   width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=8226 x=109   y=198   width=8     height=32    xoffset=4     yoffset=0     xadvance=17    page=0  chnl=15
+char id=8230 x=198   y=33    width=15    height=32    xoffset=1     yoffset=0     xadvance=17    page=0  chnl=15
+char id=8240 x=126   y=0     width=17    height=32    xoffset=0     yoffset=0     xadvance=17    page=0  chnl=15
+char id=8249 x=191   y=198   width=6     height=32    xoffset=5     yoffset=0     xadvance=17    page=0  chnl=15
+char id=8250 x=198   y=198   width=6     height=32    xoffset=6     yoffset=0     xadvance=17    page=0  chnl=15
+char id=8364 x=315   y=66    width=14    height=32    xoffset=1     yoffset=0     xadvance=17    page=0  chnl=15
+char id=8482 x=294   y=33    width=15    height=32    xoffset=0     yoffset=0     xadvance=17    page=0  chnl=15
+char id=8706 x=195   y=165   width=12    height=32    xoffset=3     yoffset=0     xadvance=17    page=0  chnl=15
+char id=8710 x=234   y=0     width=17    height=32    xoffset=0     yoffset=0     xadvance=17    page=0  chnl=15
+char id=8719 x=480   y=66    width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=8721 x=494   y=66    width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=8722 x=180   y=66    width=14    height=32    xoffset=1     yoffset=0     xadvance=17    page=0  chnl=15
+char id=8725 x=165   y=66    width=14    height=32    xoffset=1     yoffset=0     xadvance=17    page=0  chnl=15
+char id=8729 x=263   y=198   width=3     height=32    xoffset=7     yoffset=0     xadvance=17    page=0  chnl=15
+char id=8730 x=326   y=33    width=15    height=32    xoffset=1     yoffset=0     xadvance=17    page=0  chnl=15
+char id=8734 x=372   y=0     width=16    height=32    xoffset=1     yoffset=0     xadvance=17    page=0  chnl=15
+char id=8747 x=14    y=99    width=13    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
+char id=8776 x=477   y=33    width=14    height=32    xoffset=1     yoffset=0     xadvance=17    page=0  chnl=15
+char id=8800 x=432   y=33    width=14    height=32    xoffset=1     yoffset=0     xadvance=17    page=0  chnl=15
+char id=8804 x=417   y=33    width=14    height=32    xoffset=1     yoffset=0     xadvance=17    page=0  chnl=15
+char id=8805 x=402   y=33    width=14    height=32    xoffset=1     yoffset=0     xadvance=17    page=0  chnl=15
+char id=9674 x=240   y=66    width=14    height=32    xoffset=2     yoffset=0     xadvance=17    page=0  chnl=15
diff --git a/font/vera_sans_mono32_0.png b/font/vera_sans_mono32_0.png
new file mode 100644
index 0000000..e14b00d
Binary files /dev/null and b/font/vera_sans_mono32_0.png differ
diff --git a/font/vera_sans_mono36.bmfc b/font/vera_sans_mono36.bmfc
new file mode 100644
index 0000000..731038d
--- /dev/null
+++ b/font/vera_sans_mono36.bmfc
@@ -0,0 +1,57 @@
+# AngelCode Bitmap Font Generator configuration file
+fileVersion=1
+
+# font settings
+fontName=Bitstream Vera Sans Mono
+fontFile=VeraMoBd.ttf
+charSet=0
+fontSize=36
+aa=1
+scaleH=100
+useSmoothing=1
+isBold=0
+isItalic=0
+useUnicode=1
+disableBoxChars=1
+outputInvalidCharGlyph=0
+dontIncludeKerningPairs=0
+useHinting=1
+renderFromOutline=0
+useClearType=1
+
+# character alignment
+paddingDown=0
+paddingUp=0
+paddingRight=0
+paddingLeft=0
+spacingHoriz=1
+spacingVert=1
+useFixedHeight=1
+forceZero=0
+
+# output file
+outWidth=512
+outHeight=512
+outBitDepth=32
+fontDescFormat=0
+fourChnlPacked=0
+textureFormat=png
+textureCompression=0
+alphaChnl=1
+redChnl=0
+greenChnl=0
+blueChnl=0
+invA=0
+invR=0
+invG=0
+invB=0
+
+# outline
+outlineThickness=0
+
+# selected chars
+chars=32-126,160-255,262-263,268-269,273,286-287,304-305,321-322,338-339,350-353,376,381-382,402,710-711
+chars=728-733,937,960,8211-8212,8216-8218,8220-8222,8224-8226,8230,8240,8249-8250,8364,8482,8706,8710
+chars=8719,8721-8722,8725,8729-8730,8734,8747,8776,8800,8804-8805,9674
+
+# imported icon images
diff --git a/font/vera_sans_mono36.fnt b/font/vera_sans_mono36.fnt
new file mode 100644
index 0000000..0c79f79
--- /dev/null
+++ b/font/vera_sans_mono36.fnt
@@ -0,0 +1,258 @@
+info face="Bitstream Vera Sans Mono" size=36 bold=0 italic=0 charset="" unicode=1 stretchH=100 smooth=1 aa=1 padding=0,0,0,0 spacing=1,1 outline=0
+common lineHeight=36 base=29 scaleW=512 scaleH=512 pages=1 packed=0 alphaChnl=1 redChnl=0 greenChnl=0 blueChnl=0
+page id=0 file="vera_sans_mono36_0.png"
+chars count=254
+char id=32   x=107   y=296   width=3     height=36    xoffset=-1    yoffset=0     xadvance=19    page=0  chnl=15
+char id=33   x=101   y=296   width=5     height=36    xoffset=7     yoffset=0     xadvance=19    page=0  chnl=15
+char id=34   x=230   y=259   width=13    height=36    xoffset=3     yoffset=0     xadvance=19    page=0  chnl=15
+char id=35   x=63    y=0     width=20    height=36    xoffset=-1    yoffset=0     xadvance=19    page=0  chnl=15
+char id=36   x=481   y=222   width=15    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=37   x=396   y=0     width=19    height=36    xoffset=0     yoffset=0     xadvance=19    page=0  chnl=15
+char id=38   x=416   y=0     width=19    height=36    xoffset=0     yoffset=0     xadvance=19    page=0  chnl=15
+char id=39   x=89    y=296   width=5     height=36    xoffset=7     yoffset=0     xadvance=19    page=0  chnl=15
+char id=40   x=430   y=259   width=10    height=36    xoffset=5     yoffset=0     xadvance=19    page=0  chnl=15
+char id=41   x=419   y=259   width=10    height=36    xoffset=4     yoffset=0     xadvance=19    page=0  chnl=15
+char id=42   x=52    y=185   width=16    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=43   x=415   y=37    width=18    height=36    xoffset=0     yoffset=0     xadvance=19    page=0  chnl=15
+char id=44   x=501   y=259   width=8     height=36    xoffset=5     yoffset=0     xadvance=19    page=0  chnl=15
+char id=45   x=324   y=259   width=11    height=36    xoffset=4     yoffset=0     xadvance=19    page=0  chnl=15
+char id=46   x=62    y=296   width=7     height=36    xoffset=6     yoffset=0     xadvance=19    page=0  chnl=15
+char id=47   x=224   y=74    width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=48   x=440   y=74    width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=49   x=69    y=185   width=16    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=50   x=86    y=185   width=16    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=51   x=103   y=185   width=16    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=52   x=278   y=74    width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=53   x=120   y=185   width=16    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=54   x=314   y=74    width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=55   x=137   y=185   width=16    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=56   x=154   y=185   width=16    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=57   x=171   y=185   width=16    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=58   x=504   y=111   width=7     height=36    xoffset=6     yoffset=0     xadvance=19    page=0  chnl=15
+char id=59   x=492   y=259   width=8     height=36    xoffset=5     yoffset=0     xadvance=19    page=0  chnl=15
+char id=60   x=386   y=74    width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=61   x=404   y=74    width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=62   x=422   y=74    width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=63   x=159   y=259   width=14    height=36    xoffset=3     yoffset=0     xadvance=19    page=0  chnl=15
+char id=64   x=0     y=0     width=20    height=36    xoffset=-1    yoffset=0     xadvance=19    page=0  chnl=15
+char id=65   x=180   y=37    width=19    height=36    xoffset=0     yoffset=0     xadvance=19    page=0  chnl=15
+char id=66   x=458   y=74    width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=67   x=225   y=222   width=15    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=68   x=432   y=148   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=69   x=188   y=185   width=16    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=70   x=205   y=185   width=16    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=71   x=324   y=148   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=72   x=222   y=185   width=16    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=73   x=209   y=222   width=15    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=74   x=193   y=222   width=15    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=75   x=0     y=74    width=18    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=76   x=161   y=222   width=15    height=36    xoffset=3     yoffset=0     xadvance=19    page=0  chnl=15
+char id=77   x=180   y=148   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=78   x=144   y=148   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=79   x=90    y=148   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=80   x=239   y=185   width=16    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=81   x=72    y=148   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=82   x=19    y=74    width=18    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=83   x=36    y=148   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=84   x=468   y=111   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=85   x=432   y=111   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=86   x=38    y=74    width=18    height=36    xoffset=0     yoffset=0     xadvance=19    page=0  chnl=15
+char id=87   x=273   y=0     width=20    height=36    xoffset=-1    yoffset=0     xadvance=19    page=0  chnl=15
+char id=88   x=260   y=37    width=19    height=36    xoffset=0     yoffset=0     xadvance=19    page=0  chnl=15
+char id=89   x=80    y=37    width=19    height=36    xoffset=0     yoffset=0     xadvance=19    page=0  chnl=15
+char id=90   x=396   y=111   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=91   x=462   y=259   width=9     height=36    xoffset=6     yoffset=0     xadvance=19    page=0  chnl=15
+char id=92   x=270   y=111   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=93   x=452   y=259   width=9     height=36    xoffset=4     yoffset=0     xadvance=19    page=0  chnl=15
+char id=94   x=57    y=74    width=18    height=36    xoffset=0     yoffset=0     xadvance=19    page=0  chnl=15
+char id=95   x=84    y=0     width=20    height=36    xoffset=-1    yoffset=0     xadvance=19    page=0  chnl=15
+char id=96   x=408   y=259   width=10    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=97   x=180   y=111   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=98   x=494   y=74    width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=99   x=81    y=222   width=15    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=100  x=256   y=185   width=16    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=101  x=54    y=111   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=102  x=65    y=222   width=15    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=103  x=273   y=185   width=16    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=104  x=49    y=222   width=15    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=105  x=0     y=148   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=106  x=174   y=259   width=13    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=107  x=252   y=148   width=17    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=108  x=290   y=185   width=16    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=109  x=76    y=74    width=18    height=36    xoffset=0     yoffset=0     xadvance=19    page=0  chnl=15
+char id=110  x=496   y=0     width=15    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=111  x=18    y=111   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=112  x=36    y=111   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=113  x=307   y=185   width=16    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=114  x=497   y=222   width=14    height=36    xoffset=4     yoffset=0     xadvance=19    page=0  chnl=15
+char id=115  x=129   y=222   width=15    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=116  x=341   y=185   width=16    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=117  x=80    y=259   width=15    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=118  x=144   y=111   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=119  x=315   y=0     width=20    height=36    xoffset=-1    yoffset=0     xadvance=19    page=0  chnl=15
+char id=120  x=133   y=74    width=18    height=36    xoffset=0     yoffset=0     xadvance=19    page=0  chnl=15
+char id=121  x=320   y=37    width=18    height=36    xoffset=0     yoffset=0     xadvance=19    page=0  chnl=15
+char id=122  x=64    y=259   width=15    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=123  x=48    y=259   width=15    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=124  x=83    y=296   width=5     height=36    xoffset=7     yoffset=0     xadvance=19    page=0  chnl=15
+char id=125  x=16    y=259   width=15    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=126  x=288   y=111   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=160  x=111   y=296   width=3     height=36    xoffset=-1    yoffset=0     xadvance=19    page=0  chnl=15
+char id=161  x=77    y=296   width=5     height=36    xoffset=7     yoffset=0     xadvance=19    page=0  chnl=15
+char id=162  x=449   y=222   width=15    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=163  x=360   y=111   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=164  x=417   y=222   width=15    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=165  x=100   y=37    width=19    height=36    xoffset=0     yoffset=0     xadvance=19    page=0  chnl=15
+char id=166  x=95    y=296   width=5     height=36    xoffset=7     yoffset=0     xadvance=19    page=0  chnl=15
+char id=167  x=401   y=222   width=15    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=168  x=312   y=259   width=11    height=36    xoffset=4     yoffset=0     xadvance=19    page=0  chnl=15
+char id=169  x=189   y=0     width=20    height=36    xoffset=-1    yoffset=0     xadvance=19    page=0  chnl=15
+char id=170  x=202   y=259   width=13    height=36    xoffset=3     yoffset=0     xadvance=19    page=0  chnl=15
+char id=171  x=358   y=185   width=16    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=172  x=18    y=148   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=173  x=384   y=259   width=11    height=36    xoffset=4     yoffset=0     xadvance=19    page=0  chnl=15
+char id=174  x=42    y=0     width=20    height=36    xoffset=-1    yoffset=0     xadvance=19    page=0  chnl=15
+char id=175  x=372   y=259   width=11    height=36    xoffset=4     yoffset=0     xadvance=19    page=0  chnl=15
+char id=176  x=348   y=259   width=11    height=36    xoffset=4     yoffset=0     xadvance=19    page=0  chnl=15
+char id=177  x=296   y=74    width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=178  x=299   y=259   width=12    height=36    xoffset=3     yoffset=0     xadvance=19    page=0  chnl=15
+char id=179  x=360   y=259   width=11    height=36    xoffset=4     yoffset=0     xadvance=19    page=0  chnl=15
+char id=180  x=482   y=259   width=9     height=36    xoffset=7     yoffset=0     xadvance=19    page=0  chnl=15
+char id=181  x=414   y=148   width=17    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=182  x=396   y=148   width=17    height=36    xoffset=0     yoffset=0     xadvance=19    page=0  chnl=15
+char id=183  x=504   y=148   width=7     height=36    xoffset=6     yoffset=0     xadvance=19    page=0  chnl=15
+char id=184  x=0     y=296   width=8     height=36    xoffset=5     yoffset=0     xadvance=19    page=0  chnl=15
+char id=185  x=396   y=259   width=11    height=36    xoffset=4     yoffset=0     xadvance=19    page=0  chnl=15
+char id=186  x=244   y=259   width=13    height=36    xoffset=3     yoffset=0     xadvance=19    page=0  chnl=15
+char id=187  x=375   y=185   width=16    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=188  x=95    y=74    width=18    height=36    xoffset=0     yoffset=0     xadvance=19    page=0  chnl=15
+char id=189  x=114   y=74    width=18    height=36    xoffset=0     yoffset=0     xadvance=19    page=0  chnl=15
+char id=190  x=339   y=37    width=18    height=36    xoffset=0     yoffset=0     xadvance=19    page=0  chnl=15
+char id=191  x=144   y=259   width=14    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=192  x=120   y=37    width=19    height=36    xoffset=0     yoffset=0     xadvance=19    page=0  chnl=15
+char id=193  x=436   y=0     width=19    height=36    xoffset=0     yoffset=0     xadvance=19    page=0  chnl=15
+char id=194  x=60    y=37    width=19    height=36    xoffset=0     yoffset=0     xadvance=19    page=0  chnl=15
+char id=195  x=160   y=37    width=19    height=36    xoffset=0     yoffset=0     xadvance=19    page=0  chnl=15
+char id=196  x=20    y=37    width=19    height=36    xoffset=0     yoffset=0     xadvance=19    page=0  chnl=15
+char id=197  x=336   y=0     width=19    height=36    xoffset=0     yoffset=0     xadvance=19    page=0  chnl=15
+char id=198  x=252   y=0     width=20    height=36    xoffset=-1    yoffset=0     xadvance=19    page=0  chnl=15
+char id=199  x=433   y=222   width=15    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=200  x=409   y=185   width=16    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=201  x=426   y=185   width=16    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=202  x=443   y=185   width=16    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=203  x=460   y=185   width=16    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=204  x=96    y=259   width=15    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=205  x=112   y=259   width=15    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=206  x=128   y=259   width=15    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=207  x=97    y=222   width=15    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=208  x=220   y=37    width=19    height=36    xoffset=-1    yoffset=0     xadvance=19    page=0  chnl=15
+char id=209  x=332   y=74    width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=210  x=260   y=74    width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=211  x=242   y=74    width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=212  x=206   y=74    width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=213  x=486   y=111   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=214  x=450   y=111   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=215  x=477   y=185   width=16    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=216  x=231   y=0     width=20    height=36    xoffset=-1    yoffset=0     xadvance=19    page=0  chnl=15
+char id=217  x=414   y=111   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=218  x=378   y=111   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=219  x=342   y=111   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=220  x=306   y=111   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=221  x=140   y=37    width=19    height=36    xoffset=0     yoffset=0     xadvance=19    page=0  chnl=15
+char id=222  x=494   y=185   width=16    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=223  x=377   y=37    width=18    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=224  x=252   y=111   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=225  x=234   y=111   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=226  x=216   y=111   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=227  x=126   y=111   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=228  x=90    y=111   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=229  x=72    y=111   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=230  x=200   y=37    width=19    height=36    xoffset=0     yoffset=0     xadvance=19    page=0  chnl=15
+char id=231  x=17    y=222   width=15    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=232  x=54    y=148   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=233  x=198   y=111   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=234  x=108   y=111   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=235  x=216   y=148   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=236  x=360   y=148   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=237  x=0     y=111   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=238  x=162   y=111   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=239  x=324   y=111   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=240  x=234   y=148   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=241  x=177   y=222   width=15    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=242  x=270   y=148   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=243  x=306   y=148   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=244  x=476   y=74    width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=245  x=368   y=74    width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=246  x=350   y=74    width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=247  x=453   y=37    width=18    height=36    xoffset=0     yoffset=0     xadvance=19    page=0  chnl=15
+char id=248  x=0     y=37    width=19    height=36    xoffset=0     yoffset=0     xadvance=19    page=0  chnl=15
+char id=249  x=305   y=222   width=15    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=250  x=321   y=222   width=15    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=251  x=337   y=222   width=15    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=252  x=353   y=222   width=15    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=253  x=434   y=37    width=18    height=36    xoffset=0     yoffset=0     xadvance=19    page=0  chnl=15
+char id=254  x=188   y=74    width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=255  x=358   y=37    width=18    height=36    xoffset=0     yoffset=0     xadvance=19    page=0  chnl=15
+char id=262  x=145   y=222   width=15    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=263  x=0     y=222   width=16    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=268  x=18    y=185   width=16    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=269  x=465   y=222   width=15    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=273  x=376   y=0     width=19    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=286  x=170   y=74    width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=287  x=35    y=185   width=16    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=304  x=32    y=259   width=15    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=305  x=152   y=74    width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=321  x=300   y=37    width=19    height=36    xoffset=-1    yoffset=0     xadvance=19    page=0  chnl=15
+char id=322  x=396   y=37    width=18    height=36    xoffset=0     yoffset=0     xadvance=19    page=0  chnl=15
+char id=338  x=40    y=37    width=19    height=36    xoffset=0     yoffset=0     xadvance=19    page=0  chnl=15
+char id=339  x=294   y=0     width=20    height=36    xoffset=-1    yoffset=0     xadvance=19    page=0  chnl=15
+char id=350  x=108   y=148   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=351  x=33    y=222   width=15    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=352  x=126   y=148   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=353  x=113   y=222   width=15    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=376  x=476   y=0     width=19    height=36    xoffset=0     yoffset=0     xadvance=19    page=0  chnl=15
+char id=381  x=162   y=148   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=382  x=241   y=222   width=15    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=402  x=456   y=0     width=19    height=36    xoffset=-1    yoffset=0     xadvance=19    page=0  chnl=15
+char id=710  x=258   y=259   width=13    height=36    xoffset=3     yoffset=0     xadvance=19    page=0  chnl=15
+char id=711  x=272   y=259   width=13    height=36    xoffset=3     yoffset=0     xadvance=19    page=0  chnl=15
+char id=728  x=336   y=259   width=11    height=36    xoffset=4     yoffset=0     xadvance=19    page=0  chnl=15
+char id=729  x=70    y=296   width=6     height=36    xoffset=6     yoffset=0     xadvance=19    page=0  chnl=15
+char id=730  x=441   y=259   width=10    height=36    xoffset=4     yoffset=0     xadvance=19    page=0  chnl=15
+char id=731  x=18    y=296   width=8     height=36    xoffset=6     yoffset=0     xadvance=19    page=0  chnl=15
+char id=732  x=286   y=259   width=12    height=36    xoffset=3     yoffset=0     xadvance=19    page=0  chnl=15
+char id=733  x=188   y=259   width=13    height=36    xoffset=4     yoffset=0     xadvance=19    page=0  chnl=15
+char id=937  x=198   y=148   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=960  x=21    y=0     width=20    height=36    xoffset=-1    yoffset=0     xadvance=19    page=0  chnl=15
+char id=8211 x=147   y=0     width=20    height=36    xoffset=-1    yoffset=0     xadvance=19    page=0  chnl=15
+char id=8212 x=210   y=0     width=20    height=36    xoffset=-1    yoffset=0     xadvance=19    page=0  chnl=15
+char id=8216 x=36    y=296   width=8     height=36    xoffset=6     yoffset=0     xadvance=19    page=0  chnl=15
+char id=8217 x=27    y=296   width=8     height=36    xoffset=6     yoffset=0     xadvance=19    page=0  chnl=15
+char id=8218 x=45    y=296   width=8     height=36    xoffset=5     yoffset=0     xadvance=19    page=0  chnl=15
+char id=8220 x=257   y=222   width=15    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=8221 x=273   y=222   width=15    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=8222 x=289   y=222   width=15    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=8224 x=369   y=222   width=15    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=8225 x=385   y=222   width=15    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=8226 x=216   y=259   width=13    height=36    xoffset=3     yoffset=0     xadvance=19    page=0  chnl=15
+char id=8230 x=472   y=37    width=18    height=36    xoffset=0     yoffset=0     xadvance=19    page=0  chnl=15
+char id=8240 x=168   y=0     width=20    height=36    xoffset=-1    yoffset=0     xadvance=19    page=0  chnl=15
+char id=8249 x=472   y=259   width=9     height=36    xoffset=4     yoffset=0     xadvance=19    page=0  chnl=15
+char id=8250 x=9     y=296   width=8     height=36    xoffset=6     yoffset=0     xadvance=19    page=0  chnl=15
+char id=8364 x=288   y=148   width=17    height=36    xoffset=0     yoffset=0     xadvance=19    page=0  chnl=15
+char id=8482 x=356   y=0     width=19    height=36    xoffset=-1    yoffset=0     xadvance=19    page=0  chnl=15
+char id=8706 x=324   y=185   width=16    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=8710 x=126   y=0     width=20    height=36    xoffset=-1    yoffset=0     xadvance=19    page=0  chnl=15
+char id=8719 x=0     y=259   width=15    height=36    xoffset=2     yoffset=0     xadvance=19    page=0  chnl=15
+char id=8721 x=342   y=148   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=8722 x=491   y=37    width=18    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=8725 x=378   y=148   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=8729 x=54    y=296   width=7     height=36    xoffset=6     yoffset=0     xadvance=19    page=0  chnl=15
+char id=8730 x=280   y=37    width=19    height=36    xoffset=0     yoffset=0     xadvance=19    page=0  chnl=15
+char id=8734 x=105   y=0     width=20    height=36    xoffset=-1    yoffset=0     xadvance=19    page=0  chnl=15
+char id=8747 x=240   y=37    width=19    height=36    xoffset=0     yoffset=0     xadvance=19    page=0  chnl=15
+char id=8776 x=450   y=148   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=8800 x=468   y=148   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=8804 x=486   y=148   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=8805 x=0     y=185   width=17    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
+char id=9674 x=392   y=185   width=16    height=36    xoffset=1     yoffset=0     xadvance=19    page=0  chnl=15
diff --git a/font/vera_sans_mono36_0.png b/font/vera_sans_mono36_0.png
new file mode 100644
index 0000000..8a8b2c3
Binary files /dev/null and b/font/vera_sans_mono36_0.png differ
diff --git a/font/vera_sans_mono48.bmfc b/font/vera_sans_mono48.bmfc
new file mode 100644
index 0000000..c66eb80
--- /dev/null
+++ b/font/vera_sans_mono48.bmfc
@@ -0,0 +1,57 @@
+# AngelCode Bitmap Font Generator configuration file
+fileVersion=1
+
+# font settings
+fontName=Bitstream Vera Sans Mono
+fontFile=VeraMono.ttf
+charSet=0
+fontSize=48
+aa=1
+scaleH=100
+useSmoothing=1
+isBold=0
+isItalic=0
+useUnicode=1
+disableBoxChars=1
+outputInvalidCharGlyph=0
+dontIncludeKerningPairs=0
+useHinting=1
+renderFromOutline=0
+useClearType=1
+
+# character alignment
+paddingDown=0
+paddingUp=0
+paddingRight=0
+paddingLeft=0
+spacingHoriz=1
+spacingVert=1
+useFixedHeight=1
+forceZero=0
+
+# output file
+outWidth=1024
+outHeight=512
+outBitDepth=32
+fontDescFormat=0
+fourChnlPacked=0
+textureFormat=png
+textureCompression=0
+alphaChnl=1
+redChnl=0
+greenChnl=0
+blueChnl=0
+invA=0
+invR=0
+invG=0
+invB=0
+
+# outline
+outlineThickness=0
+
+# selected chars
+chars=32-126,160-255,262-263,268-269,273,286-287,304-305,321-322,338-339,350-353,376,381-382,402,710-711
+chars=728-733,937,960,8211-8212,8216-8218,8220-8222,8224-8226,8230,8240,8249-8250,8364,8482,8706,8710
+chars=8719,8721-8722,8725,8729-8730,8734,8747,8776,8800,8804-8805,9674
+
+# imported icon images
diff --git a/font/vera_sans_mono48.fnt b/font/vera_sans_mono48.fnt
new file mode 100644
index 0000000..5853dc9
--- /dev/null
+++ b/font/vera_sans_mono48.fnt
@@ -0,0 +1,258 @@
+info face="Bitstream Vera Sans Mono" size=48 bold=0 italic=0 charset="" unicode=1 stretchH=100 smooth=1 aa=1 padding=0,0,0,0 spacing=1,1 outline=0
+common lineHeight=48 base=38 scaleW=1024 scaleH=512 pages=1 packed=0 alphaChnl=1 redChnl=0 greenChnl=0 blueChnl=0
+page id=0 file="vera_sans_mono48_0.png"
+chars count=254
+char id=32   x=48    y=245   width=3     height=48    xoffset=-1    yoffset=0     xadvance=25    page=0  chnl=15
+char id=33   x=38    y=245   width=5     height=48    xoffset=10    yoffset=0     xadvance=25    page=0  chnl=15
+char id=34   x=820   y=196   width=13    height=48    xoffset=6     yoffset=0     xadvance=25    page=0  chnl=15
+char id=35   x=81    y=0     width=26    height=48    xoffset=-1    yoffset=0     xadvance=25    page=0  chnl=15
+char id=36   x=152   y=147   width=20    height=48    xoffset=3     yoffset=0     xadvance=25    page=0  chnl=15
+char id=37   x=322   y=0     width=25    height=48    xoffset=0     yoffset=0     xadvance=25    page=0  chnl=15
+char id=38   x=348   y=0     width=25    height=48    xoffset=0     yoffset=0     xadvance=25    page=0  chnl=15
+char id=39   x=8     y=245   width=5     height=48    xoffset=10    yoffset=0     xadvance=25    page=0  chnl=15
+char id=40   x=920   y=196   width=10    height=48    xoffset=8     yoffset=0     xadvance=25    page=0  chnl=15
+char id=41   x=931   y=196   width=10    height=48    xoffset=7     yoffset=0     xadvance=25    page=0  chnl=15
+char id=42   x=925   y=147   width=19    height=48    xoffset=3     yoffset=0     xadvance=25    page=0  chnl=15
+char id=43   x=336   y=49    width=23    height=48    xoffset=1     yoffset=0     xadvance=25    page=0  chnl=15
+char id=44   x=953   y=196   width=8     height=48    xoffset=8     yoffset=0     xadvance=25    page=0  chnl=15
+char id=45   x=834   y=196   width=12    height=48    xoffset=6     yoffset=0     xadvance=25    page=0  chnl=15
+char id=46   x=1014  y=196   width=7     height=48    xoffset=9     yoffset=0     xadvance=25    page=0  chnl=15
+char id=47   x=22    y=98    width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=48   x=44    y=98    width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=49   x=0     y=196   width=19    height=48    xoffset=4     yoffset=0     xadvance=25    page=0  chnl=15
+char id=50   x=341   y=147   width=20    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=51   x=110   y=98    width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=52   x=432   y=49    width=23    height=48    xoffset=1     yoffset=0     xadvance=25    page=0  chnl=15
+char id=53   x=362   y=147   width=20    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=54   x=154   y=98    width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=55   x=176   y=98    width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=56   x=198   y=98    width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=57   x=220   y=98    width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=58   x=0     y=245   width=7     height=48    xoffset=9     yoffset=0     xadvance=25    page=0  chnl=15
+char id=59   x=971   y=196   width=8     height=48    xoffset=8     yoffset=0     xadvance=25    page=0  chnl=15
+char id=60   x=600   y=49    width=23    height=48    xoffset=1     yoffset=0     xadvance=25    page=0  chnl=15
+char id=61   x=624   y=49    width=23    height=48    xoffset=1     yoffset=0     xadvance=25    page=0  chnl=15
+char id=62   x=648   y=49    width=23    height=48    xoffset=1     yoffset=0     xadvance=25    page=0  chnl=15
+char id=63   x=317   y=196   width=18    height=48    xoffset=4     yoffset=0     xadvance=25    page=0  chnl=15
+char id=64   x=296   y=0     width=25    height=48    xoffset=0     yoffset=0     xadvance=25    page=0  chnl=15
+char id=65   x=400   y=0     width=25    height=48    xoffset=0     yoffset=0     xadvance=25    page=0  chnl=15
+char id=66   x=308   y=98    width=21    height=48    xoffset=3     yoffset=0     xadvance=25    page=0  chnl=15
+char id=67   x=110   y=147   width=20    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=68   x=352   y=98    width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=69   x=446   y=147   width=20    height=48    xoffset=3     yoffset=0     xadvance=25    page=0  chnl=15
+char id=70   x=60    y=196   width=19    height=48    xoffset=4     yoffset=0     xadvance=25    page=0  chnl=15
+char id=71   x=418   y=98    width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=72   x=440   y=98    width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=73   x=412   y=196   width=18    height=48    xoffset=3     yoffset=0     xadvance=25    page=0  chnl=15
+char id=74   x=431   y=196   width=18    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=75   x=24    y=49    width=23    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=76   x=530   y=147   width=20    height=48    xoffset=4     yoffset=0     xadvance=25    page=0  chnl=15
+char id=77   x=72    y=49    width=23    height=48    xoffset=1     yoffset=0     xadvance=25    page=0  chnl=15
+char id=78   x=528   y=98    width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=79   x=550   y=98    width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=80   x=572   y=98    width=21    height=48    xoffset=3     yoffset=0     xadvance=25    page=0  chnl=15
+char id=81   x=594   y=98    width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=82   x=192   y=49    width=23    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=83   x=616   y=98    width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=84   x=530   y=0     width=25    height=48    xoffset=0     yoffset=0     xadvance=25    page=0  chnl=15
+char id=85   x=638   y=98    width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=86   x=288   y=49    width=23    height=48    xoffset=1     yoffset=0     xadvance=25    page=0  chnl=15
+char id=87   x=108   y=0     width=26    height=48    xoffset=-1    yoffset=0     xadvance=25    page=0  chnl=15
+char id=88   x=556   y=0     width=25    height=48    xoffset=0     yoffset=0     xadvance=25    page=0  chnl=15
+char id=89   x=582   y=0     width=25    height=48    xoffset=0     yoffset=0     xadvance=25    page=0  chnl=15
+char id=90   x=742   y=49    width=22    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=91   x=860   y=196   width=11    height=48    xoffset=8     yoffset=0     xadvance=25    page=0  chnl=15
+char id=92   x=682   y=98    width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=93   x=872   y=196   width=11    height=48    xoffset=6     yoffset=0     xadvance=25    page=0  chnl=15
+char id=94   x=966   y=0     width=23    height=48    xoffset=1     yoffset=0     xadvance=25    page=0  chnl=15
+char id=95   x=788   y=49    width=22    height=48    xoffset=-1    yoffset=0     xadvance=25    page=0  chnl=15
+char id=96   x=908   y=196   width=11    height=48    xoffset=5     yoffset=0     xadvance=25    page=0  chnl=15
+char id=97   x=770   y=98    width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=98   x=635   y=147   width=20    height=48    xoffset=3     yoffset=0     xadvance=25    page=0  chnl=15
+char id=99   x=160   y=196   width=19    height=48    xoffset=3     yoffset=0     xadvance=25    page=0  chnl=15
+char id=100  x=677   y=147   width=20    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=101  x=858   y=98    width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=102  x=180   y=196   width=19    height=48    xoffset=3     yoffset=0     xadvance=25    page=0  chnl=15
+char id=103  x=593   y=147   width=20    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=104  x=140   y=196   width=19    height=48    xoffset=3     yoffset=0     xadvance=25    page=0  chnl=15
+char id=105  x=614   y=147   width=20    height=48    xoffset=3     yoffset=0     xadvance=25    page=0  chnl=15
+char id=106  x=1009  y=49    width=14    height=48    xoffset=3     yoffset=0     xadvance=25    page=0  chnl=15
+char id=107  x=902   y=98    width=21    height=48    xoffset=4     yoffset=0     xadvance=25    page=0  chnl=15
+char id=108  x=719   y=147   width=20    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=109  x=216   y=49    width=23    height=48    xoffset=1     yoffset=0     xadvance=25    page=0  chnl=15
+char id=110  x=120   y=196   width=19    height=48    xoffset=3     yoffset=0     xadvance=25    page=0  chnl=15
+char id=111  x=924   y=98    width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=112  x=740   y=147   width=20    height=48    xoffset=3     yoffset=0     xadvance=25    page=0  chnl=15
+char id=113  x=782   y=147   width=20    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=114  x=279   y=196   width=18    height=48    xoffset=6     yoffset=0     xadvance=25    page=0  chnl=15
+char id=115  x=558   y=196   width=17    height=48    xoffset=4     yoffset=0     xadvance=25    page=0  chnl=15
+char id=116  x=131   y=147   width=20    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=117  x=40    y=196   width=19    height=48    xoffset=3     yoffset=0     xadvance=25    page=0  chnl=15
+char id=118  x=696   y=49    width=22    height=48    xoffset=1     yoffset=0     xadvance=25    page=0  chnl=15
+char id=119  x=54    y=0     width=26    height=48    xoffset=-1    yoffset=0     xadvance=25    page=0  chnl=15
+char id=120  x=456   y=49    width=23    height=48    xoffset=1     yoffset=0     xadvance=25    page=0  chnl=15
+char id=121  x=504   y=49    width=23    height=48    xoffset=1     yoffset=0     xadvance=25    page=0  chnl=15
+char id=122  x=80    y=196   width=19    height=48    xoffset=3     yoffset=0     xadvance=25    page=0  chnl=15
+char id=123  x=540   y=196   width=17    height=48    xoffset=4     yoffset=0     xadvance=25    page=0  chnl=15
+char id=124  x=20    y=245   width=5     height=48    xoffset=10    yoffset=0     xadvance=25    page=0  chnl=15
+char id=125  x=522   y=196   width=17    height=48    xoffset=4     yoffset=0     xadvance=25    page=0  chnl=15
+char id=126  x=576   y=49    width=23    height=48    xoffset=1     yoffset=0     xadvance=25    page=0  chnl=15
+char id=160  x=44    y=245   width=3     height=48    xoffset=-1    yoffset=0     xadvance=25    page=0  chnl=15
+char id=161  x=32    y=245   width=5     height=48    xoffset=10    yoffset=0     xadvance=25    page=0  chnl=15
+char id=162  x=945   y=147   width=19    height=48    xoffset=3     yoffset=0     xadvance=25    page=0  chnl=15
+char id=163  x=946   y=98    width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=164  x=985   y=147   width=19    height=48    xoffset=4     yoffset=0     xadvance=25    page=0  chnl=15
+char id=165  x=270   y=0     width=25    height=48    xoffset=0     yoffset=0     xadvance=25    page=0  chnl=15
+char id=166  x=14    y=245   width=5     height=48    xoffset=10    yoffset=0     xadvance=25    page=0  chnl=15
+char id=167  x=336   y=196   width=18    height=48    xoffset=3     yoffset=0     xadvance=25    page=0  chnl=15
+char id=168  x=722   y=196   width=13    height=48    xoffset=6     yoffset=0     xadvance=25    page=0  chnl=15
+char id=169  x=135   y=0     width=26    height=48    xoffset=-1    yoffset=0     xadvance=25    page=0  chnl=15
+char id=170  x=645   y=196   width=15    height=48    xoffset=4     yoffset=0     xadvance=25    page=0  chnl=15
+char id=171  x=803   y=147   width=20    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=172  x=120   y=49    width=23    height=48    xoffset=1     yoffset=0     xadvance=25    page=0  chnl=15
+char id=173  x=847   y=196   width=12    height=48    xoffset=6     yoffset=0     xadvance=25    page=0  chnl=15
+char id=174  x=216   y=0     width=26    height=48    xoffset=-1    yoffset=0     xadvance=25    page=0  chnl=15
+char id=175  x=778   y=196   width=13    height=48    xoffset=6     yoffset=0     xadvance=25    page=0  chnl=15
+char id=176  x=692   y=196   width=14    height=48    xoffset=5     yoffset=0     xadvance=25    page=0  chnl=15
+char id=177  x=384   y=49    width=23    height=48    xoffset=1     yoffset=0     xadvance=25    page=0  chnl=15
+char id=178  x=792   y=196   width=13    height=48    xoffset=6     yoffset=0     xadvance=25    page=0  chnl=15
+char id=179  x=736   y=196   width=13    height=48    xoffset=6     yoffset=0     xadvance=25    page=0  chnl=15
+char id=180  x=884   y=196   width=11    height=48    xoffset=9     yoffset=0     xadvance=25    page=0  chnl=15
+char id=181  x=968   y=98    width=21    height=48    xoffset=3     yoffset=0     xadvance=25    page=0  chnl=15
+char id=182  x=425   y=147   width=20    height=48    xoffset=1     yoffset=0     xadvance=25    page=0  chnl=15
+char id=183  x=1006  y=196   width=7     height=48    xoffset=9     yoffset=0     xadvance=25    page=0  chnl=15
+char id=184  x=942   y=196   width=10    height=48    xoffset=7     yoffset=0     xadvance=25    page=0  chnl=15
+char id=185  x=750   y=196   width=13    height=48    xoffset=6     yoffset=0     xadvance=25    page=0  chnl=15
+char id=186  x=504   y=196   width=17    height=48    xoffset=4     yoffset=0     xadvance=25    page=0  chnl=15
+char id=187  x=824   y=147   width=20    height=48    xoffset=3     yoffset=0     xadvance=25    page=0  chnl=15
+char id=188  x=168   y=49    width=23    height=48    xoffset=0     yoffset=0     xadvance=25    page=0  chnl=15
+char id=189  x=312   y=49    width=23    height=48    xoffset=0     yoffset=0     xadvance=25    page=0  chnl=15
+char id=190  x=942   y=0     width=23    height=48    xoffset=0     yoffset=0     xadvance=25    page=0  chnl=15
+char id=191  x=393   y=196   width=18    height=48    xoffset=3     yoffset=0     xadvance=25    page=0  chnl=15
+char id=192  x=426   y=0     width=25    height=48    xoffset=0     yoffset=0     xadvance=25    page=0  chnl=15
+char id=193  x=686   y=0     width=25    height=48    xoffset=0     yoffset=0     xadvance=25    page=0  chnl=15
+char id=194  x=816   y=0     width=25    height=48    xoffset=0     yoffset=0     xadvance=25    page=0  chnl=15
+char id=195  x=790   y=0     width=25    height=48    xoffset=0     yoffset=0     xadvance=25    page=0  chnl=15
+char id=196  x=764   y=0     width=25    height=48    xoffset=0     yoffset=0     xadvance=25    page=0  chnl=15
+char id=197  x=738   y=0     width=25    height=48    xoffset=0     yoffset=0     xadvance=25    page=0  chnl=15
+char id=198  x=712   y=0     width=25    height=48    xoffset=-1    yoffset=0     xadvance=25    page=0  chnl=15
+char id=199  x=467   y=147   width=20    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=200  x=488   y=147   width=20    height=48    xoffset=3     yoffset=0     xadvance=25    page=0  chnl=15
+char id=201  x=509   y=147   width=20    height=48    xoffset=3     yoffset=0     xadvance=25    page=0  chnl=15
+char id=202  x=320   y=147   width=20    height=48    xoffset=3     yoffset=0     xadvance=25    page=0  chnl=15
+char id=203  x=551   y=147   width=20    height=48    xoffset=3     yoffset=0     xadvance=25    page=0  chnl=15
+char id=204  x=374   y=196   width=18    height=48    xoffset=3     yoffset=0     xadvance=25    page=0  chnl=15
+char id=205  x=355   y=196   width=18    height=48    xoffset=3     yoffset=0     xadvance=25    page=0  chnl=15
+char id=206  x=260   y=196   width=18    height=48    xoffset=3     yoffset=0     xadvance=25    page=0  chnl=15
+char id=207  x=298   y=196   width=18    height=48    xoffset=3     yoffset=0     xadvance=25    page=0  chnl=15
+char id=208  x=917   y=0     width=24    height=48    xoffset=-1    yoffset=0     xadvance=25    page=0  chnl=15
+char id=209  x=0     y=147   width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=210  x=22    y=147   width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=211  x=44    y=147   width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=212  x=66    y=147   width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=213  x=88    y=147   width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=214  x=811   y=49    width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=215  x=383   y=147   width=20    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=216  x=660   y=0     width=25    height=48    xoffset=0     yoffset=0     xadvance=25    page=0  chnl=15
+char id=217  x=833   y=49    width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=218  x=855   y=49    width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=219  x=877   y=49    width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=220  x=899   y=49    width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=221  x=634   y=0     width=25    height=48    xoffset=0     yoffset=0     xadvance=25    page=0  chnl=15
+char id=222  x=921   y=49    width=21    height=48    xoffset=3     yoffset=0     xadvance=25    page=0  chnl=15
+char id=223  x=286   y=98    width=21    height=48    xoffset=3     yoffset=0     xadvance=25    page=0  chnl=15
+char id=224  x=943   y=49    width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=225  x=965   y=49    width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=226  x=987   y=49    width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=227  x=0     y=98    width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=228  x=66    y=98    width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=229  x=88    y=98    width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=230  x=608   y=0     width=25    height=48    xoffset=0     yoffset=0     xadvance=25    page=0  chnl=15
+char id=231  x=200   y=196   width=19    height=48    xoffset=3     yoffset=0     xadvance=25    page=0  chnl=15
+char id=232  x=132   y=98    width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=233  x=242   y=98    width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=234  x=264   y=98    width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=235  x=330   y=98    width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=236  x=215   y=147   width=20    height=48    xoffset=3     yoffset=0     xadvance=25    page=0  chnl=15
+char id=237  x=236   y=147   width=20    height=48    xoffset=3     yoffset=0     xadvance=25    page=0  chnl=15
+char id=238  x=257   y=147   width=20    height=48    xoffset=3     yoffset=0     xadvance=25    page=0  chnl=15
+char id=239  x=278   y=147   width=20    height=48    xoffset=3     yoffset=0     xadvance=25    page=0  chnl=15
+char id=240  x=374   y=98    width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=241  x=220   y=196   width=19    height=48    xoffset=3     yoffset=0     xadvance=25    page=0  chnl=15
+char id=242  x=396   y=98    width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=243  x=462   y=98    width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=244  x=484   y=98    width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=245  x=506   y=98    width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=246  x=660   y=98    width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=247  x=264   y=49    width=23    height=48    xoffset=1     yoffset=0     xadvance=25    page=0  chnl=15
+char id=248  x=892   y=0     width=24    height=48    xoffset=0     yoffset=0     xadvance=25    page=0  chnl=15
+char id=249  x=240   y=196   width=19    height=48    xoffset=3     yoffset=0     xadvance=25    page=0  chnl=15
+char id=250  x=845   y=147   width=19    height=48    xoffset=3     yoffset=0     xadvance=25    page=0  chnl=15
+char id=251  x=865   y=147   width=19    height=48    xoffset=3     yoffset=0     xadvance=25    page=0  chnl=15
+char id=252  x=885   y=147   width=19    height=48    xoffset=3     yoffset=0     xadvance=25    page=0  chnl=15
+char id=253  x=144   y=49    width=23    height=48    xoffset=1     yoffset=0     xadvance=25    page=0  chnl=15
+char id=254  x=698   y=147   width=20    height=48    xoffset=3     yoffset=0     xadvance=25    page=0  chnl=15
+char id=255  x=96    y=49    width=23    height=48    xoffset=1     yoffset=0     xadvance=25    page=0  chnl=15
+char id=262  x=299   y=147   width=20    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=263  x=905   y=147   width=19    height=48    xoffset=3     yoffset=0     xadvance=25    page=0  chnl=15
+char id=268  x=404   y=147   width=20    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=269  x=965   y=147   width=19    height=48    xoffset=3     yoffset=0     xadvance=25    page=0  chnl=15
+char id=273  x=990   y=0     width=23    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=286  x=704   y=98    width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=287  x=656   y=147   width=20    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=304  x=1005  y=147   width=18    height=48    xoffset=3     yoffset=0     xadvance=25    page=0  chnl=15
+char id=305  x=572   y=147   width=20    height=48    xoffset=3     yoffset=0     xadvance=25    page=0  chnl=15
+char id=321  x=504   y=0     width=25    height=48    xoffset=-1    yoffset=0     xadvance=25    page=0  chnl=15
+char id=322  x=726   y=98    width=21    height=48    xoffset=1     yoffset=0     xadvance=25    page=0  chnl=15
+char id=338  x=867   y=0     width=24    height=48    xoffset=1     yoffset=0     xadvance=25    page=0  chnl=15
+char id=339  x=478   y=0     width=25    height=48    xoffset=0     yoffset=0     xadvance=25    page=0  chnl=15
+char id=350  x=748   y=98    width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=351  x=450   y=196   width=17    height=48    xoffset=4     yoffset=0     xadvance=25    page=0  chnl=15
+char id=352  x=792   y=98    width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=353  x=468   y=196   width=17    height=48    xoffset=4     yoffset=0     xadvance=25    page=0  chnl=15
+char id=376  x=452   y=0     width=25    height=48    xoffset=0     yoffset=0     xadvance=25    page=0  chnl=15
+char id=381  x=765   y=49    width=22    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=382  x=20    y=196   width=19    height=48    xoffset=3     yoffset=0     xadvance=25    page=0  chnl=15
+char id=402  x=0     y=0     width=26    height=48    xoffset=-1    yoffset=0     xadvance=25    page=0  chnl=15
+char id=710  x=677   y=196   width=14    height=48    xoffset=5     yoffset=0     xadvance=25    page=0  chnl=15
+char id=711  x=707   y=196   width=14    height=48    xoffset=5     yoffset=0     xadvance=25    page=0  chnl=15
+char id=728  x=629   y=196   width=15    height=48    xoffset=5     yoffset=0     xadvance=25    page=0  chnl=15
+char id=729  x=26    y=245   width=5     height=48    xoffset=10    yoffset=0     xadvance=25    page=0  chnl=15
+char id=730  x=806   y=196   width=13    height=48    xoffset=6     yoffset=0     xadvance=25    page=0  chnl=15
+char id=731  x=1014  y=0     width=9     height=48    xoffset=8     yoffset=0     xadvance=25    page=0  chnl=15
+char id=732  x=661   y=196   width=15    height=48    xoffset=5     yoffset=0     xadvance=25    page=0  chnl=15
+char id=733  x=612   y=196   width=16    height=48    xoffset=6     yoffset=0     xadvance=25    page=0  chnl=15
+char id=937  x=552   y=49    width=23    height=48    xoffset=1     yoffset=0     xadvance=25    page=0  chnl=15
+char id=960  x=528   y=49    width=23    height=48    xoffset=1     yoffset=0     xadvance=25    page=0  chnl=15
+char id=8211 x=189   y=0     width=26    height=48    xoffset=-1    yoffset=0     xadvance=25    page=0  chnl=15
+char id=8212 x=27    y=0     width=26    height=48    xoffset=-1    yoffset=0     xadvance=25    page=0  chnl=15
+char id=8216 x=962   y=196   width=8     height=48    xoffset=9     yoffset=0     xadvance=25    page=0  chnl=15
+char id=8217 x=989   y=196   width=8     height=48    xoffset=9     yoffset=0     xadvance=25    page=0  chnl=15
+char id=8218 x=980   y=196   width=8     height=48    xoffset=8     yoffset=0     xadvance=25    page=0  chnl=15
+char id=8220 x=576   y=196   width=17    height=48    xoffset=4     yoffset=0     xadvance=25    page=0  chnl=15
+char id=8221 x=486   y=196   width=17    height=48    xoffset=4     yoffset=0     xadvance=25    page=0  chnl=15
+char id=8222 x=594   y=196   width=17    height=48    xoffset=4     yoffset=0     xadvance=25    page=0  chnl=15
+char id=8224 x=194   y=147   width=20    height=48    xoffset=3     yoffset=0     xadvance=25    page=0  chnl=15
+char id=8225 x=173   y=147   width=20    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=8226 x=764   y=196   width=13    height=48    xoffset=6     yoffset=0     xadvance=25    page=0  chnl=15
+char id=8230 x=408   y=49    width=23    height=48    xoffset=1     yoffset=0     xadvance=25    page=0  chnl=15
+char id=8240 x=243   y=0     width=26    height=48    xoffset=-1    yoffset=0     xadvance=25    page=0  chnl=15
+char id=8249 x=896   y=196   width=11    height=48    xoffset=6     yoffset=0     xadvance=25    page=0  chnl=15
+char id=8250 x=1012  y=98    width=11    height=48    xoffset=8     yoffset=0     xadvance=25    page=0  chnl=15
+char id=8364 x=719   y=49    width=22    height=48    xoffset=0     yoffset=0     xadvance=25    page=0  chnl=15
+char id=8482 x=842   y=0     width=24    height=48    xoffset=-1    yoffset=0     xadvance=25    page=0  chnl=15
+char id=8706 x=100   y=196   width=19    height=48    xoffset=3     yoffset=0     xadvance=25    page=0  chnl=15
+char id=8710 x=162   y=0     width=26    height=48    xoffset=-1    yoffset=0     xadvance=25    page=0  chnl=15
+char id=8719 x=761   y=147   width=20    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=8721 x=814   y=98    width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=8722 x=360   y=49    width=23    height=48    xoffset=1     yoffset=0     xadvance=25    page=0  chnl=15
+char id=8725 x=836   y=98    width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=8729 x=998   y=196   width=7     height=48    xoffset=9     yoffset=0     xadvance=25    page=0  chnl=15
+char id=8730 x=240   y=49    width=23    height=48    xoffset=1     yoffset=0     xadvance=25    page=0  chnl=15
+char id=8734 x=374   y=0     width=25    height=48    xoffset=0     yoffset=0     xadvance=25    page=0  chnl=15
+char id=8747 x=880   y=98    width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
+char id=8776 x=48    y=49    width=23    height=48    xoffset=1     yoffset=0     xadvance=25    page=0  chnl=15
+char id=8800 x=0     y=49    width=23    height=48    xoffset=1     yoffset=0     xadvance=25    page=0  chnl=15
+char id=8804 x=480   y=49    width=23    height=48    xoffset=1     yoffset=0     xadvance=25    page=0  chnl=15
+char id=8805 x=672   y=49    width=23    height=48    xoffset=1     yoffset=0     xadvance=25    page=0  chnl=15
+char id=9674 x=990   y=98    width=21    height=48    xoffset=2     yoffset=0     xadvance=25    page=0  chnl=15
diff --git a/font/vera_sans_mono48_0.png b/font/vera_sans_mono48_0.png
new file mode 100644
index 0000000..f5c4b9d
Binary files /dev/null and b/font/vera_sans_mono48_0.png differ
diff --git a/font/vera_sans_mono64.bmfc b/font/vera_sans_mono64.bmfc
new file mode 100644
index 0000000..e2f0166
--- /dev/null
+++ b/font/vera_sans_mono64.bmfc
@@ -0,0 +1,57 @@
+# AngelCode Bitmap Font Generator configuration file
+fileVersion=1
+
+# font settings
+fontName=Bitstream Vera Sans Mono
+fontFile=VeraMoBd.ttf
+charSet=0
+fontSize=64
+aa=1
+scaleH=100
+useSmoothing=1
+isBold=0
+isItalic=0
+useUnicode=1
+disableBoxChars=1
+outputInvalidCharGlyph=0
+dontIncludeKerningPairs=0
+useHinting=1
+renderFromOutline=0
+useClearType=1
+
+# character alignment
+paddingDown=0
+paddingUp=0
+paddingRight=0
+paddingLeft=0
+spacingHoriz=1
+spacingVert=1
+useFixedHeight=1
+forceZero=0
+
+# output file
+outWidth=1024
+outHeight=512
+outBitDepth=32
+fontDescFormat=0
+fourChnlPacked=0
+textureFormat=png
+textureCompression=0
+alphaChnl=1
+redChnl=0
+greenChnl=0
+blueChnl=0
+invA=0
+invR=0
+invG=0
+invB=0
+
+# outline
+outlineThickness=0
+
+# selected chars
+chars=32-126,160-255,262-263,268-269,273,286-287,304-305,321-322,338-339,350-353,376,381-382,402,710-711
+chars=728-733,937,960,8211-8212,8216-8218,8220-8222,8224-8226,8230,8240,8249-8250,8364,8482,8706,8710
+chars=8719,8721-8722,8725,8729-8730,8734,8747,8776,8800,8804-8805,9674
+
+# imported icon images
diff --git a/font/vera_sans_mono64.fnt b/font/vera_sans_mono64.fnt
new file mode 100644
index 0000000..17c611c
--- /dev/null
+++ b/font/vera_sans_mono64.fnt
@@ -0,0 +1,258 @@
+info face="Bitstream Vera Sans Mono" size=64 bold=0 italic=0 charset="" unicode=1 stretchH=100 smooth=1 aa=1 padding=0,0,0,0 spacing=1,1 outline=0
+common lineHeight=64 base=51 scaleW=1024 scaleH=512 pages=1 packed=0 alphaChnl=1 redChnl=0 greenChnl=0 blueChnl=0
+page id=0 file="vera_sans_mono64_0.png"
+chars count=254
+char id=32   x=1017  y=195   width=3     height=64    xoffset=-1    yoffset=0     xadvance=33    page=0  chnl=15
+char id=33   x=793   y=390   width=9     height=64    xoffset=12    yoffset=0     xadvance=33    page=0  chnl=15
+char id=34   x=154   y=390   width=23    height=64    xoffset=5     yoffset=0     xadvance=33    page=0  chnl=15
+char id=35   x=36    y=0     width=35    height=64    xoffset=-1    yoffset=0     xadvance=33    page=0  chnl=15
+char id=36   x=360   y=325   width=26    height=64    xoffset=4     yoffset=0     xadvance=33    page=0  chnl=15
+char id=37   x=0     y=65    width=33    height=64    xoffset=0     yoffset=0     xadvance=33    page=0  chnl=15
+char id=38   x=430   y=0     width=34    height=64    xoffset=0     yoffset=0     xadvance=33    page=0  chnl=15
+char id=39   x=803   y=390   width=9     height=64    xoffset=12    yoffset=0     xadvance=33    page=0  chnl=15
+char id=40   x=631   y=390   width=15    height=64    xoffset=10    yoffset=0     xadvance=33    page=0  chnl=15
+char id=41   x=548   y=390   width=16    height=64    xoffset=8     yoffset=0     xadvance=33    page=0  chnl=15
+char id=42   x=943   y=260   width=27    height=64    xoffset=3     yoffset=0     xadvance=33    page=0  chnl=15
+char id=43   x=493   y=65    width=31    height=64    xoffset=1     yoffset=0     xadvance=33    page=0  chnl=15
+char id=44   x=1010  y=0     width=13    height=64    xoffset=9     yoffset=0     xadvance=33    page=0  chnl=15
+char id=45   x=353   y=390   width=19    height=64    xoffset=7     yoffset=0     xadvance=33    page=0  chnl=15
+char id=46   x=769   y=390   width=11    height=64    xoffset=11    yoffset=0     xadvance=33    page=0  chnl=15
+char id=47   x=548   y=130   width=29    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=48   x=203   y=260   width=28    height=64    xoffset=3     yoffset=0     xadvance=33    page=0  chnl=15
+char id=49   x=0     y=325   width=27    height=64    xoffset=4     yoffset=0     xadvance=33    page=0  chnl=15
+char id=50   x=56    y=325   width=27    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=51   x=196   y=325   width=27    height=64    xoffset=3     yoffset=0     xadvance=33    page=0  chnl=15
+char id=52   x=698   y=130   width=29    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=53   x=224   y=325   width=27    height=64    xoffset=3     yoffset=0     xadvance=33    page=0  chnl=15
+char id=54   x=29    y=260   width=28    height=64    xoffset=3     yoffset=0     xadvance=33    page=0  chnl=15
+char id=55   x=831   y=260   width=27    height=64    xoffset=3     yoffset=0     xadvance=33    page=0  chnl=15
+char id=56   x=551   y=260   width=27    height=64    xoffset=3     yoffset=0     xadvance=33    page=0  chnl=15
+char id=57   x=232   y=260   width=28    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=58   x=757   y=390   width=11    height=64    xoffset=11    yoffset=0     xadvance=33    page=0  chnl=15
+char id=59   x=677   y=390   width=13    height=64    xoffset=9     yoffset=0     xadvance=33    page=0  chnl=15
+char id=60   x=938   y=130   width=29    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=61   x=968   y=130   width=29    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=62   x=0     y=195   width=29    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=63   x=999   y=260   width=24    height=64    xoffset=5     yoffset=0     xadvance=33    page=0  chnl=15
+char id=64   x=34    y=65    width=33    height=64    xoffset=-1    yoffset=0     xadvance=33    page=0  chnl=15
+char id=65   x=500   y=0     width=33    height=64    xoffset=0     yoffset=0     xadvance=33    page=0  chnl=15
+char id=66   x=60    y=195   width=29    height=64    xoffset=3     yoffset=0     xadvance=33    page=0  chnl=15
+char id=67   x=635   y=260   width=27    height=64    xoffset=3     yoffset=0     xadvance=33    page=0  chnl=15
+char id=68   x=174   y=260   width=28    height=64    xoffset=3     yoffset=0     xadvance=33    page=0  chnl=15
+char id=69   x=252   y=325   width=26    height=64    xoffset=4     yoffset=0     xadvance=33    page=0  chnl=15
+char id=70   x=663   y=260   width=27    height=64    xoffset=4     yoffset=0     xadvance=33    page=0  chnl=15
+char id=71   x=210   y=195   width=29    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=72   x=691   y=260   width=27    height=64    xoffset=3     yoffset=0     xadvance=33    page=0  chnl=15
+char id=73   x=992   y=325   width=25    height=64    xoffset=4     yoffset=0     xadvance=33    page=0  chnl=15
+char id=74   x=414   y=325   width=26    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=75   x=781   y=65    width=31    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=76   x=719   y=260   width=27    height=64    xoffset=5     yoffset=0     xadvance=33    page=0  chnl=15
+char id=77   x=360   y=195   width=29    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=78   x=390   y=195   width=29    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=79   x=420   y=195   width=29    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=80   x=116   y=260   width=28    height=64    xoffset=4     yoffset=0     xadvance=33    page=0  chnl=15
+char id=81   x=480   y=195   width=29    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=82   x=397   y=65    width=31    height=64    xoffset=3     yoffset=0     xadvance=33    page=0  chnl=15
+char id=83   x=0     y=260   width=28    height=64    xoffset=3     yoffset=0     xadvance=33    page=0  chnl=15
+char id=84   x=540   y=195   width=29    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=85   x=570   y=195   width=29    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=86   x=461   y=65    width=31    height=64    xoffset=1     yoffset=0     xadvance=33    page=0  chnl=15
+char id=87   x=288   y=0     width=35    height=64    xoffset=-1    yoffset=0     xadvance=33    page=0  chnl=15
+char id=88   x=874   y=0     width=33    height=64    xoffset=0     yoffset=0     xadvance=33    page=0  chnl=15
+char id=89   x=908   y=0     width=33    height=64    xoffset=0     yoffset=0     xadvance=33    page=0  chnl=15
+char id=90   x=937   y=65    width=30    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=91   x=599   y=390   width=15    height=64    xoffset=11    yoffset=0     xadvance=33    page=0  chnl=15
+char id=92   x=630   y=195   width=29    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=93   x=531   y=390   width=16    height=64    xoffset=7     yoffset=0     xadvance=33    page=0  chnl=15
+char id=94   x=365   y=65    width=31    height=64    xoffset=1     yoffset=0     xadvance=33    page=0  chnl=15
+char id=95   x=324   y=0     width=35    height=64    xoffset=-1    yoffset=0     xadvance=33    page=0  chnl=15
+char id=96   x=565   y=390   width=16    height=64    xoffset=5     yoffset=0     xadvance=33    page=0  chnl=15
+char id=97   x=750   y=195   width=29    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=98   x=988   y=195   width=28    height=64    xoffset=3     yoffset=0     xadvance=33    page=0  chnl=15
+char id=99   x=26    y=390   width=25    height=64    xoffset=4     yoffset=0     xadvance=33    page=0  chnl=15
+char id=100  x=290   y=260   width=28    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=101  x=875   y=65    width=30    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=102  x=522   y=325   width=26    height=64    xoffset=4     yoffset=0     xadvance=33    page=0  chnl=15
+char id=103  x=522   y=260   width=28    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=104  x=966   y=325   width=25    height=64    xoffset=4     yoffset=0     xadvance=33    page=0  chnl=15
+char id=105  x=870   y=195   width=29    height=64    xoffset=3     yoffset=0     xadvance=33    page=0  chnl=15
+char id=106  x=310   y=390   width=21    height=64    xoffset=3     yoffset=0     xadvance=33    page=0  chnl=15
+char id=107  x=959   y=195   width=28    height=64    xoffset=4     yoffset=0     xadvance=33    page=0  chnl=15
+char id=108  x=58    y=260   width=28    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=109  x=557   y=65    width=31    height=64    xoffset=1     yoffset=0     xadvance=33    page=0  chnl=15
+char id=110  x=940   y=325   width=25    height=64    xoffset=4     yoffset=0     xadvance=33    page=0  chnl=15
+char id=111  x=900   y=195   width=29    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=112  x=87    y=260   width=28    height=64    xoffset=3     yoffset=0     xadvance=33    page=0  chnl=15
+char id=113  x=145   y=260   width=28    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=114  x=914   y=325   width=25    height=64    xoffset=7     yoffset=0     xadvance=33    page=0  chnl=15
+char id=115  x=888   y=325   width=25    height=64    xoffset=4     yoffset=0     xadvance=33    page=0  chnl=15
+char id=116  x=930   y=195   width=28    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=117  x=862   y=325   width=25    height=64    xoffset=4     yoffset=0     xadvance=33    page=0  chnl=15
+char id=118  x=906   y=65    width=30    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=119  x=180   y=0     width=35    height=64    xoffset=-1    yoffset=0     xadvance=33    page=0  chnl=15
+char id=120  x=621   y=65    width=31    height=64    xoffset=1     yoffset=0     xadvance=33    page=0  chnl=15
+char id=121  x=653   y=65    width=31    height=64    xoffset=1     yoffset=0     xadvance=33    page=0  chnl=15
+char id=122  x=333   y=325   width=26    height=64    xoffset=4     yoffset=0     xadvance=33    page=0  chnl=15
+char id=123  x=836   y=325   width=25    height=64    xoffset=4     yoffset=0     xadvance=33    page=0  chnl=15
+char id=124  x=841   y=390   width=7     height=64    xoffset=13    yoffset=0     xadvance=33    page=0  chnl=15
+char id=125  x=784   y=325   width=25    height=64    xoffset=4     yoffset=0     xadvance=33    page=0  chnl=15
+char id=126  x=248   y=130   width=29    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=160  x=1018  y=325   width=3     height=64    xoffset=-1    yoffset=0     xadvance=33    page=0  chnl=15
+char id=161  x=813   y=390   width=9     height=64    xoffset=12    yoffset=0     xadvance=33    page=0  chnl=15
+char id=162  x=441   y=325   width=26    height=64    xoffset=3     yoffset=0     xadvance=33    page=0  chnl=15
+char id=163  x=278   y=130   width=29    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=164  x=84    y=325   width=27    height=64    xoffset=4     yoffset=0     xadvance=33    page=0  chnl=15
+char id=165  x=636   y=0     width=33    height=64    xoffset=0     yoffset=0     xadvance=33    page=0  chnl=15
+char id=166  x=833   y=390   width=7     height=64    xoffset=13    yoffset=0     xadvance=33    page=0  chnl=15
+char id=167  x=732   y=325   width=25    height=64    xoffset=4     yoffset=0     xadvance=33    page=0  chnl=15
+char id=168  x=433   y=390   width=19    height=64    xoffset=7     yoffset=0     xadvance=33    page=0  chnl=15
+char id=169  x=72    y=0     width=35    height=64    xoffset=-1    yoffset=0     xadvance=33    page=0  chnl=15
+char id=170  x=200   y=390   width=21    height=64    xoffset=6     yoffset=0     xadvance=33    page=0  chnl=15
+char id=171  x=859   y=260   width=27    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=172  x=308   y=130   width=29    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=173  x=373   y=390   width=19    height=64    xoffset=7     yoffset=0     xadvance=33    page=0  chnl=15
+char id=174  x=252   y=0     width=35    height=64    xoffset=-1    yoffset=0     xadvance=33    page=0  chnl=15
+char id=175  x=473   y=390   width=19    height=64    xoffset=7     yoffset=0     xadvance=33    page=0  chnl=15
+char id=176  x=413   y=390   width=19    height=64    xoffset=7     yoffset=0     xadvance=33    page=0  chnl=15
+char id=177  x=186   y=130   width=30    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=178  x=453   y=390   width=19    height=64    xoffset=7     yoffset=0     xadvance=33    page=0  chnl=15
+char id=179  x=332   y=390   width=20    height=64    xoffset=7     yoffset=0     xadvance=33    page=0  chnl=15
+char id=180  x=582   y=390   width=16    height=64    xoffset=12    yoffset=0     xadvance=33    page=0  chnl=15
+char id=181  x=493   y=260   width=28    height=64    xoffset=4     yoffset=0     xadvance=33    page=0  chnl=15
+char id=182  x=319   y=260   width=28    height=64    xoffset=1     yoffset=0     xadvance=33    page=0  chnl=15
+char id=183  x=745   y=390   width=11    height=64    xoffset=11    yoffset=0     xadvance=33    page=0  chnl=15
+char id=184  x=647   y=390   width=14    height=64    xoffset=9     yoffset=0     xadvance=33    page=0  chnl=15
+char id=185  x=493   y=390   width=19    height=64    xoffset=8     yoffset=0     xadvance=33    page=0  chnl=15
+char id=186  x=288   y=390   width=21    height=64    xoffset=7     yoffset=0     xadvance=33    page=0  chnl=15
+char id=187  x=28    y=325   width=27    height=64    xoffset=4     yoffset=0     xadvance=33    page=0  chnl=15
+char id=188  x=62    y=130   width=30    height=64    xoffset=1     yoffset=0     xadvance=33    page=0  chnl=15
+char id=189  x=93    y=130   width=30    height=64    xoffset=1     yoffset=0     xadvance=33    page=0  chnl=15
+char id=190  x=813   y=65    width=30    height=64    xoffset=1     yoffset=0     xadvance=33    page=0  chnl=15
+char id=191  x=999   y=65    width=24    height=64    xoffset=4     yoffset=0     xadvance=33    page=0  chnl=15
+char id=192  x=670   y=0     width=33    height=64    xoffset=0     yoffset=0     xadvance=33    page=0  chnl=15
+char id=193  x=738   y=0     width=33    height=64    xoffset=0     yoffset=0     xadvance=33    page=0  chnl=15
+char id=194  x=534   y=0     width=33    height=64    xoffset=0     yoffset=0     xadvance=33    page=0  chnl=15
+char id=195  x=772   y=0     width=33    height=64    xoffset=0     yoffset=0     xadvance=33    page=0  chnl=15
+char id=196  x=806   y=0     width=33    height=64    xoffset=0     yoffset=0     xadvance=33    page=0  chnl=15
+char id=197  x=102   y=65    width=33    height=64    xoffset=0     yoffset=0     xadvance=33    page=0  chnl=15
+char id=198  x=68    y=65    width=33    height=64    xoffset=-1    yoffset=0     xadvance=33    page=0  chnl=15
+char id=199  x=168   y=325   width=27    height=64    xoffset=3     yoffset=0     xadvance=33    page=0  chnl=15
+char id=200  x=468   y=325   width=26    height=64    xoffset=4     yoffset=0     xadvance=33    page=0  chnl=15
+char id=201  x=495   y=325   width=26    height=64    xoffset=4     yoffset=0     xadvance=33    page=0  chnl=15
+char id=202  x=387   y=325   width=26    height=64    xoffset=4     yoffset=0     xadvance=33    page=0  chnl=15
+char id=203  x=279   y=325   width=26    height=64    xoffset=4     yoffset=0     xadvance=33    page=0  chnl=15
+char id=204  x=0     y=390   width=25    height=64    xoffset=4     yoffset=0     xadvance=33    page=0  chnl=15
+char id=205  x=52    y=390   width=25    height=64    xoffset=4     yoffset=0     xadvance=33    page=0  chnl=15
+char id=206  x=78    y=390   width=25    height=64    xoffset=4     yoffset=0     xadvance=33    page=0  chnl=15
+char id=207  x=104   y=390   width=25    height=64    xoffset=4     yoffset=0     xadvance=33    page=0  chnl=15
+char id=208  x=202   y=65    width=32    height=64    xoffset=-1    yoffset=0     xadvance=33    page=0  chnl=15
+char id=209  x=368   y=130   width=29    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=210  x=398   y=130   width=29    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=211  x=428   y=130   width=29    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=212  x=458   y=130   width=29    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=213  x=488   y=130   width=29    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=214  x=518   y=130   width=29    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=215  x=578   y=130   width=29    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=216  x=465   y=0     width=34    height=64    xoffset=-1    yoffset=0     xadvance=33    page=0  chnl=15
+char id=217  x=608   y=130   width=29    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=218  x=638   y=130   width=29    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=219  x=668   y=130   width=29    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=220  x=728   y=130   width=29    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=221  x=976   y=0     width=33    height=64    xoffset=0     yoffset=0     xadvance=33    page=0  chnl=15
+char id=222  x=261   y=260   width=28    height=64    xoffset=4     yoffset=0     xadvance=33    page=0  chnl=15
+char id=223  x=30    y=195   width=29    height=64    xoffset=3     yoffset=0     xadvance=33    page=0  chnl=15
+char id=224  x=758   y=130   width=29    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=225  x=788   y=130   width=29    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=226  x=818   y=130   width=29    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=227  x=848   y=130   width=29    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=228  x=878   y=130   width=29    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=229  x=908   y=130   width=29    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=230  x=942   y=0     width=33    height=64    xoffset=0     yoffset=0     xadvance=33    page=0  chnl=15
+char id=231  x=998   y=130   width=25    height=64    xoffset=4     yoffset=0     xadvance=33    page=0  chnl=15
+char id=232  x=217   y=130   width=30    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=233  x=155   y=130   width=30    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=234  x=968   y=65    width=30    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=235  x=31    y=130   width=30    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=236  x=90    y=195   width=29    height=64    xoffset=3     yoffset=0     xadvance=33    page=0  chnl=15
+char id=237  x=120   y=195   width=29    height=64    xoffset=3     yoffset=0     xadvance=33    page=0  chnl=15
+char id=238  x=150   y=195   width=29    height=64    xoffset=3     yoffset=0     xadvance=33    page=0  chnl=15
+char id=239  x=180   y=195   width=29    height=64    xoffset=3     yoffset=0     xadvance=33    page=0  chnl=15
+char id=240  x=240   y=195   width=29    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=241  x=576   y=325   width=25    height=64    xoffset=4     yoffset=0     xadvance=33    page=0  chnl=15
+char id=242  x=270   y=195   width=29    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=243  x=300   y=195   width=29    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=244  x=330   y=195   width=29    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=245  x=450   y=195   width=29    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=246  x=510   y=195   width=29    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=247  x=301   y=65    width=31    height=64    xoffset=1     yoffset=0     xadvance=33    page=0  chnl=15
+char id=248  x=840   y=0     width=33    height=64    xoffset=0     yoffset=0     xadvance=33    page=0  chnl=15
+char id=249  x=602   y=325   width=25    height=64    xoffset=4     yoffset=0     xadvance=33    page=0  chnl=15
+char id=250  x=628   y=325   width=25    height=64    xoffset=4     yoffset=0     xadvance=33    page=0  chnl=15
+char id=251  x=654   y=325   width=25    height=64    xoffset=4     yoffset=0     xadvance=33    page=0  chnl=15
+char id=252  x=680   y=325   width=25    height=64    xoffset=4     yoffset=0     xadvance=33    page=0  chnl=15
+char id=253  x=749   y=65    width=31    height=64    xoffset=1     yoffset=0     xadvance=33    page=0  chnl=15
+char id=254  x=464   y=260   width=28    height=64    xoffset=3     yoffset=0     xadvance=33    page=0  chnl=15
+char id=255  x=717   y=65    width=31    height=64    xoffset=1     yoffset=0     xadvance=33    page=0  chnl=15
+char id=262  x=112   y=325   width=27    height=64    xoffset=3     yoffset=0     xadvance=33    page=0  chnl=15
+char id=263  x=140   y=325   width=27    height=64    xoffset=4     yoffset=0     xadvance=33    page=0  chnl=15
+char id=268  x=348   y=260   width=28    height=64    xoffset=3     yoffset=0     xadvance=33    page=0  chnl=15
+char id=269  x=549   y=325   width=26    height=64    xoffset=4     yoffset=0     xadvance=33    page=0  chnl=15
+char id=273  x=235   y=65    width=32    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=286  x=600   y=195   width=29    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=287  x=435   y=260   width=28    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=304  x=758   y=325   width=25    height=64    xoffset=4     yoffset=0     xadvance=33    page=0  chnl=15
+char id=305  x=660   y=195   width=29    height=64    xoffset=3     yoffset=0     xadvance=33    page=0  chnl=15
+char id=321  x=395   y=0     width=34    height=64    xoffset=-2    yoffset=0     xadvance=33    page=0  chnl=15
+char id=322  x=685   y=65    width=31    height=64    xoffset=0     yoffset=0     xadvance=33    page=0  chnl=15
+char id=338  x=268   y=65    width=32    height=64    xoffset=1     yoffset=0     xadvance=33    page=0  chnl=15
+char id=339  x=602   y=0     width=33    height=64    xoffset=0     yoffset=0     xadvance=33    page=0  chnl=15
+char id=350  x=406   y=260   width=28    height=64    xoffset=3     yoffset=0     xadvance=33    page=0  chnl=15
+char id=351  x=706   y=325   width=25    height=64    xoffset=4     yoffset=0     xadvance=33    page=0  chnl=15
+char id=352  x=377   y=260   width=28    height=64    xoffset=3     yoffset=0     xadvance=33    page=0  chnl=15
+char id=353  x=810   y=325   width=25    height=64    xoffset=4     yoffset=0     xadvance=33    page=0  chnl=15
+char id=376  x=568   y=0     width=33    height=64    xoffset=0     yoffset=0     xadvance=33    page=0  chnl=15
+char id=381  x=124   y=130   width=30    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=382  x=306   y=325   width=26    height=64    xoffset=4     yoffset=0     xadvance=33    page=0  chnl=15
+char id=402  x=169   y=65    width=32    height=64    xoffset=0     yoffset=0     xadvance=33    page=0  chnl=15
+char id=710  x=178   y=390   width=21    height=64    xoffset=6     yoffset=0     xadvance=33    page=0  chnl=15
+char id=711  x=244   y=390   width=21    height=64    xoffset=6     yoffset=0     xadvance=33    page=0  chnl=15
+char id=728  x=393   y=390   width=19    height=64    xoffset=7     yoffset=0     xadvance=33    page=0  chnl=15
+char id=729  x=823   y=390   width=9     height=64    xoffset=12    yoffset=0     xadvance=33    page=0  chnl=15
+char id=730  x=513   y=390   width=17    height=64    xoffset=8     yoffset=0     xadvance=33    page=0  chnl=15
+char id=731  x=705   y=390   width=13    height=64    xoffset=11    yoffset=0     xadvance=33    page=0  chnl=15
+char id=732  x=222   y=390   width=21    height=64    xoffset=6     yoffset=0     xadvance=33    page=0  chnl=15
+char id=733  x=130   y=390   width=23    height=64    xoffset=7     yoffset=0     xadvance=33    page=0  chnl=15
+char id=937  x=690   y=195   width=29    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=960  x=704   y=0     width=33    height=64    xoffset=0     yoffset=0     xadvance=33    page=0  chnl=15
+char id=8211 x=0     y=0     width=35    height=64    xoffset=-1    yoffset=0     xadvance=33    page=0  chnl=15
+char id=8212 x=216   y=0     width=35    height=64    xoffset=-1    yoffset=0     xadvance=33    page=0  chnl=15
+char id=8216 x=732   y=390   width=12    height=64    xoffset=11    yoffset=0     xadvance=33    page=0  chnl=15
+char id=8217 x=719   y=390   width=12    height=64    xoffset=11    yoffset=0     xadvance=33    page=0  chnl=15
+char id=8218 x=691   y=390   width=13    height=64    xoffset=9     yoffset=0     xadvance=33    page=0  chnl=15
+char id=8220 x=803   y=260   width=27    height=64    xoffset=3     yoffset=0     xadvance=33    page=0  chnl=15
+char id=8221 x=775   y=260   width=27    height=64    xoffset=3     yoffset=0     xadvance=33    page=0  chnl=15
+char id=8222 x=607   y=260   width=27    height=64    xoffset=3     yoffset=0     xadvance=33    page=0  chnl=15
+char id=8224 x=579   y=260   width=27    height=64    xoffset=3     yoffset=0     xadvance=33    page=0  chnl=15
+char id=8225 x=971   y=260   width=27    height=64    xoffset=3     yoffset=0     xadvance=33    page=0  chnl=15
+char id=8226 x=266   y=390   width=21    height=64    xoffset=6     yoffset=0     xadvance=33    page=0  chnl=15
+char id=8230 x=589   y=65    width=31    height=64    xoffset=1     yoffset=0     xadvance=33    page=0  chnl=15
+char id=8240 x=108   y=0     width=35    height=64    xoffset=-1    yoffset=0     xadvance=33    page=0  chnl=15
+char id=8249 x=662   y=390   width=14    height=64    xoffset=9     yoffset=0     xadvance=33    page=0  chnl=15
+char id=8250 x=615   y=390   width=15    height=64    xoffset=10    yoffset=0     xadvance=33    page=0  chnl=15
+char id=8364 x=0     y=130   width=30    height=64    xoffset=0     yoffset=0     xadvance=33    page=0  chnl=15
+char id=8482 x=136   y=65    width=32    height=64    xoffset=-1    yoffset=0     xadvance=33    page=0  chnl=15
+char id=8706 x=747   y=260   width=27    height=64    xoffset=3     yoffset=0     xadvance=33    page=0  chnl=15
+char id=8710 x=144   y=0     width=35    height=64    xoffset=-1    yoffset=0     xadvance=33    page=0  chnl=15
+char id=8719 x=915   y=260   width=27    height=64    xoffset=3     yoffset=0     xadvance=33    page=0  chnl=15
+char id=8721 x=887   y=260   width=27    height=64    xoffset=3     yoffset=0     xadvance=33    page=0  chnl=15
+char id=8722 x=525   y=65    width=31    height=64    xoffset=1     yoffset=0     xadvance=33    page=0  chnl=15
+char id=8725 x=720   y=195   width=29    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=8729 x=781   y=390   width=11    height=64    xoffset=11    yoffset=0     xadvance=33    page=0  chnl=15
+char id=8730 x=429   y=65    width=31    height=64    xoffset=1     yoffset=0     xadvance=33    page=0  chnl=15
+char id=8734 x=360   y=0     width=34    height=64    xoffset=0     yoffset=0     xadvance=33    page=0  chnl=15
+char id=8747 x=333   y=65    width=31    height=64    xoffset=1     yoffset=0     xadvance=33    page=0  chnl=15
+char id=8776 x=780   y=195   width=29    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=8800 x=844   y=65    width=30    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=8804 x=810   y=195   width=29    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=8805 x=840   y=195   width=29    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
+char id=9674 x=338   y=130   width=29    height=64    xoffset=2     yoffset=0     xadvance=33    page=0  chnl=15
diff --git a/font/vera_sans_mono64_0.png b/font/vera_sans_mono64_0.png
new file mode 100644
index 0000000..bbae5ee
Binary files /dev/null and b/font/vera_sans_mono64_0.png differ
diff --git a/font/vera_sans_mono72.bmfc b/font/vera_sans_mono72.bmfc
new file mode 100644
index 0000000..2b4caa4
--- /dev/null
+++ b/font/vera_sans_mono72.bmfc
@@ -0,0 +1,57 @@
+# AngelCode Bitmap Font Generator configuration file
+fileVersion=1
+
+# font settings
+fontName=Bitstream Vera Sans Mono
+fontFile=VeraMoBd.ttf
+charSet=0
+fontSize=72
+aa=1
+scaleH=100
+useSmoothing=1
+isBold=0
+isItalic=0
+useUnicode=1
+disableBoxChars=1
+outputInvalidCharGlyph=0
+dontIncludeKerningPairs=0
+useHinting=1
+renderFromOutline=0
+useClearType=1
+
+# character alignment
+paddingDown=0
+paddingUp=0
+paddingRight=0
+paddingLeft=0
+spacingHoriz=1
+spacingVert=1
+useFixedHeight=1
+forceZero=0
+
+# output file
+outWidth=1024
+outHeight=1024
+outBitDepth=32
+fontDescFormat=0
+fourChnlPacked=0
+textureFormat=png
+textureCompression=0
+alphaChnl=1
+redChnl=0
+greenChnl=0
+blueChnl=0
+invA=0
+invR=0
+invG=0
+invB=0
+
+# outline
+outlineThickness=0
+
+# selected chars
+chars=32-126,160-255,262-263,268-269,273,286-287,304-305,321-322,338-339,350-353,376,381-382,402,710-711
+chars=728-733,937,960,8211-8212,8216-8218,8220-8222,8224-8226,8230,8240,8249-8250,8364,8482,8706,8710
+chars=8719,8721-8722,8725,8729-8730,8734,8747,8776,8800,8804-8805,9674
+
+# imported icon images
diff --git a/font/vera_sans_mono72.fnt b/font/vera_sans_mono72.fnt
new file mode 100644
index 0000000..4676107
--- /dev/null
+++ b/font/vera_sans_mono72.fnt
@@ -0,0 +1,258 @@
+info face="Bitstream Vera Sans Mono" size=72 bold=0 italic=0 charset="" unicode=1 stretchH=100 smooth=1 aa=1 padding=0,0,0,0 spacing=1,1 outline=0
+common lineHeight=72 base=57 scaleW=1024 scaleH=1024 pages=1 packed=0 alphaChnl=1 redChnl=0 greenChnl=0 blueChnl=0
+page id=0 file="vera_sans_mono72_0.png"
+chars count=254
+char id=32   x=1020  y=0     width=3     height=72    xoffset=-1    yoffset=0     xadvance=37    page=0  chnl=15
+char id=33   x=609   y=511   width=9     height=72    xoffset=14    yoffset=0     xadvance=37    page=0  chnl=15
+char id=34   x=922   y=438   width=25    height=72    xoffset=6     yoffset=0     xadvance=37    page=0  chnl=15
+char id=35   x=80    y=0     width=39    height=72    xoffset=-1    yoffset=0     xadvance=37    page=0  chnl=15
+char id=36   x=689   y=365   width=30    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=37   x=479   y=0     width=38    height=72    xoffset=0     yoffset=0     xadvance=37    page=0  chnl=15
+char id=38   x=518   y=0     width=38    height=72    xoffset=0     yoffset=0     xadvance=37    page=0  chnl=15
+char id=39   x=599   y=511   width=9     height=72    xoffset=14    yoffset=0     xadvance=37    page=0  chnl=15
+char id=40   x=334   y=511   width=18    height=72    xoffset=11    yoffset=0     xadvance=37    page=0  chnl=15
+char id=41   x=372   y=511   width=17    height=72    xoffset=9     yoffset=0     xadvance=37    page=0  chnl=15
+char id=42   x=753   y=292   width=31    height=72    xoffset=3     yoffset=0     xadvance=37    page=0  chnl=15
+char id=43   x=884   y=73    width=35    height=72    xoffset=1     yoffset=0     xadvance=37    page=0  chnl=15
+char id=44   x=460   y=511   width=15    height=72    xoffset=10    yoffset=0     xadvance=37    page=0  chnl=15
+char id=45   x=206   y=511   width=21    height=72    xoffset=8     yoffset=0     xadvance=37    page=0  chnl=15
+char id=46   x=564   y=511   width=11    height=72    xoffset=13    yoffset=0     xadvance=37    page=0  chnl=15
+char id=47   x=879   y=219   width=32    height=72    xoffset=3     yoffset=0     xadvance=37    page=0  chnl=15
+char id=48   x=785   y=292   width=31    height=72    xoffset=3     yoffset=0     xadvance=37    page=0  chnl=15
+char id=49   x=472   y=365   width=30    height=72    xoffset=5     yoffset=0     xadvance=37    page=0  chnl=15
+char id=50   x=534   y=365   width=30    height=72    xoffset=3     yoffset=0     xadvance=37    page=0  chnl=15
+char id=51   x=817   y=292   width=31    height=72    xoffset=3     yoffset=0     xadvance=37    page=0  chnl=15
+char id=52   x=623   y=146   width=33    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=53   x=844   y=365   width=30    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=54   x=780   y=219   width=32    height=72    xoffset=3     yoffset=0     xadvance=37    page=0  chnl=15
+char id=55   x=813   y=365   width=30    height=72    xoffset=3     yoffset=0     xadvance=37    page=0  chnl=15
+char id=56   x=849   y=292   width=31    height=72    xoffset=3     yoffset=0     xadvance=37    page=0  chnl=15
+char id=57   x=881   y=292   width=31    height=72    xoffset=3     yoffset=0     xadvance=37    page=0  chnl=15
+char id=58   x=576   y=511   width=11    height=72    xoffset=13    yoffset=0     xadvance=37    page=0  chnl=15
+char id=59   x=1009  y=292   width=14    height=72    xoffset=10    yoffset=0     xadvance=37    page=0  chnl=15
+char id=60   x=691   y=146   width=33    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=61   x=759   y=146   width=33    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=62   x=793   y=146   width=33    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=63   x=866   y=438   width=27    height=72    xoffset=6     yoffset=0     xadvance=37    page=0  chnl=15
+char id=64   x=906   y=0     width=37    height=72    xoffset=-1    yoffset=0     xadvance=37    page=0  chnl=15
+char id=65   x=266   y=73    width=37    height=72    xoffset=0     yoffset=0     xadvance=37    page=0  chnl=15
+char id=66   x=0     y=219   width=33    height=72    xoffset=3     yoffset=0     xadvance=37    page=0  chnl=15
+char id=67   x=720   y=365   width=30    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=68   x=462   y=292   width=32    height=72    xoffset=3     yoffset=0     xadvance=37    page=0  chnl=15
+char id=69   x=658   y=365   width=30    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=70   x=210   y=438   width=29    height=72    xoffset=5     yoffset=0     xadvance=37    page=0  chnl=15
+char id=71   x=945   y=219   width=32    height=72    xoffset=3     yoffset=0     xadvance=37    page=0  chnl=15
+char id=72   x=945   y=292   width=31    height=72    xoffset=3     yoffset=0     xadvance=37    page=0  chnl=15
+char id=73   x=180   y=438   width=29    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=74   x=995   y=365   width=28    height=72    xoffset=3     yoffset=0     xadvance=37    page=0  chnl=15
+char id=75   x=560   y=73    width=35    height=72    xoffset=3     yoffset=0     xadvance=37    page=0  chnl=15
+char id=76   x=596   y=365   width=30    height=72    xoffset=6     yoffset=0     xadvance=37    page=0  chnl=15
+char id=77   x=306   y=219   width=33    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=78   x=977   y=292   width=31    height=72    xoffset=3     yoffset=0     xadvance=37    page=0  chnl=15
+char id=79   x=340   y=219   width=33    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=80   x=0     y=365   width=31    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=81   x=408   y=219   width=33    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=82   x=596   y=73    width=35    height=72    xoffset=3     yoffset=0     xadvance=37    page=0  chnl=15
+char id=83   x=32    y=365   width=31    height=72    xoffset=3     yoffset=0     xadvance=37    page=0  chnl=15
+char id=84   x=442   y=219   width=33    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=85   x=476   y=219   width=33    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=86   x=452   y=73    width=35    height=72    xoffset=1     yoffset=0     xadvance=37    page=0  chnl=15
+char id=87   x=0     y=0     width=39    height=72    xoffset=-1    yoffset=0     xadvance=37    page=0  chnl=15
+char id=88   x=76    y=73    width=37    height=72    xoffset=0     yoffset=0     xadvance=37    page=0  chnl=15
+char id=89   x=557   y=0     width=38    height=72    xoffset=0     yoffset=0     xadvance=37    page=0  chnl=15
+char id=90   x=510   y=219   width=33    height=72    xoffset=3     yoffset=0     xadvance=37    page=0  chnl=15
+char id=91   x=390   y=511   width=17    height=72    xoffset=12    yoffset=0     xadvance=37    page=0  chnl=15
+char id=92   x=396   y=292   width=32    height=72    xoffset=3     yoffset=0     xadvance=37    page=0  chnl=15
+char id=93   x=408   y=511   width=17    height=72    xoffset=8     yoffset=0     xadvance=37    page=0  chnl=15
+char id=94   x=776   y=73    width=35    height=72    xoffset=1     yoffset=0     xadvance=37    page=0  chnl=15
+char id=95   x=240   y=0     width=39    height=72    xoffset=-1    yoffset=0     xadvance=37    page=0  chnl=15
+char id=96   x=314   y=511   width=19    height=72    xoffset=5     yoffset=0     xadvance=37    page=0  chnl=15
+char id=97   x=0     y=292   width=32    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=98   x=64    y=365   width=31    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=99   x=935   y=365   width=29    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=100  x=912   y=219   width=32    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=101  x=35    y=146   width=34    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=102  x=379   y=365   width=30    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=103  x=33    y=292   width=32    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=104  x=905   y=365   width=29    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=105  x=264   y=292   width=32    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=106  x=72    y=511   width=22    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=107  x=453   y=146   width=33    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=108  x=429   y=292   width=32    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=109  x=956   y=73    width=34    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=110  x=630   y=438   width=29    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=111  x=487   y=146   width=33    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=112  x=96    y=365   width=31    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=113  x=747   y=219   width=32    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=114  x=837   y=438   width=28    height=72    xoffset=8     yoffset=0     xadvance=37    page=0  chnl=15
+char id=115  x=600   y=438   width=29    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=116  x=286   y=365   width=30    height=72    xoffset=3     yoffset=0     xadvance=37    page=0  chnl=15
+char id=117  x=450   y=438   width=29    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=118  x=140   y=146   width=34    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=119  x=200   y=0     width=39    height=72    xoffset=-1    yoffset=0     xadvance=37    page=0  chnl=15
+char id=120  x=632   y=73    width=35    height=72    xoffset=1     yoffset=0     xadvance=37    page=0  chnl=15
+char id=121  x=668   y=73    width=35    height=72    xoffset=1     yoffset=0     xadvance=37    page=0  chnl=15
+char id=122  x=317   y=365   width=30    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=123  x=750   y=438   width=28    height=72    xoffset=5     yoffset=0     xadvance=37    page=0  chnl=15
+char id=124  x=639   y=511   width=9     height=72    xoffset=14    yoffset=0     xadvance=37    page=0  chnl=15
+char id=125  x=808   y=438   width=28    height=72    xoffset=5     yoffset=0     xadvance=37    page=0  chnl=15
+char id=126  x=657   y=146   width=33    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=160  x=649   y=511   width=3     height=72    xoffset=-1    yoffset=0     xadvance=37    page=0  chnl=15
+char id=161  x=619   y=511   width=9     height=72    xoffset=14    yoffset=0     xadvance=37    page=0  chnl=15
+char id=162  x=779   y=438   width=28    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=163  x=363   y=292   width=32    height=72    xoffset=3     yoffset=0     xadvance=37    page=0  chnl=15
+char id=164  x=360   y=438   width=29    height=72    xoffset=5     yoffset=0     xadvance=37    page=0  chnl=15
+char id=165  x=674   y=0     width=38    height=72    xoffset=0     yoffset=0     xadvance=37    page=0  chnl=15
+char id=166  x=629   y=511   width=9     height=72    xoffset=14    yoffset=0     xadvance=37    page=0  chnl=15
+char id=167  x=150   y=438   width=29    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=168  x=250   y=511   width=21    height=72    xoffset=8     yoffset=0     xadvance=37    page=0  chnl=15
+char id=169  x=160   y=0     width=39    height=72    xoffset=-1    yoffset=0     xadvance=37    page=0  chnl=15
+char id=170  x=48    y=511   width=23    height=72    xoffset=7     yoffset=0     xadvance=37    page=0  chnl=15
+char id=171  x=0     y=438   width=29    height=72    xoffset=3     yoffset=0     xadvance=37    page=0  chnl=15
+char id=172  x=34    y=219   width=33    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=173  x=184   y=511   width=21    height=72    xoffset=8     yoffset=0     xadvance=37    page=0  chnl=15
+char id=174  x=400   y=0     width=39    height=72    xoffset=-1    yoffset=0     xadvance=37    page=0  chnl=15
+char id=175  x=140   y=511   width=21    height=72    xoffset=8     yoffset=0     xadvance=37    page=0  chnl=15
+char id=176  x=118   y=511   width=21    height=72    xoffset=8     yoffset=0     xadvance=37    page=0  chnl=15
+char id=177  x=204   y=219   width=33    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=178  x=272   y=511   width=21    height=72    xoffset=8     yoffset=0     xadvance=37    page=0  chnl=15
+char id=179  x=95    y=511   width=22    height=72    xoffset=8     yoffset=0     xadvance=37    page=0  chnl=15
+char id=180  x=353   y=511   width=18    height=72    xoffset=14    yoffset=0     xadvance=37    page=0  chnl=15
+char id=181  x=589   y=146   width=33    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=182  x=495   y=292   width=32    height=72    xoffset=1     yoffset=0     xadvance=37    page=0  chnl=15
+char id=183  x=1011  y=219   width=11    height=72    xoffset=13    yoffset=0     xadvance=37    page=0  chnl=15
+char id=184  x=426   y=511   width=16    height=72    xoffset=10    yoffset=0     xadvance=37    page=0  chnl=15
+char id=185  x=162   y=511   width=21    height=72    xoffset=9     yoffset=0     xadvance=37    page=0  chnl=15
+char id=186  x=998   y=438   width=23    height=72    xoffset=8     yoffset=0     xadvance=37    page=0  chnl=15
+char id=187  x=690   y=438   width=29    height=72    xoffset=5     yoffset=0     xadvance=37    page=0  chnl=15
+char id=188  x=175   y=146   width=34    height=72    xoffset=1     yoffset=0     xadvance=37    page=0  chnl=15
+char id=189  x=105   y=146   width=34    height=72    xoffset=1     yoffset=0     xadvance=37    page=0  chnl=15
+char id=190  x=0     y=146   width=34    height=72    xoffset=1     yoffset=0     xadvance=37    page=0  chnl=15
+char id=191  x=894   y=438   width=27    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=192  x=868   y=0     width=37    height=72    xoffset=0     yoffset=0     xadvance=37    page=0  chnl=15
+char id=193  x=114   y=73    width=37    height=72    xoffset=0     yoffset=0     xadvance=37    page=0  chnl=15
+char id=194  x=944   y=0     width=37    height=72    xoffset=0     yoffset=0     xadvance=37    page=0  chnl=15
+char id=195  x=228   y=73    width=37    height=72    xoffset=0     yoffset=0     xadvance=37    page=0  chnl=15
+char id=196  x=190   y=73    width=37    height=72    xoffset=0     yoffset=0     xadvance=37    page=0  chnl=15
+char id=197  x=152   y=73    width=37    height=72    xoffset=0     yoffset=0     xadvance=37    page=0  chnl=15
+char id=198  x=752   y=0     width=38    height=72    xoffset=-1    yoffset=0     xadvance=37    page=0  chnl=15
+char id=199  x=627   y=365   width=30    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=200  x=782   y=365   width=30    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=201  x=255   y=365   width=30    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=202  x=503   y=365   width=30    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=203  x=441   y=365   width=30    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=204  x=420   y=438   width=29    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=205  x=120   y=438   width=29    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=206  x=30    y=438   width=29    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=207  x=660   y=438   width=29    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=208  x=378   y=73    width=36    height=72    xoffset=-1    yoffset=0     xadvance=37    page=0  chnl=15
+char id=209  x=160   y=365   width=31    height=72    xoffset=3     yoffset=0     xadvance=37    page=0  chnl=15
+char id=210  x=238   y=219   width=33    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=211  x=725   y=146   width=33    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=212  x=170   y=219   width=33    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=213  x=136   y=219   width=33    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=214  x=68    y=219   width=33    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=215  x=192   y=365   width=31    height=72    xoffset=3     yoffset=0     xadvance=37    page=0  chnl=15
+char id=216  x=120   y=0     width=39    height=72    xoffset=-1    yoffset=0     xadvance=37    page=0  chnl=15
+char id=217  x=963   y=146   width=33    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=218  x=895   y=146   width=33    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=219  x=861   y=146   width=33    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=220  x=827   y=146   width=33    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=221  x=791   y=0     width=38    height=72    xoffset=0     yoffset=0     xadvance=37    page=0  chnl=15
+char id=222  x=561   y=292   width=31    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=223  x=929   y=146   width=33    height=72    xoffset=3     yoffset=0     xadvance=37    page=0  chnl=15
+char id=224  x=528   y=292   width=32    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=225  x=231   y=292   width=32    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=226  x=165   y=292   width=32    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=227  x=132   y=292   width=32    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=228  x=99    y=292   width=32    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=229  x=66    y=292   width=32    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=230  x=38    y=73    width=37    height=72    xoffset=0     yoffset=0     xadvance=37    page=0  chnl=15
+char id=231  x=510   y=438   width=29    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=232  x=280   y=146   width=34    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=233  x=245   y=146   width=34    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=234  x=210   y=146   width=34    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=235  x=70    y=146   width=34    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=236  x=330   y=292   width=32    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=237  x=198   y=292   width=32    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=238  x=978   y=219   width=32    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=239  x=991   y=73    width=32    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=240  x=385   y=146   width=33    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=241  x=540   y=438   width=29    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=242  x=646   y=219   width=33    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=243  x=578   y=219   width=33    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=244  x=544   y=219   width=33    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=245  x=272   y=219   width=33    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=246  x=102   y=219   width=33    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=247  x=524   y=73    width=35    height=72    xoffset=1     yoffset=0     xadvance=37    page=0  chnl=15
+char id=248  x=830   y=0     width=37    height=72    xoffset=0     yoffset=0     xadvance=37    page=0  chnl=15
+char id=249  x=240   y=438   width=29    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=250  x=270   y=438   width=29    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=251  x=300   y=438   width=29    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=252  x=330   y=438   width=29    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=253  x=488   y=73    width=35    height=72    xoffset=1     yoffset=0     xadvance=37    page=0  chnl=15
+char id=254  x=593   y=292   width=31    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=255  x=704   y=73    width=35    height=72    xoffset=1     yoffset=0     xadvance=37    page=0  chnl=15
+char id=262  x=224   y=365   width=30    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=263  x=625   y=292   width=31    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=268  x=657   y=292   width=31    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=269  x=410   y=365   width=30    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=273  x=341   y=73    width=36    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=286  x=714   y=219   width=32    height=72    xoffset=3     yoffset=0     xadvance=37    page=0  chnl=15
+char id=287  x=813   y=219   width=32    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=304  x=390   y=438   width=29    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=305  x=846   y=219   width=32    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=321  x=713   y=0     width=38    height=72    xoffset=-2    yoffset=0     xadvance=37    page=0  chnl=15
+char id=322  x=315   y=146   width=34    height=72    xoffset=1     yoffset=0     xadvance=37    page=0  chnl=15
+char id=338  x=0     y=73    width=37    height=72    xoffset=1     yoffset=0     xadvance=37    page=0  chnl=15
+char id=339  x=982   y=0     width=37    height=72    xoffset=0     yoffset=0     xadvance=37    page=0  chnl=15
+char id=350  x=689   y=292   width=31    height=72    xoffset=3     yoffset=0     xadvance=37    page=0  chnl=15
+char id=351  x=570   y=438   width=29    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=352  x=721   y=292   width=31    height=72    xoffset=3     yoffset=0     xadvance=37    page=0  chnl=15
+char id=353  x=480   y=438   width=29    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=376  x=596   y=0     width=38    height=72    xoffset=0     yoffset=0     xadvance=37    page=0  chnl=15
+char id=381  x=555   y=146   width=33    height=72    xoffset=3     yoffset=0     xadvance=37    page=0  chnl=15
+char id=382  x=751   y=365   width=30    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=402  x=304   y=73    width=36    height=72    xoffset=0     yoffset=0     xadvance=37    page=0  chnl=15
+char id=710  x=948   y=438   width=24    height=72    xoffset=7     yoffset=0     xadvance=37    page=0  chnl=15
+char id=711  x=973   y=438   width=24    height=72    xoffset=7     yoffset=0     xadvance=37    page=0  chnl=15
+char id=728  x=228   y=511   width=21    height=72    xoffset=8     yoffset=0     xadvance=37    page=0  chnl=15
+char id=729  x=588   y=511   width=10    height=72    xoffset=14    yoffset=0     xadvance=37    page=0  chnl=15
+char id=730  x=294   y=511   width=19    height=72    xoffset=9     yoffset=0     xadvance=37    page=0  chnl=15
+char id=731  x=492   y=511   width=14    height=72    xoffset=13    yoffset=0     xadvance=37    page=0  chnl=15
+char id=732  x=24    y=511   width=23    height=72    xoffset=7     yoffset=0     xadvance=37    page=0  chnl=15
+char id=733  x=997   y=146   width=25    height=72    xoffset=8     yoffset=0     xadvance=37    page=0  chnl=15
+char id=937  x=521   y=146   width=33    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=960  x=440   y=0     width=38    height=72    xoffset=0     yoffset=0     xadvance=37    page=0  chnl=15
+char id=8211 x=320   y=0     width=39    height=72    xoffset=-1    yoffset=0     xadvance=37    page=0  chnl=15
+char id=8212 x=40    y=0     width=39    height=72    xoffset=-1    yoffset=0     xadvance=37    page=0  chnl=15
+char id=8216 x=507   y=511   width=14    height=72    xoffset=12    yoffset=0     xadvance=37    page=0  chnl=15
+char id=8217 x=537   y=511   width=14    height=72    xoffset=12    yoffset=0     xadvance=37    page=0  chnl=15
+char id=8218 x=522   y=511   width=14    height=72    xoffset=10    yoffset=0     xadvance=37    page=0  chnl=15
+char id=8220 x=90    y=438   width=29    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=8221 x=60    y=438   width=29    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=8222 x=720   y=438   width=29    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=8224 x=965   y=365   width=29    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=8225 x=875   y=365   width=29    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=8226 x=0     y=511   width=23    height=72    xoffset=7     yoffset=0     xadvance=37    page=0  chnl=15
+char id=8230 x=740   y=73    width=35    height=72    xoffset=1     yoffset=0     xadvance=37    page=0  chnl=15
+char id=8240 x=360   y=0     width=39    height=72    xoffset=-1    yoffset=0     xadvance=37    page=0  chnl=15
+char id=8249 x=476   y=511   width=15    height=72    xoffset=10    yoffset=0     xadvance=37    page=0  chnl=15
+char id=8250 x=443   y=511   width=16    height=72    xoffset=12    yoffset=0     xadvance=37    page=0  chnl=15
+char id=8364 x=374   y=219   width=33    height=72    xoffset=0     yoffset=0     xadvance=37    page=0  chnl=15
+char id=8482 x=415   y=73    width=36    height=72    xoffset=-1    yoffset=0     xadvance=37    page=0  chnl=15
+char id=8706 x=348   y=365   width=30    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=8710 x=280   y=0     width=39    height=72    xoffset=-1    yoffset=0     xadvance=37    page=0  chnl=15
+char id=8719 x=565   y=365   width=30    height=72    xoffset=4     yoffset=0     xadvance=37    page=0  chnl=15
+char id=8721 x=913   y=292   width=31    height=72    xoffset=3     yoffset=0     xadvance=37    page=0  chnl=15
+char id=8722 x=812   y=73    width=35    height=72    xoffset=1     yoffset=0     xadvance=37    page=0  chnl=15
+char id=8725 x=297   y=292   width=32    height=72    xoffset=3     yoffset=0     xadvance=37    page=0  chnl=15
+char id=8729 x=552   y=511   width=11    height=72    xoffset=13    yoffset=0     xadvance=37    page=0  chnl=15
+char id=8730 x=848   y=73    width=35    height=72    xoffset=1     yoffset=0     xadvance=37    page=0  chnl=15
+char id=8734 x=635   y=0     width=38    height=72    xoffset=0     yoffset=0     xadvance=37    page=0  chnl=15
+char id=8747 x=920   y=73    width=35    height=72    xoffset=1     yoffset=0     xadvance=37    page=0  chnl=15
+char id=8776 x=612   y=219   width=33    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=8800 x=350   y=146   width=34    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=8804 x=680   y=219   width=33    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=8805 x=419   y=146   width=33    height=72    xoffset=2     yoffset=0     xadvance=37    page=0  chnl=15
+char id=9674 x=128   y=365   width=31    height=72    xoffset=3     yoffset=0     xadvance=37    page=0  chnl=15
diff --git a/font/vera_sans_mono72_0.png b/font/vera_sans_mono72_0.png
new file mode 100644
index 0000000..7af6610
Binary files /dev/null and b/font/vera_sans_mono72_0.png differ
diff --git a/font/vera_sans_mono96.bmfc b/font/vera_sans_mono96.bmfc
new file mode 100644
index 0000000..791b162
--- /dev/null
+++ b/font/vera_sans_mono96.bmfc
@@ -0,0 +1,57 @@
+# AngelCode Bitmap Font Generator configuration file
+fileVersion=1
+
+# font settings
+fontName=Bitstream Vera Sans Mono
+fontFile=VeraMoBd.ttf
+charSet=0
+fontSize=96
+aa=1
+scaleH=100
+useSmoothing=1
+isBold=0
+isItalic=0
+useUnicode=1
+disableBoxChars=1
+outputInvalidCharGlyph=0
+dontIncludeKerningPairs=0
+useHinting=1
+renderFromOutline=0
+useClearType=1
+
+# character alignment
+paddingDown=0
+paddingUp=0
+paddingRight=0
+paddingLeft=0
+spacingHoriz=1
+spacingVert=1
+useFixedHeight=1
+forceZero=0
+
+# output file
+outWidth=1024
+outHeight=1024
+outBitDepth=32
+fontDescFormat=0
+fourChnlPacked=0
+textureFormat=png
+textureCompression=0
+alphaChnl=1
+redChnl=0
+greenChnl=0
+blueChnl=0
+invA=0
+invR=0
+invG=0
+invB=0
+
+# outline
+outlineThickness=0
+
+# selected chars
+chars=32-126,160-255,262-263,268-269,273,286-287,304-305,321-322,338-339,350-353,376,381-382,402,710-711
+chars=728-733,937,960,8211-8212,8216-8218,8220-8222,8224-8226,8230,8240,8249-8250,8364,8482,8706,8710
+chars=8719,8721-8722,8725,8729-8730,8734,8747,8776,8800,8804-8805,9674
+
+# imported icon images
diff --git a/font/vera_sans_mono96.fnt b/font/vera_sans_mono96.fnt
new file mode 100644
index 0000000..e3cf376
--- /dev/null
+++ b/font/vera_sans_mono96.fnt
@@ -0,0 +1,258 @@
+info face="Bitstream Vera Sans Mono" size=96 bold=0 italic=0 charset="" unicode=1 stretchH=100 smooth=1 aa=1 padding=0,0,0,0 spacing=1,1 outline=0
+common lineHeight=96 base=77 scaleW=1024 scaleH=1024 pages=1 packed=0 alphaChnl=1 redChnl=0 greenChnl=0 blueChnl=0
+page id=0 file="vera_sans_mono96_0.png"
+chars count=254
+char id=32   x=1015  y=194   width=3     height=96    xoffset=-1    yoffset=0     xadvance=49    page=0  chnl=15
+char id=33   x=845   y=873   width=13    height=96    xoffset=18    yoffset=0     xadvance=49    page=0  chnl=15
+char id=34   x=985   y=776   width=33    height=96    xoffset=8     yoffset=0     xadvance=49    page=0  chnl=15
+char id=35   x=156   y=0     width=51    height=96    xoffset=-1    yoffset=0     xadvance=49    page=0  chnl=15
+char id=36   x=39    y=776   width=38    height=96    xoffset=6     yoffset=0     xadvance=49    page=0  chnl=15
+char id=37   x=250   y=97    width=49    height=96    xoffset=1     yoffset=0     xadvance=49    page=0  chnl=15
+char id=38   x=826   y=0     width=49    height=96    xoffset=1     yoffset=0     xadvance=49    page=0  chnl=15
+char id=39   x=887   y=873   width=12    height=96    xoffset=19    yoffset=0     xadvance=49    page=0  chnl=15
+char id=40   x=528   y=873   width=23    height=96    xoffset=15    yoffset=0     xadvance=49    page=0  chnl=15
+char id=41   x=552   y=873   width=23    height=96    xoffset=12    yoffset=0     xadvance=49    page=0  chnl=15
+char id=42   x=84    y=582   width=41    height=96    xoffset=4     yoffset=0     xadvance=49    page=0  chnl=15
+char id=43   x=141   y=194   width=46    height=96    xoffset=2     yoffset=0     xadvance=49    page=0  chnl=15
+char id=44   x=743   y=873   width=18    height=96    xoffset=14    yoffset=0     xadvance=49    page=0  chnl=15
+char id=45   x=394   y=873   width=27    height=96    xoffset=11    yoffset=0     xadvance=49    page=0  chnl=15
+char id=46   x=797   y=873   width=15    height=96    xoffset=17    yoffset=0     xadvance=49    page=0  chnl=15
+char id=47   x=516   y=485   width=42    height=96    xoffset=4     yoffset=0     xadvance=49    page=0  chnl=15
+char id=48   x=126   y=582   width=41    height=96    xoffset=4     yoffset=0     xadvance=49    page=0  chnl=15
+char id=49   x=440   y=679   width=39    height=96    xoffset=7     yoffset=0     xadvance=49    page=0  chnl=15
+char id=50   x=877   y=582   width=39    height=96    xoffset=4     yoffset=0     xadvance=49    page=0  chnl=15
+char id=51   x=168   y=582   width=41    height=96    xoffset=4     yoffset=0     xadvance=49    page=0  chnl=15
+char id=52   x=528   y=388   width=43    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=53   x=713   y=582   width=40    height=96    xoffset=5     yoffset=0     xadvance=49    page=0  chnl=15
+char id=54   x=210   y=582   width=41    height=96    xoffset=5     yoffset=0     xadvance=49    page=0  chnl=15
+char id=55   x=672   y=582   width=40    height=96    xoffset=4     yoffset=0     xadvance=49    page=0  chnl=15
+char id=56   x=294   y=582   width=41    height=96    xoffset=4     yoffset=0     xadvance=49    page=0  chnl=15
+char id=57   x=336   y=582   width=41    height=96    xoffset=4     yoffset=0     xadvance=49    page=0  chnl=15
+char id=58   x=813   y=873   width=15    height=96    xoffset=17    yoffset=0     xadvance=49    page=0  chnl=15
+char id=59   x=762   y=873   width=18    height=96    xoffset=14    yoffset=0     xadvance=49    page=0  chnl=15
+char id=60   x=835   y=194   width=44    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=61   x=132   y=388   width=43    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=62   x=745   y=194   width=44    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=63   x=913   y=776   width=35    height=96    xoffset=8     yoffset=0     xadvance=49    page=0  chnl=15
+char id=64   x=692   y=97    width=47    height=96    xoffset=0     yoffset=0     xadvance=49    page=0  chnl=15
+char id=65   x=300   y=97    width=48    height=96    xoffset=1     yoffset=0     xadvance=49    page=0  chnl=15
+char id=66   x=925   y=194   width=44    height=96    xoffset=4     yoffset=0     xadvance=49    page=0  chnl=15
+char id=67   x=520   y=679   width=39    height=96    xoffset=5     yoffset=0     xadvance=49    page=0  chnl=15
+char id=68   x=378   y=582   width=41    height=96    xoffset=5     yoffset=0     xadvance=49    page=0  chnl=15
+char id=69   x=480   y=679   width=39    height=96    xoffset=6     yoffset=0     xadvance=49    page=0  chnl=15
+char id=70   x=835   y=679   width=38    height=96    xoffset=7     yoffset=0     xadvance=49    page=0  chnl=15
+char id=71   x=172   y=485   width=42    height=96    xoffset=4     yoffset=0     xadvance=49    page=0  chnl=15
+char id=72   x=754   y=582   width=40    height=96    xoffset=5     yoffset=0     xadvance=49    page=0  chnl=15
+char id=73   x=306   y=776   width=37    height=96    xoffset=6     yoffset=0     xadvance=49    page=0  chnl=15
+char id=74   x=382   y=776   width=37    height=96    xoffset=4     yoffset=0     xadvance=49    page=0  chnl=15
+char id=75   x=515   y=194   width=45    height=96    xoffset=4     yoffset=0     xadvance=49    page=0  chnl=15
+char id=76   x=120   y=679   width=39    height=96    xoffset=8     yoffset=0     xadvance=49    page=0  chnl=15
+char id=77   x=893   y=291   width=43    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=78   x=420   y=582   width=41    height=96    xoffset=4     yoffset=0     xadvance=49    page=0  chnl=15
+char id=79   x=308   y=388   width=43    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=80   x=462   y=582   width=41    height=96    xoffset=6     yoffset=0     xadvance=49    page=0  chnl=15
+char id=81   x=805   y=291   width=43    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=82   x=469   y=194   width=45    height=96    xoffset=5     yoffset=0     xadvance=49    page=0  chnl=15
+char id=83   x=504   y=582   width=41    height=96    xoffset=4     yoffset=0     xadvance=49    page=0  chnl=15
+char id=84   x=585   y=291   width=43    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=85   x=980   y=97    width=43    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=86   x=235   y=194   width=46    height=96    xoffset=2     yoffset=0     xadvance=49    page=0  chnl=15
+char id=87   x=364   y=0     width=51    height=96    xoffset=-1    yoffset=0     xadvance=49    page=0  chnl=15
+char id=88   x=926   y=0     width=49    height=96    xoffset=0     yoffset=0     xadvance=49    page=0  chnl=15
+char id=89   x=775   y=0     width=50    height=96    xoffset=0     yoffset=0     xadvance=49    page=0  chnl=15
+char id=90   x=660   y=388   width=43    height=96    xoffset=4     yoffset=0     xadvance=49    page=0  chnl=15
+char id=91   x=599   y=873   width=22    height=96    xoffset=16    yoffset=0     xadvance=49    page=0  chnl=15
+char id=92   x=546   y=582   width=41    height=96    xoffset=4     yoffset=0     xadvance=49    page=0  chnl=15
+char id=93   x=576   y=873   width=22    height=96    xoffset=11    yoffset=0     xadvance=49    page=0  chnl=15
+char id=94   x=0     y=194   width=46    height=96    xoffset=2     yoffset=0     xadvance=49    page=0  chnl=15
+char id=95   x=416   y=0     width=51    height=96    xoffset=-1    yoffset=0     xadvance=49    page=0  chnl=15
+char id=96   x=997   y=582   width=24    height=96    xoffset=7     yoffset=0     xadvance=49    page=0  chnl=15
+char id=97   x=747   y=388   width=42    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=98   x=790   y=388   width=42    height=96    xoffset=5     yoffset=0     xadvance=49    page=0  chnl=15
+char id=99   x=78    y=776   width=37    height=96    xoffset=6     yoffset=0     xadvance=49    page=0  chnl=15
+char id=100  x=588   y=582   width=41    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=101  x=45    y=291   width=44    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=102  x=718   y=679   width=38    height=96    xoffset=6     yoffset=0     xadvance=49    page=0  chnl=15
+char id=103  x=630   y=582   width=41    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=104  x=640   y=679   width=38    height=96    xoffset=6     yoffset=0     xadvance=49    page=0  chnl=15
+char id=105  x=602   y=485   width=42    height=96    xoffset=5     yoffset=0     xadvance=49    page=0  chnl=15
+char id=106  x=129   y=873   width=30    height=96    xoffset=5     yoffset=0     xadvance=49    page=0  chnl=15
+char id=107  x=919   y=388   width=42    height=96    xoffset=6     yoffset=0     xadvance=49    page=0  chnl=15
+char id=108  x=981   y=291   width=42    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=109  x=970   y=194   width=44    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=110  x=0     y=776   width=38    height=96    xoffset=6     yoffset=0     xadvance=49    page=0  chnl=15
+char id=111  x=717   y=291   width=43    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=112  x=704   y=388   width=42    height=96    xoffset=5     yoffset=0     xadvance=49    page=0  chnl=15
+char id=113  x=688   y=485   width=41    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=114  x=876   y=776   width=36    height=96    xoffset=11    yoffset=0     xadvance=49    page=0  chnl=15
+char id=115  x=572   y=776   width=37    height=96    xoffset=6     yoffset=0     xadvance=49    page=0  chnl=15
+char id=116  x=795   y=582   width=40    height=96    xoffset=4     yoffset=0     xadvance=49    page=0  chnl=15
+char id=117  x=496   y=776   width=37    height=96    xoffset=6     yoffset=0     xadvance=49    page=0  chnl=15
+char id=118  x=270   y=291   width=44    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=119  x=468   y=0     width=51    height=96    xoffset=-1    yoffset=0     xadvance=49    page=0  chnl=15
+char id=120  x=94    y=194   width=46    height=96    xoffset=2     yoffset=0     xadvance=49    page=0  chnl=15
+char id=121  x=47    y=194   width=46    height=96    xoffset=2     yoffset=0     xadvance=49    page=0  chnl=15
+char id=122  x=757   y=679   width=38    height=96    xoffset=6     yoffset=0     xadvance=49    page=0  chnl=15
+char id=123  x=268   y=776   width=37    height=96    xoffset=6     yoffset=0     xadvance=49    page=0  chnl=15
+char id=124  x=900   y=873   width=11    height=96    xoffset=19    yoffset=0     xadvance=49    page=0  chnl=15
+char id=125  x=420   y=776   width=37    height=96    xoffset=6     yoffset=0     xadvance=49    page=0  chnl=15
+char id=126  x=0     y=291   width=44    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=160  x=1019  y=194   width=3     height=96    xoffset=-1    yoffset=0     xadvance=49    page=0  chnl=15
+char id=161  x=859   y=873   width=13    height=96    xoffset=18    yoffset=0     xadvance=49    page=0  chnl=15
+char id=162  x=344   y=776   width=37    height=96    xoffset=5     yoffset=0     xadvance=49    page=0  chnl=15
+char id=163  x=301   y=485   width=42    height=96    xoffset=4     yoffset=0     xadvance=49    page=0  chnl=15
+char id=164  x=874   y=679   width=38    height=96    xoffset=7     yoffset=0     xadvance=49    page=0  chnl=15
+char id=165  x=571   y=0     width=50    height=96    xoffset=0     yoffset=0     xadvance=49    page=0  chnl=15
+char id=166  x=912   y=873   width=11    height=96    xoffset=19    yoffset=0     xadvance=49    page=0  chnl=15
+char id=167  x=192   y=776   width=37    height=96    xoffset=6     yoffset=0     xadvance=49    page=0  chnl=15
+char id=168  x=338   y=873   width=27    height=96    xoffset=11    yoffset=0     xadvance=49    page=0  chnl=15
+char id=169  x=260   y=0     width=51    height=96    xoffset=-1    yoffset=0     xadvance=49    page=0  chnl=15
+char id=170  x=98    y=873   width=30    height=96    xoffset=10    yoffset=0     xadvance=49    page=0  chnl=15
+char id=171  x=957   y=582   width=39    height=96    xoffset=4     yoffset=0     xadvance=49    page=0  chnl=15
+char id=172  x=220   y=388   width=43    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=173  x=366   y=873   width=27    height=96    xoffset=11    yoffset=0     xadvance=49    page=0  chnl=15
+char id=174  x=52    y=0     width=51    height=96    xoffset=-1    yoffset=0     xadvance=49    page=0  chnl=15
+char id=175  x=450   y=873   width=27    height=96    xoffset=11    yoffset=0     xadvance=49    page=0  chnl=15
+char id=176  x=309   y=873   width=28    height=96    xoffset=11    yoffset=0     xadvance=49    page=0  chnl=15
+char id=177  x=450   y=291   width=44    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=178  x=280   y=873   width=28    height=96    xoffset=10    yoffset=0     xadvance=49    page=0  chnl=15
+char id=179  x=251   y=873   width=28    height=96    xoffset=11    yoffset=0     xadvance=49    page=0  chnl=15
+char id=180  x=503   y=873   width=24    height=96    xoffset=18    yoffset=0     xadvance=49    page=0  chnl=15
+char id=181  x=876   y=388   width=42    height=96    xoffset=6     yoffset=0     xadvance=49    page=0  chnl=15
+char id=182  x=730   y=485   width=41    height=96    xoffset=2     yoffset=0     xadvance=49    page=0  chnl=15
+char id=183  x=781   y=873   width=15    height=96    xoffset=17    yoffset=0     xadvance=49    page=0  chnl=15
+char id=184  x=664   y=873   width=20    height=96    xoffset=14    yoffset=0     xadvance=49    page=0  chnl=15
+char id=185  x=422   y=873   width=27    height=96    xoffset=12    yoffset=0     xadvance=49    page=0  chnl=15
+char id=186  x=991   y=679   width=31    height=96    xoffset=10    yoffset=0     xadvance=49    page=0  chnl=15
+char id=187  x=679   y=679   width=38    height=96    xoffset=7     yoffset=0     xadvance=49    page=0  chnl=15
+char id=188  x=561   y=194   width=45    height=96    xoffset=1     yoffset=0     xadvance=49    page=0  chnl=15
+char id=189  x=282   y=194   width=46    height=96    xoffset=1     yoffset=0     xadvance=49    page=0  chnl=15
+char id=190  x=607   y=194   width=45    height=96    xoffset=1     yoffset=0     xadvance=49    page=0  chnl=15
+char id=191  x=949   y=776   width=35    height=96    xoffset=6     yoffset=0     xadvance=49    page=0  chnl=15
+char id=192  x=447   y=97    width=48    height=96    xoffset=1     yoffset=0     xadvance=49    page=0  chnl=15
+char id=193  x=398   y=97    width=48    height=96    xoffset=1     yoffset=0     xadvance=49    page=0  chnl=15
+char id=194  x=349   y=97    width=48    height=96    xoffset=1     yoffset=0     xadvance=49    page=0  chnl=15
+char id=195  x=594   y=97    width=48    height=96    xoffset=1     yoffset=0     xadvance=49    page=0  chnl=15
+char id=196  x=496   y=97    width=48    height=96    xoffset=1     yoffset=0     xadvance=49    page=0  chnl=15
+char id=197  x=545   y=97    width=48    height=96    xoffset=1     yoffset=0     xadvance=49    page=0  chnl=15
+char id=198  x=100   y=97    width=49    height=96    xoffset=-1    yoffset=0     xadvance=49    page=0  chnl=15
+char id=199  x=40    y=679   width=39    height=96    xoffset=5     yoffset=0     xadvance=49    page=0  chnl=15
+char id=200  x=360   y=679   width=39    height=96    xoffset=6     yoffset=0     xadvance=49    page=0  chnl=15
+char id=201  x=320   y=679   width=39    height=96    xoffset=6     yoffset=0     xadvance=49    page=0  chnl=15
+char id=202  x=240   y=679   width=39    height=96    xoffset=6     yoffset=0     xadvance=49    page=0  chnl=15
+char id=203  x=280   y=679   width=39    height=96    xoffset=6     yoffset=0     xadvance=49    page=0  chnl=15
+char id=204  x=458   y=776   width=37    height=96    xoffset=6     yoffset=0     xadvance=49    page=0  chnl=15
+char id=205  x=610   y=776   width=37    height=96    xoffset=6     yoffset=0     xadvance=49    page=0  chnl=15
+char id=206  x=648   y=776   width=37    height=96    xoffset=6     yoffset=0     xadvance=49    page=0  chnl=15
+char id=207  x=800   y=776   width=37    height=96    xoffset=6     yoffset=0     xadvance=49    page=0  chnl=15
+char id=208  x=788   y=97    width=47    height=96    xoffset=-1    yoffset=0     xadvance=49    page=0  chnl=15
+char id=209  x=814   y=485   width=41    height=96    xoffset=4     yoffset=0     xadvance=49    page=0  chnl=15
+char id=210  x=572   y=388   width=43    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=211  x=440   y=388   width=43    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=212  x=396   y=388   width=43    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=213  x=352   y=388   width=43    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=214  x=264   y=388   width=43    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=215  x=856   y=485   width=41    height=96    xoffset=4     yoffset=0     xadvance=49    page=0  chnl=15
+char id=216  x=673   y=0     width=50    height=96    xoffset=-1    yoffset=0     xadvance=49    page=0  chnl=15
+char id=217  x=176   y=388   width=43    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=218  x=88    y=388   width=43    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=219  x=44    y=388   width=43    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=220  x=0     y=388   width=43    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=221  x=520   y=0     width=50    height=96    xoffset=0     yoffset=0     xadvance=49    page=0  chnl=15
+char id=222  x=898   y=485   width=41    height=96    xoffset=6     yoffset=0     xadvance=49    page=0  chnl=15
+char id=223  x=880   y=194   width=44    height=96    xoffset=4     yoffset=0     xadvance=49    page=0  chnl=15
+char id=224  x=258   y=485   width=42    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=225  x=129   y=485   width=42    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=226  x=86    y=485   width=42    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=227  x=43    y=485   width=42    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=228  x=0     y=485   width=42    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=229  x=962   y=388   width=42    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=230  x=50    y=97    width=49    height=96    xoffset=0     yoffset=0     xadvance=49    page=0  chnl=15
+char id=231  x=534   y=776   width=37    height=96    xoffset=6     yoffset=0     xadvance=49    page=0  chnl=15
+char id=232  x=225   y=291   width=44    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=233  x=180   y=291   width=44    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=234  x=135   y=291   width=44    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=235  x=90    y=291   width=44    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=236  x=645   y=485   width=42    height=96    xoffset=5     yoffset=0     xadvance=49    page=0  chnl=15
+char id=237  x=559   y=485   width=42    height=96    xoffset=5     yoffset=0     xadvance=49    page=0  chnl=15
+char id=238  x=473   y=485   width=42    height=96    xoffset=5     yoffset=0     xadvance=49    page=0  chnl=15
+char id=239  x=833   y=388   width=42    height=96    xoffset=5     yoffset=0     xadvance=49    page=0  chnl=15
+char id=240  x=673   y=291   width=43    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=241  x=913   y=679   width=38    height=96    xoffset=6     yoffset=0     xadvance=49    page=0  chnl=15
+char id=242  x=629   y=291   width=43    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=243  x=484   y=388   width=43    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=244  x=761   y=291   width=43    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=245  x=937   y=291   width=43    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=246  x=849   y=291   width=43    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=247  x=653   y=194   width=45    height=96    xoffset=2     yoffset=0     xadvance=49    page=0  chnl=15
+char id=248  x=150   y=97    width=49    height=96    xoffset=0     yoffset=0     xadvance=49    page=0  chnl=15
+char id=249  x=686   y=776   width=37    height=96    xoffset=6     yoffset=0     xadvance=49    page=0  chnl=15
+char id=250  x=724   y=776   width=37    height=96    xoffset=6     yoffset=0     xadvance=49    page=0  chnl=15
+char id=251  x=762   y=776   width=37    height=96    xoffset=6     yoffset=0     xadvance=49    page=0  chnl=15
+char id=252  x=838   y=776   width=37    height=96    xoffset=6     yoffset=0     xadvance=49    page=0  chnl=15
+char id=253  x=188   y=194   width=46    height=96    xoffset=2     yoffset=0     xadvance=49    page=0  chnl=15
+char id=254  x=430   y=485   width=42    height=96    xoffset=5     yoffset=0     xadvance=49    page=0  chnl=15
+char id=255  x=329   y=194   width=46    height=96    xoffset=2     yoffset=0     xadvance=49    page=0  chnl=15
+char id=262  x=80    y=679   width=39    height=96    xoffset=5     yoffset=0     xadvance=49    page=0  chnl=15
+char id=263  x=836   y=582   width=40    height=96    xoffset=6     yoffset=0     xadvance=49    page=0  chnl=15
+char id=268  x=940   y=485   width=41    height=96    xoffset=5     yoffset=0     xadvance=49    page=0  chnl=15
+char id=269  x=796   y=679   width=38    height=96    xoffset=6     yoffset=0     xadvance=49    page=0  chnl=15
+char id=273  x=932   y=97    width=47    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=286  x=387   y=485   width=42    height=96    xoffset=4     yoffset=0     xadvance=49    page=0  chnl=15
+char id=287  x=982   y=485   width=41    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=304  x=154   y=776   width=37    height=96    xoffset=6     yoffset=0     xadvance=49    page=0  chnl=15
+char id=305  x=215   y=485   width=42    height=96    xoffset=5     yoffset=0     xadvance=49    page=0  chnl=15
+char id=321  x=0     y=97    width=49    height=96    xoffset=-2    yoffset=0     xadvance=49    page=0  chnl=15
+char id=322  x=790   y=194   width=44    height=96    xoffset=1     yoffset=0     xadvance=49    page=0  chnl=15
+char id=338  x=643   y=97    width=48    height=96    xoffset=2     yoffset=0     xadvance=49    page=0  chnl=15
+char id=339  x=876   y=0     width=49    height=96    xoffset=0     yoffset=0     xadvance=49    page=0  chnl=15
+char id=350  x=0     y=582   width=41    height=96    xoffset=4     yoffset=0     xadvance=49    page=0  chnl=15
+char id=351  x=116   y=776   width=37    height=96    xoffset=6     yoffset=0     xadvance=49    page=0  chnl=15
+char id=352  x=42    y=582   width=41    height=96    xoffset=4     yoffset=0     xadvance=49    page=0  chnl=15
+char id=353  x=230   y=776   width=37    height=96    xoffset=6     yoffset=0     xadvance=49    page=0  chnl=15
+char id=376  x=724   y=0     width=50    height=96    xoffset=0     yoffset=0     xadvance=49    page=0  chnl=15
+char id=381  x=616   y=388   width=43    height=96    xoffset=4     yoffset=0     xadvance=49    page=0  chnl=15
+char id=382  x=952   y=679   width=38    height=96    xoffset=6     yoffset=0     xadvance=49    page=0  chnl=15
+char id=402  x=976   y=0     width=47    height=96    xoffset=0     yoffset=0     xadvance=49    page=0  chnl=15
+char id=710  x=66    y=873   width=31    height=96    xoffset=9     yoffset=0     xadvance=49    page=0  chnl=15
+char id=711  x=34    y=873   width=31    height=96    xoffset=9     yoffset=0     xadvance=49    page=0  chnl=15
+char id=728  x=191   y=873   width=29    height=96    xoffset=10    yoffset=0     xadvance=49    page=0  chnl=15
+char id=729  x=873   y=873   width=13    height=96    xoffset=18    yoffset=0     xadvance=49    page=0  chnl=15
+char id=730  x=478   y=873   width=24    height=96    xoffset=13    yoffset=0     xadvance=49    page=0  chnl=15
+char id=731  x=685   y=873   width=19    height=96    xoffset=17    yoffset=0     xadvance=49    page=0  chnl=15
+char id=732  x=221   y=873   width=29    height=96    xoffset=10    yoffset=0     xadvance=49    page=0  chnl=15
+char id=733  x=0     y=873   width=33    height=96    xoffset=11    yoffset=0     xadvance=49    page=0  chnl=15
+char id=937  x=360   y=291   width=44    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=960  x=200   y=97    width=49    height=96    xoffset=0     yoffset=0     xadvance=49    page=0  chnl=15
+char id=8211 x=208   y=0     width=51    height=96    xoffset=-1    yoffset=0     xadvance=49    page=0  chnl=15
+char id=8212 x=104   y=0     width=51    height=96    xoffset=-1    yoffset=0     xadvance=49    page=0  chnl=15
+char id=8216 x=1005  y=388   width=18    height=96    xoffset=16    yoffset=0     xadvance=49    page=0  chnl=15
+char id=8217 x=705   y=873   width=18    height=96    xoffset=16    yoffset=0     xadvance=49    page=0  chnl=15
+char id=8218 x=724   y=873   width=18    height=96    xoffset=14    yoffset=0     xadvance=49    page=0  chnl=15
+char id=8220 x=0     y=679   width=39    height=96    xoffset=5     yoffset=0     xadvance=49    page=0  chnl=15
+char id=8221 x=917   y=582   width=39    height=96    xoffset=5     yoffset=0     xadvance=49    page=0  chnl=15
+char id=8222 x=600   y=679   width=39    height=96    xoffset=5     yoffset=0     xadvance=49    page=0  chnl=15
+char id=8224 x=560   y=679   width=39    height=96    xoffset=5     yoffset=0     xadvance=49    page=0  chnl=15
+char id=8225 x=400   y=679   width=39    height=96    xoffset=5     yoffset=0     xadvance=49    page=0  chnl=15
+char id=8226 x=160   y=873   width=30    height=96    xoffset=10    yoffset=0     xadvance=49    page=0  chnl=15
+char id=8230 x=836   y=97    width=47    height=96    xoffset=1     yoffset=0     xadvance=49    page=0  chnl=15
+char id=8240 x=312   y=0     width=51    height=96    xoffset=-1    yoffset=0     xadvance=49    page=0  chnl=15
+char id=8249 x=622   y=873   width=20    height=96    xoffset=13    yoffset=0     xadvance=49    page=0  chnl=15
+char id=8250 x=643   y=873   width=20    height=96    xoffset=16    yoffset=0     xadvance=49    page=0  chnl=15
+char id=8364 x=315   y=291   width=44    height=96    xoffset=0     yoffset=0     xadvance=49    page=0  chnl=15
+char id=8482 x=740   y=97    width=47    height=96    xoffset=-1    yoffset=0     xadvance=49    page=0  chnl=15
+char id=8706 x=200   y=679   width=39    height=96    xoffset=5     yoffset=0     xadvance=49    page=0  chnl=15
+char id=8710 x=0     y=0     width=51    height=96    xoffset=-1    yoffset=0     xadvance=49    page=0  chnl=15
+char id=8719 x=160   y=679   width=39    height=96    xoffset=5     yoffset=0     xadvance=49    page=0  chnl=15
+char id=8721 x=252   y=582   width=41    height=96    xoffset=4     yoffset=0     xadvance=49    page=0  chnl=15
+char id=8722 x=423   y=194   width=45    height=96    xoffset=2     yoffset=0     xadvance=49    page=0  chnl=15
+char id=8725 x=344   y=485   width=42    height=96    xoffset=4     yoffset=0     xadvance=49    page=0  chnl=15
+char id=8729 x=829   y=873   width=15    height=96    xoffset=17    yoffset=0     xadvance=49    page=0  chnl=15
+char id=8730 x=884   y=97    width=47    height=96    xoffset=1     yoffset=0     xadvance=49    page=0  chnl=15
+char id=8734 x=622   y=0     width=50    height=96    xoffset=0     yoffset=0     xadvance=49    page=0  chnl=15
+char id=8747 x=376   y=194   width=46    height=96    xoffset=2     yoffset=0     xadvance=49    page=0  chnl=15
+char id=8776 x=405   y=291   width=44    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=8800 x=699   y=194   width=45    height=96    xoffset=2     yoffset=0     xadvance=49    page=0  chnl=15
+char id=8804 x=495   y=291   width=44    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=8805 x=540   y=291   width=44    height=96    xoffset=3     yoffset=0     xadvance=49    page=0  chnl=15
+char id=9674 x=772   y=485   width=41    height=96    xoffset=4     yoffset=0     xadvance=49    page=0  chnl=15
diff --git a/font/vera_sans_mono96_0.png b/font/vera_sans_mono96_0.png
new file mode 100644
index 0000000..7774097
Binary files /dev/null and b/font/vera_sans_mono96_0.png differ
diff --git a/icon/CubicSDR.icns b/icon/CubicSDR.icns
new file mode 100644
index 0000000..aeca9e6
Binary files /dev/null and b/icon/CubicSDR.icns differ
diff --git a/icon/CubicSDR.ico b/icon/CubicSDR.ico
new file mode 100644
index 0000000..5210971
Binary files /dev/null and b/icon/CubicSDR.ico differ
diff --git a/icon/NSIS_Header.bmp b/icon/NSIS_Header.bmp
new file mode 100644
index 0000000..2ee3e05
Binary files /dev/null and b/icon/NSIS_Header.bmp differ
diff --git a/src/AppConfig.cpp b/src/AppConfig.cpp
new file mode 100644
index 0000000..39d2f4d
--- /dev/null
+++ b/src/AppConfig.cpp
@@ -0,0 +1,783 @@
+#include "AppConfig.h"
+#include "CubicSDR.h"
+
+DeviceConfig::DeviceConfig() : deviceId("") {
+	ppm.store(0);
+	offset.store(0);
+    agcMode.store(true);
+    sampleRate.store(0);
+}
+
+DeviceConfig::DeviceConfig(std::string deviceId) : DeviceConfig() {
+    this->deviceId = deviceId;
+}
+
+void DeviceConfig::setPPM(int ppm) {
+    this->ppm.store(ppm);
+}
+
+int DeviceConfig::getPPM() {
+    return ppm.load();
+}
+
+void DeviceConfig::setOffset(long long offset) {
+    this->offset.store(offset);
+}
+
+long long DeviceConfig::getOffset() {
+    return offset.load();
+}
+
+
+void DeviceConfig::setSampleRate(long srate) {
+    sampleRate.store(srate);
+}
+
+long DeviceConfig::getSampleRate() {
+    return sampleRate.load();
+}
+
+void DeviceConfig::setAGCMode(bool agcMode) {
+    this->agcMode.store(agcMode);
+}
+
+bool DeviceConfig::getAGCMode() {
+    return agcMode.load();
+}
+
+
+void DeviceConfig::setDeviceId(std::string deviceId) {
+    std::lock_guard < std::mutex > lock(busy_lock);
+    this->deviceId = deviceId;
+    
+}
+
+std::string DeviceConfig::getDeviceId() {
+    std::string tmp;
+
+    std::lock_guard < std::mutex > lock(busy_lock);
+    tmp = deviceId;
+    
+
+    return tmp;
+}
+
+void DeviceConfig::setDeviceName(std::string deviceName) {
+    std::lock_guard < std::mutex > lock(busy_lock);
+    this->deviceName = deviceName;
+   
+}
+
+std::string DeviceConfig::getDeviceName() {
+    std::string tmp;
+    
+    std::lock_guard < std::mutex > lock(busy_lock);
+    tmp = (deviceName=="")?deviceId:deviceName;
+   
+    
+    return tmp;
+}
+
+void DeviceConfig::save(DataNode *node) {
+    std::lock_guard < std::mutex > lock(busy_lock);
+    *node->newChild("id") = deviceId;
+    *node->newChild("name") = deviceName;
+    *node->newChild("ppm") = (int)ppm.load();
+    *node->newChild("offset") = offset.load();
+    *node->newChild("sample_rate") = sampleRate.load();
+    *node->newChild("agc_mode") = agcMode.load()?1:0;
+
+    if (streamOpts.size()) {
+        DataNode *streamOptsNode = node->newChild("streamOpts");
+        for (ConfigSettings::const_iterator opt_i = streamOpts.begin(); opt_i != streamOpts.end(); opt_i++) {
+            *streamOptsNode->newChild(opt_i->first.c_str()) = opt_i->second;
+        }
+    }
+    if (settings.size()) {
+        DataNode *settingsNode = node->newChild("settings");
+        for (ConfigSettings::const_iterator set_i = settings.begin(); set_i != settings.end(); set_i++) {
+            *settingsNode->newChild(set_i->first.c_str()) = set_i->second;
+        }
+    }
+    if (rigIF.size()) {
+        DataNode *rigIFs = node->newChild("rig_ifs");
+        for (std::map<int, long long>::const_iterator rigIF_i = rigIF.begin(); rigIF_i != rigIF.end(); rigIF_i++) {
+            DataNode *ifNode = rigIFs->newChild("rig_if");
+            *ifNode->newChild("model") = rigIF_i->first;
+            *ifNode->newChild("sdr_if") = rigIF_i->second;
+        }
+    }
+    if (gains.size()) {
+        DataNode *gainsNode = node->newChild("gains");
+        for (ConfigGains::const_iterator gain_i = gains.begin(); gain_i != gains.end(); gain_i++) {
+            DataNode *gainNode = gainsNode->newChild("gain");
+            *gainNode->newChild("id") = gain_i->first;
+            *gainNode->newChild("value") = gain_i->second;
+        }
+    }
+   
+}
+
+void DeviceConfig::load(DataNode *node) {
+    std::lock_guard < std::mutex > lock(busy_lock);
+    if (node->hasAnother("name")) {
+        deviceName = node->getNext("name")->element()->toString();
+    }
+    if (node->hasAnother("ppm")) {
+        DataNode *ppm_node = node->getNext("ppm");
+        int ppmValue = 0;
+        ppm_node->element()->get(ppmValue);
+        setPPM(ppmValue);
+    }
+    if (node->hasAnother("offset")) {
+        DataNode *offset_node = node->getNext("offset");
+        long long offsetValue = 0;
+        offset_node->element()->get(offsetValue);
+        setOffset(offsetValue);
+    }
+    if (node->hasAnother("agc_mode")) {
+        DataNode *agc_node = node->getNext("agc_mode");
+        int agcModeValue = 0;
+        agc_node->element()->get(agcModeValue);
+        setAGCMode(agcModeValue?true:false);
+    }
+    if (node->hasAnother("sample_rate")) {
+        DataNode *sample_rate_node = node->getNext("sample_rate");
+        long sampleRateValue = 0;
+        sample_rate_node->element()->get(sampleRateValue);
+        setSampleRate(sampleRateValue);
+    }
+    if (node->hasAnother("streamOpts")) {
+        DataNode *streamOptsNode = node->getNext("streamOpts");
+        for (int i = 0, iMax = streamOptsNode->numChildren(); i<iMax; i++) {
+            DataNode *streamOptNode = streamOptsNode->child(i);
+            std::string keyName = streamOptNode->getName();
+            std::string strSettingValue = streamOptNode->element()->toString();
+            
+            if (keyName != "") {
+                setStreamOpt(keyName, strSettingValue);
+            }
+        }
+    }
+    if (node->hasAnother("settings")) {
+        DataNode *settingsNode = node->getNext("settings");
+        for (int i = 0, iMax = settingsNode->numChildren(); i<iMax; i++) {
+            DataNode *settingNode = settingsNode->child(i);
+            std::string keyName = settingNode->getName();
+            std::string strSettingValue = settingNode->element()->toString();
+            
+            if (keyName != "") {
+                setSetting(keyName, strSettingValue);
+            }
+        }
+    }
+    if (node->hasAnother("rig_ifs")) {
+        DataNode *rigIFNodes = node->getNext("rig_ifs");
+        while (rigIFNodes->hasAnother("rig_if")) {
+            DataNode *rigIFNode = rigIFNodes->getNext("rig_if");
+            if (rigIFNode->hasAnother("model") && rigIFNode->hasAnother("sdr_if")) {
+                int load_model;
+                long long load_freq;
+                
+                rigIFNode->getNext("model")->element()->get(load_model);
+                rigIFNode->getNext("sdr_if")->element()->get(load_freq);
+                
+                rigIF[load_model] = load_freq;
+            }
+        }
+    }
+    if (node->hasAnother("gains")) {
+        DataNode *gainsNode = node->getNext("gains");
+        while (gainsNode->hasAnother("gain")) {
+            DataNode *gainNode = gainsNode->getNext("gain");
+            std::string keyName;
+            float fltSettingValue;
+            
+            gainNode->getNext("id")->element()->get(keyName);
+            gainNode->getNext("value")->element()->get(fltSettingValue);
+
+            if (keyName != "" && !(fltSettingValue!=fltSettingValue)) {
+                setGain(keyName, fltSettingValue);
+            }
+        }
+    }
+   
+}
+
+void DeviceConfig::setStreamOpts(ConfigSettings opts) {
+    streamOpts = opts;
+}
+
+ConfigSettings DeviceConfig::getStreamOpts() {
+    return streamOpts;
+}
+
+void DeviceConfig::setStreamOpt(std::string key, std::string value) {
+    streamOpts[key] = value;
+}
+
+std::string DeviceConfig::getStreamOpt(std::string key, std::string defaultValue) {
+    if (streamOpts.find(key) == streamOpts.end()) {
+        return defaultValue;
+    }
+    
+    return streamOpts[key];
+}
+
+void DeviceConfig::setSettings(ConfigSettings settings) {
+    this->settings = settings;
+}
+
+void DeviceConfig::setSetting(std::string key, std::string value) {
+    this->settings[key] = value;
+}
+
+std::string DeviceConfig::getSetting(std::string key, std::string defaultValue) {
+    if (settings.find(key) == settings.end()) {
+        return defaultValue;
+    }
+    return settings[key];
+}
+
+ConfigSettings DeviceConfig::getSettings() {
+    return settings;
+}
+
+
+void DeviceConfig::setGains(ConfigGains gains) {
+    this->gains = gains;
+}
+
+ConfigGains DeviceConfig::getGains() {
+    return gains;
+}
+
+void DeviceConfig::setGain(std::string key, float value) {
+    gains[key] = value;
+}
+
+float DeviceConfig::getGain(std::string key, float defaultValue) {
+    if (gains.find(key) != gains.end()) {
+        return gains[key];
+    }
+    return defaultValue;
+}
+
+
+void DeviceConfig::setRigIF(int rigType, long long freq) {
+    rigIF[rigType] = freq;
+}
+
+long long DeviceConfig::getRigIF(int rigType) {
+    if (rigIF.find(rigType) != rigIF.end()) {
+        return rigIF[rigType];
+    }
+    return 0;
+}
+
+AppConfig::AppConfig() : configName("") {
+    winX.store(0);
+    winY.store(0);
+    winW.store(0);
+    winH.store(0);
+    winMax.store(false);
+    showTips.store(true);
+    lowPerfMode.store(false);
+    themeId.store(0);
+    fontScale.store(0);
+    snap.store(1);
+    centerFreq.store(100000000);
+    waterfallLinesPerSec.store(DEFAULT_WATERFALL_LPS);
+    spectrumAvgSpeed.store(0.65f);
+    modemPropsCollapsed.store(false);
+#ifdef USE_HAMLIB
+    rigEnabled.store(false);
+    rigModel.store(1);
+    rigRate.store(57600);
+    rigPort = "/dev/ttyUSB0";
+    rigControlMode.store(true);
+    rigFollowMode.store(true);
+#endif
+}
+
+DeviceConfig *AppConfig::getDevice(std::string deviceId) {
+	if (deviceConfig.find(deviceId) == deviceConfig.end()) {
+		deviceConfig[deviceId] = new DeviceConfig();
+	}
+    DeviceConfig *conf = deviceConfig[deviceId];
+    conf->setDeviceId(deviceId);
+    return conf;
+}
+
+std::string AppConfig::getConfigDir() {
+    std::string dataDir = wxStandardPaths::Get().GetUserDataDir().ToStdString();
+
+    bool mkStatus = false;
+
+    if (!wxDir::Exists(dataDir)) {
+        mkStatus = wxDir::Make(dataDir);
+    } else {
+        mkStatus = true;
+    }
+
+    if (!mkStatus) {
+        std::cout << "Warning, unable to initialize user data directory." << std::endl;
+    }
+
+    return dataDir;
+}
+
+
+void AppConfig::setWindow(wxPoint winXY, wxSize winWH) {
+    winX.store(winXY.x);
+    winY.store(winXY.y);
+    winW.store(winWH.x);
+    winH.store(winWH.y);
+}
+
+void AppConfig::setWindowMaximized(bool max) {
+    winMax.store(max);
+}
+
+bool AppConfig::getWindowMaximized() {
+    return winMax.load();
+}
+
+void AppConfig::setModemPropsCollapsed(bool collapse) {
+    modemPropsCollapsed.store(collapse);
+}
+
+bool AppConfig::getModemPropsCollapsed() {
+    return modemPropsCollapsed.load();
+}
+
+void AppConfig::setShowTips(bool show) {
+    showTips.store(show);
+}
+
+bool AppConfig::getShowTips() {
+    return showTips.load();
+}
+
+void AppConfig::setLowPerfMode(bool show) {
+    lowPerfMode.store(show);
+}
+
+bool AppConfig::getLowPerfMode() {
+    return lowPerfMode.load();
+}
+
+wxRect *AppConfig::getWindow() {
+    wxRect *r = NULL;
+    if (winH.load() && winW.load()) {
+        r = new wxRect(winX.load(),winY.load(),winW.load(),winH.load());
+    }
+    return r;
+}
+
+void AppConfig::setTheme(int themeId) {
+    this->themeId.store(themeId);
+}
+
+int AppConfig::getTheme() {
+    return themeId.load();
+}
+
+void AppConfig::setFontScale(int fontScale) {
+    this->fontScale.store(fontScale);
+}
+
+int AppConfig::getFontScale() {
+    return fontScale.load();
+}
+
+
+void AppConfig::setSnap(long long snapVal) {
+    this->snap.store(snapVal);
+}
+
+long long AppConfig::getSnap() {
+    return snap.load();
+}
+
+void AppConfig::setCenterFreq(long long freqVal) {
+    centerFreq.store(freqVal);
+}
+
+long long AppConfig::getCenterFreq() {
+    return centerFreq.load();
+}
+
+
+void AppConfig::setWaterfallLinesPerSec(int lps) {
+    waterfallLinesPerSec.store(lps);
+}
+
+int AppConfig::getWaterfallLinesPerSec() {
+    return waterfallLinesPerSec.load();
+}
+
+void AppConfig::setSpectrumAvgSpeed(float avgSpeed) {
+    spectrumAvgSpeed.store(avgSpeed);
+}
+
+float AppConfig::getSpectrumAvgSpeed() {
+    return spectrumAvgSpeed.load();
+}
+
+void AppConfig::setManualDevices(std::vector<SDRManualDef> manuals) {
+    manualDevices = manuals;
+}
+
+std::vector<SDRManualDef> AppConfig::getManualDevices() {
+    return manualDevices;
+}
+
+void AppConfig::setConfigName(std::string configName) {
+    this->configName = configName;
+}
+
+std::string AppConfig::getConfigFileName(bool ignoreName) {
+    std::string cfgFileDir = getConfigDir();
+    
+    wxFileName cfgFile;
+    if (configName.length() && !ignoreName) {
+        std::string tempFn("config-");
+        tempFn.append(configName);
+        tempFn.append(".xml");
+        cfgFile = wxFileName(cfgFileDir, tempFn);
+    } else {
+        cfgFile = wxFileName(cfgFileDir, "config.xml");
+    }
+    
+    std::string cfgFileName = cfgFile.GetFullPath(wxPATH_NATIVE).ToStdString();
+    
+    return cfgFileName;
+}
+
+bool AppConfig::save() {
+    DataTree cfg;
+
+    cfg.rootNode()->setName("cubicsdr_config");
+    
+    if (winW.load() && winH.load()) {
+        DataNode *window_node = cfg.rootNode()->newChild("window");
+        
+        *window_node->newChild("x") = winX.load();
+        *window_node->newChild("y") = winY.load();
+        *window_node->newChild("w") = winW.load();
+        *window_node->newChild("h") = winH.load();
+
+        *window_node->newChild("max") = winMax.load();
+        *window_node->newChild("tips") = showTips.load();
+        *window_node->newChild("low_perf_mode") = lowPerfMode.load();
+        *window_node->newChild("theme") = themeId.load();
+        *window_node->newChild("font_scale") = fontScale.load();
+        *window_node->newChild("snap") = snap.load();
+        *window_node->newChild("center_freq") = centerFreq.load();
+        *window_node->newChild("waterfall_lps") = waterfallLinesPerSec.load();
+        *window_node->newChild("spectrum_avg") = spectrumAvgSpeed.load();
+        *window_node->newChild("modemprops_collapsed") = modemPropsCollapsed.load();;
+    }
+    
+    DataNode *devices_node = cfg.rootNode()->newChild("devices");
+
+    std::map<std::string, DeviceConfig *>::iterator device_config_i;
+    for (device_config_i = deviceConfig.begin(); device_config_i != deviceConfig.end(); device_config_i++) {
+        DataNode *device_node = devices_node->newChild("device");
+        device_config_i->second->save(device_node);
+    }
+
+    if (manualDevices.size()) {
+        DataNode *manual_node = cfg.rootNode()->newChild("manual_devices");
+        for (std::vector<SDRManualDef>::const_iterator i = manualDevices.begin(); i != manualDevices.end(); i++) {
+            DataNode *rig_node = manual_node->newChild("device");
+            *rig_node->newChild("factory") = i->factory;
+            *rig_node->newChild("params") = i->params;
+        }
+    }
+    
+#ifdef USE_HAMLIB
+    DataNode *rig_node = cfg.rootNode()->newChild("rig");
+    *rig_node->newChild("enabled") = rigEnabled.load()?1:0;
+    *rig_node->newChild("model") = rigModel.load();
+    *rig_node->newChild("rate") = rigRate.load();
+    *rig_node->newChild("port") = rigPort;
+    *rig_node->newChild("control") = rigControlMode.load()?1:0;
+    *rig_node->newChild("follow") = rigFollowMode.load()?1:0;
+    *rig_node->newChild("center_lock") = rigCenterLock.load()?1:0;
+    *rig_node->newChild("follow_modem") = rigFollowModem.load()?1:0;
+#endif
+    
+    std::string cfgFileName = getConfigFileName();
+    
+    if (!cfg.SaveToFileXML(cfgFileName)) {
+        std::cout << "Error saving :: configuration file '" << cfgFileName << "' is not writable!" << std::endl;
+        return false;
+    }
+
+    return true;
+}
+
+bool AppConfig::load() {
+    DataTree cfg;
+    std::string cfgFileDir = getConfigDir();
+
+    std::string cfgFileName = getConfigFileName();
+    wxFileName cfgFile = wxFileName(cfgFileName);
+
+    if (!cfgFile.Exists()) {
+        if (configName.length()) {
+            wxFileName baseConfig = wxFileName(getConfigFileName(true));
+            if (baseConfig.Exists()) {
+                std::string baseConfigFileName = baseConfig.GetFullPath(wxPATH_NATIVE).ToStdString();
+                std::cout << "Creating new configuration file '" << cfgFileName << "' by copying '" << baseConfigFileName << "'..";
+                wxCopyFile(baseConfigFileName, cfgFileName);
+                if (!cfgFile.Exists()) {
+                    std::cout << "failed." << std::endl;
+                    return true;
+                }
+                std::cout << "ok." << std::endl;
+            } else {
+                return true;
+            }
+        } else {
+            return true;
+        }
+    }
+
+    if (cfgFile.IsFileReadable()) {
+        std::cout << "Loading:: configuration file '" << cfgFileName << "'" << std::endl;
+
+        cfg.LoadFromFileXML(cfgFileName);
+    } else {
+        std::cout << "Error loading:: configuration file '" << cfgFileName << "' is not readable!" << std::endl;
+        return false;
+    }
+
+    if (cfg.rootNode()->hasAnother("window")) {
+        int x,y,w,h;
+        int max,tips,lpm,mpc;
+        
+        DataNode *win_node = cfg.rootNode()->getNext("window");
+        
+        if (win_node->hasAnother("w") && win_node->hasAnother("h") && win_node->hasAnother("x") && win_node->hasAnother("y")) {
+            win_node->getNext("x")->element()->get(x);
+            win_node->getNext("y")->element()->get(y);
+            win_node->getNext("w")->element()->get(w);
+            win_node->getNext("h")->element()->get(h);
+            
+            winX.store(x);
+            winY.store(y);
+            winW.store(w);
+            winH.store(h);
+        }
+        
+        if (win_node->hasAnother("max")) {
+            win_node->getNext("max")->element()->get(max);
+            winMax.store(max?true:false);
+        }
+
+        if (win_node->hasAnother("tips")) {
+            win_node->getNext("tips")->element()->get(tips);
+            showTips.store(tips?true:false);
+        }
+
+        if (win_node->hasAnother("low_perf_mode")) {
+            win_node->getNext("low_perf_mode")->element()->get(lpm);
+            lowPerfMode.store(lpm?true:false);
+        }
+
+        if (win_node->hasAnother("theme")) {
+            int theme;
+            win_node->getNext("theme")->element()->get(theme);
+            themeId.store(theme);
+        }
+
+        if (win_node->hasAnother("font_scale")) {
+            int fscale;
+            win_node->getNext("font_scale")->element()->get(fscale);
+            fontScale.store(fscale);
+        }
+
+        if (win_node->hasAnother("snap")) {
+			long long snapVal;
+			win_node->getNext("snap")->element()->get(snapVal);
+			snap.store(snapVal);
+		}
+
+        if (win_node->hasAnother("center_freq")) {
+            long long freqVal;
+            win_node->getNext("center_freq")->element()->get(freqVal);
+            centerFreq.store(freqVal);
+        }
+
+        if (win_node->hasAnother("waterfall_lps")) {
+            int lpsVal;
+            win_node->getNext("waterfall_lps")->element()->get(lpsVal);
+            waterfallLinesPerSec.store(lpsVal);
+        }
+        
+        if (win_node->hasAnother("spectrum_avg")) {
+            float avgVal;
+            win_node->getNext("spectrum_avg")->element()->get(avgVal);
+            spectrumAvgSpeed.store(avgVal);
+        }
+
+        if (win_node->hasAnother("modemprops_collapsed")) {
+            win_node->getNext("modemprops_collapsed")->element()->get(mpc);
+            modemPropsCollapsed.store(mpc?true:false);
+        }
+    }
+    
+    if (cfg.rootNode()->hasAnother("devices")) {
+        DataNode *devices_node = cfg.rootNode()->getNext("devices");
+
+        while (devices_node->hasAnother("device")) {
+            DataNode *device_node = devices_node->getNext("device");
+            if (device_node->hasAnother("id")) {
+                std::string deviceId = device_node->getNext("id")->element()->toString();
+
+                getDevice(deviceId)->load(device_node);
+            }
+        }
+    }
+    
+    if (cfg.rootNode()->hasAnother("manual_devices")) {
+        DataNode *manuals_node = cfg.rootNode()->getNext("manual_devices");
+        
+        while (manuals_node->hasAnother("device")) {
+            DataNode *manual_node = manuals_node->getNext("device");
+            if (manual_node->hasAnother("factory") && manual_node->hasAnother("params")) {
+                SDRManualDef mdef;
+                
+                mdef.factory = manual_node->getNext("factory")->element()->toString();
+                mdef.params = manual_node->getNext("params")->element()->toString();
+
+                manualDevices.push_back(mdef);
+            }
+        }
+    }
+    
+#ifdef USE_HAMLIB
+    if (cfg.rootNode()->hasAnother("rig")) {
+        DataNode *rig_node = cfg.rootNode()->getNext("rig");
+
+        if (rig_node->hasAnother("enabled")) {
+            int loadEnabled;
+            rig_node->getNext("enabled")->element()->get(loadEnabled);
+            rigEnabled.store(loadEnabled?true:false);
+        }
+        if (rig_node->hasAnother("model")) {
+            int loadModel;
+            rig_node->getNext("model")->element()->get(loadModel);
+            rigModel.store(loadModel?loadModel:1);
+        }
+        if (rig_node->hasAnother("rate")) {
+            int loadRate;
+            rig_node->getNext("rate")->element()->get(loadRate);
+            rigRate.store(loadRate?loadRate:57600);
+        }
+        if (rig_node->hasAnother("port")) {
+            rigPort = rig_node->getNext("port")->element()->toString();
+        }
+        if (rig_node->hasAnother("control")) {
+            int loadControl;
+            rig_node->getNext("control")->element()->get(loadControl);
+            rigControlMode.store(loadControl?true:false);
+        }
+        if (rig_node->hasAnother("follow")) {
+            int loadFollow;
+            rig_node->getNext("follow")->element()->get(loadFollow);
+            rigFollowMode.store(loadFollow?true:false);
+        }
+        if (rig_node->hasAnother("center_lock")) {
+            int loadCenterLock;
+            rig_node->getNext("center_lock")->element()->get(loadCenterLock);
+            rigCenterLock.store(loadCenterLock?true:false);
+        }
+        if (rig_node->hasAnother("follow_modem")) {
+            int loadFollow;
+            rig_node->getNext("follow_modem")->element()->get(loadFollow);
+            rigFollowModem.store(loadFollow?true:false);
+        }
+    }
+#endif
+
+
+    return true;
+}
+
+bool AppConfig::reset() {
+
+    return true;
+}
+
+
+#if USE_HAMLIB
+
+int AppConfig::getRigModel() {
+    return rigModel.load();
+}
+
+void AppConfig::setRigModel(int rigModel) {
+    this->rigModel.store(rigModel);
+}
+
+int AppConfig::getRigRate() {
+    return rigRate.load();
+}
+
+void AppConfig::setRigRate(int rigRate) {
+    this->rigRate.store(rigRate);
+}
+
+std::string AppConfig::getRigPort() {
+    return rigPort;
+}
+
+void AppConfig::setRigPort(std::string rigPort) {
+    this->rigPort = rigPort;
+}
+
+void AppConfig::setRigControlMode(bool cMode) {
+    rigControlMode.store(cMode);
+}
+
+bool AppConfig::getRigControlMode() {
+    return rigControlMode.load();
+}
+
+void AppConfig::setRigFollowMode(bool fMode) {
+    rigFollowMode.store(fMode);
+}
+
+bool AppConfig::getRigFollowMode() {
+    return rigFollowMode.load();
+}
+
+void AppConfig::setRigCenterLock(bool cLock) {
+    rigCenterLock.store(cLock);
+}
+
+bool AppConfig::getRigCenterLock() {
+    return rigCenterLock.load();
+}
+
+void AppConfig::setRigFollowModem(bool fMode) {
+    rigFollowModem.store(fMode);
+}
+
+bool AppConfig::getRigFollowModem() {
+    return rigFollowModem.load();
+}
+
+void AppConfig::setRigEnabled(bool enabled) {
+    rigEnabled.store(enabled);
+}
+
+bool AppConfig::getRigEnabled() {
+    return rigEnabled.load();
+}
+
+#endif
diff --git a/src/AppConfig.h b/src/AppConfig.h
new file mode 100644
index 0000000..96570aa
--- /dev/null
+++ b/src/AppConfig.h
@@ -0,0 +1,167 @@
+#pragma once
+
+#include <wx/stdpaths.h>
+#include <wx/dir.h>
+#include <wx/filename.h>
+#include <wx/panel.h>
+#include <atomic>
+#include <mutex>
+
+#include "DataTree.h"
+#include "CubicSDRDefs.h"
+#include "SDRDeviceInfo.h"
+
+typedef std::map<std::string, std::string> ConfigSettings;
+typedef std::map<std::string, float> ConfigGains;
+
+class DeviceConfig {
+public:
+    DeviceConfig();
+    DeviceConfig(std::string deviceId);
+
+    void setPPM(int ppm);
+    int getPPM();
+    
+    void setOffset(long long offset);
+    long long getOffset();
+
+    void setSampleRate(long srate);
+    long getSampleRate();
+
+    void setAGCMode(bool agcMode);
+    bool getAGCMode();
+    
+    void setDeviceId(std::string deviceId);
+    std::string getDeviceId();
+
+    void setDeviceName(std::string deviceName);
+    std::string getDeviceName();
+
+    void setStreamOpts(ConfigSettings opts);
+    ConfigSettings getStreamOpts();
+    void setStreamOpt(std::string key, std::string value);
+    std::string getStreamOpt(std::string key, std::string defaultValue);
+    
+    void setSettings(ConfigSettings settings);
+    ConfigSettings getSettings();
+    void setSetting(std::string key, std::string value);
+    std::string getSetting(std::string key, std::string defaultValue);
+
+    void setGains(ConfigGains gains);
+    ConfigGains getGains();
+    void setGain(std::string key, float value);
+    float getGain(std::string key, float defaultValue);
+
+    void setRigIF(int rigType, long long freq);
+    long long getRigIF(int rigType);
+
+    void save(DataNode *node);
+    void load(DataNode *node);
+
+private:
+    std::string deviceId;
+    std::string deviceName;
+    std::mutex busy_lock;
+
+    std::atomic_int ppm;
+    std::atomic_llong offset;
+    std::atomic_bool agcMode;
+    std::atomic_long sampleRate;
+    ConfigSettings streamOpts;
+    ConfigGains gains;
+    std::map<std::string, std::string> settings;
+    std::map<int, long long> rigIF;
+};
+
+class AppConfig {
+public:
+    AppConfig();
+    std::string getConfigDir();
+    DeviceConfig *getDevice(std::string deviceId);
+
+    void setWindow(wxPoint winXY, wxSize winWH);
+    wxRect *getWindow();
+    
+    void setWindowMaximized(bool max);
+    bool getWindowMaximized();
+
+    void setModemPropsCollapsed(bool collapse);
+    bool getModemPropsCollapsed();
+
+    void setShowTips(bool show);
+    bool getShowTips();
+
+    void setLowPerfMode(bool lpm);
+    bool getLowPerfMode();
+    
+    void setTheme(int themeId);
+    int getTheme();
+
+    void setFontScale(int scaleValue);
+    int getFontScale();
+
+    void setSnap(long long snapVal);
+    long long getSnap();
+    
+    void setCenterFreq(long long freqVal);
+    long long getCenterFreq();
+    
+    void setWaterfallLinesPerSec(int lps);
+    int getWaterfallLinesPerSec();
+    
+    void setSpectrumAvgSpeed(float avgSpeed);
+    float getSpectrumAvgSpeed();
+    
+    void setManualDevices(std::vector<SDRManualDef> manuals);
+    std::vector<SDRManualDef> getManualDevices();
+    
+#if USE_HAMLIB
+    int getRigModel();
+    void setRigModel(int rigModel);
+
+    int getRigRate();
+    void setRigRate(int rigRate);
+    
+    std::string getRigPort();
+    void setRigPort(std::string rigPort);
+    
+    void setRigControlMode(bool cMode);
+    bool getRigControlMode();
+
+    void setRigFollowMode(bool fMode);
+    bool getRigFollowMode();
+    
+    void setRigCenterLock(bool cLock);
+    bool getRigCenterLock();
+    
+    void setRigFollowModem(bool fMode);
+    bool getRigFollowModem();
+
+    void setRigEnabled(bool enabled);
+    bool getRigEnabled();
+#endif
+    
+    void setConfigName(std::string configName);
+    std::string getConfigFileName(bool ignoreName=false);
+    bool save();
+    bool load();
+    bool reset();
+
+private:
+    std::string configName;
+    std::map<std::string, DeviceConfig *> deviceConfig;
+    std::atomic_int winX,winY,winW,winH;
+    std::atomic_bool winMax, showTips, lowPerfMode, modemPropsCollapsed;
+    std::atomic_int themeId;
+    std::atomic_int fontScale;
+    std::atomic_llong snap;
+    std::atomic_llong centerFreq;
+    std::atomic_int waterfallLinesPerSec;
+    std::atomic<float> spectrumAvgSpeed;
+    std::vector<SDRManualDef> manualDevices;
+#if USE_HAMLIB
+    std::atomic_int rigModel, rigRate;
+    std::string rigPort;
+    std::atomic_bool rigEnabled, rigFollowMode, rigControlMode, rigCenterLock, rigFollowModem;
+#endif
+};
diff --git a/src/AppFrame.cpp b/src/AppFrame.cpp
new file mode 100644
index 0000000..dff457b
--- /dev/null
+++ b/src/AppFrame.cpp
@@ -0,0 +1,2231 @@
+#include "AppFrame.h"
+
+#include "wx/wxprec.h"
+
+#ifndef WX_PRECOMP
+#include "wx/wx.h"
+#endif
+
+#include "wx/numdlg.h"
+#include "wx/filedlg.h"
+
+#if !wxUSE_GLCANVAS
+#error "OpenGL required: set wxUSE_GLCANVAS to 1 and rebuild the library"
+#endif
+
+#include <vector>
+#include <algorithm>
+#include "AudioThread.h"
+#include "CubicSDR.h"
+#include "DataTree.h"
+#include "ColorTheme.h"
+#include "DemodulatorMgr.h"
+
+#include <thread>
+
+#include <wx/panel.h>
+
+#ifdef __linux__
+#include "CubicSDR.xpm"
+#endif
+
+wxBEGIN_EVENT_TABLE(AppFrame, wxFrame)
+//EVT_MENU(wxID_NEW, AppFrame::OnNewWindow)
+EVT_CLOSE(AppFrame::OnClose)
+EVT_MENU(wxID_ANY, AppFrame::OnMenu)
+EVT_COMMAND(wxID_ANY, wxEVT_THREAD, AppFrame::OnThread)
+EVT_IDLE(AppFrame::OnIdle)
+EVT_SPLITTER_DCLICK(wxID_ANY, AppFrame::OnDoubleClickSash)
+EVT_SPLITTER_UNSPLIT(wxID_ANY, AppFrame::OnUnSplit)
+wxEND_EVENT_TABLE()
+
+#ifdef USE_HAMLIB
+#include "RigThread.h"
+#endif
+
+#define APPFRAME_MODEMPROPS_MINSIZE 20
+#define APPFRAME_MODEMPROPS_MAXSIZE 240
+
+AppFrame::AppFrame() :
+        wxFrame(NULL, wxID_ANY, CUBICSDR_TITLE), activeDemodulator(NULL) {
+
+#ifdef __linux__
+    SetIcon(wxICON(cubicsdr));
+#endif
+
+    wxBoxSizer *vbox = new wxBoxSizer(wxVERTICAL);
+    wxBoxSizer *demodVisuals = new wxBoxSizer(wxVERTICAL);
+    demodTray = new wxBoxSizer(wxHORIZONTAL);
+    wxBoxSizer *demodScopeTray = new wxBoxSizer(wxVERTICAL);
+    wxBoxSizer *demodTunerTray = new wxBoxSizer(wxHORIZONTAL);
+
+    int attribList[] = { WX_GL_RGBA, WX_GL_DOUBLEBUFFER, 0 };
+    //wxGLAttributes attribList;
+    //attribList.PlatformDefaults().RGBA().DoubleBuffer().EndList();
+    //attribList.PlatformDefaults().MinRGBA(8, 8, 8, 8).DoubleBuffer().Depth(16).EndList();
+
+    mainSplitter = new wxSplitterWindow( this, wxID_MAIN_SPLITTER, wxDefaultPosition, wxDefaultSize, wxSP_3DSASH | wxSP_LIVE_UPDATE );
+    mainSplitter->SetSashGravity(10.0/37.0);
+    mainSplitter->SetMinimumPaneSize(1);
+
+    wxPanel *demodPanel = new wxPanel(mainSplitter, wxID_ANY);
+            
+    gainCanvas = new GainCanvas(demodPanel, attribList);
+    
+    gainSizerItem = demodTray->Add(gainCanvas, 0, wxEXPAND | wxALL, 0);
+    gainSizerItem->Show(false);
+    gainSpacerItem = demodTray->AddSpacer(1);
+    gainSpacerItem->Show(false);
+            
+    demodModeSelector = new ModeSelectorCanvas(demodPanel, attribList);
+    demodModeSelector->addChoice("FM");
+    demodModeSelector->addChoice("FMS");
+    demodModeSelector->addChoice("NBFM");
+    demodModeSelector->addChoice("AM");
+    demodModeSelector->addChoice("LSB");
+    demodModeSelector->addChoice("USB");
+    demodModeSelector->addChoice("DSB");
+    demodModeSelector->addChoice("I/Q");
+    demodModeSelector->setSelection("FM");
+    demodModeSelector->setHelpTip("Choose modulation type: Frequency Modulation (Hotkey F), Amplitude Modulation (A) and Lower (L), Upper (U), Double Side-Band and more.");
+    demodModeSelector->SetMinSize(wxSize(50,-1));
+    demodModeSelector->SetMaxSize(wxSize(50,-1));
+    demodTray->Add(demodModeSelector, 2, wxEXPAND | wxALL, 0);
+    
+#ifdef ENABLE_DIGITAL_LAB
+    demodModeSelectorAdv = new ModeSelectorCanvas(demodPanel, attribList);
+    demodModeSelectorAdv->addChoice("ASK");
+    demodModeSelectorAdv->addChoice("APSK");
+    demodModeSelectorAdv->addChoice("BPSK");
+    demodModeSelectorAdv->addChoice("DPSK");
+    demodModeSelectorAdv->addChoice("PSK");
+    demodModeSelectorAdv->addChoice("FSK");
+    demodModeSelectorAdv->addChoice("GMSK");
+    demodModeSelectorAdv->addChoice("OOK");
+    demodModeSelectorAdv->addChoice("ST");
+    demodModeSelectorAdv->addChoice("SQAM");
+    demodModeSelectorAdv->addChoice("QAM");
+    demodModeSelectorAdv->addChoice("QPSK");
+    demodModeSelectorAdv->setHelpTip("Choose advanced modulation types.");
+    demodModeSelectorAdv->SetMinSize(wxSize(50,-1));
+    demodModeSelectorAdv->SetMaxSize(wxSize(50,-1));
+    demodTray->Add(demodModeSelectorAdv, 3, wxEXPAND | wxALL, 0);
+#endif
+            
+    modemPropertiesUpdated.store(false);
+    modemProps = new ModemProperties(demodPanel, wxID_ANY);
+    modemProps->SetMinSize(wxSize(APPFRAME_MODEMPROPS_MAXSIZE,-1));
+    modemProps->SetMaxSize(wxSize(APPFRAME_MODEMPROPS_MAXSIZE,-1));
+
+    modemProps->Hide();
+    demodTray->Add(modemProps, 15, wxEXPAND | wxALL, 0);
+
+#ifndef __APPLE__
+    demodTray->AddSpacer(1);
+#endif
+            
+    wxGetApp().getDemodSpectrumProcessor()->setup(1024);
+    demodSpectrumCanvas = new SpectrumCanvas(demodPanel, attribList);
+    demodSpectrumCanvas->setView(wxGetApp().getConfig()->getCenterFreq(), 300000);
+    demodVisuals->Add(demodSpectrumCanvas, 3, wxEXPAND | wxALL, 0);
+    wxGetApp().getDemodSpectrumProcessor()->attachOutput(demodSpectrumCanvas->getVisualDataQueue());
+
+    demodVisuals->AddSpacer(1);
+
+    demodWaterfallCanvas = new WaterfallCanvas(demodPanel, attribList);
+    demodWaterfallCanvas->setup(1024, 128);
+    demodWaterfallCanvas->setView(wxGetApp().getConfig()->getCenterFreq(), 300000);
+    demodWaterfallCanvas->attachSpectrumCanvas(demodSpectrumCanvas);
+    demodWaterfallCanvas->setMinBandwidth(8000);
+    demodSpectrumCanvas->attachWaterfallCanvas(demodWaterfallCanvas);
+    demodVisuals->Add(demodWaterfallCanvas, 6, wxEXPAND | wxALL, 0);
+    wxGetApp().getDemodSpectrumProcessor()->attachOutput(demodWaterfallCanvas->getVisualDataQueue());
+    demodWaterfallCanvas->getVisualDataQueue()->set_max_num_items(3);
+
+    demodVisuals->SetMinSize(wxSize(128,-1));
+
+    demodTray->Add(demodVisuals, 30, wxEXPAND | wxALL, 0);
+
+    demodTray->AddSpacer(1);
+
+    demodSignalMeter = new MeterCanvas(demodPanel, attribList);
+    demodSignalMeter->setMax(DEMOD_SIGNAL_MAX);
+    demodSignalMeter->setMin(DEMOD_SIGNAL_MIN);
+    demodSignalMeter->setLevel(DEMOD_SIGNAL_MIN);
+    demodSignalMeter->setInputValue(DEMOD_SIGNAL_MIN);
+    demodSignalMeter->setHelpTip("Current Signal Level.  Click / Drag to set Squelch level.  Right-Click to Auto-Zero Squelch");
+    demodSignalMeter->SetMinSize(wxSize(12,24));
+    demodTray->Add(demodSignalMeter, 1, wxEXPAND | wxALL, 0);
+
+
+    demodTray->AddSpacer(1);
+
+    scopeCanvas = new ScopeCanvas(demodPanel, attribList);
+    scopeCanvas->setHelpTip("Audio Visuals, drag left/right to toggle Scope or Spectrum.");
+    scopeCanvas->SetMinSize(wxSize(128,-1));
+    demodScopeTray->Add(scopeCanvas, 8, wxEXPAND | wxALL, 0);
+    wxGetApp().getScopeProcessor()->setup(1024);
+    wxGetApp().getScopeProcessor()->attachOutput(scopeCanvas->getInputQueue());
+
+    demodScopeTray->AddSpacer(1);
+
+    deltaLockButton = new ModeSelectorCanvas(demodPanel, attribList);
+    deltaLockButton->addChoice(1, "V");
+    deltaLockButton->setPadding(-1,-1);
+    deltaLockButton->setHighlightColor(RGBA4f(0.8f,0.8f,0.2f));
+    deltaLockButton->setHelpTip("Delta Lock Toggle (V) - Enable to lock modem relative to center frequency.");
+    deltaLockButton->setToggleMode(true);
+    deltaLockButton->setSelection(-1);
+    deltaLockButton->SetMinSize(wxSize(20,28));
+    
+    demodTunerTray->Add(deltaLockButton, 0, wxEXPAND | wxALL, 0);
+    demodTunerTray->AddSpacer(1);
+            
+    demodTuner = new TuningCanvas(demodPanel, attribList);
+    demodTuner->SetMinClientSize(wxSize(200,28));
+    demodTunerTray->Add(demodTuner, 1, wxEXPAND | wxALL, 0);
+            
+    demodScopeTray->Add(demodTunerTray, 1, wxEXPAND | wxALL, 0);
+
+    demodTray->Add(demodScopeTray, 30, wxEXPAND | wxALL, 0);
+
+    demodTray->AddSpacer(1);
+
+    wxBoxSizer *demodGainTray = new wxBoxSizer(wxVERTICAL);
+            
+    demodGainMeter = new MeterCanvas(demodPanel, attribList);
+    demodGainMeter->setMax(2.0);
+    demodGainMeter->setHelpTip("Current Demodulator Gain Level.  Click / Drag to set Gain level.");
+    demodGainMeter->setShowUserInput(false);
+    demodGainMeter->SetMinSize(wxSize(13,24));
+    demodGainTray->Add(demodGainMeter, 8, wxEXPAND | wxALL, 0);
+
+    demodGainTray->AddSpacer(1);
+    
+    soloModeButton = new ModeSelectorCanvas(demodPanel, attribList);
+    soloModeButton->addChoice(1, "S");
+    soloModeButton->setPadding(-1,-1);
+    soloModeButton->setHighlightColor(RGBA4f(0.8f,0.8f,0.2f));
+    soloModeButton->setHelpTip("Solo Mode Toggle");
+    soloModeButton->setToggleMode(true);
+    soloModeButton->setSelection(-1);
+    soloModeButton->SetMinSize(wxSize(12,28));
+    
+    demodGainTray->Add(soloModeButton, 1, wxEXPAND | wxALL, 0);
+
+    demodGainTray->AddSpacer(1);
+
+    demodMuteButton = new ModeSelectorCanvas(demodPanel, attribList);
+    demodMuteButton->addChoice(1, "M");
+    demodMuteButton->setPadding(-1,-1);
+    demodMuteButton->setHighlightColor(RGBA4f(0.8f,0.2f,0.2f));
+    demodMuteButton->setHelpTip("Demodulator Mute Toggle");
+    demodMuteButton->setToggleMode(true);
+	demodMuteButton->setSelection(-1);
+    demodMuteButton->SetMinSize(wxSize(12,28));
+            
+    demodGainTray->Add(demodMuteButton, 1, wxEXPAND | wxALL, 0);
+
+    demodTray->Add(demodGainTray, 1, wxEXPAND | wxALL, 0);
+    
+    demodPanel->SetSizer(demodTray);
+
+//    vbox->Add(demodTray, 12, wxEXPAND | wxALL, 0);
+//    vbox->AddSpacer(1);
+            
+    mainVisSplitter = new wxSplitterWindow( mainSplitter, wxID_VIS_SPLITTER, wxDefaultPosition, wxDefaultSize, wxSP_3DSASH | wxSP_LIVE_UPDATE );
+    mainVisSplitter->SetSashGravity(6.0/25.0);
+    mainVisSplitter->SetMinimumPaneSize(1);
+        
+//    mainVisSplitter->Connect( wxEVT_IDLE, wxIdleEventHandler( AppFrame::mainVisSplitterIdle ), NULL, this );
+
+    wxPanel *spectrumPanel = new wxPanel(mainVisSplitter, wxID_ANY);
+    wxBoxSizer *spectrumSizer = new wxBoxSizer(wxHORIZONTAL);
+
+    wxGetApp().getSpectrumProcessor()->setup(2048);
+    spectrumCanvas = new SpectrumCanvas(spectrumPanel, attribList);
+    spectrumCanvas->setShowDb(true);
+    spectrumCanvas->setScaleFactorEnabled(true);
+    wxGetApp().getSpectrumProcessor()->attachOutput(spectrumCanvas->getVisualDataQueue());
+           
+    wxBoxSizer *spectrumCtlTray = new wxBoxSizer(wxVERTICAL);
+            
+    peakHoldButton = new ModeSelectorCanvas(spectrumPanel, attribList);
+    peakHoldButton->addChoice(1, "P");
+    peakHoldButton->setPadding(-1,-1);
+    peakHoldButton->setHighlightColor(RGBA4f(0.2f,0.8f,0.2f));
+    peakHoldButton->setHelpTip("Peak Hold Toggle");
+    peakHoldButton->setToggleMode(true);
+    peakHoldButton->setSelection(-1);
+    peakHoldButton->SetMinSize(wxSize(12,24));
+
+    spectrumCtlTray->Add(peakHoldButton, 1, wxEXPAND | wxALL, 0);
+    spectrumCtlTray->AddSpacer(1);
+
+    spectrumAvgMeter = new MeterCanvas(spectrumPanel, attribList);
+    spectrumAvgMeter->setHelpTip("Spectrum averaging speed, click or drag to adjust.");
+    spectrumAvgMeter->setMax(1.0);
+    spectrumAvgMeter->setLevel(0.65f);
+    spectrumAvgMeter->setShowUserInput(false);
+    spectrumAvgMeter->SetMinSize(wxSize(12,24));
+            
+    spectrumCtlTray->Add(spectrumAvgMeter, 8, wxEXPAND | wxALL, 0);
+
+    spectrumSizer->Add(spectrumCanvas, 63, wxEXPAND | wxALL, 0);
+    spectrumSizer->AddSpacer(1);
+    spectrumSizer->Add(spectrumCtlTray, 1, wxEXPAND | wxALL, 0);
+    spectrumPanel->SetSizer(spectrumSizer);
+            
+//    vbox->Add(spectrumSizer, 5, wxEXPAND | wxALL, 0);
+
+//    vbox->AddSpacer(1);
+            
+    wxPanel *waterfallPanel = new wxPanel(mainVisSplitter, wxID_ANY);
+    wxBoxSizer *wfSizer = new wxBoxSizer(wxHORIZONTAL);
+           
+    waterfallCanvas = new WaterfallCanvas(waterfallPanel, attribList);
+    waterfallCanvas->setup(2048, 512);
+
+    waterfallDataThread = new FFTVisualDataThread();
+
+    waterfallDataThread->setInputQueue("IQDataInput", wxGetApp().getWaterfallVisualQueue());
+    waterfallDataThread->setOutputQueue("FFTDataOutput", waterfallCanvas->getVisualDataQueue());
+    waterfallDataThread->getProcessor()->setHideDC(true);
+
+    t_FFTData = new std::thread(&FFTVisualDataThread::threadMain, waterfallDataThread);
+
+    waterfallSpeedMeter = new MeterCanvas(waterfallPanel, attribList);
+    waterfallSpeedMeter->setHelpTip("Waterfall speed, click or drag to adjust (max 1024 lines per second)");
+    waterfallSpeedMeter->setMax(sqrt(1024));
+    waterfallSpeedMeter->setLevel(sqrt(DEFAULT_WATERFALL_LPS));
+    waterfallSpeedMeter->setShowUserInput(false);
+    waterfallSpeedMeter->SetMinSize(wxSize(12,24));
+
+    wfSizer->Add(waterfallCanvas, 63, wxEXPAND | wxALL, 0);
+    wfSizer->AddSpacer(1);
+    wfSizer->Add(waterfallSpeedMeter, 1, wxEXPAND | wxALL, 0);
+    waterfallPanel->SetSizer(wfSizer);
+            
+//    vbox->Add(wfSizer, 20, wxEXPAND | wxALL, 0);
+
+    mainVisSplitter->SplitHorizontally( spectrumPanel, waterfallPanel, 0 );
+    mainSplitter->SplitHorizontally( demodPanel, mainVisSplitter );
+            
+    vbox->Add(mainSplitter, 1, wxEXPAND | wxALL, 0);
+            
+    // TODO: refactor these..
+    waterfallCanvas->attachSpectrumCanvas(spectrumCanvas);
+    spectrumCanvas->attachWaterfallCanvas(waterfallCanvas);
+
+/* * /
+    vbox->AddSpacer(1);
+    testCanvas = new UITestCanvas(this, attribList);
+    vbox->Add(testCanvas, 20, wxEXPAND | wxALL, 0);
+// */
+            
+    this->SetSizer(vbox);
+
+    //    SetIcon(wxICON(sample));
+
+    // Make a menubar
+    menuBar = new wxMenuBar;
+    wxMenu *menu = new wxMenu;
+    
+    menu->Append(wxID_SDR_DEVICES, "SDR Devices");
+    menu->AppendSeparator();
+    menu->Append(wxID_SDR_START_STOP, "Stop / Start Device");
+    menu->AppendSeparator();
+    menu->Append(wxID_OPEN, "&Open Session");
+    menu->Append(wxID_SAVE, "&Save Session");
+    menu->Append(wxID_SAVEAS, "Save Session &As..");
+    menu->AppendSeparator();
+    menu->Append(wxID_RESET, "&Reset Session");
+            
+#ifndef __APPLE__
+        menu->AppendSeparator();
+        menu->Append(wxID_CLOSE);
+#else
+        if ( wxApp::s_macAboutMenuItemId != wxID_NONE ) {
+            wxString aboutLabel;
+            aboutLabel.Printf(_("About %s"), wxTheApp->GetAppDisplayName());
+            menu->Append( wxApp::s_macAboutMenuItemId, aboutLabel);
+        }
+#endif
+
+    menuBar->Append(menu, wxT("&File"));
+            
+    settingsMenu = new wxMenu;
+          
+    menuBar->Append(settingsMenu, wxT("&Settings"));
+            
+    menu = new wxMenu;
+
+    std::vector<RtAudio::DeviceInfo>::iterator devices_i;
+    std::map<int, RtAudio::DeviceInfo>::iterator mdevices_i;
+    AudioThread::enumerateDevices(devices);
+
+    int i = 0;
+
+    for (devices_i = devices.begin(); devices_i != devices.end(); devices_i++) {
+        if (devices_i->inputChannels) {
+            inputDevices[i] = *devices_i;
+        }
+        if (devices_i->outputChannels) {
+            outputDevices[i] = *devices_i;
+        }
+        i++;
+    }
+//
+//    for (mdevices_i = outputDevices.begin(); mdevices_i != outputDevices.end(); mdevices_i++) {
+//        wxMenuItem *itm = menu->AppendRadioItem(wxID_RT_AUDIO_DEVICE + mdevices_i->first, mdevices_i->second.name, wxT("Description?"));
+//        itm->SetId(wxID_RT_AUDIO_DEVICE + mdevices_i->first);
+//        if (mdevices_i->second.isDefaultOutput) {
+//            itm->Check(true);
+//        }
+//        outputDeviceMenuItems[mdevices_i->first] = itm;
+//    }
+//
+//    menuBar->Append(menu, wxT("Audio &Output"));
+            
+    sampleRateMenu = new wxMenu;
+    menuBar->Append(sampleRateMenu, wxT("Sample &Rate"));
+
+    // Audio Sample Rates
+    menu = new wxMenu;
+
+#define NUM_RATES_DEFAULT 4
+    unsigned int desired_rates[NUM_RATES_DEFAULT] = { 48000, 44100, 96000, 192000 };
+
+    for (mdevices_i = outputDevices.begin(); mdevices_i != outputDevices.end(); mdevices_i++) {
+        unsigned int desired_rate = 0;
+        unsigned int desired_rank = NUM_RATES_DEFAULT + 1;
+
+        for (std::vector<unsigned int>::iterator srate = mdevices_i->second.sampleRates.begin(); srate != mdevices_i->second.sampleRates.end();
+                srate++) {
+            for (unsigned int i = 0; i < NUM_RATES_DEFAULT; i++) {
+                if (desired_rates[i] == (*srate)) {
+                    if (desired_rank > i) {
+                        desired_rank = i;
+                        desired_rate = (*srate);
+                    }
+                }
+            }
+        }
+
+        if (desired_rank > NUM_RATES_DEFAULT) {
+            desired_rate = mdevices_i->second.sampleRates.back();
+        }
+        AudioThread::deviceSampleRate[mdevices_i->first] = desired_rate;
+    }
+
+    for (mdevices_i = outputDevices.begin(); mdevices_i != outputDevices.end(); mdevices_i++) {
+        int menu_id = wxID_AUDIO_BANDWIDTH_BASE + wxID_AUDIO_DEVICE_MULTIPLIER * mdevices_i->first;
+        wxMenu *subMenu = new wxMenu;
+        menu->AppendSubMenu(subMenu, mdevices_i->second.name, wxT("Description?"));
+
+        int j = 0;
+        for (std::vector<unsigned int>::iterator srate = mdevices_i->second.sampleRates.begin(); srate != mdevices_i->second.sampleRates.end();
+                srate++) {
+            std::stringstream srateName;
+            srateName << ((float) (*srate) / 1000.0f) << "kHz";
+            wxMenuItem *itm = subMenu->AppendRadioItem(menu_id + j, srateName.str(), wxT("Description?"));
+
+            if ((int)(*srate) == AudioThread::deviceSampleRate[mdevices_i->first]) {
+                itm->Check(true);
+            }
+            audioSampleRateMenuItems[menu_id + j] = itm;
+
+            j++;
+        }
+    }
+
+    menuBar->Append(menu, wxT("Audio &Sample Rate"));
+
+    //Add Display menu
+    displayMenu = new wxMenu;
+        
+    wxMenu *fontMenu = new wxMenu;
+
+    int fontScale = wxGetApp().getConfig()->getFontScale();
+
+    fontMenu->AppendRadioItem(wxID_DISPLAY_BASE, "Normal")->Check(GLFont::GLFONT_SCALE_NORMAL == fontScale);
+    fontMenu->AppendRadioItem(wxID_DISPLAY_BASE + 1, "1.5x")->Check(GLFont::GLFONT_SCALE_MEDIUM == fontScale);
+    fontMenu->AppendRadioItem(wxID_DISPLAY_BASE + 2, "2.0x")->Check(GLFont::GLFONT_SCALE_LARGE == fontScale);
+
+    displayMenu->AppendSubMenu(fontMenu, "&Text Size");
+            
+    wxMenu *themeMenu = new wxMenu;
+    
+    int themeId = wxGetApp().getConfig()->getTheme();
+    
+    themeMenu->AppendRadioItem(wxID_THEME_DEFAULT, "Default")->Check(themeId==COLOR_THEME_DEFAULT);
+    themeMenu->AppendRadioItem(wxID_THEME_RADAR, "RADAR")->Check(themeId==COLOR_THEME_RADAR);
+    themeMenu->AppendRadioItem(wxID_THEME_BW, "Black & White")->Check(themeId==COLOR_THEME_BW);
+    themeMenu->AppendRadioItem(wxID_THEME_SHARP, "Sharp")->Check(themeId==COLOR_THEME_SHARP);
+    themeMenu->AppendRadioItem(wxID_THEME_RAD, "Rad")->Check(themeId==COLOR_THEME_RAD);
+    themeMenu->AppendRadioItem(wxID_THEME_TOUCH, "Touch")->Check(themeId==COLOR_THEME_TOUCH);
+    themeMenu->AppendRadioItem(wxID_THEME_HD, "HD")->Check(themeId==COLOR_THEME_HD);
+
+    displayMenu->AppendSubMenu(themeMenu, wxT("&Color Scheme"));
+            
+    GLFont::setScale((GLFont::GLFontScale)fontScale);
+
+#ifdef USE_HAMLIB
+            
+    rigModel = wxGetApp().getConfig()->getRigModel();
+    rigSerialRate = wxGetApp().getConfig()->getRigRate();
+    rigPort = wxGetApp().getConfig()->getRigPort();
+            
+    rigMenu = new wxMenu;
+
+    rigEnableMenuItem = rigMenu->AppendCheckItem(wxID_RIG_TOGGLE, wxT("Enable Rig"));
+
+    rigMenu->Append(wxID_RIG_SDR_IF, wxT("SDR-IF"));
+
+    rigControlMenuItem = rigMenu->AppendCheckItem(wxID_RIG_CONTROL, wxT("Control Rig"));
+    rigControlMenuItem->Check(wxGetApp().getConfig()->getRigControlMode());
+            
+    rigFollowMenuItem = rigMenu->AppendCheckItem(wxID_RIG_FOLLOW, wxT("Follow Rig"));
+    rigFollowMenuItem->Check(wxGetApp().getConfig()->getRigFollowMode());
+
+    rigCenterLockMenuItem = rigMenu->AppendCheckItem(wxID_RIG_CENTERLOCK, wxT("Floating Center"));
+    rigCenterLockMenuItem->Check(wxGetApp().getConfig()->getRigCenterLock());
+
+    rigFollowModemMenuItem = rigMenu->AppendCheckItem(wxID_RIG_FOLLOW_MODEM, wxT("Track Modem"));
+    rigFollowModemMenuItem->Check(wxGetApp().getConfig()->getRigFollowModem());
+
+    wxMenu *rigModelMenu = new wxMenu;
+    RigList &rl = RigThread::enumerate();
+    numRigs = rl.size();
+            
+    int modelMenuId = wxID_RIG_MODEL_BASE;
+    for (RigList::const_iterator ri = rl.begin(); ri != rl.end(); ri++) {
+        std::string modelString((*ri)->mfg_name);
+        modelString.append(" ");
+        modelString.append((*ri)->model_name);
+        
+        rigModelMenuItems[(*ri)->rig_model] = rigModelMenu->AppendRadioItem(modelMenuId, modelString, wxT("Description?"));
+        
+        if (rigModel == (*ri)->rig_model) {
+            rigModelMenuItems[(*ri)->rig_model]->Check(true);
+        }
+        
+        modelMenuId++;
+    }
+
+    rigMenu->AppendSubMenu(rigModelMenu, wxT("Model"));
+
+    wxMenu *rigSerialMenu = new wxMenu;
+            
+    rigSerialRates.push_back(1200);
+    rigSerialRates.push_back(2400);
+    rigSerialRates.push_back(4800);
+    rigSerialRates.push_back(9600);
+    rigSerialRates.push_back(19200);
+    rigSerialRates.push_back(38400);
+    rigSerialRates.push_back(57600);
+    rigSerialRates.push_back(115200);
+    rigSerialRates.push_back(128000);
+    rigSerialRates.push_back(256000);
+
+    int rateMenuId = wxID_RIG_SERIAL_BASE;
+    for (std::vector<int>::const_iterator rate_i = rigSerialRates.begin(); rate_i != rigSerialRates.end(); rate_i++) {
+        std::string rateString;
+        rateString.append(std::to_string((*rate_i)));
+        rateString.append(" baud");
+        
+        rigSerialMenuItems[(*rate_i)] = rigSerialMenu->AppendRadioItem(rateMenuId, rateString, wxT("Description?"));
+        
+        if (rigSerialRate == (*rate_i)) {
+            rigSerialMenuItems[(*rate_i)]->Check(true);
+        }
+        
+        rateMenuId++;
+    }
+    
+    rigMenu->AppendSubMenu(rigSerialMenu, wxT("Serial Rate"));
+
+    rigPortMenuItem = rigMenu->Append(wxID_RIG_PORT, wxT("Control Port"));
+
+    menuBar->Append(rigMenu, wxT("&Rig Control"));
+#endif
+
+    menuBar->Append(displayMenu, wxT("&Display"));
+
+    SetMenuBar(menuBar);
+
+    CreateStatusBar();
+
+    wxRect *win = wxGetApp().getConfig()->getWindow();
+    if (win) {
+        this->SetPosition(win->GetPosition());
+        this->SetClientSize(win->GetSize());
+    } else {
+        SetClientSize(1280, 600);
+        Centre();
+    }
+    bool max = wxGetApp().getConfig()->getWindowMaximized();
+
+    if (max) {
+        this->Maximize();
+    }
+
+    long long freqSnap = wxGetApp().getConfig()->getSnap();
+    wxGetApp().setFrequencySnap(freqSnap);
+
+    float spectrumAvg = wxGetApp().getConfig()->getSpectrumAvgSpeed();
+            
+    spectrumAvgMeter->setLevel(spectrumAvg);
+    wxGetApp().getSpectrumProcessor()->setFFTAverageRate(spectrumAvg);
+            
+    int wflps =wxGetApp().getConfig()->getWaterfallLinesPerSec();
+            
+    waterfallSpeedMeter->setLevel(sqrt(wflps));
+    waterfallDataThread->setLinesPerSecond(wflps);
+    waterfallCanvas->setLinesPerSecond(wflps);
+            
+    ThemeMgr::mgr.setTheme(wxGetApp().getConfig()->getTheme());
+
+    int mpc =wxGetApp().getConfig()->getModemPropsCollapsed();
+
+    if (mpc) {
+        modemProps->setCollapsed(true);
+    }
+            
+    Show();
+
+#ifdef _WIN32
+    SetIcon(wxICON(frame_icon));
+#endif
+
+    wxAcceleratorEntry entries[3];
+    entries[0].Set(wxACCEL_CTRL, (int) 'O', wxID_OPEN);
+    entries[1].Set(wxACCEL_CTRL, (int) 'S', wxID_SAVE);
+    entries[2].Set(wxACCEL_CTRL, (int) 'A', wxID_SAVEAS);
+
+    wxAcceleratorTable accel(3, entries);
+    SetAcceleratorTable(accel);
+    deviceChanged.store(false);
+    devInfo = NULL;
+    wxGetApp().deviceSelector();
+            
+//    static const int attribs[] = { WX_GL_RGBA, WX_GL_DOUBLEBUFFER, 0 };
+//    wxLogStatus("Double-buffered display %s supported", wxGLCanvas::IsDisplaySupported(attribs) ? "is" : "not");
+//    ShowFullScreen(true);
+
+    //Force refresh of all
+    Refresh();
+}
+
+AppFrame::~AppFrame() {
+    waterfallDataThread->terminate();
+    t_FFTData->join();
+}
+
+void AppFrame::initDeviceParams(SDRDeviceInfo *devInfo) {
+    this->devInfo = devInfo;
+    deviceChanged.store(true);
+}
+
+void AppFrame::updateDeviceParams() {
+    
+    if (!deviceChanged.load()) {
+        return;
+    }
+    
+    int i = 0;
+    SoapySDR::Device *soapyDev = devInfo->getSoapyDevice();
+    
+    // Build settings menu
+    wxMenu *newSettingsMenu = new wxMenu;
+    showTipMenuItem = newSettingsMenu->AppendCheckItem(wxID_SET_TIPS, "Show Hover Tips");
+    showTipMenuItem->Check(wxGetApp().getConfig()->getShowTips());
+
+    lowPerfMode = wxGetApp().getConfig()->getLowPerfMode();
+    lowPerfMenuItem = newSettingsMenu->AppendCheckItem(wxID_LOW_PERF, "Reduce CPU Usage");
+    if (lowPerfMode) {
+        lowPerfMenuItem->Check(true);
+    }
+
+    newSettingsMenu->AppendSeparator();
+
+    newSettingsMenu->Append(wxID_SET_FREQ_OFFSET, "Frequency Offset");
+
+    if (devInfo->hasCORR(SOAPY_SDR_RX, 0)) {
+        newSettingsMenu->Append(wxID_SET_PPM, "Device PPM");
+    }
+
+    if (devInfo->getDriver() != "rtlsdr") {
+        iqSwapMenuItem = newSettingsMenu->AppendCheckItem(wxID_SET_IQSWAP, "I/Q Swap");
+        iqSwapMenuItem->Check(wxGetApp().getSDRThread()->getIQSwap());
+    }
+
+    agcMenuItem = nullptr;
+    if (soapyDev->listGains(SOAPY_SDR_RX, 0).size()) {
+        agcMenuItem = newSettingsMenu->AppendCheckItem(wxID_AGC_CONTROL, "Automatic Gain");
+        agcMenuItem->Check(wxGetApp().getAGCMode());
+    } else if (!wxGetApp().getAGCMode()) {
+        wxGetApp().setAGCMode(true);
+    }
+    
+    SoapySDR::ArgInfoList::const_iterator args_i;
+    settingArgs = soapyDev->getSettingInfo();
+
+    if (settingArgs.size()) {
+        newSettingsMenu->AppendSeparator();
+    }
+    
+    for (args_i = settingArgs.begin(); args_i != settingArgs.end(); args_i++) {
+        SoapySDR::ArgInfo arg = (*args_i);
+        std::string currentVal = soapyDev->readSetting(arg.key);
+        if (arg.type == SoapySDR::ArgInfo::BOOL) {
+            wxMenuItem *item = newSettingsMenu->AppendCheckItem(wxID_SETTINGS_BASE+i, arg.name, arg.description);
+            item->Check(currentVal=="true");
+            i++;
+        } else if (arg.type == SoapySDR::ArgInfo::INT) {
+            newSettingsMenu->Append(wxID_SETTINGS_BASE+i, arg.name, arg.description);
+            i++;
+        } else if (arg.type == SoapySDR::ArgInfo::FLOAT) {
+            newSettingsMenu->Append(wxID_SETTINGS_BASE+i, arg.name, arg.description);
+            i++;
+        } else if (arg.type == SoapySDR::ArgInfo::STRING) {
+            if (arg.options.size()) {
+                wxMenu *subMenu = new wxMenu;
+                int j = 0;
+                for (std::vector<std::string>::iterator str_i = arg.options.begin(); str_i != arg.options.end(); str_i++) {
+                    std::string optName = (*str_i);
+                    std::string displayName = optName;
+                    if (arg.optionNames.size()) {
+                        displayName = arg.optionNames[j];
+                    }
+                    wxMenuItem *item = subMenu->AppendRadioItem(wxID_SETTINGS_BASE+i, displayName);
+                    if (currentVal == (*str_i)) {
+                        item->Check(true);
+                    }
+                    j++;
+                    i++;
+                }
+                newSettingsMenu->AppendSubMenu(subMenu, arg.name, arg.description);
+            } else {
+                newSettingsMenu->Append(wxID_SETTINGS_BASE+i, arg.name, arg.description);
+                i++;
+            }
+        }
+    }
+    settingsIdMax = wxID_SETTINGS_BASE+i;
+    
+    menuBar->Replace(1, newSettingsMenu, wxT("&Settings"));
+    settingsMenu = newSettingsMenu;
+    
+    // Build sample rate menu
+    sampleRates = devInfo->getSampleRates(SOAPY_SDR_RX, 0);
+    sampleRateMenuItems.erase(sampleRateMenuItems.begin(),sampleRateMenuItems.end());
+    
+    wxMenu *newSampleRateMenu = new wxMenu;
+    int ofs = 0;
+    long sampleRate = wxGetApp().getSampleRate();
+    bool checked = false;
+    for (vector<long>::iterator i = sampleRates.begin(); i != sampleRates.end(); i++) {
+        sampleRateMenuItems[wxID_BANDWIDTH_BASE+ofs] = newSampleRateMenu->AppendRadioItem(wxID_BANDWIDTH_BASE+ofs, frequencyToStr(*i));
+        if (sampleRate == (*i)) {
+            sampleRateMenuItems[wxID_BANDWIDTH_BASE+ofs]->Check(true);
+            checked = true;
+        }
+        ofs++;
+    }
+    
+    sampleRateMenuItems[wxID_BANDWIDTH_MANUAL] = newSampleRateMenu->AppendRadioItem(wxID_BANDWIDTH_MANUAL, wxT("Manual Entry")); 
+    if (!checked) {
+        sampleRateMenuItems[wxID_BANDWIDTH_MANUAL]->Check(true);
+    }
+   
+    menuBar->Replace(2, newSampleRateMenu, wxT("Sample &Rate"));
+    sampleRateMenu = newSampleRateMenu;
+
+    if (!wxGetApp().getAGCMode()) {
+        gainSpacerItem->Show(true);
+        gainSizerItem->Show(true);
+        gainSizerItem->SetMinSize(devInfo->getSoapyDevice()->listGains(SOAPY_SDR_RX,0).size()*50,0);
+        demodTray->Layout();
+        gainCanvas->updateGainUI();
+        gainCanvas->Refresh();
+        gainCanvas->Refresh();
+    } else {
+        gainSpacerItem->Show(false);
+        gainSizerItem->Show(false);
+        demodTray->Layout();
+    }
+
+
+#if USE_HAMLIB
+    if (wxGetApp().getConfig()->getRigEnabled() && !wxGetApp().rigIsActive()) {
+        enableRig();
+        rigEnableMenuItem->Check(true);
+    }
+    
+    std::string deviceId = devInfo->getDeviceId();
+    DeviceConfig *devConfig = wxGetApp().getConfig()->getDevice(deviceId);
+
+    if (wxGetApp().rigIsActive()) {
+        rigSDRIF = devConfig->getRigIF(rigModel);
+        if (rigSDRIF) {
+            wxGetApp().lockFrequency(rigSDRIF);
+        } else {
+            wxGetApp().unlockFrequency();
+        }
+    }
+#endif
+    
+    deviceChanged.store(false);
+}
+
+#ifdef USE_HAMLIB
+void AppFrame::enableRig() {
+    wxGetApp().stopRig();
+    wxGetApp().initRig(rigModel, rigPort, rigSerialRate);
+
+    if (devInfo != nullptr) {
+        std::string deviceId = devInfo->getDeviceId();
+        DeviceConfig *devConfig = wxGetApp().getConfig()->getDevice(deviceId);
+        rigSDRIF = devConfig->getRigIF(rigModel);
+        if (rigSDRIF) {
+            wxGetApp().lockFrequency(rigSDRIF);
+        } else {
+            wxGetApp().unlockFrequency();
+        }
+    } else {
+        wxGetApp().unlockFrequency();
+    }
+    
+    wxGetApp().getConfig()->setRigEnabled(true);
+}
+
+void AppFrame::disableRig() {
+    wxGetApp().stopRig();
+    wxGetApp().unlockFrequency();
+    wxGetApp().getConfig()->setRigEnabled(false);
+}
+#endif
+
+
+void AppFrame::OnMenu(wxCommandEvent& event) {
+
+//    if (event.GetId() >= wxID_RT_AUDIO_DEVICE && event.GetId() < wxID_RT_AUDIO_DEVICE + (int)devices.size()) {
+//        if (activeDemodulator) {
+//            activeDemodulator->setOutputDevice(event.GetId() - wxID_RT_AUDIO_DEVICE);
+//            activeDemodulator = NULL;
+//        }
+//        return;
+//    }
+
+#ifdef __APPLE__
+    if (event.GetId() == wxApp::s_macAboutMenuItemId) {
+        wxMessageDialog *aboutDlg = new wxMessageDialog(NULL, wxT("CubicSDR v" CUBICSDR_VERSION "\nby Charles J. Cliffe (@ccliffe)\nwww.cubicsdr.com"), wxT("CubicSDR v" CUBICSDR_VERSION), wxOK);
+        aboutDlg->ShowModal();
+        return;
+    }
+#endif
+
+    if (event.GetId() == wxID_SDR_START_STOP) {
+        if (!wxGetApp().getSDRThread()->isTerminated()) {
+            wxGetApp().stopDevice(true, 2000);
+        } else {
+            SDRDeviceInfo *dev = wxGetApp().getDevice();
+            if (dev != nullptr) {
+                wxGetApp().setDevice(dev, 0);
+            }
+        }
+    } else if (event.GetId() == wxID_LOW_PERF) {
+        lowPerfMode = lowPerfMenuItem->IsChecked();
+        wxGetApp().getConfig()->setLowPerfMode(lowPerfMode);
+
+//        long srate = wxGetApp().getSampleRate();
+//        if (srate > CHANNELIZER_RATE_MAX && lowPerfMode) {
+//            if (wxGetApp().getSpectrumProcessor()->getFFTSize() != 1024) {
+//                setMainWaterfallFFTSize(1024);
+//            }
+//        } else if (srate > CHANNELIZER_RATE_MAX) {
+//            if (wxGetApp().getSpectrumProcessor()->getFFTSize() != 2048) {
+//                setMainWaterfallFFTSize(2048);
+//            }
+//        }
+
+    } else if (event.GetId() == wxID_SET_TIPS ) {
+        if (wxGetApp().getConfig()->getShowTips()) {
+            wxGetApp().getConfig()->setShowTips(false);
+        } else {
+            wxGetApp().getConfig()->setShowTips(true);
+        }
+    } else if (event.GetId() == wxID_SET_IQSWAP) {
+        wxGetApp().getSDRThread()->setIQSwap(!wxGetApp().getSDRThread()->getIQSwap());
+    } else if (event.GetId() == wxID_SET_FREQ_OFFSET) {
+        long ofs = wxGetNumberFromUser("Shift the displayed frequency by this amount.\ni.e. -125000000 for -125 MHz", "Frequency (Hz)",
+                "Frequency Offset", wxGetApp().getOffset(), -2000000000, 2000000000, this);
+        if (ofs != -1) {
+            wxGetApp().setOffset(ofs);
+        }
+    } else if (event.GetId() == wxID_AGC_CONTROL) {
+        if (wxGetApp().getDevice() == NULL) {
+            agcMenuItem->Check(true);
+            return;
+        }
+        if (!wxGetApp().getAGCMode()) {
+            wxGetApp().setAGCMode(true);
+            gainSpacerItem->Show(false);
+            gainSizerItem->Show(false);
+            demodTray->Layout();
+        } else {
+            wxGetApp().setAGCMode(false);
+            gainSpacerItem->Show(true);
+            gainSizerItem->Show(true);
+            gainSizerItem->SetMinSize(wxGetApp().getDevice()->getSoapyDevice()->listGains(SOAPY_SDR_RX, 0).size()*40,0);
+            demodTray->Layout();
+            gainCanvas->updateGainUI();
+            gainCanvas->Refresh();
+            gainCanvas->Refresh();
+        }
+    } else if (event.GetId() == wxID_SDR_DEVICES) {
+        wxGetApp().deviceSelector();
+    } else if (event.GetId() == wxID_SET_PPM) {
+        long ofs = wxGetNumberFromUser("Frequency correction for device in PPM.\ni.e. -51 for -51 PPM\n\nNote: you can adjust PPM interactively\nby holding ALT over the frequency tuning bar.\n", "Parts per million (PPM)",
+                "Frequency Correction", wxGetApp().getPPM(), -1000, 1000, this);
+            wxGetApp().setPPM(ofs);
+    } else if (event.GetId() == wxID_SAVE) {
+        if (!currentSessionFile.empty()) {
+            saveSession(currentSessionFile);
+        } else {
+            wxFileDialog saveFileDialog(this, _("Save XML Session file"), "", "", "XML files (*.xml)|*.xml", wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
+            if (saveFileDialog.ShowModal() == wxID_CANCEL) {
+                return;
+            }
+            saveSession(saveFileDialog.GetPath().ToStdString());
+        }
+    } else if (event.GetId() == wxID_OPEN) {
+        wxFileDialog openFileDialog(this, _("Open XML Session file"), "", "", "XML files (*.xml)|*.xml", wxFD_OPEN | wxFD_FILE_MUST_EXIST);
+        if (openFileDialog.ShowModal() == wxID_CANCEL) {
+            return;
+        }
+        loadSession(openFileDialog.GetPath().ToStdString());
+    } else if (event.GetId() == wxID_SAVEAS) {
+        wxFileDialog saveFileDialog(this, _("Save XML Session file"), "", "", "XML files (*.xml)|*.xml", wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
+        if (saveFileDialog.ShowModal() == wxID_CANCEL) {
+            return;
+        }
+        saveSession(saveFileDialog.GetPath().ToStdString());
+    } else if (event.GetId() == wxID_RESET) {
+        wxGetApp().getDemodMgr().terminateAll();
+        wxGetApp().setFrequency(100000000);
+        wxGetApp().getDemodMgr().setLastDemodulatorType("FM");
+        demodModeSelector->setSelection(1);
+        wxGetApp().getDemodMgr().setLastMuted(false);
+        wxGetApp().getDemodMgr().setLastBandwidth(DEFAULT_DEMOD_BW);
+        wxGetApp().getDemodMgr().setLastGain(1.0);
+        wxGetApp().getDemodMgr().setLastSquelchLevel(-100);
+        waterfallCanvas->setBandwidth(wxGetApp().getSampleRate());
+        waterfallCanvas->setCenterFrequency(wxGetApp().getFrequency());
+        spectrumCanvas->setBandwidth(wxGetApp().getSampleRate());
+        spectrumCanvas->setCenterFrequency(wxGetApp().getFrequency());
+        waterfallDataThread->setLinesPerSecond(DEFAULT_WATERFALL_LPS);
+        waterfallCanvas->setLinesPerSecond(DEFAULT_WATERFALL_LPS);
+        waterfallSpeedMeter->setLevel(sqrt(DEFAULT_WATERFALL_LPS));
+        wxGetApp().getSpectrumProcessor()->setFFTAverageRate(0.65f);
+        spectrumAvgMeter->setLevel(0.65f);
+        demodModeSelector->Refresh();
+        demodTuner->Refresh();
+        SetTitle(CUBICSDR_TITLE);
+        currentSessionFile = "";
+    } else if (event.GetId() == wxID_CLOSE || event.GetId() == wxID_EXIT) {
+        Close(false);
+    } else if (event.GetId() == wxID_THEME_DEFAULT) {
+        ThemeMgr::mgr.setTheme(COLOR_THEME_DEFAULT);
+    } else if (event.GetId() == wxID_THEME_SHARP) {
+        ThemeMgr::mgr.setTheme(COLOR_THEME_SHARP);
+    } else if (event.GetId() == wxID_THEME_BW) {
+        ThemeMgr::mgr.setTheme(COLOR_THEME_BW);
+    } else if (event.GetId() == wxID_THEME_RAD) {
+        ThemeMgr::mgr.setTheme(COLOR_THEME_RAD);
+    } else if (event.GetId() == wxID_THEME_TOUCH) {
+        ThemeMgr::mgr.setTheme(COLOR_THEME_TOUCH);
+    } else if (event.GetId() == wxID_THEME_HD) {
+        ThemeMgr::mgr.setTheme(COLOR_THEME_HD);
+    } else if (event.GetId() == wxID_THEME_RADAR) {
+        ThemeMgr::mgr.setTheme(COLOR_THEME_RADAR);
+    }
+    //Display : font sizes
+    else if (event.GetId() == wxID_DISPLAY_BASE) {
+        GLFont::setScale(GLFont::GLFONT_SCALE_NORMAL);
+        //force all windows refresh
+        Refresh();
+    }
+    else if (event.GetId() == wxID_DISPLAY_BASE + 1) {
+        GLFont::setScale(GLFont::GLFONT_SCALE_MEDIUM);
+        //force all windows refresh
+        Refresh();
+    }
+    else if (event.GetId() == wxID_DISPLAY_BASE + 2) {
+        GLFont::setScale(GLFont::GLFONT_SCALE_LARGE);
+        //force all windows refresh
+        Refresh();
+    }
+
+    if (event.GetId() >= wxID_SETTINGS_BASE && event.GetId() < settingsIdMax) {
+        int setIdx = event.GetId()-wxID_SETTINGS_BASE;
+        int menuIdx = 0;
+        for (std::vector<SoapySDR::ArgInfo>::iterator arg_i = settingArgs.begin(); arg_i != settingArgs.end(); arg_i++) {
+            SoapySDR::ArgInfo &arg = (*arg_i);
+
+            if (arg.type == SoapySDR::ArgInfo::STRING && arg.options.size() && setIdx >= menuIdx && setIdx < menuIdx+(int)arg.options.size()) {
+                int optIdx = setIdx-menuIdx;
+                wxGetApp().getSDRThread()->writeSetting(arg.key, arg.options[optIdx]);
+                break;
+            } else if (arg.type == SoapySDR::ArgInfo::STRING && arg.options.size()) {
+                menuIdx += arg.options.size();
+            } else if (menuIdx == setIdx) {
+                if (arg.type == SoapySDR::ArgInfo::BOOL) {
+                    wxGetApp().getSDRThread()->writeSetting(arg.key, (wxGetApp().getSDRThread()->readSetting(arg.key)=="true")?"false":"true");
+                    break;
+                } else if (arg.type == SoapySDR::ArgInfo::STRING) {
+                    wxString stringVal = wxGetTextFromUser(arg.description, arg.name, wxGetApp().getSDRThread()->readSetting(arg.key));
+                    if (stringVal.ToStdString() != "") {
+                        wxGetApp().getSDRThread()->writeSetting(arg.key, stringVal.ToStdString());
+                    }
+                    break;
+                } else if (arg.type == SoapySDR::ArgInfo::INT) {
+                    int currentVal;
+                    try {
+                        currentVal = std::stoi(wxGetApp().getSDRThread()->readSetting(arg.key));
+                    } catch (std::invalid_argument e) {
+                        currentVal = 0;
+                    }
+                    int intVal = wxGetNumberFromUser(arg.description, arg.units, arg.name, currentVal, arg.range.minimum(), arg.range.maximum(), this);
+                    if (intVal != -1) {
+                        wxGetApp().getSDRThread()->writeSetting(arg.key, std::to_string(intVal));
+                    }
+                    break;
+                } else if (arg.type == SoapySDR::ArgInfo::FLOAT) {
+                    wxString floatVal = wxGetTextFromUser(arg.description, arg.name, wxGetApp().getSDRThread()->readSetting(arg.key));
+                    try {
+                        wxGetApp().getSDRThread()->writeSetting(arg.key, floatVal.ToStdString());
+                    } catch (std::invalid_argument e) {
+                        // ...
+                    }
+                    break;
+                } else {
+                    menuIdx++;
+                }
+            } else {
+                menuIdx++;
+            }
+        }
+    }
+    
+    if (event.GetId() >= wxID_THEME_DEFAULT && event.GetId() <= wxID_THEME_RADAR) {
+    	demodTuner->Refresh();
+    	demodModeSelector->Refresh();
+        waterfallSpeedMeter->Refresh();
+        spectrumAvgMeter->Refresh();
+        gainCanvas->setThemeColors();
+        modemProps->updateTheme();
+    }
+
+    switch (event.GetId()) {
+        case wxID_BANDWIDTH_MANUAL:
+            int rateHigh, rateLow;
+            
+            SDRDeviceInfo *dev = wxGetApp().getDevice();
+            if (dev == NULL) {
+                break;
+            }
+            
+            std::vector<long> sampleRates = dev->getSampleRates(SOAPY_SDR_RX, 0);
+            
+            rateLow = 2000000;
+            rateHigh = 30000000;
+            
+            if (sampleRates.size()) {
+                rateLow = sampleRates[0];
+                rateHigh = sampleRates[sampleRates.size()-1];
+            }
+
+            long bw = wxGetNumberFromUser("\n" + dev->getName() + "\n\n  "
+                                          + "min: " + std::to_string(rateLow) + " Hz"
+                                          + ", max: " + std::to_string(rateHigh) + " Hz\n",
+                                          "Sample Rate in Hz",
+                                          "Manual Sample Rate Entry",
+                                          wxGetApp().getSampleRate(),
+                                          rateLow,
+                                          rateHigh,
+                                          this);
+            if (bw != -1) {
+                wxGetApp().setSampleRate(bw);
+            }
+            break;
+    }
+    
+    if (event.GetId() >= wxID_BANDWIDTH_BASE && event.GetId() < wxID_BANDWIDTH_BASE + (int)sampleRates.size()) {
+        wxGetApp().setSampleRate(sampleRates[event.GetId()-wxID_BANDWIDTH_BASE]);
+    }
+    
+    if (event.GetId() >= wxID_AUDIO_BANDWIDTH_BASE) {
+        int evId = event.GetId();
+        std::vector<RtAudio::DeviceInfo>::iterator devices_i;
+        std::map<int, RtAudio::DeviceInfo>::iterator mdevices_i;
+
+        int i = 0;
+        for (mdevices_i = outputDevices.begin(); mdevices_i != outputDevices.end(); mdevices_i++) {
+            int menu_id = wxID_AUDIO_BANDWIDTH_BASE + wxID_AUDIO_DEVICE_MULTIPLIER * mdevices_i->first;
+
+            int j = 0;
+            for (std::vector<unsigned int>::iterator srate = mdevices_i->second.sampleRates.begin(); srate != mdevices_i->second.sampleRates.end();
+                    srate++) {
+
+                if (evId == menu_id + j) {
+                    //audioSampleRateMenuItems[menu_id+j];
+                    //std::cout << "Would set audio sample rate on device " << mdevices_i->second.name << " (" << mdevices_i->first << ") to " << (*srate) << "Hz" << std::endl;
+                    AudioThread::setDeviceSampleRate(mdevices_i->first, *srate);
+                }
+
+                j++;
+            }
+            i++;
+        }
+    }
+    
+#ifdef USE_HAMLIB
+
+    bool resetRig = false;
+    if (event.GetId() >= wxID_RIG_MODEL_BASE && event.GetId() < wxID_RIG_MODEL_BASE+numRigs) {
+        int rigIdx = event.GetId()-wxID_RIG_MODEL_BASE;
+        RigList &rl = RigThread::enumerate();
+        rigModel = rl[rigIdx]->rig_model;
+        if (devInfo != nullptr) {
+            std::string deviceId = devInfo->getDeviceId();
+            DeviceConfig *devConfig = wxGetApp().getConfig()->getDevice(deviceId);
+            rigSDRIF = devConfig->getRigIF(rigModel);
+            if (rigSDRIF) {
+                wxGetApp().lockFrequency(rigSDRIF);
+            } else {
+                wxGetApp().unlockFrequency();
+            }
+        } else {
+            wxGetApp().unlockFrequency();
+        }
+        resetRig = true;
+    }
+
+    if (event.GetId() >= wxID_RIG_SERIAL_BASE && event.GetId() < wxID_RIG_SERIAL_BASE+rigSerialRates.size()) {
+        int serialIdx = event.GetId()-wxID_RIG_SERIAL_BASE;
+        rigSerialRate = rigSerialRates[serialIdx];
+        resetRig = true;
+    }
+
+    if (event.GetId() == wxID_RIG_PORT) {
+        wxString stringVal = wxGetTextFromUser("Rig Serial / COM / Address", "Rig Control Port", rigPort);
+        std::string rigPortStr = stringVal.ToStdString();
+        if (rigPortStr != "") {
+            rigPort = rigPortStr;
+            resetRig = true;
+        }
+    }
+
+    if (event.GetId() == wxID_RIG_TOGGLE) {
+        resetRig = false;
+        if (!wxGetApp().rigIsActive()) {
+            enableRig();
+        } else {
+            disableRig();
+        }
+    }
+    
+    if (event.GetId() == wxID_RIG_SDR_IF) {
+        if (devInfo != nullptr) {
+            std::string deviceId = devInfo->getDeviceId();
+            DeviceConfig *devConfig = wxGetApp().getConfig()->getDevice(deviceId);
+            long long freqRigIF = wxGetNumberFromUser("Rig SDR-IF Frequency", "Frequency (Hz)", "Frequency", devConfig->getRigIF(rigModel), 0, 2000000000);
+            if (freqRigIF != -1) {
+                rigSDRIF = freqRigIF;
+                devConfig->setRigIF(rigModel, rigSDRIF);
+            }
+            if (rigSDRIF && wxGetApp().rigIsActive()) {
+                wxGetApp().lockFrequency(rigSDRIF);
+            } else {
+                wxGetApp().unlockFrequency();
+            }
+        }
+    }
+    
+    if (event.GetId() == wxID_RIG_CONTROL) {
+        if (wxGetApp().rigIsActive()) {
+            RigThread *rt = wxGetApp().getRigThread();
+            rt->setControlMode(!rt->getControlMode());
+            rigControlMenuItem->Check(rt->getControlMode());
+            wxGetApp().getConfig()->setRigControlMode(rt->getControlMode());
+        } else {
+            wxGetApp().getConfig()->setRigControlMode(rigControlMenuItem->IsChecked());
+        }
+    }
+
+    if (event.GetId() == wxID_RIG_FOLLOW) {
+        if (wxGetApp().rigIsActive()) {
+            RigThread *rt = wxGetApp().getRigThread();
+            rt->setFollowMode(!rt->getFollowMode());
+            rigFollowMenuItem->Check(rt->getFollowMode());
+            wxGetApp().getConfig()->setRigFollowMode(rt->getFollowMode());
+        } else {
+            wxGetApp().getConfig()->setRigFollowMode(rigFollowMenuItem->IsChecked());
+        }
+    }
+    
+    if (event.GetId() == wxID_RIG_CENTERLOCK) {
+        if (wxGetApp().rigIsActive()) {
+            RigThread *rt = wxGetApp().getRigThread();
+            rt->setCenterLock(!rt->getCenterLock());
+            rigCenterLockMenuItem->Check(rt->getCenterLock());
+            wxGetApp().getConfig()->setRigCenterLock(rt->getCenterLock());
+        } else {
+            wxGetApp().getConfig()->setRigCenterLock(rigCenterLockMenuItem->IsChecked());
+        }
+    }
+
+    if (event.GetId() == wxID_RIG_FOLLOW_MODEM) {
+        if (wxGetApp().rigIsActive()) {
+            RigThread *rt = wxGetApp().getRigThread();
+            rt->setFollowModem(!rt->getFollowModem());
+            rigFollowModemMenuItem->Check(rt->getFollowModem());
+            wxGetApp().getConfig()->setRigFollowModem(rt->getFollowModem());
+        } else {
+            wxGetApp().getConfig()->setRigFollowModem(rigFollowModemMenuItem->IsChecked());
+        }
+    }
+
+    if (wxGetApp().rigIsActive() && resetRig) {
+        wxGetApp().stopRig();
+        wxGetApp().initRig(rigModel, rigPort, rigSerialRate);
+    }
+#endif
+
+}
+
+void AppFrame::OnClose(wxCloseEvent& event) {
+    wxGetApp().closeDeviceSelector();
+
+    wxGetApp().getDemodSpectrumProcessor()->removeOutput(demodSpectrumCanvas->getVisualDataQueue());
+    wxGetApp().getDemodSpectrumProcessor()->removeOutput(demodWaterfallCanvas->getVisualDataQueue());
+    wxGetApp().getSpectrumProcessor()->removeOutput(spectrumCanvas->getVisualDataQueue());
+
+    wxGetApp().getConfig()->setWindow(this->GetPosition(), this->GetClientSize());
+    wxGetApp().getConfig()->setWindowMaximized(this->IsMaximized());
+    wxGetApp().getConfig()->setTheme(ThemeMgr::mgr.getTheme());
+    wxGetApp().getConfig()->setFontScale(GLFont::getScale());
+    wxGetApp().getConfig()->setSnap(wxGetApp().getFrequencySnap());
+    wxGetApp().getConfig()->setCenterFreq(wxGetApp().getFrequency());
+    wxGetApp().getConfig()->setSpectrumAvgSpeed(wxGetApp().getSpectrumProcessor()->getFFTAverageRate());
+    wxGetApp().getConfig()->setWaterfallLinesPerSec(waterfallDataThread->getLinesPerSecond());
+    wxGetApp().getConfig()->setManualDevices(SDREnumerator::getManuals());
+    wxGetApp().getConfig()->setModemPropsCollapsed(modemProps->isCollapsed());
+#ifdef USE_HAMLIB
+    wxGetApp().getConfig()->setRigEnabled(rigEnableMenuItem->IsChecked());
+    wxGetApp().getConfig()->setRigModel(rigModel);
+    wxGetApp().getConfig()->setRigRate(rigSerialRate);
+    wxGetApp().getConfig()->setRigPort(rigPort);
+    wxGetApp().getConfig()->setRigFollowMode(rigFollowMenuItem->IsChecked());
+    wxGetApp().getConfig()->setRigControlMode(rigControlMenuItem->IsChecked());
+    wxGetApp().getConfig()->setRigCenterLock(rigCenterLockMenuItem->IsChecked());
+    wxGetApp().getConfig()->setRigFollowModem(rigFollowModemMenuItem->IsChecked());
+#endif
+    wxGetApp().getConfig()->save();
+    event.Skip();
+}
+
+void AppFrame::OnNewWindow(wxCommandEvent& WXUNUSED(event)) {
+    new AppFrame();
+}
+
+void AppFrame::OnThread(wxCommandEvent& event) {
+    event.Skip();
+}
+
+void AppFrame::OnIdle(wxIdleEvent& event) {
+
+    if (deviceChanged.load()) {
+        updateDeviceParams();
+    }
+    
+    DemodulatorInstance *demod = wxGetApp().getDemodMgr().getLastActiveDemodulator();
+
+    if (demod && demod->isModemInitialized()) {
+        if (demod->isTracking()) {
+            if (spectrumCanvas->getViewState()) {
+                long long diff = abs(demod->getFrequency() - spectrumCanvas->getCenterFrequency()) + (demod->getBandwidth()/2) + (demod->getBandwidth()/4);
+
+                if (diff > spectrumCanvas->getBandwidth()/2) {
+                    if (demod->getBandwidth() > (int)spectrumCanvas->getBandwidth()) {
+                        diff = abs(demod->getFrequency() - spectrumCanvas->getCenterFrequency());
+                    } else {
+                        diff = diff - spectrumCanvas->getBandwidth()/2;
+                    }
+                    spectrumCanvas->moveCenterFrequency((demod->getFrequency() < spectrumCanvas->getCenterFrequency())?diff:-diff);
+                    demod->setTracking(false);
+                }
+            } else {
+                demod->setTracking(false);
+            }
+        }
+        
+        if (demod->getBandwidth() != wxGetApp().getDemodMgr().getLastBandwidth()) {
+            wxGetApp().getDemodMgr().setLastBandwidth(demod->getBandwidth());
+        }
+
+        if (demod != activeDemodulator) {
+            demodSignalMeter->setInputValue(demod->getSquelchLevel());
+            demodGainMeter->setInputValue(demod->getGain());
+            wxGetApp().getDemodMgr().setLastGain(demod->getGain());
+            int outputDevice = demod->getOutputDevice();
+            scopeCanvas->setDeviceName(outputDevices[outputDevice].name);
+//            outputDeviceMenuItems[outputDevice]->Check(true);
+            std::string dType = demod->getDemodulatorType();
+            demodModeSelector->setSelection(dType);
+#ifdef ENABLE_DIGITAL_LAB
+            demodModeSelectorAdv->setSelection(dType);
+#endif
+            deltaLockButton->setSelection(demod->isDeltaLock()?1:-1);
+            demodMuteButton->setSelection(demod->isMuted()?1:-1);
+            modemPropertiesUpdated.store(true);
+            demodTuner->setHalfBand(dType=="USB" || dType=="LSB");
+        }
+        if (demodWaterfallCanvas->getDragState() == WaterfallCanvas::WF_DRAG_NONE) {
+            long long centerFreq = demod->getFrequency();
+            unsigned int demodBw = (unsigned int) ceil((float) demod->getBandwidth() * 2.25);
+
+            if (demod->getDemodulatorType() == "USB") {
+                demodBw /= 2;
+                centerFreq += demod->getBandwidth() / 4;
+            }
+
+            if (demod->getDemodulatorType() == "LSB") {
+                demodBw /= 2;
+                centerFreq -= demod->getBandwidth() / 4;
+            }
+
+            if (demodBw > wxGetApp().getSampleRate() / 2) {
+                demodBw = wxGetApp().getSampleRate() / 2;
+            }
+            if (demodBw < 20000) {
+                demodBw = 20000;
+            }
+
+            if (centerFreq != demodWaterfallCanvas->getCenterFrequency()) {
+                demodWaterfallCanvas->setCenterFrequency(centerFreq);
+                demodSpectrumCanvas->setCenterFrequency(centerFreq);
+            }
+            std::string dSelection = demodModeSelector->getSelectionLabel();
+#ifdef ENABLE_DIGITAL_LAB
+            std::string dSelectionadv = demodModeSelectorAdv->getSelectionLabel();
+
+            // basic demodulators
+            if (dSelection != "" && dSelection != demod->getDemodulatorType()) {
+                demod->setDemodulatorType(dSelection);
+                demodTuner->setHalfBand(dSelection=="USB" || dSelection=="LSB");
+                demodModeSelectorAdv->setSelection(-1);
+            }
+            // advanced demodulators
+			else if (dSelectionadv != "" && dSelectionadv != demod->getDemodulatorType()) {
+				demod->setDemodulatorType(dSelectionadv);
+                demodTuner->setHalfBand(false);
+				demodModeSelector->setSelection(-1);
+            }
+#else
+            // basic demodulators
+            if (dSelection != "" && dSelection != demod->getDemodulatorType()) {
+                demod->setDemodulatorType(dSelection);
+                demodTuner->setHalfBand(dSelection=="USB" || dSelection=="LSB");
+            }
+#endif
+
+            int muteMode = demodMuteButton->getSelection();
+            if (demodMuteButton->modeChanged()) {
+                if (demod->isMuted() && muteMode == -1) {
+                    demod->setMuted(false);
+                } else if (!demod->isMuted() && muteMode == 1) {
+                    demod->setMuted(true);
+                }
+                wxGetApp().getDemodMgr().setLastMuted(demod->isMuted());
+                demodMuteButton->clearModeChanged();
+            } else {
+                if (demod->isMuted() && muteMode == -1) {
+                    demodMuteButton->setSelection(1);
+                    wxGetApp().getDemodMgr().setLastMuted(demod->isMuted());
+                    demodMuteButton->Refresh();
+                } else if (!demod->isMuted() && muteMode == 1) {
+                    demodMuteButton->setSelection(-1);
+                    wxGetApp().getDemodMgr().setLastMuted(demod->isMuted());
+                    demodMuteButton->Refresh();
+                }
+            }
+
+            int deltaMode = deltaLockButton->getSelection();
+            if (deltaLockButton->modeChanged()) {
+                if (demod->isDeltaLock() && deltaMode == -1) {
+                    demod->setDeltaLock(false);
+                } else if (!demod->isDeltaLock() && deltaMode == 1) {
+                    demod->setDeltaLockOfs(demod->getFrequency()-wxGetApp().getFrequency());
+                    demod->setDeltaLock(true);
+                }
+                wxGetApp().getDemodMgr().setLastDeltaLock(demod->isDeltaLock());
+                deltaLockButton->clearModeChanged();
+            } else {
+                if (demod->isDeltaLock() && deltaMode == -1) {
+                    deltaLockButton->setSelection(1);
+                    wxGetApp().getDemodMgr().setLastDeltaLock(true);
+                    deltaLockButton->Refresh();
+                } else if (!demod->isDeltaLock() && deltaMode == 1) {
+                    deltaLockButton->setSelection(-1);
+                    wxGetApp().getDemodMgr().setLastDeltaLock(false);
+                    deltaLockButton->Refresh();
+                }
+            }
+
+            int soloMode = soloModeButton->getSelection();
+            if (soloModeButton->modeChanged()) {
+                if (soloMode == 1) {
+                    wxGetApp().setSoloMode(true);
+                } else {
+                    wxGetApp().setSoloMode(false);
+                }
+                soloModeButton->clearModeChanged();
+            } else {
+                if (wxGetApp().getSoloMode() != (soloMode==1)) {
+                    soloModeButton->setSelection(wxGetApp().getSoloMode()?1:-1);
+                    soloModeButton->Refresh();
+                }
+            }
+            
+            demodWaterfallCanvas->setBandwidth(demodBw);
+            demodSpectrumCanvas->setBandwidth(demodBw);
+        }
+
+        demodSignalMeter->setLevel(demod->getSignalLevel());
+        demodSignalMeter->setMin(demod->getSignalFloor());
+        demodSignalMeter->setMax(demod->getSignalCeil());
+        
+        demodGainMeter->setLevel(demod->getGain());
+        if (demodSignalMeter->inputChanged()) {
+            demod->setSquelchLevel(demodSignalMeter->getInputValue());
+        }
+        if (demodGainMeter->inputChanged()) {
+            demod->setGain(demodGainMeter->getInputValue());
+            demodGainMeter->setLevel(demodGainMeter->getInputValue());
+        }
+        activeDemodulator = demod;
+    } else if (demod) {
+        // Wait state for current demodulator modem to activate..
+    } else {
+        DemodulatorMgr *mgr = &wxGetApp().getDemodMgr();
+
+        std::string dSelection = demodModeSelector->getSelectionLabel();
+#ifdef ENABLE_DIGITAL_LAB
+        std::string dSelectionadv = demodModeSelectorAdv->getSelectionLabel();
+
+        // basic demodulators
+        if (dSelection != "" && dSelection != mgr->getLastDemodulatorType()) {
+            mgr->setLastDemodulatorType(dSelection);
+            mgr->setLastBandwidth(Modem::getModemDefaultSampleRate(dSelection));
+            demodTuner->setHalfBand(dSelection=="USB" || dSelection=="LSB");
+            demodModeSelectorAdv->setSelection(-1);
+        }
+        // advanced demodulators
+        else if(dSelectionadv != "" && dSelectionadv != mgr->getLastDemodulatorType()) {
+            mgr->setLastDemodulatorType(dSelectionadv);
+            mgr->setLastBandwidth(Modem::getModemDefaultSampleRate(dSelectionadv));
+            demodTuner->setHalfBand(false);
+            demodModeSelector->setSelection(-1);
+        }
+#else
+        // basic demodulators
+        if (dSelection != "" && dSelection != mgr->getLastDemodulatorType()) {
+            mgr->setLastDemodulatorType(dSelection);
+			mgr->setLastBandwidth(Modem::getModemDefaultSampleRate(dSelection));
+            demodTuner->setHalfBand(dSelection=="USB" || dSelection=="LSB");
+        }
+#endif
+        demodGainMeter->setLevel(mgr->getLastGain());
+        if (demodSignalMeter->inputChanged()) {
+            mgr->setLastSquelchLevel(demodSignalMeter->getInputValue());
+        }
+        if (demodGainMeter->inputChanged()) {
+            mgr->setLastGain(demodGainMeter->getInputValue());
+            demodGainMeter->setLevel(demodGainMeter->getInputValue());
+        }
+
+        if (wxGetApp().getFrequency() != demodWaterfallCanvas->getCenterFrequency()) {
+            demodWaterfallCanvas->setCenterFrequency(wxGetApp().getFrequency());
+            demodSpectrumCanvas->setCenterFrequency(wxGetApp().getFrequency());
+        }
+        if (spectrumCanvas->getViewState() && abs(wxGetApp().getFrequency()-spectrumCanvas->getCenterFrequency()) > (wxGetApp().getSampleRate()/2)) {
+            spectrumCanvas->setCenterFrequency(wxGetApp().getFrequency());
+            waterfallCanvas->setCenterFrequency(wxGetApp().getFrequency());
+        }
+        if (demodMuteButton->modeChanged()) {
+            int muteMode = demodMuteButton->getSelection();
+            if (muteMode == -1) {
+                wxGetApp().getDemodMgr().setLastMuted(false);
+            } else if (muteMode == 1) {
+                wxGetApp().getDemodMgr().setLastMuted(true);
+            }
+            demodMuteButton->clearModeChanged();
+        }
+    }
+
+    scopeCanvas->setPPMMode(demodTuner->isAltDown());
+    
+    scopeCanvas->setShowDb(spectrumCanvas->getShowDb());
+    wxGetApp().getScopeProcessor()->setScopeEnabled(scopeCanvas->scopeVisible());
+    wxGetApp().getScopeProcessor()->setSpectrumEnabled(scopeCanvas->spectrumVisible());
+    wxGetApp().getAudioVisualQueue()->set_max_num_items((scopeCanvas->scopeVisible()?1:0) + (scopeCanvas->spectrumVisible()?1:0));
+    
+    wxGetApp().getScopeProcessor()->run();
+
+    SpectrumVisualProcessor *proc = wxGetApp().getSpectrumProcessor();
+
+    if (spectrumAvgMeter->inputChanged()) {
+        float val = spectrumAvgMeter->getInputValue();
+        if (val < 0.01) {
+            val = 0.01f;
+        }
+        if (val > 0.99) {
+            val = 0.99f;
+        }
+        spectrumAvgMeter->setLevel(val);
+        proc->setFFTAverageRate(val);
+
+        GetStatusBar()->SetStatusText(wxString::Format(wxT("Spectrum averaging speed changed to %0.2f%%."),val*100.0));
+    }
+    
+    SpectrumVisualProcessor *dproc = wxGetApp().getDemodSpectrumProcessor();
+    
+    dproc->setView(demodWaterfallCanvas->getViewState(), demodWaterfallCanvas->getCenterFrequency(),demodWaterfallCanvas->getBandwidth());
+
+    SpectrumVisualProcessor *wproc = waterfallDataThread->getProcessor();
+    
+    if (waterfallSpeedMeter->inputChanged()) {
+        float val = waterfallSpeedMeter->getInputValue();
+        waterfallSpeedMeter->setLevel(val);
+        waterfallDataThread->setLinesPerSecond((int)ceil(val*val));
+        waterfallCanvas->setLinesPerSecond((int)ceil(val*val));
+        GetStatusBar()->SetStatusText(wxString::Format(wxT("Waterfall max speed changed to %d lines per second."),(int)ceil(val*val)));
+    }
+
+    wproc->setView(waterfallCanvas->getViewState(), waterfallCanvas->getCenterFrequency(), waterfallCanvas->getBandwidth());
+    wxGetApp().getSDRPostThread()->setIQVisualRange(waterfallCanvas->getCenterFrequency(), waterfallCanvas->getBandwidth());
+    
+    proc->setView(wproc->isView(), wproc->getCenterFrequency(), wproc->getBandwidth());
+    
+    demod = wxGetApp().getDemodMgr().getLastActiveDemodulator();
+    
+    if (modemPropertiesUpdated.load() && demod && demod->isModemInitialized()) {
+        
+        //reset notification flag
+        modemPropertiesUpdated.store(false);
+
+        modemProps->initProperties(demod->getModemArgs(), demod);
+        modemProps->updateTheme(); 
+        demodTray->Layout();
+        modemProps->fitColumns();
+#if ENABLE_DIGITAL_LAB
+        if (demod->getModemType() == "digital") {
+            ModemDigitalOutputConsole *outp = (ModemDigitalOutputConsole *)demod->getOutput();
+            if (!outp->getDialog()) {
+                outp->setTitle(demod->getDemodulatorType() + ": " + frequencyToStr(demod->getFrequency()));
+                outp->setDialog(new DigitalConsole(this, outp));
+            }
+            demod->showOutput();
+        }
+#endif
+    } else if (!demod) {
+        modemProps->Hide();
+        demodTray->Layout();
+    }
+    
+    if (modemProps->IsShown() && modemProps->isCollapsed() && modemProps->GetMinWidth() > 22) {
+        modemProps->SetMinSize(wxSize(APPFRAME_MODEMPROPS_MINSIZE,-1));
+        modemProps->SetMaxSize(wxSize(APPFRAME_MODEMPROPS_MINSIZE,-1));
+        demodTray->Layout();
+        modemProps->fitColumns();
+    } else if (modemProps->IsShown() && !modemProps->isCollapsed() && modemProps->GetMinWidth() < 200) {
+        modemProps->SetMinSize(wxSize(APPFRAME_MODEMPROPS_MAXSIZE,-1));
+        modemProps->SetMaxSize(wxSize(APPFRAME_MODEMPROPS_MAXSIZE,-1));
+        demodTray->Layout();
+        modemProps->fitColumns();
+    }
+    
+    int peakHoldMode = peakHoldButton->getSelection();
+    if (peakHoldButton->modeChanged()) {
+        wxGetApp().getSpectrumProcessor()->setPeakHold(peakHoldMode == 1);
+
+        //make the peak hold act on the current dmod also, like a zoomed-in version.
+        wxGetApp().getDemodSpectrumProcessor()->setPeakHold(peakHoldMode == 1);
+        peakHoldButton->clearModeChanged();
+    }
+    
+#if USE_HAMLIB
+    if (rigEnableMenuItem->IsChecked()) {
+        if (!wxGetApp().rigIsActive()) {
+            rigEnableMenuItem->Check(false);
+            wxGetApp().getConfig()->setRigEnabled(false);
+        }
+    }
+#endif
+    
+#ifdef _WIN32
+    if (scopeCanvas->HasFocus()) {
+        waterfallCanvas->SetFocus();
+    }
+#endif
+    
+    if (!this->IsActive()) {
+        std::this_thread::sleep_for(std::chrono::milliseconds(30));
+    } else {
+        if (lowPerfMode) {
+            std::this_thread::sleep_for(std::chrono::milliseconds(30));
+        } else {
+            std::this_thread::sleep_for(std::chrono::milliseconds(1));
+        }
+    }
+    
+    event.RequestMore();
+}
+
+
+void AppFrame::OnDoubleClickSash(wxSplitterEvent& event)
+{
+    wxWindow *a, *b;
+    wxSplitterWindow *w = NULL;
+    float g = 0.5;
+
+    if (event.GetId() == wxID_MAIN_SPLITTER) {
+        w = mainSplitter;
+        g = 10.0f/37.0f;
+    } else if (event.GetId() == wxID_VIS_SPLITTER) {
+        w = mainVisSplitter;
+        g = 6.0f/25.0f;
+    }
+
+    if (w != NULL) {
+        a = w->GetWindow1();
+        b = w->GetWindow2();
+        w->Unsplit();
+        w->SetSashGravity(g);
+        wxSize s = w->GetSize();
+        
+        w->SplitHorizontally(a, b, int(float(s.GetHeight()) * g));
+    }
+    
+    event.Veto();
+}
+
+void AppFrame::OnUnSplit(wxSplitterEvent& event)
+{
+    event.Veto();
+}
+
+
+
+void AppFrame::saveSession(std::string fileName) {
+    DataTree s("cubicsdr_session");
+    DataNode *header = s.rootNode()->newChild("header");
+    *header->newChild("version") = std::string(CUBICSDR_VERSION);
+    *header->newChild("center_freq") = wxGetApp().getFrequency();
+    *header->newChild("sample_rate") = wxGetApp().getSampleRate();
+    
+    if (waterfallCanvas->getViewState()) {
+        DataNode *viewState = header->newChild("view_state");
+        
+        *viewState->newChild("center_freq") = waterfallCanvas->getCenterFrequency();
+        *viewState->newChild("bandwidth") = waterfallCanvas->getBandwidth();
+    }
+    
+    DataNode *demods = s.rootNode()->newChild("demodulators");
+
+    std::vector<DemodulatorInstance *> &instances = wxGetApp().getDemodMgr().getDemodulators();
+    std::vector<DemodulatorInstance *>::iterator instance_i;
+    for (instance_i = instances.begin(); instance_i != instances.end(); instance_i++) {
+        DataNode *demod = demods->newChild("demodulator");
+        *demod->newChild("bandwidth") = (*instance_i)->getBandwidth();
+        *demod->newChild("frequency") = (*instance_i)->getFrequency();
+        *demod->newChild("type") = (*instance_i)->getDemodulatorType();
+
+        demod->newChild("user_label")->element()->set((*instance_i)->getDemodulatorUserLabel());
+
+        *demod->newChild("squelch_level") = (*instance_i)->getSquelchLevel();
+        *demod->newChild("squelch_enabled") = (*instance_i)->isSquelchEnabled() ? 1 : 0;
+        *demod->newChild("output_device") = outputDevices[(*instance_i)->getOutputDevice()].name;
+        *demod->newChild("gain") = (*instance_i)->getGain();
+        *demod->newChild("muted") = (*instance_i)->isMuted() ? 1 : 0;
+        if ((*instance_i)->isDeltaLock()) {
+            *demod->newChild("delta_lock") = (*instance_i)->isDeltaLock() ? 1 : 0;
+            *demod->newChild("delta_ofs") = (*instance_i)->getDeltaLockOfs();
+        }
+        if ((*instance_i) == wxGetApp().getDemodMgr().getLastActiveDemodulator()) {
+            *demod->newChild("active") = 1;
+        }
+
+        ModemSettings saveSettings = (*instance_i)->readModemSettings();
+        if (saveSettings.size()) {
+            DataNode *settingsNode = demod->newChild("settings");
+            for (ModemSettings::const_iterator msi = saveSettings.begin(); msi != saveSettings.end(); msi++) {
+                *settingsNode->newChild(msi->first.c_str()) = msi->second;
+            }
+        }
+    } //end for demodulators
+
+    // Make sure the file name actually ends in .xml
+    std::string lcFileName = fileName;
+    std::transform(lcFileName.begin(), lcFileName.end(), lcFileName.begin(), ::tolower);
+    
+    if (lcFileName.find_last_of(".xml") != lcFileName.length()-1) {
+        fileName.append(".xml");
+    }
+    
+    s.SaveToFileXML(fileName);
+
+    currentSessionFile = fileName;
+    std::string filePart = fileName.substr(fileName.find_last_of(filePathSeparator) + 1);
+    GetStatusBar()->SetStatusText(wxString::Format(wxT("Saved session: %s"), currentSessionFile.c_str()));
+    SetTitle(wxString::Format(wxT("%s: %s"), CUBICSDR_TITLE, filePart.c_str()));
+}
+
+bool AppFrame::loadSession(std::string fileName) {
+    DataTree l;
+    if (!l.LoadFromFileXML(fileName)) {
+        return false;
+    }
+
+	wxGetApp().getDemodMgr().setActiveDemodulator(nullptr, false);
+
+    wxGetApp().getDemodMgr().terminateAll();
+
+    try {
+        if (!l.rootNode()->hasAnother("header")) {
+            return false;
+        }
+        DataNode *header = l.rootNode()->getNext("header");
+
+        if (header->hasAnother("version")) {
+        std::string version(*header->getNext("version"));
+//            std::cout << "Loading " << version << " session file" << std::endl;
+        }
+
+        if (header->hasAnother("sample_rate")) {
+            int sample_rate = *header->getNext("sample_rate");
+            
+            SDRDeviceInfo *dev = wxGetApp().getSDRThread()->getDevice();
+            if (dev) {
+                // Try for a reasonable default sample rate.
+                sample_rate = dev->getSampleRateNear(SOAPY_SDR_RX, 0, sample_rate);
+                wxGetApp().setSampleRate(sample_rate);
+                deviceChanged.store(true);
+            } else {
+                wxGetApp().setSampleRate(sample_rate);
+            }
+            
+        }
+
+        DemodulatorInstance *loadedActiveDemod = nullptr;
+        DemodulatorInstance *newDemod = nullptr;
+        
+        if (l.rootNode()->hasAnother("demodulators")) {
+            
+        DataNode *demodulators = l.rootNode()->getNext("demodulators");
+
+        int numDemodulators = 0;
+        std::vector<DemodulatorInstance *> demodsLoaded;
+        
+        while (demodulators->hasAnother("demodulator")) {
+            DataNode *demod = demodulators->getNext("demodulator");
+
+            if (!demod->hasAnother("bandwidth") || !demod->hasAnother("frequency")) {
+                continue;
+            }
+
+            long bandwidth = *demod->getNext("bandwidth");
+            long long freq = *demod->getNext("frequency");
+            float squelch_level = demod->hasAnother("squelch_level") ? (float) *demod->getNext("squelch_level") : 0;
+            int squelch_enabled = demod->hasAnother("squelch_enabled") ? (int) *demod->getNext("squelch_enabled") : 0;
+            int muted = demod->hasAnother("muted") ? (int) *demod->getNext("muted") : 0;
+            int delta_locked = demod->hasAnother("delta_lock") ? (int) *demod->getNext("delta_lock") : 0;
+            int delta_ofs = demod->hasAnother("delta_ofs") ? (int) *demod->getNext("delta_ofs") : 0;
+            std::string output_device = demod->hasAnother("output_device") ? string(*(demod->getNext("output_device"))) : "";
+            float gain = demod->hasAnother("gain") ? (float) *demod->getNext("gain") : 1.0;
+            
+            std::string type = "FM";
+            
+
+            DataNode *demodTypeNode = demod->hasAnother("type")?demod->getNext("type"):nullptr;
+            
+            if (demodTypeNode && demodTypeNode->element()->getDataType() == DATA_INT) {
+                int legacyType = *demodTypeNode;
+                int legacyStereo = demod->hasAnother("stereo") ? (int) *demod->getNext("stereo") : 0;
+                switch (legacyType) {   // legacy demod ID
+                    case 1: type = legacyStereo?"FMS":"FM"; break;
+                    case 2: type = "AM"; break;
+                    case 3: type = "LSB"; break;
+                    case 4: type = "USB"; break;
+                    case 5: type = "DSB"; break;
+                    case 6: type = "ASK"; break;
+                    case 7: type = "APSK"; break;
+                    case 8: type = "BPSK"; break;
+                    case 9: type = "DPSK"; break;
+                    case 10: type = "PSK"; break;
+                    case 11: type = "OOK"; break;
+                    case 12: type = "ST"; break;
+                    case 13: type = "SQAM"; break;
+                    case 14: type = "QAM"; break;
+                    case 15: type = "QPSK"; break;
+                    case 16: type = "I/Q"; break;
+                    default: type = "FM"; break;
+                }
+            } else if (demodTypeNode && demodTypeNode->element()->getDataType() == DATA_STRING) {
+                demodTypeNode->element()->get(type);
+            }
+
+            //read the user label associated with the demodulator
+            std::wstring user_label = L"";
+
+            DataNode *demodUserLabel = demod->hasAnother("user_label") ? demod->getNext("user_label") : nullptr;
+
+            if (demodUserLabel) {
+              
+                demodUserLabel->element()->get(user_label);
+            }
+           
+
+            ModemSettings mSettings;
+            
+            if (demod->hasAnother("settings")) {
+                DataNode *modemSettings = demod->getNext("settings");
+                for (int msi = 0, numSettings = modemSettings->numChildren(); msi < numSettings; msi++) {
+                    DataNode *settingNode = modemSettings->child(msi);
+                    std::string keyName = settingNode->getName();
+                    std::string strSettingValue = settingNode->element()->toString();
+                    
+                    if (keyName != "" && strSettingValue != "") {
+                        mSettings[keyName] = strSettingValue;
+                    }
+                }
+            }
+
+           
+            
+            newDemod = wxGetApp().getDemodMgr().newThread();
+
+            if (demod->hasAnother("active")) {
+                loadedActiveDemod = newDemod;
+            }
+
+            numDemodulators++;
+            newDemod->setDemodulatorType(type);
+            newDemod->setDemodulatorUserLabel(user_label);
+            newDemod->writeModemSettings(mSettings);
+            newDemod->setBandwidth(bandwidth);
+            newDemod->setFrequency(freq);
+            newDemod->setGain(gain);
+            newDemod->updateLabel(freq);
+            newDemod->setMuted(muted?true:false);
+            if (delta_locked) {
+                newDemod->setDeltaLock(true);
+                newDemod->setDeltaLockOfs(delta_ofs);
+            }
+            if (squelch_enabled) {
+                newDemod->setSquelchEnabled(true);
+                newDemod->setSquelchLevel(squelch_level);
+            }
+            
+            bool found_device = false;
+            std::map<int, RtAudio::DeviceInfo>::iterator i;
+            for (i = outputDevices.begin(); i != outputDevices.end(); i++) {
+                if (i->second.name == output_device) {
+                    newDemod->setOutputDevice(i->first);
+                    found_device = true;
+                }
+            }
+
+//                if (!found_device) {
+//                    std::cout << "\tWarning: named output device '" << output_device << "' was not found. Using default output.";
+//                }
+
+            newDemod->run();
+            newDemod->setActive(true);
+            demodsLoaded.push_back(newDemod);
+//            wxGetApp().bindDemodulator(newDemod);
+
+                std::cout << "\tAdded demodulator at frequency " << newDemod->getFrequency() << " type " << type << std::endl;
+//                std::cout << "\t\tBandwidth: " << bandwidth << std::endl;
+//                std::cout << "\t\tSquelch Level: " << squelch_level << std::endl;
+//                std::cout << "\t\tSquelch Enabled: " << (squelch_enabled ? "true" : "false") << std::endl;
+//                std::cout << "\t\tOutput Device: " << output_device << std::endl;
+        }
+        
+        if (demodsLoaded.size()) {
+            wxGetApp().bindDemodulators(&demodsLoaded);
+        }
+            
+        } // if l.rootNode()->hasAnother("demodulators")
+        
+        if (header->hasAnother("center_freq")) {
+            long long center_freq = *header->getNext("center_freq");
+            wxGetApp().setFrequency(center_freq);
+            //            std::cout << "\tCenter Frequency: " << center_freq << std::endl;
+        }
+
+        if (header->hasAnother("view_state")) {
+            DataNode *viewState = header->getNext("view_state");
+            
+            if (viewState->hasAnother("center_freq") && viewState->hasAnother("bandwidth")) {
+                long long center_freq = *viewState->getNext("center_freq");
+                int bandwidth = *viewState->getNext("bandwidth");
+                spectrumCanvas->setView(center_freq, bandwidth);
+                waterfallCanvas->setView(center_freq, bandwidth);
+            }
+        } else {
+            spectrumCanvas->disableView();
+            waterfallCanvas->disableView();
+            spectrumCanvas->setCenterFrequency(wxGetApp().getFrequency());
+            waterfallCanvas->setCenterFrequency(wxGetApp().getFrequency());
+        }
+        
+        if (loadedActiveDemod || newDemod) {
+            wxGetApp().getDemodMgr().setActiveDemodulator(loadedActiveDemod?loadedActiveDemod:newDemod, false);
+        }
+    } catch (DataTypeMismatchException &e) {
+        std::cout << e.what() << std::endl;
+        return false;
+    }
+
+    currentSessionFile = fileName;
+
+    std::string filePart = fileName.substr(fileName.find_last_of(filePathSeparator) + 1);
+
+    GetStatusBar()->SetStatusText(wxString::Format(wxT("Loaded session file: %s"), currentSessionFile.c_str()));
+    SetTitle(wxString::Format(wxT("%s: %s"), CUBICSDR_TITLE, filePart.c_str()));
+
+    return true;
+}
+
+FFTVisualDataThread *AppFrame::getWaterfallDataThread() {
+    return waterfallDataThread;
+}
+
+void AppFrame::notifyUpdateModemProperties() {
+   
+    modemPropertiesUpdated.store(true);
+}
+
+void AppFrame::setMainWaterfallFFTSize(int fftSize) {
+    wxGetApp().getSpectrumProcessor()->setFFTSize(fftSize);
+    spectrumCanvas->setFFTSize(fftSize);
+    waterfallDataThread->getProcessor()->setFFTSize(fftSize);
+    waterfallCanvas->setFFTSize(fftSize);
+}
+
+void AppFrame::setScopeDeviceName(std::string deviceName) {
+    scopeCanvas->setDeviceName(deviceName);
+}
+
+
+void AppFrame::refreshGainUI() {
+    gainCanvas->updateGainUI();
+    gainCanvas->Refresh();
+}
+
+bool AppFrame::isUserDemodBusy() {
+    return (modemProps && modemProps->isMouseInView())
+        || (waterfallCanvas->isMouseInView() && waterfallCanvas->isMouseDown())
+        || (demodWaterfallCanvas->isMouseInView() && demodWaterfallCanvas->isMouseDown())
+        || (wxGetApp().getDemodMgr().getLastActiveDemodulator() &&
+            wxGetApp().getDemodMgr().getActiveDemodulator() &&
+            wxGetApp().getDemodMgr().getLastActiveDemodulator() != wxGetApp().getDemodMgr().getActiveDemodulator());
+}
+
+
+#ifdef _WIN32
+bool AppFrame::canFocus() {
+	return (!wxGetApp().isDeviceSelectorOpen() && (!modemProps || !modemProps->isMouseInView()));
+}
+#endif
+
+FrequencyDialog::FrequencyDialogTarget AppFrame::getFrequencyDialogTarget() {
+    FrequencyDialog::FrequencyDialogTarget target = FrequencyDialog::FrequencyDialogTarget::FDIALOG_TARGET_DEFAULT;
+    
+    if (waterfallSpeedMeter->getMouseTracker()->mouseInView()) {
+        target = FrequencyDialog::FrequencyDialogTarget::FDIALOG_TARGET_WATERFALL_LPS;
+    }
+    else if (spectrumAvgMeter->getMouseTracker()->mouseInView()) {
+        target = FrequencyDialog::FrequencyDialogTarget::FDIALOG_TARGET_SPECTRUM_AVG;
+    }
+    else if (demodTuner->getMouseTracker()->mouseInView()) {
+        switch (demodTuner->getHoverState()) {
+            case TuningCanvas::ActiveState::TUNING_HOVER_BW:
+                target = FrequencyDialog::FrequencyDialogTarget::FDIALOG_TARGET_BANDWIDTH;
+                break;
+            case TuningCanvas::ActiveState::TUNING_HOVER_FREQ:
+                target = FrequencyDialog::FrequencyDialogTarget::FDIALOG_TARGET_FREQ;
+                break;
+            case TuningCanvas::ActiveState::TUNING_HOVER_CENTER:
+            default:
+                target = FrequencyDialog::FrequencyDialogTarget::FDIALOG_TARGET_DEFAULT;
+                break;
+                
+        }
+    }
+    else if (gainCanvas->getMouseTracker()->mouseInView()) {
+        target = FrequencyDialog::FrequencyDialogTarget::FDIALOG_TARGET_GAIN;
+    }
+    return target;
+}
+
+void AppFrame::gkNudgeLeft(DemodulatorInstance *demod, int snap) {
+    if (demod) {
+        demod->setFrequency(demod->getFrequency()-snap);
+        demod->updateLabel(demod->getFrequency());
+    }
+}
+
+void AppFrame::gkNudgeRight(DemodulatorInstance *demod, int snap) {
+    if (demod) {
+        demod->setFrequency(demod->getFrequency()+snap);
+        demod->updateLabel(demod->getFrequency());
+    }
+}
+
+int AppFrame::OnGlobalKeyDown(wxKeyEvent &event) {
+    if (!this->IsActive()) {
+        return -1;
+    }
+    if (modemProps && (modemProps->HasFocus() || modemProps->isMouseInView())) {
+        return -1;
+    }
+    
+    DemodulatorInstance *demod = nullptr, *lastDemod = wxGetApp().getDemodMgr().getLastActiveDemodulator();
+    int snap = wxGetApp().getFrequencySnap();
+    
+    if (event.ControlDown()) {
+        return 1;
+    }
+    
+    if (event.ShiftDown()) {
+        if (snap != 1) {
+            snap /= 2;
+        }
+    }
+    
+    #ifdef wxHAS_RAW_KEY_CODES
+    switch (event.GetRawKeyCode()) {
+        case 30:
+            gkNudgeRight(lastDemod, snap);
+            return 1;
+        case 33:
+            gkNudgeLeft(lastDemod, snap);
+            return 1;
+    }
+    #endif
+    
+    
+    switch (event.GetKeyCode()) {
+        case WXK_UP:
+        case WXK_NUMPAD_UP:
+        case WXK_DOWN:
+        case WXK_NUMPAD_DOWN:
+        case WXK_LEFT:
+        case WXK_NUMPAD_LEFT:
+        case WXK_RIGHT:
+        case WXK_NUMPAD_RIGHT:
+            waterfallCanvas->OnKeyDown(event);  // TODO: Move the stuff from there to here
+            return 1;
+        case 'V':
+            return 1;
+        case ']':
+            gkNudgeRight(lastDemod, snap);
+            return 1;
+        case '[':
+            gkNudgeLeft(lastDemod, snap);
+            return 1;
+        case 'A':
+        case 'F':
+        case 'L':
+        case 'U':
+        case 'S':
+        case 'P':
+        case 'M':
+            return 1;
+        case '0':
+        case '1':
+        case '2':
+        case '3':
+        case '4':
+        case '5':
+        case '6':
+        case '7':
+        case '8':
+        case '9':
+            wxGetApp().showFrequencyInput(getFrequencyDialogTarget(), std::to_string(event.GetKeyCode() - '0'));
+            return 1;
+            break;
+        case WXK_TAB:
+            lastDemod = wxGetApp().getDemodMgr().getLastActiveDemodulator();
+            if (!lastDemod) {
+                break;
+            }
+            if (event.ShiftDown()) {
+                demod = wxGetApp().getDemodMgr().getPreviousDemodulator(lastDemod);
+            } else {
+                demod = wxGetApp().getDemodMgr().getNextDemodulator(lastDemod);
+            }
+            if (demod) {
+                wxGetApp().getDemodMgr().setActiveDemodulator(nullptr);
+                wxGetApp().getDemodMgr().setActiveDemodulator(demod, false);
+            }
+            return 1;
+        default:
+            break;
+    }
+    
+    if (demodTuner->getMouseTracker()->mouseInView()) {
+        demodTuner->OnKeyDown(event);
+    } else if (waterfallCanvas->getMouseTracker()->mouseInView()) {
+        waterfallCanvas->OnKeyDown(event);
+    }
+    
+    return 1;
+}
+
+int AppFrame::OnGlobalKeyUp(wxKeyEvent &event) {
+    if (!this->IsActive()) {
+        return -1;
+    }
+    if (modemProps && (modemProps->HasFocus() || modemProps->isMouseInView())) {
+        return -1;
+    }
+
+    if (event.ControlDown()) {
+        return 1;
+    }
+
+    DemodulatorInstance *activeDemod = wxGetApp().getDemodMgr().getActiveDemodulator();
+    DemodulatorInstance *lastDemod = wxGetApp().getDemodMgr().getLastActiveDemodulator();
+    
+#ifdef wxHAS_RAW_KEY_CODES
+    switch (event.GetRawKeyCode()) {
+        case 30:
+            return 1;
+        case 33:
+            return 1;
+    }
+#endif
+    
+    switch (event.GetKeyCode()) {
+        case WXK_SPACE:
+            if (!demodTuner->getMouseTracker()->mouseInView()) {
+                wxGetApp().showFrequencyInput(getFrequencyDialogTarget());
+                return 1;
+            }
+            break;
+        case WXK_UP:
+        case WXK_NUMPAD_UP:
+        case WXK_DOWN:
+        case WXK_NUMPAD_DOWN:
+        case WXK_LEFT:
+        case WXK_NUMPAD_LEFT:
+        case WXK_RIGHT:
+        case WXK_NUMPAD_RIGHT:
+            waterfallCanvas->OnKeyUp(event);
+            return 1;
+        case 'V':
+            if (activeDemod) {
+                lastDemod = activeDemod;
+            }
+            if (lastDemod && lastDemod->isDeltaLock()) {
+                lastDemod->setDeltaLock(false);
+            } else if (lastDemod) {
+                lastDemod->setDeltaLockOfs(lastDemod->getFrequency() - wxGetApp().getFrequency());
+                lastDemod->setDeltaLock(true);
+            }
+            break;
+        case 'A':
+            demodModeSelector->setSelection("AM");
+            return 1;
+            break;
+        case 'F':
+            if (demodModeSelector->getSelectionLabel() == "FM") {
+                demodModeSelector->setSelection("FMS");
+            } else if (demodModeSelector->getSelectionLabel() == "FMS") {
+                demodModeSelector->setSelection("NBFM");
+            } else if (demodModeSelector->getSelectionLabel() == "NBFM") {
+                demodModeSelector->setSelection("FM");
+            }
+            return 1;
+            break;
+        case 'L':
+            demodModeSelector->setSelection("LSB");
+            return 1;
+            break;
+        case 'U':
+            demodModeSelector->setSelection("USB");
+            return 1;
+            break;
+        case 'S':
+            wxGetApp().setSoloMode(!wxGetApp().getSoloMode());
+            return 1;
+            break;
+        case 'P':
+            wxGetApp().getSpectrumProcessor()->setPeakHold(!wxGetApp().getSpectrumProcessor()->getPeakHold());
+            wxGetApp().getDemodSpectrumProcessor()->setPeakHold(wxGetApp().getSpectrumProcessor()->getPeakHold());
+            peakHoldButton->setSelection(wxGetApp().getSpectrumProcessor()->getPeakHold()?1:0);
+            peakHoldButton->clearModeChanged();
+            break;
+        case ']':
+        case '[':
+            return 1;
+        case 'M':
+            if (activeDemod) {
+                lastDemod = activeDemod;
+            }
+            if (lastDemod) {
+                lastDemod->setMuted(!lastDemod->isMuted());
+            }
+            break;
+        default:
+            break;
+    }
+    
+    if (demodTuner->getMouseTracker()->mouseInView()) {
+        demodTuner->OnKeyUp(event);
+    } else if (waterfallCanvas->getMouseTracker()->mouseInView()) {
+        waterfallCanvas->OnKeyUp(event);
+    }
+    
+    
+    // TODO: Catch key-ups outside of original target
+
+    return 1;
+}
+
+
+void AppFrame::setWaterfallLinesPerSecond(int lps) {
+    waterfallSpeedMeter->setUserInputValue(sqrt(lps));
+}
+
+void AppFrame::setSpectrumAvgSpeed(double avg) {
+    spectrumAvgMeter->setUserInputValue(avg);
+}
+
+void AppFrame::setViewState(long long center_freq, int bandwidth) {
+    spectrumCanvas->setView(center_freq, bandwidth);
+    waterfallCanvas->setView(center_freq, bandwidth);
+}
+
+void AppFrame::setViewState(long long center_freq) {
+    spectrumCanvas->setCenterFrequency(wxGetApp().getFrequency());
+    waterfallCanvas->setCenterFrequency(wxGetApp().getFrequency());
+    spectrumCanvas->disableView();
+    waterfallCanvas->disableView();
+}
diff --git a/src/AppFrame.h b/src/AppFrame.h
new file mode 100644
index 0000000..4c2acee
--- /dev/null
+++ b/src/AppFrame.h
@@ -0,0 +1,199 @@
+#pragma once
+
+#include <wx/frame.h>
+#include <wx/panel.h>
+#include <wx/splitter.h>
+#include <wx/sizer.h>
+
+#include "PrimaryGLContext.h"
+
+#include "ScopeCanvas.h"
+#include "SpectrumCanvas.h"
+#include "WaterfallCanvas.h"
+#include "MeterCanvas.h"
+#include "TuningCanvas.h"
+#include "ModeSelectorCanvas.h"
+#include "GainCanvas.h"
+#include "FFTVisualDataThread.h"
+#include "SDRDeviceInfo.h"
+#include "ModemProperties.h"
+//#include "UITestCanvas.h"
+#include "FrequencyDialog.h"
+
+#include <map>
+
+#define wxID_RT_AUDIO_DEVICE 1000
+#define wxID_SET_FREQ_OFFSET 2001
+#define wxID_RESET 2002
+#define wxID_SET_PPM 2003
+#define wxID_SET_TIPS 2004
+#define wxID_SET_IQSWAP 2005
+#define wxID_SDR_DEVICES 2008
+#define wxID_AGC_CONTROL 2009
+#define wxID_SDR_START_STOP 2010
+#define wxID_LOW_PERF 2011
+
+#define wxID_MAIN_SPLITTER 2050
+#define wxID_VIS_SPLITTER 2051
+
+#define wxID_THEME_DEFAULT 2100
+#define wxID_THEME_SHARP 2101
+#define wxID_THEME_BW 2102
+#define wxID_THEME_RAD 2103
+#define wxID_THEME_TOUCH 2104
+#define wxID_THEME_HD 2105
+#define wxID_THEME_RADAR 2106
+
+#define wxID_BANDWIDTH_BASE 2150
+#define wxID_BANDWIDTH_MANUAL 2200
+
+#define wxID_DISPLAY_BASE 2250
+
+#define wxID_SETTINGS_BASE 2300
+
+#define wxID_DEVICE_ID 3500
+
+#define wxID_AUDIO_BANDWIDTH_BASE 9000
+#define wxID_AUDIO_DEVICE_MULTIPLIER 50
+
+#ifdef USE_HAMLIB
+#define wxID_RIG_TOGGLE 11900
+#define wxID_RIG_PORT 11901
+#define wxID_RIG_SDR_IF 11902
+#define wxID_RIG_CONTROL 11903
+#define wxID_RIG_FOLLOW 11904
+#define wxID_RIG_CENTERLOCK 11905
+#define wxID_RIG_FOLLOW_MODEM 11906
+#define wxID_RIG_SERIAL_BASE 11950
+#define wxID_RIG_MODEL_BASE 12000
+#endif
+
+// Define a new frame type
+class AppFrame: public wxFrame {
+public:
+    AppFrame();
+    ~AppFrame();
+    void OnThread(wxCommandEvent& event);
+    void OnEventInput(wxThreadEvent& event);
+    void initDeviceParams(SDRDeviceInfo *devInfo);
+    void updateDeviceParams();
+
+    void saveSession(std::string fileName);
+    bool loadSession(std::string fileName);
+
+    FFTVisualDataThread *getWaterfallDataThread();
+
+    void notifyUpdateModemProperties();
+    void setMainWaterfallFFTSize(int fftSize);
+    void setScopeDeviceName(std::string deviceName);
+
+    void gkNudgeLeft(DemodulatorInstance *demod, int snap);
+    void gkNudgeRight(DemodulatorInstance *demod, int snap);
+
+    int OnGlobalKeyDown(wxKeyEvent &event);
+    int OnGlobalKeyUp(wxKeyEvent &event);
+    
+    void setWaterfallLinesPerSecond(int lps);
+    void setSpectrumAvgSpeed(double avg);
+    
+    FrequencyDialog::FrequencyDialogTarget getFrequencyDialogTarget();
+    void refreshGainUI();
+    void setViewState(long long center_freq, int bandwidth);
+    void setViewState(long long center_freq);
+    
+    bool isUserDemodBusy();
+        
+#ifdef _WIN32
+	bool canFocus();
+#endif
+    
+private:
+    void OnMenu(wxCommandEvent& event);
+    void OnClose(wxCloseEvent& event);
+    void OnNewWindow(wxCommandEvent& event);
+    void OnIdle(wxIdleEvent& event);
+    void OnDoubleClickSash(wxSplitterEvent& event);
+    void OnUnSplit(wxSplitterEvent& event);
+   
+    ScopeCanvas *scopeCanvas;
+    SpectrumCanvas *spectrumCanvas;
+    WaterfallCanvas *waterfallCanvas;
+    ModeSelectorCanvas *demodModeSelector;
+#ifdef ENABLE_DIGITAL_LAB
+    ModeSelectorCanvas *demodModeSelectorAdv;
+#endif
+    SpectrumCanvas *demodSpectrumCanvas;
+    WaterfallCanvas *demodWaterfallCanvas;
+    MeterCanvas *demodSignalMeter;
+    MeterCanvas *demodGainMeter;
+    TuningCanvas *demodTuner;
+//    UITestCanvas *testCanvas;
+    MeterCanvas *spectrumAvgMeter;
+    MeterCanvas *waterfallSpeedMeter;
+    ModeSelectorCanvas *demodMuteButton, *peakHoldButton, *soloModeButton, *deltaLockButton;
+    GainCanvas *gainCanvas;
+    wxSizerItem *gainSizerItem, *gainSpacerItem;
+    wxSplitterWindow *mainVisSplitter, *mainSplitter;
+    wxBoxSizer *demodTray;
+    
+    DemodulatorInstance *activeDemodulator;
+
+    std::vector<RtAudio::DeviceInfo> devices;
+    std::map<int,RtAudio::DeviceInfo> inputDevices;
+    std::map<int,RtAudio::DeviceInfo> outputDevices;
+    std::map<int, wxMenuItem *> outputDeviceMenuItems;
+    std::map<int, wxMenuItem *> sampleRateMenuItems;
+    std::map<int, wxMenuItem *> audioSampleRateMenuItems;
+    std::map<int, wxMenuItem *> directSamplingMenuItems;
+    wxMenuBar *menuBar;
+    
+    wxMenu *sampleRateMenu;
+    wxMenu *displayMenu;
+    wxMenuItem *agcMenuItem;
+    wxMenuItem *iqSwapMenuItem;
+    wxMenuItem *lowPerfMenuItem;
+    wxMenu *settingsMenu;
+    
+    SoapySDR::ArgInfoList settingArgs;
+    int settingsIdMax;
+    std::vector<long> sampleRates;
+    
+    std::string currentSessionFile;
+    
+    FFTVisualDataThread *waterfallDataThread;
+    
+    std::thread *t_FFTData;
+    SDRDeviceInfo *devInfo;
+    std::atomic_bool deviceChanged;
+    
+    ModemProperties *modemProps;
+    std::atomic_bool modemPropertiesUpdated;
+	wxMenuItem *showTipMenuItem;
+
+    bool lowPerfMode;
+    
+#ifdef USE_HAMLIB
+    void enableRig();
+    void disableRig();
+    
+    wxMenu *rigMenu;
+    wxMenuItem *rigEnableMenuItem;
+    wxMenuItem *rigPortMenuItem;
+    wxMenuItem *rigControlMenuItem;
+    wxMenuItem *rigFollowMenuItem;
+    wxMenuItem *rigCenterLockMenuItem;
+    wxMenuItem *rigFollowModemMenuItem;
+    wxMenuItem *sdrIFMenuItem;
+    std::map<int, wxMenuItem *> rigSerialMenuItems;
+    std::map<int, wxMenuItem *> rigModelMenuItems;
+    int rigModel;
+    int rigSerialRate;
+    long long rigSDRIF;
+    std::vector<int> rigSerialRates;
+    std::string rigPort;
+    int numRigs;
+    bool rigInit;
+#endif
+
+    wxDECLARE_EVENT_TABLE();
+};
diff --git a/src/CubicSDR.cpp b/src/CubicSDR.cpp
new file mode 100644
index 0000000..11195f9
--- /dev/null
+++ b/src/CubicSDR.cpp
@@ -0,0 +1,970 @@
+#define OPENGL
+
+#include "CubicSDRDefs.h"
+#include "wx/wxprec.h"
+
+#ifndef WX_PRECOMP
+#include "wx/wx.h"
+#endif
+
+#if !wxUSE_GLCANVAS
+#error "OpenGL required: set wxUSE_GLCANVAS to 1 and rebuild the library"
+#endif
+
+#include "CubicSDR.h"
+#include <iomanip>
+
+#ifdef _OSX_APP_
+#include "CoreFoundation/CoreFoundation.h"
+#endif
+
+#ifdef USE_HAMLIB
+#include "RigThread.h"
+#endif
+
+IMPLEMENT_APP(CubicSDR)
+
+#include <fstream>
+#include <clocale>
+
+//#ifdef ENABLE_DIGITAL_LAB
+//// console output buffer for windows
+//#ifdef _WINDOWS
+//class outbuf : public std::streambuf {
+//	public:
+//	outbuf() {
+//		setp(0, 0);
+//	}
+//	virtual int_type overflow(int_type c = traits_type::eof()) {
+//		return fputc(c, stdout) == EOF ? traits_type::eof() : c;
+//	}
+//};
+//#endif
+//#endif
+
+#ifdef MINGW_PATCH
+	FILE _iob[] = { *stdin, *stdout, *stderr };
+
+	extern "C" FILE * __cdecl __iob_func(void)
+	{
+		return _iob;
+	}
+
+	extern "C" int __cdecl __isnan(double x)
+	{
+		return _finite(x)?0:1;
+	}
+
+	extern "C" int __cdecl __isnanf(float x)
+	{
+		return _finitef(x)?0:1;
+	}
+#endif
+
+
+std::string& filterChars(std::string& s, const std::string& allowed) {
+    s.erase(remove_if(s.begin(), s.end(), [&allowed](const char& c) {
+        return allowed.find(c) == std::string::npos;
+    }), s.end());
+    return s;
+}
+
+std::string frequencyToStr(long long freq) {
+    long double freqTemp;
+    
+    freqTemp = freq;
+    std::string suffix("");
+    std::stringstream freqStr;
+    
+    if (freqTemp >= 1.0e9) {
+        freqTemp /= 1.0e9;
+        freqStr << std::setprecision(10);
+        suffix = std::string("GHz");
+    } else if (freqTemp >= 1.0e6) {
+        freqTemp /= 1.0e6;
+        freqStr << std::setprecision(7);
+        suffix = std::string("MHz");
+    } else if (freqTemp >= 1.0e3) {
+        freqTemp /= 1.0e3;
+        freqStr << std::setprecision(4);
+        suffix = std::string("KHz");
+    }
+    
+    freqStr << freqTemp;
+    freqStr << suffix;
+    
+    return freqStr.str();
+}
+
+long long strToFrequency(std::string freqStr) {
+    std::string filterStr = filterChars(freqStr, std::string("0123456789.MKGHmkgh"));
+    
+    size_t numLen = filterStr.find_first_not_of("0123456789.");
+    
+    if (numLen == std::string::npos) {
+        numLen = freqStr.length();
+    }
+    
+    std::string numPartStr = freqStr.substr(0, numLen);
+    std::string suffixStr = freqStr.substr(numLen);
+    
+    std::stringstream numPartStream;
+    numPartStream.str(numPartStr);
+    
+    long double freqTemp = 0;
+    
+    numPartStream >> freqTemp;
+    
+    if (suffixStr.length()) {
+        if (suffixStr.find_first_of("Gg") != std::string::npos) {
+            freqTemp *= 1.0e9;
+        } else if (suffixStr.find_first_of("Mm") != std::string::npos) {
+            freqTemp *= 1.0e6;
+        } else if (suffixStr.find_first_of("Kk") != std::string::npos) {
+            freqTemp *= 1.0e3;
+        } else if (suffixStr.find_first_of("Hh") != std::string::npos) {
+            // ...
+        }
+    } else if (numPartStr.find_first_of(".") != std::string::npos || freqTemp <= 3000) {
+        freqTemp *= 1.0e6;
+    }
+    
+    return (long long) freqTemp;
+}
+
+
+CubicSDR::CubicSDR() : frequency(0), offset(0), ppm(0), snap(1), sampleRate(DEFAULT_SAMPLE_RATE),agcMode(false)
+       {
+        sampleRateInitialized.store(false);
+        agcMode.store(true);
+        soloMode.store(false);
+        fdlgTarget = FrequencyDialog::FDIALOG_TARGET_DEFAULT;
+        stoppedDev = nullptr;
+}
+
+bool CubicSDR::OnInit() {
+
+    //use the current locale most appropriate to this system,
+    //so that character-related functions are likely to handle Unicode
+    //better (by default, was "C" locale).
+    std::setlocale(LC_ALL, "");
+
+//#ifdef _OSX_APP_
+//    CFBundleRef mainBundle = CFBundleGetMainBundle();
+//    CFURLRef resourcesURL = CFBundleCopyResourcesDirectoryURL(mainBundle);
+//    char path[PATH_MAX];
+//    if (!CFURLGetFileSystemRepresentation(resourcesURL, TRUE, (UInt8 *)path, PATH_MAX))
+//    {
+//        // error!
+//    }
+//    CFRelease(resourcesURL);
+//    chdir(path);
+//#endif
+
+    if (!wxApp::OnInit()) {
+        return false;
+    }
+
+    //Deactivated code to allocate an explicit Console on Windows.
+    //This tends to hang the apllication on heavy demod (re)creation.
+    //To continue to debug with std::cout traces, simply run CubicSDR in a MINSYS2 compatble shell on Windows:
+    //ex: Cygwin shell, Git For Windows Bash shell....
+#if (0)
+    	if (AllocConsole()) {
+    		freopen("CONOUT$", "w", stdout);
+    		SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_RED);
+            SetConsoleTitle(L"CubicSDR: stdout");
+          
+    	}
+
+        //refresh
+        ofstream ob;
+        std::streambuf *sb = std::cout.rdbuf();
+        std::cout.rdbuf(sb);
+#endif
+        
+    
+    wxApp::SetAppName("CubicSDR");
+
+#ifdef USE_HAMLIB
+    t_Rig = nullptr;
+    rigThread = nullptr;
+    
+    RigThread::enumerate();
+#endif
+
+    Modem::addModemFactory(ModemFM::factory, "FM", 200000);
+    Modem::addModemFactory(ModemNBFM::factory, "NBFM", 12500);
+    Modem::addModemFactory(ModemFMStereo::factory, "FMS", 200000);
+    Modem::addModemFactory(ModemAM::factory, "AM", 6000);
+    Modem::addModemFactory(ModemLSB::factory, "LSB", 5400);
+    Modem::addModemFactory(ModemUSB::factory, "USB", 5400);
+    Modem::addModemFactory(ModemDSB::factory, "DSB", 5400);
+    Modem::addModemFactory(ModemIQ::factory, "I/Q", 48000);
+
+#ifdef ENABLE_DIGITAL_LAB
+    Modem::addModemFactory(ModemAPSK::factory, "APSK", 200000);
+    Modem::addModemFactory(ModemASK::factory, "ASK", 200000);
+    Modem::addModemFactory(ModemBPSK::factory, "BPSK", 200000);
+    Modem::addModemFactory(ModemDPSK::factory, "DPSK", 200000);
+#if ENABLE_LIQUID_EXPERIMENTAL
+    Modem::addModemFactory(ModemFSK::factory, "FSK", 19200);
+#endif
+    Modem::addModemFactory(ModemGMSK::factory, "GMSK", 19200);
+    Modem::addModemFactory(ModemOOK::factory, "OOK", 200000);
+    Modem::addModemFactory(ModemPSK::factory, "PSK", 200000);
+    Modem::addModemFactory(ModemQAM::factory, "QAM", 200000);
+    Modem::addModemFactory(ModemQPSK::factory, "QPSK", 200000);
+    Modem::addModemFactory(ModemSQAM::factory, "SQAM", 200000);
+    Modem::addModemFactory(ModemST::factory, "ST", 200000);
+#endif
+    
+    frequency = wxGetApp().getConfig()->getCenterFreq();
+    offset = 0;
+    ppm = 0;
+    devicesReady.store(false);
+    devicesFailed.store(false);
+    deviceSelectorOpen.store(false);
+
+    // Visual Data
+    spectrumVisualThread = new SpectrumVisualDataThread();
+    demodVisualThread = new SpectrumVisualDataThread();
+    
+    pipeIQVisualData = new DemodulatorThreadInputQueue();
+    pipeIQVisualData->set_max_num_items(1);
+
+    pipeDemodIQVisualData = new DemodulatorThreadInputQueue();
+    pipeDemodIQVisualData->set_max_num_items(1);
+    
+    pipeWaterfallIQVisualData = new DemodulatorThreadInputQueue();
+    pipeWaterfallIQVisualData->set_max_num_items(128);
+    
+    getDemodSpectrumProcessor()->setInput(pipeDemodIQVisualData);
+    getSpectrumProcessor()->setInput(pipeIQVisualData);
+    getSpectrumProcessor()->setHideDC(true);
+    
+    pipeAudioVisualData = new DemodulatorThreadOutputQueue();
+    pipeAudioVisualData->set_max_num_items(1);
+    
+    scopeProcessor.setInput(pipeAudioVisualData);
+    
+    // I/Q Data
+    pipeSDRIQData = new SDRThreadIQDataQueue();
+    pipeSDRIQData->set_max_num_items(100);
+    
+    sdrThread = new SDRThread();
+    sdrThread->setOutputQueue("IQDataOutput",pipeSDRIQData);
+
+    sdrPostThread = new SDRPostThread();
+    sdrPostThread->setInputQueue("IQDataInput", pipeSDRIQData);
+
+    sdrPostThread->setOutputQueue("IQVisualDataOutput", pipeIQVisualData);
+    sdrPostThread->setOutputQueue("IQDataOutput", pipeWaterfallIQVisualData);
+    sdrPostThread->setOutputQueue("IQActiveDemodVisualDataOutput", pipeDemodIQVisualData);
+    
+    t_PostSDR = new std::thread(&SDRPostThread::threadMain, sdrPostThread);
+    t_SpectrumVisual = new std::thread(&SpectrumVisualDataThread::threadMain, spectrumVisualThread);
+    t_DemodVisual = new std::thread(&SpectrumVisualDataThread::threadMain, demodVisualThread);
+
+    sdrEnum = new SDREnumerator();
+    
+    SDREnumerator::setManuals(config.getManualDevices());
+
+    appframe = new AppFrame();
+	t_SDREnum = new std::thread(&SDREnumerator::threadMain, sdrEnum);
+
+//#ifdef __APPLE__
+//    int main_policy;
+//    struct sched_param main_param;
+//
+//    main_policy = SCHED_RR;
+//    main_param.sched_priority = sched_get_priority_min(SCHED_RR)+2;
+//
+//    pthread_setschedparam(pthread_self(), main_policy, &main_param);
+//#endif
+
+    return true;
+}
+
+int CubicSDR::OnExit() {
+#if USE_HAMLIB
+    if (rigIsActive()) {
+        std::cout << "Terminating Rig thread.." << std::endl;
+        stopRig();
+    }
+#endif
+
+    //The thread feeding them all should be terminated first, so: 
+    std::cout << "Terminating SDR thread.." << std::endl;
+    sdrThread->terminate();
+    sdrThread->isTerminated(3000);
+   
+    if (t_SDR) {
+       t_SDR->join();
+       delete t_SDR;
+       t_SDR = nullptr;
+    }
+
+    std::cout << "Terminating SDR post-processing thread.." << std::endl;
+    sdrPostThread->terminate();
+
+    std::cout << "Terminating All Demodulators.." << std::endl;
+    demodMgr.terminateAll();
+   
+    std::cout << "Terminating Visual Processor threads.." << std::endl;
+    spectrumVisualThread->terminate();
+    demodVisualThread->terminate();
+
+    //Wait nicely
+    sdrPostThread->isTerminated(1000);
+    spectrumVisualThread->isTerminated(1000);
+    demodVisualThread->isTerminated(1000);
+
+    //Then join the thread themselves
+    t_PostSDR->join();
+    t_DemodVisual->join();
+    t_SpectrumVisual->join();
+
+    //Now only we can delete
+    delete sdrThread;
+    sdrThread = nullptr;
+
+    delete sdrPostThread;
+    sdrPostThread = nullptr;
+
+    delete t_PostSDR;
+    t_PostSDR = nullptr;
+
+    delete t_SpectrumVisual;
+    t_SpectrumVisual = nullptr;
+
+    delete spectrumVisualThread;
+    spectrumVisualThread = nullptr;
+
+    delete t_DemodVisual;
+    t_DemodVisual = nullptr;
+
+    delete demodVisualThread;
+    demodVisualThread = nullptr;
+    
+    delete pipeIQVisualData;
+    pipeIQVisualData = nullptr;
+
+    delete pipeAudioVisualData;
+    pipeAudioVisualData = nullptr;
+
+    delete pipeSDRIQData;
+    pipeSDRIQData = nullptr;
+
+    delete m_glContext;
+    m_glContext = nullptr;
+
+#ifdef __APPLE__
+    AudioThread::deviceCleanup();
+#endif
+
+    return wxApp::OnExit();
+}
+
+PrimaryGLContext& CubicSDR::GetContext(wxGLCanvas *canvas) {
+    PrimaryGLContext *glContext;
+    if (!m_glContext) {
+        m_glContext = new PrimaryGLContext(canvas, NULL);
+    }
+    glContext = m_glContext;
+
+    return *glContext;
+}
+
+void CubicSDR::OnInitCmdLine(wxCmdLineParser& parser) {
+    parser.SetDesc (commandLineInfo);
+    parser.SetSwitchChars (wxT("-"));
+}
+
+bool CubicSDR::OnCmdLineParsed(wxCmdLineParser& parser) {
+    wxString *confName = new wxString;
+    if (parser.Found("c",confName)) {
+        if (confName) {
+            config.setConfigName(confName->ToStdString());
+        }
+    }
+    
+    config.load();
+
+#ifdef BUNDLE_SOAPY_MODS
+    if (parser.Found("b")) {
+        useLocalMod.store(false);
+    } else {
+        useLocalMod.store(true);
+    }
+#else
+    useLocalMod.store(true);
+#endif
+
+    wxString *modPath = new wxString;
+
+    if (parser.Found("m",modPath)) {
+        if (modPath) {
+            modulePath = modPath->ToStdString();
+        } else {
+            modulePath = "";
+        }
+    }
+    
+    return true;
+}
+
+void CubicSDR::closeDeviceSelector() {
+    if (deviceSelectorOpen) {
+        deviceSelectorDialog->Close();
+    }
+}
+
+void CubicSDR::deviceSelector() {
+    if (deviceSelectorOpen) {
+        deviceSelectorDialog->Raise();
+        deviceSelectorDialog->SetFocus();
+        return;
+    }
+    deviceSelectorOpen.store(true);
+    deviceSelectorDialog = new SDRDevicesDialog(appframe);
+    deviceSelectorDialog->Show();
+}
+
+void CubicSDR::addRemote(std::string remoteAddr) {
+    SDREnumerator::addRemote(remoteAddr);
+    devicesReady.store(false);
+    t_SDREnum = new std::thread(&SDREnumerator::threadMain, sdrEnum);
+}
+
+void CubicSDR::removeRemote(std::string remoteAddr) {
+    SDREnumerator::removeRemote(remoteAddr);
+}
+
+void CubicSDR::sdrThreadNotify(SDRThread::SDRThreadState state, std::string message) {
+
+    std::lock_guard < std::mutex > lock(notify_busy);
+
+   
+    if (state == SDRThread::SDR_THREAD_INITIALIZED) {
+        appframe->initDeviceParams(getDevice());
+    }
+    if (state == SDRThread::SDR_THREAD_MESSAGE) {
+        notifyMessage = message;
+    }
+    if (state == SDRThread::SDR_THREAD_FAILED) {
+        notifyMessage = message;
+//        wxMessageDialog *info;
+//        info = new wxMessageDialog(NULL, message, wxT("Error initializing device"), wxOK | wxICON_ERROR);
+//        info->ShowModal();
+    }
+    //if (appframe) { appframe->SetStatusText(message); }
+  
+}
+
+
+void CubicSDR::sdrEnumThreadNotify(SDREnumerator::SDREnumState state, std::string message) {
+    std::lock_guard < std::mutex > lock(notify_busy);
+
+    if (state == SDREnumerator::SDR_ENUM_MESSAGE) {
+        notifyMessage = message;
+    }
+    if (state == SDREnumerator::SDR_ENUM_DEVICES_READY) {
+        devs = SDREnumerator::enumerate_devices("", true);
+        devicesReady.store(true);
+    }
+    if (state == SDREnumerator::SDR_ENUM_FAILED) {
+        devicesFailed.store(true);
+    }
+    //if (appframe) { appframe->SetStatusText(message); }
+   
+
+}
+
+
+void CubicSDR::setFrequency(long long freq) {
+    if (freq < sampleRate / 2) {
+        freq = sampleRate / 2;
+    }
+    frequency = freq;
+    sdrThread->setFrequency(freq);
+    getSpectrumProcessor()->setPeakHold(getSpectrumProcessor()->getPeakHold());
+
+    //make the peak hold act on the current dmod also, like a zoomed-in version.
+    getDemodSpectrumProcessor()->setPeakHold(getSpectrumProcessor()->getPeakHold());
+}
+
+long long CubicSDR::getOffset() {
+    return offset;
+}
+
+void CubicSDR::setOffset(long long ofs) {
+    offset = ofs;
+    sdrThread->setOffset(offset);
+    SDRDeviceInfo *dev = getDevice();
+    config.getDevice(dev->getDeviceId())->setOffset(ofs);
+}
+
+long long CubicSDR::getFrequency() {
+    return frequency;
+}
+
+
+void CubicSDR::lockFrequency(long long freq) {
+    frequency_locked.store(true);
+    lock_freq.store(freq);
+    
+    if (sdrThread && !sdrThread->isTerminated()) {
+        sdrThread->lockFrequency(freq);
+    }
+}
+
+bool CubicSDR::isFrequencyLocked() {
+    return frequency_locked.load();
+}
+
+void CubicSDR::unlockFrequency() {
+    frequency_locked.store(false);
+    sdrThread->unlockFrequency();
+}
+
+void CubicSDR::setSampleRate(long long rate_in) {
+    sampleRate = rate_in;
+    sdrThread->setSampleRate(sampleRate);
+    setFrequency(frequency);
+
+    if (rate_in <= CHANNELIZER_RATE_MAX / 8) {
+        appframe->setMainWaterfallFFTSize(512);
+        appframe->getWaterfallDataThread()->getProcessor()->setHideDC(false);
+        spectrumVisualThread->getProcessor()->setHideDC(false);
+    } else if (rate_in <= CHANNELIZER_RATE_MAX) {
+        appframe->setMainWaterfallFFTSize(1024);
+        appframe->getWaterfallDataThread()->getProcessor()->setHideDC(false);
+        spectrumVisualThread->getProcessor()->setHideDC(false);
+    } else if (rate_in > CHANNELIZER_RATE_MAX) {
+        appframe->setMainWaterfallFFTSize(2048);
+        appframe->getWaterfallDataThread()->getProcessor()->setHideDC(true);
+        spectrumVisualThread->getProcessor()->setHideDC(true);
+    }
+}
+
+void CubicSDR::stopDevice(bool store, int waitMsForTermination) {
+    
+    //Firt we must stop the threads
+    sdrThread->terminate();
+    sdrThread->isTerminated(waitMsForTermination);
+
+    if (t_SDR) {
+        t_SDR->join();
+        delete t_SDR;
+        t_SDR = nullptr;
+    }
+    
+    //Only now we can nullify devices
+    if (store) {
+        stoppedDev = sdrThread->getDevice();
+    }
+    else {
+        stoppedDev = nullptr;
+    }
+
+    sdrThread->setDevice(nullptr);
+}
+
+void CubicSDR::reEnumerateDevices() {
+    devicesReady.store(false);
+    devs = nullptr;
+    SDREnumerator::reset();
+    t_SDREnum = new std::thread(&SDREnumerator::threadMain, sdrEnum);
+}
+
+void CubicSDR::setDevice(SDRDeviceInfo *dev, int waitMsForTermination) {
+
+    sdrThread->terminate();
+    sdrThread->isTerminated(waitMsForTermination);
+    
+    if (t_SDR) {
+       t_SDR->join();
+       delete t_SDR;
+       t_SDR = nullptr;
+    }
+    
+    for (SoapySDR::Kwargs::const_iterator i = settingArgs.begin(); i != settingArgs.end(); i++) {
+        sdrThread->writeSetting(i->first, i->second);
+    }
+    sdrThread->setStreamArgs(streamArgs);
+    sdrThread->setDevice(dev);
+    
+    DeviceConfig *devConfig = config.getDevice(dev->getDeviceId());
+    
+    SoapySDR::Device *soapyDev = dev->getSoapyDevice();
+    
+    if (soapyDev) {
+        if (long devSampleRate = devConfig->getSampleRate()) {
+            sampleRate = dev->getSampleRateNear(SOAPY_SDR_RX, 0, devSampleRate);
+            sampleRateInitialized.store(true);
+        }
+        
+        if (!sampleRateInitialized.load()) {
+            sampleRate = dev->getSampleRateNear(SOAPY_SDR_RX, 0, DEFAULT_SAMPLE_RATE);
+            sampleRateInitialized.store(true);
+        } else {
+            sampleRate = dev->getSampleRateNear(SOAPY_SDR_RX, 0, sampleRate);
+        }
+
+        if (frequency < sampleRate/2) {
+            frequency = sampleRate/2;
+        }
+
+        setFrequency(frequency);
+        setSampleRate(sampleRate);
+
+        setPPM(devConfig->getPPM());
+        setOffset(devConfig->getOffset());
+        
+
+        if (devConfig->getAGCMode()) {
+            setAGCMode(true);
+        } else {
+            setAGCMode(false);
+        }
+
+        t_SDR = new std::thread(&SDRThread::threadMain, sdrThread);
+}
+    
+    stoppedDev = nullptr;
+}
+
+SDRDeviceInfo *CubicSDR::getDevice() {
+    if (!sdrThread->getDevice() && stoppedDev) {
+        return stoppedDev;
+    }
+
+    return sdrThread->getDevice();
+}
+
+ScopeVisualProcessor *CubicSDR::getScopeProcessor() {
+    return &scopeProcessor;
+}
+
+SpectrumVisualProcessor *CubicSDR::getSpectrumProcessor() {
+    return spectrumVisualThread->getProcessor();
+}
+
+SpectrumVisualProcessor *CubicSDR::getDemodSpectrumProcessor() {
+    return demodVisualThread->getProcessor();
+}
+
+DemodulatorThreadOutputQueue* CubicSDR::getAudioVisualQueue() {
+    return pipeAudioVisualData;
+}
+
+DemodulatorThreadInputQueue* CubicSDR::getIQVisualQueue() {
+    return pipeIQVisualData;
+}
+
+DemodulatorThreadInputQueue* CubicSDR::getWaterfallVisualQueue() {
+    return pipeWaterfallIQVisualData;
+}
+
+DemodulatorMgr &CubicSDR::getDemodMgr() {
+    return demodMgr;
+}
+
+SDRPostThread *CubicSDR::getSDRPostThread() {
+    return sdrPostThread;
+}
+
+SDRThread *CubicSDR::getSDRThread() {
+    return sdrThread;
+}
+
+
+void CubicSDR::bindDemodulator(DemodulatorInstance *demod) {
+    if (!demod) {
+        return;
+    }
+    sdrPostThread->bindDemodulator(demod);
+}
+
+void CubicSDR::bindDemodulators(std::vector<DemodulatorInstance *> *demods) {
+    if (!demods) {
+        return;
+    }
+    sdrPostThread->bindDemodulators(demods);
+}
+
+long long CubicSDR::getSampleRate() {
+    return sampleRate;
+}
+
+void CubicSDR::removeDemodulator(DemodulatorInstance *demod) {
+    if (!demod) {
+        return;
+    }
+    demod->setActive(false);
+    sdrPostThread->removeDemodulator(demod);
+}
+
+std::vector<SDRDeviceInfo*>* CubicSDR::getDevices() {
+    return devs;
+}
+
+
+AppConfig *CubicSDR::getConfig() {
+    return &config;
+}
+
+void CubicSDR::saveConfig() {
+    config.save();
+}
+
+void CubicSDR::setPPM(int ppm_in) {
+    ppm = ppm_in;
+    sdrThread->setPPM(ppm);
+
+    SDRDeviceInfo *dev = getDevice();
+    if (dev) {
+        config.getDevice(dev->getDeviceId())->setPPM(ppm_in);
+    }
+}
+
+int CubicSDR::getPPM() {
+    SDRDeviceInfo *dev = sdrThread->getDevice();
+    if (dev) {
+        ppm = config.getDevice(dev->getDeviceId())->getPPM();
+    }
+    return ppm;
+}
+
+void CubicSDR::showFrequencyInput(FrequencyDialog::FrequencyDialogTarget targetMode, wxString initString) {
+    const wxString demodTitle("Set Demodulator Frequency");
+    const wxString freqTitle("Set Center Frequency");
+    const wxString bwTitle("Modem Bandwidth (150Hz - 500KHz)");
+    const wxString lpsTitle("Lines-Per-Second (1-1024)");
+    const wxString avgTitle("Average Rate (0.1 - 0.99)");
+    const wxString gainTitle("Gain Entry: "+wxGetApp().getActiveGainEntry());
+
+    wxString title;
+    
+    switch (targetMode) {
+        case FrequencyDialog::FDIALOG_TARGET_DEFAULT:
+            title = demodMgr.getActiveDemodulator()?demodTitle:freqTitle;
+            break;
+        case FrequencyDialog::FDIALOG_TARGET_BANDWIDTH:
+            title = bwTitle;
+            break;
+        case FrequencyDialog::FDIALOG_TARGET_WATERFALL_LPS:
+            title = lpsTitle;
+            break;
+        case FrequencyDialog::FDIALOG_TARGET_SPECTRUM_AVG:
+            title = avgTitle;
+            break;
+        case FrequencyDialog::FDIALOG_TARGET_GAIN:
+            title = gainTitle;
+            if (wxGetApp().getActiveGainEntry() == "") {
+                return;
+            }
+            break;
+        default:
+            break;
+    }
+    
+    FrequencyDialog fdialog(appframe, -1, title, demodMgr.getActiveDemodulator(), wxPoint(-100,-100), wxSize(350, 75), wxDEFAULT_DIALOG_STYLE, targetMode, initString);
+    fdialog.ShowModal();
+}
+
+void CubicSDR::showLabelInput() {
+
+    DemodulatorInstance *activeDemod = wxGetApp().getDemodMgr().getActiveDemodulator();
+
+    if (activeDemod != nullptr) {
+
+        const wxString demodTitle("Edit Demodulator label");
+
+        DemodLabelDialog labelDialog(appframe, -1, demodTitle, activeDemod, wxPoint(-100, -100), wxSize(500, 75), wxDEFAULT_DIALOG_STYLE);
+        labelDialog.ShowModal();
+    }
+}
+
+AppFrame *CubicSDR::getAppFrame() {
+    return appframe;
+}
+
+void CubicSDR::setFrequencySnap(int snap) {
+    if (snap > 1000000) {
+        snap = 1000000;
+    }
+    this->snap = snap;
+}
+
+int CubicSDR::getFrequencySnap() {
+    return snap;
+}
+
+bool CubicSDR::areDevicesReady() {
+    return devicesReady.load();
+}
+
+bool CubicSDR::areDevicesEnumerating() {
+    return !sdrEnum->isTerminated();
+}
+
+bool CubicSDR::areModulesMissing() {
+    return devicesFailed.load();
+}
+
+std::string CubicSDR::getNotification() {
+    std::string msg;
+    std::lock_guard < std::mutex > lock(notify_busy);
+    msg = notifyMessage;
+   
+    return msg;
+}
+
+void CubicSDR::setDeviceSelectorClosed() {
+    deviceSelectorOpen.store(false);
+}
+
+bool CubicSDR::isDeviceSelectorOpen() {
+	return deviceSelectorOpen.load();
+}
+
+void CubicSDR::setAGCMode(bool mode) {
+    agcMode.store(mode);
+    sdrThread->setAGCMode(mode);
+}
+
+bool CubicSDR::getAGCMode() {
+    return agcMode.load();
+}
+
+
+void CubicSDR::setGain(std::string name, float gain_in) {
+    sdrThread->setGain(name,gain_in);
+}
+
+float CubicSDR::getGain(std::string name) {
+    return sdrThread->getGain(name);
+}
+
+void CubicSDR::setStreamArgs(SoapySDR::Kwargs streamArgs_in) {
+    streamArgs = streamArgs_in;
+}
+
+void CubicSDR::setDeviceArgs(SoapySDR::Kwargs settingArgs_in) {
+    settingArgs = settingArgs_in;
+}
+
+bool CubicSDR::getUseLocalMod() {
+    return useLocalMod.load();
+}
+
+std::string CubicSDR::getModulePath() {
+    return modulePath;
+}
+
+void CubicSDR::setActiveGainEntry(std::string gainName) {
+    activeGain = gainName;
+}
+
+std::string CubicSDR::getActiveGainEntry() {
+    return activeGain;
+}
+
+void CubicSDR::setSoloMode(bool solo) {
+    soloMode.store(solo);
+}
+
+bool CubicSDR::getSoloMode() {
+    return soloMode.load();
+}
+
+int CubicSDR::FilterEvent(wxEvent& event) {
+    if (!appframe) {
+        return -1;
+    }
+
+    if (event.GetEventType() == wxEVT_KEY_DOWN || event.GetEventType() == wxEVT_CHAR_HOOK) {
+		return appframe->OnGlobalKeyDown((wxKeyEvent&)event);
+    }
+    
+    if (event.GetEventType() == wxEVT_KEY_UP || event.GetEventType() == wxEVT_CHAR_HOOK) {
+        return appframe->OnGlobalKeyUp((wxKeyEvent&)event);
+    }
+    
+    return -1;  // process normally
+}
+
+#ifdef USE_HAMLIB
+RigThread *CubicSDR::getRigThread() {
+    return rigThread;
+}
+
+void CubicSDR::initRig(int rigModel, std::string rigPort, int rigSerialRate) {
+    if (rigThread) {
+
+        rigThread->terminate();
+        rigThread->isTerminated(1000);
+    }
+
+    if (t_Rig && t_Rig->joinable()) {
+        t_Rig->join();
+    }
+
+    //now we can delete
+    if (rigThread) {
+
+        delete rigThread;
+        rigThread = nullptr;
+    }
+    if (t_Rig) {
+      
+        delete t_Rig;
+        t_Rig = nullptr;
+    }
+
+    rigThread = new RigThread();
+    rigThread->initRig(rigModel, rigPort, rigSerialRate);
+    rigThread->setControlMode(wxGetApp().getConfig()->getRigControlMode());
+    rigThread->setFollowMode(wxGetApp().getConfig()->getRigFollowMode());
+    rigThread->setCenterLock(wxGetApp().getConfig()->getRigCenterLock());
+    rigThread->setFollowModem(wxGetApp().getConfig()->getRigFollowModem());
+
+    t_Rig = new std::thread(&RigThread::threadMain, rigThread);
+}
+
+void CubicSDR::stopRig() {
+    if (!rigThread) {
+        return;
+    }
+    
+    if (rigThread) {
+        
+        rigThread->terminate();
+        rigThread->isTerminated(1000);
+    }
+
+    if (t_Rig && t_Rig->joinable()) {
+        t_Rig->join();   
+    }
+
+    //now we can delete
+    if (rigThread) {
+
+        delete rigThread;
+        rigThread = nullptr;
+    }
+
+    if (t_Rig) {
+       
+        delete t_Rig;
+        t_Rig = nullptr;
+    }
+}
+
+bool CubicSDR::rigIsActive() {
+    return (rigThread && !rigThread->isTerminated());
+}
+
+#endif
diff --git a/src/CubicSDR.h b/src/CubicSDR.h
new file mode 100644
index 0000000..07b28cb
--- /dev/null
+++ b/src/CubicSDR.h
@@ -0,0 +1,247 @@
+#pragma once
+
+//WX_GL_CORE_PROFILE 1
+//WX_GL_MAJOR_VERSION 3
+//WX_GL_MINOR_VERSION 2
+
+#include <thread>
+
+#include "GLExt.h"
+#include "PrimaryGLContext.h"
+
+#include "ThreadQueue.h"
+#ifdef USE_RTL_SDR
+    #include "SDRThread.h"
+#else
+    #include "SoapySDRThread.h"
+    #include "SDREnumerator.h"
+#endif
+#include "SDRPostThread.h"
+#include "AudioThread.h"
+#include "DemodulatorMgr.h"
+#include "AppConfig.h"
+#include "AppFrame.h"
+#include "FrequencyDialog.h"
+#include "DemodLabelDialog.h"
+
+#include "ScopeVisualProcessor.h"
+#include "SpectrumVisualProcessor.h"
+#include "SpectrumVisualDataThread.h"
+#include "SDRDevices.h"
+#include "Modem.h"
+
+#include "ModemFM.h"
+#include "ModemNBFM.h"
+#include "ModemFMStereo.h"
+#include "ModemAM.h"
+#include "ModemUSB.h"
+#include "ModemLSB.h"
+#include "ModemDSB.h"
+#include "ModemIQ.h"
+
+#ifdef ENABLE_DIGITAL_LAB
+#include "ModemAPSK.h"
+#include "ModemASK.h"
+#include "ModemBPSK.h"
+#include "ModemDPSK.h"
+#if ENABLE_LIQUID_EXPERIMENTAL
+#include "ModemFSK.h"
+#endif
+#include "ModemGMSK.h"
+#include "ModemOOK.h"
+#include "ModemPSK.h"
+#include "ModemQAM.h"
+#include "ModemQPSK.h"
+#include "ModemSQAM.h"
+#include "ModemST.h"
+#endif
+
+#ifdef USE_HAMLIB
+class RigThread;
+#endif
+
+#include <wx/cmdline.h>
+
+#define NUM_DEMODULATORS 1
+
+std::string& filterChars(std::string& s, const std::string& allowed);
+std::string frequencyToStr(long long freq);
+long long strToFrequency(std::string freqStr);
+
+class CubicSDR: public wxApp {
+public:
+    CubicSDR();
+
+    PrimaryGLContext &GetContext(wxGLCanvas *canvas);
+
+    virtual bool OnInit();
+    virtual int OnExit();
+
+    virtual void OnInitCmdLine(wxCmdLineParser& parser);
+    virtual bool OnCmdLineParsed(wxCmdLineParser& parser);
+
+    void deviceSelector();
+    void sdrThreadNotify(SDRThread::SDRThreadState state, std::string message);
+    void sdrEnumThreadNotify(SDREnumerator::SDREnumState state, std::string message);
+    
+    void setFrequency(long long freq);
+    long long getFrequency();
+    
+    void lockFrequency(long long freq);
+    bool isFrequencyLocked();
+    void unlockFrequency();
+
+    void setOffset(long long ofs);
+    long long getOffset();
+
+    void setSampleRate(long long rate_in);
+    long long getSampleRate();
+
+    std::vector<SDRDeviceInfo *> *getDevices();
+    void setDevice(SDRDeviceInfo *dev, int waitMsForTermination);
+    void stopDevice(bool store, int waitMsForTermination);
+    SDRDeviceInfo * getDevice();
+
+    ScopeVisualProcessor *getScopeProcessor();
+    SpectrumVisualProcessor *getSpectrumProcessor();
+    SpectrumVisualProcessor *getDemodSpectrumProcessor();
+    
+    DemodulatorThreadOutputQueue* getAudioVisualQueue();
+    DemodulatorThreadInputQueue* getIQVisualQueue();
+    DemodulatorThreadInputQueue* getWaterfallVisualQueue();
+    DemodulatorThreadInputQueue* getActiveDemodVisualQueue();
+    DemodulatorMgr &getDemodMgr();
+
+    SDRPostThread *getSDRPostThread();
+    SDRThread *getSDRThread();
+
+    void bindDemodulator(DemodulatorInstance *demod);
+    void bindDemodulators(std::vector<DemodulatorInstance *> *demods);
+    void removeDemodulator(DemodulatorInstance *demod);
+
+    void setFrequencySnap(int snap);
+    int getFrequencySnap();
+
+    AppConfig *getConfig();
+    void saveConfig();
+
+    void setPPM(int ppm_in);
+    int getPPM();
+
+    void showFrequencyInput(FrequencyDialog::FrequencyDialogTarget targetMode = FrequencyDialog::FDIALOG_TARGET_DEFAULT, wxString initString = "");
+    void showLabelInput();
+    AppFrame *getAppFrame();
+    
+    bool areDevicesReady();
+    bool areDevicesEnumerating();
+    bool areModulesMissing();
+    std::string getNotification();
+    
+    void addRemote(std::string remoteAddr);
+    void removeRemote(std::string remoteAddr);
+
+    void setDeviceSelectorClosed();
+    void reEnumerateDevices();
+    bool isDeviceSelectorOpen();
+    void closeDeviceSelector();
+    
+    void setAGCMode(bool mode);
+    bool getAGCMode();
+
+    void setGain(std::string name, float gain_in);
+    float getGain(std::string name);
+
+    void setStreamArgs(SoapySDR::Kwargs streamArgs_in);
+    void setDeviceArgs(SoapySDR::Kwargs settingArgs_in);
+
+    bool getUseLocalMod();
+    std::string getModulePath();
+    
+    void setActiveGainEntry(std::string gainName);
+    std::string getActiveGainEntry();
+
+    void setSoloMode(bool solo);
+    bool getSoloMode();
+    
+#ifdef USE_HAMLIB
+    RigThread *getRigThread();
+    void initRig(int rigModel, std::string rigPort, int rigSerialRate);
+    void stopRig();
+    bool rigIsActive();
+#endif
+    
+private:
+    int FilterEvent(wxEvent& event);
+    
+    AppFrame *appframe = nullptr;
+    AppConfig config;
+    PrimaryGLContext *m_glContext = nullptr;
+    std::vector<SDRDeviceInfo *> *devs = nullptr;
+
+    DemodulatorMgr demodMgr;
+
+    std::atomic_llong frequency;
+    std::atomic_llong offset;
+    std::atomic_int ppm, snap;
+    std::atomic_llong sampleRate;
+    std::atomic_bool agcMode;
+
+    SDRThread *sdrThread = nullptr;
+    SDREnumerator *sdrEnum = nullptr;
+    SDRPostThread *sdrPostThread = nullptr;
+    SpectrumVisualDataThread *spectrumVisualThread = nullptr;
+    SpectrumVisualDataThread *demodVisualThread = nullptr;
+
+    SDRThreadIQDataQueue* pipeSDRIQData = nullptr;
+    DemodulatorThreadInputQueue* pipeIQVisualData = nullptr;
+    DemodulatorThreadOutputQueue* pipeAudioVisualData = nullptr;
+    DemodulatorThreadInputQueue* pipeDemodIQVisualData = nullptr;
+    DemodulatorThreadInputQueue* pipeWaterfallIQVisualData = nullptr;
+    DemodulatorThreadInputQueue* pipeActiveDemodIQVisualData = nullptr;
+
+    ScopeVisualProcessor scopeProcessor;
+    
+    SDRDevicesDialog *deviceSelectorDialog = nullptr;
+
+    SoapySDR::Kwargs streamArgs;
+    SoapySDR::Kwargs settingArgs;
+    
+    std::thread *t_SDR = nullptr;
+    std::thread *t_SDREnum = nullptr;
+    std::thread *t_PostSDR = nullptr;
+    std::thread *t_SpectrumVisual = nullptr;
+    std::thread *t_DemodVisual = nullptr;
+    std::atomic_bool devicesReady;
+    std::atomic_bool devicesFailed;
+    std::atomic_bool deviceSelectorOpen;
+    std::atomic_bool sampleRateInitialized;
+    std::atomic_bool useLocalMod;
+    std::string notifyMessage;
+    std::string modulePath;
+    
+    std::mutex notify_busy;
+    
+    std::atomic_bool frequency_locked;
+    std::atomic_llong lock_freq;
+    FrequencyDialog::FrequencyDialogTarget fdlgTarget;
+    std::string activeGain;
+    std::atomic_bool soloMode;
+    SDRDeviceInfo *stoppedDev;
+#ifdef USE_HAMLIB
+    RigThread* rigThread = nullptr;
+    std::thread *t_Rig = nullptr;
+#endif
+};
+
+static const wxCmdLineEntryDesc commandLineInfo [] =
+{
+    { wxCMD_LINE_SWITCH, "h", "help", "Command line parameter help", wxCMD_LINE_VAL_NONE, wxCMD_LINE_OPTION_HELP },
+    { wxCMD_LINE_OPTION, "c", "config", "Specify a named configuration to use, i.e. '-c ham'", wxCMD_LINE_VAL_STRING, 0 },
+    { wxCMD_LINE_OPTION, "m", "modpath", "Load modules from suppplied path, i.e. '-m ~/SoapyMods/'", wxCMD_LINE_VAL_STRING, 0 },
+#ifdef BUNDLE_SOAPY_MODS
+    { wxCMD_LINE_SWITCH, "b", "bundled", "Use bundled SoapySDR modules first instead of local.", wxCMD_LINE_VAL_NONE, 0 },
+#endif
+    { wxCMD_LINE_NONE, nullptr, nullptr, nullptr, wxCMD_LINE_VAL_NONE, 0 }
+};
+
+DECLARE_APP(CubicSDR)
diff --git a/src/CubicSDR.png b/src/CubicSDR.png
new file mode 100644
index 0000000..14b565c
Binary files /dev/null and b/src/CubicSDR.png differ
diff --git a/src/CubicSDR.xpm b/src/CubicSDR.xpm
new file mode 100644
index 0000000..a283fb5
--- /dev/null
+++ b/src/CubicSDR.xpm
@@ -0,0 +1,518 @@
+/* XPM */
+static char const *cubicsdr_xpm[] = {
+/* columns rows colors chars-per-pixel */
+"256 256 256 2 ",
+"   c #010101",
+".  c #0A0A0A",
+"X  c #171817",
+"o  c #0D0F12",
+"O  c #221E1A",
+"+  c #28251C",
+"@  c #111929",
+"#  c #1D273A",
+"$  c #1F2B3E",
+"%  c #1A2635",
+"&  c #1A2328",
+"*  c #272828",
+"=  c #212C3E",
+"-  c #232D33",
+";  c #333434",
+":  c #3A3E3E",
+">  c #363A3A",
+",  c #2D302F",
+"<  c #2A1D27",
+"1  c #463B37",
+"2  c #3B413C",
+"3  c #4B463A",
+"4  c #64603E",
+"5  c #1F2B42",
+"6  c #1D2B4B",
+"7  c #1C2B55",
+"8  c #222D41",
+"9  c #222D4A",
+"0  c #282F45",
+"q  c #243145",
+"w  c #25324A",
+"e  c #2B364B",
+"r  c #383E44",
+"t  c #283656",
+"y  c #323C58",
+"u  c #222C55",
+"i  c #131268",
+"p  c #131478",
+"a  c #091173",
+"s  c #1B2B6B",
+"d  c #283967",
+"f  c #2A3473",
+"g  c #15145D",
+"h  c #4F394A",
+"j  c #3D4243",
+"k  c #3B434A",
+"l  c #36425B",
+"z  c #3C4C6C",
+"x  c #374567",
+"c  c #374974",
+"v  c #2E4473",
+"b  c #414646",
+"n  c #42474A",
+"m  c #444A4B",
+"M  c #4A4D4D",
+"N  c #474846",
+"B  c #534D4D",
+"V  c #594B4A",
+"C  c #4B504F",
+"Z  c #454B51",
+"A  c #484C54",
+"S  c #4C5252",
+"D  c #4D535C",
+"F  c #525555",
+"G  c #545B5B",
+"H  c #5A5C5C",
+"J  c #565756",
+"K  c #57504E",
+"L  c #654C4C",
+"P  c #675656",
+"I  c #765656",
+"U  c #6F4F50",
+"Y  c #5D615C",
+"T  c #716957",
+"R  c #424C6B",
+"E  c #595F60",
+"W  c #545969",
+"Q  c #425273",
+"!  c #495577",
+"~  c #4B5370",
+"^  c #6F5668",
+"/  c #5C6363",
+"(  c #5B666A",
+")  c #5B6679",
+"_  c #646464",
+"`  c #636B6B",
+"'  c #6B6C6C",
+"]  c #686666",
+"[  c #756868",
+"{  c #6B7373",
+"}  c #697577",
+"|  c #747473",
+" . c #737C7C",
+".. c #7B7B7B",
+"X. c #797776",
+"o. c #6F6D72",
+"O. c #8D5354",
+"+. c #AF5254",
+"@. c #8A7370",
+"#. c #936A65",
+"$. c #D05456",
+"%. c #EF5557",
+"&. c #FF516C",
+"*. c #FA516C",
+"=. c #D46768",
+"-. c #7D817B",
+";. c #FFFA01",
+":. c #FCFA09",
+">. c #FBFB16",
+",. c #FAFB2B",
+"<. c #998976",
+"1. c #918773",
+"2. c #A4917B",
+"3. c #8B835E",
+"4. c #F7FB4E",
+"5. c #F0FB6D",
+"6. c #ABF975",
+"7. c #131788",
+"8. c #131994",
+"9. c #121B9B",
+"0. c #1A1C97",
+"q. c #0C188F",
+"w. c #2C328E",
+"e. c #121DA3",
+"r. c #121EAC",
+"t. c #071DA7",
+"y. c #1221B3",
+"u. c #1222BB",
+"i. c #1D25B6",
+"p. c #0F22B2",
+"a. c #2A31AF",
+"s. c #1C258F",
+"d. c #34488E",
+"f. c #3748B9",
+"g. c #394BB1",
+"h. c #4D5292",
+"j. c #596489",
+"k. c #6E7089",
+"l. c #4D53AF",
+"z. c #6868B2",
+"x. c #625EAA",
+"c. c #1224C4",
+"v. c #1226CC",
+"b. c #1B27C6",
+"n. c #1228D5",
+"m. c #122ADB",
+"M. c #1327D2",
+"N. c #0A27CE",
+"B. c #2A33CF",
+"V. c #112CE4",
+"C. c #112EEC",
+"Z. c #1B2EEC",
+"A. c #0532FF",
+"S. c #0C32FF",
+"D. c #0C38FC",
+"F. c #1232FE",
+"G. c #1C33FE",
+"H. c #1332F7",
+"J. c #0F2EEE",
+"K. c #2633FF",
+"L. c #2B33FF",
+"P. c #2734F7",
+"I. c #3336FF",
+"U. c #383AFE",
+"Y. c #2933E7",
+"T. c #423DFF",
+"R. c #394AC6",
+"E. c #3E51C7",
+"W. c #3A4BD6",
+"Q. c #114FFD",
+"!. c #334BF7",
+"~. c #0E70FE",
+"^. c #2371FB",
+"/. c #4458C9",
+"(. c #4E57CE",
+"). c #4F64D2",
+"_. c #6F70D0",
+"`. c #4845FF",
+"'. c #5453FB",
+"]. c #556DF8",
+"[. c #716FFB",
+"{. c #645BF1",
+"}. c #403D95",
+"|. c #947586",
+" X c #A57B91",
+".X c #847AB3",
+"XX c #847BFD",
+"oX c #837AD4",
+"OX c #7B8484",
+"+X c #7C8585",
+"@X c #7D8493",
+"#X c #7D88AD",
+"$X c #088FFE",
+"%X c #04AFFF",
+"&X c #229BFB",
+"*X c #728CF8",
+"=X c #04D0FF",
+"-X c #2BD1FE",
+";X c #2FF1FB",
+":X c #08F3FB",
+">X c #5EF2ED",
+",X c #838383",
+"<X c #878B8B",
+"1X c #989589",
+"2X c #8A9494",
+"3X c #969898",
+"4X c #8F8B8F",
+"5X c #A89987",
+"6X c #B99798",
+"7X c #AC8E90",
+"8X c #A6A499",
+"9X c #B8A897",
+"0X c #B0A68F",
+"qX c #B494A6",
+"wX c #9AA5A6",
+"eX c #A7A9A8",
+"rX c #B7B8B7",
+"tX c #B3B0AF",
+"yX c #9396A6",
+"uX c #C6B099",
+"iX c #C598AB",
+"pX c #C7A8A7",
+"aX c #CCB3B0",
+"sX c #D2A4A5",
+"dX c #A9F7AC",
+"fX c #CEC6B5",
+"gX c #E9ECA5",
+"hX c #9995FE",
+"jX c #8E8AFA",
+"kX c #A39CFF",
+"lX c #A49DEB",
+"zX c #AAA6FE",
+"xX c #B3ACFF",
+"cX c #B9B6FE",
+"vX c #B6B6EB",
+"bX c #9EA1D7",
+"nX c #D4B4C6",
+"mX c #C4BBFD",
+"MX c #E9B6D1",
+"NX c #B8C5C6",
+"BX c #9BF3DA",
+"VX c #C7C8C7",
+"CX c #D3D4D3",
+"ZX c #EDD1CF",
+"AX c #F0E8D3",
+"SX c #C9C7FE",
+"DX c #D8D6FF",
+"FX c #D3CCF7",
+"GX c #EBD6F6",
+"HX c #D6E7EB",
+"JX c #E9E7FF",
+"KX c #FEFEFF",
+"LX c #F5F5FB",
+"PX c #F0EEEC",
+"IX c #D4F7CB",
+"UX c None",
+/* pixels */
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX*.&.UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX*.&.*.&.&.&.UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX&.*.&.*.*.&.*.&.&.UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX*.*.*.&.*.*.*.*.*.&.UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX*.*.&.*.*.*.*.*.&.&.*.UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX&.*.&.&.&.&.*.&.&.&.*.UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX*.*.&.&.&.&.&.&.*.*.&.UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX*.*.&.&.&.&.&.&.&.&.*.UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX&.&.&.&.&.&.&.&.&.&.*.UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX&.*.*.&.&.&.&.*.*.UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX&.*.&.&.&.*.&.UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX&.&.&.&.&.UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXsX*.%.UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXsX%.+.UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX9X=.+.UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX| 7X#.O.UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX[ 6X at .I UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX at .7X@.I UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXX.3X[ P UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX| 7X| L 1 UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXX.3X at .L V UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX at .3X@.K 3 UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXX.1X| L h UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX at .3X@.P 3 UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX at .4X| P L UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXO.O.+.=.$.$.=.=.=.$.$.$.+.+.+.O.O.UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXO.O.#.O.O.+.+.+.$.%.%.&.%.%.%.%.$.$.+.+.+.O.+.O.U UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXI #.I I O.I O.+.+.+.$.$.%.%.%.%.%.%.%.$.$.+.+.O.O.U L O.U U O.UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXV ] I P U I I O.O.O.O.+.+.$.$.%.%.%.%.%.%.%.$.+.+.+.O.O.O.L U U L L L I L UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX[ _ P P P P I I I I O.O.+.+.$.=.$.$.%.%.%.$.$.$.$.+.O.O.O.L L U U U L h V L L UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX_ [ P P P ] _ I ^ I [ I O.#.#.+.+.+.+.$.$.$.$.$.$.+.+.O.O.O.L O.U L V V L L L V N V V b j j : ; UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX> : j : n [ _ Y _ _ I _ ] P I [ T P [ O.O.O.O.#.#.+.+.+.+.+.+.O.O.O.U O.I H U V U L K L V V B V V N m M N M M C M M M b j : : > UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX> r 2 j b j b N m M M C S S C F S C M M M F _ _ T ' ] ] ` ] [ ' ] [ [ ^ [ [ [ [ [ O.#.#.#.O.#.[ #.I U U I L I H P H J J K V B A N h N h N b m N N n m m N m M M M B K C D A B n b b : : ; UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX: j j b b b N M M C C C C S S C B C C B Z C M C B M M M m Z M N m M m m M B Y _ ] ' ' ' o.' ' ' ' o.[ [ o.[ [ [ [ [ [ ^ [ #.[ ^ [ #.] I [ _ _ T P Y P P P F J K M B m N N n 1 M B M B M M B M M N N N N b Z Z n m M M M M C B C B B M b b 2 2 > UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX2 j j b b b b m C B S F S S F F F S C C M C C B M M M m M M M M M Z M M M M M M M M N M M M V M M B B B C M M / o.' o.| X.X.X.X.......X.X.| X.| | X.o.| | | [ [ o.' ' ' ' o.[ ] [ [ ] ] _ _ E J G F F K N B N n N b N N n V M N n N m V m B M M N M m m n N N m m n b b M m M M M M M M M , , , & & O X UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXj k b m m C S S S S S F S G C S S C S S S C M A M m C Z M M M M M M M M M m C A C M M Z C B B M M S M B B B M C C A N C B m C B B C M M V S ' o.X...k.,X,X,X<X<X<X4X,X<X,X.. at .+X@.-...X... .X. .X.| o.| | o.| o.' ' ' ' _ _ H H J F B M M N N b j h N m h b N B N N n B N B N n B b M N n n m m m M m n b m N m m j m 3 S F J D J H J G H G F H F M M m m 2 UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXG Y G G G S S C C C M M M M M M M M m M M C C C m M M C C C C C C M M M M C C M M C C C M C M M M M Z M M M M C M C B M A C M C B M A B K K K K B B G { | ,X,X1X<X3X3X3X3X4X3X1X4X4X3X3X2X<X<X<X<X,X at .1.k.-...X.X.X.X.| X.| | ' ' ] ] Y H J F S B N M b b j : V N N V N N B N n V b B V m B N N B B M M M M N N M m B B N C C C C N b b b b b N b j b b j N b N M m m M m N b m 2 2 2 > > UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXG G S S m m m M m m m C Z M M C C C Z C C C M C C C S C C C C M C M M C M M C A C C M A M B M Z M M A M M C C B M M C C M M B B B B B M M B K B A M B C M F H ' X.,X3X3XwXeXtXeXeXeX8XwXwX8X3X3X3X3X3X3X4X<X<X,Xk.,X....| | | | | ' ' ] _ E H J F S S B M N N M V V V m V M V B M V B B m M Z M M m M N N M M M m B V M N C N C m m m C B M M M M M B M M M M M M M M C C m m m C C m m C C M C C C C C C m m m b b m UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXS G S S S S S S S S C S S C C M M Z M M M M Z M M M M M m Z M C M C A M B M C C C C C M B M S C B C B C C C M B M A M M B C B C B C M K C B K B B B M K B B K B M K J I _ | { @.,X4X4X4X3X3X3X3X3X3X3X4X3X4X1X4X<X,X1.OX at ...X.X.| [ [ ' [ ] _ ^ ^ H P P P S K B M m N V B M V N M B M B B n M V N M M M B m M M m M m M m M B C m C m C m N M m M m m m M M M m M m M m M m m m M M m C m b N m m m m m b m m m m m C m m C M S C m m UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXE G F G G S S C S C C C C S S Z S M C C C C Z m C C Z m C C C M M C m C M C M C A M M C C M C C C C C M K B B C C C C C C C C A B B C M M B Z M B K K B B C A B K S K P J U S J ^ P P I I ^ #.O.#.#.#.#.#.=.=.$.+.$.=.=.#.#.+.=.#.+.O.O.O.O.O.I I I U U K U K L L B F M M M V K M B B B m M B M B B m M M m M N M M M M m M N C N C C m m m C m C m C m C m m m M m m m m m N m b m m b m m m m m N m m N m m m m b m j m b b j j b m m b C m 2 UXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXS / E E E G G G G G G G G G S S S S S S Z S S C C C C C C C Z m C Z M C C Z C m C M M Z M M C M M C M M C C M M C C C C S m C C C C C M M B M K K K M B C M Z C C B B S L L S L J P P I I I I I O.O.O.#.O.+.+.+.+.$.$.$.$.$.$.=.$.+.+.+.O.O.O.O.O.I I U U J V K K K K L L M B M M B B M M M B B M M M B M M M m M M m M B M M M M C m C N m C C N N N m m m m m m m m m N m N m m m m m m m b m b b m b m k m b m j m m b m b b m m m m m m b j b m b 2 UXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUX2 ( G / / / / / / / Y G G G G G G G S S S S S S S S S S C C Z C C C C C C M C M C Z M M M C M C C M C M M M M M C M C M C Z C C C C S M C B B M B M C B C M C M B M B S C S P K B P K L U L P U I O.I O.O.+.+.$.=.$.$.$.$.$.=.%.$.$.$.+.+.+.+.O.O.O.I U U L L P K L L B V M B K A V B N M M C B C m B N B M M M M B M B N m M N M M m m m m m m N m N C m m m m m m N m b m m m m b b b m b b b m m m m b m b k m j k m m k b b k m m k j m b m m k b b m m m 2 UXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXG / ` ` ` ` ` ` ` ( ` / / Y G G G G E G G G S G S S S S S Z S S C C C C Z C C m m C M M M M C M M C C Z M C M C C C M C C C C C C S M M M S F M M S C A B K B C A M B B B B B A B L P L P P P P O.I I O.+.O.+.+.+.$.$.%.%.%.%.$.$.$.$.$.+.+.O.O.U U U U U L L V S B V A K V M V B M B K K N C m m B M B M N M M M N M n m m m N m n N m N m m m m m m m b m m m m m n m b m b m b m m m b m m m m b m b m k m m m m m m m m m m m m m m m m m m m m m m b k b k m UXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXC / ` ` { ` { ` ` { ` ` ` ` ` ` / ` Y / / G G G G G F G S S S G S S S C S S S Z C C Z C Z C Z m Z M M Z M C C M C M M M M M M C m C m A M M B C C M M M B K C M C K C K A L B A B C L L K J P U U U U U O.O.+.+.+.$.=.$.$.%.%.%.$.$.$.$.$.+.+.+.O.U U U U L U L K K A V m M V N B V M M M N m m N C M N N N M N M b n N m m N n N m m N b m N m m m m m m b b k m m k m m m k m m j m m m m m m m k m m m m m m m m m m m m m Z m Z m m m m m m C m M m m m b m j j m UXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUX{ G ` ` ` { { { ` { { { ` ` { ` ` ` ` ` ( ` ` ` / / G E G G G G G G S S S S S S S S S C C C C C C Z M C M M m C Z M C M M M M C C m S M M C M C M M M B B M M C B B K B B S B B C S K L D U L U L U O.U O.U O.+.+.+.$.$.$.$.$.$.$.$.$.+.+.+.O.O.O.U O.O.U V V V K V B V M Z m M m M N b M N N C N N m M m M n m b M b m m b m m m m N m m m m b m b m b m m m m k m m b m m m m m m m m m m m m m C m m C m Z C m M C m C C C C C C C C C C C C C C m m M N m m b m 2 m UXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUX{ G ` ` { { { { { } { { {  .{ } { { ` ` ` { ` ` ` ` ` ( / / Y G Y G G G S S G S F S S C S C C S C C C C C C C Z C M Z M M M M C m C m M M M M B A M M C A M M A M B M A M K C K B B S K U L U L L O.U U O.O.+.+.+.$.+.$.%.%.$.$.$.$.$.+.+.O.O.O.U L L L N L V B V Z n V m M N m m N m m N M n m m m b m m b n m m b m m b N m m m m N m m m m m m m m m m m m m m m m M m m Z m M m C C m m S C m C C C C C Z S C C C Z S S C C C Z C M C C C Z Z M m M m M m b b b j k > UXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUX, ` G ` ` ` ` { { } { {  .{  .} {  . .} }  .{ { { ` ` { ` ` ( ` ( / / / / E E G G G G F F D D S S S S C S Z C C C m M M M M M M C C m C M M m Z M M M m Z C M M M M B B M M M A C A B M B A h L L U U U U O.O.+.O.+.$.$.$.$.$.$.$.$.+.+.O.O.+.U U V L L V B N V N N B N M N N N N n m b M n m N b m m m m m m N m N m m N M m n N m b m m m m m m m C m m C m m C C C S C C C C S S C C S S S S S S S S S S S S S S S S S S C S Z C C C C C Z M C Z Z M m m m k m b j b j 2 UXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUX; ` G ( ` ` ` ` } ` { } { }  . .{  . . . . . . .}  .} { { { { { { ` ` ` ` ( / / Y Y G G G G G S G S S S C C C S C C S C C C C C C C C m m Z m Z m C C m C m M C C C S m M M M B M B M M V M B V L L L U O.O.O.+.+.+.$.+.$.+.+.+.+.+.O.O.U U U L V V b V B N N m N m m N M N m m m m m N m m b m m m m m m m k m m m M m m m C m m C Z m S m S S C S S S C C Z C S S Z S S S G S S S G S S G S S S S G S S S S S S S S S Z C C S C C C C m M C M M m m m m m b m k m j j j 2 > UXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUX> ( G / ( ` ` ` ` } ` } ` { { {  . . . . . . . . . .-. . . .}  .{ } { { } ` ` ` ` ` ( / / / Y Y G G G G G G G S S S S S S S m C C C C C C C C C C C m Z M M m m m m m C M M M m B N M M N V B V V L L U U O.O.+.+.+.+.+.+.+.1 +.O.U 3 L U 1 V V N V N b m N B m b m m m m N m m m m m m m m m m m m C m m N M Z C C C C C S S S S S S S S S S G S S G S G G G G S G G S G S S G S G S G G S G S G S S S S S S S S S S S S S Z C C C m C Z M m m m m m m m k m m m k j j j 2 M UXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUX2 ( G / / / ` ` ` ` ` ` ` { ` { { { {  . . . . .OX-. .OX-.OX . .-. . . . .{  .{ { { { ` ` ( ` / / / / E G G G G G G S S S S S S C C C C S C C C C C Z C m C Z C C C m C M m N M M M m M V V N B V V L L U U U O.1 O.O.O.O.O.O.1 V V B B h V N N N m N n m m m k m m N m m M M m M M M M C C C C S C C m S S S S S S S S S G G S G S G G G G G G G G G G G G G G G G G G G G G G S G S G S S G S S S S S S S C C S C C C C Z m C m m m m m m m j m m m b 2 m m b 2 b k 2 j > C UXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUX2 / F Y G Y / ( / ` / ` ` ` ` { { { ` }  .}  .} OX . .OXOX+X+X .+XOX+X+X .OX . . . .}  .{ { ` { ` ` ` ` / Y Y Y / G G G G S G G G S S S S S S Z C C C S Z C C C m m m C m m M M m m m m m B V M V h V V 3 V V K 1 L 1 1 1 1 1 1 C k N m 1 h V b m M m M m C C C C C C M C C C C C S S C S S S F G S G G F G G G G G E G G E G G G E E E / G G G G G G G G G G G G G G G G G G S G S G S G S S S S S S S C C C M C Z m m m m m m m b m m m k m b m j b b b b j b j b j j j > C   UXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUX2 / D E / G / ( / / / ( ` ` ` ` ` } } } { { { {  . . .-. .+X+X+X+X+X+X+X+X+X+X+X+X-.+X+X . . . .} } } { } ` ` ` ( / / / ( G Y G G G G S S S S S S S S S C S C C C C C m C m M m m m m M m M m m N M N M C m C C m m C m 1 h m m M C Z M Z M C C M C C C S S S S S F S F F S S G D G G G G G G G Y G G Y / / E / / / / / / / G / G / G / / / ( G / G / G G G G G G G G G S S F S S S S S C S C C S C C Z C m m m m m m m m m m m b m m b m b m 2 b b m b j j m 2 j j j 2 m > M . UXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUX> / S G G G G G Y / / ( Y ` / ` ` ` ` ` { { { { {  .}  . . .OXOX+X+X+X+X+X+X2X+X+X+X+X+X+XOXOX+XOX . . .} } } } } } ` ` ( ( / Y Y G Y G G G G G G S S S S S S S C C C Z C C M M Z C M Z M m C C m M M M m m m C C C C S V V C C C C S C S S F S S G F D F G G G G G G G / E / G / / / / / ` / ` / ` ( / / / / / ( / / / / / / / / / / / G G G G G G G G G G G S G S S S S S S S S S C C C C m m Z m m m b m m m m m k m m j j m b b b b 2 m b m j b j b j b j m j j j r j > m o UXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUX2 E S G G G G G G G / G Y / / / / ` ` ` ` ` { { { { {  . . .} -. .+X+X-.+X+X+X+X+X+X2X+X+X2X+X+X+X+X+X+XOX-.OX-.} } } } } } ` } ` / / / / G E G G G G G G S S S S C C C C C C C C C C C C C S S C S C C S C S S S S G S B S D F D S G G G G G G E / E E / / ` / ( ` ` / ( ` ` ` ` ` ` ( ` ` ( ` ` ( ` ` ` / / ` / / / / / / / / / G G G G G G G G G G S S G S S C S C C S S C m C m m m m m m b m m m m m m m b k b b b j b b j b j b b j m j j j b b k b b 2 k j j j j j : m X UXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUX; E M G G G G G G Y G / G Y / / / / / ` ` ` ` ` { ` { } { } }  . . . .+X+X+X+X2X+X2X+X2X+X2X2X+X2X2X2X+X2X+X+X+XOXOXOX . . .} } } } } ` ` ` ` ( / / E E E E G G G G S G D S D S S S S S S S S D S F F F G G G G G G G G J Y Y / / / / ( / ` ` ( ` ` ` ` ` ` ` { ` ` { { ` } { ` ` { ` { ` ` ` ` ` ` ` ( / ` ` / / / / / E / G G G G G G G G G S S S S S C S S m C C m m Z m m m m m m m m m m b m j m k b b b m b b m b m j b m b b b j j j j b b j j j 2 j j 2 j j j 2 j > m & UXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUX; E C D G S G G G G G G / G G / G ( G / / / ( ` ` ` ` { ` { { {  .}  . . .OXOX+X+X+X+X+X2X2X2X2X2X2X2X2X2X2X2X2X2X+X+X+X+X+XOXOXOXOX} }  . .} } { { ` ` ( ( ( / / G G G G G S G G G G S G G G G / G / / ( ( ( ` ` ` ` ` ` ` { } { ` } } } { { } } { } { { { { { { { } { { { { { } ` ` ` ` ` ` ( ( / / / / / / / G G G G G G G G S G S S S S S S C C C C m C m m m m m m m m m m m m m m k m m b b m b b m j b m b j b j j b b b j j j k b j k 2 b 2 j j j j j j j 2 j j 2 > m & UXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUX, / M G G S G G G G G G G G G G G E E ( / Y / / / ` ` ` ` ` ` { { { {  . . . .-.+X+X+X+X+X+X2X+X2X+X2X2X2X2X2X2X2X2X2X2X2X+X2X+X+X+X+X . .OXOX . .OX . .} } } ` ` / / / G Y G G G G G / G / / ` ` { { } } OX . . . . .OXOX . . . . . . . . . . . . . . . . .}  .{ { { { { ` { ` } ` ` ` / ` / / / / / E G G G G G G G S S S S S S S C C m C m m m m C m m m m m m b m m b b m b b b b b m b b m b b b b b b b j j b b j b b j j j k 2 j j j j 2 j j j j 2 j j j j j 2 r j > b & UXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUX- G m G S G S G S G G S G G G G G / G E E / E / Y ( ` / ` ` ` ` { { { { }  . .} -. . .OXOXOX+X+X2X+X2X2X2X2X2X2X2X2X2XwX2X2X2X2X2X2X2X2X2X+X2X+X2X2X2X+X+X+X+X} } { ` ` ` ( / / / / / / ` ` {  .OX2X2X2X2X2X2X#X2X2X2X+X+X2X+X+XOX-.OX .OXOX .-.-. . . . . .{ { { { { ` ` ` ` / ( / / E Y G G G G G G G G S S S S S C S S C C Z C m m m C m m m m m m m m m k m m m b m b m b m b m m b b b b k k b b j j b j j j b j j j j 2 j j j j 2 2 j j j j j j j j j j j j 2 j j j > m X UXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUX, / m S G S G S G S G G G G G G G G G E G E G / / / Y Y / ` ( / ` ` ` ` { } } }  . . .-.-.OX+X+X+X2X+X+X2X2X2X2X2XwXwX2XwXwXwX2X2XwXwXwX3XwXwXwXwXwXwXwXwXwX2X at X+XOX . . .} { { } ` ` } {  .+X2X3XwXwXeXrXeXwXwXwX2X2X2X2X2X<X<X+XOXOXOX . . .} { { { { ` ` ' ` / ` / ( / / / G E G G G G G F F S C C S S C C C C C m C Z C m m m C m m m m m m b m b m k m m m b b m b b b b b b b b b b j b 2 b b 2 b j j j j b j j j j j j j j j j j j j j j j j 2 j j j j 2 j r 2 j 2 > M X UXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUX& Y m C m S S G G G G S S G G G G G G G G G E G G G E / E / / / / ` / ` ` { ` { { {  . .}  . .-.+X+X+X+X+X2X2X2X2X2X2X2X2X2XwX2XwXwXwXwXwXwXwXNXNXNXNXNXrXwXrXwXwX+X at X@X+XOX-. . .}  . .OX+X2XwXeXrXNXNXNXrXeXrXwX2X2X2X+X+X+X+X . . .} } { { ` ` ` ` / / / / / E G G G G S G F S S S S S S C Z S C C Z Z C C m C m m m m m m m b m m b m m m b m b m b m 2 m k b b m b b b b k m j j k b j j j 2 j 2 j j j j j j 2 j j 2 j j j j 2 j j 2 j j j j j 2 j j j 2 r 2 j r j j ; m X UXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXX / m C m k m m C S S G G S G S G G G G G E G E G / / G E / E / / / / / / ` ` ` { ` { { } }  . . .+X+X+X+X+X+X+X+X2X2X2X2XwX2XwX2XwXwXwXwXrXNXNXNXCXHXCXHXIXNXNXwXwXwXyXwXwXwX2X2X+XOX .+X2X2XwXNXNXNXNXdXeXwXwXwX2X2X+XOX . . .} } ` ` ` / / ` / / / G G G G G G G G S S S S S S S C C Z C C S C C C m m m C m m m m m m m m m m m m b m k b b m b b b k m m 2 b b k 2 j b j j j j j j j j j j j j j j 2 j j j j j j j j j j j j j j j j j j j j j j j j j j j j j 2 2 j > k X UXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXo / m S C m k m k k m S G S G S G S G S G E G G G G G G G E Y / / / / / Y ` / ` ` ` ` { { { { {  . . .OXOXOX+X+X+X2X2X2X2X2X2X2XwXwXwXwXrXNXNXNXHXHXHXLXLXJXHXIXNXrXeXrXrXNXrXwXwX+XOXOX+X2X2XwXwXeXwXwXwXwX2X2X+XOX .} } } { ` ( ( / ( G G G G G G G G G S F S C S S C S C S S S C C C C C m m m m m m M m m m m m m m m m b m b b b m b b k m b b b m b b b m b m 2 b b 2 j b j j k j j j j j j 2 j j j j j j j j j j j j 2 j j j 2 j j j 2 j j j j j r 2 j 2 j j j j 2 > Z o UXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXY k G C C m j m m k m k m S S S G G G G G G G G G G G G / G G / G Y / / Y / / / / ` / ` ` { ` { { } } } OX}  .OXOX+X+X+X+X2X2X2XwXwXwXwXwXNXCXHXHXLXLXLXLXLXHXNXNXNXVXBXHXIXNXwX2X+XOXOX+X at X2XwXwX2X@X2X+XOX-.} ` ` / / Y Y G G G S G G G F S S S S S S S S S C S Z C C C Z m C m Z m m m m m m m m m k m m k j m b b b b b k b b b b b b b b 2 m 2 k b 2 b 2 2 k b b k b b 2 j j 2 j 2 j j j j j j j j j j 2 r j 2 j j j j j j j j j j j j j j j j 2 j j j j 2 r 2 2 j > C o UXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUX/ m M M S S k m m k m 2 k k 2 C C S G G G G G G G G G G G G G / G / G G / / / / / / ( ( ` ` ` ` { { ` { } }  .-.-.-.OX+X+X+X+X2X2XwX2XwXwXNXNXIXHXLXKXKXKXLXHXVXNXCXHXHXJXHXNXNX2X+XOX . . .OX .OX .} } { ` ` ( / Y G G G G G G S S S S S S S S S S S C C C C M M M m M m m M m m m m m m m m N m n b n b b n n n b b b b b b b b b b b b j b j b j j j b j j b j j 2 2 j j j j 2 j j j j j j j j j j j j j j j j j r j : 2 b r b j b r j j j r 2 j j : 2 j k j : j : j ; k o UXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUX/ m S C C S S m k k m m m m k b m k m C S S G G S G G G G G G G G G / / G G / E / / / / / / ( ( ` ` ` { { { { }  . . . .-.+X+X+X2X2X2XwXwXrXNXCXHXHXLXLXLXLXHXNXNXCXHXLXLXHXHXrX2X+X} } { { { } ` ` ` / / / Y Y G G G G G S S S S S C S S S S C C m C m M m m m m m m m m m m m m b m j m j m b n b n b n b b j b b b b b b b b j b j r j b j b j j j b j j j j j j r b 2 2 j j j j j 2 j j j j 2 j j j j j 2 j j b b b b r j j j : j j 2 j j j r r r k r > k j : 2 r j ; m . UXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXE m S m m C Z S m b m k m 2 m m b 2 m b b m m S G G G G G G G G G G G G / G E G / G / ( E / / / / ` ` / ` ` { { } } }  . . .-.+X+X+X2X2XwXwXrXNXNXHXPXLXLXHXBXNXNXNXHXLXJXHXNXwX2X .} ` ` / / ( / / / / Y G G G G G C S S C C S C S C C m C m m m m m m m m m m m m m m k m j b b m b m m m b b b n b b b b b b b j b j r b b b r b b b b b j j j j j j r j j j j j j j 2 j j j j j j j 2 j 2 j j j j j j j j j j j r r r 2 b r b b n n j j r j r j : j r k C j j 2 : 2 > m   UXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXF m C C Z C m m m b m m k m 2 m k m b k m b j k m C G G G G G G G G G G G G / G / G / E E / / / / / / / ` ` ` ` ` ` { { } }  .-.+X+X+X2X2XwXwXNXNXCXHXHXHXCXNXwXwXNXCXHXHXCXNXwX+X} } / / / / G / G G G G S G G S S C C S C C S C m C C m m m m m m m m m m k m m b m j m m b b m b m j j b b b b j b b b j b b j b b b b 2 b j b r b b r j k j j j j j j j 2 j j j j j j j j j 2 j j j k j k j j j 2 j 2 : j 2 j b Z M F S D D A A Z m r r : : j : r 2 k M Z j j j : j ; M   UXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXS m S m m C m m m b , > m m m k 2 m k m m j m m m m k k C S G G G G G G G G G G G / G E E E / E / / / ` / ` / ` ` ` ` ` ` { { }  . .-.+X2X2XwXwXrXBXNXNXCXNXNXwXwXNXBXCXNXNXwX2X+X} ( / / / G G G G S S G G S m S C C C C m m m m m m m m m m m m b m m m b m m b b m m b b b b j m j k b b b b j b b b j b j j b j j j j j j b b r b j j j j j j j j j j j j j j j j j j j k j m j j b r : : > j b n Z A D D W W D W D D D C M M M n M b : j 2 k r j k m M C j : : j j ; m UXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXS m Z C S m m C m m &   o & 2 2 m m m j m m b j k m j b m b C C S G G G G G G / G / G E E E / E E E / / / / / / / / / ` ` ` ` { } }  . .OX2X2X2XwXrXNXNXrXrXwXwXwXwXrXNXdXNXwX2X .} / G G G G G S G S C S m C C m C m m m m m m m m m m m b m j m m j k b b j m k b j j b b j b b j b b b b j b b j b j b j b b j b j j b b j r b b 2 j j j j j j j 2 j : j j j j b j j j j 2 > ; > 2 b S D ~ W ! ) ! ) W W ~ D D C A N N N M N M B A M j : j j : j j m Z Z m j j 2 2 j ; C UXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXM M C C S S C C m C -         . - 2 k m b m j m C m M C m m m C m m C G G G G G G G G G E / Y G E E / / Y / / / / ` / ` / / ` ` ` ` } }  .OX+X2X2XwXwXwXrXwXwX2X2XwXwXwXeXwX2XOX} ` / G G G S S S S C C C C m m m m m m m m b m b m b b b j m m b j m m j b b j m j b b b b b j j m j b b j m j j j b j b j b b j j b j j j j j j 2 b j : r b j j j j j b 2 j : : > ; 2 k C ~ ( ) @X#XyXvXyX) j.W W A m M N N N N M M B M B C K B S B J b r : j j j k k C M M j > 2 r j ; m UXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXj Z S S S C m m m C 2       . . . . X ; C k m m j k M m m m m b m m C m C G S G G G G G G G G G E / Y G Y G / / / / / / / ` / ` ` ` ` } } } OX+X2X2XwX2XwX2X2X2X+X2X2XwXwX2X+X .` / G G S S S C C C C m Z m C m m m k m m b b m b b m m m b j b j m b j m j b b j m b b b j b m j j b j j j j b j b j j j b j j j j j j j j j j j j r b b b r j 2 j : : : j b A ~ ] ) k. at X@X2X|.k.2XrXCXNX<XN V N m B B M B M F B B B F F K F F J J H H b : r j j m m m C Z k 2 j r 2 2 ; k UXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXS M S C S C m C k 2X-.,             X 4XZ b b C m m b C M M m C M m m m m C F C F G G G G G G G G G / E G / / E / / / / / / ( ` ` ` ` { {  . .+X+X+X at X2X+X+X+XOX+X2X2X2XOX .} ` Y G S S S C C C C m m m m m m b m b b b m b m b 2 b j b b b b k b b b j b b b b b j j b m j j j b j j b : b j j b j j j j j j r j j j b b b b : r b : > : 1 n n A R W j.j.x.k.k.k.k.) W _ J Z M > tXKXHXAXF M S B S B B F F S J K F J H H H H H H H H b > b k m m n S Z Z m j : > r 2 > j UXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXC M C N C S m m m M C { X.H *       X <XH *   * : m C k m C m m M m C C C C m m m C S G G G G G G E G G E G E E E E E / / / / / ( ` ` ` { { }  .OX-. .OX . . . . . .OX-. .} ` Y G G S S C C m m b m m m m m b m b m m k b b b m m b b b b b b b b j b b b j b j b b j b j j b j j b b j b j b : 2 j j j j j j b j j r r > : : j b M A F W ~ ! ) j.) ) ) ~ D J h n m N m N M N B M CXPXPXLXJ A K K F J J J J J H H H H H H H H Y _ _ _ b r b m n m n Z M M k : : j 2 r > 2 UXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXC C C C S m m m m ;     , Y ..| M .   1.' ,       o * 2 m 2 m C M M m m C k C S m m C m S C S G G G G G G G E E E E E E / / / / / / ` ` ` ` { }  .} }  .} } { { }  .} } } ` Y G G S S C C m m m m m m m b k m b m k m b b b k k 2 m b b b b j b b b b j b j b b j j j j m k j j j 2 j j j j j b j b k j j > : : 2 b h Z A D W W ~ ! ) W ) D D M B B n n N N m M B C B B S B K S M CX3X4XCXN E J J E H H H H H H H _ P _ _ _ ] ] ] ] _ b b n k m m M M m M k : j 2 r 2 > 2 UXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXm C C C m C C C C j X       . b ' .._ ,Xo.*             o ` J k S M C C C C C C m C j m C C C C C G G G E G G G G E G E E E / / / / / ` ` { ` { { } }  .} } ` ` ` { ` ` ` ( / G S S C C C m m m m b b b m m b b b b b b b b m 2 m m b b j b b b b j b b j b j j j j 2 j 2 j k j m m b b b r j : : : j b n D D G W ! ) ) ~ W W D A A M n N N B n B A B S K M V C B S K S F K J B ' tXY _ 3X_ H P E E Y _ Y _ _ ] ] ] _ ] ] ] ] ] ' | ] b m M m C n Z M m M m : j : 2 : > > UXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXk S M F C m S S N D J .           b X.wXeX] O             2X; | H b C m S m C b m S S M C C C m C N C G G G G G G G G / G / / E / / Y ` ` ` ` ` } { { } } ` ` ` ` ` ` ` / / G G S S C m C m m m 2 m b m m b b m b b 2 m b m 2 b b 2 b b j m b b j j j b : b r b b b m m j m b 2 > > > > b n Z W W ! ) ) W ! W W D A A M n n n M M n M B B A B B M S B F B F J K J J G J H J H K X.2Xm o.NX_ E _ _ _ ] ] _ ] _ ] ] ' o.{ | | | | o.' ] n b m m m m Z Z M Z k 2 j : j : > - UXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXj S M S S S M M S S 3X*       .   1 | _ 4X| ..' b .   .   <XN 3XF   m b b m m m b C M C m C C C S m m C S C C G G Y G G E G E G / / / / / / ` / ` ` ` ` ` ` ` ` / ` ` ( Y Y G S S S m m m m m m m m m b b b b j b b b k k b b b b b b j j 2 2 k j b b n b b b b r r ; > j k m D ` ) j.k.j.k.j.! ) W W A D n C m M B M M B M B M B B B S B B B S K J F F F H H H H H H H _ H _ K 4X3XJ ] VX1._ [ ] ] ' ' ' { | | X.o.' ' ] _ _ [ ..4X,Xn Z n m Z M Z Z m m j j 2 : : : > * UXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXj S M C S S S C F M <XN           * 4X' | ; X M | ..H ;   ,X_ ,X_   m o . , > m m m m M j C S C C C m m S m b m C S S G G G G G G G Y / / / / ` ` ` ` / ` / / ` / ` ( / ( G G S C C C C m k m k m b b m j m b b b b b 2 b b k b 2 b j j k m b k b 2 1 : : r b b D / ` k. at XwXbXNXFXwXk.) ~ D B Z A m h M M B B M M M B B B B B S K J F F J J J H J E H P H H H H Y Y _ _ _ H [ J VX,X_ H VXPX' ' ' ' ' ' ' ] _ _ J | @.4XwXVXVXVXPXCXwXM C Z A Z Z Z M C Z k : : 2 j : > UXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUX2 S M J C C F S G F <X_   .       X 3X_ X.]       , H ,X] 8X' X._   C O       X * k m 2 S S N C F m C C C C m m C m C m C G G G / G / G / Y / Y / ` / / / / / / G Y ( G G G G S S C k m m m m b b m b b b j b b b b b b b b b b m 2 m 2 2 2 2 2 b m D W ! k.k.<X|.#X4Xk. at XrXHXKXPXwXN N N V M M B M M B M B C B S K S F F J J J J H J H H H E P H E H _ Y _ _ _ _ _ _ _ ] o.] CXHX' o.] 3XKXPXk...| -.k.1.3XtXrXCXHXKXCXVXrXwX4XX.k._ r Z b M m Z M m Z Z j 2 r r 2 : > UXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUX> S C S C S C C G S +X|           X 1X] | 3X.         X , 8XeXeX,X  > O   .         n ..m G S F S S M n m M M m k M m C m M S S F G G G G / E / / / / / / E E / / G ( G G G G S C m m m m k m b b b b b b b j b b j b j j b : : : b k k l ~ ! k.k.k.x.k.k.j.( ( W K M M r 3XLXLXKXLXJ M C M S B C B K S J F K J F J J H H H H H H E H H E _ _ H / _ _ ] ] ] ] ] ' ' o.o.' H tXKX4X'  .o.-.rXVXPXPXHXPXLXPXPXCXVXVX,Xk.| X.1X1XrXVXCX2Xj A C C k C A C Z n j 2 r 2 : : ; UXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUX* S m G C F G G F m { ' .         . k.4X' eXO     .   .   _ 2X,XtX' | b         .   N eX. > k C S S m m m M m M M n C M C M F C S S G G G G G / G / E E / / G G E G G G S S S C C C m m m m b b b b b b j b b b j b j b n M A W W ~ ) j.! j.) ^ ~ W D B Z b h b m A n J > tXKXPXLXPXF B F B K F F J F J J H J H H H H H H H H P _ _ _ ] _ ] ] _ ] ] ] ] ] ' ' | | ' _ P X.tXLXeX] X.o.} ....| 4X4X<X|.,X| | ' -.<XeXVXNXCXbXbXoX}.0.d S k S Z C C k Z Z n j r 2 r : : , UXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXG N F N C G C J C { ' O           | 3Xo.tXK 3 .         F 4X' ..; | 4X| M o       j 3X. 3Xo . * > k M k M n m C m m C M C M C M S G G G G G E G E G / G G G G G G G S G S S C C m m m b m b m b b b b b b b j j j n ~ W ! W ) W D D A A A n N m M M M M B C S B B B D b 8XPX4XtXPXB H J F P J J H H P P E H H / Y _ _ _ _ _ ] ] ] ] ] ] ` ] o.' | | | | { | _ ' X.eXVXHXCX-...1.k.,X1. . .k.| | ..1X1XrXrXVXvXbXyXz.w.0.a a i i a l Z Z k A C k S Z M n j 2 : : : j , UXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXS m F F S 4 F C F Y ' ,           J <X1.wXaXPX.         ; <X{ |.  X M b X.-.] >   * 3XM VXO       j ; > C n M j M C C m M N M m C S G G G G G G E G G G G G G G G G G S S S Z Z k m b m b b b b m n r b j b j b j n E D A A n n M N m M B M B M B M B B B B B S B B H n eX' m _ 8X] J H H H H H Y E E E _ _ _ _ _ ] ] _ _ ] ] ] _ ] o.| | | ' ' ' ] _ _ | k.8XVXPXCXrX4XX...-.-.X.| X.,X1X0XVXrXCXvXbXjX/.B.c.t.c.p.p.9.7.p p p 7.y n k C A k C m n M b r j 2 : : j , UXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXS C C C F Y F F Y N H 2           J ; b eXfXfXT         & 3Xo.o.M ; >   . * _ @.X.] eXH 4X>   .   j .   X , j k M C b b b M m N M G G G G G G G G G G G G S G G S S S S S C C m m j m b b b b k b b b b j j j b j b D D h M B M M B B B A B B S K S K F F F J J J H J H rX_ _ B eXo.J E Y _ _ _ _ ] ] _ ] ] ] ] ] ' ' ' | o.| | X...o.] _ ` -.|.wXeXrXCXCXPXCXtXk.o.( o...OX1X2XtXVXVXHXkXhX[.T.P.J.J.u.t.9.y.i.b.i.y.e.8.p 7.7.0.R m M M Z m m Z Z Z j j 2 2 j : j & UXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXS C C Y F J F G Y 2 j M   .       J X   eXCXX.1X        O yX1.F ,XN >         o j | VX3X8XK   .   b X         O - j m b b j m j C S G G S S G S G G G G G G S S G G S S S C m m m m b m b b b 2 b b j j j b j j b b W A Z B M B M F B S K F F F J J J J J H H H H H H ] eXH _ F rXX.J ] _ _ _ _ _ ] _ ' ] ' o.| X.' ' ' ' ' ' ' _ ` @.3XrXVXLXCXVXVXCXrX<Xk._ o.,X3X0XVXNXVXSXIXgX4.4.-X~.A.D.A.S.S.Z.u.u.b.v.N.m.m.n.e.p i 7.7.0.n n Z Z M Z Z A Z Z k 2 r : r > 2 & UXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXC m S G F Y G F Y N ; C       .   H *   tXAX+ uXX       X 4X..N 3X_ ;             o 1X3XtX3XY ;   ; *   .         . & ; k k k N m S G S G G G G G G G G S G S G S S S C C C m m m m b m b k m b b j b b j j j b j b W D F K F J F F J J J J H H J H E H H P H E P ] B X.rXK o.M rXCX' | ' ' o.| | o.| | X.{ ' ] ] X... at .<X,X,X3XVXVXCXCXNXeX..} ] ] ' @.3X9XVXVXCXvXjXjX!.!.=X6.:.;.4.-XQ.G.G.G.F.S.J.N.n.v.M.M.M.e.7.7.8.r.9.7.7.n Z M Z Z Z n Z n Z m : r 2 j > j O UXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXm C C Y Y 4 Y Y / N > 3 X.[   o   Y ;   8XAX  1X2   .   O ,X,X2 8X at .[ .           X k.....| Y <Xo.' ;                   o * ; j k G S G G G S G G G G G G S G S G S S S Z C C b m b b b b m 2 b b b j b j b b r b b W W S J J J J J H H H E H P E P _ _ E _ _ _ ( ` o.PXrXF X.H eXKXKXtXH ' H _ H E _ ] _ | 4X3XVXPXPXPXCXHXCXVXVXeX.._ ` X.1X8XVXVXVXHXbXbX{.B.P.A.A.D.D.Q.:XgX:.,.4.;X~.S.D.S.F.S.n.r.y.c.c.y.e.e.e.y.9.7.7.7.p n n Z n Z Z Z m Z Z j : 2 r : : j o UXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXk C m J F Y Y Y T K ; C rXAX  .   K n   3XAX  <.3.  .   o 4X3X> wXeXLXK           X @.<X] F   O m ,X4X' :                     o , G S S G S G G G S G G G S S S S S S S C Z m m m b m k b b j b j j j j j j b j r b W ( J H H H H H H H / / Y _ _ ( _ ] ] ] _ ' ' E PXPX' o.' { ' rXLXLXHXVXVXVXVXVXVX8XVXCXKXAXrX2X4Xk.| | ' ' X.4X8XVXVXVXvXyXoXE.B.V.N.c.t.N.J.F.G.G.G.~.;XgX;.:.4.;X~.F.F.F.H.V.V.V.V.c.y.u.y.e.7.i g i p 7.f Z n n Z Z n Z C n C k 2 r 2 r > j UXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXj C C Y Y Y Y / Y / O -.uX5X,     b S   3XPX  T 2.  .   . ,X| , 3XCXAX8X          o ,X3X/ H       * K J ,X|.G *         .       X S G S S G G S G G S S G S G S G S S S C C m m m b b m m b b b b j b h j j j j b k ) ( J H E H _ _ _ _ _ ] ] ] ] ] ] ] ] ' | ] _ CXKX4X' | | | | X.X.tXtX,XeX3XVXVXHXCXVXeXOX{ _ | X.3X8XrXVXVXCXrXoXoXg.a.e.p.N.m.m.M.y.0.e.M.F.F.F.S.Q.%X;X4.:.;.5.-XF.F.F.F.H.F.C.u.e.y.y.e.7.i i i 7.7.7.7.i m Z M A Z Z Z Z Z Z j 2 2 r j > 2 UXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUX2 C m J Y Y Y Y Y ' o -.8X[ 3.    ; G   <XAX. , 0X  .   . X.X + 2XCX1.uXX         . k.wX] ]       , >   X j { <X| M o           o S S S G S G S S G S G S D S S S S Z C m C m m m m j b b b b b j b j j j j j j j r o.( G ^ _ ] ] ] ] ' ] ] ] ' ] o.' X.o.] H <XCXPX<Xo. .| X.X.| ..X. .| X.X. .] _ _ ` ] ..1X8XVXVXnXvXyX.Xx.a.a.9.q.a q.e.u.b.m.n.v.c.v.m.V.C.S.F.F.F.F.F.-X5.:.:.5.-XD.F.F.F.F.v.e.p.y.r.e.e.e.r.r.e.9.7.p i u Z Z n n Z m Z Z n n j j : : : > j UXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXC m G Y Y Y Y Y ' . X.8X; 8X    O F   1.AX, . uXo       [ . o +XfXY 8XN   .     . X.8XJ ' O     O >         O J X.,X' ;       o S S S G S S G S S G S G S S G S S C C Z m m m m m m b b b j j b j b b j j j j j r k.) _ ' ] ' ` ' ] ' ' ' | | | o.' _ ' 3XVXPXrX' | |  .X.k.1.OX1.,X1.k.X. .-.1X8XrXVXVXCXvXoXjXW.B.i.t.t.t.t.q.7.p p 8.y.u.n.C.J.F.S.S.J.V.F.F.F.F.F.F.$X;X,.>.:.dX%XH.S.F.J.m.v.c.u.c.v.M.v.c.e.7.7.i i i p u M b m Z Z Z Z m Z k : : j : : : > UXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXS m Y Y Y Y Y ` ' O | 8X  uX    ; S   | AXB   0X;   .   _ .   X.CXj 5X<.  .       ' 8XJ / E ;   O :             . ; ] ,X-.Y +   C G S S S S G G G S G S S S S S S Z C C m m m m k b b b j b j j b j j j j j j j r k.k.( ' ' ' o.o.X.X.| ' ` Y ] @.<XrXCXCXtX..o.-.1.+X1.k...1.-. .k.1X5XrXVXVXFXlXoX_.W.B.N.N.p.p.p.y.i.a.0.0.7.9.y.u.y.u.u.v.M.n.c.e.e.u.M.J.F.F.F.F.D.%X;X4.>.;.dX~.H.A.F.F.H.F.C.m.b.9.8.7.7.8.8.7.i g i 7.f C k m Z m Z Z Z Z n j 2 r : : 2 ; UXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXC m G G F Y _ T -., T CX  uX+   ,XH   M AXH   2.4   .   Y X   @.PXk T 1X  .       J 3X] F 1.eX5X* :                   X N { <X| S S G S G S S S S S S S S S S G C C C m m m m b m b m j b b b j j j b j j j j j j <X at X` | X.| | ' _ ] | <XwXrXVXPXHXVXeXX.o.,X,X .} {  .<X2XyXVXrXCXmXhXhX'.I.Z.A.J.V.J.c.y.y.i.c.c.c.y.p.t.u.m.m.v.y.9.8.9.8.8.9.r.c.M.u.u.M.J.F.F.F.G.F.-X,.>.;.dX=X^.S.F.F.F.m.c.e.7.9.u.m.V.m.y.8.p 7.7.8.f m Z n Z Z n Z k A b : r : : : r ; UXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXC m G Y Y Y T Y { 2 T AXo <.K   8X'   + fX'   1.2.  o   H *   ..KXB B 9X          b 3X_ b ,XPXPXP ;                         , G G S S S S S S S G S S G S S S S C C M m C m m b m j b j b b b b b j j j j j j j k #XyX` ' _ ] | 4XrXrXHXPXVXVXeX<XOX] o. . at ...1X8XtXVXVXAXgX5.AX-X^.D.A.A.A.A.F.Z.Z.M.c.n.m.J.A.J.m.y.8.9.e.9.8.7.7.7.9.r.u.c.V.C.C.C.n.v.M.V.J.F.F.F.Q.%X>X>.>.;.dX$XG.S.F.F.F.C.c.e.u.m.J.C.v.e.p p 7.7.8.e.c m n Z Z M Z Z C Z m : 2 2 2 : : , UXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXC N C Y S Y / Y -.N 1XAX# 3.1.  eX-.  + fX .  V 8X  o   _ ;   ..PX4 ; uX*         B 3X_ , 4XCX2.8X,                             k G S S S G S S G S S S S S S S C C M C m m k m b m b b b b j r b j j : j j j j b #XyX2X3XVXNXAXCXVXtX1X|.' _ ] | ,X8X8XrXNXNXSXkXzX>XgX:.>.:.5.=XQ.F.G.K.G.H.S.V.N.V.F.F.F.H.m.u.9.7.7.7.7.p p 8.8.8.y.u.u.r.e.9.9.8.r.u.v.m.C.F.F.S.G.$X>X,.:.,.BX~.F.S.F.F.F.J.c.c.V.V.V.n.u.9.7.p a p 7.9.y m Z A k m n Z n m k : r 2 r > j , UXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXm m S Y G H / _ -.J 1XfXj 3 2.  3X1.  * fX|   + uX  .   Y 2   | AXY X 3XN         F 3X' , 3XfX1 fXV                             k S S S S G S S S S S S S S S S Z m m S m m m m 2 m b b b m b 2 j b 2 b j j j b r yXyXrXCXCXtX<X| _ _ | 4X8XtXVXrXCXvXjXjX'.I.F.A.Q.=XgX;.,.;.gX-X$XQ.F.F.F.F.J.n.M.J.C.v.y.r.e.y.v.v.c.c.c.c.u.y.r.8.8.7.p p 7.9.c.V.C.m.M.F.J.F.F.S.G.%XBX:.>.:.BX=X!.S.F.F.V.r.u.n.C.C.C.m.c.8.p i 7.9.e.r.x m Z M Z n n Z Z Z k 2 r : j : j & UXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXj M M Y Y Y _ _ _ 1.fX3X` O 0X  -.2X  * uX| X   uX+   ; <X:   ' VXT X <. at .  .     J ,X' * eXeX  tX'                             2 S S S S S G S G S G S S S S S m S m m m m m m m 2 m 2 k 2 k b b j j 2 j j : j j z.#X) ] ^ | 4X8XVXVXVXFXbXyXoXW.B.J.A.A.A.S.F.G.Q.-X5.;.>.;.5.;X:X$XF.F.F.H.m.m.m.c.r.e.u.m.V.F.V.c.u.e.8.9.7.7.7.7.8.8.y.M.V.F.V.m.u.u.u.M.J.F.F.D.^.:XBX:.>.>.BX=XQ.S.F.F.C.m.c.e.r.u.c.r.8.7.7.7.8.7.7.0.y k M Z Z m Z n Z C j j : : 2 > j X UXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX2 M C G H / Y _ / 1XVX .{   uX  F 3X  * uX{ *   1Xb . J tX;   G fX' O T 0X  .     _ Y * * 8XtX  1X0X                            2 G S S S S G S S S S S S S S S S S S m m m m 2 m b b b b b 2 m j j j j r j j b N z..XwXCXVXVXCXyXoXz.a.0.q.t.t.N.J.F.K.G.G.F.F.S.$X:X5.;.,.;.6.=XQ.D.F.F.F.F.F.F.F.V.C.H.F.J.M.c.y.9.8.7.p 7.9.y.u.c.c.c.M.v.c.u.u.N.M.V.M.C.F.F.F.S.^.=XdX;.>.,.>X%XF.S.F.S.C.c.e.y.M.n.M.m.m.v.e.p i p 7.0.A M n m k C k Z Z Z j : 2 r : : 2 UXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX2 m C Y J Y Y G G { <XeXtX. 5X+ m 8X  + uXX.j   5XT   F tX;   : rXo.; , 8X        Y M   & pXrX  3.tX  .                         2 S C S S S S S G S S S S S S S S S m m m m m m 2 m b b b b 2 k j j j r b 2 j j N z.z.#X.X}.w.8.t.t.t.t.e.0.0.i.b.C.F.S.S.S.F.Q.^.=X;X5.;.,.;.gX%XQ.H.H.F.S.F.H.V.V.F.V.M.c.y.9.q.e.e.e.r.u.u.r.r.r.8.9.e.r.v.V.F.F.F.J.m.V.F.F.F.F.D.Z.%XBX:.>.,.>XQ.G.F.F.H.m.m.C.M.v.V.J.J.V.u.8.p p 7.7.0.Z m Z Z m n M n Z n j r r 2 r > 2 UXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX, C m Y G S G Y G Y X { CX-.fX3 r tX  + uX| K   @.2.  B tX:   1 tX' N X fXo   .   J F   . VXCX  j fX                            2 S C S S S S C S S S S S C S m m S m S m m k m m b b b b k 2 k j j b b 2 j j 2 j x.l.s.p q.r.c.b.B.b.r.e.9.p.N.N.C.F.F.F.F.F.F.D.~.;X5.;.>.;.6.:X%XQ.F.F.F.F.F.F.C.M.u.u.v.m.J.C.m.M.c.y.e.8.8.9.y.u.y.r.c.V.H.F.F.M.m.M.m.F.F.F.F.S.G.%XBX:.:.,.-XQ.K.F.F.F.F.J.y.r.n.V.V.n.c.r.9.7.7.8.p.0.n A m k Z Z Z n Z n j j 2 : : > > UXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXS m G Y G Y Y G / O N eX2 uXrX| eXo O uX] H   3 1X  , wXM   N rX_ Y   uX;   .   N B     CXCX  X ZX+                           , G C S S S S S S S S G S C S S S m m S m m m m 2 b b b 2 m b b b j : 2 j j j b j x.x.w.0.9.e.r.u.N.V.J.F.F.J.m.m.C.F.J.F.F.F.F.~.=X;X,.:.>.:.6.:X&XS.F.F.F.F.C.M.c.c.V.H.F.H.V.M.c.y.r.y.v.V.J.V.m.v.u.c.m.C.C.J.n.e.9.r.M.V.F.F.F.D.G.%XdX;.:.4.-XQ.F.F.F.F.C.u.r.u.M.M.n.n.u.e.8.p 7.p 7.f Z Z n A Z A Z Z Z Z j : : : : r > UXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXC C J J Y F G F / * 2 rX. 3 tX,XCX_ O fXY [ ; + 9X  O 3X_   N rXK `   1X4   .   b N     rXCXX   8X2                           - G C S S S S S S S S S S S C S S S m m m m m b m m b b m 2 2 b b r b 2 j j r j j z.x.w.7.9.u.m.H.F.C.m.c.y.p.c.c.v.C.H.F.F.F.F.^.$X;X4.;.,.;.dX=XQ.S.F.F.F.F.C.J.C.C.m.M.M.v.m.m.m.u.y.c.c.u.y.y.c.c.c.v.M.n.M.v.v.c.M.c.y.y.m.H.F.S.G.%XdX;.:.4.;X%XQ.S.F.F.C.J.v.c.V.H.V.v.y.e.8.7.8.8.9.w.N k Z m Z Z Z M Z n j : : 2 r j ; UXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXC m S J J F Y F Y , & eX, + 5X* rXwX1XfXV | -.. uX. . tX|   K 1XN |   5X at .  . X ,XN     tXCX;   0XX.  .                       - S C S S S S S S S S S S S S m S m m m m m k m m 2 k m 2 m 2 b j j : b r j j j j l.(.w.7.r.r.r.8.7.7.9.p.n.V.J.C.V.F.F.F.F.F.F.F.$X;X5.;.,.;.6.=X-XQ.F.F.F.F.F.J.M.v.m.V.F.F.F.C.c.y.y.y.y.r.r.9.8.7.7.9.r.y.v.v.m.n.N.v.m.J.F.F.D.H.Q.=XgX;.>.4.;X$XG.F.F.F.F.C.c.y.r.e.r.u.r.8.7.e.r.8.7.f m Z n Z n k Z k Z k r 2 : : : 2 , UXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXM m F F S G F G G ; X eXm   uX, 8XT J AX3X3X3X  0X,   eX}   K <X3 ..  T 5X    ; rXB     eXVX1   <.0X  .                       & G C C C C S S S S S S S S S C C m C m m b m j m 2 b b 2 b b j j j j : j j j j h x.l.w.p p p p e.n.H.S.J.m.M.v.V.S.F.H.F.H.S.F.F.Q.-X5.;.,.;.6.:X%XF.F.F.F.F.F.J.F.F.F.F.F.F.H.C.m.c.u.u.r.9.7.7.7.7.7.p 7.8.9.8.8.9.y.u.M.F.F.F.F.G.~.-X5.:.:.4.-XQ.F.F.F.F.F.V.c.u.v.m.C.v.9.8.8.8.7.7.e.f V Z m Z A n Z Z Z k 2 r 2 : : 2 - UXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXM m S S F J J S G j   8XY   5X3 3X/ . fX[ rXCX* 1X4   2X1.  K 1X; ,X  j 1X    * tX_     8XrXN   H wX  .                       & S C C C S S S S S S S S S Z C C C m m m m m m k m b b k b j j j j j j j j j j r x.l.f a 8.e.u.m.V.M.u.u.r.c.m.V.m.m.m.H.F.F.D.Q.%X;X4.;.,.;.dX=X^.~.D.F.F.F.V.m.V.J.F.F.H.m.c.e.8.9.9.e.e.e.e.e.9.8.7.e.e.e.r.u.u.e.9.e.C.F.F.F.F.F.$X;X4.:.:.5.-XG.F.F.F.F.J.m.m.J.m.n.u.y.e.8.7.7.7.8.9.f M l m n m Z n M Z m 2 : r r > j * UXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXj m M F F G S G F k . 3X|   2.T k.T o uX; | VX+XfX5X  ,X1X  J 1X< 4X  + fX.   * rXo.    tXtXN   b fX                          & C m S C S S S S S S m S Z Z S C m m m m m m j m m k b b j j j j j j j 2 j j j j x.x.s.8.e.u.u.r.r.y.y.v.n.c.u.y.M.J.F.F.F.F.F.G.~.;XgX;.,.;.6.:X-X~.D.F.F.F.F.F.H.J.C.v.r.e.e.r.e.8.7.7.i p p 7.8.e.r.y.c.M.V.V.M.y.9.u.F.F.F.F.F.F.~.;X,.>.;.gX=XQ.F.F.F.F.C.m.n.v.v.M.M.c.r.8.8.8.8.9.y.f b k m n n Z Z n Z j : 2 : : : j & UXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXk C m J J F J S S M   X.,X  T <.| | . uX1 ' 3X& 1.CX .3X3X  J 1X. <X.   uX+   , tXX.    rXrXN   * ZXO                         & S S S C S S S S S S S S S C C S C m m m m m m b 2 b b j b j b 2 b 2 j j j j j r x.l.w.7.7.p i p 7.9.y.r.8.p i e.C.F.F.F.F.F.F.^.%X:X5.;.>.;.6.:X-XD.D.F.F.m.n.m.V.v.u.r.u.c.c.u.e.8.7.p a i p 7.8.r.c.m.J.F.J.F.J.V.v.M.F.F.F.F.F.F.~.;X4.>.;.dX%XQ.S.F.C.V.V.m.n.n.v.v.c.c.c.u.e.7.7.i g r M Z Z Z Z n M Z n k : : j j > 2 X UXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX2 M C F F J S C C N . o X.  1 5XG , O uX, T wXX + 9X_ rXVX& V 1X  <X&   8XM   : 3X,X    fXrXM     fX>                         X S C S m S m S S S S m C C C C C m m m m b m b m j b b k 2 b b 2 j j r j j b r r x.l.w.i g p e.c.V.C.C.c.e.7.7.e.m.S.F.F.F.F.Q.~.%X;X4.:.>.;.dX:X&XS.F.F.F.F.J.m.u.r.u.c.n.M.v.n.M.c.e.9.8.7.8.7.8.e.M.C.C.F.V.v.y.y.y.y.v.C.F.F.F.F.$X;X4.:.;.dXQ.D.S.F.F.F.m.M.r.r.M.m.m.v.u.0.p i i 7.e.l Z Z M Z Z Z m Z m j 2 : : j > 2 UXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX; M m F J J C C C m X   ' . O uXG   O uX, / 8X> . uXO ] eX4X8X1X  ,X>   2. at .  N ..,X    ZXrXJ o   rXT   .                     X S C S S S S S S S S S S S C Z m S C m m m b b j b b b b b 2 b j b j j 2 b r r : x.x.w.p e.c.m.V.v.y.9.7.7.9.y.v.S.F.F.F.F.F.F.F.$X;X5.:.,.:.dX=XQ.S.F.F.F.F.V.u.9.8.e.c.m.J.Z.V.n.c.c.c.u.u.r.e.e.r.u.c.v.y.y.u.c.M.c.e.y.F.F.F.S.F.Q.-X4.:.;.dX%XH.S.F.F.F.m.c.c.y.e.7.a p p 7.7.r.b.M.m.x n Z n k Z Z k n m b r : : : : r UXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXM m F F F S S M M &   H *   uXX.  O uX, H eXJ   0X> J * * 4XVX' <X3   @.5X  H _ ;     AX8X_ O   uX2.  .                     X C m S S S S S S S S S S m Z C C C m m m m m b m j j j j j b 2 b : j j b : : h 1 x.(.}.7.8.9.e.e.y.v.V.C.C.V.H.F.F.F.F.F.F.F.F.Q.$X;X4.;.>.;.dX%XQ.S.F.F.F.H.F.C.v.u.v.m.V.m.V.C.J.J.H.C.N.r.8.8.7.8.e.y.v.m.V.H.C.V.M.c.c.C.F.F.S.G.Q.-X,.>.:.BX~.G.S.F.F.F.H.c.e.7.7.9.c.v.v.r.7.i p 8.0.Z n k n Z m M Z Z Z r 2 r j : : > UXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXM m F G F F S m m *   b ;   5X,X  + uX+ M 3X'   1XV H ,   T tXY NX2X  3 5X  J B     . AX<Xo.*   1X3X                        X m S S m S m S S m S Z S C C C m C m m b m b m b b m b j k b b r b 2 j : b r 1 1 l.l.}.p 8.e.v.C.J.J.m.v.c.m.J.F.F.F.F.F.F.F.S.K.Q.-X5.:.>.:.BXQ.K.S.F.F.F.F.J.V.J.C.V.C.m.n.n.m.V.u.r.8.p p 7.7.7.q.y.n.C.J.F.J.V.v.r.y.v.J.F.F.F.Z.~.>X,.:.,.vXA.G.S.F.F.H.v.c.m.J.F.F.F.F.v.7.g p 7.7.p n n Z n Z n k D n m 2 : 2 : : > > UXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXM k G J F F J M M -   ; b   3.8X  + uX+ b 3X|   2.3.Z ;   X.8X  1.wX4X<XuX  M K     X gX, | &   o.9X                        . M C C C A C C S Z C C S Z Z C C m m m m m m k b b j b j j j j j 2 j j j b 2 k 1 x.(.w.0.b.n.m.M.v.c.y.y.c.v.v.V.m.C.F.F.F.F.S.G.~.-X4.;.>.:.BX=X$XD.F.F.F.F.F.F.F.F.F.J.J.v.c.u.r.r.e.9.r.e.9.8.8.8.8.8.9.e.r.9.7.8.y.m.J.F.F.F.S.G.$X>X,.:.,.vXQ.K.S.F.F.F.F.J.v.m.C.J.M.u.9.9.9.7.p 7.p M Z k C Z M n k n Z j 2 r : r > ; UXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXk m S G G G J S k ;   , K   [ fX  + uX+ b 3X-.  T 1X: ;   3.8X  ' H ; <XAX' J F     < fX  ' *   H gXO                         m M C C C S A S Z C C C C S C C Z m m m m b m m j b b j j b j j j 2 j j 2 2 b 1 x.(.w.u.u.c.M.v.n.M.v.c.y.y.e.r.v.V.C.F.F.F.F.Q.=X;X4.;.>.;.dX=X^.S.F.F.F.F.F.J.C.V.m.M.m.n.m.m.C.H.J.V.n.n.u.e.8.p g i p 7.p 8.r.c.v.c.M.F.F.F.S.F.~.BX>.:.>.BX~.G.S.F.F.F.m.8.8.c.c.c.y.y.r.9.8.8.e.y.w.n Z k m m k Z M n n > : : : : j ; UXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXe k S F C J D M M >   O H   1XfX  + uXo 2 1X-.  1 1XB ;   @.5X  S ,XF   rX3XeX3X.   , fX  Y ,   N uX,                       . m C Z S Z C C S C C C Z C Z m C m m m m b m b j b b b k 2 j k j b 2 j j j 2 k > l.x.}.8.9.e.r.r.r.p.r.r.9.e.e.r.u.m.F.F.F.F.F.^.%X;X4.;.,.;.dX:X&XF.F.F.F.H.C.m.v.u.r.r.u.c.n.V.M.u.u.r.9.9.p i i 7.7.8.q.8.u.M.c.c.c.v.V.F.F.F.S.G.$XBX>.:.,.>X$XG.F.F.F.J.c.c.v.v.c.M.c.y.e.9.9.9.7.q.s m Z S k Z m M Z m k j r : : > 2 , UXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXw w k S S J F m Z >   X ]   1XZX. + uX  b ` |   + uX] ;   @.5X  2 3X4X  5XB F rX4XH : aX  H ;   ; 8XK   .                     m C M Z Z C C S Z C S C C C S m Z m m m m m b m b j b b b b 2 j 2 k j 2 j 2 k 1 x.(.f p 7.8.e.r.e.e.r.r.u.y.r.u.C.F.F.F.F.F.F.Q.=X;X,.>.>.;.dX=XQ.S.F.F.F.F.F.F.V.m.V.C.C.C.M.r.7.a p i i i p 7.8.8.8.8.e.v.m.v.v.v.c.u.y.v.J.F.A.^.:X6.;.>.,.-X~.F.D.F.F.H.H.C.m.J.J.C.C.M.u.8.p i i g t Z Z S A Z A Z m M m 2 : 2 r > j * UXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq w t t l A C M M r   . ]   T CX, X uX  C X J X   8XX.,   @.2.  , 8X3X  5X3.. | r 4XwXfX  M M   , 8X at .  .                     j S S S C Z S C C Z Z Z C C m m m m m m m b b b b j j j b k 2 j 2 j j j j j j r l.x.w.p 7.7.p 7.9.p.N.M.v.v.y.n.S.F.F.F.F.F.D.^.=X;X:.;.:.;.dX=XQ.S.F.F.F.F.F.F.F.F.H.C.n.u.e.8.9.8.7.p 8.e.r.r.e.e.r.r.c.c.r.e.7.7.q.9.v.J.F.F.Q.&X=XdX:.:.4.-XS.F.F.F.F.J.C.m.m.m.V.m.c.9.7.p 7.7.p 7.f N n m A S Z Z n M k > > j > > j & UXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX8 w e y y y y t k k     K ] / fX3 X uX. S   K ,   8X1.*   1.5X  X 3X3X  [ <.. '   X _ CX,X,XH   * <.5X  .                     2 S m C C C C S C C S C C C C C m m m m b m b b b b j b b j j j j j j j j 2 k 1 x.(.w.i g 7.r.v.J.J.J.V.C.V.C.F.J.F.F.F.F.F.F.H.$X;X4.:.>.;.BX%XP.S.F.F.F.F.F.m.v.r.e.8.9.e.r.r.9.7.8.9.r.r.r.u.y.e.e.8.7.p 7.r.v.m.H.F.F.F.F.S.S.H.$XdX:.:.4.-X~.F.F.F.F.F.V.u.u.c.r.9.8.e.r.8.7.7.8.8.f Z Z m k C Z Z m m k j j 2 j > j X UXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX$ w e y t y y t e 8 .   : 4X} fXT . uX  C . C N   eXaX&   1.2.  o 3X3X  N 1X. ]     > tXN eXtXC ; P 3X  .                     2 S m C Z C C S Z S m C C C Z C m m m b m b b b b b j 2 b j j j j j j j j 2 m 1 l.(.w.7.u.m.V.J.C.H.F.F.F.F.F.F.C.H.F.F.F.F.F.Q.=X;X,.:.>.:.dX:X^.S.F.F.F.F.C.v.u.r.e.r.u.u.e.e.e.e.u.u.y.e.7.p i i p 7.r.m.F.F.H.m.c.m.H.F.F.F.S.G.=XdX;.>.4.;X~.F.S.F.F.C.c.u.u.u.y.c.M.u.8.p i i g p l h M S A Z C Z k k j > : r 2 > 2 UXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX% e w y y y e y e 6 @   N <X| rX-.X fX  N X b J   8XCX*   <.2.    2X8X  * uX; '     b 9X  m 8X4XwX .uX                        2 S Z C C C Z m S C S C m m C m m m m m m m b b k b b b j j j j j 2 j j j 2 k 1 h.{.0.q.e.u.m.C.C.C.V.n.M.v.c.m.J.F.F.F.F.F.F.Q.=X>X,.;.>.:.BX=XG.S.F.F.F.F.F.H.J.J.F.J.J.C.F.J.C.n.r.p i g g p e.v.m.F.F.F.C.c.8.7.7.7.u.H.F.F.F.Q.=XgX;.;.5.-XQ.F.F.F.H.H.V.M.M.m.C.m.u.9.8.p a p 8.0.x C A Z S A k C Z n k j : 2 r : : UXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXe w l y y e t e w o   b ,X-.8X0X: 8X  b O , _   ,XCX>   <.1.    -.tX  . fXJ ]     V 8X  , ' X ..eXAXS     .                 > S M S C C m C C Z C C Z m Z C m m m m m 2 k b 2 m 2 j j j j j j j j j j j b 2 l.(.a.c.m.Z.V.M.M.c.u.y.c.M.J.F.F.F.F.F.F.F.S.Q.=X;X4.:.>.,.>XQ.F.F.F.F.F.F.F.C.J.F.F.F.H.n.c.r.7.p i i 8.r.u.u.M.m.V.C.m.c.c.r.e.8.8.v.F.F.F.F.D.Q.=X5.;.;.gX;XQ.F.F.F.F.C.y.p.u.p.y.r.e.e.7.7.a a s 7 l Z k Z C Z m k k m k : 2 : > j > UXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXt q y y y t e t w @   r rX-.4XtXT 3X  : * X _   _ AX3   2.1.    X.rXO   eX3XH O   Y 8X  O {   ; j fXtX<Xo.X   .             > C Z m Z C Z C C Z C C C C C m C m m b m m m b b k m b b k 2 2 j j j j j 2 j 2 x.l.a.y.9.9.e.y.M.C.H.J.C.m.v.M.H.F.F.F.F.F.F.D.%X>X,.:.>.>.BX$XG.F.F.F.F.F.J.F.H.m.c.r.9.9.r.u.c.c.c.c.u.8.p 7.8.r.r.8.8.e.y.u.u.r.r.y.n.F.F.D.G.Q.-X5.;.;.5.-XD.F.S.F.C.y.8.8.q.p.c.M.v.9.p g 6 w v ( } ( ( S Z m Z S Z S 2 r > j > 2 = UXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXe q y y t t y t e @   X o.' 4X9XfX1X  ; ; . H   N AXJ   2.1.    H rX>   8XrXF <   T eX  o '   > O 1XY <XPXrXo.o   .         , S M C C Z C C Z C C Z m C C C m m m k m m b b b 2 m j j j k j j j j j 2 b k 1 h.(.}.7.r.v.M.m.m.n.c.e.8.q.u.C.F.F.F.F.F.F.S.G.%X>X,.:.>.>.BX=X^.F.F.F.F.F.J.u.7.a p r.m.F.F.F.C.M.u.e.7.p p p i p p e.M.C.J.V.m.c.r.y.V.F.F.F.F.Q.-X5.>.:.gX=XD.H.F.F.V.u.r.p.c.N.c.e.7.s d x Q j.} } } } } } ( D Z m m Z 2 : : r ; 8 % UXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXe q y y l y y y w W ;   . _ wX3.PX at .  * >   Y   b HXX.  0X at .    X ,XN   T rXH O   T 8X  . ]   , o 5X1.` { 3XAXNX] o . .     , C m C C m C m C C Z C C m m m m m m m m b b b b b 2 j b j 2 j j j 2 j j 2 k 1 x.(.a.M.M.v.c.v.M.v.m.V.M.m.C.M.r.y.M.F.F.F.F.H.%X>X,.:.>.:.BX=X$XS.F.F.F.F.m.c.c.M.C.F.F.J.C.C.n.v.y.8.7.p a a i 7.r.u.v.v.n.m.m.v.n.F.F.F.F.F.F.$X;X4.>.:.dX$XF.F.F.F.F.V.c.t.7.s s c Q ! ) ) j.} j.} } } } } } } } ( G m k r > 8 $ $ % UXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXw w l l y y w e w y X._ . K 3XM P ;   X j   ]   , CX2.  8X4     . | J   2 VX_ O   1.5X    ] X < X @.0Xo.<   A tXHXrXJ + .   , C C C M m C C C C C m C m Z m C m m m b b m b b m 2 b k b 2 j j j j j j 2 j 1 h.(.0.0.r.u.M.n.J.H.F.F.V.M.e.q.8.m.F.F.F.F.F.F.$X>X4.;.>.:.BX~.G.S.F.F.F.F.F.F.C.v.v.v.m.m.C.n.y.7.p 7.8.8.8.p p 7.8.e.c.m.C.m.y.u.m.J.H.F.F.F.F.Q.;X4.;.;.dX%XP.S.S.D.V.i.w.v d.h.h.h.j.j.) ) } ) } } } } ( } } } } } ( k q $ $ 5 $ = % UXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq q e l y y y e e % . _ o.' _ .       . m   ' X & rX8X  8XB     . _ F   + ZXX.*   <.5X    ] * X , 2 tXJ o     . B rXLXfX1.+ , S m C m Z C C Z m C Z m C m m m m m b m b m b b 2 k b b k 2 j 2 j j j j j k j }.x.}.7.7.8.r.c.c.M.c.c.u.r.y.v.V.F.F.F.F.F.S.Q.%X>X,.:.>.,.>X$XQ.D.F.F.F.F.F.V.m.n.M.M.c.c.e.8.8.e.u.u.r.9.7.7.8.9.u.V.V.c.r.8.e.r.u.C.F.F.F.F.F.D.-X4.>.>.dX$XG.S.G.Y.a.}.h.h.l.h.h.j.! j.j.j.) ) } ) } ) } } } G l q % $ q 8 $ $ $ = @ UXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq w t l l l y t t $     ; | ' X       . K   ] |.O eXCX  8X3     . J B   X CX,X+   <.2.    ] r . ; O fXX..     .   3 PXPXKXAXX.m C M M M M C C Z C C C Z C m m m b m m m 2 b k 2 b 2 k 2 b j j j j j j 2 j 2 h.x.f g g i 8.r.c.v.c.u.c.m.V.J.F.F.F.F.F.F.S.F.%X>X:.>.:.,.;X=X~.S.F.F.F.F.F.H.v.r.r.u.y.e.r.u.u.u.8.8.9.9.e.r.y.c.M.v.u.c.n.n.u.y.N.V.H.F.F.S.G.$X;X,.;.:.BXQ.I.!.T./.l.l.h.l.h.h.j.j.j.j.) ) ) ) } } ( ( D e q $ % $ q $ $ 5 $ $ $ = @ UXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq e e l z l l e e 8       . J ..b       B   J tXj <XAX  3X:       J H     tX3X*   0X2.    M J . N   gX3X          aXtXO 1.PXAXC C M m m C m C m C C m C m C m m m m 2 k m b b b b b 2 b 2 j j j 2 2 j j k 2 j.z.}.g g 7.b.c.u.r.r.r.y.v.c.c.M.M.m.J.F.F.S.&X:XBX:.>.:.,.>X%XQ.F.F.F.F.F.V.u.u.m.C.n.c.n.v.c.r.u.c.u.e.q.7.9.r.y.y.u.M.u.e.p i 7.e.c.H.F.F.F.G.D.>X4.4.gXhX].'.'.(./.x.h.l.j.l.j.! j.j.) ) } ) ) ( l w 5 % $ $ 8 q $ $ $ $ $ $ 5 # $ o UXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX% w e y x x l y t 9           * | | *   C . j 3X' { fX4 uX;       K '     rXrX+   0X1.    O | H S   eXwX.       o AX3     + | S m C m C m C C Z C C m Z C m m b m b m m b b b b j j j j j j j j j j j : b h ^ (.w.8.y.u.y.u.c.v.m.V.n.v.r.p p 8.V.F.F.F.Q.&X=XBX>.>.:.,.>X%XG.S.F.F.F.H.C.H.C.n.M.n.V.m.n.M.n.v.r.p i p p 7.r.y.p.r.9.p p 8.r.c.V.G.F.D.S.K.!.hXPXgXKXHXjX[.].'./.l.l.x.l.h.j.j.j.j.j.j.( D l q $ # # 8 8 8 = 5 = # = = = 5 $ $ $ = UXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXe e R y l y y y w o           O ,XX.C 2   ; 3Xo.1.8XtXfXo       M ' O   0XfXo   1X at .  .   <XVXJ   5XtX        ; fX.       X m Z m C m S Z C m C C C m m M m m m m b b m b b j j j j j j j 2 j j j j j b > h.(.w.e.9.e.r.u.y.y.y.e.9.8.7.8.M.F.F.F.F.F.S.G.%X>X>.>.;.4.;X~.H.F.F.F.F.F.C.u.r.c.V.C.m.V.J.V.c.e.9.8.8.8.9.e.e.e.9.9.y.v.n.M.V.m.c.c.M.P.'.jXDXKXPXPXJXcXXX[.]._.).x.x.x.j.x.j.j.j.! R e 8 5 $ $ 8 8 8 8 5 = 8 = 8 8 8 = = $ $ $ $ = UXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXe w y x l z l y t @           . ,X> ] |.* O 1X' 2XOXAXtX  .     b | <   <XVXo   8XT   X   X.VXK   1.fX    .   T tX  .     O C m C m m m m m C m m C m m m m m m m j b b b j j b b j j j j j j j j r r b 1 h.z.w.i i i i p 8.e.c.n.u.c.n.F.F.F.F.F.F.F.S.^.=XBX,.:.:.>.;X=XQ.F.F.F.F.F.C.F.J.m.M.n.V.m.M.y.e.y.9.p g g i p p 8.r.y.y.r.r.r.r.i.f.(.[.jXzXDXKXLXPXJXSXlXXX[.{.).z.).l.j.j.j.~ l e 8 - $ 8 8 8 = $ = 5 8 8 8 = = = = = = = $ $ $ $ $ UXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXt w l l l l l y y @             nX<   n ..] { ' 3X,XV P         : ] | o X.PX*   8XJ   .   o ' Y   P VX    .   5X9X  .     o m m C C C m m C C m m Z C C M m m k m m m b b j b j j j j j 2 r j j j j j b > h.x.s.@ g i 8.c.H.F.C.V.v.m.F.H.S.F.F.F.F.F.S.Q.=XBX:.>.:.:.>X:X~.F.F.F.F.F.F.C.r.7.9.u.c.m.n.n.u.8.i i p 8.e.y.r.e.8.q.7.0.a.}.(.).].XXjXmXSXHXLXLXLXFXzXhXjX_._._.z.j.h.Q v 0 = = = 8 8 8 8 5 $ 8 8 = 5 = = = 5 5 = = = = = $ = $ $ $ UXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXe w l l R l R R z l O       .   tX_   . o _ <X' ] ' o           1 S eXj B AXj   9X3         M '   ; CX.   .   8X<.  .     o C m m m C S C Z m m M m C m m M k m m b b b n j j j j j j 2 b j 2 j j r j j 1 h.(.}.i 9.v.C.V.n.c.u.c.u.r.c.m.C.C.F.F.F.F.S.P.%XBX:.>.:.,.;X=X~.S.F.F.F.F.J.v.u.c.n.m.V.M.u.r.9.0.e.y.r.e.e.7.p p f }.}.l.l.(._.[.jXhXxXSXDXGXJXGXSXzXlXjX*X_.z.j.c y 5 % $ # 0 8 = 5 = = 8 8 = $ 8 = 8 = 5 0 = = = = = = 5 $ 5 $ $ % UXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXw w l z R R R ~ x 4X1.    ; o   nX|.  .     S |.' *             > M 3X_ : CXT   fX;         N ..  X AXO   .   rXP         . m m Z C m m m C m C m m m m m m m b m b b b b b b j b 2 j j j j j j j j j b > h.z.}.M.V.n.c.u.u.c.v.v.n.V.C.n.V.F.F.F.F.F.S.G.%XBX>.>.:.,.;X%XQ.S.F.F.F.F.F.m.v.u.y.r.e.e.r.y.9.8.7.7.7.p s.w.}.}.l.l.l.(._._.[.jXhXzXmXFXFXGXSXFXvXlXjX_.j.Q t 5 % q q q $ 8 = 5 5 = = 5 = 8 = 8 = 8 = 5 = = = = = = # = 5 $ 5 $ = % UXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX0 8 y R z Q x ~ c wXpX.    .N   aX4X  .     , , J ,Xj           > B 8Xo.: fX1X  AXO         b ,X    aX;       fX>         . m m m m C m m Z C C m C Z m m m m m b n n b j b j j r b j j j j j j r b 1 j 1 h.(.a.e.e.r.y.v.M.C.J.H.V.v.y.m.F.F.F.F.F.F.F.Q.%XBX,.>.;.4.-X~.G.F.F.F.F.F.C.c.9.u.m.m.C.V.y.8.p i s s.w.}.h.}.h.l.l.x.z.oXoX[.jXkXkXcXcXmXFXFXSXvXoXk.c 9 % % 5 q q q $ 5 5 8 = 5 = 8 = = 5 $ = 8 8 = 5 = = = = 9 # = = = = $ $ 5 8 & UXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX8 0 y R l l R ~ z ,X0X.   iX7X  PXqX        , ;   ; X.{ O   .   , M 3X| N rXeXX fX.         ; 4Xo   8XV     O AX+           m C C m m m C m C m M m M m m m m b n b b b n b j b j j j j j 2 j j j j j 1 1 }.f.w.7.7.8.e.c.V.J.V.c.r.e.r.v.C.F.F.F.F.F.D.^.%XBX>.>.;.4.;X~.F.F.F.F.F.V.m.n.C.C.m.n.u.0.7.p f }.}.h.h.}.}.x.l.x.x._._.oXjXjXlXzXvXmXcXmXvX.Xk.A 6 % % $ q 9 5 $ $ $ $ $ $ = 8 = 8 = 8 8 = 8 8 $ 8 = = 5 = = = = = = = # 5 $ = $ 8 @ UXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX= q y x R R x ~ h.k.fX1   ZX X  aXaX        < ;     . K ,XM     , M 3XX.F eXZX3 tX          * <X*   eX2.    , fX.           j C m M m m m m M C m m m M m m b b b m b b b j j j j j j : j j j j j j : b ; ~ x.}.7.7.r.M.m.V.n.u.r.9.8.9.n.F.F.F.F.F.F.D.^.=XBX;.,.;.4.;X~.F.F.F.F.F.C.m.m.b.M.i.i.0.}.}.}.c h.h.}.h.l.l.l.z.z._._._.oXoXlXzXxXvXbX#Xj.e # @ # 8 0 0 = = $ 8 $ 8 8 = 8 $ 8 = 8 = 8 = = 8 = = = = = = = 5 6 = = = = = = $ $ $ $ = @ UXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX= w e R R R R ~ ! ) AX|.  PXnX. 7XMXo       O ;         * |  .& X K 3X' _ 2XVXAX8X  .       O ..'   1XeX    2 tX            j Z M m m C C m Z m m C m m m m m m m b b b b j j b j j j j j r j 2 j j r b 1 h.W.}.9.u.v.M.m.m.M.M.v.u.u.u.n.C.F.F.F.F.F.S.^.:XdX;.>.;.4.;X~.G.F.D.F.H.m.v.M.B.i.f.}.l.}.}.h.h.h.h.h.h.l.z.z._.z._.oXbXjXlXlX_.j.Q 5 % & 5 q 0 8 5 $ = = 8 5 8 8 $ = 8 8 8 = 8 = 5 = 8 5 = = 9 = = = = = = # = = = = = = 5 = $ # 5 o UXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX% w e z x x R ~ ! W ZXVX_ ZXpX  <.MXX       o ;             F |._ ; 4X2XY ,X5XPXT           X ] eX, X.fX    Y 8X  .         j M m m m C m M m C m m m m m m m m m b b b b b j b j j j j 2 j j j j j j b r h.l.}.8.r.c.m.C.J.J.V.M.N.u.u.v.J.F.F.F.F.F.S.^.=XBX;.>.:.4.;X%XF.D.D.Z.Y.B.f.a.l.}.l.l.}.l.l.}.h.h.l.l.z.z.l.z.z.).oX_._.z.! t $ % % 8 q q = $ $ $ 8 5 8 = 8 = $ 8 8 8 8 = 8 5 = = 8 5 5 = = = = 5 = 9 = 5 = # # = = 5 5 $ $ $ $ # 8 UXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXe e l x R R R ! ) sX X| ZXZX; 7XLX,   .   . :               O ' -.| o.' -.F L +           o H tXN Y ZXX   1.8X  .         2 M m C m m m C m m C m m M m m m j b m b b b b j j j j j j j j j j j j r b 1 R (.a.e.u.c.c.c.v.M.m.M.c.r.c.C.F.F.F.F.F.F.S.G.%XBX:.,.;.4.;XQ.G.L.U.T.W.f.f.f.l.f.f.}.l.}.}.h.h.h.l.h.x.l.z._.z.z.h.d 5 - % $ q w q 8 $ = = 8 8 = 8 = 5 5 = = 8 5 = = = 8 $ = 8 8 = = 8 5 = = = = = = = = = = 6 = 0 9 5 $ $ $ $ = = UXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXe w x l R ~ ~ ! j.pXnXK 5XGXD 1.ZX; . F     j                   N ..' ] m   .               / tX_ N fX;   9X0X  .         2 m k m m m C m m m m m m m m m m m b b b b b j b j j j j j j j 2 j j j : b : ~ l.w.7.7.8.e.u.m.m.N.u.y.u.v.m.V.F.S.F.F.F.F.^.%XBX;.>.:.gX-X`.`.`.T.W.f.f.l.f.l.}.}.h.l.l.h.h.h.x.x.x.z.x.h.v d $ % - = 8 q 8 5 $ $ $ 8 8 8 8 8 = 8 = 8 = 8 5 8 $ 8 8 = 8 = = 8 5 = = = = 5 6 5 8 8 5 5 5 5 6 0 5 5 0 5 $ 5 = $ $ # UXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXe 0 x x R R R ~ ! 9XMXP 2.nX at .rXPXU < 3X  . j                     O _ ,XB                   _ rX| > fX at .  tX3.  .         > C M M m C m m m C C m C m m m b b b m m b b r j j j j j 2 j j j j j j b r 1 R }.w.p 8.r.b.n.c.c.M.c.M.c.u.v.V.F.F.F.F.F.S.A.~.dX,.5.AXLX*X[.'.'.'.'.(.(.l.l.l.l.g.l.l.h.h.l.l.x.h.h.l 5 # % $ 8 q q 5 $ $ $ = 8 8 = 8 $ 8 8 = 8 = 8 = 8 = = = 5 = $ 8 5 8 8 = = = 5 9 8 - < 8 8 8 5 5 5 5 = 6 9 9 9 5 = = $ $ = % UXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXe q l x x R R R ! 5XZX7X5XnXh @.LX at .O AX^   M                         : X.o.o               ] tX] - uX5X  fXN             > Z m m m m M m M m m m m b m k m b b m b b b b b 2 r j 2 b j r 2 j 2 b 2 b 1 c }.}.9.r.y.y.u.u.u.u.u.u.r.u.n.S.F.S.S.S.F.!.*XHXAXgXPXLXSXjX[.{.'.`.(.(.l.f.l.x.}.l.h.l.h.x.d.x t = # # = 8 8 8 = $ $ = 8 8 8 8 $ = 8 8 $ = 8 $ $ 8 = 5 = 5 = 8 8 8 5 8 = = 5 9 0 = - * 5 s a.0.# 9 5 9 9 5 9 9 9 0 0 5 5 = $ $ $ % UXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq e y x R R ! R R 7XsX4X at .ZX@. at .ZXrX .KX X  N .                         X Y <XM             / tX' :  .1XO AX+             , C m m C m m m m m M m m m m m m m b b b b b b j b j j j j j j b r b r b r 1 R }.w.8.8.8.8.e.r.c.M.M.u.r.r.u.V.J.K.U.'.[.cXLXKXAXPXLXDXzXjX].[.{.(.{.(.l.l.l.l.x.h.h.c R 0 # # $ = 5 8 8 = = $ 8 8 5 = 8 = 8 = 8 8 8 $ 8 5 = 8 = 8 8 = 8 = 8 5 = = = 8 8 8 8 - * = s i.Y.L.L.i.- 9 5 9 9 9 0 6 0 9 9 5 5 = 5 $ = % UXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXw q y z R z R ~ }.^ nXI T ZX X4 ZX XV PXZX  3XN                             , ..| ;     .   J eX| _ J aXT fX.             , C b C m m m m m M C m m m m m b m b m b m 2 b b 2 2 j j 2 2 j j j j r j k 2 R }.f p p 7.9.y.M.v.r.8.7.0.0.b.W.'.[.[.hXmXGXKXKXKXLXGXFXkXjX[.[.{.)._.x.(.l.h.}.! t 9 # % 5 5 8 8 8 = = 5 5 8 8 = 8 8 = 8 8 = 8 = 8 8 = 8 = 8 8 = 8 = 8 8 $ = = 5 8 9 = - - 5 f i.Y.P.K.K.P.K.0.- 6 0 9 5 8 8 q q 6 q 5 $ $ $ $ 5 & UXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX8 9 y l z R R R R h aX at .T MX at .@.ZXqXN AXMXY VX8X      N     X               , O A 3X_       b wXo.' m [ PX3X  .           - S m m m m m M m m m m m m m m m b m m b 2 b b j b k j j j j j j j j 2 j j 2 l h.f 7.8.8.8.8.7.7.8.0.a.g.(.{.[.XXjXzXmXGXLXKXKXPXPXFXxXkXjX[.[.{._.(.l.}.x u = # = = 0 0 8 = $ 5 8 = 8 8 = = $ 8 8 $ 8 = $ 5 = 5 = = 8 = 8 8 = 5 8 = = = 8 8 5 = - - 8 s i.Y.K.K.K.L.P.K.P.K.a.= 9 5 6 0 9 9 9 q t w $ $ = $ $ = @ UXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX$ q y R l R R ! ! R 6X|.I sX7X| aXiX3 aXsX4XZXaX    * 3XX   8X*   ,     .   8X<X  J ..X.;   > 8Xo.' F + KX4               * C j m m m m m m m m m m m m k m b 2 m k b k b b k j j j j j j 2 j j j 2 j j h R d p i i i p 0.}.l.x.(._.{.[.jXhXzXxXFXGXJXLXLXGXGXcXzXjXXXjX_.(.}.d 0 = = = 8 5 8 8 = = = 8 8 = 8 = 8 = 8 8 = = = 8 = = 8 = 8 = 5 8 = = 8 5 = = = = 6 8 8 * - w p i.Y.K.K.K.K.K.K.K.K.K.K.P.s.= 6 5 9 9 9 u q t 9 9 5 5 $ $ $ = @ UXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX# e e R R x x R ~ l @.7X at .sX7X1.aXiX[ fXnXo.6XPX`   J GXP   tXK   ]     .   qX7X  h O N 4XH * eX,X_ P   3 .               & S m m m m m m m m m m b m m m m b b m b m b 2 b 2 j j 2 j j j j j j j j j : R c l u i f }.}.l.}.(._._.XXXX[.hXzXzXmXFXGXGXGXGXGXFXxXkXXX_.h.d = - # 8 8 0 8 8 8 = = 8 8 8 8 $ 8 = 5 = 8 = = 5 5 8 = 8 8 = = 5 8 = 8 8 = = = = 8 9 = - - 6 w.b.Y.K.L.L.K.K.K.K.K.K.K.K.K.L.P.s.= 9 8 6 9 u 9 e w 9 8 5 $ $ $ $ 6 o UXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX& e w x x R x R ! x |.sX[ 6XsX at .uXnX[ uXnX^ @.AXqX/ CXMXh   FX|.  X.    .   nXtX  : ,   * ' ,X..| o.H   o                 & m N m m m m m m m m m m m m m m b b b b 2 j b j j 2 j j 2 j j : j j j j j : A h.R c }.l.l.l.x.(.{.oX[.XXhXhXzXcXmXmXDXGXGXGXGXSXlX.Xh.t # % # 8 0 0 = = 8 = 8 5 8 8 5 8 5 $ 8 8 = 5 = = 8 8 = 5 = 8 = 8 8 = 8 = = = = = 9 0 = * # 9 s.a.Y.K.K.K.K.P.K.K.L.K.K.K.K.K.K.K.K.K.s 0 u 9 9 q q 9 9 9 9 8 5 5 $ $ $ $ UXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXw w y R l R x ~ x  XiX at .6X6X[ MXpX at .uXnXV 4 AX XK PXMX4X  iXqX  8X1   o   qXnX  ; ,       b ..| H N                     + C m m b m m m m m m m m m b m m m 2 m b b b j b j k j j j j j j j j j j j 1 R ! Q l.h.l.x._.(._._.oXXXjXkXzXxXmXmXmXFXFXGXlX.XW 0 % < 5 8 0 8 5 = = 8 5 8 8 8 = = 8 = = 8 = 5 = 5 = 8 8 = = 8 = 8 = 8 5 = = = = 8 6 0 = * - u s.i.P.K.K.K.K.P.K.L.K.K.K.G.L.K.K.K.L.K.L.K.P.s 9 6 9 9 9 u t 9 9 w 8 $ 5 $ $ $ = UXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXe 8 y x y x R ~ f qXiX2.sX6X1XaXpX|.1XaXL V ZXnXT aXZX|.. VX7X  0X3.  X   aXiX  ; ;         O _ <XN                     X m m m m m m m m m m m m m m m b b m 2 m b j b k j j 2 j j j j j j j j r j > R h.j.l.l.(.z.z.{.oXoXXXXXjXkXkXxXmXmXmXqXk.A < % % = 9 9 8 = $ $ = 8 5 8 = 8 5 = = 8 = 5 = = 8 = 5 = 5 = 8 5 8 = 5 8 8 = = = 5 9 8 = , - u s.b.P.K.K.K.K.K.K.K.K.K.K.L.K.L.{.G.K.K.K.L.K.K.K.P.7 q w w 9 6 w t 9 9 w 8 5 $ $ $ $ $ UXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXe w l x R x x R x ^ aX6X7X6X7XaX6XqX6XaX at .P VXtX1.0XnXtX<XZXnXo fX0X  o   @.MX. & >             2 o.F                 . X C m m b m k m m m m m j m m b m b m k m b b j j j j j j j j j 2 j j : j j : A h.l.x.x.x.(._.oXoXXXoXhXkXkXkXlXoX^ t 5 @ # 0 e 9 5 $ $ = 8 8 8 = 8 5 = 5 = 8 = 8 = 8 = 5 8 = 8 = 8 = 8 = 8 5 = = = $ $ q 8 $ * = u s.b.P.K.L.L.P.K.K.K.K.L.L.K.K.K.L.S.jXJXK.I.K.K.K.K.K.K.P.7 t 7 w u 9 t 9 t w 9 8 $ = $ $ $ # UXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXe q h f x l R R R ^ <.pXpX6XaXpX7XqX5XMX|.I fXqX; 2.iX|.1 PXnX  5X0X  o   4XiX  O :               * ,X| *               X m m m m m m m m m 2 m m b m b m k b b j m j b j j j j j j j j : j j 2 j j j l h.x.l.)._._._._.*XjXjXXXoXk.c 5 @ % 5 9 0 0 5 # = = = 0 5 = 5 8 8 5 8 = 8 5 = 8 = 8 = 8 8 = = = 8 = 8 8 8 = = = 8 9 8 = - - 7 s.B.K.K.K.K.K.P.K.K.K.K.K.L.L.G.L.K.K.I.A.kXKXK.F.L.K.L.K.K.K.Y.u u 9 q q t 9 9 w 9 9 q $ $ $ $ = % UXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq q d R l x f ~ f k. at .7XpXiXpX6X6X@.<.sX#.V aXsXO 2.iX|., uXAX4XaXuX.     |.nXF   M .               X _ ,XJ o           o m m m m m m m m m C m m m m m m b b b b j b 2 j j j 2 j j j j j j j j 2 j j A l.z.x.z._._.[.oXoXx.~ y % % % 8 0 0 0 5 = 5 5 = 0 8 = = 8 = 8 = = 5 = 5 8 = = 5 5 = 5 = = = 8 8 5 8 = = = 0 8 8 = , = u s.B.P.K.L.L.P.K.L.I.L.L.K.L.K.L.G.I.'.K.L.G.I.F.cXLXT.XXG.L.K.K.K.K.Y.6 u 9 u w u q t 9 9 q 5 $ $ 5 $ = % UXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX8 q y x x x R R R ^ @.sX6X at .pXpX6X7X2.6X XP sXMX1 2.ZXiX] fXZX^ pXZXo .   o.iXB   _ '                   : ..X.;         o k m m m m m m m m m m m m b m m b m b m j b j j j j j j j j j j j j j 2 j 2 A x.z._._._.x.h.R 0 % % $ 0 w 0 5 = # 5 8 8 8 5 0 = = = 5 = 8 8 8 = 5 = 5 = 8 8 8 8 5 = = 8 8 = = = 8 5 9 = - - 8 s 0.B.K.K.K.K.K.K.K.K.K.G.G.L.K.K.K.K.L.G.zXJXL.L.K.K.L.KXJXmXLXI.K.K.K.P.L.B.9 u w 6 u t 9 q q 9 t q $ $ 5 $ 8 & UXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX5 w e x y l x R f k.^ uXqX at .6XnXsX6X#.6X6X#.fXsXh #.sX|.h fXGX at .3XfX..8X@ |.MXV   _ aXb                   X Y ,XH X     . q e j b m C 2 2 m m m m b k b b m b b j m k j j j 2 j j 2 j j 2 j j j r j 2 R z.x.h.c y 8 < # 5 0 q 8 $ $ = 5 0 8 5 5 8 = 5 5 8 5 8 5 8 = $ 8 8 = 8 = 8 8 = 8 = 8 8 5 = = 5 8 8 8 = * # s 0.B.L.L.K.K.K.K.K.K.K.K.L.G.'.{.F.L.K.K.L.K.I.LXLXI.K.L.G.`.DXmXJXxX'.F.L.K.K.K.i.0 u t t 9 t 9 6 9 w 9 $ $ $ $ 5 8 @ UXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX= e 0 x y x x x x ^ #.7X6X#.0XrX7XaX7X6X6X#.uXsX^ <.sX X|.fXZX4X3.fX,X3XC L MX X  ,XJXh                     . ; X. at .2 . . q w w e m m m m m m m m m m m b m m j b j b j 2 j b j j j r 2 j j j b j 2 > y l 0 - % $ 8 0 0 8 = $ 8 8 8 0 = 5 8 5 5 0 5 5 8 8 = 8 5 = 8 = 5 8 = 8 5 = 8 8 8 $ = = = 8 0 8 # , = s 0.Y.L.K.K.K.P.P.K.K.K.K.L.L.K.I.A.xXxXA.U.G.K.L.F.'.hXkX`.F.I.G.`.kXI.GXkX{.F.I.K.I.L.i., u t t 9 q t w 9 t w 8 $ $ $ $ = @ UXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX& e 0 y t c d c d j.^ <.iX at .3.nX@.7XaXpX7X|.6XaX#.#.sX6Xb uXnX|.5X9XL | <XtXqXP   ] MXh                         o G 1.` X 8 w q q q e 2 m m m m b m m b b b b b b j j j k j 2 j j 2 j j j j r > 0 0 = = = 8 0 q 8 8 $ = 5 8 8 8 8 8 5 5 5 5 8 = = 5 8 8 = = 8 = 8 = 8 = 5 = = 8 8 = = = 8 8 8 8 # * 5 s 0.Y.K.K.L.L.P.K.K.L.K.K.K.K.K.K.K.G.U.A.xXzXS.I.K.K.U.A.kXjXXX{.S.U.F.[.jXL.`.zXcXA.I.K.H.K.a.$ 7 q u 9 6 t t 6 t 6 $ 5 $ $ $ = o UXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXe 0 y d y y x u x.P 3.aX^ T nX7X#.sX6XpX7X7XsX[ #.7XiX at .ZXaXaX@.2.4XJ n eXMXrX  4XMX4X*                           - { +Xk w w e w w q q e k m m m m m m b m j b k b b 2 b b j j r r r r 8 8 $ 8 8 8 0 0 = 8 = 8 = 8 8 8 5 8 = 8 8 = = = 5 = 5 = 5 = = 5 8 5 8 8 5 = 8 5 8 = = = = 0 8 = - - 8 s i.Y.K.I.K.K.K.K.K.K.K.K.K.K.L.K.K.K.K.K.K.U.A.xXxXA.U.K.G.K.A.cXkXXX{.S.T.A.xXzXS.F.kXFXS.U.S.[.{.p.e 7 t t w t w 9 9 9 q $ $ $ 5 $ = UXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXu 5 y x d y x u j.^ [ MX^ 3.pX X1.pX|.pXpX4XpX|.1.6XiXV uXZX7X1XT @.P   eXnXtXN _ nX at ..                           . . S m q e w q w e q q e q q e r k k b j k j j j r 2 e > q 8 q 8 5 5 8 8 8 8 8 = 8 = 8 8 8 8 8 8 8 5 8 8 8 8 8 0 5 5 = = 0 5 = 0 = 8 = = 8 8 8 5 = = = 8 9 5 8 - - 8 s i.Y.K.K.P.K.K.K.K.K.K.K.K.L.K.K.K.K.K.K.K.K.K.K.L.I.LXzXA.I.K.L.kXA.xXjX[.{.S.T.A.xXzXF.L.`.zXS.U.A.zXDX9.k d q u 9 6 9 t w t q $ $ $ $ $ = UXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXw w y x t d c 7 j.|.j nX^ 2.aXeX at .6X@. XsXaXpX|. at .pXpXh <.ZXO.T T tXE n fXqXqX' rXiX X'                                 8 w w w e w w q w w q w w q w w q q q q q q q q $ q $ q q q $ $ q $ q $ q 8 8 8 8 8 8 8 8 8 8 5 5 = 8 8 = 8 = = 5 5 0 = = 8 = 8 8 5 8 = = = 8 6 8 = - * 9 f i.Y.K.L.K.K.P.K.K.K.K.K.K.L.K.L.K.K.K.L.K.K.K.K.L.K.I.S.jXKXjXA.I.G.I.LXL.hX`.XX'.A.K.A.kX[.F.K.T.zXI.I.G.cXKXd.e d 9 t 9 9 t 9 9 9 q 5 $ $ $ # = UXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXe 9 t t y t x t ! ^ , qX at .@.pX at .<.fX at .O.6X6XpXpX<.<.sXI <.MX|.O.<.ZX^   rXaXqX  8XGX X:     , o                         = e q w w w w e q e q e e e q q w q q q q q q q q q $ q - q q q $ q $ 8 8 8 8 8 8 8 8 8 8 8 8 = 8 5 8 8 8 8 = 0 5 = 5 5 = = 8 5 = = = 8 0 8 8 - - 5 s.i.Y.K.K.K.K.K.K.K.K.K.K.L.K.K.K.K.K.K.K.K.K.K.K.K.K.L.K.G.I.S.kXKXSXK.I.K.I.KXxXcXI.jX'.L.XXS.kX`.G.K.U.KXLXI.L.KXLXv e t t 9 w u 9 t w 9 5 $ $ $ $ $ # UXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXw q t y t y d d v W O qX|.T 9Xb #.uXiXP 6X7X|.6XaX7X6X at .<.sX4XI 1.iX at .  ZXpX|.X 4XGXqX|.+   { @.                        # e w w w q e e q w q q q q e q e e q q q q q q $ $ q q q $ q $ q q $ q - 0 8 8 8 8 8 8 8 = 8 8 8 8 8 8 8 = 0 5 5 5 8 5 5 5 = = 8 8 5 = - # 6 f b.Y.K.K.K.L.K.K.K.K.K.L.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.L.K.K.L.G.I.A.hXcXJXU.F.K.T.KXDXJXL.kX`.K.JXI.SXU.L.I.`.KXKXU.kXKXLXz u t u u t t 9 9 9 t q $ $ $ $ $ % UXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX8 q t t d d y d t ) |.o. X4 0X  [ aXnXI aX6X7X<.6XiX9X|. XsX7XI @.iX X  ZXpXqX_ 1.AX#.4Xk.N | / X                       % e q q e e q q e w e w e e q w q q q q q q q - q q q $ $ q $ q $ $ q $ q 8 8 8 8 - q 8 8 8 8 8 5 = 5 = 8 = = = 5 8 8 = 8 8 9 0 = - - 6 s.i.Y.K.K.K.K.P.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.L.K.K.K.K.K.L.L.K.L.L.K.K.L.G.cX'.SX`.XXF.XXKXKXJXF.kX`.hXSXI.LXU.G.A.'.KXKXSXKXcXSX~ s y 7 t 9 w 9 u q q 8 $ $ $ $ = % UXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX= q t t d d t t d e H 3XqX: 9X> T uXiXL 5X6X7X at .uX@.aX7X2.sX6X at .@.6X X. 2.MX7X< <XPX^ , , ,XMX, F                       % w w e q q q w w q e q w w q e w q q q q q q q $ q $ $ q 8 8 8 8 q 8 8 8 8 8 = 8 = 8 8 8 8 = 8 5 = 8 8 8 8 8 = = = 5 9 0 = * - u s.i.P.K.L.L.K.K.P.K.K.K.K.K.K.K.K.K.K.K.K.K.K.L.L.K.K.K.K.K.K.L.G.G.L.G.G.L.L.K.I.JXI.zXDXLXG.hXKXKX[.A.xX{.KXcXU.SXF.'.jXFXKXKX{.KXzXDXW 6 t t 9 t q 9 t w q 5 $ $ 8 $ = % UXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX$ w w t t y d d d ) K j 4X_ 1XX 1 fXnX[ <.pX|.#.uX6X at .iX9XiX5X@.2.sX6X< 2.uXiX|.|.PXU | F * LX<X|                       % e q q e e e e q e q q w q e q q q q q q q 5 q $ q q q $ 8 q 8 8 8 8 q 8 8 8 8 8 8 8 8 8 8 8 8 8 5 8 8 8 = = = 0 0 # * # u s.b.P.L.K.L.K.L.L.K.K.L.K.K.K.K.L.K.K.L.K.K.K.K.K.K.K.G.K.K.L.K.K.L.G.'.'.S.[.'.F.L.K.U.JXL.mXKXLXK.hXKXJXL.I.cXkXKXXXhXJXkXSXjX`.[.JXzXJXkXvXD 7 t 7 t 9 6 9 9 u 9 $ $ $ $ $ = @ UXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX# q w t d d d t d y `   B @.| j N uX4X1 <.aX at .@.iX6X at .7X6XaX6X X#.6X6X< 0XsX6X^ @.PX X^ & h MXh 4X_   .                 & e w w w q q e q e w e q q w q q q q q q q q $ q q q 5 q 8 q 8 q 8 8 - q 8 8 = 8 8 8 8 8 5 5 8 8 = = = 8 9 8 = - # 7 s.B.P.K.K.K.L.P.K.K.L.K.K.L.K.K.K.K.K.L.I.K.K.K.K.L.K.L.L.`.xXL.K.L.K.K.L.G.JXhXG.KXhXS.I.F.T.DXK.SXDXJXK.hXKXLXU.K.mXxXKXDXJXxX'.K.A.K.A.[.cXjXK.Y.9 t t t t e 9 9 9 q q 5 $ $ $ $ 8 @ UXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX@ w q t t d d d 7 t '   * ; OXo.O J _ . @.aX at .[ pXqX#.7X6XqXaXaX at .7XsXh 0XsX6X[ 1XZX XP C |.ZXh , 3Xm                   @ e w w q w w q e q w q e w q w q q q q q q $ q $ - q q $ 8 8 8 8 8 8 $ q 8 8 8 8 8 8 5 8 8 = = = 0 6 5 # - # u 0.B.P.L.L.K.K.P.K.L.L.K.K.K.K.K.K.K.L.K.I.S.XXzXF.I.K.K.K.K.L.A.hXKXI.K.K.K.L.L.I.KXSXcXKXhXF.U.S.SXmXA.JXxXDXI.jXKXcXG.{.LXDXKXcXJXK.S.L.I.I.L.T.JXjXF.a.5 u 9 9 9 9 9 9 u 9 q $ $ $ $ $ 6 o UXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXw w t d d d d 7 7 o   * D > ' ,X, ,   T MX|.P uXiXI 6XsX X<.aXpX|.6X[ 6XsX7X|.1XZX6XU n eXiX[   ^ NX3Xh             . @ e q w q e q e q e w w e w e q q q q q q q q q 8 q q q $ 8 - q q 8 8 8 q - $ 8 8 8 8 = = = 8 9 9 = - = u s.B.K.L.K.K.P.K.K.L.K.K.K.L.K.L.L.K.K.K.K.K.K.U.A.zXDXS.I.K.K.L.G.`.[.FXSXU.K.K.K.L.G.'.LXKXJXmXxXA.`.T.KXkXA.xXXXJXI.kX[.jXcXSXJXSXKXzXDX`.K.L.G.K.K.L.KXkXS.a.= t q t 9 9 9 q w 6 q 5 $ $ $ $ = UXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXw q t d d d d d 7 .     b K o J ,X_ j 4 ZX|.V pX6X[ <.6X7X#.6XsXpX9X#.0X6X6X at .3XZX6X^   AXqX#.  qXP ' CX8Xo           X e e w w q e w w q q e q q e w q q q q q q q $ q $ $ q q 8 q q $ 8 8 8 8 q q 8 $ = 8 8 9 8 # - 8 s 0.B.K.K.L.P.K.P.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.L.G.I.A.kXJXG.L.K.K.L.G.xXKXmXkXJX`.K.K.L.F.zXJXKXDXcXcXA.SXhXLXxXA.L.A.SXJXJXFXkX'.L.LXLXKXcXjXG.K.K.L.K.L.L.KXjXF.a.% 7 6 9 9 9 w 9 w w q 5 $ $ 5 $ = UXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXw q d d d d d d d .     J M X N B <X: 3 ZX7X3 uXiXU @.sX|.I 7X7X7XfX7X2.6X7XiX8XZXiX^ + ZXqX#.  aX at .  1XLX at .        . & e w w q q e q q q q q q w q q q q q q q $ q 8 8 q $ 8 8 8 8 $ q 8 8 q 8 8 8 8 q 8 8 - & 8 s 0.B.L.K.K.K.K.K.K.K.K.K.L.K.K.K.K.K.K.K.L.K.K.K.K.L.K.L.G.I.A.xXLXG.L.K.K.K.U.JXKXjXXXKX[.F.I.G.`.DXkXKXzXxXxXS.KXDXDX{.S.[.kXDXKXLXU.A.G.I.KXDXLXFXXXG.L.K.K.L.K.U.LX`.K.a.q t q t 9 9 9 9 6 q q $ $ $ $ $ $ UXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXw q t d v d d v s o     O ... ; ; , 4X_ nXsXO uXMXU @.ZXsX1 7XuX|.7XpXiXqX5X|.3XMX6X#.1 pX|.|.  uX|.  2.fXPXX.< *   . & q w e w w w e q w e w w q e e w q q q q q 8 q - q q q $ 8 8 8 8 8 8 $ q 8 $ q - - = s a.B.L.K.L.L.P.K.K.L.K.K.K.L.K.K.K.K.L.K.K.K.K.K.K.K.K.K.K.K.K.L.K.`.KXJXL.K.K.L.G.`.SXkX[.XXKXjXS.I.F.`.DXXXzXA.cXkXG.SXJXcX[.SXmX[.zXKXJXU.I.I.I.JXDXLXKXhXS.I.K.K.L.G.U.JXU.K.s.8 9 6 9 9 9 t 9 q 9 5 $ $ $ $ $ $ UXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq q w v d v v v s @ .   o F   r Y < < | 3XnX< tXMXP #.ZXeX+ 7XsX at . X|.uXsX<. XaXsX6X|.3 9X3X X< uX7X  2.fXMX3XVXeX  X 2Xe q q w q e q e e e q q w e q q q q q q 8 8 q 8 q 8 8 q 8 8 q q 8 8 q $ 8 8 $ d w.p.Y.K.K.K.P.K.K.K.K.L.L.K.K.K.K.K.K.K.K.K.K.K.K.K.K.L.L.K.K.K.L.G.I.A.kXmXDXT.G.K.I.F.'.kXA.I.F.mXzXA.U.S.`.zXF.`.A.xXSXjXSXSXJXhX'.G.A.hXKXmXK.L.F.`.DXxXKXKXhXS.I.G.L.K.K.L.T.L.K.f 8 9 q 9 6 q q 9 9 9 q $ $ $ $ $ % UXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX5 q 7 d d v v s v @           O N . j Y X.tXM X.MXh 4 uX1X+ @.aX at .#.|.7XqXpX7X at .aX5X#.I 6X6X6X< 6X X  3.aXiXX.C rX5XS -.q w q w q q e w q w q e w w q q q q q q 8 8 $ q 8 8 8 8 8 8 - 8 8 8 $ q $ t B.W.!.m.Z.K.L.K.K.K.K.L.K.K.K.K.K.K.K.K.K.L.K.K.L.K.K.K.K.K.K.K.L.K.K.K.U.A.zXcXDX`.G.K.I.A.hXzXA.`.A.xXxXA.`.S.XXkXA.G.A.xXKXJXcXkX{.A.G.I.G.hXKXhXS.I.G.U.jXjXKXKX[.F.L.K.L.K.K.K.G.K.K.s q t 9 t 9 q u 9 9 9 8 $ $ $ $ = % UXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX% q 7 v v v v s d.d           ; | * o <Xo N eX; qX|.; uXP . 2.aX|.#.6XuX7X1.aX9XiX6X#.T 6XsX at .h 2.6X. @.tXiX^   0XCXtX_ % t q w w w q w e q e q q q q q q q q q q q q q 8 q 8 8 q 8 8 8 8 8 8 q - s Y.Y.Y.B.Y.K.K.K.K.L.K.K.K.L.K.K.K.L.K.L.K.K.K.K.K.K.K.K.I.L.K.L.K.K.L.G.I.A.kXzXDXT.G.K.I.A.xXxXA.`.A.zXmXS.U.A.cXzXI.zXcXJXJXSXL.A.K.I.L.L.S.jXKXjXS.I.K.G.K.A.xXSXS.I.K.K.K.K.K.K.L.K.K.7 9 7 q u 9 q 9 w w 6 8 $ $ $ $ $ & UXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq 7 s v v v d.d.d.d .       , J O   [   : <XX.eXL 3 fX,X; 2.aX#.^ 7X6X X#.7XaXpX5X|. at .<.iX at .V 2.7XX #.pXiX^   0XfXtX| k q w q q e w w q e q e q q q q q q q $ q q q - 8 8 q 8 8 8 8 q 8 8 - q $ d !.W.!.Y.Y.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.L.K.I.A.jXzXA.I.G.L.K.K.I.F.kX[.DXT.K.K.I.S.zXhXA.U.F.hXKXhX'.XXJXJXcXzXI.hXKXJX`.I.L.K.G.I.S.zXSXF.I.K.L.G.T.S.xXzXS.I.K.L.K.K.K.K.K.L.K.u 9 9 q 9 9 9 6 q q q 8 $ $ 5 $ = @ UXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXk 7 v d.v d.d.d.c d.v X X   o -.,   .     3XB ] ` 1 gXY   8XPX^ U #.sX XT 6X0XyXaX7X2.7XsX X^ 3.|.< 3.aXaXO.  2.gXMX< q w q w q w e q q e w q q q w q q q q q 8 q q $ 8 8 8 8 8 8 8 8 8 8 = q # d !.W.!.Y.P.K.L.K.K.K.L.K.K.K.K.L.K.K.K.L.K.L.K.K.K.K.L.F.DXcXS.I.K.L.K.K.K.S.kX{.JXT.K.K.L.F.kX'.G.U.A.hXKXLXKXJXSX'.K.S.S.[.JXLXL.K.K.K.K.I.S.jXjXA.I.K.K.G.I.A.cXxXA.I.K.L.K.K.K.K.K.K.Z.6 t 9 9 9 9 9 9 q 9 9 5 $ $ $ $ 8 @ UXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq t v d.d.d.d.d.d.d.d.v X . o Y *         _ o . |.[ fX_ B J nX^ 3 2.ZX X4 7XCXqX1XqX6X XsXiX^ 3. Xh T nXiX at .. <.aXMX; 5 w q w q w w q e q q w e q q q q q q q q $ q q q $ 8 q 8 8 8 8 8 8 8 q - d Y.!.Y.Y.Z.L.K.K.K.K.K.K.K.L.K.K.K.K.K.K.K.K.K.L.K.I.F.[.DXSXL.L.K.L.G.[.LXXXcX'.DXI.G.K.K.U.JXU.A.K.'.SXKXcXhXDXjXA.I.L.U.A.xXFXF.I.K.K.K.L.L.K.L.L.K.K.K.K.I.S.SXhXS.I.K.K.K.K.L.K.K.K.B.q 9 9 9 9 q 9 9 9 9 8 $ $ $ $ # 5 o UXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX@ e 5 v v v d.d.c c d.v v % . | m         ; o   : 1.ZXJ O < 7X X1 #.ZX XV @.ZXMX at .<.sXpX<.2. at .3.|.U T iXqX|.. #.eXMXh # t q w q q w w q w e q q w e q q q q q q 8 $ q 8 q q $ q 8 8 8 8 8 8 8 = d !.!.P.Y.P.L.K.K.K.K.K.L.K.K.K.K.K.K.K.K.L.K.K.L.G.I.A.zXcXSX`.G.K.I.F.kXKXkXGXI.SXjXS.`.F.I.SX`.jXSXcXSXLXG.F.KXkXF.I.F.I.S.cXxXA.I.G.L.K.K.K.L.K.K.L.K.K.K.I.G.cX'.F.L.K.K.K.L.K.K.P.K.B.q u 8 9 9 6 q q q 9 q 5 $ $ $ $ # UXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX@ k 5 t v v v v d.d.c v v v % J 2         2 o   X > fX3X] > tXiXh uXZX|.1 <.uXGX at .7X6XaXiX5X^ T #.^ T 6X5X X; @.qXiXO.% e 8 w w e q e e e q w w q q w q q q q q 8 - q 8 - q q 8 8 8 8 8 8 8 8 # d '.!.!.Y.Z.L.K.K.L.K.K.K.K.K.K.L.K.K.K.K.K.K.K.L.K.I.A.zXmXDX`.F.K.K.K.SXKXSXLXG.zXxXA.G.K.jXJXmXzX{.S.'.[.K.U.KXkXA.I.G.U.A.cXxXA.U.G.L.K.K.K.K.K.K.K.K.K.K.L.G.[.U.K.K.K.K.K.K.K.L.K.L.i.- u q 9 9 q q w 6 q 8 $ $ $ $ 5 # UXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXl - q q v v v v v d.v v c d ) J         ; .     S ,XJ <XH P qX^ 6XZXtX* 2.1.^ T 5XqX7X XaXqX at .I ^ @.iX7X X< 3.rXpX[ # u 8 e w q w w q w q q q w w q q q q q q 8 $ q $ q $ q 8 8 8 - 8 8 8 q # d !.Y.P.P.P.L.K.L.K.K.K.K.L.K.K.K.K.K.K.L.K.K.K.L.K.I.A.xXcXDX`.K.L.G.U.JXFXKXLXA.zXzX'.SXcXJXcXG.S.S.I.K.G.K.I.LX[.F.I.G.I.A.cXxXA.I.G.L.L.K.K.K.K.K.K.K.K.K.K.L.F.K.L.K.K.K.K.K.K.K.P.G.p.5 7 8 9 q 6 w 6 w 8 8 $ $ $ $ $ # UXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXl q w q q l v v v v d.d.v v h.y         , O     X 1.. b 3X3XiX^ 8XZXZX3 6X0X .T #.6XiX at .2.aXuXT |.T 6XuX X1 3.pXiXU 0 0 0 0 q w w q q e q e w q q q q q 8 8 8 8 q q q $ q q q 8 8 q 8 8 8 8 = d !.U.U.!.H.P.K.K.K.K.K.K.K.K.K.K.L.K.K.K.K.K.K.L.G.I.A.zXxXFX`.S.L.K.`.JXT.SXjXK.DXLXFXKXXXkXxXA.`.L.K.K.L.G.U.JXI.G.L.K.L.G.{.`.G.L.K.K.K.K.K.K.K.L.K.L.K.K.K.L.L.K.K.K.K.K.K.K.K.K.G.D.d.7 u 7 6 5 9 9 w 6 9 9 $ $ $ $ = & UXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq q q - q t v v c v c c v v c t .     , *     . k.  F C X.MX| K ZXeXO 5X2.K J T 6XsX^ <. X9X1X[ @. X<. X^ 3.iX6X^ k 6 0 9 w q q e w q q w q w q q q q 8 8 8 8 q $ q $ q $ 8 8 8 8 8 8 = 8 % d `.!.!.G.!.G.K.K.L.K.K.K.K.K.L.K.K.K.K.K.K.K.K.L.G.I.S.kXjXDX'.kXJXF.K.JX`.XXmXcXJXxXhXKXXXjXXXS.L.G.L.K.K.K.L.{.I.K.L.K.K.L.G.G.L.K.K.K.K.K.K.K.K.K.K.K.K.K.L.K.K.K.K.K.K.L.K.K.P.V.a.w.v s d d d 7 7 6 q q q 8 $ $ $ $ 8 % UXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq q q q q q l x v v v v x x v v t .   , ,       .   O ] > nXnXT KXrXK 1.9XC Y #.iXsXL <.7X2.5X7X2. X7XqX^ T aX9X XA 5 e 9 e q w q q e q q q w q q q q q q q 8 8 q $ q $ q 8 q 8 8 8 8 = 8 # d '.!.`.D.cXXXS.I.K.K.K.K.L.K.K.K.K.L.K.K.K.K.K.L.K.I.A.kX[.DXDXDXLX[.{.KXSXxX[.A.zXcXSXKXkXzX`.F.I.K.K.L.L.K.L.G.K.K.K.K.K.K.L.L.K.L.K.K.K.K.L.K.K.K.K.K.L.K.K.K.K.L.K.K.K.K.Z.Y.a.a.v w.v v d d d d d t 7 w 9 q $ $ $ $ 8 @ UXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX- e q q $ q $ t z v v v v c c c v d . O ;           , ..o  XiX+XAX7X  2.0XH ' 3.pXaXh <.6XuX<.iXuXqX7X6X^ 4 iX6X XR % e q q q q w q q e q w q e q q q q 8 q 8 8 q 8 q 8 8 8 8 8 8 8 8 - q $ 7 '.!.!.!.HXzXA.U.K.L.K.K.K.K.K.L.K.K.K.K.K.K.L.K.G.I.G.zX`.`.KXzXSXKXmXSXU.S.G.A.xXcXDXKXcXDX`.G.L.K.K.K.K.L.L.L.L.K.K.K.K.K.K.K.K.K.K.K.K.L.K.L.K.K.K.L.K.K.K.K.K.K.K.P.Z.B.a.d.s.d.s.v v s d v d d d d d d t 5 $ $ = $ 8 @ UXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXy q q q q q q d v x x v v c v x x 7 - ;             _ o 4XeX> PX<Xn 2.aX; K T 7XZXh O.6X5X2. X2.tX6X7X|.3. X6X at .A 5 w w q w q w w q w q q w q q q q q q 8 q 8 8 8 8 8 q 8 q 8 q q 8 8 q % d ].].].*XcXcXSXT.G.L.K.K.K.K.K.K.K.K.K.K.K.K.K.L.I.K.F.cX'.XXKXDXkXDX'.hXF.I.`.A.xXcXSXKXcXDXT.K.L.K.K.L.K.K.K.K.K.K.K.K.K.L.K.K.K.K.K.L.K.K.K.K.K.K.K.K.K.K.L.K.K.K.Y.B.f.w.a.a.d.s.v v v s v d d d d t t q 5 $ 5 $ = # 8 @ UXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXe $ q $ q q $ y v x x c v c v v x x e             3XX [ iX  ..8XG @.0X3 ,X3 6XMXh 4 6X<.2. X2. X7XrX X3.6X9X7X^ % e 9 e w q q q q q w q q q q q q q q q 8 8 8 8 8 8 8 8 q - 8 8 8 8 8 $ t ].].].>XhXJXKX[.G.L.K.K.K.K.K.K.L.K.K.K.L.I.I.G.S.L.[.LXDXxXKX{.F.DX[.kXF.K.I.S.zXDXhXLXFXkXK.K.K.K.K.K.K.L.K.L.K.K.K.L.K.L.K.K.K.K.K.K.K.L.K.K.K.K.K.K.K.K.L.P.B.a.a.a.a.g.d.w.w.d.d.s.v d v d d v 9 6 $ $ $ $ $ $ 8 % 8 UXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq $ q q q q q t v x x c x v c v x v %           j + < 4XX * ; o.9XuX* j T 6XZX< T 6X7X9X7X6X6X#.9XiX2. X6X Xj.% e 8 q q e 0 w e q q w q w w q q - q 8 q 8 8 q 8 8 8 8 8 8 8 8 8 = 8 $ s ].*X].hX*XKXKXLX'.G.L.K.L.K.K.K.K.L.I.I.G.S.F.'.zXDXJXLXU.A.[.I.`.DXhXkXA.I.L.F.jXKXjX[.KXjXS.I.K.K.K.K.K.K.K.K.L.K.K.K.K.K.K.K.L.K.K.K.K.K.L.K.L.K.L.K.G.V.B.a.f.a.g.g.a.w.s.v w.v s v d v d d 9 $ $ 5 $ 5 $ $ $ $ = $ 8 UXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXe q q q q q 8 t v c x x v v z v x v %             * ,X.     , @.gX3  . at .<.ZX^ V uXsX0XqX6X X#.2.qXfX7X7X at .o.# u 0 w q q e q 0 e q q w q q q 8 q q q q q q q 8 8 q 8 = 8 8 8 8 8 8 $ 7 ].].].*XjXLXLXKXkXA.I.K.L.K.K.L.L.K.F.S.T.jXSXSXzXJXcXKXI.I.G.F.`.SXcXzXA.I.L.S.jXKXI.F.KXkXA.I.G.L.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.L.K.L.K.K.K.K.K.D.n.f.f.a.g.g.a.g.p.d.w.d.v w.w.v f d d 9 $ $ $ 5 $ $ $ $ $ $ $ $ $ $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX% q q $ q $ q q e x v v x x z c v x v 9             ' *     & P AX,X` K <.LXiX1 7XpX6X6X7X XU 2.iX0XpXaX at .) 5 9 9 q w q q w q e q w q w q q q q $ - q q $ 8 8 8 8 q q 8 8 8 8 8 8 $ 7 !.].].*X].SXxXSXxXA.I.G.L.I.L.F.G.I.[.cXDXzX{.F.G.JXJXSXG.K.L.K.U.JXJXzXA.I.L.G.{.JXI.U.KXXXS.I.K.K.K.K.K.K.K.K.K.L.L.K.K.K.K.L.K.K.K.K.P.P.K.K.Y.B.f.f.f.R.f.p.g.g.w.d.d.d.w.w.v f v t 9 $ $ 5 5 $ $ $ $ 8 $ $ $ $ $ $ % UXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX& q $ q q q q $ e v x x v c v x v x v 9           J .     , T fX; 1X1.<.AX> 3 9XpXpX6X6XiX^ #.qX5X|.pXuX|.% e 8 e q w q q q q q q q q q q $ q q 8 q - q 8 8 - 8 = 8 8 8 8 8 8 8 5 t ].].*X].!.[.L.jXSXF.U.L.G.A.K.'.SXJXcX[.I.A.F.L.`.LXLXU.G.L.K.K.I.LXKXjXS.I.K.G.U.[.F.U.JXT.K.L.K.K.L.K.K.L.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.P.B.B.f.f.f.f.E.f.g.g.g.g.g.a.w.d.d.w.f u 6 $ $ $ $ 5 $ 5 5 $ = $ $ = $ $ $ $ # UXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX@ q q q - q q q w x x x z z z x v x c t                   ; AXo G 3XuX8X, <.6XfXpXsXiX X^ 3.qX2. at .@.0XqX$ e q w q q q e w q w q q q q q q 8 q q = q 8 q 8 q = q 8 8 8 8 8 8 8 q t ].].].].!.F.G.{.KXjXA.G.`.hXSXcXKXzXA.G.K.I.I.G.T.KXLXI.K.K.K.K.I.JXKX[.G.L.K.K.K.G.K.U.JXU.K.K.K.K.K.K.K.K.K.L.K.K.K.K.K.L.K.K.L.K.L.Y.B.B.f.E.R.R.E.E.g.g.g.g.a.d.d.d.g.d.v t $ $ 5 5 5 $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXe q q q $ q 8 w x z v z c x z z v l t o   .         O -.5X. : V 9XVXo , 2.6XiXsXaX Xh T iXuX at .I <.|.0 0 9 w q q q q q q q q 0 q q q 8 q q 8 q q $ q $ q $ 8 8 8 = 8 8 8 8 = u ].].].].!.I.U.A.SXzX[.FXSXzX'.F.LXkXF.U.K.K.K.L.xXKXLXI.K.L.K.L.G.DXzXG.L.K.L.K.K.L.K.L.I.K.L.K.K.K.K.K.K.K.K.K.L.K.K.K.P.K.K.P.K.Z.B.f.f.R.E.R.R.E.g./.g.g.w.g.a.d.d.w.v 6 $ $ $ $ 5 $ $ 5 $ $ $ 5 8 $ $ $ $ $ $ $ $ $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq q q q $ q 8 w x z x v z v c z v x t o           . O -.o M ' 3.fXH o.5X5XiX5XeXiXh 4 iX6X X[ 2.|.0 8 w q q r q q w e q q w q q q q q 8 q 8 q $ $ q - q 8 = 8 8 0 8 = q = t ].].].*X!.A.L.'.DXLXzX[.I.S.G.I.KXxXS.L.G.L.K.I.LXKXLXI.K.K.K.L.G.'.I.G.L.K.K.K.K.K.K.L.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.P.Y.a.f.f.R.R.f.R.f.E.f./.g.g.g.a.d.d.d.v 7 # # 5 8 $ $ $ $ $ $ $ 5 $ $ $ $ $ $ $ $ $ $ $ % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXw - $ q 8 q 8 q z c z x z z z z x x t @           ` H   1 & 0X8X. 3X0XfXiX2.7X7XU 4 sX7X|.I 1. Xy 5 e q q q q w q e q w w q q q 8 8 q $ $ q q q 8 q 8 = 0 8 8 8 8 8 8 $ t ].*X*X].].hXDXcXzXcXA.[.JXI.I.I.KXSXL.I.L.U.S.[.hXkXJXG.I.K.K.K.K.G.K.L.L.K.K.K.K.L.K.K.L.K.K.K.K.L.L.K.K.K.K.L.K.K.L.K.V.B.f.a.R.R.R.a.E.R.g.g.g.E.E.g.a.g.g.d.t 5 % $ 8 $ $ $ $ $ $ $ 5 8 = = $ = $ $ $ $ $ $ $ $ 8 UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq q 8 q 8 q q q x z x z c c c z x v x @         * _ . ; k.<.0X  , aXZXiX at .8XiXh 1 uXpX at .L @.qXA # w q q e w q q q q 0 q w q q 8 q = q q q $ 8 8 8 8 8 8 8 8 = = 8 8 $ 7 ].*X*X].jXzX'.S.K.DXhXKXLXI.G.U.JXLXK.F.F.L.A.cXhXjXSXA.I.G.L.K.K.L.K.K.K.K.K.L.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.L.Z.Y.R.f.f.R.E.R.R.E.W.f.l.g././.g.g.E.d.v t - % $ 8 5 $ $ $ $ 5 5 5 $ $ $ $ = $ $ $ $ $ $ $ $ $ $ 8 UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq q 8 8 8 q $ q x z v c z x z z x x d %       b o.  + K b S   o 5XAXGX3.aXMX1 + 0X8X XV 3. Xh 5 t q q w q q w q w q q q q q 8 8 q 8 8 q = 8 q 8 8 0 = = = 5 8 = 8 # t ].*X].].!.A.G.K.T.JXJXKXLX`.S.'.zXDXFXU.[.XXA.DXI.hXxXA.I.K.L.K.K.K.K.K.L.K.K.K.K.L.L.K.K.K.K.K.K.K.K.K.K.K.L.Y.B.f.R.R.f.B.R.E.R.f.f./././.E.g.g.g.d.f 9 $ % 5 8 $ = 5 = $ $ $ = $ $ $ = $ $ $ $ $ $ $ # = $ $ $ = UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq q q 8 q q q q l z x z z c z z z x c 6 .   ; @.o . ' 4XX     K ZXfX3XZXMX; O uX3X Xh #. XR 8 0 q q q q w q 0 q 0 w q q q q q q q q = q 8 8 $ 8 8 8 0 = 8 8 8 8 $ 9 ].].*X].].L.I.K.I.LXKXJXSXKXS.jXkXL.KXhXJXxXF.LXI.L.L.K.L.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.L.K.L.K.K.K.P.Y.B.f.f.W.E.R.f.R.R.R.E.R.g.g.g.g.g.g.d.d 6 $ $ 5 8 $ $ 5 $ $ $ 5 5 $ $ $ $ $ $ $ $ $ $ $ $ $ = $ $ $ $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq - q $ q q 8 q x Q z z z c z z c x x 7 .   ` . o F M .     J KX3 <XAXMXh   9XpX X; 4 |.h 5 e 8 w q q w 0 q q q q q q q q q 8 8 8 q = - q q $ 8 8 8 8 = 5 8 8 8 5 ).].].].!.G.K.L.G.cXKXJXT.DXK.SXxXA.[.cXSXzXL.JXI.K.L.K.K.K.K.K.L.K.K.K.K.K.K.K.K.K.K.K.K.P.K.K.K.L.J.B.B.f.f.B.R.R.E.E.f.f.E.f.E.E.g./.g.g.d.7 $ % $ 5 $ $ = $ $ $ 5 $ $ $ = $ $ $ $ $ $ $ = $ $ $ $ $ $ = % $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq q $ q 8 8 q $ l c x Q x z z z z x x 9 X eX. . >           T &   4XGX1   9XZX X< P 7XA 8 w 0 q q w q q q q q q q q q 8 q 8 8 8 = q q $ q q 8 8 = 8 5 8 = 8 8 6 B.].].!.L.L.K.I.A.hXzXXXI.jXSXSXU.K.T.LXxXzX`.JXI.K.L.K.K.K.L.K.K.K.L.K.K.K.K.L.K.K.K.K.L.K.K.K.Y.B.f.f.f.R.R.E.W.R.R.E.E.E.E.g.l./.g.g.d.w % - = 8 $ $ $ $ $ 5 8 $ $ $ $ 5 $ $ $ $ $ $ $ $ $ $ $ $ = $ # # = UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq 8 8 q 8 q q q l z z z z Q z Q c z z q ; . . j                   1.eX  2.AX X< I 7Xe e q q q q q q q q 0 q q q q q 8 q 8 q 8 q $ q q 8 $ 8 8 8 8 8 8 8 5 8 5 R.!.!.!.L.K.K.K.K.L.K.F.S.hXKX[.S.L.K.KXxXSX'.xXK.L.K.K.K.K.K.L.K.K.K.K.L.K.K.K.P.L.K.K.K.P.Y.a.f.f.R.E.f.W.).R.R.E.E.R./.g.l./.f.d.f # # $ 8 5 $ $ $ $ $ $ $ 5 $ $ 5 = $ $ $ $ $ $ $ $ $ $ = # $ $ = % $ $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq q = q q $ q $ y z z z z Q c z z x z d o   j                 .   <XeXfXMXiXX 3 <.% e 9 e 0 q q 0 w q e q q q q q q q 8 $ q $ q 8 $ - q 8 8 = 8 8 8 8 = 8 0 R.!.W.!.G.K.K.K.K.L.K.U.S.kXKXL.I.K.I.LXXXJXLXhXF.L.K.L.K.K.K.K.K.K.K.L.K.K.K.L.K.K.L.Y.B.f.f.R.R.B.R.E.).E.R.f.f.E.R.g.g./.E.d.d $ # 5 8 $ $ $ $ 5 $ $ q $ $ $ 5 $ $ $ $ $ $ $ $ = $ $ = # $ $ $ $ # = % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq $ q $ q $ q q t Q Q Q z Q Q z z z Q x % N                       <XeXpXMXo 1 1.@ e 5 w 0 w q q q q q q q q q q $ q 8 q q $ 8 8 8 q q 8 8 8 8 8 8 5 8 8 9 f.W.W.P.G.K.K.K.K.L.K.K.G.`.jXL.K.K.L.XX`.JXKXkXS.I.K.K.K.K.K.K.K.K.K.K.K.K.K.L.L.Y.B.f.R.R.R.E.R.R.R.R.E.E.R.E./.g.f./.g.d.9 % % 8 8 $ $ $ $ 5 $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ % # # $ @ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq q $ q q q q q t z c Q Q z z z c z Q z k                       .   4XGX  1 5X% e q q q q q q q q e q q q q q q 8 8 q $ q 8 8 8 8 8 = 8 8 8 = 8 8 5 8 8 a.W.m.Y.P.L.K.K.K.K.K.K.L.K.S.K.L.K.L.S.U.JXKXhXS.I.G.L.K.L.L.K.K.K.K.L.K.K.P.B.R.f.f.R.W.W.R.R.R.E./.).E.E.R.g.f.E.g.v 8 - $ 0 8 = $ $ $ = $ $ 8 $ $ 8 $ $ 5 = $ $ $ $ 8 $ $ $ $ $ $ $ $ $ % = = = UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq $ $ q q $ q q t z z z Q Q c z Q c x x %                           ,XtX..<.= q q q w q e 0 8 q q q q q q q q - q 8 q 8 q 8 8 8 8 8 8 8 8 = 8 5 8 = 8 i.b.B.m.Z.L.K.K.K.K.K.K.L.L.L.L.K.K.K.I.L.cXKXhXS.I.K.K.K.K.K.K.K.K.L.G.Y.B.f.R.R.R.E.R.R.R./.R.E.E.R.f.f.E.f.E.w.d = - = = = $ $ = = 5 = 8 5 $ 5 $ 8 $ $ $ $ $ $ $ $ $ $ $ $ # = = = $ # = = # UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq q q $ q 8 q - e c Q Q Q Q Q Q Q c z z 9                         ; 3XtX5Xr q q 0 q q q 0 w q q q q q q q q 8 8 q 8 8 8 8 q q 8 8 8 8 = 8 8 8 = 8 5 a.B.B.Y.Z.L.K.L.K.K.K.K.K.K.K.K.K.K.K.K.K.L.KXhXS.I.K.K.K.K.K.K.K.P.B.R.R./.R.R.R.R./.R.R.R.f.E./.f.g./.R.l.d.t = % = 8 $ $ = 8 $ $ $ $ $ $ $ $ 5 $ $ $ $ $ $ $ $ $ $ $ $ $ $ = $ # % # = % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq q $ q 8 8 q q w Q z z Q c z Q Q z z z e                           ; ZXY % w 8 q q q q q 9 q q q q q q q q $ 8 q 8 8 8 q $ q = 8 8 8 5 8 8 8 5 q w.B.B.b.Z.K.K.K.L.K.K.K.K.L.K.K.K.K.K.K.I.F.DXzXA.I.K.L.K.K.K.Y.B.E.E./.R.W.W./.E.R.E.R.R./.f./.f.)./.g.f 0 - % 5 8 $ $ $ $ $ 5 $ $ $ 5 8 $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ # # $ = % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX$ q 8 q 8 8 q q w z Q Q Q Q z Q Q Q x z t .                     .   CX5X@ t 5 w w q q q q q q q q $ $ q q q q $ 8 8 q - q $ 8 8 8 8 8 = $ 8 5 q s.a.b.v.Z.K.L.K.K.K.K.K.K.K.K.K.K.K.K.K.I.G.[.`.G.L.K.P.P.Y.W.R././.W./.E.).R.E.E.R.E.E.f.E.E.E.g.d.d - % = 8 = = $ $ $ $ = $ $ 5 5 $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ # $ = UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq q = q q 8 q $ e z Q Q Q Q z Q Q z z x k o . .               .   1.rX@ k 5 q q q q q q q q q q 5 q 5 q $ q $ q $ q q q 8 8 8 - q $ 5 $ 8 $ q s.a.i.c.Z.K.K.K.K.K.K.L.K.K.K.K.K.K.L.K.K.L.F.G.L.K.P.B.R.R.R.W.W.E.E.E./.E.E./.W.).f.f.E.E.g.d.u $ % = 8 $ $ = = 8 $ $ $ 5 = $ $ $ 5 $ $ 5 $ $ $ $ $ $ $ $ $ $ $ = # # # $ $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX= q 8 8 8 8 q q q z Q z Q Q Q z Q z z z l @                     k @X@ e 5 q q q q q q q q q q q q q q q $ q $ q q $ - q q $ 8 8 $ q 8 $ $ q s.p.i.c.Z.L.K.K.K.L.K.K.K.K.K.L.K.K.K.K.L.K.K.P.Y.R././.E.W.R./././.)./.R.E.E./.f.E././.l.f 9 # $ 8 8 = = 5 = $ $ $ $ 5 $ $ $ 5 $ 5 $ $ $ $ $ $ $ $ $ $ $ $ $ = $ % # = $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX= q q 8 q $ $ q q x Q Q Q Q z Q Q z z l x &                     8 q q q q q q q q q q q q q q q q $ q q q 8 8 8 8 8 8 8 8 8 8 q $ $ 8 $ $ w.p.a.p.V.G.K.L.K.K.K.K.K.K.K.K.L.K.L.I.K.P.W.W.E.R.(.W.E.E.R./.).)./.E.E./.f.E./.g.d.f 8 % $ 8 8 = = = $ = $ 8 = $ $ $ 5 5 5 $ $ $ $ $ $ $ 8 $ $ $ $ $ $ $ $ % # $ = UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX$ q q 8 q q 8 8 q z Q Q z Q z Q z z z l x %                   @ q $ q q q q q q q q q q q q q $ q $ - q 8 8 8 8 8 8 8 8 8 8 8 $ q $ q - s.s.p.i.V.!.K.K.K.K.L.K.K.K.K.K.K.K.L.Y.W././.W.E.W.(.W.).E.).).W././.g././././.d.d # # 8 8 5 $ $ 5 = 8 $ $ = 8 $ 5 5 $ $ $ $ = $ $ $ = $ = $ $ $ $ $ $ $ $ $ $ $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX8 q $ $ q q q $ 8 z Q z Q Q Q z Q Q x x z % .               @ q q q q q q q q q q q q q q q $ q q $ q 8 q - q 8 8 8 8 5 8 $ = = 8 $ 8 s s.e.a.Y.K.K.K.K.K.K.K.L.K.K.K.L.W.).).).].).R.)./.W./././.E.E.).f././.)./.v w % # = 8 = = = 5 = = $ $ $ 5 = $ $ $ = $ $ $ $ $ $ $ $ $ $ # $ $ $ $ % % $ $ $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX$ q q q $ q q q q x Q z Q Q Q z Q z x l x q . .         . @ q q q q q q q q q q q q q q $ q q $ q 8 8 8 q 8 8 8 5 8 = 5 8 8 = = 8 $ s s.s.e.n.P.K.K.K.L.K.L.P.P.P.W.E.E.).].).).].W.W.).)./././.R.E.E.f.).d.d 8 - $ 8 8 = = = = = = = 5 8 = $ $ 8 $ 5 $ $ $ $ $ $ $ $ = $ $ = = $ $ $ # $ $ % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX$ q $ 8 8 $ q q q l Q Q Q Q Q Q Q z Q l l t .         . o q q q q q q q q q 5 q q q q q q q $ 8 q $ q q $ 8 q 8 8 8 8 8 = $ q 5 5 s s.s.s.M.K.L.K.K.K.K.I.Y.W.E.E.).].).].]./.).)././.).)././.E.).g.d.d $ - = 8 8 $ = = 8 = $ # 5 = $ $ = 5 = 5 $ $ $ $ $ $ $ $ $ $ $ $ $ $ = # # $ = % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX- q 8 q 8 8 - q q l Q Q Q Q Q z z z z x x y .       . X q q q q q q q q q q q 5 q q q $ q q 8 8 q 8 q $ $ q 8 8 8 $ 8 8 = $ 5 $ s s s s.b.P.K.K.K.P.B.R.E./.W.).).].).).)./.).).E./././.E./.g.v t $ $ $ 8 = 5 $ = 8 = = $ 5 = 5 = 5 $ 5 $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ % % # - UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX- q q 8 8 q - q $ y Q Q Q Q z Q z Q Q l z y @       o 9 q q q q q q q q q q q q q 5 q - q q 5 q - $ $ q $ 8 8 = = 8 5 = = 8 8 7 s s s c.G.Z.Q.W.R./.W./.).)./.].E.).).).).)./.E.).)./.d.f 0 # = 8 5 = = = = = 8 $ $ = = = = = = $ 5 $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ # $ = UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX$ q 8 8 8 q q q $ y z Q z Q Q Q Q z D l x y @     . q q q q q q q q q q q q 5 q q q q 5 5 q 5 q q q $ q 8 8 8 8 = 5 8 8 = 8 s s a s s.Y.R.f.E.W.).).E.).).).).).)./.).).)././.l.d.d $ - = 5 = = = 5 5 $ 8 = = = $ 5 $ 5 5 $ 5 $ $ $ $ $ $ = $ $ $ $ $ = # $ % # $ $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX% q 8 q 8 q 8 q $ e Q Q Q z Q Q Q z z z l x %   . 9 q q q q q q q q q q q q q 5 q 5 q 5 q q 5 q - q q 8 8 8 8 = 8 = 5 8 8 6 s s 7 v E.R.E.W././.W.).).W.).).).)./.).).).l.}.u $ - 5 8 = = = = = = = = $ = 8 $ 5 $ = $ $ 5 $ $ $ = $ $ $ $ $ $ $ = $ # # $ 8 = UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX% q 8 8 8 $ q q q t Q Q Q Q Q Q Q Q Q z x x % . q q q q q q q q q q q q - q q q q - q 5 q 5 $ q $ $ 8 8 = = 5 = 8 = = 5 6 s s s f./.E./.).).).)./.).).).).)././.)./.f 8 & # q 8 $ $ 5 = 8 8 # = 5 $ 5 $ $ $ 5 $ $ 5 $ $ $ $ $ $ $ $ $ $ $ = $ # % = # % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX% q 8 q q q $ q $ e Q Q z Q Q Q Q Q z z z l 5 = q q q q q q q q q q q q q q 5 5 q - q $ q $ $ $ q 8 8 8 5 = 8 = 8 8 # 7 g 7 w././.E./.).)./.).)./.)./.).).g.d.d $ # = = 0 = = = = = 5 5 = = = $ = $ $ $ 5 $ $ $ $ $ $ $ $ $ $ $ $ $ $ # # % $ 8 % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX% q $ q q q q q q e Q Q Q ! Q Q Q z Q z x x q q q q q q q q q q q q q 5 q q q q 5 q q q q q $ q 8 8 8 = 8 = 5 = 8 8 6 s g d.).g./.)./.)./.)./.g.).).g.d.t - % $ 9 = = = = = = = = # 8 = 5 = $ 5 $ 5 $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ # $ = # UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX$ q $ - q q $ q - e Q Q Q Q Q Q Q Q z z x w q q 5 q q q q q q q q q q 5 q - 5 5 - q $ $ q - $ 8 8 8 8 = 5 = 8 8 8 6 7 s /.)./.).E./.)././.).).l.d q % $ q q $ # = = = = = = = 5 5 = = 8 $ 5 $ 5 $ = $ $ $ $ $ $ $ = = = # $ # # $ $ % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX& q q 5 $ q q q q q z ! z Q ! ~ z Q z z e 8 q q q q q 5 q q q q q q q 5 q q q q 5 q q $ q q = 8 5 = 8 = 8 = 8 = 7 g w.g././././././.)./.d.d $ - $ q $ $ $ $ = = 5 = = = = 5 = # = = $ $ $ $ $ $ $ $ $ $ = $ $ $ # $ $ # # = $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX& 9 8 8 8 8 8 q 8 q z Q ! Q ! Q ! Q Q e q q q q q q q q q q q q q q 8 8 q q 8 8 8 8 8 = 0 8 = 5 8 8 = 8 8 5 5 7 s d././.g.g.).)./.v t $ $ = 5 8 # 8 5 = = = = = = = 5 = $ 5 $ $ 5 $ $ $ 5 $ $ $ $ $ $ $ $ # = $ # # $ = UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX& q 8 8 8 q 8 q $ q z Q Q Q ! Q Q Q e $ q - q q q q q q q q $ q q 8 8 8 8 8 8 8 8 = 8 8 8 8 8 = = 8 $ = 5 5 7 v /./.l./.).d.v w $ - $ 8 5 = 8 = = = 5 = = = = = $ = = = 5 $ $ $ $ 5 = $ $ $ $ $ = $ $ = = # # $ $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX@ q 8 q $ q 8 $ q 8 x ! Q Q Q Q Q y q q q q q q q q q q q q 8 q 8 q q 8 8 0 8 8 8 8 = 8 8 5 5 5 = = 8 $ q 6 d.)./.l.}.x $ % 8 q 8 $ $ 5 = = = = = = = 5 5 = = 5 = 5 $ $ $ 5 $ $ $ $ $ $ $ = # $ = # % # = # UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXw = q 8 q q 8 q 8 l ! Q Q Q Q y - q q q q q q q q q q q q $ q - q 8 8 8 8 8 8 8 8 8 8 8 5 = 8 8 $ 8 % t /.l.c t # - $ q $ $ 8 $ 8 = 5 = = = 5 = = = # = = $ $ $ $ $ $ $ = $ $ $ $ = # = $ $ % $ $ $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq q q 8 q 8 8 8 5 x Q Q Q Q l q q q q 5 q q q q q q 8 q q - q $ q 8 8 8 0 = = 8 = 8 5 5 $ 8 8 = q % v v 5 # # 8 8 = = = 8 = = = 8 = = = = 5 = = = = 5 5 = = $ 5 $ = $ $ $ # = $ $ $ # # $ 8 # UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq 8 8 $ q q 8 q q l Q Q Q y $ q q q q q 5 q q 8 q 8 q q 8 q q 8 8 8 8 8 8 8 8 8 5 8 = 8 = = 8 5 8 $ $ = 9 0 5 = 8 8 $ $ $ = = = = $ 8 = $ $ 5 5 # # 5 5 # $ = $ $ $ $ $ $ = $ $ # $ = % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq q q - q q 5 q 5 y Q Q l 5 q q q q q q q 5 q 8 q q 8 q 8 8 q 8 8 q 8 8 8 8 8 = = 8 = 8 = 8 8 $ 8 q = = = = = 8 = 8 $ = = = $ = = = $ 5 $ $ 5 # 5 = = = = = $ $ # = $ = # # $ $ % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq = q q = q 5 q q l Q y 5 q - q $ q q q q q q q 8 q 8 q q 8 8 8 8 = 8 8 8 8 5 = 5 8 $ 8 8 = 8 = $ = = = = = = 8 = 5 = = = 5 = $ 5 $ $ $ $ = = 5 5 # 8 # = $ $ $ $ # # $ # UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq 8 8 8 q q $ q - t x $ q q q q q q q q $ q q q $ q $ q 8 8 8 8 = 8 8 8 8 5 8 = = = 8 = = 5 = = 8 = 5 = $ = = = = $ 5 = $ = 5 $ $ $ $ $ $ $ $ $ $ $ $ $ # = # # $ $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq 8 8 - q 5 q q 8 q q q q $ q q q $ q q q q q q $ q $ 8 = 8 0 8 8 = 8 8 8 8 = 5 8 = = 8 = 5 = = = = = 5 = 5 = = = = $ 5 $ = $ $ $ $ = $ = $ = $ = # $ # # $ $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq 8 5 q 5 q 8 8 q $ q q q q q $ q q q q q $ $ q q $ 8 0 8 8 8 8 = 8 = 8 = 8 = = 8 = = 5 = 5 8 = = = = = = $ $ 5 $ $ $ 5 $ $ $ $ $ $ $ $ $ # $ # # # $ $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX8 q q - q q - q q q $ q q q q 5 5 q $ q q q q $ q 8 8 0 8 8 8 8 8 8 = 5 = 5 = 8 = 8 = 5 8 = = = = $ = 5 $ $ 5 = $ $ $ $ $ $ $ $ $ $ $ = = % $ = = UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX5 q q q - q $ q $ q q - q q q q q 8 q 8 q q $ q 8 8 8 8 = 5 0 = 5 = = = 8 = = = 5 = = = $ = 8 $ 5 $ 5 $ $ 5 $ $ $ $ $ $ $ $ = $ $ # % = $ % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX5 q q q 8 q q q $ q $ q q 5 q q q 8 8 $ q q $ 8 8 8 8 = 5 5 = 5 5 = 0 = 8 = 0 = 5 = = 8 = = = 5 $ $ $ = $ $ $ $ $ $ $ # $ = # % = = % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq q 8 q 8 $ $ q q q $ q q q $ q 8 q $ q $ q 8 8 0 8 0 5 8 = 8 = = = 5 = = 5 5 = 8 $ $ = 5 $ $ 5 $ $ $ $ $ $ $ $ $ = = $ % $ $ % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq q $ 8 q q $ $ q q $ $ q - q 8 8 q 8 q $ 8 8 5 8 8 5 = 8 5 = 8 5 = 5 = = = = = $ 5 $ $ $ $ = $ $ $ $ $ $ $ = # # # # = UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX8 8 8 8 = q = q 8 q 8 q q $ q $ q 8 8 8 q $ 8 8 8 8 $ 8 8 = 8 = 8 = 5 = = 5 5 = $ $ $ $ $ $ $ = $ $ $ $ $ # # $ $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX8 8 8 q = q 8 8 8 q 8 q q $ q 8 8 8 8 $ q 8 q 8 = = = = 8 = 5 = = = 8 = # # = $ $ = $ $ $ $ = # $ $ # # $ $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX$ 0 $ q 8 q 8 q 8 q $ q q q 8 q 8 8 q - $ $ = 8 = 8 5 = 5 = 8 = = 5 = 8 8 5 $ $ $ = $ $ = # $ % % = $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX$ q = 8 8 8 8 8 8 q $ q $ 8 8 8 $ $ q q $ 8 $ 8 = = 8 = 5 = = 5 # = 5 # = $ $ $ # $ $ = # # = $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX$ 8 8 8 8 8 8 8 8 8 q 8 8 8 8 8 = 8 8 $ = 8 8 = = 5 = $ = $ $ $ = # $ $ $ $ = $ % % # = % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX$ 8 8 8 8 8 8 q 8 8 8 8 8 $ 8 8 = 8 8 8 $ = 8 8 = = $ $ $ $ $ $ = $ = = # $ # = = % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX- q 5 8 8 8 $ 8 8 8 8 8 - 8 $ 8 8 = 5 5 = = 8 = = 5 $ $ $ $ $ $ # = # # # = % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX% 0 $ 8 8 8 8 8 8 8 8 8 8 8 = = = = $ $ 5 $ $ $ $ $ $ $ $ $ $ # # = # UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX& 8 $ 5 = 8 5 5 = 8 5 5 = = 5 $ $ 5 $ $ 5 = $ $ $ # = $ = % = # UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX@ 8 $ 8 = = 5 5 = $ 5 $ $ $ 5 $ $ $ $ $ $ $ $ = # # # = $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX0 = = 5 5 8 = 5 5 8 5 8 5 $ 5 8 = 5 5 = = = = 5 $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX& # % % % % % % % % % # % % % % % & % % % @ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXe e e e e u w 9 0 9 e 9 w e e e e UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXo o o o @ o @ o o . UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXU [...]
+};
diff --git a/src/CubicSDRDefs.h b/src/CubicSDRDefs.h
new file mode 100644
index 0000000..210126c
--- /dev/null
+++ b/src/CubicSDRDefs.h
@@ -0,0 +1,40 @@
+#pragma once
+
+#define CUBICSDR_TITLE "CubicSDR v" CUBICSDR_VERSION " by Charles J. Cliffe (@ccliffe)  ::  www.cubicsdr.com"
+
+#ifndef __BYTE_ORDER
+    #ifdef _WIN32
+        #define ATTRIBUTE
+        #define __LITTLE_ENDIAN 1234
+        #define __BIG_ENDIAN    4321
+        #define __PDP_ENDIAN    3412
+        #define __BYTE_ORDER __LITTLE_ENDIAN
+    #else
+        #ifdef __APPLE__
+            #include <machine/endian.h>
+        #else
+            #include <endian.h>
+        #endif
+    #endif
+#endif
+
+const char filePathSeparator =
+#ifdef _WIN32
+                            '\\';
+#else
+                            '/';
+#endif
+
+#define BUF_SIZE (16384*6)
+
+#define DEFAULT_SAMPLE_RATE 2500000
+#define DEFAULT_FFT_SIZE 2048
+
+#define DEFAULT_DEMOD_TYPE "FM"
+#define DEFAULT_DEMOD_BW 200000
+
+#define DEFAULT_WATERFALL_LPS 30
+
+#define CHANNELIZER_RATE_MAX 500000
+
+
diff --git a/src/DemodLabelDialog.cpp b/src/DemodLabelDialog.cpp
new file mode 100644
index 0000000..d5a7e50
--- /dev/null
+++ b/src/DemodLabelDialog.cpp
@@ -0,0 +1,97 @@
+#include "DemodLabelDialog.h"
+
+#include "wx/clipbrd.h"
+#include <sstream>
+#include "CubicSDR.h"
+
+wxBEGIN_EVENT_TABLE(DemodLabelDialog, wxDialog)
+EVT_CHAR_HOOK(DemodLabelDialog::OnChar)
+EVT_SHOW(DemodLabelDialog::OnShow)
+wxEND_EVENT_TABLE()
+
+DemodLabelDialog::DemodLabelDialog(wxWindow * parent, wxWindowID id, const wxString & title, 
+        DemodulatorInstance *demod, const wxPoint & position,
+        const wxSize & size, long style) :
+        wxDialog(parent, id, title, position, size, style) {
+
+    wxString labelStr;
+
+    //by construction, is allways != nullptr
+    activeDemod = demod;
+	
+    labelStr = activeDemod->getDemodulatorUserLabel();
+
+    if (labelStr.empty()) {
+        //propose a default value...
+        labelStr = activeDemod->getDemodulatorType();
+    }
+             
+                           
+    dialogText = new wxTextCtrl(this, wxID_LABEL_INPUT, labelStr, wxPoint(6, 1), wxSize(size.GetWidth() - 20, size.GetHeight() - 70),
+    wxTE_PROCESS_ENTER);
+    dialogText->SetFont(wxFont(15, wxFONTFAMILY_TELETYPE, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD));
+
+    Centre();
+
+    dialogText->SetValue(labelStr);     
+    dialogText->SetSelection(-1, -1);   
+}
+
+
+void DemodLabelDialog::OnChar(wxKeyEvent& event) {
+    int c = event.GetKeyCode();
+
+    //we support 16 bit strings for user labels internally.
+    wxString strValue = dialogText->GetValue();
+
+    switch (c) {
+    case WXK_RETURN:
+    case WXK_NUMPAD_ENTER:
+
+        //No need to display the demodulator type twice if the user do not change the default value...
+        //when comparing getDemodulatorType() std::string, take care of "upgrading" it to wxString which will 
+        //try to its best... 
+        if (strValue != wxString(activeDemod->getDemodulatorType())) {
+            activeDemod->setDemodulatorUserLabel(strValue.ToStdWstring());
+        }
+        else {
+            activeDemod->setDemodulatorUserLabel(L"");
+        }
+
+        Close();
+        break;
+    case WXK_ESCAPE:
+        Close();
+        break;
+    }
+
+    if (event.ControlDown() && c == 'V') {
+        // Alter clipboard contents to remove unwanted chars
+        wxTheClipboard->Open();
+        wxTextDataObject data;
+        wxTheClipboard->GetData(data);
+        std::wstring clipText = data.GetText().ToStdWstring();
+        wxTheClipboard->SetData(new wxTextDataObject(clipText));
+        wxTheClipboard->Close();
+        event.Skip();
+    }
+    else if (c == WXK_RIGHT || c == WXK_LEFT || event.ControlDown()) {
+        event.Skip();
+
+    }
+    else {
+#ifdef __linux__
+        dialogText->OnChar(event);
+        event.Skip();
+#else
+        event.DoAllowNextEvent();
+#endif
+    }
+}
+
+void DemodLabelDialog::OnShow(wxShowEvent &event) {
+		
+    dialogText->SetFocus();
+    dialogText->SetSelection(-1, -1);
+	event.Skip();
+}
diff --git a/src/DemodLabelDialog.h b/src/DemodLabelDialog.h
new file mode 100644
index 0000000..a10d34b
--- /dev/null
+++ b/src/DemodLabelDialog.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include "wx/dialog.h"
+#include "wx/textctrl.h"
+#include "wx/string.h"
+#include "wx/button.h"
+#include "DemodulatorInstance.h"
+
+#define wxID_LABEL_INPUT 3002
+
+class DemodLabelDialog : public wxDialog
+{
+public:
+  
+    DemodLabelDialog( wxWindow * parent, wxWindowID id, const wxString & title,
+                  DemodulatorInstance *demod = NULL,
+                  const wxPoint & pos = wxDefaultPosition,
+                  const wxSize & size = wxDefaultSize,
+                  long style = wxDEFAULT_DIALOG_STYLE);
+
+    wxTextCtrl * dialogText;
+
+private:
+    DemodulatorInstance *activeDemod = nullptr;
+    void OnEnter ( wxCommandEvent &event );
+    void OnChar ( wxKeyEvent &event );
+	void OnShow(wxShowEvent &event);
+    DECLARE_EVENT_TABLE()
+};
diff --git a/src/FrequencyDialog.cpp b/src/FrequencyDialog.cpp
new file mode 100644
index 0000000..3882455
--- /dev/null
+++ b/src/FrequencyDialog.cpp
@@ -0,0 +1,243 @@
+#include "FrequencyDialog.h"
+
+#include "wx/clipbrd.h"
+#include <sstream>
+#include "CubicSDR.h"
+
+wxBEGIN_EVENT_TABLE(FrequencyDialog, wxDialog)
+EVT_CHAR_HOOK(FrequencyDialog::OnChar)
+EVT_SHOW(FrequencyDialog::OnShow)
+wxEND_EVENT_TABLE()
+
+FrequencyDialog::FrequencyDialog(wxWindow * parent, wxWindowID id, const wxString & title, DemodulatorInstance *demod, const wxPoint & position,
+        const wxSize & size, long style, FrequencyDialogTarget targetMode, wxString initString) :
+        wxDialog(parent, id, title, position, size, style) {
+    wxString freqStr;
+    activeDemod = demod;
+    this->targetMode = targetMode;
+	this->initialString = initString;
+
+    if (targetMode == FDIALOG_TARGET_DEFAULT) {
+        if (activeDemod) {
+            freqStr = frequencyToStr(activeDemod->getFrequency());
+        } else {
+            freqStr = frequencyToStr(wxGetApp().getFrequency());
+        }
+    }
+            
+    if (targetMode == FDIALOG_TARGET_BANDWIDTH) {
+        std::string lastDemodType = activeDemod?activeDemod->getDemodulatorType():wxGetApp().getDemodMgr().getLastDemodulatorType();
+        if (lastDemodType == "USB" || lastDemodType == "LSB") {
+            freqStr = frequencyToStr(wxGetApp().getDemodMgr().getLastBandwidth()/2);
+        } else {
+            freqStr = frequencyToStr(wxGetApp().getDemodMgr().getLastBandwidth());
+        }
+    }
+            
+    if (targetMode == FDIALOG_TARGET_WATERFALL_LPS) {
+        freqStr = std::to_string(wxGetApp().getAppFrame()->getWaterfallDataThread()->getLinesPerSecond());
+    }
+
+    if (targetMode == FDIALOG_TARGET_SPECTRUM_AVG) {
+        freqStr = std::to_string(wxGetApp().getSpectrumProcessor()->getFFTAverageRate());
+    }
+
+    if (targetMode == FDIALOG_TARGET_GAIN) {
+        if (wxGetApp().getActiveGainEntry() != "") {
+            freqStr = std::to_string((int)wxGetApp().getGain(wxGetApp().getActiveGainEntry()));
+        }
+    }
+            
+    dialogText = new wxTextCtrl(this, wxID_FREQ_INPUT, freqStr, wxPoint(6, 1), wxSize(size.GetWidth() - 20, size.GetHeight() - 70),
+    wxTE_PROCESS_ENTER);
+    dialogText->SetFont(wxFont(20, wxFONTFAMILY_TELETYPE, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD));
+
+    Centre();
+
+    if (initString != "" && initString.length() == 1) {
+        dialogText->SetValue(initString);
+        dialogText->SetSelection(2, 2);
+        dialogText->SetFocus();
+    } else {
+        if (initString != "") {
+            dialogText->SetValue(initString);
+        }
+        dialogText->SetSelection(-1, -1);
+    }
+}
+
+
+void FrequencyDialog::OnChar(wxKeyEvent& event) {
+    int c = event.GetKeyCode();
+    long long freq, freq2, freq_ctr, range_bw;
+    double dblval;
+    std::string lastDemodType = activeDemod?activeDemod->getDemodulatorType():wxGetApp().getDemodMgr().getLastDemodulatorType();
+    std::string strValue = dialogText->GetValue().ToStdString();
+    bool ranged = false;
+    std::string strValue2;
+    size_t range_pos;
+    
+    
+    switch (c) {
+    case WXK_RETURN:
+    case WXK_NUMPAD_ENTER:
+        // Do Stuff
+        ranged = false;
+        if ((range_pos = strValue.find_first_of("-")) > 0) {
+            strValue2 = strValue.substr(range_pos+1);
+            strValue = strValue.substr(0,range_pos);
+
+            if (targetMode == FDIALOG_TARGET_DEFAULT && !activeDemod && strValue.length() && strValue2.length()) {
+                ranged = true;
+            }
+        }
+
+        if (targetMode == FDIALOG_TARGET_DEFAULT) {
+            if (ranged) {
+                freq = strToFrequency(strValue);
+                freq2 = strToFrequency(strValue2);
+            } else {
+                freq = strToFrequency(strValue);
+            }
+            if (activeDemod) {
+                activeDemod->setTracking(true);
+                activeDemod->setFollow(true);
+                activeDemod->setFrequency(freq);
+                activeDemod->updateLabel(freq);
+            } else {
+                if (ranged && (freq || freq2)) {
+                    if (freq > freq2) {
+                        std::swap(freq,freq2);
+                    }
+                    range_bw = (freq2-freq);
+                    freq_ctr = freq + (range_bw/2);
+                    if (range_bw > wxGetApp().getSampleRate()) {
+                        range_bw = wxGetApp().getSampleRate();
+                    }
+                    if (range_bw < 30000) {
+                        range_bw = 30000;
+                    }
+                    if (freq == freq2) {
+                        wxGetApp().setFrequency(freq_ctr);
+                        wxGetApp().getAppFrame()->setViewState(freq_ctr);
+                    } else {
+                        if (wxGetApp().getSampleRate()/4 > range_bw) {
+                            wxGetApp().setFrequency(freq_ctr + wxGetApp().getSampleRate()/4);
+                        } else {
+                            wxGetApp().setFrequency(freq_ctr);
+                        }
+                        wxGetApp().getAppFrame()->setViewState(freq_ctr, range_bw);
+                    }
+                } else {
+                    wxGetApp().setFrequency(freq);
+                }
+            }
+        }
+        if (targetMode == FDIALOG_TARGET_BANDWIDTH) {
+            freq = strToFrequency(strValue);
+            if (lastDemodType == "USB" || lastDemodType == "LSB") {
+                freq *= 2;
+            }
+            if (activeDemod) {
+                activeDemod->setBandwidth(freq);
+            } else {
+                wxGetApp().getDemodMgr().setLastBandwidth(freq);
+            }
+        }
+        if (targetMode == FDIALOG_TARGET_WATERFALL_LPS) {
+            try {
+                freq = std::stoi(strValue);
+            } catch (exception e) {
+                Close();
+                break;
+            }
+            if (freq > 1024) {
+                freq = 1024;
+            }
+            if (freq < 1) {
+                freq = 1;
+            }
+            wxGetApp().getAppFrame()->setWaterfallLinesPerSecond(freq);
+        }
+        if (targetMode == FDIALOG_TARGET_SPECTRUM_AVG) {
+            try {
+                dblval = std::stod(strValue);
+            } catch (exception e) {
+                Close();
+                break;
+            }
+            if (dblval > 0.99) {
+                dblval = 0.99;
+            }
+            if (dblval < 0.1) {
+                dblval = 0.1;
+            }
+            wxGetApp().getAppFrame()->setSpectrumAvgSpeed(dblval);
+        }
+        
+        if (targetMode == FDIALOG_TARGET_GAIN) {
+            try {
+                freq = std::stoi(strValue);
+            } catch (exception e) {
+                break;
+            }
+            SDRDeviceInfo *devInfo = wxGetApp().getDevice();
+            std::string gainName = wxGetApp().getActiveGainEntry();
+            if (gainName == "") {
+                break;
+            }
+            SDRRangeMap gains = devInfo->getGains(SOAPY_SDR_RX, 0);
+            if (freq > gains[gainName].maximum()) {
+                freq = gains[gainName].maximum();
+            }
+            if (freq < gains[gainName].minimum()) {
+                freq = gains[gainName].minimum();
+            }
+            wxGetApp().setGain(gainName, freq);
+            wxGetApp().getAppFrame()->refreshGainUI();
+        }
+
+        Close();
+        break;
+    case WXK_ESCAPE:
+        Close();
+        break;
+    }
+
+    std::string allowed("0123456789.MKGHZmkghz");
+
+    // Support '-' for range
+    if (targetMode == FDIALOG_TARGET_DEFAULT && !activeDemod && strValue.length() > 0) {
+        allowed.append("-");
+    }
+
+    if (allowed.find_first_of(c) != std::string::npos || c == WXK_DELETE || c == WXK_BACK || c == WXK_NUMPAD_DECIMAL
+            || (c >= WXK_NUMPAD0 && c <= WXK_NUMPAD9)) {
+#ifdef __linux__
+        dialogText->OnChar(event);
+        event.Skip();
+#else
+        event.DoAllowNextEvent();
+#endif
+    } else if (event.ControlDown() && c == 'V') {
+        // Alter clipboard contents to remove unwanted chars
+        wxTheClipboard->Open();
+        wxTextDataObject data;
+        wxTheClipboard->GetData(data);
+        std::string clipText = data.GetText().ToStdString();
+        std::string pasteText = filterChars(clipText, std::string(allowed));
+        wxTheClipboard->SetData(new wxTextDataObject(pasteText));
+        wxTheClipboard->Close();
+        event.Skip();
+    } else if (c == WXK_RIGHT || c == WXK_LEFT || event.ControlDown()) {
+        event.Skip();
+    }
+}
+
+void FrequencyDialog::OnShow(wxShowEvent &event) {
+	if (initialString.length() == 1) {	
+	    dialogText->SetFocus();
+	    dialogText->SetSelection(2, 2);
+	}
+	event.Skip();
+}
diff --git a/src/FrequencyDialog.h b/src/FrequencyDialog.h
new file mode 100644
index 0000000..6353c39
--- /dev/null
+++ b/src/FrequencyDialog.h
@@ -0,0 +1,41 @@
+#pragma once
+
+#include "wx/dialog.h"
+#include "wx/textctrl.h"
+#include "wx/string.h"
+#include "wx/button.h"
+#include "DemodulatorInstance.h"
+
+#define wxID_FREQ_INPUT 3001
+
+class FrequencyDialog: public wxDialog
+{
+public:
+    typedef enum FrequencyDialogTarget {
+        FDIALOG_TARGET_DEFAULT,
+        FDIALOG_TARGET_CENTERFREQ,
+        FDIALOG_TARGET_FREQ,
+        FDIALOG_TARGET_BANDWIDTH,
+        FDIALOG_TARGET_WATERFALL_LPS,
+        FDIALOG_TARGET_SPECTRUM_AVG,
+        FDIALOG_TARGET_GAIN
+    } FrequencyDialogTarget;
+    FrequencyDialog ( wxWindow * parent, wxWindowID id, const wxString & title,
+                  DemodulatorInstance *demod = NULL,
+                  const wxPoint & pos = wxDefaultPosition,
+                  const wxSize & size = wxDefaultSize,
+                  long style = wxDEFAULT_DIALOG_STYLE,
+                  FrequencyDialogTarget targetMode = FDIALOG_TARGET_DEFAULT,
+                  wxString initString = "");
+
+    wxTextCtrl * dialogText;
+
+private:
+    DemodulatorInstance *activeDemod;
+    void OnEnter ( wxCommandEvent &event );
+    void OnChar ( wxKeyEvent &event );
+	void OnShow(wxShowEvent &event);
+    FrequencyDialogTarget targetMode;
+	std::string initialString;
+    DECLARE_EVENT_TABLE()
+};
diff --git a/src/IOThread.cpp b/src/IOThread.cpp
new file mode 100644
index 0000000..a27c71c
--- /dev/null
+++ b/src/IOThread.cpp
@@ -0,0 +1,129 @@
+#include "IOThread.h"
+#include <typeinfo>
+
+std::mutex ReBufferGC::g_mutex;
+std::set<ReferenceCounter *> ReBufferGC::garbage;
+
+#define SPIN_WAIT_SLEEP_MS 5
+
+IOThread::IOThread() {
+    terminated.store(false);
+    stopping.store(false);
+}
+
+IOThread::~IOThread() {
+    terminated.store(true);
+    stopping.store(true);
+}
+
+#ifdef __APPLE__
+void *IOThread::threadMain() {
+    terminated.store(false);
+    stopping.store(false);
+    try {
+        run();
+    }
+    catch (...) {
+        terminated.store(true);
+        stopping.store(true);
+        throw;
+    }
+
+    terminated.store(true);
+    stopping.store(true);
+    return this;
+};
+
+void *IOThread::pthread_helper(void *context) {
+    return ((IOThread *) context)->threadMain();
+};
+#else
+void IOThread::threadMain() {
+    terminated.store(false);
+    stopping.store(false);
+    try {
+        run();
+    }
+    catch (...) {
+        terminated.store(true);
+        stopping.store(true);
+        throw;
+    }
+  
+    terminated.store(true);
+    stopping.store(true);
+};
+#endif
+
+void IOThread::setup() {
+    //redefined in subclasses
+};
+
+void IOThread::run() {
+    //redefined in subclasses
+};
+
+
+void IOThread::terminate() {
+    stopping.store(true);
+};
+
+void IOThread::onBindOutput(std::string /* name */, ThreadQueueBase* /* threadQueue */) {
+    
+};
+
+void IOThread::onBindInput(std::string /* name */, ThreadQueueBase* /* threadQueue */) {
+    
+};
+
+void IOThread::setInputQueue(std::string qname, ThreadQueueBase *threadQueue) {
+    input_queues[qname] = threadQueue;
+    this->onBindInput(qname, threadQueue);
+};
+
+ThreadQueueBase *IOThread::getInputQueue(std::string qname) {
+    return input_queues[qname];
+};
+
+void IOThread::setOutputQueue(std::string qname, ThreadQueueBase *threadQueue) {
+    output_queues[qname] = threadQueue;
+    this->onBindOutput(qname, threadQueue);
+};
+
+ThreadQueueBase *IOThread::getOutputQueue(std::string qname) {
+    return output_queues[qname];
+};
+
+bool IOThread::isTerminated(int waitMs) {
+
+    if (terminated.load()) {
+        return true;
+    }
+    else if (waitMs == 0) {
+        return false;
+    }
+
+    //this is a stupid busy plus sleep loop
+    int nbCyclesToWait = 0;
+
+    if (waitMs < 0) {
+        nbCyclesToWait = std::numeric_limits<int>::max();
+    }
+    else {
+
+        nbCyclesToWait = (waitMs / SPIN_WAIT_SLEEP_MS) + 1;
+    }
+
+    for ( int i = 0; i < nbCyclesToWait; i++) {
+
+        std::this_thread::sleep_for(std::chrono::milliseconds(SPIN_WAIT_SLEEP_MS));
+
+        if (terminated.load()) {
+            return true;
+        }
+    }
+
+    std::cout << "ERROR: thread '" << typeid(*this).name() << "' has not terminated in time ! (> " << waitMs << " ms)" << std::endl;
+
+    return terminated.load();
+}
diff --git a/src/IOThread.h b/src/IOThread.h
new file mode 100644
index 0000000..2fe8504
--- /dev/null
+++ b/src/IOThread.h
@@ -0,0 +1,230 @@
+#pragma once
+
+#include <mutex>
+#include <atomic>
+#include <deque>
+#include <map>
+#include <set>
+#include <string>
+#include <iostream>
+#include <thread>
+
+#include "ThreadQueue.h"
+#include "Timer.h"
+
+struct map_string_less : public std::binary_function<std::string,std::string,bool>
+{
+    bool operator()(const std::string& a,const std::string& b) const
+    {
+        return a.compare(b) < 0;
+    }
+};
+
+
+class ReferenceCounter {
+
+public:
+
+    //default constructor, initialized with refcont 1, sounds very natural
+    ReferenceCounter() {
+        refCount = 1;
+    }
+    
+//    void setIndex(int idx) {
+//        std::lock_guard < std::recursive_mutex > lock(m_mutex);
+//        index = idx;
+//    }
+
+//    int getIndex() {
+//        std::lock_guard < std::recursive_mutex > lock(m_mutex);
+//        return index;
+//    }
+
+    void setRefCount(int rc) {
+        std::lock_guard < std::recursive_mutex > lock(m_mutex);
+        refCount = rc;
+    }
+    
+    void decRefCount() {
+        std::lock_guard < std::recursive_mutex > lock(m_mutex);
+        refCount--;
+    }
+    
+    int getRefCount() {
+        std::lock_guard < std::recursive_mutex > lock(m_mutex);
+        return refCount;
+    }
+
+    // Access to the own mutex protecting the ReferenceCounter, i.e the monitor of the class
+     std::recursive_mutex& getMonitor() const {
+        return m_mutex;
+    }
+
+protected:
+    //this is a basic mutex for all ReferenceCounter derivatives operations INCLUDING the counter itself for consistency !
+   mutable std::recursive_mutex m_mutex;
+
+private:
+   int refCount;
+//   int index;
+};
+
+
+#define REBUFFER_GC_LIMIT 100
+
+class ReBufferGC {
+public:
+    static void garbageCollect() {
+        std::lock_guard < std::mutex > lock(g_mutex);
+        
+        std::deque<ReferenceCounter *> garbageRemoval;
+        for (typename std::set<ReferenceCounter *>::iterator i = garbage.begin(); i != garbage.end(); i++) {
+            if ((*i)->getRefCount() <= 0) {
+                garbageRemoval.push_back(*i);
+            }
+            else {
+//                std::cout << "Garbage in queue buffer idx #" << (*i)->getIndex() << ", " << (*i)->getRefCount() << " usage(s)" << std::endl;
+                std::cout << "Garbage in queue buffer with " << (*i)->getRefCount() << " usage(s)" << std::endl;
+            }
+        }
+        if ( garbageRemoval.size() ) {
+            std::cout << "Garbage collecting " << garbageRemoval.size() << " ReBuffer(s)" << std::endl;
+            while (!garbageRemoval.empty()) {
+                ReferenceCounter *ref = garbageRemoval.back();
+                garbageRemoval.pop_back();
+                garbage.erase(ref);
+                delete ref;
+            }
+        }
+    }
+    
+    static void addGarbage(ReferenceCounter *ref) {
+        std::lock_guard < std::mutex > lock(g_mutex);
+        garbage.insert(ref);
+    }
+    
+private:
+    static std::mutex g_mutex;
+    static std::set<ReferenceCounter *> garbage;
+};
+
+
+template<class BufferType = ReferenceCounter>
+class ReBuffer {
+    
+public:
+    ReBuffer(std::string bufferId) : bufferId(bufferId) {
+//        indexCounter.store(0);
+    }
+    
+    BufferType *getBuffer() {
+        std::lock_guard < std::mutex > lock(m_mutex);
+
+        BufferType* buf = nullptr;
+        for (outputBuffersI = outputBuffers.begin(); outputBuffersI != outputBuffers.end(); outputBuffersI++) {
+            if (buf == nullptr && (*outputBuffersI)->getRefCount() <= 0) {
+                buf = (*outputBuffersI);
+                buf->setRefCount(1);
+            } else if ((*outputBuffersI)->getRefCount() <= 0) {
+                (*outputBuffersI)->decRefCount();
+            }
+        }
+        
+        if (buf != nullptr) {
+            if (outputBuffers.back()->getRefCount() < -REBUFFER_GC_LIMIT) {
+                BufferType *ref = outputBuffers.back();
+                outputBuffers.pop_back();
+                delete ref;
+            }
+//            buf->setIndex(indexCounter++);
+            return buf;
+        }
+        
+#define REBUFFER_WARNING_THRESHOLD 100
+        if (outputBuffers.size() > REBUFFER_WARNING_THRESHOLD) {
+            std::cout << "Warning: ReBuffer '" << bufferId << "' count '" << outputBuffers.size() << "' exceeds threshold of '" << REBUFFER_WARNING_THRESHOLD << "'" << std::endl;
+        }
+
+        //by default created with refcount = 1
+        buf = new BufferType();
+//        buf->setIndex(indexCounter++);
+        outputBuffers.push_back(buf);
+        
+        return buf;
+    }
+    
+    void purge() {
+        std::lock_guard < std::mutex > lock(m_mutex);
+//        if (bufferId == "DemodulatorThreadBuffers") {
+//            std::cout << "'" << bufferId << "' purging.. total indexes: " << indexCounter.load() << std::endl;
+//        }
+        while (!outputBuffers.empty()) {
+            BufferType *ref = outputBuffers.front();
+            outputBuffers.pop_front();
+            if (ref->getRefCount() <= 0) {
+                delete ref;
+            } else {
+                // Something isn't done with it yet; throw it on the pile.. keep this as a bug indicator for now..
+                std::cout << "'" << bufferId << "' pushed garbage.." << std::endl;
+                ReBufferGC::addGarbage(ref);
+            }
+        }
+    }
+
+   private:
+    std::string bufferId;
+    std::deque<BufferType*> outputBuffers;
+    typename std::deque<BufferType*>::iterator outputBuffersI;
+    mutable std::mutex m_mutex;
+//    std::atomic_int indexCounter;
+};
+
+
+class IOThread {
+public:
+    IOThread();
+    virtual ~IOThread();
+
+    static void *pthread_helper(void *context);
+
+#ifdef __APPLE__
+    virtual void *threadMain();
+#else
+
+    //the thread Main call back itself
+    virtual void threadMain();
+#endif
+
+    virtual void setup();
+    virtual void run();
+
+    //Request for termination (asynchronous)
+    virtual void terminate();
+
+    //Returns true if the thread is indeed terminated, i.e the run() method
+    //has returned. 
+    //If wait > 0 ms, the call is blocking at most 'waitMs' milliseconds for the thread to die, then returns.
+    //If wait < 0, the wait in infinite until the thread dies.
+    bool isTerminated(int waitMs = 0);
+    
+    virtual void onBindOutput(std::string name, ThreadQueueBase* threadQueue);
+    virtual void onBindInput(std::string name, ThreadQueueBase* threadQueue);
+
+    void setInputQueue(std::string qname, ThreadQueueBase *threadQueue);
+    ThreadQueueBase *getInputQueue(std::string qname);
+    void setOutputQueue(std::string qname, ThreadQueueBase *threadQueue);
+    ThreadQueueBase *getOutputQueue(std::string qname);
+    
+protected:
+    std::map<std::string, ThreadQueueBase *, map_string_less> input_queues;
+    std::map<std::string, ThreadQueueBase *, map_string_less> output_queues;
+    
+    //true when a termination is ordered
+    std::atomic_bool stopping;
+    Timer gTimer;
+
+private:
+    //true when the thread has really ended, i.e run() from threadMain() has returned.
+    std::atomic_bool terminated;
+
+};
diff --git a/src/ModemProperties.cpp b/src/ModemProperties.cpp
new file mode 100644
index 0000000..be3e6ba
--- /dev/null
+++ b/src/ModemProperties.cpp
@@ -0,0 +1,316 @@
+#include "ModemProperties.h"
+#include "CubicSDR.h"
+
+ModemProperties::ModemProperties(wxWindow *parent, wxWindowID winid,
+         const wxPoint& pos, const wxSize& size, long style, const wxString& name) : wxPanel(parent, winid, pos, size, style, name) {
+    
+   	m_propertyGrid = new wxPropertyGrid(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxPG_DEFAULT_STYLE);
+
+    bSizer = new wxBoxSizer( wxVERTICAL );
+    
+    bSizer->Add(m_propertyGrid, 1, wxEXPAND, 0);
+    
+    this->SetSizer(bSizer);
+    
+    m_propertyGrid->Connect( wxEVT_PG_ITEM_COLLAPSED, wxPropertyGridEventHandler( ModemProperties::OnCollapse ), NULL, this );
+    m_propertyGrid->Connect( wxEVT_PG_ITEM_EXPANDED, wxPropertyGridEventHandler( ModemProperties::OnExpand ), NULL, this );
+    m_propertyGrid->Connect( wxEVT_PG_CHANGED, wxPropertyGridEventHandler( ModemProperties::OnChange ), NULL, this );
+    this->Connect( wxEVT_SHOW, wxShowEventHandler( ModemProperties::OnShow ), NULL, this );
+    
+    this->Connect( wxEVT_ENTER_WINDOW, wxMouseEventHandler( ModemProperties::OnMouseEnter ), NULL, this);
+    this->Connect( wxEVT_LEAVE_WINDOW, wxMouseEventHandler( ModemProperties::OnMouseLeave ), NULL, this);
+
+    updateTheme();
+
+    mouseInView = false;
+    collapsed = false;
+}
+
+void ModemProperties::OnShow(wxShowEvent & /* event */) {
+}
+
+void ModemProperties::updateTheme() {
+    wxColour bgColor(
+                 (unsigned char) (ThemeMgr::mgr.currentTheme->generalBackground.r * 255.0),
+                 (unsigned char) (ThemeMgr::mgr.currentTheme->generalBackground.g * 255.0),
+                 (unsigned char) (ThemeMgr::mgr.currentTheme->generalBackground.b * 255.0));
+
+    wxColour textColor(
+                       (unsigned char) (ThemeMgr::mgr.currentTheme->text.r * 255.0),
+                       (unsigned char) (ThemeMgr::mgr.currentTheme->text.g * 255.0),
+                       (unsigned char) (ThemeMgr::mgr.currentTheme->text.b * 255.0));
+    
+    wxColour btn(
+                       (unsigned char) (ThemeMgr::mgr.currentTheme->button.r * 255.0),
+                       (unsigned char) (ThemeMgr::mgr.currentTheme->button.g * 255.0),
+                       (unsigned char) (ThemeMgr::mgr.currentTheme->button.b * 255.0));
+
+    wxColour btnHl(
+                 (unsigned char) (ThemeMgr::mgr.currentTheme->buttonHighlight.r * 255.0),
+                 (unsigned char) (ThemeMgr::mgr.currentTheme->buttonHighlight.g * 255.0),
+                 (unsigned char) (ThemeMgr::mgr.currentTheme->buttonHighlight.b * 255.0));
+
+
+    m_propertyGrid->SetEmptySpaceColour(bgColor);
+    m_propertyGrid->SetCellBackgroundColour(bgColor);
+    m_propertyGrid->SetCellTextColour(textColor);
+    m_propertyGrid->SetSelectionTextColour(bgColor);
+    m_propertyGrid->SetSelectionBackgroundColour(btnHl);
+    m_propertyGrid->SetCaptionTextColour(bgColor);
+    m_propertyGrid->SetCaptionBackgroundColour(btn);
+    m_propertyGrid->SetLineColour(btn);
+}
+
+ModemProperties::~ModemProperties() {
+    
+}
+
+
+void ModemProperties::initDefaultProperties() {
+    
+    if (!audioOutputDevices.size()) {
+        std::vector<string> outputOpts;
+        std::vector<string> outputOptNames;
+
+        AudioThread::enumerateDevices(audioDevices);
+        
+        int i = 0;
+        
+        for (auto aDev : audioDevices) {
+            if (aDev.inputChannels) {
+                audioInputDevices[i] = aDev;
+            }
+            if (aDev.outputChannels) {
+                audioOutputDevices[i] = aDev;
+            }
+            i++;
+        }
+        
+        int defaultDevice = 0;
+        int dc = 0;
+        
+        for (auto mdevices_i : audioOutputDevices) {
+            outputOpts.push_back(std::to_string(mdevices_i.first));
+            outputOptNames.push_back(mdevices_i.second.name);
+            
+            if (mdevices_i.second.isDefaultOutput) {
+                defaultDevice = dc;
+            }
+            dc++;
+        }
+        
+        outputArg.key ="._audio_output";
+        outputArg.name = "Audio Out";
+        outputArg.description = "Set the current modem's audio output device.";
+        outputArg.type = ModemArgInfo::STRING;
+        outputArg.options = outputOpts;
+        outputArg.optionNames = outputOptNames;
+    }
+    
+    int currentOutput = demodContext->getOutputDevice();
+
+    outputArg.value = std::to_string(currentOutput);
+    
+    defaultProps["._audio_output"] = addArgInfoProperty(m_propertyGrid, outputArg);
+}
+
+void ModemProperties::initProperties(ModemArgInfoList newArgs, DemodulatorInstance *demodInstance) {
+    args = newArgs;
+    demodContext = demodInstance;
+    
+    bSizer->Layout();
+    m_propertyGrid->Clear();
+
+    if (!demodInstance) {
+        Hide();
+        return;
+    } else {
+        Show();
+    }
+    
+    m_propertyGrid->Append(new wxPropertyCategory(demodInstance->getDemodulatorType() + " Settings"));
+    
+    initDefaultProperties();
+    
+    ModemArgInfoList::const_iterator args_i;
+    
+    for (args_i = args.begin(); args_i != args.end(); args_i++) {
+        ModemArgInfo arg = (*args_i);
+        props[arg.key] = addArgInfoProperty(m_propertyGrid, arg);
+    }
+    
+    m_propertyGrid->FitColumns();
+    
+    if (collapsed) {
+        m_propertyGrid->CollapseAll();
+    }
+}
+
+wxPGProperty *ModemProperties::addArgInfoProperty(wxPropertyGrid *pg, ModemArgInfo arg) {
+    wxPGProperty *prop = nullptr;
+    
+    int intVal;
+    double floatVal;
+    std::vector<std::string>::iterator stringIter;
+    
+    switch (arg.type) {
+        case ModemArgInfo::INT:
+            try {
+                intVal = std::stoi(arg.value);
+            } catch (std::invalid_argument e) {
+                intVal = 0;
+            }
+            prop = pg->Append( new wxIntProperty(arg.name, wxPG_LABEL, intVal) );
+            if (arg.range.minimum() != arg.range.maximum()) {
+                pg->SetPropertyAttribute( prop, wxPG_ATTR_MIN, arg.range.minimum());
+                pg->SetPropertyAttribute( prop, wxPG_ATTR_MAX, arg.range.maximum());
+            }
+            break;
+        case ModemArgInfo::FLOAT:
+            try {
+                floatVal = std::stod(arg.value);
+            } catch (std::invalid_argument e) {
+                floatVal = 0;
+            }
+            prop = pg->Append( new wxFloatProperty(arg.name, wxPG_LABEL, floatVal) );
+            if (arg.range.minimum() != arg.range.maximum()) {
+                pg->SetPropertyAttribute( prop, wxPG_ATTR_MIN, arg.range.minimum());
+                pg->SetPropertyAttribute( prop, wxPG_ATTR_MAX, arg.range.maximum());
+            }
+            break;
+        case ModemArgInfo::BOOL:
+            prop = pg->Append( new wxBoolProperty(arg.name, wxPG_LABEL, (arg.value=="true")) );
+            break;
+        case ModemArgInfo::STRING:
+            if (arg.options.size()) {
+                intVal = 0;
+                prop = pg->Append( new wxEnumProperty(arg.name, wxPG_LABEL) );
+                for (stringIter = arg.options.begin(); stringIter != arg.options.end(); stringIter++) {
+                    std::string optName = (*stringIter);
+                    std::string displayName = optName;
+                    if (arg.optionNames.size()) {
+                        displayName = arg.optionNames[intVal];
+                    }
+                    
+                    prop->AddChoice(displayName);
+                    if ((*stringIter)==arg.value) {
+                        prop->SetChoiceSelection(intVal);
+                    }
+                    
+                    intVal++;
+                }
+            } else {
+                prop = pg->Append( new wxStringProperty(arg.name, wxPG_LABEL, arg.value) );
+            }
+            break;
+        case ModemArgInfo::PATH_DIR:
+            break;
+        case ModemArgInfo::PATH_FILE:
+            break;
+        case ModemArgInfo::COLOR:
+            break;
+    }
+    
+    if (prop != NULL) {
+        prop->SetHelpString(arg.name + ": " + arg.description);
+    }
+    
+    return prop;
+}
+
+std::string ModemProperties::readProperty(std::string key) {
+    int i = 0;
+    ModemArgInfoList::const_iterator args_i;
+
+    for (args_i = args.begin(); args_i != args.end(); args_i++) {
+        ModemArgInfo arg = (*args_i);
+        if (arg.key == key) {
+            wxPGProperty *prop = props[key];
+            
+            std::string result = "";
+            if (arg.type == ModemArgInfo::STRING && arg.options.size()) {
+                return arg.options[prop->GetChoiceSelection()];
+            } else if (arg.type == ModemArgInfo::BOOL) {
+                return (prop->GetValueAsString()=="True")?"true":"false";
+            } else {
+                return prop->GetValueAsString().ToStdString();
+            }
+        }
+        i++;
+    }
+    return "";
+}
+
+void ModemProperties::OnChange(wxPropertyGridEvent &event) {
+    if (!demodContext || !demodContext->isActive()) {
+        return;
+    }
+    
+    std::map<std::string, wxPGProperty *>::const_iterator prop_i;
+    
+    if (event.m_property == defaultProps["._audio_output"]) {
+        int sel = event.m_property->GetChoiceSelection();
+        
+        outputArg.value = outputArg.options[sel];
+        
+        if (demodContext) {
+            try {
+                demodContext->setOutputDevice(std::stoi(outputArg.value));
+            } catch (exception e) {
+                // .. this should never happen ;)
+            }
+
+            wxGetApp().getAppFrame()->setScopeDeviceName(outputArg.optionNames[sel]);
+        }
+        
+        return;
+    }
+    
+    for (prop_i = props.begin(); prop_i != props.end(); prop_i++) {
+        if (prop_i->second == event.m_property) {
+            std::string key = prop_i->first;
+            std::string value = readProperty(prop_i->first);
+            demodContext->writeModemSetting(key, value);
+            return;
+        }
+    }
+}
+
+void ModemProperties::OnCollapse(wxPropertyGridEvent &event) {
+    collapsed = true;
+}
+
+void ModemProperties::OnExpand(wxPropertyGridEvent &event) {
+    collapsed = false;
+}
+
+void ModemProperties::OnMouseEnter(wxMouseEvent & /* event */) {
+    mouseInView = true;
+}
+
+void ModemProperties::OnMouseLeave(wxMouseEvent & /* event */) {
+    mouseInView = false;
+}
+
+bool ModemProperties::isMouseInView() {
+    return mouseInView || (m_propertyGrid && m_propertyGrid->IsEditorFocused());
+}
+
+void ModemProperties::setCollapsed(bool state) {
+    collapsed = state;
+    if (m_propertyGrid) {
+        if (state) {
+            m_propertyGrid->CollapseAll();
+        } else {
+            m_propertyGrid->ExpandAll();
+        }
+    }
+}
+
+bool ModemProperties::isCollapsed() {
+    return collapsed;
+}
+
+void ModemProperties::fitColumns() {
+    m_propertyGrid->FitColumns();
+}
diff --git a/src/ModemProperties.h b/src/ModemProperties.h
new file mode 100644
index 0000000..bfebcde
--- /dev/null
+++ b/src/ModemProperties.h
@@ -0,0 +1,57 @@
+#pragma once
+
+#include <wx/panel.h>
+#include <wx/sizer.h>
+#include <wx/propgrid/propgrid.h>
+#include <wx/propgrid/advprops.h>
+
+#include "DemodulatorInstance.h"
+#include "Modem.h"
+
+class ModemProperties : public wxPanel {
+public:
+    ModemProperties(
+        wxWindow *parent,
+        wxWindowID winid = wxID_ANY,
+        const wxPoint& pos = wxDefaultPosition,
+        const wxSize& size = wxDefaultSize,
+        long style = wxTAB_TRAVERSAL | wxNO_BORDER,
+        const wxString& name = wxPanelNameStr
+    );
+    ~ModemProperties();
+    
+    void initDefaultProperties();
+    void initProperties(ModemArgInfoList newArgs, DemodulatorInstance *demodInstance);
+    bool isMouseInView();
+    void setCollapsed(bool state);
+    bool isCollapsed();
+    void fitColumns();
+    
+    void updateTheme();
+    
+private:
+    wxPGProperty *addArgInfoProperty(wxPropertyGrid *pg, ModemArgInfo arg);
+    std::string readProperty(std::string);
+    void OnChange(wxPropertyGridEvent &event);
+    void OnShow(wxShowEvent &event);
+    void OnCollapse(wxPropertyGridEvent &event);
+    void OnExpand(wxPropertyGridEvent &event);
+
+    void OnMouseEnter(wxMouseEvent &event);
+    void OnMouseLeave(wxMouseEvent &event);
+    
+    wxBoxSizer* bSizer;
+    wxPropertyGrid* m_propertyGrid;
+    ModemArgInfoList args;
+    DemodulatorInstance *demodContext;
+    std::map<std::string, wxPGProperty *> props;
+    bool mouseInView, collapsed;
+    
+    ModemArgInfoList defaultArgs;
+    ModemArgInfo outputArg;
+    std::map<std::string, wxPGProperty *> defaultProps;
+    
+    std::vector<RtAudio::DeviceInfo> audioDevices;
+    std::map<int,RtAudio::DeviceInfo> audioInputDevices;
+    std::map<int,RtAudio::DeviceInfo> audioOutputDevices;
+};
\ No newline at end of file
diff --git a/src/audio/AudioThread.cpp b/src/audio/AudioThread.cpp
new file mode 100644
index 0000000..f1c2038
--- /dev/null
+++ b/src/audio/AudioThread.cpp
@@ -0,0 +1,534 @@
+#include "AudioThread.h"
+#include "CubicSDRDefs.h"
+#include <vector>
+#include <algorithm>
+#include "CubicSDR.h"
+#include "DemodulatorThread.h"
+#include "DemodulatorInstance.h"
+#include <memory.h>
+#include <mutex>
+
+
+std::map<int, AudioThread *> AudioThread::deviceController;
+std::map<int, int> AudioThread::deviceSampleRate;
+std::map<int, std::thread *> AudioThread::deviceThread;
+
+AudioThread::AudioThread() : IOThread(),
+        currentInput(nullptr), inputQueue(nullptr), nBufferFrames(1024), sampleRate(0) {
+
+	audioQueuePtr.store(0); 
+	underflowCount.store(0);
+	active.store(false);
+	outputDevice.store(-1);
+    gain = 1.0;
+}
+
+AudioThread::~AudioThread() {
+
+}
+
+std::recursive_mutex & AudioThread::getMutex()
+{
+    return m_mutex;
+}
+
+void AudioThread::bindThread(AudioThread *other) {
+
+    std::lock_guard<std::recursive_mutex> lock(m_mutex);
+
+    if (std::find(boundThreads.begin(), boundThreads.end(), other) == boundThreads.end()) {
+        boundThreads.push_back(other);
+    }
+}
+
+void AudioThread::removeThread(AudioThread *other) {
+    
+    std::lock_guard<std::recursive_mutex> lock(m_mutex);
+
+    std::vector<AudioThread *>::iterator i;
+    i = std::find(boundThreads.begin(), boundThreads.end(), other);
+    if (i != boundThreads.end()) {
+        boundThreads.erase(i);
+    }
+}
+
+void AudioThread::deviceCleanup() {
+
+    std::map<int, AudioThread *>::iterator i;
+
+    for (i = deviceController.begin(); i != deviceController.end(); i++) {
+        i->second->terminate();
+    }
+}
+
+static int audioCallback(void *outputBuffer, void * /* inputBuffer */, unsigned int nBufferFrames, double /* streamTime */, RtAudioStreamStatus status,
+        void *userData) {
+
+    float *out = (float*)outputBuffer;
+
+    //Zero output buffer in all cases: this allow to mute audio if no AudioThread data is 
+    //actually active.
+    memset(out, 0, nBufferFrames * 2 * sizeof(float));
+
+    AudioThread *src = (AudioThread *) userData;
+   
+    std::lock_guard<std::recursive_mutex> lock(src->getMutex());
+
+    if (src->isTerminated()) {
+        return 1;
+    }
+
+    if (status) {
+       std::cout << "Audio buffer underflow.." << (src->underflowCount++) << std::endl;
+    }
+
+    if (src->boundThreads.empty()) {
+       return 0;
+    }
+
+    
+    double peak = 0.0;
+   
+    //for all boundThreads
+    for (size_t j = 0; j < src->boundThreads.size(); j++) {
+
+        AudioThread *srcmix = src->boundThreads[j];
+
+        //lock every single boundThread srcmix in succession the time we process 
+        //its audio samples.
+        std::lock_guard<std::recursive_mutex> lock(srcmix->getMutex());
+
+        if (srcmix->isTerminated() || !srcmix->inputQueue || srcmix->inputQueue->empty() || !srcmix->isActive()) {
+            continue;
+        }
+
+        if (!srcmix->currentInput) {
+            srcmix->audioQueuePtr = 0;
+           
+            if (!srcmix->inputQueue->try_pop(srcmix->currentInput)) {
+                continue;
+            }
+            
+            continue;
+        }
+
+        if (srcmix->currentInput->sampleRate != src->getSampleRate()) {
+
+            while (srcmix->inputQueue->try_pop(srcmix->currentInput)) {
+                
+                if (srcmix->currentInput) {
+                    if (srcmix->currentInput->sampleRate == src->getSampleRate()) {
+                        break;
+                    }
+                    srcmix->currentInput->decRefCount();
+                }
+                srcmix->currentInput = nullptr;
+            } //end while
+
+            srcmix->audioQueuePtr = 0;
+
+            if (!srcmix->currentInput) {
+                continue;
+            }
+        }
+
+
+        if (srcmix->currentInput->channels == 0 || !srcmix->currentInput->data.size()) {
+            if (!srcmix->inputQueue->empty()) {
+                srcmix->audioQueuePtr = 0;
+                if (srcmix->currentInput) {
+                    srcmix->currentInput->decRefCount();
+                    srcmix->currentInput = nullptr;
+                }
+
+                if (!srcmix->inputQueue->try_pop(srcmix->currentInput)) {
+                    continue;
+                }          
+            }
+            continue;
+        }
+
+        double mixPeak = srcmix->currentInput->peak * srcmix->gain;
+
+        if (srcmix->currentInput->channels == 1) {
+
+            for (unsigned int i = 0; i < nBufferFrames; i++) {
+
+                if (srcmix->audioQueuePtr >= srcmix->currentInput->data.size()) {
+                    srcmix->audioQueuePtr = 0;
+                    if (srcmix->currentInput) {
+                        srcmix->currentInput->decRefCount();
+                        srcmix->currentInput = nullptr;
+                    }
+
+                    if (!srcmix->inputQueue->try_pop(srcmix->currentInput)) {
+                        break;
+                    }
+
+            
+                    double srcPeak = srcmix->currentInput->peak * srcmix->gain;
+                    if (mixPeak < srcPeak) {
+                        mixPeak = srcPeak;
+                    }
+                }
+                if (srcmix->currentInput && srcmix->currentInput->data.size()) {
+                    float v = srcmix->currentInput->data[srcmix->audioQueuePtr] * srcmix->gain;
+                    out[i * 2] += v;
+                    out[i * 2 + 1] += v;
+                }
+                srcmix->audioQueuePtr++;
+            }
+        } else {
+            for (int i = 0, iMax = srcmix->currentInput->channels * nBufferFrames; i < iMax; i++) {
+
+                if (srcmix->audioQueuePtr >= srcmix->currentInput->data.size()) {
+                    srcmix->audioQueuePtr = 0;
+                    if (srcmix->currentInput) {
+                        srcmix->currentInput->decRefCount();
+                        srcmix->currentInput = nullptr;
+                    }
+
+                    if (!srcmix->inputQueue->try_pop(srcmix->currentInput)) {
+                        break;
+                    }
+
+                    double srcPeak = srcmix->currentInput->peak * srcmix->gain;
+                    if (mixPeak < srcPeak) {
+                        mixPeak = srcPeak;
+                    }
+                }
+                if (srcmix->currentInput && srcmix->currentInput->data.size()) {
+
+                    out[i] = out[i] + srcmix->currentInput->data[srcmix->audioQueuePtr] * srcmix->gain;
+                }
+                srcmix->audioQueuePtr++;
+            }
+        }
+
+        peak += mixPeak;
+    }
+
+    //normalize volume
+    if (peak > 1.0) {
+        float invPeak = (float)(1.0 / peak);
+
+        for (unsigned int i = 0; i < nBufferFrames * 2; i++) {
+            out[i] *= invPeak;
+        }
+    }
+
+    return 0;
+}
+
+void AudioThread::enumerateDevices(std::vector<RtAudio::DeviceInfo> &devs) {
+    RtAudio endac;
+
+    int numDevices = endac.getDeviceCount();
+
+    for (int i = 0; i < numDevices; i++) {
+        RtAudio::DeviceInfo info = endac.getDeviceInfo(i);
+
+        devs.push_back(info);
+
+        std::cout << std::endl;
+
+        std::cout << "Audio Device #" << i << " " << info.name << std::endl;
+        std::cout << "\tDefault Output? " << (info.isDefaultOutput ? "Yes" : "No") << std::endl;
+        std::cout << "\tDefault Input? " << (info.isDefaultInput ? "Yes" : "No") << std::endl;
+        std::cout << "\tInput channels: " << info.inputChannels << std::endl;
+        std::cout << "\tOutput channels: " << info.outputChannels << std::endl;
+        std::cout << "\tDuplex channels: " << info.duplexChannels << std::endl;
+
+        std::cout << "\t" << "Native formats:" << std::endl;
+        RtAudioFormat nFormats = info.nativeFormats;
+        if (nFormats & RTAUDIO_SINT8) {
+            std::cout << "\t\t8-bit signed integer." << std::endl;
+        }
+        if (nFormats & RTAUDIO_SINT16) {
+            std::cout << "\t\t16-bit signed integer." << std::endl;
+        }
+        if (nFormats & RTAUDIO_SINT24) {
+            std::cout << "\t\t24-bit signed integer." << std::endl;
+        }
+        if (nFormats & RTAUDIO_SINT32) {
+            std::cout << "\t\t32-bit signed integer." << std::endl;
+        }
+        if (nFormats & RTAUDIO_FLOAT32) {
+            std::cout << "\t\t32-bit float normalized between plus/minus 1.0." << std::endl;
+        }
+        if (nFormats & RTAUDIO_FLOAT64) {
+            std::cout << "\t\t32-bit float normalized between plus/minus 1.0." << std::endl;
+        }
+
+        std::vector<unsigned int>::iterator srate;
+
+        std::cout << "\t" << "Supported sample rates:" << std::endl;
+
+        for (srate = info.sampleRates.begin(); srate != info.sampleRates.end(); srate++) {
+            std::cout << "\t\t" << (*srate) << "hz" << std::endl;
+        }
+
+        std::cout << std::endl;
+    }
+}
+
+void AudioThread::setDeviceSampleRate(int deviceId, int sampleRate) {
+   
+
+    if (deviceController.find(deviceId) != deviceController.end()) {
+        AudioThreadCommand refreshDevice;
+        refreshDevice.cmd = AudioThreadCommand::AUDIO_THREAD_CMD_SET_SAMPLE_RATE;
+        refreshDevice.int_value = sampleRate;
+        deviceController[deviceId]->getCommandQueue()->push(refreshDevice);
+    }
+}
+
+void AudioThread::setSampleRate(int sampleRate) {
+
+    std::lock_guard<std::recursive_mutex> lock(m_mutex);
+
+    if (deviceController[outputDevice.load()] == this) {
+        deviceSampleRate[outputDevice.load()] = sampleRate;
+
+        dac.stopStream();
+        dac.closeStream();
+
+        for (size_t j = 0; j < boundThreads.size(); j++) {
+            AudioThread *srcmix = boundThreads[j];
+            srcmix->setSampleRate(sampleRate);
+        }
+
+        std::vector<DemodulatorInstance *>::iterator demod_i;
+        std::vector<DemodulatorInstance *> *demodulators;
+
+        demodulators = &wxGetApp().getDemodMgr().getDemodulators();
+
+        for (demod_i = demodulators->begin(); demod_i != demodulators->end(); demod_i++) {
+            if ((*demod_i)->getOutputDevice() == outputDevice.load()) {
+                (*demod_i)->setAudioSampleRate(sampleRate);
+            }
+        }
+
+        dac.openStream(&parameters, NULL, RTAUDIO_FLOAT32, sampleRate, &nBufferFrames, &audioCallback, (void *) this, &opts);
+        dac.startStream();
+    }
+
+    this->sampleRate = sampleRate;
+}
+
+int AudioThread::getSampleRate() {
+    std::lock_guard<std::recursive_mutex> lock(m_mutex);
+
+    return this->sampleRate;
+}
+
+void AudioThread::setupDevice(int deviceId) {
+
+    std::lock_guard<std::recursive_mutex> lock(m_mutex);
+
+    parameters.deviceId = deviceId;
+    parameters.nChannels = 2;
+    parameters.firstChannel = 0;
+
+    opts.streamName = "CubicSDR Audio Output";
+
+    try {
+        if (deviceController.find(outputDevice.load()) != deviceController.end()) {
+            deviceController[outputDevice.load()]->removeThread(this);
+        }
+#ifndef _MSC_VER
+        opts.priority = sched_get_priority_max(SCHED_FIFO);
+#endif
+        //    opts.flags = RTAUDIO_MINIMIZE_LATENCY;
+        opts.flags = RTAUDIO_SCHEDULE_REALTIME;
+
+        if (deviceSampleRate.find(parameters.deviceId) != deviceSampleRate.end()) {
+            sampleRate = deviceSampleRate[parameters.deviceId];
+        } else {
+        	std::cout << "Error, device sample rate wasn't initialized?" << std::endl;
+        	return;
+//            sampleRate = AudioThread::getDefaultAudioSampleRate();
+//            deviceSampleRate[parameters.deviceId] = sampleRate;
+        }
+
+        if (deviceController.find(parameters.deviceId) == deviceController.end()) {
+            deviceController[parameters.deviceId] = new AudioThread();
+
+            deviceController[parameters.deviceId]->setInitOutputDevice(parameters.deviceId, sampleRate);
+            deviceController[parameters.deviceId]->bindThread(this);
+
+            deviceThread[parameters.deviceId] = new std::thread(&AudioThread::threadMain, deviceController[parameters.deviceId]);
+        } else if (deviceController[parameters.deviceId] == this) {
+            //Attach callback
+            dac.openStream(&parameters, NULL, RTAUDIO_FLOAT32, sampleRate, &nBufferFrames, &audioCallback, (void *) this, &opts);
+            dac.startStream();
+        } else {
+            deviceController[parameters.deviceId]->bindThread(this);
+        }
+        active = true;
+
+    } catch (RtAudioError& e) {
+        e.printMessage();
+        return;
+    }
+    if (deviceId != -1) {
+        outputDevice = deviceId;
+    }
+}
+
+int AudioThread::getOutputDevice() {
+    std::lock_guard<std::recursive_mutex> lock(m_mutex);
+
+    if (outputDevice == -1) {
+        return dac.getDefaultOutputDevice();
+    }
+    return outputDevice;
+}
+
+void AudioThread::setInitOutputDevice(int deviceId, int sampleRate) {
+    
+    std::lock_guard<std::recursive_mutex> lock(m_mutex);
+
+    outputDevice = deviceId;
+    if (sampleRate == -1) {
+        if (deviceSampleRate.find(parameters.deviceId) != deviceSampleRate.end()) {
+           sampleRate = deviceSampleRate[deviceId];
+        }
+    } else {
+        deviceSampleRate[deviceId] = sampleRate;
+    }
+    this->sampleRate = sampleRate;
+}
+
+void AudioThread::run() {
+#ifdef __APPLE__
+    pthread_t tID = pthread_self();	 // ID of this thread
+    int priority = sched_get_priority_max( SCHED_RR) - 1;
+    sched_param prio = {priority}; // scheduling priority of thread
+    pthread_setschedparam(tID, SCHED_RR, &prio);
+#endif
+
+//    std::cout << "Audio thread initializing.." << std::endl;
+
+    if (dac.getDeviceCount() < 1) {
+        std::cout << "No audio devices found!" << std::endl;
+        return;
+    }
+
+    setupDevice((outputDevice.load() == -1) ? (dac.getDefaultOutputDevice()) : outputDevice.load());
+
+//    std::cout << "Audio thread started." << std::endl;
+
+    inputQueue = static_cast<AudioThreadInputQueue *>(getInputQueue("AudioDataInput"));
+    
+    //Infinite loop, witing for commands or for termination
+    while (!stopping) {
+        AudioThreadCommand command;
+
+        cmdQueue.pop(command);
+
+        if (command.cmd == AudioThreadCommand::AUDIO_THREAD_CMD_SET_DEVICE) {
+            setupDevice(command.int_value);
+        }
+        if (command.cmd == AudioThreadCommand::AUDIO_THREAD_CMD_SET_SAMPLE_RATE) {
+            setSampleRate(command.int_value);
+        }
+    }
+    
+    //Thread termination, prevent fancy things to happen, lock the whole thing:
+    //This way audioThreadCallback is rightly protected from thread termination
+    std::lock_guard<std::recursive_mutex> lock(m_mutex);
+
+    // Drain any remaining inputs, with a non-blocking pop
+    AudioThreadInput *ref;
+    while (inputQueue && inputQueue->try_pop(ref)) {
+       
+        if (ref) {
+            ref->decRefCount();
+        }
+    } //end while
+    
+    //Nullify currentInput...
+    if (currentInput) {
+        currentInput->setRefCount(0);
+        currentInput = nullptr;
+    }
+
+    //Stop 
+    if (deviceController[parameters.deviceId] != this) {
+        deviceController[parameters.deviceId]->removeThread(this);
+    } else {
+        try {
+            if (dac.isStreamOpen()) {
+                if (dac.isStreamRunning()) {
+                    dac.stopStream();
+                }
+                dac.closeStream();
+            }
+        } catch (RtAudioError& e) {
+            e.printMessage();
+        }
+    }
+
+//    std::cout << "Audio thread done." << std::endl;
+}
+
+void AudioThread::terminate() {
+    IOThread::terminate();
+    AudioThreadCommand endCond;   // push an empty input to bump the queue
+    cmdQueue.push(endCond);
+}
+
+bool AudioThread::isActive() {
+    std::lock_guard<std::recursive_mutex> lock(m_mutex);
+
+    return active;
+}
+
+void AudioThread::setActive(bool state) {
+    
+    std::lock_guard<std::recursive_mutex> lock(m_mutex);
+
+    AudioThreadInput *dummy;
+    if (state && !active && inputQueue) {
+        deviceController[parameters.deviceId]->bindThread(this);
+    } else if (!state && active) {
+        deviceController[parameters.deviceId]->removeThread(this);
+    }
+
+    // Activity state changing, clear any inputs
+    if(inputQueue) {
+
+        while (inputQueue->try_pop(dummy)) {  // flush queue, non-blocking pop
+            
+            if (dummy) {
+                dummy->decRefCount();
+            }
+        }
+    }
+    active = state;
+}
+
+AudioThreadCommandQueue *AudioThread::getCommandQueue() {
+    return &cmdQueue;
+}
+
+void AudioThread::setGain(float gain_in) {
+    
+    std::lock_guard<std::recursive_mutex> lock(m_mutex);
+
+    if (gain < 0.0) {
+        gain = 0.0;
+    }
+    if (gain > 2.0) {
+        gain = 2.0;
+    }
+    gain = gain_in;
+}
+
+float AudioThread::getGain() {
+    
+    std::lock_guard<std::recursive_mutex> lock(m_mutex);
+
+    return gain;
+}
diff --git a/src/audio/AudioThread.h b/src/audio/AudioThread.h
new file mode 100644
index 0000000..65388c7
--- /dev/null
+++ b/src/audio/AudioThread.h
@@ -0,0 +1,110 @@
+#pragma once
+
+#include <queue>
+#include <vector>
+#include <map>
+#include <string>
+#include <atomic>
+
+#include "AudioThread.h"
+#include "ThreadQueue.h"
+#include "RtAudio.h"
+#include "DemodDefs.h"
+
+class AudioThreadInput: public ReferenceCounter {
+public:
+    long long frequency;
+    int inputRate;
+    int sampleRate;
+    int channels;
+    float peak;
+    int type;
+    std::vector<float> data;
+
+    AudioThreadInput() :
+            frequency(0), sampleRate(0), channels(0), peak(0) {
+
+    }
+
+    ~AudioThreadInput() {
+        std::lock_guard < std::recursive_mutex > lock(m_mutex);
+    }
+};
+
+class AudioThreadCommand {
+public:
+    enum AudioThreadCommandEnum {
+        AUDIO_THREAD_CMD_NULL, AUDIO_THREAD_CMD_SET_DEVICE, AUDIO_THREAD_CMD_SET_SAMPLE_RATE
+    };
+
+    AudioThreadCommand() :
+            cmd(AUDIO_THREAD_CMD_NULL), int_value(0) {
+    }
+
+    AudioThreadCommandEnum cmd;
+    int int_value;
+};
+
+typedef ThreadQueue<AudioThreadInput *> AudioThreadInputQueue;
+typedef ThreadQueue<AudioThreadCommand> AudioThreadCommandQueue;
+
+class AudioThread : public IOThread {
+public:
+    AudioThreadInput *currentInput;
+    AudioThreadInputQueue *inputQueue;
+    std::atomic_uint audioQueuePtr;
+    std::atomic_uint underflowCount;
+    std::atomic_bool initialized;
+    std::atomic_bool active;
+    std::atomic_int outputDevice;
+    float gain;
+
+    AudioThread();
+    ~AudioThread();
+
+    static void enumerateDevices(std::vector<RtAudio::DeviceInfo> &devs);
+
+    void setupDevice(int deviceId);
+    void setInitOutputDevice(int deviceId, int sampleRate=-1);
+    int getOutputDevice();
+    void setSampleRate(int sampleRate);
+    int getSampleRate();
+    virtual void run();
+    virtual void terminate();
+
+    bool isActive();
+    void setActive(bool state);
+
+    void setGain(float gain_in);
+    float getGain();
+
+    AudioThreadCommandQueue *getCommandQueue();
+
+private:
+    RtAudio dac;
+    unsigned int nBufferFrames;
+    RtAudio::StreamOptions opts;
+    RtAudio::StreamParameters parameters;
+    AudioThreadCommandQueue cmdQueue;
+    int sampleRate;
+
+    //The own m_mutex protecting this AudioThread, in particular boundThreads
+    std::recursive_mutex m_mutex;
+
+public:
+    //give access to the this AudioThread lock
+    std::recursive_mutex& getMutex();
+
+    void bindThread(AudioThread *other);
+    void removeThread(AudioThread *other);
+
+    static std::map<int,AudioThread *> deviceController;
+    static std::map<int,int> deviceSampleRate;
+    static std::map<int,std::thread *> deviceThread;
+    static void deviceCleanup();
+    static void setDeviceSampleRate(int deviceId, int sampleRate);
+
+    //protected by m_mutex
+   std::vector<AudioThread *> boundThreads;
+};
+
diff --git a/src/demod/DemodDefs.h b/src/demod/DemodDefs.h
new file mode 100644
index 0000000..dab36b3
--- /dev/null
+++ b/src/demod/DemodDefs.h
@@ -0,0 +1,101 @@
+#pragma once
+
+#include "ThreadQueue.h"
+#include "CubicSDRDefs.h"
+#include "liquid/liquid.h"
+
+#include <atomic>
+#include <mutex>
+
+#include "IOThread.h"
+
+class DemodulatorThread;
+
+
+class DemodulatorThreadControlCommand {
+public:
+    enum DemodulatorThreadControlCommandEnum {
+        DEMOD_THREAD_CMD_CTL_NULL, DEMOD_THREAD_CMD_CTL_SQUELCH_ON, DEMOD_THREAD_CMD_CTL_SQUELCH_OFF, DEMOD_THREAD_CMD_CTL_TYPE
+    };
+
+    DemodulatorThreadControlCommand() :
+            cmd(DEMOD_THREAD_CMD_CTL_NULL), demodType("") {
+    }
+
+    DemodulatorThreadControlCommandEnum cmd;
+    std::string demodType;
+};
+
+class DemodulatorThreadIQData: public ReferenceCounter {
+public:
+    long long frequency;
+    long long sampleRate;
+    std::vector<liquid_float_complex> data;
+   
+
+    DemodulatorThreadIQData() :
+            frequency(0), sampleRate(0) {
+
+    }
+
+    DemodulatorThreadIQData & operator=(const DemodulatorThreadIQData &other) {
+        frequency = other.frequency;
+        sampleRate = other.sampleRate;
+        data.assign(other.data.begin(), other.data.end());
+        return *this;
+    }
+
+    ~DemodulatorThreadIQData() {
+
+    }
+};
+
+class Modem;
+class ModemKit;
+
+class DemodulatorThreadPostIQData: public ReferenceCounter {
+public:
+    std::vector<liquid_float_complex> data;
+    long long sampleRate;
+    std::string modemName;
+    std::string modemType;
+    Modem *modem;
+    ModemKit *modemKit;
+
+    DemodulatorThreadPostIQData() :
+            sampleRate(0), modem(nullptr), modemKit(nullptr) {
+
+    }
+
+    ~DemodulatorThreadPostIQData() {
+        std::lock_guard < std::recursive_mutex > lock(m_mutex);
+    }
+};
+
+
+class DemodulatorThreadAudioData: public ReferenceCounter {
+public:
+    long long frequency;
+    unsigned int sampleRate;
+    unsigned char channels;
+
+    std::vector<float> *data;
+
+    DemodulatorThreadAudioData() :
+            frequency(0), sampleRate(0), channels(0), data(NULL) {
+
+    }
+
+    DemodulatorThreadAudioData(long long frequency, unsigned int sampleRate, std::vector<float> *data) :
+            frequency(frequency), sampleRate(sampleRate), channels(1), data(data) {
+
+    }
+
+    ~DemodulatorThreadAudioData() {
+
+    }
+};
+
+typedef ThreadQueue<DemodulatorThreadIQData *> DemodulatorThreadInputQueue;
+typedef ThreadQueue<DemodulatorThreadPostIQData *> DemodulatorThreadPostInputQueue;
+typedef ThreadQueue<DemodulatorThreadControlCommand> DemodulatorThreadControlCommandQueue;
diff --git a/src/demod/DemodulatorInstance.cpp b/src/demod/DemodulatorInstance.cpp
new file mode 100644
index 0000000..bf3d788
--- /dev/null
+++ b/src/demod/DemodulatorInstance.cpp
@@ -0,0 +1,551 @@
+#include "DemodulatorInstance.h"
+#include "CubicSDR.h"
+
+#if USE_HAMLIB
+#include "RigThread.h"
+#endif
+
+DemodVisualCue::DemodVisualCue() {
+    squelchBreak.store(false);
+}
+
+DemodVisualCue::~DemodVisualCue() {
+    
+}
+
+void DemodVisualCue::triggerSquelchBreak(int counter) {
+    squelchBreak.store(counter);
+}
+
+int DemodVisualCue::getSquelchBreak() {
+    return squelchBreak.load();
+}
+
+void DemodVisualCue::step() {
+    if (squelchBreak.load()) {
+        squelchBreak--;
+        if (squelchBreak.load() < 0) {
+            squelchBreak.store(0);
+        }
+    }
+}
+
+DemodulatorInstance::DemodulatorInstance() {
+
+#if ENABLE_DIGITAL_LAB
+    activeOutput = nullptr;
+#endif
+	
+	active.store(false);
+	squelch.store(false);
+    muted.store(false);
+    deltaLock.store(false);
+    deltaLockOfs.store(0);
+	currentOutputDevice.store(-1);
+    currentAudioGain.store(1.0);
+    follow.store(false);
+    tracking.store(false);
+
+    label.store(new std::string("Unnamed"));
+    user_label.store(new std::wstring());
+
+    pipeIQInputData = new DemodulatorThreadInputQueue;
+    pipeIQDemodData = new DemodulatorThreadPostInputQueue;
+    
+    audioThread = new AudioThread();
+            
+    demodulatorPreThread = new DemodulatorPreThread(this);
+    demodulatorPreThread->setInputQueue("IQDataInput",pipeIQInputData);
+    demodulatorPreThread->setOutputQueue("IQDataOutput",pipeIQDemodData);
+            
+    pipeAudioData = new AudioThreadInputQueue;
+    threadQueueControl = new DemodulatorThreadControlCommandQueue;
+
+    demodulatorThread = new DemodulatorThread(this);
+    demodulatorThread->setInputQueue("IQDataInput",pipeIQDemodData);
+    demodulatorThread->setInputQueue("ControlQueue",threadQueueControl);
+    demodulatorThread->setOutputQueue("AudioDataOutput", pipeAudioData);
+
+    audioThread->setInputQueue("AudioDataInput", pipeAudioData);
+}
+
+DemodulatorInstance::~DemodulatorInstance() {
+#if ENABLE_DIGITAL_LAB
+    delete activeOutput;
+#endif
+    delete audioThread;
+    delete demodulatorThread;
+    delete demodulatorPreThread;
+    delete pipeIQInputData;
+    delete pipeIQDemodData;
+    delete threadQueueControl;
+    delete pipeAudioData;
+}
+
+void DemodulatorInstance::setVisualOutputQueue(DemodulatorThreadOutputQueue *tQueue) {
+    demodulatorThread->setOutputQueue("AudioVisualOutput", tQueue);
+}
+
+void DemodulatorInstance::run() {
+    if (active) {
+        return;
+    }
+
+    t_Audio = new std::thread(&AudioThread::threadMain, audioThread);
+    
+#ifdef __APPLE__    // Already using pthreads, might as well do some custom init..
+    pthread_attr_t attr;
+    size_t size;
+
+    pthread_attr_init(&attr);
+    pthread_attr_setstacksize(&attr, 2048000);
+    pthread_attr_getstacksize(&attr, &size);
+    pthread_create(&t_PreDemod, &attr, &DemodulatorPreThread::pthread_helper, demodulatorPreThread);
+    pthread_attr_destroy(&attr);
+
+    pthread_attr_init(&attr);
+    pthread_attr_setstacksize(&attr, 2048000);
+    pthread_attr_getstacksize(&attr, &size);
+    pthread_create(&t_Demod, &attr, &DemodulatorThread::pthread_helper, demodulatorThread);
+    pthread_attr_destroy(&attr);
+
+//    std::cout << "Initialized demodulator stack size of " << size << std::endl;
+
+#else
+    t_PreDemod = new std::thread(&DemodulatorPreThread::threadMain, demodulatorPreThread);
+    t_Demod = new std::thread(&DemodulatorThread::threadMain, demodulatorThread);
+#endif
+
+    active = true;
+
+}
+
+void DemodulatorInstance::updateLabel(long long freq) {
+    std::stringstream newLabel;
+    newLabel.precision(3);
+    newLabel << std::fixed << ((long double) freq / 1000000.0);
+    setLabel(newLabel.str());
+}
+
+void DemodulatorInstance::terminate() {
+
+#if ENABLE_DIGITAL_LAB
+    if (activeOutput) {
+        closeOutput();
+    }
+#endif
+
+//    std::cout << "Terminating demodulator audio thread.." << std::endl;
+    audioThread->terminate();
+//    std::cout << "Terminating demodulator thread.." << std::endl;
+    demodulatorThread->terminate();
+//    std::cout << "Terminating demodulator preprocessor thread.." << std::endl;
+    demodulatorPreThread->terminate();
+}
+
+std::string DemodulatorInstance::getLabel() {
+    return *(label.load());
+}
+
+void DemodulatorInstance::setLabel(std::string labelStr) {
+   
+    delete label.exchange(new std::string(labelStr));
+}
+
+bool DemodulatorInstance::isTerminated() {
+
+    //
+    bool audioTerminated = audioThread->isTerminated();
+    bool demodTerminated = demodulatorThread->isTerminated();
+    bool preDemodTerminated = demodulatorPreThread->isTerminated();
+
+    //Cleanup the worker threads, if the threads are indeed terminated
+    if (audioTerminated) {
+
+        if (t_Audio) {
+            t_Audio->join();
+
+            delete t_Audio;
+            t_Audio = nullptr;
+        }
+    }
+
+    if (demodTerminated) {
+
+        if (t_Demod) {
+#ifdef __APPLE__
+            pthread_join(t_Demod, nullptr);
+#else
+            t_Demod->join();
+            delete t_Demod;
+#endif
+            t_Demod = nullptr;
+        }
+    }
+
+    if (preDemodTerminated) {
+        
+         if (t_PreDemod) {
+
+#ifdef __APPLE__
+            pthread_join(t_PreDemod, NULL);
+#else
+            t_PreDemod->join();
+            delete t_PreDemod;
+#endif
+            t_PreDemod = nullptr;
+         }
+    }
+
+    bool terminated = audioTerminated && demodTerminated && preDemodTerminated;
+
+    return terminated;
+}
+
+bool DemodulatorInstance::isActive() {
+    return active;
+}
+
+void DemodulatorInstance::setActive(bool state) {
+    if (active && !state) {
+#if ENABLE_DIGITAL_LAB
+        if (activeOutput) {
+            activeOutput->Hide();
+        }
+#endif
+        audioThread->setActive(state);
+        DemodulatorThread::releaseSquelchLock(this);
+    } else if (!active && state) {
+#if ENABLE_DIGITAL_LAB
+        if (activeOutput && getModemType() == "digital") {
+            activeOutput->Show();
+        }
+#endif
+        audioThread->setActive(state);
+    }
+    if (!state) {
+        tracking = false;
+    }
+    active = state;
+}
+
+void DemodulatorInstance::squelchAuto() {
+    DemodulatorThreadControlCommand command;
+    command.cmd = DemodulatorThreadControlCommand::DEMOD_THREAD_CMD_CTL_SQUELCH_ON;
+    threadQueueControl->push(command);
+    squelch = true;
+}
+
+bool DemodulatorInstance::isSquelchEnabled() {
+    return (demodulatorThread->getSquelchLevel() != 0.0);
+}
+
+void DemodulatorInstance::setSquelchEnabled(bool state) {
+    if (!state && squelch) {
+        DemodulatorThreadControlCommand command;
+        command.cmd = DemodulatorThreadControlCommand::DEMOD_THREAD_CMD_CTL_SQUELCH_OFF;
+        threadQueueControl->push(command);
+    } else if (state && !squelch) {
+        DemodulatorThreadControlCommand command;
+        command.cmd = DemodulatorThreadControlCommand::DEMOD_THREAD_CMD_CTL_SQUELCH_ON;
+        threadQueueControl->push(command);
+    }
+
+    squelch = state;
+}
+
+float DemodulatorInstance::getSignalLevel() {
+    return demodulatorThread->getSignalLevel();
+}
+
+float DemodulatorInstance::getSignalFloor() {
+    return demodulatorThread->getSignalFloor();
+}
+
+float DemodulatorInstance::getSignalCeil() {
+    return demodulatorThread->getSignalCeil();
+}
+
+void DemodulatorInstance::setSquelchLevel(float signal_level_in) {
+    demodulatorThread->setSquelchLevel(signal_level_in);
+    wxGetApp().getDemodMgr().setLastSquelchLevel(signal_level_in);
+    wxGetApp().getDemodMgr().setLastSquelchEnabled(true);
+}
+
+float DemodulatorInstance::getSquelchLevel() {
+    return demodulatorThread->getSquelchLevel();
+}
+
+void DemodulatorInstance::setOutputDevice(int device_id) {
+    if (!active) {
+        audioThread->setInitOutputDevice(device_id);
+    } else if (audioThread) {
+        AudioThreadCommand command;
+        command.cmd = AudioThreadCommand::AUDIO_THREAD_CMD_SET_DEVICE;
+        command.int_value = device_id;
+        audioThread->getCommandQueue()->push(command);
+    }
+    setAudioSampleRate(AudioThread::deviceSampleRate[device_id]);
+    currentOutputDevice = device_id;
+}
+
+int DemodulatorInstance::getOutputDevice() {
+    if (currentOutputDevice == -1) {
+        if (audioThread) {
+            currentOutputDevice = audioThread->getOutputDevice();
+        }
+    }
+
+    return currentOutputDevice;
+}
+
+void DemodulatorInstance::setDemodulatorType(std::string demod_type_in) {
+    setGain(getGain());
+    if (demodulatorPreThread) {
+        std::string currentDemodType = demodulatorPreThread->getDemodType();
+        if ((currentDemodType != "") && (currentDemodType != demod_type_in)) {
+            lastModemSettings[currentDemodType] = demodulatorPreThread->readModemSettings();
+            lastModemBandwidth[currentDemodType] = demodulatorPreThread->getBandwidth();
+        }
+#if ENABLE_DIGITAL_LAB
+        if (activeOutput) {
+            activeOutput->Hide();
+        }
+#endif
+
+        demodulatorPreThread->setDemodType(demod_type_in);
+        int lastbw = 0;
+        if (currentDemodType != "" && lastModemBandwidth.find(demod_type_in) != lastModemBandwidth.end()) {
+            lastbw = lastModemBandwidth[demod_type_in];
+        }
+        if (!lastbw) {
+            lastbw = Modem::getModemDefaultSampleRate(demod_type_in);
+        }
+        if (lastbw) {
+            setBandwidth(lastbw);
+        }
+
+#if ENABLE_DIGITAL_LAB
+        if (isModemInitialized() && getModemType() == "digital") {
+            ModemDigitalOutputConsole *outp = (ModemDigitalOutputConsole *)getOutput();
+            outp->setTitle(getDemodulatorType() + ": " + frequencyToStr(getFrequency()));
+        }
+#endif
+}
+}
+
+std::string DemodulatorInstance::getDemodulatorType() {
+    return demodulatorPreThread->getDemodType();
+}
+
+std::wstring DemodulatorInstance::getDemodulatorUserLabel() {
+    return *(user_label.load());
+}
+
+void DemodulatorInstance::setDemodulatorUserLabel(const std::wstring& demod_user_label) {
+   
+    delete user_label.exchange(new std::wstring(demod_user_label));
+}
+
+void DemodulatorInstance::setDemodulatorLock(bool demod_lock_in) {
+    Modem *cModem = demodulatorPreThread->getModem();
+    if (cModem && cModem->getType() == "digital") {
+        ((ModemDigital *)cModem)->setDemodulatorLock(demod_lock_in);
+    }
+}
+
+int DemodulatorInstance::getDemodulatorLock() {
+    Modem *cModem = demodulatorPreThread->getModem();
+
+    if (cModem && cModem->getType() == "digital") {
+        return ((ModemDigital *)cModem)->getDemodulatorLock();
+    }
+
+    return 0;
+}
+
+void DemodulatorInstance::setBandwidth(int bw) {
+    demodulatorPreThread->setBandwidth(bw);
+}
+
+int DemodulatorInstance::getBandwidth() {
+    return demodulatorPreThread->getBandwidth();
+}
+
+void DemodulatorInstance::setFrequency(long long freq) {
+    if ((freq - getBandwidth() / 2) <= 0) {
+        freq = getBandwidth() / 2;
+    }
+    
+    demodulatorPreThread->setFrequency(freq);
+#if ENABLE_DIGITAL_LAB
+    if (activeOutput) {
+        if (isModemInitialized() && getModemType() == "digital") {
+            ModemDigitalOutputConsole *outp = (ModemDigitalOutputConsole *)getOutput();
+            outp->setTitle(getDemodulatorType() + ": " + frequencyToStr(getFrequency()));
+        }
+    }
+#endif
+#if USE_HAMLIB
+    if (wxGetApp().rigIsActive() && wxGetApp().getRigThread()->getFollowModem() && wxGetApp().getDemodMgr().getLastActiveDemodulator() == this) {
+        wxGetApp().getRigThread()->setFrequency(freq,true);
+    }
+#endif
+}
+
+long long DemodulatorInstance::getFrequency() {
+    return demodulatorPreThread->getFrequency();
+}
+
+void DemodulatorInstance::setAudioSampleRate(int sampleRate) {
+    demodulatorPreThread->setSampleRate(sampleRate);
+}
+
+int DemodulatorInstance::getAudioSampleRate() {
+    if (!audioThread) {
+        return 0;
+    }
+    return audioThread->getSampleRate();
+}
+
+
+void DemodulatorInstance::setGain(float gain_in) {
+	currentAudioGain = gain_in;
+    audioThread->setGain(gain_in);
+}
+
+float DemodulatorInstance::getGain() {
+   return currentAudioGain;
+}
+
+bool DemodulatorInstance::isFollow()  {
+    return follow;
+}
+
+void DemodulatorInstance::setFollow(bool follow) {
+    this->follow = follow;
+}
+
+bool DemodulatorInstance::isTracking()  {
+    return tracking;
+}
+
+void DemodulatorInstance::setTracking(bool tracking) {
+    this->tracking = tracking;
+}
+
+bool DemodulatorInstance::isDeltaLock() {
+    return deltaLock.load();
+}
+
+void DemodulatorInstance::setDeltaLock(bool lock) {
+    deltaLock.store(lock);
+}
+
+void DemodulatorInstance::setDeltaLockOfs(int lockOfs) {
+    deltaLockOfs.store(lockOfs);
+}
+
+int DemodulatorInstance::getDeltaLockOfs() {
+    return deltaLockOfs.load();
+}
+
+bool DemodulatorInstance::isMuted() {
+    return demodulatorThread->isMuted();
+}
+
+void DemodulatorInstance::setMuted(bool muted) {
+    this->muted = muted;
+    demodulatorThread->setMuted(muted);
+    wxGetApp().getDemodMgr().setLastMuted(muted);
+}
+
+DemodVisualCue *DemodulatorInstance::getVisualCue() {
+    return &visualCue;
+}
+
+DemodulatorThreadInputQueue *DemodulatorInstance::getIQInputDataPipe() {
+    return pipeIQInputData;
+}
+
+ModemArgInfoList DemodulatorInstance::getModemArgs() {
+    Modem *m = demodulatorPreThread->getModem();
+    
+    ModemArgInfoList args;
+    if (m != nullptr) {
+        args = m->getSettings();
+    }
+    return args;
+}
+
+std::string DemodulatorInstance::readModemSetting(std::string setting) {
+    return demodulatorPreThread->readModemSetting(setting);
+}
+
+void DemodulatorInstance::writeModemSetting(std::string setting, std::string value) {
+    demodulatorPreThread->writeModemSetting(setting, value);
+}
+
+ModemSettings DemodulatorInstance::readModemSettings() {
+    return demodulatorPreThread->readModemSettings();
+}
+
+void DemodulatorInstance::writeModemSettings(ModemSettings settings) {
+    demodulatorPreThread->writeModemSettings(settings);
+}
+
+bool DemodulatorInstance::isModemInitialized() {
+    if (!demodulatorPreThread || isTerminated()) {
+        return false;
+    }
+    return demodulatorPreThread->isInitialized();
+}
+
+std::string DemodulatorInstance::getModemType() {
+    if (isModemInitialized()) {
+        return demodulatorPreThread->getModem()->getType();
+    }
+    return "";
+}
+
+ModemSettings DemodulatorInstance::getLastModemSettings(std::string demodType) {
+    if (lastModemSettings.find(demodType) != lastModemSettings.end()) {
+        return lastModemSettings[demodType];
+    } else {
+        ModemSettings mods;
+        return mods;
+    }
+}
+
+#if ENABLE_DIGITAL_LAB
+ModemDigitalOutput *DemodulatorInstance::getOutput() {
+    if (activeOutput == nullptr) {
+        activeOutput = new ModemDigitalOutputConsole();
+    }
+    return activeOutput;
+}
+
+void DemodulatorInstance::showOutput() {
+    if (activeOutput != nullptr) {
+        activeOutput->Show();
+    }
+}
+
+void DemodulatorInstance::hideOutput() {
+    if (activeOutput != nullptr) {
+        activeOutput->Hide();
+    }
+}
+
+void DemodulatorInstance::closeOutput() {
+    if (isModemInitialized()) {
+        if (getModemType() == "digital") {
+            ModemDigital *dModem = (ModemDigital *)demodulatorPreThread->getModem();
+            dModem->setOutput(nullptr);
+        }
+    }
+    if (activeOutput) {
+        activeOutput->Close();
+    }
+}
+#endif
diff --git a/src/demod/DemodulatorInstance.h b/src/demod/DemodulatorInstance.h
new file mode 100644
index 0000000..554ed48
--- /dev/null
+++ b/src/demod/DemodulatorInstance.h
@@ -0,0 +1,159 @@
+#pragma once
+
+#include <vector>
+#include <map>
+#include <thread>
+
+#include "DemodulatorThread.h"
+#include "DemodulatorPreThread.h"
+
+#include "ModemDigital.h"
+#include "ModemAnalog.h"
+
+#if ENABLE_DIGITAL_LAB
+#include "DigitalConsole.h"
+#endif
+
+class DemodVisualCue {
+public:
+    DemodVisualCue();
+    ~DemodVisualCue();
+    
+    void triggerSquelchBreak(int counter);
+    int getSquelchBreak();
+    
+    void step();
+private:
+    std::atomic_int squelchBreak;
+};
+
+
+class DemodulatorInstance {
+public:
+
+#ifdef __APPLE__
+    pthread_t t_PreDemod;
+    pthread_t t_Demod;
+#else
+    std::thread *t_PreDemod = nullptr;
+    std::thread *t_Demod = nullptr;
+#endif
+
+    AudioThread *audioThread = nullptr;
+    std::thread *t_Audio = nullptr;
+
+    DemodulatorInstance();
+    ~DemodulatorInstance();
+
+    void setVisualOutputQueue(DemodulatorThreadOutputQueue *tQueue);
+
+    void run();
+    void terminate();
+    std::string getLabel();
+    void setLabel(std::string labelStr);
+
+    bool isTerminated();
+    void updateLabel(long long freq);
+
+    bool isActive();
+    void setActive(bool state);
+
+    void squelchAuto();
+    bool isSquelchEnabled();
+    void setSquelchEnabled(bool state);
+
+    float getSignalLevel();
+    float getSignalFloor();
+    float getSignalCeil();
+    void setSquelchLevel(float signal_level_in);
+    float getSquelchLevel();
+
+    void setOutputDevice(int device_id);
+    int getOutputDevice();
+
+    void setDemodulatorType(std::string demod_type_in);
+    std::string getDemodulatorType();
+
+    std::wstring getDemodulatorUserLabel();
+    void setDemodulatorUserLabel(const std::wstring& demod_user_label);
+ 
+    void setDemodulatorLock(bool demod_lock_in);
+    int getDemodulatorLock();
+
+    void setBandwidth(int bw);
+    int getBandwidth();
+
+    void setGain(float gain_in);
+    float getGain();
+
+    void setFrequency(long long freq);
+    long long getFrequency();
+
+    void setAudioSampleRate(int sampleRate);
+    int getAudioSampleRate();
+    
+    bool isFollow();
+    void setFollow(bool follow);
+
+    bool isTracking();
+    void setTracking(bool tracking);
+
+    bool isDeltaLock();
+    void setDeltaLock(bool lock);
+    void setDeltaLockOfs(int lockOfs);
+    int getDeltaLockOfs();
+
+    bool isMuted();
+    void setMuted(bool muted);
+
+    DemodVisualCue *getVisualCue();
+    
+    DemodulatorThreadInputQueue *getIQInputDataPipe();
+
+    ModemArgInfoList getModemArgs();
+    std::string readModemSetting(std::string setting);
+    ModemSettings readModemSettings();
+    void writeModemSetting(std::string setting, std::string value);
+    void writeModemSettings(ModemSettings settings);
+    
+    bool isModemInitialized();
+    std::string getModemType();
+    ModemSettings getLastModemSettings(std::string demodType);
+
+#if ENABLE_DIGITAL_LAB
+    ModemDigitalOutput *getOutput();
+    void showOutput();
+    void hideOutput();
+    void closeOutput();
+#endif
+        
+protected:
+    DemodulatorThreadInputQueue* pipeIQInputData;
+    DemodulatorThreadPostInputQueue* pipeIQDemodData;
+    AudioThreadInputQueue *pipeAudioData;
+    DemodulatorPreThread *demodulatorPreThread;
+    DemodulatorThread *demodulatorThread;
+    DemodulatorThreadControlCommandQueue *threadQueueControl;
+
+private:
+
+    std::atomic<std::string *> label; //
+    // User editable buffer, 16 bit string.
+    std::atomic<std::wstring *> user_label; 
+
+    std::atomic_bool active;
+    std::atomic_bool squelch;
+    std::atomic_bool muted;
+    std::atomic_bool deltaLock;
+    std::atomic_int deltaLockOfs;
+
+    std::atomic_int currentOutputDevice;
+    std::atomic<float> currentAudioGain;
+    std::atomic_bool follow, tracking;
+    std::map<std::string, ModemSettings> lastModemSettings;
+    std::map<std::string, int> lastModemBandwidth;
+    DemodVisualCue visualCue;
+#if ENABLE_DIGITAL_LAB
+    ModemDigitalOutput *activeOutput;
+#endif
+};
diff --git a/src/demod/DemodulatorMgr.cpp b/src/demod/DemodulatorMgr.cpp
new file mode 100644
index 0000000..61be65c
--- /dev/null
+++ b/src/demod/DemodulatorMgr.cpp
@@ -0,0 +1,370 @@
+#include <DemodulatorMgr.h>
+#include <sstream>
+#include <algorithm>
+#include "CubicSDR.h"
+#include <string>
+#include <sstream>
+#include <algorithm>
+
+#if USE_HAMLIB
+#include "RigThread.h"
+#endif
+
+bool demodFreqCompare (DemodulatorInstance *i, DemodulatorInstance *j) { return (i->getFrequency()<j->getFrequency()); }
+bool inactiveCompare (DemodulatorInstance *i, DemodulatorInstance *j) { return (i->isActive()<j->isActive()); }
+
+DemodulatorMgr::DemodulatorMgr() {
+    activeDemodulator = NULL;
+    lastActiveDemodulator = NULL;
+    activeVisualDemodulator = NULL;
+    lastBandwidth = DEFAULT_DEMOD_BW;
+    lastDemodType = DEFAULT_DEMOD_TYPE;
+    lastSquelchEnabled = false;
+    lastSquelch = -100;
+    lastGain = 1.0;
+    lastMuted = false;
+    lastDeltaLock = false;
+}
+
+DemodulatorMgr::~DemodulatorMgr() {
+    terminateAll();
+}
+
+DemodulatorInstance *DemodulatorMgr::newThread() {
+    std::lock_guard < std::recursive_mutex > lock(demods_busy);
+    DemodulatorInstance *newDemod = new DemodulatorInstance;
+
+    std::stringstream label;
+    label << demods.size();
+    newDemod->setLabel(label.str());
+    
+    demods.push_back(newDemod);
+    
+    return newDemod;
+}
+
+void DemodulatorMgr::terminateAll() {
+    std::lock_guard < std::recursive_mutex > lock(demods_busy);
+    while (demods.size()) {
+
+        DemodulatorInstance *d = demods.back();
+        demods.pop_back();
+        wxGetApp().removeDemodulator(d);
+        deleteThread(d);
+    }
+}
+
+std::vector<DemodulatorInstance *> &DemodulatorMgr::getDemodulators() {
+    std::lock_guard < std::recursive_mutex > lock(demods_busy);
+    return demods;
+}
+
+std::vector<DemodulatorInstance *> DemodulatorMgr::getOrderedDemodulators(bool actives) {
+    std::lock_guard < std::recursive_mutex > lock(demods_busy);
+    std::vector<DemodulatorInstance *> demods_ordered = demods;
+    if (actives) {
+        std::sort(demods_ordered.begin(), demods_ordered.end(), inactiveCompare);
+        std::vector<DemodulatorInstance *>::iterator i;
+        for (i = demods_ordered.begin(); i != demods_ordered.end(); i++) {
+            if ((*i)->isActive()) {
+                break;
+            }
+        }
+        if (i == demods_ordered.end()) {
+            demods_ordered.erase(demods_ordered.begin(), demods_ordered.end());
+        } else if ((*i) != demods_ordered.front()) {
+            demods_ordered.erase(demods_ordered.begin(), i);
+        }
+    }
+    //if by chance they have the same frequency, keep their relative order
+    std::stable_sort(demods_ordered.begin(), demods_ordered.end(), demodFreqCompare);
+    return demods_ordered;
+}
+
+DemodulatorInstance *DemodulatorMgr::getPreviousDemodulator(DemodulatorInstance *demod, bool actives) {
+    std::lock_guard < std::recursive_mutex > lock(demods_busy);
+    if (!getLastActiveDemodulator()) {
+        return nullptr;
+    }
+    std::vector<DemodulatorInstance *> demods_ordered = getOrderedDemodulators(actives);
+    std::vector<DemodulatorInstance *>::iterator p = std::find(demods_ordered.begin(), demods_ordered.end(), demod);
+    if (p == demods_ordered.end()) {
+        return nullptr;
+    }
+    if (*p == demods_ordered.front()) {
+        return demods_ordered.back();
+    }
+    return *(--p);
+}
+
+DemodulatorInstance *DemodulatorMgr::getNextDemodulator(DemodulatorInstance *demod, bool actives) {
+    std::lock_guard < std::recursive_mutex > lock(demods_busy);
+    if (!getLastActiveDemodulator()) {
+        return nullptr;
+    }
+    std::vector<DemodulatorInstance *> demods_ordered = getOrderedDemodulators(actives);
+    std::vector<DemodulatorInstance *>::iterator p = std::find(demods_ordered.begin(), demods_ordered.end(), demod);
+    if (actives) {
+        
+    }
+    if (p == demods_ordered.end()) {
+        return nullptr;
+    }
+    if (*p == demods_ordered.back()) {
+        return demods_ordered.front();
+    }
+    return *(++p);
+}
+
+DemodulatorInstance *DemodulatorMgr::getLastDemodulator() {
+    std::lock_guard < std::recursive_mutex > lock(demods_busy);
+    std::vector<DemodulatorInstance *> demods_ordered = getOrderedDemodulators();
+    return *(demods_ordered.end());
+}
+
+DemodulatorInstance *DemodulatorMgr::getFirstDemodulator() {
+    std::lock_guard < std::recursive_mutex > lock(demods_busy);
+    std::vector<DemodulatorInstance *> demods_ordered = getOrderedDemodulators();
+    return *(demods_ordered.begin());
+}
+
+void DemodulatorMgr::deleteThread(DemodulatorInstance *demod) {
+    std::lock_guard < std::recursive_mutex > lock(demods_busy);
+    
+    std::vector<DemodulatorInstance *>::iterator i;
+
+    i = std::find(demods.begin(), demods.end(), demod);
+
+    if (activeDemodulator == demod) {
+        activeDemodulator = nullptr;
+    }
+    if (lastActiveDemodulator == demod) {
+        lastActiveDemodulator = nullptr;
+    }
+    if (activeVisualDemodulator == demod) {
+        activeVisualDemodulator = nullptr;
+    }
+
+    if (i != demods.end()) {
+        demods.erase(i);
+    }
+
+    //Ask for termination
+    demod->setActive(false);
+    demod->terminate();
+
+    //Do not cleanup immediatly
+    demods_deleted.push_back(demod);
+}
+
+std::vector<DemodulatorInstance *> DemodulatorMgr::getDemodulatorsAt(long long freq, int bandwidth) {
+    std::lock_guard < std::recursive_mutex > lock(demods_busy);
+    
+    std::vector<DemodulatorInstance *> foundDemods;
+
+    for (int i = 0, iMax = demods.size(); i < iMax; i++) {
+        DemodulatorInstance *testDemod = demods[i];
+
+        long long freqTest = testDemod->getFrequency();
+        long long bandwidthTest = testDemod->getBandwidth();
+        long long halfBandwidthTest = bandwidthTest / 2;
+
+        long long halfBuffer = bandwidth / 2;
+
+        if ((freq <= (freqTest + ((testDemod->getDemodulatorType() != "LSB")?halfBandwidthTest:0) + halfBuffer)) && (freq >= (freqTest - ((testDemod->getDemodulatorType() != "USB")?halfBandwidthTest:0) - halfBuffer))) {
+            foundDemods.push_back(testDemod);
+        }
+    }
+
+    return foundDemods;
+}
+
+bool DemodulatorMgr::anyDemodulatorsAt(long long freq, int bandwidth) {
+    std::lock_guard < std::recursive_mutex > lock(demods_busy);
+    for (int i = 0, iMax = demods.size(); i < iMax; i++) {
+        DemodulatorInstance *testDemod = demods[i];
+
+        long long freqTest = testDemod->getFrequency();
+        long long bandwidthTest = testDemod->getBandwidth();
+        long long halfBandwidthTest = bandwidthTest / 2;
+        
+        long long halfBuffer = bandwidth / 2;
+        
+        if ((freq <= (freqTest + ((testDemod->getDemodulatorType() != "LSB")?halfBandwidthTest:0) + halfBuffer)) && (freq >= (freqTest - ((testDemod->getDemodulatorType() != "USB")?halfBandwidthTest:0) - halfBuffer))) {
+           
+            return true;
+        }
+    }
+
+    return false;
+}
+
+
+void DemodulatorMgr::setActiveDemodulator(DemodulatorInstance *demod, bool temporary) {
+    
+    if (!temporary) {
+        if (activeDemodulator.load() != nullptr) {
+            lastActiveDemodulator = activeDemodulator.load();
+            updateLastState();
+        } else {
+            lastActiveDemodulator = demod;
+        }
+        updateLastState();
+#if USE_HAMLIB
+        if (wxGetApp().rigIsActive() && wxGetApp().getRigThread()->getFollowModem() && lastActiveDemodulator.load()) {
+            wxGetApp().getRigThread()->setFrequency(lastActiveDemodulator.load()->getFrequency(),true);
+        }
+#endif
+    } else {
+        std::lock_guard < std::recursive_mutex > lock(demods_busy);
+        garbageCollect();
+        ReBufferGC::garbageCollect();
+    }
+
+    if (activeVisualDemodulator.load()) {
+        activeVisualDemodulator.load()->setVisualOutputQueue(nullptr);
+    }
+    if (demod) {
+        demod->setVisualOutputQueue(wxGetApp().getAudioVisualQueue());
+        activeVisualDemodulator = demod;
+    } else {
+        DemodulatorInstance *last = getLastActiveDemodulator();
+        if (last) {
+            last->setVisualOutputQueue(wxGetApp().getAudioVisualQueue());
+        }
+        activeVisualDemodulator = last;
+    }
+
+    activeDemodulator = demod;
+
+}
+
+DemodulatorInstance *DemodulatorMgr::getActiveDemodulator() {
+    if (activeDemodulator.load() && !activeDemodulator.load()->isActive()) {
+        activeDemodulator = getLastActiveDemodulator();
+    }
+    return activeDemodulator;
+}
+
+DemodulatorInstance *DemodulatorMgr::getLastActiveDemodulator() {
+    return lastActiveDemodulator;
+}
+
+//Private internal method, no need to protect it with demods_busy
+void DemodulatorMgr::garbageCollect() {
+    if (demods_deleted.size()) {
+      
+        std::vector<DemodulatorInstance *>::iterator i;
+
+        for (i = demods_deleted.begin(); i != demods_deleted.end(); i++) {
+            if ((*i)->isTerminated()) {
+                DemodulatorInstance *deleted = (*i);
+                demods_deleted.erase(i);
+
+                std::cout << "Garbage collected demodulator instance " << deleted->getLabel() << std::endl;
+
+                delete deleted;
+                return;
+            }
+        }
+      
+    }
+}
+
+void DemodulatorMgr::updateLastState() {
+    std::lock_guard < std::recursive_mutex > lock(demods_busy);
+
+    if (std::find(demods.begin(), demods.end(), lastActiveDemodulator) == demods.end()) {
+        if (activeDemodulator.load() && activeDemodulator.load()->isActive()) {
+            lastActiveDemodulator = activeDemodulator.load();
+        } else if (activeDemodulator.load() && !activeDemodulator.load()->isActive()){
+            activeDemodulator = nullptr;
+            lastActiveDemodulator = nullptr;
+        }
+    }
+
+    if (lastActiveDemodulator.load() && !lastActiveDemodulator.load()->isActive()) {
+        lastActiveDemodulator = nullptr;
+    }
+
+    if (lastActiveDemodulator.load()) {
+        lastBandwidth = lastActiveDemodulator.load()->getBandwidth();
+        lastDemodType = lastActiveDemodulator.load()->getDemodulatorType();
+        lastDemodLock = lastActiveDemodulator.load()->getDemodulatorLock()?true:false;
+        lastSquelchEnabled = lastActiveDemodulator.load()->isSquelchEnabled();
+        lastSquelch = lastActiveDemodulator.load()->getSquelchLevel();
+        lastGain = lastActiveDemodulator.load()->getGain();
+        lastModemSettings[lastDemodType] = lastActiveDemodulator.load()->readModemSettings();
+    }
+
+}
+
+int DemodulatorMgr::getLastBandwidth() const {
+    return lastBandwidth;
+}
+
+void DemodulatorMgr::setLastBandwidth(int lastBandwidth) {
+    if (lastBandwidth < MIN_BANDWIDTH) {
+        lastBandwidth = MIN_BANDWIDTH;
+    } else  if (lastBandwidth > wxGetApp().getSampleRate()) {
+        lastBandwidth = wxGetApp().getSampleRate();
+    }
+    this->lastBandwidth = lastBandwidth;
+}
+
+std::string DemodulatorMgr::getLastDemodulatorType() const {
+    return lastDemodType;
+}
+
+void DemodulatorMgr::setLastDemodulatorType(std::string lastDemodType) {
+    this->lastDemodType = lastDemodType;
+}
+
+float DemodulatorMgr::getLastGain() const {
+    return lastGain;
+}
+
+void DemodulatorMgr::setLastGain(float lastGain) {
+    this->lastGain = lastGain;
+}
+
+
+bool DemodulatorMgr::getLastDeltaLock() const {
+    return lastDeltaLock;
+}
+
+void DemodulatorMgr::setLastDeltaLock(bool lock) {
+    lastDeltaLock = lock;
+}
+
+float DemodulatorMgr::getLastSquelchLevel() const {
+    return lastSquelch;
+}
+
+void DemodulatorMgr::setLastSquelchLevel(float lastSquelch) {
+    this->lastSquelch = lastSquelch;
+}
+
+bool DemodulatorMgr::isLastSquelchEnabled() const {
+    return lastSquelchEnabled;
+}
+
+void DemodulatorMgr::setLastSquelchEnabled(bool lastSquelchEnabled) {
+    this->lastSquelchEnabled = lastSquelchEnabled;
+}
+
+bool DemodulatorMgr::isLastMuted() const {
+    return lastMuted;
+}
+
+void DemodulatorMgr::setLastMuted(bool lastMuted) {
+    this->lastMuted = lastMuted;
+}
+
+ModemSettings DemodulatorMgr::getLastModemSettings(std::string modemType) {
+    return lastModemSettings[modemType];
+}
+
+void DemodulatorMgr::setLastModemSettings(std::string modemType, ModemSettings settings) {
+    lastModemSettings[modemType] = settings;
+}
diff --git a/src/demod/DemodulatorMgr.h b/src/demod/DemodulatorMgr.h
new file mode 100644
index 0000000..a993f84
--- /dev/null
+++ b/src/demod/DemodulatorMgr.h
@@ -0,0 +1,82 @@
+#pragma once
+
+#include <vector>
+#include <map>
+#include <thread>
+
+#include "DemodulatorInstance.h"
+
+class DemodulatorMgr {
+public:
+    DemodulatorMgr();
+    ~DemodulatorMgr();
+
+    DemodulatorInstance *newThread();
+    std::vector<DemodulatorInstance *> &getDemodulators();
+    std::vector<DemodulatorInstance *> getOrderedDemodulators(bool actives = true);
+    std::vector<DemodulatorInstance *> getDemodulatorsAt(long long freq, int bandwidth);
+    DemodulatorInstance *getPreviousDemodulator(DemodulatorInstance *demod, bool actives = true);
+    DemodulatorInstance *getNextDemodulator(DemodulatorInstance *demod, bool actives = true);
+    DemodulatorInstance *getLastDemodulator();
+    DemodulatorInstance *getFirstDemodulator();
+    bool anyDemodulatorsAt(long long freq, int bandwidth);
+    void deleteThread(DemodulatorInstance *);
+
+    void terminateAll();
+
+    void setActiveDemodulator(DemodulatorInstance *demod, bool temporary = true);
+    DemodulatorInstance *getActiveDemodulator();
+    DemodulatorInstance *getLastActiveDemodulator();
+
+    int getLastBandwidth() const;
+    void setLastBandwidth(int lastBandwidth);
+
+    std::string getLastDemodulatorType() const;
+    void setLastDemodulatorType(std::string lastDemodType);
+
+    float getLastGain() const;
+    void setLastGain(float lastGain);
+
+    bool getLastDeltaLock() const;
+    void setLastDeltaLock(bool lock);
+
+    float getLastSquelchLevel() const;
+    void setLastSquelchLevel(float lastSquelch);
+
+    bool isLastSquelchEnabled() const;
+    void setLastSquelchEnabled(bool lastSquelchEnabled);
+    
+    bool isLastMuted() const;
+    void setLastMuted(bool lastMuted);
+
+    ModemSettings getLastModemSettings(std::string);
+    void setLastModemSettings(std::string, ModemSettings);
+
+    void updateLastState();
+    
+private:
+    
+    void garbageCollect();
+
+    std::vector<DemodulatorInstance *> demods;
+    std::vector<DemodulatorInstance *> demods_deleted;
+    
+    std::atomic<DemodulatorInstance *> activeDemodulator;
+    std::atomic<DemodulatorInstance *> lastActiveDemodulator;
+    std::atomic<DemodulatorInstance *> activeVisualDemodulator;
+
+    int lastBandwidth;
+    std::string lastDemodType;
+    bool lastDemodLock;
+    bool lastSquelchEnabled;
+    float lastSquelch;
+    float lastGain;
+    bool lastMuted;
+    bool lastDeltaLock;
+    
+    //protects access to demods lists and such, need to be recursive
+    //because of the usage of public re-entrant methods 
+    std::recursive_mutex demods_busy;
+    
+    std::map<std::string, ModemSettings> lastModemSettings;
+};
diff --git a/src/demod/DemodulatorPreThread.cpp b/src/demod/DemodulatorPreThread.cpp
new file mode 100644
index 0000000..e4286fe
--- /dev/null
+++ b/src/demod/DemodulatorPreThread.cpp
@@ -0,0 +1,401 @@
+#include "CubicSDRDefs.h"
+#include <vector>
+
+#ifdef __APPLE__
+#include <pthread.h>
+#endif
+
+#include "DemodulatorPreThread.h"
+#include "CubicSDR.h"
+#include "DemodulatorInstance.h"
+
+DemodulatorPreThread::DemodulatorPreThread(DemodulatorInstance *parent) : IOThread(), iqResampler(NULL), iqResampleRatio(1), cModem(nullptr), cModemKit(nullptr), iqInputQueue(NULL), iqOutputQueue(NULL)
+ {
+	initialized.store(false);
+    this->parent = parent;
+
+    freqShifter = nco_crcf_create(LIQUID_VCO);
+    shiftFrequency = 0;
+
+    workerQueue = new DemodulatorThreadWorkerCommandQueue;
+    workerResults = new DemodulatorThreadWorkerResultQueue;
+     
+    workerThread = new DemodulatorWorkerThread();
+    workerThread->setInputQueue("WorkerCommandQueue",workerQueue);
+    workerThread->setOutputQueue("WorkerResultQueue",workerResults);
+     
+    newSampleRate = currentSampleRate = 0;
+    newBandwidth = currentBandwidth = 0;
+    newAudioSampleRate = currentAudioSampleRate = 0;
+    newFrequency = currentFrequency = 0;
+
+    sampleRateChanged.store(false);
+    frequencyChanged.store(false);
+    bandwidthChanged.store(false);
+    audioSampleRateChanged.store(false);
+    modemSettingsChanged.store(false);
+    demodTypeChanged.store(false);
+}
+
+bool DemodulatorPreThread::isInitialized() {
+    return initialized.load();
+}
+
+DemodulatorPreThread::~DemodulatorPreThread() {
+}
+
+void DemodulatorPreThread::run() {
+#ifdef __APPLE__
+    pthread_t tID = pthread_self();  // ID of this thread
+    int priority = sched_get_priority_max( SCHED_FIFO) - 1;
+    sched_param prio = {priority}; // scheduling priority of thread
+    pthread_setschedparam(tID, SCHED_FIFO, &prio);
+#endif
+
+//    std::cout << "Demodulator preprocessor thread started.." << std::endl;
+
+    ReBuffer<DemodulatorThreadPostIQData> buffers("DemodulatorPreThreadBuffers");
+
+    iqInputQueue = static_cast<DemodulatorThreadInputQueue*>(getInputQueue("IQDataInput"));
+    iqOutputQueue = static_cast<DemodulatorThreadPostInputQueue*>(getOutputQueue("IQDataOutput"));
+    
+    std::vector<liquid_float_complex> in_buf_data;
+    std::vector<liquid_float_complex> out_buf_data;
+
+    t_Worker = new std::thread(&DemodulatorWorkerThread::threadMain, workerThread);
+    
+    while (!stopping) {
+        DemodulatorThreadIQData *inp;
+
+        iqInputQueue->pop(inp);
+        
+        if (frequencyChanged.load()) {
+            currentFrequency = newFrequency;
+            frequencyChanged.store(false);
+        }
+        
+        if (inp->sampleRate != currentSampleRate) {
+            newSampleRate = inp->sampleRate;
+            if (newSampleRate) {
+                sampleRateChanged.store(true);
+            }
+        }
+        
+        if (!newAudioSampleRate) {
+            newAudioSampleRate = parent->getAudioSampleRate();
+            if (newAudioSampleRate) {
+                audioSampleRateChanged.store(true);
+            }
+        } else if (parent->getAudioSampleRate() != newAudioSampleRate) {
+            int newRate;
+            if ((newRate = parent->getAudioSampleRate())) {
+                newAudioSampleRate = parent->getAudioSampleRate();
+                audioSampleRateChanged.store(true);
+            }
+        }
+        
+        if (demodTypeChanged.load() && (newSampleRate && newAudioSampleRate && newBandwidth)) {
+            DemodulatorWorkerThreadCommand command(DemodulatorWorkerThreadCommand::DEMOD_WORKER_THREAD_CMD_MAKE_DEMOD);
+            command.frequency = newFrequency;
+            command.sampleRate = newSampleRate;
+            command.demodType = newDemodType;
+            command.bandwidth = newBandwidth;
+            command.audioSampleRate = newAudioSampleRate;
+            demodType = newDemodType;
+            sampleRateChanged.store(false);
+            audioSampleRateChanged.store(false);
+            ModemSettings lastSettings = parent->getLastModemSettings(newDemodType);
+            if (lastSettings.size() != 0) {
+                command.settings = lastSettings;
+                if (modemSettingsBuffered.size()) {
+                    for (ModemSettings::const_iterator msi = modemSettingsBuffered.begin(); msi != modemSettingsBuffered.end(); msi++) {
+                        command.settings[msi->first] = msi->second;
+                    }
+                }
+            } else {
+                command.settings = modemSettingsBuffered;
+            }
+            modemSettingsBuffered.clear();
+            modemSettingsChanged.store(false);
+            workerQueue->push(command);
+            cModem = nullptr;
+            cModemKit = nullptr;
+            demodTypeChanged.store(false);
+            initialized.store(false);
+        }
+        else if (
+            cModemKit && cModem &&
+            (bandwidthChanged.load() || sampleRateChanged.load() || audioSampleRateChanged.load() || cModem->shouldRebuildKit()) &&
+            (newSampleRate && newAudioSampleRate && newBandwidth)
+        ) {
+            DemodulatorWorkerThreadCommand command(DemodulatorWorkerThreadCommand::DEMOD_WORKER_THREAD_CMD_BUILD_FILTERS);
+            command.frequency = newFrequency;
+            command.sampleRate = newSampleRate;
+            command.bandwidth = newBandwidth;
+            command.audioSampleRate = newAudioSampleRate;
+            bandwidthChanged.store(false);
+            sampleRateChanged.store(false);
+            audioSampleRateChanged.store(false);
+            modemSettingsBuffered.clear();
+            workerQueue->push(command);
+        }
+        
+        // Requested frequency is not center, shift it into the center!
+        if ((currentFrequency - inp->frequency) != shiftFrequency) {
+            shiftFrequency = currentFrequency - inp->frequency;
+            if (abs(shiftFrequency) <= (int) ((double) (inp->sampleRate / 2) * 1.5)) {
+                nco_crcf_set_frequency(freqShifter, (2.0 * M_PI) * (((double) abs(shiftFrequency)) / ((double) inp->sampleRate)));
+            }
+        }
+
+        if (cModem && cModemKit && abs(shiftFrequency) > (int) ((double) (inp->sampleRate / 2) * 1.5)) {
+            inp->decRefCount();
+            continue;
+        }
+
+//        std::lock_guard < std::mutex > lock(inp->m_mutex);
+        std::vector<liquid_float_complex> *data = &inp->data;
+        if (data->size() && (inp->sampleRate == currentSampleRate) && cModem && cModemKit) {
+            size_t bufSize = data->size();
+
+            if (in_buf_data.size() != bufSize) {
+                if (in_buf_data.capacity() < bufSize) {
+                    in_buf_data.reserve(bufSize);
+                    out_buf_data.reserve(bufSize);
+                }
+                in_buf_data.resize(bufSize);
+                out_buf_data.resize(bufSize);
+            }
+
+            in_buf_data.assign(inp->data.begin(), inp->data.end());
+
+            liquid_float_complex *in_buf = &in_buf_data[0];
+            liquid_float_complex *out_buf = &out_buf_data[0];
+            liquid_float_complex *temp_buf = NULL;
+
+            if (shiftFrequency != 0) {
+                if (shiftFrequency < 0) {
+                    nco_crcf_mix_block_up(freqShifter, in_buf, out_buf, bufSize);
+                } else {
+                    nco_crcf_mix_block_down(freqShifter, in_buf, out_buf, bufSize);
+                }
+                temp_buf = in_buf;
+                in_buf = out_buf;
+                out_buf = temp_buf;
+            }
+
+            DemodulatorThreadPostIQData *resamp = buffers.getBuffer();
+
+            size_t out_size = ceil((double) (bufSize) * iqResampleRatio) + 512;
+
+            if (resampledData.size() != out_size) {
+                if (resampledData.capacity() < out_size) {
+                    resampledData.reserve(out_size);
+                }
+                resampledData.resize(out_size);
+            }
+
+            unsigned int numWritten;
+            msresamp_crcf_execute(iqResampler, in_buf, bufSize, &resampledData[0], &numWritten);
+
+            resamp->data.assign(resampledData.begin(), resampledData.begin() + numWritten);
+
+            resamp->modemType = cModem->getType();
+            resamp->modemName = cModem->getName();
+            resamp->modem = cModem;
+            resamp->modemKit = cModemKit;
+            resamp->sampleRate = currentBandwidth;
+
+            if (!iqOutputQueue->push(resamp)) {
+                resamp->setRefCount(0);
+                std::cout << "DemodulatorPreThread::run() cannot push resamp into iqOutputQueue, is full !" << std::endl;
+                std::this_thread::yield();
+            }
+        }
+
+        inp->decRefCount();
+
+        DemodulatorWorkerThreadResult result;
+        //process all worker results until 
+        while (!stopping && workerResults->try_pop(result)) {
+              
+            switch (result.cmd) {
+                case DemodulatorWorkerThreadResult::DEMOD_WORKER_THREAD_RESULT_FILTERS:
+                    if (result.iqResampler) {
+                        if (iqResampler) {
+                            msresamp_crcf_destroy(iqResampler);
+                        }
+                        iqResampler = result.iqResampler;
+                        iqResampleRatio = result.iqResampleRatio;
+                    }
+
+                    if (result.modem != nullptr) {
+                        cModem = result.modem;
+#if ENABLE_DIGITAL_LAB
+                        if (cModem->getType() == "digital") {
+                            ModemDigital *mDigi = (ModemDigital *)cModem;
+                            mDigi->setOutput(parent->getOutput());
+                        }
+#endif
+                    }
+                    
+                    if (result.modemKit != nullptr) {
+                        cModemKit = result.modemKit;
+                        currentAudioSampleRate = cModemKit->audioSampleRate;
+                    }
+                        
+                    if (result.bandwidth) {
+                        currentBandwidth = result.bandwidth;
+                    }
+
+                    if (result.sampleRate) {
+                        currentSampleRate = result.sampleRate;
+                    }
+                        
+                    if (result.modemName != "") {
+                        demodType = result.modemName;
+                        demodTypeChanged.store(false);
+                    }
+                        
+                    shiftFrequency = inp->frequency-1;
+                    initialized.store(cModem != nullptr);
+                    break;
+                default:
+                    break;
+            }
+        } //end while
+        
+        if ((cModem != nullptr) && modemSettingsChanged.load()) {
+            cModem->writeSettings(modemSettingsBuffered);
+            modemSettingsBuffered.clear();
+            modemSettingsChanged.store(false);
+        }
+    } //end while stopping
+
+    DemodulatorThreadPostIQData *tmp;
+    while (iqOutputQueue->try_pop(tmp)) {
+        
+        tmp->decRefCount();
+    }
+    buffers.purge();
+}
+
+void DemodulatorPreThread::setDemodType(std::string demodType) {
+    newDemodType = demodType;
+    demodTypeChanged.store(true);
+}
+
+std::string DemodulatorPreThread::getDemodType() {
+    if (demodTypeChanged.load()) {
+        return newDemodType;
+    }
+    return demodType;
+}
+
+void DemodulatorPreThread::setFrequency(long long freq) {
+    frequencyChanged.store(true);
+    newFrequency = freq;
+}
+
+long long DemodulatorPreThread::getFrequency() {
+    if (frequencyChanged.load()) {
+        return newFrequency;
+    }
+    return currentFrequency;
+}
+
+void DemodulatorPreThread::setSampleRate(long long sampleRate) {
+    sampleRateChanged.store(true);
+    newSampleRate = sampleRate;
+}
+
+long long DemodulatorPreThread::getSampleRate() {
+    if (sampleRateChanged.load()) {
+        return newSampleRate;
+    }
+    return currentSampleRate;
+}
+
+void DemodulatorPreThread::setBandwidth(int bandwidth) {
+    bandwidthChanged.store(true);
+    newBandwidth = bandwidth;
+}
+
+int DemodulatorPreThread::getBandwidth() {    
+    return currentBandwidth;
+}
+
+void DemodulatorPreThread::setAudioSampleRate(int rate) {
+    audioSampleRateChanged.store(true);
+    newAudioSampleRate = rate;
+}
+
+int DemodulatorPreThread::getAudioSampleRate() {
+    if (audioSampleRateChanged.load()) {
+        return newAudioSampleRate;
+    }
+    return currentAudioSampleRate;
+}
+
+void DemodulatorPreThread::terminate() {
+    IOThread::terminate();
+    DemodulatorThreadIQData *inp = new DemodulatorThreadIQData;    // push dummy to nudge queue
+    if (!iqInputQueue->push(inp)) {
+        delete inp;
+    }
+
+    DemodulatorWorkerThreadCommand command(DemodulatorWorkerThreadCommand::DEMOD_WORKER_THREAD_CMD_NULL);
+    workerQueue->push(command);
+    
+    workerThread->terminate();
+    workerThread->isTerminated(1000);
+
+    t_Worker->join();
+    delete t_Worker;
+    t_Worker = nullptr;
+
+    delete workerThread;
+    workerThread = nullptr;
+
+    delete workerResults;
+    workerResults = nullptr;
+
+    delete workerQueue;
+    workerQueue = nullptr;
+}
+
+Modem *DemodulatorPreThread::getModem() {
+    return cModem;
+}
+
+ModemKit *DemodulatorPreThread::getModemKit() {
+    return cModemKit;
+}
+
+
+std::string DemodulatorPreThread::readModemSetting(std::string setting) {
+    if (cModem) {
+        return cModem->readSetting(setting);
+    } else if (modemSettingsBuffered.find(setting) != modemSettingsBuffered.end()) {
+        return modemSettingsBuffered[setting];
+    }
+    return "";
+}
+
+void DemodulatorPreThread::writeModemSetting(std::string setting, std::string value) {
+    modemSettingsBuffered[setting] = value;
+    modemSettingsChanged.store(true);
+}
+
+ModemSettings DemodulatorPreThread::readModemSettings() {
+    if (cModem) {
+        return cModem->readSettings();
+    } else {
+        return modemSettingsBuffered;
+    }
+}
+
+void DemodulatorPreThread::writeModemSettings(ModemSettings settings) {
+    modemSettingsBuffered = settings;
+    modemSettingsChanged.store(true);
+}
diff --git a/src/demod/DemodulatorPreThread.h b/src/demod/DemodulatorPreThread.h
new file mode 100644
index 0000000..e20bf68
--- /dev/null
+++ b/src/demod/DemodulatorPreThread.h
@@ -0,0 +1,82 @@
+#pragma once
+
+#include <queue>
+#include <vector>
+
+#include "CubicSDRDefs.h"
+#include "DemodDefs.h"
+#include "DemodulatorWorkerThread.h"
+
+class DemodulatorInstance;
+
+class DemodulatorPreThread : public IOThread {
+public:
+
+    DemodulatorPreThread(DemodulatorInstance *parent);
+    ~DemodulatorPreThread();
+
+    virtual void run();
+    
+    void setDemodType(std::string demodType);
+    std::string getDemodType();
+
+    void setFrequency(long long sampleRate);
+    long long getFrequency();
+
+    void setSampleRate(long long sampleRate);
+    long long getSampleRate();
+    
+    void setBandwidth(int bandwidth);
+    int getBandwidth();
+    
+    void setAudioSampleRate(int rate);
+    int getAudioSampleRate();
+    
+    bool isInitialized();
+    
+    virtual void terminate();
+
+    Modem *getModem();
+    ModemKit *getModemKit();
+    
+    std::string readModemSetting(std::string setting);
+    void writeModemSetting(std::string setting, std::string value);
+    ModemSettings readModemSettings();
+    void writeModemSettings(ModemSettings settings);
+
+protected:
+    DemodulatorInstance *parent;
+    msresamp_crcf iqResampler;
+    double iqResampleRatio;
+    std::vector<liquid_float_complex> resampledData;
+
+    Modem *cModem;
+    ModemKit *cModemKit;
+    
+    long long currentSampleRate, newSampleRate;
+    long long currentFrequency, newFrequency;
+    int currentBandwidth, newBandwidth;
+    int currentAudioSampleRate, newAudioSampleRate;
+
+    std::atomic_bool sampleRateChanged, frequencyChanged, bandwidthChanged, audioSampleRateChanged;
+
+    ModemSettings modemSettingsBuffered;
+    std::atomic_bool modemSettingsChanged;
+    
+    nco_crcf freqShifter;
+    int shiftFrequency;
+
+    std::atomic_bool initialized;
+    std::atomic_bool demodTypeChanged;
+    std::string demodType;
+    std::string newDemodType;
+
+    DemodulatorWorkerThread *workerThread;
+    std::thread *t_Worker;
+
+    DemodulatorThreadWorkerCommandQueue *workerQueue;
+    DemodulatorThreadWorkerResultQueue *workerResults;
+
+    DemodulatorThreadInputQueue* iqInputQueue;
+    DemodulatorThreadPostInputQueue* iqOutputQueue;
+};
diff --git a/src/demod/DemodulatorThread.cpp b/src/demod/DemodulatorThread.cpp
new file mode 100644
index 0000000..259c128
--- /dev/null
+++ b/src/demod/DemodulatorThread.cpp
@@ -0,0 +1,412 @@
+#include "CubicSDRDefs.h"
+#include "DemodulatorThread.h"
+#include "DemodulatorInstance.h"
+#include "CubicSDR.h"
+#include <vector>
+
+#include <cmath>
+#ifndef M_PI
+#define M_PI        3.14159265358979323846
+#endif
+
+#ifdef __APPLE__
+#include <pthread.h>
+#endif
+
+std::atomic<DemodulatorInstance *> DemodulatorThread::squelchLock(nullptr);
+std::mutex DemodulatorThread::squelchLockMutex;
+
+DemodulatorThread::DemodulatorThread(DemodulatorInstance *parent)
+    : IOThread(), outputBuffers("DemodulatorThreadBuffers"), squelchLevel(-100), 
+      signalLevel(-100), signalFloor(-30), signalCeil(30), squelchEnabled(false) {
+    
+    demodInstance = parent;
+    muted.store(false);
+    squelchBreak = false;
+}
+
+DemodulatorThread::~DemodulatorThread() {
+    releaseSquelchLock(demodInstance);
+}
+
+void DemodulatorThread::onBindOutput(std::string name, ThreadQueueBase *threadQueue) {
+    if (name == "AudioVisualOutput") {
+        
+        //protects because it may be changed at runtime
+        std::lock_guard < std::mutex > lock(m_mutexAudioVisOutputQueue);
+
+        audioVisOutputQueue = static_cast<DemodulatorThreadOutputQueue*>(threadQueue);
+    }
+}
+
+double DemodulatorThread::abMagnitude(float inphase, float quadrature) {
+    
+    // cast to double, so we keep precision despite the **2 op later.
+    double dinphase = (double)inphase;
+    double dquadrature = (double)quadrature;
+
+    //sqrt() has been an insanely fast intrinsic for years, use it !
+    return sqrt(dinphase * dinphase + dquadrature * dquadrature);
+
+}
+
+double DemodulatorThread::linearToDb(double linear) {
+  
+    #define SMALL 1e-20
+    if (linear <= SMALL) {
+        linear = double(SMALL);
+    }
+    return 20.0 * log10(linear);
+}
+
+void DemodulatorThread::run() {
+#ifdef __APPLE__
+    pthread_t tID = pthread_self();  // ID of this thread
+    int priority = sched_get_priority_max( SCHED_FIFO )-1;
+    sched_param prio = {priority}; // scheduling priority of thread
+    pthread_setschedparam(tID, SCHED_FIFO, &prio);
+#endif
+    
+//    std::cout << "Demodulator thread started.." << std::endl;
+    
+    iqInputQueue = static_cast<DemodulatorThreadPostInputQueue*>(getInputQueue("IQDataInput"));
+    audioOutputQueue = static_cast<AudioThreadInputQueue*>(getOutputQueue("AudioDataOutput"));
+    threadQueueControl = static_cast<DemodulatorThreadControlCommandQueue *>(getInputQueue("ControlQueue"));
+     
+    ModemIQData modemData;
+    
+    while (!stopping) {
+        DemodulatorThreadPostIQData *inp;
+        
+        iqInputQueue->pop(inp);
+        //        std::lock_guard < std::mutex > lock(inp->m_mutex);
+        
+        size_t bufSize = inp->data.size();
+        
+        if (!bufSize) {
+            inp->decRefCount();
+            continue;
+        }
+        
+        if (inp->modemKit && inp->modemKit != cModemKit) {
+            if (cModemKit != nullptr) {
+                cModem->disposeKit(cModemKit);
+            }
+            cModemKit = inp->modemKit;
+        }
+        
+        if (inp->modem && inp->modem != cModem) {
+            delete cModem;
+            cModem = inp->modem;
+        }
+        
+        if (!cModem || !cModemKit) {
+            inp->decRefCount();
+            continue;
+        }
+        
+        std::vector<liquid_float_complex> *inputData;
+        
+        inputData = &inp->data;
+        
+        modemData.sampleRate = inp->sampleRate;
+        modemData.data.assign(inputData->begin(), inputData->end());
+        
+        AudioThreadInput *ati = nullptr;
+        
+        ModemAnalog *modemAnalog = (cModem->getType() == "analog")?((ModemAnalog *)cModem):nullptr;
+        ModemDigital *modemDigital = (cModem->getType() == "digital")?((ModemDigital *)cModem):nullptr;
+        
+        if (modemAnalog != nullptr) {
+            ati = outputBuffers.getBuffer();
+            
+            ati->sampleRate = cModemKit->audioSampleRate;
+            ati->inputRate = inp->sampleRate;
+        } else if (modemDigital != nullptr) {
+            ati = outputBuffers.getBuffer();
+            
+            ati->sampleRate = cModemKit->sampleRate;
+            ati->inputRate = inp->sampleRate;
+            ati->data.resize(0);
+        }
+
+        cModem->demodulate(cModemKit, &modemData, ati);
+
+        double currentSignalLevel = 0;
+        double sampleTime = double(inp->data.size()) / double(inp->sampleRate);
+
+        if (audioOutputQueue != nullptr && ati && ati->data.size()) {
+            double accum = 0;
+
+             if (cModem->useSignalOutput()) {
+
+                for (auto i : ati->data) {
+                    accum += abMagnitude(i, 0.0);
+                }
+
+                currentSignalLevel = linearToDb(accum / double(ati->data.size()));
+
+            } else {
+   
+                for (auto i : inp->data) {
+                    accum += abMagnitude(i.real, i.imag);
+                }
+
+                currentSignalLevel = linearToDb(accum / double(inp->data.size()));
+            }
+            
+            float sf = signalFloor.load(), sc = signalCeil.load(), sl = squelchLevel.load();
+            
+         
+            if (currentSignalLevel > sc) {
+                sc = currentSignalLevel;
+            }
+            
+            if (currentSignalLevel < sf) {
+                sf = currentSignalLevel;
+            }
+            
+
+            if (sl+1.0f > sc) {
+                sc = sl+1.0f;
+            }
+            
+            if ((sf+2.0f) > sc) {
+                sc = sf+2.0f;
+            }
+            
+            sc -= (sc - (currentSignalLevel + 2.0f)) * sampleTime * 0.05f;
+            sf += ((currentSignalLevel - 5.0f) - sf) * sampleTime * 0.15f;
+            
+            signalFloor.store(sf);
+            signalCeil.store(sc);
+        }
+        
+        if (currentSignalLevel > signalLevel) {
+            signalLevel = signalLevel + (currentSignalLevel - signalLevel) * 0.5;
+        } else {
+            signalLevel = signalLevel + (currentSignalLevel - signalLevel) * 0.05 * sampleTime * 30.0;
+        }
+        
+        bool squelched = (muted.load() || (squelchEnabled && (signalLevel < squelchLevel)));
+        
+        if (squelchEnabled) {
+            if (!squelched && !squelchBreak) {
+                    if (wxGetApp().getSoloMode() && !wxGetApp().getAppFrame()->isUserDemodBusy()) {
+                        std::lock_guard < std::mutex > lock(squelchLockMutex);
+                        if (squelchLock.load() == nullptr) {
+                            squelchLock.store(demodInstance);
+                            wxGetApp().getDemodMgr().setActiveDemodulator(nullptr);
+                            wxGetApp().getDemodMgr().setActiveDemodulator(demodInstance, false);
+                            squelchBreak = true;
+                            demodInstance->getVisualCue()->triggerSquelchBreak(120);
+                        }
+                    } else {
+                        squelchBreak = true;
+                        demodInstance->getVisualCue()->triggerSquelchBreak(120);
+                    }
+                
+            } else if (squelched && squelchBreak) {
+                releaseSquelchLock(demodInstance);
+                squelchBreak = false;
+            }
+        }
+        
+        if (audioOutputQueue != nullptr && ati && ati->data.size() && !squelched) {
+            std::vector<float>::iterator data_i;
+            ati->peak = 0;
+            for (auto data_i : ati->data) {
+                float p = fabs(data_i);
+                if (p > ati->peak) {
+                    ati->peak = p;
+                }
+            }
+        } else if (ati) {
+            ati->setRefCount(0);
+            ati = nullptr;
+        }
+        
+        //At that point, capture the current state of audioVisOutputQueue in a local 
+        //variable, and works with it with now on until the next while-turn.
+        DemodulatorThreadOutputQueue* localAudioVisOutputQueue = nullptr;
+        {
+            std::lock_guard < std::mutex > lock(m_mutexAudioVisOutputQueue);
+            localAudioVisOutputQueue = audioVisOutputQueue;
+        }
+
+        if ((ati || modemDigital) && localAudioVisOutputQueue != nullptr && localAudioVisOutputQueue->empty()) {
+            AudioThreadInput *ati_vis = new AudioThreadInput;
+
+            ati_vis->sampleRate = inp->sampleRate;
+            ati_vis->inputRate = inp->sampleRate;
+            
+            size_t num_vis = DEMOD_VIS_SIZE;
+            if (modemDigital) {
+                if (ati) {  // TODO: handle digital modems with audio output
+                    ati->setRefCount(0);
+                    ati = nullptr;
+                }
+                ati_vis->data.resize(inputData->size());
+                ati_vis->channels = 2;
+                for (int i = 0, iMax = inputData->size() / 2; i < iMax; i++) {
+                    ati_vis->data[i * 2] = (*inputData)[i].real;
+                    ati_vis->data[i * 2 + 1] = (*inputData)[i].imag;
+                }
+                ati_vis->type = 2;
+            } else if (ati->channels==2) {
+                ati_vis->channels = 2;
+                int stereoSize = ati->data.size();
+                if (stereoSize > DEMOD_VIS_SIZE * 2) {
+                    stereoSize = DEMOD_VIS_SIZE * 2;
+                }
+                
+                ati_vis->data.resize(stereoSize);
+                
+                if (inp->modemName == "I/Q") {
+                    for (int i = 0; i < stereoSize / 2; i++) {
+                        ati_vis->data[i] = (*inputData)[i].real * 0.75;
+                        ati_vis->data[i + stereoSize / 2] = (*inputData)[i].imag * 0.75;
+                    }
+                } else {
+                    for (int i = 0; i < stereoSize / 2; i++) {
+                        ati_vis->inputRate = cModemKit->audioSampleRate;
+                        ati_vis->sampleRate = 36000;
+                        ati_vis->data[i] = ati->data[i * 2];
+                        ati_vis->data[i + stereoSize / 2] = ati->data[i * 2 + 1];
+                    }
+                }
+                ati_vis->type = 1;
+            } else {
+                size_t numAudioWritten = ati->data.size();
+                ati_vis->channels = 1;
+                std::vector<float> *demodOutData = (modemAnalog != nullptr)?modemAnalog->getDemodOutputData():nullptr;
+                if ((numAudioWritten > bufSize) || (demodOutData == nullptr)) {
+                    ati_vis->inputRate = cModemKit->audioSampleRate;
+                    if (num_vis > numAudioWritten) {
+                        num_vis = numAudioWritten;
+                    }
+                    ati_vis->data.assign(ati->data.begin(), ati->data.begin() + num_vis);
+                } else {
+                    if (num_vis > demodOutData->size()) {
+                        num_vis = demodOutData->size();
+                    }
+                    ati_vis->data.assign(demodOutData->begin(), demodOutData->begin() + num_vis);
+                }
+                ati_vis->type = 0;
+            }
+            
+            if (!localAudioVisOutputQueue->push(ati_vis)) {
+                ati_vis->setRefCount(0);
+                std::cout << "DemodulatorThread::run() cannot push ati_vis into localAudioVisOutputQueue, is full !" << std::endl;
+                std::this_thread::yield();
+            }
+        }
+        
+        
+        if (ati != nullptr) {
+            if (!muted.load() && (!wxGetApp().getSoloMode() || (demodInstance == wxGetApp().getDemodMgr().getLastActiveDemodulator()))) {
+                
+                if (!audioOutputQueue->push(ati)) {
+                    ati->decRefCount();
+                    std::cout << "DemodulatorThread::run() cannot push ati into audioOutputQueue, is full !" << std::endl;
+                    std::this_thread::yield();
+                }
+
+            } else {
+                ati->setRefCount(0);
+            }
+        }
+        
+        DemodulatorThreadControlCommand command;
+        
+        //empty command queue, execute commands
+        while (threadQueueControl->try_pop(command)) {
+                       
+            switch (command.cmd) {
+                case DemodulatorThreadControlCommand::DEMOD_THREAD_CMD_CTL_SQUELCH_ON:
+                    squelchEnabled = true;
+                    break;
+                case DemodulatorThreadControlCommand::DEMOD_THREAD_CMD_CTL_SQUELCH_OFF:
+                    squelchEnabled = false;
+                    break;
+                default:
+                    break;
+            }
+        }
+        
+        
+        inp->decRefCount();
+    }
+    // end while !stopping
+    
+    // Purge any unused inputs, with a non-blocking pop
+    DemodulatorThreadPostIQData *ref;
+    while (iqInputQueue->try_pop(ref)) {
+        
+        if (ref) {  // May have other consumers; just decrement
+            ref->decRefCount();
+        }
+    }
+
+    AudioThreadInput *ref_audio;
+    while (audioOutputQueue->try_pop(ref_audio)) {
+      
+        if (ref_audio) { // Originated here; set RefCount to 0
+            ref_audio->setRefCount(0);
+        }
+    }
+
+    outputBuffers.purge();
+    
+//    std::cout << "Demodulator thread done." << std::endl;
+}
+
+void DemodulatorThread::terminate() {
+    IOThread::terminate();
+    DemodulatorThreadPostIQData *inp = new DemodulatorThreadPostIQData;    // push dummy to nudge queue
+    if (!iqInputQueue->push(inp)) {
+        delete inp;
+    }
+}
+
+bool DemodulatorThread::isMuted() {
+    return muted.load();
+}
+
+void DemodulatorThread::setMuted(bool muted) {
+    this->muted.store(muted);
+}
+
+float DemodulatorThread::getSignalLevel() {
+    return signalLevel.load();
+}
+
+float DemodulatorThread::getSignalFloor() {
+    return signalFloor.load();
+}
+
+float DemodulatorThread::getSignalCeil() {
+    return signalCeil.load();
+}
+
+void DemodulatorThread::setSquelchLevel(float signal_level_in) {
+    if (!squelchEnabled) {
+        squelchEnabled = true;
+    }
+    squelchLevel = signal_level_in;
+}
+
+float DemodulatorThread::getSquelchLevel() {
+    return squelchLevel;
+}
+
+bool DemodulatorThread::getSquelchBreak() {
+    return squelchBreak;
+}
+
+void DemodulatorThread::releaseSquelchLock(DemodulatorInstance *inst) {
+    std::lock_guard < std::mutex > lock(squelchLockMutex);
+    if (inst == nullptr || squelchLock.load() == inst) {
+        squelchLock.store(nullptr);
+    }
+}
\ No newline at end of file
diff --git a/src/demod/DemodulatorThread.h b/src/demod/DemodulatorThread.h
new file mode 100644
index 0000000..10124a4
--- /dev/null
+++ b/src/demod/DemodulatorThread.h
@@ -0,0 +1,69 @@
+#pragma once
+
+#include <queue>
+#include <vector>
+
+#include "DemodDefs.h"
+#include "AudioThread.h"
+#include "Modem.h"
+
+typedef ThreadQueue<AudioThreadInput *> DemodulatorThreadOutputQueue;
+
+#define DEMOD_VIS_SIZE 2048
+#define DEMOD_SIGNAL_MIN -30
+#define DEMOD_SIGNAL_MAX 30
+
+class DemodulatorInstance;
+
+class DemodulatorThread : public IOThread {
+public:
+
+    DemodulatorThread(DemodulatorInstance *parent);
+    ~DemodulatorThread();
+
+    void onBindOutput(std::string name, ThreadQueueBase *threadQueue);
+    
+    void run();
+    void terminate();
+    
+    void setMuted(bool state);
+    bool isMuted();
+    
+    float getSignalLevel();
+    float getSignalCeil();
+    float getSignalFloor();
+    void setSquelchLevel(float signal_level_in);
+    float getSquelchLevel();
+   
+    bool getSquelchBreak();
+
+    static void releaseSquelchLock(DemodulatorInstance *inst);
+protected:
+    
+    double abMagnitude(float inphase, float quadrature);
+    double linearToDb(double linear);
+
+    DemodulatorInstance *demodInstance = nullptr;
+    ReBuffer<AudioThreadInput> outputBuffers;
+
+    std::atomic_bool muted;
+
+    std::atomic<float> squelchLevel;
+    std::atomic<float> signalLevel, signalFloor, signalCeil;
+    bool squelchEnabled, squelchBreak;
+    
+    static std::atomic<DemodulatorInstance *> squelchLock;
+    static std::mutex squelchLockMutex;
+    
+    
+    Modem *cModem = nullptr;
+    ModemKit *cModemKit = nullptr;
+    
+    DemodulatorThreadPostInputQueue* iqInputQueue = nullptr;
+    AudioThreadInputQueue *audioOutputQueue = nullptr;
+    DemodulatorThreadOutputQueue* audioVisOutputQueue = nullptr;
+    DemodulatorThreadControlCommandQueue *threadQueueControl = nullptr;
+
+    //protects the audioVisOutputQueue dynamic binding change at runtime (in DemodulatorMgr)
+    mutable std::mutex m_mutexAudioVisOutputQueue;
+};
diff --git a/src/demod/DemodulatorWorkerThread.cpp b/src/demod/DemodulatorWorkerThread.cpp
new file mode 100644
index 0000000..35efd62
--- /dev/null
+++ b/src/demod/DemodulatorWorkerThread.cpp
@@ -0,0 +1,113 @@
+#include "DemodulatorWorkerThread.h"
+#include "CubicSDRDefs.h"
+#include "CubicSDR.h"
+#include <vector>
+
+DemodulatorWorkerThread::DemodulatorWorkerThread() : IOThread(),
+        commandQueue(NULL), resultQueue(NULL), cModem(nullptr), cModemKit(nullptr) {
+}
+
+DemodulatorWorkerThread::~DemodulatorWorkerThread() {
+}
+
+void DemodulatorWorkerThread::run() {
+
+//    std::cout << "Demodulator worker thread started.." << std::endl;
+    
+    commandQueue = static_cast<DemodulatorThreadWorkerCommandQueue *>(getInputQueue("WorkerCommandQueue"));
+    resultQueue = static_cast<DemodulatorThreadWorkerResultQueue *>(getOutputQueue("WorkerResultQueue"));
+    
+    while (!stopping) {
+        bool filterChanged = false;
+        bool makeDemod = false;
+        DemodulatorWorkerThreadCommand filterCommand, demodCommand;
+        DemodulatorWorkerThreadCommand command;
+
+        bool done = false;
+        //Beware of the subtility here,
+        //we are waiting for the first command to show up (blocking!)
+        //then consuming the commands until done. 
+        while (!done) {
+            commandQueue->pop(command);
+
+            switch (command.cmd) {
+                case DemodulatorWorkerThreadCommand::DEMOD_WORKER_THREAD_CMD_BUILD_FILTERS:
+                    filterChanged = true;
+                    filterCommand = command;
+                    break;
+                case DemodulatorWorkerThreadCommand::DEMOD_WORKER_THREAD_CMD_MAKE_DEMOD:
+                    makeDemod = true;
+                    demodCommand = command;
+                    break;
+                default:
+                    break;
+            }
+            done = commandQueue->empty();
+        }
+
+        if ((makeDemod || filterChanged) && !stopping) {
+            DemodulatorWorkerThreadResult result(DemodulatorWorkerThreadResult::DEMOD_WORKER_THREAD_RESULT_FILTERS);
+            
+            
+            if (filterCommand.sampleRate) {
+                result.sampleRate = filterCommand.sampleRate;
+            }
+            
+            if (makeDemod) {
+                cModem = Modem::makeModem(demodCommand.demodType);
+                cModemName = cModem->getName();
+                cModemType = cModem->getType();
+                if (demodCommand.settings.size()) {
+                    cModem->writeSettings(demodCommand.settings);
+                }
+                result.sampleRate = demodCommand.sampleRate;
+                wxGetApp().getAppFrame()->notifyUpdateModemProperties();
+            }
+            result.modem = cModem;
+
+            if (makeDemod && demodCommand.bandwidth && demodCommand.audioSampleRate) {
+                if (cModem != nullptr) {
+                    result.bandwidth = cModem->checkSampleRate(demodCommand.bandwidth, demodCommand.audioSampleRate);
+                    cModemKit = cModem->buildKit(result.bandwidth, demodCommand.audioSampleRate);
+                } else {
+                    cModemKit = nullptr;
+                }
+            } else if (filterChanged && filterCommand.bandwidth && filterCommand.audioSampleRate) {
+                if (cModem != nullptr) {
+                    result.bandwidth = cModem->checkSampleRate(filterCommand.bandwidth, filterCommand.audioSampleRate);
+                    cModemKit = cModem->buildKit(result.bandwidth, filterCommand.audioSampleRate);
+                } else {
+                    cModemKit = nullptr;
+                }
+            } else if (makeDemod) {
+                cModemKit = nullptr;
+            }
+            if (cModem != nullptr) {
+                cModem->clearRebuildKit();
+            }
+            
+            float As = 60.0f;         // stop-band attenuation [dB]
+            
+            if (cModem && result.sampleRate && result.bandwidth) {
+                result.bandwidth = cModem->checkSampleRate(result.bandwidth, makeDemod?demodCommand.audioSampleRate:filterCommand.audioSampleRate);
+                result.iqResampleRatio = (double) (result.bandwidth) / (double) result.sampleRate;
+                result.iqResampler = msresamp_crcf_create(result.iqResampleRatio, As);
+            }
+
+            result.modemKit = cModemKit;
+            result.modemType = cModemType;
+            result.modemName = cModemName;
+            
+            resultQueue->push(result);
+        }
+
+    }
+
+//    std::cout << "Demodulator worker thread done." << std::endl;
+}
+
+void DemodulatorWorkerThread::terminate() {
+    IOThread::terminate();
+    DemodulatorWorkerThreadCommand inp;    // push dummy to nudge queue
+    commandQueue->push(inp);
+}
diff --git a/src/demod/DemodulatorWorkerThread.h b/src/demod/DemodulatorWorkerThread.h
new file mode 100644
index 0000000..140046a
--- /dev/null
+++ b/src/demod/DemodulatorWorkerThread.h
@@ -0,0 +1,98 @@
+#pragma once
+
+#include <queue>
+#include <vector>
+
+#include "liquid/liquid.h"
+#include "AudioThread.h"
+#include "ThreadQueue.h"
+#include "CubicSDRDefs.h"
+#include "Modem.h"
+
+class DemodulatorWorkerThreadResult {
+public:
+    enum DemodulatorThreadResultEnum {
+        DEMOD_WORKER_THREAD_RESULT_NULL, DEMOD_WORKER_THREAD_RESULT_FILTERS
+    };
+
+    DemodulatorWorkerThreadResult() :
+            cmd(DEMOD_WORKER_THREAD_RESULT_NULL), iqResampler(nullptr), iqResampleRatio(0), sampleRate(0), bandwidth(0), modemKit(nullptr), modemType("") {
+
+    }
+
+    DemodulatorWorkerThreadResult(DemodulatorThreadResultEnum cmd) :
+            DemodulatorWorkerThreadResult() {
+        this->cmd = cmd;
+    }
+
+    DemodulatorThreadResultEnum cmd;
+
+    msresamp_crcf iqResampler;
+    double iqResampleRatio;
+    
+    DemodulatorThread *demodThread;
+
+    long long sampleRate;
+    unsigned int bandwidth;
+    Modem *modem;
+    ModemKit *modemKit;
+    std::string modemType;
+    std::string modemName;
+};
+
+class DemodulatorWorkerThreadCommand {
+public:
+    enum DemodulatorThreadCommandEnum {
+        DEMOD_WORKER_THREAD_CMD_NULL, DEMOD_WORKER_THREAD_CMD_BUILD_FILTERS, DEMOD_WORKER_THREAD_CMD_MAKE_DEMOD
+    };
+
+    DemodulatorWorkerThreadCommand() :
+            cmd(DEMOD_WORKER_THREAD_CMD_NULL), frequency(0), sampleRate(0), bandwidth(0), audioSampleRate(0), demodType("") {
+
+    }
+
+    DemodulatorWorkerThreadCommand(DemodulatorThreadCommandEnum cmd) :
+            cmd(cmd), frequency(0), sampleRate(0), bandwidth(0), audioSampleRate(0), demodType("") {
+
+    }
+
+    DemodulatorThreadCommandEnum cmd;
+
+    long long frequency;
+    long long sampleRate;
+    unsigned int bandwidth;
+    unsigned int audioSampleRate;
+    std::string demodType;
+    ModemSettings settings;
+};
+
+typedef ThreadQueue<DemodulatorWorkerThreadCommand> DemodulatorThreadWorkerCommandQueue;
+typedef ThreadQueue<DemodulatorWorkerThreadResult> DemodulatorThreadWorkerResultQueue;
+
+class DemodulatorWorkerThread : public IOThread {
+public:
+
+    DemodulatorWorkerThread();
+    ~DemodulatorWorkerThread();
+
+    virtual void run();
+
+    void setCommandQueue(DemodulatorThreadWorkerCommandQueue *tQueue) {
+        commandQueue = tQueue;
+    }
+
+    void setResultQueue(DemodulatorThreadWorkerResultQueue *tQueue) {
+        resultQueue = tQueue;
+    }
+
+    virtual void terminate();
+
+protected:
+
+    DemodulatorThreadWorkerCommandQueue *commandQueue;
+    DemodulatorThreadWorkerResultQueue *resultQueue;
+    Modem *cModem;
+    ModemKit *cModemKit;
+    std::string cModemType;
+    std::string cModemName;
+};
diff --git a/src/forms/DigitalConsole/DigitalConsole.cpp b/src/forms/DigitalConsole/DigitalConsole.cpp
new file mode 100644
index 0000000..f472ca1
--- /dev/null
+++ b/src/forms/DigitalConsole/DigitalConsole.cpp
@@ -0,0 +1,138 @@
+#include "DigitalConsole.h"
+#include "CubicSDR.h"
+#include <iomanip>
+
+DigitalConsole::DigitalConsole( wxWindow* parent, ModemDigitalOutputConsole *doParent ): DigitalConsoleFrame( parent ), doParent(doParent) {
+    streamWritten.store(false);
+    streamPaused.store(false);
+}
+
+DigitalConsole::~DigitalConsole() {
+    doParent->setDialog(nullptr);
+}
+
+void DigitalConsole::OnClose( wxCloseEvent& event ) {
+    doParent->setDialog(nullptr);
+}
+
+void DigitalConsole::OnCopy( wxCommandEvent& event ) {
+    m_dataView->SelectAll();
+    m_dataView->Copy();
+}
+
+void DigitalConsole::OnPause( wxCommandEvent& event ) {
+    if (streamPaused.load()) {
+        m_pauseButton->SetLabel("Stop");
+        streamPaused.store(false);
+    } else {
+        m_pauseButton->SetLabel("Run");
+        streamPaused.store(true);
+    }
+}
+
+void DoRefresh( wxTimerEvent& event ) {
+    event.Skip();
+}
+
+void DigitalConsole::DoRefresh( wxTimerEvent& event ) {
+    if (streamWritten.load()) {
+        stream_busy.lock();
+        m_dataView->AppendText(streamBuf.str());
+        streamBuf.str("");
+        streamWritten.store(false);
+        stream_busy.unlock();
+    }
+}
+
+void DigitalConsole::OnClear( wxCommandEvent& event ) {
+    m_dataView->Clear();
+}
+
+void DigitalConsole::write(std::string outp) {
+    if (streamPaused.load()) {
+        return;
+    }
+    stream_busy.lock();
+    streamBuf << outp;
+    streamWritten.store(true);
+    stream_busy.unlock();
+}
+
+void DigitalConsole::write(char outc) {
+    if (streamPaused.load()) {
+        return;
+    }
+    stream_busy.lock();
+    streamBuf << outc;
+    streamWritten.store(true);
+    stream_busy.unlock();
+}
+
+
+ModemDigitalOutputConsole::ModemDigitalOutputConsole(): ModemDigitalOutput(), dialog(nullptr) {
+    streamWritten.store(false);
+}
+
+ModemDigitalOutputConsole::~ModemDigitalOutputConsole() {
+    
+}
+
+void ModemDigitalOutputConsole::setDialog(DigitalConsole *dialog_in) {
+    dialog = dialog_in;
+    if (dialog && dialogTitle != "") {
+        dialog->SetTitle(dialogTitle);
+    }
+}
+
+DigitalConsole *ModemDigitalOutputConsole::getDialog() {
+    return dialog;
+}
+
+void ModemDigitalOutputConsole::Show() {
+    if (!dialog) {
+        return;
+    }
+    if (!dialog->IsShown()) {
+        dialog->Show();
+    }
+}
+
+
+void ModemDigitalOutputConsole::Hide() {
+    if (!dialog) {
+        return;
+    }
+    if (dialog->IsShown()) {
+        dialog->Hide();
+    }
+}
+
+void ModemDigitalOutputConsole::Close() {
+    if (!dialog) {
+        return;
+    }
+    dialog->Hide();
+    dialog->Close();
+    dialog = nullptr;
+}
+
+void ModemDigitalOutputConsole::setTitle(std::string title) {
+    if (dialog) {
+        dialog->SetTitle(title);
+    }
+    dialogTitle = title;
+}
+
+void ModemDigitalOutputConsole::write(std::string outp) {
+    if (!dialog) {
+        return;
+    }
+    dialog->write(outp);
+}
+
+void ModemDigitalOutputConsole::write(char outc) {
+    if (!dialog) {
+        return;
+    }
+    dialog->write(outc);
+}
diff --git a/src/forms/DigitalConsole/DigitalConsole.fbp b/src/forms/DigitalConsole/DigitalConsole.fbp
new file mode 100644
index 0000000..90aed1d
--- /dev/null
+++ b/src/forms/DigitalConsole/DigitalConsole.fbp
@@ -0,0 +1,485 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
+<wxFormBuilder_Project>
+    <FileVersion major="1" minor="13" />
+    <object class="Project" expanded="1">
+        <property name="class_decoration"></property>
+        <property name="code_generation">C++</property>
+        <property name="disconnect_events">1</property>
+        <property name="disconnect_mode">source_name</property>
+        <property name="disconnect_php_events">0</property>
+        <property name="disconnect_python_events">0</property>
+        <property name="embedded_files_path">res</property>
+        <property name="encoding">UTF-8</property>
+        <property name="event_generation">connect</property>
+        <property name="file">DigitalConsoleFrame</property>
+        <property name="first_id">1000</property>
+        <property name="help_provider">none</property>
+        <property name="internationalize">0</property>
+        <property name="name">DigitalConsole</property>
+        <property name="namespace"></property>
+        <property name="path">.</property>
+        <property name="precompiled_header"></property>
+        <property name="relative_path">1</property>
+        <property name="skip_lua_events">1</property>
+        <property name="skip_php_events">1</property>
+        <property name="skip_python_events">1</property>
+        <property name="ui_table">UI</property>
+        <property name="use_enum">0</property>
+        <property name="use_microsoft_bom">0</property>
+        <object class="Frame" expanded="1">
+            <property name="aui_managed">0</property>
+            <property name="aui_manager_style">wxAUI_MGR_DEFAULT</property>
+            <property name="bg"></property>
+            <property name="center">wxBOTH</property>
+            <property name="context_help"></property>
+            <property name="context_menu">1</property>
+            <property name="enabled">1</property>
+            <property name="event_handler">impl_virtual</property>
+            <property name="extra_style"></property>
+            <property name="fg"></property>
+            <property name="font"></property>
+            <property name="hidden">0</property>
+            <property name="id">wxID_ANY</property>
+            <property name="maximum_size"></property>
+            <property name="minimum_size"></property>
+            <property name="name">DigitalConsoleFrame</property>
+            <property name="pos"></property>
+            <property name="size">441,394</property>
+            <property name="style">wxCAPTION|wxFRAME_FLOAT_ON_PARENT|wxMAXIMIZE|wxMAXIMIZE_BOX|wxMINIMIZE|wxMINIMIZE_BOX|wxRESIZE_BORDER</property>
+            <property name="subclass">; </property>
+            <property name="title">Digital Output</property>
+            <property name="tooltip"></property>
+            <property name="window_extra_style">wxWS_EX_PROCESS_UI_UPDATES</property>
+            <property name="window_name"></property>
+            <property name="window_style">wxFULL_REPAINT_ON_RESIZE|wxTAB_TRAVERSAL</property>
+            <property name="xrc_skip_sizer">1</property>
+            <event name="OnActivate"></event>
+            <event name="OnActivateApp"></event>
+            <event name="OnAuiFindManager"></event>
+            <event name="OnAuiPaneButton"></event>
+            <event name="OnAuiPaneClose"></event>
+            <event name="OnAuiPaneMaximize"></event>
+            <event name="OnAuiPaneRestore"></event>
+            <event name="OnAuiRender"></event>
+            <event name="OnChar"></event>
+            <event name="OnClose">OnClose</event>
+            <event name="OnEnterWindow"></event>
+            <event name="OnEraseBackground"></event>
+            <event name="OnHibernate"></event>
+            <event name="OnIconize"></event>
+            <event name="OnIdle"></event>
+            <event name="OnKeyDown"></event>
+            <event name="OnKeyUp"></event>
+            <event name="OnKillFocus"></event>
+            <event name="OnLeaveWindow"></event>
+            <event name="OnLeftDClick"></event>
+            <event name="OnLeftDown"></event>
+            <event name="OnLeftUp"></event>
+            <event name="OnMiddleDClick"></event>
+            <event name="OnMiddleDown"></event>
+            <event name="OnMiddleUp"></event>
+            <event name="OnMotion"></event>
+            <event name="OnMouseEvents"></event>
+            <event name="OnMouseWheel"></event>
+            <event name="OnPaint"></event>
+            <event name="OnRightDClick"></event>
+            <event name="OnRightDown"></event>
+            <event name="OnRightUp"></event>
+            <event name="OnSetFocus"></event>
+            <event name="OnSize"></event>
+            <event name="OnUpdateUI"></event>
+            <object class="wxBoxSizer" expanded="1">
+                <property name="minimum_size"></property>
+                <property name="name">mainSizer</property>
+                <property name="orient">wxVERTICAL</property>
+                <property name="permission">none</property>
+                <object class="sizeritem" expanded="1">
+                    <property name="border">5</property>
+                    <property name="flag">wxEXPAND</property>
+                    <property name="proportion">1</property>
+                    <object class="wxBoxSizer" expanded="0">
+                        <property name="minimum_size"></property>
+                        <property name="name">dataViewSizer</property>
+                        <property name="orient">wxVERTICAL</property>
+                        <property name="permission">none</property>
+                        <object class="sizeritem" expanded="0">
+                            <property name="border">5</property>
+                            <property name="flag">wxEXPAND</property>
+                            <property name="proportion">1</property>
+                            <object class="wxTextCtrl" expanded="0">
+                                <property name="BottomDockable">1</property>
+                                <property name="LeftDockable">1</property>
+                                <property name="RightDockable">1</property>
+                                <property name="TopDockable">1</property>
+                                <property name="aui_layer"></property>
+                                <property name="aui_name"></property>
+                                <property name="aui_position"></property>
+                                <property name="aui_row"></property>
+                                <property name="best_size"></property>
+                                <property name="bg"></property>
+                                <property name="caption"></property>
+                                <property name="caption_visible">1</property>
+                                <property name="center_pane">0</property>
+                                <property name="close_button">1</property>
+                                <property name="context_help"></property>
+                                <property name="context_menu">1</property>
+                                <property name="default_pane">0</property>
+                                <property name="dock">Dock</property>
+                                <property name="dock_fixed">0</property>
+                                <property name="docking">Left</property>
+                                <property name="enabled">1</property>
+                                <property name="fg"></property>
+                                <property name="floatable">1</property>
+                                <property name="font">,90,90,-1,76,0</property>
+                                <property name="gripper">0</property>
+                                <property name="hidden">0</property>
+                                <property name="id">wxID_ANY</property>
+                                <property name="max_size"></property>
+                                <property name="maximize_button">0</property>
+                                <property name="maximum_size"></property>
+                                <property name="maxlength"></property>
+                                <property name="min_size"></property>
+                                <property name="minimize_button">0</property>
+                                <property name="minimum_size"></property>
+                                <property name="moveable">1</property>
+                                <property name="name">m_dataView</property>
+                                <property name="pane_border">1</property>
+                                <property name="pane_position"></property>
+                                <property name="pane_size"></property>
+                                <property name="permission">protected</property>
+                                <property name="pin_button">1</property>
+                                <property name="pos"></property>
+                                <property name="resize">Resizable</property>
+                                <property name="show">1</property>
+                                <property name="size"></property>
+                                <property name="style">wxTE_CHARWRAP|wxTE_MULTILINE|wxTE_NOHIDESEL|wxTE_READONLY|wxTE_WORDWRAP</property>
+                                <property name="subclass"></property>
+                                <property name="toolbar_pane">0</property>
+                                <property name="tooltip"></property>
+                                <property name="validator_data_type"></property>
+                                <property name="validator_style">wxFILTER_NONE</property>
+                                <property name="validator_type">wxDefaultValidator</property>
+                                <property name="validator_variable"></property>
+                                <property name="value"></property>
+                                <property name="window_extra_style">wxWS_EX_PROCESS_UI_UPDATES</property>
+                                <property name="window_name"></property>
+                                <property name="window_style">wxALWAYS_SHOW_SB|wxFULL_REPAINT_ON_RESIZE|wxNO_BORDER|wxSIMPLE_BORDER|wxVSCROLL</property>
+                                <event name="OnChar"></event>
+                                <event name="OnEnterWindow"></event>
+                                <event name="OnEraseBackground"></event>
+                                <event name="OnKeyDown"></event>
+                                <event name="OnKeyUp"></event>
+                                <event name="OnKillFocus"></event>
+                                <event name="OnLeaveWindow"></event>
+                                <event name="OnLeftDClick"></event>
+                                <event name="OnLeftDown"></event>
+                                <event name="OnLeftUp"></event>
+                                <event name="OnMiddleDClick"></event>
+                                <event name="OnMiddleDown"></event>
+                                <event name="OnMiddleUp"></event>
+                                <event name="OnMotion"></event>
+                                <event name="OnMouseEvents"></event>
+                                <event name="OnMouseWheel"></event>
+                                <event name="OnPaint"></event>
+                                <event name="OnRightDClick"></event>
+                                <event name="OnRightDown"></event>
+                                <event name="OnRightUp"></event>
+                                <event name="OnSetFocus"></event>
+                                <event name="OnSize"></event>
+                                <event name="OnText"></event>
+                                <event name="OnTextEnter"></event>
+                                <event name="OnTextMaxLen"></event>
+                                <event name="OnTextURL"></event>
+                                <event name="OnUpdateUI"></event>
+                            </object>
+                        </object>
+                    </object>
+                </object>
+                <object class="sizeritem" expanded="1">
+                    <property name="border">5</property>
+                    <property name="flag">wxALL|wxEXPAND</property>
+                    <property name="proportion">0</property>
+                    <object class="wxBoxSizer" expanded="0">
+                        <property name="minimum_size"></property>
+                        <property name="name">buttonSizer</property>
+                        <property name="orient">wxHORIZONTAL</property>
+                        <property name="permission">none</property>
+                        <object class="sizeritem" expanded="0">
+                            <property name="border">5</property>
+                            <property name="flag">wxEXPAND</property>
+                            <property name="proportion">1</property>
+                            <object class="wxButton" expanded="0">
+                                <property name="BottomDockable">1</property>
+                                <property name="LeftDockable">1</property>
+                                <property name="RightDockable">1</property>
+                                <property name="TopDockable">1</property>
+                                <property name="aui_layer"></property>
+                                <property name="aui_name"></property>
+                                <property name="aui_position"></property>
+                                <property name="aui_row"></property>
+                                <property name="best_size"></property>
+                                <property name="bg"></property>
+                                <property name="caption"></property>
+                                <property name="caption_visible">1</property>
+                                <property name="center_pane">0</property>
+                                <property name="close_button">1</property>
+                                <property name="context_help"></property>
+                                <property name="context_menu">1</property>
+                                <property name="default">0</property>
+                                <property name="default_pane">0</property>
+                                <property name="dock">Dock</property>
+                                <property name="dock_fixed">0</property>
+                                <property name="docking">Left</property>
+                                <property name="enabled">1</property>
+                                <property name="fg"></property>
+                                <property name="floatable">1</property>
+                                <property name="font"></property>
+                                <property name="gripper">0</property>
+                                <property name="hidden">0</property>
+                                <property name="id">wxID_ANY</property>
+                                <property name="label">Clear</property>
+                                <property name="max_size"></property>
+                                <property name="maximize_button">0</property>
+                                <property name="maximum_size"></property>
+                                <property name="min_size"></property>
+                                <property name="minimize_button">0</property>
+                                <property name="minimum_size"></property>
+                                <property name="moveable">1</property>
+                                <property name="name">m_clearButton</property>
+                                <property name="pane_border">1</property>
+                                <property name="pane_position"></property>
+                                <property name="pane_size"></property>
+                                <property name="permission">protected</property>
+                                <property name="pin_button">1</property>
+                                <property name="pos"></property>
+                                <property name="resize">Resizable</property>
+                                <property name="show">1</property>
+                                <property name="size"></property>
+                                <property name="style"></property>
+                                <property name="subclass"></property>
+                                <property name="toolbar_pane">0</property>
+                                <property name="tooltip"></property>
+                                <property name="validator_data_type"></property>
+                                <property name="validator_style">wxFILTER_NONE</property>
+                                <property name="validator_type">wxDefaultValidator</property>
+                                <property name="validator_variable"></property>
+                                <property name="window_extra_style"></property>
+                                <property name="window_name"></property>
+                                <property name="window_style"></property>
+                                <event name="OnButtonClick">OnClear</event>
+                                <event name="OnChar"></event>
+                                <event name="OnEnterWindow"></event>
+                                <event name="OnEraseBackground"></event>
+                                <event name="OnKeyDown"></event>
+                                <event name="OnKeyUp"></event>
+                                <event name="OnKillFocus"></event>
+                                <event name="OnLeaveWindow"></event>
+                                <event name="OnLeftDClick"></event>
+                                <event name="OnLeftDown"></event>
+                                <event name="OnLeftUp"></event>
+                                <event name="OnMiddleDClick"></event>
+                                <event name="OnMiddleDown"></event>
+                                <event name="OnMiddleUp"></event>
+                                <event name="OnMotion"></event>
+                                <event name="OnMouseEvents"></event>
+                                <event name="OnMouseWheel"></event>
+                                <event name="OnPaint"></event>
+                                <event name="OnRightDClick"></event>
+                                <event name="OnRightDown"></event>
+                                <event name="OnRightUp"></event>
+                                <event name="OnSetFocus"></event>
+                                <event name="OnSize"></event>
+                                <event name="OnUpdateUI"></event>
+                            </object>
+                        </object>
+                        <object class="sizeritem" expanded="0">
+                            <property name="border">5</property>
+                            <property name="flag">wxEXPAND</property>
+                            <property name="proportion">1</property>
+                            <object class="wxButton" expanded="0">
+                                <property name="BottomDockable">1</property>
+                                <property name="LeftDockable">1</property>
+                                <property name="RightDockable">1</property>
+                                <property name="TopDockable">1</property>
+                                <property name="aui_layer"></property>
+                                <property name="aui_name"></property>
+                                <property name="aui_position"></property>
+                                <property name="aui_row"></property>
+                                <property name="best_size"></property>
+                                <property name="bg"></property>
+                                <property name="caption"></property>
+                                <property name="caption_visible">1</property>
+                                <property name="center_pane">0</property>
+                                <property name="close_button">1</property>
+                                <property name="context_help"></property>
+                                <property name="context_menu">1</property>
+                                <property name="default">0</property>
+                                <property name="default_pane">0</property>
+                                <property name="dock">Dock</property>
+                                <property name="dock_fixed">0</property>
+                                <property name="docking">Left</property>
+                                <property name="enabled">1</property>
+                                <property name="fg"></property>
+                                <property name="floatable">1</property>
+                                <property name="font"></property>
+                                <property name="gripper">0</property>
+                                <property name="hidden">0</property>
+                                <property name="id">wxID_ANY</property>
+                                <property name="label">Copy</property>
+                                <property name="max_size"></property>
+                                <property name="maximize_button">0</property>
+                                <property name="maximum_size"></property>
+                                <property name="min_size"></property>
+                                <property name="minimize_button">0</property>
+                                <property name="minimum_size"></property>
+                                <property name="moveable">1</property>
+                                <property name="name">m_copyButton</property>
+                                <property name="pane_border">1</property>
+                                <property name="pane_position"></property>
+                                <property name="pane_size"></property>
+                                <property name="permission">protected</property>
+                                <property name="pin_button">1</property>
+                                <property name="pos"></property>
+                                <property name="resize">Resizable</property>
+                                <property name="show">1</property>
+                                <property name="size"></property>
+                                <property name="style"></property>
+                                <property name="subclass"></property>
+                                <property name="toolbar_pane">0</property>
+                                <property name="tooltip"></property>
+                                <property name="validator_data_type"></property>
+                                <property name="validator_style">wxFILTER_NONE</property>
+                                <property name="validator_type">wxDefaultValidator</property>
+                                <property name="validator_variable"></property>
+                                <property name="window_extra_style"></property>
+                                <property name="window_name"></property>
+                                <property name="window_style"></property>
+                                <event name="OnButtonClick">OnCopy</event>
+                                <event name="OnChar"></event>
+                                <event name="OnEnterWindow"></event>
+                                <event name="OnEraseBackground"></event>
+                                <event name="OnKeyDown"></event>
+                                <event name="OnKeyUp"></event>
+                                <event name="OnKillFocus"></event>
+                                <event name="OnLeaveWindow"></event>
+                                <event name="OnLeftDClick"></event>
+                                <event name="OnLeftDown"></event>
+                                <event name="OnLeftUp"></event>
+                                <event name="OnMiddleDClick"></event>
+                                <event name="OnMiddleDown"></event>
+                                <event name="OnMiddleUp"></event>
+                                <event name="OnMotion"></event>
+                                <event name="OnMouseEvents"></event>
+                                <event name="OnMouseWheel"></event>
+                                <event name="OnPaint"></event>
+                                <event name="OnRightDClick"></event>
+                                <event name="OnRightDown"></event>
+                                <event name="OnRightUp"></event>
+                                <event name="OnSetFocus"></event>
+                                <event name="OnSize"></event>
+                                <event name="OnUpdateUI"></event>
+                            </object>
+                        </object>
+                        <object class="sizeritem" expanded="0">
+                            <property name="border">5</property>
+                            <property name="flag">wxEXPAND</property>
+                            <property name="proportion">1</property>
+                            <object class="wxButton" expanded="0">
+                                <property name="BottomDockable">1</property>
+                                <property name="LeftDockable">1</property>
+                                <property name="RightDockable">1</property>
+                                <property name="TopDockable">1</property>
+                                <property name="aui_layer"></property>
+                                <property name="aui_name"></property>
+                                <property name="aui_position"></property>
+                                <property name="aui_row"></property>
+                                <property name="best_size"></property>
+                                <property name="bg"></property>
+                                <property name="caption"></property>
+                                <property name="caption_visible">1</property>
+                                <property name="center_pane">0</property>
+                                <property name="close_button">1</property>
+                                <property name="context_help"></property>
+                                <property name="context_menu">1</property>
+                                <property name="default">0</property>
+                                <property name="default_pane">0</property>
+                                <property name="dock">Dock</property>
+                                <property name="dock_fixed">0</property>
+                                <property name="docking">Left</property>
+                                <property name="enabled">1</property>
+                                <property name="fg"></property>
+                                <property name="floatable">1</property>
+                                <property name="font"></property>
+                                <property name="gripper">0</property>
+                                <property name="hidden">0</property>
+                                <property name="id">wxID_ANY</property>
+                                <property name="label">Stop</property>
+                                <property name="max_size"></property>
+                                <property name="maximize_button">0</property>
+                                <property name="maximum_size"></property>
+                                <property name="min_size"></property>
+                                <property name="minimize_button">0</property>
+                                <property name="minimum_size"></property>
+                                <property name="moveable">1</property>
+                                <property name="name">m_pauseButton</property>
+                                <property name="pane_border">1</property>
+                                <property name="pane_position"></property>
+                                <property name="pane_size"></property>
+                                <property name="permission">protected</property>
+                                <property name="pin_button">1</property>
+                                <property name="pos"></property>
+                                <property name="resize">Resizable</property>
+                                <property name="show">1</property>
+                                <property name="size"></property>
+                                <property name="style"></property>
+                                <property name="subclass"></property>
+                                <property name="toolbar_pane">0</property>
+                                <property name="tooltip"></property>
+                                <property name="validator_data_type"></property>
+                                <property name="validator_style">wxFILTER_NONE</property>
+                                <property name="validator_type">wxDefaultValidator</property>
+                                <property name="validator_variable"></property>
+                                <property name="window_extra_style"></property>
+                                <property name="window_name"></property>
+                                <property name="window_style"></property>
+                                <event name="OnButtonClick">OnPause</event>
+                                <event name="OnChar"></event>
+                                <event name="OnEnterWindow"></event>
+                                <event name="OnEraseBackground"></event>
+                                <event name="OnKeyDown"></event>
+                                <event name="OnKeyUp"></event>
+                                <event name="OnKillFocus"></event>
+                                <event name="OnLeaveWindow"></event>
+                                <event name="OnLeftDClick"></event>
+                                <event name="OnLeftDown"></event>
+                                <event name="OnLeftUp"></event>
+                                <event name="OnMiddleDClick"></event>
+                                <event name="OnMiddleDown"></event>
+                                <event name="OnMiddleUp"></event>
+                                <event name="OnMotion"></event>
+                                <event name="OnMouseEvents"></event>
+                                <event name="OnMouseWheel"></event>
+                                <event name="OnPaint"></event>
+                                <event name="OnRightDClick"></event>
+                                <event name="OnRightDown"></event>
+                                <event name="OnRightUp"></event>
+                                <event name="OnSetFocus"></event>
+                                <event name="OnSize"></event>
+                                <event name="OnUpdateUI"></event>
+                            </object>
+                        </object>
+                    </object>
+                </object>
+            </object>
+            <object class="wxTimer" expanded="1">
+                <property name="enabled">1</property>
+                <property name="id">wxID_ANY</property>
+                <property name="name">m_refreshTimer</property>
+                <property name="oneshot">0</property>
+                <property name="period">250</property>
+                <property name="permission">protected</property>
+                <event name="OnTimer">DoRefresh</event>
+            </object>
+        </object>
+    </object>
+</wxFormBuilder_Project>
diff --git a/src/forms/DigitalConsole/DigitalConsole.h b/src/forms/DigitalConsole/DigitalConsole.h
new file mode 100644
index 0000000..5d5321d
--- /dev/null
+++ b/src/forms/DigitalConsole/DigitalConsole.h
@@ -0,0 +1,61 @@
+#pragma once
+
+#include <map>
+#include <vector>
+#include <sstream>
+#include <ostream>
+#include <mutex>
+
+#include "DigitalConsoleFrame.h"
+#include "ModemDigital.h"
+
+class ModemDigitalOutputConsole;
+class DigitalConsole: public DigitalConsoleFrame {
+public:
+    DigitalConsole( wxWindow* parent, ModemDigitalOutputConsole *doParent );
+    ~DigitalConsole();
+
+
+    void write(std::string outp);
+    void write(char outc);
+    
+private:
+    void DoRefresh( wxTimerEvent& event );
+    void OnClose( wxCloseEvent& event );
+    void OnClear( wxCommandEvent& event );
+    
+    void OnCopy( wxCommandEvent& event );
+    void OnPause( wxCommandEvent& event );
+
+    std::stringstream streamBuf;
+    std::mutex stream_busy;
+    std::atomic<bool> streamWritten;
+    std::atomic<bool> streamPaused;
+    ModemDigitalOutputConsole *doParent;
+};
+
+class ModemDigitalOutputConsole: public ModemDigitalOutput {
+public:
+    ModemDigitalOutputConsole();
+    ~ModemDigitalOutputConsole();
+    
+    void setDialog(DigitalConsole *dialog_in);
+    DigitalConsole *getDialog();
+
+    void setTitle(std::string title);
+    
+    void write(std::string outp);
+    void write(char outc);
+    
+    void Show();
+    void Hide();
+    void Close();
+
+private:
+    DigitalConsole *dialog;
+    std::stringstream streamBuf;
+    std::mutex stream_busy;
+    std::atomic<bool> streamWritten;
+    std::string dialogTitle;
+};
+
diff --git a/src/forms/DigitalConsole/DigitalConsoleFrame.cpp b/src/forms/DigitalConsole/DigitalConsoleFrame.cpp
new file mode 100644
index 0000000..b1e4885
--- /dev/null
+++ b/src/forms/DigitalConsole/DigitalConsoleFrame.cpp
@@ -0,0 +1,73 @@
+///////////////////////////////////////////////////////////////////////////
+// C++ code generated with wxFormBuilder (version Aug 23 2015)
+// http://www.wxformbuilder.org/
+//
+// PLEASE DO "NOT" EDIT THIS FILE!
+///////////////////////////////////////////////////////////////////////////
+
+#include "DigitalConsoleFrame.h"
+
+///////////////////////////////////////////////////////////////////////////
+
+DigitalConsoleFrame::DigitalConsoleFrame( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxFrame( parent, id, title, pos, size, style )
+{
+	this->SetSizeHints( wxDefaultSize, wxDefaultSize );
+	this->SetExtraStyle( wxWS_EX_PROCESS_UI_UPDATES );
+	
+	wxBoxSizer* mainSizer;
+	mainSizer = new wxBoxSizer( wxVERTICAL );
+	
+	wxBoxSizer* dataViewSizer;
+	dataViewSizer = new wxBoxSizer( wxVERTICAL );
+	
+	m_dataView = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_CHARWRAP|wxTE_MULTILINE|wxTE_NOHIDESEL|wxTE_READONLY|wxTE_WORDWRAP|wxALWAYS_SHOW_SB|wxFULL_REPAINT_ON_RESIZE|wxNO_BORDER|wxSIMPLE_BORDER|wxVSCROLL );
+	m_dataView->SetExtraStyle( wxWS_EX_PROCESS_UI_UPDATES );
+	m_dataView->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), 76, 90, 90, false, wxEmptyString ) );
+	
+	dataViewSizer->Add( m_dataView, 1, wxEXPAND, 5 );
+	
+	
+	mainSizer->Add( dataViewSizer, 1, wxEXPAND, 5 );
+	
+	wxBoxSizer* buttonSizer;
+	buttonSizer = new wxBoxSizer( wxHORIZONTAL );
+	
+	m_clearButton = new wxButton( this, wxID_ANY, wxT("Clear"), wxDefaultPosition, wxDefaultSize, 0 );
+	buttonSizer->Add( m_clearButton, 1, wxEXPAND, 5 );
+	
+	m_copyButton = new wxButton( this, wxID_ANY, wxT("Copy"), wxDefaultPosition, wxDefaultSize, 0 );
+	buttonSizer->Add( m_copyButton, 1, wxEXPAND, 5 );
+	
+	m_pauseButton = new wxButton( this, wxID_ANY, wxT("Stop"), wxDefaultPosition, wxDefaultSize, 0 );
+	buttonSizer->Add( m_pauseButton, 1, wxEXPAND, 5 );
+	
+	
+	mainSizer->Add( buttonSizer, 0, wxALL|wxEXPAND, 5 );
+	
+	
+	this->SetSizer( mainSizer );
+	this->Layout();
+	m_refreshTimer.SetOwner( this, wxID_ANY );
+	m_refreshTimer.Start( 250 );
+	
+	
+	this->Centre( wxBOTH );
+	
+	// Connect Events
+	this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( DigitalConsoleFrame::OnClose ) );
+	m_clearButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DigitalConsoleFrame::OnClear ), NULL, this );
+	m_copyButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DigitalConsoleFrame::OnCopy ), NULL, this );
+	m_pauseButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DigitalConsoleFrame::OnPause ), NULL, this );
+	this->Connect( wxID_ANY, wxEVT_TIMER, wxTimerEventHandler( DigitalConsoleFrame::DoRefresh ) );
+}
+
+DigitalConsoleFrame::~DigitalConsoleFrame()
+{
+	// Disconnect Events
+	this->Disconnect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( DigitalConsoleFrame::OnClose ) );
+	m_clearButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DigitalConsoleFrame::OnClear ), NULL, this );
+	m_copyButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DigitalConsoleFrame::OnCopy ), NULL, this );
+	m_pauseButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DigitalConsoleFrame::OnPause ), NULL, this );
+	this->Disconnect( wxID_ANY, wxEVT_TIMER, wxTimerEventHandler( DigitalConsoleFrame::DoRefresh ) );
+	
+}
diff --git a/src/forms/DigitalConsole/DigitalConsoleFrame.h b/src/forms/DigitalConsole/DigitalConsoleFrame.h
new file mode 100644
index 0000000..6665b66
--- /dev/null
+++ b/src/forms/DigitalConsole/DigitalConsoleFrame.h
@@ -0,0 +1,57 @@
+///////////////////////////////////////////////////////////////////////////
+// C++ code generated with wxFormBuilder (version Aug 23 2015)
+// http://www.wxformbuilder.org/
+//
+// PLEASE DO "NOT" EDIT THIS FILE!
+///////////////////////////////////////////////////////////////////////////
+
+#ifndef __DIGITALCONSOLEFRAME_H__
+#define __DIGITALCONSOLEFRAME_H__
+
+#include <wx/artprov.h>
+#include <wx/xrc/xmlres.h>
+#include <wx/string.h>
+#include <wx/textctrl.h>
+#include <wx/gdicmn.h>
+#include <wx/font.h>
+#include <wx/colour.h>
+#include <wx/settings.h>
+#include <wx/sizer.h>
+#include <wx/button.h>
+#include <wx/timer.h>
+#include <wx/frame.h>
+
+///////////////////////////////////////////////////////////////////////////
+
+
+///////////////////////////////////////////////////////////////////////////////
+/// Class DigitalConsoleFrame
+///////////////////////////////////////////////////////////////////////////////
+class DigitalConsoleFrame : public wxFrame 
+{
+	private:
+	
+	protected:
+		wxTextCtrl* m_dataView;
+		wxButton* m_clearButton;
+		wxButton* m_copyButton;
+		wxButton* m_pauseButton;
+		wxTimer m_refreshTimer;
+		
+		// Virtual event handlers, overide them in your derived class
+		virtual void OnClose( wxCloseEvent& event ) { event.Skip(); }
+		virtual void OnClear( wxCommandEvent& event ) { event.Skip(); }
+		virtual void OnCopy( wxCommandEvent& event ) { event.Skip(); }
+		virtual void OnPause( wxCommandEvent& event ) { event.Skip(); }
+		virtual void DoRefresh( wxTimerEvent& event ) { event.Skip(); }
+		
+	
+	public:
+		
+		DigitalConsoleFrame( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxT("Digital Output"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 441,394 ), long style = wxCAPTION|wxFRAME_FLOAT_ON_PARENT|wxMAXIMIZE|wxMAXIMIZE_BOX|wxMINIMIZE|wxMINIMIZE_BOX|wxRESIZE_BORDER|wxFULL_REPAINT_ON_RESIZE|wxTAB_TRAVERSAL );
+		
+		~DigitalConsoleFrame();
+	
+};
+
+#endif //__DIGITALCONSOLEFRAME_H__
diff --git a/src/forms/SDRDevices/SDRDeviceAdd.cpp b/src/forms/SDRDevices/SDRDeviceAdd.cpp
new file mode 100644
index 0000000..e7bcd39
--- /dev/null
+++ b/src/forms/SDRDevices/SDRDeviceAdd.cpp
@@ -0,0 +1,59 @@
+#include "SDRDeviceAdd.h"
+
+#include "SDREnumerator.h"
+
+SDRDeviceAddDialog::SDRDeviceAddDialog( wxWindow* parent ): SDRDeviceAddForm( parent ) {
+    okPressed = false;
+    selectedModule = "";
+    moduleParam = "";
+    selectedModule = "SoapyRemote";
+    
+    m_soapyModule->Append("SoapyRemote");
+    m_paramLabel->SetLabel("Remote Address (address[:port])");
+    
+    std::vector<std::string> &factories = SDREnumerator::getFactories();
+    std::vector<std::string>::iterator factory_i;
+    
+    for (factory_i = factories.begin(); factory_i != factories.end(); factory_i++) {
+        if (*factory_i != "remote" && *factory_i != "null") {
+            m_soapyModule->Append(*factory_i);
+        }
+    }
+}
+
+void SDRDeviceAddDialog::OnSoapyModuleChanged( wxCommandEvent& /* event */) {
+    wxString strSel = m_soapyModule->GetStringSelection();
+    
+    selectedModule = strSel.ToStdString();
+    
+    if (selectedModule == "SoapyRemote") {
+        m_paramLabel->SetLabelText("Remote Address (address[:port])");
+    } else {
+        m_paramLabel->SetLabel("SoapySDR Device Parameters, i.e. 'addr=192.168.1.105'");
+    }
+}
+
+void SDRDeviceAddDialog::OnCancelButton( wxCommandEvent& /* event */) {
+    okPressed = false;
+    Close(true);
+}
+
+void SDRDeviceAddDialog::OnOkButton( wxCommandEvent& /* event */) {
+    wxString strSel = m_soapyModule->GetStringSelection();
+    selectedModule = strSel.ToStdString();
+    moduleParam = m_paramText->GetValue().ToStdString();
+    okPressed = true;
+    Close(true);
+}
+
+bool SDRDeviceAddDialog::wasOkPressed() {
+    return okPressed;
+}
+
+std::string SDRDeviceAddDialog::getSelectedModule() {
+    return selectedModule;
+}
+
+std::string SDRDeviceAddDialog::getModuleParam() {
+    return moduleParam;
+}
diff --git a/src/forms/SDRDevices/SDRDeviceAdd.fbp b/src/forms/SDRDevices/SDRDeviceAdd.fbp
new file mode 100644
index 0000000..f5cb574
--- /dev/null
+++ b/src/forms/SDRDevices/SDRDeviceAdd.fbp
@@ -0,0 +1,681 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
+<wxFormBuilder_Project>
+    <FileVersion major="1" minor="13" />
+    <object class="Project" expanded="1">
+        <property name="class_decoration"></property>
+        <property name="code_generation">C++</property>
+        <property name="disconnect_events">1</property>
+        <property name="disconnect_mode">source_name</property>
+        <property name="disconnect_php_events">0</property>
+        <property name="disconnect_python_events">0</property>
+        <property name="embedded_files_path">res</property>
+        <property name="encoding">UTF-8</property>
+        <property name="event_generation">connect</property>
+        <property name="file">SDRDeviceAddForm</property>
+        <property name="first_id">1000</property>
+        <property name="help_provider">none</property>
+        <property name="internationalize">0</property>
+        <property name="name">SDRDeviceAddForm</property>
+        <property name="namespace"></property>
+        <property name="path">.</property>
+        <property name="precompiled_header"></property>
+        <property name="relative_path">1</property>
+        <property name="skip_lua_events">1</property>
+        <property name="skip_php_events">1</property>
+        <property name="skip_python_events">1</property>
+        <property name="ui_table">UI</property>
+        <property name="use_enum">0</property>
+        <property name="use_microsoft_bom">0</property>
+        <object class="Dialog" expanded="1">
+            <property name="aui_managed">0</property>
+            <property name="aui_manager_style">wxAUI_MGR_DEFAULT</property>
+            <property name="bg"></property>
+            <property name="center">wxBOTH</property>
+            <property name="context_help"></property>
+            <property name="context_menu">1</property>
+            <property name="enabled">1</property>
+            <property name="event_handler">impl_virtual</property>
+            <property name="extra_style"></property>
+            <property name="fg"></property>
+            <property name="font"></property>
+            <property name="hidden">0</property>
+            <property name="id">wxID_ANY</property>
+            <property name="maximum_size"></property>
+            <property name="minimum_size"></property>
+            <property name="name">SDRDeviceAddForm</property>
+            <property name="pos"></property>
+            <property name="size">395,293</property>
+            <property name="style">wxDEFAULT_DIALOG_STYLE</property>
+            <property name="subclass"></property>
+            <property name="title">Add SoapySDR Device</property>
+            <property name="tooltip"></property>
+            <property name="window_extra_style"></property>
+            <property name="window_name"></property>
+            <property name="window_style"></property>
+            <event name="OnActivate"></event>
+            <event name="OnActivateApp"></event>
+            <event name="OnAuiFindManager"></event>
+            <event name="OnAuiPaneButton"></event>
+            <event name="OnAuiPaneClose"></event>
+            <event name="OnAuiPaneMaximize"></event>
+            <event name="OnAuiPaneRestore"></event>
+            <event name="OnAuiRender"></event>
+            <event name="OnChar"></event>
+            <event name="OnClose"></event>
+            <event name="OnEnterWindow"></event>
+            <event name="OnEraseBackground"></event>
+            <event name="OnHibernate"></event>
+            <event name="OnIconize"></event>
+            <event name="OnIdle"></event>
+            <event name="OnInitDialog"></event>
+            <event name="OnKeyDown"></event>
+            <event name="OnKeyUp"></event>
+            <event name="OnKillFocus"></event>
+            <event name="OnLeaveWindow"></event>
+            <event name="OnLeftDClick"></event>
+            <event name="OnLeftDown"></event>
+            <event name="OnLeftUp"></event>
+            <event name="OnMiddleDClick"></event>
+            <event name="OnMiddleDown"></event>
+            <event name="OnMiddleUp"></event>
+            <event name="OnMotion"></event>
+            <event name="OnMouseEvents"></event>
+            <event name="OnMouseWheel"></event>
+            <event name="OnPaint"></event>
+            <event name="OnRightDClick"></event>
+            <event name="OnRightDown"></event>
+            <event name="OnRightUp"></event>
+            <event name="OnSetFocus"></event>
+            <event name="OnSize"></event>
+            <event name="OnUpdateUI"></event>
+            <object class="wxBoxSizer" expanded="1">
+                <property name="minimum_size"></property>
+                <property name="name">bSizer6</property>
+                <property name="orient">wxVERTICAL</property>
+                <property name="permission">none</property>
+                <object class="sizeritem" expanded="1">
+                    <property name="border">8</property>
+                    <property name="flag">wxALL</property>
+                    <property name="proportion">0</property>
+                    <object class="wxStaticText" expanded="1">
+                        <property name="BottomDockable">1</property>
+                        <property name="LeftDockable">1</property>
+                        <property name="RightDockable">1</property>
+                        <property name="TopDockable">1</property>
+                        <property name="aui_layer"></property>
+                        <property name="aui_name"></property>
+                        <property name="aui_position"></property>
+                        <property name="aui_row"></property>
+                        <property name="best_size"></property>
+                        <property name="bg"></property>
+                        <property name="caption"></property>
+                        <property name="caption_visible">1</property>
+                        <property name="center_pane">0</property>
+                        <property name="close_button">1</property>
+                        <property name="context_help"></property>
+                        <property name="context_menu">1</property>
+                        <property name="default_pane">0</property>
+                        <property name="dock">Dock</property>
+                        <property name="dock_fixed">0</property>
+                        <property name="docking">Left</property>
+                        <property name="enabled">1</property>
+                        <property name="fg"></property>
+                        <property name="floatable">1</property>
+                        <property name="font"></property>
+                        <property name="gripper">0</property>
+                        <property name="hidden">0</property>
+                        <property name="id">wxID_ANY</property>
+                        <property name="label">Manually add a SoapyRemote or SoapySDR device.  &#x0A;&#x0A;Useful for a device that is not detected automatically.</property>
+                        <property name="max_size"></property>
+                        <property name="maximize_button">0</property>
+                        <property name="maximum_size"></property>
+                        <property name="min_size"></property>
+                        <property name="minimize_button">0</property>
+                        <property name="minimum_size"></property>
+                        <property name="moveable">1</property>
+                        <property name="name">m_staticText4</property>
+                        <property name="pane_border">1</property>
+                        <property name="pane_position"></property>
+                        <property name="pane_size"></property>
+                        <property name="permission">protected</property>
+                        <property name="pin_button">1</property>
+                        <property name="pos"></property>
+                        <property name="resize">Resizable</property>
+                        <property name="show">1</property>
+                        <property name="size"></property>
+                        <property name="style"></property>
+                        <property name="subclass"></property>
+                        <property name="toolbar_pane">0</property>
+                        <property name="tooltip"></property>
+                        <property name="window_extra_style"></property>
+                        <property name="window_name"></property>
+                        <property name="window_style"></property>
+                        <property name="wrap">-1</property>
+                        <event name="OnChar"></event>
+                        <event name="OnEnterWindow"></event>
+                        <event name="OnEraseBackground"></event>
+                        <event name="OnKeyDown"></event>
+                        <event name="OnKeyUp"></event>
+                        <event name="OnKillFocus"></event>
+                        <event name="OnLeaveWindow"></event>
+                        <event name="OnLeftDClick"></event>
+                        <event name="OnLeftDown"></event>
+                        <event name="OnLeftUp"></event>
+                        <event name="OnMiddleDClick"></event>
+                        <event name="OnMiddleDown"></event>
+                        <event name="OnMiddleUp"></event>
+                        <event name="OnMotion"></event>
+                        <event name="OnMouseEvents"></event>
+                        <event name="OnMouseWheel"></event>
+                        <event name="OnPaint"></event>
+                        <event name="OnRightDClick"></event>
+                        <event name="OnRightDown"></event>
+                        <event name="OnRightUp"></event>
+                        <event name="OnSetFocus"></event>
+                        <event name="OnSize"></event>
+                        <event name="OnUpdateUI"></event>
+                    </object>
+                </object>
+                <object class="sizeritem" expanded="1">
+                    <property name="border">5</property>
+                    <property name="flag">wxEXPAND</property>
+                    <property name="proportion">1</property>
+                    <object class="spacer" expanded="1">
+                        <property name="height">0</property>
+                        <property name="permission">protected</property>
+                        <property name="width">0</property>
+                    </object>
+                </object>
+                <object class="sizeritem" expanded="1">
+                    <property name="border">8</property>
+                    <property name="flag">wxALL</property>
+                    <property name="proportion">0</property>
+                    <object class="wxChoice" expanded="1">
+                        <property name="BottomDockable">1</property>
+                        <property name="LeftDockable">1</property>
+                        <property name="RightDockable">1</property>
+                        <property name="TopDockable">1</property>
+                        <property name="aui_layer"></property>
+                        <property name="aui_name"></property>
+                        <property name="aui_position"></property>
+                        <property name="aui_row"></property>
+                        <property name="best_size"></property>
+                        <property name="bg"></property>
+                        <property name="caption"></property>
+                        <property name="caption_visible">1</property>
+                        <property name="center_pane">0</property>
+                        <property name="choices"></property>
+                        <property name="close_button">1</property>
+                        <property name="context_help"></property>
+                        <property name="context_menu">1</property>
+                        <property name="default_pane">0</property>
+                        <property name="dock">Dock</property>
+                        <property name="dock_fixed">0</property>
+                        <property name="docking">Left</property>
+                        <property name="enabled">1</property>
+                        <property name="fg"></property>
+                        <property name="floatable">1</property>
+                        <property name="font"></property>
+                        <property name="gripper">0</property>
+                        <property name="hidden">0</property>
+                        <property name="id">wxID_ANY</property>
+                        <property name="max_size"></property>
+                        <property name="maximize_button">0</property>
+                        <property name="maximum_size"></property>
+                        <property name="min_size"></property>
+                        <property name="minimize_button">0</property>
+                        <property name="minimum_size"></property>
+                        <property name="moveable">1</property>
+                        <property name="name">m_soapyModule</property>
+                        <property name="pane_border">1</property>
+                        <property name="pane_position"></property>
+                        <property name="pane_size"></property>
+                        <property name="permission">protected</property>
+                        <property name="pin_button">1</property>
+                        <property name="pos"></property>
+                        <property name="resize">Resizable</property>
+                        <property name="selection">0</property>
+                        <property name="show">1</property>
+                        <property name="size"></property>
+                        <property name="style"></property>
+                        <property name="subclass"></property>
+                        <property name="toolbar_pane">0</property>
+                        <property name="tooltip"></property>
+                        <property name="validator_data_type"></property>
+                        <property name="validator_style">wxFILTER_NONE</property>
+                        <property name="validator_type">wxDefaultValidator</property>
+                        <property name="validator_variable"></property>
+                        <property name="window_extra_style"></property>
+                        <property name="window_name"></property>
+                        <property name="window_style"></property>
+                        <event name="OnChar"></event>
+                        <event name="OnChoice">OnSoapyModuleChanged</event>
+                        <event name="OnEnterWindow"></event>
+                        <event name="OnEraseBackground"></event>
+                        <event name="OnKeyDown"></event>
+                        <event name="OnKeyUp"></event>
+                        <event name="OnKillFocus"></event>
+                        <event name="OnLeaveWindow"></event>
+                        <event name="OnLeftDClick"></event>
+                        <event name="OnLeftDown"></event>
+                        <event name="OnLeftUp"></event>
+                        <event name="OnMiddleDClick"></event>
+                        <event name="OnMiddleDown"></event>
+                        <event name="OnMiddleUp"></event>
+                        <event name="OnMotion"></event>
+                        <event name="OnMouseEvents"></event>
+                        <event name="OnMouseWheel"></event>
+                        <event name="OnPaint"></event>
+                        <event name="OnRightDClick"></event>
+                        <event name="OnRightDown"></event>
+                        <event name="OnRightUp"></event>
+                        <event name="OnSetFocus"></event>
+                        <event name="OnSize"></event>
+                        <event name="OnUpdateUI"></event>
+                    </object>
+                </object>
+                <object class="sizeritem" expanded="1">
+                    <property name="border">5</property>
+                    <property name="flag">wxEXPAND</property>
+                    <property name="proportion">1</property>
+                    <object class="spacer" expanded="1">
+                        <property name="height">0</property>
+                        <property name="permission">protected</property>
+                        <property name="width">0</property>
+                    </object>
+                </object>
+                <object class="sizeritem" expanded="1">
+                    <property name="border">8</property>
+                    <property name="flag">wxALL</property>
+                    <property name="proportion">0</property>
+                    <object class="wxStaticText" expanded="1">
+                        <property name="BottomDockable">1</property>
+                        <property name="LeftDockable">1</property>
+                        <property name="RightDockable">1</property>
+                        <property name="TopDockable">1</property>
+                        <property name="aui_layer"></property>
+                        <property name="aui_name"></property>
+                        <property name="aui_position"></property>
+                        <property name="aui_row"></property>
+                        <property name="best_size"></property>
+                        <property name="bg"></property>
+                        <property name="caption"></property>
+                        <property name="caption_visible">1</property>
+                        <property name="center_pane">0</property>
+                        <property name="close_button">1</property>
+                        <property name="context_help"></property>
+                        <property name="context_menu">1</property>
+                        <property name="default_pane">0</property>
+                        <property name="dock">Dock</property>
+                        <property name="dock_fixed">0</property>
+                        <property name="docking">Left</property>
+                        <property name="enabled">1</property>
+                        <property name="fg"></property>
+                        <property name="floatable">1</property>
+                        <property name="font"></property>
+                        <property name="gripper">0</property>
+                        <property name="hidden">0</property>
+                        <property name="id">wxID_ANY</property>
+                        <property name="label"><Parameter></property>
+                        <property name="max_size"></property>
+                        <property name="maximize_button">0</property>
+                        <property name="maximum_size"></property>
+                        <property name="min_size"></property>
+                        <property name="minimize_button">0</property>
+                        <property name="minimum_size"></property>
+                        <property name="moveable">1</property>
+                        <property name="name">m_paramLabel</property>
+                        <property name="pane_border">1</property>
+                        <property name="pane_position"></property>
+                        <property name="pane_size"></property>
+                        <property name="permission">protected</property>
+                        <property name="pin_button">1</property>
+                        <property name="pos"></property>
+                        <property name="resize">Resizable</property>
+                        <property name="show">1</property>
+                        <property name="size"></property>
+                        <property name="style"></property>
+                        <property name="subclass"></property>
+                        <property name="toolbar_pane">0</property>
+                        <property name="tooltip"></property>
+                        <property name="window_extra_style"></property>
+                        <property name="window_name"></property>
+                        <property name="window_style"></property>
+                        <property name="wrap">-1</property>
+                        <event name="OnChar"></event>
+                        <event name="OnEnterWindow"></event>
+                        <event name="OnEraseBackground"></event>
+                        <event name="OnKeyDown"></event>
+                        <event name="OnKeyUp"></event>
+                        <event name="OnKillFocus"></event>
+                        <event name="OnLeaveWindow"></event>
+                        <event name="OnLeftDClick"></event>
+                        <event name="OnLeftDown"></event>
+                        <event name="OnLeftUp"></event>
+                        <event name="OnMiddleDClick"></event>
+                        <event name="OnMiddleDown"></event>
+                        <event name="OnMiddleUp"></event>
+                        <event name="OnMotion"></event>
+                        <event name="OnMouseEvents"></event>
+                        <event name="OnMouseWheel"></event>
+                        <event name="OnPaint"></event>
+                        <event name="OnRightDClick"></event>
+                        <event name="OnRightDown"></event>
+                        <event name="OnRightUp"></event>
+                        <event name="OnSetFocus"></event>
+                        <event name="OnSize"></event>
+                        <event name="OnUpdateUI"></event>
+                    </object>
+                </object>
+                <object class="sizeritem" expanded="1">
+                    <property name="border">8</property>
+                    <property name="flag">wxALL|wxEXPAND</property>
+                    <property name="proportion">1</property>
+                    <object class="wxTextCtrl" expanded="1">
+                        <property name="BottomDockable">1</property>
+                        <property name="LeftDockable">1</property>
+                        <property name="RightDockable">1</property>
+                        <property name="TopDockable">1</property>
+                        <property name="aui_layer"></property>
+                        <property name="aui_name"></property>
+                        <property name="aui_position"></property>
+                        <property name="aui_row"></property>
+                        <property name="best_size"></property>
+                        <property name="bg"></property>
+                        <property name="caption"></property>
+                        <property name="caption_visible">1</property>
+                        <property name="center_pane">0</property>
+                        <property name="close_button">1</property>
+                        <property name="context_help"></property>
+                        <property name="context_menu">1</property>
+                        <property name="default_pane">0</property>
+                        <property name="dock">Dock</property>
+                        <property name="dock_fixed">0</property>
+                        <property name="docking">Left</property>
+                        <property name="enabled">1</property>
+                        <property name="fg"></property>
+                        <property name="floatable">1</property>
+                        <property name="font"></property>
+                        <property name="gripper">0</property>
+                        <property name="hidden">0</property>
+                        <property name="id">wxID_ANY</property>
+                        <property name="max_size"></property>
+                        <property name="maximize_button">0</property>
+                        <property name="maximum_size"></property>
+                        <property name="maxlength"></property>
+                        <property name="min_size">-1,-1</property>
+                        <property name="minimize_button">0</property>
+                        <property name="minimum_size">-1,48</property>
+                        <property name="moveable">1</property>
+                        <property name="name">m_paramText</property>
+                        <property name="pane_border">1</property>
+                        <property name="pane_position"></property>
+                        <property name="pane_size"></property>
+                        <property name="permission">protected</property>
+                        <property name="pin_button">1</property>
+                        <property name="pos"></property>
+                        <property name="resize">Resizable</property>
+                        <property name="show">1</property>
+                        <property name="size"></property>
+                        <property name="style">wxTE_DONTWRAP</property>
+                        <property name="subclass"></property>
+                        <property name="toolbar_pane">0</property>
+                        <property name="tooltip"></property>
+                        <property name="validator_data_type"></property>
+                        <property name="validator_style">wxFILTER_NONE</property>
+                        <property name="validator_type">wxDefaultValidator</property>
+                        <property name="validator_variable"></property>
+                        <property name="value"></property>
+                        <property name="window_extra_style"></property>
+                        <property name="window_name"></property>
+                        <property name="window_style">wxHSCROLL</property>
+                        <event name="OnChar"></event>
+                        <event name="OnEnterWindow"></event>
+                        <event name="OnEraseBackground"></event>
+                        <event name="OnKeyDown"></event>
+                        <event name="OnKeyUp"></event>
+                        <event name="OnKillFocus"></event>
+                        <event name="OnLeaveWindow"></event>
+                        <event name="OnLeftDClick"></event>
+                        <event name="OnLeftDown"></event>
+                        <event name="OnLeftUp"></event>
+                        <event name="OnMiddleDClick"></event>
+                        <event name="OnMiddleDown"></event>
+                        <event name="OnMiddleUp"></event>
+                        <event name="OnMotion"></event>
+                        <event name="OnMouseEvents"></event>
+                        <event name="OnMouseWheel"></event>
+                        <event name="OnPaint"></event>
+                        <event name="OnRightDClick"></event>
+                        <event name="OnRightDown"></event>
+                        <event name="OnRightUp"></event>
+                        <event name="OnSetFocus"></event>
+                        <event name="OnSize"></event>
+                        <event name="OnText"></event>
+                        <event name="OnTextEnter"></event>
+                        <event name="OnTextMaxLen"></event>
+                        <event name="OnTextURL"></event>
+                        <event name="OnUpdateUI"></event>
+                    </object>
+                </object>
+                <object class="sizeritem" expanded="1">
+                    <property name="border">5</property>
+                    <property name="flag">wxEXPAND</property>
+                    <property name="proportion">1</property>
+                    <object class="spacer" expanded="1">
+                        <property name="height">0</property>
+                        <property name="permission">protected</property>
+                        <property name="width">0</property>
+                    </object>
+                </object>
+                <object class="sizeritem" expanded="1">
+                    <property name="border">8</property>
+                    <property name="flag">wxEXPAND</property>
+                    <property name="proportion">1</property>
+                    <object class="wxBoxSizer" expanded="1">
+                        <property name="minimum_size"></property>
+                        <property name="name">bSizer7</property>
+                        <property name="orient">wxHORIZONTAL</property>
+                        <property name="permission">none</property>
+                        <object class="sizeritem" expanded="1">
+                            <property name="border">5</property>
+                            <property name="flag">wxEXPAND</property>
+                            <property name="proportion">1</property>
+                            <object class="spacer" expanded="1">
+                                <property name="height">0</property>
+                                <property name="permission">protected</property>
+                                <property name="width">0</property>
+                            </object>
+                        </object>
+                        <object class="sizeritem" expanded="1">
+                            <property name="border">2</property>
+                            <property name="flag">wxALL</property>
+                            <property name="proportion">0</property>
+                            <object class="wxButton" expanded="1">
+                                <property name="BottomDockable">1</property>
+                                <property name="LeftDockable">1</property>
+                                <property name="RightDockable">1</property>
+                                <property name="TopDockable">1</property>
+                                <property name="aui_layer"></property>
+                                <property name="aui_name"></property>
+                                <property name="aui_position"></property>
+                                <property name="aui_row"></property>
+                                <property name="best_size"></property>
+                                <property name="bg"></property>
+                                <property name="caption"></property>
+                                <property name="caption_visible">1</property>
+                                <property name="center_pane">0</property>
+                                <property name="close_button">1</property>
+                                <property name="context_help"></property>
+                                <property name="context_menu">1</property>
+                                <property name="default">0</property>
+                                <property name="default_pane">0</property>
+                                <property name="dock">Dock</property>
+                                <property name="dock_fixed">0</property>
+                                <property name="docking">Left</property>
+                                <property name="enabled">1</property>
+                                <property name="fg"></property>
+                                <property name="floatable">1</property>
+                                <property name="font"></property>
+                                <property name="gripper">0</property>
+                                <property name="hidden">0</property>
+                                <property name="id">wxID_ANY</property>
+                                <property name="label">Cancel</property>
+                                <property name="max_size"></property>
+                                <property name="maximize_button">0</property>
+                                <property name="maximum_size"></property>
+                                <property name="min_size"></property>
+                                <property name="minimize_button">0</property>
+                                <property name="minimum_size"></property>
+                                <property name="moveable">1</property>
+                                <property name="name">m_cancelButton</property>
+                                <property name="pane_border">1</property>
+                                <property name="pane_position"></property>
+                                <property name="pane_size"></property>
+                                <property name="permission">protected</property>
+                                <property name="pin_button">1</property>
+                                <property name="pos"></property>
+                                <property name="resize">Resizable</property>
+                                <property name="show">1</property>
+                                <property name="size"></property>
+                                <property name="style"></property>
+                                <property name="subclass"></property>
+                                <property name="toolbar_pane">0</property>
+                                <property name="tooltip"></property>
+                                <property name="validator_data_type"></property>
+                                <property name="validator_style">wxFILTER_NONE</property>
+                                <property name="validator_type">wxDefaultValidator</property>
+                                <property name="validator_variable"></property>
+                                <property name="window_extra_style"></property>
+                                <property name="window_name"></property>
+                                <property name="window_style"></property>
+                                <event name="OnButtonClick">OnCancelButton</event>
+                                <event name="OnChar"></event>
+                                <event name="OnEnterWindow"></event>
+                                <event name="OnEraseBackground"></event>
+                                <event name="OnKeyDown"></event>
+                                <event name="OnKeyUp"></event>
+                                <event name="OnKillFocus"></event>
+                                <event name="OnLeaveWindow"></event>
+                                <event name="OnLeftDClick"></event>
+                                <event name="OnLeftDown"></event>
+                                <event name="OnLeftUp"></event>
+                                <event name="OnMiddleDClick"></event>
+                                <event name="OnMiddleDown"></event>
+                                <event name="OnMiddleUp"></event>
+                                <event name="OnMotion"></event>
+                                <event name="OnMouseEvents"></event>
+                                <event name="OnMouseWheel"></event>
+                                <event name="OnPaint"></event>
+                                <event name="OnRightDClick"></event>
+                                <event name="OnRightDown"></event>
+                                <event name="OnRightUp"></event>
+                                <event name="OnSetFocus"></event>
+                                <event name="OnSize"></event>
+                                <event name="OnUpdateUI"></event>
+                            </object>
+                        </object>
+                        <object class="sizeritem" expanded="1">
+                            <property name="border">2</property>
+                            <property name="flag">wxALL</property>
+                            <property name="proportion">0</property>
+                            <object class="wxButton" expanded="1">
+                                <property name="BottomDockable">1</property>
+                                <property name="LeftDockable">1</property>
+                                <property name="RightDockable">1</property>
+                                <property name="TopDockable">1</property>
+                                <property name="aui_layer"></property>
+                                <property name="aui_name"></property>
+                                <property name="aui_position"></property>
+                                <property name="aui_row"></property>
+                                <property name="best_size"></property>
+                                <property name="bg"></property>
+                                <property name="caption"></property>
+                                <property name="caption_visible">1</property>
+                                <property name="center_pane">0</property>
+                                <property name="close_button">1</property>
+                                <property name="context_help"></property>
+                                <property name="context_menu">1</property>
+                                <property name="default">0</property>
+                                <property name="default_pane">0</property>
+                                <property name="dock">Dock</property>
+                                <property name="dock_fixed">0</property>
+                                <property name="docking">Left</property>
+                                <property name="enabled">1</property>
+                                <property name="fg"></property>
+                                <property name="floatable">1</property>
+                                <property name="font"></property>
+                                <property name="gripper">0</property>
+                                <property name="hidden">0</property>
+                                <property name="id">wxID_ANY</property>
+                                <property name="label">Ok</property>
+                                <property name="max_size"></property>
+                                <property name="maximize_button">0</property>
+                                <property name="maximum_size"></property>
+                                <property name="min_size"></property>
+                                <property name="minimize_button">0</property>
+                                <property name="minimum_size"></property>
+                                <property name="moveable">1</property>
+                                <property name="name">m_OkButton</property>
+                                <property name="pane_border">1</property>
+                                <property name="pane_position"></property>
+                                <property name="pane_size"></property>
+                                <property name="permission">protected</property>
+                                <property name="pin_button">1</property>
+                                <property name="pos"></property>
+                                <property name="resize">Resizable</property>
+                                <property name="show">1</property>
+                                <property name="size"></property>
+                                <property name="style"></property>
+                                <property name="subclass"></property>
+                                <property name="toolbar_pane">0</property>
+                                <property name="tooltip"></property>
+                                <property name="validator_data_type"></property>
+                                <property name="validator_style">wxFILTER_NONE</property>
+                                <property name="validator_type">wxDefaultValidator</property>
+                                <property name="validator_variable"></property>
+                                <property name="window_extra_style"></property>
+                                <property name="window_name"></property>
+                                <property name="window_style"></property>
+                                <event name="OnButtonClick">OnOkButton</event>
+                                <event name="OnChar"></event>
+                                <event name="OnEnterWindow"></event>
+                                <event name="OnEraseBackground"></event>
+                                <event name="OnKeyDown"></event>
+                                <event name="OnKeyUp"></event>
+                                <event name="OnKillFocus"></event>
+                                <event name="OnLeaveWindow"></event>
+                                <event name="OnLeftDClick"></event>
+                                <event name="OnLeftDown"></event>
+                                <event name="OnLeftUp"></event>
+                                <event name="OnMiddleDClick"></event>
+                                <event name="OnMiddleDown"></event>
+                                <event name="OnMiddleUp"></event>
+                                <event name="OnMotion"></event>
+                                <event name="OnMouseEvents"></event>
+                                <event name="OnMouseWheel"></event>
+                                <event name="OnPaint"></event>
+                                <event name="OnRightDClick"></event>
+                                <event name="OnRightDown"></event>
+                                <event name="OnRightUp"></event>
+                                <event name="OnSetFocus"></event>
+                                <event name="OnSize"></event>
+                                <event name="OnUpdateUI"></event>
+                            </object>
+                        </object>
+                    </object>
+                </object>
+                <object class="sizeritem" expanded="1">
+                    <property name="border">5</property>
+                    <property name="flag">wxEXPAND</property>
+                    <property name="proportion">1</property>
+                    <object class="spacer" expanded="1">
+                        <property name="height">0</property>
+                        <property name="permission">protected</property>
+                        <property name="width">0</property>
+                    </object>
+                </object>
+            </object>
+        </object>
+    </object>
+</wxFormBuilder_Project>
diff --git a/src/forms/SDRDevices/SDRDeviceAdd.h b/src/forms/SDRDevices/SDRDeviceAdd.h
new file mode 100644
index 0000000..649d130
--- /dev/null
+++ b/src/forms/SDRDevices/SDRDeviceAdd.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include "SDRDeviceAddForm.h"
+
+class SDRDeviceAddDialog : public SDRDeviceAddForm {
+public:
+    SDRDeviceAddDialog( wxWindow* parent );
+
+    void OnSoapyModuleChanged( wxCommandEvent& event );
+    void OnCancelButton( wxCommandEvent& event );
+    void OnOkButton( wxCommandEvent& event );
+    
+    bool wasOkPressed();
+    std::string getSelectedModule();
+    std::string getModuleParam();
+    
+private:
+    bool okPressed;
+    std::string selectedModule;
+    std::string moduleParam;
+};
\ No newline at end of file
diff --git a/src/forms/SDRDevices/SDRDeviceAddForm.cpp b/src/forms/SDRDevices/SDRDeviceAddForm.cpp
new file mode 100644
index 0000000..09afe15
--- /dev/null
+++ b/src/forms/SDRDevices/SDRDeviceAddForm.cpp
@@ -0,0 +1,83 @@
+///////////////////////////////////////////////////////////////////////////
+// C++ code generated with wxFormBuilder (version Aug 23 2015)
+// http://www.wxformbuilder.org/
+//
+// PLEASE DO "NOT" EDIT THIS FILE!
+///////////////////////////////////////////////////////////////////////////
+
+#include "SDRDeviceAddForm.h"
+
+///////////////////////////////////////////////////////////////////////////
+
+SDRDeviceAddForm::SDRDeviceAddForm( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxDialog( parent, id, title, pos, size, style )
+{
+	this->SetSizeHints( wxDefaultSize, wxDefaultSize );
+	
+	wxBoxSizer* bSizer6;
+	bSizer6 = new wxBoxSizer( wxVERTICAL );
+	
+	m_staticText4 = new wxStaticText( this, wxID_ANY, wxT("Manually add a SoapyRemote or SoapySDR device.  \n\nUseful for a device that is not detected automatically."), wxDefaultPosition, wxDefaultSize, 0 );
+	m_staticText4->Wrap( -1 );
+	bSizer6->Add( m_staticText4, 0, wxALL, 8 );
+	
+	
+	bSizer6->Add( 0, 0, 1, wxEXPAND, 5 );
+	
+	wxArrayString m_soapyModuleChoices;
+	m_soapyModule = new wxChoice( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, m_soapyModuleChoices, 0 );
+	m_soapyModule->SetSelection( 0 );
+	bSizer6->Add( m_soapyModule, 0, wxALL, 8 );
+	
+	
+	bSizer6->Add( 0, 0, 1, wxEXPAND, 5 );
+	
+	m_paramLabel = new wxStaticText( this, wxID_ANY, wxT("<Parameter>"), wxDefaultPosition, wxDefaultSize, 0 );
+	m_paramLabel->Wrap( -1 );
+	bSizer6->Add( m_paramLabel, 0, wxALL, 8 );
+	
+	m_paramText = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_DONTWRAP|wxHSCROLL );
+	m_paramText->SetMinSize( wxSize( -1,48 ) );
+	
+	bSizer6->Add( m_paramText, 1, wxALL|wxEXPAND, 8 );
+	
+	
+	bSizer6->Add( 0, 0, 1, wxEXPAND, 5 );
+	
+	wxBoxSizer* bSizer7;
+	bSizer7 = new wxBoxSizer( wxHORIZONTAL );
+	
+	
+	bSizer7->Add( 0, 0, 1, wxEXPAND, 5 );
+	
+	m_cancelButton = new wxButton( this, wxID_ANY, wxT("Cancel"), wxDefaultPosition, wxDefaultSize, 0 );
+	bSizer7->Add( m_cancelButton, 0, wxALL, 2 );
+	
+	m_OkButton = new wxButton( this, wxID_ANY, wxT("Ok"), wxDefaultPosition, wxDefaultSize, 0 );
+	bSizer7->Add( m_OkButton, 0, wxALL, 2 );
+	
+	
+	bSizer6->Add( bSizer7, 1, wxEXPAND, 8 );
+	
+	
+	bSizer6->Add( 0, 0, 1, wxEXPAND, 5 );
+	
+	
+	this->SetSizer( bSizer6 );
+	this->Layout();
+	
+	this->Centre( wxBOTH );
+	
+	// Connect Events
+	m_soapyModule->Connect( wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler( SDRDeviceAddForm::OnSoapyModuleChanged ), NULL, this );
+	m_cancelButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( SDRDeviceAddForm::OnCancelButton ), NULL, this );
+	m_OkButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( SDRDeviceAddForm::OnOkButton ), NULL, this );
+}
+
+SDRDeviceAddForm::~SDRDeviceAddForm()
+{
+	// Disconnect Events
+	m_soapyModule->Disconnect( wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler( SDRDeviceAddForm::OnSoapyModuleChanged ), NULL, this );
+	m_cancelButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( SDRDeviceAddForm::OnCancelButton ), NULL, this );
+	m_OkButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( SDRDeviceAddForm::OnOkButton ), NULL, this );
+	
+}
diff --git a/src/forms/SDRDevices/SDRDeviceAddForm.h b/src/forms/SDRDevices/SDRDeviceAddForm.h
new file mode 100644
index 0000000..58ffa20
--- /dev/null
+++ b/src/forms/SDRDevices/SDRDeviceAddForm.h
@@ -0,0 +1,56 @@
+///////////////////////////////////////////////////////////////////////////
+// C++ code generated with wxFormBuilder (version Aug 23 2015)
+// http://www.wxformbuilder.org/
+//
+// PLEASE DO "NOT" EDIT THIS FILE!
+///////////////////////////////////////////////////////////////////////////
+
+#ifndef __SDRDEVICEADDFORM_H__
+#define __SDRDEVICEADDFORM_H__
+
+#include <wx/artprov.h>
+#include <wx/xrc/xmlres.h>
+#include <wx/string.h>
+#include <wx/stattext.h>
+#include <wx/gdicmn.h>
+#include <wx/font.h>
+#include <wx/colour.h>
+#include <wx/settings.h>
+#include <wx/choice.h>
+#include <wx/textctrl.h>
+#include <wx/button.h>
+#include <wx/sizer.h>
+#include <wx/dialog.h>
+
+///////////////////////////////////////////////////////////////////////////
+
+
+///////////////////////////////////////////////////////////////////////////////
+/// Class SDRDeviceAddForm
+///////////////////////////////////////////////////////////////////////////////
+class SDRDeviceAddForm : public wxDialog 
+{
+	private:
+	
+	protected:
+		wxStaticText* m_staticText4;
+		wxChoice* m_soapyModule;
+		wxStaticText* m_paramLabel;
+		wxTextCtrl* m_paramText;
+		wxButton* m_cancelButton;
+		wxButton* m_OkButton;
+		
+		// Virtual event handlers, overide them in your derived class
+		virtual void OnSoapyModuleChanged( wxCommandEvent& event ) { event.Skip(); }
+		virtual void OnCancelButton( wxCommandEvent& event ) { event.Skip(); }
+		virtual void OnOkButton( wxCommandEvent& event ) { event.Skip(); }
+		
+	
+	public:
+		
+		SDRDeviceAddForm( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxT("Add SoapySDR Device"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 395,293 ), long style = wxDEFAULT_DIALOG_STYLE ); 
+		~SDRDeviceAddForm();
+	
+};
+
+#endif //__SDRDEVICEADDFORM_H__
diff --git a/src/forms/SDRDevices/SDRDevices.cpp b/src/forms/SDRDevices/SDRDevices.cpp
new file mode 100644
index 0000000..df81fb8
--- /dev/null
+++ b/src/forms/SDRDevices/SDRDevices.cpp
@@ -0,0 +1,500 @@
+#include "SDRDevices.h"
+
+#include <wx/textdlg.h>
+#include <wx/msgdlg.h>
+
+#include "CubicSDR.h"
+
+SDRDevicesDialog::SDRDevicesDialog( wxWindow* parent ): devFrame( parent ) {
+    refresh = true;
+    failed = false;
+    m_refreshButton->Disable();
+    m_addRemoteButton->Disable();
+    m_useSelectedButton->Disable();
+    m_deviceTimer.Start(250);
+    selId = nullptr;
+    editId = nullptr;
+    removeId = nullptr;
+    devAddDialog = nullptr;
+    dev = nullptr;
+}
+
+void SDRDevicesDialog::OnClose( wxCloseEvent& /* event */) {
+    wxGetApp().setDeviceSelectorClosed();
+    Destroy();
+}
+
+void SDRDevicesDialog::OnDeleteItem( wxTreeEvent& event ) {
+    event.Skip();
+}
+
+wxPGProperty *SDRDevicesDialog::addArgInfoProperty(wxPropertyGrid *pg, SoapySDR::ArgInfo arg) {
+    
+    wxPGProperty *prop = NULL;
+    
+    int intVal;
+    double floatVal;
+    std::vector<std::string>::iterator stringIter;
+    
+    switch (arg.type) {
+        case SoapySDR::ArgInfo::INT:
+            try {
+                intVal = std::stoi(arg.value);
+            } catch (std::invalid_argument e) {
+                intVal = 0;
+            }
+            prop = pg->Append( new wxIntProperty(arg.name, wxPG_LABEL, intVal) );
+            if (arg.range.minimum() != arg.range.maximum()) {
+                pg->SetPropertyAttribute( prop, wxPG_ATTR_MIN, arg.range.minimum());
+                pg->SetPropertyAttribute( prop, wxPG_ATTR_MAX, arg.range.maximum());
+            }
+            break;
+        case SoapySDR::ArgInfo::FLOAT:
+            try {
+                floatVal = std::stod(arg.value);
+            } catch (std::invalid_argument e) {
+                floatVal = 0;
+            }
+            prop = pg->Append( new wxFloatProperty(arg.name, wxPG_LABEL, floatVal) );
+            if (arg.range.minimum() != arg.range.maximum()) {
+                pg->SetPropertyAttribute( prop, wxPG_ATTR_MIN, arg.range.minimum());
+                pg->SetPropertyAttribute( prop, wxPG_ATTR_MAX, arg.range.maximum());
+            }
+            break;
+        case SoapySDR::ArgInfo::BOOL:
+            prop = pg->Append( new wxBoolProperty(arg.name, wxPG_LABEL, (arg.value=="true")) );
+            break;
+        case SoapySDR::ArgInfo::STRING:
+            if (arg.options.size()) {
+                intVal = 0;
+                prop = pg->Append( new wxEnumProperty(arg.name, wxPG_LABEL) );
+                for (stringIter = arg.options.begin(); stringIter != arg.options.end(); stringIter++) {
+                    std::string optName = (*stringIter);
+                    std::string displayName = optName;
+                    if (arg.optionNames.size()) {
+                        displayName = arg.optionNames[intVal];
+                    }
+
+                    prop->AddChoice(displayName);
+                    if ((*stringIter)==arg.value) {
+                        prop->SetChoiceSelection(intVal);
+                    }
+                    
+                    intVal++;
+                }
+            } else {
+                prop = pg->Append( new wxStringProperty(arg.name, wxPG_LABEL, arg.value) );
+            }
+            break;
+    }
+    
+    if (prop != NULL) {
+        prop->SetHelpString(arg.key + ": " + arg.description);
+    }
+    
+    return prop;
+}
+
+void SDRDevicesDialog::refreshDeviceProperties() {
+    SDRDeviceInfo *selDev = getSelectedDevice(devTree->GetSelection());
+    if (selDev && selDev->isAvailable()) {
+        dev = selDev;
+        selId = devTree->GetSelection();
+        DeviceConfig *devConfig = wxGetApp().getConfig()->getDevice(dev->getName());
+        m_propertyGrid->Clear();
+        
+        SoapySDR::Device *soapyDev = dev->getSoapyDevice();
+        SoapySDR::ArgInfoList args = soapyDev->getSettingInfo();
+        SoapySDR::ArgInfoList::const_iterator args_i;
+        
+        m_propertyGrid->Append(new wxPropertyCategory("General Settings"));
+        
+        devSettings.erase(devSettings.begin(),devSettings.end());
+        devSettings["name"] = m_propertyGrid->Append( new wxStringProperty("Name", wxPG_LABEL, devConfig->getDeviceName()) );
+        devSettings["offset"] = m_propertyGrid->Append( new wxIntProperty("Offset (Hz)", wxPG_LABEL, devConfig->getOffset()) );
+        
+        int currentSampleRate = wxGetApp().getSampleRate();
+        int deviceSampleRate = devConfig->getSampleRate();
+        
+        if (!deviceSampleRate) {
+            deviceSampleRate = selDev->getSampleRateNear(SOAPY_SDR_RX, 0, currentSampleRate);
+        }
+        
+        SoapySDR::ArgInfo sampleRateArg;
+        std::vector<long> rateOpts = selDev->getSampleRates(SOAPY_SDR_RX, 0);
+
+        for (std::vector<long>::iterator rate_i = rateOpts.begin(); rate_i != rateOpts.end(); rate_i++) {
+            sampleRateArg.options.push_back(std::to_string(*rate_i));
+            sampleRateArg.optionNames.push_back(frequencyToStr(*rate_i));
+        }
+        
+        sampleRateArg.type = SoapySDR::ArgInfo::STRING;
+        sampleRateArg.units = "Hz";
+        sampleRateArg.name = "Sample Rate";
+        sampleRateArg.key = "sample_rate";
+        sampleRateArg.value = std::to_string(deviceSampleRate);
+        
+        devSettings["sample_rate"] = addArgInfoProperty(m_propertyGrid, sampleRateArg);
+        deviceArgs["sample_rate"] = sampleRateArg;
+        
+        runtimeArgs.erase(runtimeArgs.begin(), runtimeArgs.end());
+        runtimeProps.erase(runtimeProps.begin(), runtimeProps.end());
+        streamProps.erase(streamProps.begin(), streamProps.end());
+        
+        if (args.size()) {
+            m_propertyGrid->Append(new wxPropertyCategory("Run-time Settings"));
+            
+            for (args_i = args.begin(); args_i != args.end(); args_i++) {
+                SoapySDR::ArgInfo arg = (*args_i);
+                arg.value = soapyDev->readSetting(arg.key);
+                runtimeProps[arg.key] = addArgInfoProperty(m_propertyGrid, arg);
+                runtimeArgs[arg.key] = arg;
+            }
+        }
+        
+        if (dev) {
+            args = dev->getSoapyDevice()->getStreamArgsInfo(SOAPY_SDR_RX, 0);
+            
+            DeviceConfig *devConfig = wxGetApp().getConfig()->getDevice(dev->getDeviceId());
+            ConfigSettings devStreamOpts = devConfig->getStreamOpts();
+            if (devStreamOpts.size()) {
+                for (int j = 0, jMax = args.size(); j < jMax; j++) {
+                    if (devStreamOpts.find(args[j].key) != devStreamOpts.end()) {
+                        args[j].value = devStreamOpts[args[j].key];
+                    }
+                }
+            }
+            
+            if (args.size()) {
+                m_propertyGrid->Append(new wxPropertyCategory("Stream Settings"));
+                
+                for (args_i = args.begin(); args_i != args.end(); args_i++) {
+                    SoapySDR::ArgInfo arg = (*args_i);
+                    streamProps[arg.key] = addArgInfoProperty(m_propertyGrid, arg);
+                }
+            }
+        }
+        
+        if (selDev->isManual()) {
+            m_addRemoteButton->SetLabel("Remove");
+            removeId = selId;
+        } else {
+            m_addRemoteButton->SetLabel("Add");
+            removeId = nullptr;
+        }
+        
+    } else if (selDev && !selDev->isAvailable() && selDev->isManual()) {
+        m_propertyGrid->Clear();
+        devSettings.erase(devSettings.begin(),devSettings.end());
+        runtimeArgs.erase(runtimeArgs.begin(), runtimeArgs.end());
+        runtimeProps.erase(runtimeProps.begin(), runtimeProps.end());
+        streamProps.erase(streamProps.begin(), streamProps.end());
+        removeId = devTree->GetSelection();
+        dev = nullptr;
+        selId = nullptr;
+        editId = nullptr;
+        
+        m_addRemoteButton->SetLabel("Remove");
+    } else if (!selDev) {
+        m_addRemoteButton->SetLabel("Add");
+        removeId = nullptr;
+    }
+}
+
+void SDRDevicesDialog::OnSelectionChanged( wxTreeEvent& event ) {
+    refreshDeviceProperties();
+    event.Skip();
+}
+
+void SDRDevicesDialog::OnAddRemote( wxMouseEvent& /* event */) {
+    if (removeId != nullptr) {
+        SDRDeviceInfo *selDev = getSelectedDevice(removeId);
+
+        if (selDev) {
+            SDREnumerator::removeManual(selDev->getDriver(),selDev->getManualParams());
+            m_propertyGrid->Clear();
+            devSettings.erase(devSettings.begin(),devSettings.end());
+            runtimeArgs.erase(runtimeArgs.begin(), runtimeArgs.end());
+            runtimeProps.erase(runtimeProps.begin(), runtimeProps.end());
+            streamProps.erase(streamProps.begin(), streamProps.end());
+            dev = nullptr;
+            selId = nullptr;
+            editId = nullptr;
+            devTree->Delete(removeId);
+            removeId = nullptr;
+            m_addRemoteButton->SetLabel("Add");
+        }
+        
+        return;
+    }
+    
+    devAddDialog = new SDRDeviceAddDialog(this);
+    devAddDialog->ShowModal();
+    
+    if (devAddDialog->wasOkPressed()) {
+        std::string module = devAddDialog->getSelectedModule();
+        
+        if (module == "SoapyRemote") {
+            if (!SDREnumerator::hasRemoteModule()) {
+                wxMessageDialog *info;
+                info = new wxMessageDialog(NULL, wxT("Install SoapyRemote module to add remote servers.\n\nhttps://github.com/pothosware/SoapyRemote"), wxT("SoapyRemote not found."), wxOK | wxICON_ERROR);
+                info->ShowModal();
+                return;
+            }
+
+            wxString remoteAddr = devAddDialog->getModuleParam();
+        
+            if (!remoteAddr.Trim().empty()) {
+                wxGetApp().addRemote(remoteAddr.Trim().ToStdString());
+            }
+            devTree->Disable();
+            m_addRemoteButton->Disable();
+            m_useSelectedButton->Disable();
+            refresh = true;
+        } else {
+            std::string mod = devAddDialog->getSelectedModule();
+            std::string param = devAddDialog->getModuleParam();
+            SDREnumerator::addManual(mod, param);
+            doRefreshDevices();
+        }
+    }
+}
+
+SDRDeviceInfo *SDRDevicesDialog::getSelectedDevice(wxTreeItemId selId) {
+    devItems_i = devItems.find(selId);
+    if (devItems_i != devItems.end()) {
+        return devItems[selId];
+    }
+    return NULL;
+}
+
+void SDRDevicesDialog::OnUseSelected( wxMouseEvent& event) {
+    if (dev != NULL) {
+        SoapySDR::ArgInfoList::const_iterator args_i;
+        SoapySDR::ArgInfoList args = dev->getSoapyDevice()->getSettingInfo();
+        
+        SoapySDR::Kwargs settingArgs;
+        SoapySDR::Kwargs streamArgs;
+        
+        for (args_i = args.begin(); args_i != args.end(); args_i++) {
+            SoapySDR::ArgInfo arg = (*args_i);
+            wxPGProperty *prop = runtimeProps[arg.key];
+            
+            if (arg.type == SoapySDR::ArgInfo::STRING && arg.options.size()) {
+                settingArgs[arg.key] = arg.options[prop->GetChoiceSelection()];
+            } else if (arg.type == SoapySDR::ArgInfo::BOOL) {
+                settingArgs[arg.key] = (prop->GetValueAsString()=="True")?"true":"false";
+            } else {
+                settingArgs[arg.key] = prop->GetValueAsString();
+            }
+        }
+        
+        if (dev) {
+            args = dev->getSoapyDevice()->getStreamArgsInfo(SOAPY_SDR_RX, 0);
+            
+            if (args.size()) {
+                for (args_i = args.begin(); args_i != args.end(); args_i++) {
+                    SoapySDR::ArgInfo arg = (*args_i);
+                    wxPGProperty *prop = streamProps[arg.key];
+            
+                    if (arg.type == SoapySDR::ArgInfo::STRING && arg.options.size()) {
+                        streamArgs[arg.key] = arg.options[prop->GetChoiceSelection()];
+                    } else if (arg.type == SoapySDR::ArgInfo::BOOL) {
+                        streamArgs[arg.key] = (prop->GetValueAsString()=="True")?"true":"false";
+                    } else {
+                        streamArgs[arg.key] = prop->GetValueAsString();
+                    }
+                }
+            }
+        }
+        
+        AppConfig *cfg = wxGetApp().getConfig();
+        DeviceConfig *devConfig = cfg->getDevice(dev->getDeviceId());
+        devConfig->setSettings(settingArgs);
+        devConfig->setStreamOpts(streamArgs);
+        wxGetApp().setDeviceArgs(settingArgs);
+        wxGetApp().setStreamArgs(streamArgs);
+        wxGetApp().setDevice(dev,0);
+                
+        Close();
+    }
+    event.Skip();
+}
+
+void SDRDevicesDialog::OnTreeDoubleClick( wxMouseEvent& event ) {
+    OnUseSelected(event);
+}
+
+void SDRDevicesDialog::OnDeviceTimer( wxTimerEvent& event ) {
+    if (refresh) {
+        if (wxGetApp().areModulesMissing()) {
+            if (!failed) {
+                wxMessageDialog *info;
+                info = new wxMessageDialog(NULL, wxT("\nNo SoapySDR modules were found.\n\nCubicSDR requires at least one SoapySDR device support module to be installed.\n\nPlease visit https://github.com/cjcliffe/CubicSDR/wiki and in the build instructions for your platform read the 'Support Modules' section for more information."), wxT("\x28\u256F\xB0\u25A1\xB0\uFF09\u256F\uFE35\x20\u253B\u2501\u253B"), wxOK | wxICON_ERROR);
+                info->ShowModal();
+                failed = true;
+            }
+            return;
+        }
+        
+        if (wxGetApp().areDevicesEnumerating() || !wxGetApp().areDevicesReady()) {
+            std::string msg = wxGetApp().getNotification();
+            devStatusBar->SetStatusText(msg);
+            devTree->DeleteAllItems();
+            devTree->AddRoot(msg);
+            event.Skip();
+            return;
+        }
+                
+        devTree->DeleteAllItems();
+        
+        wxTreeItemId devRoot = devTree->AddRoot("Devices");
+        wxTreeItemId localBranch = devTree->AppendItem(devRoot, "Local");
+        wxTreeItemId dsBranch = devTree->AppendItem(devRoot, "Local Net");
+        wxTreeItemId remoteBranch = devTree->AppendItem(devRoot, "Remote");
+        wxTreeItemId manualBranch = devTree->AppendItem(devRoot, "Manual");
+        
+        devs[""] = SDREnumerator::enumerate_devices("",true);
+        if (devs[""] != NULL) {
+            for (devs_i = devs[""]->begin(); devs_i != devs[""]->end(); devs_i++) {
+                DeviceConfig *devConfig = nullptr;
+                if ((*devs_i)->isManual()) {
+                    std::string devName = "Unknown";
+                    if ((*devs_i)->isAvailable()) {
+                        devConfig = wxGetApp().getConfig()->getDevice((*devs_i)->getDeviceId());
+                        devName = devConfig->getDeviceName();
+                    } else {
+                        devName = (*devs_i)->getDeviceId();
+                    }
+                    devItems[devTree->AppendItem(manualBranch, devName)] = (*devs_i);
+                } else if ((*devs_i)->isRemote()) {
+                    devConfig = wxGetApp().getConfig()->getDevice((*devs_i)->getDeviceId());
+                    devItems[devTree->AppendItem(dsBranch, devConfig->getDeviceName())] = (*devs_i);
+                } else {
+                    devConfig = wxGetApp().getConfig()->getDevice((*devs_i)->getDeviceId());
+                    devItems[devTree->AppendItem(localBranch, devConfig->getDeviceName())] = (*devs_i);
+                }
+            }
+        }
+        
+        std::vector<std::string> remotes = SDREnumerator::getRemotes();
+        std::vector<std::string>::iterator remotes_i;
+        std::vector<SDRDeviceInfo *>::iterator remoteDevs_i;
+        
+        if (remotes.size()) {
+            for (remotes_i = remotes.begin(); remotes_i != remotes.end(); remotes_i++) {
+                devs[*remotes_i] = SDREnumerator::enumerate_devices(*remotes_i, true);
+                DeviceConfig *devConfig = wxGetApp().getConfig()->getDevice(*remotes_i);
+
+                wxTreeItemId remoteNode = devTree->AppendItem(remoteBranch, devConfig->getDeviceName());
+                
+                if (devs[*remotes_i] != NULL) {
+                    for (remoteDevs_i = devs[*remotes_i]->begin(); remoteDevs_i != devs[*remotes_i]->end(); remoteDevs_i++) {
+                        devItems[devTree->AppendItem(remoteNode, (*remoteDevs_i)->getName())] = (*remoteDevs_i);
+                    }
+                }
+            }
+        }
+        
+        m_refreshButton->Enable();
+        m_addRemoteButton->Enable();
+        m_useSelectedButton->Enable();
+        devTree->Enable();
+        devTree->ExpandAll();
+        
+        devStatusBar->SetStatusText("Ready.");
+
+        refresh = false;
+    }
+}
+
+void SDRDevicesDialog::OnRefreshDevices( wxMouseEvent& /* event */) {
+    doRefreshDevices();
+}
+
+void SDRDevicesDialog::OnPropGridChanged( wxPropertyGridEvent& event ) {
+    if (editId && event.GetProperty() == devSettings["name"]) {
+        DeviceConfig *devConfig = wxGetApp().getConfig()->getDevice(dev->getDeviceId());
+        
+        wxString devName = event.GetPropertyValue().GetString();
+        
+        devConfig->setDeviceName(devName.ToStdString());
+        if (editId) {
+            devTree->SetItemText(editId, devConfig->getDeviceName());
+        }
+        if (devName == "") {
+            event.GetProperty()->SetValueFromString(devConfig->getDeviceName());
+        }
+    } else if (dev && event.GetProperty() == devSettings["offset"]) {
+        DeviceConfig *devConfig = wxGetApp().getConfig()->getDevice(dev->getDeviceId());
+        
+        long offset = event.GetPropertyValue().GetInteger();
+        
+        devConfig->setOffset(offset);
+    } else if (dev && event.GetProperty() == devSettings["sample_rate"]) {
+        DeviceConfig *devConfig = wxGetApp().getConfig()->getDevice(dev->getDeviceId());
+        
+        std::string strRate = deviceArgs["sample_rate"].options[event.GetPropertyValue().GetInteger()];
+        int srate = 0;
+        try {
+            srate = std::stoi(strRate);
+            devConfig->setSampleRate(srate);
+            
+            if (dev->isActive() || !wxGetApp().getDevice()) {
+                wxGetApp().setSampleRate(srate);
+            }            
+        } catch (std::invalid_argument e) {
+            // nop
+        }
+    } else if (editId && dev) {
+        wxPGProperty *prop = event.GetProperty();
+        
+        for (std::map<std::string, wxPGProperty *>::iterator rtp = runtimeProps.begin(); rtp != runtimeProps.end(); rtp++) {
+            if (rtp->second == prop) {
+                SoapySDR::Device *soapyDev = dev->getSoapyDevice();
+                std::string settingValue = prop->GetValueAsString().ToStdString();
+                SoapySDR::ArgInfo arg = runtimeArgs[rtp->first];
+                if (arg.type == SoapySDR::ArgInfo::STRING && arg.options.size()) {
+                    settingValue = arg.options[prop->GetChoiceSelection()];
+                } else if (arg.type == SoapySDR::ArgInfo::BOOL) {
+                    settingValue = (prop->GetValueAsString()=="True")?"true":"false";
+                } else {
+                    settingValue = prop->GetValueAsString();
+                }
+
+                soapyDev->writeSetting(rtp->first, settingValue);
+                if (dev->isActive()) {
+                    wxGetApp().getSDRThread()->writeSetting(rtp->first, settingValue);
+                }
+                refreshDeviceProperties();
+                return;
+            }
+        }
+    }
+}
+
+void SDRDevicesDialog::OnPropGridFocus( wxFocusEvent& /* event */) {
+    editId = selId;
+}
+
+
+void SDRDevicesDialog::doRefreshDevices() {
+    selId = nullptr;
+    editId = nullptr;
+    removeId = nullptr;
+    dev = nullptr;
+    wxGetApp().stopDevice(false, 2000);
+    devTree->DeleteAllItems();
+    devTree->Disable();
+    m_propertyGrid->Clear();
+    runtimeArgs.erase(runtimeArgs.begin(), runtimeArgs.end());
+    runtimeProps.erase(runtimeProps.begin(), runtimeProps.end());
+    streamProps.erase(streamProps.begin(), streamProps.end());
+    devSettings.erase(devSettings.begin(), devSettings.end());
+    m_refreshButton->Disable();
+    m_addRemoteButton->Disable();
+    m_useSelectedButton->Disable();
+    wxGetApp().reEnumerateDevices();
+    refresh = true;
+    m_addRemoteButton->SetLabel("Add");
+}
diff --git a/src/forms/SDRDevices/SDRDevices.fbp b/src/forms/SDRDevices/SDRDevices.fbp
new file mode 100644
index 0000000..4fc959b
--- /dev/null
+++ b/src/forms/SDRDevices/SDRDevices.fbp
@@ -0,0 +1,1033 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
+<wxFormBuilder_Project>
+    <FileVersion major="1" minor="13" />
+    <object class="Project" expanded="1">
+        <property name="class_decoration"></property>
+        <property name="code_generation">C++</property>
+        <property name="disconnect_events">1</property>
+        <property name="disconnect_mode">source_name</property>
+        <property name="disconnect_php_events">0</property>
+        <property name="disconnect_python_events">0</property>
+        <property name="embedded_files_path">res</property>
+        <property name="encoding">UTF-8</property>
+        <property name="event_generation">connect</property>
+        <property name="file">SDRDevicesForm</property>
+        <property name="first_id">1000</property>
+        <property name="help_provider">none</property>
+        <property name="internationalize">0</property>
+        <property name="name">SDRDevicesForm</property>
+        <property name="namespace"></property>
+        <property name="path">.</property>
+        <property name="precompiled_header"></property>
+        <property name="relative_path">1</property>
+        <property name="skip_lua_events">1</property>
+        <property name="skip_php_events">1</property>
+        <property name="skip_python_events">1</property>
+        <property name="ui_table">UI</property>
+        <property name="use_enum">0</property>
+        <property name="use_microsoft_bom">0</property>
+        <object class="Frame" expanded="1">
+            <property name="aui_managed">0</property>
+            <property name="aui_manager_style">wxAUI_MGR_DEFAULT</property>
+            <property name="bg"></property>
+            <property name="center">wxBOTH</property>
+            <property name="context_help"></property>
+            <property name="context_menu">1</property>
+            <property name="enabled">1</property>
+            <property name="event_handler">impl_virtual</property>
+            <property name="extra_style"></property>
+            <property name="fg"></property>
+            <property name="font"></property>
+            <property name="hidden">0</property>
+            <property name="id">wxID_ANY</property>
+            <property name="maximum_size"></property>
+            <property name="minimum_size"></property>
+            <property name="name">devFrame</property>
+            <property name="pos"></property>
+            <property name="size">700,467</property>
+            <property name="style">wxDEFAULT_FRAME_STYLE</property>
+            <property name="subclass"></property>
+            <property name="title">CubicSDR :: SDR Devices</property>
+            <property name="tooltip"></property>
+            <property name="window_extra_style"></property>
+            <property name="window_name"></property>
+            <property name="window_style">wxTAB_TRAVERSAL</property>
+            <property name="xrc_skip_sizer">1</property>
+            <event name="OnActivate"></event>
+            <event name="OnActivateApp"></event>
+            <event name="OnAuiFindManager"></event>
+            <event name="OnAuiPaneButton"></event>
+            <event name="OnAuiPaneClose"></event>
+            <event name="OnAuiPaneMaximize"></event>
+            <event name="OnAuiPaneRestore"></event>
+            <event name="OnAuiRender"></event>
+            <event name="OnChar"></event>
+            <event name="OnClose">OnClose</event>
+            <event name="OnEnterWindow"></event>
+            <event name="OnEraseBackground"></event>
+            <event name="OnHibernate"></event>
+            <event name="OnIconize"></event>
+            <event name="OnIdle"></event>
+            <event name="OnKeyDown"></event>
+            <event name="OnKeyUp"></event>
+            <event name="OnKillFocus"></event>
+            <event name="OnLeaveWindow"></event>
+            <event name="OnLeftDClick"></event>
+            <event name="OnLeftDown"></event>
+            <event name="OnLeftUp"></event>
+            <event name="OnMiddleDClick"></event>
+            <event name="OnMiddleDown"></event>
+            <event name="OnMiddleUp"></event>
+            <event name="OnMotion"></event>
+            <event name="OnMouseEvents"></event>
+            <event name="OnMouseWheel"></event>
+            <event name="OnPaint"></event>
+            <event name="OnRightDClick"></event>
+            <event name="OnRightDown"></event>
+            <event name="OnRightUp"></event>
+            <event name="OnSetFocus"></event>
+            <event name="OnSize"></event>
+            <event name="OnUpdateUI"></event>
+            <object class="wxStatusBar" expanded="1">
+                <property name="bg"></property>
+                <property name="context_help"></property>
+                <property name="context_menu">1</property>
+                <property name="enabled">1</property>
+                <property name="fg"></property>
+                <property name="fields">1</property>
+                <property name="font"></property>
+                <property name="hidden">0</property>
+                <property name="id">wxID_ANY</property>
+                <property name="maximum_size"></property>
+                <property name="minimum_size"></property>
+                <property name="name">devStatusBar</property>
+                <property name="permission">protected</property>
+                <property name="pos"></property>
+                <property name="size"></property>
+                <property name="style">wxST_SIZEGRIP</property>
+                <property name="subclass"></property>
+                <property name="tooltip"></property>
+                <property name="window_extra_style"></property>
+                <property name="window_name"></property>
+                <property name="window_style"></property>
+                <event name="OnChar"></event>
+                <event name="OnEnterWindow"></event>
+                <event name="OnEraseBackground"></event>
+                <event name="OnKeyDown"></event>
+                <event name="OnKeyUp"></event>
+                <event name="OnKillFocus"></event>
+                <event name="OnLeaveWindow"></event>
+                <event name="OnLeftDClick"></event>
+                <event name="OnLeftDown"></event>
+                <event name="OnLeftUp"></event>
+                <event name="OnMiddleDClick"></event>
+                <event name="OnMiddleDown"></event>
+                <event name="OnMiddleUp"></event>
+                <event name="OnMotion"></event>
+                <event name="OnMouseEvents"></event>
+                <event name="OnMouseWheel"></event>
+                <event name="OnPaint"></event>
+                <event name="OnRightDClick"></event>
+                <event name="OnRightDown"></event>
+                <event name="OnRightUp"></event>
+                <event name="OnSetFocus"></event>
+                <event name="OnSize"></event>
+                <event name="OnUpdateUI"></event>
+            </object>
+            <object class="wxBoxSizer" expanded="1">
+                <property name="minimum_size"></property>
+                <property name="name">devFrameSizer</property>
+                <property name="orient">wxVERTICAL</property>
+                <property name="permission">none</property>
+                <object class="sizeritem" expanded="1">
+                    <property name="border">5</property>
+                    <property name="flag">wxEXPAND | wxALL</property>
+                    <property name="proportion">1</property>
+                    <object class="wxPanel" expanded="1">
+                        <property name="BottomDockable">1</property>
+                        <property name="LeftDockable">1</property>
+                        <property name="RightDockable">1</property>
+                        <property name="TopDockable">1</property>
+                        <property name="aui_layer"></property>
+                        <property name="aui_name"></property>
+                        <property name="aui_position"></property>
+                        <property name="aui_row"></property>
+                        <property name="best_size"></property>
+                        <property name="bg"></property>
+                        <property name="caption"></property>
+                        <property name="caption_visible">1</property>
+                        <property name="center_pane">0</property>
+                        <property name="close_button">1</property>
+                        <property name="context_help"></property>
+                        <property name="context_menu">1</property>
+                        <property name="default_pane">0</property>
+                        <property name="dock">Dock</property>
+                        <property name="dock_fixed">0</property>
+                        <property name="docking">Left</property>
+                        <property name="enabled">1</property>
+                        <property name="fg"></property>
+                        <property name="floatable">1</property>
+                        <property name="font"></property>
+                        <property name="gripper">0</property>
+                        <property name="hidden">0</property>
+                        <property name="id">wxID_ANY</property>
+                        <property name="max_size"></property>
+                        <property name="maximize_button">0</property>
+                        <property name="maximum_size"></property>
+                        <property name="min_size"></property>
+                        <property name="minimize_button">0</property>
+                        <property name="minimum_size"></property>
+                        <property name="moveable">1</property>
+                        <property name="name">m_panel3</property>
+                        <property name="pane_border">1</property>
+                        <property name="pane_position"></property>
+                        <property name="pane_size"></property>
+                        <property name="permission">protected</property>
+                        <property name="pin_button">1</property>
+                        <property name="pos"></property>
+                        <property name="resize">Resizable</property>
+                        <property name="show">1</property>
+                        <property name="size"></property>
+                        <property name="subclass"></property>
+                        <property name="toolbar_pane">0</property>
+                        <property name="tooltip"></property>
+                        <property name="window_extra_style"></property>
+                        <property name="window_name"></property>
+                        <property name="window_style">wxTAB_TRAVERSAL</property>
+                        <event name="OnChar"></event>
+                        <event name="OnEnterWindow"></event>
+                        <event name="OnEraseBackground"></event>
+                        <event name="OnKeyDown"></event>
+                        <event name="OnKeyUp"></event>
+                        <event name="OnKillFocus"></event>
+                        <event name="OnLeaveWindow"></event>
+                        <event name="OnLeftDClick"></event>
+                        <event name="OnLeftDown"></event>
+                        <event name="OnLeftUp"></event>
+                        <event name="OnMiddleDClick"></event>
+                        <event name="OnMiddleDown"></event>
+                        <event name="OnMiddleUp"></event>
+                        <event name="OnMotion"></event>
+                        <event name="OnMouseEvents"></event>
+                        <event name="OnMouseWheel"></event>
+                        <event name="OnPaint"></event>
+                        <event name="OnRightDClick"></event>
+                        <event name="OnRightDown"></event>
+                        <event name="OnRightUp"></event>
+                        <event name="OnSetFocus"></event>
+                        <event name="OnSize"></event>
+                        <event name="OnUpdateUI"></event>
+                        <object class="wxBoxSizer" expanded="1">
+                            <property name="minimum_size"></property>
+                            <property name="name">bSizer4</property>
+                            <property name="orient">wxHORIZONTAL</property>
+                            <property name="permission">none</property>
+                            <object class="sizeritem" expanded="1">
+                                <property name="border">5</property>
+                                <property name="flag">wxEXPAND | wxALL</property>
+                                <property name="proportion">1</property>
+                                <object class="wxPanel" expanded="1">
+                                    <property name="BottomDockable">1</property>
+                                    <property name="LeftDockable">1</property>
+                                    <property name="RightDockable">1</property>
+                                    <property name="TopDockable">1</property>
+                                    <property name="aui_layer"></property>
+                                    <property name="aui_name"></property>
+                                    <property name="aui_position"></property>
+                                    <property name="aui_row"></property>
+                                    <property name="best_size"></property>
+                                    <property name="bg"></property>
+                                    <property name="caption"></property>
+                                    <property name="caption_visible">1</property>
+                                    <property name="center_pane">0</property>
+                                    <property name="close_button">1</property>
+                                    <property name="context_help"></property>
+                                    <property name="context_menu">1</property>
+                                    <property name="default_pane">0</property>
+                                    <property name="dock">Dock</property>
+                                    <property name="dock_fixed">0</property>
+                                    <property name="docking">Left</property>
+                                    <property name="enabled">1</property>
+                                    <property name="fg"></property>
+                                    <property name="floatable">1</property>
+                                    <property name="font"></property>
+                                    <property name="gripper">0</property>
+                                    <property name="hidden">0</property>
+                                    <property name="id">wxID_ANY</property>
+                                    <property name="max_size"></property>
+                                    <property name="maximize_button">0</property>
+                                    <property name="maximum_size"></property>
+                                    <property name="min_size"></property>
+                                    <property name="minimize_button">0</property>
+                                    <property name="minimum_size"></property>
+                                    <property name="moveable">1</property>
+                                    <property name="name">m_panel6</property>
+                                    <property name="pane_border">1</property>
+                                    <property name="pane_position"></property>
+                                    <property name="pane_size"></property>
+                                    <property name="permission">protected</property>
+                                    <property name="pin_button">1</property>
+                                    <property name="pos"></property>
+                                    <property name="resize">Resizable</property>
+                                    <property name="show">1</property>
+                                    <property name="size"></property>
+                                    <property name="subclass"></property>
+                                    <property name="toolbar_pane">0</property>
+                                    <property name="tooltip"></property>
+                                    <property name="window_extra_style"></property>
+                                    <property name="window_name"></property>
+                                    <property name="window_style">wxTAB_TRAVERSAL</property>
+                                    <event name="OnChar"></event>
+                                    <event name="OnEnterWindow"></event>
+                                    <event name="OnEraseBackground"></event>
+                                    <event name="OnKeyDown"></event>
+                                    <event name="OnKeyUp"></event>
+                                    <event name="OnKillFocus"></event>
+                                    <event name="OnLeaveWindow"></event>
+                                    <event name="OnLeftDClick"></event>
+                                    <event name="OnLeftDown"></event>
+                                    <event name="OnLeftUp"></event>
+                                    <event name="OnMiddleDClick"></event>
+                                    <event name="OnMiddleDown"></event>
+                                    <event name="OnMiddleUp"></event>
+                                    <event name="OnMotion"></event>
+                                    <event name="OnMouseEvents"></event>
+                                    <event name="OnMouseWheel"></event>
+                                    <event name="OnPaint"></event>
+                                    <event name="OnRightDClick"></event>
+                                    <event name="OnRightDown"></event>
+                                    <event name="OnRightUp"></event>
+                                    <event name="OnSetFocus"></event>
+                                    <event name="OnSize"></event>
+                                    <event name="OnUpdateUI"></event>
+                                    <object class="wxBoxSizer" expanded="1">
+                                        <property name="minimum_size"></property>
+                                        <property name="name">bSizer6</property>
+                                        <property name="orient">wxVERTICAL</property>
+                                        <property name="permission">none</property>
+                                        <object class="sizeritem" expanded="0">
+                                            <property name="border">5</property>
+                                            <property name="flag">wxEXPAND</property>
+                                            <property name="proportion">1</property>
+                                            <object class="wxTreeCtrl" expanded="0">
+                                                <property name="BottomDockable">1</property>
+                                                <property name="LeftDockable">1</property>
+                                                <property name="RightDockable">1</property>
+                                                <property name="TopDockable">1</property>
+                                                <property name="aui_layer"></property>
+                                                <property name="aui_name"></property>
+                                                <property name="aui_position"></property>
+                                                <property name="aui_row"></property>
+                                                <property name="best_size"></property>
+                                                <property name="bg"></property>
+                                                <property name="caption"></property>
+                                                <property name="caption_visible">1</property>
+                                                <property name="center_pane">0</property>
+                                                <property name="close_button">1</property>
+                                                <property name="context_help"></property>
+                                                <property name="context_menu">1</property>
+                                                <property name="default_pane">0</property>
+                                                <property name="dock">Dock</property>
+                                                <property name="dock_fixed">0</property>
+                                                <property name="docking">Left</property>
+                                                <property name="enabled">0</property>
+                                                <property name="fg"></property>
+                                                <property name="floatable">1</property>
+                                                <property name="font"></property>
+                                                <property name="gripper">0</property>
+                                                <property name="hidden">0</property>
+                                                <property name="id">wxID_ANY</property>
+                                                <property name="max_size"></property>
+                                                <property name="maximize_button">0</property>
+                                                <property name="maximum_size"></property>
+                                                <property name="min_size"></property>
+                                                <property name="minimize_button">0</property>
+                                                <property name="minimum_size"></property>
+                                                <property name="moveable">1</property>
+                                                <property name="name">devTree</property>
+                                                <property name="pane_border">1</property>
+                                                <property name="pane_position"></property>
+                                                <property name="pane_size"></property>
+                                                <property name="permission">protected</property>
+                                                <property name="pin_button">1</property>
+                                                <property name="pos"></property>
+                                                <property name="resize">Resizable</property>
+                                                <property name="show">1</property>
+                                                <property name="size"></property>
+                                                <property name="style">wxTR_DEFAULT_STYLE</property>
+                                                <property name="subclass"></property>
+                                                <property name="toolbar_pane">0</property>
+                                                <property name="tooltip"></property>
+                                                <property name="window_extra_style"></property>
+                                                <property name="window_name"></property>
+                                                <property name="window_style"></property>
+                                                <event name="OnChar"></event>
+                                                <event name="OnEnterWindow"></event>
+                                                <event name="OnEraseBackground"></event>
+                                                <event name="OnKeyDown"></event>
+                                                <event name="OnKeyUp"></event>
+                                                <event name="OnKillFocus"></event>
+                                                <event name="OnLeaveWindow"></event>
+                                                <event name="OnLeftDClick">OnTreeDoubleClick</event>
+                                                <event name="OnLeftDown"></event>
+                                                <event name="OnLeftUp"></event>
+                                                <event name="OnMiddleDClick"></event>
+                                                <event name="OnMiddleDown"></event>
+                                                <event name="OnMiddleUp"></event>
+                                                <event name="OnMotion"></event>
+                                                <event name="OnMouseEvents"></event>
+                                                <event name="OnMouseWheel"></event>
+                                                <event name="OnPaint"></event>
+                                                <event name="OnRightDClick"></event>
+                                                <event name="OnRightDown"></event>
+                                                <event name="OnRightUp"></event>
+                                                <event name="OnSetFocus"></event>
+                                                <event name="OnSize"></event>
+                                                <event name="OnTreeBeginDrag"></event>
+                                                <event name="OnTreeBeginLabelEdit"></event>
+                                                <event name="OnTreeBeginRDrag"></event>
+                                                <event name="OnTreeDeleteItem">OnDeleteItem</event>
+                                                <event name="OnTreeEndDrag"></event>
+                                                <event name="OnTreeEndLabelEdit"></event>
+                                                <event name="OnTreeGetInfo"></event>
+                                                <event name="OnTreeItemActivated"></event>
+                                                <event name="OnTreeItemCollapsed"></event>
+                                                <event name="OnTreeItemCollapsing"></event>
+                                                <event name="OnTreeItemExpanded"></event>
+                                                <event name="OnTreeItemExpanding"></event>
+                                                <event name="OnTreeItemGetTooltip"></event>
+                                                <event name="OnTreeItemMenu"></event>
+                                                <event name="OnTreeItemMiddleClick"></event>
+                                                <event name="OnTreeItemRightClick"></event>
+                                                <event name="OnTreeKeyDown"></event>
+                                                <event name="OnTreeSelChanged">OnSelectionChanged</event>
+                                                <event name="OnTreeSelChanging"></event>
+                                                <event name="OnTreeSetInfo"></event>
+                                                <event name="OnTreeStateImageClick"></event>
+                                                <event name="OnUpdateUI"></event>
+                                            </object>
+                                        </object>
+                                        <object class="sizeritem" expanded="1">
+                                            <property name="border">5</property>
+                                            <property name="flag">wxEXPAND</property>
+                                            <property name="proportion">0</property>
+                                            <object class="wxPanel" expanded="1">
+                                                <property name="BottomDockable">1</property>
+                                                <property name="LeftDockable">1</property>
+                                                <property name="RightDockable">1</property>
+                                                <property name="TopDockable">1</property>
+                                                <property name="aui_layer"></property>
+                                                <property name="aui_name"></property>
+                                                <property name="aui_position"></property>
+                                                <property name="aui_row"></property>
+                                                <property name="best_size"></property>
+                                                <property name="bg"></property>
+                                                <property name="caption"></property>
+                                                <property name="caption_visible">1</property>
+                                                <property name="center_pane">0</property>
+                                                <property name="close_button">1</property>
+                                                <property name="context_help"></property>
+                                                <property name="context_menu">1</property>
+                                                <property name="default_pane">0</property>
+                                                <property name="dock">Dock</property>
+                                                <property name="dock_fixed">0</property>
+                                                <property name="docking">Left</property>
+                                                <property name="enabled">1</property>
+                                                <property name="fg"></property>
+                                                <property name="floatable">1</property>
+                                                <property name="font"></property>
+                                                <property name="gripper">0</property>
+                                                <property name="hidden">0</property>
+                                                <property name="id">wxID_ANY</property>
+                                                <property name="max_size"></property>
+                                                <property name="maximize_button">0</property>
+                                                <property name="maximum_size"></property>
+                                                <property name="min_size"></property>
+                                                <property name="minimize_button">0</property>
+                                                <property name="minimum_size"></property>
+                                                <property name="moveable">1</property>
+                                                <property name="name">m_panel4</property>
+                                                <property name="pane_border">1</property>
+                                                <property name="pane_position"></property>
+                                                <property name="pane_size"></property>
+                                                <property name="permission">protected</property>
+                                                <property name="pin_button">1</property>
+                                                <property name="pos"></property>
+                                                <property name="resize">Resizable</property>
+                                                <property name="show">1</property>
+                                                <property name="size"></property>
+                                                <property name="subclass"></property>
+                                                <property name="toolbar_pane">0</property>
+                                                <property name="tooltip"></property>
+                                                <property name="window_extra_style"></property>
+                                                <property name="window_name"></property>
+                                                <property name="window_style">wxTAB_TRAVERSAL</property>
+                                                <event name="OnChar"></event>
+                                                <event name="OnEnterWindow"></event>
+                                                <event name="OnEraseBackground"></event>
+                                                <event name="OnKeyDown"></event>
+                                                <event name="OnKeyUp"></event>
+                                                <event name="OnKillFocus"></event>
+                                                <event name="OnLeaveWindow"></event>
+                                                <event name="OnLeftDClick"></event>
+                                                <event name="OnLeftDown"></event>
+                                                <event name="OnLeftUp"></event>
+                                                <event name="OnMiddleDClick"></event>
+                                                <event name="OnMiddleDown"></event>
+                                                <event name="OnMiddleUp"></event>
+                                                <event name="OnMotion"></event>
+                                                <event name="OnMouseEvents"></event>
+                                                <event name="OnMouseWheel"></event>
+                                                <event name="OnPaint"></event>
+                                                <event name="OnRightDClick"></event>
+                                                <event name="OnRightDown"></event>
+                                                <event name="OnRightUp"></event>
+                                                <event name="OnSetFocus"></event>
+                                                <event name="OnSize"></event>
+                                                <event name="OnUpdateUI"></event>
+                                                <object class="wxBoxSizer" expanded="1">
+                                                    <property name="minimum_size"></property>
+                                                    <property name="name">bSizer5</property>
+                                                    <property name="orient">wxHORIZONTAL</property>
+                                                    <property name="permission">none</property>
+                                                    <object class="sizeritem" expanded="1">
+                                                        <property name="border">5</property>
+                                                        <property name="flag">wxALL</property>
+                                                        <property name="proportion">1</property>
+                                                        <object class="wxButton" expanded="1">
+                                                            <property name="BottomDockable">1</property>
+                                                            <property name="LeftDockable">1</property>
+                                                            <property name="RightDockable">1</property>
+                                                            <property name="TopDockable">1</property>
+                                                            <property name="aui_layer"></property>
+                                                            <property name="aui_name"></property>
+                                                            <property name="aui_position"></property>
+                                                            <property name="aui_row"></property>
+                                                            <property name="best_size"></property>
+                                                            <property name="bg"></property>
+                                                            <property name="caption"></property>
+                                                            <property name="caption_visible">1</property>
+                                                            <property name="center_pane">0</property>
+                                                            <property name="close_button">1</property>
+                                                            <property name="context_help"></property>
+                                                            <property name="context_menu">1</property>
+                                                            <property name="default">0</property>
+                                                            <property name="default_pane">0</property>
+                                                            <property name="dock">Dock</property>
+                                                            <property name="dock_fixed">0</property>
+                                                            <property name="docking">Left</property>
+                                                            <property name="enabled">1</property>
+                                                            <property name="fg"></property>
+                                                            <property name="floatable">1</property>
+                                                            <property name="font"></property>
+                                                            <property name="gripper">0</property>
+                                                            <property name="hidden">0</property>
+                                                            <property name="id">wxID_ANY</property>
+                                                            <property name="label">Refresh</property>
+                                                            <property name="max_size"></property>
+                                                            <property name="maximize_button">0</property>
+                                                            <property name="maximum_size"></property>
+                                                            <property name="min_size"></property>
+                                                            <property name="minimize_button">0</property>
+                                                            <property name="minimum_size"></property>
+                                                            <property name="moveable">1</property>
+                                                            <property name="name">m_refreshButton</property>
+                                                            <property name="pane_border">1</property>
+                                                            <property name="pane_position"></property>
+                                                            <property name="pane_size"></property>
+                                                            <property name="permission">protected</property>
+                                                            <property name="pin_button">1</property>
+                                                            <property name="pos"></property>
+                                                            <property name="resize">Resizable</property>
+                                                            <property name="show">1</property>
+                                                            <property name="size"></property>
+                                                            <property name="style"></property>
+                                                            <property name="subclass"></property>
+                                                            <property name="toolbar_pane">0</property>
+                                                            <property name="tooltip"></property>
+                                                            <property name="validator_data_type"></property>
+                                                            <property name="validator_style">wxFILTER_NONE</property>
+                                                            <property name="validator_type">wxDefaultValidator</property>
+                                                            <property name="validator_variable"></property>
+                                                            <property name="window_extra_style"></property>
+                                                            <property name="window_name"></property>
+                                                            <property name="window_style"></property>
+                                                            <event name="OnButtonClick"></event>
+                                                            <event name="OnChar"></event>
+                                                            <event name="OnEnterWindow"></event>
+                                                            <event name="OnEraseBackground"></event>
+                                                            <event name="OnKeyDown"></event>
+                                                            <event name="OnKeyUp"></event>
+                                                            <event name="OnKillFocus"></event>
+                                                            <event name="OnLeaveWindow"></event>
+                                                            <event name="OnLeftDClick"></event>
+                                                            <event name="OnLeftDown"></event>
+                                                            <event name="OnLeftUp">OnRefreshDevices</event>
+                                                            <event name="OnMiddleDClick"></event>
+                                                            <event name="OnMiddleDown"></event>
+                                                            <event name="OnMiddleUp"></event>
+                                                            <event name="OnMotion"></event>
+                                                            <event name="OnMouseEvents"></event>
+                                                            <event name="OnMouseWheel"></event>
+                                                            <event name="OnPaint"></event>
+                                                            <event name="OnRightDClick"></event>
+                                                            <event name="OnRightDown"></event>
+                                                            <event name="OnRightUp"></event>
+                                                            <event name="OnSetFocus"></event>
+                                                            <event name="OnSize"></event>
+                                                            <event name="OnUpdateUI"></event>
+                                                        </object>
+                                                    </object>
+                                                    <object class="sizeritem" expanded="0">
+                                                        <property name="border">5</property>
+                                                        <property name="flag">wxALL</property>
+                                                        <property name="proportion">1</property>
+                                                        <object class="wxButton" expanded="0">
+                                                            <property name="BottomDockable">1</property>
+                                                            <property name="LeftDockable">1</property>
+                                                            <property name="RightDockable">1</property>
+                                                            <property name="TopDockable">1</property>
+                                                            <property name="aui_layer"></property>
+                                                            <property name="aui_name"></property>
+                                                            <property name="aui_position"></property>
+                                                            <property name="aui_row"></property>
+                                                            <property name="best_size"></property>
+                                                            <property name="bg"></property>
+                                                            <property name="caption"></property>
+                                                            <property name="caption_visible">1</property>
+                                                            <property name="center_pane">0</property>
+                                                            <property name="close_button">1</property>
+                                                            <property name="context_help"></property>
+                                                            <property name="context_menu">1</property>
+                                                            <property name="default">0</property>
+                                                            <property name="default_pane">0</property>
+                                                            <property name="dock">Dock</property>
+                                                            <property name="dock_fixed">0</property>
+                                                            <property name="docking">Left</property>
+                                                            <property name="enabled">1</property>
+                                                            <property name="fg"></property>
+                                                            <property name="floatable">1</property>
+                                                            <property name="font"></property>
+                                                            <property name="gripper">0</property>
+                                                            <property name="hidden">0</property>
+                                                            <property name="id">wxID_ANY</property>
+                                                            <property name="label">Add</property>
+                                                            <property name="max_size"></property>
+                                                            <property name="maximize_button">0</property>
+                                                            <property name="maximum_size"></property>
+                                                            <property name="min_size"></property>
+                                                            <property name="minimize_button">0</property>
+                                                            <property name="minimum_size"></property>
+                                                            <property name="moveable">1</property>
+                                                            <property name="name">m_addRemoteButton</property>
+                                                            <property name="pane_border">1</property>
+                                                            <property name="pane_position"></property>
+                                                            <property name="pane_size"></property>
+                                                            <property name="permission">protected</property>
+                                                            <property name="pin_button">1</property>
+                                                            <property name="pos"></property>
+                                                            <property name="resize">Resizable</property>
+                                                            <property name="show">1</property>
+                                                            <property name="size"></property>
+                                                            <property name="style"></property>
+                                                            <property name="subclass"></property>
+                                                            <property name="toolbar_pane">0</property>
+                                                            <property name="tooltip"></property>
+                                                            <property name="validator_data_type"></property>
+                                                            <property name="validator_style">wxFILTER_NONE</property>
+                                                            <property name="validator_type">wxDefaultValidator</property>
+                                                            <property name="validator_variable"></property>
+                                                            <property name="window_extra_style"></property>
+                                                            <property name="window_name"></property>
+                                                            <property name="window_style"></property>
+                                                            <event name="OnButtonClick"></event>
+                                                            <event name="OnChar"></event>
+                                                            <event name="OnEnterWindow"></event>
+                                                            <event name="OnEraseBackground"></event>
+                                                            <event name="OnKeyDown"></event>
+                                                            <event name="OnKeyUp"></event>
+                                                            <event name="OnKillFocus"></event>
+                                                            <event name="OnLeaveWindow"></event>
+                                                            <event name="OnLeftDClick"></event>
+                                                            <event name="OnLeftDown"></event>
+                                                            <event name="OnLeftUp">OnAddRemote</event>
+                                                            <event name="OnMiddleDClick"></event>
+                                                            <event name="OnMiddleDown"></event>
+                                                            <event name="OnMiddleUp"></event>
+                                                            <event name="OnMotion"></event>
+                                                            <event name="OnMouseEvents"></event>
+                                                            <event name="OnMouseWheel"></event>
+                                                            <event name="OnPaint"></event>
+                                                            <event name="OnRightDClick"></event>
+                                                            <event name="OnRightDown"></event>
+                                                            <event name="OnRightUp"></event>
+                                                            <event name="OnSetFocus"></event>
+                                                            <event name="OnSize"></event>
+                                                            <event name="OnUpdateUI"></event>
+                                                        </object>
+                                                    </object>
+                                                    <object class="sizeritem" expanded="0">
+                                                        <property name="border">5</property>
+                                                        <property name="flag">wxALL|wxALIGN_CENTER_VERTICAL</property>
+                                                        <property name="proportion">1</property>
+                                                        <object class="wxButton" expanded="0">
+                                                            <property name="BottomDockable">1</property>
+                                                            <property name="LeftDockable">1</property>
+                                                            <property name="RightDockable">1</property>
+                                                            <property name="TopDockable">1</property>
+                                                            <property name="aui_layer"></property>
+                                                            <property name="aui_name"></property>
+                                                            <property name="aui_position"></property>
+                                                            <property name="aui_row"></property>
+                                                            <property name="best_size"></property>
+                                                            <property name="bg"></property>
+                                                            <property name="caption"></property>
+                                                            <property name="caption_visible">1</property>
+                                                            <property name="center_pane">0</property>
+                                                            <property name="close_button">1</property>
+                                                            <property name="context_help"></property>
+                                                            <property name="context_menu">1</property>
+                                                            <property name="default">0</property>
+                                                            <property name="default_pane">0</property>
+                                                            <property name="dock">Dock</property>
+                                                            <property name="dock_fixed">0</property>
+                                                            <property name="docking">Left</property>
+                                                            <property name="enabled">1</property>
+                                                            <property name="fg"></property>
+                                                            <property name="floatable">1</property>
+                                                            <property name="font"></property>
+                                                            <property name="gripper">0</property>
+                                                            <property name="hidden">0</property>
+                                                            <property name="id">wxID_ANY</property>
+                                                            <property name="label">Start</property>
+                                                            <property name="max_size"></property>
+                                                            <property name="maximize_button">0</property>
+                                                            <property name="maximum_size"></property>
+                                                            <property name="min_size"></property>
+                                                            <property name="minimize_button">0</property>
+                                                            <property name="minimum_size"></property>
+                                                            <property name="moveable">1</property>
+                                                            <property name="name">m_useSelectedButton</property>
+                                                            <property name="pane_border">1</property>
+                                                            <property name="pane_position"></property>
+                                                            <property name="pane_size"></property>
+                                                            <property name="permission">protected</property>
+                                                            <property name="pin_button">1</property>
+                                                            <property name="pos"></property>
+                                                            <property name="resize">Resizable</property>
+                                                            <property name="show">1</property>
+                                                            <property name="size"></property>
+                                                            <property name="style"></property>
+                                                            <property name="subclass"></property>
+                                                            <property name="toolbar_pane">0</property>
+                                                            <property name="tooltip"></property>
+                                                            <property name="validator_data_type"></property>
+                                                            <property name="validator_style">wxFILTER_NONE</property>
+                                                            <property name="validator_type">wxDefaultValidator</property>
+                                                            <property name="validator_variable"></property>
+                                                            <property name="window_extra_style"></property>
+                                                            <property name="window_name"></property>
+                                                            <property name="window_style"></property>
+                                                            <event name="OnButtonClick"></event>
+                                                            <event name="OnChar"></event>
+                                                            <event name="OnEnterWindow"></event>
+                                                            <event name="OnEraseBackground"></event>
+                                                            <event name="OnKeyDown"></event>
+                                                            <event name="OnKeyUp"></event>
+                                                            <event name="OnKillFocus"></event>
+                                                            <event name="OnLeaveWindow"></event>
+                                                            <event name="OnLeftDClick"></event>
+                                                            <event name="OnLeftDown"></event>
+                                                            <event name="OnLeftUp">OnUseSelected</event>
+                                                            <event name="OnMiddleDClick"></event>
+                                                            <event name="OnMiddleDown"></event>
+                                                            <event name="OnMiddleUp"></event>
+                                                            <event name="OnMotion"></event>
+                                                            <event name="OnMouseEvents"></event>
+                                                            <event name="OnMouseWheel"></event>
+                                                            <event name="OnPaint"></event>
+                                                            <event name="OnRightDClick"></event>
+                                                            <event name="OnRightDown"></event>
+                                                            <event name="OnRightUp"></event>
+                                                            <event name="OnSetFocus"></event>
+                                                            <event name="OnSize"></event>
+                                                            <event name="OnUpdateUI"></event>
+                                                        </object>
+                                                    </object>
+                                                </object>
+                                            </object>
+                                        </object>
+                                    </object>
+                                </object>
+                            </object>
+                            <object class="sizeritem" expanded="1">
+                                <property name="border">5</property>
+                                <property name="flag">wxEXPAND | wxALL</property>
+                                <property name="proportion">1</property>
+                                <object class="wxPanel" expanded="1">
+                                    <property name="BottomDockable">1</property>
+                                    <property name="LeftDockable">1</property>
+                                    <property name="RightDockable">1</property>
+                                    <property name="TopDockable">1</property>
+                                    <property name="aui_layer"></property>
+                                    <property name="aui_name"></property>
+                                    <property name="aui_position"></property>
+                                    <property name="aui_row"></property>
+                                    <property name="best_size"></property>
+                                    <property name="bg"></property>
+                                    <property name="caption"></property>
+                                    <property name="caption_visible">1</property>
+                                    <property name="center_pane">0</property>
+                                    <property name="close_button">1</property>
+                                    <property name="context_help"></property>
+                                    <property name="context_menu">1</property>
+                                    <property name="default_pane">0</property>
+                                    <property name="dock">Dock</property>
+                                    <property name="dock_fixed">0</property>
+                                    <property name="docking">Left</property>
+                                    <property name="enabled">1</property>
+                                    <property name="fg"></property>
+                                    <property name="floatable">1</property>
+                                    <property name="font"></property>
+                                    <property name="gripper">0</property>
+                                    <property name="hidden">0</property>
+                                    <property name="id">wxID_ANY</property>
+                                    <property name="max_size"></property>
+                                    <property name="maximize_button">0</property>
+                                    <property name="maximum_size"></property>
+                                    <property name="min_size"></property>
+                                    <property name="minimize_button">0</property>
+                                    <property name="minimum_size"></property>
+                                    <property name="moveable">1</property>
+                                    <property name="name">m_panel61</property>
+                                    <property name="pane_border">1</property>
+                                    <property name="pane_position"></property>
+                                    <property name="pane_size"></property>
+                                    <property name="permission">protected</property>
+                                    <property name="pin_button">1</property>
+                                    <property name="pos"></property>
+                                    <property name="resize">Resizable</property>
+                                    <property name="show">1</property>
+                                    <property name="size"></property>
+                                    <property name="subclass"></property>
+                                    <property name="toolbar_pane">0</property>
+                                    <property name="tooltip"></property>
+                                    <property name="window_extra_style"></property>
+                                    <property name="window_name"></property>
+                                    <property name="window_style">wxTAB_TRAVERSAL</property>
+                                    <event name="OnChar"></event>
+                                    <event name="OnEnterWindow"></event>
+                                    <event name="OnEraseBackground"></event>
+                                    <event name="OnKeyDown"></event>
+                                    <event name="OnKeyUp"></event>
+                                    <event name="OnKillFocus"></event>
+                                    <event name="OnLeaveWindow"></event>
+                                    <event name="OnLeftDClick"></event>
+                                    <event name="OnLeftDown"></event>
+                                    <event name="OnLeftUp"></event>
+                                    <event name="OnMiddleDClick"></event>
+                                    <event name="OnMiddleDown"></event>
+                                    <event name="OnMiddleUp"></event>
+                                    <event name="OnMotion"></event>
+                                    <event name="OnMouseEvents"></event>
+                                    <event name="OnMouseWheel"></event>
+                                    <event name="OnPaint"></event>
+                                    <event name="OnRightDClick"></event>
+                                    <event name="OnRightDown"></event>
+                                    <event name="OnRightUp"></event>
+                                    <event name="OnSetFocus"></event>
+                                    <event name="OnSize"></event>
+                                    <event name="OnUpdateUI"></event>
+                                    <object class="wxBoxSizer" expanded="1">
+                                        <property name="minimum_size"></property>
+                                        <property name="name">bSizer7</property>
+                                        <property name="orient">wxVERTICAL</property>
+                                        <property name="permission">none</property>
+                                        <object class="sizeritem" expanded="1">
+                                            <property name="border">5</property>
+                                            <property name="flag">wxALL</property>
+                                            <property name="proportion">0</property>
+                                            <object class="wxStaticText" expanded="1">
+                                                <property name="BottomDockable">1</property>
+                                                <property name="LeftDockable">1</property>
+                                                <property name="RightDockable">1</property>
+                                                <property name="TopDockable">1</property>
+                                                <property name="aui_layer"></property>
+                                                <property name="aui_name"></property>
+                                                <property name="aui_position"></property>
+                                                <property name="aui_row"></property>
+                                                <property name="best_size"></property>
+                                                <property name="bg"></property>
+                                                <property name="caption"></property>
+                                                <property name="caption_visible">1</property>
+                                                <property name="center_pane">0</property>
+                                                <property name="close_button">1</property>
+                                                <property name="context_help"></property>
+                                                <property name="context_menu">1</property>
+                                                <property name="default_pane">0</property>
+                                                <property name="dock">Dock</property>
+                                                <property name="dock_fixed">0</property>
+                                                <property name="docking">Left</property>
+                                                <property name="enabled">1</property>
+                                                <property name="fg"></property>
+                                                <property name="floatable">1</property>
+                                                <property name="font"></property>
+                                                <property name="gripper">0</property>
+                                                <property name="hidden">0</property>
+                                                <property name="id">wxID_ANY</property>
+                                                <property name="label">SoapySDR Device Options</property>
+                                                <property name="max_size"></property>
+                                                <property name="maximize_button">0</property>
+                                                <property name="maximum_size"></property>
+                                                <property name="min_size"></property>
+                                                <property name="minimize_button">0</property>
+                                                <property name="minimum_size"></property>
+                                                <property name="moveable">1</property>
+                                                <property name="name">m_staticText1</property>
+                                                <property name="pane_border">1</property>
+                                                <property name="pane_position"></property>
+                                                <property name="pane_size"></property>
+                                                <property name="permission">protected</property>
+                                                <property name="pin_button">1</property>
+                                                <property name="pos"></property>
+                                                <property name="resize">Resizable</property>
+                                                <property name="show">1</property>
+                                                <property name="size"></property>
+                                                <property name="style"></property>
+                                                <property name="subclass"></property>
+                                                <property name="toolbar_pane">0</property>
+                                                <property name="tooltip"></property>
+                                                <property name="window_extra_style"></property>
+                                                <property name="window_name"></property>
+                                                <property name="window_style"></property>
+                                                <property name="wrap">-1</property>
+                                                <event name="OnChar"></event>
+                                                <event name="OnEnterWindow"></event>
+                                                <event name="OnEraseBackground"></event>
+                                                <event name="OnKeyDown"></event>
+                                                <event name="OnKeyUp"></event>
+                                                <event name="OnKillFocus"></event>
+                                                <event name="OnLeaveWindow"></event>
+                                                <event name="OnLeftDClick"></event>
+                                                <event name="OnLeftDown"></event>
+                                                <event name="OnLeftUp"></event>
+                                                <event name="OnMiddleDClick"></event>
+                                                <event name="OnMiddleDown"></event>
+                                                <event name="OnMiddleUp"></event>
+                                                <event name="OnMotion"></event>
+                                                <event name="OnMouseEvents"></event>
+                                                <event name="OnMouseWheel"></event>
+                                                <event name="OnPaint"></event>
+                                                <event name="OnRightDClick"></event>
+                                                <event name="OnRightDown"></event>
+                                                <event name="OnRightUp"></event>
+                                                <event name="OnSetFocus"></event>
+                                                <event name="OnSize"></event>
+                                                <event name="OnUpdateUI"></event>
+                                            </object>
+                                        </object>
+                                        <object class="sizeritem" expanded="1">
+                                            <property name="border">5</property>
+                                            <property name="flag">wxALL|wxEXPAND</property>
+                                            <property name="proportion">1</property>
+                                            <object class="wxPropertyGrid" expanded="1">
+                                                <property name="BottomDockable">1</property>
+                                                <property name="LeftDockable">1</property>
+                                                <property name="RightDockable">1</property>
+                                                <property name="TopDockable">1</property>
+                                                <property name="aui_layer"></property>
+                                                <property name="aui_name"></property>
+                                                <property name="aui_position"></property>
+                                                <property name="aui_row"></property>
+                                                <property name="best_size"></property>
+                                                <property name="bg"></property>
+                                                <property name="bitmap"></property>
+                                                <property name="caption"></property>
+                                                <property name="caption_visible">1</property>
+                                                <property name="center_pane">0</property>
+                                                <property name="close_button">1</property>
+                                                <property name="context_help"></property>
+                                                <property name="context_menu">1</property>
+                                                <property name="default_pane">0</property>
+                                                <property name="dock">Dock</property>
+                                                <property name="dock_fixed">0</property>
+                                                <property name="docking">Left</property>
+                                                <property name="enabled">1</property>
+                                                <property name="extra_style"></property>
+                                                <property name="fg"></property>
+                                                <property name="floatable">1</property>
+                                                <property name="font"></property>
+                                                <property name="gripper">0</property>
+                                                <property name="hidden">0</property>
+                                                <property name="id">wxID_ANY</property>
+                                                <property name="include_advanced">1</property>
+                                                <property name="max_size"></property>
+                                                <property name="maximize_button">0</property>
+                                                <property name="maximum_size"></property>
+                                                <property name="min_size"></property>
+                                                <property name="minimize_button">0</property>
+                                                <property name="minimum_size"></property>
+                                                <property name="moveable">1</property>
+                                                <property name="name">m_propertyGrid</property>
+                                                <property name="pane_border">1</property>
+                                                <property name="pane_position"></property>
+                                                <property name="pane_size"></property>
+                                                <property name="permission">protected</property>
+                                                <property name="pin_button">1</property>
+                                                <property name="pos"></property>
+                                                <property name="resize">Resizable</property>
+                                                <property name="show">1</property>
+                                                <property name="size"></property>
+                                                <property name="style">wxPG_DEFAULT_STYLE</property>
+                                                <property name="subclass"></property>
+                                                <property name="toolbar_pane">0</property>
+                                                <property name="tooltip"></property>
+                                                <property name="window_extra_style"></property>
+                                                <property name="window_name"></property>
+                                                <property name="window_style"></property>
+                                                <event name="OnChar"></event>
+                                                <event name="OnEnterWindow"></event>
+                                                <event name="OnEraseBackground"></event>
+                                                <event name="OnKeyDown"></event>
+                                                <event name="OnKeyUp"></event>
+                                                <event name="OnKillFocus"></event>
+                                                <event name="OnLeaveWindow"></event>
+                                                <event name="OnLeftDClick"></event>
+                                                <event name="OnLeftDown"></event>
+                                                <event name="OnLeftUp"></event>
+                                                <event name="OnMiddleDClick"></event>
+                                                <event name="OnMiddleDown"></event>
+                                                <event name="OnMiddleUp"></event>
+                                                <event name="OnMotion"></event>
+                                                <event name="OnMouseEvents"></event>
+                                                <event name="OnMouseWheel"></event>
+                                                <event name="OnPaint"></event>
+                                                <event name="OnPropertyGridChanged">OnPropGridChanged</event>
+                                                <event name="OnPropertyGridChanging"></event>
+                                                <event name="OnRightDClick"></event>
+                                                <event name="OnRightDown"></event>
+                                                <event name="OnRightUp"></event>
+                                                <event name="OnSetFocus">OnPropGridFocus</event>
+                                                <event name="OnSize"></event>
+                                                <event name="OnUpdateUI"></event>
+                                            </object>
+                                        </object>
+                                    </object>
+                                </object>
+                            </object>
+                        </object>
+                    </object>
+                </object>
+            </object>
+            <object class="wxTimer" expanded="1">
+                <property name="enabled">0</property>
+                <property name="id">wxID_ANY</property>
+                <property name="name">m_deviceTimer</property>
+                <property name="oneshot">0</property>
+                <property name="period">5000</property>
+                <property name="permission">protected</property>
+                <event name="OnTimer">OnDeviceTimer</event>
+            </object>
+        </object>
+    </object>
+</wxFormBuilder_Project>
diff --git a/src/forms/SDRDevices/SDRDevices.h b/src/forms/SDRDevices/SDRDevices.h
new file mode 100644
index 0000000..199abbe
--- /dev/null
+++ b/src/forms/SDRDevices/SDRDevices.h
@@ -0,0 +1,48 @@
+#pragma once
+
+#include <map>
+#include <vector>
+
+#include "SDRDevicesForm.h"
+#include "SoapySDRThread.h"
+#include "SDREnumerator.h"
+#include "SDRDeviceAdd.h"
+
+class SDRDevicesDialog: public devFrame {
+public:
+    SDRDevicesDialog( wxWindow* parent );
+    
+    void OnClose( wxCloseEvent& event );
+    void OnDeleteItem( wxTreeEvent& event );
+    void OnSelectionChanged( wxTreeEvent& event );
+    void OnAddRemote( wxMouseEvent& event );
+    void OnUseSelected( wxMouseEvent& event );
+    void OnTreeDoubleClick( wxMouseEvent& event );
+    void OnDeviceTimer( wxTimerEvent& event );
+    void OnRefreshDevices( wxMouseEvent& event );
+    void OnPropGridChanged( wxPropertyGridEvent& event );
+    void OnPropGridFocus( wxFocusEvent& event );
+
+private:
+    void refreshDeviceProperties();
+    void doRefreshDevices();
+    
+    SDRDeviceInfo *getSelectedDevice(wxTreeItemId selId);
+    wxPGProperty *addArgInfoProperty(wxPropertyGrid *pg, SoapySDR::ArgInfo arg);
+
+    bool refresh, failed;
+    std::map<std::string, std::vector<SDRDeviceInfo *>* > devs;
+    std::vector<SDRDeviceInfo *>::iterator devs_i;
+    std::map<wxTreeItemId, SDRDeviceInfo *> devItems;
+    std::map<wxTreeItemId, SDRDeviceInfo *>::iterator devItems_i;
+    SDRDeviceInfo *dev;
+    std::map<std::string, SoapySDR::ArgInfo> deviceArgs;
+    std::map<std::string, wxPGProperty *> runtimeProps;
+    std::map<std::string, SoapySDR::ArgInfo> runtimeArgs;
+    std::map<std::string, wxPGProperty *> streamProps;
+    std::map<std::string, wxPGProperty *> devSettings;
+    wxTreeItemId selId;
+    wxTreeItemId editId;
+    wxTreeItemId removeId;
+    SDRDeviceAddDialog *devAddDialog;
+};
\ No newline at end of file
diff --git a/src/forms/SDRDevices/SDRDevicesForm.cpp b/src/forms/SDRDevices/SDRDevicesForm.cpp
new file mode 100644
index 0000000..cea3da8
--- /dev/null
+++ b/src/forms/SDRDevices/SDRDevicesForm.cpp
@@ -0,0 +1,115 @@
+///////////////////////////////////////////////////////////////////////////
+// C++ code generated with wxFormBuilder (version Aug 23 2015)
+// http://www.wxformbuilder.org/
+//
+// PLEASE DO "NOT" EDIT THIS FILE!
+///////////////////////////////////////////////////////////////////////////
+
+#include "SDRDevicesForm.h"
+
+///////////////////////////////////////////////////////////////////////////
+
+devFrame::devFrame( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxFrame( parent, id, title, pos, size, style )
+{
+	this->SetSizeHints( wxDefaultSize, wxDefaultSize );
+	
+	devStatusBar = this->CreateStatusBar( 1, wxST_SIZEGRIP, wxID_ANY );
+	wxBoxSizer* devFrameSizer;
+	devFrameSizer = new wxBoxSizer( wxVERTICAL );
+	
+	m_panel3 = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
+	wxBoxSizer* bSizer4;
+	bSizer4 = new wxBoxSizer( wxHORIZONTAL );
+	
+	m_panel6 = new wxPanel( m_panel3, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
+	wxBoxSizer* bSizer6;
+	bSizer6 = new wxBoxSizer( wxVERTICAL );
+	
+	devTree = new wxTreeCtrl( m_panel6, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTR_DEFAULT_STYLE );
+	devTree->Enable( false );
+	
+	bSizer6->Add( devTree, 1, wxEXPAND, 5 );
+	
+	m_panel4 = new wxPanel( m_panel6, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
+	wxBoxSizer* bSizer5;
+	bSizer5 = new wxBoxSizer( wxHORIZONTAL );
+	
+	m_refreshButton = new wxButton( m_panel4, wxID_ANY, wxT("Refresh"), wxDefaultPosition, wxDefaultSize, 0 );
+	bSizer5->Add( m_refreshButton, 1, wxALL, 5 );
+	
+	m_addRemoteButton = new wxButton( m_panel4, wxID_ANY, wxT("Add"), wxDefaultPosition, wxDefaultSize, 0 );
+	bSizer5->Add( m_addRemoteButton, 1, wxALL, 5 );
+	
+	m_useSelectedButton = new wxButton( m_panel4, wxID_ANY, wxT("Start"), wxDefaultPosition, wxDefaultSize, 0 );
+	bSizer5->Add( m_useSelectedButton, 1, wxALL|wxALIGN_CENTER_VERTICAL, 5 );
+	
+	
+	m_panel4->SetSizer( bSizer5 );
+	m_panel4->Layout();
+	bSizer5->Fit( m_panel4 );
+	bSizer6->Add( m_panel4, 0, wxEXPAND, 5 );
+	
+	
+	m_panel6->SetSizer( bSizer6 );
+	m_panel6->Layout();
+	bSizer6->Fit( m_panel6 );
+	bSizer4->Add( m_panel6, 1, wxEXPAND | wxALL, 5 );
+	
+	m_panel61 = new wxPanel( m_panel3, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
+	wxBoxSizer* bSizer7;
+	bSizer7 = new wxBoxSizer( wxVERTICAL );
+	
+	m_staticText1 = new wxStaticText( m_panel61, wxID_ANY, wxT("SoapySDR Device Options"), wxDefaultPosition, wxDefaultSize, 0 );
+	m_staticText1->Wrap( -1 );
+	bSizer7->Add( m_staticText1, 0, wxALL, 5 );
+	
+	m_propertyGrid = new wxPropertyGrid(m_panel61, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxPG_DEFAULT_STYLE);
+	bSizer7->Add( m_propertyGrid, 1, wxALL|wxEXPAND, 5 );
+	
+	
+	m_panel61->SetSizer( bSizer7 );
+	m_panel61->Layout();
+	bSizer7->Fit( m_panel61 );
+	bSizer4->Add( m_panel61, 1, wxEXPAND | wxALL, 5 );
+	
+	
+	m_panel3->SetSizer( bSizer4 );
+	m_panel3->Layout();
+	bSizer4->Fit( m_panel3 );
+	devFrameSizer->Add( m_panel3, 1, wxEXPAND | wxALL, 5 );
+	
+	
+	this->SetSizer( devFrameSizer );
+	this->Layout();
+	m_deviceTimer.SetOwner( this, wxID_ANY );
+	
+	this->Centre( wxBOTH );
+	
+	// Connect Events
+	this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( devFrame::OnClose ) );
+	devTree->Connect( wxEVT_LEFT_DCLICK, wxMouseEventHandler( devFrame::OnTreeDoubleClick ), NULL, this );
+	devTree->Connect( wxEVT_COMMAND_TREE_DELETE_ITEM, wxTreeEventHandler( devFrame::OnDeleteItem ), NULL, this );
+	devTree->Connect( wxEVT_COMMAND_TREE_SEL_CHANGED, wxTreeEventHandler( devFrame::OnSelectionChanged ), NULL, this );
+	m_refreshButton->Connect( wxEVT_LEFT_UP, wxMouseEventHandler( devFrame::OnRefreshDevices ), NULL, this );
+	m_addRemoteButton->Connect( wxEVT_LEFT_UP, wxMouseEventHandler( devFrame::OnAddRemote ), NULL, this );
+	m_useSelectedButton->Connect( wxEVT_LEFT_UP, wxMouseEventHandler( devFrame::OnUseSelected ), NULL, this );
+	m_propertyGrid->Connect( wxEVT_PG_CHANGED, wxPropertyGridEventHandler( devFrame::OnPropGridChanged ), NULL, this );
+	m_propertyGrid->Connect( wxEVT_SET_FOCUS, wxFocusEventHandler( devFrame::OnPropGridFocus ), NULL, this );
+	this->Connect( wxID_ANY, wxEVT_TIMER, wxTimerEventHandler( devFrame::OnDeviceTimer ) );
+}
+
+devFrame::~devFrame()
+{
+	// Disconnect Events
+	this->Disconnect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( devFrame::OnClose ) );
+	devTree->Disconnect( wxEVT_LEFT_DCLICK, wxMouseEventHandler( devFrame::OnTreeDoubleClick ), NULL, this );
+	devTree->Disconnect( wxEVT_COMMAND_TREE_DELETE_ITEM, wxTreeEventHandler( devFrame::OnDeleteItem ), NULL, this );
+	devTree->Disconnect( wxEVT_COMMAND_TREE_SEL_CHANGED, wxTreeEventHandler( devFrame::OnSelectionChanged ), NULL, this );
+	m_refreshButton->Disconnect( wxEVT_LEFT_UP, wxMouseEventHandler( devFrame::OnRefreshDevices ), NULL, this );
+	m_addRemoteButton->Disconnect( wxEVT_LEFT_UP, wxMouseEventHandler( devFrame::OnAddRemote ), NULL, this );
+	m_useSelectedButton->Disconnect( wxEVT_LEFT_UP, wxMouseEventHandler( devFrame::OnUseSelected ), NULL, this );
+	m_propertyGrid->Disconnect( wxEVT_PG_CHANGED, wxPropertyGridEventHandler( devFrame::OnPropGridChanged ), NULL, this );
+	m_propertyGrid->Disconnect( wxEVT_SET_FOCUS, wxFocusEventHandler( devFrame::OnPropGridFocus ), NULL, this );
+	this->Disconnect( wxID_ANY, wxEVT_TIMER, wxTimerEventHandler( devFrame::OnDeviceTimer ) );
+	
+}
diff --git a/src/forms/SDRDevices/SDRDevicesForm.h b/src/forms/SDRDevices/SDRDevicesForm.h
new file mode 100644
index 0000000..74d8d6c
--- /dev/null
+++ b/src/forms/SDRDevices/SDRDevicesForm.h
@@ -0,0 +1,77 @@
+///////////////////////////////////////////////////////////////////////////
+// C++ code generated with wxFormBuilder (version Aug 23 2015)
+// http://www.wxformbuilder.org/
+//
+// PLEASE DO "NOT" EDIT THIS FILE!
+///////////////////////////////////////////////////////////////////////////
+
+#ifndef __SDRDEVICESFORM_H__
+#define __SDRDEVICESFORM_H__
+
+#include <wx/artprov.h>
+#include <wx/xrc/xmlres.h>
+#include <wx/statusbr.h>
+#include <wx/gdicmn.h>
+#include <wx/font.h>
+#include <wx/colour.h>
+#include <wx/settings.h>
+#include <wx/string.h>
+#include <wx/treectrl.h>
+#include <wx/button.h>
+#include <wx/sizer.h>
+#include <wx/panel.h>
+#include <wx/stattext.h>
+#include <wx/bitmap.h>
+#include <wx/image.h>
+#include <wx/icon.h>
+#include <wx/propgrid/propgrid.h>
+#include <wx/propgrid/advprops.h>
+#include <wx/timer.h>
+#include <wx/frame.h>
+
+///////////////////////////////////////////////////////////////////////////
+
+
+///////////////////////////////////////////////////////////////////////////////
+/// Class devFrame
+///////////////////////////////////////////////////////////////////////////////
+class devFrame : public wxFrame 
+{
+	private:
+	
+	protected:
+		wxStatusBar* devStatusBar;
+		wxPanel* m_panel3;
+		wxPanel* m_panel6;
+		wxTreeCtrl* devTree;
+		wxPanel* m_panel4;
+		wxButton* m_refreshButton;
+		wxButton* m_addRemoteButton;
+		wxButton* m_useSelectedButton;
+		wxPanel* m_panel61;
+		wxStaticText* m_staticText1;
+		wxPropertyGrid* m_propertyGrid;
+		wxTimer m_deviceTimer;
+		
+		// Virtual event handlers, overide them in your derived class
+		virtual void OnClose( wxCloseEvent& event ) { event.Skip(); }
+		virtual void OnTreeDoubleClick( wxMouseEvent& event ) { event.Skip(); }
+		virtual void OnDeleteItem( wxTreeEvent& event ) { event.Skip(); }
+		virtual void OnSelectionChanged( wxTreeEvent& event ) { event.Skip(); }
+		virtual void OnRefreshDevices( wxMouseEvent& event ) { event.Skip(); }
+		virtual void OnAddRemote( wxMouseEvent& event ) { event.Skip(); }
+		virtual void OnUseSelected( wxMouseEvent& event ) { event.Skip(); }
+		virtual void OnPropGridChanged( wxPropertyGridEvent& event ) { event.Skip(); }
+		virtual void OnPropGridFocus( wxFocusEvent& event ) { event.Skip(); }
+		virtual void OnDeviceTimer( wxTimerEvent& event ) { event.Skip(); }
+		
+	
+	public:
+		
+		devFrame( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxT("CubicSDR :: SDR Devices"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 700,467 ), long style = wxDEFAULT_FRAME_STYLE|wxTAB_TRAVERSAL );
+		
+		~devFrame();
+	
+};
+
+#endif //__SDRDEVICESFORM_H__
diff --git a/src/modules/modem/Modem.cpp b/src/modules/modem/Modem.cpp
new file mode 100644
index 0000000..da782ee
--- /dev/null
+++ b/src/modules/modem/Modem.cpp
@@ -0,0 +1,119 @@
+#include "Modem.h"
+#include "CubicSDR.h"
+
+
+ModemFactoryList Modem::modemFactories;
+DefaultRatesList Modem::modemDefaultRates;
+
+//! Create an empty range (0.0, 0.0)
+ModemRange::ModemRange(void) {
+    _min = 0;
+    _max = 0;
+}
+
+//! Create a min/max range
+ModemRange::ModemRange(const double minimum, const double maximum) {
+    _min = minimum;
+    _max = maximum;
+}
+
+//! Get the range minimum
+double ModemRange::minimum(void) const {
+    return _min;
+}
+
+//! Get the range maximum
+double ModemRange::maximum(void) const {
+    return _max;
+}
+
+ModemArgInfo::ModemArgInfo(void) {
+    
+}
+
+Modem::Modem() {
+    useSignalOutput(false);
+}
+
+Modem::~Modem() {
+    
+}
+
+void Modem::addModemFactory(ModemFactoryFn factoryFunc, std::string modemName, int defaultRate) {
+    modemFactories[modemName] = factoryFunc;
+    modemDefaultRates[modemName] = defaultRate;
+}
+
+ModemFactoryList Modem::getFactories() {
+    return modemFactories;
+}
+
+Modem *Modem::makeModem(std::string modemName) {
+    if (modemFactories.find(modemName) != modemFactories.end()) {
+        return (Modem *)modemFactories[modemName]();
+    }
+    
+    return nullptr;
+}
+
+int Modem::getModemDefaultSampleRate(std::string modemName) {
+    if (modemDefaultRates.find(modemName) != modemDefaultRates.end()) {
+        return modemDefaultRates[modemName];
+    }
+    
+    return 0;
+}
+
+ModemArgInfoList Modem::getSettings() {
+    ModemArgInfoList args;
+    
+    return args;
+}
+
+int Modem::getDefaultSampleRate() {
+    return 200000;
+}
+
+void Modem::writeSetting(std::string /* setting */, std::string /* value */) {
+    // ...
+}
+
+std::string Modem::readSetting(std::string /* setting */) {
+    return "";
+}
+
+void Modem::writeSettings(ModemSettings settings) {
+    for (ModemSettings::const_iterator i = settings.begin(); i != settings.end(); i++) {
+        writeSetting(i->first, i->second);
+    }
+}
+
+ModemSettings Modem::readSettings() {
+    ModemArgInfoList args = getSettings();
+    ModemSettings rs;
+    for (ModemArgInfoList::const_iterator i = args.begin(); i != args.end(); i++) {
+        rs[i->key] = readSetting(i->key);
+    }
+    return rs;
+}
+
+bool Modem::shouldRebuildKit() {
+    return refreshKit.load();
+}
+
+void Modem::rebuildKit() {
+    refreshKit.store(true);
+}
+
+void Modem::clearRebuildKit() {
+    refreshKit.store(false);
+}
+
+
+bool Modem::useSignalOutput() {
+    return _useSignalOutput.load();
+}
+
+void Modem::useSignalOutput(bool useOutput) {
+    _useSignalOutput.store(useOutput);
+}
diff --git a/src/modules/modem/Modem.h b/src/modules/modem/Modem.h
new file mode 100644
index 0000000..423c65b
--- /dev/null
+++ b/src/modules/modem/Modem.h
@@ -0,0 +1,162 @@
+#pragma once
+
+#include "liquid/liquid.h"
+#include "IOThread.h"
+#include "AudioThread.h"
+#include <cmath>
+#include <atomic>
+
+#define MIN_BANDWIDTH 500
+
+#ifndef M_PI
+#define M_PI        3.14159265358979323846
+#endif
+
+class ModemKit {
+public:
+    ModemKit() : sampleRate(0), audioSampleRate(0) {
+        
+    }
+    
+    long long sampleRate;
+    int audioSampleRate;
+};
+
+class ModemIQData: public ReferenceCounter {
+public:
+    std::vector<liquid_float_complex> data;
+    long long sampleRate;
+    
+    ModemIQData() : sampleRate(0) {
+        
+    }
+    
+    ~ModemIQData() {
+        std::lock_guard < std::recursive_mutex > lock(m_mutex);
+    }
+};
+
+// Copy of SoapySDR::Range, original comments
+class ModemRange
+{
+public:
+    
+    //! Create an empty range (0.0, 0.0)
+    ModemRange(void);
+    
+    //! Create a min/max range
+    ModemRange(const double minimum, const double maximum);
+    
+    //! Get the range minimum
+    double minimum(void) const;
+    
+    //! Get the range maximum
+    double maximum(void) const;
+    
+private:
+    double _min, _max;
+};
+
+// Modified version of SoapySDR::ArgInfo, original comments
+class ModemArgInfo
+{
+public:
+    //! Default constructor
+    ModemArgInfo(void);
+    
+    //! The key used to identify the argument (required)
+    std::string key;
+    
+    /*!
+     * The default value of the argument when not specified (required)
+     * Numbers should use standard floating point and integer formats.
+     * Boolean values should be represented as "true" and  "false".
+     */
+    std::string value;
+    
+    //! The displayable name of the argument (optional, use key if empty)
+    std::string name;
+    
+    //! A brief description about the argument (optional)
+    std::string description;
+    
+    //! The units of the argument: dB, Hz, etc (optional)
+    std::string units;
+    
+    //! The data type of the argument (required)
+    enum Type { BOOL, INT, FLOAT, STRING, PATH_DIR, PATH_FILE, COLOR } type;
+    
+    /*!
+     * The range of possible numeric values (optional)
+     * When specified, the argument should be restricted to this range.
+     * The range is only applicable to numeric argument types.
+     */
+    ModemRange range;
+    
+    /*!
+     * A discrete list of possible values (optional)
+     * When specified, the argument should be restricted to this options set.
+     */
+    std::vector<std::string> options;
+    
+    /*!
+     * A discrete list of displayable names for the enumerated options (optional)
+     * When not specified, the option value itself can be used as a display name.
+     */
+    std::vector<std::string> optionNames;
+};
+
+typedef std::vector<ModemArgInfo> ModemArgInfoList;
+
+class ModemBase {
+    
+};
+
+typedef ModemBase *(*ModemFactoryFn)();
+
+
+typedef std::map<std::string, ModemFactoryFn> ModemFactoryList;
+typedef std::map<std::string, int> DefaultRatesList;
+
+typedef std::map<std::string, std::string> ModemSettings;
+
+class Modem : public ModemBase  {
+public:
+    static void addModemFactory(ModemFactoryFn, std::string modemName, int defaultRate);
+    static ModemFactoryList getFactories();
+    
+    static Modem *makeModem(std::string modemName);
+    static int getModemDefaultSampleRate(std::string modemName);
+    
+    virtual std::string getType() = 0;
+    virtual std::string getName() = 0;
+    
+    Modem();
+    virtual ~Modem();
+    
+    virtual ModemArgInfoList getSettings();
+    virtual int getDefaultSampleRate();
+    virtual void writeSetting(std::string setting, std::string value);
+    virtual void writeSettings(ModemSettings settings);
+    virtual std::string readSetting(std::string setting);
+    virtual ModemSettings readSettings();
+    
+    virtual int checkSampleRate(long long sampleRate, int audioSampleRate) = 0;
+    
+    virtual ModemKit *buildKit(long long sampleRate, int audioSampleRate) = 0;
+    virtual void disposeKit(ModemKit *kit) = 0;
+    
+    virtual void demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut) = 0;
+    
+    bool shouldRebuildKit();
+    void rebuildKit();
+    void clearRebuildKit();
+    
+    bool useSignalOutput();
+    void useSignalOutput(bool useOutput);
+    
+private:
+    static ModemFactoryList modemFactories;
+    static DefaultRatesList modemDefaultRates;
+    std::atomic_bool refreshKit, _useSignalOutput;
+};
diff --git a/src/modules/modem/ModemAnalog.cpp b/src/modules/modem/ModemAnalog.cpp
new file mode 100644
index 0000000..14f0b3c
--- /dev/null
+++ b/src/modules/modem/ModemAnalog.cpp
@@ -0,0 +1,98 @@
+#include "ModemAnalog.h"
+
+ModemAnalog::ModemAnalog() : Modem(), aOutputCeil(1), aOutputCeilMA(1), aOutputCeilMAA(1) {
+    
+}
+
+std::string ModemAnalog::getType() {
+    return "analog";
+}
+
+int ModemAnalog::checkSampleRate(long long sampleRate, int /* audioSampleRate */) {
+    if (sampleRate < MIN_BANDWIDTH) {
+        return MIN_BANDWIDTH;
+    }
+    return sampleRate;
+}
+
+ModemKit *ModemAnalog::buildKit(long long sampleRate, int audioSampleRate) {
+    ModemKitAnalog *akit = new ModemKitAnalog;
+    
+    // stop-band attenuation [dB]
+    float As = 60.0f;
+    
+    akit->sampleRate = sampleRate;
+    akit->audioSampleRate = audioSampleRate;
+    akit->audioResampleRatio = double(audioSampleRate) / double(sampleRate);
+    akit->audioResampler = msresamp_rrrf_create(akit->audioResampleRatio, As);
+    
+    return akit;
+}
+
+void ModemAnalog::disposeKit(ModemKit *kit) {
+    ModemKitAnalog *akit = (ModemKitAnalog *)kit;
+    
+    msresamp_rrrf_destroy(akit->audioResampler);
+    delete akit;
+}
+
+void ModemAnalog::initOutputBuffers(ModemKitAnalog *akit, ModemIQData *input) {
+    bufSize = input->data.size();
+    
+    if (!bufSize) {
+        return;
+    }
+    
+    double audio_resample_ratio = akit->audioResampleRatio;
+    
+    size_t audio_out_size = (size_t)ceil((double) (bufSize) * audio_resample_ratio) + 512;
+    
+    if (demodOutputData.size() != bufSize) {
+        if (demodOutputData.capacity() < bufSize) {
+            demodOutputData.reserve(bufSize);
+        }
+        demodOutputData.resize(bufSize);
+    }
+    if (resampledOutputData.size() != audio_out_size) {
+        if (resampledOutputData.capacity() < audio_out_size) {
+            resampledOutputData.reserve(audio_out_size);
+        }
+        resampledOutputData.resize(audio_out_size);
+    }
+}
+
+void ModemAnalog::buildAudioOutput(ModemKitAnalog *akit, AudioThreadInput *audioOut, bool autoGain) {
+    unsigned int numAudioWritten;
+
+    if (autoGain) {
+        aOutputCeilMA = aOutputCeilMA + (aOutputCeil - aOutputCeilMA) * 0.025f;
+        aOutputCeilMAA = aOutputCeilMAA + (aOutputCeilMA - aOutputCeilMAA) * 0.025f;
+        aOutputCeil = 0;
+        
+        for (size_t i = 0; i < bufSize; i++) {
+            if (demodOutputData[i] > aOutputCeil) {
+                aOutputCeil = demodOutputData[i];
+            }
+        }
+        
+        float gain = 0.5f / aOutputCeilMAA;
+        
+        for (size_t i = 0; i < bufSize; i++) {
+            demodOutputData[i] *= gain;
+        }
+    }
+    
+    msresamp_rrrf_execute(akit->audioResampler, &demodOutputData[0], demodOutputData.size(), &resampledOutputData[0], &numAudioWritten);
+    
+    audioOut->channels = 1;
+    audioOut->sampleRate = akit->audioSampleRate;
+    audioOut->data.assign(resampledOutputData.begin(), resampledOutputData.begin() + numAudioWritten);
+}
+
+std::vector<float> *ModemAnalog::getDemodOutputData() {
+    return &demodOutputData;
+}
+
+std::vector<float> *ModemAnalog::getResampledOutputData() {
+    return &resampledOutputData;
+}
diff --git a/src/modules/modem/ModemAnalog.h b/src/modules/modem/ModemAnalog.h
new file mode 100644
index 0000000..be19159
--- /dev/null
+++ b/src/modules/modem/ModemAnalog.h
@@ -0,0 +1,34 @@
+#pragma once
+#include "Modem.h"
+
+class ModemKitAnalog : public ModemKit {
+public:
+    ModemKitAnalog() : ModemKit(), audioResampler(nullptr), audioResampleRatio(0) {
+        
+    };
+    
+    msresamp_rrrf audioResampler;
+    double audioResampleRatio;
+};
+
+
+class ModemAnalog : public Modem {
+public:
+    ModemAnalog();
+    std::string getType();
+    virtual int checkSampleRate(long long sampleRate, int audioSampleRate);
+    virtual ModemKit *buildKit(long long sampleRate, int audioSampleRate);
+    virtual void disposeKit(ModemKit *kit);
+    virtual void initOutputBuffers(ModemKitAnalog *akit, ModemIQData *input);
+    virtual void buildAudioOutput(ModemKitAnalog *akit, AudioThreadInput *audioOut, bool autoGain);
+    virtual std::vector<float> *getDemodOutputData();
+    virtual std::vector<float> *getResampledOutputData();
+protected:
+    size_t bufSize;
+    std::vector<float> demodOutputData;
+    std::vector<float> resampledOutputData;
+
+    float aOutputCeil;
+    float aOutputCeilMA;
+    float aOutputCeilMAA;
+};
diff --git a/src/modules/modem/ModemDigital.cpp b/src/modules/modem/ModemDigital.cpp
new file mode 100644
index 0000000..3723d15
--- /dev/null
+++ b/src/modules/modem/ModemDigital.cpp
@@ -0,0 +1,82 @@
+#include "ModemDigital.h"
+
+
+ModemDigitalOutput::ModemDigitalOutput() {
+    
+}
+
+ModemDigital::ModemDigital() : Modem() {
+#if ENABLE_DIGITAL_LAB
+    digitalOut = nullptr;
+#endif
+}
+
+ModemDigitalOutput::~ModemDigitalOutput() {
+    
+}
+
+std::string ModemDigital::getType() {
+    return "digital";
+}
+
+int ModemDigital::checkSampleRate(long long sampleRate, int /* audioSampleRate */) {
+    if (sampleRate < MIN_BANDWIDTH) {
+        return MIN_BANDWIDTH;
+    }
+    return sampleRate;
+}
+
+ModemKit *ModemDigital::buildKit(long long sampleRate, int audioSampleRate) {
+    ModemKitDigital *dkit = new ModemKitDigital;
+    
+    dkit->sampleRate = sampleRate;
+    dkit->audioSampleRate = audioSampleRate;
+    
+    return dkit;
+}
+
+void ModemDigital::disposeKit(ModemKit *kit) {
+    ModemKitDigital *dkit = (ModemKitDigital *)kit;
+    
+    delete dkit;
+}
+
+void ModemDigital::setDemodulatorLock(bool demod_lock_in) {
+    currentDemodLock.store(demod_lock_in);
+}
+
+int ModemDigital::getDemodulatorLock() {
+    return currentDemodLock.load();
+}
+
+void ModemDigital::updateDemodulatorLock(modem mod, float sensitivity) {
+    setDemodulatorLock(modem_get_demodulator_evm(mod) <= sensitivity);
+}
+
+void ModemDigital::digitalStart(ModemKitDigital * /* kit */, modem /* mod */, ModemIQData *input) {
+    size_t bufSize = input->data.size();
+    
+    if (demodOutputDataDigital.size() != bufSize) {
+        if (demodOutputDataDigital.capacity() < bufSize) {
+            demodOutputDataDigital.reserve(bufSize);
+        }
+        demodOutputDataDigital.resize(bufSize);
+    }
+}
+
+void ModemDigital::digitalFinish(ModemKitDigital * /* kit */, modem /* mod */) {
+#if ENABLE_DIGITAL_LAB
+    if (digitalOut && outStream.str().length()) {
+        digitalOut->write(outStream.str());
+        outStream.str("");
+    } else {
+        outStream.str("");
+    }
+#endif
+}
+
+#if ENABLE_DIGITAL_LAB
+void ModemDigital::setOutput(ModemDigitalOutput *modemDigitalOutput) {
+    digitalOut = modemDigitalOutput;
+}
+#endif
diff --git a/src/modules/modem/ModemDigital.h b/src/modules/modem/ModemDigital.h
new file mode 100644
index 0000000..e1991c1
--- /dev/null
+++ b/src/modules/modem/ModemDigital.h
@@ -0,0 +1,61 @@
+#pragma once
+#include "Modem.h"
+#include <map>
+#include <vector>
+#include <sstream>
+#include <ostream>
+#include <mutex>
+
+class ModemKitDigital : public ModemKit {
+public:
+    ModemKitDigital() : ModemKit() {
+        
+    };
+};
+
+class ModemDigitalOutput {
+public:
+    ModemDigitalOutput();
+    virtual ~ModemDigitalOutput();
+    
+    virtual void write(std::string outp) = 0;
+    virtual void write(char outc) = 0;
+    
+    virtual void Show() = 0;
+    virtual void Hide() = 0;
+    virtual void Close() = 0;
+    
+private:
+};
+
+class ModemDigital : public Modem {
+public:
+    ModemDigital();
+    
+    std::string getType();
+    
+    virtual int checkSampleRate(long long sampleRate, int audioSampleRate);
+    
+    virtual ModemKit *buildKit(long long sampleRate, int audioSampleRate);
+    virtual void disposeKit(ModemKit *kit);
+    
+    virtual void digitalStart(ModemKitDigital *kit, modem mod, ModemIQData *input);
+    virtual void digitalFinish(ModemKitDigital *kit, modem mod);
+
+    virtual void setDemodulatorLock(bool demod_lock_in);
+    virtual int getDemodulatorLock();
+    
+    virtual void updateDemodulatorLock(modem mod, float sensitivity);
+
+#if ENABLE_DIGITAL_LAB
+    void setOutput(ModemDigitalOutput *digitalOutput);
+#endif
+    
+protected:
+    std::vector<unsigned int> demodOutputDataDigital;
+    std::atomic_bool currentDemodLock;
+#if ENABLE_DIGITAL_LAB
+    ModemDigitalOutput *digitalOut;
+    std::stringstream outStream;
+#endif
+};
\ No newline at end of file
diff --git a/src/modules/modem/analog/ModemAM.cpp b/src/modules/modem/analog/ModemAM.cpp
new file mode 100644
index 0000000..ab0aac0
--- /dev/null
+++ b/src/modules/modem/analog/ModemAM.cpp
@@ -0,0 +1,39 @@
+#include "ModemAM.h"
+
+ModemAM::ModemAM() : ModemAnalog() {
+    demodAM = ampmodem_create(0.5, 0.0, LIQUID_AMPMODEM_DSB, 0);
+    useSignalOutput(true);
+}
+
+ModemAM::~ModemAM() {
+    ampmodem_destroy(demodAM);
+}
+
+ModemBase *ModemAM::factory() {
+    return new ModemAM;
+}
+
+std::string ModemAM::getName() {
+    return "AM";
+}
+
+int ModemAM::getDefaultSampleRate() {
+    return 6000;
+}
+
+void ModemAM::demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut) {
+    ModemKitAnalog *amkit = (ModemKitAnalog *)kit;
+    
+    initOutputBuffers(amkit,input);
+    
+    if (!bufSize) {
+        input->decRefCount();
+        return;
+    }
+    
+	for (size_t i = 0; i < bufSize; i++) {
+		ampmodem_demodulate(demodAM, input->data[i], &demodOutputData[i]);
+	}
+    
+    buildAudioOutput(amkit,audioOut,true);
+}
diff --git a/src/modules/modem/analog/ModemAM.h b/src/modules/modem/analog/ModemAM.h
new file mode 100644
index 0000000..9da1ca2
--- /dev/null
+++ b/src/modules/modem/analog/ModemAM.h
@@ -0,0 +1,20 @@
+#pragma once
+#include "Modem.h"
+#include "ModemAnalog.h"
+
+class ModemAM : public ModemAnalog {
+public:
+    ModemAM();
+    ~ModemAM();
+    
+    std::string getName();
+    
+    static ModemBase *factory();
+    
+    int getDefaultSampleRate();
+
+    void demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut);
+    
+private:
+    ampmodem demodAM;
+};
\ No newline at end of file
diff --git a/src/modules/modem/analog/ModemDSB.cpp b/src/modules/modem/analog/ModemDSB.cpp
new file mode 100644
index 0000000..2cfe35c
--- /dev/null
+++ b/src/modules/modem/analog/ModemDSB.cpp
@@ -0,0 +1,39 @@
+#include "ModemDSB.h"
+
+ModemDSB::ModemDSB() : ModemAnalog() {
+    demodAM_DSB = ampmodem_create(0.5, 0.0, LIQUID_AMPMODEM_DSB, 1);
+    useSignalOutput(true);
+}
+
+ModemDSB::~ModemDSB() {
+    ampmodem_destroy(demodAM_DSB);
+}
+
+ModemBase *ModemDSB::factory() {
+    return new ModemDSB;
+}
+
+std::string ModemDSB::getName() {
+    return "DSB";
+}
+
+int ModemDSB::getDefaultSampleRate() {
+    return 5400;
+}
+
+void ModemDSB::demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut) {
+    ModemKitAnalog *amkit = (ModemKitAnalog *)kit;
+
+    initOutputBuffers(amkit, input);
+    
+    if (!bufSize) {
+        input->decRefCount();
+        return;
+    }
+    
+	for (size_t i = 0; i < bufSize; i++) {
+		ampmodem_demodulate(demodAM_DSB, input->data[i], &demodOutputData[i]);
+	}
+    
+    buildAudioOutput(amkit, audioOut, true);
+}
diff --git a/src/modules/modem/analog/ModemDSB.h b/src/modules/modem/analog/ModemDSB.h
new file mode 100644
index 0000000..4a80784
--- /dev/null
+++ b/src/modules/modem/analog/ModemDSB.h
@@ -0,0 +1,20 @@
+#pragma once
+#include "Modem.h"
+#include "ModemAnalog.h"
+
+class ModemDSB : public ModemAnalog {
+public:
+    ModemDSB();
+    ~ModemDSB();
+    
+    std::string getName();
+    
+    static ModemBase *factory();
+    
+    int getDefaultSampleRate();
+
+    void demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut);
+    
+private:
+    ampmodem demodAM_DSB;
+};
\ No newline at end of file
diff --git a/src/modules/modem/analog/ModemFM.cpp b/src/modules/modem/analog/ModemFM.cpp
new file mode 100644
index 0000000..933e6cf
--- /dev/null
+++ b/src/modules/modem/analog/ModemFM.cpp
@@ -0,0 +1,36 @@
+#include "ModemFM.h"
+
+ModemFM::ModemFM() : ModemAnalog() {
+    demodFM = freqdem_create(0.5);
+}
+
+ModemFM::~ModemFM() {
+    freqdem_destroy(demodFM);
+}
+
+ModemBase *ModemFM::factory() {
+    return new ModemFM;
+}
+
+std::string ModemFM::getName() {
+    return "FM";
+}
+
+int ModemFM::getDefaultSampleRate() {
+    return 200000;
+}
+
+void ModemFM::demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut) {
+    ModemKitAnalog *fmkit = (ModemKitAnalog *)kit;
+    
+    initOutputBuffers(fmkit, input);
+    
+    if (!bufSize) {
+        input->decRefCount();
+        return;
+    }
+    
+    freqdem_demodulate_block(demodFM, &input->data[0], bufSize, &demodOutputData[0]);
+
+    buildAudioOutput(fmkit, audioOut, false);
+}
diff --git a/src/modules/modem/analog/ModemFM.h b/src/modules/modem/analog/ModemFM.h
new file mode 100644
index 0000000..a62e729
--- /dev/null
+++ b/src/modules/modem/analog/ModemFM.h
@@ -0,0 +1,20 @@
+#pragma once
+#include "Modem.h"
+#include "ModemAnalog.h"
+
+class ModemFM : public ModemAnalog {
+public:
+    ModemFM();
+    ~ModemFM();
+    
+    std::string getName();
+    
+    static ModemBase *factory();
+
+    int getDefaultSampleRate();
+
+    void demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut);
+
+private:
+    freqdem demodFM;
+};
\ No newline at end of file
diff --git a/src/modules/modem/analog/ModemFMStereo.cpp b/src/modules/modem/analog/ModemFMStereo.cpp
new file mode 100644
index 0000000..f98fa21
--- /dev/null
+++ b/src/modules/modem/analog/ModemFMStereo.cpp
@@ -0,0 +1,283 @@
+#include "ModemFMStereo.h"
+
+ModemFMStereo::ModemFMStereo() {
+    demodFM = freqdem_create(0.5);
+    _demph = 75;
+}
+
+ModemFMStereo::~ModemFMStereo() {
+    freqdem_destroy(demodFM);
+}
+
+std::string ModemFMStereo::getType() {
+    return "analog";
+}
+
+std::string ModemFMStereo::getName() {
+    return "FMS";
+}
+
+ModemBase *ModemFMStereo::factory() {
+    return new ModemFMStereo;
+}
+
+int ModemFMStereo::checkSampleRate(long long sampleRate, int /* audioSampleRate */) {
+    if (sampleRate < 100000) {
+        return 100000;
+    } else if (sampleRate < 1500) {
+        return 1500;
+    } else {
+        return sampleRate;
+    }
+}
+
+int ModemFMStereo::getDefaultSampleRate() {
+    return 200000;
+}
+
+ModemArgInfoList ModemFMStereo::getSettings() {
+    ModemArgInfoList args;
+    
+    ModemArgInfo demphArg;
+    demphArg.key = "demph";
+    demphArg.name = "De-emphasis";
+    demphArg.value = std::to_string(_demph);
+    demphArg.description = "FM Stereo De-Emphasis, typically 75us in US/Canada, 50us elsewhere.";
+    
+    demphArg.type = ModemArgInfo::STRING;
+    
+    std::vector<std::string> demphOptNames;
+    demphOptNames.push_back("None");
+    demphOptNames.push_back("10us");
+    demphOptNames.push_back("25us");
+    demphOptNames.push_back("32us");
+    demphOptNames.push_back("50us");
+    demphOptNames.push_back("75us");
+    demphArg.optionNames = demphOptNames;
+    
+    std::vector<std::string> demphOpts;
+    demphOpts.push_back("0");
+    demphOpts.push_back("10");
+    demphOpts.push_back("25");
+    demphOpts.push_back("32");
+    demphOpts.push_back("50");
+    demphOpts.push_back("75");
+    demphArg.options = demphOpts;
+
+    args.push_back(demphArg);
+    
+    return args;
+}
+
+void ModemFMStereo::writeSetting(std::string setting, std::string value) {
+    if (setting == "demph") {
+        _demph = std::stoi(value);
+        rebuildKit();
+    }
+}
+
+std::string ModemFMStereo::readSetting(std::string setting) {
+    if (setting == "demph") {
+        return std::to_string(_demph);
+    }
+    return "";
+}
+
+ModemKit *ModemFMStereo::buildKit(long long sampleRate, int audioSampleRate) {
+    ModemKitFMStereo *kit = new ModemKitFMStereo;
+    
+    kit->audioResampleRatio = double(audioSampleRate) / double(sampleRate);
+    kit->sampleRate = sampleRate;
+    kit->audioSampleRate = audioSampleRate;
+   
+    float As = 60.0f;         // stop-band attenuation [dB]
+    
+    kit->audioResampler = msresamp_rrrf_create(kit->audioResampleRatio, As);
+    kit->stereoResampler = msresamp_rrrf_create(kit->audioResampleRatio, As);
+    
+    // Stereo filters / shifters
+    double firStereoCutoff = 16000.0 / double(audioSampleRate);
+    // filter transition
+    float ft = 1000.0f / double(audioSampleRate);
+    // fractional timing offset
+    float mu = 0.0f;
+    
+    if (firStereoCutoff < 0) {
+        firStereoCutoff = 0;
+    }
+    
+    if (firStereoCutoff > 0.5) {
+        firStereoCutoff = 0.5;
+    }
+    
+    unsigned int h_len = estimate_req_filter_len(ft, As);
+    float *h = new float[h_len];
+    liquid_firdes_kaiser(h_len, firStereoCutoff, As, mu, h);
+    
+    kit->firStereoLeft = firfilt_rrrf_create(h, h_len);
+    kit->firStereoRight = firfilt_rrrf_create(h, h_len);
+    
+    // stereo pilot filter
+    float bw = float(sampleRate);
+    if (bw < 100000.0) {
+        bw = 100000.0;
+    }
+    unsigned int order =   5;       // filter order
+    float        f0    =   ((float) 19000 / bw);
+    float        fc    =   ((float) 19500 / bw);
+    float        Ap    =   1.0f;
+    
+    kit->iirStereoPilot = iirfilt_crcf_create_prototype(LIQUID_IIRDES_CHEBY2, LIQUID_IIRDES_BANDPASS, LIQUID_IIRDES_SOS, order, fc, f0, Ap, As);
+    
+    kit->firStereoR2C = firhilbf_create(5, 60.0f);
+    kit->firStereoC2R = firhilbf_create(5, 60.0f);
+    
+    kit->stereoPilot = nco_crcf_create(LIQUID_VCO);
+    nco_crcf_reset(kit->stereoPilot);
+    nco_crcf_pll_set_bandwidth(kit->stereoPilot, 0.25f);
+    
+    kit->demph = _demph;
+    
+    if (_demph) {
+        float f = (1.0f / (2.0f * M_PI * double(_demph) * 1e-6));
+        float t = 1.0f / (2.0f * M_PI * f);
+        t = 1.0f / (2.0f * float(audioSampleRate) * tan(1.0f / (2.0f * float(audioSampleRate) * t)));
+        
+        float tb = (1.0f + 2.0f * t * float(audioSampleRate));
+        float b_demph[2] = { 1.0f / tb, 1.0f / tb };
+        float a_demph[2] = { 1.0f, (1.0f - 2.0f * t * float(audioSampleRate)) / tb };
+        
+        kit->iirDemphL = iirfilt_rrrf_create(b_demph, 2, a_demph, 2);
+        kit->iirDemphR = iirfilt_rrrf_create(b_demph, 2, a_demph, 2);
+    } else {
+        kit->iirDemphL = nullptr;
+        kit->iirDemphR = nullptr;
+        kit->demph = 0;
+    }
+    return kit;
+}
+
+void ModemFMStereo::disposeKit(ModemKit *kit) {
+    ModemKitFMStereo *fmkit = (ModemKitFMStereo *)kit;
+    
+    msresamp_rrrf_destroy(fmkit->audioResampler);
+    msresamp_rrrf_destroy(fmkit->stereoResampler);
+    firfilt_rrrf_destroy(fmkit->firStereoLeft);
+    firfilt_rrrf_destroy(fmkit->firStereoRight);
+    firhilbf_destroy(fmkit->firStereoR2C);
+    firhilbf_destroy(fmkit->firStereoC2R);
+    nco_crcf_destroy(fmkit->stereoPilot);
+    if (fmkit->iirDemphR) { iirfilt_rrrf_destroy(fmkit->iirDemphR); }
+    if (fmkit->iirDemphL) { iirfilt_rrrf_destroy(fmkit->iirDemphL); }
+}
+
+
+void ModemFMStereo::demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut) {
+    ModemKitFMStereo *fmkit = (ModemKitFMStereo *)kit;
+    size_t bufSize = input->data.size();
+    liquid_float_complex u, v, w, x, y;
+    
+    double audio_resample_ratio = fmkit->audioResampleRatio;
+    
+    if (demodOutputData.size() != bufSize) {
+        if (demodOutputData.capacity() < bufSize) {
+            demodOutputData.reserve(bufSize);
+        }
+        demodOutputData.resize(bufSize);
+    }
+    
+    size_t audio_out_size = (size_t)ceil((double) (bufSize) * audio_resample_ratio) + 512;
+    
+    freqdem_demodulate_block(demodFM, &input->data[0], bufSize, &demodOutputData[0]);
+    
+    if (resampledOutputData.size() != audio_out_size) {
+        if (resampledOutputData.capacity() < audio_out_size) {
+            resampledOutputData.reserve(audio_out_size);
+        }
+        resampledOutputData.resize(audio_out_size);
+    }
+    
+    unsigned int numAudioWritten;
+    
+    msresamp_rrrf_execute(fmkit->audioResampler, &demodOutputData[0], bufSize, &resampledOutputData[0], &numAudioWritten);
+    
+    if (demodStereoData.size() != bufSize) {
+        if (demodStereoData.capacity() < bufSize) {
+            demodStereoData.reserve(bufSize);
+        }
+        demodStereoData.resize(bufSize);
+    }
+    
+    float phase_error = 0;
+    
+    for (size_t i = 0; i < bufSize; i++) {
+        // real -> complex
+        firhilbf_r2c_execute(fmkit->firStereoR2C, demodOutputData[i], &x);
+        
+        // 19khz pilot band-pass
+        iirfilt_crcf_execute(fmkit->iirStereoPilot, x, &v);
+        nco_crcf_cexpf(fmkit->stereoPilot, &w);
+        
+        w.imag = -w.imag; // conjf(w)
+        
+        // multiply u = v * conjf(w)
+        u.real = v.real * w.real - v.imag * w.imag;
+        u.imag = v.real * w.imag + v.imag * w.real;
+        
+        // cargf(u)
+        phase_error = atan2f(u.imag,u.real);
+        
+        // step pll
+        nco_crcf_pll_step(fmkit->stereoPilot, phase_error);
+        nco_crcf_step(fmkit->stereoPilot);
+        
+        // 38khz down-mix
+        nco_crcf_mix_down(fmkit->stereoPilot, x, &y);
+        nco_crcf_mix_down(fmkit->stereoPilot, y, &x);
+        
+        // complex -> real
+        firhilbf_c2r_execute(fmkit->firStereoC2R, x, &demodStereoData[i]);
+    }
+    
+    //            std::cout << "[PLL] phase error: " << phase_error;
+    //            std::cout << " freq:" << (((nco_crcf_get_frequency(stereoPilot) / (2.0 * M_PI)) * inp->sampleRate)) << std::endl;
+    
+    if (audio_out_size != resampledStereoData.size()) {
+        if (resampledStereoData.capacity() < audio_out_size) {
+            resampledStereoData.reserve(audio_out_size);
+        }
+        resampledStereoData.resize(audio_out_size);
+    }
+    
+    msresamp_rrrf_execute(fmkit->stereoResampler, &demodStereoData[0], bufSize, &resampledStereoData[0], &numAudioWritten);
+    
+    audioOut->channels = 2;
+    if (audioOut->data.capacity() < (numAudioWritten * 2)) {
+        audioOut->data.reserve(numAudioWritten * 2);
+    }
+    audioOut->data.resize(numAudioWritten * 2);
+    for (size_t i = 0; i < numAudioWritten; i++) {
+        float l, r;
+        float ld, rd;
+
+        if (fmkit->demph) {
+            iirfilt_rrrf_execute(fmkit->iirDemphL, 0.568f * (resampledOutputData[i] - (resampledStereoData[i])), &ld);
+            iirfilt_rrrf_execute(fmkit->iirDemphR, 0.568f * (resampledOutputData[i] + (resampledStereoData[i])), &rd);
+            
+            firfilt_rrrf_push(fmkit->firStereoLeft, ld);
+            firfilt_rrrf_execute(fmkit->firStereoLeft, &l);
+            
+            firfilt_rrrf_push(fmkit->firStereoRight, rd);
+            firfilt_rrrf_execute(fmkit->firStereoRight, &r);
+        } else {
+            firfilt_rrrf_push(fmkit->firStereoLeft, 0.568f * (resampledOutputData[i] - (resampledStereoData[i])));
+            firfilt_rrrf_execute(fmkit->firStereoLeft, &l);
+            
+            firfilt_rrrf_push(fmkit->firStereoRight, 0.568f * (resampledOutputData[i] + (resampledStereoData[i])));
+            firfilt_rrrf_execute(fmkit->firStereoRight, &r);
+        }
+        
+        audioOut->data[i * 2] = l;
+        audioOut->data[i * 2 + 1] = r;
+    }
+}
diff --git a/src/modules/modem/analog/ModemFMStereo.h b/src/modules/modem/analog/ModemFMStereo.h
new file mode 100644
index 0000000..ccba7dd
--- /dev/null
+++ b/src/modules/modem/analog/ModemFMStereo.h
@@ -0,0 +1,58 @@
+#pragma once
+#include "Modem.h"
+
+class ModemKitFMStereo: public ModemKit {
+public:
+    ModemKitFMStereo() : audioResampler(nullptr), stereoResampler(nullptr), audioResampleRatio(0), firStereoLeft(nullptr), firStereoRight(nullptr), iirStereoPilot(nullptr) {
+    }
+    
+    msresamp_rrrf audioResampler;
+    msresamp_rrrf stereoResampler;
+    double audioResampleRatio;
+    
+    firfilt_rrrf firStereoLeft;
+    firfilt_rrrf firStereoRight;
+    iirfilt_crcf iirStereoPilot;
+
+    int demph;
+    iirfilt_rrrf iirDemphR;
+    iirfilt_rrrf iirDemphL;
+
+    firhilbf firStereoR2C;
+    firhilbf firStereoC2R;
+    
+    nco_crcf stereoPilot;
+};
+
+
+class ModemFMStereo : public Modem {
+public:
+    ModemFMStereo();
+    ~ModemFMStereo();
+    
+    std::string getType();
+    std::string getName();
+    
+    static ModemBase *factory();
+    
+    int checkSampleRate(long long sampleRate, int audioSampleRate);
+    int getDefaultSampleRate();
+    
+    ModemArgInfoList getSettings();
+    void writeSetting(std::string setting, std::string value);
+    std::string readSetting(std::string setting);
+
+    ModemKit *buildKit(long long sampleRate, int audioSampleRate);
+    void disposeKit(ModemKit *kit);
+    
+    void demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut);
+    
+private:
+    std::vector<float> demodOutputData;
+    std::vector<float> demodStereoData;
+    std::vector<float> resampledOutputData;
+    std::vector<float> resampledStereoData;
+    freqdem demodFM;
+    
+    int _demph;
+};
\ No newline at end of file
diff --git a/src/modules/modem/analog/ModemIQ.cpp b/src/modules/modem/analog/ModemIQ.cpp
new file mode 100644
index 0000000..78bfddd
--- /dev/null
+++ b/src/modules/modem/analog/ModemIQ.cpp
@@ -0,0 +1,56 @@
+#include "ModemIQ.h"
+
+ModemIQ::ModemIQ() {
+    
+}
+
+std::string ModemIQ::getType() {
+    return "analog";
+}
+
+std::string ModemIQ::getName() {
+    return "I/Q";
+}
+
+ModemBase *ModemIQ::factory() {
+    return new ModemIQ;
+}
+
+ModemKit *ModemIQ::buildKit(long long sampleRate, int audioSampleRate) {
+    ModemKit *kit = new ModemKit;
+    kit->sampleRate = sampleRate;
+    kit->audioSampleRate = audioSampleRate;
+    return kit;
+}
+
+void ModemIQ::disposeKit(ModemKit *kit) {
+    delete kit;
+}
+
+int ModemIQ::checkSampleRate(long long /* sampleRate */, int audioSampleRate) {
+    return audioSampleRate;
+}
+
+int ModemIQ::getDefaultSampleRate() {
+    return 48000;
+}
+
+void ModemIQ::demodulate(ModemKit * /* kit */, ModemIQData *input, AudioThreadInput *audioOut) {
+    size_t bufSize = input->data.size();
+    
+    if (!bufSize) {
+        input->decRefCount();
+        return;
+    }
+    
+    audioOut->channels = 2;
+    if (audioOut->data.capacity() < (bufSize * 2)) {
+        audioOut->data.reserve(bufSize * 2);
+    }
+    
+    audioOut->data.resize(bufSize * 2);
+    for (size_t i = 0; i < bufSize; i++) {
+        audioOut->data[i * 2] = input->data[i].imag;
+        audioOut->data[i * 2 + 1] = input->data[i].real;
+    }
+}
diff --git a/src/modules/modem/analog/ModemIQ.h b/src/modules/modem/analog/ModemIQ.h
new file mode 100644
index 0000000..e3d8901
--- /dev/null
+++ b/src/modules/modem/analog/ModemIQ.h
@@ -0,0 +1,24 @@
+#pragma once
+#include "Modem.h"
+
+class ModemIQ : public Modem {
+public:
+    ModemIQ();
+
+    std::string getType();
+    std::string getName();
+    
+    static ModemBase *factory();
+
+    int checkSampleRate(long long sampleRate, int audioSampleRate);
+    int getDefaultSampleRate();
+
+    ModemKit *buildKit(long long sampleRate, int audioSampleRate);
+
+    void disposeKit(ModemKit *kit);
+    
+    void demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut);
+    
+private:
+    
+};
\ No newline at end of file
diff --git a/src/modules/modem/analog/ModemLSB.cpp b/src/modules/modem/analog/ModemLSB.cpp
new file mode 100644
index 0000000..2d704cf
--- /dev/null
+++ b/src/modules/modem/analog/ModemLSB.cpp
@@ -0,0 +1,77 @@
+#include "ModemLSB.h"
+
+ModemLSB::ModemLSB() : ModemAnalog() {
+    // half band filter used for side-band elimination
+    //    demodAM_LSB = ampmodem_create(0.25, 0.25, LIQUID_AMPMODEM_LSB, 1);
+#ifdef WIN32
+	ssbFilt = firfilt_crcf_create_kaiser(23, 0.25, 90.0, 0.01f);
+#else
+    ssbFilt = iirfilt_crcf_create_lowpass(6, 0.25);
+#endif
+	ssbShift = nco_crcf_create(LIQUID_NCO);
+    nco_crcf_set_frequency(ssbShift,  (2.0 * M_PI) * 0.25);
+    c2rFilt = firhilbf_create(5, 90.0);
+    useSignalOutput(true);
+}
+
+ModemBase *ModemLSB::factory() {
+    return new ModemLSB;
+}
+
+std::string ModemLSB::getName() {
+    return "LSB";
+}
+
+ModemLSB::~ModemLSB() {
+#ifdef WIN32
+    firfilt_crcf_destroy(ssbFilt);
+#else
+	iirfilt_crcf_destroy(ssbFilt);
+#endif
+	nco_crcf_destroy(ssbShift);
+    firhilbf_destroy(c2rFilt);
+    //    ampmodem_destroy(demodAM_LSB);
+}
+
+int ModemLSB::checkSampleRate(long long sampleRate, int /* audioSampleRate */) {
+    if (sampleRate < MIN_BANDWIDTH) {
+        return MIN_BANDWIDTH;
+    }
+    if (sampleRate % 2 == 0) {
+        return sampleRate;
+    }
+    return sampleRate+1;
+}
+
+int ModemLSB::getDefaultSampleRate() {
+    return 5400;
+}
+
+void ModemLSB::demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut) {
+    ModemKitAnalog *akit = (ModemKitAnalog *)kit;
+    
+    initOutputBuffers(akit,input);
+    
+    if (!bufSize) {
+        input->decRefCount();
+        return;
+    }
+    
+    liquid_float_complex x, y;
+    for (size_t i = 0; i < bufSize; i++) { // Reject upper band
+        nco_crcf_step(ssbShift);
+        nco_crcf_mix_up(ssbShift, input->data[i], &x);
+#ifdef WIN32
+		firfilt_crcf_push(ssbFilt, x);
+		firfilt_crcf_execute(ssbFilt, &y);
+#else
+		iirfilt_crcf_execute(ssbFilt, x, &y);
+#endif
+        nco_crcf_mix_down(ssbShift, y, &x);
+        // Liquid-DSP AMPModem SSB drifts with strong signals near baseband (like a carrier?)
+        // ampmodem_demodulate(demodAM_LSB, y, &demodOutputData[i]);
+        firhilbf_c2r_execute(c2rFilt, x, &demodOutputData[i]);
+    }
+    
+    buildAudioOutput(akit, audioOut, true);
+}
diff --git a/src/modules/modem/analog/ModemLSB.h b/src/modules/modem/analog/ModemLSB.h
new file mode 100644
index 0000000..e787dda
--- /dev/null
+++ b/src/modules/modem/analog/ModemLSB.h
@@ -0,0 +1,28 @@
+#pragma once
+#include "ModemAnalog.h"
+
+class ModemLSB : public ModemAnalog {
+public:
+    ModemLSB();
+    ~ModemLSB();
+    
+    std::string getName();
+    
+    static ModemBase *factory();
+    
+    int checkSampleRate(long long sampleRate, int audioSampleRate);
+    int getDefaultSampleRate();
+
+    void demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut);
+    
+private:
+#ifdef WIN32
+	firfilt_crcf ssbFilt;
+#else
+	iirfilt_crcf ssbFilt;
+#endif
+    firhilbf c2rFilt;
+    nco_crcf ssbShift;
+    //    firfilt_crcf ssbFilt;
+    //    ampmodem demodAM_LSB;
+};
\ No newline at end of file
diff --git a/src/modules/modem/analog/ModemNBFM.cpp b/src/modules/modem/analog/ModemNBFM.cpp
new file mode 100644
index 0000000..5dc77a6
--- /dev/null
+++ b/src/modules/modem/analog/ModemNBFM.cpp
@@ -0,0 +1,36 @@
+#include "ModemNBFM.h"
+
+ModemNBFM::ModemNBFM() : ModemAnalog() {
+    demodFM = freqdem_create(0.5);
+}
+
+ModemNBFM::~ModemNBFM() {
+    freqdem_destroy(demodFM);
+}
+
+ModemBase *ModemNBFM::factory() {
+    return new ModemNBFM;
+}
+
+std::string ModemNBFM::getName() {
+    return "NBFM";
+}
+
+int ModemNBFM::getDefaultSampleRate() {
+    return 12500;
+}
+
+void ModemNBFM::demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut) {
+    ModemKitAnalog *fmkit = (ModemKitAnalog *)kit;
+    
+    initOutputBuffers(fmkit, input);
+    
+    if (!bufSize) {
+        input->decRefCount();
+        return;
+    }
+    
+    freqdem_demodulate_block(demodFM, &input->data[0], bufSize, &demodOutputData[0]);
+
+    buildAudioOutput(fmkit, audioOut, false);
+}
diff --git a/src/modules/modem/analog/ModemNBFM.h b/src/modules/modem/analog/ModemNBFM.h
new file mode 100644
index 0000000..7d15cdb
--- /dev/null
+++ b/src/modules/modem/analog/ModemNBFM.h
@@ -0,0 +1,20 @@
+#pragma once
+#include "Modem.h"
+#include "ModemAnalog.h"
+
+class ModemNBFM : public ModemAnalog {
+public:
+    ModemNBFM();
+    ~ModemNBFM();
+    
+    std::string getName();
+    
+    static ModemBase *factory();
+
+    int getDefaultSampleRate();
+
+    void demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut);
+
+private:
+    freqdem demodFM;
+};
\ No newline at end of file
diff --git a/src/modules/modem/analog/ModemUSB.cpp b/src/modules/modem/analog/ModemUSB.cpp
new file mode 100644
index 0000000..534974e
--- /dev/null
+++ b/src/modules/modem/analog/ModemUSB.cpp
@@ -0,0 +1,78 @@
+#include "ModemUSB.h"
+
+ModemUSB::ModemUSB() : ModemAnalog() {
+    // half band filter used for side-band elimination
+    //    demodAM_USB = ampmodem_create(0.25, -0.25, LIQUID_AMPMODEM_USB, 1);
+#ifdef WIN32
+	ssbFilt = firfilt_crcf_create_kaiser(23, 0.25, 90.0, 0.01f);
+#else
+	ssbFilt = iirfilt_crcf_create_lowpass(6, 0.25);
+#endif
+	ssbShift = nco_crcf_create(LIQUID_NCO);
+    nco_crcf_set_frequency(ssbShift,  (2.0f * M_PI) * 0.25f);
+    c2rFilt = firhilbf_create(5, 90.0);
+    useSignalOutput(true);
+}
+
+ModemBase *ModemUSB::factory() {
+    return new ModemUSB;
+}
+
+std::string ModemUSB::getName() {
+    return "USB";
+}
+
+ModemUSB::~ModemUSB() {
+#ifdef WIN32
+	firfilt_crcf_destroy(ssbFilt);
+#else
+	iirfilt_crcf_destroy(ssbFilt);
+#endif
+	nco_crcf_destroy(ssbShift);
+    firhilbf_destroy(c2rFilt);
+    //    ampmodem_destroy(demodAM_USB);
+}
+
+int ModemUSB::checkSampleRate(long long sampleRate, int /* audioSampleRate */) {
+    if (sampleRate < MIN_BANDWIDTH) {
+        return MIN_BANDWIDTH;
+    }
+    if (sampleRate % 2 == 0) {
+        return sampleRate;
+    }
+    return sampleRate+1;
+}
+
+int ModemUSB::getDefaultSampleRate() {
+    return 5400;
+}
+
+void ModemUSB::demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut) {
+    ModemKitAnalog *akit = (ModemKitAnalog *)kit;
+    
+    initOutputBuffers(akit,input);
+    
+    if (!bufSize) {
+        input->decRefCount();
+        return;
+    }
+    
+    liquid_float_complex x, y;
+    for (size_t i = 0; i < bufSize; i++) { // Reject lower band
+        nco_crcf_step(ssbShift);
+        nco_crcf_mix_down(ssbShift, input->data[i], &x);
+#ifdef WIN32
+		firfilt_crcf_push(ssbFilt, x);
+		firfilt_crcf_execute(ssbFilt, &y);
+#else
+		iirfilt_crcf_execute(ssbFilt, x, &y);
+#endif
+		nco_crcf_mix_up(ssbShift, y, &x);
+        // Liquid-DSP AMPModem SSB drifts with strong signals near baseband (like a carrier?)
+        // ampmodem_demodulate(demodAM_USB, y, &demodOutputData[i]);
+        firhilbf_c2r_execute(c2rFilt, x, &demodOutputData[i]);
+    }
+    
+    buildAudioOutput(akit, audioOut, true);
+}
+
diff --git a/src/modules/modem/analog/ModemUSB.h b/src/modules/modem/analog/ModemUSB.h
new file mode 100644
index 0000000..f9dec65
--- /dev/null
+++ b/src/modules/modem/analog/ModemUSB.h
@@ -0,0 +1,27 @@
+#pragma once
+#include "ModemAnalog.h"
+
+class ModemUSB : public ModemAnalog {
+public:
+    ModemUSB();
+    ~ModemUSB();
+    
+    std::string getName();
+    
+    static ModemBase *factory();
+    
+    int checkSampleRate(long long sampleRate, int audioSampleRate);
+    int getDefaultSampleRate();
+
+    void demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut);
+    
+private:
+#ifdef WIN32
+	firfilt_crcf ssbFilt;
+#else
+	iirfilt_crcf ssbFilt;
+#endif
+	firhilbf c2rFilt;
+    nco_crcf ssbShift;
+//    ampmodem demodAM_USB;
+};
\ No newline at end of file
diff --git a/src/modules/modem/digital/ModemAPSK.cpp b/src/modules/modem/digital/ModemAPSK.cpp
new file mode 100644
index 0000000..d67bd3a
--- /dev/null
+++ b/src/modules/modem/digital/ModemAPSK.cpp
@@ -0,0 +1,109 @@
+#include "ModemAPSK.h"
+
+ModemAPSK::ModemAPSK() : ModemDigital() {
+    demodAPSK4 = modem_create(LIQUID_MODEM_APSK4);
+    demodAPSK8 = modem_create(LIQUID_MODEM_APSK8);
+    demodAPSK16 = modem_create(LIQUID_MODEM_APSK16);
+    demodAPSK32 = modem_create(LIQUID_MODEM_APSK32);
+    demodAPSK64 = modem_create(LIQUID_MODEM_APSK64);
+    demodAPSK128 = modem_create(LIQUID_MODEM_APSK128);
+    demodAPSK256 = modem_create(LIQUID_MODEM_APSK256);
+    demodAPSK = demodAPSK4;
+    cons = 4;
+}
+
+ModemBase *ModemAPSK::factory() {
+    return new ModemAPSK;
+}
+
+ModemAPSK::~ModemAPSK() {
+    modem_destroy(demodAPSK4);
+    modem_destroy(demodAPSK8);
+    modem_destroy(demodAPSK16);
+    modem_destroy(demodAPSK32);
+    modem_destroy(demodAPSK64);
+    modem_destroy(demodAPSK128);
+    modem_destroy(demodAPSK256);
+}
+
+std::string ModemAPSK::getName() {
+    return "APSK";
+}
+
+ModemArgInfoList ModemAPSK::getSettings() {
+    ModemArgInfoList args;
+    
+    ModemArgInfo consArg;
+    consArg.key = "cons";
+    consArg.name = "Constellation";
+    consArg.description = "Modem Constellation Pattern";
+    consArg.value = std::to_string(cons);
+    consArg.type = ModemArgInfo::STRING;
+    std::vector<std::string> consOpts;
+    consOpts.push_back("4");
+    consOpts.push_back("8");
+    consOpts.push_back("16");
+    consOpts.push_back("32");
+    consOpts.push_back("64");
+    consOpts.push_back("128");
+    consOpts.push_back("256");
+    consArg.options = consOpts;
+    args.push_back(consArg);
+    
+    return args;
+}
+
+void ModemAPSK::writeSetting(std::string setting, std::string value) {
+    if (setting == "cons") {
+        int newCons = std::stoi(value);
+        updateDemodulatorCons(newCons);
+    }
+}
+
+std::string ModemAPSK::readSetting(std::string setting) {
+    if (setting == "cons") {
+        return std::to_string(cons);
+    }
+    return "";
+}
+
+void ModemAPSK::updateDemodulatorCons(int cons) {
+    this->cons = cons;
+    switch (cons) {
+        case 4:
+            demodAPSK = demodAPSK4;
+            break;
+        case 8:
+            demodAPSK = demodAPSK8;
+            break;
+        case 16:
+            demodAPSK = demodAPSK16;
+            break;
+        case 32:
+            demodAPSK = demodAPSK32;
+            break;
+        case 64:
+            demodAPSK = demodAPSK64;
+            break;
+        case 128:
+            demodAPSK = demodAPSK128;
+            break;
+        case 256:
+            demodAPSK = demodAPSK256;
+            break;
+    }
+}
+
+void ModemAPSK::demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut) {
+    ModemKitDigital *dkit = (ModemKitDigital *)kit;
+    
+    digitalStart(dkit, demodAPSK, input);
+    
+    for (size_t i = 0, bufSize = input->data.size(); i < bufSize; i++) {
+        modem_demodulate(demodAPSK, input->data[i], &demodOutputDataDigital[i]);
+    }
+    
+    updateDemodulatorLock(demodAPSK, 0.005f);
+    
+    digitalFinish(dkit, demodAPSK);
+}
diff --git a/src/modules/modem/digital/ModemAPSK.h b/src/modules/modem/digital/ModemAPSK.h
new file mode 100644
index 0000000..e3dabb5
--- /dev/null
+++ b/src/modules/modem/digital/ModemAPSK.h
@@ -0,0 +1,30 @@
+#pragma once
+#include "ModemDigital.h"
+
+class ModemAPSK : public ModemDigital {
+public:
+    ModemAPSK();
+    ~ModemAPSK();
+
+    std::string getName();
+    
+    static ModemBase *factory();
+
+    ModemArgInfoList getSettings();
+    void writeSetting(std::string setting, std::string value);
+    std::string readSetting(std::string setting);
+
+    void updateDemodulatorCons(int cons);
+    void demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut);
+    
+private:
+    int cons;
+    modem demodAPSK;
+    modem demodAPSK4;
+    modem demodAPSK8;
+    modem demodAPSK16;
+    modem demodAPSK32;
+    modem demodAPSK64;
+    modem demodAPSK128;
+    modem demodAPSK256;
+};
diff --git a/src/modules/modem/digital/ModemASK.cpp b/src/modules/modem/digital/ModemASK.cpp
new file mode 100644
index 0000000..b1e87c6
--- /dev/null
+++ b/src/modules/modem/digital/ModemASK.cpp
@@ -0,0 +1,113 @@
+#include "ModemASK.h"
+
+ModemASK::ModemASK() : ModemDigital()  {
+    demodASK2 = modem_create(LIQUID_MODEM_ASK2);
+    demodASK4 = modem_create(LIQUID_MODEM_ASK4);
+    demodASK8 = modem_create(LIQUID_MODEM_ASK8);
+    demodASK16 = modem_create(LIQUID_MODEM_ASK16);
+    demodASK32 = modem_create(LIQUID_MODEM_ASK32);
+    demodASK64 = modem_create(LIQUID_MODEM_ASK64);
+    demodASK128 = modem_create(LIQUID_MODEM_ASK128);
+    demodASK256 = modem_create(LIQUID_MODEM_ASK256);
+    demodASK = demodASK2;
+    cons = 2;
+}
+
+ModemBase *ModemASK::factory() {
+    return new ModemASK;
+}
+
+ModemASK::~ModemASK() {
+    modem_destroy(demodASK4);
+    modem_destroy(demodASK8);
+    modem_destroy(demodASK16);
+    modem_destroy(demodASK32);
+    modem_destroy(demodASK64);
+    modem_destroy(demodASK128);
+    modem_destroy(demodASK256);
+}
+
+std::string ModemASK::getName() {
+    return "ASK";
+}
+
+ModemArgInfoList ModemASK::getSettings() {
+    ModemArgInfoList args;
+    
+    ModemArgInfo consArg;
+    consArg.key = "cons";
+    consArg.name = "Constellation";
+    consArg.description = "Modem Constellation Pattern";
+    consArg.value = std::to_string(cons);
+    consArg.type = ModemArgInfo::STRING;
+    std::vector<std::string> consOpts;
+    consOpts.push_back("2");
+    consOpts.push_back("4");
+    consOpts.push_back("8");
+    consOpts.push_back("16");
+    consOpts.push_back("32");
+    consOpts.push_back("64");
+    consOpts.push_back("128");
+    consOpts.push_back("256");
+    consArg.options = consOpts;
+    args.push_back(consArg);
+    
+    return args;
+}
+
+void ModemASK::writeSetting(std::string setting, std::string value) {
+    if (setting == "cons") {
+        int newCons = std::stoi(value);
+        updateDemodulatorCons(newCons);
+    }
+}
+
+std::string ModemASK::readSetting(std::string setting) {
+    if (setting == "cons") {
+        return std::to_string(cons);
+    }
+    return "";
+}
+
+void ModemASK::updateDemodulatorCons(int cons) {
+    this->cons = cons;
+    switch (cons) {
+        case 2:
+            demodASK = demodASK2;
+            break;
+        case 4:
+            demodASK = demodASK4;
+            break;
+        case 8:
+            demodASK = demodASK8;
+            break;
+        case 16:
+            demodASK = demodASK16;
+            break;
+        case 32:
+            demodASK = demodASK32;
+            break;
+        case 64:
+            demodASK = demodASK64;
+            break;
+        case 128:
+            demodASK = demodASK128;
+            break;
+        case 256:
+            demodASK = demodASK256;
+            break;
+    }
+}
+
+void ModemASK::demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut) {
+    ModemKitDigital *dkit = (ModemKitDigital *)kit;
+    
+    digitalStart(dkit, demodASK, input);
+
+    for (size_t i = 0, bufSize = input->data.size(); i < bufSize; i++) {
+        modem_demodulate(demodASK, input->data[i], &demodOutputDataDigital[i]);
+    }
+    updateDemodulatorLock(demodASK, 0.005f);
+    
+    digitalFinish(dkit, demodASK);
+}
diff --git a/src/modules/modem/digital/ModemASK.h b/src/modules/modem/digital/ModemASK.h
new file mode 100644
index 0000000..a672d14
--- /dev/null
+++ b/src/modules/modem/digital/ModemASK.h
@@ -0,0 +1,31 @@
+#pragma once
+#include "ModemDigital.h"
+
+class ModemASK : public ModemDigital {
+public:
+    ModemASK();
+    ~ModemASK();
+    
+    std::string getName();
+    
+    static ModemBase *factory();
+    
+    ModemArgInfoList getSettings();
+    void writeSetting(std::string setting, std::string value);
+    std::string readSetting(std::string setting);
+    
+    void updateDemodulatorCons(int cons);
+    void demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut);
+    
+private:
+    int cons;
+    modem demodASK;
+    modem demodASK2;
+    modem demodASK4;
+    modem demodASK8;
+    modem demodASK16;
+    modem demodASK32;
+    modem demodASK64;
+    modem demodASK128;
+    modem demodASK256;
+};
diff --git a/src/modules/modem/digital/ModemBPSK.cpp b/src/modules/modem/digital/ModemBPSK.cpp
new file mode 100644
index 0000000..59cb4f1
--- /dev/null
+++ b/src/modules/modem/digital/ModemBPSK.cpp
@@ -0,0 +1,29 @@
+#include "ModemBPSK.h"
+
+ModemBPSK::ModemBPSK() : ModemDigital()  {
+    demodBPSK = modem_create(LIQUID_MODEM_BPSK);
+}
+
+ModemBase *ModemBPSK::factory() {
+    return new ModemBPSK;
+}
+
+ModemBPSK::~ModemBPSK() {
+    modem_destroy(demodBPSK);
+}
+
+std::string ModemBPSK::getName() {
+    return "BPSK";
+}
+
+void ModemBPSK::demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut) {
+    ModemKitDigital *dkit = (ModemKitDigital *)kit;
+    digitalStart(dkit, demodBPSK, input);
+
+    for (size_t i = 0, bufSize=input->data.size(); i < bufSize; i++) {
+        modem_demodulate(demodBPSK, input->data[i], &demodOutputDataDigital[i]);
+    }
+    updateDemodulatorLock(demodBPSK, 0.005f);
+    
+    digitalFinish(dkit, demodBPSK);
+}
diff --git a/src/modules/modem/digital/ModemBPSK.h b/src/modules/modem/digital/ModemBPSK.h
new file mode 100644
index 0000000..f986ca4
--- /dev/null
+++ b/src/modules/modem/digital/ModemBPSK.h
@@ -0,0 +1,17 @@
+#pragma once
+#include "ModemDigital.h"
+
+class ModemBPSK : public ModemDigital {
+public:
+    ModemBPSK();
+    ~ModemBPSK();
+    
+    std::string getName();
+    
+    static ModemBase *factory();
+
+    void demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut);
+    
+private:
+    modem demodBPSK;
+};
diff --git a/src/modules/modem/digital/ModemDPSK.cpp b/src/modules/modem/digital/ModemDPSK.cpp
new file mode 100644
index 0000000..4927183
--- /dev/null
+++ b/src/modules/modem/digital/ModemDPSK.cpp
@@ -0,0 +1,114 @@
+#include "ModemDPSK.h"
+
+ModemDPSK::ModemDPSK() : ModemDigital() {
+    demodDPSK2 = modem_create(LIQUID_MODEM_DPSK2);
+    demodDPSK4 = modem_create(LIQUID_MODEM_DPSK4);
+    demodDPSK8 = modem_create(LIQUID_MODEM_DPSK8);
+    demodDPSK16 = modem_create(LIQUID_MODEM_DPSK16);
+    demodDPSK32 = modem_create(LIQUID_MODEM_DPSK32);
+    demodDPSK64 = modem_create(LIQUID_MODEM_DPSK64);
+    demodDPSK128 = modem_create(LIQUID_MODEM_DPSK128);
+    demodDPSK256 = modem_create(LIQUID_MODEM_DPSK256);
+    demodDPSK = demodDPSK2;
+    cons = 2;
+}
+
+ModemBase *ModemDPSK::factory() {
+    return new ModemDPSK;
+}
+
+std::string ModemDPSK::getName() {
+    return "DPSK";
+}
+
+ModemDPSK::~ModemDPSK() {
+    modem_destroy(demodDPSK2);
+    modem_destroy(demodDPSK4);
+    modem_destroy(demodDPSK8);
+    modem_destroy(demodDPSK16);
+    modem_destroy(demodDPSK32);
+    modem_destroy(demodDPSK64);
+    modem_destroy(demodDPSK128);
+    modem_destroy(demodDPSK256);
+}
+
+ModemArgInfoList ModemDPSK::getSettings() {
+    ModemArgInfoList args;
+    
+    ModemArgInfo consArg;
+    consArg.key = "cons";
+    consArg.name = "Constellation";
+    consArg.description = "Modem Constellation Pattern";
+    consArg.value = std::to_string(cons);
+    consArg.type = ModemArgInfo::STRING;
+    std::vector<std::string> consOpts;
+    consOpts.push_back("2");
+    consOpts.push_back("4");
+    consOpts.push_back("8");
+    consOpts.push_back("16");
+    consOpts.push_back("32");
+    consOpts.push_back("64");
+    consOpts.push_back("128");
+    consOpts.push_back("256");
+    consArg.options = consOpts;
+    args.push_back(consArg);
+    
+    return args;
+}
+
+void ModemDPSK::writeSetting(std::string setting, std::string value) {
+    if (setting == "cons") {
+        int newCons = std::stoi(value);
+        updateDemodulatorCons(newCons);
+    }
+}
+
+std::string ModemDPSK::readSetting(std::string setting) {
+    if (setting == "cons") {
+        return std::to_string(cons);
+    }
+    return "";
+}
+
+void ModemDPSK::updateDemodulatorCons(int cons) {
+    this->cons = cons;
+    switch (cons) {
+        case 2:
+            demodDPSK = demodDPSK2;
+            break;
+        case 4:
+            demodDPSK = demodDPSK4;
+            break;
+        case 8:
+            demodDPSK = demodDPSK8;
+            break;
+        case 16:
+            demodDPSK = demodDPSK16;
+            break;
+        case 32:
+            demodDPSK = demodDPSK32;
+            break;
+        case 64:
+            demodDPSK = demodDPSK64;
+            break;
+        case 128:
+            demodDPSK = demodDPSK128;
+            break;
+        case 256:
+            demodDPSK = demodDPSK256;
+            break;
+    }
+}
+
+void ModemDPSK::demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut) {
+    ModemKitDigital *dkit = (ModemKitDigital *)kit;
+   
+    digitalStart(dkit, demodDPSK, input);
+ 
+    for (size_t i = 0, bufSize = input->data.size(); i < bufSize; i++) {
+        modem_demodulate(demodDPSK, input->data[i], &demodOutputDataDigital[i]);
+    }
+    updateDemodulatorLock(demodDPSK, 0.005f);
+    
+    digitalFinish(dkit, demodDPSK);
+}
diff --git a/src/modules/modem/digital/ModemDPSK.h b/src/modules/modem/digital/ModemDPSK.h
new file mode 100644
index 0000000..ffeb5e3
--- /dev/null
+++ b/src/modules/modem/digital/ModemDPSK.h
@@ -0,0 +1,32 @@
+#pragma once
+#include "ModemDigital.h"
+
+class ModemDPSK : public ModemDigital {
+public:
+    ModemDPSK();
+    ~ModemDPSK();
+
+    std::string getName();
+    
+    static ModemBase *factory();
+    
+    ModemArgInfoList getSettings();
+    void writeSetting(std::string setting, std::string value);
+    std::string readSetting(std::string setting);
+    
+    void updateDemodulatorCons(int cons);
+    void demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut);
+    
+private:
+    int cons;
+    modem demodDPSK;
+    modem demodDPSK2;
+    modem demodDPSK4;
+    modem demodDPSK8;
+    modem demodDPSK16;
+    modem demodDPSK32;
+    modem demodDPSK64;
+    modem demodDPSK128;
+    modem demodDPSK256;
+};
+
diff --git a/src/modules/modem/digital/ModemFSK.cpp b/src/modules/modem/digital/ModemFSK.cpp
new file mode 100644
index 0000000..8db47f8
--- /dev/null
+++ b/src/modules/modem/digital/ModemFSK.cpp
@@ -0,0 +1,143 @@
+#include "ModemFSK.h"
+#include <iomanip>
+
+ModemFSK::ModemFSK() : ModemDigital()  {
+    // DMR defaults?
+    bps = 1;
+    sps = 9600;
+    bw = 0.45;
+    outStream << std::hex;
+}
+
+ModemBase *ModemFSK::factory() {
+    return new ModemFSK;
+}
+
+int ModemFSK::checkSampleRate(long long sampleRate, int audioSampleRate) {
+    float minSps = pow(2.0,bps);
+    float nextSps = (float(sampleRate) / float(sps));
+    if (nextSps < minSps) {
+        return 2 * bps * sps;
+    } else {
+        return sampleRate;
+    }
+}
+
+int ModemFSK::getDefaultSampleRate() {
+    return 19200;
+}
+
+ModemArgInfoList ModemFSK::getSettings() {
+    ModemArgInfoList args;
+    
+    ModemArgInfo bpsArg;
+    bpsArg.key = "bps";
+    bpsArg.name = "Bits/symbol";
+    bpsArg.value = std::to_string(bps);
+    bpsArg.description = "Modem bits-per-symbol";
+    bpsArg.type = ModemArgInfo::STRING;
+    bpsArg.units = "bits";
+    
+    std::vector<std::string> bpsOpts;
+    bpsOpts.push_back("1");
+    bpsOpts.push_back("2");
+    bpsOpts.push_back("4");
+    bpsOpts.push_back("8");
+    bpsOpts.push_back("16");
+    bpsArg.options = bpsOpts;
+    
+    args.push_back(bpsArg);
+    
+    ModemArgInfo spsArg;
+    spsArg.key = "sps";
+    spsArg.name = "Symbols/second";
+    spsArg.value = std::to_string(sps);
+    spsArg.description = "Modem symbols-per-second";
+    spsArg.type = ModemArgInfo::INT;
+    spsArg.range = ModemRange(10,115200);
+    std::vector<std::string> spsOpts;
+    
+    args.push_back(spsArg);
+    
+    ModemArgInfo bwArg;
+    bwArg.key = "bw";
+    bwArg.name = "Signal bandwidth";
+    bwArg.value = std::to_string(bw);
+    bwArg.description = "Total signal bandwidth";
+    bwArg.type = ModemArgInfo::FLOAT;
+    bwArg.range = ModemRange(0.1,0.49);
+    args.push_back(bwArg);
+
+    return args;
+}
+
+void ModemFSK::writeSetting(std::string setting, std::string value) {
+    if (setting == "bps") {
+        bps = std::stoi(value);
+        rebuildKit();
+    } else if (setting == "sps") {
+        sps = std::stoi(value);
+        rebuildKit();
+    }  else if (setting == "bw") {
+        bw = std::stof(value);
+        rebuildKit();
+    }
+}
+
+std::string ModemFSK::readSetting(std::string setting) {
+    if (setting == "bps") {
+        return std::to_string(bps);
+    } else if (setting == "sps") {
+        return std::to_string(sps);
+    } else if (setting == "bw") {
+        return std::to_string(bw);
+    }
+    return "";
+}
+
+ModemKit *ModemFSK::buildKit(long long sampleRate, int audioSampleRate) {
+    ModemKitFSK *dkit = new ModemKitFSK;
+    dkit->m           = bps;
+    dkit->k           = sampleRate / sps;
+    dkit->bw          = bw;
+
+    dkit->demodFSK = fskdem_create(dkit->m, dkit->k, dkit->bw);
+
+    dkit->sampleRate = sampleRate;
+    dkit->audioSampleRate = audioSampleRate;
+ 
+    return dkit;
+}
+
+void ModemFSK::disposeKit(ModemKit *kit) {
+    ModemKitFSK *dkit = (ModemKitFSK *)kit;
+    
+    fskdem_destroy(dkit->demodFSK);
+    
+    delete dkit;
+}
+
+std::string ModemFSK::getName() {
+    return "FSK";
+}
+
+ModemFSK::~ModemFSK() {
+
+}
+
+void ModemFSK::demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut) {
+    ModemKitFSK *dkit = (ModemKitFSK *)kit;
+    
+    digitalStart(dkit, nullptr, input);
+
+    dkit->inputBuffer.insert(dkit->inputBuffer.end(),input->data.begin(),input->data.end());
+
+    while (dkit->inputBuffer.size() >= dkit->k) {
+        outStream << fskdem_demodulate(dkit->demodFSK, &dkit->inputBuffer[0]);
+        
+//        float err = fskdem_get_frequency_error(dkit->demodFSK);
+        dkit->inputBuffer.erase(dkit->inputBuffer.begin(),dkit->inputBuffer.begin()+dkit->k);
+    }
+    
+    digitalFinish(dkit, nullptr);
+}
\ No newline at end of file
diff --git a/src/modules/modem/digital/ModemFSK.h b/src/modules/modem/digital/ModemFSK.h
new file mode 100644
index 0000000..e8cbcce
--- /dev/null
+++ b/src/modules/modem/digital/ModemFSK.h
@@ -0,0 +1,40 @@
+#pragma once
+#include "ModemDigital.h"
+#include <sstream>
+
+class ModemKitFSK : public ModemKitDigital {
+public:
+    unsigned int m, k;
+    float bw;
+    
+    fskdem demodFSK;
+    std::vector<liquid_float_complex> inputBuffer;
+};
+
+
+class ModemFSK : public ModemDigital {
+public:
+    ModemFSK();
+    ~ModemFSK();
+
+    std::string getName();
+    
+    static ModemBase *factory();
+    
+    int checkSampleRate(long long sampleRate, int audioSampleRate);
+    int getDefaultSampleRate();
+
+    ModemArgInfoList getSettings();
+    void writeSetting(std::string setting, std::string value);
+    std::string readSetting(std::string setting);
+    
+    ModemKit *buildKit(long long sampleRate, int audioSampleRate);
+    void disposeKit(ModemKit *kit);
+    
+    void demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut);
+
+private:
+    int sps, bps;
+    float bw;
+};
+
diff --git a/src/modules/modem/digital/ModemGMSK.cpp b/src/modules/modem/digital/ModemGMSK.cpp
new file mode 100644
index 0000000..e7ec132
--- /dev/null
+++ b/src/modules/modem/digital/ModemGMSK.cpp
@@ -0,0 +1,133 @@
+#include "ModemGMSK.h"
+#include <iomanip>
+
+ModemGMSK::ModemGMSK() : ModemDigital()  {
+    _sps = 4;
+    _fdelay = 3;
+    _ebf = 0.3;
+    outStream << std::hex;
+}
+
+ModemGMSK::~ModemGMSK() {
+    
+}
+
+std::string ModemGMSK::getName() {
+    return "GMSK";
+}
+
+ModemBase *ModemGMSK::factory() {
+    return new ModemGMSK;
+}
+
+int ModemGMSK::checkSampleRate(long long sampleRate, int audioSampleRate) {
+    if (sampleRate < MIN_BANDWIDTH) {
+        return MIN_BANDWIDTH;
+    }
+    return sampleRate;
+}
+
+int ModemGMSK::getDefaultSampleRate() {
+    return 19200;
+}
+
+ModemArgInfoList ModemGMSK::getSettings() {
+    ModemArgInfoList args;
+    
+    ModemArgInfo fdelayArg;
+    fdelayArg.key = "fdelay";
+    fdelayArg.name = "Filter delay";
+    fdelayArg.value = std::to_string(_fdelay);
+    fdelayArg.description = "Filter delay in samples";
+    fdelayArg.type = ModemArgInfo::INT;
+    fdelayArg.units = "samples";
+    fdelayArg.range = ModemRange(1,128);
+    args.push_back(fdelayArg);
+    
+    ModemArgInfo spsArg;
+    spsArg.key = "sps";
+    spsArg.name = "Samples / symbol";
+    spsArg.value = std::to_string(_sps);
+    spsArg.description = "Modem samples-per-symbol";
+    spsArg.type = ModemArgInfo::INT;
+    spsArg.units = "samples/symbol";
+    spsArg.range = ModemRange(2,512);
+    args.push_back(spsArg);
+    
+    ModemArgInfo ebfArg;
+    ebfArg.key = "ebf";
+    ebfArg.name = "Excess bandwidth";
+    ebfArg.value = std::to_string(_ebf);
+    ebfArg.description = "Modem excess bandwidth factor";
+    ebfArg.type = ModemArgInfo::FLOAT;
+    ebfArg.range = ModemRange(0.1,0.49);
+    args.push_back(ebfArg);
+
+    return args;
+}
+
+void ModemGMSK::writeSetting(std::string setting, std::string value) {
+    if (setting == "fdelay") {
+        _fdelay = std::stoi(value);
+        rebuildKit();
+    } else if (setting == "sps") {
+        _sps = std::stoi(value);
+        rebuildKit();
+    } else if (setting == "ebf") {
+        _ebf = std::stof(value);
+        rebuildKit();
+    }
+}
+
+std::string ModemGMSK::readSetting(std::string setting) {
+    if (setting == "fdelay") {
+        return std::to_string(_fdelay);
+    } else if (setting == "sps") {
+        return std::to_string(_sps);
+    } else if (setting == "ebf") {
+        return std::to_string(_ebf);
+    }
+    return "";
+}
+
+ModemKit *ModemGMSK::buildKit(long long sampleRate, int audioSampleRate) {
+    ModemKitGMSK *dkit = new ModemKitGMSK;
+    dkit->sps    = _sps;
+    dkit->fdelay = _fdelay;
+    dkit->ebf    = _ebf;
+
+    dkit->demodGMSK = gmskdem_create(dkit->sps, dkit->fdelay, dkit->ebf);
+
+    dkit->sampleRate = sampleRate;
+    dkit->audioSampleRate = audioSampleRate;
+ 
+    return dkit;
+}
+
+void ModemGMSK::disposeKit(ModemKit *kit) {
+    ModemKitGMSK *dkit = (ModemKitGMSK *)kit;
+    
+    gmskdem_destroy(dkit->demodGMSK);
+    
+    delete dkit;
+}
+
+void ModemGMSK::demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut) {
+    ModemKitGMSK *dkit = (ModemKitGMSK *)kit;
+    unsigned int sym_out;
+    
+    digitalStart(dkit, nullptr, input);
+
+    dkit->inputBuffer.insert(dkit->inputBuffer.end(),input->data.begin(),input->data.end());
+
+    int numProcessed = 0;
+    for (size_t i = 0, iMax = dkit->inputBuffer.size()/dkit->sps; i < iMax; i+= dkit->sps) {
+        gmskdem_demodulate(dkit->demodGMSK, &input->data[i],&sym_out);
+        outStream << sym_out;
+        numProcessed += dkit->sps;
+    }
+    
+    dkit->inputBuffer.erase(dkit->inputBuffer.begin(),dkit->inputBuffer.begin()+numProcessed);
+    
+    digitalFinish(dkit, nullptr);
+}
diff --git a/src/modules/modem/digital/ModemGMSK.h b/src/modules/modem/digital/ModemGMSK.h
new file mode 100644
index 0000000..714670b
--- /dev/null
+++ b/src/modules/modem/digital/ModemGMSK.h
@@ -0,0 +1,41 @@
+#pragma once
+#include "ModemDigital.h"
+#include <sstream>
+
+class ModemKitGMSK : public ModemKitDigital {
+public:
+    unsigned int fdelay, sps;
+    float ebf;
+    
+    gmskdem demodGMSK;
+    std::vector<liquid_float_complex> inputBuffer;
+};
+
+
+class ModemGMSK : public ModemDigital {
+public:
+    ModemGMSK();
+    ~ModemGMSK();
+
+    std::string getName();
+    
+    static ModemBase *factory();
+    
+    int checkSampleRate(long long sampleRate, int audioSampleRate);
+    int getDefaultSampleRate();
+
+    ModemArgInfoList getSettings();
+    void writeSetting(std::string setting, std::string value);
+    std::string readSetting(std::string setting);
+    
+    ModemKit *buildKit(long long sampleRate, int audioSampleRate);
+    void disposeKit(ModemKit *kit);
+    
+    void demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut);
+
+private:
+    int _sps;    // samples per symbol
+    int _fdelay;    // filter delay
+    float _ebf;
+};
+
diff --git a/src/modules/modem/digital/ModemOOK.cpp b/src/modules/modem/digital/ModemOOK.cpp
new file mode 100644
index 0000000..36f90ed
--- /dev/null
+++ b/src/modules/modem/digital/ModemOOK.cpp
@@ -0,0 +1,36 @@
+#include "ModemOOK.h"
+
+ModemOOK::ModemOOK() : ModemDigital()  {
+    demodOOK = modem_create(LIQUID_MODEM_OOK);
+}
+
+ModemOOK::~ModemOOK() {
+    modem_destroy(demodOOK);
+}
+
+std::string ModemOOK::getName() {
+    return "OOK";
+}
+
+ModemBase *ModemOOK::factory() {
+    return new ModemOOK;
+}
+
+int ModemOOK::checkSampleRate(long long sampleRate, int audioSampleRate) {
+    if (sampleRate < 100) {
+        return 100;
+    }
+    return sampleRate;
+}
+
+void ModemOOK::demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut) {
+    ModemKitDigital *dkit = (ModemKitDigital *)kit;
+    digitalStart(dkit, demodOOK, input);
+   
+    for (size_t i = 0, bufSize=input->data.size(); i < bufSize; i++) {
+        modem_demodulate(demodOOK, input->data[i], &demodOutputDataDigital[i]);
+    }
+    updateDemodulatorLock(demodOOK, 0.005f);
+    
+    digitalFinish(dkit, demodOOK);
+}
diff --git a/src/modules/modem/digital/ModemOOK.h b/src/modules/modem/digital/ModemOOK.h
new file mode 100644
index 0000000..d4396e3
--- /dev/null
+++ b/src/modules/modem/digital/ModemOOK.h
@@ -0,0 +1,19 @@
+#pragma once
+#include "ModemDigital.h"
+
+class ModemOOK : public ModemDigital {
+public:
+    ModemOOK();
+    ~ModemOOK();
+    
+    std::string getName();
+    
+    static ModemBase *factory();
+    
+    int checkSampleRate(long long sampleRate, int audioSampleRate);
+
+    void demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut);
+    
+private:
+    modem demodOOK;
+};
diff --git a/src/modules/modem/digital/ModemPSK.cpp b/src/modules/modem/digital/ModemPSK.cpp
new file mode 100644
index 0000000..c1e66bc
--- /dev/null
+++ b/src/modules/modem/digital/ModemPSK.cpp
@@ -0,0 +1,115 @@
+#include "ModemPSK.h"
+
+ModemPSK::ModemPSK() : ModemDigital()  {
+    demodPSK2 = modem_create(LIQUID_MODEM_PSK2);
+    demodPSK4 = modem_create(LIQUID_MODEM_PSK4);
+    demodPSK8 = modem_create(LIQUID_MODEM_PSK8);
+    demodPSK16 = modem_create(LIQUID_MODEM_PSK16);
+    demodPSK32 = modem_create(LIQUID_MODEM_PSK32);
+    demodPSK64 = modem_create(LIQUID_MODEM_PSK64);
+    demodPSK128 = modem_create(LIQUID_MODEM_PSK128);
+    demodPSK256 = modem_create(LIQUID_MODEM_PSK256);
+    demodPSK = demodPSK2;
+    cons = 2;
+}
+
+ModemBase *ModemPSK::factory() {
+    return new ModemPSK;
+}
+
+std::string ModemPSK::getName() {
+    return "PSK";
+}
+
+ModemPSK::~ModemPSK() {
+    modem_destroy(demodPSK2);
+    modem_destroy(demodPSK4);
+    modem_destroy(demodPSK8);
+    modem_destroy(demodPSK16);
+    modem_destroy(demodPSK32);
+    modem_destroy(demodPSK64);
+    modem_destroy(demodPSK128);
+    modem_destroy(demodPSK256);
+}
+
+
+ModemArgInfoList ModemPSK::getSettings() {
+    ModemArgInfoList args;
+    
+    ModemArgInfo consArg;
+    consArg.key = "cons";
+    consArg.name = "Constellation";
+    consArg.description = "Modem Constellation Pattern";
+    consArg.value = std::to_string(cons);
+    consArg.type = ModemArgInfo::STRING;
+    std::vector<std::string> consOpts;
+    consOpts.push_back("2");
+    consOpts.push_back("4");
+    consOpts.push_back("8");
+    consOpts.push_back("16");
+    consOpts.push_back("32");
+    consOpts.push_back("64");
+    consOpts.push_back("128");
+    consOpts.push_back("256");
+    consArg.options = consOpts;
+    args.push_back(consArg);
+    
+    return args;
+}
+
+void ModemPSK::writeSetting(std::string setting, std::string value) {
+    if (setting == "cons") {
+        int newCons = std::stoi(value);
+        updateDemodulatorCons(newCons);
+    }
+}
+
+std::string ModemPSK::readSetting(std::string setting) {
+    if (setting == "cons") {
+        return std::to_string(cons);
+    }
+    return "";
+}
+
+void ModemPSK::updateDemodulatorCons(int cons) {
+    this->cons = cons;
+    switch (cons) {
+        case 2:
+            demodPSK = demodPSK2;
+            break;
+        case 4:
+            demodPSK = demodPSK4;
+            break;
+        case 8:
+            demodPSK = demodPSK8;
+            break;
+        case 16:
+            demodPSK = demodPSK16;
+            break;
+        case 32:
+            demodPSK = demodPSK32;
+            break;
+        case 64:
+            demodPSK = demodPSK64;
+            break;
+        case 128:
+            demodPSK = demodPSK128;
+            break;
+        case 256:
+            demodPSK = demodPSK256;
+            break;
+    }
+}
+
+void ModemPSK::demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut) {
+    ModemKitDigital *dkit = (ModemKitDigital *)kit;
+
+    digitalStart(dkit, demodPSK, input);
+    
+    for (size_t i = 0, bufSize = input->data.size(); i < bufSize; i++) {
+        modem_demodulate(demodPSK, input->data[i], &demodOutputDataDigital[i]);
+    }
+    updateDemodulatorLock(demodPSK, 0.005f);
+    
+    digitalFinish(dkit, demodPSK);
+}
diff --git a/src/modules/modem/digital/ModemPSK.h b/src/modules/modem/digital/ModemPSK.h
new file mode 100644
index 0000000..7732342
--- /dev/null
+++ b/src/modules/modem/digital/ModemPSK.h
@@ -0,0 +1,32 @@
+#pragma once
+#include "ModemDigital.h"
+
+class ModemPSK : public ModemDigital {
+public:
+    ModemPSK();
+    ~ModemPSK();
+    
+    std::string getName();
+    
+    static ModemBase *factory();
+    
+    ModemArgInfoList getSettings();
+    void writeSetting(std::string setting, std::string value);
+    std::string readSetting(std::string setting);
+    
+    void updateDemodulatorCons(int cons);
+    void demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut);
+    
+private:
+    int cons;
+    modem demodPSK;
+    modem demodPSK2;
+    modem demodPSK4;
+    modem demodPSK8;
+    modem demodPSK16;
+    modem demodPSK32;
+    modem demodPSK64;
+    modem demodPSK128;
+    modem demodPSK256;
+};
+
diff --git a/src/modules/modem/digital/ModemQAM.cpp b/src/modules/modem/digital/ModemQAM.cpp
new file mode 100644
index 0000000..84bd203
--- /dev/null
+++ b/src/modules/modem/digital/ModemQAM.cpp
@@ -0,0 +1,107 @@
+#include "ModemQAM.h"
+
+ModemQAM::ModemQAM() : ModemDigital()  {
+    demodQAM4 = modem_create(LIQUID_MODEM_QAM4);
+    demodQAM8 = modem_create(LIQUID_MODEM_QAM8);
+    demodQAM16 = modem_create(LIQUID_MODEM_QAM16);
+    demodQAM32 = modem_create(LIQUID_MODEM_QAM32);
+    demodQAM64 = modem_create(LIQUID_MODEM_QAM64);
+    demodQAM128 = modem_create(LIQUID_MODEM_QAM128);
+    demodQAM256 = modem_create(LIQUID_MODEM_QAM256);
+    demodQAM = demodQAM4;
+    cons = 4;
+}
+
+ModemBase *ModemQAM::factory() {
+    return new ModemQAM;
+}
+
+std::string ModemQAM::getName() {
+    return "QAM";
+}
+
+ModemQAM::~ModemQAM() {
+    modem_destroy(demodQAM4);
+    modem_destroy(demodQAM8);
+    modem_destroy(demodQAM16);
+    modem_destroy(demodQAM32);
+    modem_destroy(demodQAM64);
+    modem_destroy(demodQAM128);
+    modem_destroy(demodQAM256);
+}
+
+ModemArgInfoList ModemQAM::getSettings() {
+    ModemArgInfoList args;
+    
+    ModemArgInfo consArg;
+    consArg.key = "cons";
+    consArg.name = "Constellation";
+    consArg.description = "Modem Constellation Pattern";
+    consArg.value = std::to_string(cons);
+    consArg.type = ModemArgInfo::STRING;
+    std::vector<std::string> consOpts;
+    consOpts.push_back("4");
+    consOpts.push_back("8");
+    consOpts.push_back("16");
+    consOpts.push_back("32");
+    consOpts.push_back("64");
+    consOpts.push_back("128");
+    consOpts.push_back("256");
+    consArg.options = consOpts;
+    args.push_back(consArg);
+    
+    return args;
+}
+
+void ModemQAM::writeSetting(std::string setting, std::string value) {
+    if (setting == "cons") {
+        int newCons = std::stoi(value);
+        updateDemodulatorCons(newCons);
+    }
+}
+
+std::string ModemQAM::readSetting(std::string setting) {
+    if (setting == "cons") {
+        return std::to_string(cons);
+    }
+    return "";
+}
+
+void ModemQAM::updateDemodulatorCons(int cons) {
+    this->cons = cons;
+    switch (cons) {
+        case 4:
+            demodQAM = demodQAM4;
+            break;
+        case 8:
+            demodQAM = demodQAM8;
+            break;
+        case 16:
+            demodQAM = demodQAM16;
+            break;
+        case 32:
+            demodQAM = demodQAM32;
+            break;
+        case 64:
+            demodQAM = demodQAM64;
+            break;
+        case 128:
+            demodQAM = demodQAM128;
+            break;
+        case 256:
+            demodQAM = demodQAM256;
+            break;
+    }
+}
+
+void ModemQAM::demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut) {
+    ModemKitDigital *dkit = (ModemKitDigital *)kit;
+    digitalStart(dkit, demodQAM, input);
+   
+    for (size_t i = 0, bufSize = input->data.size(); i < bufSize; i++) {
+        modem_demodulate(demodQAM, input->data[i], &demodOutputDataDigital[i]);
+    }
+    updateDemodulatorLock(demodQAM, 0.5f);
+    
+    digitalFinish(dkit, demodQAM);
+}
diff --git a/src/modules/modem/digital/ModemQAM.h b/src/modules/modem/digital/ModemQAM.h
new file mode 100644
index 0000000..21da247
--- /dev/null
+++ b/src/modules/modem/digital/ModemQAM.h
@@ -0,0 +1,32 @@
+#pragma once
+#include "ModemDigital.h"
+
+class ModemQAM : public ModemDigital {
+public:
+    ModemQAM();
+    ~ModemQAM();
+    
+    std::string getName();
+    
+    static ModemBase *factory();
+    
+    ModemArgInfoList getSettings();
+    void writeSetting(std::string setting, std::string value);
+    std::string readSetting(std::string setting);
+    
+    void updateDemodulatorCons(int cons);
+    void demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut);
+    
+private:
+    int cons;
+    modem demodQAM;
+    modem demodQAM4;
+    modem demodQAM8;
+    modem demodQAM16;
+    modem demodQAM32;
+    modem demodQAM64;
+    modem demodQAM128;
+    modem demodQAM256;
+};
+
+
diff --git a/src/modules/modem/digital/ModemQPSK.cpp b/src/modules/modem/digital/ModemQPSK.cpp
new file mode 100644
index 0000000..6164af5
--- /dev/null
+++ b/src/modules/modem/digital/ModemQPSK.cpp
@@ -0,0 +1,29 @@
+#include "ModemQPSK.h"
+
+ModemQPSK::ModemQPSK() : ModemDigital()  {
+    demodQPSK = modem_create(LIQUID_MODEM_QPSK);
+}
+
+ModemBase *ModemQPSK::factory() {
+    return new ModemQPSK;
+}
+
+ModemQPSK::~ModemQPSK() {
+    modem_destroy(demodQPSK);
+}
+
+std::string ModemQPSK::getName() {
+    return "QPSK";
+}
+
+void ModemQPSK::demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut) {
+    ModemKitDigital *dkit = (ModemKitDigital *)kit;
+    digitalStart(dkit, demodQPSK, input);
+
+    for (size_t i = 0, bufSize = input->data.size(); i < bufSize; i++) {
+        modem_demodulate(demodQPSK, input->data[i], &demodOutputDataDigital[i]);
+    }
+    updateDemodulatorLock(demodQPSK, 0.8f);
+    
+    digitalFinish(dkit, demodQPSK);
+}
diff --git a/src/modules/modem/digital/ModemQPSK.h b/src/modules/modem/digital/ModemQPSK.h
new file mode 100644
index 0000000..b532e38
--- /dev/null
+++ b/src/modules/modem/digital/ModemQPSK.h
@@ -0,0 +1,17 @@
+#pragma once
+#include "ModemDigital.h"
+
+class ModemQPSK : public ModemDigital {
+public:
+    ModemQPSK();
+    ~ModemQPSK();
+    
+    std::string getName();
+    
+    static ModemBase *factory();
+
+    void demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut);
+    
+private:
+    modem demodQPSK;
+};
diff --git a/src/modules/modem/digital/ModemSQAM.cpp b/src/modules/modem/digital/ModemSQAM.cpp
new file mode 100644
index 0000000..fee4b2f
--- /dev/null
+++ b/src/modules/modem/digital/ModemSQAM.cpp
@@ -0,0 +1,78 @@
+#include "ModemSQAM.h"
+
+ModemSQAM::ModemSQAM() : ModemDigital()  {
+    demodSQAM32 = modem_create(LIQUID_MODEM_SQAM32);
+    demodSQAM128 = modem_create(LIQUID_MODEM_SQAM128);
+    demodSQAM = demodSQAM32;
+    cons = 32;
+}
+
+ModemBase *ModemSQAM::factory() {
+    return new ModemSQAM;
+}
+
+ModemSQAM::~ModemSQAM() {
+    modem_destroy(demodSQAM32);
+    modem_destroy(demodSQAM128);
+}
+
+std::string ModemSQAM::getName() {
+    return "SQAM";
+}
+
+ModemArgInfoList ModemSQAM::getSettings() {
+    ModemArgInfoList args;
+    
+    ModemArgInfo consArg;
+    consArg.key = "cons";
+    consArg.name = "Constellation";
+    consArg.description = "Modem Constellation Pattern";
+    consArg.value = std::to_string(cons);
+    consArg.type = ModemArgInfo::STRING;
+    std::vector<std::string> consOpts;
+    consOpts.push_back("32");
+    consOpts.push_back("128");
+    consArg.options = consOpts;
+    args.push_back(consArg);
+    
+    return args;
+}
+
+void ModemSQAM::writeSetting(std::string setting, std::string value) {
+    if (setting == "cons") {
+        int newCons = std::stoi(value);
+        updateDemodulatorCons(newCons);
+    }
+}
+
+std::string ModemSQAM::readSetting(std::string setting) {
+    if (setting == "cons") {
+        return std::to_string(cons);
+    }
+    return "";
+}
+
+void ModemSQAM::updateDemodulatorCons(int cons) {
+    this->cons = cons;
+    switch (cons) {
+        case 32:
+            demodSQAM = demodSQAM32;
+            break;
+        case 128:
+            demodSQAM = demodSQAM128;
+            break;
+    }
+}
+
+void ModemSQAM::demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut) {
+    ModemKitDigital *dkit = (ModemKitDigital *)kit;
+
+    digitalStart(dkit, demodSQAM, input);
+    
+    for (size_t i = 0, bufSize = input->data.size(); i < bufSize; i++) {
+        modem_demodulate(demodSQAM, input->data[i], &demodOutputDataDigital[i]);
+    }
+    updateDemodulatorLock(demodSQAM, 0.005f);
+    
+    digitalFinish(dkit, demodSQAM);
+}
diff --git a/src/modules/modem/digital/ModemSQAM.h b/src/modules/modem/digital/ModemSQAM.h
new file mode 100644
index 0000000..46db613
--- /dev/null
+++ b/src/modules/modem/digital/ModemSQAM.h
@@ -0,0 +1,25 @@
+#pragma once
+#include "ModemDigital.h"
+
+class ModemSQAM : public ModemDigital {
+public:
+    ModemSQAM();
+    ~ModemSQAM();
+    
+    std::string getName();
+    
+    static ModemBase *factory();
+    
+    ModemArgInfoList getSettings();
+    void writeSetting(std::string setting, std::string value);
+    std::string readSetting(std::string setting);
+
+    void updateDemodulatorCons(int cons);
+    void demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut);
+    
+private:
+    int cons;
+    modem demodSQAM;
+    modem demodSQAM32;
+    modem demodSQAM128;
+};
diff --git a/src/modules/modem/digital/ModemST.cpp b/src/modules/modem/digital/ModemST.cpp
new file mode 100644
index 0000000..993cd03
--- /dev/null
+++ b/src/modules/modem/digital/ModemST.cpp
@@ -0,0 +1,29 @@
+#include "ModemST.h"
+
+ModemST::ModemST() : ModemDigital()  {
+    demodST = modem_create(LIQUID_MODEM_V29);
+}
+
+ModemBase *ModemST::factory() {
+    return new ModemST;
+}
+
+std::string ModemST::getName() {
+    return "ST";
+}
+
+ModemST::~ModemST() {
+    modem_destroy(demodST);
+}
+
+void ModemST::demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut) {
+    ModemKitDigital *dkit = (ModemKitDigital *)kit;
+    digitalStart(dkit, demodST, input);
+
+    for (size_t i = 0, bufSize = input->data.size(); i < bufSize; i++) {
+        modem_demodulate(demodST, input->data[i], &demodOutputDataDigital[i]);
+    }
+    updateDemodulatorLock(demodST, 0.005f);
+    
+    digitalFinish(dkit, demodST);
+}
diff --git a/src/modules/modem/digital/ModemST.h b/src/modules/modem/digital/ModemST.h
new file mode 100644
index 0000000..76b62ea
--- /dev/null
+++ b/src/modules/modem/digital/ModemST.h
@@ -0,0 +1,18 @@
+#pragma once
+#include "ModemDigital.h"
+
+class ModemST : public ModemDigital {
+public:
+    ModemST();
+    ~ModemST();
+    
+    std::string getName();
+    
+    static ModemBase *factory();
+    
+    void demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut);
+    
+private:
+    modem demodST;
+};
+
diff --git a/src/panel/MeterPanel.cpp b/src/panel/MeterPanel.cpp
new file mode 100644
index 0000000..54cbe12
--- /dev/null
+++ b/src/panel/MeterPanel.cpp
@@ -0,0 +1,209 @@
+
+#include "MeterPanel.h"
+#include "ColorTheme.h"
+
+
+MeterPanel::MeterPanel(std::string name, float low, float high, float current) {
+    this->name = name;
+    this->low = low;
+    this->high = high;
+    this->current = current;
+    
+    RGBA4f c1, c2;
+    
+    setFill(GLPanel::GLPANEL_FILL_NONE);
+    
+    bgPanel.setBorderPx(1);
+    bgPanel.setCoordinateSystem(GLPanel::GLPANEL_Y_UP);
+    bgPanel.setFill(GLPanel::GLPANEL_FILL_GRAD_X);
+    
+    levelPanel.setBorderPx(0);
+    levelPanel.setMarginPx(1);
+    
+    setPanelLevel(current, levelPanel);
+    levelPanel.setFill(GLPanel::GLPANEL_FILL_GRAD_BAR_X);
+    levelPanel.setBlend(GL_ONE, GL_ONE);
+    
+    bgPanel.addChild(&levelPanel);
+    
+    setPanelLevel(current, highlightPanel);
+    highlightPanel.setBorderPx(0);
+    highlightPanel.setMarginPx(1);
+    highlightPanel.setFill(GLPanel::GLPANEL_FILL_GRAD_BAR_X);
+    highlightPanel.setBlend(GL_ONE, GL_ONE);
+    highlightPanel.visible = false;
+    c1 = RGBA4f(0.3f,0.3f,0.3f,1.0f);
+    c2 = RGBA4f(0.65f,0.65f,0.65f,1.0f);;
+    highlightPanel.setFillColor(c1, c2);
+    
+    bgPanel.addChild(&highlightPanel);
+    
+    addChild(&bgPanel);
+    
+    labelPanel.setSize(1.0, 0.1);
+    labelPanel.setPosition(0.0, 1.0);
+    labelPanel.setText(name,GLFont::GLFONT_ALIGN_CENTER, GLFont::GLFONT_ALIGN_CENTER, true);
+    labelPanel.setFill(GLPanel::GLPANEL_FILL_NONE);
+    
+    addChild(&labelPanel);
+    
+    valuePanel.setSize(1.0, 0.1);
+    valuePanel.setPosition(0.0, -1.0);
+    
+    setValueLabel(std::to_string(int(current)));
+    valuePanel.setFill(GLPanel::GLPANEL_FILL_NONE);
+    
+    addChild(&valuePanel);
+}
+
+MeterPanel::~MeterPanel() {
+    
+}
+
+
+void MeterPanel::setName(std::string name_in) {
+    name = name_in;
+}
+
+std::string MeterPanel::getName() {
+    return name;
+}
+
+void MeterPanel::setRange(float low, float high) {
+    this->low = low;
+    this->high = high;
+}
+
+float MeterPanel::getLow() {
+    return this->low;
+}
+
+float MeterPanel::getHigh() {
+    return this->high;
+}
+
+void MeterPanel::setValue(float value) {
+    if (value > high) {
+        value = high;
+    }
+    if (value < low) {
+        value = low;
+    }
+    
+    current = value;
+    setValueLabel(std::to_string(int(current)));
+    setPanelLevel(value, levelPanel);
+}
+
+void MeterPanel::setHighlight(float value) {
+    if (value > high) {
+        value = high;
+    }
+    if (value < low) {
+        value = low;
+    }
+    
+    setPanelLevel(value, highlightPanel);
+}
+
+void MeterPanel::setHighlightVisible(bool vis) {
+    highlightPanel.visible = vis;
+}
+
+float MeterPanel::getValue() {
+    return current;
+}
+
+bool MeterPanel::isMeterHit(CubicVR::vec2 mousePoint) {
+    CubicVR::vec2 hitResult;
+    
+    if (bgPanel.hitTest(mousePoint, hitResult)) {
+        return true;
+    }
+    
+    return false;
+}
+
+float MeterPanel::getMeterHitValue(CubicVR::vec2 mousePoint, GLPanel &panel) {
+    CubicVR::vec2 hitResult;
+    
+    if (bgPanel.hitTest(mousePoint, hitResult)) {
+        float hitLevel = ((hitResult.y + 1.0) * 0.5);
+        
+        if (hitLevel < 0.0f) {
+            hitLevel = 0.0f;
+        }
+        if (hitLevel > 1.0f) {
+            hitLevel = 1.0f;
+        }
+        
+        return low + (hitLevel * (high-low));
+    } else {
+        return 0;
+    }
+}
+
+void MeterPanel::drawPanelContents() {
+    GLint vp[4];
+
+    glGetIntegerv( GL_VIEWPORT, vp);
+    
+    double viewHeight = vp[3];
+    
+    CubicVR::vec4 t = CubicVR::mat4::vec4_multiply(CubicVR::vec4(0,0.5,0,1), transform);
+    CubicVR::vec4 b = CubicVR::mat4::vec4_multiply(CubicVR::vec4(0,-0.5,0,1), transform);
+    
+    double hScale = t.y-b.y;
+    
+    viewHeight = viewHeight * hScale;
+    
+    double labelHeight = GLFont::getScaledPx(18, GLFont::getScaleFactor());
+    double labelPad = 8.0;
+    
+    double pScale = (1.0/viewHeight);
+    RGBA4f c1, c2;
+    
+    bgPanel.setSize(1.0f, 1.0 - pScale * (labelHeight + labelPad * 2.0));
+    
+    valuePanel.setPosition(0.0, (pScale * (labelHeight / 2.0 + labelPad) ) - 1.0);
+    valuePanel.setSize(1.0, pScale*labelHeight);
+    
+    labelPanel.setPosition(0.0, 1.0 - (pScale * (labelHeight / 2.0 + labelPad)));
+    labelPanel.setSize(1.0, pScale*labelHeight);
+
+    c1 = ThemeMgr::mgr.currentTheme->generalBackground;
+    c2 = ThemeMgr::mgr.currentTheme->generalBackground * 0.5;
+    c1.a = 1.0;
+    c2.a = 1.0;
+    bgPanel.setFillColor(c1, c2);
+
+    c1 = ThemeMgr::mgr.currentTheme->meterLevel * 0.5;
+    c2 = ThemeMgr::mgr.currentTheme->meterLevel;
+    c1.a = 1.0;
+    c2.a = 1.0;
+    levelPanel.setFillColor(c1, c2);
+    
+    drawChildren();
+}
+
+void MeterPanel::setValueLabel(std::string label) {
+    valuePanel.setText(label,
+                       GLFont::GLFONT_ALIGN_CENTER,
+                       GLFont::GLFONT_ALIGN_CENTER,
+                       true);
+    
+}
+
+void MeterPanel::setPanelLevel(float setValue, GLPanel &panel) {
+    float valueNorm = (setValue - low) / (high - low);
+    panel.setSize(1.0, valueNorm);
+    panel.setPosition(0.0, (-1.0+(valueNorm)));
+}
+
+bool MeterPanel::getChanged() {
+    return changed;
+}
+
+void MeterPanel::setChanged(bool changed) {
+    this->changed = changed;
+}
diff --git a/src/panel/MeterPanel.h b/src/panel/MeterPanel.h
new file mode 100644
index 0000000..75781d7
--- /dev/null
+++ b/src/panel/MeterPanel.h
@@ -0,0 +1,38 @@
+#pragma once
+
+#include "GLPanel.h"
+
+class MeterPanel : public GLPanel {
+    
+public:
+    MeterPanel(std::string name, float low, float high, float current);
+    ~MeterPanel();
+    void setName(std::string name_in);
+    std::string getName();
+    void setRange(float low, float high);
+    float getLow();
+    float getHigh();
+    void setValue(float value);
+    void setHighlight(float value);
+    void setHighlightVisible(bool vis);
+    float getValue();
+    bool isMeterHit(CubicVR::vec2 mousePoint);
+    float getMeterHitValue(CubicVR::vec2 mousePoint, GLPanel &panel);
+    void setChanged(bool changed);
+    bool getChanged();
+    
+protected:
+    void drawPanelContents();
+    void setValueLabel(std::string label);
+    void setPanelLevel(float setValue, GLPanel &panel);
+    
+private:
+    std::string name;
+    float low, high, current;
+    bool changed;
+    GLPanel bgPanel;
+    GLPanel levelPanel;
+    GLPanel highlightPanel;
+    GLTextPanel labelPanel;
+    GLTextPanel valuePanel;
+};
\ No newline at end of file
diff --git a/src/panel/ScopePanel.cpp b/src/panel/ScopePanel.cpp
new file mode 100644
index 0000000..f83e00c
--- /dev/null
+++ b/src/panel/ScopePanel.cpp
@@ -0,0 +1,112 @@
+#include "ScopePanel.h"
+#include "ColorTheme.h"
+
+ScopePanel::ScopePanel() : GLPanel(), scopeMode(SCOPE_MODE_Y) {
+    setFill(GLPanelFillType::GLPANEL_FILL_NONE);
+    bgPanel.setFill(GLPanelFillType::GLPANEL_FILL_GRAD_BAR_Y);
+    bgPanelStereo[0].setFill(GLPanelFillType::GLPANEL_FILL_GRAD_BAR_Y);
+    bgPanelStereo[0].setPosition(0, 0.5);
+    bgPanelStereo[0].setSize(1, 0.5);
+    bgPanelStereo[1].setFill(GLPanelFillType::GLPANEL_FILL_GRAD_BAR_Y);
+    bgPanelStereo[1].setPosition(0, -0.5);
+    bgPanelStereo[1].setSize(1, 0.5);
+}
+
+void ScopePanel::setMode(ScopeMode scopeMode) {
+    this->scopeMode = scopeMode;
+}
+
+ScopePanel::ScopeMode ScopePanel::getMode() {
+    return this->scopeMode;
+}
+
+void ScopePanel::setPoints(std::vector<float> &points) {
+    this->points.assign(points.begin(),points.end());
+}
+
+void ScopePanel::drawPanelContents() {
+    
+    if (scopeMode == SCOPE_MODE_Y) {
+        bgPanel.setFillColor(ThemeMgr::mgr.currentTheme->scopeBackground, ThemeMgr::mgr.currentTheme->scopeBackground * 2.0);
+        bgPanel.calcTransform(transform);
+        bgPanel.draw();
+        glLineWidth(1.0);
+        glEnable(GL_LINE_SMOOTH);
+        glLoadMatrixf(transform);
+        glColor3f(ThemeMgr::mgr.currentTheme->scopeLine.r * 0.35, ThemeMgr::mgr.currentTheme->scopeLine.g * 0.35,
+                  ThemeMgr::mgr.currentTheme->scopeLine.b * 0.35);
+        glBegin (GL_LINES);
+        glVertex2f(-1.0, 0.0);
+        glVertex2f(1.0, 0.0);
+        glEnd();
+    } else if (scopeMode == SCOPE_MODE_2Y)  {
+        bgPanelStereo[0].setFillColor(ThemeMgr::mgr.currentTheme->scopeBackground, ThemeMgr::mgr.currentTheme->scopeBackground * 2.0);
+        bgPanelStereo[1].setFillColor(ThemeMgr::mgr.currentTheme->scopeBackground, ThemeMgr::mgr.currentTheme->scopeBackground * 2.0);
+
+        bgPanelStereo[0].calcTransform(transform);
+        bgPanelStereo[0].draw();
+        bgPanelStereo[1].calcTransform(transform);
+        bgPanelStereo[1].draw();
+
+        glLineWidth(1.0);
+        glLoadMatrixf(transform);
+        glColor3f(ThemeMgr::mgr.currentTheme->scopeLine.r, ThemeMgr::mgr.currentTheme->scopeLine.g, ThemeMgr::mgr.currentTheme->scopeLine.b);
+        glEnable(GL_LINE_SMOOTH);
+        glBegin (GL_LINES);
+        glVertex2f(-1.0, 0.0);
+        glVertex2f(1.0, 0.0);
+        glColor3f(ThemeMgr::mgr.currentTheme->scopeLine.r * 0.35, ThemeMgr::mgr.currentTheme->scopeLine.g * 0.35,
+                  ThemeMgr::mgr.currentTheme->scopeLine.b * 0.35);
+        glVertex2f(-1.0, 0.5);
+        glVertex2f(1.0, 0.5);
+        glVertex2f(-1.0, -0.5);
+        glVertex2f(1.0, -0.5);
+        glEnd();
+
+    } else if (scopeMode == SCOPE_MODE_XY) {
+        RGBA4f bg1(ThemeMgr::mgr.currentTheme->scopeBackground), bg2(ThemeMgr::mgr.currentTheme->scopeBackground * 2.0);
+        bg1.a = 0.05f;
+        bg2.a = 0.05f;
+        bgPanel.setFillColor(bg1, bg2);
+        bgPanel.calcTransform(transform);
+        bgPanel.draw();
+        glLineWidth(1.0);
+        glEnable(GL_POINT_SMOOTH);
+        glPointSize(1.0);
+        glLoadMatrixf(transform);
+        glColor3f(ThemeMgr::mgr.currentTheme->scopeLine.r * 0.15, ThemeMgr::mgr.currentTheme->scopeLine.g * 0.15,
+                  ThemeMgr::mgr.currentTheme->scopeLine.b * 0.15);
+    }
+    
+    if (points.size()) {
+        glEnable (GL_BLEND);
+        glEnable (GL_LINE_SMOOTH);
+        glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
+        if (scopeMode == SCOPE_MODE_XY) {
+            glBlendFunc(GL_ONE, GL_ONE);
+        } else {
+            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+        }
+        glColor4f(ThemeMgr::mgr.currentTheme->scopeLine.r, ThemeMgr::mgr.currentTheme->scopeLine.g, ThemeMgr::mgr.currentTheme->scopeLine.b, 1.0);
+        glEnableClientState (GL_VERTEX_ARRAY);
+        glVertexPointer(2, GL_FLOAT, 0, &points[0]);
+        glLineWidth(1.5);
+        if (scopeMode == SCOPE_MODE_Y) {
+            glLoadMatrixf(bgPanel.transform);
+            glDrawArrays(GL_LINE_STRIP, 0, points.size() / 2);
+        } else if (scopeMode == SCOPE_MODE_2Y)  {
+            glLoadMatrixf(bgPanelStereo[0].transform);
+            glDrawArrays(GL_LINE_STRIP, 0, points.size() / 4);
+
+            glLoadMatrixf(bgPanelStereo[1].transform);
+            glDrawArrays(GL_LINE_STRIP, points.size() / 4, points.size() / 4);
+        } else if (scopeMode == SCOPE_MODE_XY) {
+            glLoadMatrixf(bgPanel.transform);
+            glDrawArrays(GL_POINTS, 0, points.size() / 2);
+        }
+        glLineWidth(1.0);
+        glDisableClientState(GL_VERTEX_ARRAY);
+        glDisable(GL_BLEND);
+    }
+}
+
diff --git a/src/panel/ScopePanel.h b/src/panel/ScopePanel.h
new file mode 100644
index 0000000..2ce9224
--- /dev/null
+++ b/src/panel/ScopePanel.h
@@ -0,0 +1,24 @@
+#pragma once
+
+#include "GLPanel.h"
+
+class ScopePanel : public GLPanel {
+    
+public:
+    typedef enum ScopeMode { SCOPE_MODE_Y, SCOPE_MODE_2Y, SCOPE_MODE_XY } ScopeMode;
+    
+    ScopePanel();
+    
+    void setMode(ScopeMode scopeMode);
+    ScopeMode getMode();
+    void setPoints(std::vector<float> &points);
+    
+protected:
+    void drawPanelContents();
+
+private:
+    std::vector<float> points;
+    ScopeMode scopeMode;
+    GLPanel bgPanel;
+    GLPanel bgPanelStereo[2];
+};
\ No newline at end of file
diff --git a/src/panel/SpectrumPanel.cpp b/src/panel/SpectrumPanel.cpp
new file mode 100644
index 0000000..60dfcd3
--- /dev/null
+++ b/src/panel/SpectrumPanel.cpp
@@ -0,0 +1,292 @@
+#include "SpectrumPanel.h"
+
+#include <sstream>
+#include <iostream>
+#include <iomanip>
+#include "ColorTheme.h"
+#include "CubicSDRDefs.h"
+
+SpectrumPanel::SpectrumPanel() {
+    floorValue = 0;
+    ceilValue = 1;
+    showDb = false;
+    fftSize = DEFAULT_FFT_SIZE;
+    bandwidth = DEFAULT_DEMOD_BW;
+    freq = 0;
+    
+    setFill(GLPANEL_FILL_GRAD_Y);
+    setFillColor(ThemeMgr::mgr.currentTheme->fftBackground * 2.0, ThemeMgr::mgr.currentTheme->fftBackground);
+    
+    dbPanelCeil.setMarginPx(0);
+    dbPanelCeil.setFill(GLPanel::GLPANEL_FILL_GRAD_X);
+    dbPanelCeil.setFillColor(RGBA4f(0.2f,0.2f,0.2f,5.0f), RGBA4f(0.2f,0.2f,0.2f,0.0f));
+    
+    dbPanelFloor.setMarginPx(0);
+    dbPanelFloor.setFill(GLPanel::GLPANEL_FILL_GRAD_X);
+    dbPanelFloor.setFillColor(RGBA4f(0.2f,0.2f,0.2f,5.0f), RGBA4f(0.2f,0.2f,0.2f,0.0f));
+}
+
+
+float SpectrumPanel::getFloorValue() {
+    return floorValue;
+}
+
+void SpectrumPanel::setFloorValue(float floorValue) {
+    this->floorValue = floorValue;
+}
+
+float SpectrumPanel::getCeilValue() {
+    return ceilValue;
+}
+
+void SpectrumPanel::setCeilValue(float ceilValue) {
+    this->ceilValue = ceilValue;
+}
+
+void SpectrumPanel::setFreq(long long freq) {
+    this->freq = freq;
+}
+
+long long SpectrumPanel::getFreq() {
+    return freq;
+}
+
+void SpectrumPanel::setBandwidth(long long bandwidth) {
+    this->bandwidth = bandwidth;
+}
+
+long long SpectrumPanel::getBandwidth() {
+    return bandwidth;
+}
+
+void SpectrumPanel::setFFTSize(int fftSize_in) {
+    this->fftSize = fftSize_in;
+}
+
+int SpectrumPanel::getFFTSize() {
+    return fftSize;
+}
+
+void SpectrumPanel::setShowDb(bool showDb) {
+    this->showDb = showDb;
+    if (showDb) {
+        addChild(&dbPanelCeil);
+        addChild(&dbPanelFloor);
+    } else {
+        removeChild(&dbPanelCeil);
+        removeChild(&dbPanelFloor);
+    }
+    
+}
+
+bool SpectrumPanel::getShowDb() {
+    return showDb;
+}
+
+
+void SpectrumPanel::setPoints(std::vector<float> &points) {
+    this->points.assign(points.begin(), points.end());
+}
+
+void SpectrumPanel::setPeakPoints(std::vector<float> &points) {
+    this->peak_points.assign(points.begin(), points.end());
+}
+
+
+void SpectrumPanel::drawPanelContents() {
+    glDisable(GL_TEXTURE_2D);
+    
+    glEnable(GL_BLEND);
+    glEnable(GL_LINE_SMOOTH);
+    glHint( GL_LINE_SMOOTH_HINT, GL_NICEST );
+
+    glLoadMatrixf(transform * (CubicVR::mat4::translate(-1.0f, -0.75f, 0.0f) * CubicVR::mat4::scale(2.0f, 1.5f, 1.0f)));
+
+    if (points.size()) {
+        glBlendFunc(GL_SRC_ALPHA, GL_ONE);
+        double range = ceilValue-floorValue;
+        double ranges[3][4] = { { 90.0, 5000.0, 10.0, 100.0 }, { 20.0, 150.0, 10.0, 10.0 }, { -20.0, 30.0, 10.0, 1.0 } };
+        
+        for (int i = 0; i < 3; i++) {
+            double p = 0;
+            double rangeMin = ranges[i][0];
+            double rangeMax = ranges[i][1];
+            double rangeTrans = ranges[i][2];
+            double rangeStep = ranges[i][3];
+            
+            if (range >= rangeMin && range <= rangeMax) {
+                double a = 1.0;
+                
+                if (range <= rangeMin+rangeTrans) {
+                    a *= (range-rangeMin)/rangeTrans;
+                }
+                if (range >= rangeMax-rangeTrans) {
+                    a *= (rangeTrans-(range-(rangeMax-rangeTrans)))/rangeTrans;
+                }
+                
+                glColor4f(0.12f, 0.12f, 0.12f, a);
+                glBegin(GL_LINES);
+                for (double l = floorValue; l<=ceilValue+rangeStep; l+=rangeStep) {
+                    p += rangeStep/range;
+                    glVertex2f(0,p);  glVertex2f(1,p);
+                }
+                glEnd();
+            }
+        }
+        
+        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+        glColor3f(ThemeMgr::mgr.currentTheme->fftLine.r, ThemeMgr::mgr.currentTheme->fftLine.g, ThemeMgr::mgr.currentTheme->fftLine.b);
+        glEnableClientState(GL_VERTEX_ARRAY);
+        glVertexPointer(2, GL_FLOAT, 0, &points[0]);
+        glDrawArrays(GL_LINE_STRIP, 0, points.size() / 2);
+        if (peak_points.size()) {
+            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+            glColor4f(0, 1.0, 0, 0.5);
+            glVertexPointer(2, GL_FLOAT, 0, &peak_points[0]);
+            glDrawArrays(GL_LINE_STRIP, 0, peak_points.size() / 2);
+        }
+        glDisableClientState(GL_VERTEX_ARRAY);
+    }
+  
+    GLint vp[4];
+    glGetIntegerv( GL_VIEWPORT, vp);
+    
+    float viewHeight = (float) vp[3];
+    float viewWidth = (float) vp[2];
+    glLoadMatrixf(transform);
+
+    
+    long long leftFreq = (double) freq - ((double) bandwidth / 2.0);
+    long long rightFreq = leftFreq + (double) bandwidth;
+
+    long long hzStep = 1000000;
+    
+    long double mhzStep = (100000.0 / (long double) (rightFreq - leftFreq)) * 2.0;
+    double mhzVisualStep = 0.1;
+
+    std::stringstream label;
+    label.precision(1);
+
+    double fontScale = GLFont::getScaleFactor();
+
+    if (mhzStep * 0.5 * viewWidth < 40 * fontScale) {
+        mhzStep = (250000.0 / (long double) (rightFreq - leftFreq)) * 2.0;
+        mhzVisualStep = 0.25;
+
+        if (mhzStep * 0.5 * viewWidth < 40 * fontScale) {
+            mhzStep = (500000.0 / (long double) (rightFreq - leftFreq)) * 2.0;
+            mhzVisualStep = 0.5;
+        }
+
+        if (mhzStep * 0.5 * viewWidth < 40 * fontScale) {
+            mhzStep = (1000000.0 / (long double) (rightFreq - leftFreq)) * 2.0;
+            mhzVisualStep = 1.0;
+        }
+        
+        if (mhzStep * 0.5 * viewWidth < 40 * fontScale) {
+            mhzStep = (2500000.0 / (long double) (rightFreq - leftFreq)) * 2.0;
+            mhzVisualStep = 2.5;
+        }
+        
+        if (mhzStep * 0.5 * viewWidth < 40 * fontScale) {
+            mhzStep = (5000000.0 / (long double) (rightFreq - leftFreq)) * 2.0;
+            mhzVisualStep = 5.0;
+        }
+
+        if (mhzStep * 0.5 * viewWidth < 40 * fontScale) {
+            mhzStep = (10000000.0 / (long double) (rightFreq - leftFreq)) * 2.0;
+            mhzVisualStep = 10.0;
+        }
+
+        if (mhzStep * 0.5 * viewWidth < 40 * fontScale) {
+            mhzStep = (50000000.0 / (long double) (rightFreq - leftFreq)) * 2.0;
+            mhzVisualStep = 50.0;
+        }
+    } else if (mhzStep * 0.5 * viewWidth > 350 * fontScale) {
+        mhzStep = (10000.0 / (long double) (rightFreq - leftFreq)) * 2.0;
+        mhzVisualStep = 0.01;
+        label.precision(2);
+    }
+    
+    long long firstMhz = (leftFreq / hzStep) * hzStep;
+    long double mhzStart = ((long double) (firstMhz - leftFreq) / (long double) (rightFreq - leftFreq)) * 2.0;
+    long double currentMhz = trunc(floor(firstMhz / (long double)1000000.0));
+    
+    
+    double hPos = 1.0 - (16.0 / viewHeight) *  GLFont::getScaleFactor();
+    double lMhzPos = 1.0 - (5.0 / viewHeight);
+    
+    int fontSize = 12;
+    
+    if (viewHeight > 135) {
+
+        fontSize = 16;
+        hPos = 1.0 - (18.0 / viewHeight) *  GLFont::getScaleFactor();
+    }
+    
+    GLFont::Drawer refDrawingFont = GLFont::getFont(fontSize, GLFont::getScaleFactor());
+
+    for (double m = -1.0 + mhzStart, mMax = 1.0 + ((mhzStart>0)?mhzStart:-mhzStart); m <= mMax; m += mhzStep) {
+        if (m < -1.0) {
+            currentMhz += mhzVisualStep;
+            continue;
+        }
+        if (m > 1.0) {
+            break;
+        }
+        label << std::fixed << currentMhz;
+        
+        double fractpart, intpart;
+        
+        fractpart = modf(currentMhz, &intpart);
+        
+        if (fractpart < 0.001) {
+            glLineWidth(4.0);
+            glColor3f(ThemeMgr::mgr.currentTheme->freqLine.r, ThemeMgr::mgr.currentTheme->freqLine.g, ThemeMgr::mgr.currentTheme->freqLine.b);
+        } else {
+            glLineWidth(1.0);
+            glColor3f(ThemeMgr::mgr.currentTheme->freqLine.r * 0.65, ThemeMgr::mgr.currentTheme->freqLine.g * 0.65,
+                      ThemeMgr::mgr.currentTheme->freqLine.b * 0.65);
+        }
+        
+        glDisable(GL_TEXTURE_2D);
+        glBegin(GL_LINES);
+        glVertex2f(m, lMhzPos);
+        glVertex2f(m, 1);
+        glEnd();
+        
+        glColor4f(ThemeMgr::mgr.currentTheme->text.r, ThemeMgr::mgr.currentTheme->text.g, ThemeMgr::mgr.currentTheme->text.b,1.0);
+        
+        refDrawingFont.drawString(label.str(), m, hPos, GLFont::GLFONT_ALIGN_CENTER, GLFont::GLFONT_ALIGN_CENTER, 0, 0, true);
+        
+        label.str(std::string());
+        
+        currentMhz += mhzVisualStep;
+    }
+    
+    glLineWidth(1.0);
+
+    if (showDb) {
+        float dbPanelWidth = (1.0 / viewWidth)*88.0 * GLFont::getScaleFactor();
+        float dbPanelHeight = (1.0/viewHeight)*14.0 * GLFont::getScaleFactor();
+        
+        
+        std::stringstream ssLabel("");
+        if (getCeilValue() != getFloorValue() && fftSize) {
+            ssLabel << std::fixed << std::setprecision(1) << (20.0 * log10(2.0*(getCeilValue())/(double)fftSize)) << "dB";
+        }
+        dbPanelCeil.setText(ssLabel.str(), GLFont::GLFONT_ALIGN_RIGHT);
+        dbPanelCeil.setSize(dbPanelWidth, dbPanelHeight);
+        dbPanelCeil.setPosition(-1.0 + dbPanelWidth, 1.0 - dbPanelHeight);
+
+        
+        ssLabel.str("");
+        if (getCeilValue() != getFloorValue() && fftSize) {
+            ssLabel <<  (20.0 * log10(2.0*(getFloorValue())/(double)fftSize)) << "dB";
+        }
+
+        dbPanelFloor.setText(ssLabel.str(), GLFont::GLFONT_ALIGN_RIGHT);
+        dbPanelFloor.setSize(dbPanelWidth, dbPanelHeight);
+        dbPanelFloor.setPosition(-1.0 + dbPanelWidth, - 1.0 + dbPanelHeight);
+    }
+}
diff --git a/src/panel/SpectrumPanel.h b/src/panel/SpectrumPanel.h
new file mode 100644
index 0000000..39aa3fe
--- /dev/null
+++ b/src/panel/SpectrumPanel.h
@@ -0,0 +1,44 @@
+#pragma once
+
+#include "GLPanel.h"
+
+class SpectrumPanel : public GLPanel {
+public:
+    SpectrumPanel();
+    
+    void setPoints(std::vector<float> &points);
+    void setPeakPoints(std::vector<float> &points);
+    
+    float getFloorValue();
+    void setFloorValue(float floorValue);
+
+    float getCeilValue();
+    void setCeilValue(float ceilValue);
+    
+    void setFreq(long long freq);
+    long long getFreq();
+    
+    void setBandwidth(long long bandwidth);
+    long long getBandwidth();
+
+    void setFFTSize(int fftSize_in);
+    int getFFTSize();
+
+    void setShowDb(bool showDb);
+    bool getShowDb();
+    
+protected:
+    void drawPanelContents();
+
+private:
+    float floorValue, ceilValue;
+    int fftSize;
+    long long freq;
+    long long bandwidth;
+    std::vector<float> points;
+    std::vector<float> peak_points;
+    
+    GLTextPanel dbPanelCeil;
+    GLTextPanel dbPanelFloor;
+    bool showDb;
+};
\ No newline at end of file
diff --git a/src/panel/WaterfallPanel.cpp b/src/panel/WaterfallPanel.cpp
new file mode 100644
index 0000000..eab8a7b
--- /dev/null
+++ b/src/panel/WaterfallPanel.cpp
@@ -0,0 +1,211 @@
+#include "WaterfallPanel.h"
+
+WaterfallPanel::WaterfallPanel() : GLPanel(), fft_size(0), waterfall_lines(0), waterfall_slice(NULL), activeTheme(NULL) {
+	setFillColor(RGBA4f(0,0,0));
+    for (int i = 0; i < 2; i++) {
+        waterfall[i] = 0;
+    }
+}
+
+void WaterfallPanel::setup(unsigned int fft_size_in, int num_waterfall_lines_in) {
+    waterfall_lines = num_waterfall_lines_in;
+    fft_size = fft_size_in;
+    lines_buffered.store(0);
+    
+    if (points.size() != fft_size) {
+        points.resize(fft_size);
+    }
+    
+    texInitialized.store(false);
+    bufferInitialized.store(false);
+}
+
+void WaterfallPanel::refreshTheme() {
+    glEnable (GL_TEXTURE_2D);
+    
+    for (int i = 0; i < 2; i++) {
+        glBindTexture(GL_TEXTURE_2D, waterfall[i]);
+        
+        glPixelTransferi(GL_MAP_COLOR, GL_TRUE);
+        glPixelMapfv(GL_PIXEL_MAP_I_TO_R, 256, &(ThemeMgr::mgr.currentTheme->waterfallGradient.getRed())[0]);
+        glPixelMapfv(GL_PIXEL_MAP_I_TO_G, 256, &(ThemeMgr::mgr.currentTheme->waterfallGradient.getGreen())[0]);
+        glPixelMapfv(GL_PIXEL_MAP_I_TO_B, 256, &(ThemeMgr::mgr.currentTheme->waterfallGradient.getBlue())[0]);
+    }
+}
+
+void WaterfallPanel::setPoints(std::vector<float> &points) {
+    size_t halfPts = points.size()/2;
+    if (halfPts == fft_size) {
+        for (unsigned int i = 0; i < fft_size; i++) {
+            this->points[i] = points[i*2+1];
+        }
+    } else {
+        this->points.assign(points.begin(), points.end());
+    }
+}
+
+void WaterfallPanel::step() {
+    unsigned int half_fft_size = fft_size / 2;
+
+    if (!bufferInitialized.load()) {
+        delete waterfall_slice;
+        waterfall_slice = new unsigned char[half_fft_size];
+        bufferInitialized.store(true);
+    }
+    
+    if (!texInitialized.load()) {
+        return;
+    }
+    
+    if (points.size() && points.size() == fft_size) {
+        for (int j = 0; j < 2; j++) {
+            for (int i = 0, iMax = half_fft_size; i < iMax; i++) {
+                float v = points[j * half_fft_size + i];
+                
+                float wv = v < 0 ? 0 : (v > 0.99 ? 0.99 : v);
+                
+                waterfall_slice[i] = (unsigned char) floor(wv * 255.0);
+            }
+            
+            unsigned int newBufSize = (half_fft_size*lines_buffered.load()+half_fft_size);
+            if (lineBuffer[j].size() < newBufSize) {
+                lineBuffer[j].resize(newBufSize);
+                rLineBuffer[j].resize(newBufSize);
+            }
+            memcpy(&(lineBuffer[j][half_fft_size*lines_buffered.load()]), waterfall_slice, sizeof(unsigned char) * half_fft_size);
+        }
+        lines_buffered++;
+    }
+}
+
+void WaterfallPanel::update() {
+    int half_fft_size = fft_size / 2;
+    
+    if (!bufferInitialized.load()) {
+        return;
+    }
+    
+    if (!texInitialized.load()) {
+        for (int i = 0; i < 2; i++) {
+            if (waterfall[i]) {
+                glDeleteTextures(1, &waterfall[i]);
+                waterfall[i] = 0;
+            }
+            
+            waterfall_ofs[i] = waterfall_lines - 1;
+        }
+
+        glGenTextures(2, waterfall);
+        
+        unsigned char *waterfall_tex;
+        
+        waterfall_tex = new unsigned char[half_fft_size * waterfall_lines];
+        memset(waterfall_tex, 0, half_fft_size * waterfall_lines);
+        
+        for (int i = 0; i < 2; i++) {
+            glBindTexture(GL_TEXTURE_2D, waterfall[i]);
+            glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+            
+            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+            
+            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, half_fft_size, waterfall_lines, 0, GL_COLOR_INDEX, GL_UNSIGNED_BYTE, (GLvoid *) waterfall_tex);
+        }
+        
+        delete[] waterfall_tex;
+
+        refreshTheme();
+
+        texInitialized.store(true);
+    }
+    
+    for (int i = 0, iMax = lines_buffered.load(); i < iMax; i++) {
+        for (int j = 0; j < 2; j++) {
+            memcpy(&(rLineBuffer[j][i*half_fft_size]),
+                   &(lineBuffer[j][((iMax-1)*half_fft_size)-(i*half_fft_size)]), sizeof(unsigned char) * half_fft_size);
+        }
+    }
+    
+    int run_ofs = 0;
+    while (lines_buffered.load()) {
+        int run_lines = lines_buffered.load();
+        if (run_lines > waterfall_ofs[0]) {
+            run_lines = waterfall_ofs[0];
+        }
+        for (int j = 0; j < 2; j++) {
+            glBindTexture(GL_TEXTURE_2D, waterfall[j]);
+            glTexSubImage2D(GL_TEXTURE_2D, 0, 0, waterfall_ofs[j]-run_lines, half_fft_size, run_lines,
+                            GL_COLOR_INDEX, GL_UNSIGNED_BYTE, (GLvoid *) &(rLineBuffer[j][run_ofs]));
+            
+            waterfall_ofs[j]-=run_lines;
+            
+            if (waterfall_ofs[j] == 0) {
+                waterfall_ofs[j] = waterfall_lines;
+            }
+        }
+        run_ofs += run_lines*half_fft_size;
+        lines_buffered.store(lines_buffered.load()-run_lines);
+    }
+}
+
+void WaterfallPanel::drawPanelContents() {
+    if (!texInitialized.load()) {
+        return;
+    }
+
+    int half_fft_size = fft_size / 2;
+    
+    glLoadMatrixf(transform);
+    
+    glEnable (GL_TEXTURE_2D);
+    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
+    
+    if (activeTheme != ThemeMgr::mgr.currentTheme) {
+        refreshTheme();
+        activeTheme = ThemeMgr::mgr.currentTheme;
+    }
+    glColor3f(1.0, 1.0, 1.0);
+    
+    GLint vp[4];
+    glGetIntegerv(GL_VIEWPORT, vp);
+    
+    float viewWidth = (float) vp[2];
+    
+    // some bias to prevent seams at odd scales
+    float half_pixel = 1.0 / viewWidth;
+    float half_texel = 1.0 / (float) half_fft_size;
+    float vtexel = 1.0 / (float) waterfall_lines;
+    float vofs = (float) (waterfall_ofs[0]) * vtexel;
+    
+    glBindTexture(GL_TEXTURE_2D, waterfall[0]);
+    glBegin (GL_QUADS);
+    glTexCoord2f(0.0 + half_texel, 1.0 + vofs);
+    glVertex3f(-1.0, -1.0, 0.0);
+    glTexCoord2f(1.0 - half_texel, 1.0 + vofs);
+    glVertex3f(0.0 + half_pixel, -1.0, 0.0);
+    glTexCoord2f(1.0 - half_texel, 0.0 + vofs);
+    glVertex3f(0.0 + half_pixel, 1.0, 0.0);
+    glTexCoord2f(0.0 + half_texel, 0.0 + vofs);
+    glVertex3f(-1.0, 1.0, 0.0);
+    glEnd();
+    
+    vofs = (float) (waterfall_ofs[1]) * vtexel;
+    glBindTexture(GL_TEXTURE_2D, waterfall[1]);
+    glBegin(GL_QUADS);
+    glTexCoord2f(0.0 + half_texel, 1.0 + vofs);
+    glVertex3f(0.0 - half_pixel, -1.0, 0.0);
+    glTexCoord2f(1.0 - half_texel, 1.0 + vofs);
+    glVertex3f(1.0, -1.0, 0.0);
+    glTexCoord2f(1.0 - half_texel, 0.0 + vofs);
+    glVertex3f(1.0, 1.0, 0.0);
+    glTexCoord2f(0.0 + half_texel, 0.0 + vofs);
+    glVertex3f(0.0 - half_pixel, 1.0, 0.0);
+    glEnd();
+    
+    glBindTexture(GL_TEXTURE_2D, 0);
+    
+    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
+    glDisable(GL_TEXTURE_2D);
+}
diff --git a/src/panel/WaterfallPanel.h b/src/panel/WaterfallPanel.h
new file mode 100644
index 0000000..451358a
--- /dev/null
+++ b/src/panel/WaterfallPanel.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include "GLPanel.h"
+#include <atomic>
+
+class WaterfallPanel : public GLPanel {
+public:
+    WaterfallPanel();
+    void setup(unsigned int fft_size_in, int num_waterfall_lines_in);
+    void refreshTheme();
+    void setPoints(std::vector<float> &points);
+    void step();
+    void update();
+    
+protected:
+    void drawPanelContents();
+    
+private:
+    std::vector<float> points;
+
+    GLuint waterfall[2];
+    int waterfall_ofs[2];
+    unsigned int fft_size;
+    int waterfall_lines;
+    unsigned char *waterfall_slice;
+    std::vector<unsigned char> lineBuffer[2];
+    std::vector<unsigned char> rLineBuffer[2];
+    std::atomic_int lines_buffered;
+    std::atomic_bool texInitialized, bufferInitialized;
+    
+    ColorTheme *activeTheme;
+};
diff --git a/src/process/FFTDataDistributor.cpp b/src/process/FFTDataDistributor.cpp
new file mode 100644
index 0000000..140a50f
--- /dev/null
+++ b/src/process/FFTDataDistributor.cpp
@@ -0,0 +1,98 @@
+#include "FFTDataDistributor.h"
+
+FFTDataDistributor::FFTDataDistributor() : outputBuffers("FFTDataDistributorBuffers"), fftSize(DEFAULT_FFT_SIZE), linesPerSecond(DEFAULT_WATERFALL_LPS), lineRateAccum(0.0) {
+    bufferedItems = 0;
+}
+
+void FFTDataDistributor::setFFTSize(unsigned int fftSize) {
+	this->fftSize = fftSize;
+}
+
+void FFTDataDistributor::setLinesPerSecond(unsigned int lines) {
+	this->linesPerSecond = lines;
+}
+
+unsigned int FFTDataDistributor::getLinesPerSecond() {
+	return this->linesPerSecond;
+}
+
+void FFTDataDistributor::process() {
+
+	while (!input->empty()) {
+		if (!isAnyOutputEmpty()) {
+			return;
+		}
+		DemodulatorThreadIQData *inp;
+		input->pop(inp);
+
+		if (inp) {
+			if (inputBuffer.sampleRate != inp->sampleRate || inputBuffer.frequency != inp->frequency) {
+                
+                bufferMax = inp->sampleRate / 4;
+//                std::cout << "Buffer Max: " << bufferMax << std::endl;
+                bufferOffset = 0;
+                
+				inputBuffer.sampleRate = inp->sampleRate;
+				inputBuffer.frequency = inp->frequency;
+                inputBuffer.data.resize(bufferMax);
+			}
+            if ((bufferOffset + bufferedItems + inp->data.size()) > bufferMax) {
+                memmove(&inputBuffer.data[0], &inputBuffer.data[bufferOffset], bufferedItems*sizeof(liquid_float_complex));
+                bufferOffset = 0;
+            } else {
+                memcpy(&inputBuffer.data[bufferOffset+bufferedItems],&inp->data[0],inp->data.size()*sizeof(liquid_float_complex));
+                bufferedItems += inp->data.size();
+            }
+			inp->decRefCount();
+		} else {
+			continue;
+		}
+
+		// number of seconds contained in input
+		double inputTime = (double)bufferedItems / (double)inputBuffer.sampleRate;
+		// number of lines in input
+		double inputLines = (double)bufferedItems / (double)fftSize;
+
+		// ratio required to achieve the desired rate
+		double lineRateStep = ((double)linesPerSecond * inputTime)/(double)inputLines;
+
+		if (bufferedItems >= fftSize) {
+			int numProcessed = 0;
+
+			if (lineRateAccum + (lineRateStep * ((double)bufferedItems/(double)fftSize)) < 1.0) {
+				// move along, nothing to see here..
+				lineRateAccum += (lineRateStep * ((double)bufferedItems/(double)fftSize));
+				numProcessed = bufferedItems;
+			} else {
+				for (unsigned int i = 0, iMax = bufferedItems; i < iMax; i += fftSize) {
+					if ((i + fftSize) > iMax) {
+						break;
+					}
+					lineRateAccum += lineRateStep;
+
+					if (lineRateAccum >= 1.0) {
+						DemodulatorThreadIQData *outp = outputBuffers.getBuffer();
+						outp->frequency = inputBuffer.frequency;
+						outp->sampleRate = inputBuffer.sampleRate;
+						outp->data.assign(inputBuffer.data.begin()+bufferOffset+i,inputBuffer.data.begin()+bufferOffset+i+fftSize);
+						distribute(outp);
+
+						while (lineRateAccum >= 1.0) {
+							lineRateAccum -= 1.0;
+						}
+					}
+
+					numProcessed += fftSize;
+				}
+			}
+			if (numProcessed) {
+                bufferedItems -= numProcessed;
+                bufferOffset += numProcessed;
+            }
+            if (bufferedItems <= 0) {
+                bufferedItems = 0;
+                bufferOffset = 0;
+            }
+		}
+	}
+}
diff --git a/src/process/FFTDataDistributor.h b/src/process/FFTDataDistributor.h
new file mode 100644
index 0000000..da58828
--- /dev/null
+++ b/src/process/FFTDataDistributor.h
@@ -0,0 +1,24 @@
+#pragma once
+
+#include "VisualProcessor.h"
+#include "DemodDefs.h"
+#include <cmath>
+#include <cstring>
+
+class FFTDataDistributor : public VisualProcessor<DemodulatorThreadIQData, DemodulatorThreadIQData> {
+public:
+    FFTDataDistributor();
+    void setFFTSize(unsigned int fftSize);
+    void setLinesPerSecond(unsigned int lines);
+    unsigned int getLinesPerSecond();
+
+protected:
+    void process();
+    
+    DemodulatorThreadIQData inputBuffer, tempBuffer;
+    ReBuffer<DemodulatorThreadIQData> outputBuffers;
+    unsigned int fftSize;
+    unsigned int linesPerSecond;
+    double lineRateAccum;
+    size_t bufferMax, bufferOffset, bufferedItems;
+};
diff --git a/src/process/FFTVisualDataThread.cpp b/src/process/FFTVisualDataThread.cpp
new file mode 100644
index 0000000..8eb9186
--- /dev/null
+++ b/src/process/FFTVisualDataThread.cpp
@@ -0,0 +1,68 @@
+#include "FFTVisualDataThread.h"
+#include "CubicSDR.h"
+
+FFTVisualDataThread::FFTVisualDataThread() {
+	linesPerSecond.store(DEFAULT_WATERFALL_LPS);
+    lpsChanged.store(true);
+}
+
+FFTVisualDataThread::~FFTVisualDataThread() {
+    
+}
+
+void FFTVisualDataThread::setLinesPerSecond(int lps) {
+    linesPerSecond.store(lps);
+    lpsChanged.store(true);
+}
+
+int FFTVisualDataThread::getLinesPerSecond() {
+    return linesPerSecond.load();
+}
+
+SpectrumVisualProcessor *FFTVisualDataThread::getProcessor() {
+    return &wproc;
+}
+
+void FFTVisualDataThread::run() {
+    DemodulatorThreadInputQueue *pipeIQDataIn = static_cast<DemodulatorThreadInputQueue *>(getInputQueue("IQDataInput"));
+    SpectrumVisualDataQueue *pipeFFTDataOut = static_cast<SpectrumVisualDataQueue *>(getOutputQueue("FFTDataOutput"));
+    
+    fftQueue.set_max_num_items(100);
+    pipeFFTDataOut->set_max_num_items(100);
+    fftDistrib.setInput(pipeIQDataIn);
+    fftDistrib.attachOutput(&fftQueue);
+    wproc.setInput(&fftQueue);
+    wproc.attachOutput(pipeFFTDataOut);
+    wproc.setup(DEFAULT_FFT_SIZE);
+
+//    std::cout << "FFT visual data thread started." << std::endl;
+    
+    while(!stopping) {
+        
+        std::this_thread::sleep_for(std::chrono::milliseconds(10));
+//        std::this_thread::yield();
+        
+        int fftSize = wproc.getDesiredInputSize();
+        
+        if (fftSize) {
+            fftDistrib.setFFTSize(fftSize);
+        } else {
+            fftDistrib.setFFTSize(DEFAULT_FFT_SIZE * SPECTRUM_VZM);
+        }
+    
+        if (lpsChanged.load()) {
+            fftDistrib.setLinesPerSecond(linesPerSecond.load());
+//            pipeIQDataIn->set_max_num_items(linesPerSecond.load());
+            lpsChanged.store(false);
+        }
+        
+        fftDistrib.run();
+        
+        while (!wproc.isInputEmpty()) {
+            wproc.run();
+        }
+    }
+    
+//    std::cout << "FFT visual data thread done." << std::endl;
+}
+
diff --git a/src/process/FFTVisualDataThread.h b/src/process/FFTVisualDataThread.h
new file mode 100644
index 0000000..bb16be4
--- /dev/null
+++ b/src/process/FFTVisualDataThread.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#include "IOThread.h"
+#include "SpectrumVisualProcessor.h"
+#include "FFTDataDistributor.h"
+
+class FFTVisualDataThread : public IOThread {
+public:
+    FFTVisualDataThread();
+    ~FFTVisualDataThread();
+    
+    void setLinesPerSecond(int lps);
+    int getLinesPerSecond();
+    SpectrumVisualProcessor *getProcessor();
+    
+    virtual void run();
+    
+protected:
+    FFTDataDistributor fftDistrib;
+    DemodulatorThreadInputQueue fftQueue;
+    SpectrumVisualProcessor wproc;
+    
+    std::atomic_int linesPerSecond;
+    std::atomic_bool lpsChanged;
+};
diff --git a/src/process/ScopeVisualProcessor.cpp b/src/process/ScopeVisualProcessor.cpp
new file mode 100644
index 0000000..e5d57fc
--- /dev/null
+++ b/src/process/ScopeVisualProcessor.cpp
@@ -0,0 +1,216 @@
+#include "ScopeVisualProcessor.h"
+#include <cstring>
+#include <string>
+
+ScopeVisualProcessor::ScopeVisualProcessor(): outputBuffers("ScopeVisualProcessorBuffers") {
+    scopeEnabled.store(true);
+    spectrumEnabled.store(true);
+    fft_average_rate = 0.65f;
+	fft_ceil_ma = fft_ceil_maa = 0;
+	fft_floor_ma = fft_floor_maa = 0;
+    maxScopeSamples = 1024;
+    fftPlan = nullptr;
+}
+
+ScopeVisualProcessor::~ScopeVisualProcessor() {
+    if (fftPlan) {
+        fft_destroy_plan(fftPlan);
+    }
+}
+
+
+void ScopeVisualProcessor::setup(int fftSize_in) {
+    fftSize = fftSize_in;
+    desiredInputSize = fftSize;
+ 
+    fftInData.resize(fftSize);
+    fftOutput.resize(fftSize);
+
+    if (fftPlan) {
+        fft_destroy_plan(fftPlan);
+    }
+    fftPlan = fft_create_plan(fftSize, fftInData.data(), fftOutput.data(), LIQUID_FFT_FORWARD, 0);
+}
+
+void ScopeVisualProcessor::setScopeEnabled(bool scopeEnable) {
+    scopeEnabled.store(scopeEnable);
+}
+
+void ScopeVisualProcessor::setSpectrumEnabled(bool spectrumEnable) {
+    spectrumEnabled.store(spectrumEnable);
+}
+
+void ScopeVisualProcessor::process() {
+    if (!isOutputEmpty()) {
+        return;
+    }
+    AudioThreadInput *audioInputData;
+
+    if (input->try_pop(audioInputData)) {
+          
+        if (!audioInputData) {
+            return;
+        }
+        size_t i, iMax = audioInputData->data.size();
+        if (!iMax) {
+            delete audioInputData; //->decRefCount();
+            return;
+        }
+                
+        ScopeRenderData *renderData = NULL;
+        
+        if (scopeEnabled) {
+            iMax = audioInputData->data.size();
+            if (iMax > maxScopeSamples) {
+                iMax = maxScopeSamples;
+            }
+
+            renderData = outputBuffers.getBuffer();
+            renderData->channels = audioInputData->channels;
+            renderData->inputRate = audioInputData->inputRate;
+            renderData->sampleRate = audioInputData->sampleRate;
+
+            if (renderData->waveform_points.size() != iMax * 2) {
+                renderData->waveform_points.resize(iMax * 2);
+            }
+
+            float peak = 1.0f;
+            
+            for (i = 0; i < iMax; i++) {
+                float p = fabs(audioInputData->data[i]);
+                if (p > peak) {
+                    peak = p;
+                }
+            }
+            
+            if (audioInputData->type == 1) {
+                iMax = audioInputData->data.size();
+                if (renderData->waveform_points.size() != iMax * 2) {
+                    renderData->waveform_points.resize(iMax * 2);
+                }
+                for (i = 0; i < iMax; i++) {
+                    renderData->waveform_points[i * 2] = (((double) (i % (iMax/2)) / (double) iMax) * 2.0 - 0.5) * 2.0;
+                    renderData->waveform_points[i * 2 + 1] = audioInputData->data[i] / peak;
+                }
+                renderData->mode = ScopePanel::SCOPE_MODE_2Y;
+            } else if (audioInputData->type == 2) {
+                iMax = audioInputData->data.size();
+                if (renderData->waveform_points.size() != iMax) {
+                    renderData->waveform_points.resize(iMax);
+                }
+                for (i = 0; i < iMax/2; i++) {
+                    renderData->waveform_points[i * 2] = audioInputData->data[i * 2] / peak;
+                    renderData->waveform_points[i * 2 + 1] = audioInputData->data[i * 2 + 1] / peak;
+                }
+                renderData->mode = ScopePanel::SCOPE_MODE_XY;
+            } else {
+                for (i = 0; i < iMax; i++) {
+                    renderData->waveform_points[i * 2] = (((double) i / (double) iMax) - 0.5) * 2.0;
+                    renderData->waveform_points[i * 2 + 1] = audioInputData->data[i] / peak;
+                }
+                renderData->mode = ScopePanel::SCOPE_MODE_Y;
+            }
+
+            renderData->spectrum = false;
+            distribute(renderData);
+        }
+        
+        if (spectrumEnabled) {
+            iMax = audioInputData->data.size();
+
+            if (audioInputData->channels==1) {
+                for (i = 0; i < fftSize; i++) {
+                    if (i < iMax) {
+                        fftInData[i].real = audioInputData->data[i];
+                        fftInData[i].imag = 0;
+                    } else {
+                        fftInData[i].real = 0;
+                        fftInData[i].imag = 0;
+                    }
+                }
+            } else if (audioInputData->channels==2) {
+                iMax = iMax/2;
+                for (i = 0; i < fftSize; i++) {
+                    if (i < iMax) {
+                        fftInData[i].real = audioInputData->data[i] + audioInputData->data[iMax+i];
+                        fftInData[i].imag = 0;
+                    } else {
+                        fftInData[i].real = 0;
+                        fftInData[i].imag = 0;
+                    }
+                }
+            }
+            
+            renderData = outputBuffers.getBuffer();
+
+            renderData->channels = audioInputData->channels;
+            renderData->inputRate = audioInputData->inputRate;
+            renderData->sampleRate = audioInputData->sampleRate;
+            
+            delete audioInputData; //->decRefCount();
+
+            double fft_ceil = 0, fft_floor = 1;
+            
+            if (fft_result.size() < (fftSize/2)) {
+                fft_result.resize((fftSize/2));
+                fft_result_ma.resize((fftSize/2));
+                fft_result_maa.resize((fftSize/2));
+            }
+
+            fft_execute(fftPlan);
+            for (i = 0; i < (fftSize/2); i++) {
+
+                //cast result to double to prevent overflows / excessive precision losses in the following computations...
+                double a = (double) fftOutput[i].real;
+                double b = (double) fftOutput[i].imag;
+
+                //computes norm = sqrt(a**2 + b**2)
+                //being actually floats cast into doubles, we are indeed overflow-free here.
+                fft_result[i] = sqrt(a*a + b*b);
+            }
+            
+            for (i = 0; i < (fftSize/2); i++) {
+                fft_result_ma[i] += (fft_result[i] - fft_result_ma[i]) * fft_average_rate;
+				fft_result_maa[i] += (fft_result_ma[i] - fft_result_maa[i]) * fft_average_rate;
+
+                if (fft_result_maa[i] > fft_ceil) {
+                    fft_ceil = fft_result_maa[i];
+                }
+                if (fft_result_maa[i] < fft_floor) {
+                    fft_floor = fft_result_maa[i];
+                }
+            }
+
+            fft_ceil_ma = fft_ceil_ma + (fft_ceil - fft_ceil_ma) * 0.05;
+            fft_ceil_maa = fft_ceil_maa + (fft_ceil_ma - fft_ceil_maa) * 0.05;
+            
+            fft_floor_ma = fft_floor_ma + (fft_floor - fft_floor_ma) * 0.05;
+            fft_floor_maa = fft_floor_maa + (fft_floor_ma - fft_floor_maa) * 0.05;
+
+            unsigned int outSize = fftSize/2;
+            
+            if (renderData->sampleRate != renderData->inputRate) {
+                outSize = (int)floor((float)outSize * ((float)renderData->sampleRate/(float)renderData->inputRate));
+            }
+            
+            if (renderData->waveform_points.size() != outSize*2) {
+                renderData->waveform_points.resize(outSize*2);
+            }
+            
+            for (i = 0; i < outSize; i++) {
+                float v = (log10(fft_result_maa[i]+0.25 - (fft_floor_maa-0.75)) / log10((fft_ceil_maa+0.25) - (fft_floor_maa-0.75)));
+                renderData->waveform_points[i * 2] = ((double) i / (double) (outSize));
+                renderData->waveform_points[i * 2 + 1] = v;
+            }
+            
+            renderData->fft_floor = fft_floor_maa;
+            renderData->fft_ceil = fft_ceil_maa;
+            renderData->fft_size = fftSize/2;
+            renderData->spectrum = true;
+
+            distribute(renderData);
+        } else {
+            delete audioInputData; //->decRefCount();
+        }
+    } //end if try_pop()
+}
diff --git a/src/process/ScopeVisualProcessor.h b/src/process/ScopeVisualProcessor.h
new file mode 100644
index 0000000..ed85354
--- /dev/null
+++ b/src/process/ScopeVisualProcessor.h
@@ -0,0 +1,50 @@
+#pragma once
+
+#include "VisualProcessor.h"
+#include "AudioThread.h"
+#include "ScopePanel.h"
+
+class ScopeRenderData: public ReferenceCounter {
+public:
+	std::vector<float> waveform_points;
+    ScopePanel::ScopeMode mode;
+    int inputRate;
+    int sampleRate;
+	int channels;
+    bool spectrum;
+    int fft_size;
+    double fft_floor, fft_ceil;
+};
+
+typedef ThreadQueue<ScopeRenderData *> ScopeRenderDataQueue;
+
+class ScopeVisualProcessor : public VisualProcessor<AudioThreadInput, ScopeRenderData> {
+public:
+    ScopeVisualProcessor();
+    ~ScopeVisualProcessor();
+    void setup(int fftSize_in);
+    void setScopeEnabled(bool scopeEnable);
+    void setSpectrumEnabled(bool spectrumEnable);
+protected:
+    void process();
+    ReBuffer<ScopeRenderData> outputBuffers;
+
+    std::atomic_bool scopeEnabled;
+    std::atomic_bool spectrumEnabled;
+    
+    std::vector<liquid_float_complex> fftInData;
+    std::vector<liquid_float_complex> fftOutput;
+    fftplan fftPlan;
+    
+    unsigned int fftSize;
+    int desiredInputSize;
+    unsigned int maxScopeSamples;
+    
+    double fft_ceil_ma, fft_ceil_maa;
+    double fft_floor_ma, fft_floor_maa;
+    double fft_average_rate;
+    
+    std::vector<double> fft_result;
+    std::vector<double> fft_result_ma;
+    std::vector<double> fft_result_maa;
+};
diff --git a/src/process/SpectrumVisualDataThread.cpp b/src/process/SpectrumVisualDataThread.cpp
new file mode 100644
index 0000000..0c8981a
--- /dev/null
+++ b/src/process/SpectrumVisualDataThread.cpp
@@ -0,0 +1,26 @@
+#include "SpectrumVisualDataThread.h"
+#include "CubicSDR.h"
+
+SpectrumVisualDataThread::SpectrumVisualDataThread() {
+}
+
+SpectrumVisualDataThread::~SpectrumVisualDataThread() {
+    
+}
+
+SpectrumVisualProcessor *SpectrumVisualDataThread::getProcessor() {
+    return &sproc;
+}
+
+void SpectrumVisualDataThread::run() {
+//    std::cout << "Spectrum visual data thread started." << std::endl;
+    
+    while(!stopping) {
+        std::this_thread::sleep_for(std::chrono::milliseconds(10));
+//        std::this_thread::yield();
+        sproc.run();
+    }
+    
+//    std::cout << "Spectrum visual data thread done." << std::endl;
+}
+
diff --git a/src/process/SpectrumVisualDataThread.h b/src/process/SpectrumVisualDataThread.h
new file mode 100644
index 0000000..0987193
--- /dev/null
+++ b/src/process/SpectrumVisualDataThread.h
@@ -0,0 +1,16 @@
+#pragma once
+
+#include "IOThread.h"
+#include "SpectrumVisualProcessor.h"
+
+class SpectrumVisualDataThread : public IOThread {
+public:
+    SpectrumVisualDataThread();
+    ~SpectrumVisualDataThread();
+    SpectrumVisualProcessor *getProcessor();
+    
+    virtual void run();
+    
+protected:
+    SpectrumVisualProcessor sproc;
+};
diff --git a/src/process/SpectrumVisualProcessor.cpp b/src/process/SpectrumVisualProcessor.cpp
new file mode 100644
index 0000000..ad80374
--- /dev/null
+++ b/src/process/SpectrumVisualProcessor.cpp
@@ -0,0 +1,613 @@
+#include "SpectrumVisualProcessor.h"
+#include "CubicSDR.h"
+
+
+SpectrumVisualProcessor::SpectrumVisualProcessor() : outputBuffers("SpectrumVisualProcessorBuffers") {
+    lastInputBandwidth = 0;
+    lastBandwidth = 0;
+    lastDataSize = 0;
+    resampler = nullptr;
+    resamplerRatio = 0;
+
+    fftInput = nullptr;
+    fftOutput = nullptr;
+    fftInData = nullptr;
+    fftLastData = nullptr;
+    fftPlan = nullptr;
+    
+    is_view.store(false);
+    fftSize.store(0);
+    centerFreq.store(0);
+    bandwidth.store(0);
+    hideDC.store(false);
+    
+    freqShifter = nco_crcf_create(LIQUID_NCO);
+    shiftFrequency = 0;
+    
+    fft_ceil_ma = fft_ceil_maa = 100.0;
+    fft_floor_ma = fft_floor_maa = 0.0;
+    desiredInputSize.store(0);
+    fft_average_rate = 0.65f;
+    scaleFactor.store(1.0);
+    fftSizeChanged.store(false);
+    newFFTSize.store(0);
+    lastView = false;
+    peakHold.store(false);
+    peakReset.store(false);
+    
+}
+
+SpectrumVisualProcessor::~SpectrumVisualProcessor() {
+    nco_crcf_destroy(freqShifter);
+}
+
+bool SpectrumVisualProcessor::isView() {
+    return is_view.load();
+}
+
+void SpectrumVisualProcessor::setView(bool bView) {
+    
+    std::lock_guard < std::mutex > busy_lock(busy_run);    
+    is_view.store(bView);
+    
+}
+
+void SpectrumVisualProcessor::setView(bool bView, long long centerFreq_in, long bandwidth_in) {
+    
+    std::lock_guard < std::mutex > busy_lock(busy_run);    
+    is_view.store(bView);
+    bandwidth.store(bandwidth_in);
+    centerFreq.store(centerFreq_in);
+   
+}
+
+
+void SpectrumVisualProcessor::setFFTAverageRate(float fftAverageRate) {
+   
+    std::lock_guard < std::mutex > busy_lock(busy_run);    
+    this->fft_average_rate.store(fftAverageRate);
+    
+}
+
+float SpectrumVisualProcessor::getFFTAverageRate() {
+    return this->fft_average_rate.load();
+}
+
+void SpectrumVisualProcessor::setCenterFrequency(long long centerFreq_in) {
+   
+    std::lock_guard < std::mutex > busy_lock(busy_run);    
+    centerFreq.store(centerFreq_in);
+   
+}
+
+long long SpectrumVisualProcessor::getCenterFrequency() {
+    return centerFreq.load();
+}
+
+void SpectrumVisualProcessor::setBandwidth(long bandwidth_in) {
+   
+    std::lock_guard < std::mutex > busy_lock(busy_run);    
+    bandwidth.store(bandwidth_in);
+   
+}
+
+long SpectrumVisualProcessor::getBandwidth() {
+    return bandwidth.load();
+}
+
+void SpectrumVisualProcessor::setPeakHold(bool peakHold_in) {
+   
+    if (peakHold.load() && peakHold_in) {
+        peakReset.store(PEAK_RESET_COUNT);
+    } else {
+        peakHold.store(peakHold_in);
+        peakReset.store(1);
+    }
+}
+
+bool SpectrumVisualProcessor::getPeakHold() {
+    return peakHold.load();
+}
+
+int SpectrumVisualProcessor::getDesiredInputSize() {
+    return desiredInputSize.load();
+}
+
+void SpectrumVisualProcessor::setup(unsigned int fftSize_in) {
+
+    std::lock_guard < std::mutex > busy_lock(busy_run);    
+
+    fftSize = fftSize_in;
+    fftSizeInternal = fftSize_in * SPECTRUM_VZM;
+    lastDataSize = 0;
+
+    int memSize = sizeof(liquid_float_complex) * fftSizeInternal;
+    
+    if (fftInput) {
+        free(fftInput);
+    }
+    fftInput = (liquid_float_complex*)malloc(memSize);
+    memset(fftInput,0,memSize);
+    
+    if (fftInData) {
+        free(fftInData);
+    }
+    fftInData = (liquid_float_complex*)malloc(memSize);
+    memset(fftInput,0,memSize);
+    
+    if (fftLastData) {
+        free(fftLastData);
+    }
+    fftLastData = (liquid_float_complex*)malloc(memSize);
+    memset(fftInput,0,memSize);
+    
+    if (fftOutput) {
+        free(fftOutput);
+    }
+    fftOutput = (liquid_float_complex*)malloc(memSize);
+    memset(fftInput,0,memSize);
+    
+    if (fftPlan) {
+        fft_destroy_plan(fftPlan);
+    }
+    fftPlan = fft_create_plan(fftSizeInternal, fftInput, fftOutput, LIQUID_FFT_FORWARD, 0);
+    
+}
+
+void SpectrumVisualProcessor::setFFTSize(unsigned int fftSize_in) {
+    if (fftSize_in == fftSize) {
+        return;
+    }
+    newFFTSize = fftSize_in;
+    fftSizeChanged.store(true);
+}
+
+unsigned int SpectrumVisualProcessor::getFFTSize() {
+    if (fftSizeChanged.load()) {
+        return newFFTSize;
+    }
+    return fftSize.load();
+}
+
+
+void SpectrumVisualProcessor::setHideDC(bool hideDC) {
+    this->hideDC.store(hideDC);
+}
+
+
+void SpectrumVisualProcessor::process() {
+    if (!isOutputEmpty()) {
+        return;
+    }
+    if (!input || input->empty()) {
+        return;
+    }
+    
+    if (fftSizeChanged.load()) {
+        setup(newFFTSize);
+        fftSizeChanged.store(false);
+    }
+
+    DemodulatorThreadIQData *iqData;
+    
+    input->pop(iqData);
+    
+    if (!iqData) {
+        return;
+    }
+
+   
+    //Start by locking concurrent access to iqData
+    std::lock_guard < std::recursive_mutex > lock(iqData->getMonitor());
+
+    //then get the busy_lock
+    std::lock_guard < std::mutex > busy_lock(busy_run);    
+
+
+   
+    bool doPeak = peakHold.load() && (peakReset.load() == 0);
+    
+    if (fft_result.size() != fftSizeInternal) {
+        if (fft_result.capacity() < fftSizeInternal) {
+            fft_result.reserve(fftSizeInternal);
+            fft_result_ma.reserve(fftSizeInternal);
+            fft_result_maa.reserve(fftSizeInternal);
+            fft_result_peak.reserve(fftSizeInternal);
+        }
+        fft_result.resize(fftSizeInternal);
+        fft_result_ma.resize(fftSizeInternal);
+        fft_result_maa.resize(fftSizeInternal);
+        fft_result_temp.resize(fftSizeInternal);
+        fft_result_peak.resize(fftSizeInternal);
+    }
+    
+    if (peakReset.load() != 0) {
+        peakReset--;
+        if (peakReset.load() == 0) {
+            for (unsigned int i = 0, iMax = fftSizeInternal; i < iMax; i++) {
+                fft_result_peak[i] = fft_floor_maa;
+            }
+            fft_ceil_peak = fft_floor_maa;
+            fft_floor_peak = fft_ceil_maa;
+        }
+    }
+    
+    std::vector<liquid_float_complex> *data = &iqData->data;
+    
+    if (data && data->size()) {
+        unsigned int num_written;
+        long resampleBw = iqData->sampleRate;
+        bool newResampler = false;
+        int bwDiff;
+        
+        if (is_view.load()) {
+            if (!iqData->sampleRate) {
+                iqData->decRefCount();
+               
+                return;
+            }
+            
+            while (resampleBw / SPECTRUM_VZM >= bandwidth) {
+                resampleBw /= SPECTRUM_VZM;
+            }
+            
+            resamplerRatio = (double) (resampleBw) / (double) iqData->sampleRate;
+            
+            size_t desired_input_size = fftSizeInternal / resamplerRatio;
+            
+            this->desiredInputSize.store(desired_input_size);
+            
+            if (iqData->data.size() < desired_input_size) {
+                //                std::cout << "fft underflow, desired: " << desired_input_size << " actual:" << input->data.size() << std::endl;
+                desired_input_size = iqData->data.size();
+            }
+            
+            if (centerFreq != iqData->frequency) {
+                if ((centerFreq - iqData->frequency) != shiftFrequency || lastInputBandwidth != iqData->sampleRate) {
+                    if (abs(iqData->frequency - centerFreq) < (wxGetApp().getSampleRate() / 2)) {
+                        long lastShiftFrequency = shiftFrequency;
+                        shiftFrequency = centerFreq - iqData->frequency;
+                        nco_crcf_set_frequency(freqShifter, (2.0 * M_PI) * (((double) abs(shiftFrequency)) / ((double) iqData->sampleRate)));
+                        
+                        if (is_view.load()) {
+                            long freqDiff = shiftFrequency - lastShiftFrequency;
+                            
+                            if (lastBandwidth!=0) {
+                                double binPerHz = double(lastBandwidth) / double(fftSizeInternal);
+                                
+                                unsigned int numShift = floor(double(abs(freqDiff)) / binPerHz);
+                                
+                                if (numShift < fftSizeInternal/2 && numShift) {
+                                    if (freqDiff > 0) {
+                                        memmove(&fft_result_ma[0], &fft_result_ma[numShift], (fftSizeInternal-numShift) * sizeof(double));
+                                        memmove(&fft_result_maa[0], &fft_result_maa[numShift], (fftSizeInternal-numShift) * sizeof(double));
+//                                        memmove(&fft_result_peak[0], &fft_result_peak[numShift], (fftSizeInternal-numShift) * sizeof(double));
+//                                        memset(&fft_result_peak[fftSizeInternal-numShift], 0, numShift * sizeof(double));
+                                    } else {
+                                        memmove(&fft_result_ma[numShift], &fft_result_ma[0], (fftSizeInternal-numShift) * sizeof(double));
+                                        memmove(&fft_result_maa[numShift], &fft_result_maa[0], (fftSizeInternal-numShift) * sizeof(double));
+//                                        memmove(&fft_result_peak[numShift], &fft_result_peak[0], (fftSizeInternal-numShift) * sizeof(double));
+//                                        memset(&fft_result_peak[0], 0, numShift * sizeof(double));
+                                    }
+                                }
+                            }
+                        }
+                    }
+                    peakReset.store(PEAK_RESET_COUNT);
+                }
+                
+                if (shiftBuffer.size() != desired_input_size) {
+                    if (shiftBuffer.capacity() < desired_input_size) {
+                        shiftBuffer.reserve(desired_input_size);
+                    }
+                    shiftBuffer.resize(desired_input_size);
+                }
+                
+                if (shiftFrequency < 0) {
+                    nco_crcf_mix_block_up(freqShifter, &iqData->data[0], &shiftBuffer[0], desired_input_size);
+                } else {
+                    nco_crcf_mix_block_down(freqShifter, &iqData->data[0], &shiftBuffer[0], desired_input_size);
+                }
+            } else {
+                shiftBuffer.assign(iqData->data.begin(), iqData->data.begin()+desired_input_size);
+            }
+            
+            if (!resampler || resampleBw != lastBandwidth || lastInputBandwidth != iqData->sampleRate) {
+                float As = 480.0;
+                
+                if (resampler) {
+                    msresamp_crcf_destroy(resampler);
+                }
+                
+                resampler = msresamp_crcf_create(resamplerRatio, As);
+                
+                bwDiff = resampleBw-lastBandwidth;
+                lastBandwidth = resampleBw;
+                lastInputBandwidth = iqData->sampleRate;
+                newResampler = true;
+                peakReset.store(PEAK_RESET_COUNT);
+            }
+            
+            
+            unsigned int out_size = ceil((double) (desired_input_size) * resamplerRatio) + 512;
+            
+            if (resampleBuffer.size() != out_size) {
+                if (resampleBuffer.capacity() < out_size) {
+                    resampleBuffer.reserve(out_size);
+                }
+                resampleBuffer.resize(out_size);
+            }
+            
+            msresamp_crcf_execute(resampler, &shiftBuffer[0], desired_input_size, &resampleBuffer[0], &num_written);
+            
+            if (num_written < fftSizeInternal) {
+                memcpy(fftInData, resampleBuffer.data(), num_written * sizeof(liquid_float_complex));
+                memset(&(fftInData[num_written]), 0, (fftSizeInternal-num_written) * sizeof(liquid_float_complex));
+            } else {
+                memcpy(fftInData, resampleBuffer.data(), fftSizeInternal * sizeof(liquid_float_complex));
+            }
+        } else {
+            this->desiredInputSize.store(fftSizeInternal);
+
+            num_written = data->size();
+            if (data->size() < fftSizeInternal) {
+                memcpy(fftInData, data->data(), data->size() * sizeof(liquid_float_complex));
+                memset(&fftInData[data->size()], 0, (fftSizeInternal - data->size()) * sizeof(liquid_float_complex));
+            } else {
+                memcpy(fftInData, data->data(), fftSizeInternal * sizeof(liquid_float_complex));
+            }
+        }
+        
+        bool execute = false;
+
+        if (num_written >= fftSizeInternal) {
+            execute = true;
+            memcpy(fftInput, fftInData, fftSizeInternal * sizeof(liquid_float_complex));
+            memcpy(fftLastData, fftInput, fftSizeInternal * sizeof(liquid_float_complex));
+            
+        } else {
+            if (lastDataSize + num_written < fftSizeInternal) { // priming
+                unsigned int num_copy = fftSizeInternal - lastDataSize;
+                if (num_written > num_copy) {
+                    num_copy = num_written;
+                }
+                memcpy(fftLastData, fftInData, num_copy * sizeof(liquid_float_complex));
+                lastDataSize += num_copy;
+            } else {
+                unsigned int num_last = (fftSizeInternal - num_written);
+                memcpy(fftInput, fftLastData + (lastDataSize - num_last), num_last * sizeof(liquid_float_complex));
+                memcpy(fftInput + num_last, fftInData, num_written * sizeof(liquid_float_complex));
+                memcpy(fftLastData, fftInput, fftSizeInternal * sizeof(liquid_float_complex));
+                execute = true;
+            }
+        }
+        
+        if (execute) {
+            SpectrumVisualData *output = outputBuffers.getBuffer();
+            
+            if (output->spectrum_points.size() != fftSize * 2) {
+                output->spectrum_points.resize(fftSize * 2);
+            }
+            if (doPeak) {
+                if (output->spectrum_hold_points.size() != fftSize * 2) {
+                    output->spectrum_hold_points.resize(fftSize * 2);
+                }
+            } else {
+                output->spectrum_hold_points.resize(0);
+            }
+            
+            float fft_ceil = 0, fft_floor = 1;
+
+            fft_execute(fftPlan);
+            
+            for (int i = 0, iMax = fftSizeInternal / 2; i < iMax; i++) {
+                float a = fftOutput[i].real;
+                float b = fftOutput[i].imag;
+                float c = sqrt(a * a + b * b);
+                
+                float x = fftOutput[fftSizeInternal / 2 + i].real;
+                float y = fftOutput[fftSizeInternal / 2 + i].imag;
+                float z = sqrt(x * x + y * y);
+                
+                fft_result[i] = (z);
+                fft_result[fftSizeInternal / 2 + i] = (c);
+            }
+            
+            if (newResampler && lastView) {
+                if (bwDiff < 0) {
+                    for (unsigned int i = 0, iMax = fftSizeInternal; i < iMax; i++) {
+                        fft_result_temp[i] = fft_result_ma[(fftSizeInternal/4) + (i/2)];
+                    }
+                    for (unsigned int i = 0, iMax = fftSizeInternal; i < iMax; i++) {
+                        fft_result_ma[i] = fft_result_temp[i];
+                        
+                        fft_result_temp[i] = fft_result_maa[(fftSizeInternal/4) + (i/2)];
+                    }
+                    for (unsigned int i = 0, iMax = fftSizeInternal; i < iMax; i++) {
+                        fft_result_maa[i] = fft_result_temp[i];
+                    }
+                } else {
+                    for (size_t i = 0, iMax = fftSizeInternal; i < iMax; i++) {
+                        if (i < fftSizeInternal/4) {
+                            fft_result_temp[i] = 0; // fft_result_ma[fftSizeInternal/4];
+                        } else if (i >= fftSizeInternal - fftSizeInternal/4) {
+                            fft_result_temp[i] = 0; // fft_result_ma[fftSizeInternal - fftSizeInternal/4-1];
+                        } else {
+                            fft_result_temp[i] = fft_result_ma[(i-fftSizeInternal/4)*2];
+                        }
+                    }
+                    for (unsigned int i = 0, iMax = fftSizeInternal; i < iMax; i++) {
+                        fft_result_ma[i] = fft_result_temp[i];
+                        
+                        if (i < fftSizeInternal/4) {
+                            fft_result_temp[i] = 0; //fft_result_maa[fftSizeInternal/4];
+                        } else if (i >= fftSizeInternal - fftSizeInternal/4) {
+                            fft_result_temp[i] = 0; // fft_result_maa[fftSizeInternal - fftSizeInternal/4-1];
+                        } else {
+                            fft_result_temp[i] = fft_result_maa[(i-fftSizeInternal/4)*2];
+                        }
+                    }
+                    for (unsigned int i = 0, iMax = fftSizeInternal; i < iMax; i++) {
+                        fft_result_maa[i] = fft_result_temp[i];
+                    }
+                }
+            }
+            
+            for (int i = 0, iMax = fftSizeInternal; i < iMax; i++) {
+                if (fft_result_maa[i] != fft_result_maa[i]) fft_result_maa[i] = fft_result[i];
+                fft_result_maa[i] += (fft_result_ma[i] - fft_result_maa[i]) * fft_average_rate;
+                if (fft_result_ma[i] != fft_result_ma[i]) fft_result_ma[i] = fft_result[i];
+                fft_result_ma[i] += (fft_result[i] - fft_result_ma[i]) * fft_average_rate;
+                
+                if (fft_result_maa[i] > fft_ceil || fft_ceil != fft_ceil) {
+                    fft_ceil = fft_result_maa[i];
+                }
+                if (fft_result_maa[i] < fft_floor || fft_floor != fft_floor) {
+                    fft_floor = fft_result_maa[i];
+                }
+                if (doPeak) {
+                    if (fft_result_maa[i] > fft_result_peak[i]) {
+                        fft_result_peak[i] = fft_result_maa[i];
+                    }
+                }
+            }
+            
+            if (fft_ceil_ma != fft_ceil_ma) fft_ceil_ma = fft_ceil;
+            fft_ceil_ma = fft_ceil_ma + (fft_ceil - fft_ceil_ma) * 0.05;
+            if (fft_ceil_maa != fft_ceil_maa) fft_ceil_maa = fft_ceil;
+            fft_ceil_maa = fft_ceil_maa + (fft_ceil_ma - fft_ceil_maa) * 0.05;
+            
+            if (fft_floor_ma != fft_floor_ma) fft_floor_ma = fft_floor;
+            fft_floor_ma = fft_floor_ma + (fft_floor - fft_floor_ma) * 0.05;
+            if (fft_floor_maa != fft_floor_maa) fft_floor_maa = fft_floor;
+            fft_floor_maa = fft_floor_maa + (fft_floor_ma - fft_floor_maa) * 0.05;
+
+            if (doPeak) {
+                if (fft_ceil_maa > fft_ceil_peak) {
+                    fft_ceil_peak = fft_ceil_maa;
+                }
+                if (fft_floor_maa < fft_floor_peak) {
+                    fft_floor_peak = fft_floor_maa;
+                }
+            }
+            
+            float sf = scaleFactor.load();
+ 
+            double visualRatio = (double(bandwidth) / double(resampleBw));
+            double visualStart = (double(fftSizeInternal) / 2.0) - (double(fftSizeInternal) * (visualRatio / 2.0));
+            double visualAccum = 0;
+            double peak_acc = 0, acc = 0, accCount = 0, i = 0;
+   
+            double point_ceil = doPeak?fft_ceil_peak:fft_ceil_maa;
+            double point_floor = doPeak?fft_floor_peak:fft_floor_maa;
+            
+            for (int x = 0, xMax = output->spectrum_points.size() / 2; x < xMax; x++) {
+                visualAccum += visualRatio * double(SPECTRUM_VZM);
+
+                while (visualAccum >= 1.0) {
+                    unsigned int idx = round(visualStart+i);
+                    if (idx > 0 && idx < fftSizeInternal) {
+                        acc += fft_result_maa[idx];
+                        if (doPeak) {
+                            peak_acc += fft_result_peak[idx];
+                        }
+                    } else {
+                        acc += fft_floor_maa;
+                        if (doPeak) {
+                            peak_acc += fft_floor_maa;
+                        }
+                    }
+                    accCount += 1.0;
+                    visualAccum -= 1.0;
+                    i++;
+                }
+
+                output->spectrum_points[x * 2] = ((float) x / (float) xMax);
+                if (doPeak) {
+                    output->spectrum_hold_points[x * 2] = ((float) x / (float) xMax);
+                }
+                if (accCount) {
+                    output->spectrum_points[x * 2 + 1] = ((log10((acc/accCount)+0.25 - (point_floor-0.75)) / log10((point_ceil+0.25) - (point_floor-0.75))))*sf;
+                    acc = 0.0;
+                    if (doPeak) {
+                        output->spectrum_hold_points[x * 2 + 1] = ((log10((peak_acc/accCount)+0.25 - (point_floor-0.75)) / log10((point_ceil+0.25) - (point_floor-0.75))))*sf;
+                        peak_acc = 0.0;
+                    }
+                    accCount = 0.0;
+                }
+            }
+            
+            if (hideDC.load()) { // DC-spike removal
+                long long freqMin = centerFreq-(bandwidth/2);
+                long long freqMax = centerFreq+(bandwidth/2);
+                long long zeroPt = (iqData->frequency-freqMin);
+                
+                if (freqMin < iqData->frequency && freqMax > iqData->frequency) {
+                    int freqRange = int(freqMax-freqMin);
+                    int freqStep = freqRange/fftSize;
+                    int fftStart = (zeroPt/freqStep)-(2000/freqStep);
+                    int fftEnd = (zeroPt/freqStep)+(2000/freqStep);
+                    
+//                    std::cout << "range:" << freqRange << ", step: " << freqStep << ", start: " << fftStart << ", end: " << fftEnd << std::endl;
+                    
+                    if (fftEnd-fftStart < 2) {
+                        fftEnd++;
+                        fftStart--;
+                    }
+                    
+                    int numSteps = (fftEnd-fftStart);
+                    int halfWay = fftStart+(numSteps/2);
+
+                    if ((fftEnd+numSteps/2+1 < (long long) fftSize) && (fftStart-numSteps/2-1 >= 0) && (fftEnd > fftStart)) {
+                        int n = 1;
+                        for (int i = fftStart; i < halfWay; i++) {
+                            output->spectrum_points[i * 2 + 1] = output->spectrum_points[(fftStart - n) * 2 + 1];
+                            n++;
+                        }
+                        n = 1;
+                        for (int i = halfWay; i < fftEnd; i++) {
+                            output->spectrum_points[i * 2 + 1] = output->spectrum_points[(fftEnd + n) * 2 + 1];
+                            n++;
+                        }
+                        if (doPeak) {
+                            int n = 1;
+                            for (int i = fftStart; i < halfWay; i++) {
+                                output->spectrum_hold_points[i * 2 + 1] = output->spectrum_hold_points[(fftStart - n) * 2 + 1];
+                                n++;
+                            }
+                            n = 1;
+                            for (int i = halfWay; i < fftEnd; i++) {
+                                output->spectrum_hold_points[i * 2 + 1] = output->spectrum_hold_points[(fftEnd + n) * 2 + 1];
+                                n++;
+                            }
+                        }
+                    }
+                }
+            }
+            
+            output->fft_ceiling = point_ceil/sf;
+            output->fft_floor = point_floor;
+
+            output->centerFreq = centerFreq;
+            output->bandwidth = bandwidth;
+
+            distribute(output);
+        }
+    }
+ 
+    iqData->decRefCount();
+   
+    
+    lastView = is_view.load();
+}
+
+
+void SpectrumVisualProcessor::setScaleFactor(float sf) {
+    scaleFactor.store(sf);
+}
+
+
+float SpectrumVisualProcessor::getScaleFactor() {
+    return scaleFactor.load();
+}
+
diff --git a/src/process/SpectrumVisualProcessor.h b/src/process/SpectrumVisualProcessor.h
new file mode 100644
index 0000000..80a5d05
--- /dev/null
+++ b/src/process/SpectrumVisualProcessor.h
@@ -0,0 +1,97 @@
+#pragma once
+
+#include "VisualProcessor.h"
+#include "DemodDefs.h"
+#include <cmath>
+
+#define SPECTRUM_VZM 2
+#define PEAK_RESET_COUNT 30
+
+class SpectrumVisualData : public ReferenceCounter {
+public:
+    std::vector<float> spectrum_points;
+    std::vector<float> spectrum_hold_points;
+    double fft_ceiling, fft_floor;
+    long long centerFreq;
+    int bandwidth;
+};
+
+typedef ThreadQueue<SpectrumVisualData *> SpectrumVisualDataQueue;
+
+class SpectrumVisualProcessor : public VisualProcessor<DemodulatorThreadIQData, SpectrumVisualData> {
+public:
+    SpectrumVisualProcessor();
+    ~SpectrumVisualProcessor();
+    
+    bool isView();
+    void setView(bool bView);
+    void setView(bool bView, long long centerFreq_in, long bandwidth_in);
+    
+    void setFFTAverageRate(float fftAverageRate);
+    float getFFTAverageRate();
+    
+    void setCenterFrequency(long long centerFreq_in);
+    long long getCenterFrequency();
+    
+    void setBandwidth(long bandwidth_in);
+    long getBandwidth();
+    
+    void setPeakHold(bool peakHold_in);
+    bool getPeakHold();
+    
+    int getDesiredInputSize();
+    
+    void setup(unsigned int fftSize);
+    void setFFTSize(unsigned int fftSize);
+    unsigned int getFFTSize();
+    void setHideDC(bool hideDC);
+    
+    void setScaleFactor(float sf);
+    float getScaleFactor();
+    
+protected:
+    void process();
+    
+    ReBuffer<SpectrumVisualData> outputBuffers;
+    std::atomic_bool is_view;
+    std::atomic_uint fftSize, newFFTSize;
+    std::atomic_uint fftSizeInternal;
+    std::atomic_llong centerFreq;
+    std::atomic_long bandwidth;
+    
+private:
+    long lastInputBandwidth;
+    long lastBandwidth;
+    bool lastView;
+
+    liquid_float_complex *fftInput, *fftOutput, *fftInData, *fftLastData;
+    fftplan fftPlan;
+
+    unsigned int lastDataSize;
+    
+    double fft_ceil_ma, fft_ceil_maa;
+    double fft_floor_ma, fft_floor_maa;
+    double fft_ceil_peak, fft_floor_peak;
+    std::atomic<float> fft_average_rate;
+    
+    std::vector<double> fft_result;
+    std::vector<double> fft_result_ma;
+    std::vector<double> fft_result_maa;
+    std::vector<double> fft_result_peak;
+    std::vector<double> fft_result_temp;
+    
+    msresamp_crcf resampler;
+    double resamplerRatio;
+    nco_crcf freqShifter;
+    long shiftFrequency;
+    
+    std::vector<liquid_float_complex> shiftBuffer;
+    std::vector<liquid_float_complex> resampleBuffer;
+    std::atomic_int desiredInputSize;
+   
+    std::mutex busy_run;
+    std::atomic_bool hideDC, peakHold;
+    std::atomic_int peakReset;
+    std::atomic<float> scaleFactor;
+    std::atomic_bool fftSizeChanged;
+};
diff --git a/src/process/VisualProcessor.cpp b/src/process/VisualProcessor.cpp
new file mode 100644
index 0000000..c19fe88
--- /dev/null
+++ b/src/process/VisualProcessor.cpp
@@ -0,0 +1 @@
+#include "VisualProcessor.h"
diff --git a/src/process/VisualProcessor.h b/src/process/VisualProcessor.h
new file mode 100644
index 0000000..2b3bf2c
--- /dev/null
+++ b/src/process/VisualProcessor.h
@@ -0,0 +1,150 @@
+#pragma once
+
+#include "CubicSDRDefs.h"
+#include "ThreadQueue.h"
+#include "IOThread.h"
+#include <algorithm>
+
+template<class InputDataType = ReferenceCounter, class OutputDataType = ReferenceCounter>
+class VisualProcessor {
+public:
+	virtual ~VisualProcessor() {
+
+	}
+    
+    bool isInputEmpty() {
+        std::lock_guard < std::recursive_mutex > busy_lock(busy_update);
+
+        return input->empty();
+    }
+    
+    bool isOutputEmpty() {
+        std::lock_guard < std::recursive_mutex > busy_lock(busy_update);
+
+        for (outputs_i = outputs.begin(); outputs_i != outputs.end(); outputs_i++) {
+            if ((*outputs_i)->full()) {
+                return false;
+            }
+        }
+        return true;
+    }
+    
+    bool isAnyOutputEmpty() {
+        std::lock_guard < std::recursive_mutex > busy_lock(busy_update);
+
+        for (outputs_i = outputs.begin(); outputs_i != outputs.end(); outputs_i++) {
+            if (!(*outputs_i)->full()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    void setInput(ThreadQueue<InputDataType *> *vis_in) {
+        std::lock_guard < std::recursive_mutex > busy_lock(busy_update);
+        input = vis_in;
+        
+    }
+    
+    void attachOutput(ThreadQueue<OutputDataType *> *vis_out) {
+        // attach an output queue
+        std::lock_guard < std::recursive_mutex > busy_lock(busy_update);
+        outputs.push_back(vis_out);
+       
+    }
+    
+    void removeOutput(ThreadQueue<OutputDataType *> *vis_out) {
+        // remove an output queue
+        std::lock_guard < std::recursive_mutex > busy_lock(busy_update);
+
+        typename std::vector<ThreadQueue<OutputDataType *> *>::iterator i = std::find(outputs.begin(), outputs.end(), vis_out);
+        if (i != outputs.end()) {
+            outputs.erase(i);
+        }
+      
+    }
+    
+    void run() {
+        
+        std::lock_guard < std::recursive_mutex > busy_lock(busy_update);
+
+        if (input && !input->empty()) {
+            process();
+        }
+       
+    }
+    
+protected:
+    virtual void process() {
+        // process inputs to output
+        // distribute(output);
+    }
+
+    void distribute(OutputDataType *output) {
+        // distribute outputs
+        std::lock_guard < std::recursive_mutex > busy_lock(busy_update);
+
+        output->setRefCount(outputs.size());
+        for (outputs_i = outputs.begin(); outputs_i != outputs.end(); outputs_i++) {
+
+        	if (!(*outputs_i)->push(output)) {
+        		output->decRefCount();
+        	} 
+        }
+    }
+
+    ThreadQueue<InputDataType *> *input;
+    std::vector<ThreadQueue<OutputDataType *> *> outputs;
+	typename std::vector<ThreadQueue<OutputDataType *> *>::iterator outputs_i;
+
+    //protects input and outputs, must be recursive because ao reentrance
+    std::recursive_mutex busy_update;
+};
+
+
+template<class OutputDataType = ReferenceCounter>
+class VisualDataDistributor : public VisualProcessor<OutputDataType, OutputDataType> {
+protected:
+    void process() {
+        OutputDataType *inp;
+        while (VisualProcessor<OutputDataType, OutputDataType>::input->try_pop(inp)) {
+            
+            if (!VisualProcessor<OutputDataType, OutputDataType>::isAnyOutputEmpty()) {
+                if (inp) {
+            	    inp->decRefCount();
+                }
+                return;
+            }
+      
+            if (inp) {
+            	VisualProcessor<OutputDataType, OutputDataType>::distribute(inp);
+            }
+        }
+    }
+};
+
+
+template<class OutputDataType = ReferenceCounter>
+class VisualDataReDistributor : public VisualProcessor<OutputDataType, OutputDataType> {
+protected:
+    void process() {
+        OutputDataType *inp;
+        while (VisualProcessor<OutputDataType, OutputDataType>::input->try_pop(inp)) {
+            
+            if (!VisualProcessor<OutputDataType, OutputDataType>::isAnyOutputEmpty()) {
+                if (inp) {
+            	    inp->decRefCount();
+                }
+                return;
+            }
+            
+            if (inp) {
+                OutputDataType *outp = buffers.getBuffer();
+                (*outp) = (*inp);
+                inp->decRefCount();
+                VisualProcessor<OutputDataType, OutputDataType>::distribute(outp);
+            }
+        }
+    }
+    ReBuffer<OutputDataType> buffers;
+};
diff --git a/src/rig/RigThread.cpp b/src/rig/RigThread.cpp
new file mode 100644
index 0000000..e14d86d
--- /dev/null
+++ b/src/rig/RigThread.cpp
@@ -0,0 +1,184 @@
+#include "RigThread.h"
+
+std::vector<const struct rig_caps *> RigThread::rigCaps;
+
+RigThread::RigThread() {
+    freq = wxGetApp().getFrequency();
+    newFreq = freq;
+    freqChanged.store(true);
+    termStatus = 0;
+    controlMode.store(true);
+    followMode.store(true);
+    centerLock.store(false);
+    followModem.store(false);
+}
+
+RigThread::~RigThread() {
+
+}
+
+RigList &RigThread::enumerate() {
+    if (RigThread::rigCaps.empty()) {
+        rig_set_debug(RIG_DEBUG_ERR);
+        rig_load_all_backends();
+        
+        rig_list_foreach(RigThread::add_hamlib_rig, 0);
+        std::sort(RigThread::rigCaps.begin(), RigThread::rigCaps.end(), rigGreater());
+        std::cout << "Loaded " << RigThread::rigCaps.size() << " rig models via hamlib." << std::endl;
+    }
+    return RigThread::rigCaps;
+}
+
+int RigThread::add_hamlib_rig(const struct rig_caps *rc, void* f)
+{
+    rigCaps.push_back(rc);
+    return 1;
+}
+
+void RigThread::initRig(rig_model_t rig_model, std::string rig_file, int serial_rate) {
+    rigModel = rig_model;
+    rigFile = rig_file;
+    serialRate = serial_rate;
+};
+
+void RigThread::run() {
+    int retcode, status;
+
+    termStatus = 0;
+    
+    std::cout << "Rig thread starting." << std::endl;
+
+    rig = rig_init(rigModel);
+	strncpy(rig->state.rigport.pathname, rigFile.c_str(), FILPATHLEN - 1);
+	rig->state.rigport.parm.serial.rate = serialRate;
+	retcode = rig_open(rig);
+    
+    if (retcode != 0) {
+        std::cout << "Rig failed to init. " << std::endl;
+        IOThread::terminate();
+        return;
+    }
+    
+	char *info_buf = (char *)rig_get_info(rig);
+
+    if (info_buf) {
+        std::cout << "Rig info: " << info_buf << std::endl;
+    } else {
+        std::cout << "Rig info was NULL." << std::endl;
+    }
+    
+    while (!stopping) {
+        std::this_thread::sleep_for(std::chrono::milliseconds(150));
+        
+        DemodulatorInstance *activeDemod = wxGetApp().getDemodMgr().getActiveDemodulator();
+        DemodulatorInstance *lastDemod = wxGetApp().getDemodMgr().getLastActiveDemodulator();
+
+        if (freqChanged.load() && (controlMode.load() || setOneShot.load())) {
+            status = rig_get_freq(rig, RIG_VFO_CURR, &freq);
+            if (status == 0 && !stopping) {
+                
+                if (freq != newFreq && setOneShot.load()) {
+                    freq = newFreq;
+                    rig_set_freq(rig, RIG_VFO_CURR, freq);
+    //                std::cout << "Set Rig Freq: %f" <<  newFreq << std::endl;
+                }
+                
+                freqChanged.store(false);
+                setOneShot.store(false);
+            } else {
+                termStatus = 0;
+                break;
+            }
+        } else {
+            freq_t checkFreq;
+            
+            status = rig_get_freq(rig, RIG_VFO_CURR, &checkFreq);
+
+            if (status == 0 && !stopping) {
+                if (checkFreq != freq && followMode.load()) {
+                    freq = checkFreq;
+                    if (followModem.load()) {
+                        if (lastDemod) {
+                            lastDemod->setFrequency(freq);
+                            lastDemod->updateLabel(freq);
+                            lastDemod->setFollow(true);
+                        }
+                    } else {
+                        wxGetApp().setFrequency((long long)checkFreq);
+                    }
+                } else if (wxGetApp().getFrequency() != freq && controlMode.load() && !centerLock.load() && !followModem.load()) {
+                    freq = wxGetApp().getFrequency();
+                    rig_set_freq(rig, RIG_VFO_CURR, freq);
+                } else if (followModem.load()) {
+                    if (lastDemod) {
+                        if (lastDemod->getFrequency() != freq) {
+                            lastDemod->setFrequency(freq);
+                            lastDemod->updateLabel(freq);
+                            lastDemod->setFollow(true);
+                        }
+                    }
+                }
+            } else {
+                termStatus = 0;
+                break;
+            }
+        }
+        
+        if (!centerLock.load() && followModem.load() && wxGetApp().getFrequency() != freq && (lastDemod && lastDemod != activeDemod)) {
+            wxGetApp().setFrequency((long long)freq);
+        }
+        
+//        std::cout <<  "Rig Freq: " << freq << std::endl;
+    }
+    
+    rig_close(rig);
+    rig_cleanup(rig);
+    
+    std::cout << "Rig thread exiting status " << termStatus << "." << std::endl;
+};
+
+freq_t RigThread::getFrequency() {
+    if (freqChanged.load() && (setOneShot.load() || controlMode.load())) {
+        return newFreq;
+    } else {
+        return freq;
+    }
+}
+
+void RigThread::setFrequency(freq_t new_freq, bool oneShot) {
+    newFreq = new_freq;
+    freqChanged.store(true);
+    setOneShot.store(oneShot);
+}
+
+void RigThread::setControlMode(bool cMode) {
+    controlMode.store(cMode);
+}
+
+bool RigThread::getControlMode() {
+    return controlMode.load();
+}
+
+void RigThread::setFollowMode(bool fMode) {
+    followMode.store(fMode);
+}
+
+bool RigThread::getFollowMode() {
+    return followMode.load();
+}
+
+void RigThread::setCenterLock(bool cLock) {
+    centerLock.store(cLock);
+}
+
+bool RigThread::getCenterLock() {
+    return centerLock.load();
+}
+
+void RigThread::setFollowModem(bool mFollow) {
+    followModem.store(mFollow);
+}
+
+bool RigThread::getFollowModem() {
+    return followModem.load();
+}
diff --git a/src/rig/RigThread.h b/src/rig/RigThread.h
new file mode 100644
index 0000000..53b521f
--- /dev/null
+++ b/src/rig/RigThread.h
@@ -0,0 +1,58 @@
+#pragma once
+
+#include "IOThread.h"
+#include "CubicSDR.h"
+#include <hamlib/rig.h>
+#include <hamlib/riglist.h>
+
+struct rigGreater
+{
+    bool operator()( const struct rig_caps *lx, const struct rig_caps *rx ) const {
+        std::string ln(std::string(std::string(lx->mfg_name) + " " + std::string(lx->model_name)));
+        std::string rn(std::string(std::string(rx->mfg_name) + " " + std::string(rx->model_name)));
+    	return ln.compare(rn)<0;
+    }
+};
+
+typedef std::vector<const struct rig_caps *> RigList;
+
+class RigThread : public IOThread {
+public:
+    RigThread();
+    ~RigThread();
+
+    void initRig(rig_model_t rig_model, std::string rig_file, int serial_rate);
+    virtual void run();
+    
+    int terminationStatus();
+    
+    freq_t getFrequency();
+    void setFrequency(freq_t new_freq, bool oneShot);
+    
+    void setControlMode(bool cMode);
+    bool getControlMode();
+
+    void setFollowMode(bool fMode);
+    bool getFollowMode();
+
+    void setCenterLock(bool cLock);
+    bool getCenterLock();
+    
+    void setFollowModem(bool mFollow);
+    bool getFollowModem();
+
+    static RigList &enumerate();
+    static int add_hamlib_rig(const struct rig_caps *rc, void* f);
+    
+private:
+	RIG *rig;
+    rig_model_t rigModel;
+    std::string rigFile;
+    int serialRate;
+    int termStatus;
+    freq_t freq;
+    freq_t newFreq;
+    std::atomic_bool freqChanged, setOneShot;
+    std::atomic_bool controlMode, followMode, centerLock, followModem;
+    static RigList rigCaps;
+};
\ No newline at end of file
diff --git a/src/sdr/SDRDeviceInfo.cpp b/src/sdr/SDRDeviceInfo.cpp
new file mode 100644
index 0000000..1e326ed
--- /dev/null
+++ b/src/sdr/SDRDeviceInfo.cpp
@@ -0,0 +1,215 @@
+#include "SDRDeviceInfo.h"
+#include <cstdlib>
+#include <algorithm>
+
+SDRDeviceInfo::SDRDeviceInfo() : name(""), serial(""), available(false), remote(false), manual(false), soapyDevice(nullptr) {
+    active.store(false);
+}
+
+SDRDeviceInfo::~SDRDeviceInfo() {
+    if (soapyDevice != nullptr) {
+        SoapySDR::Device::unmake(soapyDevice);
+    }
+}
+
+std::string SDRDeviceInfo::getDeviceId() {
+    std::string deviceId;
+    
+    deviceId.append(getName());
+//    deviceId.append(" :: ");
+//    deviceId.append(getSerial());
+    
+    return deviceId;
+}
+
+int SDRDeviceInfo::getIndex() const {
+    return index;
+}
+
+void SDRDeviceInfo::setIndex(const int index) {
+    this->index = index;
+}
+
+bool SDRDeviceInfo::isAvailable() const {
+    return available;
+}
+
+void SDRDeviceInfo::setAvailable(bool available) {
+    this->available = available;
+}
+
+bool SDRDeviceInfo::isActive() const {
+    return active.load();
+}
+
+void SDRDeviceInfo::setActive(bool active) {
+    this->active.store(active);
+}
+
+const std::string& SDRDeviceInfo::getName() const {
+    return name;
+}
+
+void SDRDeviceInfo::setName(const std::string& name) {
+    this->name = name;
+}
+
+const std::string& SDRDeviceInfo::getSerial() const {
+    return serial;
+}
+
+void SDRDeviceInfo::setSerial(const std::string& serial) {
+    this->serial = serial;
+}
+
+const std::string& SDRDeviceInfo::getTuner() const {
+    return tuner;
+}
+
+void SDRDeviceInfo::setTuner(const std::string& tuner) {
+    this->tuner = tuner;
+}
+
+const std::string& SDRDeviceInfo::getManufacturer() const {
+    return manufacturer;
+}
+
+void SDRDeviceInfo::setManufacturer(const std::string& manufacturer) {
+    this->manufacturer = manufacturer;
+}
+
+const std::string& SDRDeviceInfo::getProduct() const {
+    return product;
+}
+
+void SDRDeviceInfo::setProduct(const std::string& product) {
+    this->product = product;
+}
+
+const std::string& SDRDeviceInfo::getDriver() const {
+    return driver;
+}
+
+void SDRDeviceInfo::setDriver(const std::string& driver) {
+    this->driver = driver;
+}
+
+const std::string& SDRDeviceInfo::getHardware() const {
+    return hardware;
+}
+
+void SDRDeviceInfo::setHardware(const std::string& hardware) {
+    this->hardware = hardware;
+}
+
+bool SDRDeviceInfo::hasTimestamps() const {
+    return timestamps;
+}
+
+void SDRDeviceInfo::setTimestamps(bool timestamps) {
+    this->timestamps = timestamps;
+}
+
+bool SDRDeviceInfo::isRemote() const {
+    return remote;
+}
+
+void SDRDeviceInfo::setRemote(bool remote) {
+    this->remote = remote;
+}
+
+bool SDRDeviceInfo::isManual() const {
+    return manual;
+}
+
+void SDRDeviceInfo::setManual(bool manual) {
+    this->manual = manual;
+}
+
+void SDRDeviceInfo::setManualParams(std::string manualParams) {
+    this->manual_params = manualParams;
+}
+
+std::string SDRDeviceInfo::getManualParams() {
+    return manual_params;
+}
+
+void SDRDeviceInfo::setDeviceArgs(SoapySDR::Kwargs deviceArgs) {
+    this->deviceArgs = deviceArgs;
+}
+
+SoapySDR::Kwargs SDRDeviceInfo::getDeviceArgs() {
+    return deviceArgs;
+}
+
+void SDRDeviceInfo::setStreamArgs(SoapySDR::Kwargs streamArgs) {
+    this->streamArgs = streamArgs;
+}
+
+SoapySDR::Kwargs SDRDeviceInfo::getStreamArgs() {
+    return streamArgs;
+}
+
+void SDRDeviceInfo::setSoapyDevice(SoapySDR::Device *dev) {
+    if (soapyDevice) {
+        SoapySDR::Device::unmake(soapyDevice);
+    }
+    soapyDevice = dev;
+}
+
+SoapySDR::Device *SDRDeviceInfo::getSoapyDevice() {
+    if (soapyDevice == nullptr) {
+        soapyDevice = SoapySDR::Device::make(deviceArgs);
+    }
+    return soapyDevice;
+}
+
+bool SDRDeviceInfo::hasCORR(int direction, size_t channel) {
+    SoapySDR::Device *dev = getSoapyDevice();
+    
+    std::vector<std::string> freqs = dev->listFrequencies(direction, channel);
+    if (std::find(freqs.begin(), freqs.end(), "CORR") != freqs.end()) {
+        return true;
+    } else {
+        return false;
+    }
+}
+
+std::vector<long> SDRDeviceInfo::getSampleRates(int direction, size_t channel) {
+    SoapySDR::Device *dev = getSoapyDevice();
+    
+    std::vector<long> result;
+    std::vector<double> sampleRates = dev->listSampleRates(direction, channel);
+    for (std::vector<double>::iterator si = sampleRates.begin(); si != sampleRates.end(); si++) {
+        result.push_back((long)(*si));
+    }
+    
+    return result;
+}
+
+long SDRDeviceInfo::getSampleRateNear(int direction, size_t channel, long sampleRate_in) {
+    std::vector<long> sampleRates = getSampleRates(direction, channel);
+    long returnRate = sampleRates[0];
+    long sDelta = (long)sampleRate_in-sampleRates[0];
+    long minDelta = std::abs(sDelta);
+    for (std::vector<long>::iterator i = sampleRates.begin(); i != sampleRates.end(); i++) {
+        long thisDelta = std::abs(sampleRate_in - (*i));
+        if (thisDelta < minDelta) {
+            minDelta = thisDelta;
+            returnRate = (*i);
+        }
+    }
+    return returnRate;
+}
+
+SDRRangeMap SDRDeviceInfo::getGains(int direction, size_t channel) {
+    SoapySDR::Device *dev = getSoapyDevice();
+    std::vector<std::string> gainNames = dev->listGains(direction, channel);
+    std::map<std::string, SoapySDR::Range> gainMap;
+    
+    for (std::vector<std::string>::iterator gname = gainNames.begin(); gname!= gainNames.end(); gname++) {
+        gainMap[(*gname)] = dev->getGainRange(direction, channel, (*gname));
+    }
+    
+    return gainMap;
+}
diff --git a/src/sdr/SDRDeviceInfo.h b/src/sdr/SDRDeviceInfo.h
new file mode 100644
index 0000000..77714de
--- /dev/null
+++ b/src/sdr/SDRDeviceInfo.h
@@ -0,0 +1,97 @@
+#pragma once
+
+#include <string>
+#include <vector>
+#include <atomic>
+
+#include <SoapySDR/Types.hpp>
+#include <SoapySDR/Device.hpp>
+
+typedef struct _SDRManualDef {
+    std::string factory;
+    std::string params;
+} SDRManualDef;
+
+typedef std::map<std::string, SoapySDR::Range> SDRRangeMap;
+
+class SDRDeviceInfo {
+public:
+    SDRDeviceInfo();
+    ~SDRDeviceInfo();
+    
+    std::string getDeviceId();
+    
+    int getIndex() const;
+    void setIndex(const int index);
+    
+    bool isAvailable() const;
+    void setAvailable(bool available);
+
+    bool isActive() const;
+    void setActive(bool active);
+
+    const std::string& getName() const;
+    void setName(const std::string& name);
+    
+    const std::string& getSerial() const;
+    void setSerial(const std::string& serial);
+    
+    const std::string& getTuner() const;
+    void setTuner(const std::string& tuner);
+    
+    const std::string& getManufacturer() const;
+    void setManufacturer(const std::string& manufacturer);
+    
+    const std::string& getProduct() const;
+    void setProduct(const std::string& product);
+
+    const std::string& getDriver() const;
+    void setDriver(const std::string& driver);
+    
+    const std::string& getHardware() const;
+    void setHardware(const std::string& hardware);
+    
+    bool hasTimestamps() const;
+    void setTimestamps(bool timestamps);
+
+    bool isRemote() const;
+    void setRemote(bool remote);
+
+    bool isManual() const;
+    void setManual(bool manual);
+    
+    void setManualParams(std::string manualParams);
+    std::string getManualParams();
+    
+    void setDeviceArgs(SoapySDR::Kwargs deviceArgs);
+    SoapySDR::Kwargs getDeviceArgs();
+
+    void setStreamArgs(SoapySDR::Kwargs deviceArgs);
+    SoapySDR::Kwargs getStreamArgs();
+
+//    void setSettingsInfo(SoapySDR::ArgInfoList settingsArgs);
+//    SoapySDR::ArgInfoList getSettingsArgInfo();
+
+//    std::vector<std::string> getSettingNames();
+    
+    void setSoapyDevice(SoapySDR::Device *dev);
+    SoapySDR::Device *getSoapyDevice();
+    
+    bool hasCORR(int direction, size_t channel);
+    
+    std::vector<long> getSampleRates(int direction, size_t channel);
+    
+    long getSampleRateNear(int direction, size_t channel, long sampleRate_in);
+
+    SDRRangeMap getGains(int direction, size_t channel);
+
+private:
+    int index;
+    std::string name, serial, product, manufacturer, tuner;
+    std::string driver, hardware, manual_params;
+    bool timestamps, available, remote, manual;
+    std::atomic_bool active;
+    
+    SoapySDR::Kwargs deviceArgs, streamArgs;
+    SoapySDR::Device *soapyDevice;
+};
diff --git a/src/sdr/SDREnumerator.cpp b/src/sdr/SDREnumerator.cpp
new file mode 100644
index 0000000..116234b
--- /dev/null
+++ b/src/sdr/SDREnumerator.cpp
@@ -0,0 +1,413 @@
+#include "SDREnumerator.h"
+#include "CubicSDRDefs.h"
+#include <vector>
+#include "CubicSDR.h"
+#include <string>
+
+#ifdef WIN32
+#include <locale>
+#endif
+
+
+std::vector<std::string> SDREnumerator::factories;
+std::vector<std::string> SDREnumerator::modules;
+std::vector<std::string> SDREnumerator::remotes;
+std::map< std::string, std::vector<SDRDeviceInfo *> > SDREnumerator::devs;
+bool SDREnumerator::soapy_initialized = false;
+bool SDREnumerator::has_remote = false;
+std::vector<SDRManualDef> SDREnumerator::manuals;
+
+SDREnumerator::SDREnumerator() : IOThread() {
+  
+}
+
+SDREnumerator::~SDREnumerator() {
+
+}
+
+// Some utility from SoapySDR :)
+static std::string trim(const std::string &s)
+{
+#if WIN32
+	std::string out = s;
+	locale loc("");
+	while (not out.empty() and std::isspace(out[0], loc)) out = out.substr(1);
+	while (not out.empty() and std::isspace(out[out.size() - 1], loc)) out = out.substr(0, out.size() - 1);
+	return out;
+#else
+	std::string out = s;
+    while (not out.empty() and std::isspace(out[0])) out = out.substr(1);
+    while (not out.empty() and std::isspace(out[out.size()-1])) out = out.substr(0, out.size()-1);
+    return out;
+#endif
+}
+
+SoapySDR::Kwargs SDREnumerator::argsStrToKwargs(const std::string &args)
+{
+    SoapySDR::Kwargs kwargs;
+    
+    bool inKey = true;
+    std::string key, val;
+    for (size_t i = 0; i < args.size(); i++)
+    {
+        const char ch = args[i];
+        if (inKey)
+        {
+            if (ch == '=') inKey = false;
+            else if (ch == ',') inKey = true;
+            else key += ch;
+        }
+        else
+        {
+            if (ch == ',') inKey = true;
+            else val += ch;
+        }
+        if ((inKey and not val.empty()) or ((i+1) == args.size()))
+        {
+            key = trim(key);
+            val = trim(val);
+            if (not key.empty()) kwargs[key] = val;
+            key = "";
+            val = "";
+        }
+    }
+    
+    return kwargs;
+}
+
+
+std::vector<SDRDeviceInfo *> *SDREnumerator::enumerate_devices(std::string remoteAddr, bool noInit) {
+
+    if (SDREnumerator::devs[remoteAddr].size()) {
+        return &SDREnumerator::devs[remoteAddr];
+    }
+    
+    if (noInit) {
+        return NULL;
+    }
+    
+    if (!soapy_initialized) {
+        std::cout << "SoapySDR init.." << std::endl;
+        std::cout << "\tAPI Version: v" << SoapySDR::getAPIVersion() << std::endl;
+        std::cout << "\tABI Version: v" << SoapySDR::getABIVersion() << std::endl;
+        std::cout << "\tInstall root: " << SoapySDR::getRootPath() << std::endl;
+        
+        std::cout << "\tLoading modules... " << std::endl;
+        
+        std::string userModPath = wxGetApp().getModulePath();
+        
+        if (userModPath != "") {
+            wxGetApp().sdrEnumThreadNotify(SDREnumerator::SDR_ENUM_MESSAGE, "Loading SoapySDR modules from " + userModPath + "..");
+            std::vector<std::string> localMods = SoapySDR::listModules(userModPath);
+            for (std::vector<std::string>::iterator mods_i = localMods.begin(); mods_i != localMods.end(); mods_i++) {
+                wxGetApp().sdrEnumThreadNotify(SDREnumerator::SDR_ENUM_MESSAGE, "Initializing user specified SoapySDR module " + (*mods_i) + "..");
+                std::cout << "Initializing user specified SoapySDR module " << (*mods_i) <<  ".." << std::endl;
+                SoapySDR::loadModule(*mods_i);
+            }
+        } else {
+            #ifdef BUNDLE_SOAPY_MODS
+			#ifdef BUNDLED_MODS_ONLY
+			wxFileName exePath = wxFileName(wxStandardPaths::Get().GetExecutablePath());
+			std::vector<std::string> localMods = SoapySDR::listModules(exePath.GetPath().ToStdString() + "/modules/");
+			for (std::vector<std::string>::iterator mods_i = localMods.begin(); mods_i != localMods.end(); mods_i++) {
+				wxGetApp().sdrEnumThreadNotify(SDREnumerator::SDR_ENUM_MESSAGE, "Initializing bundled SoapySDR module " + (*mods_i) + "..");
+				std::cout << "Loading bundled SoapySDR module " << (*mods_i) << ".." << std::endl;
+				SoapySDR::loadModule(*mods_i);
+			}
+			#else
+            bool localModPref = wxGetApp().getUseLocalMod();
+            if (localModPref) {
+                wxGetApp().sdrEnumThreadNotify(SDREnumerator::SDR_ENUM_MESSAGE, "Loading SoapySDR modules..");
+                std::cout << "Checking local system SoapySDR modules.." << std::flush;
+                SoapySDR::loadModules();
+            }
+
+            wxFileName exePath = wxFileName(wxStandardPaths::Get().GetExecutablePath());
+            std::vector<std::string> localMods = SoapySDR::listModules(exePath.GetPath().ToStdString() + "/modules/");
+            for (std::vector<std::string>::iterator mods_i = localMods.begin(); mods_i != localMods.end(); mods_i++) {
+                wxGetApp().sdrEnumThreadNotify(SDREnumerator::SDR_ENUM_MESSAGE, "Initializing bundled SoapySDR module " + (*mods_i) + "..");
+                std::cout << "Loading bundled SoapySDR module " << (*mods_i) <<  ".." << std::endl;
+                SoapySDR::loadModule(*mods_i);
+            }
+        
+            if (!localModPref) {
+                wxGetApp().sdrEnumThreadNotify(SDREnumerator::SDR_ENUM_MESSAGE, "Loading SoapySDR modules..");
+                std::cout << "Checking system SoapySDR modules.." << std::flush;
+                SoapySDR::loadModules();
+            }
+			#endif
+            #else
+            wxGetApp().sdrEnumThreadNotify(SDREnumerator::SDR_ENUM_MESSAGE, "Loading SoapySDR modules..");
+            SoapySDR::loadModules();
+            #endif
+
+        }
+        
+        if (SDREnumerator::factories.size()) {
+            SDREnumerator::factories.erase(SDREnumerator::factories.begin(), SDREnumerator::factories.end());
+        }
+        
+        std::cout << "\tAvailable factories...";
+        SoapySDR::FindFunctions factories = SoapySDR::Registry::listFindFunctions();
+        for (SoapySDR::FindFunctions::const_iterator it = factories.begin(); it != factories.end(); ++it) {
+            if (it != factories.begin()) {
+                std::cout << ", ";
+            }
+            std::cout << it->first;
+            
+            if (it->first == "remote") {
+                has_remote = true;
+            }
+            SDREnumerator::factories.push_back(it->first);
+        }
+        if (factories.empty()) {
+            std::cout << "No factories found!" << std::endl;
+        }
+        if ((factories.size() == 1) && factories.find("null") != factories.end()) {
+            std::cout << "Just 'null' factory found." << std::endl;
+            wxGetApp().sdrEnumThreadNotify(SDREnumerator::SDR_ENUM_FAILED, std::string("No modules available."));
+        }
+        std::cout << std::endl;
+        soapy_initialized = true;
+    }
+    
+    modules = SoapySDR::listModules();
+
+    std::vector<SoapySDR::Kwargs> results;
+    SoapySDR::Kwargs enumArgs;
+    bool isRemote = false;
+    
+    if (remoteAddr.length()) {
+        std::cout << "Enumerating remote address: " << remoteAddr << std::endl;
+        enumArgs["driver"] = "remote";
+        enumArgs["remote"] = remoteAddr;
+        isRemote = true;
+        
+        results = SoapySDR::Device::enumerate(enumArgs);
+    } else {
+        results = SoapySDR::Device::enumerate();
+    }
+    
+    size_t manualsIdx = results.size();
+    std::vector<std::string> manualParams;
+    std::vector<bool> manualResult;
+    
+    if (manuals.size()) {
+        for (std::vector<SDRManualDef>::const_iterator m_i = manuals.begin(); m_i != manuals.end(); m_i++) {
+            std::vector<SoapySDR::Kwargs> manual_result;
+
+            std::string strDevArgs = "driver="+m_i->factory+","+m_i->params;
+            
+            manualParams.push_back(m_i->params);
+            
+            wxGetApp().sdrEnumThreadNotify(SDREnumerator::SDR_ENUM_MESSAGE, std::string("Enumerating manual device '") + strDevArgs + "'..");
+
+            manual_result = SoapySDR::Device::enumerate(strDevArgs);
+            
+            if (manual_result.size()) {
+                for (std::vector<SoapySDR::Kwargs>::const_iterator i = manual_result.begin(); i != manual_result.end(); i++) {
+                    results.push_back(*i);
+                    manualResult.push_back(true);
+                }
+            } else {
+                SoapySDR::Kwargs failedEnum;
+                failedEnum = argsStrToKwargs(strDevArgs);
+                failedEnum["label"] = "Not Found ("+m_i->factory+")";
+                results.push_back(failedEnum);
+                manualResult.push_back(false);
+            }
+        }
+    }
+    
+    if (isRemote) {
+        wxGetApp().sdrEnumThreadNotify(SDREnumerator::SDR_ENUM_MESSAGE, std::string("Opening remote server ") + remoteAddr + "..");
+    }
+    for (size_t i = 0; i < results.size(); i++) {
+        SDRDeviceInfo *dev = new SDRDeviceInfo();
+        
+        SoapySDR::Kwargs deviceArgs = results[i];
+
+        for (SoapySDR::Kwargs::const_iterator it = deviceArgs.begin(); it != deviceArgs.end(); ++it) {
+            std::cout << "  " << it->first << " = " << it->second << std::endl;
+            if (it->first == "driver") {
+                dev->setDriver(it->second);
+            } else if (it->first == "label" || it->first == "device") {
+                dev->setName(it->second);
+			}
+        }
+        
+        
+        if (deviceArgs.count("remote")) {
+            isRemote = true;
+        } else {
+            isRemote = false;
+        }
+        
+        dev->setRemote(isRemote);
+        dev->setManual(i>=manualsIdx);
+        if (i>=manualsIdx) {
+            dev->setManualParams(manualParams[i-manualsIdx]);
+        }
+        
+        std::cout << "Make device " << i << std::endl;
+        if (i<manualsIdx || manualResult[i-manualsIdx]) try {
+            SoapySDR::Device *device = SoapySDR::Device::make(deviceArgs);
+            SoapySDR::Kwargs info = device->getHardwareInfo();
+            for (SoapySDR::Kwargs::const_iterator it = info.begin(); it != info.end(); ++it) {
+                std::cout << "  " << it->first << "=" << it->second << std::endl;
+                if (it->first == "hardware") {
+                    dev->setHardware(it->second);
+                }
+            }
+            
+            if (isRemote) {
+                wxGetApp().sdrEnumThreadNotify(SDREnumerator::SDR_ENUM_MESSAGE, "Querying remote " + remoteAddr + " device #" + std::to_string(i) + ": " + dev-> getName());
+            } else {
+                wxGetApp().sdrEnumThreadNotify(SDREnumerator::SDR_ENUM_MESSAGE, std::string("Querying device #") + std::to_string(i) + ": " + dev->getName());
+            }
+
+            SoapySDR::ArgInfoList settingsInfo = device->getSettingInfo();
+
+            DeviceConfig *cfg = wxGetApp().getConfig()->getDevice(dev->getDeviceId());
+
+            ConfigSettings devSettings = cfg->getSettings();
+            if (devSettings.size()) {
+                for (ConfigSettings::const_iterator set_i = devSettings.begin(); set_i != devSettings.end(); set_i++) {
+                    deviceArgs[set_i->first] = set_i->second;
+                }
+                for (size_t j = 0; j < settingsInfo.size(); j++) {
+                    if (deviceArgs.find(settingsInfo[j].key) != deviceArgs.end()) {
+                        settingsInfo[j].value = deviceArgs[settingsInfo[j].key];
+                    }
+                }
+            }
+            
+            dev->setDeviceArgs(deviceArgs);
+            
+            SoapySDR::Device::unmake(device);
+            dev->setAvailable(true);
+        } catch (const std::exception &ex) {
+            std::cerr << "Error making device: " << ex.what() << std::endl;
+            wxGetApp().sdrEnumThreadNotify(SDREnumerator::SDR_ENUM_MESSAGE, std::string("Error querying device #") + std::to_string(i));
+            dev->setAvailable(false);
+        } else {
+            dev->setAvailable(false);
+        }
+        std::cout << std::endl;
+
+        SDREnumerator::devs[remoteAddr].push_back(dev);
+    }
+    if (SDREnumerator::devs[remoteAddr].empty()) {
+        wxGetApp().sdrEnumThreadNotify(SDREnumerator::SDR_ENUM_MESSAGE, std::string("No devices found!"));
+    }
+    std::cout << std::endl;
+
+    return &SDREnumerator::devs[remoteAddr];
+}
+
+
+void SDREnumerator::run() {
+
+    std::cout << "SDR enumerator starting." << std::endl;
+    
+
+    wxGetApp().sdrEnumThreadNotify(SDREnumerator::SDR_ENUM_MESSAGE, "Scanning local devices, please wait..");
+    SDREnumerator::enumerate_devices("");
+
+    if (remotes.size()) {
+        std::vector<std::string>::iterator remote_i;
+        for (remote_i = remotes.begin(); remote_i != remotes.end(); remote_i++) {
+            wxGetApp().sdrEnumThreadNotify(SDREnumerator::SDR_ENUM_MESSAGE, "Scanning devices at " + (*remote_i) + ", please wait..");
+            SDREnumerator::enumerate_devices(*remote_i);
+        }
+    }
+    
+    std::cout << "Reporting enumeration complete." << std::endl;
+    wxGetApp().sdrEnumThreadNotify(SDREnumerator::SDR_ENUM_DEVICES_READY, "Finished scanning devices.");
+    std::cout << "SDR enumerator done." << std::endl;
+
+}
+
+
+
+void SDREnumerator::addRemote(std::string remoteAddr) {
+    std::vector<std::string>::iterator remote_i = std::find(remotes.begin(), remotes.end(), remoteAddr);
+    
+    if (remote_i != remotes.end()) {
+        return;
+    } else {
+        remotes.push_back(remoteAddr);
+    }
+}
+
+void SDREnumerator::removeRemote(std::string remoteAddr) {
+    std::vector<std::string>::iterator remote_i = std::find(remotes.begin(), remotes.end(), remoteAddr);
+    
+    if (remote_i != remotes.end()) {
+        if (devs.find(*remote_i) != devs.end()) {
+            while (devs[*remote_i].size()) {
+                SDRDeviceInfo *devRemove = devs[*remote_i].back();
+                devs[*remote_i].pop_back();
+                delete devRemove;
+            }
+        }
+        remotes.erase(remote_i);
+    } else {
+        return;
+    }
+}
+
+std::vector<std::string> &SDREnumerator::getRemotes() {
+    return remotes;
+}
+
+void SDREnumerator::addManual(std::string factory, std::string params) {
+    SDRManualDef def;
+    def.factory = factory;
+    def.params = params;
+    manuals.push_back(def);
+}
+
+void SDREnumerator::removeManual(std::string factory, std::string params) {
+    for (std::vector<SDRManualDef>::iterator i = manuals.begin(); i != manuals.end(); i++) {
+        if (i->factory == factory && i->params == params) {
+            manuals.erase(i);
+            for (std::vector<SDRDeviceInfo *>::iterator subdevs_i = devs[""].begin(); subdevs_i != devs[""].end(); subdevs_i++) {
+                if ((*subdevs_i)->isManual() && (*subdevs_i)->getDriver() == factory && (*subdevs_i)->getManualParams() == params) {
+                    devs[""].erase(subdevs_i);
+                    break;
+                }
+            }
+            break;
+        }
+    }
+}
+
+std::vector<SDRManualDef> &SDREnumerator::getManuals() {
+    return SDREnumerator::manuals;
+}
+
+void SDREnumerator::setManuals(std::vector<SDRManualDef> manuals) {
+    SDREnumerator::manuals = manuals;
+}
+
+bool SDREnumerator::hasRemoteModule() {
+    return SDREnumerator::has_remote;
+}
+
+void SDREnumerator::reset() {
+    soapy_initialized = false;
+    factories.erase(factories.begin(), factories.end());
+    modules.erase(modules.begin(), modules.end());
+    for (std::map< std::string, std::vector<SDRDeviceInfo *> >::iterator di = devs.begin(); di != devs.end(); di++) {
+        for (std::vector<SDRDeviceInfo *>::iterator i = di->second.begin(); i != di->second.end(); i++) {
+            (*i)->setSoapyDevice(nullptr);
+        }
+        
+    }
+    devs.erase(devs.begin(), devs.end());
+}
+
+std::vector<std::string> &SDREnumerator::getFactories() {
+    return SDREnumerator::factories;
+}
diff --git a/src/sdr/SDREnumerator.h b/src/sdr/SDREnumerator.h
new file mode 100644
index 0000000..6511243
--- /dev/null
+++ b/src/sdr/SDREnumerator.h
@@ -0,0 +1,48 @@
+#pragma once
+
+#include <atomic>
+#include <map>
+#include <string>
+#include "IOThread.h"
+#include "SDRDeviceInfo.h"
+#include "AppConfig.h"
+
+#include <SoapySDR/Version.hpp>
+#include <SoapySDR/Modules.hpp>
+#include <SoapySDR/Registry.hpp>
+#include <SoapySDR/Device.hpp>
+
+
+class SDREnumerator: public IOThread {
+private:
+
+public:
+    SDREnumerator();
+    ~SDREnumerator();
+    enum SDREnumState { SDR_ENUM_DEVICES_READY, SDR_ENUM_MESSAGE, SDR_ENUM_TERMINATED, SDR_ENUM_FAILED };
+    
+    static std::vector<SDRDeviceInfo *> *enumerate_devices(std::string remoteAddr = "", bool noInit=false);
+
+    virtual void run();
+
+    static SoapySDR::Kwargs argsStrToKwargs(const std::string &args);
+    static void addRemote(std::string remoteAddr);
+    static void removeRemote(std::string remoteAddr);
+    static std::vector<std::string> &getRemotes();
+    static bool hasRemoteModule();
+    static void addManual(std::string factory, std::string params);
+    static void removeManual(std::string factory, std::string params);
+    static std::vector<SDRManualDef> &getManuals();
+    static void setManuals(std::vector<SDRManualDef> manuals);
+    static void reset();
+    static std::vector<std::string> &getFactories();
+    
+protected:
+    static bool soapy_initialized, has_remote;
+    static std::vector<std::string> factories;
+    static std::vector<std::string> modules;
+    static std::vector<std::string> remotes;
+    static std::map< std::string, std::vector<SDRDeviceInfo *> > devs;
+    static std::vector<SDRManualDef> manuals;
+    static std::mutex devs_busy;
+};
diff --git a/src/sdr/SDRPostThread.cpp b/src/sdr/SDRPostThread.cpp
new file mode 100644
index 0000000..052969e
--- /dev/null
+++ b/src/sdr/SDRPostThread.cpp
@@ -0,0 +1,492 @@
+#include "SDRPostThread.h"
+#include "CubicSDRDefs.h"
+#include "CubicSDR.h"
+
+#include <vector>
+#include <deque>
+
+SDRPostThread::SDRPostThread() : IOThread(), buffers("SDRPostThreadBuffers"), visualDataBuffers("SDRPostThreadVisualDataBuffers"), frequency(0) {
+    iqDataInQueue = NULL;
+    iqDataOutQueue = NULL;
+    iqVisualQueue = NULL;
+
+    numChannels = 0;
+    channelizer = NULL;
+    
+    sampleRate = 0;
+    nRunDemods = 0;
+    
+    visFrequency.store(0);
+    visBandwidth.store(0);
+    
+    doRefresh.store(false);
+    dcFilter = iirfilt_crcf_create_dc_blocker(0.0005f);
+}
+
+SDRPostThread::~SDRPostThread() {
+}
+
+void SDRPostThread::bindDemodulator(DemodulatorInstance *demod) {
+    
+    std::lock_guard < std::mutex > lock(busy_demod);
+
+    demodulators.push_back(demod);
+    doRefresh.store(true);
+   
+}
+
+void SDRPostThread::bindDemodulators(std::vector<DemodulatorInstance *> *demods) {
+    if (!demods) {
+        return;
+    }
+    std::lock_guard < std::mutex > lock(busy_demod);
+
+    for (std::vector<DemodulatorInstance *>::iterator di = demods->begin(); di != demods->end(); di++) {
+        demodulators.push_back(*di);
+        doRefresh.store(true);
+    }
+   
+}
+
+void SDRPostThread::removeDemodulator(DemodulatorInstance *demod) {
+    if (!demod) {
+        return;
+    }
+
+    std::lock_guard < std::mutex > lock(busy_demod);
+
+    std::vector<DemodulatorInstance *>::iterator i = std::find(demodulators.begin(), demodulators.end(), demod);
+    
+    if (i != demodulators.end()) {
+        demodulators.erase(i);
+        doRefresh.store(true);
+    }
+  
+}
+
+void SDRPostThread::initPFBChannelizer() {
+//    std::cout << "Initializing post-process FIR polyphase filterbank channelizer with " << numChannels << " channels." << std::endl;
+    if (channelizer) {
+        firpfbch_crcf_destroy(channelizer);
+    }
+    channelizer = firpfbch_crcf_create_kaiser(LIQUID_ANALYZER, numChannels, 4, 60);
+    
+    chanBw = (sampleRate / numChannels);
+    
+    chanCenters.resize(numChannels+1);
+    demodChannelActive.resize(numChannels+1);
+    
+//    std::cout << "Channel bandwidth spacing: " << (chanBw) << std::endl;
+}
+
+void SDRPostThread::updateActiveDemodulators() {
+    // In range?
+    std::vector<DemodulatorInstance *>::iterator demod_i;
+    
+    nRunDemods = 0;
+    
+    long long centerFreq = wxGetApp().getFrequency();
+
+    for (demod_i = demodulators.begin(); demod_i != demodulators.end(); demod_i++) {
+        DemodulatorInstance *demod = *demod_i;
+        DemodulatorThreadInputQueue *demodQueue = demod->getIQInputDataPipe();
+        
+        // not in range?
+        if (demod->isDeltaLock()) {
+            if (demod->getFrequency() != centerFreq + demod->getDeltaLockOfs()) {
+                demod->setFrequency(centerFreq + demod->getDeltaLockOfs());
+                demod->updateLabel(demod->getFrequency());
+                demod->setFollow(false);
+                demod->setTracking(false);
+            }
+        }
+        
+        if (abs(frequency - demod->getFrequency()) > (sampleRate / 2)) {
+            // deactivate if active
+            if (demod->isActive() && !demod->isFollow() && !demod->isTracking()) {
+                demod->setActive(false);
+            //    DemodulatorThreadIQData *dummyDataOut = new DemodulatorThreadIQData;
+            //    dummyDataOut->frequency = frequency;
+            //    dummyDataOut->sampleRate = sampleRate;
+            //    if (!demodQueue->push(dummyDataOut)) {
+            //        delete dummyDataOut;
+            //    }
+            }
+            
+            // follow if follow mode
+            if (demod->isFollow() && centerFreq != demod->getFrequency()) {
+                wxGetApp().setFrequency(demod->getFrequency());
+                demod->setFollow(false);
+            }
+        } else if (!demod->isActive()) { // in range, activate if not activated
+            demod->setActive(true);
+            if (wxGetApp().getDemodMgr().getLastActiveDemodulator() == NULL) {
+
+                wxGetApp().getDemodMgr().setActiveDemodulator(demod);
+            }
+        }
+        
+        if (!demod->isActive()) {
+            continue;
+        }
+        
+        // Add to the current run
+        if (nRunDemods == runDemods.size()) {
+            runDemods.push_back(demod);
+            demodChannel.push_back(-1);
+        } else {
+            runDemods[nRunDemods] = demod;
+            demodChannel[nRunDemods] = -1;
+        }
+        nRunDemods++;
+    }
+}
+
+void SDRPostThread::updateChannels() {
+    // calculate channel center frequencies, todo: cache
+    for (int i = 0; i < numChannels/2; i++) {
+        int ofs = ((chanBw) * i);
+        chanCenters[i] = frequency + ofs;
+        chanCenters[i+(numChannels/2)] = frequency - (sampleRate/2) + ofs;
+    }
+    chanCenters[numChannels] = frequency + (sampleRate/2);
+}
+
+int SDRPostThread::getChannelAt(long long frequency) {
+    int chan = -1;
+    long long minDelta = sampleRate;
+    for (int i = 0; i < numChannels+1; i++) {
+        long long fdelta = abs(frequency - chanCenters[i]);
+        if (fdelta < minDelta) {
+            minDelta = fdelta;
+            chan = i;
+        }
+    }
+    return chan;
+}
+
+void SDRPostThread::setIQVisualRange(long long frequency, int bandwidth) {
+    visFrequency.store(frequency);
+    visBandwidth.store(bandwidth);
+}
+
+void SDRPostThread::run() {
+#ifdef __APPLE__
+    pthread_t tID = pthread_self();  // ID of this thread
+    int priority = sched_get_priority_max( SCHED_FIFO);
+    sched_param prio = {priority}; // scheduling priority of thread
+    pthread_setschedparam(tID, SCHED_FIFO, &prio);
+#endif
+
+//    std::cout << "SDR post-processing thread started.." << std::endl;
+
+    iqDataInQueue = static_cast<SDRThreadIQDataQueue*>(getInputQueue("IQDataInput"));
+    iqDataOutQueue = static_cast<DemodulatorThreadInputQueue*>(getOutputQueue("IQDataOutput"));
+    iqVisualQueue = static_cast<DemodulatorThreadInputQueue*>(getOutputQueue("IQVisualDataOutput"));
+    iqActiveDemodVisualQueue = static_cast<DemodulatorThreadInputQueue*>(getOutputQueue("IQActiveDemodVisualDataOutput"));
+    
+    while (!stopping) {
+        SDRThreadIQData *data_in;
+        
+        iqDataInQueue->pop(data_in);
+        //        std::lock_guard < std::mutex > lock(data_in->m_mutex);
+
+        std::lock_guard < std::mutex > lock(busy_demod);
+
+        if (data_in && data_in->data.size()) {
+            if(data_in->numChannels > 1) {
+                runPFBCH(data_in);
+            } else {
+                runSingleCH(data_in);
+            }
+        }
+
+        if (data_in) {
+            data_in->decRefCount();   
+        }
+
+        bool doUpdate = false;
+        for (size_t j = 0; j < nRunDemods; j++) {
+            DemodulatorInstance *demod = runDemods[j];
+            if (abs(frequency - demod->getFrequency()) > (sampleRate / 2)) {
+                doUpdate = true;
+            }
+        }
+        
+        //Only update the list of demodulators here
+        if (doUpdate) {
+            updateActiveDemodulators();
+        }
+    } //end while
+    
+    //Be safe, remove as many elements as possible
+    DemodulatorThreadIQData *visualDataDummy;
+    while (iqVisualQueue && iqVisualQueue->try_pop(visualDataDummy)) {
+        visualDataDummy->decRefCount();        
+    }
+
+    //    buffers.purge();
+    //    visualDataBuffers.purge();
+
+//    std::cout << "SDR post-processing thread done." << std::endl;
+}
+
+void SDRPostThread::terminate() {
+    IOThread::terminate();
+    SDRThreadIQData *dummy = new SDRThreadIQData;
+    if (!iqDataInQueue->push(dummy)) {
+        delete dummy;
+    }
+}
+
+void SDRPostThread::runSingleCH(SDRThreadIQData *data_in) {
+    if (sampleRate != data_in->sampleRate) {
+        sampleRate = data_in->sampleRate;
+        numChannels = 1;
+        doRefresh.store(true);
+    }
+    
+    size_t dataSize = data_in->data.size();
+    size_t outSize = data_in->data.size();
+    
+    if (outSize > dataOut.capacity()) {
+        dataOut.reserve(outSize);
+    }
+    if (outSize != dataOut.size()) {
+        dataOut.resize(outSize);
+    }
+    
+    if (frequency != data_in->frequency) {
+        frequency = data_in->frequency;
+        doRefresh.store(true);
+    }
+    
+    if (doRefresh.load()) {
+        updateActiveDemodulators();
+        doRefresh.store(false);
+    }
+    
+    size_t refCount = nRunDemods;
+    bool doIQDataOut = (iqDataOutQueue != NULL && !iqDataOutQueue->full());
+    bool doDemodVisOut = (nRunDemods && iqActiveDemodVisualQueue != NULL && !iqActiveDemodVisualQueue->full());
+    bool doVisOut = (iqVisualQueue != NULL && !iqVisualQueue->full());
+    
+    if (doIQDataOut) {
+        refCount++;
+    }
+    if (doDemodVisOut) {
+        refCount++;
+    }
+    if (doVisOut) {
+        refCount++;
+    }
+    
+    if (refCount) {
+        DemodulatorThreadIQData *demodDataOut = buffers.getBuffer();
+        demodDataOut->setRefCount(refCount);
+        demodDataOut->frequency = frequency;
+        demodDataOut->sampleRate = sampleRate;
+        
+        if (demodDataOut->data.size() != dataSize) {
+            if (demodDataOut->data.capacity() < dataSize) {
+                demodDataOut->data.reserve(dataSize);
+            }
+            demodDataOut->data.resize(dataSize);
+        }
+        
+        iirfilt_crcf_execute_block(dcFilter, &data_in->data[0], dataSize, &demodDataOut->data[0]);
+
+        if (doDemodVisOut) {
+            if (!iqActiveDemodVisualQueue->push(demodDataOut)) {
+                demodDataOut->decRefCount();
+                std::cout << "SDRPostThread::runSingleCH() cannot push demodDataOut into iqActiveDemodVisualQueue, is full !" << std::endl;
+                std::this_thread::yield();
+            }
+        }
+        
+        if (doIQDataOut) {
+            if (!iqDataOutQueue->push(demodDataOut)) {
+                demodDataOut->decRefCount();
+                std::cout << "SDRPostThread::runSingleCH() cannot push demodDataOut into iqDataOutQueue, is full !" << std::endl;
+                std::this_thread::yield();
+            }
+        }
+
+        if (doVisOut) {
+            if (!iqVisualQueue->push(demodDataOut)) {
+                demodDataOut->decRefCount();
+                std::cout << "SDRPostThread::runSingleCH() cannot push demodDataOut into iqVisualQueue, is full !" << std::endl;
+                std::this_thread::yield();
+            }
+        }
+        
+        for (size_t i = 0; i < nRunDemods; i++) {
+            if (!runDemods[i]->getIQInputDataPipe()->push(demodDataOut)) {
+                demodDataOut->decRefCount();
+                std::this_thread::yield();
+           }
+        }
+    }
+}
+
+void SDRPostThread::runPFBCH(SDRThreadIQData *data_in) {
+    if (numChannels != data_in->numChannels || sampleRate != data_in->sampleRate) {
+        numChannels = data_in->numChannels;
+        sampleRate = data_in->sampleRate;
+        initPFBChannelizer();
+        doRefresh.store(true);
+    }
+    
+    size_t dataSize = data_in->data.size();
+    size_t outSize = data_in->data.size();
+    
+    if (outSize > dataOut.capacity()) {
+        dataOut.reserve(outSize);
+    }
+    if (outSize != dataOut.size()) {
+        dataOut.resize(outSize);
+    }
+    
+    if (iqDataOutQueue != NULL && !iqDataOutQueue->full()) {
+        DemodulatorThreadIQData *iqDataOut = visualDataBuffers.getBuffer();
+        
+        bool doVis = false;
+        
+        if (iqVisualQueue != NULL && !iqVisualQueue->full()) {
+            doVis = true;
+        }
+        
+        iqDataOut->setRefCount(1 + (doVis?1:0));
+        
+        iqDataOut->frequency = data_in->frequency;
+        iqDataOut->sampleRate = data_in->sampleRate;
+        iqDataOut->data.assign(data_in->data.begin(), data_in->data.begin() + dataSize);
+        
+        if (!iqDataOutQueue->push(iqDataOut)) {
+            std::cout << "SDRPostThread::runPFBCH() cannot push iqDataOut into iqDataOutQueue, is full !" << std::endl;
+            iqDataOut->decRefCount();
+            std::this_thread::yield();
+        }
+
+
+        if (doVis) {           
+            if (!iqVisualQueue->push(iqDataOut)) {
+                std::cout << "SDRPostThread::runPFBCH() cannot push iqDataOut into iqVisualQueue, is full !" << std::endl;
+                iqDataOut->decRefCount();               
+                std::this_thread::yield();
+            }
+        }
+    }
+    
+    if (frequency != data_in->frequency) {
+        frequency = data_in->frequency;
+        doRefresh.store(true);
+    }
+    
+    if (doRefresh.load()) {
+        updateActiveDemodulators();
+        updateChannels();
+        doRefresh.store(false);
+    }
+    
+    DemodulatorInstance *activeDemod = wxGetApp().getDemodMgr().getLastActiveDemodulator();
+    int activeDemodChannel = -1;
+    
+    // Find active demodulators
+    if (nRunDemods) {
+        
+        // channelize data
+        // firpfbch output rate is (input rate / channels)
+        for (int i = 0, iMax = dataSize; i < iMax; i+=numChannels) {
+            firpfbch_crcf_analyzer_execute(channelizer, &data_in->data[i], &dataOut[i]);
+        }
+        
+        for (int i = 0, iMax = numChannels+1; i < iMax; i++) {
+            demodChannelActive[i] = 0;
+        }
+        
+        // Find nearest channel for each demodulator
+        for (size_t i = 0; i < nRunDemods; i++) {
+            DemodulatorInstance *demod = runDemods[i];
+            demodChannel[i] = getChannelAt(demod->getFrequency());
+            if (demod == activeDemod) {
+                activeDemodChannel = demodChannel[i];
+            }
+        }
+        
+        for (size_t i = 0; i < nRunDemods; i++) {
+            // cache channel usage refcounts
+            if (demodChannel[i] >= 0) {
+                demodChannelActive[demodChannel[i]]++;
+            }
+        }
+        
+        // Run channels
+        for (int i = 0; i < numChannels+1; i++) {
+            int doDemodVis = ((activeDemodChannel == i) && (iqActiveDemodVisualQueue != NULL) && !iqActiveDemodVisualQueue->full())?1:0;
+            
+            if (!doDemodVis && demodChannelActive[i] == 0) {
+                continue;
+            }
+            
+            DemodulatorThreadIQData *demodDataOut = buffers.getBuffer();
+            demodDataOut->setRefCount(demodChannelActive[i] + doDemodVis);
+            demodDataOut->frequency = chanCenters[i];
+            demodDataOut->sampleRate = chanBw;
+            
+            // Calculate channel buffer size
+            size_t chanDataSize = (outSize/numChannels);
+            
+            if (demodDataOut->data.size() != chanDataSize) {
+                if (demodDataOut->data.capacity() < chanDataSize) {
+                    demodDataOut->data.reserve(chanDataSize);
+                }
+                demodDataOut->data.resize(chanDataSize);
+            }
+            
+            int idx = i;
+            
+            // Extra channel wraps lower side band of lowest channel
+            // to fix frequency gap on upper side of spectrum
+            if (i == numChannels) {
+                idx = (numChannels/2);
+            }
+            
+            // prepare channel data buffer
+            if (i == 0) {   // Channel 0 requires DC correction
+                if (dcBuf.size() != chanDataSize) {
+                    dcBuf.resize(chanDataSize);
+                }
+                for (size_t j = 0; j < chanDataSize; j++) {
+                    dcBuf[j] = dataOut[idx];
+                    idx += numChannels;
+                }
+                iirfilt_crcf_execute_block(dcFilter, &dcBuf[0], chanDataSize, &demodDataOut->data[0]);
+            } else {
+                for (size_t j = 0; j < chanDataSize; j++) {
+                    demodDataOut->data[j] = dataOut[idx];
+                    idx += numChannels;
+                }
+            }
+            
+            if (doDemodVis) {
+                if (!iqActiveDemodVisualQueue->push(demodDataOut)) {
+                    std::cout << "SDRPostThread::runPFBCH() cannot push demodDataOut into iqActiveDemodVisualQueue, is full !" << std::endl;
+                    demodDataOut->decRefCount();
+                    std::this_thread::yield();
+                }
+            }
+            
+            for (size_t j = 0; j < nRunDemods; j++) {
+                if (demodChannel[j] == i) {
+                    DemodulatorInstance *demod = runDemods[j];
+                    
+                    if (!demod->getIQInputDataPipe()->push(demodDataOut)) {
+                        demodDataOut->decRefCount();
+                        std::this_thread::yield();
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/src/sdr/SDRPostThread.h b/src/sdr/SDRPostThread.h
new file mode 100644
index 0000000..c27fba1
--- /dev/null
+++ b/src/sdr/SDRPostThread.h
@@ -0,0 +1,65 @@
+#pragma once
+
+#if USE_RTL_SDR
+#include "SDRThread.h"
+#else
+#include "SoapySDRThread.h"
+#endif
+#include <algorithm>
+
+class SDRPostThread : public IOThread {
+public:
+    SDRPostThread();
+    ~SDRPostThread();
+
+    void bindDemodulator(DemodulatorInstance *demod);
+    void bindDemodulators(std::vector<DemodulatorInstance *> *demods);
+    void removeDemodulator(DemodulatorInstance *demod);
+    
+    virtual void run();
+    virtual void terminate();
+
+    void runSingleCH(SDRThreadIQData *data_in);
+    void runPFBCH(SDRThreadIQData *data_in);
+    void setIQVisualRange(long long frequency, int bandwidth);
+        
+protected:
+    SDRThreadIQDataQueue *iqDataInQueue;
+    DemodulatorThreadInputQueue *iqDataOutQueue;
+    DemodulatorThreadInputQueue *iqVisualQueue;
+    DemodulatorThreadInputQueue *iqActiveDemodVisualQueue;
+    
+    //protects access to demodulators lists and such
+    std::mutex busy_demod;
+    std::vector<DemodulatorInstance *> demodulators;
+
+    
+
+private:
+
+    void initPFBChannelizer();
+    void updateActiveDemodulators();
+    void updateChannels();
+    int getChannelAt(long long frequency);
+
+    ReBuffer<DemodulatorThreadIQData> buffers;
+    std::vector<liquid_float_complex> fpData;
+    std::vector<liquid_float_complex> dataOut;
+    std::vector<long long> chanCenters;
+    long long chanBw;
+    
+    size_t nRunDemods;
+    std::vector<DemodulatorInstance *> runDemods;
+    std::vector<int> demodChannel;
+    std::vector<int> demodChannelActive;
+
+    ReBuffer<DemodulatorThreadIQData> visualDataBuffers;
+    atomic_bool doRefresh;
+    atomic_llong visFrequency;
+    atomic_int visBandwidth;
+    int numChannels, sampleRate;
+    long long frequency;
+    firpfbch_crcf channelizer;
+    iirfilt_crcf dcFilter;
+    std::vector<liquid_float_complex> dcBuf;
+};
diff --git a/src/sdr/SDRThread.cpp b/src/sdr/SDRThread.cpp
new file mode 100644
index 0000000..5ee8080
--- /dev/null
+++ b/src/sdr/SDRThread.cpp
@@ -0,0 +1,314 @@
+#include "SDRThread.h"
+#include "CubicSDRDefs.h"
+#include <vector>
+#include "CubicSDR.h"
+
+SDRThread::SDRThread() : IOThread() {
+	offset.store(0);
+	deviceId.store(-1);
+    dev = NULL;
+    sampleRate.store(DEFAULT_SAMPLE_RATE);
+}
+
+SDRThread::~SDRThread() {
+    rtlsdr_close(dev);
+}
+
+int SDRThread::enumerate_rtl(std::vector<SDRDeviceInfo *> *devs) {
+
+    int first_available = -1;
+
+    char manufact[256], product[256], serial[256];
+
+    unsigned int rtl_count = rtlsdr_get_device_count();
+
+    std::cout << "RTL Devices: " << rtl_count << std::endl;
+
+    for (int i = 0; i < rtl_count; i++) {
+        std::string deviceName(rtlsdr_get_device_name(i));
+        std::string deviceManufacturer;
+        std::string deviceProduct;
+        std::string deviceTuner;
+        std::string deviceSerial;
+
+        bool deviceAvailable = false;
+        std::cout << "Device #" << i << ": " << deviceName << std::endl;
+        if (rtlsdr_get_device_usb_strings(i, manufact, product, serial) == 0) {
+            std::cout << "\tManufacturer: " << manufact << ", Product Name: " << product << ", Serial: " << serial << std::endl;
+
+            deviceSerial = serial;
+            deviceAvailable = true;
+            deviceProduct = product;
+            deviceManufacturer = manufact;
+
+            rtlsdr_dev_t *devTest = nullptr;
+            if(rtlsdr_open(&devTest, i) < 0)
+            {
+                std::cout << "\tFailed to open device " << i << std::endl;
+                continue;
+            }
+
+            std::cout << "\t Tuner type: ";
+            switch (rtlsdr_get_tuner_type(devTest)) {
+            case RTLSDR_TUNER_UNKNOWN:
+                deviceTuner = "Unknown";
+                break;
+            case RTLSDR_TUNER_E4000:
+                deviceTuner = "Elonics E4000";
+                break;
+            case RTLSDR_TUNER_FC0012:
+                deviceTuner = "Fitipower FC0012";
+                break;
+            case RTLSDR_TUNER_FC0013:
+                deviceTuner = "Fitipower FC0013";
+                break;
+            case RTLSDR_TUNER_FC2580:
+                deviceTuner = "Fitipower FC2580";
+                break;
+            case RTLSDR_TUNER_R820T:
+                deviceTuner = "Rafael Micro R820T";
+                break;
+            case RTLSDR_TUNER_R828D:
+                deviceTuner = "Rafael Micro R828D";
+                break;
+            }
+
+            std::cout << deviceTuner << std::endl;
+            /*
+             int num_gains = rtlsdr_get_tuner_gains(dev, NULL);
+
+             int *gains = (int *)malloc(sizeof(int) * num_gains);
+             rtlsdr_get_tuner_gains(dev, gains);
+
+             std::cout << "\t Valid gains: ";
+             for (int g = 0; g < num_gains; g++) {
+             if (g > 0) {
+             std::cout << ", ";
+             }
+             std::cout << ((float)gains[g]/10.0f);
+             }
+             std::cout << std::endl;
+
+             free(gains);
+             */
+
+            rtlsdr_close(devTest);
+            if (first_available == -1) {
+                first_available = i;
+            }
+
+        } else {
+            std::cout << "\tUnable to access device #" << i << " (in use?)" << std::endl;
+        }
+
+        if (devs != NULL) {
+            SDRDeviceInfo *devInfo = new SDRDeviceInfo();
+            devInfo->setName(deviceName);
+            devInfo->setAvailable(deviceAvailable);
+            devInfo->setProduct(deviceProduct);
+            devInfo->setSerial(deviceSerial);
+            devInfo->setManufacturer(deviceManufacturer);
+            devs->push_back(devInfo);
+        }
+    }
+
+    return first_available;
+
+}
+
+void SDRThread::run() {
+#ifdef __APPLE__
+    pthread_t tID = pthread_self();  // ID of this thread
+    int priority = sched_get_priority_max( SCHED_FIFO) - 1;
+    sched_param prio = { priority }; // scheduling priority of thread
+    pthread_setschedparam(tID, SCHED_FIFO, &prio);
+#endif
+
+    std::cout << "SDR thread initializing.." << std::endl;
+
+    std::vector<SDRDeviceInfo *> devs;
+    if (deviceId == -1) {
+        deviceId = enumerate_rtl(&devs);
+    } else {
+        enumerate_rtl(&devs);
+    }
+
+    if (deviceId == -1) {
+        std::cout << "No devices found.. SDR Thread exiting.." << std::endl;
+        return;
+    } else {
+        std::cout << "Using device #" << deviceId << std::endl;
+    }
+
+    DeviceConfig *devConfig = wxGetApp().getConfig()->getDevice(devs[deviceId]->getDeviceId());
+    
+    signed char buf[BUF_SIZE];
+
+    long long frequency = wxGetApp().getConfig()->getCenterFreq();
+    int ppm = devConfig->getPPM();
+    int direct_sampling_mode = devConfig->getDirectSampling();;
+    int buf_size = BUF_SIZE;
+    offset.store(devConfig->getOffset());
+    wxGetApp().setSwapIQ(devConfig->getIQSwap());
+    
+    rtlsdr_open(&dev, deviceId);
+    rtlsdr_set_sample_rate(dev, sampleRate.load());
+    rtlsdr_set_center_freq(dev, frequency - offset.load());
+    rtlsdr_set_freq_correction(dev, ppm);
+    rtlsdr_set_agc_mode(dev, 1);
+    rtlsdr_set_offset_tuning(dev, 0);
+    rtlsdr_reset_buffer(dev);
+
+//    sampleRate = rtlsdr_get_sample_rate(dev);
+
+    std::cout << "Sample Rate is: " << sampleRate.load() << std::endl;
+
+    int n_read;
+    double seconds = 0.0;
+
+    std::cout << "SDR thread started.." << std::endl;
+
+    ReBuffer<SDRThreadIQData> buffers;
+
+    SDRThreadIQDataQueue* iqDataOutQueue = (SDRThreadIQDataQueue*) getOutputQueue("IQDataOutput");
+    SDRThreadCommandQueue* cmdQueue = (SDRThreadCommandQueue*) getInputQueue("SDRCommandQueue");
+
+    while (!terminated) {
+        if (!cmdQueue->empty()) {
+            bool freq_changed = false;
+            bool offset_changed = false;
+            bool rate_changed = false;
+            bool device_changed = false;
+            bool ppm_changed = false;
+            bool direct_sampling_changed = false;
+            long long new_freq = frequency;
+            long long new_offset = offset.load();
+            long long new_rate = sampleRate.load();
+            int new_device = deviceId;
+            int new_ppm = ppm;
+
+            while (!cmdQueue->empty()) {
+                SDRThreadCommand command;
+                cmdQueue->pop(command);
+
+                switch (command.cmd) {
+                case SDRThreadCommand::SDR_THREAD_CMD_TUNE:
+                    freq_changed = true;
+                    new_freq = command.llong_value;
+                    if (new_freq < sampleRate.load() / 2) {
+                        new_freq = sampleRate.load() / 2;
+                    }
+//                    std::cout << "Set frequency: " << new_freq << std::endl;
+                    break;
+                case SDRThreadCommand::SDR_THREAD_CMD_SET_OFFSET:
+                    offset_changed = true;
+                    new_offset = command.llong_value;
+                    std::cout << "Set offset: " << new_offset << std::endl;
+                    break;
+                case SDRThreadCommand::SDR_THREAD_CMD_SET_SAMPLERATE:
+                    rate_changed = true;
+                    new_rate = command.llong_value;
+                    if (new_rate <= 250000) {
+                        buf_size = BUF_SIZE/4;
+                    } else if (new_rate < 1500000) {
+                        buf_size = BUF_SIZE/2;
+                    } else {
+                        buf_size = BUF_SIZE;
+                    }
+                    std::cout << "Set sample rate: " << new_rate << std::endl;
+                    break;
+                case SDRThreadCommand::SDR_THREAD_CMD_SET_DEVICE:
+                    device_changed = true;
+                    new_device = (int) command.llong_value;
+                    std::cout << "Set device: " << new_device << std::endl;
+                    break;
+                case SDRThreadCommand::SDR_THREAD_CMD_SET_PPM:
+                    ppm_changed = true;
+                    new_ppm = (int) command.llong_value;
+                    //std::cout << "Set PPM: " << new_ppm << std::endl;
+                    break;
+                case SDRThreadCommand::SDR_THREAD_CMD_SET_DIRECT_SAMPLING:
+                    direct_sampling_mode = (int)command.llong_value;
+                    direct_sampling_changed = true;
+                    break;
+                default:
+                    break;
+                }
+            }
+
+            if (device_changed) {
+                rtlsdr_close(dev);
+                rtlsdr_open(&dev, new_device);
+                rtlsdr_set_sample_rate(dev, sampleRate.load());
+                rtlsdr_set_center_freq(dev, frequency - offset.load());
+                rtlsdr_set_freq_correction(dev, ppm);
+                rtlsdr_set_agc_mode(dev, 1);
+                rtlsdr_set_offset_tuning(dev, 0);
+                rtlsdr_set_direct_sampling(dev, direct_sampling_mode);
+                rtlsdr_reset_buffer(dev);
+            }
+            if (offset_changed) {
+                if (!freq_changed) {
+                    new_freq = frequency;
+                    freq_changed = true;
+                }
+                offset.store(new_offset);
+            }
+            if (rate_changed) {
+                rtlsdr_set_sample_rate(dev, new_rate);
+                rtlsdr_reset_buffer(dev);
+                sampleRate.store(rtlsdr_get_sample_rate(dev));
+            }
+            if (freq_changed) {
+                frequency = new_freq;
+                rtlsdr_set_center_freq(dev, frequency - offset.load());
+            }
+            if (ppm_changed) {
+                ppm = new_ppm;
+                rtlsdr_set_freq_correction(dev, ppm);
+            }
+            if (direct_sampling_changed) {
+                rtlsdr_set_direct_sampling(dev, direct_sampling_mode);
+            }
+        }
+
+        rtlsdr_read_sync(dev, buf, buf_size, &n_read);
+
+        SDRThreadIQData *dataOut = buffers.getBuffer();
+
+//        std::lock_guard < std::mutex > lock(dataOut->m_mutex);
+        dataOut->setRefCount(1);
+        dataOut->frequency = frequency;
+        dataOut->sampleRate = sampleRate.load();
+
+        if (dataOut->data.capacity() < n_read) {
+            dataOut->data.reserve(n_read);
+        }
+
+        if (dataOut->data.size() != n_read) {
+            dataOut->data.resize(n_read);
+        }
+
+        memcpy(&dataOut->data[0], buf, n_read);
+
+        double time_slice = (double) n_read / (double) sampleRate.load();
+        seconds += time_slice;
+
+        if (iqDataOutQueue != NULL) {
+            iqDataOutQueue->push(dataOut);
+        }
+    }
+
+//     buffers.purge();
+    
+    std::cout << "SDR thread done." << std::endl;
+}
+
+
+int SDRThread::getDeviceId() const {
+    return deviceId.load();
+}
+
+void SDRThread::setDeviceId(int deviceId) {
+    this->deviceId.store(deviceId);
+}
diff --git a/src/sdr/SDRThread.h b/src/sdr/SDRThread.h
new file mode 100644
index 0000000..4b3a8ac
--- /dev/null
+++ b/src/sdr/SDRThread.h
@@ -0,0 +1,73 @@
+#pragma once
+
+#include <atomic>
+
+#include "rtl-sdr.h"
+
+#include "ThreadQueue.h"
+#include "DemodulatorMgr.h"
+#include "SDRDeviceInfo.h"
+
+class SDRThreadCommand {
+public:
+    enum SDRThreadCommandEnum {
+        SDR_THREAD_CMD_NULL, SDR_THREAD_CMD_TUNE, SDR_THREAD_CMD_SET_OFFSET, SDR_THREAD_CMD_SET_SAMPLERATE, SDR_THREAD_CMD_SET_PPM, SDR_THREAD_CMD_SET_DEVICE, SDR_THREAD_CMD_SET_DIRECT_SAMPLING
+    };
+
+    SDRThreadCommand() :
+            cmd(SDR_THREAD_CMD_NULL), llong_value(0) {
+
+    }
+
+    SDRThreadCommand(SDRThreadCommandEnum cmd) :
+            cmd(cmd), llong_value(0) {
+
+    }
+
+    SDRThreadCommandEnum cmd;
+    long long llong_value;
+};
+
+class SDRThreadIQData: public ReferenceCounter {
+public:
+    long long frequency;
+    long long sampleRate;
+    std::vector<unsigned char> data;
+
+    SDRThreadIQData() :
+            frequency(0), sampleRate(DEFAULT_SAMPLE_RATE) {
+
+    }
+
+    SDRThreadIQData(long long bandwidth, long long frequency, std::vector<signed char> *data) :
+            frequency(frequency), sampleRate(bandwidth) {
+
+    }
+
+    ~SDRThreadIQData() {
+
+    }
+};
+
+typedef ThreadQueue<SDRThreadCommand> SDRThreadCommandQueue;
+typedef ThreadQueue<SDRThreadIQData *> SDRThreadIQDataQueue;
+
+class SDRThread : public IOThread {
+public:
+    rtlsdr_dev_t *dev;
+
+    SDRThread();
+    ~SDRThread();
+
+    static int enumerate_rtl(std::vector<SDRDeviceInfo *> *devs);
+
+    void run();
+    
+    int getDeviceId() const;
+    void setDeviceId(int deviceId);
+
+protected:
+    std::atomic<uint32_t> sampleRate;
+    std::atomic_llong offset;
+    std::atomic_int deviceId;
+};
diff --git a/src/sdr/SoapySDRThread.cpp b/src/sdr/SoapySDRThread.cpp
new file mode 100644
index 0000000..7a6b645
--- /dev/null
+++ b/src/sdr/SoapySDRThread.cpp
@@ -0,0 +1,601 @@
+#include "SoapySDRThread.h"
+#include "CubicSDRDefs.h"
+#include <vector>
+#include "CubicSDR.h"
+#include <string>
+#include <SoapySDR/Logger.h>
+
+
+SDRThread::SDRThread() : IOThread(), buffers("SDRThreadBuffers") {
+    device = NULL;
+
+    deviceConfig.store(NULL);
+    deviceInfo.store(NULL);
+
+    sampleRate.store(DEFAULT_SAMPLE_RATE);
+    frequency.store(0);
+    offset.store(0);
+    ppm.store(0);
+
+    numElems.store(0);
+    
+    rate_changed.store(false);
+    freq_changed.store(false);
+    offset_changed.store(false);
+    ppm_changed .store(false);
+    device_changed.store(false);
+
+    hasPPM.store(false);
+    hasHardwareDC.store(false);
+    numChannels.store(8);
+    
+    agc_mode.store(true);
+    agc_mode_changed.store(false);
+    gain_value_changed.store(false);
+    setting_value_changed.store(false);
+    frequency_lock_init.store(false);
+    frequency_locked.store(false);
+    lock_freq.store(0);
+    iq_swap.store(false);
+}
+
+SDRThread::~SDRThread() {
+
+}
+
+SoapySDR::Kwargs SDRThread::combineArgs(SoapySDR::Kwargs a, SoapySDR::Kwargs b) {
+    SoapySDR::Kwargs c;
+    SoapySDR::Kwargs::iterator i;
+    for (i = a.begin(); i != a.end(); i++) {
+        c[i->first] = i->second;
+    }
+    for (i = b.begin(); i != b.end(); i++) {
+        c[i->first] = i->second;
+    }
+    return c;
+}
+
+bool SDRThread::init() {
+//#warning Debug On
+//    SoapySDR_setLogLevel(SOAPY_SDR_DEBUG);
+    
+    SDRDeviceInfo *devInfo = deviceInfo.load();
+    deviceConfig.store(wxGetApp().getConfig()->getDevice(devInfo->getDeviceId()));
+    DeviceConfig *devConfig = deviceConfig.load();
+    
+    ppm.store(devConfig->getPPM());
+    ppm_changed.store(true);
+    
+    std::string driverName = devInfo->getDriver();
+
+    offset = devConfig->getOffset();
+    
+    SoapySDR::Kwargs args = devInfo->getDeviceArgs();
+    
+    wxGetApp().sdrEnumThreadNotify(SDREnumerator::SDR_ENUM_MESSAGE, std::string("Initializing device."));
+    
+    device = devInfo->getSoapyDevice();
+    
+    SoapySDR::Kwargs currentStreamArgs = combineArgs(devInfo->getStreamArgs(),streamArgs);
+    
+    std::string streamExceptionStr("");
+    
+    try {
+        stream = device->setupStream(SOAPY_SDR_RX,"CF32", std::vector<size_t>(), currentStreamArgs);
+    } catch(exception e) {
+        streamExceptionStr = e.what();
+    }
+
+    if (!stream) {
+        wxGetApp().sdrThreadNotify(SDRThread::SDR_THREAD_FAILED, std::string("Stream setup failed, stream is null. ") + streamExceptionStr);
+        std::cout << "Stream setup failed, stream is null. " << streamExceptionStr << std::endl;
+        return false;
+    }
+    
+    int streamMTU = device->getStreamMTU(stream);
+    mtuElems.store(streamMTU);
+    
+    std::cout << "Stream MTU: " << mtuElems.load() << std::endl << std::flush;
+    
+    deviceInfo.load()->setStreamArgs(currentStreamArgs);
+    deviceConfig.load()->setStreamOpts(currentStreamArgs);
+    
+    wxGetApp().sdrEnumThreadNotify(SDREnumerator::SDR_ENUM_MESSAGE, std::string("Activating stream."));
+    device->setSampleRate(SOAPY_SDR_RX,0,sampleRate.load());
+    
+    // TODO: explore bandwidth setting option to see if this is necessary for others
+    if (device->getDriverKey() == "bladeRF") {
+        device->setBandwidth(SOAPY_SDR_RX, 0, sampleRate.load());
+    }
+        
+    device->setFrequency(SOAPY_SDR_RX,0,"RF",frequency - offset.load());
+    device->activateStream(stream);
+    if (devInfo->hasCORR(SOAPY_SDR_RX, 0)) {
+        hasPPM.store(true);
+        device->setFrequency(SOAPY_SDR_RX,0,"CORR",ppm.load());
+    } else {
+        hasPPM.store(false);
+    }
+    if (device->hasDCOffsetMode(SOAPY_SDR_RX, 0)) {
+        hasHardwareDC.store(true);
+//        wxGetApp().sdrEnumThreadNotify(SDREnumerator::SDR_ENUM_MESSAGE, std::string("Found hardware DC offset correction support, internal disabled."));
+        device->setDCOffsetMode(SOAPY_SDR_RX, 0, true);
+    } else {
+        hasHardwareDC.store(false);
+    }
+    
+    device->setGainMode(SOAPY_SDR_RX,0,agc_mode.load());
+    
+    numChannels.store(getOptimalChannelCount(sampleRate.load()));
+    numElems.store(getOptimalElementCount(sampleRate.load(), 30));
+    if (!mtuElems.load()) {
+        mtuElems.store(numElems.load());
+    }
+    inpBuffer.data.resize(numElems.load());
+    overflowBuffer.data.resize(mtuElems.load());
+    
+    buffs[0] = malloc(mtuElems.load() * 4 * sizeof(float));
+    numOverflow = 0;
+    
+    SoapySDR::ArgInfoList settingsInfo = device->getSettingInfo();
+    SoapySDR::ArgInfoList::const_iterator settings_i;
+    
+    if (!setting_value_changed.load()) {
+        settings.erase(settings.begin(), settings.end());
+        settingChanged.erase(settingChanged.begin(), settingChanged.end());
+    }
+    
+    { //enter scoped-lock
+        std::lock_guard < std::mutex > lock(setting_busy);
+
+        for (settings_i = settingsInfo.begin(); settings_i != settingsInfo.end(); settings_i++) {
+            SoapySDR::ArgInfo setting = (*settings_i);
+            if ((settingChanged.find(setting.key) != settingChanged.end()) && (settings.find(setting.key) != settings.end())) {
+                device->writeSetting(setting.key, settings[setting.key]);
+                settingChanged[setting.key] = false;
+            } else {
+                settings[setting.key] = device->readSetting(setting.key);
+                settingChanged[setting.key] = false;
+            }
+        }
+        setting_value_changed.store(false);
+
+    } //leave lock guard scope
+    
+    updateSettings();
+    
+    wxGetApp().sdrThreadNotify(SDRThread::SDR_THREAD_INITIALIZED, std::string("Device Initialized."));
+    
+    return true;
+}
+
+void SDRThread::deinit() {
+    device->deactivateStream(stream);
+    device->closeStream(stream);
+    free(buffs[0]);
+}
+
+void SDRThread::readStream(SDRThreadIQDataQueue* iqDataOutQueue) {
+    int flags;
+    long long timeNs;
+
+    int n_read = 0;
+    int nElems = numElems.load();
+    int mtElems = mtuElems.load();
+
+    //If overflow occured on the previous readStream(), transfer it in inpBuffer now
+    if (numOverflow > 0) {
+        int n_overflow = numOverflow;
+        if (n_overflow > nElems) {
+            n_overflow = nElems;
+        }
+        memcpy(&inpBuffer.data[0], &overflowBuffer.data[0], n_overflow * sizeof(float) * 2);
+        n_read = n_overflow;
+        numOverflow -= n_overflow;
+        
+        if (numOverflow) { // still some left..
+            memmove(&overflowBuffer.data[0], &overflowBuffer.data[n_overflow], numOverflow * sizeof(float) * 2);
+        }
+    }
+    
+    //attempt readStream() at most nElems, by mtElems-sized chunks, append inpBuffer.
+    while (n_read < nElems && !stopping) {
+        int n_requested = nElems-n_read;
+        
+        int n_stream_read = device->readStream(stream, buffs, mtElems, flags, timeNs);
+
+        //if the n_stream_read <= 0, bail out from reading. 
+        if (n_stream_read <= 0) {
+            break;
+        }
+
+        //sucess read beyond nElems, with overflow
+        if ((n_read + n_stream_read) > nElems) {
+            memcpy(&inpBuffer.data[n_read], buffs[0], n_requested * sizeof(float) * 2);
+            numOverflow = n_stream_read-n_requested;
+            liquid_float_complex **pp = (liquid_float_complex **)buffs[0];
+            pp += n_requested;
+            memcpy(&overflowBuffer.data[0], pp, numOverflow * sizeof(float) * 2);
+            n_read += n_requested;
+        } else if (n_stream_read > 0) {
+            memcpy(&inpBuffer.data[n_read], buffs[0], n_stream_read * sizeof(float) * 2);
+            n_read += n_stream_read;
+        } else {
+            break;
+        }
+    }
+    
+    if (n_read > 0 && !stopping && !iqDataOutQueue->full()) {
+        SDRThreadIQData *dataOut = buffers.getBuffer();
+
+        if (iq_swap.load()) {
+            dataOut->data.resize(n_read);
+            for (int i = 0; i < n_read; i++) {
+                dataOut->data[i].imag = inpBuffer.data[i].real;
+                dataOut->data[i].real = inpBuffer.data[i].imag;
+            }
+        } else {
+            dataOut->data.assign(inpBuffer.data.begin(), inpBuffer.data.begin()+n_read);
+        }
+        
+        dataOut->frequency = frequency.load();
+        dataOut->sampleRate = sampleRate.load();
+        dataOut->dcCorrected = hasHardwareDC.load();
+        dataOut->numChannels = numChannels.load();
+        
+        if (!iqDataOutQueue->push(dataOut)) {
+            //The rest of the system saturates,
+            //finally the push didn't suceeded, recycle dataOut immediatly.
+            dataOut->setRefCount(0);
+            
+            std::cout << "SDRThread::readStream(): iqDataOutQueue output queue is full, discard processing ! " << std::endl;
+
+            //saturation, let a chance to the other threads to consume the existing samples
+            std::this_thread::yield();
+        }
+    }
+}
+
+void SDRThread::readLoop() {
+    SDRThreadIQDataQueue* iqDataOutQueue = static_cast<SDRThreadIQDataQueue*>( getOutputQueue("IQDataOutput"));
+    
+    if (iqDataOutQueue == NULL) {
+        return;
+    }
+    
+    updateGains();
+
+    while (!stopping.load()) {
+        updateSettings();
+        readStream(iqDataOutQueue);
+    }
+
+    buffers.purge();
+}
+
+void SDRThread::updateGains() {
+    SDRDeviceInfo *devInfo = deviceInfo.load();
+    
+    gainValues.erase(gainValues.begin(),gainValues.end());
+    gainChanged.erase(gainChanged.begin(),gainChanged.end());
+    
+    SDRRangeMap gains = devInfo->getGains(SOAPY_SDR_RX, 0);
+    for (SDRRangeMap::iterator gi = gains.begin(); gi != gains.end(); gi++) {
+        gainValues[gi->first] = device->getGain(SOAPY_SDR_RX, 0, gi->first);
+        gainChanged[gi->first] = false;
+    }
+    
+    gain_value_changed.store(false);
+}
+
+void SDRThread::updateSettings() {
+    bool doUpdate = false;
+    
+    if (!stream) {
+        return;
+    }
+    
+    if (offset_changed.load()) {
+        if (!freq_changed.load()) {
+            frequency.store(frequency.load());
+            freq_changed.store(true);
+        }
+        offset_changed.store(false);
+    }
+    
+    if (rate_changed.load()) {
+        device->setSampleRate(SOAPY_SDR_RX,0,sampleRate.load());
+        // TODO: explore bandwidth setting option to see if this is necessary for others
+        if (device->getDriverKey() == "bladeRF") {
+            device->setBandwidth(SOAPY_SDR_RX, 0, sampleRate.load());
+        }
+        sampleRate.store(device->getSampleRate(SOAPY_SDR_RX,0));
+        numChannels.store(getOptimalChannelCount(sampleRate.load()));
+        numElems.store(getOptimalElementCount(sampleRate.load(), 60));
+        int streamMTU = device->getStreamMTU(stream);
+        mtuElems.store(streamMTU);
+        if (!mtuElems.load()) {
+            mtuElems.store(numElems.load());
+        }
+        inpBuffer.data.resize(numElems.load());
+        overflowBuffer.data.resize(mtuElems.load());
+        free(buffs[0]);
+        buffs[0] = malloc(mtuElems.load() * 4 * sizeof(float));
+        numOverflow = 0;
+        rate_changed.store(false);
+        doUpdate = true;
+    }
+    
+    if (ppm_changed.load() && hasPPM.load()) {
+        device->setFrequency(SOAPY_SDR_RX,0,"CORR",ppm.load());
+        ppm_changed.store(false);
+    }
+    
+    if (freq_changed.load()) {
+        if (frequency_locked.load() && !frequency_lock_init.load()) {
+            device->setFrequency(SOAPY_SDR_RX,0,"RF",lock_freq.load());
+            frequency_lock_init.store(true);
+        } else if (!frequency_locked.load()) {
+            device->setFrequency(SOAPY_SDR_RX,0,"RF",frequency.load() - offset.load());
+        }
+        freq_changed.store(false);
+    }
+    
+//    double devFreq = device->getFrequency(SOAPY_SDR_RX,0);
+//    if (((long long)devFreq + offset.load()) != frequency.load()) {
+//        wxGetApp().setFrequency((long long)devFreq + offset.load());
+//    }
+    
+    if (agc_mode_changed.load()) {
+        device->setGainMode(SOAPY_SDR_RX, 0, agc_mode.load());
+        agc_mode_changed.store(false);
+        if (!agc_mode.load()) {
+            updateGains();
+            
+            DeviceConfig *devConfig = deviceConfig.load();
+            ConfigGains gains = devConfig->getGains();
+            
+            if (gains.size()) {
+                for (ConfigGains::iterator gain_i = gains.begin(); gain_i != gains.end(); gain_i++) {
+                    setGain(gain_i->first, gain_i->second);
+                }
+            }
+        }
+        doUpdate = true;
+    }
+    
+    if (gain_value_changed.load() && !agc_mode.load()) {
+        std::lock_guard < std::mutex > lock(gain_busy); 
+
+        for (std::map<std::string,bool>::iterator gci = gainChanged.begin(); gci != gainChanged.end(); gci++) {
+            if (gci->second) {
+                device->setGain(SOAPY_SDR_RX, 0, gci->first, gainValues[gci->first]);
+                gainChanged[gci->first] = false;
+            }
+        }
+        
+        gain_value_changed.store(false);
+    }
+    
+    
+    if (setting_value_changed.load()) {
+
+        std::lock_guard < std::mutex > lock(setting_busy);
+        
+        for (std::map<std::string, bool>::iterator sci = settingChanged.begin(); sci != settingChanged.end(); sci++) {
+            if (sci->second) {
+                device->writeSetting(sci->first, settings[sci->first]);
+                settingChanged[sci->first] = false;
+            }
+        }
+        
+        setting_value_changed.store(false);
+        
+        doUpdate = true;
+    }
+    
+    if (doUpdate) {
+        wxGetApp().sdrThreadNotify(SDRThread::SDR_THREAD_INITIALIZED, std::string("Settings updated."));
+    }
+}
+
+void SDRThread::run() {
+//#ifdef __APPLE__
+//    pthread_t tID = pthread_self();  // ID of this thread
+//    int priority = sched_get_priority_max( SCHED_FIFO);
+//    sched_param prio = { priority }; // scheduling priority of thread
+//    pthread_setschedparam(tID, SCHED_FIFO, &prio);
+//#endif
+
+    std::cout << "SDR thread starting." << std::endl;
+    
+    SDRDeviceInfo *activeDev = deviceInfo.load();
+    
+    if (activeDev != NULL) {
+        std::cout << "device init()" << std::endl;
+        if (!init()) {
+            std::cout << "SDR Thread stream init error." << std::endl;
+            return;
+        }
+        std::cout << "starting readLoop()" << std::endl;
+        activeDev->setActive(true);
+        readLoop();
+        activeDev->setActive(false);
+        std::cout << "readLoop() ended." << std::endl;
+        deinit();
+        std::cout << "device deinit()" << std::endl;
+    } else {
+        std::cout << "SDR Thread started with null device?" << std::endl;
+    }
+    
+    std::cout << "SDR thread done." << std::endl;
+}
+
+
+SDRDeviceInfo *SDRThread::getDevice() {
+    return deviceInfo.load();
+}
+
+void SDRThread::setDevice(SDRDeviceInfo *dev) {
+    deviceInfo.store(dev);
+    if (dev) {
+        deviceConfig.store(wxGetApp().getConfig()->getDevice(dev->getDeviceId()));
+    } else {
+        deviceConfig.store(nullptr);
+    }
+}
+
+int SDRThread::getOptimalElementCount(long long sampleRate, int fps) {
+    int elemCount = (int)floor((double)sampleRate/(double)fps);
+    int nch = numChannels.load();
+    elemCount = int(ceil((double)elemCount/(double)nch))*nch;
+//    std::cout << "Calculated optimal " << numChannels.load() << " channel element count of " << elemCount << std::endl;
+    return elemCount;
+}
+
+int SDRThread::getOptimalChannelCount(long long sampleRate) {
+    if (sampleRate <= CHANNELIZER_RATE_MAX) {
+        return 1;
+    }
+    
+    int optimal_rate = CHANNELIZER_RATE_MAX;
+    int optimal_count = int(ceil(double(sampleRate)/double(optimal_rate)));
+    
+    if (optimal_count % 2 == 1) {
+        optimal_count--;
+    }
+    
+    if (optimal_count < 2) {
+        optimal_count = 2;
+    }
+
+    return optimal_count;
+}
+
+
+void SDRThread::setFrequency(long long freq) {
+    if (freq < sampleRate.load() / 2) {
+        freq = sampleRate.load() / 2;
+    }
+    frequency.store(freq);
+    freq_changed.store(true);
+}
+
+long long SDRThread::getFrequency() {
+    return frequency.load();
+}
+
+void SDRThread::lockFrequency(long long freq) {
+    lock_freq.store(freq);
+    frequency_locked.store(true);
+    frequency_lock_init.store(false);
+    setFrequency(freq);
+}
+
+bool SDRThread::isFrequencyLocked() {
+    return frequency_locked.load();
+}
+
+void SDRThread::unlockFrequency() {
+    frequency_locked.store(false);
+    frequency_lock_init.store(false);
+    freq_changed.store(true);
+}
+
+void SDRThread::setOffset(long long ofs) {
+    offset.store(ofs);
+    offset_changed.store(true);
+//    std::cout << "Set offset: " << offset.load() << std::endl;
+}
+
+long long SDRThread::getOffset() {
+    return offset.load();
+}
+
+void SDRThread::setSampleRate(int rate) {
+    sampleRate.store(rate);
+    rate_changed = true;
+    DeviceConfig *devConfig = deviceConfig.load();
+    if (devConfig) {
+        devConfig->setSampleRate(rate);
+    }
+//    std::cout << "Set sample rate: " << sampleRate.load() << std::endl;
+}
+int SDRThread::getSampleRate() {
+    return sampleRate.load();
+}
+
+void SDRThread::setPPM(int ppm) {
+    this->ppm.store(ppm);
+    ppm_changed.store(true);
+//    std::cout << "Set PPM: " << this->ppm.load() << std::endl;
+}
+
+int SDRThread::getPPM() {
+    return ppm.load();
+}
+
+void SDRThread::setAGCMode(bool mode) {
+    agc_mode.store(mode);
+    agc_mode_changed.store(true);
+    DeviceConfig *devConfig = deviceConfig.load();
+    if (devConfig) {
+        devConfig->setAGCMode(mode);
+    }
+}
+
+bool SDRThread::getAGCMode() {
+    return agc_mode.load();
+}
+
+void SDRThread::setIQSwap(bool swap) {
+    iq_swap.store(swap);
+}
+
+bool SDRThread::getIQSwap() {
+    return iq_swap.load();
+}
+
+void SDRThread::setGain(std::string name, float value) {
+    std::lock_guard < std::mutex > lock(gain_busy);
+    gainValues[name] = value;
+    gainChanged[name] = true;
+    gain_value_changed.store(true);
+    
+    DeviceConfig *devConfig = deviceConfig.load();
+    if (devConfig) {
+        devConfig->setGain(name, value);
+    }
+}
+
+float SDRThread::getGain(std::string name) {
+    std::lock_guard < std::mutex > lock(gain_busy);
+	float val = gainValues[name];
+	
+	return val;
+}
+
+void SDRThread::writeSetting(std::string name, std::string value) {
+
+    std::lock_guard < std::mutex > lock(setting_busy);
+
+    settings[name] = value;
+    settingChanged[name] = true;
+    setting_value_changed.store(true);
+    if (deviceConfig.load() != nullptr) {
+        deviceConfig.load()->setSetting(name, value);
+    }
+}
+
+std::string SDRThread::readSetting(std::string name) {
+    std::string val;
+    std::lock_guard < std::mutex > lock(setting_busy);
+
+    val = device->readSetting(name);
+   
+    return val;
+}
+
+void SDRThread::setStreamArgs(SoapySDR::Kwargs streamArgs_in) {
+    streamArgs = streamArgs_in;
+}
diff --git a/src/sdr/SoapySDRThread.h b/src/sdr/SoapySDRThread.h
new file mode 100644
index 0000000..1c79103
--- /dev/null
+++ b/src/sdr/SoapySDRThread.h
@@ -0,0 +1,121 @@
+#pragma once
+
+#include <atomic>
+
+#include "ThreadQueue.h"
+#include "DemodulatorMgr.h"
+#include "SDRDeviceInfo.h"
+#include "AppConfig.h"
+
+#include <SoapySDR/Version.hpp>
+#include <SoapySDR/Modules.hpp>
+#include <SoapySDR/Registry.hpp>
+#include <SoapySDR/Device.hpp>
+
+
+class SDRThreadIQData: public ReferenceCounter {
+public:
+    long long frequency;
+    long long sampleRate;
+    bool dcCorrected;
+    int numChannels;
+    std::vector<liquid_float_complex> data;
+
+    SDRThreadIQData() :
+            frequency(0), sampleRate(DEFAULT_SAMPLE_RATE), dcCorrected(true), numChannels(0) {
+
+    }
+
+    SDRThreadIQData(long long bandwidth, long long frequency, std::vector<signed char> * /* data */) :
+            frequency(frequency), sampleRate(bandwidth) {
+
+    }
+
+    ~SDRThreadIQData() {
+
+    }
+};
+
+typedef ThreadQueue<SDRThreadIQData *> SDRThreadIQDataQueue;
+
+class SDRThread : public IOThread {
+private:
+    bool init();
+    void deinit();
+    void readStream(SDRThreadIQDataQueue* iqDataOutQueue);
+    void readLoop();
+
+public:
+    SDRThread();
+    ~SDRThread();
+    enum SDRThreadState { SDR_THREAD_MESSAGE, SDR_THREAD_INITIALIZED, SDR_THREAD_FAILED };
+    
+    virtual void run();
+
+    SDRDeviceInfo *getDevice();
+    void setDevice(SDRDeviceInfo *dev);
+    int getOptimalElementCount(long long sampleRate, int fps);
+    int getOptimalChannelCount(long long sampleRate);
+    
+    void setFrequency(long long freq);
+    long long getFrequency();
+    
+    void lockFrequency(long long freq);
+    bool isFrequencyLocked();
+    void unlockFrequency();
+    
+    void setOffset(long long ofs);
+    long long getOffset();
+    
+    void setSampleRate(int rate);
+    int getSampleRate();
+
+    void setPPM(int ppm);
+    int getPPM();
+    
+    void setAGCMode(bool mode);
+    bool getAGCMode();
+
+    void setIQSwap(bool swap);
+    bool getIQSwap();
+
+    void setGain(std::string name, float value);
+    float getGain(std::string name);
+    
+    void writeSetting(std::string name, std::string value);
+    std::string readSetting(std::string name);
+    
+    void setStreamArgs(SoapySDR::Kwargs streamArgs);
+    
+protected:
+    void updateGains();
+    void updateSettings();
+    SoapySDR::Kwargs combineArgs(SoapySDR::Kwargs a, SoapySDR::Kwargs b);
+
+    SoapySDR::Stream *stream;
+    SoapySDR::Device *device;
+    void *buffs[1];
+    ReBuffer<SDRThreadIQData> buffers;
+    SDRThreadIQData inpBuffer;
+    SDRThreadIQData overflowBuffer;
+    int numOverflow;
+    std::atomic<DeviceConfig *> deviceConfig;
+    std::atomic<SDRDeviceInfo *> deviceInfo;
+    
+    std::mutex setting_busy;
+    std::map<std::string, std::string> settings;
+    std::map<std::string, bool> settingChanged;
+
+    std::atomic<uint32_t> sampleRate;
+    std::atomic_llong frequency, offset, lock_freq;
+    std::atomic_int ppm, numElems, mtuElems, numChannels;
+    std::atomic_bool hasPPM, hasHardwareDC;
+    std::atomic_bool agc_mode, rate_changed, freq_changed, offset_changed,
+        ppm_changed, device_changed, agc_mode_changed, gain_value_changed, setting_value_changed, frequency_locked, frequency_lock_init, iq_swap;
+
+    std::mutex gain_busy;
+    std::map<std::string, float> gainValues;
+    std::map<std::string, bool> gainChanged;
+    
+    SoapySDR::Kwargs streamArgs;
+};
diff --git a/src/ui/GLPanel.cpp b/src/ui/GLPanel.cpp
new file mode 100644
index 0000000..bf8af6d
--- /dev/null
+++ b/src/ui/GLPanel.cpp
@@ -0,0 +1,464 @@
+
+#include "GLPanel.h"
+#include "cubic_math.h"
+#include <algorithm>
+
+using namespace CubicVR;
+
+GLPanel::GLPanel() : fillType(GLPANEL_FILL_SOLID), contentsVisible(true), visible(true), transform(mat4::identity()) {
+    pos[0] = 0.0f;
+    pos[1] = 0.0f;
+    rot[0] = 0.0f;
+    rot[1] = 0.0f;
+    rot[2] = 0.0f;
+    size[0] = 1.0f;
+    size[1] = 1.0f;
+    fill[0] = RGBA4f(0.5f,0.5f,0.5f);
+    fill[1] = RGBA4f(0.1f,0.1f,0.1f);
+    borderColor = RGBA4f(0.8f, 0.8f, 0.8f);
+    setCoordinateSystem(GLPANEL_Y_UP);
+    setMarginPx(0);
+    setBorderPx(0);
+    srcBlend = GL_SRC_ALPHA;
+    dstBlend = GL_ONE_MINUS_SRC_ALPHA;
+}
+
+void GLPanel::genArrays() {
+    float min = -1.0, mid = 0, max = 1.0;
+    
+    if (fillType == GLPANEL_FILL_SOLID || fillType == GLPANEL_FILL_GRAD_X || fillType == GLPANEL_FILL_GRAD_Y) {
+        glPoints.reserve(2 * 4);
+        glPoints.resize(2 * 4);
+        glColors.reserve(4 * 4);
+        glColors.resize(4 * 4);
+        
+        float pts[2 * 4] = {
+            min, min,
+            min, max,
+            max, max,
+            max, min
+        };
+        
+        RGBA4f c[4];
+        
+        if (fillType == GLPANEL_FILL_SOLID) {
+            c[0] = c[1] = c[2] = c[3] = fill[0];
+        } else if (fillType == GLPANEL_FILL_GRAD_X) {
+            c[0] = c[1] = fill[0];
+            c[2] = c[3] = fill[1];
+        } else if (fillType == GLPANEL_FILL_GRAD_Y) {
+            c[0] = c[3] = fill[0];
+            c[1] = c[2] = fill[1];
+        }
+        
+        float clr[4 * 4] = {
+             c[0].r, c[0].g, c[0].b, c[0].a,
+             c[1].r, c[1].g, c[1].b, c[1].a,
+             c[2].r, c[2].g, c[2].b, c[2].a,
+             c[3].r, c[3].g, c[3].b, c[3].a
+         };
+        
+        glPoints.assign(pts, pts + (2 * 4));
+        glColors.assign(clr, clr + (4 * 4));
+    } else {
+        glPoints.reserve(2 * 8);
+        glPoints.resize(2 * 8);
+        glColors.reserve(4 * 8);
+        glColors.resize(4 * 8);
+        
+        RGBA4f c[8];
+        
+        if (fillType == GLPANEL_FILL_GRAD_BAR_X) {
+            float pts[2 * 8] = {
+                min, min,
+                min, max,
+                mid, max,
+                mid, min,
+                
+                mid, min,
+                mid, max,
+                max, max,
+                max, min
+            };
+            glPoints.assign(pts, pts + (2 * 8));
+
+            c[0] = c[1] = fill[0];
+            c[2] = c[3] = fill[1];
+
+            c[4] = c[5] = fill[1];
+            c[6] = c[7] = fill[0];
+
+        } else if (fillType == GLPANEL_FILL_GRAD_BAR_Y) {
+            float pts[2 * 8] = {
+                min, min,
+                min, mid,
+                max, mid,
+                max, min,
+                
+                min, mid,
+                min, max,
+                max, max,
+                max, mid
+            };
+            glPoints.assign(pts, pts + (2 * 8));
+
+            c[0] = c[3] = fill[0];
+            c[1] = c[2] = fill[1];
+
+            c[4] = c[7] = fill[1];
+            c[5] = c[6] = fill[0];
+        }
+        
+        float clr[4 * 8] = {
+            c[0].r, c[0].g, c[0].b, c[0].a,
+            c[1].r, c[1].g, c[1].b, c[1].a,
+            c[2].r, c[2].g, c[2].b, c[2].a,
+            c[3].r, c[3].g, c[3].b, c[3].a,
+            c[4].r, c[4].g, c[4].b, c[4].a,
+            c[5].r, c[5].g, c[5].b, c[5].a,
+            c[6].r, c[6].g, c[6].b, c[6].a,
+            c[7].r, c[7].g, c[7].b, c[7].a
+        };
+        
+        glColors.assign(clr, clr + (4 * 8));
+    }
+}
+
+
+void GLPanel::setViewport() {
+    GLint vp[4];
+    glGetIntegerv(GL_VIEWPORT, vp);
+    
+    view[0] = vp[2];
+    view[1] = vp[3];
+}
+
+void GLPanel::setPosition(float x, float y) {
+    pos[0] = x;
+    pos[1] = y;
+}
+
+void GLPanel::setSize(float w, float h) {
+    size[0] = w;
+    size[1] = h;
+}
+
+float GLPanel::getWidth() {
+    return size[0];
+}
+
+float GLPanel::getHeight() {
+    return size[1];
+}
+
+float GLPanel::getWidthPx() {
+    return pdim.x;
+}
+
+float GLPanel::getHeightPx() {
+    return pdim.y;
+}
+
+
+void GLPanel::setCoordinateSystem(GLPanelCoordinateSystem coord_in) {
+    coord = coord_in;
+    
+    if (coord == GLPANEL_Y_DOWN || coord == GLPANEL_Y_UP) {
+        min = -1;
+        mid = 0;
+        max = 1;
+    } else {
+        min = 0;
+        mid = 0.5;
+        max = 1;
+    }
+    
+    genArrays();
+}
+
+bool GLPanel::hitTest(CubicVR::vec2 pos, CubicVR::vec2 &result) {
+    CubicVR::vec4 hitPos = CubicVR::mat4::vec4_multiply(CubicVR::vec4(pos.x, pos.y, 0.0, 1.0), transformInverse);
+    
+    if (hitPos.x >= -1.0 && hitPos.x <= 1.0 && hitPos.y >= -1.0 && hitPos.y <= 1.0) {
+        result.x = hitPos.x;
+        result.y = hitPos.y;
+        return true;
+    }
+    
+    return false;
+}
+
+
+void GLPanel::setFill(GLPanelFillType fill_mode) {
+    fillType = fill_mode;
+    genArrays();
+}
+
+void GLPanel::setFillColor(RGBA4f color1) {
+    fill[0] = color1;
+    genArrays();
+}
+
+void GLPanel::setFillColor(RGBA4f color1, RGBA4f color2) {
+    fill[0] = color1;
+    fill[1] = color2;
+    genArrays();
+}
+
+void GLPanel::setMarginPx(float marg) {
+    marginPx = marg;
+}
+
+
+void GLPanel::setBorderColor(RGBA4f clr) {
+    borderColor = clr;
+}
+
+void GLPanel::setBorderPx(float bord) {
+    borderPx.left = borderPx.right = borderPx.top = borderPx.bottom = bord;
+}
+
+void GLPanel::setBorderPx(float bordl, float bordr, float bordt, float bordb) {
+    borderPx.left = bordl;
+    borderPx.right = bordr;
+    borderPx.top = bordt;
+    borderPx.bottom = bordb;
+}
+
+void GLPanel::setBlend(GLuint src, GLuint dst) {
+    srcBlend = src;
+    dstBlend = dst;
+}
+
+void GLPanel::addChild(GLPanel *childPanel) {
+    std::vector<GLPanel *>::iterator i = std::find(children.begin(), children.end(), childPanel);
+
+    if (i == children.end()) {
+        children.push_back(childPanel);
+    }
+}
+
+void GLPanel::removeChild(GLPanel *childPanel) {
+    std::vector<GLPanel *>::iterator i = std::find(children.begin(), children.end(), childPanel);
+
+    if (i != children.end()) {
+        children.erase(i);
+    }
+}
+
+void GLPanel::drawChildren() {
+    if (children.size()) {
+        std::vector<GLPanel *>::iterator panel_i;
+        
+        for (panel_i = children.begin(); panel_i != children.end(); panel_i++) {
+            (*panel_i)->calcTransform(transform);
+            (*panel_i)->draw();
+        }
+    }
+}
+
+void GLPanel::drawPanelContents() {
+    drawChildren();
+}
+
+void GLPanel::calcTransform(mat4 transform_in) {
+    // compute local transform
+    localTransform = mat4::translate(pos[0], pos[1], 0) * mat4::scale(size[0], size[1], 1);
+    
+    if (rot[0] || rot[1] || rot[2]) {
+        localTransform *= mat4::rotate(rot[0], rot[1], rot[2]);
+    }
+    
+    // compute global transform
+    transform = transform_in * localTransform;
+    
+    // init view[]
+    setViewport();
+    
+    // get min/max transform
+    vec4 vmin_t = mat4::vec4_multiply(vec4(min, min, 0, 1), transform);
+    vec4 vmax_t = mat4::vec4_multiply(vec4(max, max, 0, 1), transform);
+    
+    // screen dimensions
+    vmin = vec2((vmin_t.x > vmax_t.x)?vmax_t.x:vmin_t.x, (vmin_t.y > vmax_t.y)?vmax_t.y:vmin_t.y);
+    vmax = vec2((vmin_t.x > vmax_t.x)?vmin_t.x:vmax_t.x, (vmin_t.y > vmax_t.y)?vmin_t.y:vmax_t.y);
+    
+    // unit dimensions
+    umin = (vmin * 0.5) + vec2(1,1);
+    umax = (vmax * 0.5) + vec2(1,1);
+    
+    ucenter = vec2((umin + umax) * 0.5);
+    
+    // pixel dimensions
+    pdim = vec2((vmax.x - vmin.x) / 2.0 * view[0], (vmax.y  - vmin.y) / 2.0 * view[1]);
+    pvec = vec2(((vmax.x - vmin.x) / 2.0) / pdim.x, ((vmax.y - vmin.y) / 2.0) / pdim.y);
+    
+//    std::cout << umin << " :: " << ucenter << " :: " << pdim << " :: " << pvec << std::endl;
+    
+    if (marginPx) {
+        transform *= mat4::scale(1.0 - marginPx * 2.0 * pvec.x / size[0], 1.0 - marginPx * 2.0 * pvec.y / size[1], 1);
+    }
+    
+    transformInverse = CubicVR::mat4::inverse(transform);
+}
+
+void GLPanel::draw() {
+    float min = -1.0, max = 1.0;
+
+    glLoadMatrixf(transform);
+    
+    if (fillType != GLPANEL_FILL_NONE && visible) {
+        glEnable(GL_BLEND);
+        glBlendFunc(srcBlend, dstBlend);
+        glEnableClientState(GL_VERTEX_ARRAY);
+        glEnableClientState(GL_COLOR_ARRAY);
+        glVertexPointer(2, GL_FLOAT, 0, &glPoints[0]);
+        glColorPointer(4, GL_FLOAT, 0, &glColors[0]);
+        
+        glDrawArrays(GL_QUADS, 0, glPoints.size() / 2);
+        
+        glDisableClientState(GL_VERTEX_ARRAY);
+        glDisableClientState(GL_COLOR_ARRAY);
+        
+        if (borderPx.left || borderPx.right || borderPx.top || borderPx.bottom) {
+            glEnable(GL_LINE_SMOOTH);
+            glColor4f(borderColor.r, borderColor.g, borderColor.b, borderColor.a);
+            
+            if (borderPx.left) {
+                glLineWidth(borderPx.left);
+                glBegin(GL_LINES);
+                glVertex2f(min, min);
+                glVertex2f(min, max);
+                glEnd();
+            }
+        
+            if (borderPx.right) {
+                glLineWidth(borderPx.right);
+                glBegin(GL_LINES);
+                glVertex2f(max, min);
+                glVertex2f(max, max);
+                glEnd();
+            }
+
+            if (borderPx.top) {
+                glLineWidth(borderPx.top);
+                glBegin(GL_LINES);
+                glVertex2f(min, min);
+                glVertex2f(max, min);
+                glEnd();
+            }
+
+            if (borderPx.bottom) {
+                glLineWidth(borderPx.bottom);
+                glBegin(GL_LINES);
+                glVertex2f(min, max);
+                glVertex2f(max, max);
+                glEnd();
+            }
+
+            glDisable(GL_LINE_SMOOTH);
+        }
+        glDisable(GL_BLEND);
+    }
+    
+    if (contentsVisible) {
+        mat4 mCoord = mat4::identity();
+
+        if (coord == GLPANEL_Y_DOWN_ZERO_ONE) {
+            mCoord *= mat4::translate(-1.0f, 1.0f, 0.0f) * mat4::scale(2.0f, -2.0f, 2.0f);
+        }
+        if (coord == GLPANEL_Y_UP_ZERO_ONE) {
+            mCoord = mat4::translate(-1.0f, -1.0f, 0.0f) * mat4::scale(2.0f, 2.0f, 2.0f);
+        }
+        if (coord == GLPANEL_Y_DOWN) {
+            mCoord = mat4::scale(1.0f, -1.0f, 1.0f);
+        }
+        //        if (coord == GLPANEL_Y_UP) {
+        //        }
+        glLoadMatrixf(transform * mCoord);
+        drawPanelContents();
+    }
+}
+
+
+GLTextPanel::GLTextPanel() : GLPanel() {
+    coord = GLPANEL_Y_UP;
+    horizAlign = GLFont::GLFONT_ALIGN_CENTER;
+    vertAlign = GLFont::GLFONT_ALIGN_CENTER;
+    useNativeFont = true;
+}
+
+void GLTextPanel::drawPanelContents() {
+    glColor4f(1, 1, 1, 1.0);
+   
+    
+    
+    float pdimy = pdim.y;
+
+    double appliedScaleFactor = GLFont::getScaleFactor();
+        
+    if (useNativeFont) {
+        appliedScaleFactor = 1.0;
+    }
+    
+    //pdimy is considered un-scaled 
+    pdimy = round(pdimy / appliedScaleFactor);
+ 
+    int size = 12;
+
+    if (pdimy <= 16) {
+    
+        size = 12;
+    } else if (pdimy <= 18) {
+      
+        size = 16;
+    } else if(pdimy <= 24) {
+      
+        size = 18;
+    } else if(pdimy <= 32) {
+      
+        size = 24;
+    } else if(pdimy <= 48) {
+      
+        size = 32;
+    } else {
+     
+        size = 48;
+    }
+
+    GLFont::getFont(size, appliedScaleFactor).drawString(textVal, mid, mid, horizAlign, vertAlign, (int)pdim.x, (int)pdim.y);
+}
+
+void GLTextPanel::setText(std::string text, GLFont::Align hAlign, GLFont::Align vAlign, bool useNative) {
+    textVal = text;
+    horizAlign = hAlign;
+    vertAlign = vAlign;
+    useNativeFont = useNative;
+}
+
+std::string GLTextPanel::getText() {
+    return textVal;
+}
+
+
+
+void GLTestPanel::drawPanelContents() {
+    glColor3f(1.0,1.0,1.0);
+    glBegin(GL_LINES);
+    glVertex2f(min, mid);
+    glVertex2f(max, mid);
+    glVertex2f(mid, min);
+    glVertex2f(mid, max);
+    
+    glVertex2f(mid, max);
+    glVertex2f(mid - 0.02, max - 0.2);
+    glVertex2f(mid, 1);
+    glVertex2f(mid + 0.02, max - 0.2);
+    
+    glVertex2f(max, mid);
+    glVertex2f(max - 0.1, mid + max * 0.25);
+    glVertex2f(max, mid);
+    glVertex2f(max - 0.1, mid - max * 0.25);
+    
+    glEnd();
+}
diff --git a/src/ui/GLPanel.h b/src/ui/GLPanel.h
new file mode 100644
index 0000000..ebdb646
--- /dev/null
+++ b/src/ui/GLPanel.h
@@ -0,0 +1,119 @@
+#pragma once
+
+#include <vector>
+#include "GLExt.h"
+#include "GLFont.h"
+#include "ColorTheme.h"
+#include "cubic_math.h"
+
+class GLPanelEdges {
+public:
+    float left;
+    float right;
+    float top;
+    float bottom;
+    
+    GLPanelEdges(): left(0), right(0), top(0), bottom(0) {
+    }
+    
+    GLPanelEdges(float l, float r, float t, float b) {
+        left = l;
+        right = r;
+        top = t;
+        bottom = b;
+    }
+};
+
+class GLPanel {
+private:
+    std::vector<float> glPoints;
+    std::vector<float> glColors;
+    
+    void genArrays();
+    void setViewport();
+    
+public:
+    typedef enum GLPanelFillType { GLPANEL_FILL_NONE, GLPANEL_FILL_SOLID, GLPANEL_FILL_GRAD_X, GLPANEL_FILL_GRAD_Y, GLPANEL_FILL_GRAD_BAR_X, GLPANEL_FILL_GRAD_BAR_Y } GLPanelFillType;
+    typedef enum GLPanelCoordinateSystem { GLPANEL_Y_DOWN_ZERO_ONE, GLPANEL_Y_UP_ZERO_ONE, GLPANEL_Y_UP, GLPANEL_Y_DOWN } GLPanelCoordinateSystem;
+    float pos[2];
+    float rot[3];
+    float size[2];
+    float view[2];
+    GLPanelFillType fillType;
+    GLPanelCoordinateSystem coord;
+    float marginPx;
+    GLPanelEdges borderPx;
+    RGBA4f fill[2];
+    RGBA4f borderColor;
+    bool contentsVisible, visible;
+    CubicVR::mat4 transform, transformInverse;
+    CubicVR::mat4 localTransform;
+    float min, mid, max;
+    // screen dimensions
+    CubicVR::vec2 vmin, vmax;
+    // unit dimensions
+    CubicVR::vec2 umin, umax, ucenter;
+    // pixel dimensions
+    CubicVR::vec2 pdim, pvec;
+    GLuint srcBlend, dstBlend;
+    
+    std::vector<GLPanel *> children;
+    
+    GLPanel();
+    
+    void setPosition(float x, float y);
+
+
+    void setSize(float w, float h);
+    float getWidth();
+    float getHeight();
+    float getWidthPx();
+    float getHeightPx();
+    void setCoordinateSystem(GLPanelCoordinateSystem coord);
+    
+    bool hitTest(CubicVR::vec2 pos, CubicVR::vec2 &result);
+    
+    void setFill(GLPanelFillType fill_mode);
+    void setFillColor(RGBA4f color1);
+    void setFillColor(RGBA4f color1, RGBA4f color2);
+    void setMarginPx(float marg);
+
+    void setBorderColor(RGBA4f clr);
+    void setBorderPx(float bord);
+    void setBorderPx(float bordl, float bordr, float bordt, float bordb);
+    
+    void setBlend(GLuint src, GLuint dst);
+    
+    void addChild(GLPanel *childPanel);
+    void removeChild(GLPanel *childPanel);
+    
+    void drawChildren();
+    virtual void drawPanelContents();
+    void calcTransform(CubicVR::mat4 transform);
+    void draw();
+};
+
+
+class GLTextPanel : public GLPanel {
+private:
+    std::string textVal;
+    GLFont::Align horizAlign;
+    GLFont::Align vertAlign;
+    bool useNativeFont;
+public:
+    GLTextPanel();
+    
+    void drawPanelContents();
+    
+    void setText(std::string text, GLFont::Align hAlign = GLFont::GLFONT_ALIGN_CENTER, GLFont::Align vAlign = GLFont::GLFONT_ALIGN_CENTER , bool useNativeFont = false);
+    std::string getText();
+};
+
+class GLTestPanel : public GLPanel {
+public:
+    GLTestPanel() : GLPanel() {
+        
+    }
+    
+    void drawPanelContents();
+};
diff --git a/src/ui/UITestCanvas.cpp b/src/ui/UITestCanvas.cpp
new file mode 100644
index 0000000..ba14f4a
--- /dev/null
+++ b/src/ui/UITestCanvas.cpp
@@ -0,0 +1,82 @@
+#include "UITestCanvas.h"
+
+#include "wx/wxprec.h"
+
+#ifndef WX_PRECOMP
+#include "wx/wx.h"
+#endif
+
+#if !wxUSE_GLCANVAS
+#error "OpenGL required: set wxUSE_GLCANVAS to 1 and rebuild the library"
+#endif
+
+#include "CubicSDR.h"
+#include "CubicSDRDefs.h"
+#include "AppFrame.h"
+#include <algorithm>
+
+wxBEGIN_EVENT_TABLE(UITestCanvas, wxGLCanvas) EVT_PAINT(UITestCanvas::OnPaint)
+EVT_IDLE(UITestCanvas::OnIdle)
+EVT_MOTION(UITestCanvas::OnMouseMoved)
+EVT_LEFT_DOWN(UITestCanvas::OnMouseDown)
+EVT_LEFT_UP(UITestCanvas::OnMouseReleased)
+EVT_LEAVE_WINDOW(UITestCanvas::OnMouseLeftWindow)
+EVT_ENTER_WINDOW(UITestCanvas::OnMouseEnterWindow)
+wxEND_EVENT_TABLE()
+
+UITestCanvas::UITestCanvas(wxWindow *parent, int *dispAttrs) :
+InteractiveCanvas(parent, dispAttrs) {
+    
+    glContext = new UITestContext(this, &wxGetApp().GetContext(this));
+}
+
+UITestCanvas::~UITestCanvas() {
+    
+}
+
+void UITestCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) {
+    wxPaintDC dc(this);
+    const wxSize ClientSize = GetClientSize();
+    
+    glContext->SetCurrent(*this);
+    initGLExtensions();
+    
+    glViewport(0, 0, ClientSize.x, ClientSize.y);
+    
+    glContext->DrawBegin();
+
+    glContext->Draw();
+    
+    glContext->DrawEnd();
+    
+    SwapBuffers();
+}
+
+void UITestCanvas::OnIdle(wxIdleEvent& /* event */) {
+    Refresh(false);
+}
+
+void UITestCanvas::OnMouseMoved(wxMouseEvent& event) {
+    InteractiveCanvas::OnMouseMoved(event);
+    
+}
+
+void UITestCanvas::OnMouseDown(wxMouseEvent& event) {
+    InteractiveCanvas::OnMouseDown(event);
+}
+
+void UITestCanvas::OnMouseWheelMoved(wxMouseEvent& event) {
+    InteractiveCanvas::OnMouseWheelMoved(event);
+}
+
+void UITestCanvas::OnMouseReleased(wxMouseEvent& event) {
+    InteractiveCanvas::OnMouseReleased(event);
+}
+
+void UITestCanvas::OnMouseLeftWindow(wxMouseEvent& event) {
+    InteractiveCanvas::OnMouseLeftWindow(event);
+}
+
+void UITestCanvas::OnMouseEnterWindow(wxMouseEvent& event) {
+    InteractiveCanvas::mouseTracker.OnMouseEnterWindow(event);
+}
diff --git a/src/ui/UITestCanvas.h b/src/ui/UITestCanvas.h
new file mode 100644
index 0000000..a75481f
--- /dev/null
+++ b/src/ui/UITestCanvas.h
@@ -0,0 +1,35 @@
+#pragma once
+
+#include "wx/glcanvas.h"
+#include "wx/timer.h"
+
+#include <vector>
+#include <queue>
+
+#include "InteractiveCanvas.h"
+#include "UITestContext.h"
+#include "MouseTracker.h"
+
+#include "Timer.h"
+
+class UITestCanvas: public InteractiveCanvas {
+public:
+    UITestCanvas(wxWindow *parent, int *dispAttrs);
+    ~UITestCanvas();
+    
+private:
+    void OnPaint(wxPaintEvent& event);
+    void OnIdle(wxIdleEvent &event);
+    
+    void OnMouseMoved(wxMouseEvent& event);
+    void OnMouseDown(wxMouseEvent& event);
+    void OnMouseWheelMoved(wxMouseEvent& event);
+    void OnMouseReleased(wxMouseEvent& event);
+    void OnMouseEnterWindow(wxMouseEvent& event);
+    void OnMouseLeftWindow(wxMouseEvent& event);
+    
+    UITestContext *glContext;
+    
+    wxDECLARE_EVENT_TABLE();
+};
+
diff --git a/src/ui/UITestContext.cpp b/src/ui/UITestContext.cpp
new file mode 100644
index 0000000..1f5696a
--- /dev/null
+++ b/src/ui/UITestContext.cpp
@@ -0,0 +1,72 @@
+#include "UITestContext.h"
+#include "UITestCanvas.h"
+#include "ColorTheme.h"
+
+UITestContext::UITestContext(UITestCanvas *canvas, wxGLContext *sharedContext) :
+PrimaryGLContext(canvas, sharedContext), testMeter("TEST",0,100,50) {
+    
+    testPanel.setPosition(0.0, 0.0);
+    testPanel.setSize(1.0, 1.0);
+    testPanel.setMarginPx(10);
+    testPanel.setFill(GLPanel::GLPANEL_FILL_SOLID);
+    testPanel.setFillColor(RGBA4f(0.0,0.0,1.0));
+    
+    testChildPanel.setPosition(0.0, 0.0);
+    testChildPanel.setMarginPx(5);
+    testChildPanel.setSize(1.0f, 0.33f);
+    testChildPanel.setCoordinateSystem(GLPanel::GLPANEL_Y_DOWN_ZERO_ONE);
+    testChildPanel.setFill(GLPanel::GLPANEL_FILL_GRAD_BAR_X);
+    testChildPanel.setFillColor(RGBA4f(0.0,0.0,1.0), RGBA4f(0.0,1.0,0.0));
+    testChildPanel.setBorderPx(1);
+
+    testChildPanel2.setPosition(0.0f, -0.66f);
+    testChildPanel2.setSize(1.0f, 0.33f);
+    testChildPanel2.setMarginPx(5);
+    testChildPanel2.setFill(GLPanel::GLPANEL_FILL_GRAD_X);
+    testChildPanel2.setFillColor(RGBA4f(0.0,0.0,1.0), RGBA4f(0.0,1.0,0.0));
+    testChildPanel2.setBorderColor(RGBA4f(1.0,0.0,0.0));
+    testChildPanel2.setBorderPx(1);
+
+    testChildPanel3.setPosition(0.0f, 0.66f);
+    testChildPanel3.setSize(1.0f, 0.33f);
+    testChildPanel3.setMarginPx(5);
+    testChildPanel3.setFill(GLPanel::GLPANEL_FILL_GRAD_X);
+    testChildPanel3.setFillColor(RGBA4f(0.0,0.0,1.0), RGBA4f(0.0,1.0,0.0));
+    testChildPanel3.setBorderColor(RGBA4f(1.0,0.0,0.0));
+    testChildPanel3.setBorderPx(1);
+
+    testText1.setText("Testing 123..");
+    testText1.setFill(GLPanel::GLPANEL_FILL_NONE);
+    testChildPanel2.addChild(&testText1);
+    
+//    testPanel.addChild(&testChildPanel);
+//    testPanel.addChild(&testChildPanel2);
+//    testPanel.addChild(&testChildPanel3);
+    testMeter.setSize(0.1,0.9);
+    testPanel.addChild(&testMeter);
+}
+
+void UITestContext::DrawBegin() {
+    glDisable(GL_CULL_FACE);
+    glDisable(GL_DEPTH_TEST);
+    
+    glClearColor(ThemeMgr::mgr.currentTheme->generalBackground.r, ThemeMgr::mgr.currentTheme->generalBackground.g, ThemeMgr::mgr.currentTheme->generalBackground.b, 1.0);
+    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+    
+    glMatrixMode(GL_MODELVIEW);
+    glLoadIdentity();
+    
+    glDisable(GL_TEXTURE_2D);
+}
+
+void UITestContext::Draw() {
+    testPanel.calcTransform(CubicVR::mat4::identity());
+    testPanel.draw();
+}
+
+void UITestContext::DrawEnd() {
+//    glFlush();
+    
+//    CheckGLError();
+}
+
diff --git a/src/ui/UITestContext.h b/src/ui/UITestContext.h
new file mode 100644
index 0000000..45dd3e3
--- /dev/null
+++ b/src/ui/UITestContext.h
@@ -0,0 +1,24 @@
+#pragma once
+
+#include "PrimaryGLContext.h"
+#include "GLPanel.h"
+#include "MeterPanel.h"
+
+class UITestCanvas;
+
+class UITestContext: public PrimaryGLContext {
+public:
+    UITestContext(UITestCanvas *canvas, wxGLContext *sharedContext);
+    
+    void DrawBegin();
+    void Draw();
+    void DrawEnd();
+    
+private:
+    GLPanel testPanel;
+    GLTestPanel testChildPanel;
+    GLPanel testChildPanel2;
+    GLPanel testChildPanel3;
+    GLTextPanel testText1;
+    MeterPanel testMeter;
+};
diff --git a/src/util/DataTree.cpp b/src/util/DataTree.cpp
new file mode 100755
index 0000000..3c1d1ce
--- /dev/null
+++ b/src/util/DataTree.cpp
@@ -0,0 +1,1793 @@
+/*
+ * DataElement/DataNode/DataTree -- structured serialization/unserialization system 
+ * designed for the CoolMule project :)
+ *
+ Copyright (C) 2003 by Charles J. Cliffe
+ 
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+ 
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+ 
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+ */
+
+#include "DataTree.h"
+#include <fstream>
+#include <math.h>
+#include <iomanip>
+#include <locale>
+#include <stdlib.h>
+#include <algorithm>
+
+
+/* DataElement class */
+
+using namespace std;
+
+#define STRINGIFY(A)  #A
+
+DataElement::DataElement() : data_type(DATA_NULL), data_size(0), unit_size(0), data_val(NULL) {
+}
+
+DataElement::~DataElement() {
+    if (data_val) {
+        delete[] data_val;
+        data_val = NULL;
+    }
+}
+
+void DataElement::data_init(size_t data_size_in) {
+    if (data_val) {
+        delete[] data_val;
+        data_val = NULL;
+    }
+    data_size = data_size_in;
+    if (data_size) {
+        data_val = new char[data_size];
+        //memset to zero
+        std::fill_n(data_val, data_size, 0);
+    }
+}
+
+char * DataElement::getDataPointer() {
+    return data_val;
+}
+
+int DataElement::getDataType() {
+    return data_type;
+}
+
+size_t DataElement::getDataSize() {
+    return data_size;
+}
+
+unsigned int DataElement::getUnitSize() {
+    return unit_size;
+}
+
+#define DataElementSetNumericDef(enumtype, datatype) void DataElement::set(const datatype& val_in) { \
+        data_type = enumtype; \
+        unit_size = sizeof(datatype); \
+        data_init(unit_size); \
+        memcpy(data_val, &val_in, data_size); \
+}
+
+DataElementSetNumericDef(DATA_CHAR, char)
+DataElementSetNumericDef(DATA_UCHAR, unsigned char)
+DataElementSetNumericDef(DATA_INT, int)
+DataElementSetNumericDef(DATA_UINT, unsigned int)
+DataElementSetNumericDef(DATA_LONG, long)
+DataElementSetNumericDef(DATA_ULONG, unsigned long)
+DataElementSetNumericDef(DATA_LONGLONG, long long)
+DataElementSetNumericDef(DATA_FLOAT, float)
+DataElementSetNumericDef(DATA_DOUBLE, double)
+DataElementSetNumericDef(DATA_LONGDOUBLE, long double)
+
+void DataElement::set(const char *data_in, long size_in) {
+    data_type = DATA_VOID;
+    if (!data_size) { return; }
+    data_init(size_in);
+    memcpy(data_val, data_in, data_size);
+}
+
+void DataElement::set(const char *data_in) {
+    data_type = DATA_STRING;
+    data_init(strlen(data_in) + 1);
+    memcpy(data_val, data_in, data_size);
+}
+
+void DataElement::set(const string &str_in) {
+    data_type = DATA_STRING;
+    data_init(str_in.length() + 1);
+    memcpy(data_val, str_in.c_str(), data_size);
+}
+
+void DataElement::set(const wstring &wstr_in) {
+    data_type = DATA_WSTRING;
+
+    //wchar_t is tricky, the terminating zero is actually a (wchar_t)0 !
+    //wchar_t is typically 16 bits on windows, and 32 bits on Unix, so use sizeof(wchar_t) everywhere.
+    size_t maxLenBytes = (wstr_in.length()+1) * sizeof(wchar_t);
+
+    //be paranoid, zero the buffer
+    char *tmp_str = (char *)calloc(maxLenBytes, sizeof(char));
+
+    //if something awful happens, the last sizeof(wchar_t) is at least zero...
+    wcstombs(tmp_str, wstr_in.c_str(), maxLenBytes - sizeof(wchar_t));
+
+    data_init(maxLenBytes);
+
+    memcpy(data_val, tmp_str, data_size);
+    free(tmp_str);
+}
+
+void DataElement::set(vector<string> &strvect_in) {
+    vector<string>::iterator i;
+    long vectsize;
+    long ptr;
+
+    data_type = DATA_STR_VECTOR;
+
+    vectsize = 0;
+
+    for (i = strvect_in.begin(); i != strvect_in.end(); i++) {
+        vectsize += (*i).length() + 1;
+    }
+
+    data_init(vectsize);
+
+    ptr = 0;
+
+    for (i = strvect_in.begin(); i != strvect_in.end(); i++) {
+        int str_length;
+
+        str_length = (*i).length() + 1;
+
+        memcpy(data_val + ptr, (*i).c_str(), str_length);
+        ptr += str_length;
+    }
+}
+
+void DataElement::set(std::set<string> &strset_in) {
+    std::set<string>::iterator i;
+    vector<string> tmp_vect;
+
+    for (i = strset_in.begin(); i != strset_in.end(); i++) {
+        tmp_vect.push_back(*i);
+    }
+
+    set(tmp_vect);
+}
+
+#define DataElementSetNumericVectorDef(enumtype, datatype) void DataElement::set(vector<datatype>& val_in) { \
+        data_type = enumtype; \
+        unit_size = sizeof(datatype); \
+        data_init(unit_size * val_in.size()); \
+        memcpy(data_val, &val_in[0], data_size); \
+}
+
+DataElementSetNumericVectorDef(DATA_CHAR_VECTOR, char)
+DataElementSetNumericVectorDef(DATA_UCHAR_VECTOR, unsigned char)
+DataElementSetNumericVectorDef(DATA_INT_VECTOR, int)
+DataElementSetNumericVectorDef(DATA_UINT_VECTOR, unsigned int)
+DataElementSetNumericVectorDef(DATA_LONG_VECTOR, long)
+DataElementSetNumericVectorDef(DATA_ULONG_VECTOR, unsigned long)
+DataElementSetNumericVectorDef(DATA_LONGLONG_VECTOR, long long)
+DataElementSetNumericVectorDef(DATA_FLOAT_VECTOR, float)
+DataElementSetNumericVectorDef(DATA_DOUBLE_VECTOR, double)
+DataElementSetNumericVectorDef(DATA_LONGDOUBLE_VECTOR, long double)
+
+
+#define DataElementGetNumericDef(enumtype, datatype, ...) void DataElement::get(datatype& val_out) { \
+if (!data_type) \
+return; \
+    int _compat[] = {__VA_ARGS__}; \
+    if (data_type != enumtype) { \
+        bool compat = false; \
+        for (size_t i = 0; i < sizeof(_compat)/sizeof(int); i++) { \
+              if (_compat[i] == data_type) { \
+                  compat = true; \
+                  break; \
+              } \
+        } \
+        if (!compat) { \
+            throw(new DataTypeMismatchException("Type mismatch, element type " #enumtype " is not compatible with a " #datatype)); \
+        } \
+        if (sizeof(datatype) < data_size) { \
+            std::cout << "Warning, data type mismatch requested size for '" << #datatype << "(" << sizeof(datatype) << ")' < data size '" << data_size << "'; possible loss of data."; \
+        } \
+        memset(&val_out, 0, sizeof(datatype)); \
+        if (sizeof(datatype) > 4 && data_size <= 4) { \
+            int v = 0; memcpy(&v,data_val,data_size); \
+            val_out = (datatype)v; \
+            return; \
+        } else { \
+            memcpy(&val_out, data_val, (sizeof(datatype) < data_size) ? sizeof(datatype) : data_size); \
+        } \
+        return; \
+    } \
+    memcpy(&val_out, data_val, data_size); \
+}
+
+DataElementGetNumericDef(DATA_CHAR, char, DATA_UCHAR, DATA_UINT, DATA_ULONG, DATA_LONGLONG, DATA_LONGDOUBLE, DATA_INT, DATA_LONG)
+DataElementGetNumericDef(DATA_UCHAR, unsigned char, DATA_CHAR, DATA_UINT, DATA_ULONG, DATA_LONGLONG, DATA_LONGDOUBLE, DATA_INT, DATA_LONG)
+DataElementGetNumericDef(DATA_UINT, unsigned int, DATA_CHAR, DATA_UCHAR, DATA_ULONG, DATA_LONGLONG, DATA_LONGDOUBLE, DATA_INT, DATA_LONG)
+DataElementGetNumericDef(DATA_ULONG, unsigned long, DATA_CHAR, DATA_UCHAR, DATA_UINT, DATA_LONGLONG, DATA_LONGDOUBLE, DATA_INT, DATA_LONG)
+DataElementGetNumericDef(DATA_LONGLONG, long long, DATA_CHAR, DATA_UCHAR, DATA_UINT, DATA_ULONG, DATA_LONGDOUBLE, DATA_INT, DATA_LONG)
+DataElementGetNumericDef(DATA_LONGDOUBLE, long double, DATA_CHAR, DATA_UCHAR, DATA_UINT, DATA_ULONG, DATA_LONGLONG, DATA_INT, DATA_LONG)
+DataElementGetNumericDef(DATA_INT, int, DATA_CHAR, DATA_UCHAR, DATA_UINT, DATA_ULONG, DATA_LONGLONG, DATA_LONGDOUBLE, DATA_LONG)
+DataElementGetNumericDef(DATA_LONG, long, DATA_CHAR, DATA_UCHAR, DATA_UINT, DATA_ULONG, DATA_LONGLONG, DATA_LONGDOUBLE, DATA_INT)
+
+
+
+
+#define DataElementGetFloatingPointDef(enumtype, datatype, ...) void DataElement::get(datatype& val_out) { \
+if (!data_type) \
+return; \
+    int _compat[] = {__VA_ARGS__}; \
+    if (data_type != enumtype) { \
+        bool compat = false; \
+        for (size_t i = 0; i < sizeof(_compat)/sizeof(int); i++) { \
+              if (_compat[i] == data_type) { \
+                  compat = true; \
+                  break; \
+              } \
+        } \
+        if (!compat) { \
+            throw(new DataTypeMismatchException("Type mismatch, element type " #enumtype " is not compatible with a " #datatype)); \
+        } \
+        if (data_type == DATA_FLOAT || data_type == DATA_DOUBLE) { \
+            if (sizeof(datatype) < data_size) { \
+                std::cout << "Warning, data type mismatch requested size for '" << #datatype << "(" << sizeof(datatype) << ")' < data size '" << data_size << "'; possible loss of data."; \
+            } \
+            memset(&val_out, 0, sizeof(datatype)); \
+            if (sizeof(datatype) > 4 && data_size <= 4) { \
+                int v = 0; memcpy(&v,data_val,data_size); \
+                val_out = (datatype)v; \
+                return; \
+            } else { \
+                memcpy(&val_out, data_val, (sizeof(datatype) < data_size) ? sizeof(datatype) : data_size); \
+            } \
+        } else { \
+            long long tmp_int; \
+            get(tmp_int); \
+            datatype tmp_float = (float)tmp_int; \
+            memcpy(&val_out, &tmp_float, sizeof(datatype)); \
+        } \
+        return; \
+    } \
+    memcpy(&val_out, data_val, data_size); \
+}
+
+DataElementGetFloatingPointDef(DATA_FLOAT, float, DATA_DOUBLE, DATA_CHAR, DATA_UCHAR, DATA_UINT, DATA_ULONG, DATA_LONGLONG, DATA_LONGDOUBLE, DATA_INT,
+        DATA_LONG)
+DataElementGetFloatingPointDef(DATA_DOUBLE, double, DATA_FLOAT, DATA_CHAR, DATA_UCHAR, DATA_UINT, DATA_ULONG, DATA_LONGLONG, DATA_LONGDOUBLE, DATA_INT,
+        DATA_LONG)
+
+void DataElement::get(char **data_in) {
+    if (data_type != DATA_VOID)
+        throw(new DataTypeMismatchException("Type mismatch, not a CHAR*"));
+    *data_in = new char[data_size];
+    memcpy(*data_in, data_val, data_size);
+}
+
+void DataElement::get(string &str_in) {
+    if (!data_type)
+        return;
+
+    if (data_type != DATA_STRING)
+        throw(new DataTypeMismatchException("Type mismatch, not a STRING"));
+
+    if (!str_in.empty())	// flush the string
+    {
+        str_in.erase(str_in.begin(), str_in.end());
+    }
+
+    if (data_val) {
+        str_in.append(data_val);
+    }
+}
+
+void DataElement::get(wstring &wstr_in) {
+    if (!data_type)
+        return;
+    
+    if (data_type != DATA_WSTRING)
+        throw(new DataTypeMismatchException("Type mismatch, not a WSTRING"));
+
+    // flush the string
+    wstr_in.clear();
+       
+    if (data_val) {
+
+        //data_val is an array of bytes holding wchar_t characters, plus a terminating (wchar_t)0
+        //wchar_t is typically 16 bits on windows, and 32 bits on Unix, so use sizeof(wchar_t) everywhere.
+        int maxNbWchars = (data_size - sizeof(wchar_t)) / sizeof(wchar_t);
+
+        //be paranoid, zero the buffer
+        wchar_t *tmp_wstr = (wchar_t *)calloc(maxNbWchars + 1, sizeof(wchar_t));
+
+        //the last wchar_t is actually zero if anything goes wrong...
+        mbstowcs(tmp_wstr, data_val, maxNbWchars);
+
+        wstr_in.assign(tmp_wstr);
+
+        free(tmp_wstr);
+    }
+}
+
+void DataElement::get(vector<string> &strvect_in) {
+    size_t ptr;
+    if (!data_type)
+        return;
+
+    if (data_type != DATA_STR_VECTOR)
+        throw(new DataTypeMismatchException("Type mismatch, not a STRING VECTOR"));
+
+    ptr = 0;
+
+    while (ptr != data_size) {
+        strvect_in.push_back(string(data_val + ptr));
+        ptr += strlen(data_val + ptr) + 1;
+    }
+
+}
+
+void DataElement::get(std::set<string> &strset_in) {
+    if (!data_type)
+        return;
+
+    if (data_type != DATA_STR_VECTOR)
+        throw(new DataTypeMismatchException("Type mismatch, not a STRING VECTOR/SET"));
+
+    std::vector<string> tmp_vect;
+    std::vector<string>::iterator i;
+
+    get(tmp_vect);
+
+    for (i = tmp_vect.begin(); i != tmp_vect.end(); i++) {
+        strset_in.insert(*i);
+    }
+}
+
+#define DataElementGetNumericVectorDef(enumtype, datatype, ...) void DataElement::get(vector<datatype>& val_out) { \
+if (!data_type || !unit_size) return; \
+if (data_type != enumtype) { \
+       int _compat[] = {__VA_ARGS__}; \
+       bool compat = false; \
+       for (size_t i = 0; i < sizeof(_compat)/sizeof(int); i++) { \
+             if (_compat[i] == data_type) { \
+                 compat = true; \
+                 break; \
+             } \
+       } \
+       if (!compat) { \
+           throw(new DataTypeMismatchException("Type mismatch, element type is not compatible with a " #datatype)); \
+       } \
+       if (sizeof(datatype) < unit_size) { \
+           std::cout << "Warning, data type mismatch for vector<" #datatype ">; " #datatype " size " << sizeof(datatype) << " is less than unit size " << unit_size << "; possible loss of data."; \
+       } \
+       datatype temp_val; \
+       size_t ptr = 0; \
+           while (ptr < data_size) { \
+               temp_val = 0; \
+               memcpy(&temp_val, data_val + ptr, (unit_size > sizeof(datatype))?sizeof(datatype):unit_size); \
+               val_out.push_back(temp_val); \
+               ptr += unit_size; \
+           } \
+           return; \
+    } \
+    val_out.assign(data_val, data_val + (data_size / sizeof(datatype))); \
+}
+
+DataElementGetNumericVectorDef(DATA_CHAR_VECTOR, char, DATA_UCHAR_VECTOR, DATA_INT_VECTOR, DATA_UINT_VECTOR, DATA_LONG_VECTOR, DATA_ULONG_VECTOR,
+        DATA_LONGLONG_VECTOR);
+DataElementGetNumericVectorDef(DATA_UCHAR_VECTOR, unsigned char, DATA_CHAR_VECTOR, DATA_INT_VECTOR, DATA_UINT_VECTOR, DATA_LONG_VECTOR,
+        DATA_ULONG_VECTOR, DATA_LONGLONG_VECTOR);
+DataElementGetNumericVectorDef(DATA_INT_VECTOR, int, DATA_CHAR_VECTOR, DATA_UCHAR_VECTOR, DATA_UINT_VECTOR, DATA_LONG_VECTOR, DATA_ULONG_VECTOR,
+        DATA_LONGLONG_VECTOR);
+DataElementGetNumericVectorDef(DATA_UINT_VECTOR, unsigned int, DATA_CHAR_VECTOR, DATA_UCHAR_VECTOR, DATA_INT_VECTOR, DATA_LONG_VECTOR,
+        DATA_ULONG_VECTOR, DATA_LONGLONG_VECTOR);
+DataElementGetNumericVectorDef(DATA_LONG_VECTOR, long, DATA_CHAR_VECTOR, DATA_UCHAR_VECTOR, DATA_INT_VECTOR, DATA_UINT_VECTOR, DATA_ULONG_VECTOR,
+        DATA_LONGLONG_VECTOR);
+DataElementGetNumericVectorDef(DATA_ULONG_VECTOR, unsigned long, DATA_CHAR_VECTOR, DATA_UCHAR_VECTOR, DATA_INT_VECTOR, DATA_UINT_VECTOR,
+        DATA_LONG_VECTOR, DATA_LONGLONG_VECTOR);
+DataElementGetNumericVectorDef(DATA_LONGLONG_VECTOR, long long, DATA_CHAR_VECTOR, DATA_UCHAR_VECTOR, DATA_INT_VECTOR, DATA_UINT_VECTOR,
+        DATA_LONG_VECTOR, DATA_ULONG_VECTOR);
+DataElementGetNumericVectorDef(DATA_FLOAT_VECTOR, float, DATA_DOUBLE_VECTOR, DATA_LONGDOUBLE_VECTOR);
+DataElementGetNumericVectorDef(DATA_DOUBLE_VECTOR, double, DATA_FLOAT_VECTOR, DATA_LONGDOUBLE_VECTOR);
+DataElementGetNumericVectorDef(DATA_LONGDOUBLE_VECTOR, long double, DATA_DOUBLE_VECTOR, DATA_FLOAT_VECTOR);
+
+std::string DataElement::toString() {
+    int dataType = getDataType();
+    std::string strValue = "";
+    
+    try {
+        if (dataType == DATA_STRING) {
+            get(strValue);
+        } else if (dataType == DATA_INT || dataType == DATA_LONG || dataType == DATA_LONGLONG) {
+            long long intSettingValue;
+            get(intSettingValue);
+            strValue = std::to_string(intSettingValue);
+        } else if (dataType == DATA_FLOAT || dataType == DATA_DOUBLE) {
+            double floatSettingValue;
+            get(floatSettingValue);
+            strValue = std::to_string(floatSettingValue);
+        } else if (dataType == DATA_NULL) {
+            strValue = "";
+        } else {
+            std::cout << "Unhandled DataElement toString for type: " << dataType  << std::endl;
+        }
+    } catch (DataTypeMismatchException e) {
+        std::cout << "toString() DataTypeMismatch: " << dataType  << std::endl;
+    }
+    
+    return strValue;
+}
+
+long DataElement::getSerializedSize() {
+    return sizeof(int) + sizeof(long) + data_size;
+}
+
+long DataElement::getSerialized(char **ser_str) {
+    long ser_size = getSerializedSize();
+
+    *ser_str = new char[ser_size];
+
+    char *ser_pointer;
+
+    ser_pointer = *ser_str;
+
+    memcpy(ser_pointer, &data_type, sizeof(int));
+    ser_pointer += sizeof(int);
+    memcpy(ser_pointer, &data_size, sizeof(long));
+    ser_pointer += sizeof(long);
+    memcpy(ser_pointer, data_val, data_size);
+
+    return ser_size;
+}
+
+void DataElement::setSerialized(char *ser_str) {
+    char *ser_pointer = ser_str;
+
+    memcpy(&data_type, ser_pointer, sizeof(unsigned char));
+    ser_pointer += sizeof(unsigned char);
+    memcpy(&data_size, ser_pointer, sizeof(unsigned int));
+    ser_pointer += sizeof(unsigned int);
+
+    data_init(data_size);
+    memcpy(data_val, ser_pointer, data_size);
+}
+
+/* DataNode class */
+
+DataNode::DataNode(): parentNode(NULL), ptr(0) {
+    data_elem = new DataElement();
+}
+
+DataNode::DataNode(const char *name_in): parentNode(NULL), ptr(0) {
+    node_name = name_in;
+    data_elem = new DataElement();
+}
+
+DataNode::~DataNode() {
+    while (children.size()) {
+        DataNode *del = children.back();
+        children.pop_back();
+        delete del;
+    }
+    if (data_elem) {
+        delete data_elem;
+    }
+}
+
+void DataNode::setName(const char *name_in) {
+    node_name = name_in;
+}
+
+DataElement *DataNode::element() {
+    return data_elem;
+}
+
+DataNode *DataNode::newChild(const char *name_in) {
+    children.push_back(new DataNode(name_in));
+    childmap[name_in].push_back(children.back());
+
+    children.back()->setParentNode(*this);
+
+    return children.back();
+}
+
+DataNode *DataNode::child(const char *name_in, int index) {
+    DataNode *child_ret;
+
+    child_ret = childmap[name_in][index];
+
+    if (!child_ret) {
+        stringstream error_str;
+        error_str << "no child '" << index << "' in DataNode '" << node_name << "'";
+        throw(DataInvalidChildException(error_str.str().c_str()));
+    }
+
+    return child_ret;
+}
+
+DataNode *DataNode::child(int index) {
+
+    DataNode *child_ret;
+
+    child_ret = children[index];
+
+    if (!child_ret) {
+        stringstream error_str;
+        error_str << "no child '" << index << "' in DataNode '" << node_name << "'";
+        throw(DataInvalidChildException(error_str.str().c_str()));
+    }
+
+    return child_ret;
+}
+
+int DataNode::numChildren() {
+    return children.size();
+}
+
+int DataNode::numChildren(const char *name_in) {
+    return childmap[name_in].size();
+}
+
+bool DataNode::hasAnother() {
+    return children.size() != ptr;
+}
+
+bool DataNode::hasAnother(const char *name_in) {
+    return childmap[name_in].size() != childmap_ptr[name_in];
+}
+
+DataNode *DataNode::getNext() {
+    return child(ptr++);
+}
+
+DataNode *DataNode::getNext(const char *name_in) {
+    return child(name_in, childmap_ptr[name_in]++);
+}
+
+void DataNode::rewind() {
+    ptr = 0;
+}
+
+void DataNode::rewind(const char *name_in) {
+    childmap_ptr[name_in] = 0;
+}
+
+/* DataTree class */
+
+DataTree::DataTree(const char *name_in) {
+    dn_root.setName(name_in);
+}
+
+DataTree::DataTree() {
+
+}
+
+DataTree::~DataTree() {
+}
+;
+
+DataNode *DataTree::rootNode() {
+    return &dn_root;
+}
+
+std::string trim(std::string& s, const std::string& drop = " ") {
+    std::string r = s.erase(s.find_last_not_of(drop) + 1);
+    return r.erase(0, r.find_first_not_of(drop));
+}
+
+string DataTree::wsEncode(const wstring& wstr) {
+    stringstream encStream;
+    
+    //wchar_t is typically 16 bits on windows, and 32 bits on Unix, so use sizeof(wchar_t) everywhere.
+    int bufSizeBytes = (wstr.length()+1) * sizeof(wchar_t);
+
+    char *data_str = (char *)calloc(bufSizeBytes, sizeof(char));
+    
+    wcstombs(data_str, wstr.c_str(), bufSizeBytes - sizeof(wchar_t));
+    
+    std::string byte_str(data_str);
+
+    free(data_str);
+    
+    encStream << std::hex;
+
+    for(auto i = byte_str.begin(); i != byte_str.end(); i++) {
+        encStream << '%' << setfill('0') << (unsigned int)((unsigned char)(*i));
+    }
+    
+    return encStream.str();
+}
+
+wstring DataTree::wsDecode(const string& str) {
+    
+    std::stringstream decStream;
+    std::stringstream mbstr;
+    unsigned int x;
+    
+    string decStr = str;
+    std::replace( decStr.begin(), decStr.end(), '%', ' ');
+    decStream << trim(decStr);
+  
+    string sResult;
+
+    //this actually assume we will get as many char as wchar_t from the decodes string,
+    //who cares ?
+    int maxLen = decStr.length();
+
+    //wchar_t is typically 16 bits on windows, and 32 bits on Unix, so use sizeof(wchar_t) everywhere.
+    wchar_t *wc_str = (wchar_t *) calloc(maxLen  + 1, sizeof(wchar_t));
+
+    while (!decStream.eof()) {
+        decStream >> std::hex >> x;
+        //extract actually 2 hex-chars by 2 hex-chars to form a char value.
+        mbstr << (unsigned char) x;
+    }
+
+    mbstowcs(wc_str, mbstr.str().c_str(), maxLen);
+    
+    wstring result(wc_str);
+
+    free(wc_str);
+    
+    return result;
+}
+
+void DataTree::decodeXMLText(DataNode *elem, const char *src_text, DT_FloatingPointPolicy fpp) {
+
+    int tmp_char;
+    int tmp_int;
+    long tmp_long;
+    long long tmp_llong;
+    double tmp_double;
+    float tmp_float;
+    string tmp_str;
+    string tmp_str2;
+    std::stringstream tmp_stream;
+    std::stringstream tmp_stream2;
+
+    vector<char> tmp_charvect;
+    vector<int> tmp_intvect;
+    vector<long> tmp_longvect;
+    vector<long> tmp_llongvect;
+    vector<long>::iterator tmp_llongvect_i;
+    vector<double> tmp_doublevect;
+    vector<double>::iterator tmp_doublevect_i;
+    vector<float> tmp_floatvect;
+
+    bool vChars = false;
+    bool vInts = false;
+    bool vLongs = false;
+
+    string in_text = src_text;
+
+    trim(in_text);
+    trim(in_text, "\r\n");
+    tmp_stream.str("");
+    tmp_stream2.str("");
+
+    if (in_text.find_first_not_of("0123456789-") == string::npos) {
+        tmp_stream << in_text;
+        tmp_stream >> tmp_llong;
+
+        tmp_int = (int)tmp_llong;
+        tmp_long = (long)tmp_llong;
+
+        if (tmp_int == tmp_llong) {
+            elem->element()->set(tmp_int);
+        } else if (tmp_long == tmp_llong) {
+            elem->element()->set(tmp_long);
+        } else {
+            elem->element()->set(tmp_llong);
+        }
+    } else if (in_text.find_first_not_of("0123456789.e+-") == string::npos) {
+        tmp_stream << in_text;
+
+        if (fpp == USE_FLOAT) {
+            tmp_stream >> tmp_float;
+
+            elem->element()->set((float) tmp_float);
+        } else {
+            tmp_stream >> tmp_double;
+
+            elem->element()->set((double) tmp_double);
+        }
+    } else if (in_text.find_first_not_of("0123456789- ") == string::npos) {
+        tmp_stream << in_text;
+
+        vChars = true;
+        vInts = true;
+        vLongs = true;
+
+        while (!tmp_stream.eof()) {
+            tmp_stream >> tmp_llong;
+            tmp_char = tmp_llong;
+            tmp_int = tmp_llong;
+            tmp_long = tmp_llong;
+            if (tmp_char != tmp_llong) {
+                vChars = false;
+            }
+            if (tmp_int != tmp_llong) {
+                vInts = false;
+            }
+            if (tmp_long != tmp_llong) {
+                vLongs = false;
+            }
+            tmp_llongvect.push_back((long) tmp_long);
+        }
+
+        if (vChars) {
+            for (tmp_llongvect_i = tmp_llongvect.begin(); tmp_llongvect_i != tmp_llongvect.end(); tmp_llongvect_i++) {
+                tmp_charvect.push_back(*tmp_llongvect_i);
+            }
+            tmp_llongvect.clear();
+            elem->element()->set(tmp_charvect);
+            tmp_charvect.clear();
+
+        } else if (vInts) {
+            for (tmp_llongvect_i = tmp_llongvect.begin(); tmp_llongvect_i != tmp_llongvect.end(); tmp_llongvect_i++) {
+                tmp_intvect.push_back(*tmp_llongvect_i);
+            }
+            tmp_llongvect.clear();
+            elem->element()->set(tmp_intvect);
+            tmp_intvect.clear();
+        } else if (vLongs) {
+            for (tmp_llongvect_i = tmp_llongvect.begin(); tmp_llongvect_i != tmp_llongvect.end(); tmp_llongvect_i++) {
+                tmp_longvect.push_back(*tmp_llongvect_i);
+            }
+            tmp_llongvect.clear();
+            elem->element()->set(tmp_longvect);
+            tmp_longvect.clear();
+        } else {
+            elem->element()->set(tmp_llongvect);
+        }
+    } else if (in_text.find_first_not_of("0123456789.e-+ ") == string::npos) {
+        tmp_stream << in_text;
+
+        if (fpp == USE_FLOAT) {
+            tmp_floatvect.clear();
+        } else {
+            tmp_doublevect.clear();
+        }
+
+        while (!tmp_stream.eof()) {
+
+            if (fpp == USE_FLOAT) {
+                tmp_stream >> tmp_float;
+                tmp_floatvect.push_back(tmp_float);
+            } else {
+                tmp_stream >> tmp_double;
+                tmp_doublevect.push_back(tmp_double);
+            }
+        }
+
+        if (fpp == USE_FLOAT) {
+            elem->element()->set(tmp_floatvect);
+        } else {
+            elem->element()->set(tmp_doublevect);
+        }
+    } else if (in_text.find_first_not_of("0123456789abcdef%") == string::npos) {
+        elem->element()->set(wsDecode(src_text));
+    } else {
+        elem->element()->set(src_text);
+        //					printf( "Unhandled DataTree XML Field: [%s]", tmp_str.c_str() );
+    }
+
+}
+
+void DataTree::setFromXML(DataNode *elem, TiXmlNode *elxml, bool root_node, DT_FloatingPointPolicy fpp) {
+    TiXmlText *pText;
+    int t = elxml->Type();
+    string tmp_str;
+
+    switch (t) {
+    case TiXmlNode::TINYXML_DOCUMENT:
+        //				printf( "Document" );
+        break;
+
+    case TiXmlNode::TINYXML_ELEMENT:
+        if (!root_node)
+            elem = elem->newChild(elxml->Value());
+
+        const TiXmlAttribute *attribs;
+        attribs = elxml->ToElement()->FirstAttribute();
+
+        while (attribs) {
+
+// following badgerfish xml->json and xml->ruby convention for attributes..
+            string attrName("@");
+            attrName.append(attribs->Name());
+
+            decodeXMLText(elem->newChild(attrName.c_str()), attribs->Value(), fpp);
+
+            attribs = attribs->Next();
+        }
+
+        //				printf( "Element \"%s\"", elxml->Value());
+        break;
+
+    case TiXmlNode::TINYXML_COMMENT:
+//				printf( "Comment: \"%s\"", elxml->Value());
+        break;
+
+    case TiXmlNode::TINYXML_UNKNOWN:
+//				printf( "Unknown" );
+        break;
+
+    case TiXmlNode::TINYXML_TEXT:
+        pText = elxml->ToText();
+
+        decodeXMLText(elem, pText->Value(), fpp);
+
+//				pText = elxml->ToText();
+//				printf( "Text: [%s]", pText->Value() );
+        break;
+
+    case TiXmlNode::TINYXML_DECLARATION:
+//				printf( "Declaration" );
+        break;
+    default:
+        break;
+    }
+
+//	printf( "\n" );
+
+    TiXmlNode * pChild;
+
+    if (!elxml->NoChildren()) {
+        if (elxml->FirstChild()->Type() == TiXmlNode::TINYXML_ELEMENT) {
+            if (elxml->FirstChild()->Value() == TIXML_STRING("str")) {
+                std::vector<std::string> tmp_strvect;
+
+                for (pChild = elxml->FirstChild(); pChild != 0; pChild = pChild->NextSibling()) {
+                    if (pChild->Value() == TIXML_STRING("str")) {
+                        if (!pChild->FirstChild()) {
+                            tmp_strvect.push_back("");
+                            continue;
+                        }
+
+                        pText = pChild->FirstChild()->ToText();
+
+                        if (pText) {
+                            tmp_str = pText->Value();
+                            tmp_strvect.push_back(tmp_str);
+                        }
+                    }
+                }
+
+                elem->element()->set(tmp_strvect);
+
+                return;
+            }
+        }
+    }
+
+    for (pChild = elxml->FirstChild(); pChild != 0; pChild = pChild->NextSibling()) {
+        setFromXML(elem, pChild, false, fpp);
+    }
+
+}
+
+void DataTree::nodeToXML(DataNode *elem, TiXmlElement *elxml) {
+    DataNode *child;
+
+    elem->rewind();
+
+    while (elem->hasAnother()) {
+        child = elem->getNext();
+
+        std::string nodeName = child->getName();
+
+        TiXmlElement *element;
+
+        element = new TiXmlElement(nodeName.length() ? nodeName.c_str() : "node");
+        std::string tmp;
+        std::wstring wtmp;
+        std::stringstream tmp_stream;
+        TiXmlText *text;
+        std::vector<float> tmp_floatvect;
+        std::vector<float>::iterator tmp_floatvect_i;
+        std::vector<double> tmp_doublevect;
+        std::vector<double>::iterator tmp_doublevect_i;
+        std::vector<int> tmp_intvect;
+        std::vector<int>::iterator tmp_intvect_i;
+        std::vector<char> tmp_charvect;
+        std::vector<char>::iterator tmp_charvect_i;
+        std::vector<unsigned char> tmp_ucharvect;
+        std::vector<unsigned char>::iterator tmp_ucharvect_i;
+        std::vector<unsigned int> tmp_uintvect;
+        std::vector<unsigned int>::iterator tmp_uintvect_i;
+        std::vector<long> tmp_longvect;
+        std::vector<long>::iterator tmp_longvect_i;
+        std::vector<unsigned long> tmp_ulongvect;
+        std::vector<unsigned long>::iterator tmp_ulongvect_i;
+        std::vector<long long> tmp_llongvect;
+        std::vector<long long>::iterator tmp_llongvect_i;
+        std::vector<unsigned long long> tmp_ullongvect;
+        std::vector<unsigned long long>::iterator tmp_ullongvect_i;
+        std::vector<string> tmp_stringvect;
+        std::vector<string>::iterator tmp_stringvect_i;
+        TiXmlElement *tmp_node;
+        char *tmp_pstr;
+        double tmp_double;
+        long double tmp_ldouble;
+        float tmp_float;
+        char tmp_char;
+        unsigned char tmp_uchar;
+        int tmp_int;
+        unsigned int tmp_uint;
+        long tmp_long;
+        unsigned long tmp_ulong;
+        long long tmp_llong;
+
+        switch (child->element()->getDataType()) {
+        case DATA_NULL:
+            break;
+        case DATA_VOID:
+            child->element()->get(&tmp_pstr);
+// following badgerfish xml->json and xml->ruby convention for attributes..
+            if (nodeName.substr(0, 1) == string("@")) {
+                elxml->SetAttribute(nodeName.substr(1).c_str(), tmp_pstr);
+                delete element;
+                element = NULL;
+            } else {
+                text = new TiXmlText(tmp_pstr);
+                element->LinkEndChild(text);
+            }
+            delete[] tmp_pstr;
+            break;
+        case DATA_CHAR:
+            child->element()->get(tmp_char);
+
+            tmp_stream.str("");
+
+            tmp_stream << tmp_char;
+
+            text = new TiXmlText(tmp_stream.str().c_str());
+            element->LinkEndChild(text);
+            break;
+        case DATA_UCHAR:
+            child->element()->get(tmp_uchar);
+
+            tmp_stream.str("");
+
+            tmp_stream << tmp_uchar;
+
+            text = new TiXmlText(tmp_stream.str().c_str());
+            element->LinkEndChild(text);
+            break;
+        case DATA_INT:
+            child->element()->get(tmp_int);
+
+            tmp_stream.str("");
+
+            tmp_stream << tmp_int;
+
+            text = new TiXmlText(tmp_stream.str().c_str());
+            element->LinkEndChild(text);
+            break;
+        case DATA_UINT:
+            child->element()->get(tmp_uint);
+
+            tmp_stream.str("");
+
+            tmp_stream << tmp_uint;
+
+            text = new TiXmlText(tmp_stream.str().c_str());
+            element->LinkEndChild(text);
+            break;
+        case DATA_LONG:
+            child->element()->get(tmp_long);
+
+            tmp_stream.str("");
+
+            tmp_stream << tmp_long;
+
+            text = new TiXmlText(tmp_stream.str().c_str());
+            element->LinkEndChild(text);
+            break;
+        case DATA_ULONG:
+            child->element()->get(tmp_ulong);
+
+            tmp_stream.str("");
+
+            tmp_stream << tmp_ulong;
+
+            text = new TiXmlText(tmp_stream.str().c_str());
+            element->LinkEndChild(text);
+            break;
+        case DATA_LONGLONG:
+            child->element()->get(tmp_llong);
+
+            tmp_stream.str("");
+
+            tmp_stream << tmp_llong;
+
+            text = new TiXmlText(tmp_stream.str().c_str());
+            element->LinkEndChild(text);
+            break;
+        case DATA_FLOAT:
+            child->element()->get(tmp_float);
+
+            tmp_stream.str("");
+
+            tmp_stream << tmp_float;
+
+            text = new TiXmlText(tmp_stream.str().c_str());
+            element->LinkEndChild(text);
+            break;
+        case DATA_DOUBLE:
+            child->element()->get(tmp_double);
+
+            tmp_stream.str("");
+
+            tmp_stream << tmp_double;
+
+            text = new TiXmlText(tmp_stream.str().c_str());
+            element->LinkEndChild(text);
+            break;
+        case DATA_LONGDOUBLE:
+            child->element()->get(tmp_ldouble);
+
+            tmp_stream.str("");
+
+            tmp_stream << tmp_ldouble;
+
+            text = new TiXmlText(tmp_stream.str().c_str());
+            element->LinkEndChild(text);
+            break;
+        case DATA_STRING:
+            child->element()->get(tmp);
+            if (nodeName.substr(0, 1) == string("@")) {
+                elxml->SetAttribute(nodeName.substr(1).c_str(), tmp.c_str());
+                delete element;
+                element = NULL;
+            } else {
+                text = new TiXmlText(tmp.c_str());
+                element->LinkEndChild(text);
+            }
+            break;
+        case DATA_WSTRING:
+            child->element()->get(wtmp);
+            tmp = wsEncode(wtmp);
+            if (nodeName.substr(0, 1) == string("@")) {
+                elxml->SetAttribute(nodeName.substr(1).c_str(), tmp.c_str());
+                delete element;
+                element = NULL;
+            } else {
+                text = new TiXmlText(tmp.c_str());
+                element->LinkEndChild(text);
+            }
+            break;
+        case DATA_STR_VECTOR:
+            child->element()->get(tmp_stringvect);
+
+            tmp_stream.str("");
+
+            for (tmp_stringvect_i = tmp_stringvect.begin(); tmp_stringvect_i != tmp_stringvect.end(); tmp_stringvect_i++) {
+                tmp_node = new TiXmlElement("str");
+                text = new TiXmlText((*tmp_stringvect_i).c_str());
+                tmp_node->LinkEndChild(text);
+                element->LinkEndChild(tmp_node);
+            }
+
+            tmp_stringvect.clear();
+            break;
+        case DATA_CHAR_VECTOR:
+            child->element()->get(tmp_charvect);
+
+            tmp_stream.str("");
+
+            for (tmp_charvect_i = tmp_charvect.begin(); tmp_charvect_i != tmp_charvect.end(); tmp_charvect_i++) {
+                tmp_stream << (*tmp_charvect_i);
+                if (tmp_charvect_i != tmp_charvect.end() - 1)
+                    tmp_stream << " ";
+            }
+
+            text = new TiXmlText(tmp_stream.str().c_str());
+            element->LinkEndChild(text);
+            tmp_charvect.clear();
+            break;
+        case DATA_UCHAR_VECTOR:
+            child->element()->get(tmp_ucharvect);
+
+            tmp_stream.str("");
+
+            for (tmp_ucharvect_i = tmp_ucharvect.begin(); tmp_ucharvect_i != tmp_ucharvect.end(); tmp_ucharvect_i++) {
+                tmp_stream << (*tmp_ucharvect_i);
+                if (tmp_ucharvect_i != tmp_ucharvect.end() - 1)
+                    tmp_stream << " ";
+            }
+
+            text = new TiXmlText(tmp_stream.str().c_str());
+            element->LinkEndChild(text);
+            tmp_ucharvect.clear();
+            break;
+        case DATA_INT_VECTOR:
+            child->element()->get(tmp_intvect);
+
+            tmp_stream.str("");
+
+            for (tmp_intvect_i = tmp_intvect.begin(); tmp_intvect_i != tmp_intvect.end(); tmp_intvect_i++) {
+                tmp_stream << (*tmp_intvect_i);
+                if (tmp_intvect_i != tmp_intvect.end() - 1)
+                    tmp_stream << " ";
+            }
+
+            text = new TiXmlText(tmp_stream.str().c_str());
+            element->LinkEndChild(text);
+            tmp_intvect.clear();
+            break;
+        case DATA_UINT_VECTOR:
+            child->element()->get(tmp_uintvect);
+
+            tmp_stream.str("");
+
+            for (tmp_uintvect_i = tmp_uintvect.begin(); tmp_uintvect_i != tmp_uintvect.end(); tmp_uintvect_i++) {
+                tmp_stream << (*tmp_intvect_i);
+                if (tmp_uintvect_i != tmp_uintvect.end() - 1)
+                    tmp_stream << " ";
+            }
+
+            text = new TiXmlText(tmp_stream.str().c_str());
+            element->LinkEndChild(text);
+            tmp_uintvect.clear();
+            break;
+        case DATA_LONG_VECTOR:
+            child->element()->get(tmp_longvect);
+
+            tmp_stream.str("");
+
+            for (tmp_longvect_i = tmp_longvect.begin(); tmp_longvect_i != tmp_longvect.end(); tmp_longvect_i++) {
+                tmp_stream << (*tmp_longvect_i);
+                if (tmp_longvect_i != tmp_longvect.end() - 1)
+                    tmp_stream << " ";
+            }
+
+            text = new TiXmlText(tmp_stream.str().c_str());
+            element->LinkEndChild(text);
+            tmp_longvect.clear();
+            break;
+        case DATA_ULONG_VECTOR:
+            child->element()->get(tmp_ulongvect);
+
+            tmp_stream.str("");
+
+            for (tmp_ulongvect_i = tmp_ulongvect.begin(); tmp_ulongvect_i != tmp_ulongvect.end(); tmp_ulongvect_i++) {
+                tmp_stream << (*tmp_ulongvect_i);
+                if (tmp_ulongvect_i != tmp_ulongvect.end() - 1)
+                    tmp_stream << " ";
+            }
+
+            text = new TiXmlText(tmp_stream.str().c_str());
+            element->LinkEndChild(text);
+            tmp_ulongvect.clear();
+            break;
+        case DATA_LONGLONG_VECTOR:
+            child->element()->get(tmp_llongvect);
+
+            tmp_stream.str("");
+
+            for (tmp_llongvect_i = tmp_llongvect.begin(); tmp_llongvect_i != tmp_llongvect.end(); tmp_llongvect_i++) {
+                tmp_stream << (*tmp_llongvect_i);
+                if (tmp_llongvect_i != tmp_llongvect.end() - 1)
+                    tmp_stream << " ";
+            }
+
+            text = new TiXmlText(tmp_stream.str().c_str());
+            element->LinkEndChild(text);
+            tmp_llongvect.clear();
+            break;
+        case DATA_FLOAT_VECTOR:
+            child->element()->get(tmp_floatvect);
+
+            tmp_stream.str("");
+
+            for (tmp_floatvect_i = tmp_floatvect.begin(); tmp_floatvect_i != tmp_floatvect.end(); tmp_floatvect_i++) {
+                tmp_stream << (*tmp_floatvect_i);
+                if (tmp_floatvect_i != tmp_floatvect.end() - 1)
+                    tmp_stream << " ";
+            }
+
+            text = new TiXmlText(tmp_stream.str().c_str());
+            element->LinkEndChild(text);
+            tmp_floatvect.clear();
+            break;
+        case DATA_DOUBLE_VECTOR:
+            child->element()->get(tmp_doublevect);
+
+            tmp_stream.str("");
+
+            for (tmp_doublevect_i = tmp_doublevect.begin(); tmp_doublevect_i != tmp_doublevect.end(); tmp_doublevect_i++) {
+                tmp_stream << (*tmp_doublevect_i);
+                if (tmp_doublevect_i != tmp_doublevect.end() - 1)
+                    tmp_stream << " ";
+            }
+
+            text = new TiXmlText(tmp_stream.str().c_str());
+            element->LinkEndChild(text);
+            tmp_doublevect.clear();
+            break;
+        }
+
+        if (element) {
+            elxml->LinkEndChild(element);
+
+            if (child->numChildren()) {
+                nodeToXML(child, element);
+            }
+        }
+    }
+
+    elem->rewind();
+}
+
+void DataTree::printXML() /* get serialized size + return node names header */
+{
+    TiXmlDocument doc;
+    TiXmlDeclaration * decl = new TiXmlDeclaration("1.0", "", "");
+    doc.LinkEndChild(decl);
+
+    DataNode *root = rootNode();
+
+    string rootName = root->getName();
+
+    TiXmlElement *element = new TiXmlElement(rootName.empty() ? "root" : rootName.c_str());
+    doc.LinkEndChild(element);
+
+    if (!root->numChildren())
+        doc.Print();
+
+    nodeToXML(root, element);
+
+    root->rewind();
+
+    doc.Print();
+}
+
+long DataTree::getSerializedSize(DataElement &de_node_names, bool debug) /* get serialized size + return node names header */
+{
+    long total_size = 0;
+
+    stack<DataNode *> dn_stack;
+    vector<string> node_names;
+    map<string, int, string_less> node_name_index_map;
+
+    DataElement de_name_index;	// just used for sizing purposes
+    DataElement de_num_children;
+
+    de_name_index.set((int) 0);
+    de_num_children.set((int) 0);
+
+    int de_name_index_size = de_name_index.getSerializedSize();
+    int de_num_children_size = de_num_children.getSerializedSize();
+
+    dn_stack.push(&dn_root);
+
+    while (!dn_stack.empty()) {
+        int name_index;
+        //					int num_children;
+
+        /* build the name list */
+        if (dn_stack.top()->getName().empty()) {
+            name_index = 0; /* empty string */
+        } else if (node_name_index_map[dn_stack.top()->getName().c_str()] == 0) {
+            node_names.push_back(string(dn_stack.top()->getName()));
+            name_index = node_names.size();
+            node_name_index_map[dn_stack.top()->getName().c_str()] = name_index;
+        } else {
+            name_index = node_name_index_map[dn_stack.top()->getName().c_str()];
+        }
+
+        /* add on the size of the name index and number of children */
+        total_size += de_name_index_size;
+        total_size += de_num_children_size;
+        total_size += dn_stack.top()->element()->getSerializedSize();
+
+        /* debug output */
+        if (debug) {
+            for (unsigned int i = 0; i < dn_stack.size() - 1; i++)
+                cout << "--";
+            cout << (dn_stack.top()->getName().empty() ? "NULL" : dn_stack.top()->getName()) << "(" << dn_stack.top()->element()->getSerializedSize()
+                    << ")";
+            cout << " type: " << dn_stack.top()->element()->getDataType() << endl;
+//cout << " index: " << name_index << endl;
+        }
+        /* end debug output */
+
+        /* if it has children, traverse into them */
+        if (dn_stack.top()->hasAnother()) {
+            dn_stack.push(dn_stack.top()->getNext());
+            dn_stack.top()->rewind();
+        } else {
+            /* no more children, back out until we have children, then add next child to the top */
+            while (!dn_stack.empty()) {
+                if (!dn_stack.top()->hasAnother()) {
+                    dn_stack.top()->rewind();
+                    dn_stack.pop();
+                } else
+                    break;
+            }
+
+            if (!dn_stack.empty()) {
+                dn_stack.push(dn_stack.top()->getNext());
+                dn_stack.top()->rewind();
+            }
+        }
+    }
+
+    /* set the header for use in serialization */
+    de_node_names.set(node_names);
+
+    total_size += de_node_names.getSerializedSize();
+
+    return total_size;
+}
+
+void DataNode::findAll(const char *name_in, vector<DataNode *> &node_list_out) {
+    stack<DataNode *> dn_stack;
+
+    /* start at the root */
+    dn_stack.push(this);
+
+    if (string(getName()) == string(name_in))
+        node_list_out.push_back(this);
+
+    while (!dn_stack.empty()) {
+        while (dn_stack.top()->hasAnother(name_in)) {
+            node_list_out.push_back(dn_stack.top()->getNext(name_in));
+        }
+
+        /* if it has children, traverse into them */
+        if (dn_stack.top()->hasAnother()) {
+            dn_stack.push(dn_stack.top()->getNext());
+            dn_stack.top()->rewind();
+        } else {
+            /* no more children, back out until we have children, then add next child to the top */
+            while (!dn_stack.empty()) {
+                if (!dn_stack.top()->hasAnother()) {
+                    dn_stack.top()->rewind();
+                    dn_stack.pop();
+                } else
+                    break;
+            }
+
+            if (!dn_stack.empty()) {
+                dn_stack.push(dn_stack.top()->getNext());
+                dn_stack.top()->rewind();
+            }
+        }
+    }
+
+}
+
+long DataTree::getSerialized(char **ser_str, bool debug) {
+    long data_ptr = 0;
+    long data_size = 0;
+
+    stack<DataNode *> dn_stack;
+    vector<string> node_names;
+    map<string, int, string_less> node_name_index_map;
+
+    /* header of node names, grabbed from getserializedsize to avoid having to memmove() or realloc() */
+    DataElement de_node_names;
+
+    data_size = getSerializedSize(de_node_names, debug);
+
+    *ser_str = (char *) malloc(data_size);
+
+    char *data_out = *ser_str;
+
+    /* name list header */
+    char *de_node_names_serialized;
+    long de_node_names_serialized_size;
+
+    de_node_names.getSerialized(&de_node_names_serialized);
+    de_node_names_serialized_size = de_node_names.getSerializedSize();
+
+    /* copy the header and increase the pointer */
+    memcpy(data_out, de_node_names_serialized, de_node_names_serialized_size);
+    data_ptr += de_node_names_serialized_size;
+
+    /* start at the root */
+    dn_stack.push(&dn_root);
+
+    while (!dn_stack.empty()) {
+        int name_index;
+        int num_children;
+
+        DataElement de_name_index;
+        DataElement de_num_children;
+
+        char *de_name_index_serialized;
+        char *de_num_children_serialized;
+        char *element_serialized;
+
+        long de_name_index_serialized_size;
+        long de_num_children_serialized_size;
+        long element_serialized_size;
+
+        /* build the name list */
+        if (dn_stack.top()->getName().empty()) {
+            name_index = 0; /* empty string */
+        } else if (node_name_index_map[dn_stack.top()->getName().c_str()] == 0) {
+            node_names.push_back(string(dn_stack.top()->getName()));
+            name_index = node_names.size();
+            node_name_index_map[dn_stack.top()->getName().c_str()] = name_index;
+        } else {
+            name_index = node_name_index_map[dn_stack.top()->getName().c_str()];
+        }
+
+        num_children = dn_stack.top()->numChildren();
+
+        de_name_index.set(name_index);
+        de_num_children.set(num_children);
+
+        de_name_index_serialized_size = de_name_index.getSerializedSize();
+        de_num_children_serialized_size = de_num_children.getSerializedSize();
+        element_serialized_size = dn_stack.top()->element()->getSerializedSize();
+
+        de_name_index.getSerialized(&de_name_index_serialized);
+        de_num_children.getSerialized(&de_num_children_serialized);
+        dn_stack.top()->element()->getSerialized(&element_serialized);
+
+        /* add on the name index and number of children */
+        memcpy(data_out + data_ptr, de_name_index_serialized, de_name_index_serialized_size);
+        data_ptr += de_name_index_serialized_size;
+
+        memcpy(data_out + data_ptr, de_num_children_serialized, de_num_children_serialized_size);
+        data_ptr += de_num_children_serialized_size;
+
+        /* add on the data element */
+        memcpy(data_out + data_ptr, element_serialized, element_serialized_size);
+        data_ptr += element_serialized_size;
+
+        delete[] de_name_index_serialized;
+        delete[] de_num_children_serialized;
+        delete[] element_serialized;
+
+        /* if it has children, traverse into them */
+        if (dn_stack.top()->hasAnother()) {
+            dn_stack.push(dn_stack.top()->getNext());
+            dn_stack.top()->rewind();
+        } else {
+            /* no more children, back out until we have children, then add next child to the top */
+            while (!dn_stack.empty()) {
+                if (!dn_stack.top()->hasAnother()) {
+                    dn_stack.top()->rewind();
+                    dn_stack.pop();
+                } else
+                    break;
+            }
+
+            if (!dn_stack.empty()) {
+                dn_stack.push(dn_stack.top()->getNext());
+                dn_stack.top()->rewind();
+            }
+        }
+    }
+
+    return data_size;
+}
+
+void DataTree::setSerialized(char *ser_str, bool debug) {
+    long data_ptr = 0;
+//	long data_size = 0;
+
+    stack<DataNode *> dn_stack;
+    stack<int> dn_childcount_stack;
+    vector<string> node_names;
+
+    DataElement de_node_names;
+
+    de_node_names.setSerialized(ser_str);
+    data_ptr += de_node_names.getSerializedSize();
+    de_node_names.get(node_names);
+
+    DataElement de_name_index;
+    DataElement de_num_children;
+    DataElement de_element;
+
+    dn_stack.push(&dn_root);
+    dn_childcount_stack.push(0); /* root (parent null) has no siblings */
+
+    /* unserialization is a little less straightforward since we have to do a countdown of remaining children */
+    while (!dn_stack.empty()) {
+        int name_index = 0;
+        int num_children = 0;
+
+        /* pull the index of the name of this node */
+        de_name_index.setSerialized(ser_str + data_ptr);
+        data_ptr += de_name_index.getSerializedSize();
+
+        /* pull the number of children this node has */
+        de_num_children.setSerialized(ser_str + data_ptr);
+        data_ptr += de_num_children.getSerializedSize();
+
+        /* get values from the temp dataelements */
+        de_name_index.get(name_index);
+        de_num_children.get(num_children);
+
+        /* pull the node's element */
+        dn_stack.top()->element()->setSerialized(ser_str + data_ptr);
+        data_ptr += dn_stack.top()->element()->getSerializedSize();
+
+        /* debug output */
+        if (debug) {
+            for (unsigned int i = 0; i < dn_stack.size() - 1; i++)
+                cout << "--";
+            cout << (name_index ? node_names[name_index - 1] : "NULL") << "(" << dn_stack.top()->element()->getSerializedSize() << ")";
+            cout << " index: " << name_index << endl;
+        }
+        /* end debug output */
+
+        /* name index >= 1 means it has a name */
+        if (name_index >= 1) {
+            dn_stack.top()->setName(node_names[name_index - 1].c_str());
+
+        } else /* name is nil */
+        {
+            dn_stack.top()->setName("");
+        }
+
+        if (num_children) /* Has children, create first child and push it to the top */
+        {
+            dn_childcount_stack.push(num_children); /* push the child count onto the stack */
+
+            de_name_index.setSerialized(ser_str + data_ptr); /* peek at the new child name but don't increment pointer */
+            de_name_index.get(name_index);
+            /* add this child onto the top of the stack */
+            dn_stack.push(dn_stack.top()->newChild((name_index ? node_names[name_index - 1] : string("")).c_str()));
+            dn_childcount_stack.top()--; /* decrement to count the new child */
+            }
+        else /* No children, move on to the next sibling */
+        {
+            if (dn_childcount_stack.top()) /* any siblings remaining? */
+            {
+                de_name_index.setSerialized(ser_str + data_ptr); /* peek at the new child name but don't increment pointer */
+                de_name_index.get(name_index);
+
+                dn_stack.pop();
+                dn_stack.push(dn_stack.top()->newChild((name_index ? node_names[name_index - 1] : string("")).c_str())); /* create the next sibling and throw it on the stack */
+                dn_childcount_stack.top()--; /* decrement to count the new sibling */
+                }
+            else /* This is the last sibling, move up the stack and find the next */
+            {
+                while (!dn_stack.empty()) /* move up the stack until we find the next sibling */
+                {
+                    if (dn_childcount_stack.top()) {
+                        de_name_index.setSerialized(ser_str + data_ptr); /* peek at the new child name but don't increment pointer */
+                        de_name_index.get(name_index);
+
+                        dn_stack.pop();
+                        dn_stack.push(dn_stack.top()->newChild((name_index ? node_names[name_index - 1] : string("")).c_str())); /* throw it on the stack */
+                        dn_childcount_stack.top()--; /* count it */
+                        break
+;                    }
+                    else
+                    {
+                        dn_childcount_stack.pop();
+                        dn_stack.pop(); /* if no more siblings found the stack will empty naturally */
+                    }
+                }
+            }
+        }
+    }
+}
+
+bool DataTree::LoadFromFileXML(const std::string& filename, DT_FloatingPointPolicy fpp) {
+    TiXmlDocument doc(filename.c_str());
+
+    bool loadOkay = doc.LoadFile();
+
+    if (!loadOkay) {
+        std::cout << "LoadFromFileXML[error loading]: " << filename << std::endl;
+        return false;
+    }
+
+    TiXmlNode *xml_root_node = doc.RootElement();
+
+    if (!xml_root_node) {
+        std::cout << "LoadFromFileXML[error no root]: " << filename << std::endl;
+        return false;
+    }
+
+    rootNode()->setName(xml_root_node->ToElement()->Value());
+
+    setFromXML(rootNode(), xml_root_node, true, fpp);
+
+    return true;
+}
+
+bool DataTree::SaveToFileXML(const std::string& filename) {
+    TiXmlDocument doc;
+    TiXmlDeclaration * decl = new TiXmlDeclaration("1.0", "", "");
+    doc.LinkEndChild(decl);
+
+    string rootName = rootNode()->getName();
+
+    TiXmlElement *element = new TiXmlElement(rootName.empty() ? "root" : rootName.c_str());
+
+    doc.LinkEndChild(element);
+
+    nodeToXML(rootNode(), element);
+
+    doc.SaveFile(filename.c_str());
+
+    return true;
+}
+
+/*
+ bool DataTree::SaveToFile(const std::string& filename)
+ {
+ char *serialized;
+
+ long dataSize = getSerialized(&serialized);
+
+ std::ofstream fout(filename.c_str(), ios::binary);
+
+ fout.write(serialized, dataSize);
+
+ fout << flush;
+ fout.close();
+
+ delete serialized;
+
+ return true;
+ }
+
+ bool DataTree::LoadFromFile(const std::string& filename)
+ {
+ char *serialized;
+ long dataSize;
+
+ ifstream fin(filename.c_str(), ios::binary);
+
+ fin.seekg (0, ios::end);
+ dataSize = fin.tellg();
+ fin.seekg (0, ios::beg);
+
+ serialized = new char[dataSize];
+ fin.read(serialized,dataSize);
+
+ fin.close();
+
+ setSerialized(serialized);
+
+ delete serialized;
+
+ return true;
+ }
+ */
+
+bool DataTree::SaveToFile(const std::string& filename, bool compress, int /* compress_level */) {
+    long dataSize, compressedSize = 0, headerSize;
+    char *serialized = nullptr, *hdr_serialized = nullptr, *compressed = nullptr;
+    DataTree dtHeader;
+
+    dataSize = getSerialized(&serialized);
+
+#if USE_FASTLZ
+    if (compress) {
+        compressed = new char[(int) ceil(dataSize * 1.5)];
+
+        compressedSize = fastlz_compress_level(compress_level, serialized, dataSize, compressed);
+
+        compressed = (char *) realloc(compressed, compressedSize);
+
+        delete serialized;
+    }
+#else
+    if (compress) {
+        std::cout << "Can't compress, FASTLZ disabled";
+        compress = false;
+    }
+#endif
+    DataNode *header = dtHeader.rootNode();
+
+    *header->newChild("version") = 1.0f;
+    *header->newChild("compression") = string(compress ? "FastLZ" : "none");
+    *header->newChild("uncompressed_size") = dataSize;
+
+    headerSize = dtHeader.getSerialized(&hdr_serialized);
+
+    std::ofstream fout(filename.c_str(), ios::binary);
+
+    fout.write((char *) &headerSize, sizeof(long));
+    fout.write((char *) &(compress ? compressedSize : dataSize), sizeof(long));
+
+    fout.write(hdr_serialized, headerSize);
+    fout.write(compress ? compressed : serialized, compress ? compressedSize : dataSize);
+
+    fout << flush;
+    fout.close();
+
+    free(hdr_serialized);
+
+    if (!compress) {
+        free(serialized);
+    } else {
+        delete[] compressed;
+    }
+
+    return true;
+}
+
+bool DataTree::LoadFromFile(const std::string& filename) {
+#if USE_FASTLZ
+    char *compressed;
+#endif
+    char *serialized, *hdr_serialized;
+    long dataSize, headerSize, compressedSize;
+
+    ifstream fin(filename.c_str(), ios::binary);
+
+    fin.read((char *) &headerSize, sizeof(long));
+    fin.read((char *) &compressedSize, sizeof(long));
+
+    hdr_serialized = new char[headerSize];
+    fin.read(hdr_serialized, headerSize);
+
+    DataTree dtHeader;
+    dtHeader.setSerialized(hdr_serialized);
+    DataNode *header = dtHeader.rootNode();
+
+    string compressionType(*header->getNext("compression"));
+    dataSize = *header->getNext("uncompressed_size");
+
+#if USE_FASTLZ
+    bool uncompress = false;
+    if (compressionType == "FastLZ") {
+        uncompress = true;
+    }
+
+    if (uncompress) {
+        compressed = new char[compressedSize];
+        fin.read(compressed, compressedSize);
+
+        serialized = new char[dataSize];
+        fastlz_decompress(compressed, compressedSize, serialized, dataSize);
+
+        delete[] compressed;
+    } else {
+        serialized = new char[dataSize];
+        fin.read(serialized, dataSize);
+    }
+#else
+    if (compressionType == "FastLZ") {
+        std::cout << "DataTree Unable to load FastLZ compressed file -- FastLZ is disabled";
+        return false;
+    }
+
+    serialized = new char[dataSize];
+    fin.read(serialized, dataSize);
+#endif
+
+    fin.close();
+
+    setSerialized(serialized);
+
+    delete[] serialized;
+    delete[] hdr_serialized;
+
+    return true;
+}
+
diff --git a/src/util/DataTree.h b/src/util/DataTree.h
new file mode 100755
index 0000000..5baa6e5
--- /dev/null
+++ b/src/util/DataTree.h
@@ -0,0 +1,362 @@
+#pragma once
+/*
+ * DataElement/DataNode/DataTree -- structured serialization/deserialization system
+ * designed for the CoolMule project :)
+ *
+ Copyright (C) 2003 by Charles J. Cliffe
+ 
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+ 
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+ 
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+ */
+
+#define USE_FASTLZ 0
+
+#include <vector>
+#include <map>
+#include <set>
+#include <string>
+#include <sstream>
+#include <stack>
+#include <iostream>
+#include "tinyxml.h"
+
+#if USE_FASTLZ
+#include "fastlz.h"
+#endif
+
+using namespace std;
+
+
+/* type defines */
+#define DATA_NULL				0
+#define DATA_CHAR               1
+#define DATA_UCHAR              2
+#define DATA_INT				3
+#define DATA_UINT               4
+#define DATA_LONG				5
+#define DATA_ULONG              6
+#define DATA_LONGLONG           7
+#define DATA_FLOAT				8
+#define DATA_DOUBLE				9
+#define DATA_LONGDOUBLE         10
+#define DATA_STRING				11
+#define DATA_STR_VECTOR		    12
+#define DATA_CHAR_VECTOR        13
+#define DATA_UCHAR_VECTOR       14
+#define DATA_INT_VECTOR		    15
+#define DATA_UINT_VECTOR        16
+#define DATA_LONG_VECTOR		17
+#define DATA_ULONG_VECTOR       18
+#define DATA_LONGLONG_VECTOR    19
+#define DATA_FLOAT_VECTOR		20
+#define DATA_DOUBLE_VECTOR		21
+#define DATA_LONGDOUBLE_VECTOR  22
+#define DATA_VOID               23
+#define DATA_WSTRING            24
+
+/* map comparison function */
+struct string_less : public std::binary_function<std::string,std::string,bool>
+{
+ bool operator()(const std::string& a,const std::string& b) const
+ {
+  return a.compare(b) < 0;
+ }
+};
+
+/* int comparison function */
+struct int_less : public std::binary_function<int,int,bool>
+{
+ bool operator()(int a,int b) const
+ {
+        return a < b;
+ }
+};
+
+
+/* Data Exceptions */
+class DataException
+{
+private:
+    string reason;
+    
+public:
+    DataException(const char *why) : reason(why) {}
+    string what() { return reason; } 
+    operator string() { return reason; }
+};
+
+
+class DataTypeMismatchException : public DataException
+{
+public:
+    DataTypeMismatchException(const char *why) : DataException(why) { }
+};
+
+
+class DataInvalidChildException : public DataException
+{
+public:
+    DataInvalidChildException(const char *why) : DataException(why) { }
+};
+
+
+class DataElement
+{
+private:
+    int data_type;
+    size_t data_size;
+    unsigned int unit_size;
+
+    char *data_val;
+    
+    void data_init(size_t data_size_in);
+    
+public:
+    DataElement();
+    ~DataElement();
+    
+    int getDataType();
+    char *getDataPointer();
+    size_t getDataSize();
+    unsigned int getUnitSize();
+    
+    /* set overloads */		
+    void set(const char &char_in);
+    void set(const unsigned char &uchar_in);
+    void set(const int &int_in);
+    void set(const unsigned int &uint_in);
+    void set(const long &long_in);
+    void set(const unsigned long &ulong_in);
+    void set(const long long &llong_in);
+    void set(const float &float_in);
+    void set(const double &double_in);
+    void set(const long double &ldouble_in);
+    
+    void set(const char *data_in, long size_in); /* voids, file chunks anyone? */
+    void set(const char *data_in);	/* strings, stops at NULL, returns as string */
+    
+    void set(const string &str_in);
+    void set(const wstring &wstr_in);
+    
+    void set(vector<string> &strvect_in);
+    void set(std::set<string> &strset_in);
+    void set(vector<char> &charvect_in);
+    void set(vector<unsigned char> &ucharvect_in);
+    void set(vector<int> &intvect_in);
+    void set(vector<unsigned int> &uintvect_in);
+    void set(vector<long> &longvect_in);
+    void set(vector<unsigned long> &ulongvect_in);
+    void set(vector<long long> &llongvect_in);
+    void set(vector<float> &floatvect_in);
+    void set(vector<double> &doublevect_in);
+    void set(vector<long double> &ldoublevect_in);
+    
+    
+    /* get overloads */
+    void get(char &char_in);
+	void get(unsigned char &uchar_in);
+    void get(int &int_in);
+    void get(unsigned int &uint_in);
+    void get(long &long_in);
+    void get(unsigned long &ulong_in);
+    void get(long long &long_in);
+    void get(float &float_in);
+    void get(double &double_in);
+    void get(long double &ldouble_in);
+    
+    void get(char **data_in); /* getting a void or string */
+    void get(string &str_in); 
+    void get(wstring &wstr_in);
+    void get(std::set<string> &strset_in);
+    
+    void get(vector<string> &strvect_in);
+    void get(vector<char> &charvect_in);
+    void get(vector<unsigned char> &ucharvect_in);
+    void get(vector<int> &intvect_in);
+    void get(vector<unsigned int> &uintvect_in);
+    void get(vector<long> &longvect_in);
+    void get(vector<unsigned long> &ulongvect_in);
+    void get(vector<long long> &llongvect_in);
+    void get(vector<float> &floatvect_in);
+    void get(vector<double> &doublevect_in);
+    void get(vector<long double> &ldoublevect_in);
+    
+    
+    /* special get functions, saves creating unnecessary vars */
+    int getChar() { char i_get; get(i_get); return i_get; };
+    unsigned int getUChar() { unsigned char i_get; get(i_get); return i_get; };
+    int getInt() { int i_get; get(i_get); return i_get; };
+    unsigned int getUInt() { unsigned int i_get; get(i_get); return i_get; };
+    long getLong()  { long l_get; get(l_get); return l_get; };
+    unsigned long getULong()  { unsigned long l_get; get(l_get); return l_get; };
+    long getLongLong()  { long long l_get; get(l_get); return l_get; };
+    float getFloat()  { float f_get; get(f_get); return f_get; };
+    double getDouble()  { double d_get; get(d_get); return d_get; };
+    long double getLongDouble()  { long double d_get; get(d_get); return d_get; };
+    
+    std::string toString();
+    
+    /* serialize functions */
+    long getSerializedSize();
+    long getSerialized(char **ser_str);
+    
+    void setSerialized(char *ser_str);
+};
+
+
+class DataNode
+{
+private:
+    DataNode *parentNode;
+    vector<DataNode *> children;
+    map<string, vector<DataNode *>, string_less> childmap;
+    map<string, unsigned int, string_less> childmap_ptr;
+    
+    string node_name;
+    DataElement *data_elem;
+    unsigned int ptr;
+    
+    
+public:
+    DataNode();
+    DataNode(const char *name_in);
+    
+    ~DataNode();		
+    
+    void setName(const char *name_in);
+    string &getName() { return node_name; }
+
+    DataNode *getParentNode() { return parentNode; };
+    void setParentNode(DataNode &parentNode_in) { parentNode = &parentNode_in; };
+
+    int numChildren();	/* Number of children */
+    int numChildren(const char *name_in); /* Number of children named 'name_in' */
+    
+    DataElement *element(); /* DataElement at this node */
+    
+    DataNode *newChild(const char *name_in);
+    DataNode *child(const char *name_in, int index = 0);
+    DataNode *child(int index);
+    
+    
+    bool hasAnother(const char *name_in);	/* useful for while() loops in conjunction with getNext() */
+    bool hasAnother();
+    DataNode *getNext(const char *name_in); /* get next of specified name */
+    DataNode *getNext();	/* get next child */
+    void rewind(const char *name_in);	/* rewind specific */
+    void rewind();	/* rewind generic */
+        
+    void findAll(const char *name_in, vector<DataNode *> &node_list_out);
+    
+//    operator string () { string s; element()->get(s); return s; }
+    operator const char * () { if (element()->getDataType() == DATA_STRING) return element()->getDataPointer(); else return NULL; }
+    operator char () { char v; element()->get(v); return v; }
+    operator unsigned char () { unsigned char v; element()->get(v); return v; }
+    operator int () { int v; element()->get(v); return v; }
+    operator unsigned int () { unsigned int v; element()->get(v); return v; }
+    operator long () { long v; element()->get(v); return v; }
+    operator unsigned long () { unsigned long v; element()->get(v); return v; }
+    operator long long () { long long v; element()->get(v); return v; }
+    operator float () { float v; element()->get(v); return v; }
+    operator double () { double v; element()->get(v); return v; }
+    operator long double () { long double v; element()->get(v); return v; }
+
+    operator vector<char> () { vector<char> v; element()->get(v);  return v; }
+    operator vector<unsigned char> () { vector<unsigned char> v; element()->get(v);  return v; }
+    operator vector<int> () { vector<int> v; element()->get(v);  return v; }
+    operator vector<unsigned int> () { vector<unsigned int> v; element()->get(v);  return v; }
+    operator vector<long> () { vector<long> v; element()->get(v);  return v; }
+    operator vector<unsigned long> () { vector<unsigned long> v; element()->get(v);  return v; }
+    operator vector<float> () { vector<float> v; element()->get(v);  return v; }
+    operator vector<double> () { vector<double> v; element()->get(v);  return v; }
+    operator vector<long double> () { vector<long double> v; element()->get(v);  return v; }
+    
+    const string &operator= (const string &s) { element()->set(s); return s; }
+
+    char operator= (char i) { element()->set(i); return i; }
+    unsigned char operator= (unsigned char i) { element()->set(i); return i; }
+    int operator= (int i) { element()->set(i); return i; }
+    unsigned int operator= (unsigned int i) { element()->set(i); return i; }
+    long operator= (long i) { element()->set(i); return i; }
+    unsigned long operator= (unsigned long i) { element()->set(i); return i; }
+    long long operator= (long long i) { element()->set(i); return i; }
+    float operator= (float i) { element()->set(i); return i; }
+    double operator= (double i) { element()->set(i); return i; }
+    long double operator= (long double i) { element()->set(i); return i; }
+    
+    vector<char> &operator= (vector<char> &v) { element()->set(v); return v; }
+    vector<unsigned char> &operator= (vector<unsigned char> &v) { element()->set(v); return v; }
+    vector<int> &operator= (vector<int> &v) { element()->set(v); return v; }
+    vector<unsigned int> &operator= (vector<unsigned int> &v) { element()->set(v); return v; }
+    vector<long> &operator= (vector<long> &v) { element()->set(v); return v; }
+    vector<unsigned long> &operator= (vector<unsigned long> &v) { element()->set(v); return v; }
+    vector<float> &operator= (vector<float> &v) { element()->set(v); return v; }
+    vector<double> &operator= (vector<double> &v) { element()->set(v); return v; }
+    vector<long double> &operator= (vector<long double> &v) { element()->set(v); return v; }
+
+    DataNode *operator[] (const char *name_in) { return getNext(name_in); }
+    DataNode *operator[] (int idx) { return child(idx); }
+
+    bool operator() (const char *name_in) { return hasAnother(name_in); }
+    bool operator() () { return hasAnother(); }
+
+    DataNode *operator ^(const char *name_in) { return newChild(name_in); }
+
+};
+
+
+typedef vector<DataNode *> DataNodeList;
+
+enum DT_FloatingPointPolicy {
+    USE_FLOAT,
+    USE_DOUBLE
+};
+
+class DataTree
+{
+private:
+    DataNode dn_root;
+    
+    string wsEncode(const wstring& wstr);
+    wstring wsDecode(const string& str);
+
+public:
+    DataTree(const char *name_in);
+    DataTree();
+    ~DataTree();
+    
+    DataNode *rootNode();
+    
+    void nodeToXML(DataNode *elem, TiXmlElement *elxml);
+    void setFromXML(DataNode *elem, TiXmlNode *elxml, bool root_node=true, DT_FloatingPointPolicy fpp=USE_FLOAT);
+    void decodeXMLText(DataNode *elem, const char *in_text, DT_FloatingPointPolicy fpp);
+    
+    void printXML();	/* print datatree as XML */
+    long getSerializedSize(DataElement &de_node_names, bool debug=false);	/* get serialized size + return node names header */
+    long getSerialized(char **ser_str, bool debug=false);	
+    void setSerialized(char *ser_str, bool debug=false);
+    
+    bool LoadFromFileXML(const std::string& filename, DT_FloatingPointPolicy fpp=USE_FLOAT);
+    bool SaveToFileXML(const std::string& filename);
+    
+//    bool SaveToFile(const std::string& filename);
+//    bool LoadFromFile(const std::string& filename);
+
+    bool SaveToFile(const std::string& filename, bool compress = true, int compress_level = 2);
+    bool LoadFromFile(const std::string& filename);
+};
diff --git a/src/util/GLExt.cpp b/src/util/GLExt.cpp
new file mode 100644
index 0000000..efab5f0
--- /dev/null
+++ b/src/util/GLExt.cpp
@@ -0,0 +1,100 @@
+#include "GLExt.h"
+#include <cstring>
+#include <iostream>
+
+#ifdef __APPLE__
+#include <OpenGL/OpenGL.h>
+#endif
+
+#ifdef __linux__
+#include <dlfcn.h>
+#endif
+
+#ifdef _WIN32
+
+PFNWGLGETEXTENSIONSSTRINGEXTPROC wglGetExtensionsStringEXT = NULL;
+PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT = NULL;
+PFNWGLGETSWAPINTERVALEXTPROC wglGetSwapIntervalEXT = NULL;
+
+bool GLExtSupported(const char *extension_name) {
+    const GLubyte *extensions = glGetString(GL_EXTENSIONS);
+
+    return (std::strstr((const char *)extensions, extension_name) != NULL);
+}
+
+#endif
+
+
+bool GLExt_initialized = false;
+
+void initGLExtensions() {
+    if (GLExt_initialized) {
+        return;
+    }
+
+//    const GLubyte *extensions = glGetString(GL_EXTENSIONS);
+//    std::cout << std::endl << "Supported GL Extensions: " << std::endl << extensions << std::endl << std::endl;
+
+#ifdef __APPLE__
+    const GLint interval = 1;
+#else
+    const GLint interval = 2;
+#endif
+
+#ifdef _WIN32
+    if (GLExtSupported("WGL_EXT_swap_control")) {
+        std::cout << "Initializing WGL swap control extensions.." << std::endl;
+        wglSwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC) wglGetProcAddress("wglSwapIntervalEXT");
+        wglGetSwapIntervalEXT = (PFNWGLGETSWAPINTERVALEXTPROC) wglGetProcAddress("wglGetSwapIntervalEXT");
+
+        wglSwapIntervalEXT(interval);
+    }
+#endif
+
+#ifdef __APPLE__
+    // OSX is just ON / OFF
+    CGLSetParameter (CGLGetCurrentContext(), kCGLCPSwapInterval, &interval);
+#endif
+
+#ifdef __linux__
+    dlopen("libglx.so",RTLD_LAZY);
+
+    void (*glxSwapIntervalEXTFunc) (Display *dpy, GLXDrawable drawable, int interval) = 0;
+    int (*glxSwapIntervalMESAFunc)(unsigned int interval) = 0;
+    int (*glxSwapIntervalSGIFunc) (int interval) = 0;
+    void (*DRI2SwapIntervalFunc) (Display *dpy, XID drawable, int interval) = 0;
+
+    glxSwapIntervalEXTFunc = (void (*) (Display *dpy, GLXDrawable drawable, int interval)) dlsym(RTLD_DEFAULT,"glXSwapIntervalEXT");
+    glxSwapIntervalMESAFunc = (int (*)(unsigned int interval)) dlsym(RTLD_DEFAULT,"glXSwapIntervalMESA");
+    glxSwapIntervalSGIFunc = (int (*) (int interval)) dlsym(RTLD_DEFAULT,"glXSwapIntervalSGI");
+    DRI2SwapIntervalFunc = (void (*) (Display *dpy, XID drawable, int interval)) dlsym(RTLD_DEFAULT,"DRI2SwapInterval");
+
+    std::cout << "Available vertical sync SwapInterval functions: " << std::endl;
+    std::cout << "\tglxSwapIntervalEXT: " << ((glxSwapIntervalEXTFunc != 0)?"Yes":"No") << std::endl;
+    std::cout << "\tDRI2SwapInterval: " << ((DRI2SwapIntervalFunc != 0)?"Yes":"No") << std::endl;
+    std::cout << "\tglxSwapIntervalMESA: " << ((glxSwapIntervalMESAFunc != 0)?"Yes":"No") << std::endl;
+    std::cout << "\tglxSwapIntervalSGI: " << ((glxSwapIntervalSGIFunc != 0)?"Yes":"No") << std::endl;
+
+    if (glxSwapIntervalEXTFunc) {
+        Display *dpy = glXGetCurrentDisplay();
+        GLXDrawable drawable = glXGetCurrentDrawable();
+        glxSwapIntervalEXTFunc(dpy, drawable, interval);
+        std::cout << "Using glxSwapIntervalEXT." << std::endl << std::endl;
+    } else if (DRI2SwapIntervalFunc) {
+        Display *dpy = glXGetCurrentDisplay();
+        GLXDrawable drawable = glXGetCurrentDrawable();
+        DRI2SwapIntervalFunc(dpy, drawable, interval);
+        std::cout << "Using DRI2SwapInterval." << std::endl << std::endl;
+    } else if (glxSwapIntervalMESAFunc) {
+    	glxSwapIntervalMESAFunc(interval);
+        std::cout << "Using glxSwapIntervalMESA." << std::endl << std::endl;
+    } else if (glxSwapIntervalSGIFunc) {
+    	glxSwapIntervalSGIFunc(interval);
+        std::cout << "Using glxSwapIntervalSGI." << std::endl << std::endl;
+    } else {
+        std::cout << "No vertical sync swap interval functions available." << std::endl;
+    }
+#endif
+
+    GLExt_initialized = true;
+}
diff --git a/src/util/GLExt.h b/src/util/GLExt.h
new file mode 100644
index 0000000..619a337
--- /dev/null
+++ b/src/util/GLExt.h
@@ -0,0 +1,24 @@
+#pragma once
+
+#include "wx/glcanvas.h"
+
+#ifdef _WIN32
+#include <windows.h>
+#ifdef __MINGW32__
+#include <gl/wglext.h>
+#else
+#include "wglext.h"
+#endif
+
+extern PFNWGLGETEXTENSIONSSTRINGEXTPROC _wglGetExtensionsStringEXT;
+extern PFNWGLSWAPINTERVALEXTPROC       wglSwapIntervalEXT;
+extern PFNWGLGETSWAPINTERVALEXTPROC    wglGetSwapIntervalEXT;
+
+bool GLExtSupported(const char *extension_name);
+
+#endif
+
+extern bool GLExt_initialized;
+
+void initGLExtensions();
+
diff --git a/src/util/GLFont.cpp b/src/util/GLFont.cpp
new file mode 100644
index 0000000..ee20ef9
--- /dev/null
+++ b/src/util/GLFont.cpp
@@ -0,0 +1,931 @@
+#include "GLFont.h"
+
+#include <iostream>
+#include <fstream>
+#include <algorithm>
+#include "cubic_math.h"
+
+#ifdef _OSX_APP_
+#include "CoreFoundation/CoreFoundation.h"
+#endif
+
+static std::wstring getExePath(void)
+{
+    //get the dir path of the executable
+    wxFileName exePath = wxFileName(wxStandardPaths::Get().GetExecutablePath());
+   
+    return  std::wstring(exePath.GetPath().ToStdWstring());
+}
+
+#ifndef RES_FOLDER
+#define RES_FOLDER ""
+#endif
+
+#define GC_DRAW_COUNT_PERIOD 50
+#define GC_DRAW_COUNT_LIMIT 10
+
+GLFontStringCache::GLFontStringCache() {
+    gc = 0;
+}
+
+//Static initialization of all available fonts,
+//using aggregate syntax (Cx11+)
+
+//Fonts must be listed in increasing size for Drawer to work !
+GLFont GLFont::fonts[GLFont::GLFontSize::GLFONT_SIZE_MAX] = {
+
+    { GLFont::GLFontSize::GLFONT_SIZE12, L"fonts/vera_sans_mono12.fnt" },
+    { GLFont::GLFontSize::GLFONT_SIZE16, L"fonts/vera_sans_mono16.fnt" },
+    { GLFont::GLFontSize::GLFONT_SIZE18, L"fonts/vera_sans_mono18.fnt" },
+    { GLFont::GLFontSize::GLFONT_SIZE24, L"fonts/vera_sans_mono24.fnt" },
+    { GLFont::GLFontSize::GLFONT_SIZE27, L"fonts/vera_sans_mono27.fnt" },
+    { GLFont::GLFontSize::GLFONT_SIZE32, L"fonts/vera_sans_mono32.fnt" },
+    { GLFont::GLFontSize::GLFONT_SIZE36, L"fonts/vera_sans_mono36.fnt" },
+    { GLFont::GLFontSize::GLFONT_SIZE48, L"fonts/vera_sans_mono48.fnt" },
+    { GLFont::GLFontSize::GLFONT_SIZE64, L"fonts/vera_sans_mono64.fnt" },
+    { GLFont::GLFontSize::GLFONT_SIZE72, L"fonts/vera_sans_mono72.fnt" },
+    { GLFont::GLFontSize::GLFONT_SIZE96, L"fonts/vera_sans_mono96.fnt" },
+
+};
+
+
+std::atomic<GLFont::GLFontScale> GLFont::currentScale{ GLFont::GLFontScale::GLFONT_SCALE_NORMAL };
+
+
+GLFontChar::GLFontChar() :
+        id(0), x(0), y(0), width(0), height(0), xoffset(0), yoffset(0), xadvance(0), aspect(1), index(0) {
+
+}
+
+GLFontChar::~GLFontChar() {
+
+}
+
+void GLFontChar::setId(int idval) {
+    id = idval;
+}
+
+int GLFontChar::getId() {
+    return id;
+}
+
+void GLFontChar::setXOffset(int xofs) {
+    xoffset = xofs;
+}
+
+int GLFontChar::getXOffset() {
+    return xoffset;
+}
+
+void GLFontChar::setYOffset(int yofs) {
+    yoffset = yofs;
+}
+
+int GLFontChar::getYOffset() {
+    return yoffset;
+}
+
+void GLFontChar::setX(int xpos) {
+    x = xpos;
+}
+
+int GLFontChar::getX() {
+    return x;
+}
+
+void GLFontChar::setY(int ypos) {
+    y = ypos;
+}
+
+int GLFontChar::getY() {
+    return y;
+}
+
+void GLFontChar::setWidth(int w) {
+    width = w;
+    if (width && height) {
+        aspect = (float) width / (float) height;
+    }
+}
+
+int GLFontChar::getWidth() {
+    return width;
+}
+
+void GLFontChar::setHeight(int h) {
+    height = h;
+    if (width && height) {
+        aspect = (float) width / (float) height;
+    }
+}
+
+int GLFontChar::getHeight() {
+    return height;
+}
+
+void GLFontChar::setXAdvance(int xadv) {
+    xadvance = xadv;
+}
+
+int GLFontChar::getXAdvance() {
+    return xadvance;
+}
+
+float GLFontChar::getAspect() {
+    return aspect;
+}
+
+void GLFontChar::setIndex(unsigned int idx) {
+    index = idx;
+}
+
+int GLFontChar::getIndex() {
+    return index;
+}
+
+GLFont::GLFont(GLFontSize size, std::wstring defFileName):
+        lineHeight(0), base(0), imageWidth(0), imageHeight(0), loaded(false), texId(0), gcCounter(0) {
+
+    fontSizeClass = size;
+  
+    fontDefFileSource = defFileName;
+}
+
+GLFont::~GLFont() {
+
+}
+
+std::wstring GLFont::nextParam(std::wistringstream &str) {
+    std::wstring param_str;
+
+    str >> param_str;
+
+    if (param_str.find(L'"') != std::wstring::npos) {
+        std::wstring rest;
+        while (!str.eof() && (std::count(param_str.begin(), param_str.end(), L'"') % 2)) {
+            str >> rest;
+            param_str.append(L" " + rest);
+        }
+    }
+
+    return param_str;
+}
+
+std::wstring GLFont::getParamKey(const std::wstring& param_str) {
+    std::wstring keyName;
+
+    size_t eqpos = param_str.find(L"=");
+
+    if (eqpos != std::wstring::npos) {
+        keyName = param_str.substr(0, eqpos);
+    }
+
+    return keyName;
+}
+
+std::wstring GLFont::getParamValue(const std::wstring& param_str) {
+    std::wstring value;
+
+    size_t eqpos = param_str.find(L"=");
+
+    if (eqpos != std::wstring::npos) {
+        value = param_str.substr(eqpos + 1);
+    }
+
+    if (value[0] == L'"' && value[value.length() - 1] == L'"') {
+        value = value.substr(1, value.length() - 2);
+    }
+
+    return value;
+}
+
+void GLFont::loadFontOnce() {
+
+    if (loaded) {
+        return;
+    }
+
+#if _OSX_APP_
+    CFBundleRef mainBundle = CFBundleGetMainBundle();
+    CFURLRef resourcesURL = CFBundleCopyResourcesDirectoryURL(mainBundle);
+    char path[PATH_MAX];
+    if (!CFURLGetFileSystemRepresentation(resourcesURL, TRUE, (UInt8 *)path, PATH_MAX))
+    {
+        // error!
+    }
+    CFRelease(resourcesURL);
+    wxString resourceFolder = std::string(path) + "/";
+    
+#else
+    wxString resourceFolder = RES_FOLDER;
+#endif
+    
+    //full font file path
+    wxFileName fontDefFileName = wxFileName(resourceFolder + L"/" + fontDefFileSource);
+    
+    if (!fontDefFileName.Exists()) {
+        wxFileName exePath = wxFileName(wxStandardPaths::Get().GetExecutablePath());
+       
+        //Full Path where the fonts are, including file name
+        fontDefFileName = wxFileName(exePath.GetPath() + L"/"+ fontDefFileSource);
+
+        if (!fontDefFileName.FileExists()) {
+            std::cout << "Font file " << fontDefFileName.GetFullPath() << " does not exist?" << std::endl;
+            return;
+        }
+
+        if (!fontDefFileName.IsFileReadable()) {
+            std::cout << "Font file " << fontDefFileName.GetFullPath() << " is not readable?" << std::endl;
+            return;
+        }
+    }
+    else {
+
+        if (!fontDefFileName.IsFileReadable()) {
+            std::cout << "Font file " << fontDefFileName.GetFullPath() << " is not readable?" << std::endl;
+            return;
+        }
+    }
+
+    //Re-compute the resource dir.
+    resourceFolder = fontDefFileName.GetPath();
+
+    std::wstring fontDefFileNamePath = fontDefFileName.GetFullPath(wxPATH_NATIVE).ToStdWstring();
+    
+    std::wifstream input;
+    std::string inpFileStr(fontDefFileNamePath.begin(), fontDefFileNamePath.end());
+    input.open(inpFileStr, std::ios::in);
+
+    std::wstring op;
+
+    while (!input.eof()) {
+        input >> op;
+        if (op == L"info") {
+            std::wstring info_param_str;
+            getline(input, info_param_str);
+            std::wistringstream info_param(info_param_str);
+
+            while (!info_param.eof()) {
+                std::wstring param = nextParam(info_param);
+
+                std::wstring paramKey = getParamKey(param);               
+                if (paramKey == L"face") {
+                    fontName = getParamValue(param);
+                }
+
+                param = nextParam(info_param);
+                paramKey = getParamKey(param);
+                if (paramKey == L"size") {
+
+                    std::wistringstream paramValue(getParamValue(param));
+                    paramValue >> pixHeight;
+                }
+
+//                std::cout << "[" << paramKey << "] = '" << paramValue << "'" << std::endl;
+            }
+        } else if (op == L"common") {
+            std::wstring common_param_str;
+            getline(input, common_param_str);
+            std::wistringstream common_param(common_param_str);
+
+            while (!common_param.eof()) {
+                std::wstring param = nextParam(common_param);
+
+                std::wstring paramKey = getParamKey(param);
+                std::wistringstream paramValue(getParamValue(param));
+
+                if (paramKey == L"lineHeight") {
+                    paramValue >> lineHeight;
+                } else if (paramKey == L"base") {
+                    paramValue >> base;
+                } else if (paramKey == L"scaleW") {
+                    paramValue >> imageWidth;
+                } else if (paramKey == L"scaleH") {
+                    paramValue >> imageHeight;
+                }
+//                std::cout << "[" << paramKey << "] = '" << getParamValue(param) << "'" << std::endl;
+            }
+        } else if (op == L"page") {
+            std::wstring page_param_str;
+            getline(input, page_param_str);
+            std::wistringstream page_param(page_param_str);
+
+            while (!page_param.eof()) {
+                std::wstring param = nextParam(page_param);
+
+                std::wstring paramKey = getParamKey(param);
+                std::wstring paramValue = getParamValue(param);
+
+                if (paramKey == L"file") {
+                    wxFileName imgFileName = wxFileName(resourceFolder, paramValue);
+                    imageFile = imgFileName.GetFullPath(wxPATH_NATIVE).ToStdWstring();
+                }
+//                std::cout << "[" << paramKey << "] = '" << paramValue << "'" << std::endl;
+            }
+
+        } else if (op == L"char") {
+            std::wstring char_param_str;
+            getline(input, char_param_str);
+            std::wistringstream char_param(char_param_str);
+
+            GLFontChar *newChar = new GLFontChar;
+
+            while (!char_param.eof()) {
+                std::wstring param = nextParam(char_param);
+
+                std::wstring paramKey = getParamKey(param);
+                std::wistringstream paramValue(getParamValue(param));
+
+                int val;
+
+                if (paramKey == L"id") {
+                    paramValue >> val;
+                    newChar->setId(val);
+                } else if (paramKey == L"x") {
+                    paramValue >> val;
+                    newChar->setX(val);
+                } else if (paramKey == L"y") {
+                    paramValue >> val;
+                    newChar->setY(val);
+                } else if (paramKey == L"width") {
+                    paramValue >> val;
+                    newChar->setWidth(val);
+                } else if (paramKey == L"height") {
+                    paramValue >> val;
+                    newChar->setHeight(val);
+                } else if (paramKey == L"xoffset") {
+                    paramValue >> val;
+                    newChar->setXOffset(val);
+                } else if (paramKey == L"yoffset") {
+                    paramValue >> val;
+                    newChar->setYOffset(val);
+                } else if (paramKey == L"xadvance") {
+                    paramValue >> val;
+                    newChar->setXAdvance(val);
+                }
+
+//                std::cout << "[" << paramKey << "] = '" << getParamValue(param) << "'" << std::endl;
+            }
+
+            characters[newChar->getId()] = newChar;
+
+        } else {
+            std::wstring dummy;
+            getline(input, dummy);
+        }
+    }
+
+    if (imageFile != "" && imageWidth && imageHeight && characters.size()) {
+
+        // Load file and decode image.
+        std::vector<unsigned char> image;
+
+        unsigned int imgWidth = imageWidth, imgHeight = imageHeight;
+
+        //1) First load the raw file to memory using wstring filenames
+        wxFile png_file(imageFile);
+
+        int png_size = png_file.Length();
+        
+        unsigned char* raw_image = new unsigned char[png_size];
+
+        if (png_size > 0) {
+
+            int nbRead = png_file.Read((void*)raw_image, png_size);
+
+            if (png_size != nbRead) {
+
+                std::cout << "Error loading the full PNG image file in memory: '" << imageFile << "'" << std::endl;
+            }
+        }
+
+        //2) then load from memory
+        unsigned error = lodepng::decode(image, imgWidth, imgHeight, raw_image, png_size);
+
+        delete[] raw_image;
+        png_file.Close();
+
+        if (error) {
+            std::cout << "Error decoding PNG image file: '" << imageFile << "'" << std::endl;
+        }
+ 
+        glGenTextures(1, &texId);
+        glEnable(GL_TEXTURE_2D);
+        glBindTexture(GL_TEXTURE_2D, texId);
+        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+        glTexImage2D(GL_TEXTURE_2D, 0, 4, imageWidth, imageHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, error?nullptr:(&image[0]));
+        glDisable(GL_TEXTURE_2D);
+
+        std::map<int, GLFontChar *>::iterator char_i;
+
+        gl_vertices.resize(characters.size() * 8); // one quad per char
+        gl_uv.resize(characters.size() * 8);
+
+        unsigned int ofs = 0;
+        for (char_i = characters.begin(); char_i != characters.end(); char_i++) {
+//            int charId = (*char_i).first;
+            GLFontChar *fchar = (*char_i).second;
+
+            float faspect = fchar->getAspect();
+
+            float uv_xpos = (float) fchar->getX() / (float) imageWidth;
+            float uv_ypos = ((float) fchar->getY() / (float) imageHeight);
+            float uv_xofs = (float) fchar->getWidth() / (float) imageWidth;
+            float uv_yofs = ((float) fchar->getHeight() / (float) imageHeight);
+
+            gl_vertices[ofs] = 0;
+            gl_vertices[ofs + 1] = 0;
+            gl_uv[ofs] = uv_xpos;
+            gl_uv[ofs + 1] = uv_ypos + uv_yofs;
+
+            gl_vertices[ofs + 2] = faspect;
+            gl_vertices[ofs + 3] = 0;
+            gl_uv[ofs + 2] = uv_xpos + uv_xofs;
+            gl_uv[ofs + 3] = uv_ypos + uv_yofs;
+
+            gl_vertices[ofs + 4] = faspect;
+            gl_vertices[ofs + 5] = 1;
+            gl_uv[ofs + 4] = uv_xpos + uv_xofs;
+            gl_uv[ofs + 5] = uv_ypos;
+
+            gl_vertices[ofs + 6] = 0;
+            gl_vertices[ofs + 7] = 1;
+            gl_uv[ofs + 6] = uv_xpos;
+            gl_uv[ofs + 7] = uv_ypos;
+
+            fchar->setIndex(ofs);
+
+            ofs += 8;
+        }
+
+        std::cout << "Loaded font '" << fontName << "' from '" << imageFile << "', parsed " << characters.size() << " characters." << std::endl;
+
+        loaded = true;
+    } else {
+        std::cout << "Error loading font file " << imageFile << std::endl;
+    }
+
+    input.close();
+    loaded = true;
+}
+
+float GLFont::getStringWidth(const std::wstring& str, float size, float viewAspect) {
+
+    float scalex = size / viewAspect;
+
+    float width = 0;
+
+    for (int i = 0, iMax = str.length(); i < iMax; i++) {
+        int charId = str.at(i);
+
+        if (characters.find(charId) == characters.end()) {
+            continue;
+        }
+
+        GLFontChar *fchar = characters[charId];
+
+        float ofsx = (float) fchar->getXOffset() / (float) imageWidth;
+        float advx = (float) fchar->getXAdvance() / (float) imageWidth;
+
+        if (charId == 32) {
+            advx = characters[L'_']->getAspect();
+        }
+
+        width += fchar->getAspect() + advx + ofsx;
+    }
+
+    width *= scalex;
+
+    return width;
+}
+
+// Draw string, immediate
+void GLFont::drawString(const std::wstring& str, int pxHeight, float xpos, float ypos, Align hAlign, Align vAlign, int vpx, int vpy, bool cacheable) {
+
+    pxHeight *= 2;
+  
+    if (!vpx || !vpy) {
+        GLint vp[4];
+        glGetIntegerv( GL_VIEWPORT, vp);
+        vpx = vp[2];
+        vpy = vp[3];
+    }
+    
+    if (cacheable) {
+        gcCounter++;
+
+        std::lock_guard<std::mutex> lock(cache_busy);
+        
+        if (gcCounter > GC_DRAW_COUNT_PERIOD) {
+            
+            doCacheGC();
+            gcCounter = 0;
+        }
+        
+        GLFontStringCache *fc = nullptr;
+
+        std::map<std::wstring, GLFontStringCache * >::iterator cache_iter;
+        
+        std::wstringstream sscacheIdx;
+        
+        sscacheIdx << vpx << "." << vpy << "." << pxHeight << "." << str;
+        
+        std::wstring cacheIdx(sscacheIdx.str());
+        
+        cache_iter = stringCache.find(cacheIdx);
+        if (cache_iter != stringCache.end()) {
+            fc = cache_iter->second;
+            fc->gc = 0;
+        }
+        
+        if (fc == nullptr) {
+//            std::cout << "cache miss" << std::endl;
+            fc = cacheString(str, pxHeight, vpx, vpy);
+            stringCache[cacheIdx] = fc;
+        }
+
+        drawCacheString(fc, xpos, ypos, hAlign, vAlign);
+        
+        return;
+    }
+    
+    float size = (float) pxHeight / (float) vpy;
+    float viewAspect = (float) vpx / (float) vpy;
+    float msgWidth = getStringWidth(str, size, viewAspect);
+
+    glPushMatrix();
+    glTranslatef(xpos, ypos, 0.0f);
+
+    switch (vAlign) {
+    case GLFONT_ALIGN_TOP:
+        glTranslatef(0.0, -size, 0.0);
+        break;
+    case GLFONT_ALIGN_CENTER:
+        glTranslatef(0.0, -size/2.0, 0.0);
+        break;
+    default:
+        break;
+    }
+
+    switch (hAlign) {
+    case GLFONT_ALIGN_RIGHT:
+        glTranslatef(-msgWidth, 0.0, 0.0);
+        break;
+    case GLFONT_ALIGN_CENTER:
+        glTranslatef(-msgWidth / 2.0, 0.0, 0.0);
+        break;
+    default:
+        break;
+    }
+
+    glPushMatrix();
+    glScalef(size / viewAspect, size, 1.0f);
+
+    glEnable(GL_TEXTURE_2D);
+    glBindTexture(GL_TEXTURE_2D, texId);
+
+    glEnable(GL_BLEND);
+    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+    glEnableClientState(GL_VERTEX_ARRAY);
+    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+    glVertexPointer(2, GL_FLOAT, 0, &gl_vertices[0]);
+    glTexCoordPointer(2, GL_FLOAT, 0, &gl_uv[0]);
+
+    for (int i = 0, iMax = str.length(); i < iMax; i++) {
+        int charId = str.at(i);
+
+        if (characters.find(charId) == characters.end()) {
+            continue;
+        }
+
+        GLFontChar *fchar = characters[charId];
+
+        float ofsx = (float) fchar->getXOffset() / (float) imageWidth;
+        float advx = (float) fchar->getXAdvance() / (float) imageWidth;
+
+        if (charId == 32) {
+            advx = characters[L'_']->getAspect();
+        }
+
+        glTranslatef(ofsx, 0.0, 0.0);
+        glDrawArrays(GL_QUADS, fchar->getIndex() / 2, 4);
+        glTranslatef(fchar->getAspect() + advx, 0.0, 0.0);
+    }
+
+    glVertexPointer(2, GL_FLOAT, 0, nullptr);
+    glTexCoordPointer(2, GL_FLOAT, 0, nullptr);
+
+    glDisableClientState(GL_VERTEX_ARRAY);
+    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
+    glPopMatrix();
+    glPopMatrix();
+
+    glDisable(GL_BLEND);
+    glDisable(GL_TEXTURE_2D);
+}
+
+// Draw string, immediate, 8 bit version
+void GLFont::drawString(const std::string& str, int pxHeight, float xpos, float ypos, Align hAlign, Align vAlign, int vpx, int vpy, bool cacheable) {
+
+    //Displayed string is wstring, so use wxString to do the heavy lifting of converting  str...
+#ifdef WIN32
+    //try to reuse the memory with thread_local, unsupported on OSX ? 
+    static thread_local wxString wsTmp;
+#else
+    wxString wsTmp;
+#endif
+    
+    wsTmp.assign(str);
+
+    drawString(wsTmp.ToStdWstring(), pxHeight, xpos, ypos, hAlign, vAlign, vpx, vpy, cacheable);
+}
+
+// Draw cached GLFontCacheString
+void GLFont::drawCacheString(GLFontStringCache *fc, float xpos, float ypos, Align hAlign, Align vAlign) {
+    
+    float size = (float) fc->pxHeight / (float) fc->vpy;
+    
+    glPushMatrix();
+    glTranslatef(xpos, ypos, 0.0f);
+    
+    switch (vAlign) {
+        case GLFONT_ALIGN_TOP:
+            glTranslatef(0.0, -size, 0.0);
+            break;
+        case GLFONT_ALIGN_CENTER:
+            glTranslatef(0.0, -size/2.0, 0.0);
+            break;
+        default:
+            break;
+    }
+    
+    switch (hAlign) {
+        case GLFONT_ALIGN_RIGHT:
+            glTranslatef(-fc->msgWidth, 0.0, 0.0);
+            break;
+        case GLFONT_ALIGN_CENTER:
+            glTranslatef(-fc->msgWidth / 2.0, 0.0, 0.0);
+            break;
+        default:
+            break;
+    }
+    
+    glEnable(GL_TEXTURE_2D);
+    glBindTexture(GL_TEXTURE_2D, texId);
+    
+    glEnable(GL_BLEND);
+    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+    
+    glEnableClientState(GL_VERTEX_ARRAY);
+    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+    glVertexPointer(2, GL_FLOAT, 0, &fc->gl_vertices[0]);
+    glTexCoordPointer(2, GL_FLOAT, 0, &fc->gl_uv[0]);
+    
+    glDrawArrays(GL_QUADS, 0, 4 * fc->drawlen);
+    
+    glVertexPointer(2, GL_FLOAT, 0, nullptr);
+    glTexCoordPointer(2, GL_FLOAT, 0, nullptr);
+
+    glDisableClientState(GL_VERTEX_ARRAY);
+    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
+    
+    glPopMatrix();
+
+    glDisable(GL_BLEND);
+    glDisable(GL_TEXTURE_2D);
+}
+
+// Compile optimized GLFontCacheString
+GLFontStringCache *GLFont::cacheString(const std::wstring& str, int pxHeight, int vpx, int vpy) {
+
+    GLFontStringCache *fc = new GLFontStringCache;
+    
+    fc->pxHeight = pxHeight;
+    fc->vpx = vpx;
+    fc->vpy = vpy;
+    
+    float size = (float) pxHeight / (float) vpy;
+    float viewAspect = (float) vpx / (float) vpy;
+    
+    fc->msgWidth = getStringWidth(str, size, viewAspect);
+
+    int nChar = 0;
+    for (int i = 0, iMax = str.length(); i < iMax; i++) {
+        int charId = str.at(i);
+
+        if (characters.find(charId) == characters.end()) {
+            continue;
+        }
+        nChar++;
+    }
+    
+    fc->drawlen = nChar;
+    fc->gl_vertices.resize(nChar*8);
+    fc->gl_uv.resize(nChar*8);
+    
+    
+    CubicVR::mat4 trans = CubicVR::mat4::scale(size / viewAspect, size, 1.0f);
+    
+    int c = 0;
+    for (int i = 0, iMax = str.length(); i < iMax; i++) {
+        int charId = str.at(i);
+        
+        if (characters.find(charId) == characters.end()) {
+            continue;
+        }
+        
+        GLFontChar *fchar = characters[charId];
+        
+        float ofsx = (float) fchar->getXOffset() / (float) imageWidth;
+        float advx = (float) fchar->getXAdvance() / (float) imageWidth;
+        
+        if (charId == 32) {
+            advx = characters[L'_']->getAspect();
+        }
+        
+        // freeze transform to buffer
+        trans *= CubicVR::mat4::translate(ofsx, 0.0, 0.0);
+        int charIdx = fchar->getIndex();
+        for (int j = 0; j < 8; j+=2) {
+            CubicVR::vec3 pt(gl_vertices[charIdx + j],gl_vertices[charIdx + j + 1], 0.0);
+            pt = CubicVR::mat4::multiply(trans, pt, true);
+            fc->gl_vertices[c * 8 + j] = pt[0];
+            fc->gl_vertices[c * 8 + j + 1] = pt[1];
+            fc->gl_uv[c * 8 + j] = gl_uv[charIdx + j];
+            fc->gl_uv[c * 8 + j + 1] = gl_uv[charIdx + j + 1];
+        }
+        trans *= CubicVR::mat4::translate(fchar->getAspect() + advx, 0.0, 0.0);
+        c++;
+    }
+    
+    return fc;
+}
+
+void GLFont::doCacheGC() {
+
+    std::map<std::wstring, GLFontStringCache * >::iterator cache_iter;
+
+    bool flushDone = false;
+
+    //do aging and remove in one pass.
+    cache_iter = stringCache.begin();
+
+    while (cache_iter != stringCache.end()) {
+
+        //aging
+        cache_iter->second->gc--;
+
+        //only flush 1 element per call
+        if (!flushDone && cache_iter->second->gc < -GC_DRAW_COUNT_LIMIT) {
+
+            delete cache_iter->second;
+            cache_iter = stringCache.erase(cache_iter);
+            flushDone = true;
+        }
+        else {
+            cache_iter++;
+        }
+    } //end while
+}
+
+void GLFont::clearCache() {
+
+    std::lock_guard<std::mutex> lock(cache_busy);
+
+    std::map<std::wstring, GLFontStringCache * >::iterator cache_iter;
+
+    cache_iter = stringCache.begin();
+
+    while (cache_iter != stringCache.end()) {
+        
+        delete cache_iter->second;
+        cache_iter = stringCache.erase(cache_iter);
+               
+    }
+}
+
+void GLFont::clearAllCaches() {
+
+    for (int i = 0; i < GLFont::GLFONT_SIZE_MAX; i++) {
+
+        fonts[i].clearCache();
+    }
+}
+
+
+GLFont::Drawer GLFont::getFont(int requestedSize, double scaleFactor) {
+
+    return GLFont::Drawer(requestedSize, scaleFactor);
+}
+
+
+
+void GLFont::setScale(GLFontScale scale) {
+    
+    //safety vs. inputs
+    if (scale < GLFONT_SCALE_NORMAL || scale > GLFONT_SCALE_LARGE) {
+
+        scale = GLFontScale::GLFONT_SCALE_NORMAL;
+    }
+    
+    currentScale.store(scale);
+
+    //Flush all the GC stuff
+    clearAllCaches();
+}
+
+GLFont::GLFontScale GLFont::getScale() {
+
+    return currentScale.load();
+}
+
+double GLFont::getScaleFactor() {
+
+    GLFontScale scale = currentScale.load();
+
+    if (scale == GLFONT_SCALE_MEDIUM) {
+
+        return 1.5;
+    }
+    else if (scale == GLFONT_SCALE_LARGE) {
+
+        return 2.0;
+    }
+
+    return 1.0;
+}
+
+int GLFont::getScaledPx(int basicFontSize, double scaleFactor) {
+    //try to align on an integer pixel size if the targetSize font is available
+    int targetSize = round(basicFontSize * scaleFactor);
+    int resultIndex = 0;
+    
+    fonts[0].loadFontOnce();
+    
+    for (int i = 0; i < GLFONT_SIZE_MAX - 1; i++) {
+        
+        fonts[i + 1].loadFontOnce();
+        
+        if (fonts[i + 1].pixHeight <= targetSize) {
+            resultIndex = i + 1;
+        }
+        else {
+            break;
+        }
+    } //end for
+    
+    // return font height px
+    return fonts[resultIndex].pixHeight;
+}
+
+
+GLFont::Drawer::Drawer(int basicFontSize, double scaleFactor) {
+
+    //Selection of the final font: scan GLFont::fonts to find the biggest font such as 
+    // its pixHeight <= basicFontSize * scaleFactor.
+    //then compute finalScaleFactor the zooming factor of renderingFont to reach a 
+    //final font size of  basicFontSize* scaleFactor:
+    renderingFontIndex = 0;
+
+    //try to align on an integer pixel size if the targetSize font is available
+    int targetSize = round(basicFontSize * scaleFactor);
+
+    fonts[0].loadFontOnce();
+
+    for (int i = 0; i < GLFONT_SIZE_MAX - 1; i++) {
+
+        fonts[i + 1].loadFontOnce();
+
+        if (fonts[i + 1].pixHeight <= targetSize) {
+
+            renderingFontIndex = i + 1;
+        }
+        else {
+            break;
+        }
+    } //end for
+
+      //
+    int rawSize = fonts[renderingFontIndex].pixHeight;
+
+    //targetSize may not be reached yet, so the effective rendering font: fonts[renderingFontIndex] must be scaled up a bit.
+    renderingFontScaleFactor = (double) targetSize / rawSize;
+}
+
+void GLFont::Drawer::drawString(const std::wstring& str, float xpos, float ypos, Align hAlign, Align vAlign, int vpx, int vpy, bool cacheable) {
+
+    GLFont& appliedFont = fonts[renderingFontIndex];
+
+    appliedFont.drawString(str, round(appliedFont.pixHeight * renderingFontScaleFactor), xpos, ypos, hAlign, vAlign, vpx, vpy, cacheable);
+}
+
+//Public drawing font, 8 bit char version.
+void GLFont::Drawer::drawString(const std::string& str, float xpos, float ypos, Align hAlign, Align vAlign, int vpx, int vpy, bool cacheable) {
+
+    GLFont& appliedFont = fonts[renderingFontIndex];
+
+    appliedFont.drawString(str, round(appliedFont.pixHeight * renderingFontScaleFactor), xpos, ypos, hAlign, vAlign, vpx, vpy, cacheable);
+}
+
diff --git a/src/util/GLFont.h b/src/util/GLFont.h
new file mode 100644
index 0000000..3feac86
--- /dev/null
+++ b/src/util/GLFont.h
@@ -0,0 +1,207 @@
+#pragma once
+
+#include <map>
+#include <string>
+#include <sstream>
+#include <mutex>
+#include <atomic>
+#include "lodepng.h"
+#include "wx/glcanvas.h"
+#include "wx/filename.h"
+#include "wx/stdpaths.h"
+
+class GLFontStringCache {
+public:
+    GLFontStringCache();
+    int drawlen;
+    int vpx, vpy;
+    int pxHeight;
+    float msgWidth;
+    std::atomic_int gc;
+    std::vector<float> gl_vertices;
+    std::vector<float> gl_uv;
+};
+
+class GLFontChar {
+public:
+    GLFontChar();
+    ~GLFontChar();
+
+    void setId(int idval);
+    
+    // Returns the code point of the 16bit character, supposely Unicode.    
+    int getId();
+
+    void setXOffset(int xofs);
+    int getXOffset();
+
+    void setYOffset(int yofs);
+    int getYOffset();
+
+    void setX(int xpos);
+    int getX();
+
+    void setY(int ypos);
+    int getY();
+
+    void setWidth(int w);
+    int getWidth();
+
+    void setHeight(int h);
+    int getHeight();
+
+    void setXAdvance(int xadv);
+    int getXAdvance();
+
+    float getAspect();
+
+    void setIndex(unsigned int idx);
+    int getIndex();
+
+private:
+    // this is the code point of the 16bit character, supposly Unicode.
+    int id; 
+    int x, y, width, height;
+    int xoffset, yoffset;
+    int xadvance;
+    float aspect;
+    int index;
+};
+
+
+
+class GLFont {
+public:
+
+
+
+
+    enum Align {
+        GLFONT_ALIGN_LEFT, GLFONT_ALIGN_RIGHT, GLFONT_ALIGN_CENTER, GLFONT_ALIGN_TOP, GLFONT_ALIGN_BOTTOM
+    };
+    enum GLFontSize {
+        GLFONT_SIZE12,
+        GLFONT_SIZE16,
+        GLFONT_SIZE18,
+        GLFONT_SIZE24,
+        GLFONT_SIZE27, //new
+        GLFONT_SIZE32,
+        GLFONT_SIZE36, //new
+        GLFONT_SIZE48,
+        GLFONT_SIZE64, //new
+        GLFONT_SIZE72, //new
+        GLFONT_SIZE96, //new
+        GLFONT_SIZE_MAX
+    };
+
+    enum GLFontScale {
+        GLFONT_SCALE_NORMAL,
+        GLFONT_SCALE_MEDIUM, // x1.5
+        GLFONT_SCALE_LARGE,  // x2
+        GLFONT_SCALE_MAX
+    };
+
+    GLFont(GLFontSize size, std::wstring fontFileName);
+    ~GLFont();
+
+   
+    //Called to change the scale of the rendered fonts
+    static void setScale(GLFontScale scale);
+
+    static GLFontScale getScale();
+
+    //Mean current scale factor: 1.0 in normal, 1.5 medium, 2.0 for large
+    static double getScaleFactor();
+    
+    //Return a valid font px height given the font size and scale factor
+    static int getScaledPx(int basicFontSize, double scaleFactor);
+
+   
+private:
+
+    std::wstring nextParam(std::wistringstream &str);
+    std::wstring getParamKey(const std::wstring& param_str);
+    std::wstring getParamValue(const std::wstring& param_str);
+
+    //Repository of all loaded fonts
+    static GLFont fonts[GLFontSize::GLFONT_SIZE_MAX];
+
+    static std::atomic<GLFontScale> currentScale;
+
+    //load a given font file, (lazy loading) 
+    void loadFontOnce();
+
+    //private drawing font, 16 bit char version, called by Drawer object
+    void drawString(const std::wstring& str, int pxHeight, float xpos, float ypos, Align hAlign = GLFONT_ALIGN_LEFT, Align vAlign = GLFONT_ALIGN_TOP, int vpx = 0, int vpy = 0, bool cacheable = false);
+
+    //private drawing font, 8 bit char version, called by Drawer object
+    void drawString(const std::string& str, int pxHeight, float xpos, float ypos, Align hAlign = GLFONT_ALIGN_LEFT, Align vAlign = GLFONT_ALIGN_TOP, int vpx = 0, int vpy = 0, bool cacheable = false);
+
+    GLFontStringCache *cacheString(const std::wstring& str, int pxHeight, int vpx, int vpy);
+    void drawCacheString(GLFontStringCache *fc, float xpos, float ypos, Align hAlign, Align vAlign);
+
+    void doCacheGC();
+    void clearCache();
+
+    //force GC of all available fonts
+    static void clearAllCaches();
+
+    float getStringWidth(const std::wstring& str, float size, float viewAspect);
+
+    //the string cache is per-front (internal font)
+    std::map<std::wstring, GLFontStringCache * > stringCache;
+
+    int lineHeight;
+    int base;
+    int imageWidth, imageHeight, pixHeight;
+    bool loaded;
+    GLFontSize fontSizeClass;
+
+    std::map<int, GLFontChar *> characters;
+
+    std::vector<float> gl_vertices;
+    std::vector<float> gl_uv;
+
+    //The font name as written in the def file.
+    std::wstring fontName;
+
+    //The full path font PNG filename
+    std::wstring imageFile;
+
+    //the real path location of the font definition file
+    std::wstring fontDefFileSource;
+
+    GLuint texId;
+    int gcCounter;
+    std::mutex cache_busy;
+
+public:
+
+    //Proxy class computing and caching the selection of the underlying fonts
+    //depending of the user input and requested scale for the fonts.
+    class Drawer {
+
+    private:
+       
+        //result of the computation
+        int renderingFontIndex = 0;
+
+        double renderingFontScaleFactor = 1.0;
+
+    public:
+
+        Drawer(int basicFontSize, double scaleFactor);
+
+        //Public drawing font, 16 bit char version.
+        void drawString(const std::wstring& str, float xpos, float ypos, Align hAlign = GLFONT_ALIGN_LEFT, Align vAlign = GLFONT_ALIGN_TOP, int vpx = 0, int vpy = 0, bool cacheable = false);
+
+        //Public drawing font, 8 bit char version.
+        void drawString(const std::string& str, float xpos, float ypos, Align hAlign = GLFONT_ALIGN_LEFT, Align vAlign = GLFONT_ALIGN_TOP, int vpx = 0, int vpy = 0, bool cacheable = false);
+          
+    }; //end class Drawer
+
+    //The User request a font of size requestedSize to display, with an additional 
+    //optional scale factor scaleFactor.
+    static GLFont::Drawer getFont(int requestedSize, double scaleFactor = 1.0);
+
+};
diff --git a/src/util/Gradient.cpp b/src/util/Gradient.cpp
new file mode 100644
index 0000000..7279296
--- /dev/null
+++ b/src/util/Gradient.cpp
@@ -0,0 +1,75 @@
+#include "Gradient.h"
+
+Gradient::Gradient() {
+
+}
+
+void Gradient::addColor(GradientColor c) {
+    colors.push_back(c);
+}
+
+std::vector<float> &Gradient::getRed() {
+    return r_val;
+}
+
+std::vector<float> &Gradient::getGreen() {
+    return g_val;
+}
+
+std::vector<float> &Gradient::getBlue() {
+    return b_val;
+}
+
+void Gradient::generate(unsigned int len) {
+    unsigned int chunk_size = len / (colors.size() - 1);
+
+    unsigned int p = 0;
+    r_val.resize(len);
+    g_val.resize(len);
+    b_val.resize(len);
+
+    for (unsigned int j = 0, jMax = colors.size() - 1; j < jMax; j++) {
+        if ((chunk_size * (jMax)) < len && (j == jMax - 1)) {
+            chunk_size += len - chunk_size * (jMax);
+        }
+
+        for (unsigned int i = 0; i < chunk_size; i++) {
+            float idx = (float) (i) / (float) chunk_size;
+
+            float r1 = colors[j].r;
+            float g1 = colors[j].g;
+            float b1 = colors[j].b;
+
+            float r2 = colors[j + 1].r;
+            float g2 = colors[j + 1].g;
+            float b2 = colors[j + 1].b;
+
+            float r = r1 + (r2 - r1) * idx;
+            float g = g1 + (g2 - g1) * idx;
+            float b = b1 + (b2 - b1) * idx;
+
+            if (r < 0.0)
+                r = 0.0;
+            if (r > 1.0)
+                r = 1.0;
+            if (g < 0.0)
+                g = 0.0;
+            if (g > 1.0)
+                g = 1.0;
+            if (b < 0.0)
+                b = 0.0;
+            if (b > 1.0)
+                b = 1.0;
+
+            r_val[p] = r;
+            g_val[p] = g;
+            b_val[p] = b;
+
+            p++;
+        }
+    }
+}
+
+Gradient::~Gradient() {
+
+}
diff --git a/src/util/Gradient.h b/src/util/Gradient.h
new file mode 100644
index 0000000..650daf3
--- /dev/null
+++ b/src/util/Gradient.h
@@ -0,0 +1,33 @@
+#pragma once
+
+#include <vector>
+
+class GradientColor {
+public:
+    float r, g, b;
+    float w;
+
+    GradientColor(float r_in, float g_in, float b_in) :
+            r(r_in), g(g_in), b(b_in), w(1) {
+    }
+};
+
+class Gradient {
+public:
+    Gradient();
+
+    void addColor(GradientColor c);
+
+    std::vector<float> &getRed();
+    std::vector<float> &getGreen();
+    std::vector<float> &getBlue();
+
+    void generate(unsigned int len);
+
+    ~Gradient();
+private:
+    std::vector<GradientColor> colors;
+    std::vector<float> r_val;
+    std::vector<float> g_val;
+    std::vector<float> b_val;
+};
diff --git a/src/util/MouseTracker.cpp b/src/util/MouseTracker.cpp
new file mode 100644
index 0000000..00fb878
--- /dev/null
+++ b/src/util/MouseTracker.cpp
@@ -0,0 +1,181 @@
+#include "MouseTracker.h"
+#include <iostream>
+
+MouseTracker::MouseTracker(wxWindow *target) :
+        mouseX(0), mouseY(0), lastMouseX(0), lastMouseY(0), originMouseX(0), originMouseY(0), deltaMouseX(0), deltaMouseY(0), vertDragLock(false), horizDragLock(
+                false), isMouseDown(false), isMouseRightDown(false), isMouseInView(false), target(target) {
+
+}
+
+MouseTracker::MouseTracker() :
+        MouseTracker(NULL) {
+
+}
+
+void MouseTracker::OnMouseMoved(wxMouseEvent& event) {
+    if (target == NULL) {
+        return;
+    }
+
+    const wxSize ClientSize = target->GetClientSize();
+
+    mouseX = (float) event.m_x / (float) ClientSize.x;
+    mouseY = 1.0 - (float) event.m_y / (float) ClientSize.y;
+
+    deltaMouseX = mouseX - lastMouseX;
+    deltaMouseY = mouseY - lastMouseY;
+
+    if (isMouseDown || isMouseRightDown) {
+#ifndef __APPLE__
+#ifndef __linux__
+        if (horizDragLock && vertDragLock) {
+            target->WarpPointer(originMouseX * ClientSize.x, (1.0 - originMouseY) * ClientSize.y);
+            mouseX = originMouseX;
+            mouseY = originMouseY;
+        } else if (vertDragLock && mouseY != lastMouseY) {
+            target->WarpPointer(event.m_x, (1.0 - originMouseY) * ClientSize.y);
+            mouseY = originMouseY;
+        } else if (horizDragLock && mouseX != lastMouseX) {
+            target->WarpPointer(originMouseX * ClientSize.x, event.m_y);
+            mouseX = originMouseX;
+        }
+#endif
+#endif
+    }
+
+    lastMouseX = mouseX;
+    lastMouseY = mouseY;
+}
+
+void MouseTracker::OnMouseWheelMoved(wxMouseEvent& /* event */) {
+//    std::cout << "wheel?" << std::endl;
+}
+
+void MouseTracker::OnMouseDown(wxMouseEvent& event) {
+    if (isMouseRightDown || target == NULL) {
+        return;
+    }
+
+    const wxSize ClientSize = target->GetClientSize();
+
+    mouseX = lastMouseX = (float) event.m_x / (float) ClientSize.x;
+    mouseY = lastMouseY = 1.0 - (float) event.m_y / (float) ClientSize.y;
+
+    originMouseX = mouseX;
+    originMouseY = mouseY;
+
+    isMouseDown = true;
+}
+
+void MouseTracker::OnMouseReleased(wxMouseEvent& /* event */) {
+    isMouseDown = false;
+}
+
+void MouseTracker::OnMouseRightDown(wxMouseEvent& event) {
+    if (isMouseDown || target == NULL) {
+        return;
+    }
+
+    const wxSize ClientSize = target->GetClientSize();
+
+    mouseX = lastMouseX = (float) event.m_x / (float) ClientSize.x;
+    mouseY = lastMouseY = 1.0 - (float) event.m_y / (float) ClientSize.y;
+
+    originMouseX = mouseX;
+    originMouseY = mouseY;
+
+    isMouseRightDown = true;
+}
+
+void MouseTracker::OnMouseRightReleased(wxMouseEvent& /* event */) {
+    isMouseRightDown = false;
+}
+
+void MouseTracker::OnMouseEnterWindow(wxMouseEvent& /* event */) {
+    isMouseInView = true;
+    isMouseDown = false;
+    isMouseRightDown = false;
+}
+
+void MouseTracker::OnMouseLeftWindow(wxMouseEvent& /* event */) {
+    isMouseDown = false;
+    isMouseRightDown = false;
+    isMouseInView = false;
+}
+
+float MouseTracker::getOriginMouseX() {
+    return originMouseX;
+}
+
+float MouseTracker::getOriginMouseY() {
+    return originMouseY;
+}
+
+float MouseTracker::getOriginDeltaMouseX() {
+    return mouseX - originMouseX;
+}
+
+float MouseTracker::getOriginDeltaMouseY() {
+    return mouseY - originMouseY;
+}
+
+float MouseTracker::getDeltaMouseX() {
+    return deltaMouseX;
+}
+
+float MouseTracker::getDeltaMouseY() {
+    return deltaMouseY;
+}
+
+float MouseTracker::getLastMouseX() {
+    return lastMouseX;
+}
+
+float MouseTracker::getLastMouseY() {
+    return lastMouseY;
+}
+
+CubicVR::vec2 MouseTracker::getGLXY() {
+    return CubicVR::vec2((getMouseX()-0.5)*2.0, (getMouseY()-0.5)*2.0);
+}
+
+float MouseTracker::getMouseX() {
+    return mouseX;
+}
+
+float MouseTracker::getMouseY() {
+    return mouseY;
+}
+
+void MouseTracker::setVertDragLock(bool dragLock) {
+    vertDragLock = dragLock;
+}
+
+void MouseTracker::setHorizDragLock(bool dragLock) {
+    horizDragLock = dragLock;
+}
+
+bool MouseTracker::getVertDragLock() {
+    return vertDragLock;
+}
+
+bool MouseTracker::getHorizDragLock() {
+    return horizDragLock;
+}
+
+bool MouseTracker::mouseDown() {
+    return isMouseDown;
+}
+
+bool MouseTracker::mouseInView() {
+    return isMouseInView;
+}
+
+void MouseTracker::setTarget(wxWindow *target_in) {
+    target = target_in;
+}
+
+
+bool MouseTracker::mouseRightDown() {
+    return isMouseRightDown;
+}
diff --git a/src/util/MouseTracker.h b/src/util/MouseTracker.h
new file mode 100644
index 0000000..e23455a
--- /dev/null
+++ b/src/util/MouseTracker.h
@@ -0,0 +1,50 @@
+#pragma once
+
+#include "wx/window.h"
+#include "cubic_math.h"
+
+class MouseTracker {
+public:
+    MouseTracker(wxWindow *target);
+    MouseTracker();
+
+    void OnMouseMoved(wxMouseEvent& event);
+    void OnMouseWheelMoved(wxMouseEvent& event);
+    void OnMouseDown(wxMouseEvent& event);
+    void OnMouseReleased(wxMouseEvent& event);
+    void OnMouseRightDown(wxMouseEvent& event);
+    void OnMouseRightReleased(wxMouseEvent& event);
+    void OnMouseEnterWindow(wxMouseEvent& event);
+    void OnMouseLeftWindow(wxMouseEvent& event);
+
+    float getOriginMouseX();
+    float getOriginMouseY();
+    float getOriginDeltaMouseX();
+    float getOriginDeltaMouseY();
+    float getDeltaMouseX();
+    float getDeltaMouseY();
+    float getLastMouseX();
+    float getLastMouseY();
+    CubicVR::vec2 getGLXY();
+    float getMouseX();
+    float getMouseY();
+
+    void setVertDragLock(bool dragLock);
+    void setHorizDragLock(bool dragLock);
+    bool getVertDragLock();
+    bool getHorizDragLock();
+    bool mouseDown();
+    bool mouseRightDown();
+    bool mouseInView();
+    void setTarget(wxWindow *target_in);
+
+private:
+    float mouseX, mouseY;
+    float lastMouseX, lastMouseY;
+    float originMouseX, originMouseY;
+    float deltaMouseX, deltaMouseY;
+
+    bool vertDragLock, horizDragLock;
+    bool isMouseDown, isMouseRightDown, isMouseInView;
+    wxWindow *target;
+};
diff --git a/src/util/ThreadQueue.cpp b/src/util/ThreadQueue.cpp
new file mode 100644
index 0000000..bf390c9
--- /dev/null
+++ b/src/util/ThreadQueue.cpp
@@ -0,0 +1 @@
+#include <ThreadQueue.h>
\ No newline at end of file
diff --git a/src/util/ThreadQueue.h b/src/util/ThreadQueue.h
new file mode 100644
index 0000000..5d0c52f
--- /dev/null
+++ b/src/util/ThreadQueue.h
@@ -0,0 +1,301 @@
+#pragma once 
+
+/* Credit to Alfredo Pons / https://plus.google.com/109903449837592676231
+ * Code from http://gnodebian.blogspot.com.es/2013/07/a-thread-safe-asynchronous-queue-in-c11.html
+ *
+ * Changes:
+ *   Charles J. Nov-19-2014
+ *     - Renamed SafeQueue -> ThreadQueue 
+ */
+
+#include <queue>
+#include <list>
+#include <mutex>
+#include <thread>
+#include <cstdint>
+#include <condition_variable>
+#include <atomic>
+
+class ThreadQueueBase {
+    
+};
+
+/** A thread-safe asynchronous queue */
+template<class T, class Container = std::list<T>>
+class ThreadQueue : public ThreadQueueBase {
+
+    typedef typename Container::value_type value_type;
+    typedef typename Container::size_type size_type;
+    typedef Container container_type;
+
+public:
+
+    /*! Create safe queue. */
+    ThreadQueue() {
+        m_max_num_items.store(0);
+    };
+    ThreadQueue(ThreadQueue&& sq) {
+        m_queue = std::move(sq.m_queue);
+        m_max_num_items.store(0);
+    }
+    ThreadQueue(const ThreadQueue& sq) {
+        std::lock_guard < std::mutex > lock(sq.m_mutex);
+        m_queue = sq.m_queue;
+        m_max_num_items.store(0);
+    }
+
+    /*! Destroy safe queue. */
+    ~ThreadQueue() {
+        std::lock_guard < std::mutex > lock(m_mutex);
+    }
+
+    /**
+     * Sets the maximum number of items in the queue. Defaults is 0: No limit
+     * \param[in] item An item.
+     */
+    void set_max_num_items(unsigned int max_num_items) {
+        std::lock_guard < std::mutex > lock(m_mutex);
+        if (m_max_num_items.load() != max_num_items) {
+            m_max_num_items.store(max_num_items);
+        }
+    }
+
+    /**
+     *  Pushes the item into the queue.
+     * \param[in] item An item.
+     * \return true if an item was pushed into the queue
+     */
+    bool push(const value_type& item) {
+        std::lock_guard < std::mutex > lock(m_mutex);
+
+        if (m_max_num_items.load() > 0 && m_queue.size() > m_max_num_items.load()) {
+            m_condition.notify_all();
+            return false;
+        }
+
+        m_queue.push(item);
+        m_condition.notify_all();
+        return true;
+    }
+
+    /**
+     *  Pushes the item into the queue.
+     * \param[in] item An item.
+     * \return true if an item was pushed into the queue
+     */
+    bool push(const value_type&& item) {
+        std::lock_guard < std::mutex > lock(m_mutex);
+
+        if (m_max_num_items.load() > 0 && m_queue.size() > m_max_num_items.load()) {
+            m_condition.notify_all();
+            return false;
+        }
+
+        m_queue.push(item);
+        m_condition.notify_all();
+        return true;
+    }
+
+    /**
+     *  Pops item from the queue. If queue is empty, this function blocks until item becomes available.
+     * \param[out] item The item.
+     */
+    void pop(value_type& item) {
+        std::unique_lock < std::mutex > lock(m_mutex);
+        m_condition.wait(lock, [this]() // Lambda funct
+                {
+                    return !m_queue.empty();
+                });
+        item = m_queue.front();
+        m_queue.pop();
+    }
+
+    /**
+     *  Pops item from the queue using the contained type's move assignment operator, if it has one..
+     *  This method is identical to the pop() method if that type has no move assignment operator.
+     *  If queue is empty, this function blocks until item becomes available.
+     * \param[out] item The item.
+     */
+    void move_pop(value_type& item) {
+        std::unique_lock < std::mutex > lock(m_mutex);
+        m_condition.wait(lock, [this]() // Lambda funct
+                {
+                    return !m_queue.empty();
+                });
+        item = std::move(m_queue.front());
+        m_queue.pop();
+    }
+
+    /**
+     *  Tries to pop item from the queue.
+     * \param[out] item The item.
+     * \return False is returned if no item is available.
+     */
+    bool try_pop(value_type& item) {
+        std::unique_lock < std::mutex > lock(m_mutex);
+
+        if (m_queue.empty())
+            return false;
+
+        item = m_queue.front();
+        m_queue.pop();
+        return true;
+    }
+
+    /**
+     *  Tries to pop item from the queue using the contained type's move assignment operator, if it has one..
+     *  This method is identical to the try_pop() method if that type has no move assignment operator.
+     * \param[out] item The item.
+     * \return False is returned if no item is available.
+     */
+    bool try_move_pop(value_type& item) {
+        std::unique_lock < std::mutex > lock(m_mutex);
+
+        if (m_queue.empty())
+            return false;
+
+        item = std::move(m_queue.front());
+        m_queue.pop();
+        return true;
+    }
+
+    /**
+     *  Pops item from the queue. If the queue is empty, blocks for timeout microseconds, or until item becomes available.
+     * \param[out] t An item.
+     * \param[in] timeout The number of microseconds to wait.
+     * \return true if get an item from the queue, false if no item is received before the timeout.
+     */
+    bool timeout_pop(value_type& item, std::uint64_t timeout) {
+        std::unique_lock < std::mutex > lock(m_mutex);
+
+        if (m_queue.empty()) {
+            if (timeout == 0)
+                return false;
+
+            if (m_condition.wait_for(lock, std::chrono::microseconds(timeout)) == std::cv_status::timeout)
+                return false;
+        }
+
+        item = m_queue.front();
+        m_queue.pop();
+        return true;
+    }
+
+    /**
+     *  Pops item from the queue using the contained type's move assignment operator, if it has one..
+     *  If the queue is empty, blocks for timeout microseconds, or until item becomes available.
+     *  This method is identical to the try_pop() method if that type has no move assignment operator.
+     * \param[out] t An item.
+     * \param[in] timeout The number of microseconds to wait.
+     * \return true if get an item from the queue, false if no item is received before the timeout.
+     */
+    bool timeout_move_pop(value_type& item, std::uint64_t timeout) {
+        std::unique_lock < std::mutex > lock(m_mutex);
+
+        if (m_queue.empty()) {
+            if (timeout == 0)
+                return false;
+
+            if (m_condition.wait_for(lock, std::chrono::microseconds(timeout)) == std::cv_status::timeout)
+                return false;
+        }
+
+        item = std::move(m_queue.front());
+        m_queue.pop();
+        return true;
+    }
+
+    /**
+     *  Gets the number of items in the queue.
+     * \return Number of items in the queue.
+     */
+    size_type size() const {
+        std::lock_guard < std::mutex > lock(m_mutex);
+        return m_queue.size();
+    }
+
+    /**
+     *  Check if the queue is empty.
+     * \return true if queue is empty.
+     */
+    bool empty() const {
+        std::lock_guard < std::mutex > lock(m_mutex);
+        return m_queue.empty();
+    }
+
+    /**
+     *  Check if the queue is full.
+     * \return true if queue is full.
+     */
+    bool full() const {
+        std::lock_guard < std::mutex > lock(m_mutex);
+        return (m_max_num_items.load() != 0) && (m_queue.size() >= m_max_num_items.load());
+    }
+
+    /**
+     *  Remove any items in the queue.
+     */
+    void flush() {
+        std::lock_guard < std::mutex > lock(m_mutex);
+        m_queue = std::queue<T, Container>();
+        std::queue<T, Container> emptyQueue;
+        std::swap(m_queue, emptyQueue);
+    }
+
+    /**
+     *  Swaps the contents.
+     * \param[out] sq The ThreadQueue to swap with 'this'.
+     */
+    void swap(ThreadQueue& sq) {
+        if (this != &sq) {
+            std::lock_guard < std::mutex > lock1(m_mutex);
+            std::lock_guard < std::mutex > lock2(sq.m_mutex);
+            m_queue.swap(sq.m_queue);
+
+            if (!m_queue.empty())
+                m_condition.notify_all();
+
+            if (!sq.m_queue.empty())
+                sq.m_condition.notify_all();
+        }
+    }
+
+    /*! The copy assignment operator */
+    ThreadQueue& operator=(const ThreadQueue& sq) {
+        if (this != &sq) {
+            std::lock_guard < std::mutex > lock1(m_mutex);
+            std::lock_guard < std::mutex > lock2(sq.m_mutex);
+            std::queue<T, Container> temp { sq.m_queue };
+            m_queue.swap(temp);
+
+            if (!m_queue.empty())
+                m_condition.notify_all();
+        }
+
+        return *this;
+    }
+
+    /*! The move assignment operator */
+    ThreadQueue& operator=(ThreadQueue && sq) {
+        std::lock_guard < std::mutex > lock(m_mutex);
+        m_queue = std::move(sq.m_queue);
+
+        if (!m_queue.empty())
+            m_condition.notify_all();
+
+        return *this;
+    }
+
+private:
+
+    std::queue<T, Container> m_queue;
+    mutable std::mutex m_mutex;
+    std::condition_variable m_condition;
+    std::atomic_uint m_max_num_items;
+};
+
+/*! Swaps the contents of two ThreadQueue objects. */
+template<class T, class Container>
+void swap(ThreadQueue<T, Container>& q1, ThreadQueue<T, Container>& q2) {
+    q1.swap(q2);
+}
diff --git a/src/util/Timer.cpp b/src/util/Timer.cpp
new file mode 100644
index 0000000..aa449e7
--- /dev/null
+++ b/src/util/Timer.cpp
@@ -0,0 +1,175 @@
+
+#include "Timer.h"
+
+#ifdef _WIN32
+	#include <windows.h>
+	#include <mmsystem.h>
+#endif
+
+#include <iostream>
+
+Timer::Timer(void) : time_elapsed(0), system_milliseconds(0), start_time(0), end_time(0), last_update(0), num_updates(0), paused_time(0), offset(0), paused_state(false), lock_state(false), lock_rate(0)
+{
+}
+
+
+void Timer::start(void) 
+{
+	update();
+	num_updates = 0;
+	start_time = system_milliseconds;
+	last_update = start_time;
+	paused_state = false;
+	lock_state = false;
+	lock_rate = 0;
+	paused_time = 0;
+	offset = 0;
+}
+
+
+void Timer::stop(void) 
+{
+	end_time = system_milliseconds;
+}
+
+
+void Timer::reset(void)
+{
+	start();
+}
+
+
+void Timer::lockFramerate(float f_rate)
+{
+	lock_rate = 1.0f/f_rate;
+	lock_state = true;
+}
+
+
+void Timer::unlock()
+{
+	unsigned long msec_tmp = system_milliseconds;
+	
+	lock_state = false;
+
+	update();
+	
+	last_update = system_milliseconds-lock_rate;
+	
+	offset += msec_tmp-system_milliseconds;
+	
+	lock_rate = 0;
+}
+
+bool Timer::locked()
+{
+	return lock_state;
+}
+
+void Timer::update(void) 
+{
+	num_updates++;
+	last_update = system_milliseconds;
+	
+	
+	if (lock_state)
+	{
+		system_milliseconds += (unsigned long)(lock_rate*1000.0);
+	}
+	else
+	{
+#ifdef _WIN32
+
+		system_milliseconds = timeGetTime ();
+
+#else
+		gettimeofday(&time_val,&time_zone);
+
+		system_milliseconds = (unsigned long)time_val.tv_usec;
+		system_milliseconds /= 1000;
+		system_milliseconds += (unsigned long)(time_val.tv_sec*1000);
+#endif
+	}
+
+
+	if (paused_state) paused_time += system_milliseconds-last_update;
+
+	time_elapsed = system_milliseconds-start_time-paused_time+offset;
+}
+
+
+unsigned long Timer::getMilliseconds(void) 
+{
+	return time_elapsed;
+}
+
+
+
+double Timer::getSeconds(void) 
+{
+	return ((double)getMilliseconds())/1000.0;
+}
+
+
+void Timer::setMilliseconds(unsigned long milliseconds_in) 
+{
+	offset -= (system_milliseconds-start_time-paused_time+offset)-milliseconds_in;
+}
+
+
+
+void Timer::setSeconds(double seconds_in) 
+{
+	setMilliseconds((long)(seconds_in*1000.0));
+}
+
+
+double Timer::lastUpdateSeconds(void)
+{
+	return ((double)lastUpdateMilliseconds())/1000.0;
+}
+
+
+unsigned long Timer::lastUpdateMilliseconds(void)
+{
+	return system_milliseconds-last_update;
+}
+
+unsigned long Timer::totalMilliseconds()
+{
+	return system_milliseconds-start_time;
+}
+
+
+double Timer::totalSeconds(void)
+{
+	return totalMilliseconds()/1000.0;
+}
+
+
+unsigned long Timer::getNumUpdates(void)
+{
+	return num_updates;
+}
+
+
+void Timer::paused(bool pause_in)
+{
+	paused_state = pause_in;
+}
+
+bool Timer::paused()
+{
+	return paused_state;
+}
+
+void Timer::timerTestFunc() {
+    update();
+    if (getNumUpdates() % 120 == 0) {
+        std::cout << getNumUpdates() << "," << getSeconds() << " Rate: " << ((double)getNumUpdates()/getSeconds()) << "/sec" << std::endl;
+    }
+    if (getNumUpdates() >= 600) {
+        reset();
+    }
+}
+
diff --git a/src/util/Timer.h b/src/util/Timer.h
new file mode 100644
index 0000000..18ab841
--- /dev/null
+++ b/src/util/Timer.h
@@ -0,0 +1,164 @@
+#ifndef TIMER_H
+#define TIMER_H
+
+#ifdef WIN32
+#include <windows.h>
+#else
+#include <sys/time.h>
+#endif
+
+/// Timer Class, high resolution timer
+/**
+ * Class provides high resolution timing and useful time control functions
+ */
+
+class Timer {
+private:
+
+    unsigned long time_elapsed;
+    unsigned long system_milliseconds;
+    unsigned long start_time;
+    unsigned long end_time;
+    unsigned long last_update;
+    unsigned long num_updates;
+    unsigned long paused_time;
+    unsigned long offset;
+
+#ifndef _WIN32
+    struct timeval time_val;
+    struct timezone time_zone;
+#endif
+
+    bool paused_state;
+    bool lock_state;
+    float lock_rate;
+
+public:
+
+    /// Constructor
+    Timer();
+
+    /// Start the timer
+    /**
+     *  Resets the timer to 0 and begins timing
+     */
+    void start(void);
+
+    /// Stop the timer
+    /**
+     *  Stops the timer and records the end time
+     */
+    void stop(void);
+
+    /// Locks the timer to a specified framerate (for recording / benchmarking purposes typically)
+    /**
+     *  Locks the timer to a specified framerate (for recording / benchmarking purposes typically)
+     */
+    void lockFramerate(float f_rate);
+
+    /// Unlock any framerate lock that's been applied
+    /**
+     *  Unlock any framerate lock that's been applied
+     */
+    void unlock();
+
+    /// Check locked state
+    /**
+     *  Check locked state
+     */
+    bool locked();
+
+    /// Reset the timer counter
+    /**
+     *	Resetting the timer will reset the current time to 0
+     */
+    void reset(void);
+
+    /// Timer update
+    /**
+     *	Calling the update command will bring the timer value up to date, this is meant
+     *	to be called at the begining of the frame to establish the time index which is being drawn.
+     */
+    void update(void);
+
+    /// Get the total time elapsed since the timer start, not counting paused time
+    /**
+     *	Returns the total time elapsed in since the timer start() to the last update() but
+     *	does not count the time elapsed while the timer is paused().
+     *  \return Total time elapsed since the timer start() to the last update() excluding time paused() in milliseconds
+     */
+    unsigned long getMilliseconds(void);
+    /// Alias of getMilliseconds() which returns time in seconds
+    /**
+     *  \return Total time elapsed since the timer start() to the last update() excluding time paused() in seconds
+     */
+    double getSeconds(void);
+
+    /// Get the total time elapsed since the timer start
+    /**
+     *	Returns the total time elapsed in since the timer start() to the last update()
+     *  this includes any time accumulated during updates while paused()
+     *  \return Total time elapsed since the timer start() to the last update() including time paused() in milliseconds
+     */
+    unsigned long totalMilliseconds(void);
+    /// Alias of totalMilliseconds() which returns time in seconds
+    /**
+     *  \return Total time elapsed since the timer start() to the last update() including time paused() in seconds
+     */
+    double totalSeconds(void);
+
+    /// Set the amount of time elapsed
+    /**
+     *	Force the timer duration to a specific value, useful for rolling forward or back in a system
+     *	based upon the timer.
+     *  \param milliseconds_in Time to set timer to in milliseconds
+     */
+    void setMilliseconds(unsigned long milliseconds_in);
+    /// alias of setMilliseconds() which accepts time in seconds
+    /**
+     *  \param seconds_in Time to set timer to in seconds
+     */
+    void setSeconds(double seconds_in);
+
+    /// Get the amount of times the update() command has been called
+    /**
+     * 	By using the number of times the update() command has been called you can easily determine
+     *  an average frame rate. Also useful for merely determining how many frames have been drawn.
+     *  \return Number of times update() has been called
+     */
+    unsigned long getNumUpdates(void);
+
+    /// Get the timer duration during the last update
+    /**
+     *	Useful for determining the amount of time which elapsed during the last update
+     *	can be used to accurately control values with a per-second rate or determine the current frame rate.
+     *  \return Duration of time between the last two calls to update() in milliseconds
+     */
+    unsigned long lastUpdateMilliseconds(void);
+    /// Alias of lastUpdateMilliseconds() which returns time in seconds
+    /**
+     *  \return Duration of time between the last two calls to update() in seconds
+     */
+    double lastUpdateSeconds(void);
+
+    /// Set the timer pause state
+    /**
+     *  Pause the timer, allowing for continued update() calls without an increment in timing but
+     *	maintaining the update and total time count, useful for pausing a scene but allowing frame
+     *	timing to resume.
+     *  \param pause_in Value to set the current pause state to
+     */
+    void paused(bool pause_in);
+
+    /// Check if the timer is currently in a paused state
+    /**
+     *  \return Current pause state, true if paused, false otherwise
+     */
+    bool paused();
+    
+    
+    void timerTestFunc();
+};
+
+#endif
+
diff --git a/src/visual/ColorTheme.cpp b/src/visual/ColorTheme.cpp
new file mode 100644
index 0000000..6180778
--- /dev/null
+++ b/src/visual/ColorTheme.cpp
@@ -0,0 +1,265 @@
+#include "ColorTheme.h"
+#include "CubicSDR.h"
+#include "CubicSDRDefs.h"
+
+ThemeMgr ThemeMgr::mgr;
+
+void ThemeMgr::setTheme(int themeId) {
+    currentTheme = themes[themeId];
+    this->themeId = themeId;
+}
+
+int ThemeMgr::getTheme() {
+    return themeId;
+}
+
+ThemeMgr::ThemeMgr() {
+    themes[COLOR_THEME_DEFAULT] = new DefaultColorTheme;
+    themes[COLOR_THEME_BW] = new BlackAndWhiteColorTheme;
+    themes[COLOR_THEME_SHARP] = new SharpColorTheme;
+    themes[COLOR_THEME_RAD] = new RadColorTheme;
+    themes[COLOR_THEME_TOUCH] = new TouchColorTheme;
+    themes[COLOR_THEME_HD] = new HDColorTheme;
+    themes[COLOR_THEME_RADAR] = new RadarColorTheme;
+
+    currentTheme = themes[COLOR_THEME_DEFAULT];
+    themeId = COLOR_THEME_DEFAULT;
+}
+
+ThemeMgr::~ThemeMgr() {
+    std::map<int, ColorTheme *>::iterator i;
+    for (i = themes.begin(); i != themes.end(); i++) {
+        delete i->second;
+    }
+}
+
+DefaultColorTheme::DefaultColorTheme() {
+    name = "Default";
+    waterfallGradient.addColor(GradientColor(0, 0, 0));
+    waterfallGradient.addColor(GradientColor(0, 0, 1.0));
+    waterfallGradient.addColor(GradientColor(0, 1.0, 0));
+    waterfallGradient.addColor(GradientColor(1.0, 1.0, 0));
+    waterfallGradient.addColor(GradientColor(1.0, 0.2f, 0.0));
+    waterfallGradient.generate(256);
+    waterfallHighlight = RGBA4f(1, 1, 1);
+    waterfallNew = RGBA4f(0, 1, 0);
+    waterfallHover = RGBA4f(1, 1, 0);
+    waterfallDestroy = RGBA4f(1, 0, 0);
+    fftLine = RGBA4f(0.9f, 0.9f, 0.9f);
+    fftHighlight = RGBA4f(1, 1, 1);
+    scopeLine = RGBA4f(0.9f, 0.9f, 0.9f);
+    tuningBarLight = RGBA4f(0.2f, 0.2f, 0.9f);
+    tuningBarDark = RGBA4f(0.0f, 0.0f, 0.35f);
+    tuningBarUp = RGBA4f(1.0f, 139.0f/255.0f, 96.0f/255.0f);
+    tuningBarDown = RGBA4f(148.0f/255.0f, 148.0f/255.0f, 1.0f);
+    meterLevel = RGBA4f(0.1f, 0.75f, 0.1f);
+    meterValue = RGBA4f(0.75f, 0.1f, 0.1f);
+    text = RGBA4f(1, 1, 1);
+    freqLine = RGBA4f(1, 1, 1);
+    button = RGBA4f(0.65f, 0.65f, 0.65f);
+    buttonHighlight = RGBA4f(1, 1, 0);
+
+    scopeBackground = RGBA4f(0.1f, 0.1f, 0.1f);
+    fftBackground = RGBA4f(0.1f, 0.1f, 0.1f);
+    generalBackground = RGBA4f(0.1f, 0.1f, 0.1f);
+}
+
+
+RadarColorTheme::RadarColorTheme() {
+    name = "Rad";
+    waterfallGradient.addColor(GradientColor(5.0f / 255.0f, 45.0f / 255.0f, 10.0f / 255.0f));
+    waterfallGradient.addColor(GradientColor(30.0f / 255.0f, 150.0f / 255.0f, 40.0f / 255.0f));
+    waterfallGradient.addColor(GradientColor(40.0f / 255.0f, 240.0f / 255.0f, 60.0f / 255.0f));
+    waterfallGradient.addColor(GradientColor(250.0f / 255.0f, 250.0f / 255.0f, 250.0f / 255.0f));
+    waterfallGradient.generate(256);
+    waterfallHighlight = RGBA4f(1, 1, 1);
+    waterfallNew = RGBA4f(0, 1, 0);
+    waterfallHover = RGBA4f(1, 1, 0);
+    waterfallDestroy = RGBA4f(1, 0, 0);
+    fftLine = RGBA4f(0.8f, 1.0f, 0.8f);
+    fftHighlight = RGBA4f(1, 1, 1);
+    scopeLine = RGBA4f(0.8f, 1.0f, 0.8f);
+    tuningBarLight = RGBA4f(0.0, 0.45f, 0.0);
+    tuningBarDark = RGBA4f(0.0, 0.1f, 0.0);
+    tuningBarUp = RGBA4f(1.0f, 139.0f/255.0f, 96.0f/255.0f);
+    tuningBarDown = RGBA4f(148.0f/255.0f, 0.0, 0.0);
+    meterLevel = RGBA4f(0, 0.5f, 0);
+    meterValue = RGBA4f(0, 0.5f, 0);
+    text = RGBA4f(0.8f, 1.0, 0.8f);
+    freqLine = RGBA4f(1, 1, 1);
+    button = RGBA4f(0.65f, 0.75f, 0.65f);
+    buttonHighlight = RGBA4f(0.65f, 1.0f, 0.65f);
+
+    scopeBackground = RGBA4f(0.05f, 0.1f, 0.05f);
+    fftBackground = RGBA4f(0.05f, 0.1f, 0.05f);
+    generalBackground = RGBA4f(0.05f, 0.1f, 0.05f);
+}
+
+BlackAndWhiteColorTheme::BlackAndWhiteColorTheme() {
+    name = "Black & White";
+    waterfallGradient.addColor(GradientColor(0, 0, 0));
+    waterfallGradient.addColor(GradientColor(0.75f, 0.75f, 0.75f));
+    waterfallGradient.addColor(GradientColor(1.0f, 1.0f, 1.0f));
+    waterfallGradient.generate(256);
+    waterfallHighlight = RGBA4f(1, 1, 0.9f);
+    waterfallNew = RGBA4f(0, 1, 0);
+    waterfallHover = RGBA4f(1, 1, 0);
+    waterfallDestroy = RGBA4f(1, 0, 0);
+    fftLine = RGBA4f(0.9f, 0.9f, 0.9f);
+    fftHighlight = RGBA4f(1, 1, 0.9f);
+    scopeLine = RGBA4f(0.9f, 0.9f, 0.9f);
+    tuningBarLight = RGBA4f(0.4f, 0.4f, 0.4f);
+    tuningBarDark = RGBA4f(0.1f, 0.1f, 0.1f);
+    tuningBarUp = RGBA4f(0.8f, 0.8f, 0.8f);
+    tuningBarDown = RGBA4f(0.4f, 0.4f, 0.4f);
+    meterLevel = RGBA4f(0.5f, 0.5f, 0.5f);
+    meterValue = RGBA4f(0.5f, 0.5f, 0.5f);
+    text = RGBA4f(1, 1, 1);
+    freqLine = RGBA4f(1, 1, 1);
+    button = RGBA4f(0.65f, 0.65f, 0.65f);
+    buttonHighlight = RGBA4f(1, 1, 1);
+
+    scopeBackground = RGBA4f(0.1f, 0.1f, 0.1f);
+    fftBackground = RGBA4f(0.1f, 0.1f, 0.1f);
+    generalBackground = RGBA4f(0.1f, 0.1f, 0.1f);
+
+}
+
+SharpColorTheme::SharpColorTheme() {
+    name = "Sharp";
+    waterfallGradient.addColor(GradientColor(0, 0, 0));
+    waterfallGradient.addColor(GradientColor(0.0, 0, 0.5f));
+    waterfallGradient.addColor(GradientColor(0.0, 0.0, 1.0f));
+    waterfallGradient.addColor(GradientColor(65.0f / 255.0f, 161.0f / 255.0f, 1.0f));
+    waterfallGradient.addColor(GradientColor(1.0f, 1.0f, 1.0f));
+    waterfallGradient.addColor(GradientColor(1.0f, 1.0f, 1.0f));
+    waterfallGradient.addColor(GradientColor(1.0f, 1.0f, 0.5f));
+    waterfallGradient.addColor(GradientColor(1.0f, 1.0f, 0.0f));
+    waterfallGradient.addColor(GradientColor(1.0f, 0.5f, 0.0f));
+    waterfallGradient.addColor(GradientColor(1.0f, 0.25f, 0.0f));
+    waterfallGradient.addColor(GradientColor(0.5f, 0.1f, 0.0f));
+    waterfallGradient.generate(256);
+    waterfallHighlight = RGBA4f(0.9f, 0.9f, 1.0f);
+    waterfallNew = RGBA4f(0, 1, 0);
+    waterfallHover = RGBA4f(1, 1, 0);
+    waterfallDestroy = RGBA4f(1, 0, 0);
+    fftLine = RGBA4f(0.9f, 0.9f, 1.0);
+    fftHighlight = RGBA4f(0.9f, 0.9f, 1.0);
+    scopeLine = RGBA4f(0.85f, 0.85f, 1.0);
+    tuningBarLight = RGBA4f(28.0f / 255.0f, 106.0f / 255.0f, 179.0f / 255.0f);
+    tuningBarDark = RGBA4f(14.0f / 255.0f, 53.0f / 255.0f, 89.5f / 255.0f);
+    tuningBarUp = RGBA4f(0.7f, 0.7f, 0.7f);
+    tuningBarDown = RGBA4f(1.0f, 0.0, 0.0);
+    meterLevel = RGBA4f(28.0f / 255.0f, 106.0f / 255.0f, 179.0f / 255.0f);
+    meterValue = RGBA4f(190.0f / 255.0f, 190.0f / 255.0f, 60.0f / 255.0f);
+    text = RGBA4f(0.9f, 0.9f, 1);
+    freqLine = RGBA4f(0.85f, 0.85f, 1.0f);
+    button = RGBA4f(217.0f / 255.0f, 218.0f / 255.0f, 228.0f / 255.0f);
+    buttonHighlight = RGBA4f(208.0f / 255.0f, 249.0f / 255.0f, 255.0f / 255.0f);
+
+    scopeBackground = RGBA4f(0.05f, 0.05f, 0.15f);
+    fftBackground = RGBA4f(0.05f, 0.05f, 0.15f);
+    generalBackground = RGBA4f(0.05f, 0.05f, 0.15f);
+}
+
+RadColorTheme::RadColorTheme() {
+    name = "Rad";
+    waterfallGradient.addColor(GradientColor(0, 0, 0.5f));
+    waterfallGradient.addColor(GradientColor(25.0f / 255.0f, 154.0f / 255.0f, 0.0));
+    waterfallGradient.addColor(GradientColor(201.0f / 255.0f, 115.0f / 255.0f, 0.0));
+    waterfallGradient.addColor(GradientColor(1.0, 40.0f / 255.0f, 40.0f / 255.0f));
+    waterfallGradient.addColor(GradientColor(1.0, 1.0, 1.0));
+    waterfallGradient.generate(256);
+    waterfallHighlight = RGBA4f(1, 1, 1);
+    waterfallNew = RGBA4f(0, 1, 0);
+    waterfallHover = RGBA4f(1, 1, 0);
+    waterfallDestroy = RGBA4f(1, 0, 0);
+    fftLine = RGBA4f(1.0, 0.9f, 0.9f);
+    fftHighlight = RGBA4f(1, 1, 1);
+    scopeLine = RGBA4f(1.0, 0.9f, 0.9f);
+    tuningBarLight = RGBA4f(0.0, 0.45f, 0.0);
+    tuningBarDark = RGBA4f(0.0, 0.1f, 0.0);
+    tuningBarUp = RGBA4f(1.0, 0.0, 0.0);
+    tuningBarDown = RGBA4f(0.0, 0.5f, 1.0);
+    meterLevel = RGBA4f(0, 0.5f, 0);
+    meterValue = RGBA4f(0.5f, 0, 0);
+    text = RGBA4f(1, 1, 1);
+    freqLine = RGBA4f(1, 1, 1);
+    button = RGBA4f(0.65f, 0.65f, 0.65f);
+    buttonHighlight = RGBA4f(0.76f, 0.65f, 0);
+
+    scopeBackground = RGBA4f(13.0f / 255.0f, 47.0f / 255.0f, 9.0f / 255.0f);
+    fftBackground = RGBA4f(0, 0, 50.0f / 255.0f);
+    generalBackground = RGBA4f(13.0f / 255.0f, 47.0f / 255.0f, 9.0f / 255.0f);
+}
+
+TouchColorTheme::TouchColorTheme() {
+    name = "Touch";
+    waterfallGradient.addColor(GradientColor(0, 0, 0));
+    waterfallGradient.addColor(GradientColor(55.0f / 255.0f, 40.0f / 255.0f, 55.0f / 255.0f));
+    waterfallGradient.addColor(GradientColor(61.0f / 255.0f, 57.0f / 255.0f, 88.0f / 255.0f));
+    waterfallGradient.addColor(GradientColor(0.0f / 255.0f, 255.0f / 255.0f, 255.0f / 255.0f));
+    waterfallGradient.addColor(GradientColor(10.0f / 255.0f, 255.0f / 255.0f, 85.0f / 255.0f));
+    waterfallGradient.addColor(GradientColor(255.0f / 255.0f, 255.0f / 255.0f, 75.0f / 255.0f));
+    waterfallGradient.addColor(GradientColor(255.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f));
+    waterfallGradient.addColor(GradientColor(255.0f / 255.0f, 255.0f / 255.0f, 255.0f / 255.0f));
+    waterfallGradient.generate(256);
+    waterfallHighlight = RGBA4f(1, 1, 1);
+    waterfallNew = RGBA4f(0, 1, 0);
+    waterfallHover = RGBA4f(1, 1, 0);
+    waterfallDestroy = RGBA4f(1, 0, 0);
+    fftLine = RGBA4f(234.0f / 255.0f, 232.0f / 255.0f, 247.0f / 255.0f);
+    fftHighlight = RGBA4f(1.0f, 1.0f, 1.0f);
+    scopeLine = RGBA4f(234.0f / 255.0f, 232.0f / 255.0f, 247.0f / 255.0f);
+    tuningBarLight = RGBA4f(0.2f, 0.2f, 0.7f);
+    tuningBarDark = RGBA4f(0.1f, 0.1f, 0.45f);
+    tuningBarUp = RGBA4f(0.5f, 139.0f/255.0f, 96.0f/255.0f);
+    tuningBarDown = RGBA4f(0.6f, 108.0f/255.0f, 1.0f);
+    meterLevel = RGBA4f(61.0f / 255.0f, 57.0f / 255.0f, 88.0f / 255.0f);
+    meterValue = RGBA4f(61.0f / 255.0f, 57.0f / 255.0f, 88.0f / 255.0f);
+    text = RGBA4f(1, 1, 1);
+    freqLine = RGBA4f(1, 1, 1);
+    button = RGBA4f(1.0f, 1.0f, 1.0f);
+    buttonHighlight = RGBA4f(208.0f / 255.0f, 202.0f / 255.0f, 247.0f / 255.0f);
+
+    scopeBackground = RGBA4f(39.0f / 255.0f, 36.0f / 255.0f, 56.0f / 255.0f);
+    fftBackground = RGBA4f(39.0f / 255.0f, 36.0f / 255.0f, 56.0f / 255.0f);
+    generalBackground = RGBA4f(61.0f / 255.0f, 57.0f / 255.0f, 88.0f / 255.0f);
+
+}
+
+HDColorTheme::HDColorTheme() {
+    name = "HD";
+    waterfallGradient.addColor(GradientColor(5.0f / 255.0f, 5.0f / 255.0f, 60.0f / 255.0f));
+    waterfallGradient.addColor(GradientColor(5.0f / 255.0f, 20.0f / 255.0f, 120.0f / 255.0f));
+    waterfallGradient.addColor(GradientColor(50.0f / 255.0f, 100.0f / 255.0f, 200.0f / 255.0f));
+    waterfallGradient.addColor(GradientColor(75.0f / 255.0f, 190.0f / 255.0f, 100.0f / 255.0f));
+    waterfallGradient.addColor(GradientColor(240.0f / 255.0f, 55.0f / 255.0f, 5.0f / 255.0f));
+    waterfallGradient.addColor(GradientColor(255.0f / 255.0f, 55.0f / 255.0f, 100.0f / 255.0f));
+    waterfallGradient.addColor(GradientColor(255.0f / 255.0f, 235.0f / 255.0f, 100.0f / 255.0f));
+    waterfallGradient.addColor(GradientColor(250.0f / 255.0f, 250.0f / 255.0f, 250.0f / 255.0f));
+    waterfallGradient.generate(256);
+    waterfallHighlight = RGBA4f(1, 1, 1);
+    waterfallNew = RGBA4f(0, 1, 0);
+    waterfallHover = RGBA4f(1, 1, 0);
+    waterfallDestroy = RGBA4f(1, 0, 0);
+    fftLine = RGBA4f(0.9f, 0.9f, 0.9f);
+    fftHighlight = RGBA4f(1, 1, 1);
+    scopeLine = RGBA4f(0.9f, 0.9f, 0.9f);
+    tuningBarLight = RGBA4f(0.4f, 0.4f, 1.0);
+    tuningBarDark = RGBA4f(0.1f, 0.1f, 0.45f);
+    tuningBarUp = RGBA4f(1.0, 139.0f/255.0f, 96.0f/255.0f);
+    tuningBarDown = RGBA4f(148.0f/255.0f, 148.0f/255.0f, 1.0f);
+    meterLevel = RGBA4f(0, 0.5f, 0);
+    meterValue = RGBA4f(0.0, 0.0, 1.0);
+    text = RGBA4f(1, 1, 1);
+    freqLine = RGBA4f(1, 1, 1);
+    button = RGBA4f(0, 0.7f, 0.7f);
+    buttonHighlight = RGBA4f(1, 1, 1);
+
+    scopeBackground = RGBA4f(0.0, 0.0, 48.0f / 255.0f);
+    fftBackground = RGBA4f(0.0, 0.0, 48.0f / 255.0f);
+    generalBackground = RGBA4f(0.0, 0.0, 0.0);
+
+}
+
diff --git a/src/visual/ColorTheme.h b/src/visual/ColorTheme.h
new file mode 100644
index 0000000..6f35324
--- /dev/null
+++ b/src/visual/ColorTheme.h
@@ -0,0 +1,121 @@
+#pragma once
+
+#include "Gradient.h"
+
+#include <map>
+#include <vector>
+#include <string>
+
+#define COLOR_THEME_DEFAULT 0
+#define COLOR_THEME_BW 1
+#define COLOR_THEME_SHARP 2
+#define COLOR_THEME_RAD 3
+#define COLOR_THEME_TOUCH 4
+#define COLOR_THEME_HD 5
+#define COLOR_THEME_RADAR 6
+#define COLOR_THEME_MAX 7
+
+class RGBA4f {
+public:
+    float r, g, b, a;
+    RGBA4f(float r, float g, float b, float a = 1.0) :
+            r(r), g(g), b(b), a(a) {
+    }
+
+    RGBA4f() :
+            RGBA4f(0, 0, 0) {
+    }
+
+    ~RGBA4f() {
+    }
+
+    RGBA4f & operator=(const RGBA4f &other) {
+        r = other.r;
+        g = other.g;
+        b = other.b;
+        a = other.a;
+        return *this;
+    }
+    
+    RGBA4f operator*(float v) { return RGBA4f(r*v, g*v, b*v); }
+
+};
+
+class ColorTheme {
+public:
+    RGBA4f waterfallHighlight;
+    RGBA4f waterfallNew;
+    RGBA4f wfHighlight;
+    RGBA4f waterfallHover;
+    RGBA4f waterfallDestroy;
+    RGBA4f fftLine;
+    RGBA4f fftHighlight;
+    RGBA4f scopeLine;
+    RGBA4f tuningBarLight;
+    RGBA4f tuningBarDark;
+    RGBA4f tuningBarUp;
+    RGBA4f tuningBarDown;
+    RGBA4f meterLevel;
+    RGBA4f meterValue;
+    RGBA4f text;
+    RGBA4f freqLine;
+    RGBA4f button;
+    RGBA4f buttonHighlight;
+
+    RGBA4f scopeBackground;
+    RGBA4f fftBackground;
+    RGBA4f generalBackground;
+
+    Gradient waterfallGradient;
+
+    std::string name;
+};
+
+class ThemeMgr {
+public:
+    ThemeMgr();
+    ~ThemeMgr();
+    ColorTheme *currentTheme;
+    std::map<int, ColorTheme *> themes;
+    void setTheme(int themeId);
+    int getTheme();
+    int themeId;
+
+    static ThemeMgr mgr;
+};
+
+class DefaultColorTheme: public ColorTheme {
+public:
+    DefaultColorTheme();
+};
+
+class BlackAndWhiteColorTheme: public ColorTheme {
+public:
+    BlackAndWhiteColorTheme();
+};
+
+class SharpColorTheme: public ColorTheme {
+public:
+    SharpColorTheme();
+};
+
+class RadColorTheme: public ColorTheme {
+public:
+    RadColorTheme();
+};
+
+class TouchColorTheme: public ColorTheme {
+public:
+    TouchColorTheme();
+};
+
+class HDColorTheme: public ColorTheme {
+public:
+    HDColorTheme();
+};
+
+class RadarColorTheme: public ColorTheme {
+public:
+    RadarColorTheme();
+};
+
diff --git a/src/visual/GainCanvas.cpp b/src/visual/GainCanvas.cpp
new file mode 100644
index 0000000..6f64193
--- /dev/null
+++ b/src/visual/GainCanvas.cpp
@@ -0,0 +1,220 @@
+#include "GainCanvas.h"
+
+#include "wx/wxprec.h"
+
+#ifndef WX_PRECOMP
+#include "wx/wx.h"
+#endif
+
+#if !wxUSE_GLCANVAS
+#error "OpenGL required: set wxUSE_GLCANVAS to 1 and rebuild the library"
+#endif
+
+#include "CubicSDR.h"
+#include "CubicSDRDefs.h"
+#include "AppFrame.h"
+#include <algorithm>
+
+wxBEGIN_EVENT_TABLE(GainCanvas, wxGLCanvas) EVT_PAINT(GainCanvas::OnPaint)
+EVT_IDLE(GainCanvas::OnIdle)
+EVT_MOTION(GainCanvas::OnMouseMoved)
+EVT_LEFT_DOWN(GainCanvas::OnMouseDown)
+EVT_LEFT_UP(GainCanvas::OnMouseReleased)
+EVT_LEAVE_WINDOW(GainCanvas::OnMouseLeftWindow)
+EVT_ENTER_WINDOW(GainCanvas::OnMouseEnterWindow)
+EVT_MOUSEWHEEL(GainCanvas::OnMouseWheelMoved)
+wxEND_EVENT_TABLE()
+
+GainCanvas::GainCanvas(wxWindow *parent, int *dispAttrs) :
+        InteractiveCanvas(parent, dispAttrs) {
+
+    glContext = new PrimaryGLContext(this, &wxGetApp().GetContext(this));
+    bgPanel.setCoordinateSystem(GLPanel::GLPANEL_Y_UP);
+    bgPanel.setFill(GLPanel::GLPANEL_FILL_GRAD_X);
+
+    numGains = 1;
+    spacing = 2.0/numGains;
+    barWidth = (1.0/numGains)*0.8;
+    startPos = spacing/2.0;
+    barHeight = 0.8f;
+    refreshCounter = 0;
+}
+
+GainCanvas::~GainCanvas() {
+
+}
+
+void GainCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) {
+    wxPaintDC dc(this);
+    const wxSize ClientSize = GetClientSize();
+
+    glContext->SetCurrent(*this);
+    initGLExtensions();
+
+    glViewport(0, 0, ClientSize.x, ClientSize.y);
+
+    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+    
+    bgPanel.draw();
+    
+    SwapBuffers();
+}
+
+void GainCanvas::OnIdle(wxIdleEvent &event) {
+	if (mouseTracker.mouseInView()) {
+	    Refresh();
+	} else {
+		event.Skip();
+	}
+    
+    for (auto gi : gainPanels) {
+        if (gi->getChanged()) {
+            wxGetApp().setGain(gi->getName(), gi->getValue());
+            gi->setChanged(false);
+        }
+    }
+}
+
+void GainCanvas::SetLevel() {
+    CubicVR::vec2 mpos = mouseTracker.getGLXY();
+    
+    for (auto gi : gainPanels) {
+        if (gi->isMeterHit(mpos)) {
+            float value = gi->getMeterHitValue(mpos, *gi);
+            
+            gi->setValue(value);
+            gi->setChanged(true);
+            
+            break;
+        }
+    }
+}
+
+void GainCanvas::OnMouseMoved(wxMouseEvent& event) {
+    InteractiveCanvas::OnMouseMoved(event);
+
+    CubicVR::vec2 mpos = mouseTracker.getGLXY();
+    
+    for (auto gi : gainPanels) {
+        if (gi->isMeterHit(mpos)) {
+            float value = gi->getMeterHitValue(mpos, *gi);
+        
+            gi->setHighlight(value);
+            gi->setHighlightVisible(true);
+            wxGetApp().setActiveGainEntry(gi->getName());
+        } else {
+            gi->setHighlightVisible(false);
+        }
+    }
+    
+    if (mouseTracker.mouseDown()) {
+        SetLevel();
+    }
+}
+
+void GainCanvas::OnMouseDown(wxMouseEvent& event) {
+    InteractiveCanvas::OnMouseDown(event);
+    SetLevel();
+}
+
+void GainCanvas::OnMouseWheelMoved(wxMouseEvent& event) {
+    InteractiveCanvas::OnMouseWheelMoved(event);
+    
+    CubicVR::vec2 hitResult;
+    
+    CubicVR::vec2 mpos = mouseTracker.getGLXY();
+    
+    for (auto gi : gainPanels) {
+        if (gi->isMeterHit(mpos)) {
+            float movement = 3.0 * (float)event.GetWheelRotation();
+            gi->setValue(gi->getValue() + ((movement / 100.0) * ((gi->getHigh() - gi->getLow()) / 100.0)));
+            gi->setChanged(true);
+            break;
+        }
+    }
+}
+
+void GainCanvas::OnMouseReleased(wxMouseEvent& event) {
+    InteractiveCanvas::OnMouseReleased(event);
+}
+
+void GainCanvas::OnMouseLeftWindow(wxMouseEvent& event) {
+    InteractiveCanvas::OnMouseLeftWindow(event);
+    SetCursor(wxCURSOR_CROSS);
+   
+    for (auto gi : gainPanels) {
+        gi->setHighlightVisible(false);
+    }
+    
+    Refresh();
+}
+
+void GainCanvas::OnMouseEnterWindow(wxMouseEvent& event) {
+    InteractiveCanvas::mouseTracker.OnMouseEnterWindow(event);
+    SetCursor(wxCURSOR_CROSS);
+#ifdef _WIN32
+	if (wxGetApp().getAppFrame()->canFocus()) {
+		this->SetFocus();
+	}
+#endif
+}
+
+
+
+void GainCanvas::setHelpTip(std::string tip) {
+    helpTip = tip;
+}
+
+void GainCanvas::updateGainUI() {
+    SDRDeviceInfo *devInfo = wxGetApp().getDevice();
+    DeviceConfig *devConfig = wxGetApp().getConfig()->getDevice(devInfo->getDeviceId());
+    
+    gains = devInfo->getGains(SOAPY_SDR_RX, 0);
+    SDRRangeMap::iterator gi;
+    
+    numGains = gains.size();
+    float i = 0;
+    
+    if (!numGains) {
+        return;
+    }
+    
+    spacing = 2.0/numGains;
+    barWidth = (1.0/numGains)*0.7;
+    startPos = spacing/2.0;
+    barHeight = 1.0f;
+    
+    while (gainPanels.size()) {
+        MeterPanel *mDel = gainPanels.back();
+        gainPanels.pop_back();
+        bgPanel.removeChild(mDel);
+        delete mDel;
+    }
+    
+    for (auto gi : gains) {
+        MeterPanel *mPanel = new MeterPanel(gi.first, gi.second.minimum(), gi.second.maximum(), devConfig->getGain(gi.first,wxGetApp().getGain(gi.first)));
+
+        float midPos = -1.0+startPos+spacing*i;
+        mPanel->setPosition(midPos, 0);
+        mPanel->setSize(barWidth, barHeight);
+        bgPanel.addChild(mPanel);
+        
+        gainPanels.push_back(mPanel);
+        i++;
+    }
+    
+    setThemeColors();
+}
+
+void GainCanvas::setThemeColors() {
+    RGBA4f c1, c2;
+    
+    c1 = ThemeMgr::mgr.currentTheme->generalBackground;
+    c2 = ThemeMgr::mgr.currentTheme->generalBackground * 0.5;
+    c1.a = 1.0;
+    c2.a = 1.0;
+    bgPanel.setFillColor(c1, c2);
+
+    Refresh();
+}
+
diff --git a/src/visual/GainCanvas.h b/src/visual/GainCanvas.h
new file mode 100644
index 0000000..469dafc
--- /dev/null
+++ b/src/visual/GainCanvas.h
@@ -0,0 +1,53 @@
+#pragma once
+
+#include "wx/glcanvas.h"
+#include "wx/timer.h"
+
+#include <vector>
+#include <queue>
+
+#include "InteractiveCanvas.h"
+#include "MouseTracker.h"
+#include "GLPanel.h"
+#include "PrimaryGLContext.h"
+#include "SDRDeviceInfo.h"
+#include "Timer.h"
+#include "MeterPanel.h"
+
+
+class GainCanvas: public InteractiveCanvas {
+public:
+    GainCanvas(wxWindow *parent, int *dispAttrs);
+    ~GainCanvas();
+
+    void setHelpTip(std::string tip);
+    void updateGainUI();
+    void setThemeColors();
+    
+private:
+    void OnPaint(wxPaintEvent& event);
+    void OnIdle(wxIdleEvent &event);
+
+    void SetLevel();
+
+    void OnShow(wxShowEvent& event);
+    void OnMouseMoved(wxMouseEvent& event);
+    void OnMouseDown(wxMouseEvent& event);
+    void OnMouseWheelMoved(wxMouseEvent& event);
+    void OnMouseReleased(wxMouseEvent& event);
+    void OnMouseEnterWindow(wxMouseEvent& event);
+    void OnMouseLeftWindow(wxMouseEvent& event);
+
+    PrimaryGLContext *glContext;
+    std::string helpTip;
+    std::vector<MeterPanel *> gainPanels;
+    GLPanel bgPanel;
+    SDRRangeMap gains;
+    
+    float spacing, barWidth, startPos, barHeight, numGains;
+    int refreshCounter;
+    wxSize clientSize;
+    //
+wxDECLARE_EVENT_TABLE();
+};
+
diff --git a/src/visual/InteractiveCanvas.cpp b/src/visual/InteractiveCanvas.cpp
new file mode 100644
index 0000000..e91464a
--- /dev/null
+++ b/src/visual/InteractiveCanvas.cpp
@@ -0,0 +1,193 @@
+#include "InteractiveCanvas.h"
+
+#include "wx/wxprec.h"
+
+#ifndef WX_PRECOMP
+#include "wx/wx.h"
+#endif
+
+#if !wxUSE_GLCANVAS
+#error "OpenGL required: set wxUSE_GLCANVAS to 1 and rebuild the library"
+#endif
+
+#include "CubicSDR.h"
+#include "CubicSDRDefs.h"
+#include "AppFrame.h"
+#include <algorithm>
+
+#include <wx/numformatter.h>
+
+InteractiveCanvas::InteractiveCanvas(wxWindow *parent, int *dispAttrs) :
+        wxGLCanvas(parent, wxID_ANY, dispAttrs, wxDefaultPosition, wxDefaultSize,
+        wxFULL_REPAINT_ON_RESIZE), parent(parent), shiftDown(false), altDown(false), ctrlDown(false), centerFreq(0), bandwidth(0), lastBandwidth(0), isView(
+        false) {
+    mouseTracker.setTarget(this);
+}
+
+InteractiveCanvas::~InteractiveCanvas() {
+}
+
+void InteractiveCanvas::setView(long long center_freq_in, int bandwidth_in) {
+    isView = true;
+    centerFreq = center_freq_in;
+    bandwidth = bandwidth_in;
+    lastBandwidth = 0;
+}
+
+void InteractiveCanvas::disableView() {
+    isView = false;
+    centerFreq = wxGetApp().getFrequency();
+    bandwidth = wxGetApp().getSampleRate();
+    lastBandwidth = 0;
+}
+
+bool InteractiveCanvas::getViewState() {
+    return isView;
+}
+
+long long InteractiveCanvas::getFrequencyAt(float x) {
+    long long iqCenterFreq = getCenterFrequency();
+    long long iqBandwidth = getBandwidth();
+    long long freq = iqCenterFreq - (long long)(0.5 * (long double) iqBandwidth) + ((long double) x * (long double) iqBandwidth);
+
+    return freq;
+}
+
+long long InteractiveCanvas::getFrequencyAt(float x, long long iqCenterFreq, long long iqBandwidth) {
+    long long freq = iqCenterFreq - (long long)(0.5 * (long double) iqBandwidth) + ((long double) x * (long double) iqBandwidth);
+    
+    return freq;
+}
+
+void InteractiveCanvas::setCenterFrequency(long long center_freq_in) {
+    centerFreq = center_freq_in;
+}
+
+long long InteractiveCanvas::getCenterFrequency() {
+    if (isView) {
+        return centerFreq;
+    } else {
+        return wxGetApp().getFrequency();
+    }
+}
+
+void InteractiveCanvas::setBandwidth(unsigned int bandwidth_in) {
+    bandwidth = bandwidth_in;
+}
+
+unsigned int InteractiveCanvas::getBandwidth() {
+    if (isView) {
+        return bandwidth;
+    } else {
+        return wxGetApp().getSampleRate();
+    }
+}
+
+MouseTracker *InteractiveCanvas::getMouseTracker() {
+    return &mouseTracker;
+}
+
+bool InteractiveCanvas::isAltDown() {
+    return altDown;
+}
+
+bool InteractiveCanvas::isCtrlDown() {
+    return ctrlDown;
+}
+
+bool InteractiveCanvas::isShiftDown() {
+    return shiftDown;
+}
+
+void InteractiveCanvas::OnKeyUp(wxKeyEvent& event) {
+    shiftDown = event.ShiftDown();
+    altDown = event.AltDown();
+    ctrlDown = event.ControlDown();
+}
+
+void InteractiveCanvas::OnKeyDown(wxKeyEvent& event) {
+    shiftDown = event.ShiftDown();
+    altDown = event.AltDown();
+    ctrlDown = event.ControlDown();
+}
+
+void InteractiveCanvas::OnMouseMoved(wxMouseEvent& event) {
+    mouseTracker.OnMouseMoved(event);
+
+    shiftDown = event.ShiftDown();
+    altDown = event.AltDown();
+    ctrlDown = event.ControlDown();
+}
+
+void InteractiveCanvas::OnMouseDown(wxMouseEvent& event) {
+    mouseTracker.OnMouseDown(event);
+
+    shiftDown = event.ShiftDown();
+    altDown = event.AltDown();
+    ctrlDown = event.ControlDown();
+}
+
+void InteractiveCanvas::OnMouseWheelMoved(wxMouseEvent& event) {
+    mouseTracker.OnMouseWheelMoved(event);
+}
+
+void InteractiveCanvas::OnMouseReleased(wxMouseEvent& event) {
+    mouseTracker.OnMouseReleased(event);
+
+    shiftDown = event.ShiftDown();
+    altDown = event.AltDown();
+    ctrlDown = event.ControlDown();
+}
+
+void InteractiveCanvas::OnMouseLeftWindow(wxMouseEvent& event) {
+    mouseTracker.OnMouseLeftWindow(event);
+
+    shiftDown = false;
+    altDown = false;
+    ctrlDown = false;
+}
+
+void InteractiveCanvas::OnMouseEnterWindow(wxMouseEvent& event) {
+    mouseTracker.OnMouseEnterWindow(event);
+
+    shiftDown = event.ShiftDown();
+    altDown = event.AltDown();
+    ctrlDown = event.ControlDown();
+}
+
+void InteractiveCanvas::setStatusText(std::string statusText) {
+    wxGetApp().getAppFrame()->GetStatusBar()->SetStatusText(statusText);
+	if (wxGetApp().getConfig()->getShowTips()) {
+		if (statusText != lastToolTip) {
+			wxToolTip::Enable(false);
+			this->SetToolTip(statusText);
+			lastToolTip = statusText;
+			wxToolTip::SetDelay(1000);
+			wxToolTip::Enable(true);
+		}
+    } else {
+        this->SetToolTip("");
+        lastToolTip = "";
+    }
+}
+
+void InteractiveCanvas::setStatusText(std::string statusText, int value) {
+    wxGetApp().getAppFrame()->GetStatusBar()->SetStatusText(
+            wxString::Format(statusText.c_str(), wxNumberFormatter::ToString((long) value, wxNumberFormatter::Style_WithThousandsSep)));
+}
+
+void InteractiveCanvas::OnMouseRightDown(wxMouseEvent& event) {
+    mouseTracker.OnMouseRightDown(event);
+}
+
+void InteractiveCanvas::OnMouseRightReleased(wxMouseEvent& event) {
+    mouseTracker.OnMouseRightReleased(event);
+}
+
+bool InteractiveCanvas::isMouseInView() {
+    return mouseTracker.mouseInView();
+}
+
+bool InteractiveCanvas::isMouseDown() {
+    return mouseTracker.mouseInView() && mouseTracker.mouseDown();
+}
diff --git a/src/visual/InteractiveCanvas.h b/src/visual/InteractiveCanvas.h
new file mode 100644
index 0000000..1877e43
--- /dev/null
+++ b/src/visual/InteractiveCanvas.h
@@ -0,0 +1,65 @@
+#pragma once
+
+#include "wx/glcanvas.h"
+#include "wx/timer.h"
+
+#include "MouseTracker.h"
+#include <string>
+
+class InteractiveCanvas: public wxGLCanvas {
+public:
+    InteractiveCanvas(wxWindow *parent, int *dispAttrs);
+    ~InteractiveCanvas();
+
+    long long getFrequencyAt(float x);
+    long long getFrequencyAt(float x, long long iqCenterFreq, long long iqBandwidth);
+    
+    virtual void setView(long long center_freq_in, int bandwidth_in);
+    virtual void disableView();
+    bool getViewState();
+
+    void setCenterFrequency(long long center_freq_in);
+    long long getCenterFrequency();
+
+    void setBandwidth(unsigned int bandwidth_in);
+    unsigned int getBandwidth();
+
+    MouseTracker *getMouseTracker();
+    bool isMouseInView();
+    bool isMouseDown();
+    
+    bool isAltDown();
+    bool isCtrlDown();
+    bool isShiftDown();
+
+protected:
+    void OnKeyDown(wxKeyEvent& event);
+    void OnKeyUp(wxKeyEvent& event);
+
+    void OnMouseMoved(wxMouseEvent& event);
+    void OnMouseWheelMoved(wxMouseEvent& event);
+    void OnMouseDown(wxMouseEvent& event);
+    void OnMouseReleased(wxMouseEvent& event);
+    void OnMouseRightDown(wxMouseEvent& event);
+    void OnMouseRightReleased(wxMouseEvent& event);
+    void OnMouseEnterWindow(wxMouseEvent& event);
+    void OnMouseLeftWindow(wxMouseEvent& event);
+
+    void setStatusText(std::string statusText);
+    void setStatusText(std::string statusText, int value);
+
+    wxWindow *parent;
+    MouseTracker mouseTracker;
+
+    bool shiftDown;
+    bool altDown;
+    bool ctrlDown;
+
+    long long centerFreq;
+    unsigned int bandwidth;
+    unsigned int lastBandwidth;
+
+    bool isView;
+	std::string lastToolTip;
+};
+
diff --git a/src/visual/MeterCanvas.cpp b/src/visual/MeterCanvas.cpp
new file mode 100644
index 0000000..c1bb2ad
--- /dev/null
+++ b/src/visual/MeterCanvas.cpp
@@ -0,0 +1,188 @@
+#include "MeterCanvas.h"
+
+#include "wx/wxprec.h"
+
+#ifndef WX_PRECOMP
+#include "wx/wx.h"
+#endif
+
+#if !wxUSE_GLCANVAS
+#error "OpenGL required: set wxUSE_GLCANVAS to 1 and rebuild the library"
+#endif
+
+#include "CubicSDR.h"
+#include "CubicSDRDefs.h"
+#include "AppFrame.h"
+#include <algorithm>
+
+wxBEGIN_EVENT_TABLE(MeterCanvas, wxGLCanvas) EVT_PAINT(MeterCanvas::OnPaint)
+EVT_IDLE(MeterCanvas::OnIdle)
+EVT_MOTION(MeterCanvas::OnMouseMoved)
+EVT_LEFT_DOWN(MeterCanvas::OnMouseDown)
+EVT_LEFT_UP(MeterCanvas::OnMouseReleased)
+EVT_MOUSEWHEEL(MeterCanvas::OnMouseWheelMoved)
+EVT_RIGHT_DOWN(MeterCanvas::OnMouseRightDown)
+EVT_RIGHT_UP(MeterCanvas::OnMouseRightReleased)
+EVT_LEAVE_WINDOW(MeterCanvas::OnMouseLeftWindow)
+EVT_ENTER_WINDOW(MeterCanvas::OnMouseEnterWindow)
+wxEND_EVENT_TABLE()
+
+MeterCanvas::MeterCanvas(wxWindow *parent, int *dispAttrs) :
+        InteractiveCanvas(parent, dispAttrs), level(0), level_min(0), level_max(1), inputValue(0), userInputValue(0), showUserInput(true) {
+
+    glContext = new MeterContext(this, &wxGetApp().GetContext(this));
+}
+
+MeterCanvas::~MeterCanvas() {
+
+}
+
+void MeterCanvas::setLevel(float level_in) {
+    level = level_in;
+    Refresh();
+}
+float MeterCanvas::getLevel() {
+    return level;
+}
+
+void MeterCanvas::setMax(float max_in) {
+    level_max = max_in;
+    Refresh();
+}
+
+void MeterCanvas::setMin(float min_in) {
+    level_min = min_in;
+    Refresh();
+}
+
+void MeterCanvas::setUserInputValue(float slider_in) {
+    userInputValue = slider_in;
+    Refresh();
+}
+
+void MeterCanvas::setInputValue(float slider_in) {
+    userInputValue = inputValue = slider_in;
+    Refresh();
+}
+
+bool MeterCanvas::inputChanged() {
+    return (inputValue != userInputValue);
+}
+
+float MeterCanvas::getInputValue() {
+    inputValue = userInputValue;
+    return userInputValue;
+}
+
+void MeterCanvas::setShowUserInput(bool showUserInput) {
+    this->showUserInput = showUserInput;
+}
+
+void MeterCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) {
+    wxPaintDC dc(this);
+    const wxSize ClientSize = GetClientSize();
+
+    glContext->SetCurrent(*this);
+    initGLExtensions();
+
+    glViewport(0, 0, ClientSize.x, ClientSize.y);
+
+    glContext->DrawBegin();
+    glContext->Draw(ThemeMgr::mgr.currentTheme->generalBackground.r, ThemeMgr::mgr.currentTheme->generalBackground.g, ThemeMgr::mgr.currentTheme->generalBackground.b, 0.5, 1.0);
+    
+    if (mouseTracker.mouseInView()) {
+        glContext->Draw(0.4f, 0.4f, 0.4f, 0.5f, mouseTracker.getMouseY());
+    }
+    glContext->Draw(ThemeMgr::mgr.currentTheme->meterLevel.r, ThemeMgr::mgr.currentTheme->meterLevel.g, ThemeMgr::mgr.currentTheme->meterLevel.b, 0.5, (level-level_min) / (level_max-level_min));
+    if (showUserInput) {
+        glContext->Draw(ThemeMgr::mgr.currentTheme->meterValue.r, ThemeMgr::mgr.currentTheme->meterValue.g, ThemeMgr::mgr.currentTheme->meterValue.b, 0.5, (userInputValue-level_min) / (level_max-level_min));
+    } 
+    glContext->DrawEnd();
+
+    SwapBuffers();
+}
+
+void MeterCanvas::OnIdle(wxIdleEvent &event) {
+	if (mouseTracker.mouseInView()) {
+	    Refresh();
+	} else {
+		event.Skip();
+	}
+}
+
+void MeterCanvas::OnMouseMoved(wxMouseEvent& event) {
+    InteractiveCanvas::OnMouseMoved(event);
+
+    if (mouseTracker.mouseDown()) {
+        userInputValue = mouseTracker.getMouseY() * (level_max-level_min) + level_min;
+    } else {
+        if (!helpTip.empty()) {
+            setStatusText(helpTip);
+        }
+    }
+}
+
+void MeterCanvas::OnMouseDown(wxMouseEvent& event) {
+    InteractiveCanvas::OnMouseDown(event);
+    userInputValue = mouseTracker.getMouseY() * (level_max-level_min) + level_min;
+    mouseTracker.setHorizDragLock(true);
+}
+
+void MeterCanvas::OnMouseReleased(wxMouseEvent& event) {
+    InteractiveCanvas::OnMouseReleased(event);
+    userInputValue = mouseTracker.getMouseY() * (level_max-level_min) + level_min;
+}
+
+void MeterCanvas::OnMouseRightDown(wxMouseEvent& event) {
+    InteractiveCanvas::OnMouseRightDown(event);
+}
+
+void MeterCanvas::OnMouseRightReleased(wxMouseEvent& event) {
+    InteractiveCanvas::OnMouseRightReleased(event);
+	if (showUserInput) {
+		userInputValue = level - level * 0.02;
+	}
+}
+
+void MeterCanvas::OnMouseWheelMoved(wxMouseEvent& event) {
+    InteractiveCanvas::OnMouseWheelMoved(event);
+    float movement = 3.0 * (float)event.GetWheelRotation();
+
+	float currentValue = 0;
+	if (showUserInput) {
+		currentValue = userInputValue;
+	} else {
+		currentValue = level;
+	}
+
+    currentValue = currentValue + ((movement / 100.0) * ((level_max - level_min) / 100.0));
+  
+	if (currentValue > level_max) {
+		currentValue = level_max;
+	}
+	if (currentValue < level_min) {
+		currentValue = level_min;
+	}
+
+	userInputValue = currentValue;
+}
+
+void MeterCanvas::OnMouseLeftWindow(wxMouseEvent& event) {
+    InteractiveCanvas::OnMouseLeftWindow(event);
+    SetCursor(wxCURSOR_CROSS);
+    Refresh();
+}
+
+void MeterCanvas::OnMouseEnterWindow(wxMouseEvent& event) {
+    InteractiveCanvas::mouseTracker.OnMouseEnterWindow(event);
+    SetCursor(wxCURSOR_CROSS);
+#ifdef _WIN32
+	if (wxGetApp().getAppFrame()->canFocus()) {
+		this->SetFocus();
+	}
+#endif
+}
+
+void MeterCanvas::setHelpTip(std::string tip) {
+    helpTip = tip;
+}
diff --git a/src/visual/MeterCanvas.h b/src/visual/MeterCanvas.h
new file mode 100644
index 0000000..4b955ba
--- /dev/null
+++ b/src/visual/MeterCanvas.h
@@ -0,0 +1,61 @@
+#pragma once
+
+#include "wx/glcanvas.h"
+#include "wx/timer.h"
+
+#include <vector>
+#include <queue>
+
+#include "InteractiveCanvas.h"
+#include "MeterContext.h"
+#include "MouseTracker.h"
+
+#include "Timer.h"
+
+class MeterCanvas: public InteractiveCanvas {
+public:
+    MeterCanvas(wxWindow *parent, int *dispAttrs);
+    ~MeterCanvas();
+
+    void setLevel(float level_in);
+    float getLevel();
+
+    void setMax(float max_in);
+    void setMin(float max_in);
+
+    void setUserInputValue(float slider_in);
+    void setInputValue(float slider_in);
+    bool inputChanged();
+    float getInputValue();
+    void setShowUserInput(bool showUserInput);
+
+    void setHelpTip(std::string tip);
+
+private:
+    void OnPaint(wxPaintEvent& event);
+    void OnIdle(wxIdleEvent &event);
+
+    void OnMouseMoved(wxMouseEvent& event);
+    void OnMouseDown(wxMouseEvent& event);
+    void OnMouseReleased(wxMouseEvent& event);
+	void OnMouseWheelMoved(wxMouseEvent& event);
+	void OnMouseRightDown(wxMouseEvent& event);
+	void OnMouseRightReleased(wxMouseEvent& event);
+    void OnMouseEnterWindow(wxMouseEvent& event);
+    void OnMouseLeftWindow(wxMouseEvent& event);
+
+    MeterContext *glContext;
+
+    float level;
+    float level_min, level_max;
+
+    float inputValue;
+    float userInputValue;
+
+    bool showUserInput;
+    
+    std::string helpTip;
+    //
+wxDECLARE_EVENT_TABLE();
+};
+
diff --git a/src/visual/MeterContext.cpp b/src/visual/MeterContext.cpp
new file mode 100644
index 0000000..8387e64
--- /dev/null
+++ b/src/visual/MeterContext.cpp
@@ -0,0 +1,51 @@
+#include "MeterContext.h"
+#include "MeterCanvas.h"
+#include "ColorTheme.h"
+
+MeterContext::MeterContext(MeterCanvas *canvas, wxGLContext *sharedContext) :
+        PrimaryGLContext(canvas, sharedContext) {
+}
+
+void MeterContext::DrawBegin() {
+    glDisable(GL_CULL_FACE);
+    glDisable(GL_DEPTH_TEST);
+
+    glClearColor(ThemeMgr::mgr.currentTheme->generalBackground.r, ThemeMgr::mgr.currentTheme->generalBackground.g, ThemeMgr::mgr.currentTheme->generalBackground.b, 1.0);
+    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+    glMatrixMode(GL_MODELVIEW);
+    glLoadIdentity();
+
+    glDisable(GL_TEXTURE_2D);
+}
+
+void MeterContext::Draw(float r, float g, float b, float a, float level) {
+    glEnable(GL_BLEND);
+    glBlendFunc(GL_ONE, GL_ONE);
+    glBegin(GL_QUADS);
+    glColor4f(r*0.65,g*0.65,b*0.65,a);
+    glVertex2f(-1.0, -1.0 + 2.0 * level);
+    glVertex2f(-1.0, -1.0);
+
+    glColor4f(r,g,b,a);
+    glVertex2f(0.0, -1.0);
+    glVertex2f(0.0, -1.0 + 2.0 * level);
+
+    glColor4f(r,g,b,a);
+    glVertex2f(0.0, -1.0 + 2.0 * level);
+    glVertex2f(0.0, -1.0);
+
+    glColor4f(r*0.65,g*0.65,b*0.65,a*0.65);
+    glVertex2f(1.0, -1.0);
+    glVertex2f(1.0, -1.0 + 2.0 * level);
+
+    glEnd();
+    glDisable(GL_BLEND);
+}
+
+void MeterContext::DrawEnd() {
+//    glFlush();
+
+//    CheckGLError();
+}
+
diff --git a/src/visual/MeterContext.h b/src/visual/MeterContext.h
new file mode 100644
index 0000000..3fac2f2
--- /dev/null
+++ b/src/visual/MeterContext.h
@@ -0,0 +1,19 @@
+#pragma once
+
+#include "PrimaryGLContext.h"
+#include "Gradient.h"
+
+#define NUM_WATERFALL_LINES 512
+
+class MeterCanvas;
+
+class MeterContext: public PrimaryGLContext {
+public:
+    MeterContext(MeterCanvas *canvas, wxGLContext *sharedContext);
+
+    void DrawBegin();
+    void Draw(float r, float g, float b, float a, float level);
+    void DrawEnd();
+
+private:
+};
diff --git a/src/visual/ModeSelectorCanvas.cpp b/src/visual/ModeSelectorCanvas.cpp
new file mode 100644
index 0000000..dcfdaa4
--- /dev/null
+++ b/src/visual/ModeSelectorCanvas.cpp
@@ -0,0 +1,221 @@
+#include "ModeSelectorCanvas.h"
+
+#include "wx/wxprec.h"
+
+#ifndef WX_PRECOMP
+#include "wx/wx.h"
+#endif
+
+#if !wxUSE_GLCANVAS
+#error "OpenGL required: set wxUSE_GLCANVAS to 1 and rebuild the library"
+#endif
+
+#include "CubicSDR.h"
+#include "CubicSDRDefs.h"
+#include "AppFrame.h"
+#include <algorithm>
+
+wxBEGIN_EVENT_TABLE(ModeSelectorCanvas, wxGLCanvas) EVT_PAINT(ModeSelectorCanvas::OnPaint)
+EVT_IDLE(ModeSelectorCanvas::OnIdle)
+EVT_MOTION(ModeSelectorCanvas::OnMouseMoved)
+EVT_LEFT_DOWN(ModeSelectorCanvas::OnMouseDown)
+EVT_LEFT_UP(ModeSelectorCanvas::OnMouseReleased)
+EVT_LEAVE_WINDOW(ModeSelectorCanvas::OnMouseLeftWindow)
+EVT_ENTER_WINDOW(ModeSelectorCanvas::OnMouseEnterWindow)
+wxEND_EVENT_TABLE()
+
+ModeSelectorCanvas::ModeSelectorCanvas(wxWindow *parent, int *dispAttrs) :
+InteractiveCanvas(parent, dispAttrs), numChoices(0), currentSelection(-1), toggleMode(false), inputChanged(false), padX(4.0), padY(4.0), highlightOverride(false) {
+
+    glContext = new ModeSelectorContext(this, &wxGetApp().GetContext(this));
+    
+    highlightColor = RGBA4f(1.0,1.0,1.0,1.0);
+}
+
+ModeSelectorCanvas::~ModeSelectorCanvas() {
+
+}
+
+int ModeSelectorCanvas::getHoveredSelection() {
+    if (!mouseTracker.mouseInView()) {
+        return -1;
+    }
+
+    float ypos = 1.0 - (mouseTracker.getMouseY() * 2.0);
+    float yval = (int) (((ypos + 1.0) / 2.0) * (float) numChoices);
+
+    return yval;
+}
+
+void ModeSelectorCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) {
+    wxPaintDC dc(this);
+    const wxSize ClientSize = GetClientSize();
+
+    glContext->SetCurrent(*this);
+    initGLExtensions();
+
+    glViewport(0, 0, ClientSize.x, ClientSize.y);
+
+    glContext->DrawBegin();
+
+    int yval = getHoveredSelection();
+
+    for (int i = 0; i < numChoices; i++) {
+        if (yval == i && !highlightOverride) {
+            RGBA4f hc = ThemeMgr::mgr.currentTheme->buttonHighlight;
+            glContext->DrawSelector(selections[i].label, i, numChoices, true, hc.r, hc.g, hc.b, 1.0, padX, padY);
+        } else {
+            RGBA4f hc = ThemeMgr::mgr.currentTheme->button;
+            if (highlightOverride) {
+                hc = highlightColor;
+            }
+            glContext->DrawSelector(selections[i].label, i, numChoices, i == currentSelection, hc.r, hc.g, hc.b, 1.0, padX, padY);
+        }
+    }
+
+    glContext->DrawEnd();
+
+    SwapBuffers();
+}
+
+void ModeSelectorCanvas::OnIdle(wxIdleEvent &event) {
+	if (mouseTracker.mouseInView()) {
+		Refresh();
+	} else {
+		event.Skip();
+	}
+}
+
+void ModeSelectorCanvas::OnMouseMoved(wxMouseEvent& event) {
+    InteractiveCanvas::OnMouseMoved(event);
+}
+
+void ModeSelectorCanvas::OnMouseDown(wxMouseEvent& event) {
+    InteractiveCanvas::OnMouseDown(event);
+    mouseTracker.setHorizDragLock(true);
+    mouseTracker.setVertDragLock(true);
+}
+
+void ModeSelectorCanvas::OnMouseWheelMoved(wxMouseEvent& event) {
+    InteractiveCanvas::OnMouseWheelMoved(event);
+}
+
+void ModeSelectorCanvas::OnMouseReleased(wxMouseEvent& event) {
+    InteractiveCanvas::OnMouseReleased(event);
+    mouseTracker.setHorizDragLock(false);
+    mouseTracker.setVertDragLock(false);
+
+    const wxSize ClientSize = GetClientSize();
+
+    int selectedButton = currentSelection;
+    if (mouseTracker.getOriginDeltaMouseX() < 2.0 / ClientSize.y) {
+        selectedButton = getHoveredSelection();
+    }
+
+    if (toggleMode && (currentSelection == selectedButton)) {
+        selectedButton = -1;
+    }
+    
+    if (currentSelection != selectedButton) {
+        inputChanged = true;
+    }
+    
+    currentSelection = selectedButton;
+    
+    SetCursor (wxCURSOR_HAND);
+    Refresh();
+}
+
+void ModeSelectorCanvas::OnMouseLeftWindow(wxMouseEvent& event) {
+    InteractiveCanvas::OnMouseLeftWindow(event);
+    SetCursor (wxCURSOR_CROSS);
+    Refresh();
+}
+
+void ModeSelectorCanvas::OnMouseEnterWindow(wxMouseEvent& event) {
+    InteractiveCanvas::mouseTracker.OnMouseEnterWindow(event);
+    SetCursor (wxCURSOR_HAND);
+    if (!helpTip.empty()) {
+        setStatusText(helpTip);
+    }
+    Refresh();
+}
+
+void ModeSelectorCanvas::setHelpTip(std::string tip) {
+    helpTip = tip;
+}
+
+void ModeSelectorCanvas::setNumChoices(int numChoices_in) {
+    numChoices = numChoices_in;
+    Refresh();
+}
+
+void ModeSelectorCanvas::addChoice(int value, std::string label) {
+    selections.push_back(ModeSelectorMode(value, label));
+    numChoices = selections.size();
+}
+
+void ModeSelectorCanvas::addChoice(std::string label) {
+    selections.push_back(ModeSelectorMode(selections.size()+1, label));
+    numChoices = selections.size();
+}
+
+void ModeSelectorCanvas::setSelection(std::string label) {
+    for (int i = 0; i < numChoices; i++) {
+        if (selections[i].label == label) {
+            currentSelection = i;
+            Refresh();
+            return;
+        }
+    }
+    currentSelection = -1;
+    Refresh();
+}
+
+std::string ModeSelectorCanvas::getSelectionLabel() {
+    if (currentSelection == -1) {
+        return "";
+    }
+    return selections[currentSelection].label;
+}
+
+void ModeSelectorCanvas::setSelection(int value) {
+    for (int i = 0; i < numChoices; i++) {
+        if (selections[i].value == value) {
+            currentSelection = i;
+            Refresh();
+            return;
+        }
+    }
+    currentSelection = -1;
+    Refresh();
+}
+
+int ModeSelectorCanvas::getSelection() {
+    if (currentSelection == -1) {
+        return -1;
+    }
+    return selections[currentSelection].value;
+}
+
+void ModeSelectorCanvas::setToggleMode(bool toggleMode) {
+    this->toggleMode = toggleMode;
+}
+
+bool ModeSelectorCanvas::modeChanged() {
+    return inputChanged;
+}
+
+void ModeSelectorCanvas::clearModeChanged() {
+    inputChanged = false;
+}
+
+void ModeSelectorCanvas::setPadding(float padX, float padY) {
+    this->padX = padX;
+    this->padY = padY;
+}
+
+void ModeSelectorCanvas::setHighlightColor(RGBA4f hc) {
+    this->highlightColor = hc;
+    this->highlightOverride = true;
+}
diff --git a/src/visual/ModeSelectorCanvas.h b/src/visual/ModeSelectorCanvas.h
new file mode 100644
index 0000000..004dd4c
--- /dev/null
+++ b/src/visual/ModeSelectorCanvas.h
@@ -0,0 +1,75 @@
+#pragma once
+
+#include "wx/glcanvas.h"
+#include "wx/timer.h"
+
+#include <vector>
+#include <queue>
+
+#include "InteractiveCanvas.h"
+#include "ModeSelectorContext.h"
+#include "MouseTracker.h"
+
+#include "Timer.h"
+
+class ModeSelectorMode {
+public:
+    int value;
+    std::string label;
+
+    ModeSelectorMode(int value, std::string label) : value(value), label(label) {
+
+    }
+};
+
+class ModeSelectorCanvas: public InteractiveCanvas {
+public:
+    ModeSelectorCanvas(wxWindow *parent, int *dispAttrs);
+    ~ModeSelectorCanvas();
+
+    int getHoveredSelection();
+    void setHelpTip(std::string tip);
+
+    void addChoice(int value, std::string label);
+    void addChoice(std::string label);
+    void setSelection(std::string label);
+    std::string getSelectionLabel();
+    void setSelection(int value);
+    int getSelection();
+
+    void setToggleMode(bool toggleMode);
+
+    bool modeChanged();
+    void clearModeChanged();
+    
+    void setPadding(float padX, float padY);
+    void setHighlightColor(RGBA4f hc);
+    
+private:
+    void setNumChoices(int numChoices_in);
+
+    void OnPaint(wxPaintEvent& event);
+    void OnIdle(wxIdleEvent &event);
+
+    void OnMouseMoved(wxMouseEvent& event);
+    void OnMouseDown(wxMouseEvent& event);
+    void OnMouseWheelMoved(wxMouseEvent& event);
+    void OnMouseReleased(wxMouseEvent& event);
+    void OnMouseEnterWindow(wxMouseEvent& event);
+    void OnMouseLeftWindow(wxMouseEvent& event);
+
+    ModeSelectorContext *glContext;
+
+    std::string helpTip;
+    int numChoices;
+    int currentSelection;
+    bool toggleMode;
+    bool inputChanged;
+    std::vector<ModeSelectorMode> selections;
+    float padX, padY;
+    RGBA4f highlightColor;
+    bool highlightOverride;
+    //
+wxDECLARE_EVENT_TABLE();
+};
+
diff --git a/src/visual/ModeSelectorContext.cpp b/src/visual/ModeSelectorContext.cpp
new file mode 100644
index 0000000..44c793c
--- /dev/null
+++ b/src/visual/ModeSelectorContext.cpp
@@ -0,0 +1,71 @@
+#include "ModeSelectorContext.h"
+#include "ModeSelectorCanvas.h"
+#include "ColorTheme.h"
+
+
+ModeSelectorContext::ModeSelectorContext(ModeSelectorCanvas *canvas, wxGLContext *sharedContext) :
+        PrimaryGLContext(canvas, sharedContext) {
+    glDisable(GL_CULL_FACE);
+    glDisable(GL_DEPTH_TEST);
+
+    glMatrixMode(GL_PROJECTION);
+    glLoadIdentity();
+}
+
+void ModeSelectorContext::DrawBegin() {
+    glClearColor(ThemeMgr::mgr.currentTheme->generalBackground.r, ThemeMgr::mgr.currentTheme->generalBackground.g, ThemeMgr::mgr.currentTheme->generalBackground.b,1.0);
+    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+    glMatrixMode(GL_MODELVIEW);
+    glLoadIdentity();
+
+    glDisable(GL_TEXTURE_2D);
+}
+
+void ModeSelectorContext::DrawSelector(std::string label, int c, int cMax, bool on, float r, float g, float b, float a, float px, float py) {
+    GLint vp[4];
+    glGetIntegerv( GL_VIEWPORT, vp);
+
+    float viewHeight = (float) vp[3];
+    float viewWidth = (float) vp[2];
+
+    int fontSize = 18;
+
+    if (viewWidth < 30 || viewHeight < 200) {
+        fontSize = 16;
+    }
+
+    glColor4f(r, g, b, a);
+
+    float y = 1.0 - ((float) (c+1) / (float) cMax * 2.0);
+    float height = (2.0 / (float) cMax);
+    float padX = (px / viewWidth);
+    float padY = (py / viewHeight);
+
+    if (a < 1.0) {
+        glEnable(GL_BLEND);
+    }
+    glBegin(on?GL_QUADS:GL_LINE_LOOP);
+    glVertex2f(-1.0 + padX, y + padY);
+    glVertex2f(1.0 - padX, y + padY);
+    glVertex2f(1.0 - padX, y + height - padY);
+    glVertex2f(-1.0 + padX, y + height - padY);
+    glEnd();
+    if (a < 1.0) {
+        glDisable(GL_BLEND);
+    }
+
+    if (on) {
+        glColor4f(0, 0, 0, a);
+    }
+
+    //Do not zoom the selectors
+    GLFont::getFont(fontSize).drawString(label, 0.0, y + height / 2.0, GLFont::GLFONT_ALIGN_CENTER, GLFont::GLFONT_ALIGN_CENTER);
+}
+
+void ModeSelectorContext::DrawEnd() {
+//    glFlush();
+
+//    CheckGLError();
+}
+
diff --git a/src/visual/ModeSelectorContext.h b/src/visual/ModeSelectorContext.h
new file mode 100644
index 0000000..2d84fa3
--- /dev/null
+++ b/src/visual/ModeSelectorContext.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include "PrimaryGLContext.h"
+#include "Gradient.h"
+
+#define NUM_WATERFALL_LINES 512
+
+class ModeSelectorCanvas;
+
+class ModeSelectorContext: public PrimaryGLContext {
+public:
+    ModeSelectorContext(ModeSelectorCanvas *canvas, wxGLContext *sharedContext);
+
+    void DrawBegin();
+    void DrawSelector(std::string label, int c, int cMax, bool on, float r, float g, float b, float a, float padx, float pady);
+    void DrawEnd();
+};
diff --git a/src/visual/PrimaryGLContext.cpp b/src/visual/PrimaryGLContext.cpp
new file mode 100644
index 0000000..64c8963
--- /dev/null
+++ b/src/visual/PrimaryGLContext.cpp
@@ -0,0 +1,524 @@
+#include "PrimaryGLContext.h"
+
+#include "wx/wxprec.h"
+
+#ifndef WX_PRECOMP
+#include "wx/wx.h"
+#endif
+
+#if !wxUSE_GLCANVAS
+#error "OpenGL required: set wxUSE_GLCANVAS to 1 and rebuild the library"
+#endif
+
+#include "CubicSDR.h"
+#include "CubicSDRDefs.h"
+#include "AppFrame.h"
+#include <algorithm>
+
+wxString PrimaryGLContext::glGetwxString(GLenum name) {
+    const GLubyte *v = glGetString(name);
+    if (v == 0) {
+        // The error is not important. It is GL_INVALID_ENUM.
+        // We just want to clear the error stack.
+        glGetError();
+
+        return wxString();
+    }
+
+    return wxString((const char*) v);
+}
+
+void PrimaryGLContext::CheckGLError() {
+    GLenum errLast = GL_NO_ERROR;
+
+    for (;;) {
+        GLenum err = glGetError();
+        if (err == GL_NO_ERROR)
+            return;
+
+        if (err == errLast) {
+            std::cout << "OpenGL error state couldn't be reset." << std::endl;
+            return;
+        }
+
+        errLast = err;
+
+        std::cout << "OpenGL Error " << err << std::endl;
+    }
+}
+
+PrimaryGLContext::PrimaryGLContext(wxGLCanvas *canvas, wxGLContext *sharedContext) :
+        wxGLContext(canvas, sharedContext), hoverAlpha(1.0) {
+//#ifndef __linux__
+//    SetCurrent(*canvas);
+//    // Pre-load fonts
+//    for (int i = 0; i < GLFONT_MAX; i++) {
+//        getFont((GLFontSize) i);
+//    }
+//    CheckGLError();
+//#endif
+}
+
+void PrimaryGLContext::DrawDemodInfo(DemodulatorInstance *demod, RGBA4f color, long long center_freq, long long srate, bool centerline) {
+    if (!demod) {
+        return;
+    }
+    if (!srate) {
+        srate = wxGetApp().getSampleRate();
+    }
+
+    GLint vp[4];
+    glGetIntegerv( GL_VIEWPORT, vp);
+
+    float viewHeight = (float) vp[3];
+    float viewWidth = (float) vp[2];
+
+    if (center_freq == -1) {
+        center_freq = wxGetApp().getFrequency();
+    }
+    
+    long long demodFreq = demod->getFrequency();
+
+    if (demod->isDeltaLock()) {
+        demodFreq = wxGetApp().getFrequency() + demod->getDeltaLockOfs();
+    }
+    
+    float uxPos = (float) (demodFreq - (center_freq - srate / 2)) / (float) srate;
+    uxPos = (uxPos - 0.5) * 2.0;
+
+    glDisable(GL_TEXTURE_2D);
+
+    glEnable(GL_BLEND);
+    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+    glColor4f(color.r, color.g, color.b, 0.6f);
+
+    float ofs = ((float) demod->getBandwidth()) / (float) srate;
+    float ofsLeft = (demod->getDemodulatorType()!="USB")?ofs:0, ofsRight = (demod->getDemodulatorType()!="LSB")?ofs:0;
+
+    float labelHeight = 20.0 / viewHeight;
+    float hPos = -1.0 + labelHeight;
+
+    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+    bool soloMode = wxGetApp().getSoloMode();
+    bool isSolo = soloMode && demod == wxGetApp().getDemodMgr().getLastActiveDemodulator();
+    
+    if (isSolo) {
+        glColor4f(0.8f, 0.8f, 0, 0.35f);
+    } else if (demod->isMuted()) {
+        glColor4f(0.8f, 0, 0, 0.35f);
+    } else if (soloMode) {
+        glColor4f(0.2f, 0, 0, 0.35f);
+    } else {
+        glColor4f(0, 0, 0, 0.35f);
+    }
+    
+    glBegin(GL_QUADS);
+    glVertex3f(uxPos - ofsLeft, hPos + labelHeight, 0.0);
+    glVertex3f(uxPos - ofsLeft, -1.0, 0.0);
+
+    glVertex3f(uxPos + ofsRight, -1.0, 0.0);
+    glVertex3f(uxPos + ofsRight, hPos + labelHeight, 0.0);
+    glEnd();
+
+    glBlendFunc(GL_SRC_ALPHA, GL_ONE);
+
+    glColor4f(color.r, color.g, color.b, 0.2f);
+    glBegin(GL_QUADS);
+    glVertex3f(uxPos - ofsLeft, 1.0, 0.0);
+    glVertex3f(uxPos - ofsLeft, -1.0, 0.0);
+
+    glVertex3f(uxPos + ofsRight, -1.0, 0.0);
+    glVertex3f(uxPos + ofsRight, 1.0, 0.0);
+    glEnd();
+
+    if (ofs * 2.0 < 16.0 / viewWidth) {
+        glColor4f(color.r, color.g, color.b, 0.2f);
+        glBegin(GL_QUADS);
+        glVertex3f(uxPos - ofsLeft, hPos + labelHeight, 0.0);
+        glVertex3f(uxPos - ofsLeft, -1.0, 0.0);
+
+        glVertex3f(uxPos + ofsRight, -1.0, 0.0);
+        glVertex3f(uxPos + ofsRight, hPos + labelHeight, 0.0);
+        glEnd();
+    }
+
+    if (centerline) {
+        glColor4f(color.r, color.g, color.b, 0.5);
+        glBegin(GL_LINES);
+        glVertex3f(uxPos, 1.0, 0.0);
+        glVertex3f(uxPos, -1.0, 0.0);
+        glEnd();
+    }
+
+    glColor4f(1.0, 1.0, 1.0, 0.8f);
+
+    std::string demodLabel = demod->getLabel();
+    
+    if (demod->isMuted()) {
+        demodLabel = std::string("[M] ") + demodLabel;
+    } else if (isSolo) {
+        demodLabel = std::string("[S] ") + demodLabel;
+    }
+    
+    if (demod->isDeltaLock()) {
+        demodLabel.append(" [V]");
+    }
+    
+    if (demod->getDemodulatorType() == "USB") {
+        GLFont::getFont(16, GLFont::getScaleFactor()).drawString(demodLabel, uxPos, hPos, GLFont::GLFONT_ALIGN_LEFT, GLFont::GLFONT_ALIGN_CENTER, 0, 0, true);
+    } else if (demod->getDemodulatorType() == "LSB") {
+        GLFont::getFont(16, GLFont::getScaleFactor()).drawString(demodLabel, uxPos, hPos, GLFont::GLFONT_ALIGN_RIGHT, GLFont::GLFONT_ALIGN_CENTER, 0, 0, true);
+    } else {
+        GLFont::getFont(16, GLFont::getScaleFactor()).drawString(demodLabel, uxPos, hPos, GLFont::GLFONT_ALIGN_CENTER, GLFont::GLFONT_ALIGN_CENTER, 0, 0, true);
+    }
+
+    glDisable(GL_BLEND);
+
+}
+
+void PrimaryGLContext::DrawFreqBwInfo(long long freq, int bw, RGBA4f color, long long center_freq, long long srate, bool stack, bool centerline) {
+    if (!srate) {
+        srate = wxGetApp().getSampleRate();
+    }
+    
+    GLint vp[4];
+    glGetIntegerv( GL_VIEWPORT, vp);
+    
+    float viewHeight = (float) vp[3];
+    float viewWidth = (float) vp[2];
+    
+    if (center_freq == -1) {
+        center_freq = wxGetApp().getFrequency();
+    }
+    
+    float uxPos = (float) (freq - (center_freq - srate / 2)) / (float) srate;
+    uxPos = (uxPos - 0.5) * 2.0;
+
+    std::string lastType = wxGetApp().getDemodMgr().getLastDemodulatorType();
+
+    float ofs = (float) bw / (float) srate;
+    float ofsLeft = (lastType!="USB")?ofs:0, ofsRight = (lastType!="LSB")?ofs:0;
+
+    float labelHeight = 20.0 / viewHeight;
+    float hPos = -1.0 + (stack?(labelHeight*3.0):labelHeight);
+
+    glDisable(GL_TEXTURE_2D);
+    glEnable(GL_BLEND);
+    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+    glColor4f(0, 0, 0, 0.35f);
+    
+    glBegin(GL_QUADS);
+    glVertex3f(uxPos - ofsLeft, hPos + labelHeight, 0.0);
+    glVertex3f(uxPos - ofsLeft, -1.0, 0.0);
+    
+    glVertex3f(uxPos + ofsRight, -1.0, 0.0);
+    glVertex3f(uxPos + ofsRight, hPos + labelHeight, 0.0);
+    glEnd();
+
+    glBlendFunc(GL_SRC_ALPHA, GL_ONE);
+    
+    glColor4f(color.r, color.g, color.b, 0.1f);
+    glBegin(GL_QUADS);
+    glVertex3f(uxPos - ofsLeft, 1.0, 0.0);
+    glVertex3f(uxPos - ofsLeft, -1.0, 0.0);
+    
+    glVertex3f(uxPos + ofsRight, -1.0, 0.0);
+    glVertex3f(uxPos + ofsRight, 1.0, 0.0);
+    glEnd();
+    
+    if (ofs * 2.0 < 16.0 / viewWidth) {
+        glColor4f(color.r, color.g, color.b, 0.1f);
+        glBegin(GL_QUADS);
+        glVertex3f(uxPos - ofsLeft, hPos + labelHeight, 0.0);
+        glVertex3f(uxPos - ofsLeft, -1.0, 0.0);
+        
+        glVertex3f(uxPos + ofsRight, -1.0, 0.0);
+        glVertex3f(uxPos + ofsRight, hPos + labelHeight, 0.0);
+        glEnd();
+    }
+
+    if (centerline) {
+        glColor4f(color.r, color.g, color.b, 0.5);
+        glBegin(GL_LINES);
+        glVertex3f(uxPos, 1.0, 0.0);
+        glVertex3f(uxPos, -1.0, 0.0);
+        glEnd();
+    }
+    
+    glColor4f(1.0, 1.0, 1.0, 0.8f);
+    
+    std::string demodLabel = std::to_string((double)freq/1000000.0);
+    
+    double shadowOfsX = 4.0 / viewWidth, shadowOfsY = 2.0 / viewHeight;
+
+    GLFont::Drawer refDrawingFont = GLFont::getFont(16, GLFont::getScaleFactor());
+    
+    if (lastType == "USB") {
+        glColor4f(0,0,0, 1.0);
+        glBlendFunc(GL_ONE, GL_ZERO);
+        refDrawingFont.drawString(demodLabel, uxPos+shadowOfsX, hPos+shadowOfsY, GLFont::GLFONT_ALIGN_LEFT, GLFont::GLFONT_ALIGN_CENTER);
+        refDrawingFont.drawString(demodLabel, uxPos-shadowOfsX, hPos-shadowOfsY, GLFont::GLFONT_ALIGN_LEFT, GLFont::GLFONT_ALIGN_CENTER);
+        glColor4f(color.r, color.g, color.b, 1.0);
+        glBlendFunc(GL_SRC_ALPHA, GL_ONE);
+        refDrawingFont.drawString(demodLabel, uxPos, hPos, GLFont::GLFONT_ALIGN_LEFT, GLFont::GLFONT_ALIGN_CENTER);
+    } else if (lastType == "LSB") {
+        glBlendFunc(GL_ONE, GL_ZERO);
+        glColor4f(0,0,0, 1.0);
+        refDrawingFont.drawString(demodLabel, uxPos+shadowOfsX, hPos+shadowOfsY, GLFont::GLFONT_ALIGN_RIGHT, GLFont::GLFONT_ALIGN_CENTER);
+        refDrawingFont.drawString(demodLabel, uxPos-shadowOfsX, hPos-shadowOfsY, GLFont::GLFONT_ALIGN_RIGHT, GLFont::GLFONT_ALIGN_CENTER);
+        glColor4f(color.r, color.g, color.b, 1.0);
+        glBlendFunc(GL_SRC_ALPHA, GL_ONE);
+        refDrawingFont.drawString(demodLabel, uxPos, hPos, GLFont::GLFONT_ALIGN_RIGHT, GLFont::GLFONT_ALIGN_CENTER);
+    } else {
+        glBlendFunc(GL_ONE, GL_ZERO);
+        glColor4f(0,0,0, 1.0);
+        glBlendFunc(GL_SRC_ALPHA, GL_ONE);
+        refDrawingFont.drawString(demodLabel, uxPos+shadowOfsX, hPos+shadowOfsY, GLFont::GLFONT_ALIGN_CENTER, GLFont::GLFONT_ALIGN_CENTER);
+        refDrawingFont.drawString(demodLabel, uxPos-shadowOfsX, hPos-shadowOfsY, GLFont::GLFONT_ALIGN_CENTER, GLFont::GLFONT_ALIGN_CENTER);
+        glColor4f(color.r, color.g, color.b, 1.0);
+        refDrawingFont.drawString(demodLabel, uxPos, hPos, GLFont::GLFONT_ALIGN_CENTER, GLFont::GLFONT_ALIGN_CENTER);
+    }
+    
+    glDisable(GL_BLEND);
+}
+
+void PrimaryGLContext::DrawDemod(DemodulatorInstance *demod, RGBA4f color, long long center_freq, long long srate) {
+    if (!demod) {
+        return;
+    }
+    if (!srate) {
+        srate = wxGetApp().getSampleRate();
+    }
+
+    if (center_freq == -1) {
+        center_freq = wxGetApp().getFrequency();
+    }
+
+    long long demodFreq = demod->getFrequency();
+
+    if (demod->isDeltaLock()) {
+        demodFreq = wxGetApp().getFrequency() + demod->getDeltaLockOfs();
+    }
+    
+    float uxPos = (float) (demodFreq - (center_freq - srate / 2)) / (float) srate;
+
+    glDisable(GL_TEXTURE_2D);
+
+    glEnable(GL_BLEND);
+    glBlendFunc(GL_SRC_ALPHA, GL_ONE);
+    glColor4f(color.r, color.g, color.b, 0.6f);
+
+    float ofs = ((float) demod->getBandwidth()) / (float) srate;
+    float ofsLeft = (demod->getDemodulatorType()!="USB")?ofs:0, ofsRight = (demod->getDemodulatorType()!="LSB")?ofs:0;
+
+    glBegin(GL_LINES);
+    glVertex3f((uxPos - 0.5) * 2.0, 1.0, 0.0);
+    glVertex3f((uxPos - 0.5) * 2.0, -1.0, 0.0);
+
+    glVertex3f((uxPos - 0.5) * 2.0 - ofsLeft, 1.0, 0.0);
+    glVertex3f((uxPos - 0.5) * 2.0 - ofsLeft, -1.0, 0.0);
+
+    glVertex3f((uxPos - 0.5) * 2.0 + ofsRight, 1.0, 0.0);
+    glVertex3f((uxPos - 0.5) * 2.0 + ofsRight, -1.0, 0.0);
+
+    glEnd();
+
+    glBlendFunc(GL_SRC_ALPHA, GL_ONE);
+    glColor4f(color.r, color.g, color.b, 0.2*hoverAlpha);
+    glBegin(GL_QUADS);
+    glVertex3f((uxPos - 0.5) * 2.0 - ofsLeft, 1.0, 0.0);
+    glVertex3f((uxPos - 0.5) * 2.0 - ofsLeft, -1.0, 0.0);
+
+    glVertex3f((uxPos - 0.5) * 2.0 + ofsRight, -1.0, 0.0);
+    glVertex3f((uxPos - 0.5) * 2.0 + ofsRight, 1.0, 0.0);
+    glEnd();
+
+    GLint vp[4];
+    glGetIntegerv( GL_VIEWPORT, vp);
+
+    float viewHeight = (float) vp[3];
+    float viewWidth = (float) vp[2];
+
+    float labelHeight = 20.0 / viewHeight;
+    float xOfs = (2.0 / viewWidth);
+    float yOfs = (2.0 / viewHeight);
+    float hPos = labelHeight;
+
+    glDisable(GL_BLEND);
+
+    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+    glDisable(GL_COLOR_MATERIAL);
+
+    glEnable(GL_BLEND);
+
+    GLFont::Align demodAlign = GLFont::GLFONT_ALIGN_CENTER;
+
+    //Displayed string is wstring, so use wxString to do the heavy lifting of converting  getDemodulatorType()...
+#ifdef WIN32
+    //try to reuse the memory with thread_local, unsupported on OSX ? 
+    static thread_local wxString demodStr;
+#else
+    wxString demodStr;
+#endif
+    demodStr.assign(demod->getDemodulatorType());
+
+    demodAlign = GLFont::GLFONT_ALIGN_CENTER;
+
+    if (demodStr == "LSB") {
+        demodAlign = GLFont::GLFONT_ALIGN_RIGHT;
+        uxPos -= xOfs;
+    } else if (demodStr == "USB") {
+        demodAlign = GLFont::GLFONT_ALIGN_LEFT;
+        uxPos += xOfs;
+    }
+    // advanced demodulators start here
+
+//	if (demod->getDemodulatorCons() > 0) {
+//		demodStr = demodStr + std::to_string(demod->getDemodulatorCons());
+//	}
+    
+    // add lock to string if we have an lock
+    if(demod->getDemodulatorLock()) {
+        demodStr += " Lock";
+    }
+
+    // else {
+    //     demodStr = demodStr + " UnLock";
+    // }
+
+    //demodulator user label if present: type is displayed above the label, which is at the bottom of the screen.
+    if (!demod->getDemodulatorUserLabel().empty()) {
+        hPos += 1.3 * labelHeight;
+    }
+
+    drawSingleDemodLabel(demodStr.ToStdWstring(), uxPos, hPos, xOfs, yOfs, GLFont::GLFONT_ALIGN_CENTER);
+
+    //revert...
+    if (!demod->getDemodulatorUserLabel().empty()) {
+       hPos -= 1.3 * labelHeight;
+       drawSingleDemodLabel(demod->getDemodulatorUserLabel(), uxPos, hPos, xOfs, yOfs, GLFont::GLFONT_ALIGN_CENTER);
+    }
+
+    glDisable(GL_BLEND);
+
+}
+
+void PrimaryGLContext::drawSingleDemodLabel(const std::wstring& demodStr, float uxPos, float hPos, float xOfs, float yOfs, GLFont::Align demodAlign) {
+
+    GLFont::Drawer refDrawingFont = GLFont::getFont(16, GLFont::getScaleFactor());
+
+    glColor3f(0, 0, 0);
+    refDrawingFont.drawString(demodStr, 2.0 * (uxPos - 0.5) + xOfs,
+        -1.0 + hPos - yOfs, demodAlign,
+        GLFont::GLFONT_ALIGN_CENTER, 0, 0, true);
+    
+    glColor3f(1, 1, 1);
+    refDrawingFont.drawString(demodStr, 2.0 * (uxPos - 0.5),
+        -1.0 + hPos, demodAlign,
+        GLFont::GLFONT_ALIGN_CENTER, 0, 0, true);
+}
+
+void PrimaryGLContext::DrawFreqSelector(float uxPos, RGBA4f color, float w, long long /* center_freq */, long long srate) {
+    DemodulatorInstance *demod = wxGetApp().getDemodMgr().getLastActiveDemodulator();
+
+    long long bw = 0;
+
+    std::string last_type = wxGetApp().getDemodMgr().getLastDemodulatorType();
+
+    if (!demod) {
+        bw = wxGetApp().getDemodMgr().getLastBandwidth();
+    } else {
+        bw = demod->getBandwidth();
+    }
+
+    if (!srate) {
+        srate = wxGetApp().getSampleRate();
+    }
+
+    glDisable(GL_TEXTURE_2D);
+
+    glEnable(GL_BLEND);
+    glBlendFunc(GL_SRC_ALPHA, GL_ONE);
+    glColor4f(color.r, color.g, color.b, 0.6f);
+
+    glBegin(GL_LINES);
+
+    glVertex3f((uxPos - 0.5) * 2.0, 1.0, 0.0);
+    glVertex3f((uxPos - 0.5) * 2.0, -1.0, 0.0);
+
+    float ofs;
+
+    if (w) {
+        ofs = w;
+    } else {
+        ofs = ((float) bw) / (float) srate;
+    }
+
+    if (last_type != "USB") {
+        glVertex3f((uxPos - 0.5) * 2.0 - ofs, 1.0, 0.0);
+        glVertex3f((uxPos - 0.5) * 2.0 - ofs, -1.0, 0.0);
+    }
+
+    if (last_type != "LSB") {
+        glVertex3f((uxPos - 0.5) * 2.0 + ofs, 1.0, 0.0);
+        glVertex3f((uxPos - 0.5) * 2.0 + ofs, -1.0, 0.0);
+    }
+
+    glEnd();
+    glDisable(GL_BLEND);
+
+}
+
+void PrimaryGLContext::DrawRangeSelector(float uxPos1, float uxPos2, RGBA4f color) {
+    if (uxPos2 < uxPos1) {
+        float temp = uxPos2;
+        uxPos2=uxPos1;
+        uxPos1=temp;
+    }
+
+    std::string last_type = wxGetApp().getDemodMgr().getLastDemodulatorType();
+
+    glDisable(GL_TEXTURE_2D);
+
+    glEnable(GL_BLEND);
+    glBlendFunc(GL_SRC_ALPHA, GL_ONE);
+    glColor4f(color.r, color.g, color.b, 0.6f);
+
+    glLineWidth((last_type == "USB")?2.0:1.0);
+
+    glBegin(GL_LINES);
+    glVertex3f((uxPos1 - 0.5) * 2.0, 1.0, 0.0);
+    glVertex3f((uxPos1 - 0.5) * 2.0, -1.0, 0.0);
+    glEnd();
+
+    glLineWidth((last_type == "LSB")?2.0:1.0);
+
+    glBegin(GL_LINES);
+    glVertex3f((uxPos2 - 0.5) * 2.0, 1.0, 0.0);
+    glVertex3f((uxPos2 - 0.5) * 2.0, -1.0, 0.0);
+    glEnd();
+
+    glLineWidth(1.0);
+
+    glDisable(GL_BLEND);
+}
+
+void PrimaryGLContext::BeginDraw(float r, float g, float b) {
+    glClearColor(r,g,b, 1);
+    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+    glMatrixMode(GL_MODELVIEW);
+    glLoadIdentity();
+}
+
+void PrimaryGLContext::EndDraw() {
+//    glFlush();
+
+//    CheckGLError();
+}
+
+void PrimaryGLContext::setHoverAlpha(float hoverAlpha) {
+    this->hoverAlpha = hoverAlpha;
+}
diff --git a/src/visual/PrimaryGLContext.h b/src/visual/PrimaryGLContext.h
new file mode 100644
index 0000000..2f19460
--- /dev/null
+++ b/src/visual/PrimaryGLContext.h
@@ -0,0 +1,36 @@
+#pragma once
+
+#include "wx/glcanvas.h"
+#include "wx/timer.h"
+
+#include <vector>
+#include <queue>
+
+#include "CubicSDRDefs.h"
+#include "GLFont.h"
+#include "DemodulatorMgr.h"
+#include "ColorTheme.h"
+
+class PrimaryGLContext: public wxGLContext {
+public:
+    PrimaryGLContext(wxGLCanvas *canvas, wxGLContext *sharedContext);
+
+    static wxString glGetwxString(GLenum name);
+    static void CheckGLError();
+
+    void BeginDraw(float r, float g, float b);
+    void EndDraw();
+
+    void DrawFreqSelector(float uxPos, RGBA4f color, float w = 0, long long center_freq = -1, long long srate = 0);
+    void DrawRangeSelector(float uxPos1, float uxPos2, RGBA4f color);
+    void DrawDemod(DemodulatorInstance *demod, RGBA4f color, long long center_freq = -1, long long srate = 0);
+    
+    void DrawDemodInfo(DemodulatorInstance *demod, RGBA4f color, long long center_freq = -1, long long srate = 0, bool centerline = false);
+    void DrawFreqBwInfo(long long freq, int bw, RGBA4f color, long long center_freq = - 1, long long srate = 0, bool stack = false, bool centerline = false);
+
+    void setHoverAlpha(float hoverAlpha);
+
+private:
+    float hoverAlpha;
+    void drawSingleDemodLabel(const std::wstring& demodStr, float uxPos, float hPos, float xOfs, float yOfs, GLFont::Align demodAlign);
+};
diff --git a/src/visual/ScopeCanvas.cpp b/src/visual/ScopeCanvas.cpp
new file mode 100644
index 0000000..83e04e7
--- /dev/null
+++ b/src/visual/ScopeCanvas.cpp
@@ -0,0 +1,274 @@
+#include "ScopeCanvas.h"
+
+#include "wx/wxprec.h"
+
+#ifndef WX_PRECOMP
+#include "wx/wx.h"
+#endif
+
+#if !wxUSE_GLCANVAS
+#error "OpenGL required: set wxUSE_GLCANVAS to 1 and rebuild the library"
+#endif
+
+#include "CubicSDR.h"
+#include "CubicSDRDefs.h"
+#include "AppFrame.h"
+#include <algorithm>
+#include <cmath>
+
+
+wxBEGIN_EVENT_TABLE(ScopeCanvas, wxGLCanvas) EVT_PAINT(ScopeCanvas::OnPaint)
+EVT_IDLE(ScopeCanvas::OnIdle)
+EVT_MOTION(ScopeCanvas::OnMouseMoved)
+EVT_LEFT_DOWN(ScopeCanvas::OnMouseDown)
+EVT_LEFT_UP(ScopeCanvas::OnMouseReleased)
+EVT_RIGHT_DOWN(ScopeCanvas::OnMouseRightDown)
+EVT_RIGHT_UP(ScopeCanvas::OnMouseRightReleased)
+EVT_LEAVE_WINDOW(ScopeCanvas::OnMouseLeftWindow)
+EVT_ENTER_WINDOW(ScopeCanvas::OnMouseEnterWindow)
+wxEND_EVENT_TABLE()
+
+ScopeCanvas::ScopeCanvas(wxWindow *parent, int *dispAttrs) : InteractiveCanvas(parent, dispAttrs), ppmMode(false), ctr(0), ctrTarget(0), dragAccel(0), helpTip("") {
+
+    glContext = new ScopeContext(this, &wxGetApp().GetContext(this));
+    inputData.set_max_num_items(2);
+    bgPanel.setFill(GLPanel::GLPANEL_FILL_GRAD_Y);
+    bgPanel.setSize(1.0, 0.5f);
+    bgPanel.setPosition(0.0, -0.5f);
+    panelSpacing = 0.4f;
+    
+    parentPanel.addChild(&scopePanel);
+    parentPanel.addChild(&spectrumPanel);
+    parentPanel.setFill(GLPanel::GLPANEL_FILL_NONE);
+    scopePanel.setSize(1.0,-1.0);
+    spectrumPanel.setSize(1.0,-1.0);
+    spectrumPanel.setShowDb(true);
+}
+
+ScopeCanvas::~ScopeCanvas() {
+
+}
+
+bool ScopeCanvas::scopeVisible() {
+    float panelInterval = (2.0 + panelSpacing);
+    
+    ctrTarget = abs(round(ctr / panelInterval));
+
+    if (ctrTarget == 0 || dragAccel || (ctr != ctrTarget)) {
+        return true;
+    }
+    
+    return false;
+}
+
+bool ScopeCanvas::spectrumVisible() {
+    float panelInterval = (2.0 + panelSpacing);
+    
+    ctrTarget = abs(round(ctr / panelInterval));
+ 
+    if (ctrTarget == 1 || dragAccel || (ctr != ctrTarget)) {
+        return true;
+    }
+    
+    return false;
+}
+
+void ScopeCanvas::setDeviceName(std::string device_name) {
+    deviceName = device_name;
+    deviceName.append(" ");
+}
+
+void ScopeCanvas::setPPMMode(bool ppmMode) {
+    this->ppmMode = ppmMode;
+}
+
+bool ScopeCanvas::getPPMMode() {
+    return ppmMode;
+}
+
+void ScopeCanvas::setShowDb(bool showDb) {
+    this->showDb = showDb;
+}
+
+bool ScopeCanvas::getShowDb() {
+    return showDb;
+}
+
+void ScopeCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) {
+    wxPaintDC dc(this);
+    const wxSize ClientSize = GetClientSize();
+    
+    ScopeRenderData *avData;
+    while (inputData.try_pop(avData)) {
+       
+        
+        if (!avData->spectrum) {
+            scopePanel.setMode(avData->mode);
+            if (avData->waveform_points.size()) {
+                scopePanel.setPoints(avData->waveform_points);
+            }
+            avData->decRefCount();
+        } else {
+            if (avData->waveform_points.size()) {
+                spectrumPanel.setPoints(avData->waveform_points);
+                spectrumPanel.setFloorValue(avData->fft_floor);
+                spectrumPanel.setCeilValue(avData->fft_ceil);
+                spectrumPanel.setBandwidth((avData->sampleRate/2)*1000);
+                spectrumPanel.setFreq((avData->sampleRate/4)*1000);
+                spectrumPanel.setFFTSize(avData->fft_size);
+                spectrumPanel.setShowDb(showDb);
+            }
+            
+            avData->decRefCount();
+        }
+    }
+
+    glContext->SetCurrent(*this);
+    initGLExtensions();
+
+    glViewport(0, 0, ClientSize.x, ClientSize.y);
+    
+    // TODO: find out why frontbuffer drawing has stopped working in wx 3.1.0?
+//    if (scopePanel.getMode() == ScopePanel::SCOPE_MODE_XY && !spectrumVisible()) {
+//        glDrawBuffer(GL_FRONT);
+//        glContext->DrawBegin(false);
+//    } else {
+//        glDrawBuffer(GL_BACK);
+        glContext->DrawBegin();
+
+        bgPanel.setFillColor(ThemeMgr::mgr.currentTheme->scopeBackground * 3.0, RGBA4f(0,0,0,1));
+        bgPanel.calcTransform(CubicVR::mat4::identity());
+        bgPanel.draw();
+//    }
+    
+    glMatrixMode(GL_PROJECTION);
+    glLoadIdentity();
+    glLoadMatrixf(CubicVR::mat4::perspective(45.0, 1.0, 1.0, 1000.0));
+    glMatrixMode(GL_MODELVIEW);
+    glLoadIdentity();
+    
+    CubicVR::mat4 modelView = CubicVR::mat4::lookat(0, 0, -1.205f, 0, 0, 0, 0, -1, 0);
+
+    float panelWidth = 1.0;
+    float panelInterval = (panelWidth * 2.0 + panelSpacing);
+
+    if (!mouseTracker.mouseDown()) {
+        ctrTarget = round(ctr / panelInterval);
+        if (ctrTarget < -1.0) {
+            ctrTarget = -1.0;
+        } else if (ctrTarget > 0.0) {
+            ctrTarget = 0.0;
+        }
+        ctrTarget *= panelInterval;
+        if (!dragAccel) {
+            if (ctr != ctrTarget) {
+                ctr += (ctrTarget-ctr)*0.2;
+            }
+            if (abs(ctr - ctrTarget) < 0.001) {
+                ctr=ctrTarget;
+            }
+        } else {
+            dragAccel -= dragAccel * 0.1;
+            if ((abs(dragAccel) < 0.2) || (ctr < (ctrTarget-panelInterval/2.0)) || (ctr > (ctrTarget+panelInterval/2.0)) ) {
+                dragAccel = 0;
+            } else {
+                ctr += dragAccel;
+            }
+        }
+    }
+
+    float roty = 0;
+    
+    scopePanel.setPosition(ctr, 0);
+    if (scopeVisible()) {
+        scopePanel.contentsVisible = true;
+        roty = atan2(scopePanel.pos[0],1.2);
+        scopePanel.rot[1] = -(roty * (180.0 / M_PI));
+    } else {
+        scopePanel.contentsVisible = false;
+    }
+    
+    spectrumPanel.setPosition(panelInterval+ctr, 0);
+    if (spectrumVisible()) {
+        spectrumPanel.setFillColor(ThemeMgr::mgr.currentTheme->scopeBackground * 2.0, RGBA4f(0,0,0,1));
+        spectrumPanel.contentsVisible = true;
+        roty = atan2(spectrumPanel.pos[0],1.2);
+        spectrumPanel.rot[1] = -(roty * (180.0 / M_PI));
+    } else {
+        spectrumPanel.contentsVisible = false;
+    }
+
+    parentPanel.calcTransform(modelView);
+    parentPanel.draw();
+
+    if (spectrumVisible()) {
+        spectrumPanel.drawChildren();
+    }
+    
+    glLoadMatrixf(scopePanel.transform);
+    if (!deviceName.empty()) {
+        glContext->DrawDeviceName(deviceName);
+    }
+
+    glMatrixMode(GL_PROJECTION);
+    glLoadIdentity();
+    glMatrixMode(GL_MODELVIEW);
+    glLoadIdentity();
+    glContext->DrawTunerTitles(ppmMode);
+    glContext->DrawEnd();
+
+//    if (scopePanel.getMode() != ScopePanel::SCOPE_MODE_XY || spectrumVisible()) {
+        SwapBuffers();
+//    }
+}
+
+
+void ScopeCanvas::OnIdle(wxIdleEvent &event) {
+    Refresh();
+    event.RequestMore();
+}
+
+ScopeRenderDataQueue *ScopeCanvas::getInputQueue() {
+    return &inputData;
+}
+
+void ScopeCanvas::OnMouseMoved(wxMouseEvent& event) {
+    InteractiveCanvas::OnMouseMoved(event);
+    if (mouseTracker.mouseDown()) {
+        dragAccel = 4.0*mouseTracker.getDeltaMouseX();
+        ctr += dragAccel;
+    }
+}
+
+void ScopeCanvas::OnMouseWheelMoved(wxMouseEvent& /* event */) {
+    
+}
+
+void ScopeCanvas::OnMouseDown(wxMouseEvent& event) {
+    InteractiveCanvas::OnMouseDown(event);
+    
+}
+
+void ScopeCanvas::OnMouseReleased(wxMouseEvent& event) {
+    InteractiveCanvas::OnMouseReleased(event);
+    
+}
+
+void ScopeCanvas::OnMouseEnterWindow(wxMouseEvent& event) {
+    InteractiveCanvas::OnMouseEnterWindow(event);
+    if (!helpTip.empty()) {
+        setStatusText(helpTip);
+    }
+    SetCursor(wxCURSOR_SIZEWE);
+}
+
+void ScopeCanvas::OnMouseLeftWindow(wxMouseEvent& event) {
+    InteractiveCanvas::OnMouseLeftWindow(event);
+    
+}
+
+
+void ScopeCanvas::setHelpTip(std::string tip) {
+    helpTip = tip;
+}
+
diff --git a/src/visual/ScopeCanvas.h b/src/visual/ScopeCanvas.h
new file mode 100644
index 0000000..006f7f4
--- /dev/null
+++ b/src/visual/ScopeCanvas.h
@@ -0,0 +1,61 @@
+#pragma once
+
+#include "wx/glcanvas.h"
+#include "wx/timer.h"
+
+#include <vector>
+#include <queue>
+
+#include "ScopeContext.h"
+#include "ScopeVisualProcessor.h"
+#include "ScopePanel.h"
+#include "SpectrumPanel.h"
+#include "InteractiveCanvas.h"
+
+class ScopeCanvas: public InteractiveCanvas {
+public:
+    ScopeCanvas(wxWindow *parent, int *dispAttrs);
+    ~ScopeCanvas();
+
+    void setDeviceName(std::string device_name);
+    void setPPMMode(bool ppmMode);
+    bool getPPMMode();
+
+    void setShowDb(bool showDb);
+    bool getShowDb();
+
+    bool scopeVisible();
+    bool spectrumVisible();
+    
+    void setHelpTip(std::string tip);
+
+    ScopeRenderDataQueue *getInputQueue();
+    
+private:
+    void OnPaint(wxPaintEvent& event);
+    void OnIdle(wxIdleEvent &event);
+    void OnMouseMoved(wxMouseEvent& event);
+    void OnMouseWheelMoved(wxMouseEvent& event);
+    void OnMouseDown(wxMouseEvent& event);
+    void OnMouseReleased(wxMouseEvent& event);
+    void OnMouseEnterWindow(wxMouseEvent& event);
+    void OnMouseLeftWindow(wxMouseEvent& event);
+
+    ScopeRenderDataQueue inputData;
+    ScopePanel scopePanel;
+    GLPanel parentPanel;
+    SpectrumPanel spectrumPanel;
+    GLPanel bgPanel;
+    ScopeContext *glContext;
+    std::string deviceName;
+    bool ppmMode;
+    bool showDb;
+    float panelSpacing;
+    float ctr;
+    float ctrTarget;
+    float dragAccel;
+    std::string helpTip;
+// event table
+wxDECLARE_EVENT_TABLE();
+};
+
diff --git a/src/visual/ScopeContext.cpp b/src/visual/ScopeContext.cpp
new file mode 100644
index 0000000..a71f74f
--- /dev/null
+++ b/src/visual/ScopeContext.cpp
@@ -0,0 +1,68 @@
+#include "ScopeContext.h"
+
+#include "ScopeCanvas.h"
+#include "ColorTheme.h"
+
+ScopeContext::ScopeContext(ScopeCanvas *canvas, wxGLContext *sharedContext) :
+        PrimaryGLContext(canvas, sharedContext) {
+    glDisable (GL_CULL_FACE);
+    glDisable (GL_DEPTH_TEST);
+
+    glMatrixMode (GL_PROJECTION);
+    glLoadIdentity();
+}
+
+void ScopeContext::DrawBegin(bool clear) {
+    if (clear) {
+        glClearColor(ThemeMgr::mgr.currentTheme->scopeBackground.r, ThemeMgr::mgr.currentTheme->scopeBackground.g,
+                ThemeMgr::mgr.currentTheme->scopeBackground.b, 1.0);
+        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+    }
+
+    glMatrixMode (GL_MODELVIEW);
+    glLoadIdentity();
+
+    glDisable (GL_TEXTURE_2D);
+}
+
+void ScopeContext::DrawTunerTitles(bool ppmMode) {
+    glLoadIdentity();
+
+    GLint vp[4];
+    glGetIntegerv(GL_VIEWPORT, vp);
+    float viewHeight = (float) vp[3];
+    float hPos = (float) (13) / viewHeight;
+
+    glColor3f(0.65f, 0.65f, 0.65f);
+
+    GLFont::Drawer refDrawingFont = GLFont::getFont(12, GLFont::getScaleFactor());
+
+    refDrawingFont.drawString(ppmMode?"Device PPM":"Frequency", -0.66f, -1.0+hPos, GLFont::GLFONT_ALIGN_CENTER, GLFont::GLFONT_ALIGN_CENTER, 0, 0, true);
+    refDrawingFont.drawString("Bandwidth", 0.0, -1.0+hPos, GLFont::GLFONT_ALIGN_CENTER, GLFont::GLFONT_ALIGN_CENTER, 0, 0, true);
+    refDrawingFont.drawString("Center Frequency", 0.66f, -1.0+hPos, GLFont::GLFONT_ALIGN_CENTER, GLFont::GLFONT_ALIGN_CENTER, 0, 0, true);
+}
+
+void ScopeContext::DrawDeviceName(std::string deviceName) {
+    GLint vp[4];
+    glGetIntegerv(GL_VIEWPORT, vp);
+    float viewHeight = (float) vp[3];
+    float hPos = (float) (viewHeight - 20) / viewHeight;
+
+    glColor3f(0.65f, 0.65f, 0.65f);
+
+    GLFont::getFont(12, GLFont::getScaleFactor()).drawString(deviceName.c_str(), 1.0, hPos, GLFont::GLFONT_ALIGN_RIGHT, GLFont::GLFONT_ALIGN_CENTER, 0, 0, true);
+}
+
+void ScopeContext::DrawEnd() {
+//    glFlush();
+
+//    CheckGLError();
+}
+
+void ScopeContext::DrawDivider() {
+    glColor3f(1.0, 1.0, 1.0);
+    glBegin (GL_LINES);
+    glVertex2f(0.0, -1.0);
+    glVertex2f(0.0, 1.0);
+    glEnd();
+}
diff --git a/src/visual/ScopeContext.h b/src/visual/ScopeContext.h
new file mode 100644
index 0000000..873bde0
--- /dev/null
+++ b/src/visual/ScopeContext.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include "PrimaryGLContext.h"
+#include "Gradient.h"
+
+#define NUM_WATERFALL_LINES 512
+
+class ScopeCanvas;
+
+class ScopeContext: public PrimaryGLContext {
+public:
+    ScopeContext(ScopeCanvas *canvas, wxGLContext *sharedContext);
+
+    void DrawBegin(bool clear=true);
+    void DrawTunerTitles(bool ppmMode=false);
+    void DrawDeviceName(std::string deviceName);
+    void DrawDivider();
+    void DrawEnd();
+
+private:
+};
diff --git a/src/visual/SpectrumCanvas.cpp b/src/visual/SpectrumCanvas.cpp
new file mode 100644
index 0000000..8a31c85
--- /dev/null
+++ b/src/visual/SpectrumCanvas.cpp
@@ -0,0 +1,299 @@
+#include "SpectrumCanvas.h"
+
+#include "wx/wxprec.h"
+
+#ifndef WX_PRECOMP
+#include "wx/wx.h"
+#endif
+
+#if !wxUSE_GLCANVAS
+#error "OpenGL required: set wxUSE_GLCANVAS to 1 and rebuild the library"
+#endif
+
+#include "CubicSDR.h"
+#include "CubicSDRDefs.h"
+#include "AppFrame.h"
+#include <algorithm>
+#include <wx/numformatter.h>
+#include "WaterfallCanvas.h"
+
+wxBEGIN_EVENT_TABLE(SpectrumCanvas, wxGLCanvas) EVT_PAINT(SpectrumCanvas::OnPaint)
+EVT_IDLE(SpectrumCanvas::OnIdle)
+EVT_MOTION(SpectrumCanvas::OnMouseMoved)
+EVT_LEFT_DOWN(SpectrumCanvas::OnMouseDown)
+EVT_LEFT_UP(SpectrumCanvas::OnMouseReleased)
+EVT_ENTER_WINDOW(SpectrumCanvas::OnMouseEnterWindow)
+EVT_LEAVE_WINDOW(SpectrumCanvas::OnMouseLeftWindow)
+EVT_MOUSEWHEEL(SpectrumCanvas::OnMouseWheelMoved)
+EVT_RIGHT_DOWN(SpectrumCanvas::OnMouseRightDown)
+EVT_RIGHT_UP(SpectrumCanvas::OnMouseRightReleased)
+wxEND_EVENT_TABLE()
+
+SpectrumCanvas::SpectrumCanvas(wxWindow *parent, int *dispAttrs) :
+        InteractiveCanvas(parent, dispAttrs), waterfallCanvas(NULL) {
+
+    glContext = new PrimaryGLContext(this, &wxGetApp().GetContext(this));
+
+    visualDataQueue.set_max_num_items(1);
+            
+    SetCursor(wxCURSOR_SIZEWE);
+    scaleFactor = 1.0;
+    resetScaleFactor = false;
+    scaleFactorEnabled = false;
+    bwChange = 0.0;
+}
+
+SpectrumCanvas::~SpectrumCanvas() {
+
+}
+
+void SpectrumCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) {
+    wxPaintDC dc(this);
+    const wxSize ClientSize = GetClientSize();
+    
+    SpectrumVisualData *vData;
+    if (visualDataQueue.try_pop(vData)) {
+            
+        if (vData) {
+            spectrumPanel.setPoints(vData->spectrum_points);
+            spectrumPanel.setPeakPoints(vData->spectrum_hold_points);
+            spectrumPanel.setFloorValue(vData->fft_floor);
+            spectrumPanel.setCeilValue(vData->fft_ceiling);
+            vData->decRefCount();
+        }
+    }
+    
+    if (resetScaleFactor) {
+        scaleFactor += (1.0-scaleFactor)*0.05;
+        if (fabs(scaleFactor-1.0) < 0.01) {
+            scaleFactor = 1.0;
+            resetScaleFactor = false;
+        }
+        updateScaleFactor(scaleFactor);
+    }
+    
+    
+    glContext->SetCurrent(*this);
+    initGLExtensions();
+
+    glViewport(0, 0, ClientSize.x, ClientSize.y);
+
+    glContext->BeginDraw(0,0,0);
+
+    spectrumPanel.setFreq(getCenterFrequency());
+    spectrumPanel.setBandwidth(getBandwidth());
+    
+    spectrumPanel.calcTransform(CubicVR::mat4::identity());
+    spectrumPanel.draw();
+    
+    glLoadIdentity();
+    
+    std::vector<DemodulatorInstance *> &demods = wxGetApp().getDemodMgr().getDemodulators();
+
+    DemodulatorInstance *activeDemodulator = wxGetApp().getDemodMgr().getActiveDemodulator();
+
+    for (int i = 0, iMax = demods.size(); i < iMax; i++) {
+        if (!demods[i]->isActive()) {
+            continue;
+        }
+        glContext->DrawDemodInfo(demods[i], ThemeMgr::mgr.currentTheme->fftHighlight, getCenterFrequency(), getBandwidth(), activeDemodulator==demods[i]);
+    }
+
+    if (waterfallCanvas && !activeDemodulator) {
+        MouseTracker *wfmt = waterfallCanvas->getMouseTracker();
+        if (wfmt->mouseInView()) {
+            int snap = wxGetApp().getFrequencySnap();
+            
+            long long freq = getFrequencyAt(wfmt->getMouseX());
+            
+            if (snap > 1) {
+                freq = roundf((float)freq/(float)snap)*snap;
+            }
+
+            DemodulatorInstance *lastActiveDemodulator = wxGetApp().getDemodMgr().getLastActiveDemodulator();
+
+            bool isNew = (((waterfallCanvas->isShiftDown() || (lastActiveDemodulator && !lastActiveDemodulator->isActive())) && lastActiveDemodulator) || (!lastActiveDemodulator));
+            
+            glContext->DrawFreqBwInfo(freq, wxGetApp().getDemodMgr().getLastBandwidth(), isNew?ThemeMgr::mgr.currentTheme->waterfallNew:ThemeMgr::mgr.currentTheme->waterfallHover, getCenterFrequency(), getBandwidth(), true, true);
+        }
+    }
+    
+    glContext->EndDraw();
+
+    spectrumPanel.drawChildren();
+
+    SwapBuffers();
+}
+
+
+void SpectrumCanvas::OnIdle(wxIdleEvent &event) {
+    Refresh();
+    event.RequestMore();
+}
+
+
+void SpectrumCanvas::moveCenterFrequency(long long freqChange) {
+    long long freq = wxGetApp().getFrequency();
+
+    if (isView) {
+        if (centerFreq - freqChange < bandwidth/2) {
+            centerFreq = bandwidth/2;
+        } else {
+            centerFreq -= freqChange;
+        }
+
+        if (waterfallCanvas) {
+            waterfallCanvas->setCenterFrequency(centerFreq);
+        }
+
+        long long bwOfs = (centerFreq > freq) ? ((long long) bandwidth / 2) : (-(long long) bandwidth / 2);
+        long long freqEdge = centerFreq + bwOfs;
+
+        if (abs(freq - freqEdge) > (wxGetApp().getSampleRate() / 2)) {
+            freqChange = -((centerFreq > freq) ? (freqEdge - freq - (wxGetApp().getSampleRate() / 2)) : (freqEdge - freq + (wxGetApp().getSampleRate() / 2)));
+        } else {
+            freqChange = 0;
+        }
+    }
+
+    if (freqChange) {
+        if (freq - freqChange < wxGetApp().getSampleRate()/2) {
+            freq = wxGetApp().getSampleRate()/2;
+        } else {
+            freq -= freqChange;
+        }
+        wxGetApp().setFrequency(freq);
+    }
+}
+
+void SpectrumCanvas::setShowDb(bool showDb) {
+    spectrumPanel.setShowDb(showDb);
+}
+
+bool SpectrumCanvas::getShowDb() {
+    return spectrumPanel.getShowDb();
+}
+
+void SpectrumCanvas::setView(long long center_freq_in, int bandwidth_in) {
+    bwChange += bandwidth_in-bandwidth;
+    #define BW_RESET_TH 400000
+    if (bwChange > BW_RESET_TH || bwChange < -BW_RESET_TH) {
+        resetScaleFactor = true;
+        bwChange = 0;
+    }
+    InteractiveCanvas::setView(center_freq_in, bandwidth_in);
+}
+
+void SpectrumCanvas::disableView() {
+    InteractiveCanvas::disableView();
+}
+
+void SpectrumCanvas::setScaleFactorEnabled(bool en) {
+    scaleFactorEnabled = en;
+}
+
+void SpectrumCanvas::setFFTSize(int fftSize) {
+    spectrumPanel.setFFTSize(fftSize);
+}
+
+void SpectrumCanvas::updateScaleFactor(float factor) {
+    SpectrumVisualProcessor *sp = wxGetApp().getSpectrumProcessor();
+    FFTVisualDataThread *wdt = wxGetApp().getAppFrame()->getWaterfallDataThread();
+    SpectrumVisualProcessor *wp = wdt->getProcessor();
+
+    scaleFactor = factor;
+    sp->setScaleFactor(factor);
+    wp->setScaleFactor(factor);
+}
+
+void SpectrumCanvas::OnMouseMoved(wxMouseEvent& event) {
+    InteractiveCanvas::OnMouseMoved(event);
+    if (mouseTracker.mouseDown()) {
+        int freqChange = mouseTracker.getDeltaMouseX() * getBandwidth();
+
+        if (freqChange != 0) {
+            moveCenterFrequency(freqChange);
+        }
+    }
+    else if (scaleFactorEnabled && mouseTracker.mouseRightDown()) {
+        
+        float yDelta = mouseTracker.getDeltaMouseY();
+
+        scaleFactor += yDelta*2.0;
+        if (scaleFactor < 0.25) {
+            scaleFactor = 0.25;
+        }
+        if (scaleFactor > 10.0) {
+            scaleFactor = 10.0;
+        }
+        
+        resetScaleFactor = false;
+        updateScaleFactor(scaleFactor);
+    } else {
+        if (scaleFactorEnabled) {
+            setStatusText("Drag horizontal to adjust center frequency. Right-drag or SHIFT+UP/DOWN to adjust vertical scale; right-click to reset. 'B' to toggle decibels display.");
+        } else {
+            setStatusText("Displaying spectrum of active demodulator.");
+        }
+    }
+}
+
+void SpectrumCanvas::OnMouseDown(wxMouseEvent& event) {
+	mouseTracker.setVertDragLock(true);
+    InteractiveCanvas::OnMouseDown(event);
+    SetCursor(wxCURSOR_CROSS);
+}
+
+void SpectrumCanvas::OnMouseWheelMoved(wxMouseEvent& event) {
+    InteractiveCanvas::OnMouseWheelMoved(event);
+    if (waterfallCanvas) {
+        waterfallCanvas->OnMouseWheelMoved(event);
+    }
+}
+
+void SpectrumCanvas::OnMouseReleased(wxMouseEvent& event) {
+	mouseTracker.setVertDragLock(false);
+	InteractiveCanvas::OnMouseReleased(event);
+    SetCursor(wxCURSOR_SIZEWE);
+}
+
+void SpectrumCanvas::OnMouseEnterWindow(wxMouseEvent& event) {
+    InteractiveCanvas::OnMouseEnterWindow(event);
+    SetCursor(wxCURSOR_SIZEWE);
+#ifdef _WIN32
+    if (wxGetApp().getAppFrame()->canFocus()) {
+        this->SetFocus();
+    }
+#endif
+}
+
+void SpectrumCanvas::OnMouseLeftWindow(wxMouseEvent& event) {
+    InteractiveCanvas::OnMouseLeftWindow(event);
+    SetCursor(wxCURSOR_SIZEWE);
+}
+
+void SpectrumCanvas::attachWaterfallCanvas(WaterfallCanvas* canvas_in) {
+    waterfallCanvas = canvas_in;
+}
+
+SpectrumVisualDataQueue *SpectrumCanvas::getVisualDataQueue() {
+    return &visualDataQueue;
+}
+
+void SpectrumCanvas::OnMouseRightDown(wxMouseEvent& event) {
+	mouseTracker.setHorizDragLock(true);
+    mouseTracker.OnMouseRightDown(event);
+    scaleFactor = wxGetApp().getSpectrumProcessor()->getScaleFactor();
+}
+
+void SpectrumCanvas::OnMouseRightReleased(wxMouseEvent& event) {
+	mouseTracker.setHorizDragLock(false);
+    if (!mouseTracker.getOriginDeltaMouseY()) {
+        resetScaleFactor = true;
+        wxGetApp().getSpectrumProcessor()->setPeakHold(wxGetApp().getSpectrumProcessor()->getPeakHold());
+
+        //make the peak hold act on the current dmod also, like a zoomed-in version.
+        wxGetApp().getDemodSpectrumProcessor()->setPeakHold(wxGetApp().getSpectrumProcessor()->getPeakHold());
+    }
+    mouseTracker.OnMouseRightReleased(event);
+}
diff --git a/src/visual/SpectrumCanvas.h b/src/visual/SpectrumCanvas.h
new file mode 100644
index 0000000..85af252
--- /dev/null
+++ b/src/visual/SpectrumCanvas.h
@@ -0,0 +1,61 @@
+#pragma once
+
+#include <vector>
+#include <queue>
+
+#include "InteractiveCanvas.h"
+#include "PrimaryGLContext.h"
+#include "MouseTracker.h"
+#include "SpectrumVisualProcessor.h"
+#include "SpectrumPanel.h"
+
+class WaterfallCanvas;
+
+class SpectrumCanvas: public InteractiveCanvas {
+public:
+    SpectrumCanvas(wxWindow *parent, int *dispAttrs);
+    ~SpectrumCanvas();
+
+    void attachWaterfallCanvas(WaterfallCanvas *canvas_in);
+    void moveCenterFrequency(long long freqChange);
+
+    void setShowDb(bool showDb);
+    bool getShowDb();
+    
+    void setView(long long center_freq_in, int bandwidth_in);
+    void disableView();
+
+    void setScaleFactorEnabled(bool en);
+    void setFFTSize(int fftSize);
+    
+    SpectrumVisualDataQueue *getVisualDataQueue();
+    
+private:
+    void OnPaint(wxPaintEvent& event);
+
+    void OnIdle(wxIdleEvent &event);
+
+    void OnMouseMoved(wxMouseEvent& event);
+    void OnMouseDown(wxMouseEvent& event);
+    void OnMouseWheelMoved(wxMouseEvent& event);
+    void OnMouseReleased(wxMouseEvent& event);
+    void OnMouseEnterWindow(wxMouseEvent& event);
+    void OnMouseLeftWindow(wxMouseEvent& event);
+    void OnMouseRightDown(wxMouseEvent& event);
+    void OnMouseRightReleased(wxMouseEvent& event);
+
+    void updateScaleFactor(float factor);
+    
+    PrimaryGLContext *glContext;
+    WaterfallCanvas *waterfallCanvas;
+    SpectrumPanel spectrumPanel;
+    float scaleFactor;
+    int bwChange;
+    bool resetScaleFactor, scaleFactorEnabled;
+    
+    SpectrumVisualDataQueue visualDataQueue;
+
+// event table
+wxDECLARE_EVENT_TABLE();
+};
+
diff --git a/src/visual/TuningCanvas.cpp b/src/visual/TuningCanvas.cpp
new file mode 100644
index 0000000..6d58520
--- /dev/null
+++ b/src/visual/TuningCanvas.cpp
@@ -0,0 +1,442 @@
+#include "TuningCanvas.h"
+
+#include "wx/wxprec.h"
+
+#ifndef WX_PRECOMP
+#include "wx/wx.h"
+#endif
+
+#if !wxUSE_GLCANVAS
+#error "OpenGL required: set wxUSE_GLCANVAS to 1 and rebuild the library"
+#endif
+
+#include "CubicSDR.h"
+#include "CubicSDRDefs.h"
+#include "AppFrame.h"
+#include <algorithm>
+
+wxBEGIN_EVENT_TABLE(TuningCanvas, wxGLCanvas) EVT_PAINT(TuningCanvas::OnPaint)
+EVT_IDLE(TuningCanvas::OnIdle)
+EVT_MOTION(TuningCanvas::OnMouseMoved)
+EVT_LEFT_DOWN(TuningCanvas::OnMouseDown)
+EVT_LEFT_UP(TuningCanvas::OnMouseReleased)
+EVT_RIGHT_DOWN(TuningCanvas::OnMouseRightDown)
+EVT_RIGHT_UP(TuningCanvas::OnMouseRightReleased)
+
+EVT_LEAVE_WINDOW(TuningCanvas::OnMouseLeftWindow)
+EVT_ENTER_WINDOW(TuningCanvas::OnMouseEnterWindow)
+EVT_MOUSEWHEEL(TuningCanvas::OnMouseWheelMoved)
+//EVT_KEY_DOWN(TuningCanvas::OnKeyDown)
+//EVT_KEY_UP(TuningCanvas::OnKeyUp)
+wxEND_EVENT_TABLE()
+
+TuningCanvas::TuningCanvas(wxWindow *parent, int *dispAttrs) :
+        InteractiveCanvas(parent, dispAttrs), dragAccum(0), uxDown(0), top(false), bottom(false), freq(-1), bw(-1), center(-1), halfBand(false) {
+
+    glContext = new TuningContext(this, &wxGetApp().GetContext(this));
+
+    hoverIndex = 0;
+    downIndex = 0;
+    hoverState = TUNING_HOVER_NONE;
+    downState = TUNING_HOVER_NONE;
+    dragging = false;
+
+    freqDP = -1.0;
+    freqW = (1.0f / 3.0f) * 2.0f;
+
+    bwDP = -1.0 + (2.25 / 3.0);
+    bwW = (1.0 / 4.0) * 2.0;
+
+    centerDP = -1.0f + (2.0f / 3.0f) * 2.0f;
+    centerW = (1.0f / 3.0f) * 2.0f;
+
+    currentPPM = lastPPM = 0;
+}
+
+TuningCanvas::~TuningCanvas() {
+
+}
+
+bool TuningCanvas::changed() {
+    DemodulatorInstance *activeDemod = wxGetApp().getDemodMgr().getLastActiveDemodulator();
+    
+    long long current_freq = 0;
+    if (activeDemod != NULL) {
+        freq = activeDemod->getFrequency();
+    }
+    long long current_bw = wxGetApp().getDemodMgr().getLastBandwidth();
+    long long current_center = wxGetApp().getFrequency();
+    
+    if (current_freq != freq || current_bw != bw || current_center != center) {
+        return true;
+    }
+    
+    return false;
+}
+
+void TuningCanvas::setHalfBand(bool hb) {
+    halfBand = hb;
+	Refresh();
+}
+
+void TuningCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) {
+    wxPaintDC dc(this);
+    const wxSize ClientSize = GetClientSize();
+    
+    glContext->SetCurrent(*this);
+    initGLExtensions();
+    glViewport(0, 0, ClientSize.x, ClientSize.y);
+
+    glContext->DrawBegin();
+
+    DemodulatorInstance *activeDemod = wxGetApp().getDemodMgr().getLastActiveDemodulator();
+    
+    freq = 0;
+    if (activeDemod != NULL) {
+        freq = activeDemod->getFrequency();
+    }
+    bw = wxGetApp().getDemodMgr().getLastBandwidth();
+    center = wxGetApp().getFrequency();
+
+    if (mouseTracker.mouseDown()) {
+        glContext->Draw(ThemeMgr::mgr.currentTheme->tuningBarDark.r, ThemeMgr::mgr.currentTheme->tuningBarDark.g, ThemeMgr::mgr.currentTheme->tuningBarDark.b,
+                0.75, mouseTracker.getOriginMouseX(), mouseTracker.getMouseX());
+    }
+
+    RGBA4f clr = top ? ThemeMgr::mgr.currentTheme->tuningBarUp : ThemeMgr::mgr.currentTheme->tuningBarDown;
+
+    RGBA4f clrDark = ThemeMgr::mgr.currentTheme->tuningBarDark;
+    RGBA4f clrMid = ThemeMgr::mgr.currentTheme->tuningBarLight;
+
+    glContext->DrawTunerBarIndexed(1, 3, 11, freqDP, freqW, clrMid, 0.25, true, true); // freq
+    glContext->DrawTunerBarIndexed(4, 6, 11, freqDP, freqW, clrDark, 0.25, true, true);
+    glContext->DrawTunerBarIndexed(7, 9, 11, freqDP, freqW, clrMid, 0.25, true, true);
+    glContext->DrawTunerBarIndexed(10, 11, 11, freqDP, freqW, clrDark, 0.25, true, true);
+
+    glContext->DrawTunerBarIndexed(1, 3, 7, bwDP, bwW, clrMid, 0.25, true, true); // bw
+    glContext->DrawTunerBarIndexed(4, 6, 7, bwDP, bwW, clrDark, 0.25, true, true);
+    glContext->DrawTunerBarIndexed(7, 7, 7, bwDP, bwW, clrMid, 0.25, true, true);
+
+    glContext->DrawTunerBarIndexed(1, 3, 11, centerDP, centerW, clrMid, 0.25, true, true); // freq
+    glContext->DrawTunerBarIndexed(4, 6, 11, centerDP, centerW, clrDark, 0.25, true, true);
+    glContext->DrawTunerBarIndexed(7, 9, 11, centerDP, centerW, clrMid, 0.25, true, true);
+    glContext->DrawTunerBarIndexed(10, 11, 11, centerDP, centerW, clrDark, 0.25, true, true);
+
+    if (hoverIndex > 0 && !mouseTracker.mouseDown()) {
+        switch (hoverState) {
+
+        case TUNING_HOVER_FREQ:
+            glContext->DrawTunerBarIndexed(hoverIndex, hoverIndex, 11, freqDP, freqW, clr, 0.25, top, bottom); // freq
+            break;
+        case TUNING_HOVER_BW:
+            glContext->DrawTunerBarIndexed(hoverIndex, hoverIndex, 7, bwDP, bwW, clr, 0.25, top, bottom); // bw
+            break;
+        case TUNING_HOVER_CENTER:
+            glContext->DrawTunerBarIndexed(hoverIndex, hoverIndex, 11, centerDP, centerW, clr, 0.25, top, bottom); // center
+            break;
+        case TUNING_HOVER_NONE:
+            break;
+        case TUNING_HOVER_PPM:
+            glContext->DrawTunerBarIndexed(hoverIndex, hoverIndex, 11, freqDP, freqW, clr, 0.25, top, bottom); // freq
+            break;
+         }
+    }
+
+    if (altDown) {
+        glContext->DrawTuner(currentPPM, 11, freqDP, freqW);
+    } else {
+        glContext->DrawTuner(freq, 11, freqDP, freqW);
+        int snap = wxGetApp().getFrequencySnap();
+        if (snap != 1) {
+            glContext->DrawTunerDigitBox((int)log10(snap), 11, freqDP, freqW, RGBA4f(1.0,0.0,0.0));
+        }
+    }
+    glContext->DrawTuner(halfBand?(bw/2):bw, 7, bwDP, bwW);
+    glContext->DrawTuner(center, 11, centerDP, centerW);
+
+    glContext->DrawEnd();
+
+    SwapBuffers();
+}
+
+void TuningCanvas::StepTuner(ActiveState state, int exponent, bool up) {
+    double exp = pow(10, exponent);
+    long long amount = up?exp:-exp;
+
+    if (halfBand && state == TUNING_HOVER_BW) {
+        amount *= 2;
+    }
+    
+    DemodulatorInstance *activeDemod = wxGetApp().getDemodMgr().getLastActiveDemodulator();
+    if (state == TUNING_HOVER_FREQ && activeDemod) {
+        long long freq = activeDemod->getFrequency();
+        long long diff = abs(wxGetApp().getFrequency() - freq);
+
+        if (shiftDown) {
+            bool carried = (long long)((freq) / (exp * 10)) != (long long)((freq + amount) / (exp * 10)) || (bottom && freq < exp);
+            freq += carried?(9*-amount):amount;
+        } else {
+            freq += amount;
+        }
+
+        if (wxGetApp().getSampleRate() / 2 < diff) {
+            wxGetApp().setFrequency(freq);
+        }
+
+        activeDemod->setTracking(true);
+        activeDemod->setFollow(true);
+        activeDemod->setFrequency(freq);
+        if (activeDemod->isDeltaLock()) {
+            activeDemod->setDeltaLockOfs(activeDemod->getFrequency() - wxGetApp().getFrequency());
+        }
+        activeDemod->updateLabel(freq);
+    }
+
+    if (state == TUNING_HOVER_BW) {
+        long bw = wxGetApp().getDemodMgr().getLastBandwidth();
+
+        if (shiftDown) {
+            bool carried = (long)((bw) / (exp * 10)) != (long)((bw + amount) / (exp * 10)) || (bottom && bw < exp);
+            bw += carried?(9*-amount):amount;
+        } else {
+            bw += amount;
+        }
+
+        if (bw > CHANNELIZER_RATE_MAX) {
+            bw = CHANNELIZER_RATE_MAX;
+        }
+
+        wxGetApp().getDemodMgr().setLastBandwidth(bw);
+
+        if (activeDemod) {
+            activeDemod->setBandwidth(wxGetApp().getDemodMgr().getLastBandwidth());
+        }
+    }
+
+    if (state == TUNING_HOVER_CENTER) {
+        long long ctr = wxGetApp().getFrequency();
+        if (shiftDown) {
+            bool carried = (long long)((ctr) / (exp * 10)) != (long long)((ctr + amount) / (exp * 10)) || (bottom && ctr < exp);
+            ctr += carried?(9*-amount):amount;
+        } else {
+            ctr += amount;
+        }
+
+        wxGetApp().setFrequency(ctr);
+    }
+
+    if (state == TUNING_HOVER_PPM) {
+        if (shiftDown) {
+            bool carried = (long long)((currentPPM) / (exp * 10)) != (long long)((currentPPM + amount) / (exp * 10)) || (bottom && currentPPM < exp);
+            currentPPM += carried?(9*-amount):amount;
+        } else {
+            currentPPM += amount;
+        }
+
+        if (currentPPM > 2000) {
+            currentPPM = 2000;
+        }
+
+        if (currentPPM < -2000) {
+            currentPPM = -2000;
+        }
+
+        wxGetApp().setPPM(currentPPM);
+    }
+}
+
+void TuningCanvas::OnIdle(wxIdleEvent &event) {
+    if (mouseTracker.mouseDown()) {
+        if (downState != TUNING_HOVER_NONE) {
+            dragAccum += 5.0*mouseTracker.getOriginDeltaMouseX();
+            while (dragAccum > 1.0) {
+                StepTuner(downState, downIndex-1, true);
+                dragAccum -= 1.0;
+                dragging = true;
+            }
+            while (dragAccum < -1.0) {
+                StepTuner(downState, downIndex-1, false);
+                dragAccum += 1.0;
+                dragging = true;
+            }
+        } else {
+            dragAccum = 0;
+            dragging = false;
+        }
+    }
+    if (mouseTracker.mouseInView() || changed()) {
+        Refresh();
+    }
+    event.RequestMore();
+}
+
+void TuningCanvas::OnMouseMoved(wxMouseEvent& event) {
+    InteractiveCanvas::OnMouseMoved(event);
+
+    int index = 0;
+
+    top = mouseTracker.getMouseY() >= 0.5;
+    bottom = mouseTracker.getMouseY() <= 0.5;
+
+    index = glContext->GetTunerDigitIndex(mouseTracker.getMouseX(), 11, freqDP, freqW); // freq
+    if (index > 0) {
+        hoverIndex = index;
+        hoverState = altDown?TUNING_HOVER_PPM:TUNING_HOVER_FREQ;
+    }
+
+    if (!index) {
+        index = glContext->GetTunerDigitIndex(mouseTracker.getMouseX(), 7, bwDP, bwW); // bw
+        if (index > 0) {
+            hoverIndex = index;
+            hoverState = TUNING_HOVER_BW;
+        }
+    }
+
+    if (!index) {
+        index = glContext->GetTunerDigitIndex(mouseTracker.getMouseX(), 11, centerDP, centerW); // center
+        if (index > 0) {
+            hoverIndex = index;
+            hoverState = TUNING_HOVER_CENTER;
+        }
+    }
+
+    if (!index) {
+        hoverIndex = 0;
+        hoverState = TUNING_HOVER_NONE;
+    } else {
+        switch (hoverState) {
+        case TUNING_HOVER_FREQ:
+                setStatusText("Click, wheel or drag a digit to change frequency; SPACE or numeric key for direct input. Right click to set/clear snap. Hold ALT to change PPM. Hold SHIFT to disable carry.");
+            break;
+        case TUNING_HOVER_BW:
+                setStatusText("Click, wheel or drag a digit to change bandwidth; SPACE or numeric key for direct input.  Hold SHIFT to disable carry.");
+            break;
+        case TUNING_HOVER_CENTER:
+                setStatusText("Click, wheel or drag a digit to change center frequency; SPACE or numeric key for direct input.  Hold SHIFT to disable carry.");
+            break;
+        case TUNING_HOVER_PPM:
+                 setStatusText("Click, wheel or drag a digit to change device PPM offset.  Hold SHIFT to disable carry.");
+             break;
+        case TUNING_HOVER_NONE:
+            setStatusText("");
+            break;
+      }
+    }
+
+    if (hoverState == TUNING_HOVER_BW || hoverState == TUNING_HOVER_FREQ) {
+         wxGetApp().getDemodMgr().setActiveDemodulator(wxGetApp().getDemodMgr().getLastActiveDemodulator());
+     } else {
+         wxGetApp().getDemodMgr().setActiveDemodulator(NULL);
+     }
+}
+
+void TuningCanvas::OnMouseDown(wxMouseEvent& event) {
+    InteractiveCanvas::OnMouseDown(event);
+
+    uxDown = 2.0 * (mouseTracker.getMouseX() - 0.5);
+    dragAccum = 0;
+
+    mouseTracker.setVertDragLock(true);
+    downIndex = hoverIndex;
+    downState = hoverState;
+}
+
+void TuningCanvas::OnMouseWheelMoved(wxMouseEvent& event) {
+    InteractiveCanvas::OnMouseWheelMoved(event);
+
+    int hExponent = hoverIndex - 1;
+
+    if (hoverState != TUNING_HOVER_NONE && !mouseTracker.mouseDown() && hoverIndex) {
+        if (event.m_wheelAxis == wxMOUSE_WHEEL_VERTICAL) {
+            StepTuner(hoverState, hExponent, (event.m_wheelRotation > 0)?true:false);
+        } else {
+            StepTuner(hoverState, hExponent, (event.m_wheelRotation < 0)?true:false);
+        }
+    }
+}
+
+void TuningCanvas::OnMouseReleased(wxMouseEvent& event) {
+    InteractiveCanvas::OnMouseReleased(event);
+
+    int hExponent = hoverIndex - 1;
+
+    if (hoverState != TUNING_HOVER_NONE && !dragging && (downState == hoverState) && (downIndex == hoverIndex)) {
+        StepTuner(hoverState, hExponent, top);
+    }
+
+    mouseTracker.setVertDragLock(false);
+
+    dragging = false;
+    SetCursor(wxCURSOR_ARROW);
+}
+
+void TuningCanvas::OnMouseRightDown(wxMouseEvent& event) {
+    InteractiveCanvas::OnMouseRightDown(event);
+}
+
+void TuningCanvas::OnMouseRightReleased(wxMouseEvent& event) {
+    InteractiveCanvas::OnMouseRightReleased(event);
+
+    if (hoverState == TUNING_HOVER_FREQ) {
+        if (hoverIndex == 1) {
+            wxGetApp().setFrequencySnap(1);
+        } else if (hoverIndex > 1 && hoverIndex < 8) {
+            int exp = pow(10, hoverIndex-1);
+            if (wxGetApp().getFrequencySnap() == exp) {
+                wxGetApp().setFrequencySnap(1);
+            } else {
+                wxGetApp().setFrequencySnap(exp);
+            }
+        }
+    }
+}
+
+void TuningCanvas::OnMouseLeftWindow(wxMouseEvent& event) {
+    InteractiveCanvas::OnMouseLeftWindow(event);
+    SetCursor(wxCURSOR_CROSS);
+    hoverIndex = 0;
+    hoverState = TUNING_HOVER_NONE;
+    wxGetApp().getDemodMgr().setActiveDemodulator(NULL);
+
+    if (currentPPM != lastPPM) {
+        wxGetApp().saveConfig();
+    }
+    Refresh();
+}
+
+void TuningCanvas::OnMouseEnterWindow(wxMouseEvent& event) {
+    InteractiveCanvas::mouseTracker.OnMouseEnterWindow(event);
+    SetCursor(wxCURSOR_ARROW);
+    hoverIndex = 0;
+    hoverState = TUNING_HOVER_NONE;
+    lastPPM = currentPPM = wxGetApp().getPPM();
+#ifdef _WIN32
+	if (wxGetApp().getAppFrame()->canFocus()) {
+		this->SetFocus();
+	}
+#endif
+}
+
+void TuningCanvas::setHelpTip(std::string tip) {
+    helpTip = tip;
+}
+
+void TuningCanvas::OnKeyDown(wxKeyEvent& event) {
+    InteractiveCanvas::OnKeyDown(event);
+
+    if (event.GetKeyCode() == WXK_SPACE) {
+        if (hoverState == TUNING_HOVER_CENTER || hoverState == TUNING_HOVER_FREQ) {
+            wxGetApp().showFrequencyInput(FrequencyDialog::FDIALOG_TARGET_DEFAULT);
+        } else if (hoverState == TUNING_HOVER_BW) {
+            wxGetApp().showFrequencyInput(FrequencyDialog::FDIALOG_TARGET_BANDWIDTH);
+        }
+    } 
+}
+
+void TuningCanvas::OnKeyUp(wxKeyEvent& event) {
+    InteractiveCanvas::OnKeyUp(event);
+}
+
+TuningCanvas::ActiveState TuningCanvas::getHoverState() {
+    return hoverState;
+}
diff --git a/src/visual/TuningCanvas.h b/src/visual/TuningCanvas.h
new file mode 100644
index 0000000..eae7083
--- /dev/null
+++ b/src/visual/TuningCanvas.h
@@ -0,0 +1,78 @@
+#pragma once
+
+#include "wx/glcanvas.h"
+#include "wx/timer.h"
+
+#include <vector>
+#include <queue>
+
+#include "InteractiveCanvas.h"
+#include "TuningContext.h"
+#include "MouseTracker.h"
+
+#include "Timer.h"
+
+class TuningCanvas: public InteractiveCanvas {
+public:
+    enum ActiveState {
+        TUNING_HOVER_NONE, TUNING_HOVER_FREQ, TUNING_HOVER_BW, TUNING_HOVER_PPM, TUNING_HOVER_CENTER
+    };
+    TuningCanvas(wxWindow *parent, int *dispAttrs);
+    ~TuningCanvas();
+
+    void setHelpTip(std::string tip);
+    bool changed();
+    
+    void setHalfBand(bool hb);
+    void OnKeyDown(wxKeyEvent& event);
+    void OnKeyUp(wxKeyEvent& event);
+    
+    ActiveState getHoverState();
+    
+private:
+    void OnPaint(wxPaintEvent& event);
+    void OnIdle(wxIdleEvent &event);
+
+    void OnMouseMoved(wxMouseEvent& event);
+    void OnMouseDown(wxMouseEvent& event);
+    void OnMouseWheelMoved(wxMouseEvent& event);
+    void OnMouseReleased(wxMouseEvent& event);
+    void OnMouseEnterWindow(wxMouseEvent& event);
+    void OnMouseLeftWindow(wxMouseEvent& event);
+    void OnMouseRightDown(wxMouseEvent& event);
+    void OnMouseRightReleased(wxMouseEvent& event);
+
+    void StepTuner(ActiveState state, int factor, bool up = true);
+
+    TuningContext *glContext;
+
+    std::string helpTip;
+    float dragAccum;
+    float uxDown;
+    ActiveState hoverState;
+    ActiveState downState;
+    int hoverIndex;
+    int downIndex;
+    bool dragging;
+
+    float freqDP;
+    float freqW;
+
+    float bwDP;
+    float bwW;
+
+    float centerDP;
+    float centerW;
+
+    bool top;
+    bool bottom;
+
+    int currentPPM;
+    int lastPPM;
+
+    long long freq, bw, center;
+    bool halfBand;
+    //
+wxDECLARE_EVENT_TABLE();
+};
+
diff --git a/src/visual/TuningContext.cpp b/src/visual/TuningContext.cpp
new file mode 100644
index 0000000..58f49de
--- /dev/null
+++ b/src/visual/TuningContext.cpp
@@ -0,0 +1,196 @@
+#include "TuningContext.h"
+#include "TuningCanvas.h"
+
+#include "ColorTheme.h"
+
+// http://stackoverflow.com/questions/7276826/c-format-number-with-commas
+class comma_numpunct: public std::numpunct<char> {
+protected:
+    virtual char do_thousands_sep() const {
+        return ',';
+    }
+
+    virtual std::string do_grouping() const {
+        return "\03";
+    }
+};
+
+TuningContext::TuningContext(TuningCanvas *canvas, wxGLContext *sharedContext) :
+        PrimaryGLContext(canvas, sharedContext) {
+    glDisable(GL_CULL_FACE);
+    glDisable(GL_DEPTH_TEST);
+
+    glMatrixMode(GL_PROJECTION);
+    glLoadIdentity();
+
+    comma_locale = std::locale(std::locale(), new comma_numpunct());
+    freqStrFormatted.imbue(comma_locale);
+}
+
+void TuningContext::DrawBegin() {
+    glClearColor(ThemeMgr::mgr.currentTheme->generalBackground.r, ThemeMgr::mgr.currentTheme->generalBackground.g,
+            ThemeMgr::mgr.currentTheme->generalBackground.b, 1.0);
+    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+    glMatrixMode(GL_MODELVIEW);
+    glLoadIdentity();
+
+    glDisable(GL_TEXTURE_2D);
+}
+
+void TuningContext::Draw(float r, float g, float b, float a, float p1, float p2) {
+    glEnable(GL_BLEND);
+    glBlendFunc(GL_ONE, GL_ONE);
+    glBegin(GL_QUADS);
+    glColor4f(r * 0.5, g * 0.5, b * 0.5, a);
+    glVertex2f(-1.0 + p2 * 2.0, 1.0);
+    glVertex2f(-1.0 + p1 * 2.0, 1.0);
+    glColor4f(r, g, b, a);
+    glVertex2f(-1.0 + p1 * 2.0, 0.0);
+    glVertex2f(-1.0 + p2 * 2.0, 0.0);
+
+    glVertex2f(-1.0 + p2 * 2.0, 0.0);
+    glVertex2f(-1.0 + p1 * 2.0, 0.0);
+    glColor4f(r * 0.5, g * 0.5, b * 0.5, a);
+    glVertex2f(-1.0 + p1 * 2.0, -1.0);
+    glVertex2f(-1.0 + p2 * 2.0, -1.0);
+    glEnd();
+    glDisable(GL_BLEND);
+}
+
+void TuningContext::DrawEnd() {
+//    glFlush();
+
+//    CheckGLError();
+}
+
+void TuningContext::DrawTuner(long long freq, int count, float displayPos, float displayWidth) {
+    GLint vp[4];
+    glGetIntegerv( GL_VIEWPORT, vp);
+
+    float viewWidth = (float) vp[2];
+    float viewHeight = (float) vp[3];
+
+    freqStr.str("");
+    freqStr << freq;
+    std::string freqChars = freqStr.str();
+
+    int fontSize = 32;
+
+    if (viewWidth < 300) {
+        fontSize = 18;
+    } else if (viewWidth < 500) {
+        fontSize = 24;
+    }
+    
+    if (viewHeight < 18) {
+        fontSize = 12;
+    } else if (viewHeight < 24) {
+        fontSize = 16;
+    } else if (viewHeight < 28) {
+        fontSize = 18;
+    }
+
+    glColor3f(ThemeMgr::mgr.currentTheme->text.r, ThemeMgr::mgr.currentTheme->text.g, ThemeMgr::mgr.currentTheme->text.b);
+    int numChars = freqChars.length();
+    int ofs = count - numChars;
+
+    //do not zoom this one:
+    GLFont::Drawer refDrawingFont = GLFont::getFont(fontSize);
+
+    for (int i = ofs; i < count; i++) {
+        float xpos = displayPos + (displayWidth / (float) count) * (float) i + ((displayWidth / 2.0) / (float) count);
+        refDrawingFont.drawString(freqStr.str().substr(i - ofs, 1), xpos, 0, GLFont::GLFONT_ALIGN_CENTER, GLFont::GLFONT_ALIGN_CENTER);
+    }
+
+    glColor4f(0.65f, 0.65f, 0.65f, 0.25f);
+    glEnable(GL_BLEND);
+    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+    glBegin(GL_LINES);
+    for (int i = count; i >= 0; i--) {
+        float xpos = displayPos + (displayWidth / (float) count) * (float) i;
+        glVertex2f(xpos, -1.0);
+        glVertex2f(xpos, 1.0);
+    }
+    glEnd();
+    glDisable(GL_BLEND);
+}
+
+
+void TuningContext::DrawTunerDigitBox(int index, int count, float displayPos, float displayWidth, RGBA4f /* c */) {
+    GLint vp[4];
+    glGetIntegerv( GL_VIEWPORT, vp);
+
+    float viewHeight = (float) vp[3];
+    float pixelHeight = 2.0/viewHeight;
+
+    glColor4f(1.0, 0,0,1);
+    glEnable(GL_BLEND);
+    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+    float xpos = displayPos + (displayWidth / (float) count) * (float) (count-index);
+    float xpos2 = displayPos + (displayWidth / (float) count) * (float) ((count-1)-index);
+    glBegin(GL_LINE_STRIP);
+    glVertex2f(xpos, 1.0-pixelHeight);
+    glVertex2f(xpos, -1.0+pixelHeight);
+    glVertex2f(xpos2, -1.0+pixelHeight);
+    glVertex2f(xpos2, 1.0-pixelHeight);
+    glVertex2f(xpos, 1.0-pixelHeight);
+    glEnd();
+    glDisable(GL_BLEND);
+}
+
+
+
+
+int TuningContext::GetTunerDigitIndex(float mPos, int count, float displayPos, float displayWidth) {
+    mPos -= 0.5;
+    mPos *= 2.0;
+
+    float delta = mPos - displayPos;
+
+    if (delta < 0 || delta > displayWidth) {
+        return 0;
+    }
+
+    int index = floor((delta / displayWidth) * (count));
+
+    return count - index;
+}
+
+void TuningContext::DrawTunerBarIndexed(int start, int end, int count, float displayPos, float displayWidth, RGBA4f color, float /* alpha */, bool top,
+bool bottom) {
+    float ofs = (displayWidth / (float) count);
+    float p2 = displayPos + ofs * (float) (count - start + 1);
+    float p1 = displayPos + ofs * (float) (count - end);
+
+    float r = color.r, g = color.g, b = color.b, a = 0.6f;
+
+    glEnable(GL_BLEND);
+    glBlendFunc(GL_ONE, GL_ONE);
+    glBegin(GL_QUADS);
+    if (top) {
+        glColor4f(r * 0.5, g * 0.5, b * 0.5, a);
+        glVertex2f(p2, 1.0);
+        glVertex2f(p1, 1.0);
+        glColor4f(r, g, b, a);
+        glVertex2f(p1, 0.0);
+        glVertex2f(p2, 0.0);
+    }
+    if (bottom) {
+        glColor4f(r, g, b, a);
+        glVertex2f(p2, 0.0);
+        glVertex2f(p1, 0.0);
+        glColor4f(r * 0.5, g * 0.5, b * 0.5, a);
+        glVertex2f(p1, -1.0);
+        glVertex2f(p2, -1.0);
+    }
+    glEnd();
+    glDisable(GL_BLEND);
+}
+
+void TuningContext::DrawDemodFreqBw(long long freq, unsigned int bw, long long center) {
+    DrawTuner(freq, 11, -1.0, (1.0f / 3.0f) * 2.0f);
+    DrawTuner(bw, 7, -1.0 + (2.25f / 3.0f), (1.0f / 4.0f) * 2.0f);
+    DrawTuner(center, 11, -1.0 + (2.0f / 3.0f) * 2.0, (1.0f / 3.0f) * 2.0f);
+}
+
diff --git a/src/visual/TuningContext.h b/src/visual/TuningContext.h
new file mode 100644
index 0000000..3bbec2f
--- /dev/null
+++ b/src/visual/TuningContext.h
@@ -0,0 +1,28 @@
+#pragma once
+
+#include "PrimaryGLContext.h"
+#include "Gradient.h"
+
+#define NUM_WATERFALL_LINES 512
+
+class TuningCanvas;
+
+class TuningContext: public PrimaryGLContext {
+public:
+    TuningContext(TuningCanvas *canvas, wxGLContext *sharedContext);
+
+    void DrawBegin();
+    void Draw(float r, float g, float b, float a, float p1, float p2);
+    void DrawTuner(long long freq, int count, float displayPos, float displayWidth);
+    void DrawTunerDigitBox(int index, int count, float displayPos, float displayWidth, RGBA4f c);
+    int GetTunerDigitIndex(float mPos, int count, float displayPos, float displayWidth);
+    void DrawTunerBarIndexed(int start, int end, int count, float displayPos, float displayWidth, RGBA4f color, float alpha, bool top, bool bottom);
+
+    void DrawDemodFreqBw(long long freq, unsigned int bw, long long center);
+    void DrawEnd();
+
+private:
+    std::locale comma_locale;
+    std::stringstream freqStr;
+    std::stringstream freqStrFormatted;
+};
diff --git a/src/visual/WaterfallCanvas.cpp b/src/visual/WaterfallCanvas.cpp
new file mode 100644
index 0000000..69fe127
--- /dev/null
+++ b/src/visual/WaterfallCanvas.cpp
@@ -0,0 +1,931 @@
+#include "WaterfallCanvas.h"
+
+#include "wx/wxprec.h"
+
+#ifndef WX_PRECOMP
+#include "wx/wx.h"
+#endif
+
+#if !wxUSE_GLCANVAS
+#error "OpenGL required: set wxUSE_GLCANVAS to 1 and rebuild the library"
+#endif
+
+#include "CubicSDR.h"
+#include "CubicSDRDefs.h"
+#include "AppFrame.h"
+#include <algorithm>
+
+#ifdef USE_HAMLIB
+#include "RigThread.h"
+#endif
+
+#include <wx/numformatter.h>
+
+wxBEGIN_EVENT_TABLE(WaterfallCanvas, wxGLCanvas)
+EVT_PAINT(WaterfallCanvas::OnPaint)
+EVT_IDLE(WaterfallCanvas::OnIdle)
+EVT_MOTION(WaterfallCanvas::OnMouseMoved)
+EVT_LEFT_DOWN(WaterfallCanvas::OnMouseDown)
+EVT_LEFT_UP(WaterfallCanvas::OnMouseReleased)
+EVT_RIGHT_DOWN(WaterfallCanvas::OnMouseRightDown)
+EVT_RIGHT_UP(WaterfallCanvas::OnMouseRightReleased)
+EVT_LEAVE_WINDOW(WaterfallCanvas::OnMouseLeftWindow)
+EVT_ENTER_WINDOW(WaterfallCanvas::OnMouseEnterWindow)
+EVT_MOUSEWHEEL(WaterfallCanvas::OnMouseWheelMoved)
+wxEND_EVENT_TABLE()
+
+WaterfallCanvas::WaterfallCanvas(wxWindow *parent, int *dispAttrs) :
+        InteractiveCanvas(parent, dispAttrs), dragState(WF_DRAG_NONE), nextDragState(WF_DRAG_NONE), fft_size(0), new_fft_size(0), waterfall_lines(0),
+        dragOfs(0), mouseZoom(1), zoom(1), freqMoving(false), freqMove(0.0), hoverAlpha(1.0) {
+
+    glContext = new PrimaryGLContext(this, &wxGetApp().GetContext(this));
+    linesPerSecond = 30;
+    lpsIndex = 0;
+    preBuf = false;
+    SetCursor(wxCURSOR_CROSS);
+    scaleMove = 0;
+    minBandwidth = 30000;
+    fft_size_changed.store(false);
+}
+
+WaterfallCanvas::~WaterfallCanvas() {
+}
+
+void WaterfallCanvas::setup(unsigned int fft_size_in, int waterfall_lines_in) {
+    if (fft_size == fft_size_in && waterfall_lines_in == waterfall_lines) {
+        return;
+    }
+    fft_size = fft_size_in;
+    waterfall_lines = waterfall_lines_in;
+
+    waterfallPanel.setup(fft_size, waterfall_lines);
+    gTimer.start();
+}
+
+void WaterfallCanvas::setFFTSize(unsigned int fft_size_in) {
+    if (fft_size_in == fft_size) {
+        return;
+    }
+    new_fft_size = fft_size_in;
+    fft_size_changed.store(true);
+}
+
+WaterfallCanvas::DragState WaterfallCanvas::getDragState() {
+    return dragState;
+}
+
+WaterfallCanvas::DragState WaterfallCanvas::getNextDragState() {
+    return nextDragState;
+}
+
+void WaterfallCanvas::attachSpectrumCanvas(SpectrumCanvas *canvas_in) {
+    spectrumCanvas = canvas_in;
+}
+
+void WaterfallCanvas::processInputQueue() {
+    std::lock_guard < std::mutex > lock(tex_update);
+    
+    gTimer.update();
+    
+    double targetVis =  1.0 / (double)linesPerSecond;
+    lpsIndex += gTimer.lastUpdateSeconds();
+
+    bool updated = false;
+    if (linesPerSecond) {
+        if (lpsIndex >= targetVis) {
+            while (lpsIndex >= targetVis) {
+                SpectrumVisualData *vData;
+
+                if (visualDataQueue.try_pop(vData)) {
+                    
+                    if (vData) {
+                        if (vData->spectrum_points.size() == fft_size * 2) {
+                            waterfallPanel.setPoints(vData->spectrum_points);
+                        }
+                        waterfallPanel.step();
+                        vData->decRefCount();
+                        updated = true;
+                    }
+                    lpsIndex-=targetVis;
+                } else {
+                	break;
+                }
+            }
+        }
+    }
+    if (updated) {
+        wxClientDC(this);
+        glContext->SetCurrent(*this);
+        waterfallPanel.update();
+    }
+   
+}
+
+void WaterfallCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) {
+    std::lock_guard < std::mutex > lock(tex_update);
+    wxPaintDC dc(this);
+    
+    const wxSize ClientSize = GetClientSize();
+    long double currentZoom = zoom;
+    
+    if (mouseZoom != 1) {
+        currentZoom = mouseZoom;
+        mouseZoom = mouseZoom + (1.0 - mouseZoom) * 0.2;
+        if (fabs(mouseZoom-1.0)<0.01) {
+            mouseZoom = 1;
+        }
+    }
+    
+    if (scaleMove != 0) {
+        SpectrumVisualProcessor *sp = wxGetApp().getSpectrumProcessor();
+        FFTVisualDataThread *wdt = wxGetApp().getAppFrame()->getWaterfallDataThread();
+        SpectrumVisualProcessor *wp = wdt->getProcessor();
+        float factor = sp->getScaleFactor();
+
+        factor += scaleMove * 0.02;
+        
+        if (factor < 0.25) {
+            factor = 0.25;
+        }
+        if (factor > 10.0) {
+            factor = 10.0;
+        }
+        
+        sp->setScaleFactor(factor);
+        wp->setScaleFactor(factor);
+    }
+    
+    if (freqMove != 0.0) {
+        long long newFreq = getCenterFrequency() + (long long)((long double)getBandwidth()*freqMove) * 0.01;
+        
+        long long minFreq = bandwidth/2;
+        if (newFreq < minFreq) {
+            newFreq = minFreq;
+        }
+
+        updateCenterFrequency(newFreq);
+        
+        if (!freqMoving) {
+            freqMove -= (freqMove * 0.2);
+            if (fabs(freqMove) < 0.01) {
+                freqMove = 0.0;
+            }
+        }
+    }
+    
+    long long bw;
+    if (currentZoom != 1) {
+        long long freq = wxGetApp().getFrequency();
+        bw = getBandwidth();
+
+        double mpos = 0;
+        float mouseInView = false;
+
+        if (mouseTracker.mouseInView()) {
+            mpos = mouseTracker.getMouseX();
+            mouseInView = true;
+        } else if (spectrumCanvas && spectrumCanvas->getMouseTracker()->mouseInView()) {
+            mpos = spectrumCanvas->getMouseTracker()->getMouseX();
+            mouseInView = true;
+        }
+
+        if (currentZoom < 1) {
+            bw = (long long) ceil((long double) bw * currentZoom);
+            if (bw < minBandwidth) {
+                bw = minBandwidth;
+            }
+            if (mouseInView) {
+                long long mfreqA = getFrequencyAt(mpos, centerFreq, getBandwidth());
+                long long mfreqB = getFrequencyAt(mpos, centerFreq, bw);
+                centerFreq += mfreqA - mfreqB;
+            }
+            
+            setView(centerFreq, bw);
+        } else {
+            if (isView) {
+                bw = (long long) ceil((long double) bw * currentZoom);
+
+                if (bw >= wxGetApp().getSampleRate()) {
+                    disableView();
+                    if (spectrumCanvas) {
+                        spectrumCanvas->disableView();
+                    }
+                    bw = wxGetApp().getSampleRate();
+                    centerFreq = wxGetApp().getFrequency();
+                } else {
+                    if (mouseInView) {
+                        long long mfreqA = getFrequencyAt(mpos, centerFreq, getBandwidth());
+                        long long mfreqB = getFrequencyAt(mpos, centerFreq, bw);
+                        centerFreq += mfreqA - mfreqB;
+                        setBandwidth(bw);
+                    } else {
+                        setBandwidth(bw);
+                    }
+                }
+            }
+        }
+        if (centerFreq < freq && (centerFreq - bandwidth / 2) < (freq - wxGetApp().getSampleRate() / 2)) {
+            centerFreq = (freq - wxGetApp().getSampleRate() / 2) + bandwidth / 2;
+        }
+        if (centerFreq > freq && (centerFreq + bandwidth / 2) > (freq + wxGetApp().getSampleRate() / 2)) {
+            centerFreq = (freq + wxGetApp().getSampleRate() / 2) - bandwidth / 2;
+        }
+
+        if (spectrumCanvas) {
+            if ((spectrumCanvas->getCenterFrequency() != centerFreq) || (spectrumCanvas->getBandwidth() != bw)) {
+                if (getViewState()) {
+                    spectrumCanvas->setView(centerFreq,bw);
+                } else {
+                    spectrumCanvas->disableView();
+                    spectrumCanvas->setCenterFrequency(centerFreq);
+                    spectrumCanvas->setBandwidth(bw);
+                }
+            }
+        }
+    }
+    
+
+    glContext->SetCurrent(*this);
+    initGLExtensions();
+    glViewport(0, 0, ClientSize.x, ClientSize.y);
+    
+    if (fft_size_changed.load()) {
+        fft_size = new_fft_size;
+        waterfallPanel.setup(fft_size, waterfall_lines);
+        fft_size_changed.store(false);
+    }
+
+    glContext->BeginDraw(0,0,0);
+
+    waterfallPanel.calcTransform(CubicVR::mat4::identity());
+    waterfallPanel.draw();
+
+    std::vector<DemodulatorInstance *> &demods = wxGetApp().getDemodMgr().getDemodulators();
+
+    DemodulatorInstance *activeDemodulator = wxGetApp().getDemodMgr().getActiveDemodulator();
+    DemodulatorInstance *lastActiveDemodulator = wxGetApp().getDemodMgr().getLastActiveDemodulator();
+
+    bool isNew = shiftDown
+            || (wxGetApp().getDemodMgr().getLastActiveDemodulator() && !wxGetApp().getDemodMgr().getLastActiveDemodulator()->isActive());
+
+    int currentBandwidth = getBandwidth();
+    long long currentCenterFreq = getCenterFrequency();
+
+    ColorTheme *currentTheme = ThemeMgr::mgr.currentTheme;
+    std::string last_type = wxGetApp().getDemodMgr().getLastDemodulatorType();
+
+    if (mouseTracker.mouseInView() || wxGetApp().getDemodMgr().getActiveDemodulator()) {
+        hoverAlpha += (1.0f-hoverAlpha)*0.1f;
+        if (hoverAlpha > 1.5f) {
+            hoverAlpha = 1.5f;
+        }
+        glContext->setHoverAlpha(hoverAlpha);
+        if (nextDragState == WF_DRAG_RANGE) {
+            float width = (1.0 / (float) ClientSize.x);
+            float rangeWidth = mouseTracker.getOriginDeltaMouseX();
+            float centerPos;
+
+            if (mouseTracker.mouseDown()) {
+                if (rangeWidth) {
+                    width = rangeWidth;
+                }
+                centerPos = mouseTracker.getOriginMouseX() + width / 2.0;
+            } else {
+                centerPos = mouseTracker.getMouseX();
+            }
+
+            glContext->DrawDemod(lastActiveDemodulator, isNew?currentTheme->waterfallHighlight:currentTheme->waterfallDestroy, currentCenterFreq, currentBandwidth);
+
+            if ((last_type == "LSB" || last_type == "USB") && mouseTracker.mouseDown()) {
+                centerPos = mouseTracker.getMouseX();
+                glContext->DrawRangeSelector(centerPos, centerPos-width, isNew?currentTheme->waterfallNew:currentTheme->waterfallHover);
+            } else {
+                glContext->DrawFreqSelector(centerPos, isNew?currentTheme->waterfallNew:currentTheme->waterfallHover, width, currentCenterFreq, currentBandwidth);
+            }
+        } else {
+            if (lastActiveDemodulator) {
+                glContext->DrawDemod(lastActiveDemodulator, ((isNew && activeDemodulator == NULL) || (activeDemodulator != NULL))?currentTheme->waterfallHighlight:currentTheme->waterfallDestroy, currentCenterFreq, currentBandwidth);
+            }
+            if (activeDemodulator == NULL) {
+                glContext->DrawFreqSelector(mouseTracker.getMouseX(), ((isNew && lastActiveDemodulator) || (!lastActiveDemodulator) )?currentTheme->waterfallNew:currentTheme->waterfallHover, 0, currentCenterFreq, currentBandwidth);
+            } else {
+                glContext->DrawDemod(activeDemodulator, currentTheme->waterfallHover, currentCenterFreq, currentBandwidth);
+            }
+        }
+    } else {
+        hoverAlpha += (0.0f-hoverAlpha)*0.05f;
+        if (hoverAlpha < 1.0e-5f) {
+            hoverAlpha = 0;
+        }
+        glContext->setHoverAlpha(hoverAlpha);
+        if (activeDemodulator) {
+            glContext->DrawDemod(activeDemodulator, currentTheme->waterfallHighlight, currentCenterFreq, currentBandwidth);
+        }
+        if (lastActiveDemodulator) {
+            glContext->DrawDemod(lastActiveDemodulator, currentTheme->waterfallHighlight, currentCenterFreq, currentBandwidth);
+        }
+    }
+
+    glContext->setHoverAlpha(0);
+
+    for (int i = 0, iMax = demods.size(); i < iMax; i++) {
+        if (!demods[i]->isActive()) {
+            continue;
+        }
+        if (activeDemodulator == demods[i] || lastActiveDemodulator == demods[i]) {
+            continue;
+        }
+        glContext->DrawDemod(demods[i], currentTheme->waterfallHighlight, currentCenterFreq, currentBandwidth);
+    }
+
+    for (int i = 0, iMax = demods.size(); i < iMax; i++) {
+        demods[i]->getVisualCue()->step();
+
+        int squelchBreak = demods[i]->getVisualCue()->getSquelchBreak();
+        if (squelchBreak) {
+            glContext->setHoverAlpha((float(squelchBreak) / 60.0));
+            glContext->DrawDemod(demods[i], currentTheme->waterfallHover, currentCenterFreq, currentBandwidth);
+        }
+    }
+    
+    glContext->EndDraw();
+
+    SwapBuffers();
+}
+
+void WaterfallCanvas::OnKeyUp(wxKeyEvent& event) {
+    InteractiveCanvas::OnKeyUp(event);
+    shiftDown = event.ShiftDown();
+    altDown = event.AltDown();
+    ctrlDown = event.ControlDown();
+    switch (event.GetKeyCode()) {
+    case WXK_UP:
+    case WXK_NUMPAD_UP:
+            scaleMove = 0.0;
+            zoom = 1.0;
+            if (mouseZoom != 1.0) {
+                mouseZoom = 0.95f;
+            }
+        break;
+    case WXK_DOWN:
+    case WXK_NUMPAD_DOWN:
+            scaleMove = 0.0;
+            zoom = 1.0;
+            if (mouseZoom != 1.0) {
+                mouseZoom = 1.05f;
+            }
+        break;
+    case WXK_LEFT:
+    case WXK_NUMPAD_LEFT:
+    case WXK_RIGHT:
+    case WXK_NUMPAD_RIGHT:
+            freqMoving = false;
+            break;
+    }
+}
+
+void WaterfallCanvas::OnKeyDown(wxKeyEvent& event) {
+    InteractiveCanvas::OnKeyDown(event);
+
+    DemodulatorInstance *activeDemod = wxGetApp().getDemodMgr().getActiveDemodulator();
+
+    long long originalFreq = getCenterFrequency();
+    long long freq = originalFreq;
+
+    switch (event.GetKeyCode()) {
+    case WXK_UP:
+    case WXK_NUMPAD_UP:
+            if (!shiftDown) {
+                mouseZoom = 1.0;
+                zoom = 0.95f;
+            } else {
+                scaleMove = 1.0;
+            }
+        break;
+    case WXK_DOWN:
+    case WXK_NUMPAD_DOWN:
+            if (!shiftDown) {
+                mouseZoom = 1.0;
+                zoom = 1.05f;
+            } else {
+                scaleMove = -1.0;
+            }
+        break;
+    case WXK_RIGHT:
+    case WXK_NUMPAD_RIGHT:
+        if (isView) {
+            freqMove = shiftDown?5.0:1.0;
+            freqMoving = true;
+        } else {
+            freq += shiftDown?(getBandwidth() * 10):(getBandwidth() / 2);
+        }
+        break;
+    case WXK_LEFT:
+    case WXK_NUMPAD_LEFT:
+        if (isView) {
+            freqMove = shiftDown?-5.0:-1.0;
+            freqMoving = true;
+        } else {
+            freq -= shiftDown?(getBandwidth() * 10):(getBandwidth() / 2);
+        }
+        break;
+    case 'D':
+    case WXK_DELETE:
+        if (!activeDemod) {
+            break;
+        }
+        wxGetApp().removeDemodulator(activeDemod);
+        wxGetApp().getDemodMgr().deleteThread(activeDemod);
+        break;
+    case 'B':
+        if (spectrumCanvas) {
+            spectrumCanvas->setShowDb(!spectrumCanvas->getShowDb());
+        }
+        break;
+    case WXK_SPACE:
+        wxGetApp().showFrequencyInput();
+        break;
+    case 'E': //E is for 'Edit the label' of the active demodulator. 
+        wxGetApp().showLabelInput();
+        break;
+    case 'C':
+        if (wxGetApp().getDemodMgr().getActiveDemodulator()) {
+            wxGetApp().setFrequency(wxGetApp().getDemodMgr().getActiveDemodulator()->getFrequency());
+        } else if (mouseTracker.mouseInView()) {
+            long long freq = getFrequencyAt(mouseTracker.getMouseX());
+            
+            int snap = wxGetApp().getFrequencySnap();
+            
+            if (snap > 1) {
+                freq = roundf((float)freq/(float)snap)*snap;
+            }
+            
+            wxGetApp().setFrequency(freq);
+        }
+#ifdef USE_HAMLIB
+        if (wxGetApp().rigIsActive() && (!wxGetApp().getRigThread()->getControlMode() || wxGetApp().getRigThread()->getCenterLock())) {
+            wxGetApp().getRigThread()->setFrequency(wxGetApp().getFrequency(),true);
+        }
+#endif
+        break;
+    default:
+        event.Skip();
+        return;
+    }
+
+    long long minFreq = bandwidth/2;
+    if (freq < minFreq) {
+        freq = minFreq;
+    }
+
+    if (freq != originalFreq) {
+        updateCenterFrequency(freq);
+    }
+
+}
+void WaterfallCanvas::OnIdle(wxIdleEvent &event) {
+    processInputQueue();
+    Refresh();
+    event.RequestMore();
+}
+
+void WaterfallCanvas::updateHoverState() {
+    long long freqPos = getFrequencyAt(mouseTracker.getMouseX());
+    
+    std::vector<DemodulatorInstance *> demodsHover = wxGetApp().getDemodMgr().getDemodulatorsAt(freqPos, 15000);
+    
+    wxGetApp().getDemodMgr().setActiveDemodulator(NULL);
+    
+    if (altDown) {
+        nextDragState = WF_DRAG_RANGE;
+        mouseTracker.setVertDragLock(true);
+        mouseTracker.setHorizDragLock(false);
+        if (shiftDown) {
+            setStatusText("Click and drag to create a new demodulator by range.");
+        } else {
+            setStatusText("Click and drag to set the current demodulator range.");
+        }
+    } else if (demodsHover.size() && !shiftDown) {
+        long near_dist = getBandwidth();
+        
+        DemodulatorInstance *activeDemodulator = NULL;
+        
+        for (int i = 0, iMax = demodsHover.size(); i < iMax; i++) {
+            DemodulatorInstance *demod = demodsHover[i];
+            long long freqDiff = demod->getFrequency() - freqPos;
+            long halfBw = (demod->getBandwidth() / 2);
+            long long currentBw = getBandwidth();
+            long long globalBw = wxGetApp().getSampleRate();
+            long dist = abs(freqDiff);
+            double bufferBw = 10000.0 * ((double)currentBw / (double)globalBw);
+            double maxDist = ((double)halfBw + bufferBw);
+            
+            if ((double)dist <= maxDist) {
+                if ((freqDiff > 0 && demod->getDemodulatorType() == "USB") ||
+                    (freqDiff < 0 && demod->getDemodulatorType() == "LSB")) {
+                    continue;
+                }
+                
+                if (dist < near_dist) {
+                    activeDemodulator = demod;
+                    near_dist = dist;
+                }
+                
+                long edge_dist = abs(halfBw - dist);
+                if (edge_dist < near_dist) {
+                    activeDemodulator = demod;
+                    near_dist = edge_dist;
+                }
+            }
+        }
+        
+        if (activeDemodulator == NULL) {
+            nextDragState = WF_DRAG_NONE;
+            SetCursor(wxCURSOR_CROSS);
+            return;
+        }
+        
+        wxGetApp().getDemodMgr().setActiveDemodulator(activeDemodulator);
+        
+        long long freqDiff = activeDemodulator->getFrequency() - freqPos;
+        
+        if (abs(freqDiff) > (activeDemodulator->getBandwidth() / 3)) {
+            
+            if (freqDiff > 0) {
+                if (activeDemodulator->getDemodulatorType() != "USB") {
+                    nextDragState = WF_DRAG_BANDWIDTH_LEFT;
+                    SetCursor(wxCURSOR_SIZEWE);
+                }
+            } else {
+                if (activeDemodulator->getDemodulatorType() != "LSB") {
+                    nextDragState = WF_DRAG_BANDWIDTH_RIGHT;
+                    SetCursor(wxCURSOR_SIZEWE);
+                }
+            }
+            
+            mouseTracker.setVertDragLock(true);
+            mouseTracker.setHorizDragLock(false);
+            setStatusText("Click and drag to change demodulator bandwidth. SPACE or numeric key for direct frequency input. [, ] to nudge, M for mute, D to delete, C to center, E to edit label.");
+        } else {
+            SetCursor(wxCURSOR_SIZING);
+            nextDragState = WF_DRAG_FREQUENCY;
+            
+            mouseTracker.setVertDragLock(true);
+            mouseTracker.setHorizDragLock(false);
+            setStatusText("Click and drag to change demodulator frequency; SPACE or numeric key for direct input. [, ] to nudge, M for mute, D to delete, C to center, E to edit label.");
+        }
+    }
+    else {
+        SetCursor(wxCURSOR_CROSS);
+        nextDragState = WF_DRAG_NONE;
+        if (shiftDown) {
+            setStatusText("Click to create a new demodulator or hold ALT to drag range, SPACE or numeric key for direct center frequency input.");
+        }
+        else {
+            setStatusText(
+                "Click to set active demodulator frequency or hold ALT to drag range; hold SHIFT to create new.  Right drag or wheel to Zoom.  Arrow keys to navigate/zoom, C to center.");
+        }
+    }
+}
+
+void WaterfallCanvas::OnMouseMoved(wxMouseEvent& event) {
+    InteractiveCanvas::OnMouseMoved(event);
+    DemodulatorInstance *demod = wxGetApp().getDemodMgr().getActiveDemodulator();
+
+    if (mouseTracker.mouseDown()) {
+        if (demod == NULL) {
+            return;
+        }
+        if (dragState == WF_DRAG_BANDWIDTH_LEFT || dragState == WF_DRAG_BANDWIDTH_RIGHT) {
+
+            int bwDiff = (int) (mouseTracker.getDeltaMouseX() * (float) getBandwidth()) * 2;
+
+            if (dragState == WF_DRAG_BANDWIDTH_LEFT) {
+                bwDiff = -bwDiff;
+            }
+
+            int currentBW = dragBW;
+
+            currentBW = currentBW + bwDiff;
+            if (currentBW > CHANNELIZER_RATE_MAX) {
+                currentBW = CHANNELIZER_RATE_MAX;
+            }
+            if (currentBW < MIN_BANDWIDTH) {
+                currentBW = MIN_BANDWIDTH;
+            }
+
+            demod->setBandwidth(currentBW);
+            dragBW = currentBW;
+        }
+
+        if (dragState == WF_DRAG_FREQUENCY) {
+            long long bwTarget = getFrequencyAt(mouseTracker.getMouseX()) - dragOfs;
+            long long currentFreq = demod->getFrequency();
+            long long bwDiff = bwTarget - currentFreq;
+            int snap = wxGetApp().getFrequencySnap();
+
+            if (snap > 1) {
+                bwDiff = roundf((float)bwDiff/(float)snap)*snap;
+            }
+
+            if (bwDiff) {
+                demod->setFrequency(currentFreq + bwDiff);
+                if (demod->isDeltaLock()) {
+                    demod->setDeltaLockOfs(demod->getFrequency() - wxGetApp().getFrequency());
+                }
+                currentFreq = demod->getFrequency();
+                demod->updateLabel(currentFreq);
+            }
+        }
+    } else if (mouseTracker.mouseRightDown()) {
+        mouseZoom = mouseZoom + ((1.0 - (mouseTracker.getDeltaMouseY() * 4.0)) - mouseZoom) * 0.1;
+    } else {
+        updateHoverState();
+    }
+}
+
+void WaterfallCanvas::OnMouseDown(wxMouseEvent& event) {
+    InteractiveCanvas::OnMouseDown(event);
+
+    updateHoverState();
+    dragState = nextDragState;
+    wxGetApp().getDemodMgr().updateLastState();
+
+    if (dragState && dragState != WF_DRAG_RANGE) {
+        DemodulatorInstance *demod = wxGetApp().getDemodMgr().getActiveDemodulator();
+        if (demod) {
+            dragOfs = (long long) (mouseTracker.getMouseX() * (float) getBandwidth()) + getCenterFrequency() - (getBandwidth() / 2) - demod->getFrequency();
+            dragBW = demod->getBandwidth();
+        }
+        wxGetApp().getDemodMgr().setActiveDemodulator(wxGetApp().getDemodMgr().getActiveDemodulator(), false);
+    }
+}
+
+void WaterfallCanvas::OnMouseWheelMoved(wxMouseEvent& event) {
+    InteractiveCanvas::OnMouseWheelMoved(event);
+    float movement = (float)event.GetWheelRotation() / (float)event.GetLinesPerAction();
+
+    mouseZoom = 1.0f - movement/1000.0f;
+}
+
+void WaterfallCanvas::OnMouseReleased(wxMouseEvent& event) {
+    InteractiveCanvas::OnMouseReleased(event);
+    wxGetApp().getDemodMgr().updateLastState();
+
+    bool isNew = shiftDown || (wxGetApp().getDemodMgr().getLastActiveDemodulator() == NULL)
+            || (wxGetApp().getDemodMgr().getLastActiveDemodulator() && !wxGetApp().getDemodMgr().getLastActiveDemodulator()->isActive());
+
+    mouseTracker.setVertDragLock(false);
+    mouseTracker.setHorizDragLock(false);
+
+    DemodulatorInstance *demod = isNew?NULL:wxGetApp().getDemodMgr().getLastActiveDemodulator();
+    DemodulatorInstance *activeDemod = isNew?NULL:wxGetApp().getDemodMgr().getActiveDemodulator();
+
+    DemodulatorMgr *mgr = &wxGetApp().getDemodMgr();
+
+    if (mouseTracker.getOriginDeltaMouseX() == 0 && mouseTracker.getOriginDeltaMouseY() == 0) {
+        float pos = mouseTracker.getMouseX();
+        long long input_center_freq = getCenterFrequency();
+        long long freqTarget = input_center_freq - (long long) (0.5 * (float) getBandwidth()) + (long long) ((float) pos * (float) getBandwidth());
+        long long demodFreq = demod?demod->getFrequency():freqTarget;
+        long long bwDiff = freqTarget - demodFreq;
+        long long freq = demodFreq;
+
+         int snap = wxGetApp().getFrequencySnap();
+
+         if (snap > 1) {
+             if (demod) {
+                 bwDiff = roundf((double)bwDiff/(double)snap)*snap;
+                 freq += bwDiff;
+             } else {
+                 freq = roundl((long double)freq/(double)snap)*snap;
+             }
+         } else {
+             freq += bwDiff;
+         }
+
+
+        if (dragState == WF_DRAG_NONE) {
+            if (!isNew && wxGetApp().getDemodMgr().getDemodulators().size()) {
+                mgr->updateLastState();
+                demod = wxGetApp().getDemodMgr().getLastActiveDemodulator();
+            } else {
+                isNew = true;
+                demod = wxGetApp().getDemodMgr().newThread();
+                demod->setFrequency(freq);
+
+                demod->setDemodulatorType(mgr->getLastDemodulatorType());
+                demod->setBandwidth(mgr->getLastBandwidth());
+                demod->setSquelchLevel(mgr->getLastSquelchLevel());
+                demod->setSquelchEnabled(mgr->isLastSquelchEnabled());
+                demod->setGain(mgr->getLastGain());
+                demod->setMuted(mgr->isLastMuted());
+                if (mgr->getLastDeltaLock()) {
+                    demod->setDeltaLock(true);
+                    demod->setDeltaLockOfs(wxGetApp().getFrequency()-freq);
+                } else {
+                    demod->setDeltaLock(false);
+                }
+                demod->writeModemSettings(mgr->getLastModemSettings(mgr->getLastDemodulatorType()));
+                demod->run();
+
+                wxGetApp().bindDemodulator(demod);
+                DemodulatorThread::releaseSquelchLock(nullptr);
+            }
+
+            if (!demod) {
+                dragState = WF_DRAG_NONE;
+                return;
+            }
+
+            demod->updateLabel(freq);
+            demod->setFrequency(freq);
+            if (demod->isDeltaLock()) {
+                demod->setDeltaLockOfs(demod->getFrequency() - wxGetApp().getFrequency());
+            }
+  
+            if (isNew) {
+                setStatusText("New demodulator at frequency: %s", freq);
+            } else {
+                setStatusText("Moved demodulator to frequency: %s", freq);
+            }
+
+            wxGetApp().getDemodMgr().setActiveDemodulator(demod, false);
+          SetCursor(wxCURSOR_SIZING);
+            nextDragState = WF_DRAG_FREQUENCY;
+            mouseTracker.setVertDragLock(true);
+            mouseTracker.setHorizDragLock(false);
+        } else {
+            if (activeDemod) {
+                wxGetApp().getDemodMgr().setActiveDemodulator(activeDemod, false);
+                mgr->updateLastState();
+                activeDemod->setTracking(true);
+                nextDragState = WF_DRAG_FREQUENCY;
+            } else {
+                nextDragState = WF_DRAG_NONE;
+            }
+        }
+    } else if (dragState == WF_DRAG_RANGE) {
+        float width = mouseTracker.getOriginDeltaMouseX();
+
+        float pos;
+        std::string last_type = mgr->getLastDemodulatorType();
+
+        if (last_type == "LSB" || last_type == "USB") {
+            float pos1 = mouseTracker.getOriginMouseX();
+            float pos2 = mouseTracker.getMouseX();
+
+            if (pos2 < pos1) {
+                float tmp = pos1;
+                pos1 = pos2;
+                pos2 = tmp;
+            }
+
+            pos = (last_type == "LSB")?pos2:pos1;
+            width *= 2;
+        } else {
+            pos = mouseTracker.getOriginMouseX() + width / 2.0;
+        }
+
+        long long input_center_freq = getCenterFrequency();
+        long long freq = input_center_freq - (long long) (0.5 * (float) getBandwidth()) + (long long) ((float) pos * (float) getBandwidth());
+        unsigned int bw = (unsigned int) (fabs(width) * (float) getBandwidth());
+
+        if (bw < MIN_BANDWIDTH) {
+            bw = MIN_BANDWIDTH;
+        }
+
+        if (!bw) {
+            dragState = WF_DRAG_NONE;
+            return;
+        }
+
+        int snap = wxGetApp().getFrequencySnap();
+
+        if (snap > 1) {
+            freq = roundl((long double)freq/(double)snap)*snap;
+        }
+
+
+        if (!isNew && wxGetApp().getDemodMgr().getDemodulators().size()) {
+            mgr->updateLastState();
+            demod = wxGetApp().getDemodMgr().getLastActiveDemodulator();
+        } else {
+            demod = wxGetApp().getDemodMgr().newThread();
+            demod->setFrequency(freq);
+            demod->setDemodulatorType(mgr->getLastDemodulatorType());
+            demod->setBandwidth(bw);
+            demod->setSquelchLevel(mgr->getLastSquelchLevel());
+            demod->setSquelchEnabled(mgr->isLastSquelchEnabled());
+            demod->setGain(mgr->getLastGain());
+            demod->setMuted(mgr->isLastMuted());
+            if (mgr->getLastDeltaLock()) {
+                demod->setDeltaLock(true);
+                demod->setDeltaLockOfs(wxGetApp().getFrequency()-freq);
+            } else {
+                demod->setDeltaLock(false);
+            }
+            demod->writeModemSettings(mgr->getLastModemSettings(mgr->getLastDemodulatorType()));
+
+            demod->run();
+
+            wxGetApp().bindDemodulator(demod);
+        }
+
+        if (demod == NULL) {
+            dragState = WF_DRAG_NONE;
+            return;
+        }
+
+        setStatusText("New demodulator at frequency: %s", freq);
+
+        demod->updateLabel(freq);
+        demod->setFrequency(freq);
+        demod->setBandwidth(bw);
+        mgr->setActiveDemodulator(demod, false);
+        mgr->updateLastState();
+    }
+
+    dragState = WF_DRAG_NONE;
+}
+
+void WaterfallCanvas::OnMouseLeftWindow(wxMouseEvent& event) {
+    InteractiveCanvas::OnMouseLeftWindow(event);
+    SetCursor(wxCURSOR_CROSS);
+    wxGetApp().getDemodMgr().setActiveDemodulator(NULL);
+    mouseZoom = 1.0;
+}
+
+void WaterfallCanvas::OnMouseEnterWindow(wxMouseEvent& event) {
+    InteractiveCanvas::OnMouseEnterWindow(event);
+    SetCursor(wxCURSOR_CROSS);
+#ifdef _WIN32
+	if (wxGetApp().getAppFrame()->canFocus()) {
+		this->SetFocus();
+	}
+#endif
+}
+
+void WaterfallCanvas::OnMouseRightDown(wxMouseEvent& event) {
+    InteractiveCanvas::OnMouseRightDown(event);
+
+    SetCursor(wxCURSOR_SIZENS);
+    mouseTracker.setVertDragLock(true);
+    mouseTracker.setHorizDragLock(true);
+}
+
+void WaterfallCanvas::OnMouseRightReleased(wxMouseEvent& event) {
+    InteractiveCanvas::OnMouseRightReleased(event);
+    SetCursor(wxCURSOR_CROSS);
+    mouseTracker.setVertDragLock(false);
+    mouseTracker.setHorizDragLock(false);
+    mouseZoom = 1.0;
+}
+
+SpectrumVisualDataQueue *WaterfallCanvas::getVisualDataQueue() {
+    return &visualDataQueue;
+}
+
+void WaterfallCanvas::updateCenterFrequency(long long freq) {
+    if (isView) {
+        setView(freq, getBandwidth());
+        if (spectrumCanvas) {
+            spectrumCanvas->setView(freq, getBandwidth());
+        }
+        
+        long long minFreq = wxGetApp().getFrequency()-(wxGetApp().getSampleRate()/2);
+        long long maxFreq = wxGetApp().getFrequency()+(wxGetApp().getSampleRate()/2);
+        
+        if (freq - bandwidth / 2 < minFreq) {
+            wxGetApp().setFrequency(wxGetApp().getFrequency() - (minFreq - (freq - bandwidth/2)));
+        }
+        if (freq + bandwidth / 2 > maxFreq) {
+            wxGetApp().setFrequency(wxGetApp().getFrequency() + ((freq + bandwidth/2) - maxFreq));
+        }
+    } else {
+        if (spectrumCanvas) {
+            spectrumCanvas->setCenterFrequency(freq);
+        }
+        wxGetApp().setFrequency(freq);
+    }
+
+}
+
+void WaterfallCanvas::setLinesPerSecond(int lps) {
+    std::lock_guard < std::mutex > lock(tex_update);
+    
+    linesPerSecond = lps;
+
+    //empty all
+    SpectrumVisualData *vData;
+    while (visualDataQueue.try_pop(vData)) {
+        
+        if (vData) {
+            vData->decRefCount();
+        }
+    }
+}
+
+void WaterfallCanvas::setMinBandwidth(int min) {
+    minBandwidth = min;
+}
diff --git a/src/visual/WaterfallCanvas.h b/src/visual/WaterfallCanvas.h
new file mode 100644
index 0000000..aa53448
--- /dev/null
+++ b/src/visual/WaterfallCanvas.h
@@ -0,0 +1,87 @@
+#pragma once
+
+#include "wx/glcanvas.h"
+#include "wx/timer.h"
+
+#include <vector>
+#include <queue>
+
+#include "InteractiveCanvas.h"
+#include "MouseTracker.h"
+#include "SpectrumCanvas.h"
+#include "WaterfallPanel.h"
+#include "Timer.h"
+
+class WaterfallCanvas: public InteractiveCanvas {
+public:
+    enum DragState {
+        WF_DRAG_NONE, WF_DRAG_BANDWIDTH_LEFT, WF_DRAG_BANDWIDTH_RIGHT, WF_DRAG_FREQUENCY, WF_DRAG_RANGE
+    };
+
+    WaterfallCanvas(wxWindow *parent, int *dispAttrs);
+    void setup(unsigned int fft_size_in, int waterfall_lines_in);
+    void setFFTSize(unsigned int fft_size_in);
+    ~WaterfallCanvas();
+
+    DragState getDragState();
+    DragState getNextDragState();
+    
+    void attachSpectrumCanvas(SpectrumCanvas *canvas_in);
+    void processInputQueue();
+    SpectrumVisualDataQueue *getVisualDataQueue();
+
+    void setLinesPerSecond(int lps);
+    void setMinBandwidth(int min);
+
+    void OnKeyDown(wxKeyEvent& event);
+    void OnKeyUp(wxKeyEvent& event);
+    void OnMouseWheelMoved(wxMouseEvent& event);
+    
+private:
+    void OnPaint(wxPaintEvent& event);
+    void OnIdle(wxIdleEvent &event);
+
+    void updateHoverState();
+    
+    void OnMouseMoved(wxMouseEvent& event);
+    void OnMouseDown(wxMouseEvent& event);
+    void OnMouseReleased(wxMouseEvent& event);
+    void OnMouseRightDown(wxMouseEvent& event);
+    void OnMouseRightReleased(wxMouseEvent& event);
+    void OnMouseEnterWindow(wxMouseEvent& event);
+    void OnMouseLeftWindow(wxMouseEvent& event);
+
+    void updateCenterFrequency(long long freq);
+    
+    std::vector<float> spectrum_points;
+
+    SpectrumCanvas *spectrumCanvas;
+    PrimaryGLContext *glContext;
+    WaterfallPanel waterfallPanel;
+
+    DragState dragState;
+    DragState nextDragState;
+
+    unsigned int fft_size, new_fft_size;
+    int waterfall_lines;
+    int dragOfs;
+
+    float mouseZoom, zoom;
+    bool freqMoving;
+    long double freqMove;
+    float hoverAlpha;
+    int linesPerSecond;
+    float scaleMove;
+    int dragBW;
+    
+    SpectrumVisualDataQueue visualDataQueue;
+    Timer gTimer;
+    double lpsIndex;
+    bool preBuf;
+    std::mutex tex_update;
+    int minBandwidth;
+    std::atomic_bool fft_size_changed;
+    // event table
+wxDECLARE_EVENT_TABLE();
+};
+

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-hamradio/cubicsdr.git



More information about the pkg-hamradio-commits mailing list