[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(¶meters, 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(¶meters, 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. 

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