[hamradio-commits] [soapysdr] 01/05: Imported Upstream version 0.4.3+git20160605

Andreas E. Bombe aeb at moszumanska.debian.org
Tue Jul 12 23:25:06 UTC 2016


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

aeb pushed a commit to branch master
in repository soapysdr.

commit 0a70f0b52c280dff4fd6bd49e0a70650dbd14de6
Author: Andreas Bombe <aeb at debian.org>
Date:   Thu Jul 7 00:52:05 2016 +0200

    Imported Upstream version 0.4.3+git20160605
---
 .travis.yml                              |   80 ++
 CMakeLists.txt                           |  161 +++
 Changelog.txt                            |  131 ++
 ExampleDriver/CMakeLists.txt             |   38 +
 ExampleDriver/MyDeviceSupport.cpp        |   36 +
 ExampleDriver/README.md                  |    7 +
 LICENSE_1_0.txt                          |   23 +
 README.md                                |   16 +
 apps/CMakeLists.txt                      |   14 +
 apps/SoapySDRProbe.cpp                   |  266 ++++
 apps/SoapySDRUtil.cpp                    |  203 +++
 apps/msvc/getopt.h                       |  607 ++++++++
 appveyor.yml                             |   55 +
 buildkite/build.sh                       |   40 +
 cmake/GetGitRevisionDescription.cmake    |  130 ++
 cmake/GetGitRevisionDescription.cmake.in |   38 +
 cmake/SoapySDRConfig.cmake               |  163 +++
 cmake/SoapySDRConfigVersion.in.cmake     |   12 +
 cmake/SoapySDRUtil.cmake                 |   53 +
 cmake_uninstall.cmake.in                 |   21 +
 debian/changelog                         |   59 +
 debian/compat                            |    1 +
 debian/control                           |   75 +
 debian/copyright                         |   32 +
 debian/docs                              |    1 +
 debian/libsoapysdr-dev.install           |    4 +
 debian/libsoapysdr0.5-2.install          |    1 +
 debian/python-soapysdr.install           |    1 +
 debian/python3-soapysdr.install          |    1 +
 debian/rules                             |   27 +
 debian/soapysdr.install                  |    1 +
 debian/source/format                     |    1 +
 docs/CMakeLists.txt                      |   45 +
 docs/Doxyfile.in                         | 2303 ++++++++++++++++++++++++++++++
 docs/soapy_sdr_logo.png                  |  Bin 0 -> 3889 bytes
 include/SoapySDR/Config.h                |   55 +
 include/SoapySDR/Config.hpp              |   13 +
 include/SoapySDR/Constants.h             |   66 +
 include/SoapySDR/Device.h                | 1377 ++++++++++++++++++
 include/SoapySDR/Device.hpp              | 1225 ++++++++++++++++
 include/SoapySDR/Errors.h                |   68 +
 include/SoapySDR/Errors.hpp              |   26 +
 include/SoapySDR/Formats.h               |   88 ++
 include/SoapySDR/Formats.hpp             |   27 +
 include/SoapySDR/Logger.h                |   96 ++
 include/SoapySDR/Logger.hpp              |   68 +
 include/SoapySDR/Modules.h               |   77 +
 include/SoapySDR/Modules.hpp             |   69 +
 include/SoapySDR/Registry.hpp            |   72 +
 include/SoapySDR/Time.h                  |   36 +
 include/SoapySDR/Time.hpp                |   44 +
 include/SoapySDR/Types.h                 |  135 ++
 include/SoapySDR/Types.hpp               |  124 ++
 include/SoapySDR/Version.h               |  122 ++
 include/SoapySDR/Version.hpp             |   28 +
 lib/CMakeLists.txt                       |  110 ++
 lib/Device.cpp                           |  760 ++++++++++
 lib/DeviceC.cpp                          |  693 +++++++++
 lib/Errors.cpp                           |    9 +
 lib/ErrorsC.cpp                          |   24 +
 lib/Factory.cpp                          |  188 +++
 lib/FactoryC.cpp                         |   37 +
 lib/Formats.cpp                          |    9 +
 lib/FormatsC.cpp                         |   23 +
 lib/Logger.cpp                           |   24 +
 lib/LoggerC.cpp                          |  114 ++
 lib/Modules.in.cpp                       |  289 ++++
 lib/ModulesC.cpp                         |   48 +
 lib/NullDevice.cpp                       |   57 +
 lib/Registry.cpp                         |   90 ++
 lib/SoapySDR.in.pc                       |   15 +
 lib/TimeC.cpp                            |   38 +
 lib/TypeHelpers.hpp                      |  105 ++
 lib/Types.cpp                            |   23 +
 lib/TypesC.cpp                           |   95 ++
 lib/Version.in.cpp                       |   14 +
 lib/VersionC.cpp                         |   20 +
 python/CMakeLists.txt                    |  109 ++
 python/SoapySDR.i                        |  195 +++
 python/apps/MeasureDelay.py              |  187 +++
 python/apps/SimpleSiggen.py              |  112 ++
 python3/CMakeLists.txt                   |   95 ++
 python3/FindPython3Interp.cmake          |   67 +
 python3/FindPython3Libs.cmake            |  246 ++++
 python3/README.md                        |   11 +
 tests/CMakeLists.txt                     |   21 +
 tests/TestFormatParser.cpp               |   46 +
 tests/TestTimeConversion.cpp             |   90 ++
 88 files changed, 12426 insertions(+)

diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..393bbc9
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,80 @@
+########################################################################
+## Travis CI config for SoapySDR
+##
+## * matrix tests compilers and build types
+## * python bindings built and installed
+## * python3 bindings built and installed
+## * minimal testing for command line util
+## * minimal testing for python bindings
+########################################################################
+
+sudo: required
+dist: trusty
+
+language: cpp
+compiler: gcc
+
+env:
+  global:
+    - INSTALL_PREFIX=/usr/local
+    - PYTHON_EXECUTABLE=/usr/bin/python
+    - PYTHON_INSTALL_DIR=lib/python2.7/dist-packages
+    - PYTHON3_EXECUTABLE=/usr/bin/python3
+    - PYTHON3_INSTALL_DIR=lib/python3/dist-packages
+  matrix:
+    - BUILD_TYPE=Debug
+    - BUILD_TYPE=Release
+
+before_install:
+  # regular ubuntu packages
+  - sudo add-apt-repository main
+  - sudo add-apt-repository universe
+
+  # update after package changes
+  - sudo apt-get update
+
+install:
+
+  # install python support dependencies
+  - sudo apt-get install -qq python python-dev python-numpy swig
+
+  # install python3 support dependencies
+  - sudo apt-get install -qq python3 python3-dev python3-numpy swig
+
+script:
+  - mkdir build
+  - cd build
+  - cmake ../ -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DPYTHON_EXECUTABLE=${PYTHON_EXECUTABLE} -DPYTHON3_EXECUTABLE=${PYTHON3_EXECUTABLE}
+  - make
+  - sudo make install
+
+  # setup environment paths
+  - export LD_LIBRARY_PATH=${INSTALL_PREFIX}/lib:${LD_LIBRARY_PATH}
+  - export PATH=${INSTALL_PREFIX}/bin:${PATH}
+
+  # basic test for command line utility
+  - SoapySDRUtil --info
+  - SoapySDRUtil --check=null
+  - SoapySDRUtil --make="driver=null"
+
+  # basic test for python bindings
+  - export PYTHONPATH=${INSTALL_PREFIX}/${PYTHON_INSTALL_DIR}:/usr/${PYTHON_INSTALL_DIR}
+  - echo ${PYTHONPATH}
+  - ${PYTHON_EXECUTABLE} -c "import SoapySDR; print(SoapySDR.getAPIVersion())"
+  - ${PYTHON_EXECUTABLE} -c "from SoapySDR import *; print(SOAPY_SDR_ABI_VERSION)"
+  - ${PYTHON_EXECUTABLE} -c "from SoapySDR import *; print(SOAPY_SDR_TIMEOUT)"
+  - ${PYTHON_EXECUTABLE} -c "import SoapySDR; print(SoapySDR.errToStr(SoapySDR.SOAPY_SDR_TIMEOUT))"
+  - ${PYTHON_EXECUTABLE} -c "import SoapySDR; print(SoapySDR.Device.make('driver=null'))"
+
+  # basic test for python3 bindings
+  - export PYTHONPATH=${INSTALL_PREFIX}/${PYTHON3_INSTALL_DIR}:/usr/${PYTHON3_INSTALL_DIR}
+  - echo ${PYTHONPATH}
+  - ${PYTHON3_EXECUTABLE} -c "import SoapySDR; print(SoapySDR.getAPIVersion())"
+  - ${PYTHON3_EXECUTABLE} -c "from SoapySDR import *; print(SOAPY_SDR_ABI_VERSION)"
+  - ${PYTHON3_EXECUTABLE} -c "from SoapySDR import *; print(SOAPY_SDR_TIMEOUT)"
+  - ${PYTHON3_EXECUTABLE} -c "import SoapySDR; print(SoapySDR.errToStr(SoapySDR.SOAPY_SDR_TIMEOUT))"
+  - ${PYTHON3_EXECUTABLE} -c "import SoapySDR; print(SoapySDR.Device.make('driver=null'))"
+
+  # run unit tests
+  - make test
+
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..1b1a7b0
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,161 @@
+########################################################################
+# Project setup
+########################################################################
+cmake_minimum_required(VERSION 2.8.7)
+project(SoapySDR)
+enable_language(CXX)
+enable_testing()
+
+########################################################################
+# gather version information
+# packagers may specify -DSOAPY_SDR_EXTVER="foo" to replace the git hash
+########################################################################
+set(SOAPY_SDR_LIBVER "0.5.0")
+
+if (NOT SOAPY_SDR_EXTVER)
+    include(${PROJECT_SOURCE_DIR}/cmake/GetGitRevisionDescription.cmake)
+    get_git_head_revision(GITREFSPEC GITHASH)
+    if (GITHASH)
+        string(SUBSTRING "${GITHASH}" 0 8 GITHASH)
+        set(SOAPY_SDR_EXTVER "g${GITHASH}")
+    else (GITHASH)
+        set(SOAPY_SDR_EXTVER "unknown")
+    endif (GITHASH)
+endif()
+
+set(SOAPY_SDR_VERSION "${SOAPY_SDR_LIBVER}-${SOAPY_SDR_EXTVER}")
+
+#SOAPY_SDR_ROOT is compiled into the library to locate the install base.
+#By default, the SOAPY_SDR_ROOT is set to the CMAKE_INSTALL_PREFIX.
+#However users may overload this by specifying -DSOAPY_SDR_ROOT=<path>.
+if(NOT SOAPY_SDR_ROOT)
+    file(TO_CMAKE_PATH ${CMAKE_INSTALL_PREFIX} SOAPY_SDR_ROOT)
+endif(NOT SOAPY_SDR_ROOT)
+
+########################################################################
+# select the release build type by default to get optimization flags
+########################################################################
+if(NOT CMAKE_BUILD_TYPE)
+   set(CMAKE_BUILD_TYPE "Release")
+   message(STATUS "Build type not specified: defaulting to release.")
+endif(NOT CMAKE_BUILD_TYPE)
+set(CMAKE_BUILD_TYPE ${CMAKE_BUILD_TYPE} CACHE STRING "")
+
+########################################################################
+# rpath setup - http://www.cmake.org/Wiki/CMake_RPATH_handling
+########################################################################
+# use, i.e. don't skip the full RPATH for the build tree
+option(CMAKE_SKIP_BUILD_RPATH "skip rpath build" FALSE)
+
+# when building, don't use the install RPATH already
+# (but later on when installing)
+option(CMAKE_BUILD_WITH_INSTALL_RPATH "build with install rpath" FALSE)
+
+# the RPATH to be used when installing, but only if it's not a system directory
+option(CMAKE_AUTOSET_INSTALL_RPATH TRUE)
+if(CMAKE_AUTOSET_INSTALL_RPATH)
+LIST(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_PREFIX}/lib${LIB_SUFFIX}" isSystemDir)
+IF("${isSystemDir}" STREQUAL "-1")
+    SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib${LIB_SUFFIX}")
+ENDIF("${isSystemDir}" STREQUAL "-1")
+endif(CMAKE_AUTOSET_INSTALL_RPATH)
+
+# add the automatically determined parts of the RPATH
+# which point to directories outside the build tree to the install RPATH
+option(CMAKE_INSTALL_RPATH_USE_LINK_PATH "build with automatic rpath" TRUE)
+
+if(APPLE)
+    set(CMAKE_MACOSX_RPATH ON)
+endif()
+
+########################################################################
+# Allows in-tree module util
+########################################################################
+set(SoapySDR_DIR ${PROJECT_SOURCE_DIR}/cmake)
+set(SOAPY_SDR_IN_TREE_SOURCE_DIR ${PROJECT_SOURCE_DIR})
+find_package(SoapySDR NO_MODULE REQUIRED)
+include_directories(${SoapySDR_INCLUDE_DIRS}) #local include precedence
+
+########################################################################
+# Install cmake helper modules
+########################################################################
+configure_file(
+    ${PROJECT_SOURCE_DIR}/cmake/SoapySDRConfigVersion.in.cmake
+    ${PROJECT_BINARY_DIR}/SoapySDRConfigVersion.cmake
+ at ONLY)
+set(cmake_files
+    ${PROJECT_SOURCE_DIR}/cmake/SoapySDRConfig.cmake
+    ${PROJECT_SOURCE_DIR}/cmake/SoapySDRUtil.cmake
+    ${PROJECT_BINARY_DIR}/SoapySDRConfigVersion.cmake)
+if (UNIX)
+    install(FILES ${cmake_files} DESTINATION share/cmake/${PROJECT_NAME})
+elseif (WIN32)
+    install(FILES ${cmake_files} DESTINATION cmake)
+endif ()
+
+########################################################################
+# Install headers
+########################################################################
+install(DIRECTORY include/SoapySDR DESTINATION include)
+
+########################################################################
+# Build subdirs
+########################################################################
+add_subdirectory(lib)
+add_subdirectory(apps)
+add_subdirectory(tests)
+add_subdirectory(docs)
+
+########################################################################
+# Python support (optional)
+########################################################################
+option(ENABLE_PYTHON "enable python bindings" ON)
+if (ENABLE_PYTHON)
+message(STATUS "")
+message(STATUS "#############################################")
+message(STATUS "## Begin configuration for Python support...")
+message(STATUS "#############################################")
+message(STATUS "Enabling optional Python bindings if possible...")
+add_subdirectory(python)
+
+if(BUILD_PYTHON3)
+message(STATUS "")
+message(STATUS "#############################################")
+message(STATUS "## Begin configuration for Python3 support...")
+message(STATUS "#############################################")
+message(STATUS "Enabling optional Python3 bindings if possible...")
+add_subdirectory(python3)
+endif(BUILD_PYTHON3)
+
+endif (ENABLE_PYTHON)
+
+########################################################################
+# uninstall target
+########################################################################
+configure_file(
+    "${CMAKE_CURRENT_SOURCE_DIR}/cmake_uninstall.cmake.in"
+    "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake"
+    IMMEDIATE @ONLY)
+
+#only add uninstall target if this is the top project
+if(${CMAKE_PROJECT_NAME} STREQUAL ${PROJECT_NAME})
+add_custom_target(uninstall
+    COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake)
+endif()
+
+#########################################################################
+# summary
+#########################################################################
+include(FeatureSummary)
+message(STATUS "")
+message(STATUS "######################################################")
+message(STATUS "## ${PROJECT_NAME} enabled features")
+message(STATUS "######################################################")
+feature_summary(WHAT ENABLED_FEATURES)
+message(STATUS "######################################################")
+message(STATUS "## ${PROJECT_NAME} disabled features")
+message(STATUS "######################################################")
+feature_summary(WHAT DISABLED_FEATURES)
+message(STATUS "SoapySDR version: v${SOAPY_SDR_VERSION}")
+message(STATUS "ABI/so version: v${SOAPY_SDR_SOVER}")
+message(STATUS "Install prefix: ${CMAKE_INSTALL_PREFIX}")
diff --git a/Changelog.txt b/Changelog.txt
new file mode 100644
index 0000000..e22fe36
--- /dev/null
+++ b/Changelog.txt
@@ -0,0 +1,131 @@
+This this the changelog file for the SoapySDR project.
+
+Release 0.5.0 (pending)
+==========================
+
+Deprecated Device API calls:
+
+- Deprecated setCommandTime, use setHardwareTime
+- Deprecated listBandwidths, use getBandwidthRange
+- Deprecated unnamed read/writeRegister, use named
+
+General additions and changes:
+
+- Created wait trigger flag for trigger-based streaming
+- Added device factory lock to the python enumerate wrapper
+- Added named register API for multiple register interfaces
+- Added channel info API call for channel-specific info
+- Setup CMake rpath handling to be enabled by default
+- Moved time source calls to time API section
+- C bindings return status code and stash the error message
+- Added getBandwidthRange() API call for continuous ranges
+- Use current DLL path to determine the system install root
+- Implemented channel-specific settings API
+
+Release 0.4.4 (pending)
+==========================
+
+- Add known python version 3.5 to FindPython3Libs.cmake
+- Fix FindPython3Libs.cmake use of python3-config --includes
+
+Release 0.4.3 (2016-04-21)
+==========================
+
+- CMake SOAPY_SDR_SOVER variable cache fix use INTERNAL type
+- Added missing memset for internal use of SoapySDRKwargs type
+- Fix SoapySDRDevice declarations for use in a C only compiler
+
+Release 0.4.2 (2016-03-02)
+==========================
+
+- Fixes for SOAPY_SDR_MODULE_UTIL destination and install prefix
+- CMake override for PYTHON_INSTALL_DIR and PYTHON3_INSTALL_DIR
+
+Release 0.4.1 (2016-01-27)
+==========================
+
+- Added readStreamStatus() to python bindings
+- Notes on stream timeouts for client code compatibility
+- Fix debian upstream changelog and license install
+- Added official modules to debian Recommends
+
+Release 0.4.0 (2015-12-10)
+==========================
+
+- Added check for driver present to util app
+- Workaround for multiple null module load
+- Added defines for common stream formats
+- Support dual build of python2 and python3
+- Improved module loader API and results query
+- New query information for SoapySDRUtil --probe
+- Added setLogLevel() and default log threshold
+- Added API to query native stream format
+- Added C++ typedef for list of Kwargs
+- Added API to query tune argument info
+- Added API to query sensors meta info
+- Added API to query setting argument info
+- Added API to query stream argument info
+- CMAKE_BUILD_TYPE automatically defaults to Release
+- Added API to query AGC mode availability
+- Added API to query available clock rates
+- Set library SOVERSION using SOAPY_SDR_ABI_VERSION
+
+Release 0.3.1 (2015-10-11)
+==========================
+
+- Fixed missing python bindings for Errors.hpp
+- Added -DSOAPY_SDR_ROOT=<path> option to build
+
+Release 0.3.0 (2015-10-10)
+==========================
+
+- Util --probe option for detailed info summary
+- Added corrections API to check for availability
+- Added API to convert error codes to string
+- Added per-channel API to query readback sensors
+- Added GPIO API data direction modification mask
+- Removed SoapyUHD and SoapyOsmo submodules
+
+Release 0.2.3 (2015-10-08)
+==========================
+
+- Append to linker flags to avoid overwriting them
+- Link with -flat_namespace for OSX application bundle
+- GCC link with -pthread for threaded client support
+
+Release 0.2.2 (2015-08-15)
+==========================
+
+- Fixed vector<double> in python swig bindings
+- Support CMake 2.8.7 builds (ubuntu 12.04 lts)
+
+Release 0.2.1 (2015-07-09)
+==========================
+
+- Fixed Device::make() factory argument forwarding
+- Added Time.h/Time.hpp time conversion API calls
+- Implement SoapySDRConfigVersion.cmake version check
+
+Release 0.2.0 (2015-06-15)
+==========================
+
+- Support /usr/local module installs when root is /usr
+- Support SOAPY_SDR_PLUGIN_PATH environment variable
+- Added logger support for stream status indicator
+- Frequency component API and tune distribution algorithm
+- Added direct buffer access API for read and write streams
+- Automatic gain distribution and overall range calculations
+- Added read stream status Device API call
+- Added additional error codes and status flags
+
+Release 0.1.1 (2015-06-14)
+==========================
+
+- Fix path issues for listModules() implementation under MSVC
+- Support openSuSE for automatic LIB_SUFFIX detection logic
+- patch for missing add_compile_options in older CMake
+
+Release 0.1.0 (2015-01-31)
+==========================
+
+This is the first public release of the SoapySDR project.
diff --git a/ExampleDriver/CMakeLists.txt b/ExampleDriver/CMakeLists.txt
new file mode 100644
index 0000000..4e83dbe
--- /dev/null
+++ b/ExampleDriver/CMakeLists.txt
@@ -0,0 +1,38 @@
+########################################################################
+# Project setup -- only needed if device support is a stand-alone build
+# We recommend that the support module be built in-tree with the driver.
+########################################################################
+cmake_minimum_required(VERSION 2.6)
+project(SoapySDRMyDevice CXX)
+enable_testing()
+
+#select the release build type by default to get optimization flags
+if(NOT CMAKE_BUILD_TYPE)
+   set(CMAKE_BUILD_TYPE "Release")
+   message(STATUS "Build type not specified: defaulting to release.")
+endif(NOT CMAKE_BUILD_TYPE)
+set(CMAKE_BUILD_TYPE ${CMAKE_BUILD_TYPE} CACHE STRING "")
+
+########################################################################
+# Header and library resources needed to communicate with the device.
+# These may be found within the build tree or in an external project.
+########################################################################
+set(MY_DEVICE_INCLUDE_DIRS ...)
+set(MY_DEVICE_LIBRARIES ...)
+
+########################################################################
+# build the module
+########################################################################
+find_package(SoapySDR CONFIG)
+
+if (NOT SoapySDR_FOUND)
+    message(WARNING "SoapySDR development files not found - skipping support")
+    return()
+endif ()
+
+include_directories(${MY_DEVICE_INCLUDE_DIRS})
+SOAPY_SDR_MODULE_UTIL(
+    TARGET MyDevice
+    SOURCES MyDeviceSupport.cpp
+    LIBRARIES ${MY_DEVICE_LIBRARIES}
+)
diff --git a/ExampleDriver/MyDeviceSupport.cpp b/ExampleDriver/MyDeviceSupport.cpp
new file mode 100644
index 0000000..b6f6836
--- /dev/null
+++ b/ExampleDriver/MyDeviceSupport.cpp
@@ -0,0 +1,36 @@
+#include <SoapySDR/Device.hpp>
+#include <SoapySDR/Registry.hpp>
+
+/***********************************************************************
+ * Device interface
+ **********************************************************************/
+class MyDevice : public SoapySDR::Device
+{
+    //Implement constructor with device specific arguments...
+
+    //Implement all applicable virtual methods from SoapySDR::Device
+};
+
+/***********************************************************************
+ * Find available devices
+ **********************************************************************/
+SoapySDR::KwargsList findMyDevice(const SoapySDR::Kwargs &args)
+{
+    //locate the device on the system...
+    //return a list of 0, 1, or more argument maps that each identify a device
+}
+
+/***********************************************************************
+ * Make device instance
+ **********************************************************************/
+SoapySDR::Device *makeMyDevice(const SoapySDR::Kwargs &args)
+{
+    //create an instance of the device object given the args
+    //here we will translate args into something used in the constructor
+    return new MyDevice(...);
+}
+
+/***********************************************************************
+ * Registration
+ **********************************************************************/
+static SoapySDR::Registry registerMyDevice("my_device", &findMyDevice, &makeMyDevice, SOAPY_SDR_ABI_VERSION);
diff --git a/ExampleDriver/README.md b/ExampleDriver/README.md
new file mode 100644
index 0000000..caab6fd
--- /dev/null
+++ b/ExampleDriver/README.md
@@ -0,0 +1,7 @@
+# Example module build for SoapySDR device support
+
+Copy this example wholesale either as a new cmake build project,
+or copy it into a subdirectory of an existing driver build system.
+Customize the CMakeLists.txt for your driver dependencies,
+and customize MyDeviceSupport.cpp to make calls into the
+low-level device driver for streaming and configuration.
diff --git a/LICENSE_1_0.txt b/LICENSE_1_0.txt
new file mode 100644
index 0000000..36b7cd9
--- /dev/null
+++ b/LICENSE_1_0.txt
@@ -0,0 +1,23 @@
+Boost Software License - Version 1.0 - August 17th, 2003
+
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by
+this license (the "Software") to use, reproduce, display, distribute,
+execute, and transmit the Software, and to prepare derivative works of the
+Software, and to permit third-parties to whom the Software is furnished to
+do so, all subject to the following:
+
+The copyright notices in the Software and this entire statement, including
+the above license grant, this restriction and the following disclaimer,
+must be included in all copies of the Software, in whole or in part, and
+all derivative works of the Software, unless such copies or derivative
+works are solely in the form of machine-executable object code generated by
+a source language processor.
+
+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, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..2494865
--- /dev/null
+++ b/README.md
@@ -0,0 +1,16 @@
+# Soapy SDR - vendor and platform neutral SDR support library.
+
+##Build Status
+
+- Travis: [![Travis Build Status](https://travis-ci.org/pothosware/SoapySDR.svg?branch=master)](https://travis-ci.org/pothosware/SoapySDR)
+- AppVeyor: [![AppVeyor Build status](https://ci.appveyor.com/api/projects/status/6chajdpy7uk5ax89)](https://ci.appveyor.com/project/guruofquality/soapysdr)
+
+##Documentation
+
+* https://github.com/pothosware/SoapySDR/wiki
+
+## Licensing information
+
+Use, modification and distribution is subject to the Boost Software
+License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
+http://www.boost.org/LICENSE_1_0.txt)
diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt
new file mode 100644
index 0000000..7706a55
--- /dev/null
+++ b/apps/CMakeLists.txt
@@ -0,0 +1,14 @@
+########################################################################
+# Build utility executable
+########################################################################
+set(SOAPY_SDR_UTIL_SOURCES
+    SoapySDRUtil.cpp
+    SoapySDRProbe.cpp
+)
+include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../include)
+if (MSVC)
+    include_directories(${CMAKE_CURRENT_SOURCE_DIR}/msvc)
+endif ()
+add_executable(SoapySDRUtil ${SOAPY_SDR_UTIL_SOURCES})
+target_link_libraries(SoapySDRUtil SoapySDR)
+install(TARGETS SoapySDRUtil DESTINATION bin)
diff --git a/apps/SoapySDRProbe.cpp b/apps/SoapySDRProbe.cpp
new file mode 100644
index 0000000..228bafc
--- /dev/null
+++ b/apps/SoapySDRProbe.cpp
@@ -0,0 +1,266 @@
+// Copyright (c) 2015-2016 Josh Blum
+// Copyright (c) 2016-2016 Bastille Networks
+// SPDX-License-Identifier: BSL-1.0
+
+#include <SoapySDR/Device.hpp>
+#include <sstream>
+
+template <typename Type>
+std::string toString(const std::vector<Type> &options)
+{
+    std::stringstream ss;
+    if (options.empty()) return "";
+    for (size_t i = 0; i < options.size(); i++)
+    {
+        if (not ss.str().empty()) ss << ", ";
+        ss << options[i];
+    }
+    return ss.str();
+}
+
+std::string toString(const SoapySDR::Range &range)
+{
+    std::stringstream ss;
+    ss << "[" << range.minimum() << ", " << range.maximum() << "]";
+    return ss.str();
+}
+
+std::string toString(const SoapySDR::RangeList &range, const double scale)
+{
+    std::stringstream ss;
+    for (size_t i = 0; i < range.size(); i++)
+    {
+        if (not ss.str().empty()) ss << ", ";
+        ss << "[" << (range[i].minimum()/scale) << ", " << (range[i].maximum()/scale) << "]";
+    }
+    return ss.str();
+}
+
+std::string toString(const std::vector<double> &nums, const double scale)
+{
+    std::stringstream ss;
+
+    if (nums.size() > 3)
+    {
+        ss << "[" << (nums.front()/scale) << ", " << (nums.back()/scale) << "]";
+        return ss.str();
+    }
+
+    for (size_t i = 0; i < nums.size(); i++)
+    {
+        if (not ss.str().empty()) ss << ", ";
+        ss << (nums[i]/scale);
+    }
+    return "[" + ss.str() + "]";
+}
+
+std::string toString(const SoapySDR::ArgInfo &argInfo, const std::string indent = "    ")
+{
+    std::stringstream ss;
+
+    //name, or use key if missing
+    std::string name = argInfo.name;
+    if (argInfo.name.empty()) name = argInfo.key;
+    ss << indent << " * " << name;
+
+    //optional description
+    std::string desc = argInfo.description;
+    const std::string replace("\n"+indent+"   ");
+    for (size_t pos = 0; (pos=desc.find("\n", pos)) != std::string::npos; pos+=replace.size())
+    {
+        desc.replace(pos, 1, replace);
+    }
+    if (not desc.empty()) ss << " - " << desc << std::endl << indent << "  ";
+
+    //other fields
+    ss << " [key=" << argInfo.key;
+    if (not argInfo.units.empty()) ss << ", units=" << argInfo.units;
+    if (not argInfo.value.empty()) ss << ", default=" << argInfo.value;
+
+    //type
+    switch (argInfo.type)
+    {
+    case SoapySDR::ArgInfo::BOOL: ss << ", type=bool"; break;
+    case SoapySDR::ArgInfo::INT: ss << ", type=int"; break;
+    case SoapySDR::ArgInfo::FLOAT: ss << ", type=float"; break;
+    case SoapySDR::ArgInfo::STRING: ss << ", type=string"; break;
+    }
+
+    //optional range/enumeration
+    if (argInfo.range.minimum() < argInfo.range.maximum()) ss << ", range=" << toString(argInfo.range);
+    if (not argInfo.options.empty()) ss << ", options=(" << toString(argInfo.options) << ")";
+
+    ss << "]";
+
+    return ss.str();
+}
+
+std::string toString(const SoapySDR::ArgInfoList &argInfos)
+{
+    std::stringstream ss;
+
+    for (size_t i = 0; i < argInfos.size(); i++)
+    {
+        ss << toString(argInfos[i]) << std::endl;
+    }
+
+    return ss.str();
+}
+
+static std::string probeChannel(SoapySDR::Device *device, const int dir, const size_t chan)
+{
+    std::stringstream ss;
+
+    std::string dirName = (dir==SOAPY_SDR_TX)?"TX":"RX";
+    ss << std::endl;
+    ss << "----------------------------------------------------" << std::endl;
+    ss << "-- " << dirName << " Channel " << chan << std::endl;
+    ss << "----------------------------------------------------" << std::endl;
+
+    // info
+    const auto info = device->getChannelInfo(dir, chan);
+    if (info.size() > 0)
+    {
+        ss << "  Channel Information:" << std::endl;
+        for (const auto &it : info)
+        {
+            ss << "    " << it.first << "=" << it.second << std::endl;
+        }
+    }
+
+    ss << "  Full-duplex: " << (device->getFullDuplex(dir, chan)?"YES":"NO") << std::endl;
+    ss << "  Supports AGC: " << (device->hasGainMode(dir, chan)?"YES":"NO") << std::endl;
+
+    //formats
+    std::string formats = toString(device->getStreamFormats(dir, chan));
+    if (not formats.empty()) ss << "  Stream formats: " << formats << std::endl;
+
+    //native
+    double fullScale = 0.0;
+    std::string native = device->getNativeStreamFormat(dir, chan, fullScale);
+    ss << "  Native format: " << native << " [full-scale=" << fullScale << "]" << std::endl;    
+
+    //stream args
+    std::string streamArgs = toString(device->getStreamArgsInfo(dir, chan));
+    if (not streamArgs.empty()) ss << "  Stream args:" << std::endl << streamArgs;
+
+    //antennas
+    std::string antennas = toString(device->listAntennas(dir, chan));
+    if (not antennas.empty()) ss << "  Antennas: " << antennas << std::endl;
+
+    //corrections
+    std::vector<std::string> correctionsList;
+    if (device->hasDCOffsetMode(dir, chan)) correctionsList.push_back("DC removal");
+    if (device->hasDCOffset(dir, chan)) correctionsList.push_back("DC offset");
+    if (device->hasIQBalance(dir, chan)) correctionsList.push_back("IQ balance");
+    std::string corrections = toString(correctionsList);
+    if (not corrections.empty()) ss << "  Corrections: " << corrections << std::endl;
+
+    //gains
+    ss << "  Full gain range: " << toString(device->getGainRange(dir, chan)) << " dB" << std::endl;
+    std::vector<std::string> gainsList = device->listGains(dir, chan);
+    for (size_t i = 0; i < gainsList.size(); i++)
+    {
+        const std::string name = gainsList[i];
+        ss << "    " << name << " gain range: " << toString(device->getGainRange(dir, chan, name)) << " dB" << std::endl;
+    }
+
+    //frequencies
+    ss << "  Full freq range: " << toString(device->getFrequencyRange(dir, chan), 1e6) << " MHz" << std::endl;
+    std::vector<std::string> freqsList = device->listFrequencies(dir, chan);
+    for (size_t i = 0; i < freqsList.size(); i++)
+    {
+        const std::string name = freqsList[i];
+        ss << "    " << name << " freq range: " << toString(device->getFrequencyRange(dir, chan, name), 1e6) << " MHz" << std::endl;
+    }
+
+    //freq args
+    std::string freqArgs = toString(device->getFrequencyArgsInfo(dir, chan));
+    if (not freqArgs.empty()) ss << "  Tune args:" << std::endl << freqArgs;
+
+    //rates
+    ss << "  Sample rates: " << toString(device->listSampleRates(dir, chan), 1e6) << " MHz" << std::endl;
+
+    //bandwidths
+    const std::vector<double> bws = device->listBandwidths(dir, chan);
+    if (not bws.empty()) ss << "  Filter bandwidths: " << toString(bws, 1e6) << " MHz" << std::endl;
+
+    //sensors
+    std::string sensors = toString(device->listSensors(dir, chan));
+    if (not sensors.empty()) ss << "  Sensors: " << sensors << std::endl;
+
+    //settings
+    std::string settings = toString(device->getSettingInfo(dir, chan));
+    if (not settings.empty()) ss << "  Other Settings:" << std::endl << settings;
+
+    return ss.str();
+}
+
+std::string SoapySDRDeviceProbe(SoapySDR::Device *device)
+{
+    std::stringstream ss;
+
+    /*******************************************************************
+     * Identification info
+     ******************************************************************/
+    ss << std::endl;
+    ss << "----------------------------------------------------" << std::endl;
+    ss << "-- Device identification" << std::endl;
+    ss << "----------------------------------------------------" << std::endl;
+
+    ss << "  driver=" << device->getDriverKey() << std::endl;
+    ss << "  hardware=" << device->getHardwareKey() << std::endl;
+    for (const auto &it : device->getHardwareInfo())
+    {
+        ss << "  " << it.first << "=" << it.second << std::endl;
+    }
+
+    /*******************************************************************
+     * Available peripherals
+     ******************************************************************/
+    ss << std::endl;
+    ss << "----------------------------------------------------" << std::endl;
+    ss << "-- Peripheral summary" << std::endl;
+    ss << "----------------------------------------------------" << std::endl;
+
+    size_t numRxChans = device->getNumChannels(SOAPY_SDR_RX);
+    size_t numTxChans = device->getNumChannels(SOAPY_SDR_TX);
+    ss << "  Channels: " << numRxChans << " Rx, " << numTxChans << " Tx" << std::endl;
+
+    ss << "  Timestamps: " << (device->hasHardwareTime()?"YES":"NO") << std::endl;
+
+    std::string clockSources = toString(device->listClockSources());
+    if (not clockSources.empty()) ss << "  Clock sources: " << clockSources << std::endl;
+
+    std::string timeSources = toString(device->listTimeSources());
+    if (not timeSources.empty()) ss << "  Time sources: " << timeSources << std::endl;
+
+    std::string sensors = toString(device->listSensors());
+    if (not sensors.empty()) ss << "  Sensors: " << sensors << std::endl;
+
+    std::string registers = toString(device->listRegisterInterfaces());
+    if (not registers.empty()) ss << "  Registers: " << registers << std::endl;
+
+    std::string settings = toString(device->getSettingInfo());
+    if (not settings.empty()) ss << "  Other Settings:" << std::endl << settings;
+
+    std::string gpios = toString(device->listGPIOBanks());
+    if (not gpios.empty()) ss << "  GPIOs: " << gpios << std::endl;
+
+    std::string uarts = toString(device->listUARTs());
+    if (not uarts.empty()) ss << "  UARTs: " << uarts << std::endl;
+
+    /*******************************************************************
+     * Per-channel info
+     ******************************************************************/
+    for (size_t chan = 0; chan < numRxChans; chan++)
+    {
+        ss << probeChannel(device, SOAPY_SDR_RX, chan);
+    }
+    for (size_t chan = 0; chan < numTxChans; chan++)
+    {
+        ss << probeChannel(device, SOAPY_SDR_TX, chan);
+    }
+
+    return ss.str();
+}
diff --git a/apps/SoapySDRUtil.cpp b/apps/SoapySDRUtil.cpp
new file mode 100644
index 0000000..18095d5
--- /dev/null
+++ b/apps/SoapySDRUtil.cpp
@@ -0,0 +1,203 @@
+// Copyright (c) 2014-2016 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#include <SoapySDR/Version.hpp>
+#include <SoapySDR/Modules.hpp>
+#include <SoapySDR/Registry.hpp>
+#include <SoapySDR/Device.hpp>
+#include <cstdlib>
+#include <cstddef>
+#include <iostream>
+#include <getopt.h>
+
+std::string SoapySDRDeviceProbe(SoapySDR::Device *);
+
+/***********************************************************************
+ * Print help message
+ **********************************************************************/
+static int printHelp(void)
+{
+    std::cout << "Usage SoapySDRUtil [options]" << std::endl;
+    std::cout << "  Options summary:" << std::endl;
+    std::cout << "    --help \t\t\t\t Print this help message" << std::endl;
+    std::cout << "    --info \t\t\t\t Print module information" << std::endl;
+    std::cout << "    --find[=\"driver=foo,type=bar\"] \t Discover available devices" << std::endl;
+    std::cout << "    --make[=\"driver=foo,type=bar\"] \t Create a device instance" << std::endl;
+    std::cout << "    --probe[=\"driver=foo,type=bar\"] \t Print detailed information" << std::endl;
+    std::cout << "    --check[=driverName] \t\t Check if driver is present" << std::endl;
+    std::cout << std::endl;
+    return EXIT_SUCCESS;
+}
+
+/***********************************************************************
+ * Print version and module info
+ **********************************************************************/
+static int printInfo(void)
+{
+    std::cout << "API Version: v" << SoapySDR::getAPIVersion() << std::endl;
+    std::cout << "ABI Version: v" << SoapySDR::getABIVersion() << std::endl;
+    std::cout << "Install root: " << SoapySDR::getRootPath() << std::endl;
+
+    const auto modules = SoapySDR::listModules();
+    for (const auto &mod : modules) std::cout << "Module found: " << mod << std::endl;
+    if (modules.empty()) std::cout << "No modules found!" << std::endl;
+
+    std::cout << "Loading modules... " << std::flush;
+    SoapySDR::loadModules();
+    std::cout << "done" << std::endl;
+
+    std::cout << "Available factories...";
+    const auto factories = SoapySDR::Registry::listFindFunctions();
+    for (const auto &it : factories) std::cout << it.first << ", ";
+    if (factories.empty()) std::cout << "No factories found!" << std::endl;
+    std::cout << std::endl;
+    return EXIT_SUCCESS;
+}
+
+/***********************************************************************
+ * Find devices and print args
+ **********************************************************************/
+static int findDevices(void)
+{
+    std::string argStr;
+    if (optarg != NULL) argStr = optarg;
+
+    const auto results = SoapySDR::Device::enumerate(argStr);
+    for (size_t i = 0; i < results.size(); i++)
+    {
+        std::cout << "Found device " << i << std::endl;
+        for (const auto &it : results[i])
+        {
+            std::cout << "  " << it.first << " = " << it.second << std::endl;
+        }
+        std::cout << std::endl;
+    }
+    if (results.empty())
+    {
+        std::cerr << "No devices found!" << std::endl;
+        return EXIT_FAILURE;
+    }
+    std::cout << std::endl;
+    return EXIT_SUCCESS;
+}
+
+/***********************************************************************
+ * Make device and print hardware info
+ **********************************************************************/
+static int makeDevice(void)
+{
+    std::string argStr;
+    if (optarg != NULL) argStr = optarg;
+
+    std::cout << "Make device " << argStr << std::endl;
+    try
+    {
+        auto device = SoapySDR::Device::make(argStr);
+        std::cout << "  driver=" << device->getDriverKey() << std::endl;
+        std::cout << "  hardware=" << device->getHardwareKey() << std::endl;
+        for (const auto &it : device->getHardwareInfo())
+        {
+            std::cout << "  " << it.first << "=" << it.second << std::endl;
+        }
+        SoapySDR::Device::unmake(device);
+    }
+    catch (const std::exception &ex)
+    {
+        std::cerr << "Error making device: " << ex.what() << std::endl;
+        return EXIT_FAILURE;
+    }
+    std::cout << std::endl;
+    return EXIT_SUCCESS;
+}
+
+/***********************************************************************
+ * Make device and print detailed info
+ **********************************************************************/
+static int probeDevice(void)
+{
+    std::string argStr;
+    if (optarg != NULL) argStr = optarg;
+
+    std::cout << "Probe device " << argStr << std::endl;
+    try
+    {
+        auto device = SoapySDR::Device::make(argStr);
+        std::cout << SoapySDRDeviceProbe(device) << std::endl;
+        SoapySDR::Device::unmake(device);
+    }
+    catch (const std::exception &ex)
+    {
+        std::cerr << "Error probing device: " << ex.what() << std::endl;
+        return EXIT_FAILURE;
+    }
+    std::cout << std::endl;
+    return EXIT_SUCCESS;
+}
+
+/***********************************************************************
+ * Check the registry for a specific driver
+ **********************************************************************/
+static int checkDriver(void)
+{
+    std::string driverName;
+    if (optarg != NULL) driverName = optarg;
+
+    std::cout << "Loading modules... " << std::flush;
+    SoapySDR::loadModules();
+    std::cout << "done" << std::endl;
+
+    std::cout << "Checking driver '" << driverName << "'... " << std::flush;
+    const auto factories = SoapySDR::Registry::listFindFunctions();
+
+    if (factories.find(driverName) == factories.end())
+    {
+        std::cout << "MISSING!" << std::endl;
+        return EXIT_FAILURE;
+    }
+    else
+    {
+        std::cout << "PRESENT" << std::endl;
+        return EXIT_SUCCESS;
+    }
+}
+
+/***********************************************************************
+ * main utility entry point
+ **********************************************************************/
+int main(int argc, char *argv[])
+{
+    std::cout << "######################################################" << std::endl;
+    std::cout << "## Soapy SDR -- the SDR abstraction library" << std::endl;
+    std::cout << "######################################################" << std::endl;
+    std::cout << std::endl;
+
+    /*******************************************************************
+     * parse command line options
+     ******************************************************************/
+    static struct option long_options[] = {
+        {"help", no_argument, 0, 'h'},
+        {"find", optional_argument, 0, 'f'},
+        {"make", optional_argument, 0, 'm'},
+        {"info", optional_argument, 0, 'i'},
+        {"probe", optional_argument, 0, 'p'},
+        {"check", optional_argument, 0, 'c'},
+        {0, 0, 0,  0}
+    };
+    int long_index = 0;
+    int option = 0;
+    while ((option = getopt_long_only(argc, argv, "", long_options, &long_index)) != -1)
+    {
+        switch (option)
+        {
+        case 'h': return printHelp();
+        case 'i': return printInfo();
+        case 'f': return findDevices();
+        case 'm': return makeDevice();
+        case 'p': return probeDevice();
+        case 'c': return checkDriver();
+        }
+    }
+
+    //unknown or unspecified options, do help...
+    return printHelp();
+}
diff --git a/apps/msvc/getopt.h b/apps/msvc/getopt.h
new file mode 100644
index 0000000..6c834b4
--- /dev/null
+++ b/apps/msvc/getopt.h
@@ -0,0 +1,607 @@
+#ifndef __GETOPT_H__
+/**
+ * DISCLAIMER
+ * This file has no copyright assigned and is placed in the Public Domain.
+ * This file is part of the mingw-w64 runtime package.
+ *
+ * The mingw-w64 runtime package and its code is distributed in the hope that it 
+ * will be useful but WITHOUT ANY WARRANTY.  ALL WARRANTIES, EXPRESSED OR 
+ * IMPLIED ARE HEREBY DISCLAIMED.  This includes but is not limited to 
+ * warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+#pragma warning(disable:4996)
+
+#define __GETOPT_H__
+
+/* All the headers include this file. */
+#include <crtdefs.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <windows.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define	REPLACE_GETOPT		/* use this getopt as the system getopt(3) */
+
+#ifdef REPLACE_GETOPT
+int	opterr = 1;		/* if error message should be printed */
+int	optind = 1;		/* index into parent argv vector */
+int	optopt = '?';		/* character checked for validity */
+#undef	optreset		/* see getopt.h */
+#define	optreset		__mingw_optreset
+int	optreset;		/* reset getopt */
+char    *optarg;		/* argument associated with option */
+#endif
+
+//extern int optind;		/* index of first non-option in argv      */
+//extern int optopt;		/* single option character, as parsed     */
+//extern int opterr;		/* flag to enable built-in diagnostics... */
+//				/* (user may set to zero, to suppress)    */
+//
+//extern char *optarg;		/* pointer to argument of current option  */
+
+#define PRINT_ERROR	((opterr) && (*options != ':'))
+
+#define FLAG_PERMUTE	0x01	/* permute non-options to the end of argv */
+#define FLAG_ALLARGS	0x02	/* treat non-options as args to option "-1" */
+#define FLAG_LONGONLY	0x04	/* operate as getopt_long_only */
+
+/* return values */
+#define	BADCH		(int)'?'
+#define	BADARG		((*options == ':') ? (int)':' : (int)'?')
+#define	INORDER 	(int)1
+
+#ifndef __CYGWIN__
+#define __progname __argv[0]
+#else
+extern char __declspec(dllimport) *__progname;
+#endif
+
+#ifdef __CYGWIN__
+static char EMSG[] = "";
+#else
+#define	EMSG		""
+#endif
+
+static int getopt_internal(int, char * const *, const char *,
+			   const struct option *, int *, int);
+static int parse_long_options(char * const *, const char *,
+			      const struct option *, int *, int);
+static int gcd(int, int);
+static void permute_args(int, int, int, char * const *);
+
+static char *place = EMSG; /* option letter processing */
+
+/* XXX: set optreset to 1 rather than these two */
+static int nonopt_start = -1; /* first non option argument (for permute) */
+static int nonopt_end = -1;   /* first option after non options (for permute) */
+
+/* Error messages */
+static const char recargchar[] = "option requires an argument -- %c";
+static const char recargstring[] = "option requires an argument -- %s";
+static const char ambig[] = "ambiguous option -- %.*s";
+static const char noarg[] = "option doesn't take an argument -- %.*s";
+static const char illoptchar[] = "unknown option -- %c";
+static const char illoptstring[] = "unknown option -- %s";
+
+static void
+_vwarnx(const char *fmt,va_list ap)
+{
+  (void)fprintf(stderr,"%s: ",__progname);
+  if (fmt != NULL)
+    (void)vfprintf(stderr,fmt,ap);
+  (void)fprintf(stderr,"\n");
+}
+
+static void
+warnx(const char *fmt,...)
+{
+  va_list ap;
+  va_start(ap,fmt);
+  _vwarnx(fmt,ap);
+  va_end(ap);
+}
+
+/*
+ * Compute the greatest common divisor of a and b.
+ */
+static int
+gcd(int a, int b)
+{
+	int c;
+
+	c = a % b;
+	while (c != 0) {
+		a = b;
+		b = c;
+		c = a % b;
+	}
+
+	return (b);
+}
+
+/*
+ * Exchange the block from nonopt_start to nonopt_end with the block
+ * from nonopt_end to opt_end (keeping the same order of arguments
+ * in each block).
+ */
+static void
+permute_args(int panonopt_start, int panonopt_end, int opt_end,
+	char * const *nargv)
+{
+	int cstart, cyclelen, i, j, ncycle, nnonopts, nopts, pos;
+	char *swap;
+
+	/*
+	 * compute lengths of blocks and number and size of cycles
+	 */
+	nnonopts = panonopt_end - panonopt_start;
+	nopts = opt_end - panonopt_end;
+	ncycle = gcd(nnonopts, nopts);
+	cyclelen = (opt_end - panonopt_start) / ncycle;
+
+	for (i = 0; i < ncycle; i++) {
+		cstart = panonopt_end+i;
+		pos = cstart;
+		for (j = 0; j < cyclelen; j++) {
+			if (pos >= panonopt_end)
+				pos -= nnonopts;
+			else
+				pos += nopts;
+			swap = nargv[pos];
+			/* LINTED const cast */
+			((char **) nargv)[pos] = nargv[cstart];
+			/* LINTED const cast */
+			((char **)nargv)[cstart] = swap;
+		}
+	}
+}
+
+#ifdef REPLACE_GETOPT
+/*
+ * getopt --
+ *	Parse argc/argv argument vector.
+ *
+ * [eventually this will replace the BSD getopt]
+ */
+int
+getopt(int nargc, char * const *nargv, const char *options)
+{
+
+	/*
+	 * We don't pass FLAG_PERMUTE to getopt_internal() since
+	 * the BSD getopt(3) (unlike GNU) has never done this.
+	 *
+	 * Furthermore, since many privileged programs call getopt()
+	 * before dropping privileges it makes sense to keep things
+	 * as simple (and bug-free) as possible.
+	 */
+	return (getopt_internal(nargc, nargv, options, NULL, NULL, 0));
+}
+#endif /* REPLACE_GETOPT */
+
+//extern int getopt(int nargc, char * const *nargv, const char *options);
+
+#ifdef _BSD_SOURCE
+/*
+ * BSD adds the non-standard `optreset' feature, for reinitialisation
+ * of `getopt' parsing.  We support this feature, for applications which
+ * proclaim their BSD heritage, before including this header; however,
+ * to maintain portability, developers are advised to avoid it.
+ */
+# define optreset  __mingw_optreset
+extern int optreset;
+#endif
+#ifdef __cplusplus
+}
+#endif
+/*
+ * POSIX requires the `getopt' API to be specified in `unistd.h';
+ * thus, `unistd.h' includes this header.  However, we do not want
+ * to expose the `getopt_long' or `getopt_long_only' APIs, when
+ * included in this manner.  Thus, close the standard __GETOPT_H__
+ * declarations block, and open an additional __GETOPT_LONG_H__
+ * specific block, only when *not* __UNISTD_H_SOURCED__, in which
+ * to declare the extended API.
+ */
+#endif /* !defined(__GETOPT_H__) */
+
+#if !defined(__UNISTD_H_SOURCED__) && !defined(__GETOPT_LONG_H__)
+#define __GETOPT_LONG_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct option		/* specification for a long form option...	*/
+{
+  const char *name;		/* option name, without leading hyphens */
+  int         has_arg;		/* does it take an argument?		*/
+  int        *flag;		/* where to save its status, or NULL	*/
+  int         val;		/* its associated status value		*/
+};
+
+enum    		/* permitted values for its `has_arg' field...	*/
+{
+  no_argument = 0,      	/* option never takes an argument	*/
+  required_argument,		/* option always requires an argument	*/
+  optional_argument		/* option may take an argument		*/
+};
+
+/*
+ * parse_long_options --
+ *	Parse long options in argc/argv argument vector.
+ * Returns -1 if short_too is set and the option does not match long_options.
+ */
+static int
+parse_long_options(char * const *nargv, const char *options,
+	const struct option *long_options, int *idx, int short_too)
+{
+	char *current_argv, *has_equal;
+	size_t current_argv_len;
+	int i, ambiguous, match;
+
+#define IDENTICAL_INTERPRETATION(_x, _y)                                \
+	(long_options[(_x)].has_arg == long_options[(_y)].has_arg &&    \
+	 long_options[(_x)].flag == long_options[(_y)].flag &&          \
+	 long_options[(_x)].val == long_options[(_y)].val)
+
+	current_argv = place;
+	match = -1;
+	ambiguous = 0;
+
+	optind++;
+
+	if ((has_equal = strchr(current_argv, '=')) != NULL) {
+		/* argument found (--option=arg) */
+		current_argv_len = has_equal - current_argv;
+		has_equal++;
+	} else
+		current_argv_len = strlen(current_argv);
+
+	for (i = 0; long_options[i].name; i++) {
+		/* find matching long option */
+		if (strncmp(current_argv, long_options[i].name,
+		    current_argv_len))
+			continue;
+
+		if (strlen(long_options[i].name) == current_argv_len) {
+			/* exact match */
+			match = i;
+			ambiguous = 0;
+			break;
+		}
+		/*
+		 * If this is a known short option, don't allow
+		 * a partial match of a single character.
+		 */
+		if (short_too && current_argv_len == 1)
+			continue;
+
+		if (match == -1)	/* partial match */
+			match = i;
+		else if (!IDENTICAL_INTERPRETATION(i, match))
+			ambiguous = 1;
+	}
+	if (ambiguous) {
+		/* ambiguous abbreviation */
+		if (PRINT_ERROR)
+			warnx(ambig, (int)current_argv_len,
+			     current_argv);
+		optopt = 0;
+		return (BADCH);
+	}
+	if (match != -1) {		/* option found */
+		if (long_options[match].has_arg == no_argument
+		    && has_equal) {
+			if (PRINT_ERROR)
+				warnx(noarg, (int)current_argv_len,
+				     current_argv);
+			/*
+			 * XXX: GNU sets optopt to val regardless of flag
+			 */
+			if (long_options[match].flag == NULL)
+				optopt = long_options[match].val;
+			else
+				optopt = 0;
+			return (BADARG);
+		}
+		if (long_options[match].has_arg == required_argument ||
+		    long_options[match].has_arg == optional_argument) {
+			if (has_equal)
+				optarg = has_equal;
+			else if (long_options[match].has_arg ==
+			    required_argument) {
+				/*
+				 * optional argument doesn't use next nargv
+				 */
+				optarg = nargv[optind++];
+			}
+		}
+		if ((long_options[match].has_arg == required_argument)
+		    && (optarg == NULL)) {
+			/*
+			 * Missing argument; leading ':' indicates no error
+			 * should be generated.
+			 */
+			if (PRINT_ERROR)
+				warnx(recargstring,
+				    current_argv);
+			/*
+			 * XXX: GNU sets optopt to val regardless of flag
+			 */
+			if (long_options[match].flag == NULL)
+				optopt = long_options[match].val;
+			else
+				optopt = 0;
+			--optind;
+			return (BADARG);
+		}
+	} else {			/* unknown option */
+		if (short_too) {
+			--optind;
+			return (-1);
+		}
+		if (PRINT_ERROR)
+			warnx(illoptstring, current_argv);
+		optopt = 0;
+		return (BADCH);
+	}
+	if (idx)
+		*idx = match;
+	if (long_options[match].flag) {
+		*long_options[match].flag = long_options[match].val;
+		return (0);
+	} else
+		return (long_options[match].val);
+#undef IDENTICAL_INTERPRETATION
+}
+
+/*
+ * getopt_internal --
+ *	Parse argc/argv argument vector.  Called by user level routines.
+ */
+static int
+getopt_internal(int nargc, char * const *nargv, const char *options,
+	const struct option *long_options, int *idx, int flags)
+{
+	char *oli;				/* option letter list index */
+	int optchar, short_too;
+	static int posixly_correct = -1;
+
+	if (options == NULL)
+		return (-1);
+
+	/*
+	 * XXX Some GNU programs (like cvs) set optind to 0 instead of
+	 * XXX using optreset.  Work around this braindamage.
+	 */
+	if (optind == 0)
+		optind = optreset = 1;
+
+	/*
+	 * Disable GNU extensions if POSIXLY_CORRECT is set or options
+	 * string begins with a '+'.
+	 *
+	 * CV, 2009-12-14: Check POSIXLY_CORRECT anew if optind == 0 or
+	 *                 optreset != 0 for GNU compatibility.
+	 */
+	if (posixly_correct == -1 || optreset != 0)
+		posixly_correct = (getenv("POSIXLY_CORRECT") != NULL);
+	if (*options == '-')
+		flags |= FLAG_ALLARGS;
+	else if (posixly_correct || *options == '+')
+		flags &= ~FLAG_PERMUTE;
+	if (*options == '+' || *options == '-')
+		options++;
+
+	optarg = NULL;
+	if (optreset)
+		nonopt_start = nonopt_end = -1;
+start:
+	if (optreset || !*place) {		/* update scanning pointer */
+		optreset = 0;
+		if (optind >= nargc) {          /* end of argument vector */
+			place = EMSG;
+			if (nonopt_end != -1) {
+				/* do permutation, if we have to */
+				permute_args(nonopt_start, nonopt_end,
+				    optind, nargv);
+				optind -= nonopt_end - nonopt_start;
+			}
+			else if (nonopt_start != -1) {
+				/*
+				 * If we skipped non-options, set optind
+				 * to the first of them.
+				 */
+				optind = nonopt_start;
+			}
+			nonopt_start = nonopt_end = -1;
+			return (-1);
+		}
+		if (*(place = nargv[optind]) != '-' ||
+		    (place[1] == '\0' && strchr(options, '-') == NULL)) {
+			place = EMSG;		/* found non-option */
+			if (flags & FLAG_ALLARGS) {
+				/*
+				 * GNU extension:
+				 * return non-option as argument to option 1
+				 */
+				optarg = nargv[optind++];
+				return (INORDER);
+			}
+			if (!(flags & FLAG_PERMUTE)) {
+				/*
+				 * If no permutation wanted, stop parsing
+				 * at first non-option.
+				 */
+				return (-1);
+			}
+			/* do permutation */
+			if (nonopt_start == -1)
+				nonopt_start = optind;
+			else if (nonopt_end != -1) {
+				permute_args(nonopt_start, nonopt_end,
+				    optind, nargv);
+				nonopt_start = optind -
+				    (nonopt_end - nonopt_start);
+				nonopt_end = -1;
+			}
+			optind++;
+			/* process next argument */
+			goto start;
+		}
+		if (nonopt_start != -1 && nonopt_end == -1)
+			nonopt_end = optind;
+
+		/*
+		 * If we have "-" do nothing, if "--" we are done.
+		 */
+		if (place[1] != '\0' && *++place == '-' && place[1] == '\0') {
+			optind++;
+			place = EMSG;
+			/*
+			 * We found an option (--), so if we skipped
+			 * non-options, we have to permute.
+			 */
+			if (nonopt_end != -1) {
+				permute_args(nonopt_start, nonopt_end,
+				    optind, nargv);
+				optind -= nonopt_end - nonopt_start;
+			}
+			nonopt_start = nonopt_end = -1;
+			return (-1);
+		}
+	}
+
+	/*
+	 * Check long options if:
+	 *  1) we were passed some
+	 *  2) the arg is not just "-"
+	 *  3) either the arg starts with -- we are getopt_long_only()
+	 */
+	if (long_options != NULL && place != nargv[optind] &&
+	    (*place == '-' || (flags & FLAG_LONGONLY))) {
+		short_too = 0;
+		if (*place == '-')
+			place++;		/* --foo long option */
+		else if (*place != ':' && strchr(options, *place) != NULL)
+			short_too = 1;		/* could be short option too */
+
+		optchar = parse_long_options(nargv, options, long_options,
+		    idx, short_too);
+		if (optchar != -1) {
+			place = EMSG;
+			return (optchar);
+		}
+	}
+
+	if ((optchar = (int)*place++) == (int)':' ||
+	    (optchar == (int)'-' && *place != '\0') ||
+	    (oli = (char*)strchr(options, optchar)) == NULL) {
+		/*
+		 * If the user specified "-" and  '-' isn't listed in
+		 * options, return -1 (non-option) as per POSIX.
+		 * Otherwise, it is an unknown option character (or ':').
+		 */
+		if (optchar == (int)'-' && *place == '\0')
+			return (-1);
+		if (!*place)
+			++optind;
+		if (PRINT_ERROR)
+			warnx(illoptchar, optchar);
+		optopt = optchar;
+		return (BADCH);
+	}
+	if (long_options != NULL && optchar == 'W' && oli[1] == ';') {
+		/* -W long-option */
+		if (*place)			/* no space */
+			/* NOTHING */;
+		else if (++optind >= nargc) {	/* no arg */
+			place = EMSG;
+			if (PRINT_ERROR)
+				warnx(recargchar, optchar);
+			optopt = optchar;
+			return (BADARG);
+		} else				/* white space */
+			place = nargv[optind];
+		optchar = parse_long_options(nargv, options, long_options,
+		    idx, 0);
+		place = EMSG;
+		return (optchar);
+	}
+	if (*++oli != ':') {			/* doesn't take argument */
+		if (!*place)
+			++optind;
+	} else {				/* takes (optional) argument */
+		optarg = NULL;
+		if (*place)			/* no white space */
+			optarg = place;
+		else if (oli[1] != ':') {	/* arg not optional */
+			if (++optind >= nargc) {	/* no arg */
+				place = EMSG;
+				if (PRINT_ERROR)
+					warnx(recargchar, optchar);
+				optopt = optchar;
+				return (BADARG);
+			} else
+				optarg = nargv[optind];
+		}
+		place = EMSG;
+		++optind;
+	}
+	/* dump back option letter */
+	return (optchar);
+}
+
+/*
+ * getopt_long --
+ *	Parse argc/argv argument vector.
+ */
+int
+getopt_long(int nargc, char * const *nargv, const char *options,
+    const struct option *long_options, int *idx)
+{
+
+	return (getopt_internal(nargc, nargv, options, long_options, idx,
+	    FLAG_PERMUTE));
+}
+
+/*
+ * getopt_long_only --
+ *	Parse argc/argv argument vector.
+ */
+int
+getopt_long_only(int nargc, char * const *nargv, const char *options,
+    const struct option *long_options, int *idx)
+{
+
+	return (getopt_internal(nargc, nargv, options, long_options, idx,
+	    FLAG_PERMUTE|FLAG_LONGONLY));
+}
+
+//extern int getopt_long(int nargc, char * const *nargv, const char *options,
+//    const struct option *long_options, int *idx);
+//extern int getopt_long_only(int nargc, char * const *nargv, const char *options,
+//    const struct option *long_options, int *idx);
+/*
+ * Previous MinGW implementation had...
+ */
+#ifndef HAVE_DECL_GETOPT
+/*
+ * ...for the long form API only; keep this for compatibility.
+ */
+# define HAVE_DECL_GETOPT	1
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* !defined(__UNISTD_H_SOURCED__) && !defined(__GETOPT_LONG_H__) */
diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 0000000..ffd5895
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,55 @@
+########################################################################
+## AppVeyor config for SoapySDR
+##
+## * build only on a 64-bit environment
+## * the matrix builds Release and Debug
+## * minimal testing for command line util
+########################################################################
+
+version: '{build}'
+
+platform: x64
+
+configuration:
+  - Debug
+  - Release
+
+environment:
+  global:
+    INSTALL_PREFIX: C:/Program Files/SoapySDR
+
+# Operating system (build VM template)
+os: Windows Server 2012 R2
+
+# branches to build
+branches:
+  # whitelist
+  only:
+    - master
+
+# dependencies for python bindings
+# disabled because of link issues on VM
+#install:
+#  - choco install swig
+
+# configure and build
+build_script:
+  - mkdir build
+  - cd build
+  - cmake ../ -G "Visual Studio 12 Win64" -DCMAKE_INSTALL_PREFIX="%INSTALL_PREFIX%" -DCMAKE_BUILD_TYPE=%CONFIGURATION%
+  - cmake --build . --config %CONFIGURATION%
+  - cmake --build . --config %CONFIGURATION% --target install
+
+# setup environment paths
+before_test:
+  - set PATH=%INSTALL_PREFIX%/bin;%PATH%
+  - set PYTHONPATH=%INSTALL_PREFIX%/Lib/site-packages;%PYTHONPATH%
+
+# basic test for command line utility
+test_script:
+  - SoapySDRUtil --info
+  - SoapySDRUtil --check=null
+  - SoapySDRUtil --make="driver=null"
+
+  # run the unit tests
+  - ctest
diff --git a/buildkite/build.sh b/buildkite/build.sh
new file mode 100755
index 0000000..1f90ca2
--- /dev/null
+++ b/buildkite/build.sh
@@ -0,0 +1,40 @@
+#!/bin/bash
+#
+# See: https://buildkite.com/docs/guides/writing-build-scripts
+#
+# TL;DR
+#
+# # You can group your build output by echoing "--- group name" in your build output.
+#
+# echo "--- build group here"
+# # If you want to have the group open by default, use +++ instead of ---:
+#
+# echo "+++ build group here"
+# # You can even include colors and emojis!
+#
+# echo -e "--- Running \033[33mspecs\033[0m \:cow:\:bell:"
+#
+
+
+#
+# Add a buildkite format comment with each new item added.
+# This script is run from the top directory of the FPGA repo.
+#
+set -eo pipefail
+
+echo "--- Enviroment."
+env
+
+#
+# Add hook(s) to build commands/script here.
+#
+echo "--- Build SoapySDR"
+rm -rf build
+mkdir build
+cd build
+cmake ../
+make -j4
+
+# Got to the end, all is good, close out last buildkite output group.
+echo "--- BUILD SUCCEEDED"
+exit 0
diff --git a/cmake/GetGitRevisionDescription.cmake b/cmake/GetGitRevisionDescription.cmake
new file mode 100644
index 0000000..c8d27f2
--- /dev/null
+++ b/cmake/GetGitRevisionDescription.cmake
@@ -0,0 +1,130 @@
+# - Returns a version string from Git
+#
+# These functions force a re-configure on each git commit so that you can
+# trust the values of the variables in your build system.
+#
+#  get_git_head_revision(<refspecvar> <hashvar> [<additional arguments to git describe> ...])
+#
+# Returns the refspec and sha hash of the current head revision
+#
+#  git_describe(<var> [<additional arguments to git describe> ...])
+#
+# Returns the results of git describe on the source tree, and adjusting
+# the output so that it tests false if an error occurs.
+#
+#  git_get_exact_tag(<var> [<additional arguments to git describe> ...])
+#
+# Returns the results of git describe --exact-match on the source tree,
+# and adjusting the output so that it tests false if there was no exact
+# matching tag.
+#
+# Requires CMake 2.6 or newer (uses the 'function' command)
+#
+# Original Author:
+# 2009-2010 Ryan Pavlik <rpavlik at iastate.edu> <abiryan at ryand.net>
+# http://academic.cleardefinition.com
+# Iowa State University HCI Graduate Program/VRAC
+#
+# Copyright Iowa State University 2009-2010.
+# Distributed under the Boost Software License, Version 1.0.
+# (See accompanying file LICENSE_1_0.txt or copy at
+# http://www.boost.org/LICENSE_1_0.txt)
+
+if(__get_git_revision_description)
+	return()
+endif()
+set(__get_git_revision_description YES)
+
+# We must run the following at "include" time, not at function call time,
+# to find the path to this module rather than the path to a calling list file
+get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH)
+
+function(get_git_head_revision _refspecvar _hashvar)
+	set(GIT_PARENT_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
+	set(GIT_DIR "${GIT_PARENT_DIR}/.git")
+	while(NOT EXISTS "${GIT_DIR}")	# .git dir not found, search parent directories
+		set(GIT_PREVIOUS_PARENT "${GIT_PARENT_DIR}")
+		get_filename_component(GIT_PARENT_DIR ${GIT_PARENT_DIR} PATH)
+		if(GIT_PARENT_DIR STREQUAL GIT_PREVIOUS_PARENT)
+			# We have reached the root directory, we are not in git
+			set(${_refspecvar} "GITDIR-NOTFOUND" PARENT_SCOPE)
+			set(${_hashvar} "GITDIR-NOTFOUND" PARENT_SCOPE)
+			return()
+		endif()
+		set(GIT_DIR "${GIT_PARENT_DIR}/.git")
+	endwhile()
+	# check if this is a submodule
+	if(NOT IS_DIRECTORY ${GIT_DIR})
+		file(READ ${GIT_DIR} submodule)
+		string(REGEX REPLACE "gitdir: (.*)\n$" "\\1" GIT_DIR_RELATIVE ${submodule})
+		get_filename_component(SUBMODULE_DIR ${GIT_DIR} PATH)
+		get_filename_component(GIT_DIR ${SUBMODULE_DIR}/${GIT_DIR_RELATIVE} ABSOLUTE)
+	endif()
+	set(GIT_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data")
+	if(NOT EXISTS "${GIT_DATA}")
+		file(MAKE_DIRECTORY "${GIT_DATA}")
+	endif()
+
+	if(NOT EXISTS "${GIT_DIR}/HEAD")
+		return()
+	endif()
+	set(HEAD_FILE "${GIT_DATA}/HEAD")
+	configure_file("${GIT_DIR}/HEAD" "${HEAD_FILE}" COPYONLY)
+
+	configure_file("${_gitdescmoddir}/GetGitRevisionDescription.cmake.in"
+		"${GIT_DATA}/grabRef.cmake"
+		@ONLY)
+	include("${GIT_DATA}/grabRef.cmake")
+
+	set(${_refspecvar} "${HEAD_REF}" PARENT_SCOPE)
+	set(${_hashvar} "${HEAD_HASH}" PARENT_SCOPE)
+endfunction()
+
+function(git_describe _var)
+	if(NOT GIT_FOUND)
+		find_package(Git QUIET)
+	endif()
+	get_git_head_revision(refspec hash)
+	if(NOT GIT_FOUND)
+		set(${_var} "GIT-NOTFOUND" PARENT_SCOPE)
+		return()
+	endif()
+	if(NOT hash)
+		set(${_var} "HEAD-HASH-NOTFOUND" PARENT_SCOPE)
+		return()
+	endif()
+
+	# TODO sanitize
+	#if((${ARGN}" MATCHES "&&") OR
+	#	(ARGN MATCHES "||") OR
+	#	(ARGN MATCHES "\\;"))
+	#	message("Please report the following error to the project!")
+	#	message(FATAL_ERROR "Looks like someone's doing something nefarious with git_describe! Passed arguments ${ARGN}")
+	#endif()
+
+	#message(STATUS "Arguments to execute_process: ${ARGN}")
+
+	execute_process(COMMAND
+		"${GIT_EXECUTABLE}"
+		describe
+		${hash}
+		${ARGN}
+		WORKING_DIRECTORY
+		"${CMAKE_SOURCE_DIR}"
+		RESULT_VARIABLE
+		res
+		OUTPUT_VARIABLE
+		out
+		ERROR_QUIET
+		OUTPUT_STRIP_TRAILING_WHITESPACE)
+	if(NOT res EQUAL 0)
+		set(out "${out}-${res}-NOTFOUND")
+	endif()
+
+	set(${_var} "${out}" PARENT_SCOPE)
+endfunction()
+
+function(git_get_exact_tag _var)
+	git_describe(out --exact-match ${ARGN})
+	set(${_var} "${out}" PARENT_SCOPE)
+endfunction()
diff --git a/cmake/GetGitRevisionDescription.cmake.in b/cmake/GetGitRevisionDescription.cmake.in
new file mode 100644
index 0000000..888ce13
--- /dev/null
+++ b/cmake/GetGitRevisionDescription.cmake.in
@@ -0,0 +1,38 @@
+# 
+# Internal file for GetGitRevisionDescription.cmake
+#
+# Requires CMake 2.6 or newer (uses the 'function' command)
+#
+# Original Author:
+# 2009-2010 Ryan Pavlik <rpavlik at iastate.edu> <abiryan at ryand.net>
+# http://academic.cleardefinition.com
+# Iowa State University HCI Graduate Program/VRAC
+#
+# Copyright Iowa State University 2009-2010.
+# Distributed under the Boost Software License, Version 1.0.
+# (See accompanying file LICENSE_1_0.txt or copy at
+# http://www.boost.org/LICENSE_1_0.txt)
+
+set(HEAD_HASH)
+
+file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024)
+
+string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS)
+if(HEAD_CONTENTS MATCHES "ref")
+	# named branch
+	string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}")
+	if(EXISTS "@GIT_DIR@/${HEAD_REF}")
+		configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY)
+	elseif(EXISTS "@GIT_DIR@/logs/${HEAD_REF}")
+		configure_file("@GIT_DIR@/logs/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY)
+		set(HEAD_HASH "${HEAD_REF}")
+	endif()
+else()
+	# detached HEAD
+	configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY)
+endif()
+
+if(NOT HEAD_HASH)
+	file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024)
+	string(STRIP "${HEAD_HASH}" HEAD_HASH)
+endif()
diff --git a/cmake/SoapySDRConfig.cmake b/cmake/SoapySDRConfig.cmake
new file mode 100644
index 0000000..2af2d23
--- /dev/null
+++ b/cmake/SoapySDRConfig.cmake
@@ -0,0 +1,163 @@
+if(DEFINED INCLUDED_SOAPY_SDR_CONFIG_CMAKE)
+    return()
+endif()
+set(INCLUDED_SOAPY_SDR_CONFIG_CMAKE TRUE)
+
+########################################################################
+# SoapySDRConfig - cmake project configuration for client clibraries
+#
+# The following will be set after find_package(SoapySDR):
+# SOAPY_SDR_MODULE_UTIL() - utility function to build modules
+# SoapySDR_LIBRARIES - SoapySDR development libraries
+# SoapySDR_INCLUDE_DIRS - SoapySDR development includes
+########################################################################
+list(INSERT CMAKE_MODULE_PATH 0 ${CMAKE_CURRENT_LIST_DIR})
+include(SoapySDRUtil)
+
+########################################################################
+# select the release build type by default to get optimization flags
+########################################################################
+if(NOT CMAKE_BUILD_TYPE)
+   set(CMAKE_BUILD_TYPE "Release")
+   message(STATUS "Build type not specified: defaulting to release.")
+endif(NOT CMAKE_BUILD_TYPE)
+set(CMAKE_BUILD_TYPE ${CMAKE_BUILD_TYPE} CACHE STRING "")
+
+########################################################################
+# Automatic LIB_SUFFIX detection + configuration option
+########################################################################
+if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
+    set(LINUX TRUE)
+endif()
+
+if(LINUX AND EXISTS "/etc/debian_version")
+    set(DEBIAN TRUE)
+endif()
+
+if(LINUX AND EXISTS "/etc/redhat-release")
+    set(REDHAT TRUE)
+endif()
+
+if(LINUX AND EXISTS "/etc/SuSE-release")
+    set(SUSE TRUE)
+endif()
+
+if(LINUX AND EXISTS "/etc/slackware-version")
+    set(SLACKWARE TRUE)
+endif()
+
+if(NOT DEFINED LIB_SUFFIX AND (REDHAT OR SUSE OR SLACKWARE) AND CMAKE_SYSTEM_PROCESSOR MATCHES "64$")
+    SET(LIB_SUFFIX 64)
+endif()
+set(LIB_SUFFIX ${LIB_SUFFIX} CACHE STRING "lib directory suffix")
+
+########################################################################
+# Provide add_compile_options() when not available
+########################################################################
+if(CMAKE_VERSION VERSION_LESS "2.8.12")
+    function(add_compile_options)
+        add_definitions(${ARGN})
+    endfunction(add_compile_options)
+endif()
+
+########################################################################
+# Helpful compiler flags
+########################################################################
+
+#C++11 is a required language feature for this project
+set(CMAKE_CXX_STANDARD 11)
+
+if(CMAKE_COMPILER_IS_GNUCXX)
+
+    #enable C++11 on older versions of cmake
+    if (CMAKE_VERSION VERSION_LESS "3.1")
+        set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x")
+    endif()
+
+    #force a compile-time error when symbols are missing
+    set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined")
+    set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--no-undefined")
+
+    #common warnings to help encourage good coding practices
+    add_compile_options(-Wall)
+    add_compile_options(-Wextra)
+
+    #symbols are only exported from libraries/modules explicitly
+    add_compile_options(-fvisibility=hidden)
+    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility-inlines-hidden")
+endif()
+
+if(APPLE)
+    #fixes issue with duplicate module registry when using application bundle
+    set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -flat_namespace")
+    set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -flat_namespace")
+endif()
+
+if(MSVC)
+    #C++11 is a required language feature for this project
+    if (${MSVC_VERSION} LESS 1700)
+        message(FATAL_ERROR "the build requires MSVC 2012 or newer for C++11 support")
+    endif()
+
+    #we always want to use multiple cores for compilation
+    add_compile_options(/MP)
+
+    #suppress the following warnings which are commonly caused by project headers
+    add_compile_options(/wd4251) #disable 'identifier' : class 'type' needs to have dll-interface to be used by clients of class 'type2'
+    add_compile_options(/wd4503) #'identifier' : decorated name length exceeded, name was truncated
+
+    #projects should be cross-platform and standard stl functions should work
+    add_definitions(-DNOMINMAX) #enables std::min and std::max
+endif()
+
+if ("${CMAKE_SYSTEM_NAME}" STREQUAL "FreeBSD")
+    add_compile_options(-stdlib=libc++)
+endif()
+
+########################################################################
+# In-tree settings
+########################################################################
+if (SOAPY_SDR_IN_TREE_SOURCE_DIR)
+    if(NOT SOAPY_SDR_ROOT)
+        set(SOAPY_SDR_ROOT ${CMAKE_INSTALL_PREFIX})
+    endif(NOT SOAPY_SDR_ROOT)
+    set(SoapySDR_INCLUDE_DIRS ${SOAPY_SDR_IN_TREE_SOURCE_DIR}/include)
+    set(SoapySDR_LIBRARIES SoapySDR)
+    return()
+endif (SOAPY_SDR_IN_TREE_SOURCE_DIR)
+
+########################################################################
+## installation root
+########################################################################
+if (UNIX)
+    get_filename_component(SOAPY_SDR_ROOT "${CMAKE_CURRENT_LIST_DIR}/../../.." ABSOLUTE)
+elseif (WIN32)
+    get_filename_component(SOAPY_SDR_ROOT "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE)
+endif ()
+
+########################################################################
+## locate the library
+########################################################################
+find_library(
+    SOAPY_SDR_LIBRARY SoapySDR SoapySDRd
+    PATHS ${SOAPY_SDR_ROOT}/lib${LIB_SUFFIX}
+    PATH_SUFFIXES ${CMAKE_LIBRARY_ARCHITECTURE}
+    NO_DEFAULT_PATH
+)
+if(NOT SOAPY_SDR_LIBRARY)
+    message(FATAL_ERROR "cannot find SoapySDR library in ${SOAPY_SDR_ROOT}/lib${LIB_SUFFIX}")
+endif()
+set(SoapySDR_LIBRARIES ${SOAPY_SDR_LIBRARY})
+
+########################################################################
+## locate the includes
+########################################################################
+find_path(
+    SOAPY_SDR_INCLUDE_DIR SoapySDR/Config.hpp
+    PATHS ${SOAPY_SDR_ROOT}/include
+    NO_DEFAULT_PATH
+)
+if(NOT SOAPY_SDR_INCLUDE_DIR)
+    message(FATAL_ERROR "cannot find SoapySDR includes in ${SOAPY_SDR_ROOT}/include")
+endif()
+set(SoapySDR_INCLUDE_DIRS ${SOAPY_SDR_INCLUDE_DIR})
diff --git a/cmake/SoapySDRConfigVersion.in.cmake b/cmake/SoapySDRConfigVersion.in.cmake
new file mode 100644
index 0000000..7ca92b1
--- /dev/null
+++ b/cmake/SoapySDRConfigVersion.in.cmake
@@ -0,0 +1,12 @@
+set(PACKAGE_FIND_NAME "@PROJECT_NAME@")
+set(PACKAGE_VERSION "@SOAPY_SDR_VERSION@")
+
+# Check whether the requested PACKAGE_FIND_VERSION is compatible
+if("${PACKAGE_VERSION}" VERSION_LESS "${PACKAGE_FIND_VERSION}")
+  set(PACKAGE_VERSION_COMPATIBLE FALSE)
+else()
+  set(PACKAGE_VERSION_COMPATIBLE TRUE)
+  if ("${PACKAGE_VERSION}" VERSION_EQUAL "${PACKAGE_FIND_VERSION}")
+    set(PACKAGE_VERSION_EXACT TRUE)
+  endif()
+endif()
diff --git a/cmake/SoapySDRUtil.cmake b/cmake/SoapySDRUtil.cmake
new file mode 100644
index 0000000..96b89bd
--- /dev/null
+++ b/cmake/SoapySDRUtil.cmake
@@ -0,0 +1,53 @@
+if(DEFINED INCLUDED_SOAPY_SDR_UTIL_CMAKE)
+    return()
+endif()
+set(INCLUDED_SOAPY_SDR_UTIL_CMAKE TRUE)
+
+########################################################################
+## SOAPY_SDR_MODULE_UTIL - build and install modules for Soapy SDR
+##
+## This utility can handle the build and installation operations.
+##
+## Arguments:
+##
+## TARGET - the name of the module to build
+##
+## SOURCES - a list of c++ source files
+##
+## LIBRARIES - a list of libraries to link the module to
+## The module will automatically link to SoapySDR library.
+##
+## DESTINATION - override the default install path when specified
+## The default destination is a relative path (<lib>/SoapySDR/modules).
+## This argument specifies an alternative relative or absolute path,
+## and can be used standalone or in conjunction with PREFIX.
+##
+## PREFIX - override the default install prefix when specified
+## The prefix modifies the destination with an absolute path
+## to replace the typical CMAKE_INSTALL_PREFIX install rules.
+##
+########################################################################
+function(SOAPY_SDR_MODULE_UTIL)
+
+    include(CMakeParseArguments)
+    CMAKE_PARSE_ARGUMENTS(MODULE "" "TARGET;DESTINATION;PREFIX" "SOURCES;LIBRARIES" ${ARGN})
+
+    include_directories(${SoapySDR_INCLUDE_DIRS})
+    add_library(${MODULE_TARGET} MODULE ${MODULE_SOURCES})
+    target_link_libraries(${MODULE_TARGET} ${MODULE_LIBRARIES} ${SoapySDR_LIBRARIES})
+    set_target_properties(${MODULE_TARGET} PROPERTIES DEBUG_POSTFIX "") #same name in debug mode
+
+    if (NOT MODULE_DESTINATION)
+        set(MODULE_DESTINATION lib${LIB_SUFFIX}/SoapySDR/modules/)
+    endif()
+
+    if (MODULE_PREFIX)
+        set(MODULE_DESTINATION ${MODULE_PREFIX}/${MODULE_DESTINATION})
+    endif()
+
+    install(
+        TARGETS ${MODULE_TARGET}
+        DESTINATION ${MODULE_DESTINATION}
+    )
+
+endfunction(SOAPY_SDR_MODULE_UTIL)
diff --git a/cmake_uninstall.cmake.in b/cmake_uninstall.cmake.in
new file mode 100644
index 0000000..2037e36
--- /dev/null
+++ b/cmake_uninstall.cmake.in
@@ -0,0 +1,21 @@
+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 "\n" ";" files "${files}")
+foreach(file ${files})
+  message(STATUS "Uninstalling $ENV{DESTDIR}${file}")
+  if(IS_SYMLINK "$ENV{DESTDIR}${file}" OR 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(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}")
+    message(STATUS "File $ENV{DESTDIR}${file} does not exist.")
+  endif(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}")
+endforeach(file)
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 0000000..bd47458
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,59 @@
+soapysdr (0.4.3) unstable; urgency=low
+
+  * Release 0.4.3 (2016-04-21)
+
+ -- Josh Blum <josh at pothosware.com>  Thu, 21 Apr 2016 14:42:02 -0400
+
+soapysdr (0.4.2) unstable; urgency=low
+
+  * Release 0.4.2 (2016-03-02)
+
+ -- Josh Blum <josh at pothosware.com>  Wed, 02 Mar 2016 22:36:12 -0800
+
+soapysdr (0.4.1) unstable; urgency=low
+
+  * Release 0.4.1 (2016-01-27)
+
+ -- Josh Blum <josh at pothosware.com>  Wed, 27 Jan 2016 21:55:26 -0800
+
+soapysdr (0.4.0) unstable; urgency=low
+
+  * Release 0.4.0 (2015-12-10)
+
+ -- Josh Blum <josh at pothosware.com>  Thu, 10 Dec 2015 17:15:50 -0800
+
+soapysdr (0.3.1) unstable; urgency=low
+
+  * Release 0.3.1 (2015-10-11)
+
+ -- Josh Blum <josh at pothosware.com>  Sun, 11 Oct 2015 11:25:09 -0700
+
+soapysdr (0.3.0) unstable; urgency=low
+
+  * Release 0.3.0 (2015-10-10)
+
+ -- Josh Blum <josh at pothosware.com>  Sat, 10 Oct 2015 10:59:43 -0700
+
+soapysdr (0.2.3) unstable; urgency=low
+
+  * Release 0.2.3 (2015-10-08)
+
+ -- Josh Blum <josh at pothosware.com>  Thu, 08 Oct 2015 13:10:53 -0700
+
+soapysdr (0.2.2) unstable; urgency=low
+
+  * Release 0.2.2 (2015-08-15)
+
+ -- Josh Blum <josh at pothosware.com>  Sat, 15 Aug 2015 11:37:07 -0700
+
+soapysdr (0.2.1) unstable; urgency=low
+
+  * Release 0.2.1 (2015-07-09)
+
+ -- Josh Blum <josh at pothosware.com>  Thu, 09 Jul 2015 18:44:05 -0700
+
+soapysdr (0.2.0) unstable; urgency=low
+
+  * Release 0.2.0 (2015-06-15)
+
+ -- Josh Blum <josh at pothosware.com>  Mon, 15 Jun 2015 01:11:14 -0400
diff --git a/debian/compat b/debian/compat
new file mode 100644
index 0000000..ec63514
--- /dev/null
+++ b/debian/compat
@@ -0,0 +1 @@
+9
diff --git a/debian/control b/debian/control
new file mode 100644
index 0000000..10d5d4d
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,75 @@
+Source: soapysdr
+Section: comm
+Priority: optional
+Maintainer: Josh Blum <josh at pothosware.com>
+Build-Depends:
+    debhelper (>= 9.0.0),
+    cmake (>= 2.8.7),
+    swig2.0,
+    python,
+    python-dev,
+    python3,
+    python3-dev
+Standards-Version: 3.9.5
+Homepage: https://github.com/pothosware/SoapySDR/wiki
+Vcs-Git: https://github.com/pothosware/SoapySDR.git
+Vcs-Browser: https://github.com/pothosware/SoapySDR
+
+Package: libsoapysdr0.5-2
+Section: libs
+Architecture: any
+Multi-Arch: same
+Pre-Depends: multiarch-support, ${misc:Pre-Depends}
+Depends: ${shlibs:Depends}, ${misc:Depends}
+Description: Soapy SDR - shared library
+ Vendor and platform neutral SDR support library.
+
+Package: libsoapysdr-dev
+Section: libdevel
+Architecture: any
+Depends:
+    libsoapysdr0.5-2 (= ${binary:Version}),
+    ${misc:Depends}
+Description: Soapy SDR - development files
+ Vendor and platform neutral SDR support library.
+
+Package: soapysdr
+Section: comm
+Architecture: any
+Recommends:
+    soapysdr-remote,
+    soapysdr-rtlsdr,
+    soapysdr-hackrf,
+    soapysdr-bladerf,
+    soapysdr-uhd,
+    soapysdr-osmo
+Depends:
+    libsoapysdr0.5-2 (= ${binary:Version}),
+    ${shlibs:Depends},
+    ${misc:Depends}
+Description: Soapy SDR - runtime application
+ Vendor and platform neutral SDR support library.
+
+Package: python-soapysdr
+Section: python
+Architecture: any
+Depends:
+    libsoapysdr0.5-2 (= ${binary:Version}),
+    python,
+    ${shlibs:Depends},
+    ${misc:Depends}
+Recommends: python-numpy
+Description: Soapy SDR - python bindings
+ Vendor and platform neutral SDR support library.
+
+Package: python3-soapysdr
+Section: python
+Architecture: any
+Depends:
+    libsoapysdr0.5-2 (= ${binary:Version}),
+    python3,
+    ${shlibs:Depends},
+    ${misc:Depends}
+Recommends: python-numpy
+Description: Soapy SDR - python3 bindings
+ Vendor and platform neutral SDR support library.
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 0000000..3166daa
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,32 @@
+Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: soapysdr
+Source: https://github.com/pothosware/SoapySDR/wiki
+
+Files: *
+Copyright:
+    Copyright (c) 2014-2016 Josh Blum <josh at pothosware.com>
+    Copyright (c) 2016-2016 Bastille Networks
+License: BSL-1.0
+ Boost Software License - Version 1.0 - August 17th, 2003
+ .
+ Permission is hereby granted, free of charge, to any person or organization
+ obtaining a copy of the software and accompanying documentation covered by
+ this license (the "Software") to use, reproduce, display, distribute,
+ execute, and transmit the Software, and to prepare derivative works of the
+ Software, and to permit third-parties to whom the Software is furnished to
+ do so, all subject to the following:
+ .
+ The copyright notices in the Software and this entire statement, including
+ the above license grant, this restriction and the following disclaimer,
+ must be included in all copies of the Software, in whole or in part, and
+ all derivative works of the Software, unless such copies or derivative
+ works are solely in the form of machine-executable object code generated by
+ a source language processor.
+ .
+ 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, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+ SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+ FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
diff --git a/debian/docs b/debian/docs
new file mode 100644
index 0000000..b43bf86
--- /dev/null
+++ b/debian/docs
@@ -0,0 +1 @@
+README.md
diff --git a/debian/libsoapysdr-dev.install b/debian/libsoapysdr-dev.install
new file mode 100644
index 0000000..a3c9942
--- /dev/null
+++ b/debian/libsoapysdr-dev.install
@@ -0,0 +1,4 @@
+usr/include/
+usr/share/cmake/
+usr/lib/*/pkgconfig/
+usr/lib/*/libSoapySDR.so
diff --git a/debian/libsoapysdr0.5-2.install b/debian/libsoapysdr0.5-2.install
new file mode 100644
index 0000000..e655f59
--- /dev/null
+++ b/debian/libsoapysdr0.5-2.install
@@ -0,0 +1 @@
+usr/lib/*/libSoapySDR.so.*
diff --git a/debian/python-soapysdr.install b/debian/python-soapysdr.install
new file mode 100644
index 0000000..4c0c522
--- /dev/null
+++ b/debian/python-soapysdr.install
@@ -0,0 +1 @@
+usr/lib/python2*/
diff --git a/debian/python3-soapysdr.install b/debian/python3-soapysdr.install
new file mode 100644
index 0000000..5b790f6
--- /dev/null
+++ b/debian/python3-soapysdr.install
@@ -0,0 +1 @@
+usr/lib/python3*/
diff --git a/debian/rules b/debian/rules
new file mode 100755
index 0000000..54955ab
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,27 @@
+#!/usr/bin/make -f
+# -*- makefile -*-
+
+# extract the architecture for setting the library path suffix
+DEB_HOST_MULTIARCH ?= $(shell dpkg-architecture -qDEB_HOST_MULTIARCH)
+
+# extract the last section of the changelog version for extra info
+DEB_VERSION_EXTVER ?= $(shell dpkg-parsechangelog | grep ^Version: | rev | cut -d'-' -f1 | rev)
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+# This has to be exported to make some magic below work.
+export DH_OPTIONS
+
+
+%:
+	dh $@ --buildsystem=cmake
+
+override_dh_auto_configure:
+	dh_auto_configure -- \
+		-DCMAKE_AUTOSET_INSTALL_RPATH=FALSE \
+		-DLIB_SUFFIX="/$(DEB_HOST_MULTIARCH)" \
+		-DSOAPY_SDR_EXTVER="$(DEB_VERSION_EXTVER)"
+
+override_dh_installchangelogs:
+	dh_installchangelogs Changelog.txt
diff --git a/debian/soapysdr.install b/debian/soapysdr.install
new file mode 100644
index 0000000..c703cf8
--- /dev/null
+++ b/debian/soapysdr.install
@@ -0,0 +1 @@
+usr/bin/
diff --git a/debian/source/format b/debian/source/format
new file mode 100644
index 0000000..163aaf8
--- /dev/null
+++ b/debian/source/format
@@ -0,0 +1 @@
+3.0 (quilt)
diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt
new file mode 100644
index 0000000..c7937c1
--- /dev/null
+++ b/docs/CMakeLists.txt
@@ -0,0 +1,45 @@
+########################################################################
+# Optional doxygen generation
+########################################################################
+find_package(Doxygen)
+
+if(DOXYGEN_DOT_FOUND)
+    set(HAVE_DOT YES)
+else()
+    set(HAVE_DOT NO)
+endif()
+
+########################################################################
+## Feature registration
+########################################################################
+include(FeatureSummary)
+include(CMakeDependentOption)
+cmake_dependent_option(ENABLE_DOCS "Enable doxygen documentation" ON "DOXYGEN_FOUND" OFF)
+add_feature_info(Docs ENABLE_DOCS "doxygen documentation")
+if (NOT ENABLE_DOCS)
+    return()
+endif()
+
+#where to look for headers
+set(SOAPY_SDR_INCLUDE_ROOT ${PROJECT_SOURCE_DIR}/include/SoapySDR)
+
+#header sources used in the generate rule
+file(GLOB SOAPY_INCLUDES ${SOAPY_SDR_INCLUDE_ROOT}/*.h*)
+
+#Configure doxygen config file
+configure_file(
+    ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in
+    ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile
+ at ONLY)
+
+#Create doxygen generation build rules
+if (DOXYGEN_FOUND)
+    set(CMAKE_CURRENT_BINARY_DIR_DOXYGEN ${CMAKE_CURRENT_BINARY_DIR}/html)
+    add_custom_command(
+        OUTPUT ${CMAKE_CURRENT_BINARY_DIR_DOXYGEN}
+        DEPENDS ${SOAPY_INCLUDES} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile
+        COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile
+        COMMENT "Generating documentation with doxygen"
+    )
+    add_custom_target(docs ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR_DOXYGEN})
+endif()
diff --git a/docs/Doxyfile.in b/docs/Doxyfile.in
new file mode 100644
index 0000000..0196d8d
--- /dev/null
+++ b/docs/Doxyfile.in
@@ -0,0 +1,2303 @@
+# Doxyfile 1.8.6
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project.
+#
+# All text after a double hash (##) is considered a comment and is placed in
+# front of the TAG it is preceding.
+#
+# All text after a single hash (#) is considered a comment and will be ignored.
+# The format is:
+# TAG = value [value, ...]
+# For lists, items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (\" \").
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all text
+# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv
+# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv
+# for the list of possible encodings.
+# The default value is: UTF-8.
+
+DOXYFILE_ENCODING      = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
+# double-quotes, unless you are using Doxywizard) that should identify the
+# project for which the documentation is generated. This name is used in the
+# title of most generated pages and in a few other places.
+# The default value is: My Project.
+
+PROJECT_NAME           = "SoapySDR"
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
+# could be handy for archiving the generated documentation or if some version
+# control system is used.
+
+PROJECT_NUMBER         = "@SOAPY_SDR_VERSION@"
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer a
+# quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF          = "Vendor and platform neutral SDR interface library"
+
+# With the PROJECT_LOGO tag one can specify an logo or icon that is included in
+# the documentation. The maximum height of the logo should not exceed 55 pixels
+# and the maximum width should not exceed 200 pixels. Doxygen will copy the logo
+# to the output directory.
+
+PROJECT_LOGO           = "@CMAKE_CURRENT_SOURCE_DIR@/soapy_sdr_logo.png"
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
+# into which the generated documentation will be written. If a relative path is
+# entered, it will be relative to the location where doxygen was started. If
+# left blank the current directory will be used.
+
+OUTPUT_DIRECTORY       = "@CMAKE_CURRENT_BINARY_DIR@"
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 4096 sub-
+# directories (in 2 levels) under the output directory of each output format and
+# will distribute the generated files over these directories. Enabling this
+# option can be useful when feeding doxygen a huge amount of source files, where
+# putting all generated files in the same directory would otherwise causes
+# performance problems for the file system.
+# The default value is: NO.
+
+CREATE_SUBDIRS         = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,
+# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),
+# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,
+# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),
+# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,
+# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,
+# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,
+# Ukrainian and Vietnamese.
+# The default value is: English.
+
+OUTPUT_LANGUAGE        = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES doxygen will include brief member
+# descriptions after the members that are listed in the file and class
+# documentation (similar to Javadoc). Set to NO to disable this.
+# The default value is: YES.
+
+BRIEF_MEMBER_DESC      = YES
+
+# If the REPEAT_BRIEF tag is set to YES doxygen will prepend the brief
+# description of a member or function before the detailed description
+#
+# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+# The default value is: YES.
+
+REPEAT_BRIEF           = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator that is
+# used to form the text in various listings. Each string in this list, if found
+# as the leading text of the brief description, will be stripped from the text
+# and the result, after processing the whole list, is used as the annotated
+# text. Otherwise, the brief description is used as-is. If left blank, the
+# following values are used ($name is automatically replaced with the name of
+# the entity):The $name class, The $name widget, The $name file, is, provides,
+# specifies, contains, represents, a, an and the.
+
+ABBREVIATE_BRIEF       =
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# doxygen will generate a detailed section even if there is only a brief
+# description.
+# The default value is: NO.
+
+ALWAYS_DETAILED_SEC    = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+# The default value is: NO.
+
+INLINE_INHERITED_MEMB  = NO
+
+# If the FULL_PATH_NAMES tag is set to YES doxygen will prepend the full path
+# before files name in the file list and in the header files. If set to NO the
+# shortest path that makes the file name unique will be used
+# The default value is: YES.
+
+FULL_PATH_NAMES        = YES
+
+# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.
+# Stripping is only done if one of the specified strings matches the left-hand
+# part of the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the path to
+# strip.
+#
+# Note that you can specify absolute paths here, but also relative paths, which
+# will be relative from the directory where doxygen is started.
+# This tag requires that the tag FULL_PATH_NAMES is set to YES.
+
+STRIP_FROM_PATH        = "@SOAPY_SDR_INCLUDE_ROOT@"
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
+# path mentioned in the documentation of a class, which tells the reader which
+# header file to include in order to use a class. If left blank only the name of
+# the header file containing the class definition is used. Otherwise one should
+# specify the list of include paths that are normally passed to the compiler
+# using the -I flag.
+
+STRIP_FROM_INC_PATH    =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
+# less readable) file names. This can be useful is your file systems doesn't
+# support long names like on DOS, Mac, or CD-ROM.
+# The default value is: NO.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the
+# first line (until the first dot) of a Javadoc-style comment as the brief
+# description. If set to NO, the Javadoc-style will behave just like regular Qt-
+# style comments (thus requiring an explicit @brief command for a brief
+# description.)
+# The default value is: NO.
+
+JAVADOC_AUTOBRIEF      = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
+# line (until the first dot) of a Qt-style comment as the brief description. If
+# set to NO, the Qt-style will behave just like regular Qt-style comments (thus
+# requiring an explicit \brief command for a brief description.)
+# The default value is: NO.
+
+QT_AUTOBRIEF           = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a
+# multi-line C++ special comment block (i.e. a block of //! or /// comments) as
+# a brief description. This used to be the default behavior. The new default is
+# to treat a multi-line C++ comment block as a detailed description. Set this
+# tag to YES if you prefer the old behavior instead.
+#
+# Note that setting this tag to YES also means that rational rose comments are
+# not recognized any more.
+# The default value is: NO.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
+# documentation from any documented member that it re-implements.
+# The default value is: YES.
+
+INHERIT_DOCS           = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce a
+# new page for each member. If set to NO, the documentation of a member will be
+# part of the file/class/namespace that contains it.
+# The default value is: NO.
+
+SEPARATE_MEMBER_PAGES  = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen
+# uses this value to replace tabs by spaces in code fragments.
+# Minimum value: 1, maximum value: 16, default value: 4.
+
+TAB_SIZE               = 4
+
+# This tag can be used to specify a number of aliases that act as commands in
+# the documentation. An alias has the form:
+# name=value
+# For example adding
+# "sideeffect=@par Side Effects:\n"
+# will allow you to put the command \sideeffect (or @sideeffect) in the
+# documentation, which will result in a user-defined paragraph with heading
+# "Side Effects:". You can put \n's in the value part of an alias to insert
+# newlines.
+
+ALIASES                =
+
+# This tag can be used to specify a number of word-keyword mappings (TCL only).
+# A mapping has the form "name=value". For example adding "class=itcl::class"
+# will allow you to use the command class in the itcl::class meaning.
+
+TCL_SUBST              =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
+# only. Doxygen will then generate output that is more tailored for C. For
+# instance, some of the names that are used will be different. The list of all
+# members will be omitted, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_FOR_C  = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or
+# Python sources only. Doxygen will then generate output that is more tailored
+# for that language. For instance, namespaces will be presented as packages,
+# qualified scopes will look different, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_JAVA   = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources. Doxygen will then generate output that is tailored for Fortran.
+# The default value is: NO.
+
+OPTIMIZE_FOR_FORTRAN   = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for VHDL.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_VHDL   = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given
+# extension. Doxygen has a built-in mapping, but you can override or extend it
+# using this tag. The format is ext=language, where ext is a file extension, and
+# language is one of the parsers supported by doxygen: IDL, Java, Javascript,
+# C#, C, C++, D, PHP, Objective-C, Python, Fortran, VHDL. For instance to make
+# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C
+# (default is Fortran), use: inc=Fortran f=C.
+#
+# Note For files without extension you can use no_extension as a placeholder.
+#
+# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
+# the files are not read by doxygen.
+
+EXTENSION_MAPPING      =
+
+# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
+# according to the Markdown format, which allows for more readable
+# documentation. See http://daringfireball.net/projects/markdown/ for details.
+# The output of markdown processing is further processed by doxygen, so you can
+# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
+# case of backward compatibilities issues.
+# The default value is: YES.
+
+MARKDOWN_SUPPORT       = YES
+
+# When enabled doxygen tries to link words that correspond to documented
+# classes, or namespaces to their corresponding documentation. Such a link can
+# be prevented in individual cases by by putting a % sign in front of the word
+# or globally by setting AUTOLINK_SUPPORT to NO.
+# The default value is: YES.
+
+AUTOLINK_SUPPORT       = YES
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should set this
+# tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string);
+# versus func(std::string) {}). This also make the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+# The default value is: NO.
+
+BUILTIN_STL_SUPPORT    = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+# The default value is: NO.
+
+CPP_CLI_SUPPORT        = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:
+# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen
+# will parse them like normal C++ but will assume all classes use public instead
+# of private inheritance when no explicit protection keyword is present.
+# The default value is: NO.
+
+SIP_SUPPORT            = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate
+# getter and setter methods for a property. Setting this option to YES will make
+# doxygen to replace the get and set methods by a property in the documentation.
+# This will only work if the methods are indeed getting or setting a simple
+# type. If this is not the case, or you want to show the methods anyway, you
+# should set this option to NO.
+# The default value is: YES.
+
+IDL_PROPERTY_SUPPORT   = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES, then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+# The default value is: NO.
+
+DISTRIBUTE_GROUP_DOC   = NO
+
+# Set the SUBGROUPING tag to YES to allow class member groups of the same type
+# (for instance a group of public functions) to be put as a subgroup of that
+# type (e.g. under the Public Functions section). Set it to NO to prevent
+# subgrouping. Alternatively, this can be done per class using the
+# \nosubgrouping command.
+# The default value is: YES.
+
+SUBGROUPING            = YES
+
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions
+# are shown inside the group in which they are included (e.g. using \ingroup)
+# instead of on a separate page (for HTML and Man pages) or section (for LaTeX
+# and RTF).
+#
+# Note that this feature does not work in combination with
+# SEPARATE_MEMBER_PAGES.
+# The default value is: NO.
+
+INLINE_GROUPED_CLASSES = NO
+
+# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions
+# with only public data fields or simple typedef fields will be shown inline in
+# the documentation of the scope in which they are defined (i.e. file,
+# namespace, or group documentation), provided this scope is documented. If set
+# to NO, structs, classes, and unions are shown on a separate page (for HTML and
+# Man pages) or section (for LaTeX and RTF).
+# The default value is: NO.
+
+INLINE_SIMPLE_STRUCTS  = NO
+
+# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or
+# enum is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically be
+# useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+# The default value is: NO.
+
+TYPEDEF_HIDES_STRUCT   = NO
+
+# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This
+# cache is used to resolve symbols given their name and scope. Since this can be
+# an expensive process and often the same symbol appears multiple times in the
+# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small
+# doxygen will become slower. If the cache is too large, memory is wasted. The
+# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range
+# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536
+# symbols. At the end of a run doxygen will report the cache usage and suggest
+# the optimal cache size from a speed point of view.
+# Minimum value: 0, maximum value: 9, default value: 0.
+
+LOOKUP_CACHE_SIZE      = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
+# documentation are documented, even if no documentation was available. Private
+# class members and static file members will be hidden unless the
+# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
+# Note: This will also disable the warnings about undocumented members that are
+# normally produced when WARNINGS is set to YES.
+# The default value is: NO.
+
+EXTRACT_ALL            = YES
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class will
+# be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PRIVATE        = NO
+
+# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal
+# scope will be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PACKAGE        = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file will be
+# included in the documentation.
+# The default value is: NO.
+
+EXTRACT_STATIC         = YES
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) defined
+# locally in source files will be included in the documentation. If set to NO
+# only classes defined in header files are included. Does not have any effect
+# for Java sources.
+# The default value is: YES.
+
+EXTRACT_LOCAL_CLASSES  = YES
+
+# This flag is only useful for Objective-C code. When set to YES local methods,
+# which are defined in the implementation section but not in the interface are
+# included in the documentation. If set to NO only methods in the interface are
+# included.
+# The default value is: NO.
+
+EXTRACT_LOCAL_METHODS  = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base name of
+# the file that contains the anonymous namespace. By default anonymous namespace
+# are hidden.
+# The default value is: NO.
+
+EXTRACT_ANON_NSPACES   = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
+# undocumented members inside documented classes or files. If set to NO these
+# members will be included in the various overviews, but no documentation
+# section is generated. This option has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_MEMBERS     = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy. If set
+# to NO these classes will be included in the various overviews. This option has
+# no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_CLASSES     = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
+# (class|struct|union) declarations. If set to NO these declarations will be
+# included in the documentation.
+# The default value is: NO.
+
+HIDE_FRIEND_COMPOUNDS  = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
+# documentation blocks found inside the body of a function. If set to NO these
+# blocks will be appended to the function's detailed documentation block.
+# The default value is: NO.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation that is typed after a
+# \internal command is included. If the tag is set to NO then the documentation
+# will be excluded. Set it to YES to include the internal documentation.
+# The default value is: NO.
+
+INTERNAL_DOCS          = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file
+# names in lower-case letters. If set to YES upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+# The default value is: system dependent.
+
+CASE_SENSE_NAMES       = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with
+# their full class and namespace scopes in the documentation. If set to YES the
+# scope will be hidden.
+# The default value is: NO.
+
+HIDE_SCOPE_NAMES       = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
+# the files that are included by a file in the documentation of that file.
+# The default value is: YES.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each
+# grouped member an include statement to the documentation, telling the reader
+# which file to include in order to use the member.
+# The default value is: NO.
+
+SHOW_GROUPED_MEMB_INC  = NO
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include
+# files with double quotes in the documentation rather than with sharp brackets.
+# The default value is: NO.
+
+FORCE_LOCAL_INCLUDES   = NO
+
+# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the
+# documentation for inline members.
+# The default value is: YES.
+
+INLINE_INFO            = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the
+# (detailed) documentation of file and class members alphabetically by member
+# name. If set to NO the members will appear in declaration order.
+# The default value is: YES.
+
+SORT_MEMBER_DOCS       = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
+# descriptions of file, namespace and class members alphabetically by member
+# name. If set to NO the members will appear in declaration order. Note that
+# this will also influence the order of the classes in the class list.
+# The default value is: NO.
+
+SORT_BRIEF_DOCS        = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the
+# (brief and detailed) documentation of class members so that constructors and
+# destructors are listed first. If set to NO the constructors will appear in the
+# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.
+# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief
+# member documentation.
+# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting
+# detailed member documentation.
+# The default value is: NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy
+# of group names into alphabetical order. If set to NO the group names will
+# appear in their defined order.
+# The default value is: NO.
+
+SORT_GROUP_NAMES       = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by
+# fully-qualified names, including namespaces. If set to NO, the class list will
+# be sorted only by class name, not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the alphabetical
+# list.
+# The default value is: NO.
+
+SORT_BY_SCOPE_NAME     = NO
+
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper
+# type resolution of all parameters of a function it will reject a match between
+# the prototype and the implementation of a member function even if there is
+# only one candidate or it is obvious which candidate to choose by doing a
+# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still
+# accept a match between prototype and implementation in such cases.
+# The default value is: NO.
+
+STRICT_PROTO_MATCHING  = NO
+
+# The GENERATE_TODOLIST tag can be used to enable ( YES) or disable ( NO) the
+# todo list. This list is created by putting \todo commands in the
+# documentation.
+# The default value is: YES.
+
+GENERATE_TODOLIST      = YES
+
+# The GENERATE_TESTLIST tag can be used to enable ( YES) or disable ( NO) the
+# test list. This list is created by putting \test commands in the
+# documentation.
+# The default value is: YES.
+
+GENERATE_TESTLIST      = YES
+
+# The GENERATE_BUGLIST tag can be used to enable ( YES) or disable ( NO) the bug
+# list. This list is created by putting \bug commands in the documentation.
+# The default value is: YES.
+
+GENERATE_BUGLIST       = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable ( YES) or disable ( NO)
+# the deprecated list. This list is created by putting \deprecated commands in
+# the documentation.
+# The default value is: YES.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional documentation
+# sections, marked by \if <section_label> ... \endif and \cond <section_label>
+# ... \endcond blocks.
+
+ENABLED_SECTIONS       =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the
+# initial value of a variable or macro / define can have for it to appear in the
+# documentation. If the initializer consists of more lines than specified here
+# it will be hidden. Use a value of 0 to hide initializers completely. The
+# appearance of the value of individual variables and macros / defines can be
+# controlled using \showinitializer or \hideinitializer command in the
+# documentation regardless of this setting.
+# Minimum value: 0, maximum value: 10000, default value: 30.
+
+MAX_INITIALIZER_LINES  = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at
+# the bottom of the documentation of classes and structs. If set to YES the list
+# will mention the files that were used to generate the documentation.
+# The default value is: YES.
+
+SHOW_USED_FILES        = YES
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This
+# will remove the Files entry from the Quick Index and from the Folder Tree View
+# (if specified).
+# The default value is: YES.
+
+SHOW_FILES             = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
+# page. This will remove the Namespaces entry from the Quick Index and from the
+# Folder Tree View (if specified).
+# The default value is: YES.
+
+SHOW_NAMESPACES        = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command command input-file, where command is the value of the
+# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided
+# by doxygen. Whatever the program writes to standard output is used as the file
+# version. For an example see the documentation.
+
+FILE_VERSION_FILTER    =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. To create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option. You can
+# optionally specify a file name after the option, if omitted DoxygenLayout.xml
+# will be used as the name of the layout file.
+#
+# Note that if you run doxygen from a directory containing a file called
+# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
+# tag is left empty.
+
+LAYOUT_FILE            =
+
+# The CITE_BIB_FILES tag can be used to specify one or more bib files containing
+# the reference definitions. This must be a list of .bib files. The .bib
+# extension is automatically appended if omitted. This requires the bibtex tool
+# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info.
+# For LaTeX the style of the bibliography can be controlled using
+# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the
+# search path. Do not use file names with spaces, bibtex cannot handle them. See
+# also \cite for info how to create references.
+
+CITE_BIB_FILES         =
+
+#---------------------------------------------------------------------------
+# Configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated to
+# standard output by doxygen. If QUIET is set to YES this implies that the
+# messages are off.
+# The default value is: NO.
+
+QUIET                  = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated to standard error ( stderr) by doxygen. If WARNINGS is set to YES
+# this implies that the warnings are on.
+#
+# Tip: Turn warnings on while writing the documentation.
+# The default value is: YES.
+
+WARNINGS               = YES
+
+# If the WARN_IF_UNDOCUMENTED tag is set to YES, then doxygen will generate
+# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
+# will automatically be disabled.
+# The default value is: YES.
+
+WARN_IF_UNDOCUMENTED   = YES
+
+# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some parameters
+# in a documented function, or documenting parameters that don't exist or using
+# markup commands wrongly.
+# The default value is: YES.
+
+WARN_IF_DOC_ERROR      = YES
+
+# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
+# are documented, but have no documentation for their parameters or return
+# value. If set to NO doxygen will only warn about wrong or incomplete parameter
+# documentation, but not about the absence of documentation.
+# The default value is: NO.
+
+WARN_NO_PARAMDOC       = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that doxygen
+# can produce. The string should contain the $file, $line, and $text tags, which
+# will be replaced by the file and line number from which the warning originated
+# and the warning text. Optionally the format may contain $version, which will
+# be replaced by the version of the file (if it could be obtained via
+# FILE_VERSION_FILTER)
+# The default value is: $file:$line: $text.
+
+WARN_FORMAT            = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning and error
+# messages should be written. If left blank the output is written to standard
+# error (stderr).
+
+WARN_LOGFILE           = "@CMAKE_CURRENT_BINARY_DIR@/warn.log"
+
+#---------------------------------------------------------------------------
+# Configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag is used to specify the files and/or directories that contain
+# documented source files. You may enter file names like myfile.cpp or
+# directories like /usr/src/myproject. Separate the files or directories with
+# spaces.
+# Note: If this tag is empty the current directory is searched.
+
+INPUT                  = @SOAPY_SDR_INCLUDE_ROOT@
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
+# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
+# documentation (see: http://www.gnu.org/software/libiconv) for the list of
+# possible encodings.
+# The default value is: UTF-8.
+
+INPUT_ENCODING         = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
+# *.h) to filter out the source-files in the directories. If left blank the
+# following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii,
+# *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp,
+# *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown,
+# *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf,
+# *.qsf, *.as and *.js.
+
+FILE_PATTERNS          =
+
+# The RECURSIVE tag can be used to specify whether or not subdirectories should
+# be searched for input files as well.
+# The default value is: NO.
+
+RECURSIVE              = NO
+
+# The EXCLUDE tag can be used to specify files and/or directories that should be
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+#
+# Note that relative paths are relative to the directory from which doxygen is
+# run.
+
+EXCLUDE                =
+
+# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
+# from the input.
+# The default value is: NO.
+
+EXCLUDE_SYMLINKS       = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories.
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories for example use the pattern */test/*
+
+EXCLUDE_PATTERNS       =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories use the pattern */test/*
+
+EXCLUDE_SYMBOLS        =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or directories
+# that contain example code fragments that are included (see the \include
+# command).
+
+EXAMPLE_PATH           =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
+# *.h) to filter out the source-files in the directories. If left blank all
+# files are included.
+
+EXAMPLE_PATTERNS       =
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude commands
+# irrespective of the value of the RECURSIVE tag.
+# The default value is: NO.
+
+EXAMPLE_RECURSIVE      = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or directories
+# that contain images that are to be included in the documentation (see the
+# \image command).
+
+IMAGE_PATH             =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command:
+#
+# <filter> <input-file>
+#
+# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the
+# name of an input file. Doxygen will then use the output that the filter
+# program writes to standard output. If FILTER_PATTERNS is specified, this tag
+# will be ignored.
+#
+# Note that the filter must not add or remove lines; it is applied before the
+# code is scanned, but not when the output code is generated. If lines are added
+# or removed, the anchors will not be placed correctly.
+
+INPUT_FILTER           =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis. Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match. The filters are a list of the form: pattern=filter
+# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how
+# filters are used. If the FILTER_PATTERNS tag is empty or if none of the
+# patterns match the file name, INPUT_FILTER is applied.
+
+FILTER_PATTERNS        =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER ) will also be used to filter the input files that are used for
+# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).
+# The default value is: NO.
+
+FILTER_SOURCE_FILES    = NO
+
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and
+# it is also possible to disable source filtering for a specific pattern using
+# *.ext= (so without naming a filter).
+# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.
+
+FILTER_SOURCE_PATTERNS =
+
+# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that
+# is part of the input, its contents will be placed on the main page
+# (index.html). This can be useful if you have a project on for instance GitHub
+# and want to reuse the introduction page also for the doxygen output.
+
+USE_MDFILE_AS_MAINPAGE =
+
+#---------------------------------------------------------------------------
+# Configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will be
+# generated. Documented entities will be cross-referenced with these sources.
+#
+# Note: To get rid of all source code in the generated output, make sure that
+# also VERBATIM_HEADERS is set to NO.
+# The default value is: NO.
+
+SOURCE_BROWSER         = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body of functions,
+# classes and enums directly into the documentation.
+# The default value is: NO.
+
+INLINE_SOURCES         = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any
+# special comment blocks from generated source code fragments. Normal C, C++ and
+# Fortran comments will always remain visible.
+# The default value is: YES.
+
+STRIP_CODE_COMMENTS    = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES then for each documented
+# function all documented functions referencing it will be listed.
+# The default value is: NO.
+
+REFERENCED_BY_RELATION = NO
+
+# If the REFERENCES_RELATION tag is set to YES then for each documented function
+# all documented entities called/used by that function will be listed.
+# The default value is: NO.
+
+REFERENCES_RELATION    = NO
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
+# to YES, then the hyperlinks from functions in REFERENCES_RELATION and
+# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will
+# link to the documentation.
+# The default value is: YES.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the
+# source code will show a tooltip with additional information such as prototype,
+# brief description and links to the definition and documentation. Since this
+# will make the HTML file larger and loading of large files a bit slower, you
+# can opt to disable this feature.
+# The default value is: YES.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+SOURCE_TOOLTIPS        = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code will
+# point to the HTML generated by the htags(1) tool instead of doxygen built-in
+# source browser. The htags tool is part of GNU's global source tagging system
+# (see http://www.gnu.org/software/global/global.html). You will need version
+# 4.8.6 or higher.
+#
+# To use it do the following:
+# - Install the latest version of global
+# - Enable SOURCE_BROWSER and USE_HTAGS in the config file
+# - Make sure the INPUT points to the root of the source tree
+# - Run doxygen as normal
+#
+# Doxygen will invoke htags (and that will in turn invoke gtags), so these
+# tools must be available from the command line (i.e. in the search path).
+#
+# The result: instead of the source browser generated by doxygen, the links to
+# source code will now point to the output of htags.
+# The default value is: NO.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+USE_HTAGS              = NO
+
+# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a
+# verbatim copy of the header file for each class for which an include is
+# specified. Set to NO to disable this.
+# See also: Section \class.
+# The default value is: YES.
+
+VERBATIM_HEADERS       = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all
+# compounds will be generated. Enable this if the project contains a lot of
+# classes, structs, unions or interfaces.
+# The default value is: YES.
+
+ALPHABETICAL_INDEX     = YES
+
+# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in
+# which the alphabetical index list will be split.
+# Minimum value: 1, maximum value: 20, default value: 5.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+COLS_IN_ALPHA_INDEX    = 5
+
+# In case all classes in a project start with a common prefix, all classes will
+# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
+# can be used to specify a prefix (or a list of prefixes) that should be ignored
+# while generating the index headers.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+IGNORE_PREFIX          =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES doxygen will generate HTML output
+# The default value is: YES.
+
+GENERATE_HTML          = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_OUTPUT            = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
+# generated HTML page (for example: .htm, .php, .asp).
+# The default value is: .html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a user-defined HTML header file for
+# each generated HTML page. If the tag is left blank doxygen will generate a
+# standard header.
+#
+# To get valid HTML the header file that includes any scripts and style sheets
+# that doxygen needs, which is dependent on the configuration options used (e.g.
+# the setting GENERATE_TREEVIEW). It is highly recommended to start with a
+# default header using
+# doxygen -w html new_header.html new_footer.html new_stylesheet.css
+# YourConfigFile
+# and then modify the file new_header.html. See also section "Doxygen usage"
+# for information on how to generate the default header that doxygen normally
+# uses.
+# Note: The header is subject to change so you typically have to regenerate the
+# default header when upgrading to a newer version of doxygen. For a description
+# of the possible markers and block names see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_HEADER            =
+
+# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
+# generated HTML page. If the tag is left blank doxygen will generate a standard
+# footer. See HTML_HEADER for more information on how to generate a default
+# footer and what special commands can be used inside the footer. See also
+# section "Doxygen usage" for information on how to generate the default footer
+# that doxygen normally uses.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FOOTER            =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
+# sheet that is used by each HTML page. It can be used to fine-tune the look of
+# the HTML output. If left blank doxygen will generate a default style sheet.
+# See also section "Doxygen usage" for information on how to generate the style
+# sheet that doxygen normally uses.
+# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as
+# it is more robust and this tag (HTML_STYLESHEET) will in the future become
+# obsolete.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_STYLESHEET        =
+
+# The HTML_EXTRA_STYLESHEET tag can be used to specify an additional user-
+# defined cascading style sheet that is included after the standard style sheets
+# created by doxygen. Using this option one can overrule certain style aspects.
+# This is preferred over using HTML_STYLESHEET since it does not replace the
+# standard style sheet and is therefor more robust against future updates.
+# Doxygen will copy the style sheet file to the output directory. For an example
+# see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_STYLESHEET  =
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that the
+# files will be copied as-is; there are no commands or markers available.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_FILES       =
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
+# will adjust the colors in the stylesheet and background images according to
+# this color. Hue is specified as an angle on a colorwheel, see
+# http://en.wikipedia.org/wiki/Hue for more information. For instance the value
+# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
+# purple, and 360 is red again.
+# Minimum value: 0, maximum value: 359, default value: 220.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_HUE    = 220
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
+# in the HTML output. For a value of 0 the output will use grayscales only. A
+# value of 255 will produce the most vivid colors.
+# Minimum value: 0, maximum value: 255, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_SAT    = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the
+# luminance component of the colors in the HTML output. Values below 100
+# gradually make the output lighter, whereas values above 100 make the output
+# darker. The value divided by 100 is the actual gamma applied, so 80 represents
+# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not
+# change the gamma.
+# Minimum value: 40, maximum value: 240, default value: 80.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_GAMMA  = 80
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
+# page will contain the date and time when the page was generated. Setting this
+# to NO can help when comparing the output of multiple runs.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_TIMESTAMP         = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_DYNAMIC_SECTIONS  = NO
+
+# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
+# shown in the various tree structured indices initially; the user can expand
+# and collapse entries dynamically later on. Doxygen will expand the tree to
+# such a level that at most the specified number of entries are visible (unless
+# a fully collapsed tree already exceeds this amount). So setting the number of
+# entries 1 will produce a full collapsed tree by default. 0 is a special value
+# representing an infinite number of entries and will result in a full expanded
+# tree by default.
+# Minimum value: 0, maximum value: 9999, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_INDEX_NUM_ENTRIES = 100
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files will be
+# generated that can be used as input for Apple's Xcode 3 integrated development
+# environment (see: http://developer.apple.com/tools/xcode/), introduced with
+# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a
+# Makefile in the HTML output directory. Running make will produce the docset in
+# that directory and running make install will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
+# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
+# for more information.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_DOCSET        = NO
+
+# This tag determines the name of the docset feed. A documentation feed provides
+# an umbrella under which multiple documentation sets from a single provider
+# (such as a company or product suite) can be grouped.
+# The default value is: Doxygen generated docs.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_FEEDNAME        = "Doxygen generated docs"
+
+# This tag specifies a string that should uniquely identify the documentation
+# set bundle. This should be a reverse domain-name style string, e.g.
+# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_BUNDLE_ID       = org.doxygen.Project
+
+# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+# The default value is: org.doxygen.Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_ID    = org.doxygen.Publisher
+
+# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.
+# The default value is: Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_NAME  = Publisher
+
+# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
+# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
+# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
+# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on
+# Windows.
+#
+# The HTML Help Workshop contains a compiler that can convert all HTML output
+# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
+# files are now used as the Windows 98 help format, and will replace the old
+# Windows help format (.hlp) on all Windows platforms in the future. Compressed
+# HTML files also contain an index, a table of contents, and you can search for
+# words in the documentation. The HTML workshop also contains a viewer for
+# compressed HTML files.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_HTMLHELP      = NO
+
+# The CHM_FILE tag can be used to specify the file name of the resulting .chm
+# file. You can add a path in front of the file if the result should not be
+# written to the html output directory.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_FILE               =
+
+# The HHC_LOCATION tag can be used to specify the location (absolute path
+# including file name) of the HTML help compiler ( hhc.exe). If non-empty
+# doxygen will try to run the HTML help compiler on the generated index.hhp.
+# The file has to be specified with full path.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+HHC_LOCATION           =
+
+# The GENERATE_CHI flag controls if a separate .chi index file is generated (
+# YES) or that it should be included in the master .chm file ( NO).
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+GENERATE_CHI           = NO
+
+# The CHM_INDEX_ENCODING is used to encode HtmlHelp index ( hhk), content ( hhc)
+# and project file content.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_INDEX_ENCODING     =
+
+# The BINARY_TOC flag controls whether a binary table of contents is generated (
+# YES) or a normal table of contents ( NO) in the .chm file.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members to
+# the table of contents of the HTML help documentation and to the tree view.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+TOC_EXPAND             = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
+# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
+# (.qch) of the generated HTML documentation.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_QHP           = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify
+# the file name of the resulting .qch file. The path specified is relative to
+# the HTML output folder.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QCH_FILE               =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
+# Project output. For more information please see Qt Help Project / Namespace
+# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace).
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_NAMESPACE          = org.doxygen.Project
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
+# Help Project output. For more information please see Qt Help Project / Virtual
+# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual-
+# folders).
+# The default value is: doc.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_VIRTUAL_FOLDER     = doc
+
+# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
+# filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
+# filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_NAME   =
+
+# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
+# filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_ATTRS  =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's filter section matches. Qt Help Project / Filter Attributes (see:
+# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_SECT_FILTER_ATTRS  =
+
+# The QHG_LOCATION tag can be used to specify the location of Qt's
+# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the
+# generated .qhp file.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHG_LOCATION           =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be
+# generated, together with the HTML files, they form an Eclipse help plugin. To
+# install this plugin and make it available under the help contents menu in
+# Eclipse, the contents of the directory containing the HTML and XML files needs
+# to be copied into the plugins directory of eclipse. The name of the directory
+# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.
+# After copying Eclipse needs to be restarted before the help appears.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_ECLIPSEHELP   = NO
+
+# A unique identifier for the Eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have this
+# name. Each documentation set should have its own identifier.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.
+
+ECLIPSE_DOC_ID         = org.doxygen.Project
+
+# If you want full control over the layout of the generated HTML pages it might
+# be necessary to disable the index and replace it with your own. The
+# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top
+# of each HTML page. A value of NO enables the index and the value YES disables
+# it. Since the tabs in the index contain the same information as the navigation
+# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+DISABLE_INDEX          = NO
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information. If the tag
+# value is set to YES, a side panel will be generated containing a tree-like
+# index structure (just like the one that is generated for HTML Help). For this
+# to work a browser that supports JavaScript, DHTML, CSS and frames is required
+# (i.e. any modern browser). Windows users are probably better off using the
+# HTML help feature. Via custom stylesheets (see HTML_EXTRA_STYLESHEET) one can
+# further fine-tune the look of the index. As an example, the default style
+# sheet generated by doxygen has an example that shows how to put an image at
+# the root of the tree instead of the PROJECT_NAME. Since the tree basically has
+# the same information as the tab index, you could consider setting
+# DISABLE_INDEX to YES when enabling this option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_TREEVIEW      = NO
+
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
+# doxygen will group on one line in the generated HTML documentation.
+#
+# Note that a value of 0 will completely suppress the enum values from appearing
+# in the overview section.
+# Minimum value: 0, maximum value: 20, default value: 4.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+ENUM_VALUES_PER_LINE   = 4
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used
+# to set the initial width (in pixels) of the frame in which the tree is shown.
+# Minimum value: 0, maximum value: 1500, default value: 250.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+TREEVIEW_WIDTH         = 250
+
+# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open links to
+# external symbols imported via tag files in a separate window.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+EXT_LINKS_IN_WINDOW    = NO
+
+# Use this tag to change the font size of LaTeX formulas included as images in
+# the HTML documentation. When you change the font size after a successful
+# doxygen run you need to manually remove any form_*.png images from the HTML
+# output directory to force them to be regenerated.
+# Minimum value: 8, maximum value: 50, default value: 10.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_FONTSIZE       = 10
+
+# Use the FORMULA_TRANPARENT tag to determine whether or not the images
+# generated for formulas are transparent PNGs. Transparent PNGs are not
+# supported properly for IE 6.0, but are supported on all modern browsers.
+#
+# Note that when changing this option you need to delete any form_*.png files in
+# the HTML output directory before the changes have effect.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_TRANSPARENT    = YES
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
+# http://www.mathjax.org) which uses client side Javascript for the rendering
+# instead of using prerendered bitmaps. Use this if you do not have LaTeX
+# installed or if you want to formulas look prettier in the HTML output. When
+# enabled you may also need to install MathJax separately and configure the path
+# to it using the MATHJAX_RELPATH option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+USE_MATHJAX            = NO
+
+# When MathJax is enabled you can set the default output format to be used for
+# the MathJax output. See the MathJax site (see:
+# http://docs.mathjax.org/en/latest/output.html) for more details.
+# Possible values are: HTML-CSS (which is slower, but has the best
+# compatibility), NativeMML (i.e. MathML) and SVG.
+# The default value is: HTML-CSS.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_FORMAT         = HTML-CSS
+
+# When MathJax is enabled you need to specify the location relative to the HTML
+# output directory using the MATHJAX_RELPATH option. The destination directory
+# should contain the MathJax.js script. For instance, if the mathjax directory
+# is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
+# Content Delivery Network so you can quickly see the result without installing
+# MathJax. However, it is strongly recommended to install a local copy of
+# MathJax from http://www.mathjax.org before deployment.
+# The default value is: http://cdn.mathjax.org/mathjax/latest.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_RELPATH        = http://cdn.mathjax.org/mathjax/latest
+
+# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
+# extension names that should be enabled during MathJax rendering. For example
+# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_EXTENSIONS     =
+
+# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
+# of code that will be used on startup of the MathJax code. See the MathJax site
+# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an
+# example see the documentation.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_CODEFILE       =
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box for
+# the HTML output. The underlying search engine uses javascript and DHTML and
+# should work on any modern browser. Note that when using HTML help
+# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)
+# there is already a search function so this one should typically be disabled.
+# For large projects the javascript based search engine can be slow, then
+# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to
+# search using the keyboard; to jump to the search box use <access key> + S
+# (what the <access key> is depends on the OS and browser, but it is typically
+# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down
+# key> to jump into the search results window, the results can be navigated
+# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel
+# the search. The filter options can be selected when the cursor is inside the
+# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>
+# to select a filter and <Enter> or <escape> to activate or cancel the filter
+# option.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+SEARCHENGINE           = YES
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a web server instead of a web client using Javascript. There
+# are two flavours of web server based searching depending on the
+# EXTERNAL_SEARCH setting. When disabled, doxygen will generate a PHP script for
+# searching and an index file used by the script. When EXTERNAL_SEARCH is
+# enabled the indexing and searching needs to be provided by external tools. See
+# the section "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SERVER_BASED_SEARCH    = NO
+
+# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP
+# script for searching. Instead the search results are written to an XML file
+# which needs to be processed by an external indexer. Doxygen will invoke an
+# external search engine pointed to by the SEARCHENGINE_URL option to obtain the
+# search results.
+#
+# Doxygen ships with an example indexer ( doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: http://xapian.org/).
+#
+# See the section "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH        = NO
+
+# The SEARCHENGINE_URL should point to a search engine hosted by a web server
+# which will return the search results when EXTERNAL_SEARCH is enabled.
+#
+# Doxygen ships with an example indexer ( doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: http://xapian.org/). See the section "External Indexing and
+# Searching" for details.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHENGINE_URL       =
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed
+# search data is written to a file for indexing by an external tool. With the
+# SEARCHDATA_FILE tag the name of this file can be specified.
+# The default file is: searchdata.xml.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHDATA_FILE        = searchdata.xml
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the
+# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is
+# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple
+# projects and redirect the results back to the right project.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH_ID     =
+
+# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen
+# projects other than the one defined by this configuration file, but that are
+# all added to the same external search index. Each project needs to have a
+# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of
+# to a relative location where the documentation can be found. The format is:
+# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTRA_SEARCH_MAPPINGS  =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES doxygen will generate LaTeX output.
+# The default value is: YES.
+
+GENERATE_LATEX         = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_OUTPUT           = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked.
+#
+# Note that when enabling USE_PDFLATEX this option is only used for generating
+# bitmaps for formulas in the HTML output, but not in the Makefile that is
+# written to the output directory.
+# The default file is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_CMD_NAME         = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
+# index for LaTeX.
+# The default file is: makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+MAKEINDEX_CMD_NAME     = makeindex
+
+# If the COMPACT_LATEX tag is set to YES doxygen generates more compact LaTeX
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+COMPACT_LATEX          = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used by the
+# printer.
+# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x
+# 14 inches) and executive (7.25 x 10.5 inches).
+# The default value is: a4.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PAPER_TYPE             = a4
+
+# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
+# that should be included in the LaTeX output. To get the times font for
+# instance you can specify
+# EXTRA_PACKAGES=times
+# If left blank no extra packages will be included.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+EXTRA_PACKAGES         =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the
+# generated LaTeX document. The header should contain everything until the first
+# chapter. If it is left blank doxygen will generate a standard header. See
+# section "Doxygen usage" for information on how to let doxygen write the
+# default header to a separate file.
+#
+# Note: Only use a user-defined header if you know what you are doing! The
+# following commands have a special meaning inside the header: $title,
+# $datetime, $date, $doxygenversion, $projectname, $projectnumber. Doxygen will
+# replace them by respectively the title of the page, the current date and time,
+# only the current date, the version number of doxygen, the project name (see
+# PROJECT_NAME), or the project number (see PROJECT_NUMBER).
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HEADER           =
+
+# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the
+# generated LaTeX document. The footer should contain everything after the last
+# chapter. If it is left blank doxygen will generate a standard footer.
+#
+# Note: Only use a user-defined footer if you know what you are doing!
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_FOOTER           =
+
+# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the LATEX_OUTPUT output
+# directory. Note that the files will be copied as-is; there are no commands or
+# markers available.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_FILES      =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is
+# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will
+# contain links (just like the HTML output) instead of page references. This
+# makes the output suitable for online browsing using a PDF viewer.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PDF_HYPERLINKS         = YES
+
+# If the LATEX_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate
+# the PDF file directly from the LaTeX files. Set this option to YES to get a
+# higher quality PDF documentation.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+USE_PDFLATEX           = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
+# command to the generated LaTeX files. This will instruct LaTeX to keep running
+# if errors occur, instead of asking the user for help. This option is also used
+# when generating formulas in HTML.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BATCHMODE        = NO
+
+# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the
+# index chapters (such as File Index, Compound Index, etc.) in the output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HIDE_INDICES     = NO
+
+# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source
+# code with syntax highlighting in the LaTeX output.
+#
+# Note that which sources are shown also depends on other settings such as
+# SOURCE_BROWSER.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_SOURCE_CODE      = NO
+
+# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
+# bibliography, e.g. plainnat, or ieeetr. See
+# http://en.wikipedia.org/wiki/BibTeX and \cite for more info.
+# The default value is: plain.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BIB_STYLE        = plain
+
+#---------------------------------------------------------------------------
+# Configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES doxygen will generate RTF output. The
+# RTF output is optimized for Word 97 and may not look too pretty with other RTF
+# readers/editors.
+# The default value is: NO.
+
+GENERATE_RTF           = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: rtf.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_OUTPUT             = rtf
+
+# If the COMPACT_RTF tag is set to YES doxygen generates more compact RTF
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+COMPACT_RTF            = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will
+# contain hyperlink fields. The RTF file will contain links (just like the HTML
+# output) instead of page references. This makes the output suitable for online
+# browsing using Word or some other Word compatible readers that support those
+# fields.
+#
+# Note: WordPad (write) and others do not support links.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_HYPERLINKS         = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's config
+# file, i.e. a series of assignments. You only have to provide replacements,
+# missing definitions are set to their default value.
+#
+# See also section "Doxygen usage" for information on how to generate the
+# default style sheet that doxygen normally uses.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_STYLESHEET_FILE    =
+
+# Set optional variables used in the generation of an RTF document. Syntax is
+# similar to doxygen's config file. A template extensions file can be generated
+# using doxygen -e rtf extensionFile.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_EXTENSIONS_FILE    =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES doxygen will generate man pages for
+# classes and files.
+# The default value is: NO.
+
+GENERATE_MAN           = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it. A directory man3 will be created inside the directory specified by
+# MAN_OUTPUT.
+# The default directory is: man.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_OUTPUT             = man
+
+# The MAN_EXTENSION tag determines the extension that is added to the generated
+# man pages. In case the manual section does not start with a number, the number
+# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is
+# optional.
+# The default value is: .3.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_EXTENSION          = .3
+
+# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it
+# will generate one additional man file for each entity documented in the real
+# man page(s). These additional files only source the real man page, but without
+# them the man command would be unable to find the correct page.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_LINKS              = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES doxygen will generate an XML file that
+# captures the structure of the code including all documentation.
+# The default value is: NO.
+
+GENERATE_XML           = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: xml.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_OUTPUT             = xml
+
+# The XML_SCHEMA tag can be used to specify a XML schema, which can be used by a
+# validating XML parser to check the syntax of the XML files.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_SCHEMA             =
+
+# The XML_DTD tag can be used to specify a XML DTD, which can be used by a
+# validating XML parser to check the syntax of the XML files.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_DTD                =
+
+# If the XML_PROGRAMLISTING tag is set to YES doxygen will dump the program
+# listings (including syntax highlighting and cross-referencing information) to
+# the XML output. Note that enabling this will significantly increase the size
+# of the XML output.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_PROGRAMLISTING     = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to the DOCBOOK output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_DOCBOOK tag is set to YES doxygen will generate Docbook files
+# that can be used to generate PDF.
+# The default value is: NO.
+
+GENERATE_DOCBOOK       = NO
+
+# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in
+# front of it.
+# The default directory is: docbook.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_OUTPUT         = docbook
+
+#---------------------------------------------------------------------------
+# Configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES doxygen will generate an AutoGen
+# Definitions (see http://autogen.sf.net) file that captures the structure of
+# the code including all documentation. Note that this feature is still
+# experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES doxygen will generate a Perl module
+# file that captures the structure of the code including all documentation.
+#
+# Note that this feature is still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_PERLMOD       = NO
+
+# If the PERLMOD_LATEX tag is set to YES doxygen will generate the necessary
+# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI
+# output from the Perl module output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_LATEX          = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be nicely
+# formatted so it can be parsed by a human reader. This is useful if you want to
+# understand what is going on. On the other hand, if this tag is set to NO the
+# size of the Perl module output will be much smaller and Perl will parse it
+# just the same.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_PRETTY         = YES
+
+# The names of the make variables in the generated doxyrules.make file are
+# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful
+# so different doxyrules.make files included by the same Makefile don't
+# overwrite each other's variables.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES doxygen will evaluate all
+# C-preprocessor directives found in the sources and include files.
+# The default value is: YES.
+
+ENABLE_PREPROCESSING   = YES
+
+# If the MACRO_EXPANSION tag is set to YES doxygen will expand all macro names
+# in the source code. If set to NO only conditional compilation will be
+# performed. Macro expansion can be done in a controlled way by setting
+# EXPAND_ONLY_PREDEF to YES.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+MACRO_EXPANSION        = YES
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
+# the macro expansion is limited to the macros specified with the PREDEFINED and
+# EXPAND_AS_DEFINED tags.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_ONLY_PREDEF     = NO
+
+# If the SEARCH_INCLUDES tag is set to YES the includes files in the
+# INCLUDE_PATH will be searched if a #include is found.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by the
+# preprocessor.
+# This tag requires that the tag SEARCH_INCLUDES is set to YES.
+
+INCLUDE_PATH           =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will be
+# used.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+INCLUDE_FILE_PATTERNS  =
+
+# The PREDEFINED tag can be used to specify one or more macro names that are
+# defined before the preprocessor is started (similar to the -D option of e.g.
+# gcc). The argument of the tag is a list of macros of the form: name or
+# name=definition (no spaces). If the definition and the "=" are omitted, "=1"
+# is assumed. To prevent a macro definition from being undefined via #undef or
+# recursively expanded use the := operator instead of the = operator.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+PREDEFINED             =
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
+# tag can be used to specify a list of macro names that should be expanded. The
+# macro definition that is found in the sources will be used. Use the PREDEFINED
+# tag if you want to use a different macro definition that overrules the
+# definition found in the source code.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_AS_DEFINED      =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
+# remove all refrences to function-like macros that are alone on a line, have an
+# all uppercase name, and do not end with a semicolon. Such function macros are
+# typically used for boiler-plate code, and will confuse the parser if not
+# removed.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES tag can be used to specify one or more tag files. For each tag
+# file the location of the external documentation should be added. The format of
+# a tag file without this location is as follows:
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where loc1 and loc2 can be relative or absolute paths or URLs. See the
+# section "Linking to external documentation" for more information about the use
+# of tag files.
+# Note: Each tag file must have an unique name (where the name does NOT include
+# the path). If a tag file is not located in the directory in which doxygen is
+# run, you must also specify the path to the tagfile here.
+
+TAGFILES               =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create a
+# tag file that is based on the input files it reads. See section "Linking to
+# external documentation" for more information about the usage of tag files.
+
+GENERATE_TAGFILE       =
+
+# If the ALLEXTERNALS tag is set to YES all external class will be listed in the
+# class index. If set to NO only the inherited external classes will be listed.
+# The default value is: NO.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed in
+# the modules index. If set to NO, only the current project's groups will be
+# listed.
+# The default value is: YES.
+
+EXTERNAL_GROUPS        = YES
+
+# If the EXTERNAL_PAGES tag is set to YES all external pages will be listed in
+# the related pages index. If set to NO, only the current project's pages will
+# be listed.
+# The default value is: YES.
+
+EXTERNAL_PAGES         = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of 'which perl').
+# The default file (with absolute path) is: /usr/bin/perl.
+
+PERL_PATH              = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES doxygen will generate a class diagram
+# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to
+# NO turns the diagrams off. Note that this option also works with HAVE_DOT
+# disabled, but it is recommended to install and use dot, since it yields more
+# powerful graphs.
+# The default value is: YES.
+
+CLASS_DIAGRAMS         = YES
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see:
+# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH            =
+
+# You can include diagrams made with dia in doxygen documentation. Doxygen will
+# then run dia to produce the diagram and insert it in the documentation. The
+# DIA_PATH tag allows you to specify the directory where the dia binary resides.
+# If left empty dia is assumed to be found in the default search path.
+
+DIA_PATH               =
+
+# If set to YES, the inheritance and collaboration graphs will hide inheritance
+# and usage relations if the target is undocumented or is not a class.
+# The default value is: YES.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz (see:
+# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
+# Bell Labs. The other options in this section have no effect if this option is
+# set to NO
+# The default value is: NO.
+
+HAVE_DOT               = @HAVE_DOT@
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
+# to run in parallel. When set to 0 doxygen will base this on the number of
+# processors available in the system. You can set it explicitly to a value
+# larger than 0 to get control over the balance between CPU load and processing
+# speed.
+# Minimum value: 0, maximum value: 32, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_NUM_THREADS        = 0
+
+# When you want a differently looking font n the dot files that doxygen
+# generates you can specify the font name using DOT_FONTNAME. You need to make
+# sure dot is able to find the font, which can be done by putting it in a
+# standard location or by setting the DOTFONTPATH environment variable or by
+# setting DOT_FONTPATH to the directory containing the font.
+# The default value is: Helvetica.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTNAME           = Helvetica
+
+# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
+# dot graphs.
+# Minimum value: 4, maximum value: 24, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTSIZE           = 10
+
+# By default doxygen will tell dot to use the default font as specified with
+# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
+# the path where dot can find it using this tag.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTPATH           =
+
+# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for
+# each documented class showing the direct and indirect inheritance relations.
+# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CLASS_GRAPH            = YES
+
+# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
+# graph for each documented class showing the direct and indirect implementation
+# dependencies (inheritance, containment, and class references variables) of the
+# class with other documented classes.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+COLLABORATION_GRAPH    = NO
+
+# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
+# groups, showing the direct groups dependencies.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GROUP_GRAPHS           = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LOOK               = NO
+
+# If the UML_LOOK tag is enabled, the fields and methods are shown inside the
+# class node. If there are many fields or methods and many nodes the graph may
+# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the
+# number of items for each type to make the size more manageable. Set this to 0
+# for no limit. Note that the threshold may be exceeded by 50% before the limit
+# is enforced. So when you set the threshold to 10, up to 15 fields may appear,
+# but if the number exceeds 15, the total amount of fields shown is limited to
+# 10.
+# Minimum value: 0, maximum value: 100, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LIMIT_NUM_FIELDS   = 10
+
+# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
+# collaboration graphs will show the relations between templates and their
+# instances.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+TEMPLATE_RELATIONS     = NO
+
+# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
+# YES then doxygen will generate a graph for each documented file showing the
+# direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDE_GRAPH          = NO
+
+# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
+# set to YES then doxygen will generate a graph for each documented file showing
+# the direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDED_BY_GRAPH      = NO
+
+# If the CALL_GRAPH tag is set to YES then doxygen will generate a call
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable call graphs for selected
+# functions only using the \callgraph command.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALL_GRAPH             = NO
+
+# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable caller graphs for selected
+# functions only using the \callergraph command.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALLER_GRAPH           = NO
+
+# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
+# hierarchy of all classes instead of a textual one.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GRAPHICAL_HIERARCHY    = YES
+
+# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
+# dependencies a directory has on other directories in a graphical way. The
+# dependency relations are determined by the #include relations between the
+# files in the directories.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DIRECTORY_GRAPH        = NO
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot.
+# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
+# to make the SVG files visible in IE 9+ (other browsers do not have this
+# requirement).
+# Possible values are: png, jpg, gif and svg.
+# The default value is: png.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_IMAGE_FORMAT       = png
+
+# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
+# enable generation of interactive SVG images that allow zooming and panning.
+#
+# Note that this requires a modern browser other than Internet Explorer. Tested
+# and working are Firefox, Chrome, Safari, and Opera.
+# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make
+# the SVG files visible. Older versions of IE do not have SVG support.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INTERACTIVE_SVG        = NO
+
+# The DOT_PATH tag can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_PATH               = "@DOXYGEN_DOT_PATH@"
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the \dotfile
+# command).
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOTFILE_DIRS           =
+
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the \mscfile
+# command).
+
+MSCFILE_DIRS           =
+
+# The DIAFILE_DIRS tag can be used to specify one or more directories that
+# contain dia files that are included in the documentation (see the \diafile
+# command).
+
+DIAFILE_DIRS           =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
+# that will be shown in the graph. If the number of nodes in a graph becomes
+# larger than this value, doxygen will truncate the graph, which is visualized
+# by representing a node as a red box. Note that doxygen if the number of direct
+# children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that
+# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+# Minimum value: 0, maximum value: 10000, default value: 50.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_GRAPH_MAX_NODES    = 1000
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs
+# generated by dot. A depth value of 3 means that only nodes reachable from the
+# root by following a path via at most 3 edges will be shown. Nodes that lay
+# further from the root node will be omitted. Note that setting this option to 1
+# or 2 may greatly reduce the computation time needed for large code bases. Also
+# note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+# Minimum value: 0, maximum value: 1000, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+MAX_DOT_GRAPH_DEPTH    = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not seem
+# to support this out of the box.
+#
+# Warning: Depending on the platform used, enabling this option may lead to
+# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
+# read).
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_TRANSPARENT        = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10) support
+# this, this feature is disabled by default.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_MULTI_TARGETS      = YES
+
+# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
+# explaining the meaning of the various boxes and arrows in the dot generated
+# graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES doxygen will remove the intermediate dot
+# files that are used to generate the various graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_CLEANUP            = YES
diff --git a/docs/soapy_sdr_logo.png b/docs/soapy_sdr_logo.png
new file mode 100644
index 0000000..73cf125
Binary files /dev/null and b/docs/soapy_sdr_logo.png differ
diff --git a/include/SoapySDR/Config.h b/include/SoapySDR/Config.h
new file mode 100644
index 0000000..4221015
--- /dev/null
+++ b/include/SoapySDR/Config.h
@@ -0,0 +1,55 @@
+///
+/// \file SoapySDR/Config.h
+///
+/// Common macro definitions for Soapy SDR library API export.
+///
+/// \copyright
+/// Copyright (c) 2014-2014 Josh Blum
+/// SPDX-License-Identifier: BSL-1.0
+///
+
+#pragma once
+// http://gcc.gnu.org/wiki/Visibility
+// Generic helper definitions for shared library support
+#if defined _WIN32 || defined __CYGWIN__
+  #define SOAPY_SDR_HELPER_DLL_IMPORT __declspec(dllimport)
+  #define SOAPY_SDR_HELPER_DLL_EXPORT __declspec(dllexport)
+  #define SOAPY_SDR_HELPER_DLL_LOCAL
+#else
+  #if __GNUC__ >= 4
+    #define SOAPY_SDR_HELPER_DLL_IMPORT __attribute__ ((visibility ("default")))
+    #define SOAPY_SDR_HELPER_DLL_EXPORT __attribute__ ((visibility ("default")))
+    #define SOAPY_SDR_HELPER_DLL_LOCAL  __attribute__ ((visibility ("hidden")))
+  #else
+    #define SOAPY_SDR_HELPER_DLL_IMPORT
+    #define SOAPY_SDR_HELPER_DLL_EXPORT
+    #define SOAPY_SDR_HELPER_DLL_LOCAL
+  #endif
+#endif
+
+// Now we use the generic helper definitions above to define SOAPY_SDR_API and SOAPY_SDR_LOCAL.
+// SOAPY_SDR_API is used for the public API symbols. It either DLL imports or DLL exports (or does nothing for static build)
+// SOAPY_SDR_LOCAL is used for non-api symbols.
+
+#define SOAPY_SDR_DLL //always building a DLL
+
+#ifdef SOAPY_SDR_DLL // defined if SOAPY is compiled as a DLL
+  #ifdef SOAPY_SDR_DLL_EXPORTS // defined if we are building the SOAPY DLL (instead of using it)
+    #define SOAPY_SDR_API SOAPY_SDR_HELPER_DLL_EXPORT
+    #define SOAPY_SDR_EXTERN
+  #else
+    #define SOAPY_SDR_API SOAPY_SDR_HELPER_DLL_IMPORT
+    #define SOAPY_SDR_EXTERN extern
+  #endif // SOAPY_SDR_DLL_EXPORTS
+  #define SOAPY_SDR_LOCAL SOAPY_SDR_HELPER_DLL_LOCAL
+#else // SOAPY_SDR_DLL is not defined: this means SOAPY is a static lib.
+  #define SOAPY_SDR_API
+  #define SOAPY_SDR_LOCAL
+  #define SOAPY_SDR_EXTERN
+#endif // SOAPY_SDR_DLL
+
+#include <iso646.h>
+
+#ifndef _MSC_VER
+#include <stdbool.h>
+#endif
diff --git a/include/SoapySDR/Config.hpp b/include/SoapySDR/Config.hpp
new file mode 100644
index 0000000..e448837
--- /dev/null
+++ b/include/SoapySDR/Config.hpp
@@ -0,0 +1,13 @@
+///
+/// \file SoapySDR/Config.hpp
+///
+/// Common macro definitions for Soapy SDR library API export.
+///
+/// \copyright
+/// Copyright (c) 2014-2014 Josh Blum
+/// SPDX-License-Identifier: BSL-1.0
+///
+
+#pragma once
+#include <SoapySDR/Config.h>
+#include <ciso646>
diff --git a/include/SoapySDR/Constants.h b/include/SoapySDR/Constants.h
new file mode 100644
index 0000000..f826335
--- /dev/null
+++ b/include/SoapySDR/Constants.h
@@ -0,0 +1,66 @@
+///
+/// \file SoapySDR/Constants.h
+///
+/// Constants used in the device API.
+///
+/// \copyright
+/// Copyright (c) 2014-2016 Josh Blum
+/// SPDX-License-Identifier: BSL-1.0
+///
+
+#pragma once
+#include <SoapySDR/Config.h>
+
+/*!
+ * Constant to represent the transmit direction
+ */
+#define SOAPY_SDR_TX 0
+
+/*!
+ * Constant to represent the receive direction
+ */
+#define SOAPY_SDR_RX 1
+
+/*!
+ * Indicate end of burst for transmit or receive.
+ * For write, end of burst if set by the caller.
+ * For read, end of burst is set by the driver.
+ */
+#define SOAPY_SDR_END_BURST (1 << 1)
+
+/*!
+ * Indicates that the time stamp is valid.
+ * For write, the caller must set has time when timeNs is provided.
+ * For read, the driver sets has time when timeNs is provided.
+ */
+#define SOAPY_SDR_HAS_TIME (1 << 2)
+
+/*!
+ * Indicates that stream terminated prematurely.
+ * This is the flag version of an overflow error
+ * that indicates an overflow with the end samples.
+ */
+#define SOAPY_SDR_END_ABRUPT (1 << 3)
+
+/*!
+ * Indicates transmit or receive only a single packet.
+ * Applicable when the driver fragments samples into packets.
+ * For write, the user sets this flag to only send a single packet.
+ * For read, the user sets this flag to only receive a single packet.
+ */
+#define SOAPY_SDR_ONE_PACKET (1 << 4)
+
+/*!
+ * Indicate that this read call and the next results in a fragment.
+ * Used when the implementation has an underlying packet interface.
+ * The caller can use this indicator and the SOAPY_SDR_ONE_PACKET flag
+ * on subsequent read stream calls to re-align with packet boundaries.
+ */
+#define SOAPY_SDR_MORE_FRAGMENTS (1 << 5)
+
+/*!
+ * Indicate that the stream should wait for an external trigger event.
+ * This flag might be used with the flags argument in any of the
+ * stream API calls. The trigger implementation is hardware-specific.
+ */
+#define SOAPY_SDR_WAIT_TRIGGER (1 << 6)
diff --git a/include/SoapySDR/Device.h b/include/SoapySDR/Device.h
new file mode 100644
index 0000000..35cb84d
--- /dev/null
+++ b/include/SoapySDR/Device.h
@@ -0,0 +1,1377 @@
+///
+/// \file SoapySDR/Device.h
+///
+/// Interface definition for Soapy SDR devices.
+/// This is the C version of the Device interface.
+///
+/// General design rules about the API:
+/// The caller must free non-const array results.
+///
+/// \copyright
+/// Copyright (c) 2014-2016 Josh Blum
+/// Copyright (c) 2016-2016 Bastille Networks
+/// SPDX-License-Identifier: BSL-1.0
+///
+
+#pragma once
+#include <SoapySDR/Config.h>
+#include <SoapySDR/Types.h>
+#include <SoapySDR/Constants.h>
+#include <SoapySDR/Errors.h>
+#include <SoapySDR/Device.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+//! Forward declaration of device handle
+typedef struct SoapySDRDevice SoapySDRDevice;
+
+//! Forward declaration of stream handle
+typedef struct SoapySDRStream SoapySDRStream;
+
+/*!
+ * Get the last error message after a device call fails.
+ * When an device API call throws, the C bindings catch
+ * the exception, store its message in thread-safe storage,
+ * and return a non-zero status code to inidicate failure.
+ * Use lastError() to access the exception's error message.
+ */
+SOAPY_SDR_API const char *SoapySDRDevice_lastError(void);
+
+/*!
+ * Enumerate a list of available devices on the system.
+ * \param args device construction key/value argument filters
+ * \param [out] length the number of elements in the result.
+ * \return a list of arguments strings, each unique to a device
+ */
+SOAPY_SDR_API SoapySDRKwargs *SoapySDRDevice_enumerate(const SoapySDRKwargs *args, size_t *length);
+
+/*!
+ * Enumerate a list of available devices on the system.
+ * Markup format for args: "keyA=valA, keyB=valB".
+ * \param args a markup string of key/value argument filters
+ * \param [out] length the number of elements in the result.
+ * \return a list of arguments strings, each unique to a device
+ */
+SOAPY_SDR_API SoapySDRKwargs *SoapySDRDevice_enumerateStrArgs(const char *args, size_t *length);
+
+/*!
+ * Make a new Device object given device construction args.
+ * The device pointer will be stored in a table so subsequent calls
+ * with the same arguments will produce the same device.
+ * For every call to make, there should be a matched call to unmake.
+ *
+ * \param args device construction key/value argument map
+ * \return a pointer to a new Device object
+ */
+SOAPY_SDR_API SoapySDRDevice *SoapySDRDevice_make(const SoapySDRKwargs *args);
+
+/*!
+ * Make a new Device object given device construction args.
+ * The device pointer will be stored in a table so subsequent calls
+ * with the same arguments will produce the same device.
+ * For every call to make, there should be a matched call to unmake.
+ *
+ * \param args a markup string of key/value arguments
+ * \return a pointer to a new Device object
+ */
+SOAPY_SDR_API SoapySDRDevice *SoapySDRDevice_makeStrArgs(const char *args);
+
+/*!
+ * Unmake or release a device object handle.
+ *
+ * \note This call is not thread safe. Implementations calling into unmake
+ * from multiple threads should protect this call with a mutex.
+ *
+ * \param device a pointer to a device object
+ */
+SOAPY_SDR_API void SoapySDRDevice_unmake(SoapySDRDevice *device);
+
+/*******************************************************************
+ * Identification API
+ ******************************************************************/
+
+/*!
+ * A key that uniquely identifies the device driver.
+ * This key identifies the underlying implementation.
+ * Serveral variants of a product may share a driver.
+ * \param device a pointer to a device instance
+ */
+SOAPY_SDR_API char *SoapySDRDevice_getDriverKey(const SoapySDRDevice *device);
+
+/*!
+ * A key that uniquely identifies the hardware.
+ * This key should be meaningful to the user
+ * to optimize for the underlying hardware.
+ * \param device a pointer to a device instance
+ */
+SOAPY_SDR_API char *SoapySDRDevice_getHardwareKey(const SoapySDRDevice *device);
+
+/*!
+ * Query a dictionary of available device information.
+ * This dictionary can any number of values like
+ * vendor name, product name, revisions, serials...
+ * This information can be displayed to the user
+ * to help identify the instantiated device.
+ * \param device a pointer to a device instance
+ */
+SOAPY_SDR_API SoapySDRKwargs SoapySDRDevice_getHardwareInfo(const SoapySDRDevice *device);
+
+/*******************************************************************
+ * Channels API
+ ******************************************************************/
+
+/*!
+ * Set the frontend mapping of available DSP units to RF frontends.
+ * This mapping controls channel mapping and channel availability.
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param mapping a vendor-specific mapping string
+ * \return an error code or 0 for success
+ */
+SOAPY_SDR_API int SoapySDRDevice_setFrontendMapping(SoapySDRDevice *device, const int direction, const char *mapping);
+
+/*!
+ * Get the mapping configuration string.
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param the vendor-specific mapping string
+ */
+SOAPY_SDR_API char *SoapySDRDevice_getFrontendMapping(const SoapySDRDevice *device, const int direction);
+
+/*!
+ * Get a number of channels given the streaming direction
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \return the number of channels
+ */
+SOAPY_SDR_API size_t SoapySDRDevice_getNumChannels(const SoapySDRDevice *device, const int direction);
+
+/*!
+ * Get channel info given the streaming direction
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel the channel number to get info for
+ * \return channel information
+ */
+SOAPY_SDR_API SoapySDRKwargs SoapySDRDevice_getChannelInfo(const SoapySDRDevice *device, const int direction, const size_t channel);
+
+/*!
+ * Find out if the specified channel is full or half duplex.
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel on the device
+ * \return true for full duplex, false for half duplex
+ */
+SOAPY_SDR_API bool SoapySDRDevice_getFullDuplex(const SoapySDRDevice *device, const int direction, const size_t channel);
+
+/*******************************************************************
+ * Stream API
+ ******************************************************************/
+
+/*!
+ * Query a list of the available stream formats.
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel on the device
+ * \param [out] length the number of format strings
+ * \return a list of allowed format strings
+ */
+SOAPY_SDR_API char **SoapySDRDevice_getStreamFormats(const SoapySDRDevice *device, const int direction, const size_t channel, size_t *length);
+
+/*!
+ * Get the hardware's native stream format for this channel.
+ * This is the format used by the underlying transport layer,
+ * and the direct buffer access API calls (when available).
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel on the device
+ * \param [out] fullScale the maximum possible value
+ * \return the native stream buffer format string
+ */
+SOAPY_SDR_API char *SoapySDRDevice_getNativeStreamFormat(const SoapySDRDevice *device, const int direction, const size_t channel, double *fullScale);
+
+/*!
+ * Query the argument info description for stream args.
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel on the device
+ * \param [out] length the number of argument infos
+ * \return a list of argument info structures
+ */
+SOAPY_SDR_API SoapySDRArgInfo *SoapySDRDevice_getStreamArgsInfo(const SoapySDRDevice *device, const int direction, const size_t channel, size_t *length);
+
+/*!
+ * Initialize a stream given a list of channels and stream arguments.
+ * All stream API calls should be usable with the new stream object
+ * after setupStream() is complete, regardless of the activity state.
+ *
+ * Format string markup guidelines:
+ *  - C means complex
+ *  - F means floating point
+ *  - S means signed integer
+ *  - U means unsigned integer
+ *  - number float/int size in bytes (complex is 2x this size)
+ *
+ * Example format strings:
+ *  - CF32 complex float32 (8 bytes per element)
+ *  - CS16 complex int16 (4 bytes per element)
+ *  - CS12 complex int12 (3 bytes per element)
+ *  - CS4 complex int4 (1 byte per element)
+ *  - S32 int32 (4 bytes per element)
+ *  - U8 uint8 (1 byte per element)
+ *
+ * Recommended keys to use in the args dictionary:
+ *  - "WIRE" - format of the samples between device and host
+ *
+ * \param device a pointer to a device instance
+ * \param [out] stream the opaque pointer to a stream handle
+ * \param direction the channel direction RX or TX
+ * \param format the desired buffer format in read/writeStream()
+ * \param channels a list of channels for empty for automatic
+ * \param numChans the number of elements in the channels array
+ * \param args stream args or empty for defaults
+ * \return 0 for success or error code on failure
+ */
+SOAPY_SDR_API int SoapySDRDevice_setupStream(SoapySDRDevice *device,
+    SoapySDRStream **stream,
+    const int direction,
+    const char *format,
+    const size_t *channels,
+    const size_t numChans,
+    const SoapySDRKwargs *args);
+
+/*!
+ * Close an open stream created by setupStream
+ * \param device a pointer to a device instance
+ * \param stream the opaque pointer to a stream handle
+ */
+SOAPY_SDR_API void SoapySDRDevice_closeStream(SoapySDRDevice *device, SoapySDRStream *stream);
+
+/*!
+ * Get the stream's maximum transmission unit (MTU) in number of elements.
+ * The MTU specifies the maximum payload transfer in a stream operation.
+ * This value can be used as a stream buffer allocation size that can
+ * best optimize throughput given the underlying stream implementation.
+ *
+ * \param device a pointer to a device instance
+ * \param stream the opaque pointer to a stream handle
+ * \return the MTU in number of stream elements (never zero)
+ */
+SOAPY_SDR_API size_t SoapySDRDevice_getStreamMTU(const SoapySDRDevice *device, SoapySDRStream *stream);
+
+/*!
+ * Activate a stream.
+ * Call activate to prepare a stream before using read/write().
+ * The implementation control switches or stimulate data flow.
+ *
+ * The timeNs is only valid when the flags have SOAPY_SDR_HAS_TIME.
+ * The numElems count can be used to request a finite burst size.
+ * The SOAPY_SDR_END_BURST flag can signal end on the finite burst.
+ * Not all implementations will support the full range of options.
+ * In this case, the implementation returns SOAPY_SDR_NOT_SUPPORTED.
+ *
+ * \param device a pointer to a device instance
+ * \param stream the opaque pointer to a stream handle
+ * \param flags optional flag indicators about the stream
+ * \param timeNs optional activation time in nanoseconds
+ * \param numElems optional element count for burst control
+ * \return 0 for success or error code on failure
+ */
+SOAPY_SDR_API int SoapySDRDevice_activateStream(SoapySDRDevice *device,
+    SoapySDRStream *stream,
+    const int flags,
+    const long long timeNs,
+    const size_t numElems);
+
+/*!
+ * Deactivate a stream.
+ * Call deactivate when not using using read/write().
+ * The implementation control switches or halt data flow.
+ *
+ * The timeNs is only valid when the flags have SOAPY_SDR_HAS_TIME.
+ * Not all implementations will support the full range of options.
+ * In this case, the implementation returns SOAPY_SDR_NOT_SUPPORTED.
+ *
+ * \param device a pointer to a device instance
+ * \param stream the opaque pointer to a stream handle
+ * \param flags optional flag indicators about the stream
+ * \param timeNs optional deactivation time in nanoseconds
+ * \return 0 for success or error code on failure
+ */
+SOAPY_SDR_API int SoapySDRDevice_deactivateStream(SoapySDRDevice *device,
+    SoapySDRStream *stream,
+    const int flags,
+    const long long timeNs);
+
+/*!
+ * Read elements from a stream for reception.
+ * This is a multi-channel call, and buffs should be an array of void *,
+ * where each pointer will be filled with data from a different channel.
+ *
+ * **Client code compatibility:**
+ * The readStream() call should be well defined at all times,
+ * including prior to activation and after deactivation.
+ * When inactive, readStream() should implement the timeout
+ * specified by the caller and return SOAPY_SDR_TIMEOUT.
+ *
+ * \param device a pointer to a device instance
+ * \param stream the opaque pointer to a stream handle
+ * \param buffs an array of void* buffers num chans in size
+ * \param numElems the number of elements in each buffer
+ * \param [out] flags optional flag indicators about the result
+ * \param [out] timeNs the buffer's timestamp in nanoseconds
+ * \param timeoutUs the timeout in microseconds
+ * \return the number of elements read per buffer or error code
+ */
+SOAPY_SDR_API int SoapySDRDevice_readStream(SoapySDRDevice *device,
+    SoapySDRStream *stream,
+    void * const *buffs,
+    const size_t numElems,
+    int *flags,
+    long long *timeNs,
+    const long timeoutUs);
+
+/*!
+ * Write elements to a stream for transmission.
+ * This is a multi-channel call, and buffs should be an array of void *,
+ * where each pointer will be filled with data for a different channel.
+ *
+ * **Client code compatibility:**
+ * Client code relies on writeStream() for proper back-pressure.
+ * The writeStream() implementation must enforce the timeout
+ * such that the call blocks until space becomes available
+ * or timeout expiration.
+ *
+ * \param device a pointer to a device instance
+ * \param stream the opaque pointer to a stream handle
+ * \param buffs an array of void* buffers num chans in size
+ * \param numElems the number of elements in each buffer
+ * \param [in,out] flags optional input flags and output flags
+ * \param timeNs the buffer's timestamp in nanoseconds
+ * \param timeoutUs the timeout in microseconds
+ * \return the number of elements written per buffer or error
+ */
+SOAPY_SDR_API int SoapySDRDevice_writeStream(SoapySDRDevice *device,
+    SoapySDRStream *stream,
+    const void * const *buffs,
+    const size_t numElems,
+    int *flags,
+    const long long timeNs,
+    const long timeoutUs);
+
+/*!
+ * Readback status information about a stream.
+ * This call is typically used on a transmit stream
+ * to report time errors, underflows, and burst completion.
+ *
+ * **Client code compatibility:**
+ * Client code may continually poll readStreamStatus() in a loop.
+ * Implementations of readStreamStatus() should wait in the call
+ * for a status change event or until the timeout expiration.
+ * When stream status is not implemented on a particular stream,
+ * readStreamStatus() should return SOAPY_SDR_NOT_SUPPORTED.
+ * Client code may use this indication to disable a polling loop.
+ *
+ * \param device a pointer to a device instance
+ * \param stream the opaque pointer to a stream handle
+ * \param chanMask to which channels this status applies
+ * \param flags optional input flags and output flags
+ * \param timeNs the buffer's timestamp in nanoseconds
+ * \param timeoutUs the timeout in microseconds
+ * \return 0 for success or error code like timeout
+ */
+SOAPY_SDR_API int SoapySDRDevice_readStreamStatus(SoapySDRDevice *device,
+    SoapySDRStream *stream,
+    size_t *chanMask,
+    int *flags,
+    long long *timeNs,
+    const long timeoutUs);
+
+/*******************************************************************
+ * Direct buffer access API
+ ******************************************************************/
+
+/*!
+ * How many direct access buffers can the stream provide?
+ * This is the number of times the user can call acquire()
+ * on a stream without making subsequent calls to release().
+ * A return value of 0 means that direct access is not supported.
+ *
+ * \param device a pointer to a device instance
+ * \param stream the opaque pointer to a stream handle
+ * \return the number of direct access buffers or 0
+ */
+SOAPY_SDR_API size_t SoapySDRDevice_getNumDirectAccessBuffers(SoapySDRDevice *device, SoapySDRStream *stream);
+
+/*!
+ * Get the buffer addresses for a scatter/gather table entry.
+ * When the underlying DMA implementation uses scatter/gather
+ * then this call provides the user addresses for that table.
+ *
+ * Example: The caller may query the DMA memory addresses once
+ * after stream creation to pre-allocate a re-usable ring-buffer.
+ *
+ * \param device a pointer to a device instance
+ * \param stream the opaque pointer to a stream handle
+ * \param handle an index value between 0 and num direct buffers - 1
+ * \param buffs an array of void* buffers num chans in size
+ * \return 0 for success or error code when not supported
+ */
+SOAPY_SDR_API int SoapySDRDevice_getDirectAccessBufferAddrs(SoapySDRDevice *device, SoapySDRStream *stream, const size_t handle, void **buffs);
+
+/*!
+ * Acquire direct buffers from a receive stream.
+ * This call is part of the direct buffer access API.
+ *
+ * The buffs array will be filled with a stream pointer for each channel.
+ * Each pointer can be read up to the number of return value elements.
+ *
+ * The handle will be set by the implementation so that the caller
+ * may later release access to the buffers with releaseReadBuffer().
+ * Handle represents an index into the internal scatter/gather table
+ * such that handle is between 0 and num direct buffers - 1.
+ *
+ * \param device a pointer to a device instance
+ * \param stream the opaque pointer to a stream handle
+ * \param handle an index value used in the release() call
+ * \param buffs an array of void* buffers num chans in size
+ * \param flags optional flag indicators about the result
+ * \param timeNs the buffer's timestamp in nanoseconds
+ * \param timeoutUs the timeout in microseconds
+ * \return the number of elements read per buffer or error code
+ */
+SOAPY_SDR_API int SoapySDRDevice_acquireReadBuffer(SoapySDRDevice *device,
+    SoapySDRStream *stream,
+    size_t *handle,
+    const void **buffs,
+    int *flags,
+    long long *timeNs,
+    const long timeoutUs);
+
+/*!
+ * Release an acquired buffer back to the receive stream.
+ * This call is part of the direct buffer access API.
+ *
+ * \param device a pointer to a device instance
+ * \param stream the opaque pointer to a stream handle
+ * \param handle the opaque handle from the acquire() call
+ */
+SOAPY_SDR_API void SoapySDRDevice_releaseReadBuffer(SoapySDRDevice *device,
+    SoapySDRStream *stream,
+    const size_t handle);
+
+/*!
+ * Acquire direct buffers from a transmit stream.
+ * This call is part of the direct buffer access API.
+ *
+ * The buffs array will be filled with a stream pointer for each channel.
+ * Each pointer can be written up to the number of return value elements.
+ *
+ * The handle will be set by the implementation so that the caller
+ * may later release access to the buffers with releaseWriteBuffer().
+ * Handle represents an index into the internal scatter/gather table
+ * such that handle is between 0 and num direct buffers - 1.
+ *
+ * \param device a pointer to a device instance
+ * \param stream the opaque pointer to a stream handle
+ * \param handle an index value used in the release() call
+ * \param buffs an array of void* buffers num chans in size
+ * \param flags optional input flags and output flags
+ * \param timeNs the buffer's timestamp in nanoseconds
+ * \param timeoutUs the timeout in microseconds
+ * \return the number of available elements per buffer or error
+ */
+SOAPY_SDR_API int SoapySDRDevice_acquireWriteBuffer(SoapySDRDevice *device,
+    SoapySDRStream *stream,
+    size_t *handle,
+    void **buffs,
+    const long timeoutUs);
+
+/*!
+ * Release an acquired buffer back to the transmit stream.
+ * This call is part of the direct buffer access API.
+ *
+ * Stream meta-data is provided as part of the release call,
+ * and not the acquire call so that the caller may acquire
+ * buffers without committing to the contents of the meta-data,
+ * which can be determined by the user as the buffers are filled.
+ *
+ * \param device a pointer to a device instance
+ * \param stream the opaque pointer to a stream handle
+ * \param handle the opaque handle from the acquire() call
+ * \param numElems the number of elements written to each buffer
+ * \param flags optional input flags and output flags
+ * \param timeNs the buffer's timestamp in nanoseconds
+ */
+SOAPY_SDR_API void SoapySDRDevice_releaseWriteBuffer(SoapySDRDevice *device,
+    SoapySDRStream *stream,
+    const size_t handle,
+    const size_t numElems,
+    int *flags,
+    const long long timeNs);
+
+/*******************************************************************
+ * Antenna API
+ ******************************************************************/
+
+/*!
+ * Get a list of available antennas to select on a given chain.
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel on the device
+ * \param [out] length the number of antenna names
+ * \return a list of available antenna names
+ */
+SOAPY_SDR_API char **SoapySDRDevice_listAntennas(const SoapySDRDevice *device, const int direction, const size_t channel, size_t *length);
+
+/*!
+ * Set the selected antenna on a chain.
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel on the device
+ * \param name the name of an available antenna
+ * \return an error code or 0 for success
+ */
+SOAPY_SDR_API int SoapySDRDevice_setAntenna(SoapySDRDevice *device, const int direction, const size_t channel, const char *name);
+
+/*!
+ * Get the selected antenna on a chain.
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel on the device
+ * \return the name of an available antenna
+ */
+SOAPY_SDR_API char *SoapySDRDevice_getAntenna(const SoapySDRDevice *device, const int direction, const size_t channel);
+
+/*******************************************************************
+ * Frontend corrections API
+ ******************************************************************/
+
+/*!
+ * Does the device support automatic DC offset corrections?
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel on the device
+ * \return true if automatic corrections are supported
+ */
+SOAPY_SDR_API bool SoapySDRDevice_hasDCOffsetMode(const SoapySDRDevice *device, const int direction, const size_t channel);
+
+/*!
+ * Set the automatic DC offset corrections mode.
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel on the device
+ * \param automatic true for automatic offset correction
+ * \return an error code or 0 for success
+ */
+SOAPY_SDR_API int SoapySDRDevice_setDCOffsetMode(SoapySDRDevice *device, const int direction, const size_t channel, const bool automatic);
+
+/*!
+ * Get the automatic DC offset corrections mode.
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel on the device
+ * \return true for automatic offset correction
+ */
+SOAPY_SDR_API bool SoapySDRDevice_getDCOffsetMode(const SoapySDRDevice *device, const int direction, const size_t channel);
+
+/*!
+ * Does the device support frontend DC offset correction?
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel on the device
+ * \return true if DC offset corrections are supported
+ */
+SOAPY_SDR_API bool SoapySDRDevice_hasDCOffset(const SoapySDRDevice *device, const int direction, const size_t channel);
+
+/*!
+ * Set the frontend DC offset correction.
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel on the device
+ * \param offsetI the relative correction (1.0 max)
+ * \param offsetQ the relative correction (1.0 max)
+ * \return an error code or 0 for success
+ */
+SOAPY_SDR_API int SoapySDRDevice_setDCOffset(SoapySDRDevice *device, const int direction, const size_t channel, const double offsetI, const double offsetQ);
+
+/*!
+ * Get the frontend DC offset correction.
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel on the device
+ * \param [out] offsetI the relative correction (1.0 max)
+ * \param [out] offsetQ the relative correction (1.0 max)
+ */
+SOAPY_SDR_API void SoapySDRDevice_getDCOffset(const SoapySDRDevice *device, const int direction, const size_t channel, double *offsetI, double *offsetQ);
+
+/*!
+ * Does the device support frontend IQ balance correction?
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel on the device
+ * \return true if IQ balance corrections are supported
+ */
+SOAPY_SDR_API bool SoapySDRDevice_hasIQBalance(const SoapySDRDevice *device, const int direction, const size_t channel);
+
+/*!
+ * Set the frontend IQ balance correction.
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel on the device
+ * \param balanceI the relative correction (1.0 max)
+ * \param balanceQ the relative correction (1.0 max)
+ * \return an error code or 0 for success
+ */
+SOAPY_SDR_API int SoapySDRDevice_setIQBalance(SoapySDRDevice *device, const int direction, const size_t channel, const double balanceI, const double balanceQ);
+
+/*!
+ * Get the frontend IQ balance correction.
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel on the device
+ * \param [out] balanceI the relative correction (1.0 max)
+ * \param [out] balanceQ the relative correction (1.0 max)
+ */
+SOAPY_SDR_API void SoapySDRDevice_getIQBalance(const SoapySDRDevice *device, const int direction, const size_t channel, double *balanceI, double *balanceQ);
+
+/*******************************************************************
+ * Gain API
+ ******************************************************************/
+
+/*!
+ * List available amplification elements.
+ * Elements should be in order RF to baseband.
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel
+ * \param [out] length the number of gain names
+ * \return a list of gain string names
+ */
+SOAPY_SDR_API char **SoapySDRDevice_listGains(const SoapySDRDevice *device, const int direction, const size_t channel, size_t *length);
+
+/*!
+ * Does the device support automatic gain control?
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel on the device
+ * \return true for automatic gain control
+ */
+SOAPY_SDR_API bool SoapySDRDevice_hasGainMode(const SoapySDRDevice *device, const int direction, const size_t channel);
+
+/*!
+ * Set the automatic gain mode on the chain.
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel on the device
+ * \param automatic true for automatic gain setting
+ * \return an error code or 0 for success
+ */
+SOAPY_SDR_API int SoapySDRDevice_setGainMode(SoapySDRDevice *device, const int direction, const size_t channel, const bool automatic);
+
+/*!
+ * Get the automatic gain mode on the chain.
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel on the device
+ * \return true for automatic gain setting
+ */
+SOAPY_SDR_API bool SoapySDRDevice_getGainMode(const SoapySDRDevice *device, const int direction, const size_t channel);
+
+/*!
+ * Set the overall amplification in a chain.
+ * The gain will be distributed automatically across available element.
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel on the device
+ * \param name the name of an amplification element
+ * \param value the new amplification value in dB
+ * \return an error code or 0 for success
+ */
+SOAPY_SDR_API int SoapySDRDevice_setGain(SoapySDRDevice *device, const int direction, const size_t channel, const double value);
+
+/*!
+ * Set the value of a amplification element in a chain.
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel on the device
+ * \param name the name of an amplification element
+ * \param value the new amplification value in dB
+ * \return an error code or 0 for success
+ */
+SOAPY_SDR_API int SoapySDRDevice_setGainElement(SoapySDRDevice *device, const int direction, const size_t channel, const char *name, const double value);
+
+/*!
+ * Get the overall value of the gain elements in a chain.
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel on the device
+ * \return the value of the gain in dB
+ */
+SOAPY_SDR_API double SoapySDRDevice_getGain(const SoapySDRDevice *device, const int direction, const size_t channel);
+
+/*!
+ * Get the value of an individual amplification element in a chain.
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel on the device
+ * \param name the name of an amplification element
+ * \return the value of the gain in dB
+ */
+SOAPY_SDR_API double SoapySDRDevice_getGainElement(const SoapySDRDevice *device, const int direction, const size_t channel, const char *name);
+
+/*!
+ * Get the overall range of possible gain values.
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel on the device
+ * \return a list of gain ranges in dB
+ */
+SOAPY_SDR_API SoapySDRRange SoapySDRDevice_getGainRange(const SoapySDRDevice *device, const int direction, const size_t channel);
+
+/*!
+ * Get the range of possible gain values for a specific element.
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel on the device
+ * \param name the name of an amplification element
+ * \return a list of gain ranges in dB
+ */
+SOAPY_SDR_API SoapySDRRange SoapySDRDevice_getGainElementRange(const SoapySDRDevice *device, const int direction, const size_t channel, const char *name);
+
+/*******************************************************************
+ * Frequency API
+ ******************************************************************/
+
+/*!
+ * Set the center frequency of the chain.
+ *  - For RX, this specifies the down-conversion frequency.
+ *  - For TX, this specifies the up-conversion frequency.
+ *
+ * The default implementation of setFrequency() will tune the "RF"
+ * component as close as possible to the requested center frequency.
+ * Tuning inaccuracies will be compensated for with the "BB" component.
+ *
+ * The args can be used to augment the tuning algorithm.
+ *  - Use "OFFSET" to specify an "RF" tuning offset,
+ *    usually with the intention of moving the LO out of the passband.
+ *    The offset will be compensated for using the "BB" component.
+ *  - Use the name of a component for the key and a frequency in Hz
+ *    as the value (any format) to enforce a specific frequency.
+ *    The other components will be tuned with compensation
+ *    to achieve the specified overall frequency.
+ *  - Use the name of a component for the key and the value "IGNORE"
+ *    so that the tuning algorithm will avoid altering the component.
+ *  - Vendor specific implementations can also use the same args to augment
+ *    tuning in other ways such as specifying fractional vs integer N tuning.
+ *
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel on the device
+ * \param frequency the center frequency in Hz
+ * \param args optional tuner arguments
+ * \return an error code or 0 for success
+ */
+SOAPY_SDR_API int SoapySDRDevice_setFrequency(SoapySDRDevice *device, const int direction, const size_t channel, const double frequency, const SoapySDRKwargs *args);
+
+/*!
+ * Tune the center frequency of the specified element.
+ *  - For RX, this specifies the down-conversion frequency.
+ *  - For TX, this specifies the up-conversion frequency.
+ *
+ * Recommended names used to represent tunable components:
+ *  - "CORR" - freq error correction in PPM
+ *  - "RF" - frequency of the RF frontend
+ *  - "BB" - frequency of the baseband DSP
+ *
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel on the device
+ * \param name the name of a tunable element
+ * \param frequency the center frequency in Hz
+ * \param args optional tuner arguments
+ * \return an error code or 0 for success
+ */
+SOAPY_SDR_API int SoapySDRDevice_setFrequencyComponent(SoapySDRDevice *device, const int direction, const size_t channel, const char *name, const double frequency, const SoapySDRKwargs *args);
+
+/*!
+ * Get the overall center frequency of the chain.
+ *  - For RX, this specifies the down-conversion frequency.
+ *  - For TX, this specifies the up-conversion frequency.
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel on the device
+ * \return the center frequency in Hz
+ */
+SOAPY_SDR_API double SoapySDRDevice_getFrequency(const SoapySDRDevice *device, const int direction, const size_t channel);
+
+/*!
+ * Get the frequency of a tunable element in the chain.
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel on the device
+ * \param name the name of a tunable element
+ * \return the tunable element's frequency in Hz
+ */
+SOAPY_SDR_API double SoapySDRDevice_getFrequencyComponent(const SoapySDRDevice *device, const int direction, const size_t channel, const char *name);
+
+/*!
+ * List available tunable elements in the chain.
+ * Elements should be in order RF to baseband.
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel
+ * \param [out] length the number names
+ * \return a list of tunable elements by name
+ */
+SOAPY_SDR_API char **SoapySDRDevice_listFrequencies(const SoapySDRDevice *device, const int direction, const size_t channel, size_t *length);
+
+/*!
+ * Get the range of overall frequency values.
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel on the device
+ * \param [out] length the number of ranges
+ * \return a list of frequency ranges in Hz
+ */
+SOAPY_SDR_API SoapySDRRange *SoapySDRDevice_getFrequencyRange(const SoapySDRDevice *device, const int direction, const size_t channel, size_t *length);
+
+/*!
+ * Get the range of tunable values for the specified element.
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel on the device
+ * \param name the name of a tunable element
+ * \param [out] length the number of ranges
+ * \return a list of frequency ranges in Hz
+ */
+SOAPY_SDR_API SoapySDRRange *SoapySDRDevice_getFrequencyRangeComponent(const SoapySDRDevice *device, const int direction, const size_t channel, const char *name, size_t *length);
+
+/*!
+ * Query the argument info description for tune args.
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel on the device
+ * \param [out] length the number of argument infos
+ * \return a list of argument info structures
+ */
+SOAPY_SDR_API SoapySDRArgInfo *SoapySDRDevice_getFrequencyArgsInfo(const SoapySDRDevice *device, const int direction, const size_t channel, size_t *length);
+
+/*******************************************************************
+ * Sample Rate API
+ ******************************************************************/
+
+/*!
+ * Set the baseband sample rate of the chain.
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel on the device
+ * \param rate the sample rate in samples per second
+ * \return an error code or 0 for success
+ */
+SOAPY_SDR_API int SoapySDRDevice_setSampleRate(SoapySDRDevice *device, const int direction, const size_t channel, const double rate);
+
+/*!
+ * Get the baseband sample rate of the chain.
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel on the device
+ * \return the sample rate in samples per second
+ */
+SOAPY_SDR_API double SoapySDRDevice_getSampleRate(const SoapySDRDevice *device, const int direction, const size_t channel);
+
+/*!
+ * Get the range of possible baseband sample rates.
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel on the device
+ * \param [out] length the number of sample rates
+ * \return a list of possible rates in samples per second
+ */
+SOAPY_SDR_API double *SoapySDRDevice_listSampleRates(const SoapySDRDevice *device, const int direction, const size_t channel, size_t *length);
+
+/*******************************************************************
+ * Bandwidth API
+ ******************************************************************/
+
+/*!
+ * Set the baseband filter width of the chain.
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel on the device
+ * \param bw the baseband filter width in Hz
+ * \return an error code or 0 for success
+ */
+SOAPY_SDR_API int SoapySDRDevice_setBandwidth(SoapySDRDevice *device, const int direction, const size_t channel, const double bw);
+
+/*!
+ * Get the baseband filter width of the chain.
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel on the device
+ * \return the baseband filter width in Hz
+ */
+SOAPY_SDR_API double SoapySDRDevice_getBandwidth(const SoapySDRDevice *device, const int direction, const size_t channel);
+
+/*!
+ * Get the range of possible baseband filter widths.
+ * \deprecated replaced by getBandwidthRange()
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel on the device
+ * \param [out] length the number of bandwidths
+ * \return a list of possible bandwidths in Hz
+ */
+SOAPY_SDR_API double *SoapySDRDevice_listBandwidths(const SoapySDRDevice *device, const int direction, const size_t channel, size_t *length);
+
+/*!
+ * Get the range of possible baseband filter widths.
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel on the device
+ * \param [out] length the number of ranges
+ * \return a list of bandwidth ranges in Hz
+ */
+SOAPY_SDR_API SoapySDRRange *SoapySDRDevice_getBandwidthRange(const SoapySDRDevice *device, const int direction, const size_t channel, size_t *length);
+
+/*******************************************************************
+ * Clocking API
+ ******************************************************************/
+
+/*!
+ * Set the master clock rate of the device.
+ * \param device a pointer to a device instance
+ * \param rate the clock rate in Hz
+ * \return an error code or 0 for success
+ */
+SOAPY_SDR_API int SoapySDRDevice_setMasterClockRate(SoapySDRDevice *device, const double rate);
+
+/*!
+ * Get the master clock rate of the device.
+ * \param device a pointer to a device instance
+ * \return the clock rate in Hz
+ */
+SOAPY_SDR_API double SoapySDRDevice_getMasterClockRate(const SoapySDRDevice *device);
+
+/*!
+ * Get the range of available master clock rates.
+ * \param device a pointer to a device instance
+ * \param [out] length the number of ranges
+ * \return a list of clock rate ranges in Hz
+ */
+SOAPY_SDR_API SoapySDRRange *SoapySDRDevice_getMasterClockRates(const SoapySDRDevice *device, size_t *length);
+
+/*!
+ * Get the list of available clock sources.
+ * \param device a pointer to a device instance
+ * \param [out] length the number of sources
+ * \return a list of clock source names
+ */
+SOAPY_SDR_API char **SoapySDRDevice_listClockSources(const SoapySDRDevice *device, size_t *length);
+
+/*!
+ * Set the clock source on the device
+ * \param device a pointer to a device instance
+ * \param source the name of a clock source
+ * \return an error code or 0 for success
+ */
+SOAPY_SDR_API int SoapySDRDevice_setClockSource(SoapySDRDevice *device, const char *source);
+
+/*!
+ * Get the clock source of the device
+ * \param device a pointer to a device instance
+ * \return the name of a clock source
+ */
+SOAPY_SDR_API char *SoapySDRDevice_getClockSource(const SoapySDRDevice *device);
+
+/*******************************************************************
+ * Time API
+ ******************************************************************/
+
+/*!
+ * Get the list of available time sources.
+ * \param device a pointer to a device instance
+ * \param [out] length the number of sources
+ * \return a list of time source names
+ */
+SOAPY_SDR_API char **SoapySDRDevice_listTimeSources(const SoapySDRDevice *device, size_t *length);
+
+/*!
+ * Set the time source on the device
+ * \param device a pointer to a device instance
+ * \param source the name of a time source
+ * \return an error code or 0 for success
+ */
+SOAPY_SDR_API int SoapySDRDevice_setTimeSource(SoapySDRDevice *device, const char *source);
+
+/*!
+ * Get the time source of the device
+ * \param device a pointer to a device instance
+ * \return the name of a time source
+ */
+SOAPY_SDR_API char *SoapySDRDevice_getTimeSource(const SoapySDRDevice *device);
+
+/*!
+ * Does this device have a hardware clock?
+ * \param device a pointer to a device instance
+ * \param what optional argument
+ * \return true if the hardware clock exists
+ */
+SOAPY_SDR_API bool SoapySDRDevice_hasHardwareTime(const SoapySDRDevice *device, const char *what);
+
+/*!
+ * Read the time from the hardware clock on the device.
+ * The what argument can refer to a specific time counter.
+ * \param device a pointer to a device instance
+ * \param what optional argument
+ * \return the time in nanoseconds
+ */
+SOAPY_SDR_API long long SoapySDRDevice_getHardwareTime(const SoapySDRDevice *device, const char *what);
+
+/*!
+ * Write the time to the hardware clock on the device.
+ * The what argument can refer to a specific time counter.
+ * \param device a pointer to a device instance
+ * \param timeNs time in nanoseconds
+ * \param what optional argument
+ */
+SOAPY_SDR_API void SoapySDRDevice_setHardwareTime(SoapySDRDevice *device, const long long timeNs, const char *what);
+
+/*!
+ * Set the time of subsequent configuration calls.
+ * The what argument can refer to a specific command queue.
+ * Implementations may use a time of 0 to clear.
+ * \deprecated replaced by setHardwareTime()
+ * \param device a pointer to a device instance
+ * \param timeNs time in nanoseconds
+ * \param what optional argument
+ */
+SOAPY_SDR_API void SoapySDRDevice_setCommandTime(SoapySDRDevice *device, const long long timeNs, const char *what);
+
+/*******************************************************************
+ * Sensor API
+ ******************************************************************/
+
+/*!
+ * List the available global readback sensors.
+ * A sensor can represent a reference lock, RSSI, temperature.
+ * \param device a pointer to a device instance
+ * \param [out] length the number of sensor names
+ * \return a list of available sensor string names
+ */
+SOAPY_SDR_API char **SoapySDRDevice_listSensors(const SoapySDRDevice *device, size_t *length);
+
+/*!
+ * Get meta-information about a sensor.
+ * Example: displayable name, type, range.
+ * \param device a pointer to a device instance
+ * \param name the name of an available sensor
+ * \return meta-information about a sensor
+ */
+SOAPY_SDR_API SoapySDRArgInfo SoapySDRDevice_getSensorInfo(const SoapySDRDevice *device, const char *name);
+
+/*!
+ * Readback a global sensor given the name.
+ * The value returned is a string which can represent
+ * a boolean ("true"/"false"), an integer, or float.
+ * \param device a pointer to a device instance
+ * \param name the name of an available sensor
+ * \return the current value of the sensor
+ */
+SOAPY_SDR_API char *SoapySDRDevice_readSensor(const SoapySDRDevice *device, const char *name);
+
+/*!
+ * List the available channel readback sensors.
+ * A sensor can represent a reference lock, RSSI, temperature.
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel on the device
+ * \param [out] length the number of sensor names
+ * \return a list of available sensor string names
+ */
+SOAPY_SDR_API char **SoapySDRDevice_listChannelSensors(const SoapySDRDevice *device, const int direction, const size_t channel, size_t *length);
+
+/*!
+ * Get meta-information about a channel sensor.
+ * Example: displayable name, type, range.
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel on the device
+ * \param name the name of an available sensor
+ * \return meta-information about a sensor
+ */
+SOAPY_SDR_API SoapySDRArgInfo SoapySDRDevice_getChannelSensorInfo(const SoapySDRDevice *device, const int direction, const size_t channel, const char *name);
+
+/*!
+ * Readback a channel sensor given the name.
+ * The value returned is a string which can represent
+ * a boolean ("true"/"false"), an integer, or float.
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel on the device
+ * \param name the name of an available sensor
+ * \return the current value of the sensor
+ */
+SOAPY_SDR_API char *SoapySDRDevice_readChannelSensor(const SoapySDRDevice *device, const int direction, const size_t channel, const char *name);
+
+/*******************************************************************
+ * Register API
+ ******************************************************************/
+
+/*!
+ * Get a list of available register interfaces by name.
+ * \param device a pointer to a device instance
+ * \param [out] length the number of interfaces
+ * \return a list of available register interfaces
+ */
+SOAPY_SDR_API char **SoapySDRDevice_listRegisterInterfaces(const SoapySDRDevice *device, size_t *length);
+
+/*!
+ * Write a register on the device given the interface name.
+ * This can represent a register on a soft CPU, FPGA, IC;
+ * the interpretation is up the implementation to decide.
+ * \param device a pointer to a device instance
+ * \param name the name of a available register interface
+ * \param addr the register address
+ * \param value the register value
+ */
+SOAPY_SDR_API void SoapySDRDevice_writeNamedRegister(SoapySDRDevice *device, const char *name, const unsigned addr, const unsigned value);
+
+/*!
+ * Read a register on the device given the interface name.
+ * \param device a pointer to a device instance
+ * \param name the name of a available register interface
+ * \param addr the register address
+ * \return the register value
+ */
+SOAPY_SDR_API unsigned SoapySDRDevice_readNamedRegister(const SoapySDRDevice *device, const char *name, const unsigned addr);
+
+/*!
+ * Write a register on the device.
+ * This can represent a register on a soft CPU, FPGA, IC;
+ * the interpretation is up the implementation to decide.
+ * \deprecated replaced by writeRegister(name)
+ * \param device a pointer to a device instance
+ * \param addr the register address
+ * \param value the register value
+ */
+SOAPY_SDR_API void SoapySDRDevice_writeRegister(SoapySDRDevice *device, const unsigned addr, const unsigned value);
+
+/*!
+ * Read a register on the device.
+ * \deprecated replaced by readRegister(name)
+ * \param device a pointer to a device instance
+ * \param addr the register address
+ * \return the register value
+ */
+SOAPY_SDR_API unsigned SoapySDRDevice_readRegister(const SoapySDRDevice *device, const unsigned addr);
+
+/*******************************************************************
+ * Settings API
+ ******************************************************************/
+
+/*!
+ * Describe the allowed keys and values used for settings.
+ * \param device a pointer to a device instance
+ * \param [out] length the number of sensor names
+ * \return a list of argument info structures
+ */
+SOAPY_SDR_API SoapySDRArgInfo *SoapySDRDevice_getSettingInfo(const SoapySDRDevice *device, size_t *length);
+
+/*!
+ * Write an arbitrary setting on the device.
+ * The interpretation is up the implementation.
+ * \param device a pointer to a device instance
+ * \param key the setting identifier
+ * \param value the setting value
+ */
+SOAPY_SDR_API void SoapySDRDevice_writeSetting(SoapySDRDevice *device, const char *key, const char *value);
+
+/*!
+ * Read an arbitrary setting on the device.
+ * \param device a pointer to a device instance
+ * \param key the setting identifier
+ * \return the setting value
+ */
+SOAPY_SDR_API char *SoapySDRDevice_readSetting(const SoapySDRDevice *device, const char *key);
+
+/*!
+ * Describe the allowed keys and values used for channel settings.
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel on the device
+ * \param [out] length the number of sensor names
+ * \return a list of argument info structures
+ */
+SOAPY_SDR_API SoapySDRArgInfo *SoapySDRDevice_getChannelSettingInfo(const SoapySDRDevice *device, const int direction, const size_t channel, size_t *length);
+
+/*!
+ * Write an arbitrary channel setting on the device.
+ * The interpretation is up the implementation.
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel on the device
+ * \param key the setting identifier
+ * \param value the setting value
+ */
+SOAPY_SDR_API void SoapySDRDevice_writeChannelSetting(SoapySDRDevice *device, const int direction, const size_t channel, const char *key, const char *value);
+
+/*!
+ * Read an arbitrary channel setting on the device.
+ * \param device a pointer to a device instance
+ * \param direction the channel direction RX or TX
+ * \param channel an available channel on the device
+ * \param key the setting identifier
+ * \return the setting value
+ */
+SOAPY_SDR_API char *SoapySDRDevice_readChannelSetting(const SoapySDRDevice *device, const int direction, const size_t channel, const char *key);
+
+/*******************************************************************
+ * GPIO API
+ ******************************************************************/
+
+/*!
+ * Get a list of available GPIO banks by name.
+ * \param [out] length the number of GPIO banks
+ * \param device a pointer to a device instance
+ */
+SOAPY_SDR_API char **SoapySDRDevice_listGPIOBanks(const SoapySDRDevice *device, size_t *length);
+
+/*!
+ * Write the value of a GPIO bank.
+ * \param device a pointer to a device instance
+ * \param bank the name of an available bank
+ * \param value an integer representing GPIO bits
+ */
+SOAPY_SDR_API void SoapySDRDevice_writeGPIO(SoapySDRDevice *device, const char *bank, const unsigned value);
+
+/*!
+ * Write the value of a GPIO bank with modification mask.
+ * \param device a pointer to a device instance
+ * \param bank the name of an available bank
+ * \param value an integer representing GPIO bits
+ * \param mask a modification mask where 1 = modify
+ */
+SOAPY_SDR_API void SoapySDRDevice_writeGPIOMasked(SoapySDRDevice *device, const char *bank, const unsigned value, const unsigned mask);
+
+/*!
+ * Readback the value of a GPIO bank.
+ * \param device a pointer to a device instance
+ * \param bank the name of an available bank
+ * \return an integer representing GPIO bits
+ */
+SOAPY_SDR_API unsigned SoapySDRDevice_readGPIO(const SoapySDRDevice *device, const char *bank);
+
+/*!
+ * Write the data direction of a GPIO bank.
+ * 1 bits represent outputs, 0 bits represent inputs.
+ * \param device a pointer to a device instance
+ * \param bank the name of an available bank
+ * \param dir an integer representing data direction bits
+ */
+SOAPY_SDR_API void SoapySDRDevice_writeGPIODir(SoapySDRDevice *device, const char *bank, const unsigned dir);
+
+/*!
+ * Write the data direction of a GPIO bank with modification mask.
+ * 1 bits represent outputs, 0 bits represent inputs.
+ * \param device a pointer to a device instance
+ * \param bank the name of an available bank
+ * \param dir an integer representing data direction bits
+ * \param mask a modification mask where 1 = modify
+ */
+SOAPY_SDR_API void SoapySDRDevice_writeGPIODirMasked(SoapySDRDevice *device, const char *bank, const unsigned dir, const unsigned mask);
+
+/*!
+ * Read the data direction of a GPIO bank.
+ * \param device a pointer to a device instance
+ * 1 bits represent outputs, 0 bits represent inputs.
+ * \param bank the name of an available bank
+ * \return an integer representing data direction bits
+ */
+SOAPY_SDR_API unsigned SoapySDRDevice_readGPIODir(const SoapySDRDevice *device, const char *bank);
+
+/*******************************************************************
+ * I2C API
+ ******************************************************************/
+
+/*!
+ * Write to an available I2C slave.
+ * If the device contains multiple I2C masters,
+ * the address bits can encode which master.
+ * \param device a pointer to a device instance
+ * \param addr the address of the slave
+ * \param data an array of bytes write out
+ * \param numBytes the number of bytes to write
+ */
+SOAPY_SDR_API void SoapySDRDevice_writeI2C(SoapySDRDevice *device, const int addr, const char *data, const size_t numBytes);
+
+/*!
+ * Read from an available I2C slave.
+ * If the device contains multiple I2C masters,
+ * the address bits can encode which master.
+ * \param device a pointer to a device instance
+ * \param addr the address of the slave
+ * \param numBytes the number of bytes to read
+ * \return an array of bytes read from the slave
+ */
+SOAPY_SDR_API char *SoapySDRDevice_readI2C(SoapySDRDevice *device, const int addr, const size_t numBytes);
+
+/*******************************************************************
+ * SPI API
+ ******************************************************************/
+
+/*!
+ * Perform a SPI transaction and return the result.
+ * Its up to the implementation to set the clock rate,
+ * and read edge, and the write edge of the SPI core.
+ * SPI slaves without a readback pin will return 0.
+ *
+ * If the device contains multiple SPI masters,
+ * the address bits can encode which master.
+ *
+ * \param device a pointer to a device instance
+ * \param addr an address of an available SPI slave
+ * \param data the SPI data, numBits-1 is first out
+ * \param numBits the number of bits to clock out
+ * \return the readback data, numBits-1 is first in
+ */
+SOAPY_SDR_API unsigned SoapySDRDevice_transactSPI(SoapySDRDevice *device, const int addr, const unsigned data, const size_t numBits);
+
+/*******************************************************************
+ * UART API
+ ******************************************************************/
+
+/*!
+ * Enumerate the available UART devices.
+ * \param device a pointer to a device instance
+ * \param [out] length the number of UART names
+ * \return a list of names of available UARTs
+ */
+SOAPY_SDR_API char **SoapySDRDevice_listUARTs(const SoapySDRDevice *device, size_t *length);
+
+/*!
+ * Write data to a UART device.
+ * Its up to the implementation to set the baud rate,
+ * carriage return settings, flushing on newline.
+ * \param device a pointer to a device instance
+ * \param which the name of an available UART
+ * \param data a null terminated array of bytes
+ */
+SOAPY_SDR_API void SoapySDRDevice_writeUART(SoapySDRDevice *device, const char *which, const char *data);
+
+/*!
+ * Read bytes from a UART until timeout or newline.
+ * Its up to the implementation to set the baud rate,
+ * carriage return settings, flushing on newline.
+ * \param device a pointer to a device instance
+ * \param which the name of an available UART
+ * \param timeoutUs a timeout in microseconds
+ * \return a null terminated array of bytes
+ */
+SOAPY_SDR_API char *SoapySDRDevice_readUART(const SoapySDRDevice *device, const char *which, const long timeoutUs);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/include/SoapySDR/Device.hpp b/include/SoapySDR/Device.hpp
new file mode 100644
index 0000000..867c48f
--- /dev/null
+++ b/include/SoapySDR/Device.hpp
@@ -0,0 +1,1225 @@
+///
+/// \file SoapySDR/Device.hpp
+///
+/// Interface definition for Soapy SDR devices.
+///
+/// \copyright
+/// Copyright (c) 2014-2016 Josh Blum
+/// Copyright (c) 2016-2016 Bastille Networks
+/// SPDX-License-Identifier: BSL-1.0
+///
+
+#pragma once
+#include <SoapySDR/Config.hpp>
+#include <SoapySDR/Types.hpp>
+#include <SoapySDR/Constants.h>
+#include <SoapySDR/Errors.h>
+#include <vector>
+#include <string>
+#include <complex>
+#include <cstddef> //size_t
+
+namespace SoapySDR
+{
+
+//! Forward declaration of stream handle for type safety
+class Stream;
+
+/*!
+ * Abstraction for an SDR tranceiver device - configuration and streaming.
+ */
+class SOAPY_SDR_API Device
+{
+public:
+
+    //! virtual destructor for inheritance
+    virtual ~Device(void);
+
+    /*!
+     * Enumerate a list of available devices on the system.
+     * \param args device construction key/value argument filters
+     * \return a list of argument maps, each unique to a device
+     */
+    static KwargsList enumerate(const Kwargs &args = Kwargs());
+
+    /*!
+     * Enumerate a list of available devices on the system.
+     * Markup format for args: "keyA=valA, keyB=valB".
+     * \param args a markup string of key/value argument filters
+     * \return a list of argument maps, each unique to a device
+     */
+    static KwargsList enumerate(const std::string &args);
+
+    /*!
+     * Make a new Device object given device construction args.
+     * The device pointer will be stored in a table so subsequent calls
+     * with the same arguments will produce the same device.
+     * For every call to make, there should be a matched call to unmake.
+     *
+     * \param args device construction key/value argument map
+     * \return a pointer to a new Device object
+     */
+    static Device *make(const Kwargs &args = Kwargs());
+
+    /*!
+     * Make a new Device object given device construction args.
+     * The device pointer will be stored in a table so subsequent calls
+     * with the same arguments will produce the same device.
+     * For every call to make, there should be a matched call to unmake.
+     *
+     * \param args a markup string of key/value arguments
+     * \return a pointer to a new Device object
+     */
+    static Device *make(const std::string &args);
+
+    /*!
+     * Unmake or release a device object handle.
+     *
+     * \note This call is not thread safe. Implementations calling into unmake
+     * from multiple threads should protect this call with a mutex.
+     *
+     * \param device a pointer to a device object
+     */
+    static void unmake(Device *device);
+
+    /*******************************************************************
+     * Identification API
+     ******************************************************************/
+
+    /*!
+     * A key that uniquely identifies the device driver.
+     * This key identifies the underlying implementation.
+     * Serveral variants of a product may share a driver.
+     */
+    virtual std::string getDriverKey(void) const;
+
+    /*!
+     * A key that uniquely identifies the hardware.
+     * This key should be meaningful to the user
+     * to optimize for the underlying hardware.
+     */
+    virtual std::string getHardwareKey(void) const;
+
+    /*!
+     * Query a dictionary of available device information.
+     * This dictionary can any number of values like
+     * vendor name, product name, revisions, serials...
+     * This information can be displayed to the user
+     * to help identify the instantiated device.
+     */
+    virtual Kwargs getHardwareInfo(void) const;
+
+    /*******************************************************************
+     * Channels API
+     ******************************************************************/
+
+    /*!
+     * Set the frontend mapping of available DSP units to RF frontends.
+     * This mapping controls channel mapping and channel availability.
+     * \param direction the channel direction RX or TX
+     * \param mapping a vendor-specific mapping string
+     */
+    virtual void setFrontendMapping(const int direction, const std::string &mapping);
+
+    /*!
+     * Get the mapping configuration string.
+     * \param direction the channel direction RX or TX
+     * \param the vendor-specific mapping string
+     */
+    virtual std::string getFrontendMapping(const int direction) const;
+
+    /*!
+     * Get a number of channels given the streaming direction
+     */
+    virtual size_t getNumChannels(const int direction) const;
+
+    /*!
+     * Query a dictionary of available channel information.
+     * This dictionary can any number of values like
+     * decoder type, version, available functions...
+     * This information can be displayed to the user
+     * to help identify the instantiated channel.
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \return channel information
+     */
+    virtual Kwargs getChannelInfo(const int direction, const size_t channel) const;
+
+    /*!
+     * Find out if the specified channel is full or half duplex.
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \return true for full duplex, false for half duplex
+     */
+    virtual bool getFullDuplex(const int direction, const size_t channel) const;
+
+    /*******************************************************************
+     * Stream API
+     ******************************************************************/
+
+    /*!
+     * Query a list of the available stream formats.
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \return a list of allowed format strings
+     */
+    virtual std::vector<std::string> getStreamFormats(const int direction, const size_t channel) const;
+
+    /*!
+     * Get the hardware's native stream format for this channel.
+     * This is the format used by the underlying transport layer,
+     * and the direct buffer access API calls (when available).
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \param [out] fullScale the maximum possible value
+     * \return the native stream buffer format string
+     */
+    virtual std::string getNativeStreamFormat(const int direction, const size_t channel, double &fullScale) const;
+
+    /*!
+     * Query the argument info description for stream args.
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \return a list of argument info structures
+     */
+    virtual ArgInfoList getStreamArgsInfo(const int direction, const size_t channel) const;
+
+    /*!
+     * Initialize a stream given a list of channels and stream arguments.
+     * The implementation may change switches or power-up components.
+     * All stream API calls should be usable with the new stream object
+     * after setupStream() is complete, regardless of the activity state.
+     *
+     * Format string markup guidelines:
+     *  - C means complex
+     *  - F means floating point
+     *  - S means signed integer
+     *  - U means unsigned integer
+     *  - number float/int size in bytes (complex is 2x this size)
+     *
+     * Example format strings:
+     *  - CF32 complex float32 (8 bytes per element)
+     *  - CS16 complex int16 (4 bytes per element)
+     *  - CS12 complex int12 (3 bytes per element)
+     *  - CS4 complex int4 (1 byte per element)
+     *  - S32 int32 (4 bytes per element)
+     *  - U8 uint8 (1 byte per element)
+     *
+     * Recommended keys to use in the args dictionary:
+     *  - "WIRE" - format of the samples between device and host
+     *
+     * \param direction the channel direction RX or TX
+     * \param format the desired buffer format in read/writeStream()
+     * \param channels a list of channels for empty for automatic
+     * \param args stream args or empty for defaults
+     * \return an opaque pointer to a stream handle
+     */
+    virtual Stream *setupStream(
+        const int direction,
+        const std::string &format,
+        const std::vector<size_t> &channels = std::vector<size_t>(),
+        const Kwargs &args = Kwargs());
+
+    /*!
+     * Close an open stream created by setupStream
+     * The implementation may change switches or power-down components.
+     * \param stream the opaque pointer to a stream handle
+     */
+    virtual void closeStream(Stream *stream);
+
+    /*!
+     * Get the stream's maximum transmission unit (MTU) in number of elements.
+     * The MTU specifies the maximum payload transfer in a stream operation.
+     * This value can be used as a stream buffer allocation size that can
+     * best optimize throughput given the underlying stream implementation.
+     *
+     * \param stream the opaque pointer to a stream handle
+     * \return the MTU in number of stream elements (never zero)
+     */
+    virtual size_t getStreamMTU(Stream *stream) const;
+
+    /*!
+     * Activate a stream.
+     * Call activate to prepare a stream before using read/write().
+     * The implementation control switches or stimulate data flow.
+     *
+     * The timeNs is only valid when the flags have SOAPY_SDR_HAS_TIME.
+     * The numElems count can be used to request a finite burst size.
+     * The SOAPY_SDR_END_BURST flag can signal end on the finite burst.
+     * Not all implementations will support the full range of options.
+     * In this case, the implementation returns SOAPY_SDR_NOT_SUPPORTED.
+     *
+     * \param stream the opaque pointer to a stream handle
+     * \param flags optional flag indicators about the stream
+     * \param timeNs optional activation time in nanoseconds
+     * \param numElems optional element count for burst control
+     * \return 0 for success or error code on failure
+     */
+    virtual int activateStream(
+        Stream *stream,
+        const int flags = 0,
+        const long long timeNs = 0,
+        const size_t numElems = 0);
+
+    /*!
+     * Deactivate a stream.
+     * Call deactivate when not using using read/write().
+     * The implementation control switches or halt data flow.
+     *
+     * The timeNs is only valid when the flags have SOAPY_SDR_HAS_TIME.
+     * Not all implementations will support the full range of options.
+     * In this case, the implementation returns SOAPY_SDR_NOT_SUPPORTED.
+     *
+     * \param stream the opaque pointer to a stream handle
+     * \param flags optional flag indicators about the stream
+     * \param timeNs optional deactivation time in nanoseconds
+     * \return 0 for success or error code on failure
+     */
+    virtual int deactivateStream(
+        Stream *stream,
+        const int flags = 0,
+        const long long timeNs = 0);
+
+    /*!
+     * Read elements from a stream for reception.
+     * This is a multi-channel call, and buffs should be an array of void *,
+     * where each pointer will be filled with data from a different channel.
+     *
+     * **Client code compatibility:**
+     * The readStream() call should be well defined at all times,
+     * including prior to activation and after deactivation.
+     * When inactive, readStream() should implement the timeout
+     * specified by the caller and return SOAPY_SDR_TIMEOUT.
+     *
+     * \param stream the opaque pointer to a stream handle
+     * \param buffs an array of void* buffers num chans in size
+     * \param numElems the number of elements in each buffer
+     * \param flags optional flag indicators about the result
+     * \param timeNs the buffer's timestamp in nanoseconds
+     * \param timeoutUs the timeout in microseconds
+     * \return the number of elements read per buffer or error code
+     */
+    virtual int readStream(
+        Stream *stream,
+        void * const *buffs,
+        const size_t numElems,
+        int &flags,
+        long long &timeNs,
+        const long timeoutUs = 100000);
+
+    /*!
+     * Write elements to a stream for transmission.
+     * This is a multi-channel call, and buffs should be an array of void *,
+     * where each pointer will be filled with data for a different channel.
+     *
+     * **Client code compatibility:**
+     * Client code relies on writeStream() for proper back-pressure.
+     * The writeStream() implementation must enforce the timeout
+     * such that the call blocks until space becomes available
+     * or timeout expiration.
+     *
+     * \param stream the opaque pointer to a stream handle
+     * \param buffs an array of void* buffers num chans in size
+     * \param numElems the number of elements in each buffer
+     * \param flags optional input flags and output flags
+     * \param timeNs the buffer's timestamp in nanoseconds
+     * \param timeoutUs the timeout in microseconds
+     * \return the number of elements written per buffer or error
+     */
+    virtual int writeStream(
+        Stream *stream,
+        const void * const *buffs,
+        const size_t numElems,
+        int &flags,
+        const long long timeNs = 0,
+        const long timeoutUs = 100000);
+
+    /*!
+     * Readback status information about a stream.
+     * This call is typically used on a transmit stream
+     * to report time errors, underflows, and burst completion.
+     *
+     * **Client code compatibility:**
+     * Client code may continually poll readStreamStatus() in a loop.
+     * Implementations of readStreamStatus() should wait in the call
+     * for a status change event or until the timeout expiration.
+     * When stream status is not implemented on a particular stream,
+     * readStreamStatus() should return SOAPY_SDR_NOT_SUPPORTED.
+     * Client code may use this indication to disable a polling loop.
+     *
+     * \param stream the opaque pointer to a stream handle
+     * \param chanMask to which channels this status applies
+     * \param flags optional input flags and output flags
+     * \param timeNs the buffer's timestamp in nanoseconds
+     * \param timeoutUs the timeout in microseconds
+     * \return 0 for success or error code like timeout
+     */
+    virtual int readStreamStatus(
+        Stream *stream,
+        size_t &chanMask,
+        int &flags,
+        long long &timeNs,
+        const long timeoutUs = 100000);
+
+    /*******************************************************************
+     * Direct buffer access API
+     ******************************************************************/
+
+    /*!
+     * How many direct access buffers can the stream provide?
+     * This is the number of times the user can call acquire()
+     * on a stream without making subsequent calls to release().
+     * A return value of 0 means that direct access is not supported.
+     *
+     * \param stream the opaque pointer to a stream handle
+     * \return the number of direct access buffers or 0
+     */
+    virtual size_t getNumDirectAccessBuffers(Stream *stream);
+
+    /*!
+     * Get the buffer addresses for a scatter/gather table entry.
+     * When the underlying DMA implementation uses scatter/gather
+     * then this call provides the user addresses for that table.
+     *
+     * Example: The caller may query the DMA memory addresses once
+     * after stream creation to pre-allocate a re-usable ring-buffer.
+     *
+     * \param stream the opaque pointer to a stream handle
+     * \param handle an index value between 0 and num direct buffers - 1
+     * \param buffs an array of void* buffers num chans in size
+     * \return 0 for success or error code when not supported
+     */
+    virtual int getDirectAccessBufferAddrs(Stream *stream, const size_t handle, void **buffs);
+
+    /*!
+     * Acquire direct buffers from a receive stream.
+     * This call is part of the direct buffer access API.
+     *
+     * The buffs array will be filled with a stream pointer for each channel.
+     * Each pointer can be read up to the number of return value elements.
+     *
+     * The handle will be set by the implementation so that the caller
+     * may later release access to the buffers with releaseReadBuffer().
+     * Handle represents an index into the internal scatter/gather table
+     * such that handle is between 0 and num direct buffers - 1.
+     *
+     * \param stream the opaque pointer to a stream handle
+     * \param handle an index value used in the release() call
+     * \param buffs an array of void* buffers num chans in size
+     * \param flags optional flag indicators about the result
+     * \param timeNs the buffer's timestamp in nanoseconds
+     * \param timeoutUs the timeout in microseconds
+     * \return the number of elements read per buffer or error code
+     */
+    virtual int acquireReadBuffer(
+        Stream *stream,
+        size_t &handle,
+        const void **buffs,
+        int &flags,
+        long long &timeNs,
+        const long timeoutUs = 100000);
+
+    /*!
+     * Release an acquired buffer back to the receive stream.
+     * This call is part of the direct buffer access API.
+     *
+     * \param stream the opaque pointer to a stream handle
+     * \param handle the opaque handle from the acquire() call
+     */
+    virtual void releaseReadBuffer(
+        Stream *stream,
+        const size_t handle);
+
+    /*!
+     * Acquire direct buffers from a transmit stream.
+     * This call is part of the direct buffer access API.
+     *
+     * The buffs array will be filled with a stream pointer for each channel.
+     * Each pointer can be written up to the number of return value elements.
+     *
+     * The handle will be set by the implementation so that the caller
+     * may later release access to the buffers with releaseWriteBuffer().
+     * Handle represents an index into the internal scatter/gather table
+     * such that handle is between 0 and num direct buffers - 1.
+     *
+     * \param stream the opaque pointer to a stream handle
+     * \param handle an index value used in the release() call
+     * \param buffs an array of void* buffers num chans in size
+     * \param flags optional input flags and output flags
+     * \param timeNs the buffer's timestamp in nanoseconds
+     * \param timeoutUs the timeout in microseconds
+     * \return the number of available elements per buffer or error
+     */
+    virtual int acquireWriteBuffer(
+        Stream *stream,
+        size_t &handle,
+        void **buffs,
+        const long timeoutUs = 100000);
+
+    /*!
+     * Release an acquired buffer back to the transmit stream.
+     * This call is part of the direct buffer access API.
+     *
+     * Stream meta-data is provided as part of the release call,
+     * and not the acquire call so that the caller may acquire
+     * buffers without committing to the contents of the meta-data,
+     * which can be determined by the user as the buffers are filled.
+     *
+     * \param stream the opaque pointer to a stream handle
+     * \param handle the opaque handle from the acquire() call
+     * \param numElems the number of elements written to each buffer
+     * \param flags optional input flags and output flags
+     * \param timeNs the buffer's timestamp in nanoseconds
+     */
+    virtual void releaseWriteBuffer(
+        Stream *stream,
+        const size_t handle,
+        const size_t numElems,
+        int &flags,
+        const long long timeNs = 0);
+
+    /*******************************************************************
+     * Antenna API
+     ******************************************************************/
+
+    /*!
+     * Get a list of available antennas to select on a given chain.
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \return a list of available antenna names
+     */
+    virtual std::vector<std::string> listAntennas(const int direction, const size_t channel) const;
+
+    /*!
+     * Set the selected antenna on a chain.
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \param name the name of an available antenna
+     */
+    virtual void setAntenna(const int direction, const size_t channel, const std::string &name);
+
+    /*!
+     * Get the selected antenna on a chain.
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \return the name of an available antenna
+     */
+    virtual std::string getAntenna(const int direction, const size_t channel) const;
+
+    /*******************************************************************
+     * Frontend corrections API
+     ******************************************************************/
+
+    /*!
+     * Does the device support automatic DC offset corrections?
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \return true if automatic corrections are supported
+     */
+    virtual bool hasDCOffsetMode(const int direction, const size_t channel) const;
+
+    /*!
+     * Set the automatic DC offset corrections mode.
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \param automatic true for automatic offset correction
+     */
+    virtual void setDCOffsetMode(const int direction, const size_t channel, const bool automatic);
+
+    /*!
+     * Get the automatic DC offset corrections mode.
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \return true for automatic offset correction
+     */
+    virtual bool getDCOffsetMode(const int direction, const size_t channel) const;
+
+    /*!
+     * Does the device support frontend DC offset correction?
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \return true if DC offset corrections are supported
+     */
+    virtual bool hasDCOffset(const int direction, const size_t channel) const;
+
+    /*!
+     * Set the frontend DC offset correction.
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \param offset the relative correction (1.0 max)
+     */
+    virtual void setDCOffset(const int direction, const size_t channel, const std::complex<double> &offset);
+
+    /*!
+     * Get the frontend DC offset correction.
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \return the relative correction (1.0 max)
+     */
+    virtual std::complex<double> getDCOffset(const int direction, const size_t channel) const;
+
+    /*!
+     * Does the device support frontend IQ balance correction?
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \return true if IQ balance corrections are supported
+     */
+    virtual bool hasIQBalance(const int direction, const size_t channel) const;
+
+    /*!
+     * Set the frontend IQ balance correction.
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \param balance the relative correction (1.0 max)
+     */
+    virtual void setIQBalance(const int direction, const size_t channel, const std::complex<double> &balance);
+
+    /*!
+     * Get the frontend IQ balance correction.
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \return the relative correction (1.0 max)
+     */
+    virtual std::complex<double> getIQBalance(const int direction, const size_t channel) const;
+
+    /*******************************************************************
+     * Gain API
+     ******************************************************************/
+
+    /*!
+     * List available amplification elements.
+     * Elements should be in order RF to baseband.
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel
+     * \return a list of gain string names
+     */
+    virtual std::vector<std::string> listGains(const int direction, const size_t channel) const;
+
+    /*!
+     * Does the device support automatic gain control?
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \return true for automatic gain control
+     */
+    virtual bool hasGainMode(const int direction, const size_t channel) const;
+
+    /*!
+     * Set the automatic gain mode on the chain.
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \param automatic true for automatic gain setting
+     */
+    virtual void setGainMode(const int direction, const size_t channel, const bool automatic);
+
+    /*!
+     * Get the automatic gain mode on the chain.
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \return true for automatic gain setting
+     */
+    virtual bool getGainMode(const int direction, const size_t channel) const;
+
+    /*!
+     * Set the overall amplification in a chain.
+     * The gain will be distributed automatically across available element.
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \param name the name of an amplification element
+     * \param value the new amplification value in dB
+     */
+    virtual void setGain(const int direction, const size_t channel, const double value);
+
+    /*!
+     * Set the value of a amplification element in a chain.
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \param name the name of an amplification element
+     * \param value the new amplification value in dB
+     */
+    virtual void setGain(const int direction, const size_t channel, const std::string &name, const double value);
+
+    /*!
+     * Get the overall value of the gain elements in a chain.
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \return the value of the gain in dB
+     */
+    virtual double getGain(const int direction, const size_t channel) const;
+
+    /*!
+     * Get the value of an individual amplification element in a chain.
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \param name the name of an amplification element
+     * \return the value of the gain in dB
+     */
+    virtual double getGain(const int direction, const size_t channel, const std::string &name) const;
+
+    /*!
+     * Get the overall range of possible gain values.
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \return a list of gain ranges in dB
+     */
+    virtual Range getGainRange(const int direction, const size_t channel) const;
+
+    /*!
+     * Get the range of possible gain values for a specific element.
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \param name the name of an amplification element
+     * \return a list of gain ranges in dB
+     */
+    virtual Range getGainRange(const int direction, const size_t channel, const std::string &name) const;
+
+    /*******************************************************************
+     * Frequency API
+     ******************************************************************/
+
+    /*!
+     * Set the center frequency of the chain.
+     *  - For RX, this specifies the down-conversion frequency.
+     *  - For TX, this specifies the up-conversion frequency.
+     *
+     * The default implementation of setFrequency() will tune the "RF"
+     * component as close as possible to the requested center frequency.
+     * Tuning inaccuracies will be compensated for with the "BB" component.
+     *
+     * The args can be used to augment the tuning algorithm.
+     *  - Use "OFFSET" to specify an "RF" tuning offset,
+     *    usually with the intention of moving the LO out of the passband.
+     *    The offset will be compensated for using the "BB" component.
+     *  - Use the name of a component for the key and a frequency in Hz
+     *    as the value (any format) to enforce a specific frequency.
+     *    The other components will be tuned with compensation
+     *    to achieve the specified overall frequency.
+     *  - Use the name of a component for the key and the value "IGNORE"
+     *    so that the tuning algorithm will avoid altering the component.
+     *  - Vendor specific implementations can also use the same args to augment
+     *    tuning in other ways such as specifying fractional vs integer N tuning.
+     *
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \param frequency the center frequency in Hz
+     * \param args optional tuner arguments
+     */
+    virtual void setFrequency(const int direction, const size_t channel, const double frequency, const Kwargs &args = Kwargs());
+
+    /*!
+     * Tune the center frequency of the specified element.
+     *  - For RX, this specifies the down-conversion frequency.
+     *  - For TX, this specifies the up-conversion frequency.
+     *
+     * Recommended names used to represent tunable components:
+     *  - "CORR" - freq error correction in PPM
+     *  - "RF" - frequency of the RF frontend
+     *  - "BB" - frequency of the baseband DSP
+     *
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \param name the name of a tunable element
+     * \param frequency the center frequency in Hz
+     * \param args optional tuner arguments
+     */
+    virtual void setFrequency(const int direction, const size_t channel, const std::string &name, const double frequency, const Kwargs &args = Kwargs());
+
+    /*!
+     * Get the overall center frequency of the chain.
+     *  - For RX, this specifies the down-conversion frequency.
+     *  - For TX, this specifies the up-conversion frequency.
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \return the center frequency in Hz
+     */
+    virtual double getFrequency(const int direction, const size_t channel) const;
+
+    /*!
+     * Get the frequency of a tunable element in the chain.
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \param name the name of a tunable element
+     * \return the tunable element's frequency in Hz
+     */
+    virtual double getFrequency(const int direction, const size_t channel, const std::string &name) const;
+
+    /*!
+     * List available tunable elements in the chain.
+     * Elements should be in order RF to baseband.
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel
+     * \return a list of tunable elements by name
+     */
+    virtual std::vector<std::string> listFrequencies(const int direction, const size_t channel) const;
+
+    /*!
+     * Get the range of overall frequency values.
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \return a list of frequency ranges in Hz
+     */
+    virtual RangeList getFrequencyRange(const int direction, const size_t channel) const;
+
+    /*!
+     * Get the range of tunable values for the specified element.
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \param name the name of a tunable element
+     * \return a list of frequency ranges in Hz
+     */
+    virtual RangeList getFrequencyRange(const int direction, const size_t channel, const std::string &name) const;
+
+    /*!
+     * Query the argument info description for tune args.
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \return a list of argument info structures
+     */
+    virtual ArgInfoList getFrequencyArgsInfo(const int direction, const size_t channel) const;
+
+    /*******************************************************************
+     * Sample Rate API
+     ******************************************************************/
+
+    /*!
+     * Set the baseband sample rate of the chain.
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \param rate the sample rate in samples per second
+     */
+    virtual void setSampleRate(const int direction, const size_t channel, const double rate);
+
+    /*!
+     * Get the baseband sample rate of the chain.
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \return the sample rate in samples per second
+     */
+    virtual double getSampleRate(const int direction, const size_t channel) const;
+
+    /*!
+     * Get the range of possible baseband sample rates.
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \return a list of possible rates in samples per second
+     */
+    virtual std::vector<double> listSampleRates(const int direction, const size_t channel) const;
+
+    /*******************************************************************
+     * Bandwidth API
+     ******************************************************************/
+
+    /*!
+     * Set the baseband filter width of the chain.
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \param bw the baseband filter width in Hz
+     */
+    virtual void setBandwidth(const int direction, const size_t channel, const double bw);
+
+    /*!
+     * Get the baseband filter width of the chain.
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \return the baseband filter width in Hz
+     */
+    virtual double getBandwidth(const int direction, const size_t channel) const;
+
+    /*!
+     * Get the range of possible baseband filter widths.
+     * \deprecated replaced by getBandwidthRange()
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \return a list of possible bandwidths in Hz
+     */
+    virtual std::vector<double> listBandwidths(const int direction, const size_t channel) const;
+
+    /*!
+     * Get the range of possible baseband filter widths.
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \return a list of bandwidth ranges in Hz
+     */
+    virtual RangeList getBandwidthRange(const int direction, const size_t channel) const;
+
+    /*******************************************************************
+     * Clocking API
+     ******************************************************************/
+
+    /*!
+     * Set the master clock rate of the device.
+     * \param rate the clock rate in Hz
+     */
+    virtual void setMasterClockRate(const double rate);
+
+    /*!
+     * Get the master clock rate of the device.
+     * \return the clock rate in Hz
+     */
+    virtual double getMasterClockRate(void) const;
+
+    /*!
+     * Get the range of available master clock rates.
+     * \return a list of clock rate ranges in Hz
+     */
+    virtual RangeList getMasterClockRates(void) const;
+
+    /*!
+     * Get the list of available clock sources.
+     * \return a list of clock source names
+     */
+    virtual std::vector<std::string> listClockSources(void) const;
+
+    /*!
+     * Set the clock source on the device
+     * \param source the name of a clock source
+     */
+    virtual void setClockSource(const std::string &source);
+
+    /*!
+     * Get the clock source of the device
+     * \return the name of a clock source
+     */
+    virtual std::string getClockSource(void) const;
+
+    /*******************************************************************
+     * Time API
+     ******************************************************************/
+
+    /*!
+     * Get the list of available time sources.
+     * \return a list of time source names
+     */
+    virtual std::vector<std::string> listTimeSources(void) const;
+
+    /*!
+     * Set the time source on the device
+     * \param source the name of a time source
+     */
+    virtual void setTimeSource(const std::string &source);
+
+    /*!
+     * Get the time source of the device
+     * \return the name of a time source
+     */
+    virtual std::string getTimeSource(void) const;
+
+    /*!
+     * Does this device have a hardware clock?
+     * \param what optional argument
+     * \return true if the hardware clock exists
+     */
+    virtual bool hasHardwareTime(const std::string &what = "") const;
+
+    /*!
+     * Read the time from the hardware clock on the device.
+     * The what argument can refer to a specific time counter.
+     * \param what optional argument
+     * \return the time in nanoseconds
+     */
+    virtual long long getHardwareTime(const std::string &what = "") const;
+
+    /*!
+     * Write the time to the hardware clock on the device.
+     * The what argument can refer to a specific time counter.
+     * \param timeNs time in nanoseconds
+     * \param what optional argument
+     */
+    virtual void setHardwareTime(const long long timeNs, const std::string &what = "");
+
+    /*!
+     * Set the time of subsequent configuration calls.
+     * The what argument can refer to a specific command queue.
+     * Implementations may use a time of 0 to clear.
+     * \deprecated replaced by setHardwareTime()
+     * \param timeNs time in nanoseconds
+     * \param what optional argument
+     */
+    virtual void setCommandTime(const long long timeNs, const std::string &what = "");
+
+    /*******************************************************************
+     * Sensor API
+     ******************************************************************/
+
+    /*!
+     * List the available global readback sensors.
+     * A sensor can represent a reference lock, RSSI, temperature.
+     * \return a list of available sensor string names
+     */
+    virtual std::vector<std::string> listSensors(void) const;
+
+    /*!
+     * Get meta-information about a sensor.
+     * Example: displayable name, type, range.
+     * \param name the name of an available sensor
+     * \return meta-information about a sensor
+     */
+    virtual ArgInfo getSensorInfo(const std::string &name) const;
+
+    /*!
+     * Readback a global sensor given the name.
+     * The value returned is a string which can represent
+     * a boolean ("true"/"false"), an integer, or float.
+     * \param name the name of an available sensor
+     * \return the current value of the sensor
+     */
+    virtual std::string readSensor(const std::string &name) const;
+
+    /*!
+     * List the available channel readback sensors.
+     * A sensor can represent a reference lock, RSSI, temperature.
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \return a list of available sensor string names
+     */
+    virtual std::vector<std::string> listSensors(const int direction, const size_t channel) const;
+
+    /*!
+     * Get meta-information about a channel sensor.
+     * Example: displayable name, type, range.
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \param name the name of an available sensor
+     * \return meta-information about a sensor
+     */
+    virtual ArgInfo getSensorInfo(const int direction, const size_t channel, const std::string &name) const;
+
+    /*!
+     * Readback a channel sensor given the name.
+     * The value returned is a string which can represent
+     * a boolean ("true"/"false"), an integer, or float.
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \param name the name of an available sensor
+     * \return the current value of the sensor
+     */
+    virtual std::string readSensor(const int direction, const size_t channel, const std::string &name) const;
+
+    /*******************************************************************
+     * Register API
+     ******************************************************************/
+
+    /*!
+     * Get a list of available register interfaces by name.
+     * \return a list of available register interfaces
+     */
+    virtual std::vector<std::string> listRegisterInterfaces(void) const;
+
+    /*!
+     * Write a register on the device given the interface name.
+     * This can represent a register on a soft CPU, FPGA, IC;
+     * the interpretation is up the implementation to decide.
+     * \param name the name of a available register interface
+     * \param addr the register address
+     * \param value the register value
+     */
+    virtual void writeRegister(const std::string &name, const unsigned addr, const unsigned value);
+
+    /*!
+     * Read a register on the device given the interface name.
+     * \param name the name of a available register interface
+     * \param addr the register address
+     * \return the register value
+     */
+    virtual unsigned readRegister(const std::string &name, const unsigned addr) const;
+
+    /*!
+     * Write a register on the device.
+     * This can represent a register on a soft CPU, FPGA, IC;
+     * the interpretation is up the implementation to decide.
+     * \deprecated replaced by writeRegister(name)
+     * \param addr the register address
+     * \param value the register value
+     */
+    virtual void writeRegister(const unsigned addr, const unsigned value);
+
+    /*!
+     * Read a register on the device.
+     * \deprecated replaced by readRegister(name)
+     * \param addr the register address
+     * \return the register value
+     */
+    virtual unsigned readRegister(const unsigned addr) const;
+
+    /*******************************************************************
+     * Settings API
+     ******************************************************************/
+
+    /*!
+     * Describe the allowed keys and values used for settings.
+     * \return a list of argument info structures
+     */
+    virtual ArgInfoList getSettingInfo(void) const;
+
+    /*!
+     * Write an arbitrary setting on the device.
+     * The interpretation is up the implementation.
+     * \param key the setting identifier
+     * \param value the setting value
+     */
+    virtual void writeSetting(const std::string &key, const std::string &value);
+
+    /*!
+     * Read an arbitrary setting on the device.
+     * \param key the setting identifier
+     * \return the setting value
+     */
+    virtual std::string readSetting(const std::string &key) const;
+
+    /*!
+     * Describe the allowed keys and values used for channel settings.
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \return a list of argument info structures
+     */
+    virtual ArgInfoList getSettingInfo(const int direction, const size_t channel) const;
+
+    /*!
+     * Write an arbitrary channel setting on the device.
+     * The interpretation is up the implementation.
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \param key the setting identifier
+     * \param value the setting value
+     */
+    virtual void writeSetting(const int direction, const size_t channel, const std::string &key, const std::string &value);
+
+    /*!
+     * Read an arbitrary channel setting on the device.
+     * \param direction the channel direction RX or TX
+     * \param channel an available channel on the device
+     * \param key the setting identifier
+     * \return the setting value
+     */
+    virtual std::string readSetting(const int direction, const size_t channel, const std::string &key) const;
+
+    /*******************************************************************
+     * GPIO API
+     ******************************************************************/
+
+    /*!
+     * Get a list of available GPIO banks by name.
+     */
+    virtual std::vector<std::string> listGPIOBanks(void) const;
+
+    /*!
+     * Write the value of a GPIO bank.
+     * \param bank the name of an available bank
+     * \param value an integer representing GPIO bits
+     */
+    virtual void writeGPIO(const std::string &bank, const unsigned value);
+
+    /*!
+     * Write the value of a GPIO bank with modification mask.
+     * \param bank the name of an available bank
+     * \param value an integer representing GPIO bits
+     * \param mask a modification mask where 1 = modify
+     */
+    virtual void writeGPIO(const std::string &bank, const unsigned value, const unsigned mask);
+
+    /*!
+     * Readback the value of a GPIO bank.
+     * \param bank the name of an available bank
+     * \return an integer representing GPIO bits
+     */
+    virtual unsigned readGPIO(const std::string &bank) const;
+
+    /*!
+     * Write the data direction of a GPIO bank.
+     * 1 bits represent outputs, 0 bits represent inputs.
+     * \param bank the name of an available bank
+     * \param dir an integer representing data direction bits
+     */
+    virtual void writeGPIODir(const std::string &bank, const unsigned dir);
+
+    /*!
+     * Write the data direction of a GPIO bank with modification mask.
+     * 1 bits represent outputs, 0 bits represent inputs.
+     * \param bank the name of an available bank
+     * \param dir an integer representing data direction bits
+     * \param mask a modification mask where 1 = modify
+     */
+    virtual void writeGPIODir(const std::string &bank, const unsigned dir, const unsigned mask);
+
+    /*!
+     * Read the data direction of a GPIO bank.
+     * 1 bits represent outputs, 0 bits represent inputs.
+     * \param bank the name of an available bank
+     * \return an integer representing data direction bits
+     */
+    virtual unsigned readGPIODir(const std::string &bank) const;
+
+    /*******************************************************************
+     * I2C API
+     ******************************************************************/
+
+    /*!
+     * Write to an available I2C slave.
+     * If the device contains multiple I2C masters,
+     * the address bits can encode which master.
+     * \param addr the address of the slave
+     * \param data an array of bytes write out
+     */
+    virtual void writeI2C(const int addr, const std::string &data);
+
+    /*!
+     * Read from an available I2C slave.
+     * If the device contains multiple I2C masters,
+     * the address bits can encode which master.
+     * \param addr the address of the slave
+     * \param numBytes the number of bytes to read
+     * \return an array of bytes read from the slave
+     */
+    virtual std::string readI2C(const int addr, const size_t numBytes);
+
+    /*******************************************************************
+     * SPI API
+     ******************************************************************/
+
+    /*!
+     * Perform a SPI transaction and return the result.
+     * Its up to the implementation to set the clock rate,
+     * and read edge, and the write edge of the SPI core.
+     * SPI slaves without a readback pin will return 0.
+     *
+     * If the device contains multiple SPI masters,
+     * the address bits can encode which master.
+     *
+     * \param addr an address of an available SPI slave
+     * \param data the SPI data, numBits-1 is first out
+     * \param numBits the number of bits to clock out
+     * \return the readback data, numBits-1 is first in
+     */
+    virtual unsigned transactSPI(const int addr, const unsigned data, const size_t numBits);
+
+    /*******************************************************************
+     * UART API
+     ******************************************************************/
+
+    /*!
+     * Enumerate the available UART devices.
+     * \return a list of names of available UARTs
+     */
+    virtual std::vector<std::string> listUARTs(void) const;
+
+    /*!
+     * Write data to a UART device.
+     * Its up to the implementation to set the baud rate,
+     * carriage return settings, flushing on newline.
+     * \param which the name of an available UART
+     * \param data an array of bytes to write out
+     */
+    virtual void writeUART(const std::string &which, const std::string &data);
+
+    /*!
+     * Read bytes from a UART until timeout or newline.
+     * Its up to the implementation to set the baud rate,
+     * carriage return settings, flushing on newline.
+     * \param which the name of an available UART
+     * \param timeoutUs a timeout in microseconds
+     * \return an array of bytes read from the UART
+     */
+    virtual std::string readUART(const std::string &which, const long timeoutUs = 100000) const;
+
+};
+
+};
diff --git a/include/SoapySDR/Errors.h b/include/SoapySDR/Errors.h
new file mode 100644
index 0000000..c6fcfdb
--- /dev/null
+++ b/include/SoapySDR/Errors.h
@@ -0,0 +1,68 @@
+///
+/// \file SoapySDR/Errors.h
+///
+/// Error codes used in the device API.
+///
+/// \copyright
+/// Copyright (c) 2014-2015 Josh Blum
+/// SPDX-License-Identifier: BSL-1.0
+///
+
+#pragma once
+#include <SoapySDR/Config.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*!
+ * Convert a error code to a string for printing purposes.
+ * If the error code is unrecognized, errToStr returns "UNKNOWN".
+ * \param errorCode a negative integer return code
+ * \return a pointer to a string representing the error
+ */
+SOAPY_SDR_API const char *SoapySDR_errToStr(const int errorCode);
+
+#ifdef __cplusplus
+}
+#endif
+
+/*!
+ * Returned when read has a timeout.
+ */
+#define SOAPY_SDR_TIMEOUT (-1)
+
+/*!
+ * Returned for non-specific stream errors.
+ */
+#define SOAPY_SDR_STREAM_ERROR (-2)
+
+/*!
+ * Returned when read has data corruption.
+ * For example, the driver saw a malformed packet.
+ */
+#define SOAPY_SDR_CORRUPTION (-3)
+
+/*!
+ * Returned when read has an overflow condition.
+ * For example, and internal buffer has filled.
+ */
+#define SOAPY_SDR_OVERFLOW (-4)
+
+/*!
+ * Returned when a requested operation or flag setting
+ * is not supported by the underlying implementation.
+ */
+#define SOAPY_SDR_NOT_SUPPORTED (-5)
+
+/*!
+ * Returned when a the device encountered a stream time
+ * which was expired (late) or too early to process.
+ */
+#define SOAPY_SDR_TIME_ERROR (-6)
+
+/*!
+ * Returned when write caused an underflow condition.
+ * For example, a continuous stream was interrupted.
+ */
+#define SOAPY_SDR_UNDERFLOW (-7)
diff --git a/include/SoapySDR/Errors.hpp b/include/SoapySDR/Errors.hpp
new file mode 100644
index 0000000..03537ad
--- /dev/null
+++ b/include/SoapySDR/Errors.hpp
@@ -0,0 +1,26 @@
+///
+/// \file SoapySDR/Errors.hpp
+///
+/// Error codes used in the device API.
+///
+/// \copyright
+/// Copyright (c) 2015-2015 Josh Blum
+/// SPDX-License-Identifier: BSL-1.0
+///
+
+#pragma once
+#include <SoapySDR/Config.h>
+#include <SoapySDR/Errors.h>
+
+namespace SoapySDR
+{
+
+/*!
+ * Convert a error code to a string for printing purposes.
+ * If the error code is unrecognized, errToStr returns "UNKNOWN".
+ * \param errorCode a negative integer return code
+ * \return a pointer to a string representing the error
+ */
+SOAPY_SDR_API const char *errToStr(const int errorCode);
+
+}
diff --git a/include/SoapySDR/Formats.h b/include/SoapySDR/Formats.h
new file mode 100644
index 0000000..1087bae
--- /dev/null
+++ b/include/SoapySDR/Formats.h
@@ -0,0 +1,88 @@
+///
+/// \file SoapySDR/Formats.h
+///
+/// Format strings used in the stream API.
+///
+/// \copyright
+/// Copyright (c) 2015-2015 Josh Blum
+/// SPDX-License-Identifier: BSL-1.0
+///
+
+#pragma once
+#include <SoapySDR/Config.h>
+#include <stddef.h> //size_t
+
+//! Complex 64-bit floats (complex double)
+#define SOAPY_SDR_CF64 "CF64"
+
+//! Complex 32-bit floats (complex float)
+#define SOAPY_SDR_CF32 "CF32"
+
+//! Complex signed 32-bit integers (complex int32)
+#define SOAPY_SDR_CS32 "CS32"
+
+//! Complex unsigned 32-bit integers (complex uint32)
+#define SOAPY_SDR_CU32 "CU32"
+
+//! Complex signed 16-bit integers (complex int16)
+#define SOAPY_SDR_CS16 "CS16"
+
+//! Complex unsigned 16-bit integers (complex uint16)
+#define SOAPY_SDR_CU16 "CU16"
+
+//! Complex signed 12-bit integers (3 bytes)
+#define SOAPY_SDR_CS12 "CS12"
+
+//! Complex unsigned 12-bit integers (3 bytes)
+#define SOAPY_SDR_CU12 "CU12"
+
+//! Complex signed 8-bit integers (complex int8)
+#define SOAPY_SDR_CS8 "CS8"
+
+//! Complex unsigned 8-bit integers (complex uint8)
+#define SOAPY_SDR_CU8 "CU8"
+
+//! Complex signed 4-bit integers (1 byte)
+#define SOAPY_SDR_CS4 "CS4"
+
+//! Complex unsigned 4-bit integers (1 byte)
+#define SOAPY_SDR_CU4 "CU4"
+
+//! Real 64-bit floats (double)
+#define SOAPY_SDR_F64 "F64"
+
+//! Real 32-bit floats (float)
+#define SOAPY_SDR_F32 "F32"
+
+//! Real signed 32-bit integers (int32)
+#define SOAPY_SDR_S32 "S32"
+
+//! Real unsigned 32-bit integers (uint32)
+#define SOAPY_SDR_U32 "U32"
+
+//! Real signed 16-bit integers (int16)
+#define SOAPY_SDR_S16 "S16"
+
+//! Real unsigned 16-bit integers (uint16)
+#define SOAPY_SDR_U16 "U16"
+
+//! Real signed 8-bit integers (int8)
+#define SOAPY_SDR_S8 "S8"
+
+//! Real unsigned 8-bit integers (uint8)
+#define SOAPY_SDR_U8 "U8"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*!
+ * Get the size of a single element in the specified format.
+ * \param format a supported format string
+ * \return the size of an element in bytes
+ */
+SOAPY_SDR_API size_t SoapySDR_formatToSize(const char *format);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/include/SoapySDR/Formats.hpp b/include/SoapySDR/Formats.hpp
new file mode 100644
index 0000000..16f5432
--- /dev/null
+++ b/include/SoapySDR/Formats.hpp
@@ -0,0 +1,27 @@
+///
+/// \file SoapySDR/Formats.hpp
+///
+/// Format strings used in the stream API.
+///
+/// \copyright
+/// Copyright (c) 2015-2015 Josh Blum
+/// SPDX-License-Identifier: BSL-1.0
+///
+
+#pragma once
+#include <SoapySDR/Config.h>
+#include <SoapySDR/Formats.h>
+#include <string>
+#include <cstddef>
+
+namespace SoapySDR
+{
+
+/*!
+ * Get the size of a single element in the specified format.
+ * \param format a supported format string
+ * \return the size of an element in bytes
+ */
+SOAPY_SDR_API size_t formatToSize(const std::string &format);
+
+}
diff --git a/include/SoapySDR/Logger.h b/include/SoapySDR/Logger.h
new file mode 100644
index 0000000..7f03ee6
--- /dev/null
+++ b/include/SoapySDR/Logger.h
@@ -0,0 +1,96 @@
+///
+/// \file SoapySDR/Logger.h
+///
+/// Logger API for SoapySDR devices.
+/// Implementations should use the logger rather than stdio.
+/// The default log handler prints to the stdout and stderr.
+///
+/// \copyright
+/// Copyright (c) 2014-2015 Josh Blum
+/// SPDX-License-Identifier: BSL-1.0
+///
+
+#pragma once
+#include <SoapySDR/Config.h>
+#include <stdarg.h>
+
+/*!
+ * The available priority levels for log messages.
+ *
+ * The default log level threshold is SOAPY_SDR_INFO.
+ * Log messages with lower priorities are dropped.
+ *
+ * The default threshold can be set via the
+ * SOAPY_SDR_LOG_LEVEL environment variable.
+ * Set SOAPY_SDR_LOG_LEVEL to the string value:
+ * "WARNING", "ERROR", "DEBUG", etc...
+ * or set it to the equivalent integer value.
+ */
+typedef enum
+{
+    SOAPY_SDR_FATAL    = 1, //!< A fatal error. The application will most likely terminate. This is the highest priority.
+    SOAPY_SDR_CRITICAL = 2, //!< A critical error. The application might not be able to continue running successfully.
+    SOAPY_SDR_ERROR    = 3, //!< An error. An operation did not complete successfully, but the application as a whole is not affected.
+    SOAPY_SDR_WARNING  = 4, //!< A warning. An operation completed with an unexpected result.
+    SOAPY_SDR_NOTICE   = 5, //!< A notice, which is an information with just a higher priority.
+    SOAPY_SDR_INFO     = 6, //!< An informational message, usually denoting the successful completion of an operation.
+    SOAPY_SDR_DEBUG    = 7, //!< A debugging message.
+    SOAPY_SDR_TRACE    = 8, //!< A tracing message. This is the lowest priority.
+    SOAPY_SDR_SSI      = 9, //!< Streaming status indicators such as "U" (underflow) and "O" (overflow).
+} SoapySDRLogLevel;
+
+//! Compile-time detection macro for SSI feature
+#define SOAPY_SDR_SSI SOAPY_SDR_SSI
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*!
+ * Send a message to the registered logger.
+ * \param logLevel a possible logging level
+ * \param message a logger message string
+ */
+SOAPY_SDR_API void SoapySDR_log(const SoapySDRLogLevel logLevel, const char *message);
+
+/*!
+ * Send a message to the registered logger.
+ * \param logLevel a possible logging level
+ * \param format a printf style format string
+ * \param argList an argument list for the formatter
+ */
+SOAPY_SDR_API void SoapySDR_vlogf(const SoapySDRLogLevel logLevel, const char *format, va_list argList);
+
+/*!
+ * Send a message to the registered logger.
+ * \param logLevel a possible logging level
+ * \param format a printf style format string
+ */
+static inline void SoapySDR_logf(const SoapySDRLogLevel logLevel, const char *format, ...)
+{
+    va_list argList;
+    va_start(argList, format);
+    SoapySDR_vlogf(logLevel, format, argList);
+    va_end(argList);
+}
+
+/*!
+ * Typedef for the registered log handler function.
+ */
+typedef void (*SoapySDRLogHandler)(const SoapySDRLogLevel logLevel, const char *message);
+
+/*!
+ * Register a new system log handler.
+ * Platforms should call this to replace the default stdio handler.
+ */
+SOAPY_SDR_API void SoapySDR_registerLogHandler(const SoapySDRLogHandler handler);
+
+/*!
+ * Set the log level threshold.
+ * Log messages with lower priority are dropped.
+ */
+SOAPY_SDR_API void SoapySDR_setLogLevel(const SoapySDRLogLevel logLevel);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/include/SoapySDR/Logger.hpp b/include/SoapySDR/Logger.hpp
new file mode 100644
index 0000000..2fad49d
--- /dev/null
+++ b/include/SoapySDR/Logger.hpp
@@ -0,0 +1,68 @@
+///
+/// \file SoapySDR/Logger.hpp
+///
+/// Logger API for SoapySDR devices.
+/// Implementations should use the logger rather than stdio.
+/// The default log handler prints to the stdout and stderr.
+///
+/// \copyright
+/// Copyright (c) 2014-2015 Josh Blum
+/// SPDX-License-Identifier: BSL-1.0
+///
+
+#pragma once
+#include <SoapySDR/Logger.h>
+#include <string>
+#include <cstdarg>
+
+namespace SoapySDR
+{
+
+typedef SoapySDRLogLevel LogLevel;
+
+/*!
+ * Send a message to the registered logger.
+ * \param logLevel a possible logging level
+ * \param message a logger message string
+ */
+SOAPY_SDR_API void log(const LogLevel logLevel, const std::string &message);
+
+/*!
+ * Send a message to the registered logger.
+ * \param logLevel a possible logging level
+ * \param format a printf style format string
+ * \param argList an argument list for the formatter
+ */
+SOAPY_SDR_API void vlogf(const SoapySDRLogLevel logLevel, const char *format, va_list argList);
+
+/*!
+ * Send a message to the registered logger.
+ * \param logLevel a possible logging level
+ * \param format a printf style format string
+ */
+static inline void logf(const SoapySDRLogLevel logLevel, const char *format, ...)
+{
+    va_list argList;
+    va_start(argList, format);
+    SoapySDR::vlogf(logLevel, format, argList);
+    va_end(argList);
+}
+
+/*!
+ * Typedef for the registered log handler function.
+ */
+typedef SoapySDRLogHandler LogHandler;
+
+/*!
+ * Register a new system log handler.
+ * Platforms should call this to replace the default stdio handler.
+ */
+SOAPY_SDR_API void registerLogHandler(const LogHandler &handler);
+
+/*!
+ * Set the log level threshold.
+ * Log messages with lower priority are dropped.
+ */
+SOAPY_SDR_API void setLogLevel(const LogLevel logLevel);
+
+}
diff --git a/include/SoapySDR/Modules.h b/include/SoapySDR/Modules.h
new file mode 100644
index 0000000..b11b826
--- /dev/null
+++ b/include/SoapySDR/Modules.h
@@ -0,0 +1,77 @@
+///
+/// \file SoapySDR/Modules.h
+///
+/// Utility functions to deal with modules.
+/// These utility functions are made available for advanced usage.
+/// For most use cases, the API will automatically load modules.
+///
+/// \copyright
+/// Copyright (c) 2014-2015 Josh Blum
+/// SPDX-License-Identifier: BSL-1.0
+///
+
+#pragma once
+#include <SoapySDR/Config.h>
+#include <SoapySDR/Types.h>
+#include <stddef.h> //size_t
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+//! Query the root installation path
+SOAPY_SDR_API const char *SoapySDR_getRootPath(void);
+
+/*!
+ * List all modules found in default path.
+ * The result is an array of strings owned by the caller.
+ * \param [out] length the number of elements in the result.
+ * \return a list of file paths to loadable modules
+ */
+SOAPY_SDR_API char **SoapySDR_listModules(size_t *length);
+
+/*!
+ * List all modules found in the given path.
+ * The result is an array of strings owned by the caller.
+ * \param path a directory on the system
+ * \param [out] length the number of elements in the result.
+ * \return a list of file paths to loadable modules
+ */
+SOAPY_SDR_API char **SoapySDR_listModulesPath(const char *path, size_t *length);
+
+/*!
+ * Load a single module given its file system path.
+ * The caller must free the result error string.
+ * \param path the path to a specific module file
+ * \param return an error message, empty on success
+ */
+SOAPY_SDR_API char *SoapySDR_loadModule(const char *path);
+
+/*!
+ * List all registration loader errors for a given module path.
+ * The resulting dictionary contains all registry entry names
+ * provided by the specified module. The value of each entry
+ * is an error message string or empty on successful load.
+ * \param path the path to a specific module file
+ * \return a dictionary of registry names to error messages
+ */
+SOAPY_SDR_API SoapySDRKwargs SoapySDR_getLoaderResult(const char *path);
+
+/*!
+ * Unload a module that was loaded with loadModule().
+ * The caller must free the result error string.
+ * \param path the path to a specific module file
+ * \param return an error message, empty on success
+ */
+SOAPY_SDR_API char *SoapySDR_unloadModule(const char *path);
+
+/*!
+ * Load the support modules installed on this system.
+ * This call will only actually perform the load once.
+ * Subsequent calls are a NOP.
+ */
+SOAPY_SDR_API void SoapySDR_loadModules(void);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/include/SoapySDR/Modules.hpp b/include/SoapySDR/Modules.hpp
new file mode 100644
index 0000000..39bca74
--- /dev/null
+++ b/include/SoapySDR/Modules.hpp
@@ -0,0 +1,69 @@
+///
+/// \file SoapySDR/Modules.hpp
+///
+/// Utility functions to deal with modules.
+/// These utility functions are made available for advanced usage.
+/// For most use cases, the API will automatically load modules.
+///
+/// \copyright
+/// Copyright (c) 2014-2015 Josh Blum
+/// SPDX-License-Identifier: BSL-1.0
+///
+
+#pragma once
+#include <SoapySDR/Config.hpp>
+#include <SoapySDR/Types.hpp>
+#include <vector>
+#include <string>
+
+namespace SoapySDR
+{
+
+//! Query the root installation path
+SOAPY_SDR_API std::string getRootPath(void);
+
+/*!
+ * List all modules found in default path.
+ * \return a list of file paths to loadable modules
+ */
+SOAPY_SDR_API std::vector<std::string> listModules(void);
+
+/*!
+ * List all modules found in the given path.
+ * \param path a directory on the system
+ * \return a list of file paths to loadable modules
+ */
+SOAPY_SDR_API std::vector<std::string> listModules(const std::string &path);
+
+/*!
+ * Load a single module given its file system path.
+ * \param path the path to a specific module file
+ * \param return an error message, empty on success
+ */
+SOAPY_SDR_API std::string loadModule(const std::string &path);
+
+/*!
+ * List all registration loader errors for a given module path.
+ * The resulting dictionary contains all registry entry names
+ * provided by the specified module. The value of each entry
+ * is an error message string or empty on successful load.
+ * \param path the path to a specific module file
+ * \return a dictionary of registry names to error messages
+ */
+SOAPY_SDR_API Kwargs getLoaderResult(const std::string &path);
+
+/*!
+ * Unload a module that was loaded with loadModule().
+ * \param path the path to a specific module file
+ * \param return an error message, empty on success
+ */
+SOAPY_SDR_API std::string unloadModule(const std::string &path);
+
+/*!
+ * Load the support modules installed on this system.
+ * This call will only actually perform the load once.
+ * Subsequent calls are a NOP.
+ */
+SOAPY_SDR_API void loadModules(void);
+
+}
diff --git a/include/SoapySDR/Registry.hpp b/include/SoapySDR/Registry.hpp
new file mode 100644
index 0000000..95cf12a
--- /dev/null
+++ b/include/SoapySDR/Registry.hpp
@@ -0,0 +1,72 @@
+///
+/// \file SoapySDR/Registry.hpp
+///
+/// Device factory registration API.
+///
+/// \copyright
+/// Copyright (c) 2014-2015 Josh Blum
+/// SPDX-License-Identifier: BSL-1.0
+///
+
+#pragma once
+#include <SoapySDR/Config.hpp>
+#include <SoapySDR/Version.hpp>
+#include <SoapySDR/Types.hpp>
+#include <vector>
+#include <string>
+#include <map>
+
+namespace SoapySDR
+{
+
+//! forward declaration of device
+class Device;
+
+//! typedef for a device enumeration function
+typedef KwargsList (*FindFunction)(const Kwargs &);
+
+//! typedef for a device factory function
+typedef Device* (*MakeFunction)(const Kwargs &);
+
+//! typedef for a dictionary of find functions
+typedef std::map<std::string, FindFunction> FindFunctions;
+
+//! typedef for a dictionary of make functions
+typedef std::map<std::string, MakeFunction> MakeFunctions;
+
+/*!
+ * A registry object loads device functions into the global registry.
+ */
+class SOAPY_SDR_API Registry
+{
+public:
+
+    /*!
+     * Register a SDR device find and make function.
+     * \param name a unique name to identify the entry
+     * \param find the find function returns an arg list
+     * \param make the make function returns a device sptr
+     * \param abi this value must be SOAPY_SDR_ABI_VERSION
+     */
+    Registry(const std::string &name, const FindFunction &find, const MakeFunction &make, const std::string &abi);
+
+    //! Cleanup this registry entry
+    ~Registry(void);
+
+    /*!
+     * List all loaded find functions.
+     * \return a dictionary of registry entry names to find functions
+     */
+    static FindFunctions listFindFunctions(void);
+
+    /*!
+     * List all loaded make functions.
+     * \return a dictionary of registry entry names to make functions
+     */
+    static MakeFunctions listMakeFunctions(void);
+
+private:
+    std::string _name;
+};
+
+}
diff --git a/include/SoapySDR/Time.h b/include/SoapySDR/Time.h
new file mode 100644
index 0000000..7da0f3e
--- /dev/null
+++ b/include/SoapySDR/Time.h
@@ -0,0 +1,36 @@
+///
+/// \file SoapySDR/Time.h
+///
+/// Utility functions to convert time and ticks.
+///
+/// \copyright
+/// Copyright (c) 2015-2015 Josh Blum
+/// SPDX-License-Identifier: BSL-1.0
+///
+
+#pragma once
+#include <SoapySDR/Config.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*!
+ * Convert a tick count into a time in nanoseconds using the tick rate.
+ * \param ticks a integer tick count
+ * \param rate the ticks per second
+ * \return the time in nanoseconds
+ */
+SOAPY_SDR_API long long SoapySDR_ticksToTimeNs(const long long ticks, const double rate);
+
+/*!
+ * Convert a time in nanoseconds into a tick count using the tick rate.
+ * \param timeNs time in nanoseconds
+ * \param rate the ticks per second
+ * \return the integer tick count
+ */
+SOAPY_SDR_API long long SoapySDR_timeNsToTicks(const long long timeNs, const double rate);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/include/SoapySDR/Time.hpp b/include/SoapySDR/Time.hpp
new file mode 100644
index 0000000..f9393bc
--- /dev/null
+++ b/include/SoapySDR/Time.hpp
@@ -0,0 +1,44 @@
+///
+/// \file SoapySDR/Time.hpp
+///
+/// Utility functions to convert time and ticks.
+///
+/// \copyright
+/// Copyright (c) 2015-2015 Josh Blum
+/// SPDX-License-Identifier: BSL-1.0
+///
+
+#pragma once
+#include <SoapySDR/Config.hpp>
+#include <SoapySDR/Time.h>
+
+namespace SoapySDR
+{
+
+/*!
+ * Convert a tick count into a time in nanoseconds using the tick rate.
+ * \param ticks a integer tick count
+ * \param rate the ticks per second
+ * \return the time in nanoseconds
+ */
+static inline long long ticksToTimeNs(const long long ticks, const double rate);
+
+/*!
+ * Convert a time in nanoseconds into a tick count using the tick rate.
+ * \param timeNs time in nanoseconds
+ * \param rate the ticks per second
+ * \return the integer tick count
+ */
+static inline long long timeNsToTicks(const long long timeNs, const double rate);
+
+}
+
+static inline long long SoapySDR::ticksToTimeNs(const long long ticks, const double rate)
+{
+    return SoapySDR_ticksToTimeNs(ticks, rate);
+}
+
+static inline long long SoapySDR::timeNsToTicks(const long long timeNs, const double rate)
+{
+    return SoapySDR_timeNsToTicks(timeNs, rate);
+}
diff --git a/include/SoapySDR/Types.h b/include/SoapySDR/Types.h
new file mode 100644
index 0000000..33caf52
--- /dev/null
+++ b/include/SoapySDR/Types.h
@@ -0,0 +1,135 @@
+///
+/// \file SoapySDR/Types.h
+///
+/// Misc data type definitions used in the API.
+///
+/// \copyright
+/// Copyright (c) 2014-2015 Josh Blum
+/// SPDX-License-Identifier: BSL-1.0
+///
+
+#pragma once
+#include <SoapySDR/Config.h>
+#include <stddef.h> //size_t
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+//! Definition for a min/max numeric range
+typedef struct
+{
+    double minimum;
+    double maximum;
+} SoapySDRRange;
+
+//! Definition for a key/value string map
+typedef struct
+{
+    size_t size;
+    char **keys;
+    char **vals;
+} SoapySDRKwargs;
+
+//! Possible data types for argument info
+typedef enum
+{
+    SOAPY_SDR_ARG_INFO_BOOL,
+    SOAPY_SDR_ARG_INFO_INT,
+    SOAPY_SDR_ARG_INFO_FLOAT,
+    SOAPY_SDR_ARG_INFO_STRING
+} SoapySDRArgInfoType;
+
+//! Definition for argument info
+typedef struct
+{
+    //! The key used to identify the argument (required)
+    char *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".
+     */
+    char *value;
+
+    //! The displayable name of the argument (optional, use key if empty)
+    char *name;
+
+    //! A brief description about the argument (optional)
+    char *description;
+
+    //! The units of the argument: dB, Hz, etc (optional)
+    char *units;
+
+    //! The data type of the argument (required)
+    SoapySDRArgInfoType 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.
+     */
+    SoapySDRRange range;
+
+    //! The size of the options set, or 0 when not used.
+    size_t numOptions;
+
+    /*!
+     * A discrete list of possible values (optional)
+     * When specified, the argument should be restricted to this options set.
+     */
+    char **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.
+     */
+    char **optionNames;
+
+} SoapySDRArgInfo;
+
+/*!
+ * Clear the contents of a list of string
+ * Convenience call to deal with results that return a string list.
+ */
+SOAPY_SDR_API void SoapySDRStrings_clear(char ***elems, const size_t length);
+
+/*!
+ * Set a key/value pair in a kwargs structure.
+ */
+SOAPY_SDR_API void SoapySDRKwargs_set(SoapySDRKwargs *args, const char *key, const char *val);
+
+/*!
+ * Get a value given a key in a kwargs structure.
+ * \return the string or NULL if not found
+ */
+SOAPY_SDR_API const char *SoapySDRKwargs_get(SoapySDRKwargs *args, const char *key);
+
+/*!
+ * Clear the contents of a kwargs structure.
+ * This frees all the underlying memory and clears the members.
+ */
+SOAPY_SDR_API void SoapySDRKwargs_clear(SoapySDRKwargs *args);
+
+/*!
+ * Clear a list of kwargs structures.
+ * This frees all the underlying memory and clears the members.
+ */
+SOAPY_SDR_API void SoapySDRKwargsList_clear(SoapySDRKwargs *args, const size_t length);
+
+/*!
+ * Clear the contents of a argument info structure.
+ * This frees all the underlying memory and clears the members.
+ */
+SOAPY_SDR_API void SoapySDRArgInfo_clear(SoapySDRArgInfo *info);
+
+/*!
+ * Clear a list of argument info structures.
+ * This frees all the underlying memory and clears the members.
+ */
+SOAPY_SDR_API void SoapySDRArgInfoList_clear(SoapySDRArgInfo *info, const size_t length);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/include/SoapySDR/Types.hpp b/include/SoapySDR/Types.hpp
new file mode 100644
index 0000000..74ccbf3
--- /dev/null
+++ b/include/SoapySDR/Types.hpp
@@ -0,0 +1,124 @@
+///
+/// \file SoapySDR/Types.hpp
+///
+/// Misc data type definitions used in the API.
+///
+/// \copyright
+/// Copyright (c) 2014-2015 Josh Blum
+/// SPDX-License-Identifier: BSL-1.0
+///
+
+#pragma once
+#include <SoapySDR/Config.hpp>
+#include <SoapySDR/Types.h>
+#include <vector>
+#include <string>
+#include <map>
+
+namespace SoapySDR
+{
+
+//! Typedef for a dictionary of key-value string arguments
+typedef std::map<std::string, std::string> Kwargs;
+
+//! Typedef for a list of key-word dictionaries
+typedef std::vector<Kwargs> KwargsList;
+
+/*!
+ * A simple min/max numeric range pair
+ */
+class SOAPY_SDR_API Range
+{
+public:
+
+    //! Create an empty range (0.0, 0.0)
+    Range(void);
+
+    //! Create a min/max range
+    Range(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;
+};
+
+/*!
+ * Typedef for a list of min/max range pairs.
+ * Overall minimum: rl.front().minimum();
+ * Overall maximum: rl.back().maximum();
+ */
+typedef std::vector<Range> RangeList;
+
+/*!
+ * Argument info describes a key/value argument.
+ */
+class SOAPY_SDR_API ArgInfo
+{
+public:
+
+    //! Default constructor
+    ArgInfo(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} 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.
+     */
+    Range 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 for a list of Argument infos.
+ */
+typedef std::vector<ArgInfo> ArgInfoList;
+
+}
+
+inline double SoapySDR::Range::minimum(void) const
+{
+    return _min;
+}
+
+inline double SoapySDR::Range::maximum(void) const
+{
+    return _max;
+}
diff --git a/include/SoapySDR/Version.h b/include/SoapySDR/Version.h
new file mode 100644
index 0000000..e4b1446
--- /dev/null
+++ b/include/SoapySDR/Version.h
@@ -0,0 +1,122 @@
+///
+/// \file SoapySDR/Version.h
+///
+/// Utility functions to query version information.
+///
+/// \copyright
+/// Copyright (c) 2014-2016 Josh Blum
+/// Copyright (c) 2016-2016 Bastille Networks
+/// SPDX-License-Identifier: BSL-1.0
+///
+
+#pragma once
+#include <SoapySDR/Config.h>
+
+/*!
+ * ABI Version Information - incremented when the ABI is changed.
+ */
+#define SOAPY_SDR_ABI_VERSION "0.5-2"
+
+/*!
+ * Compatibility define for GPIO access API with masks
+ */
+#define SOAPY_SDR_API_HAS_MASKED_GPIO
+
+/*!
+ * Compatibility define for channel sensors access API
+ */
+#define SOAPY_SDR_API_HAS_CHANNEL_SENSORS
+
+/*!
+ * Compatibility define for error to string function
+ */
+#define SOAPY_SDR_API_HAS_ERR_TO_STR
+
+/*!
+ * Compatibility define for corrections support checks
+ */
+#define SOAPY_SDR_API_HAS_CORRECTIONS_QUERY
+
+/*!
+ * Compatibility define for clock rates query API
+ */
+#define SOAPY_SDR_API_HAS_CLOCK_RATES_QUERY
+
+/*!
+ * Compatibility define for AGC support check
+ */
+#define SOAPY_SDR_API_HAS_AGC_MODE_QUERY
+
+/*!
+ * Compatibility define for querying stream argument info
+ */
+#define SOAPY_SDR_API_HAS_STREAM_ARG_INFO
+
+/*!
+ * Compatibility define for querying setting argument info
+ */
+#define SOAPY_SDR_API_HAS_SETTING_ARG_INFO
+
+/*!
+ * Compatibility define for querying sensor info API
+ */
+#define SOAPY_SDR_API_HAS_QUERY_SENSOR_INFO
+
+/*!
+ * Compatibility define for querying tune args info
+ */
+#define SOAPY_SDR_API_HAS_QUERY_TUNE_ARG_INFO
+
+/*!
+ * Compatibility define for querying native stream format
+ */
+#define SOAPY_SDR_API_HAS_NATIVE_STREAM_FORMAT
+
+/*!
+ * Compatibility define for setting the log level
+ */
+#define SOAPY_SDR_API_HAS_SET_LOG_LEVEL
+
+/*!
+ * Compatibility define for format header and defines
+ */
+#define SOAPY_SDR_API_HAS_FORMAT_DEFINES
+
+/*!
+ * Compatibility define for arbitrary channel settings
+ */
+#define SOAPY_SDR_API_HAS_CHANNEL_SETTINGS
+
+/*!
+ * Compatibility define for get bandwidth range API
+ */
+#define SOAPY_SDR_API_HAS_GET_BANDWIDTH_RANGE
+
+/*!
+ * Compatibility define for get channel info API
+ */
+#define SOAPY_SDR_API_HAS_GET_CHANNEL_INFO
+
+/*!
+ * Compatibility define for named register interface API
+ */
+#define SOAPY_SDR_API_HAS_NAMED_REGISTER_API
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*!
+ * Query the API version string.
+ */
+SOAPY_SDR_API const char *SoapySDR_getAPIVersion(void);
+
+/*!
+ * Get the ABI version string.
+ * This is the SOAPY_SDR_ABI_VERSION that the library was built against.
+ */
+SOAPY_SDR_API const char *SoapySDR_getABIVersion(void);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/include/SoapySDR/Version.hpp b/include/SoapySDR/Version.hpp
new file mode 100644
index 0000000..2dcb2b6
--- /dev/null
+++ b/include/SoapySDR/Version.hpp
@@ -0,0 +1,28 @@
+///
+/// \file SoapySDR/Version.hpp
+///
+/// Utility functions to query version information.
+///
+/// \copyright
+/// Copyright (c) 2014-2014 Josh Blum
+/// SPDX-License-Identifier: BSL-1.0
+///
+
+#pragma once
+#include <SoapySDR/Config.hpp>
+#include <SoapySDR/Version.h>
+#include <string>
+
+namespace SoapySDR
+{
+
+//! Query the API version string
+SOAPY_SDR_API std::string getAPIVersion(void);
+
+/*!
+ * Get the ABI version string.
+ * This is the SOAPY_SDR_ABI_VERSION that the library was built against.
+ */
+SOAPY_SDR_API std::string getABIVersion(void);
+
+}
diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt
new file mode 100644
index 0000000..581fac6
--- /dev/null
+++ b/lib/CMakeLists.txt
@@ -0,0 +1,110 @@
+########################################################################
+## Feature registration
+########################################################################
+include(FeatureSummary)
+include(CMakeDependentOption)
+cmake_dependent_option(ENABLE_LIBRARY "Enable runtime library" ON "TRUE" OFF)
+add_feature_info(Library ENABLE_LIBRARY "runtime library v${SOAPY_SDR_VERSION}")
+if (NOT ENABLE_LIBRARY)
+    return()
+endif()
+
+########################################################################
+# Soapy SDR sources and dependencies
+########################################################################
+list(APPEND SOAPY_SDR_SOURCES
+    Device.cpp
+    Factory.cpp
+    Registry.cpp
+    Types.cpp
+    NullDevice.cpp
+    Logger.cpp
+    Errors.cpp
+    Formats.cpp
+)
+
+#dl libs used by dlopen in unix
+list(APPEND SOAPY_SDR_LIBRARIES
+    ${CMAKE_DL_LIBS}
+)
+
+#support threaded client code
+#notice that -pthread is not the same as -lpthread
+if(CMAKE_COMPILER_IS_GNUCXX)
+    list(APPEND SOAPY_SDR_LIBRARIES -pthread)
+endif()
+
+include_directories(${PROJECT_SOURCE_DIR}/include)
+
+list(APPEND SOAPY_SDR_SOURCES ${CMAKE_CURRENT_BINARY_DIR}/Modules.cpp)
+configure_file(
+    ${CMAKE_CURRENT_SOURCE_DIR}/Modules.in.cpp
+    ${CMAKE_CURRENT_BINARY_DIR}/Modules.cpp
+ at ONLY)
+
+list(APPEND SOAPY_SDR_SOURCES ${CMAKE_CURRENT_BINARY_DIR}/Version.cpp)
+configure_file(
+    ${CMAKE_CURRENT_SOURCE_DIR}/Version.in.cpp
+    ${CMAKE_CURRENT_BINARY_DIR}/Version.cpp
+ at ONLY)
+
+#C API support sources
+list(APPEND SOAPY_SDR_SOURCES
+    TypesC.cpp
+    ModulesC.cpp
+    VersionC.cpp
+    DeviceC.cpp
+    FactoryC.cpp
+    LoggerC.cpp
+    TimeC.cpp
+    ErrorsC.cpp
+    FormatsC.cpp
+)
+
+#disable MSVC warnings
+if (MSVC)
+    add_definitions(-D_CRT_NONSTDC_NO_DEPRECATE)
+endif ()
+
+########################################################################
+# Extract SOVERSION
+########################################################################
+file(READ "${PROJECT_SOURCE_DIR}/include/SoapySDR/Version.h" version_h)
+string(REGEX MATCH "\\#define SOAPY_SDR_ABI_VERSION \"([0-9]+\\.[0-9]+-[0-9]+)\"" SOAPY_SDR_ABI_VERSION_MATCHES "${version_h}")
+if(NOT SOAPY_SDR_ABI_VERSION_MATCHES)
+    message(FATAL_ERROR "Failed to extract version number from Version.h")
+endif(NOT SOAPY_SDR_ABI_VERSION_MATCHES)
+set(SOAPY_SDR_SOVER "${CMAKE_MATCH_1}" CACHE INTERNAL "")
+
+########################################################################
+# Build the library
+########################################################################
+add_library(SoapySDR SHARED ${SOAPY_SDR_SOURCES})
+if (SOAPY_SDR_LIBRARIES)
+    target_link_libraries(SoapySDR ${SOAPY_SDR_LIBRARIES})
+endif()
+set_target_properties(SoapySDR PROPERTIES SOVERSION ${SOAPY_SDR_SOVER})
+set_target_properties(SoapySDR PROPERTIES VERSION ${SOAPY_SDR_LIBVER})
+set_target_properties(SoapySDR PROPERTIES DEFINE_SYMBOL "SOAPY_SDR_DLL_EXPORTS")
+
+########################################################################
+# Install the library
+########################################################################
+install(TARGETS SoapySDR
+    LIBRARY DESTINATION lib${LIB_SUFFIX} # .so file
+    ARCHIVE DESTINATION lib${LIB_SUFFIX} # .lib file
+    RUNTIME DESTINATION bin              # .dll file
+)
+
+########################################################################
+# Build pkg config file
+########################################################################
+configure_file(
+    ${CMAKE_CURRENT_SOURCE_DIR}/SoapySDR.in.pc
+    ${CMAKE_CURRENT_BINARY_DIR}/SoapySDR.pc
+ at ONLY)
+
+install(
+    FILES ${CMAKE_CURRENT_BINARY_DIR}/SoapySDR.pc
+    DESTINATION lib${LIB_SUFFIX}/pkgconfig
+)
diff --git a/lib/Device.cpp b/lib/Device.cpp
new file mode 100644
index 0000000..f2490da
--- /dev/null
+++ b/lib/Device.cpp
@@ -0,0 +1,760 @@
+// Copyright (c) 2014-2016 Josh Blum
+// Copyright (c) 2016-2016 Bastille Networks
+// SPDX-License-Identifier: BSL-1.0
+
+#include <SoapySDR/Device.hpp>
+#include <SoapySDR/Formats.hpp>
+#include <cstdlib>
+#include <algorithm> //min/max
+
+SoapySDR::Device::~Device(void)
+{
+    return;
+}
+
+/*******************************************************************
+ * Identification API
+ ******************************************************************/
+std::string SoapySDR::Device::getDriverKey(void) const
+{
+    return "";
+}
+
+std::string SoapySDR::Device::getHardwareKey(void) const
+{
+    return "";
+}
+
+SoapySDR::Kwargs SoapySDR::Device::getHardwareInfo(void) const
+{
+    return SoapySDR::Kwargs();
+}
+
+/*******************************************************************
+ * Channels API
+ ******************************************************************/
+void SoapySDR::Device::setFrontendMapping(const int, const std::string &)
+{
+    return;
+}
+
+std::string SoapySDR::Device::getFrontendMapping(const int) const
+{
+    return "";
+}
+
+size_t SoapySDR::Device::getNumChannels(const int) const
+{
+    return 0;
+}
+
+SoapySDR::Kwargs SoapySDR::Device::getChannelInfo(const int, const size_t) const
+{
+    return SoapySDR::Kwargs();
+}
+
+bool SoapySDR::Device::getFullDuplex(const int, const size_t) const
+{
+    return true;
+}
+
+/*******************************************************************
+ * Stream API
+ ******************************************************************/
+std::vector<std::string> SoapySDR::Device::getStreamFormats(const int, const size_t) const
+{
+    return std::vector<std::string>();
+}
+
+std::string SoapySDR::Device::getNativeStreamFormat(const int, const size_t, double &fullScale) const
+{
+    fullScale = double(1 << 15);
+    return SOAPY_SDR_CS16;
+}
+
+SoapySDR::ArgInfoList SoapySDR::Device::getStreamArgsInfo(const int, const size_t) const
+{
+    return SoapySDR::ArgInfoList();
+}
+
+SoapySDR::Stream *SoapySDR::Device::setupStream(const int, const std::string &, const std::vector<size_t> &, const Kwargs &)
+{
+    return nullptr;
+}
+
+void SoapySDR::Device::closeStream(Stream *)
+{
+    return;
+}
+
+size_t SoapySDR::Device::getStreamMTU(Stream *) const
+{
+    //provide a non-zero default when the implementation does not overload the MTU
+    return 1024;
+}
+
+int SoapySDR::Device::activateStream(Stream *, const int flags, const long long, const size_t)
+{
+    return (flags == 0)? 0 : SOAPY_SDR_NOT_SUPPORTED;
+}
+
+int SoapySDR::Device::deactivateStream(Stream *, const int flags, const long long)
+{
+    return (flags == 0)? 0 : SOAPY_SDR_NOT_SUPPORTED;
+}
+
+int SoapySDR::Device::readStream(Stream *, void * const *, const size_t, int &, long long &, const long)
+{
+    return SOAPY_SDR_NOT_SUPPORTED;
+}
+
+int SoapySDR::Device::writeStream(Stream *, const void * const *, const size_t, int &, const long long, const long)
+{
+    return SOAPY_SDR_NOT_SUPPORTED;
+}
+
+int SoapySDR::Device::readStreamStatus(Stream *, size_t &, int &, long long &, const long)
+{
+    return SOAPY_SDR_NOT_SUPPORTED;
+}
+
+/*******************************************************************
+ * Direct buffer access API
+ ******************************************************************/
+size_t SoapySDR::Device::getNumDirectAccessBuffers(Stream *)
+{
+    return 0;
+}
+
+int SoapySDR::Device::getDirectAccessBufferAddrs(Stream *, const size_t, void **)
+{
+    return SOAPY_SDR_NOT_SUPPORTED;
+}
+
+int SoapySDR::Device::acquireReadBuffer(Stream *, size_t &, const void **, int &, long long &, const long)
+{
+    return SOAPY_SDR_NOT_SUPPORTED;
+}
+
+void SoapySDR::Device::releaseReadBuffer(Stream *, const size_t)
+{
+    return;
+}
+
+int SoapySDR::Device::acquireWriteBuffer(Stream *, size_t &, void **, const long)
+{
+    return SOAPY_SDR_NOT_SUPPORTED;
+}
+
+void SoapySDR::Device::releaseWriteBuffer(Stream *, const size_t, const size_t, int &, const long long)
+{
+    return;
+}
+
+/*******************************************************************
+ * Antenna API
+ ******************************************************************/
+std::vector<std::string> SoapySDR::Device::listAntennas(const int, const size_t) const
+{
+    return std::vector<std::string>();
+}
+
+void SoapySDR::Device::setAntenna(const int, const size_t, const std::string &)
+{
+    return;
+}
+
+std::string SoapySDR::Device::getAntenna(const int, const size_t) const
+{
+    return "";
+}
+
+/*******************************************************************
+ * Frontend corrections API
+ ******************************************************************/
+
+bool SoapySDR::Device::hasDCOffsetMode(const int, const size_t) const
+{
+    return false;
+}
+
+void SoapySDR::Device::setDCOffsetMode(const int, const size_t, const bool)
+{
+    return;
+}
+
+bool SoapySDR::Device::getDCOffsetMode(const int, const size_t) const
+{
+    return false;
+}
+
+bool SoapySDR::Device::hasDCOffset(const int, const size_t) const
+{
+    return false;
+}
+
+void SoapySDR::Device::setDCOffset(const int, const size_t, const std::complex<double> &)
+{
+    return;
+}
+
+std::complex<double> SoapySDR::Device::getDCOffset(const int, const size_t) const
+{
+    return std::complex<double>();
+}
+
+bool SoapySDR::Device::hasIQBalance(const int, const size_t) const
+{
+    return false;
+}
+
+void SoapySDR::Device::setIQBalance(const int, const size_t, const std::complex<double> &)
+{
+    return;
+}
+
+std::complex<double> SoapySDR::Device::getIQBalance(const int, const size_t) const
+{
+    return std::complex<double>();
+}
+
+/*******************************************************************
+ * Gain API
+ ******************************************************************/
+std::vector<std::string> SoapySDR::Device::listGains(const int, const size_t) const
+{
+    return std::vector<std::string>();
+}
+
+bool SoapySDR::Device::hasGainMode(const int, const size_t) const
+{
+    return false;
+}
+
+void SoapySDR::Device::setGainMode(const int, const size_t, const bool)
+{
+    return;
+}
+
+bool SoapySDR::Device::getGainMode(const int, const size_t) const
+{
+    return false;
+}
+
+void SoapySDR::Device::setGain(const int dir, const size_t channel, double gain)
+{
+    //algorithm to distribute overall gain (TX gets BB first, RX gets RF first)
+    const auto names = this->listGains(dir, channel);
+    if (dir == SOAPY_SDR_TX)
+    {
+        for (int i = names.size()-1; i >= 0; i--)
+        {
+            const auto r = this->getGainRange(dir, channel, names[i]);
+            const double g = std::min(gain, r.maximum()-r.minimum());
+            this->setGain(dir, channel, names[i], g+r.minimum());
+            gain -= this->getGain(dir, channel, names[i])-r.minimum();
+        }
+    }
+    if (dir == SOAPY_SDR_RX)
+    {
+        for (size_t i = 0; i < names.size(); i++)
+        {
+            const auto r = this->getGainRange(dir, channel, names[i]);
+            const double g = std::min(gain, r.maximum()-r.minimum());
+            this->setGain(dir, channel, names[i], g+r.minimum());
+            gain -= this->getGain(dir, channel, names[i])-r.minimum();
+        }
+    }
+}
+
+void SoapySDR::Device::setGain(const int, const size_t, const std::string &, const double)
+{
+    return;
+}
+
+double SoapySDR::Device::getGain(const int dir, const size_t channel) const
+{
+    //algorithm to return an overall gain (summing each normalized gain)
+    double gain = 0.0;
+    for (const auto &name : this->listGains(dir, channel))
+    {
+        const auto r = this->getGainRange(dir, channel, name);
+        gain += this->getGain(dir, channel, name)-r.minimum();
+    }
+    return gain;
+}
+
+double SoapySDR::Device::getGain(const int, const size_t, const std::string &) const
+{
+    return 0.0;
+}
+
+SoapySDR::Range SoapySDR::Device::getGainRange(const int, const size_t, const std::string &) const
+{
+    return SoapySDR::Range(0.0, 0.0);
+}
+
+SoapySDR::Range SoapySDR::Device::getGainRange(const int dir, const size_t channel) const
+{
+    //algorithm to return an overall gain range (use 0 to max possible on each element)
+    double gain = 0.0;
+    for (const auto &name : this->listGains(dir, channel))
+    {
+        const auto r = this->getGainRange(dir, channel, name);
+        gain += r.maximum()-r.minimum();
+    }
+    return SoapySDR::Range(0.0, gain);
+}
+
+/*******************************************************************
+ * Frequency API
+ ******************************************************************/
+void SoapySDR::Device::setFrequency(const int dir, const size_t chan, double freq, const Kwargs &args)
+{
+    const auto comps = this->listFrequencies(dir, chan);
+    if (comps.empty()) return;
+
+    //optional offset, use on RF element when specified
+    const double offset = (args.count("OFFSET") != 0)?std::atof(args.at("OFFSET").c_str()):0.0;
+
+    //distribute freq into RF then baseband components
+    for (size_t comp_i = 0; comp_i < comps.size(); comp_i++)
+    {
+        const std::string &name = comps[comp_i];
+
+        //add offset for RF element (first element)
+        if (comp_i == 0) freq += offset;
+
+        if (args.count(name) != 0 and args.at(name) == "IGNORE")
+        {
+            //do nothing, dont change component
+        }
+        else if (args.count(name) != 0 and args.at(name) != "DEFAULT")
+        {
+            //specific frequency for component specified
+            const double f(std::atof(args.at(name).c_str()));
+            this->setFrequency(dir, chan, name, f, args);
+        }
+        else
+        {
+            //otherwise use the remainder frequency
+            this->setFrequency(dir, chan, name, freq, args);
+        }
+
+        //adjust overall freq for the remainder
+        freq -= this->getFrequency(dir, chan, name);
+
+        //then remove to compensate
+        if (comp_i == 0) freq -= offset;
+    }
+}
+
+void SoapySDR::Device::setFrequency(const int, const size_t, const std::string &, const double, const Kwargs &)
+{
+    return;
+}
+
+double SoapySDR::Device::getFrequency(const int dir, const size_t chan) const
+{
+    double freq = 0.0;
+
+    //overall frequency is the sum of components
+    for (const auto &comp : this->listFrequencies(dir, chan))
+    {
+        const std::string &name = comp;
+        freq += this->getFrequency(dir, chan, name);
+    }
+
+    return freq;
+}
+
+double SoapySDR::Device::getFrequency(const int, const size_t, const std::string &) const
+{
+    return 0.0;
+}
+
+std::vector<std::string> SoapySDR::Device::listFrequencies(const int, const size_t) const
+{
+    return std::vector<std::string>();
+}
+
+SoapySDR::RangeList SoapySDR::Device::getFrequencyRange(const int dir, const size_t chan) const
+{
+    //get a list of tunable components
+    const auto comps = this->listFrequencies(dir, chan);
+    if (comps.empty()) return SoapySDR::RangeList();
+
+    //get the range list for the RF component
+    auto ranges = this->getFrequencyRange(dir, chan, comps.front());
+
+    //use bandwidth setting to clip the range
+    const double bw = this->getBandwidth(dir, chan);
+
+    //add to the range list given subsequent components
+    for (size_t comp_i = 1; comp_i < comps.size(); comp_i++)
+    {
+        const auto subRange = this->getFrequencyRange(dir, chan, comps[comp_i]);
+        if (subRange.empty()) continue;
+
+        double subRangeLow = subRange.front().minimum();
+        if (bw > 0.0) subRangeLow = std::max(-bw/2, subRangeLow);
+
+        double subRangeHigh = subRange.back().maximum();
+        if (bw > 0.0) subRangeHigh = std::min(bw/2, subRangeHigh);
+
+        for (size_t range_i = 0; range_i < ranges.size(); range_i++)
+        {
+            SoapySDR::Range &range = ranges[range_i];
+            range = SoapySDR::Range(
+                range.minimum() + subRangeLow,
+                range.maximum() + subRangeHigh);
+        }
+    }
+
+    return ranges;
+}
+
+SoapySDR::RangeList SoapySDR::Device::getFrequencyRange(const int, const size_t, const std::string &) const
+{
+    return SoapySDR::RangeList();
+}
+
+SoapySDR::ArgInfoList SoapySDR::Device::getFrequencyArgsInfo(const int dir, const size_t chan) const
+{
+    SoapySDR::ArgInfoList args;
+
+    const auto comps = this->listFrequencies(dir, chan);
+
+    if (comps.size() < 2) return args; //no tuning options with single components
+
+    //support offset tuning
+    {
+        SoapySDR::ArgInfo info;
+        info.key = "OFFSET";
+        info.name = "LO Offset";
+        info.value = "0.0";
+        info.units = "Hz";
+        info.type = SoapySDR::ArgInfo::FLOAT;
+        info.description = "Tune the LO with an offset and compensate with the baseband CORDIC.";
+        SoapySDR::RangeList ranges = this->getFrequencyRange(dir, chan, comps.at(1));
+        if (not ranges.empty()) info.range = ranges.front();
+        args.push_back(info);
+    }
+
+    //every tunable component becomes an option
+    for (size_t comp_i = 1; comp_i < comps.size(); comp_i++)
+    {
+        SoapySDR::ArgInfo info;
+        info.key = comps[comp_i];
+        info.value = "DEFAULT";
+        info.units = "Hz";
+        info.type = SoapySDR::ArgInfo::FLOAT;
+        info.description = "Specify a specific value for this component or IGNORE to skip tuning it.";
+        info.options.push_back("DEFAULT");
+        info.optionNames.push_back("Default");
+        info.options.push_back("IGNORE");
+        info.optionNames.push_back("Ignore");
+        SoapySDR::RangeList ranges = this->getFrequencyRange(dir, chan, comps.at(comp_i));
+        if (not ranges.empty()) info.range = ranges.front();
+        args.push_back(info);
+    }
+
+    return args;
+}
+
+/*******************************************************************
+ * Sample Rate API
+ ******************************************************************/
+void SoapySDR::Device::setSampleRate(const int, const size_t, const double)
+{
+    return;
+}
+
+double SoapySDR::Device::getSampleRate(const int, const size_t) const
+{
+    return 0.0;
+}
+
+std::vector<double> SoapySDR::Device::listSampleRates(const int, const size_t) const
+{
+    return std::vector<double>();
+}
+
+/*******************************************************************
+ * Bandwidth API
+ ******************************************************************/
+void SoapySDR::Device::setBandwidth(const int, const size_t, const double)
+{
+    return;
+}
+
+double SoapySDR::Device::getBandwidth(const int, const size_t) const
+{
+    return 0.0;
+}
+
+std::vector<double> SoapySDR::Device::listBandwidths(const int, const size_t) const
+{
+    return std::vector<double>();
+}
+
+SoapySDR::RangeList SoapySDR::Device::getBandwidthRange(const int direction, const size_t channel) const
+{
+    SoapySDR::RangeList ranges;
+    //call into the older deprecated listBandwidths() call
+    for (auto &bw : this->listBandwidths(direction, channel))
+    {
+        ranges.push_back(SoapySDR::Range(bw, bw));
+    }
+    return ranges;
+}
+
+/*******************************************************************
+ * Clocking API
+ ******************************************************************/
+void SoapySDR::Device::setMasterClockRate(const double)
+{
+    return;
+}
+
+double SoapySDR::Device::getMasterClockRate(void) const
+{
+    return 0.0;
+}
+
+SoapySDR::RangeList SoapySDR::Device::getMasterClockRates(void) const
+{
+    return SoapySDR::RangeList();
+}
+
+std::vector<std::string> SoapySDR::Device::listClockSources(void) const
+{
+    return std::vector<std::string>();
+}
+
+void SoapySDR::Device::setClockSource(const std::string &)
+{
+    return;
+}
+
+std::string SoapySDR::Device::getClockSource(void) const
+{
+    return "";
+}
+
+/*******************************************************************
+ * Time API
+ ******************************************************************/
+
+std::vector<std::string> SoapySDR::Device::listTimeSources(void) const
+{
+    return std::vector<std::string>();
+}
+
+void SoapySDR::Device::setTimeSource(const std::string &)
+{
+    return;
+}
+
+std::string SoapySDR::Device::getTimeSource(void) const
+{
+    return "";
+}
+
+bool SoapySDR::Device::hasHardwareTime(const std::string &) const
+{
+    return false;
+}
+
+long long SoapySDR::Device::getHardwareTime(const std::string &) const
+{
+    return 0;
+}
+
+void SoapySDR::Device::setHardwareTime(const long long timeNs, const std::string &what)
+{
+    if (what == "CMD") this->setCommandTime(timeNs, what);
+}
+
+void SoapySDR::Device::setCommandTime(const long long, const std::string &)
+{
+    return;
+}
+
+/*******************************************************************
+ * Sensor API
+ ******************************************************************/
+std::vector<std::string> SoapySDR::Device::listSensors(void) const
+{
+    return std::vector<std::string>();
+}
+
+SoapySDR::ArgInfo SoapySDR::Device::getSensorInfo(const std::string &) const
+{
+    return SoapySDR::ArgInfo();
+}
+
+std::string SoapySDR::Device::readSensor(const std::string &) const
+{
+    return "";
+}
+
+std::vector<std::string> SoapySDR::Device::listSensors(const int, const size_t) const
+{
+    return std::vector<std::string>();
+}
+
+SoapySDR::ArgInfo SoapySDR::Device::getSensorInfo(const int, const size_t, const std::string &) const
+{
+    return SoapySDR::ArgInfo();
+}
+
+std::string SoapySDR::Device::readSensor(const int, const size_t, const std::string &) const
+{
+    return "";
+}
+
+/*******************************************************************
+ * Register API
+ ******************************************************************/
+std::vector<std::string> SoapySDR::Device::listRegisterInterfaces(void) const
+{
+    return std::vector<std::string>();
+}
+
+void SoapySDR::Device::writeRegister(const std::string &, const unsigned addr, const unsigned value)
+{
+    this->writeRegister(addr, value);
+}
+
+unsigned SoapySDR::Device::readRegister(const std::string &, const unsigned addr) const
+{
+    return this->readRegister(addr);
+}
+
+void SoapySDR::Device::writeRegister(const unsigned, const unsigned)
+{
+    return;
+}
+
+unsigned SoapySDR::Device::readRegister(const unsigned) const
+{
+    return 0;
+}
+
+/*******************************************************************
+ * Settings API
+ ******************************************************************/
+SoapySDR::ArgInfoList SoapySDR::Device::getSettingInfo(void) const
+{
+    return SoapySDR::ArgInfoList();
+}
+
+void SoapySDR::Device::writeSetting(const std::string &, const std::string &)
+{
+    return;
+}
+
+std::string SoapySDR::Device::readSetting(const std::string &) const
+{
+    return "";
+}
+
+SoapySDR::ArgInfoList SoapySDR::Device::getSettingInfo(const int, const size_t) const
+{
+    return SoapySDR::ArgInfoList();
+}
+
+void SoapySDR::Device::writeSetting(const int, const size_t, const std::string &, const std::string &)
+{
+    return;
+}
+
+std::string SoapySDR::Device::readSetting(const int, const size_t, const std::string &) const
+{
+    return "";
+}
+
+/*******************************************************************
+ * GPIO API
+ ******************************************************************/
+std::vector<std::string> SoapySDR::Device::listGPIOBanks(void) const
+{
+    return std::vector<std::string>();
+}
+
+void SoapySDR::Device::writeGPIO(const std::string &, const unsigned)
+{
+    return;
+}
+
+void SoapySDR::Device::writeGPIO(const std::string &bank, const unsigned value, const unsigned mask)
+{
+    //read, modify, write
+    unsigned readback = this->readGPIO(bank);
+    unsigned newValue = value | (readback & (~mask));
+    this->writeGPIO(bank, newValue);
+}
+
+unsigned SoapySDR::Device::readGPIO(const std::string &) const
+{
+    return 0;
+}
+
+void SoapySDR::Device::writeGPIODir(const std::string &, const unsigned)
+{
+    return;
+}
+
+void SoapySDR::Device::writeGPIODir(const std::string &bank, const unsigned dir, const unsigned mask)
+{
+    //read, modify, write
+    unsigned readback = this->readGPIODir(bank);
+    unsigned newValue = dir | (readback & (~mask));
+    this->writeGPIODir(bank, newValue);
+}
+
+unsigned SoapySDR::Device::readGPIODir(const std::string &) const
+{
+    return 0;
+}
+
+
+/*******************************************************************
+ * I2C API
+ ******************************************************************/
+void SoapySDR::Device::writeI2C(const int, const std::string &)
+{
+    return;
+}
+
+std::string SoapySDR::Device::readI2C(const int, const size_t)
+{
+    return "";
+}
+
+/*******************************************************************
+ * SPI API
+ ******************************************************************/
+unsigned SoapySDR::Device::transactSPI(const int, const unsigned, const size_t)
+{
+    return 0;
+}
+
+/*******************************************************************
+ * UART API
+ ******************************************************************/
+std::vector<std::string> SoapySDR::Device::listUARTs(void) const
+{
+    return std::vector<std::string>();
+}
+
+void SoapySDR::Device::writeUART(const std::string &, const std::string &)
+{
+    return;
+}
+
+std::string SoapySDR::Device::readUART(const std::string &, const long) const
+{
+    return "";
+}
diff --git a/lib/DeviceC.cpp b/lib/DeviceC.cpp
new file mode 100644
index 0000000..3c6d420
--- /dev/null
+++ b/lib/DeviceC.cpp
@@ -0,0 +1,693 @@
+// Copyright (c) 2014-2016 Josh Blum
+// Copyright (c) 2016-2016 Bastille Networks
+// SPDX-License-Identifier: BSL-1.0
+
+#include "TypeHelpers.hpp"
+#include <SoapySDR/Device.h>
+#include <SoapySDR/Device.hpp>
+#include <algorithm>
+#include <cstdlib>
+#include <cstring>
+
+/*******************************************************************
+ * Helper macros for dealing with error messages
+ ******************************************************************/
+#define __SOAPY_SDR_C_TRY try {
+#define __SOAPY_SDR_C_CATCH } \
+    catch (const std::exception &ex) { return SoapySDRDevice_reportError(ex.what()); } \
+    catch (...) { return SoapySDRDevice_reportError("unknown"); } \
+    return 0;
+
+#ifdef _MSC_VER
+#define __thread __declspec(thread)
+#endif
+
+static __thread char lastErrorMsg[1024];
+
+static int SoapySDRDevice_reportError(const char *msg)
+{
+    strncpy(lastErrorMsg, msg, sizeof(lastErrorMsg));
+    lastErrorMsg[sizeof(lastErrorMsg)-1] = '\0';
+    return -1;
+}
+
+const char *SoapySDRDevice_lastError(void)
+{
+    return lastErrorMsg;
+}
+
+/*******************************************************************
+ * Simple subclass definition for device
+ ******************************************************************/
+struct SoapySDRDevice : SoapySDR::Device {};
+
+extern "C" {
+
+/*******************************************************************
+ * Identification API
+ ******************************************************************/
+char *SoapySDRDevice_getDriverKey(const SoapySDRDevice *device)
+{
+    return strdup(device->getDriverKey().c_str());
+}
+
+char *SoapySDRDevice_getHardwareKey(const SoapySDRDevice *device)
+{
+    return strdup(device->getHardwareKey().c_str());
+}
+
+SoapySDRKwargs SoapySDRDevice_getHardwareInfo(const SoapySDRDevice *device)
+{
+    return toKwargs(device->getHardwareInfo());
+}
+
+/*******************************************************************
+ * Channels API
+ ******************************************************************/
+int SoapySDRDevice_setFrontendMapping(SoapySDRDevice *device, const int direction, const char *mapping)
+{
+    __SOAPY_SDR_C_TRY
+    device->setFrontendMapping(direction, mapping);
+    __SOAPY_SDR_C_CATCH
+}
+
+char *SoapySDRDevice_getFrontendMapping(const SoapySDRDevice *device, const int direction)
+{
+    return strdup(device->getFrontendMapping(direction).c_str());
+}
+
+size_t SoapySDRDevice_getNumChannels(const SoapySDRDevice *device, const int direction)
+{
+    return device->getNumChannels(direction);
+}
+
+SoapySDRKwargs SoapySDRDevice_getChannelInfo(const SoapySDRDevice *device, const int direction, const size_t channel)
+{
+    return toKwargs(device->getChannelInfo(direction, channel));
+}
+
+bool SoapySDRDevice_getFullDuplex(const SoapySDRDevice *device, const int direction, const size_t channel)
+{
+    return device->getFullDuplex(direction, channel);
+}
+
+/*******************************************************************
+ * Stream API
+ ******************************************************************/
+char **SoapySDRDevice_getStreamFormats(const SoapySDRDevice *device, const int direction, const size_t channel, size_t *length)
+{
+    return toStrArray(device->getStreamFormats(direction, channel), length);
+}
+
+char *SoapySDRDevice_getNativeStreamFormat(const SoapySDRDevice *device, const int direction, const size_t channel, double *fullScale)
+{
+    return strdup(device->getNativeStreamFormat(direction, channel, *fullScale).c_str());
+}
+
+SoapySDRArgInfo *SoapySDRDevice_getStreamArgsInfo(const SoapySDRDevice *device, const int direction, const size_t channel, size_t *length)
+{
+    return toArgInfoList(device->getStreamArgsInfo(direction, channel), length);
+}
+
+int SoapySDRDevice_setupStream(SoapySDRDevice *device, SoapySDRStream **stream, const int direction, const char *format, const size_t *channels, const size_t numChans, const SoapySDRKwargs *args)
+{
+    __SOAPY_SDR_C_TRY
+    *stream = reinterpret_cast<SoapySDRStream *>(device->setupStream(direction, format, std::vector<size_t>(channels, channels+numChans), toKwargs(args)));
+    __SOAPY_SDR_C_CATCH
+}
+
+void SoapySDRDevice_closeStream(SoapySDRDevice *device, SoapySDRStream *stream)
+{
+    return device->closeStream(reinterpret_cast<SoapySDR::Stream *>(stream));
+}
+
+size_t SoapySDRDevice_getStreamMTU(const SoapySDRDevice *device, SoapySDRStream *stream)
+{
+    return device->getStreamMTU(reinterpret_cast<SoapySDR::Stream *>(stream));
+}
+
+int SoapySDRDevice_activateStream(SoapySDRDevice *device,
+    SoapySDRStream *stream,
+    const int flags,
+    const long long timeNs,
+    const size_t numElems)
+{
+    return device->activateStream(reinterpret_cast<SoapySDR::Stream *>(stream), flags, timeNs, numElems);
+}
+
+int SoapySDRDevice_deactivateStream(SoapySDRDevice *device,
+    SoapySDRStream *stream,
+    const int flags,
+    const long long timeNs)
+{
+    return device->deactivateStream(reinterpret_cast<SoapySDR::Stream *>(stream), flags, timeNs);
+}
+
+int SoapySDRDevice_readStream(SoapySDRDevice *device, SoapySDRStream *stream, void * const *buffs, const size_t numElems, int *flags, long long *timeNs, const long timeoutUs)
+{
+    return device->readStream(reinterpret_cast<SoapySDR::Stream *>(stream), buffs, numElems, *flags, *timeNs, timeoutUs);
+}
+
+int SoapySDRDevice_writeStream(SoapySDRDevice *device, SoapySDRStream *stream, const void * const *buffs, const size_t numElems, int *flags, const long long timeNs, const long timeoutUs)
+{
+    return device->writeStream(reinterpret_cast<SoapySDR::Stream *>(stream), buffs, numElems, *flags, timeNs, timeoutUs);
+}
+
+int SoapySDRDevice_readStreamStatus(SoapySDRDevice *device, SoapySDRStream *stream, size_t *chanMask, int *flags, long long *timeNs, const long timeoutUs)
+{
+    return device->readStreamStatus(reinterpret_cast<SoapySDR::Stream *>(stream), *chanMask, *flags, *timeNs, timeoutUs);
+}
+
+
+/*******************************************************************
+ * Direct buffer access API
+ ******************************************************************/
+size_t SoapySDRDevice_getNumDirectAccessBuffers(SoapySDRDevice *device, SoapySDRStream *stream)
+{
+    return device->getNumDirectAccessBuffers(reinterpret_cast<SoapySDR::Stream *>(stream));
+}
+
+int SoapySDRDevice_getDirectAccessBufferAddrs(SoapySDRDevice *device, SoapySDRStream *stream, const size_t handle, void **buffs)
+{
+    return device->getDirectAccessBufferAddrs(reinterpret_cast<SoapySDR::Stream *>(stream), handle, buffs);
+}
+
+int SoapySDRDevice_acquireReadBuffer(SoapySDRDevice *device,
+    SoapySDRStream *stream,
+    size_t *handle,
+    const void **buffs,
+    int *flags,
+    long long *timeNs,
+    const long timeoutUs)
+{
+    return device->acquireReadBuffer(reinterpret_cast<SoapySDR::Stream *>(stream), *handle, buffs, *flags, *timeNs, timeoutUs);
+}
+
+void SoapySDRDevice_releaseReadBuffer(SoapySDRDevice *device,
+    SoapySDRStream *stream,
+    const size_t handle)
+{
+    return device->releaseReadBuffer(reinterpret_cast<SoapySDR::Stream *>(stream), handle);
+}
+
+int SoapySDRDevice_acquireWriteBuffer(SoapySDRDevice *device,
+    SoapySDRStream *stream,
+    size_t *handle,
+    void **buffs,
+    const long timeoutUs)
+{
+    return device->acquireWriteBuffer(reinterpret_cast<SoapySDR::Stream *>(stream), *handle, buffs, timeoutUs);
+}
+
+void SoapySDRDevice_releaseWriteBuffer(SoapySDRDevice *device,
+    SoapySDRStream *stream,
+    const size_t handle,
+    const size_t numElems,
+    int *flags,
+    const long long timeNs)
+{
+    return device->releaseWriteBuffer(reinterpret_cast<SoapySDR::Stream *>(stream), handle, numElems, *flags, timeNs);
+}
+
+/*******************************************************************
+ * Antenna API
+ ******************************************************************/
+char **SoapySDRDevice_listAntennas(const SoapySDRDevice *device, const int direction, const size_t channel, size_t *length)
+{
+    return toStrArray(device->listAntennas(direction, channel), length);
+}
+
+int SoapySDRDevice_setAntenna(SoapySDRDevice *device, const int direction, const size_t channel, const char *name)
+{
+    __SOAPY_SDR_C_TRY
+    device->setAntenna(direction, channel, name);
+    __SOAPY_SDR_C_CATCH
+}
+
+char *SoapySDRDevice_getAntenna(const SoapySDRDevice *device, const int direction, const size_t channel)
+{
+    return strdup(device->getAntenna(direction, channel).c_str());
+}
+
+/*******************************************************************
+ * Frontend corrections API
+ ******************************************************************/
+bool SoapySDRDevice_hasDCOffsetMode(const SoapySDRDevice *device, const int direction, const size_t channel)
+{
+    return device->hasDCOffsetMode(direction, channel);
+}
+
+int SoapySDRDevice_setDCOffsetMode(SoapySDRDevice *device, const int direction, const size_t channel, const bool automatic)
+{
+    __SOAPY_SDR_C_TRY
+    device->setDCOffsetMode(direction, channel, automatic);
+    __SOAPY_SDR_C_CATCH
+}
+
+bool SoapySDRDevice_getDCOffsetMode(const SoapySDRDevice *device, const int direction, const size_t channel)
+{
+    return device->getDCOffsetMode(direction, channel);
+}
+
+bool SoapySDRDevice_hasDCOffset(const SoapySDRDevice *device, const int direction, const size_t channel)
+{
+    return device->hasDCOffset(direction, channel);
+}
+
+int SoapySDRDevice_setDCOffset(SoapySDRDevice *device, const int direction, const size_t channel, const double offsetI, const double offsetQ)
+{
+    __SOAPY_SDR_C_TRY
+    device->setDCOffset(direction, channel, std::complex<double>(offsetI, offsetQ));
+    __SOAPY_SDR_C_CATCH
+}
+
+void SoapySDRDevice_getDCOffset(const SoapySDRDevice *device, const int direction, const size_t channel, double *offsetI, double *offsetQ)
+{
+    std::complex<double> ret = device->getDCOffset(direction, channel);
+    *offsetI = ret.real();
+    *offsetQ = ret.imag();
+}
+
+bool SoapySDRDevice_hasIQBalance(const SoapySDRDevice *device, const int direction, const size_t channel)
+{
+    return device->hasIQBalance(direction, channel);
+}
+
+int SoapySDRDevice_setIQBalance(SoapySDRDevice *device, const int direction, const size_t channel, const double balanceI, const double balanceQ)
+{
+    __SOAPY_SDR_C_TRY
+    device->setDCOffset(direction, channel, std::complex<double>(balanceI, balanceQ));
+    __SOAPY_SDR_C_CATCH
+}
+
+void SoapySDRDevice_getIQBalance(const SoapySDRDevice *device, const int direction, const size_t channel, double *balanceI, double *balanceQ)
+{
+    std::complex<double> ret = device->getIQBalance(direction, channel);
+    *balanceI = ret.real();
+    *balanceQ = ret.imag();
+}
+
+/*******************************************************************
+ * Gain API
+ ******************************************************************/
+char **SoapySDRDevice_listGains(const SoapySDRDevice *device, const int direction, const size_t channel, size_t *length)
+{
+    return toStrArray(device->listGains(direction, channel), length);
+}
+
+bool SoapySDRDevice_hasGainMode(const SoapySDRDevice *device, const int direction, const size_t channel)
+{
+    return device->hasGainMode(direction, channel);
+}
+
+int SoapySDRDevice_setGainMode(SoapySDRDevice *device, const int direction, const size_t channel, const bool automatic)
+{
+    __SOAPY_SDR_C_TRY
+    device->setGainMode(direction, channel, automatic);
+    __SOAPY_SDR_C_CATCH
+}
+
+bool SoapySDRDevice_getGainMode(const SoapySDRDevice *device, const int direction, const size_t channel)
+{
+    return device->getGainMode(direction, channel);
+}
+
+int SoapySDRDevice_setGain(SoapySDRDevice *device, const int direction, const size_t channel, const double value)
+{
+    __SOAPY_SDR_C_TRY
+    device->setGain(direction, channel, value);
+    __SOAPY_SDR_C_CATCH
+}
+
+int SoapySDRDevice_setGainElement(SoapySDRDevice *device, const int direction, const size_t channel, const char *name, const double value)
+{
+    __SOAPY_SDR_C_TRY
+    device->setGain(direction, channel, name, value);
+    __SOAPY_SDR_C_CATCH
+}
+
+double SoapySDRDevice_getGain(const SoapySDRDevice *device, const int direction, const size_t channel)
+{
+    return device->getGain(direction, channel);
+}
+
+double SoapySDRDevice_getGainElement(const SoapySDRDevice *device, const int direction, const size_t channel, const char *name)
+{
+    return device->getGain(direction, channel, name);
+}
+
+SoapySDRRange SoapySDRDevice_getGainRange(const SoapySDRDevice *device, const int direction, const size_t channel)
+{
+    return toRange(device->getGainRange(direction, channel));
+}
+
+SoapySDRRange SoapySDRDevice_getGainElementRange(const SoapySDRDevice *device, const int direction, const size_t channel, const char *name)
+{
+    return toRange(device->getGainRange(direction, channel, name));
+}
+
+/*******************************************************************
+ * Frequency API
+ ******************************************************************/
+int SoapySDRDevice_setFrequency(SoapySDRDevice *device, const int direction, const size_t channel, const double frequency, const SoapySDRKwargs *args)
+{
+    __SOAPY_SDR_C_TRY
+    device->setFrequency(direction, channel, frequency, toKwargs(args));
+    __SOAPY_SDR_C_CATCH
+}
+
+int SoapySDRDevice_setFrequencyComponent(SoapySDRDevice *device, const int direction, const size_t channel, const char *name, const double frequency, const SoapySDRKwargs *args)
+{
+    __SOAPY_SDR_C_TRY
+    device->setFrequency(direction, channel, name, frequency, toKwargs(args));
+    __SOAPY_SDR_C_CATCH
+}
+
+double SoapySDRDevice_getFrequency(const SoapySDRDevice *device, const int direction, const size_t channel)
+{
+    return device->getFrequency(direction, channel);
+}
+
+double SoapySDRDevice_getFrequencyComponent(const SoapySDRDevice *device, const int direction, const size_t channel, const char *name)
+{
+    return device->getFrequency(direction, channel, name);
+}
+
+char **SoapySDRDevice_listFrequencies(const SoapySDRDevice *device, const int direction, const size_t channel, size_t *length)
+{
+    return toStrArray(device->listFrequencies(direction, channel), length);
+}
+
+SoapySDRRange *SoapySDRDevice_getFrequencyRange(const SoapySDRDevice *device, const int direction, const size_t channel, size_t *length)
+{
+    return toRangeList(device->getFrequencyRange(direction, channel), length);
+}
+
+SoapySDRRange *SoapySDRDevice_getFrequencyRangeComponent(const SoapySDRDevice *device, const int direction, const size_t channel, const char *name, size_t *length)
+{
+    return toRangeList(device->getFrequencyRange(direction, channel, name), length);
+}
+
+SoapySDRArgInfo *SoapySDRDevice_getFrequencyArgsInfo(const SoapySDRDevice *device, const int direction, const size_t channel, size_t *length)
+{
+    return toArgInfoList(device->getFrequencyArgsInfo(direction, channel), length);
+}
+
+/*******************************************************************
+ * Sample Rate API
+ ******************************************************************/
+int SoapySDRDevice_setSampleRate(SoapySDRDevice *device, const int direction, const size_t channel, const double rate)
+{
+    __SOAPY_SDR_C_TRY
+    device->setSampleRate(direction, channel, rate);
+    __SOAPY_SDR_C_CATCH
+}
+
+double SoapySDRDevice_getSampleRate(const SoapySDRDevice *device, const int direction, const size_t channel)
+{
+    return device->getSampleRate(direction, channel);
+}
+
+double *SoapySDRDevice_listSampleRates(const SoapySDRDevice *device, const int direction, const size_t channel, size_t *length)
+{
+    return toNumericList(device->listSampleRates(direction, channel), length);
+}
+
+/*******************************************************************
+ * Bandwidth API
+ ******************************************************************/
+int SoapySDRDevice_setBandwidth(SoapySDRDevice *device, const int direction, const size_t channel, const double bw)
+{
+    __SOAPY_SDR_C_TRY
+    device->setBandwidth(direction, channel, bw);
+    __SOAPY_SDR_C_CATCH
+}
+
+double SoapySDRDevice_getBandwidth(const SoapySDRDevice *device, const int direction, const size_t channel)
+{
+    return device->getBandwidth(direction, channel);
+}
+
+double *SoapySDRDevice_listBandwidths(const SoapySDRDevice *device, const int direction, const size_t channel, size_t *length)
+{
+    return toNumericList(device->listBandwidths(direction, channel), length);
+}
+
+SoapySDRRange *SoapySDRDevice_getBandwidthRange(const SoapySDRDevice *device, const int direction, const size_t channel, size_t *length)
+{
+    return toRangeList(device->getBandwidthRange(direction, channel), length);
+}
+
+/*******************************************************************
+ * Clocking API
+ ******************************************************************/
+int SoapySDRDevice_setMasterClockRate(SoapySDRDevice *device, const double rate)
+{
+    __SOAPY_SDR_C_TRY
+    device->setMasterClockRate(rate);
+    __SOAPY_SDR_C_CATCH
+}
+
+double SoapySDRDevice_getMasterClockRate(const SoapySDRDevice *device)
+{
+    return device->getMasterClockRate();
+}
+
+SoapySDRRange *SoapySDRDevice_getMasterClockRates(const SoapySDRDevice *device, size_t *length)
+{
+    return toRangeList(device->getMasterClockRates(), length);
+}
+
+char **SoapySDRDevice_listClockSources(const SoapySDRDevice *device, size_t *length)
+{
+    return toStrArray(device->listClockSources(), length);
+}
+
+int SoapySDRDevice_setClockSource(SoapySDRDevice *device, const char *source)
+{
+    __SOAPY_SDR_C_TRY
+    device->setClockSource(source);
+    __SOAPY_SDR_C_CATCH
+}
+
+char *SoapySDRDevice_getClockSource(const SoapySDRDevice *device)
+{
+    return strdup(device->getClockSource().c_str());
+}
+
+/*******************************************************************
+ * Time API
+ ******************************************************************/
+
+char **SoapySDRDevice_listTimeSources(const SoapySDRDevice *device, size_t *length)
+{
+    return toStrArray(device->listTimeSources(), length);
+}
+
+int SoapySDRDevice_setTimeSource(SoapySDRDevice *device, const char *source)
+{
+    __SOAPY_SDR_C_TRY
+    device->setTimeSource(source);
+    __SOAPY_SDR_C_CATCH
+}
+
+char *SoapySDRDevice_getTimeSource(const SoapySDRDevice *device)
+{
+    return strdup(device->getTimeSource().c_str());
+}
+
+bool SoapySDRDevice_hasHardwareTime(const SoapySDRDevice *device, const char *what)
+{
+    return device->hasHardwareTime(what);
+}
+
+long long SoapySDRDevice_getHardwareTime(const SoapySDRDevice *device, const char *what)
+{
+    return device->getHardwareTime(what);
+}
+
+void SoapySDRDevice_setHardwareTime(SoapySDRDevice *device, const long long timeNs, const char *what)
+{
+    device->setHardwareTime(timeNs, what);
+}
+
+void SoapySDRDevice_setCommandTime(SoapySDRDevice *device, const long long timeNs, const char *what)
+{
+    device->setCommandTime(timeNs, what);
+}
+
+/*******************************************************************
+ * Sensor API
+ ******************************************************************/
+char **SoapySDRDevice_listSensors(const SoapySDRDevice *device, size_t *length)
+{
+    return toStrArray(device->listSensors(), length);
+}
+
+SoapySDRArgInfo SoapySDRDevice_getSensorInfo(const SoapySDRDevice *device, const char *name)
+{
+    return toArgInfo(device->getSensorInfo(name));
+}
+
+char *SoapySDRDevice_readSensor(const SoapySDRDevice *device, const char *name)
+{
+    return strdup(device->readSensor(name).c_str());
+}
+
+char **SoapySDRDevice_listChannelSensors(const SoapySDRDevice *device, const int direction, const size_t channel, size_t *length)
+{
+    return toStrArray(device->listSensors(direction, channel), length);
+}
+
+SoapySDRArgInfo SoapySDRDevice_getChannelSensorInfo(const SoapySDRDevice *device, const int direction, const size_t channel, const char *name)
+{
+    return toArgInfo(device->getSensorInfo(direction, channel, name));
+}
+
+char *SoapySDRDevice_readChannelSensor(const SoapySDRDevice *device, const int direction, const size_t channel, const char *name)
+{
+    return strdup(device->readSensor(direction, channel, name).c_str());
+}
+
+/*******************************************************************
+ * Register API
+ ******************************************************************/
+char **SoapySDRDevice_listRegisterInterfaces(const SoapySDRDevice *device, size_t *length)
+{
+    return toStrArray(device->listRegisterInterfaces(), length);
+}
+
+void SoapySDRDevice_writeNamedRegister(SoapySDRDevice *device, const char *name, const unsigned addr, const unsigned value)
+{
+    return device->writeRegister(name, addr, value);
+}
+
+unsigned SoapySDRDevice_readNamedRegister(const SoapySDRDevice *device, const char *name, const unsigned addr)
+{
+    return device->readRegister(name, addr);
+}
+
+void SoapySDRDevice_writeRegister(SoapySDRDevice *device, const unsigned addr, const unsigned value)
+{
+    return device->writeRegister(addr, value);
+}
+
+unsigned SoapySDRDevice_readRegister(const SoapySDRDevice *device, const unsigned addr)
+{
+    return device->readRegister(addr);
+}
+
+/*******************************************************************
+ * Settings API
+ ******************************************************************/
+SoapySDRArgInfo *SoapySDRDevice_getSettingInfo(const SoapySDRDevice *device, size_t *length)
+{
+    return toArgInfoList(device->getSettingInfo(), length);
+}
+
+void SoapySDRDevice_writeSetting(SoapySDRDevice *device, const char *key, const char *value)
+{
+    return device->writeSetting(key, value);
+}
+
+char *SoapySDRDevice_readSetting(const SoapySDRDevice *device, const char *key)
+{
+    return strdup(device->readSetting(key).c_str());
+}
+
+SoapySDRArgInfo *SoapySDRDevice_getChannelSettingInfo(const SoapySDRDevice *device, const int direction, const size_t channel, size_t *length)
+{
+    return toArgInfoList(device->getSettingInfo(direction, channel), length);
+}
+
+void SoapySDRDevice_writeChannelSetting(SoapySDRDevice *device, const int direction, const size_t channel, const char *key, const char *value)
+{
+    return device->writeSetting(direction, channel, key, value);
+}
+
+char *SoapySDRDevice_readChannelSetting(const SoapySDRDevice *device, const int direction, const size_t channel, const char *key)
+{
+    return strdup(device->readSetting(direction, channel, key).c_str());
+}
+
+/*******************************************************************
+ * GPIO API
+ ******************************************************************/
+char **SoapySDRDevice_listGPIOBanks(const SoapySDRDevice *device, size_t *length)
+{
+    return toStrArray(device->listGPIOBanks(), length);
+}
+
+void SoapySDRDevice_writeGPIO(SoapySDRDevice *device, const char *bank, const unsigned value)
+{
+    return device->writeGPIO(bank, value);
+}
+
+void SoapySDRDevice_writeGPIOMasked(SoapySDRDevice *device, const char *bank, const unsigned value, const unsigned mask)
+{
+    return device->writeGPIO(bank, value, mask);
+}
+
+unsigned SoapySDRDevice_readGPIO(const SoapySDRDevice *device, const char *bank)
+{
+    return device->readGPIO(bank);
+}
+
+void SoapySDRDevice_writeGPIODir(SoapySDRDevice *device, const char *bank, const unsigned dir)
+{
+    return device->writeGPIODir(bank, dir);
+}
+
+void SoapySDRDevice_writeGPIODirMasked(SoapySDRDevice *device, const char *bank, const unsigned dir, const unsigned mask)
+{
+    return device->writeGPIODir(bank, dir, mask);
+}
+
+unsigned SoapySDRDevice_readGPIODir(const SoapySDRDevice *device, const char *bank)
+{
+    return device->readGPIODir(bank);
+}
+
+/*******************************************************************
+ * I2C API
+ ******************************************************************/
+void SoapySDRDevice_writeI2C(SoapySDRDevice *device, const int addr, const char *data, const size_t numBytes)
+{
+    return device->writeI2C(addr, std::string(data, numBytes));
+}
+
+char *SoapySDRDevice_readI2C(SoapySDRDevice *device, const int addr, const size_t numBytes)
+{
+    const std::string bytes = device->readI2C(addr, numBytes).c_str();
+    char *buff = (char *)std::malloc(bytes.size());
+    std::copy(bytes.begin(), bytes.end(), buff);
+    return buff;
+}
+
+/*******************************************************************
+ * SPI API
+ ******************************************************************/
+unsigned SoapySDRDevice_transactSPI(SoapySDRDevice *device, const int addr, const unsigned data, const size_t numBits)
+{
+    return device->transactSPI(addr, data, numBits);
+}
+
+/*******************************************************************
+ * UART API
+ ******************************************************************/
+char **SoapySDRDevice_listUARTs(const SoapySDRDevice *device, size_t *length)
+{
+    return toStrArray(device->listUARTs(), length);
+}
+
+void SoapySDRDevice_writeUART(SoapySDRDevice *device, const char *which, const char *data)
+{
+    return device->writeUART(which, data);
+}
+
+char *SoapySDRDevice_readUART(const SoapySDRDevice *device, const char *which, const long timeoutUs)
+{
+    return strdup(device->readUART(which, timeoutUs).c_str());
+}
+
+} //extern "C"
diff --git a/lib/Errors.cpp b/lib/Errors.cpp
new file mode 100644
index 0000000..f03a7b6
--- /dev/null
+++ b/lib/Errors.cpp
@@ -0,0 +1,9 @@
+// Copyright (c) 2015-2015 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#include <SoapySDR/Errors.hpp>
+
+const char *SoapySDR::errToStr(const int errorCode)
+{
+    return SoapySDR_errToStr(errorCode);
+}
diff --git a/lib/ErrorsC.cpp b/lib/ErrorsC.cpp
new file mode 100644
index 0000000..db4ba79
--- /dev/null
+++ b/lib/ErrorsC.cpp
@@ -0,0 +1,24 @@
+// Copyright (c) 2015-2015 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#include <SoapySDR/Errors.h>
+
+extern "C" {
+
+const char *SoapySDR_errToStr(const int errorCode)
+{
+    switch(errorCode)
+    {
+    case SOAPY_SDR_TIMEOUT:         return "TIMEOUT";
+    case SOAPY_SDR_STREAM_ERROR:    return "STREAM_ERROR";
+    case SOAPY_SDR_CORRUPTION:      return "CORRUPTION";
+    case SOAPY_SDR_OVERFLOW:        return "OVERFLOW";
+    case SOAPY_SDR_NOT_SUPPORTED:   return "NOT_SUPPORTED";
+    case SOAPY_SDR_TIME_ERROR:      return "TIME_ERROR";
+    case SOAPY_SDR_UNDERFLOW:       return "UNDERFLOW";
+    default: break;
+    }
+    return "UNKNOWN";
+}
+
+} //extern "C"
diff --git a/lib/Factory.cpp b/lib/Factory.cpp
new file mode 100644
index 0000000..493c096
--- /dev/null
+++ b/lib/Factory.cpp
@@ -0,0 +1,188 @@
+// Copyright (c) 2014-2016 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#include <SoapySDR/Device.hpp>
+#include <SoapySDR/Registry.hpp>
+#include <SoapySDR/Modules.hpp>
+#include <stdexcept>
+#include <iostream>
+#include <cctype>
+#include <mutex>
+
+static std::recursive_mutex &getFactoryMutex(void)
+{
+    static std::recursive_mutex mutex;
+    return mutex;
+}
+
+typedef std::map<SoapySDR::Kwargs, SoapySDR::Device *> DeviceTable;
+
+static DeviceTable &getDeviceTable(void)
+{
+    static DeviceTable table;
+    return table;
+}
+
+typedef std::map<SoapySDR::Device *, size_t> DeviceCounts;
+
+static DeviceCounts &getDeviceCounts(void)
+{
+    static DeviceCounts table;
+    return table;
+}
+
+SoapySDR::KwargsList SoapySDR::Device::enumerate(const Kwargs &args)
+{
+    std::lock_guard<std::recursive_mutex> lock(getFactoryMutex());
+
+    loadModules();
+    SoapySDR::KwargsList results;
+    for (const auto &it : Registry::listFindFunctions())
+    {
+        if (args.count("driver") != 0 and args.at("driver") != it.first) continue;
+        try
+        {
+            SoapySDR::KwargsList results0 = it.second(args);
+            for (size_t i = 0; i < results0.size(); i++)
+            {
+                results0[i]["driver"] = it.first;
+                results.push_back(results0[i]);
+            }
+        }
+        catch (const std::exception &ex)
+        {
+            std::cerr << "SoapySDR::Device::enumerate(" << it.first << ") " << ex.what() << std::endl;
+        }
+        catch (...)
+        {
+            std::cerr << "SoapySDR::Device::enumerate(" << it.first << ") " << "unknown error" << std::endl;
+        }
+    }
+    return results;
+}
+
+static std::string trim(const std::string &s)
+{
+    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;
+}
+
+static SoapySDR::Kwargs 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;
+}
+
+SoapySDR::KwargsList SoapySDR::Device::enumerate(const std::string &args)
+{
+    return enumerate(argsStrToKwargs(args));
+}
+
+SoapySDR::Device* SoapySDR::Device::make(const Kwargs &args_)
+{
+    std::lock_guard<std::recursive_mutex> lock(getFactoryMutex());
+
+    loadModules();
+    Kwargs args = args_;
+    Device *device = nullptr;
+
+    //check the device table for an already allocated device
+    if (getDeviceTable().count(args_) != 0 and getDeviceCounts().count(getDeviceTable().at(args_)) != 0)
+    {
+        device = getDeviceTable().at(args_);
+    }
+
+    //otherwise call into one of the factory functions
+    else
+    {
+        //the args must always come from an enumeration result
+        {
+            const auto results = Device::enumerate(args);
+            if (not results.empty()) args = results.front();
+        }
+
+        //load the enumeration args with missing keys from the make argument
+        for (const auto &it : args_)
+        {
+            if (args.count(it.first) == 0) args[it.first] = it.second;
+        }
+
+        //loop through make functions and call on module match
+        for (const auto &it : Registry::listMakeFunctions())
+        {
+            if (args.count("driver") != 0 and args.at("driver") != it.first) continue;
+            device = it.second(args);
+            break;
+        }
+    }
+
+    if (device == nullptr) throw std::runtime_error("SoapySDR::Device::make() no match");
+
+    //store into the table
+    getDeviceTable()[args_] = device;
+    getDeviceCounts()[device]++;
+
+    return device;
+}
+
+
+SoapySDR::Device *SoapySDR::Device::make(const std::string &args)
+{
+    return make(argsStrToKwargs(args));
+}
+
+void SoapySDR::Device::unmake(Device *device)
+{
+    std::lock_guard<std::recursive_mutex> lock(getFactoryMutex());
+
+    if (getDeviceCounts().count(device) == 0)
+    {
+        throw std::runtime_error("SoapySDR::Device::unmake() unknown device");
+    }
+
+    getDeviceCounts()[device]--;
+    if (getDeviceCounts()[device] == 0)
+    {
+        getDeviceCounts().erase(device);
+        delete device;
+
+        //cleanup the argument to device table
+        for (auto it = getDeviceTable().begin(); it != getDeviceTable().end(); ++it)
+        {
+            if (it->second == device)
+            {
+                getDeviceTable().erase(it);
+                return;
+            }
+        }
+    }
+}
diff --git a/lib/FactoryC.cpp b/lib/FactoryC.cpp
new file mode 100644
index 0000000..8b614c8
--- /dev/null
+++ b/lib/FactoryC.cpp
@@ -0,0 +1,37 @@
+// Copyright (c) 2014-2015 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#include "TypeHelpers.hpp"
+#include <SoapySDR/Device.h>
+#include <SoapySDR/Device.hpp>
+#include <cstdlib>
+#include <cstring>
+
+extern "C" {
+
+SoapySDRKwargs *SoapySDRDevice_enumerate(const SoapySDRKwargs *args, size_t *length)
+{
+    return toKwargsList(SoapySDR::Device::enumerate(toKwargs(args)), length);
+}
+
+SOAPY_SDR_API SoapySDRKwargs *SoapySDRDevice_enumerateStrArgs(const char *args, size_t *length)
+{
+    return toKwargsList(SoapySDR::Device::enumerate(args), length);
+}
+
+SoapySDRDevice *SoapySDRDevice_make(const SoapySDRKwargs *args)
+{
+    return (SoapySDRDevice *)SoapySDR::Device::make(toKwargs(args));
+}
+
+SoapySDRDevice *SoapySDRDevice_makeStrArgs(const char *args)
+{
+    return (SoapySDRDevice *)SoapySDR::Device::make(args);
+}
+
+void SoapySDRDevice_unmake(SoapySDRDevice *device)
+{
+    SoapySDR::Device::unmake((SoapySDR::Device *)device);
+}
+
+}
diff --git a/lib/Formats.cpp b/lib/Formats.cpp
new file mode 100644
index 0000000..67a2d85
--- /dev/null
+++ b/lib/Formats.cpp
@@ -0,0 +1,9 @@
+// Copyright (c) 2015-2015 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#include <SoapySDR/Formats.hpp>
+
+size_t SoapySDR::formatToSize(const std::string &format)
+{
+    return SoapySDR_formatToSize(format.c_str());
+}
diff --git a/lib/FormatsC.cpp b/lib/FormatsC.cpp
new file mode 100644
index 0000000..bd939c7
--- /dev/null
+++ b/lib/FormatsC.cpp
@@ -0,0 +1,23 @@
+// Copyright (c) 2015-2015 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#include <SoapySDR/Formats.h>
+#include <cctype>
+
+extern "C" {
+
+size_t SoapySDR_formatToSize(const char *format)
+{
+    size_t size = 0;
+    size_t isComplex = false;
+    char ch = 0;
+    while ((ch = *format++) != '\0')
+    {
+        if (ch == 'C') isComplex = true;
+        if (std::isdigit(ch)) size = (size*10) + size_t(ch-'0');
+    }
+    if (isComplex) size *= 2;
+    return size / 8; //bits to bytes
+}
+
+} //extern "C"
diff --git a/lib/Logger.cpp b/lib/Logger.cpp
new file mode 100644
index 0000000..0350a20
--- /dev/null
+++ b/lib/Logger.cpp
@@ -0,0 +1,24 @@
+// Copyright (c) 2014-2015 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#include <SoapySDR/Logger.hpp>
+
+void SoapySDR::log(const LogLevel logLevel, const std::string &message)
+{
+    return SoapySDR_log(logLevel, message.c_str());
+}
+
+void SoapySDR::vlogf(const SoapySDRLogLevel logLevel, const char *format, va_list argList)
+{
+    return SoapySDR_vlogf(logLevel, format, argList);
+}
+
+void SoapySDR::registerLogHandler(const LogHandler &handler)
+{
+    return SoapySDR_registerLogHandler(handler);
+}
+
+void SoapySDR::setLogLevel(const LogLevel logLevel)
+{
+    SoapySDR_setLogLevel(logLevel);
+}
diff --git a/lib/LoggerC.cpp b/lib/LoggerC.cpp
new file mode 100644
index 0000000..b7d73d6
--- /dev/null
+++ b/lib/LoggerC.cpp
@@ -0,0 +1,114 @@
+// Copyright (c) 2014-2015 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#include <SoapySDR/Logger.h>
+#include <cstdio>
+#include <cstdlib>
+#include <string>
+#include <iostream>
+
+/***********************************************************************
+ * default log level supports environment variable
+ **********************************************************************/
+std::string getEnvImpl(const char *name);
+
+static SoapySDRLogLevel getDefaultLogLevel(void)
+{
+    const std::string logLevelEnvStr = getEnvImpl("SOAPY_SDR_LOG_LEVEL");
+    if (logLevelEnvStr.empty()) return SOAPY_SDR_INFO;
+
+    //check string names
+    #define checkLogLevelEnvStr(level) \
+        if (logLevelEnvStr == #level) return SOAPY_SDR_ ## level
+    checkLogLevelEnvStr(FATAL);
+    checkLogLevelEnvStr(CRITICAL);
+    checkLogLevelEnvStr(ERROR);
+    checkLogLevelEnvStr(WARNING);
+    checkLogLevelEnvStr(NOTICE);
+    checkLogLevelEnvStr(DEBUG);
+    checkLogLevelEnvStr(TRACE);
+
+    //check int values
+    const int logLevelInt = std::atoi(logLevelEnvStr.c_str());
+    if (logLevelInt < SOAPY_SDR_FATAL) return SOAPY_SDR_FATAL;
+    if (logLevelInt > SOAPY_SDR_TRACE) return SOAPY_SDR_TRACE;
+    return SoapySDRLogLevel(logLevelInt);
+}
+
+/***********************************************************************
+ * Compatibility for vasprintf under MSVC
+ **********************************************************************/
+#ifdef _MSC_VER
+int vasprintf(char **strp, const char *fmt, va_list ap)
+{
+    int r = _vscprintf(fmt, ap);
+    if (r < 0) return r;
+    *strp = (char *)malloc(r+1);
+    return vsprintf_s(*strp, r+1, fmt, ap);
+}
+#endif
+
+/***********************************************************************
+ * ANSI terminal colors for default logger
+ **********************************************************************/
+#define ANSI_COLOR_RED     "\x1b[31m"
+#define ANSI_COLOR_GREEN   "\x1b[32m"
+#define ANSI_COLOR_YELLOW  "\x1b[33m"
+#define ANSI_COLOR_BLUE    "\x1b[34m"
+#define ANSI_COLOR_MAGENTA "\x1b[35m"
+#define ANSI_COLOR_CYAN    "\x1b[36m"
+#define ANSI_COLOR_RESET   "\x1b[0m"
+#define ANSI_COLOR_BOLD    "\x1b[1m"
+
+/***********************************************************************
+ * Default log message handler implementation
+ **********************************************************************/
+void defaultLogHandler(const SoapySDRLogLevel logLevel, const char *message)
+{
+    switch (logLevel)
+    {
+    case SOAPY_SDR_FATAL:    fprintf(stderr, ANSI_COLOR_BOLD ANSI_COLOR_RED "[FATAL] %s" ANSI_COLOR_RESET "\n", message); break;
+    case SOAPY_SDR_CRITICAL: fprintf(stderr, ANSI_COLOR_BOLD ANSI_COLOR_RED "[CRITICAL] %s" ANSI_COLOR_RESET "\n", message); break;
+    case SOAPY_SDR_ERROR:    fprintf(stderr, ANSI_COLOR_BOLD ANSI_COLOR_RED "[ERROR] %s" ANSI_COLOR_RESET "\n", message); break;
+    case SOAPY_SDR_WARNING:  fprintf(stderr, ANSI_COLOR_BOLD ANSI_COLOR_YELLOW "[WARNING] %s" ANSI_COLOR_RESET "\n", message); break;
+    case SOAPY_SDR_NOTICE:   fprintf(stdout, ANSI_COLOR_GREEN "[NOTICE] %s" ANSI_COLOR_RESET "\n", message); break;
+    case SOAPY_SDR_INFO:     fprintf(stdout, "[INFO] %s\n", message); break;
+    case SOAPY_SDR_DEBUG:    fprintf(stdout, "[DEBUG] %s\n", message); break;
+    case SOAPY_SDR_TRACE:    fprintf(stdout, "[TRACE] %s\n", message); break;
+    case SOAPY_SDR_SSI:      fputs(message, stderr); fflush(stderr); break;
+    }
+}
+
+static SoapySDRLogHandler registeredLogHandler = &defaultLogHandler;
+static SoapySDRLogLevel registeredLogLevel = getDefaultLogLevel();
+
+extern "C" {
+
+void SoapySDR_log(const SoapySDRLogLevel logLevel, const char *message)
+{
+    if (logLevel > registeredLogLevel and logLevel != SOAPY_SDR_SSI) return;
+    return registeredLogHandler(logLevel, message);
+}
+
+void SoapySDR_vlogf(const SoapySDRLogLevel logLevel, const char *format, va_list argList)
+{
+    if (logLevel > registeredLogLevel) return;
+    char *message = NULL;
+    if (vasprintf(&message, format, argList) != -1)
+    {
+        SoapySDR_log(logLevel, message);
+        free(message);
+    }
+}
+
+void SoapySDR_registerLogHandler(const SoapySDRLogHandler handler)
+{
+    registeredLogHandler = handler;
+}
+
+void SoapySDR_setLogLevel(const SoapySDRLogLevel logLevel)
+{
+    registeredLogLevel = logLevel;
+}
+
+}
diff --git a/lib/Modules.in.cpp b/lib/Modules.in.cpp
new file mode 100644
index 0000000..a1f9701
--- /dev/null
+++ b/lib/Modules.in.cpp
@@ -0,0 +1,289 @@
+// Copyright (c) 2014-2016 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#include <SoapySDR/Modules.hpp>
+#include <SoapySDR/Logger.hpp>
+#include <vector>
+#include <string>
+#include <cstdlib> //getenv
+#include <sstream>
+#include <map>
+
+#ifdef _MSC_VER
+#include <windows.h>
+#else
+#include <dlfcn.h>
+#include <glob.h>
+#endif
+
+/***********************************************************************
+ * root installation path
+ **********************************************************************/
+std::string getEnvImpl(const char *name)
+{
+    #ifdef _MSC_VER
+    const DWORD len = GetEnvironmentVariableA(name, 0, 0);
+    if (len == 0) return "";
+    char* buffer = new char[len];
+    GetEnvironmentVariableA(name, buffer, len);
+    std::string result(buffer);
+    delete [] buffer;
+    return result;
+    #else
+    const char *result = getenv(name);
+    if (result != NULL) return result;
+    #endif
+    return "";
+}
+
+std::string SoapySDR::getRootPath(void)
+{
+    const std::string rootPathEnv = getEnvImpl("SOAPY_SDR_ROOT");
+    if (not rootPathEnv.empty()) return rootPathEnv;
+
+    // Get the path to the current dynamic linked library.
+    // The path to this library can be used to determine
+    // the installation root without prior knowledge.
+    #ifdef _MSC_VER
+    char path[MAX_PATH];
+    HMODULE hm = NULL;
+    if (GetModuleHandleExA(
+        GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
+        GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
+        (LPCSTR) &SoapySDR::getRootPath, &hm))
+    {
+        const DWORD size = GetModuleFileNameA(hm, path, sizeof(path));
+        if (size != 0)
+        {
+            const std::string libPath(path, size);
+            const size_t slash0Pos = libPath.find_last_of("/\\");
+            const size_t slash1Pos = libPath.substr(0, slash0Pos).find_last_of("/\\");
+            if (slash0Pos != std::string::npos and slash1Pos != std::string::npos)
+                return libPath.substr(0, slash1Pos);
+        }
+    }
+    #endif
+
+    return "@SOAPY_SDR_ROOT@";
+}
+
+/***********************************************************************
+ * list modules API call
+ **********************************************************************/
+static std::vector<std::string> searchModulePath(const std::string &path)
+{
+    const std::string pattern = path + "*.*";
+    std::vector<std::string> modulePaths;
+
+#ifdef _MSC_VER
+
+    //http://stackoverflow.com/questions/612097/how-can-i-get-a-list-of-files-in-a-directory-using-c-or-c
+    WIN32_FIND_DATA fd; 
+    HANDLE hFind = ::FindFirstFile(pattern.c_str(), &fd); 
+    if(hFind != INVALID_HANDLE_VALUE) 
+    { 
+        do 
+        { 
+            // read all (real) files in current folder
+            // , delete '!' read other 2 default folder . and ..
+            if(! (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ) 
+            {
+                modulePaths.push_back(path + fd.cFileName);
+            }
+        }while(::FindNextFile(hFind, &fd)); 
+        ::FindClose(hFind); 
+    }
+
+#else
+
+    glob_t globResults;
+
+    const int ret = glob(pattern.c_str(), 0/*no flags*/, NULL, &globResults);
+    if (ret == 0) for (size_t i = 0; i < globResults.gl_pathc; i++)
+    {
+        modulePaths.push_back(globResults.gl_pathv[i]);
+    }
+    else if (ret == GLOB_NOMATCH) {/* acceptable error condition, do not print error */}
+    else SoapySDR::logf(SOAPY_SDR_ERROR, "SoapySDR::listModules(%s) glob(%s) error %d", path.c_str(), pattern.c_str(), ret);
+
+    globfree(&globResults);
+
+#endif
+
+    return modulePaths;
+}
+
+std::vector<std::string> SoapySDR::listModules(void)
+{
+    //the default search path
+    std::vector<std::string> searchPaths;
+    searchPaths.push_back(SoapySDR::getRootPath() + "/lib at LIB_SUFFIX@/SoapySDR/modules");
+
+    //support /usr/local module installs when the install prefix is /usr
+    if (SoapySDR::getRootPath() == "/usr")
+    {
+        searchPaths.push_back("/usr/local/lib at LIB_SUFFIX@/SoapySDR/modules");
+        //when using a multi-arch directory, support single-arch path as well
+        static const std::string libsuffix("@LIB_SUFFIX@");
+        if (not libsuffix.empty() and libsuffix.at(0) == '/')
+            searchPaths.push_back("/usr/local/lib/SoapySDR/modules");
+    }
+
+    //separator for search paths
+    #ifdef _MSC_VER
+    static const char sep = ';';
+    #else
+    static const char sep = ':';
+    #endif
+
+    //check the environment's search path
+    std::stringstream pluginPaths(getEnvImpl("SOAPY_SDR_PLUGIN_PATH"));
+    std::string pluginPath;
+    while (std::getline(pluginPaths, pluginPath, sep))
+    {
+        if (pluginPath.empty()) continue;
+        searchPaths.push_back(pluginPath);
+    }
+
+    //traverse the search paths
+    std::vector<std::string> modules;
+    for (size_t i = 0; i < searchPaths.size(); i++)
+    {
+        const std::vector<std::string> subModules = SoapySDR::listModules(searchPaths.at(i));
+        modules.insert(modules.end(), subModules.begin(), subModules.end());
+    }
+    return modules;
+}
+
+std::vector<std::string> SoapySDR::listModules(const std::string &path)
+{
+    return searchModulePath(path + "/"); //requires trailing slash
+}
+
+/***********************************************************************
+ * load module API call
+ **********************************************************************/
+std::map<std::string, void *> &getModuleHandles(void)
+{
+    static std::map<std::string, void *> handles;
+    return handles;
+}
+
+//! share the module path during loadModule
+std::string &getModuleLoading(void)
+{
+    static std::string moduleLoading;
+    return moduleLoading;
+}
+
+//! share registration errors during loadModule
+std::map<std::string, SoapySDR::Kwargs> &getLoaderResults(void)
+{
+    static std::map<std::string, SoapySDR::Kwargs> results;
+    return results;
+}
+
+#ifdef _MSC_VER
+static std::string GetLastErrorMessage(void)
+{
+    LPVOID lpMsgBuf;
+    DWORD dw = GetLastError();
+
+    FormatMessage(
+        FORMAT_MESSAGE_ALLOCATE_BUFFER |
+        FORMAT_MESSAGE_FROM_SYSTEM |
+        FORMAT_MESSAGE_IGNORE_INSERTS,
+        NULL,
+        dw,
+        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+        (LPTSTR) &lpMsgBuf,
+        0, NULL );
+
+    std::string msg((char *)lpMsgBuf);
+    LocalFree(lpMsgBuf);
+    return msg;
+}
+#endif
+
+std::string SoapySDR::loadModule(const std::string &path)
+{
+    //check if already loaded
+    if (getModuleHandles().count(path) != 0) return path + " already loaded";
+
+    //stash the path for registry access
+    getModuleLoading().assign(path);
+
+    //load the module
+#ifdef _MSC_VER
+    HMODULE handle = LoadLibrary(path.c_str());
+    getModuleLoading().clear();
+    if (handle == NULL) return "LoadLibrary() failed: " + GetLastErrorMessage();
+#else
+    void *handle = dlopen(path.c_str(), RTLD_LAZY);
+    getModuleLoading().clear();
+    if (handle == NULL) return "dlopen() failed: " + std::string(dlerror());
+#endif
+
+    //stash the handle
+    getModuleHandles()[path] = handle;
+    return "";
+}
+
+SoapySDR::Kwargs SoapySDR::getLoaderResult(const std::string &path)
+{
+    if (getLoaderResults().count(path) == 0) return SoapySDR::Kwargs();
+    return getLoaderResults()[path];
+}
+
+std::string SoapySDR::unloadModule(const std::string &path)
+{
+    //check if already loaded
+    if (getModuleHandles().count(path) == 0) return path + " never loaded";
+
+    //stash the path for registry access
+    getModuleLoading().assign(path);
+
+    //unload the module
+    void *handle = getModuleHandles()[path];
+#ifdef _MSC_VER
+    BOOL success = FreeLibrary((HMODULE)handle);
+    getModuleLoading().clear();
+    if (not success) return "FreeLibrary() failed: " + GetLastErrorMessage();
+#else
+    int status = dlclose(handle);
+    getModuleLoading().clear();
+    if (status != 0) return "dlclose() failed: " + std::string(dlerror());
+#endif
+
+    //clear the handle
+    getLoaderResults().erase(path);
+    getModuleHandles().erase(path);
+    return "";
+}
+
+/***********************************************************************
+ * load modules API call
+ **********************************************************************/
+
+void lateLoadNullDevice(void);
+
+void SoapySDR::loadModules(void)
+{
+    static bool loaded = false;
+    if (loaded) return;
+    loaded = true;
+    lateLoadNullDevice();
+
+    const auto paths = listModules();
+    for (size_t i = 0; i < paths.size(); i++)
+    {
+        if (getModuleHandles().count(paths[i]) != 0) continue; //was manually loaded
+        const std::string errorMsg = loadModule(paths[i]);
+        if (not errorMsg.empty()) SoapySDR::logf(SOAPY_SDR_ERROR, "SoapySDR::loadModule(%s)\n  %s", paths[i].c_str(), errorMsg.c_str());
+        for (const auto &it : SoapySDR::getLoaderResult(paths[i]))
+        {
+            if (it.second.empty()) continue;
+            SoapySDR::logf(SOAPY_SDR_ERROR, "SoapySDR::loadModule(%s)\n  %s", paths[i].c_str(), it.second.c_str());
+        }
+    }
+}
diff --git a/lib/ModulesC.cpp b/lib/ModulesC.cpp
new file mode 100644
index 0000000..1894fd2
--- /dev/null
+++ b/lib/ModulesC.cpp
@@ -0,0 +1,48 @@
+// Copyright (c) 2014-2015 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#include "TypeHelpers.hpp"
+#include <SoapySDR/Modules.h>
+#include <SoapySDR/Modules.hpp>
+#include <cstdlib>
+#include <cstring>
+
+extern "C" {
+
+const char *SoapySDR_getRootPath(void)
+{
+    static const std::string root = SoapySDR::getRootPath();
+    return root.c_str();
+}
+
+char **SoapySDR_listModules(size_t *length)
+{
+    return toStrArray(SoapySDR::listModules(), length);
+}
+
+char **SoapySDR_listModulesPath(const char *path, size_t *length)
+{
+    return toStrArray(SoapySDR::listModules(path), length);
+}
+
+char *SoapySDR_loadModule(const char *path)
+{
+    return strdup(SoapySDR::loadModule(path).c_str());
+}
+
+SoapySDRKwargs SoapySDR_getLoaderResult(const char *path)
+{
+    return toKwargs(SoapySDR::getLoaderResult(path));
+}
+
+char *SoapySDR_unloadModule(const char *path)
+{
+    return strdup(SoapySDR::unloadModule(path).c_str());
+}
+
+void SoapySDR_loadModules(void)
+{
+    SoapySDR::loadModules();
+}
+
+}
diff --git a/lib/NullDevice.cpp b/lib/NullDevice.cpp
new file mode 100644
index 0000000..df60af2
--- /dev/null
+++ b/lib/NullDevice.cpp
@@ -0,0 +1,57 @@
+// Copyright (c) 2014-2015 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#include <SoapySDR/Device.hpp>
+#include <SoapySDR/Registry.hpp>
+
+class NullDevice : public SoapySDR::Device
+{
+public:
+    NullDevice(void)
+    {
+        return;
+    }
+
+    std::string getDriverKey(void) const
+    {
+        return "null";
+    }
+
+    std::string getHardwareKey(void) const
+    {
+        return "null";
+    }
+};
+
+SoapySDR::KwargsList findNullDevice(const SoapySDR::Kwargs &args)
+{
+    SoapySDR::KwargsList results;
+
+    //require that the user specify type=null
+    if (args.count("type") == 0) return results;
+    if (args.at("type") != "null") return results;
+
+    SoapySDR::Kwargs nullArgs;
+    nullArgs["type"] = "null";
+    results.push_back(nullArgs);
+
+    return results;
+}
+
+SoapySDR::Device *makeNullDevice(const SoapySDR::Kwargs &)
+{
+    return new NullDevice();
+}
+
+/*!
+ * lateLoadNullDevice() is called by loadModules()
+ * to load the null device on-demand/not statically.
+ * This works around an issue when a loading module
+ * is linked against an older copy of SoapySDR
+ * which also tries to load its null device
+ * into the running copy of the library.
+ */
+void lateLoadNullDevice(void)
+{
+    static SoapySDR::Registry registerNullDevice("null", &findNullDevice, &makeNullDevice, SOAPY_SDR_ABI_VERSION);
+}
diff --git a/lib/Registry.cpp b/lib/Registry.cpp
new file mode 100644
index 0000000..0c2c582
--- /dev/null
+++ b/lib/Registry.cpp
@@ -0,0 +1,90 @@
+// Copyright (c) 2014-2016 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#include <SoapySDR/Registry.hpp>
+
+/***********************************************************************
+ * Function table holds all registration entries by name
+ **********************************************************************/
+struct FunctionsEntry
+{
+    std::string modulePath;
+    SoapySDR::FindFunction find;
+    SoapySDR::MakeFunction make;
+};
+
+typedef std::map<std::string, FunctionsEntry> FunctionTable;
+
+static FunctionTable &getFunctionTable(void)
+{
+    static FunctionTable table;
+    return table;
+}
+
+/***********************************************************************
+ * Module loader shared data structures
+ **********************************************************************/
+std::string &getModuleLoading(void);
+
+std::map<std::string, SoapySDR::Kwargs> &getLoaderResults(void);
+
+/***********************************************************************
+ * Registry entry-point implementation
+ **********************************************************************/
+SoapySDR::Registry::Registry(const std::string &name, const FindFunction &find, const MakeFunction &make, const std::string &abi)
+{
+    //create an entry for the loader result
+    std::string &errorMsg = getLoaderResults()[getModuleLoading()][name];
+
+    //abi check
+    if (abi != SOAPY_SDR_ABI_VERSION)
+    {
+        errorMsg = name + " failed ABI check: Library ABI=" SOAPY_SDR_ABI_VERSION ", Module ABI="+abi;
+        return;
+    }
+
+    //duplicate check
+    if (getFunctionTable().count(name) != 0)
+    {
+        errorMsg = "duplicate entry for " + name + " ("+getFunctionTable()[name].modulePath + ")";
+        return;
+    }
+
+    //register functions
+    FunctionsEntry entry;
+    entry.modulePath = getModuleLoading();
+    entry.find = find;
+    entry.make = make;
+    getFunctionTable()[name] = entry;
+    _name = name;
+}
+
+SoapySDR::Registry::~Registry(void)
+{
+    //erase entry
+    if (_name.empty()) return;
+    getFunctionTable().erase(_name);
+}
+
+/***********************************************************************
+ * Registry access API
+ **********************************************************************/
+SoapySDR::FindFunctions SoapySDR::Registry::listFindFunctions(void)
+{
+    FindFunctions functions;
+    for (const auto &it : getFunctionTable())
+    {
+        functions[it.first] = it.second.find;
+    }
+    return functions;
+}
+
+SoapySDR::MakeFunctions SoapySDR::Registry::listMakeFunctions(void)
+{
+    MakeFunctions functions;
+    for (const auto &it : getFunctionTable())
+    {
+        functions[it.first] = it.second.make;
+    }
+    return functions;
+}
diff --git a/lib/SoapySDR.in.pc b/lib/SoapySDR.in.pc
new file mode 100644
index 0000000..25a8a18
--- /dev/null
+++ b/lib/SoapySDR.in.pc
@@ -0,0 +1,15 @@
+prefix=@CMAKE_INSTALL_PREFIX@
+exec_prefix=${prefix}
+libdir=${exec_prefix}/lib at LIB_SUFFIX@
+includedir=${prefix}/include
+
+Name: Soapy SDR
+Description: Vendor and platform neutral SDR interface library.
+URL: https://github.com/pothosware/SoapySDR/wiki
+Version: @SOAPY_SDR_LIBVER@
+Requires:
+Requires.private:
+Conflicts:
+Cflags: -I${includedir}
+Libs: -L${libdir} -lSoapySDR
+Libs.private:
diff --git a/lib/TimeC.cpp b/lib/TimeC.cpp
new file mode 100644
index 0000000..e569b10
--- /dev/null
+++ b/lib/TimeC.cpp
@@ -0,0 +1,38 @@
+// Copyright (c) 2015-2015 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#include <SoapySDR/Time.h>
+#include <cmath>
+
+extern "C" {
+
+#if defined(_MSC_VER) && (_MSC_VER < 1800)
+
+static inline long long int llround(const double x)
+{
+    return (long long int)((x < (0.0)) ? (x - (0.5)) : (x + (0.5)));
+}
+
+#endif
+
+long long SoapySDR_ticksToTimeNs(const long long ticks, const double rate)
+{
+    const long long ratell = (long long)(rate);
+    const long long full = (long long)(ticks/ratell);
+    const long long err = ticks - (full*ratell);
+    const double part = full*(rate - ratell);
+    const double frac = ((err - part)*1000000000)/rate;
+    return (full*1000000000) + llround(frac);
+}
+
+long long SoapySDR_timeNsToTicks(const long long timeNs, const double rate)
+{
+    const long long ratell = (long long)(rate);
+    const long long full = (long long)(timeNs/1000000000);
+    const long long err = timeNs - (full*1000000000);
+    const double part = full*(rate - ratell);
+    const double frac = part + ((err*rate)/1000000000);
+    return (full*ratell) + llround(frac);
+}
+
+}
diff --git a/lib/TypeHelpers.hpp b/lib/TypeHelpers.hpp
new file mode 100644
index 0000000..edcec52
--- /dev/null
+++ b/lib/TypeHelpers.hpp
@@ -0,0 +1,105 @@
+// Copyright (c) 2014-2016 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#pragma once
+#include <SoapySDR/Config.hpp>
+#include <SoapySDR/Types.hpp>
+#include <SoapySDR/Types.h>
+#include <vector>
+#include <string>
+#include <cstdlib>
+#include <cstring>
+
+/*******************************************************************
+ * Helpful converters
+ ******************************************************************/
+static inline char **toStrArray(const std::vector<std::string> &strs, size_t *length)
+{
+    char **out = (char **)calloc(strs.size(), sizeof(char *));
+    for (size_t i = 0; i < strs.size(); i++)
+    {
+        out[i] = strdup(strs[i].c_str());
+    }
+    *length = strs.size();
+    return out;
+}
+
+static inline SoapySDRRange toRange(const SoapySDR::Range &range)
+{
+    SoapySDRRange out;
+    out.minimum = range.minimum();
+    out.maximum = range.maximum();
+    return out;
+}
+
+static inline SoapySDRRange *toRangeList(const SoapySDR::RangeList &ranges, size_t *length)
+{
+    SoapySDRRange *out = (SoapySDRRange *)calloc(ranges.size(), sizeof(SoapySDRRange));
+    for (size_t i = 0; i < ranges.size(); i++) out[i] = toRange(ranges[i]);
+    *length = ranges.size();
+    return out;
+}
+
+static inline double *toNumericList(const std::vector<double> &values, size_t *length)
+{
+    double *out = (double *)calloc(values.size(), sizeof(double));
+    for (size_t i = 0; i < values.size(); i++) out[i] = values[i];
+    *length = values.size();
+    return out;
+}
+
+static inline SoapySDR::Kwargs toKwargs(const SoapySDRKwargs *args)
+{
+    SoapySDR::Kwargs out;
+    if (args == NULL) return out;
+    for (size_t i = 0; i < args->size; i++)
+    {
+        out[args->keys[i]] = args->vals[i];
+    }
+    return out;
+}
+
+static inline SoapySDRKwargs toKwargs(const SoapySDR::Kwargs &args)
+{
+    SoapySDRKwargs out;
+    std::memset(&out, 0, sizeof(out));
+    for (const auto &it : args)
+    {
+        SoapySDRKwargs_set(&out, it.first.c_str(), it.second.c_str());
+    }
+    return out;
+}
+
+static inline SoapySDRKwargs *toKwargsList(const SoapySDR::KwargsList &args, size_t *length)
+{
+    SoapySDRKwargs *outArgs = (SoapySDRKwargs *)calloc(args.size(), sizeof(SoapySDRKwargs));
+    for (size_t i = 0; i < args.size(); i++) outArgs[i] = toKwargs(args[i]);
+    *length = args.size();
+    return outArgs;
+}
+
+static inline SoapySDRArgInfo toArgInfo(const SoapySDR::ArgInfo &info)
+{
+    SoapySDRArgInfo out;
+    out.key = strdup(info.key.c_str());
+    out.name = strdup(info.name.c_str());
+    out.description = strdup(info.description.c_str());
+    out.units = strdup(info.units.c_str());
+    out.type = SoapySDRArgInfoType(info.type);
+    out.range = toRange(info.range);
+    out.options = toStrArray(info.options, &out.numOptions);
+    out.optionNames = toStrArray(info.optionNames, &out.numOptions);
+
+    return out;
+}
+
+static inline SoapySDRArgInfo *toArgInfoList(const SoapySDR::ArgInfoList &infos, size_t *length)
+{
+    SoapySDRArgInfo *out = (SoapySDRArgInfo *)calloc(infos.size(), sizeof(SoapySDRArgInfo));
+    for (size_t i = 0; i < infos.size(); i++)
+    {
+        out[i] = toArgInfo(infos[i]);
+    }
+    *length = infos.size();
+    return out;
+}
diff --git a/lib/Types.cpp b/lib/Types.cpp
new file mode 100644
index 0000000..ae87e44
--- /dev/null
+++ b/lib/Types.cpp
@@ -0,0 +1,23 @@
+// Copyright (c) 2014-2015 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#include <SoapySDR/Types.hpp>
+
+SoapySDR::Range::Range(void):
+    _min(0.0),
+    _max(0.0)
+{
+    return;
+}
+
+SoapySDR::Range::Range(const double minimum, const double maximum):
+    _min(minimum),
+    _max(maximum)
+{
+    return;
+}
+
+SoapySDR::ArgInfo::ArgInfo(void)
+{
+    return;
+}
diff --git a/lib/TypesC.cpp b/lib/TypesC.cpp
new file mode 100644
index 0000000..d545146
--- /dev/null
+++ b/lib/TypesC.cpp
@@ -0,0 +1,95 @@
+// Copyright (c) 2014-2015 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#include <SoapySDR/Types.h>
+#include <cstdlib>
+#include <cstring>
+
+extern "C" {
+
+void SoapySDRStrings_clear(char ***elems, const size_t length)
+{
+    for (size_t i = 0; i < length; i++)
+    {
+        free((*elems)[i]);
+    }
+    free(*elems);
+    *elems = NULL;
+}
+
+void SoapySDRKwargs_set(SoapySDRKwargs *args, const char *key, const char *val)
+{
+    for (size_t i = 0; i < args->size; i++)
+    {
+        if (strcmp(args->keys[i], key) == 0)
+        {
+            free(args->vals[i]);
+            args->vals[i] = strdup(val);
+            return;
+        }
+    }
+
+    args->size++;
+    args->keys = (char **)realloc(args->keys, sizeof(char *)*args->size);
+    args->vals = (char **)realloc(args->vals, sizeof(char *)*args->size);
+
+    args->keys[args->size-1] = strdup(key);
+    args->vals[args->size-1] = strdup(val);
+}
+
+const char *SoapySDRKwargs_get(SoapySDRKwargs *args, const char *key)
+{
+    for (size_t i = 0; i < args->size; i++)
+    {
+        if (strcmp(args->keys[i], key) == 0)
+        {
+            return args->vals[i];
+        }
+    }
+    return NULL;
+}
+
+void SoapySDRKwargs_clear(SoapySDRKwargs *args)
+{
+    SoapySDRStrings_clear(&args->keys, args->size);
+    SoapySDRStrings_clear(&args->vals, args->size);
+    args->size = 0;
+}
+
+void SoapySDRKwargsList_clear(SoapySDRKwargs *args, const size_t length)
+{
+    for (size_t i = 0; i < length; i++) SoapySDRKwargs_clear(args+i);
+    free(args);
+}
+
+void SoapySDRArgInfo_clear(SoapySDRArgInfo *info)
+{
+    //clear strings
+    free(info->key);
+    info->key = NULL;
+
+    free(info->value);
+    info->value = NULL;
+
+    free(info->name);
+    info->name = NULL;
+
+    free(info->description);
+    info->description = NULL;
+
+    free(info->units);
+    info->units = NULL;
+
+    //clear options
+    SoapySDRStrings_clear(&info->options, info->numOptions);
+    SoapySDRStrings_clear(&info->optionNames, info->numOptions);
+    info->numOptions = 0;
+}
+
+void SoapySDRArgInfoList_clear(SoapySDRArgInfo *info, const size_t length)
+{
+    for (size_t i = 0; i < length; i++) SoapySDRArgInfo_clear(info+i);
+    free(info);
+}
+
+}
diff --git a/lib/Version.in.cpp b/lib/Version.in.cpp
new file mode 100644
index 0000000..a4faa19
--- /dev/null
+++ b/lib/Version.in.cpp
@@ -0,0 +1,14 @@
+// Copyright (c) 2014-2014 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#include <SoapySDR/Version.hpp>
+
+std::string SoapySDR::getAPIVersion(void)
+{
+    return "@SOAPY_SDR_VERSION@";
+}
+
+std::string SoapySDR::getABIVersion(void)
+{
+    return SOAPY_SDR_ABI_VERSION;
+}
diff --git a/lib/VersionC.cpp b/lib/VersionC.cpp
new file mode 100644
index 0000000..2c1a8c4
--- /dev/null
+++ b/lib/VersionC.cpp
@@ -0,0 +1,20 @@
+// Copyright (c) 2014-2014 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#include <SoapySDR/Version.hpp>
+
+extern "C" {
+
+const char *SoapySDR_getAPIVersion(void)
+{
+    static const std::string api = SoapySDR::getAPIVersion();
+    return api.c_str();
+}
+
+const char *SoapySDR_getABIVersion(void)
+{
+    static const std::string abi = SoapySDR::getABIVersion();
+    return abi.c_str();
+}
+
+}
diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt
new file mode 100644
index 0000000..9b08121
--- /dev/null
+++ b/python/CMakeLists.txt
@@ -0,0 +1,109 @@
+########################################################################
+# Project setup
+########################################################################
+cmake_minimum_required(VERSION 2.8)
+project(SoapySDRPython CXX)
+enable_testing()
+
+find_package(SoapySDR NO_MODULE REQUIRED)
+
+########################################################################
+# Find SWIG
+########################################################################
+find_package(SWIG)
+message(STATUS "SWIG_FOUND: ${SWIG_FOUND} - ${SWIG_VERSION}")
+
+########################################################################
+# Find python interp
+########################################################################
+find_package(PythonInterp)
+message(STATUS "PYTHONINTERP_FOUND: ${PYTHONINTERP_FOUND} - ${PYTHON_VERSION_STRING}")
+message(STATUS "PYTHON_EXECUTABLE: ${PYTHON_EXECUTABLE}")
+
+#help find_package(PythonLibs) by setting Python_ADDITIONAL_VERSIONS from PYTHON_VERSION_STRING
+if(PYTHONINTERP_FOUND AND DEFINED PYTHON_VERSION_STRING AND NOT DEFINED Python_ADDITIONAL_VERSIONS)
+    string(SUBSTRING "${PYTHON_VERSION_STRING}" 0 3 Python_ADDITIONAL_VERSIONS)
+endif()
+
+########################################################################
+# Determine install directory
+########################################################################
+execute_process(
+    COMMAND ${PYTHON_EXECUTABLE} -c
+    "from distutils.sysconfig import get_python_lib; print(get_python_lib(plat_specific=True, prefix=''))"
+    OUTPUT_STRIP_TRAILING_WHITESPACE
+    OUTPUT_VARIABLE PYTHON_INSTALL_DIR
+)
+set(PYTHON_INSTALL_DIR ${PYTHON_INSTALL_DIR} CACHE STRING "python install prefix")
+message(STATUS "PYTHON_INSTALL_DIR: \${prefix}/${PYTHON_INSTALL_DIR}")
+
+########################################################################
+# Find Python libs
+########################################################################
+find_package(PythonLibs)
+message(STATUS "PYTHONLIBS_FOUND: ${PYTHONLIBS_FOUND} - ${PYTHONLIBS_VERSION_STRING}")
+message(STATUS "PYTHON_INCLUDE_DIRS: ${PYTHON_INCLUDE_DIRS}")
+message(STATUS "PYTHON_LIBRARIES: ${PYTHON_LIBRARIES}")
+
+#on windows, we require a pythonxx_d.lib in debug mode
+#require that the PYTHON_DEBUG_LIBRARY flag is set
+#or the build assumes that the debug library DNE
+set(PYTHON_DEBUG_OK TRUE)
+if(WIN32 AND NOT PYTHON_DEBUG_LIBRARY AND "${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
+    message(WARNING "WIN32 Debug mode requires PYTHON_DEBUG_LIBRARY")
+    set(PYTHON_DEBUG_OK FALSE)
+endif()
+
+########################################################################
+# Python version check
+########################################################################
+set(PYTHON_VERSION_MATCH TRUE)
+if (PYTHON_VERSION_STRING AND PYTHONLIBS_VERSION_STRING)
+    if(NOT "${PYTHON_VERSION_STRING}" VERSION_EQUAL "${PYTHONLIBS_VERSION_STRING}")
+        message(WARNING "Python interp and library version mismatch")
+        set(PYTHON_VERSION_MATCH FALSE)
+    endif()
+endif()
+
+#set once we know that executable and libs are found and match
+#this tells the parent scope to build python3 when this is python2
+if(PYTHON_VERSION_STRING AND "${PYTHON_VERSION_STRING}" VERSION_LESS "3.0")
+    set(BUILD_PYTHON3 TRUE PARENT_SCOPE)
+endif()
+
+########################################################################
+## Feature registration
+########################################################################
+include(FeatureSummary)
+include(CMakeDependentOption)
+cmake_dependent_option(ENABLE_PYTHON "Enable python bindings" ON "ENABLE_LIBRARY;SWIG_FOUND;PYTHONINTERP_FOUND;PYTHONLIBS_FOUND;PYTHON_DEBUG_OK;PYTHON_VERSION_MATCH" OFF)
+add_feature_info(Python ENABLE_PYTHON "python bindings v${PYTHON_VERSION_STRING}")
+if (NOT ENABLE_PYTHON)
+    return()
+endif()
+
+########################################################################
+# Build Module
+########################################################################
+include(UseSWIG)
+include_directories(${SoapySDR_INCLUDE_DIRS})
+include_directories(${PYTHON_INCLUDE_DIRS})
+
+set(CMAKE_SWIG_FLAGS -c++ -threads)
+set_source_files_properties(SoapySDR.i PROPERTIES CPLUSPLUS ON)
+
+SWIG_ADD_MODULE(SoapySDR python SoapySDR.i)
+SWIG_LINK_LIBRARIES(SoapySDR ${SoapySDR_LIBRARIES} ${PYTHON_LIBRARIES})
+
+########################################################################
+# Install Module
+########################################################################
+install(
+    TARGETS ${SWIG_MODULE_SoapySDR_REAL_NAME}
+    DESTINATION ${PYTHON_INSTALL_DIR}
+)
+
+install(
+    FILES ${CMAKE_CURRENT_BINARY_DIR}/SoapySDR.py
+    DESTINATION ${PYTHON_INSTALL_DIR}
+)
diff --git a/python/SoapySDR.i b/python/SoapySDR.i
new file mode 100644
index 0000000..4702215
--- /dev/null
+++ b/python/SoapySDR.i
@@ -0,0 +1,195 @@
+// Copyright (c) 2014-2016 Josh Blum
+// Copyright (c) 2016-2016 Bastille Networks
+// SPDX-License-Identifier: BSL-1.0
+
+%module SoapySDR
+
+////////////////////////////////////////////////////////////////////////
+// Include all major headers to compile against
+////////////////////////////////////////////////////////////////////////
+%{
+#include <SoapySDR/Version.hpp>
+#include <SoapySDR/Modules.hpp>
+#include <SoapySDR/Device.hpp>
+#include <SoapySDR/Errors.hpp>
+#include <SoapySDR/Formats.hpp>
+%}
+
+////////////////////////////////////////////////////////////////////////
+// http://www.swig.org/Doc2.0/Library.html#Library_stl_exceptions
+////////////////////////////////////////////////////////////////////////
+%include <exception.i>
+
+%exception
+{
+    try{$action}
+    catch (const std::exception &ex)
+    {SWIG_exception(SWIG_RuntimeError, ex.what());}
+    catch (...)
+    {SWIG_exception(SWIG_RuntimeError, "unknown");}
+}
+
+////////////////////////////////////////////////////////////////////////
+// Config header defines API export
+////////////////////////////////////////////////////////////////////////
+%include <SoapySDR/Config.h>
+
+////////////////////////////////////////////////////////////////////////
+// Commonly used data types
+////////////////////////////////////////////////////////////////////////
+%include <std_complex.i>
+%include <std_string.i>
+%include <std_vector.i>
+%include <std_map.i>
+%include <SoapySDR/Types.hpp>
+
+%template(SoapySDRKwargs) std::map<std::string, std::string>;
+%template(SoapySDRKwargsList) std::vector<SoapySDR::Kwargs>;
+%template(SoapySDRArgInfoList) std::vector<SoapySDR::ArgInfo>;
+%template(SoapySDRStringList) std::vector<std::string>;
+%template(SoapySDRRangeList) std::vector<SoapySDR::Range>;
+%template(SoapySDRSizeList) std::vector<size_t>;
+%template(SoapySDRDoubleList) std::vector<double>;
+
+%extend std::map<std::string, std::string>
+{
+    %insert("python")
+    %{
+        def __str__(self):
+            out = list()
+            for k, v in self.iteritems():
+                out.append("%s=%s"%(k, v))
+            return '{'+(', '.join(out))+'}'
+    %}
+};
+
+%extend SoapySDR::Range
+{
+    %insert("python")
+    %{
+        def __str__(self):
+            return "%s, %s"%(self.minimum(), self.maximum())
+    %}
+};
+
+////////////////////////////////////////////////////////////////////////
+// Stream result class
+// Helps us deal with stream calls that return by reference
+////////////////////////////////////////////////////////////////////////
+%inline %{
+    struct StreamResult
+    {
+        StreamResult(void):
+            ret(0), flags(0), timeNs(0), chanMask(0){}
+        int ret;
+        int flags;
+        long long timeNs;
+        size_t chanMask;
+    };
+%}
+
+%extend StreamResult
+{
+    %insert("python")
+    %{
+        def __str__(self):
+            return "ret=%s, flags=%s, timeNs=%s"%(self.ret, self.flags, self.timeNs)
+    %}
+};
+
+////////////////////////////////////////////////////////////////////////
+// Constants SOAPY_SDR_*
+////////////////////////////////////////////////////////////////////////
+%include <SoapySDR/Constants.h>
+%include <SoapySDR/Errors.h>
+%include <SoapySDR/Version.h>
+%include <SoapySDR/Formats.h>
+
+////////////////////////////////////////////////////////////////////////
+// Utility functions
+////////////////////////////////////////////////////////////////////////
+%include <SoapySDR/Errors.hpp>
+%include <SoapySDR/Version.hpp>
+%include <SoapySDR/Modules.hpp>
+%include <SoapySDR/Formats.hpp>
+
+////////////////////////////////////////////////////////////////////////
+// Device object
+////////////////////////////////////////////////////////////////////////
+%nodefaultctor SoapySDR::Device;
+%include <SoapySDR/Device.hpp>
+
+//global factory lock support
+%pythoncode %{
+
+__all__ = list()
+for key in sorted(globals().keys()):
+    if key.startswith('SOAPY_SDR_'):
+        __all__.append(key)
+%}
+
+//make device a constructable class
+%insert("python")
+%{
+_Device = Device
+class Device(Device):
+    def __new__(cls, *args, **kwargs):
+        return cls.make(*args, **kwargs)
+
+def extractBuffPointer(buff):
+    if hasattr(buff, '__array_interface__'): return buff.__array_interface__['data'][0]
+    if hasattr(buff, '__long__'): return long(buff)
+    if hasattr(buff, '__int__'): return int(buff)
+    raise Exception("Unrecognized data format: " + str(type(buff)))
+%}
+
+%extend SoapySDR::Device
+{
+    StreamResult readStream__(SoapySDR::Stream *stream, const std::vector<size_t> &buffs, const size_t numElems, const int flags, const long timeoutUs)
+    {
+        StreamResult sr;
+        sr.flags = flags;
+        std::vector<void *> ptrs(buffs.size());
+        for (size_t i = 0; i < buffs.size(); i++) ptrs[i] = (void *)buffs[i];
+        sr.ret = self->readStream(stream, (&ptrs[0]), numElems, sr.flags, sr.timeNs, timeoutUs);
+        return sr;
+    }
+
+    StreamResult writeStream__(SoapySDR::Stream *stream, const std::vector<size_t> &buffs, const size_t numElems, const int flags, const long long timeNs, const long timeoutUs)
+    {
+        StreamResult sr;
+        sr.flags = flags;
+        std::vector<const void *> ptrs(buffs.size());
+        for (size_t i = 0; i < buffs.size(); i++) ptrs[i] = (const void *)buffs[i];
+        sr.ret = self->writeStream(stream, (&ptrs[0]), numElems, sr.flags, timeNs, timeoutUs);
+        return sr;
+    }
+
+    StreamResult readStreamStatus__(SoapySDR::Stream *stream, const long timeoutUs)
+    {
+        StreamResult sr;
+        sr.ret = self->readStreamStatus(stream, sr.chanMask, sr.flags, sr.timeNs, timeoutUs);
+        return sr;
+    }
+
+    %insert("python")
+    %{
+        #call unmake from custom deleter
+        def __del__(self):
+            Device.unmake(self)
+
+        def __str__(self):
+            return "%s:%s"%(self.getDriverKey(), self.getHardwareKey())
+
+        def readStream(self, stream, buffs, numElems, flags = 0, timeoutUs = 100000):
+            ptrs = [extractBuffPointer(b) for b in buffs]
+            return self.readStream__(stream, ptrs, numElems, flags, timeoutUs)
+
+        def writeStream(self, stream, buffs, numElems, flags = 0, timeNs = 0, timeoutUs = 100000):
+            ptrs = [extractBuffPointer(b) for b in buffs]
+            return self.writeStream__(stream, ptrs, numElems, flags, timeNs, timeoutUs)
+
+        def readStreamStatus(self, stream, timeoutUs = 100000):
+            return self.readStreamStatus__(stream, timeoutUs)
+    %}
+};
diff --git a/python/apps/MeasureDelay.py b/python/apps/MeasureDelay.py
new file mode 100644
index 0000000..8f3592c
--- /dev/null
+++ b/python/apps/MeasureDelay.py
@@ -0,0 +1,187 @@
+########################################################################
+## Measure round trip delay through RF loopback/leakage
+########################################################################
+
+import SoapySDR
+from SoapySDR import * #SOAPY_SDR_ constants
+import numpy as np
+from scipy import signal
+from optparse import OptionParser
+import time
+import os
+
+def generate_cf32_pulse(numSamps, width=5, scaleFactor=0.3):
+    x = np.linspace(-width, width, numSamps)
+    pulse = np.sinc(x).astype(np.complex64)
+    return pulse*scaleFactor
+
+def measure_delay(
+    args,
+    rate,
+    freq=None,
+    rxBw=None,
+    txBw=None,
+    rxChan=0,
+    txChan=0,
+    rxAnt=None,
+    txAnt=None,
+    rxGain=None,
+    txGain=None,
+    clockRate=None,
+    numTxSamps=200,
+    numRxSamps=10000,
+    dumpDir=None,
+):
+    sdr = SoapySDR.Device(args)
+    if not sdr.hasHardwareTime():
+        raise Exception('this device does not support timed streaming')
+
+    #set clock rate first
+    if clockRate is not None: sdr.setMasterClockRate(clockRate)
+
+    #set sample rate
+    sdr.setSampleRate(SOAPY_SDR_RX, rxChan, rate)
+    sdr.setSampleRate(SOAPY_SDR_TX, txChan, rate)
+    print("Actual Rx Rate %f Msps"%(sdr.getSampleRate(SOAPY_SDR_RX, rxChan)/1e6))
+    print("Actual Tx Rate %f Msps"%(sdr.getSampleRate(SOAPY_SDR_TX, txChan)/1e6))
+
+    #set antenna
+    if rxAnt is not None: sdr.setAntenna(SOAPY_SDR_RX, rxChan, rxAnt)
+    if txAnt is not None: sdr.setAntenna(SOAPY_SDR_TX, txChan, txAnt)
+
+    #set overall gain
+    if rxGain is not None: sdr.setGain(SOAPY_SDR_RX, rxChan, rxGain)
+    if txGain is not None: sdr.setGain(SOAPY_SDR_TX, txChan, txGain)
+
+    #tune frontends
+    if freq is not None: sdr.setFrequency(SOAPY_SDR_RX, rxChan, freq)
+    if freq is not None: sdr.setFrequency(SOAPY_SDR_TX, txChan, freq)
+
+    #set bandwidth
+    if rxBw is not None: sdr.setBandwidth(SOAPY_SDR_RX, rxChan, rxBw)
+    if txBw is not None: sdr.setBandwidth(SOAPY_SDR_TX, txChan, txBw)
+
+    #create rx and tx streams
+    print("Create Rx and Tx streams")
+    rxStream = sdr.setupStream(SOAPY_SDR_RX, "CF32", [rxChan])
+    txStream = sdr.setupStream(SOAPY_SDR_TX, "CF32", [txChan])
+
+    #let things settle
+    time.sleep(1)
+
+    #transmit a pulse in the near future
+    sdr.activateStream(txStream)
+    txPulse = generate_cf32_pulse(numTxSamps)
+    txTime0 = sdr.getHardwareTime() + long(0.1e9) #100ms
+    txFlags = SOAPY_SDR_HAS_TIME | SOAPY_SDR_END_BURST
+    sr = sdr.writeStream(txStream, [txPulse], len(txPulse), txFlags, txTime0)
+    if sr.ret != len(txPulse): raise Exception('transmit failed %s'%str(sr))
+
+    #receive slightly before transmit time
+    rxBuffs = np.array([], np.complex64)
+    rxFlags = SOAPY_SDR_HAS_TIME | SOAPY_SDR_END_BURST
+    #half of the samples come before the transmit time
+    receiveTime = txTime0 - long((numRxSamps/rate)*1e9)/2
+    sdr.activateStream(rxStream, rxFlags, receiveTime, numRxSamps)
+    rxTime0 = None
+
+    #accumulate receive buffer into large contiguous buffer
+    while True:
+        rxBuff = np.array([0]*1024, np.complex64)
+        timeoutUs = long(5e5) #500 ms >> stream time
+        sr = sdr.readStream(rxStream, [rxBuff], len(rxBuff), timeoutUs=timeoutUs)
+
+        #stash time on first buffer
+        if sr.ret > 0 and len(rxBuffs) == 0:
+            rxTime0 = sr.timeNs
+            if (sr.flags & SOAPY_SDR_HAS_TIME) == 0:
+                raise Exception('receive fail - no timestamp on first readStream %s'%(str(sr)))
+
+        #accumulate buffer or exit loop
+        if sr.ret > 0: rxBuffs = np.concatenate((rxBuffs, rxBuff[:sr.ret]))
+        else: break
+
+    #check resulting buffer
+    if len(rxBuffs) != numRxSamps:
+        raise Exception('receive fail - captured samples %d out of %d'%(len(rxBuffs), numRxSamps))
+    if rxTime0 is None:
+        raise Exception('receive fail - no valid timestamp')
+
+    #clear initial samples because transients
+    rxMean = np.mean(rxBuffs)
+    for i in range(len(rxBuffs)/100): rxBuffs[i] = rxMean
+
+    #normalize the samples
+    def normalize(samps):
+        samps = samps - np.mean(samps) #remove dc
+        samps = np.absolute(samps) #magnitude
+        samps = samps/max(samps) #norm ampl to peak
+        #print samps[:100]
+        return samps
+
+    txPulseNorm = normalize(txPulse)
+    rxBuffsNorm = normalize(rxBuffs)
+
+    #dump debug samples
+    if dumpDir is not None:
+        txPulseNorm.tofile(os.path.join(dumpDir, 'txNorm.dat'))
+        rxBuffsNorm.tofile(os.path.join(dumpDir, 'rxNorm.dat'))
+        np.real(rxBuffs).tofile(os.path.join(dumpDir, 'rxRawI.dat'))
+        np.imag(rxBuffs).tofile(os.path.join(dumpDir, 'rxRawQ.dat'))
+
+    #look for the for peak index for time offsets
+    rxArgmaxIndex = np.argmax(rxBuffsNorm)
+    txArgmaxIndex = np.argmax(txPulseNorm)
+
+    #check goodness of peak by comparing argmax and correlation
+    rxCoorIndex = np.argmax(np.correlate(rxBuffsNorm, txPulseNorm))+len(txPulseNorm)/2
+    if abs(rxCoorIndex-rxArgmaxIndex) > len(txPulseNorm)/4:
+        raise Exception('correlation(%d) does not match argmax(%d), probably bad data'%(rxCoorIndex, rxArgmaxIndex))
+
+    #calculate time offset
+    txPeakTime = txTime0 + long((txArgmaxIndex/rate)*1e9)
+    rxPeakTime = rxTime0 + long((rxArgmaxIndex/rate)*1e9)
+    timeDelta = rxPeakTime - txPeakTime
+    print('>>> Time delta %f us'%(timeDelta/1e3))
+
+    #cleanup streams
+    print("Cleanup streams")
+    sdr.deactivateStream(rxStream)
+    sdr.deactivateStream(txStream)
+    sdr.closeStream(rxStream)
+    sdr.closeStream(txStream)
+    print("Done!")
+
+def main():
+    parser = OptionParser()
+    parser.add_option("--args", type="string", dest="args", help="device factor arguments", default="")
+    parser.add_option("--rate", type="float", dest="rate", help="Tx and Rx sample rate", default=1e6)
+    parser.add_option("--rxAnt", type="string", dest="rxAnt", help="Optional Rx antenna", default=None)
+    parser.add_option("--txAnt", type="string", dest="txAnt", help="Optional Tx antenna", default=None)
+    parser.add_option("--rxGain", type="float", dest="rxGain", help="Optional Rx gain (dB)", default=None)
+    parser.add_option("--txGain", type="float", dest="txGain", help="Optional Tx gain (dB)", default=None)
+    parser.add_option("--rxBw", type="float", dest="rxBw", help="Optional Rx filter bw (Hz)", default=None)
+    parser.add_option("--txBw", type="float", dest="txBw", help="Optional Tx filter bw (Hz)", default=None)
+    parser.add_option("--rxChan", type="int", dest="rxChan", help="Receiver channel (def=0)", default=0)
+    parser.add_option("--txChan", type="int", dest="txChan", help="Transmitter channel (def=0)", default=0)
+    parser.add_option("--freq", type="float", dest="freq", help="Optional Tx and Rx freq (Hz)", default=None)
+    parser.add_option("--clockRate", type="float", dest="clockRate", help="Optional clock rate (Hz)", default=None)
+    parser.add_option("--dumpDir", type="string", dest="dumpDir", help="Optional directory to dump debug samples", default=None)
+    (options, args) = parser.parse_args()
+    measure_delay(
+        args=options.args,
+        rate=options.rate,
+        freq=options.freq,
+        rxBw=options.rxBw,
+        txBw=options.txBw,
+        rxAnt=options.rxAnt,
+        txAnt=options.txAnt,
+        rxGain=options.rxGain,
+        txGain=options.txGain,
+        rxChan=options.rxChan,
+        txChan=options.txChan,
+        clockRate=options.clockRate,
+        dumpDir=options.dumpDir,
+    )
+
+if __name__ == '__main__': main()
diff --git a/python/apps/SimpleSiggen.py b/python/apps/SimpleSiggen.py
new file mode 100644
index 0000000..8ea7898
--- /dev/null
+++ b/python/apps/SimpleSiggen.py
@@ -0,0 +1,112 @@
+########################################################################
+## Simple signal generator for testing transmit
+########################################################################
+
+import SoapySDR
+from SoapySDR import * #SOAPY_SDR_ constants
+import numpy as np
+from optparse import OptionParser
+import time
+import os
+import math
+
+def siggen_app(
+    args,
+    rate,
+    ampl=0.7,
+    freq=None,
+    txBw=None,
+    txChan=0,
+    rxChan=0,
+    txGain=None,
+    txAnt=None,
+    clockRate=None,
+    waveFreq=None
+):
+    if waveFreq is None: waveFreq = rate/10
+
+    sdr = SoapySDR.Device(args)
+    #set clock rate first
+    if clockRate is not None: sdr.setMasterClockRate(clockRate)
+
+    #set sample rate
+    sdr.setSampleRate(SOAPY_SDR_TX, txChan, rate)
+    print("Actual Tx Rate %f Msps"%(sdr.getSampleRate(SOAPY_SDR_TX, txChan)/1e6))
+
+    #set bandwidth
+    if txBw is not None: sdr.setBandwidth(SOAPY_SDR_TX, txChan, txBw)
+
+    #set antenna
+    print("Set the antenna")
+    if txAnt is not None: sdr.setAntenna(SOAPY_SDR_TX, txChan, txAnt)
+
+    #set overall gain
+    print("Set the gain")
+    if txGain is not None: sdr.setGain(SOAPY_SDR_TX, txChan, txGain)
+
+    #tune frontends
+    print("Tune the frontend")
+    if freq is not None: sdr.setFrequency(SOAPY_SDR_TX, txChan, freq)
+
+    #tx loop
+    #create tx stream
+    print("Create Tx stream")
+    txStream = sdr.setupStream(SOAPY_SDR_TX, "CF32", [txChan])
+    print("Activate Tx Stream")
+    sdr.activateStream(txStream)
+    phaseAcc = 0
+    phaseInc = 2*math.pi*waveFreq/rate
+    streamMTU = sdr.getStreamMTU(txStream)
+    sampsCh0 = np.array([ampl]*streamMTU, np.complex64)
+    
+    timeLastPrint = time.time()
+    totalSamps = 0
+    while True:
+        phaseAccNext = phaseAcc + streamMTU*phaseInc
+        sampsCh0 = ampl*np.exp(1j*np.linspace(phaseAcc, phaseAccNext, streamMTU)).astype(np.complex64)
+        phaseAcc = phaseAccNext
+        while phaseAcc > math.pi*2: phaseAcc -= math.pi*2
+
+        sr = sdr.writeStream(txStream, [sampsCh0], sampsCh0.size)
+        if sr.ret != sampsCh0.size:
+            raise Exception("Expected writeStream() to consume all samples! %d"%sr.ret)
+        totalSamps += sr.ret
+
+        if time.time() > timeLastPrint + 5.0:
+            print("Python siggen rate: %f Msps"%(totalSamps/(time.time()-timeLastPrint)/1e6))
+            totalSamps = 0
+            timeLastPrint = time.time()
+
+    #cleanup streams
+    print("Cleanup stream")
+    sdr.deactivateStream(txStream)
+    sdr.closeStream(txStream)
+    print("Done!")
+
+def main():
+    parser = OptionParser()
+    parser.add_option("--args", type="string", dest="args", help="device factor arguments", default="")
+    parser.add_option("--rate", type="float", dest="rate", help="Tx and Rx sample rate", default=1e6)
+    parser.add_option("--ampl", type="float", dest="ampl", help="Tx digital amplitude rate", default=0.7)
+    parser.add_option("--txAnt", type="string", dest="txAnt", help="Optional Tx antenna", default=None)
+    parser.add_option("--txGain", type="float", dest="txGain", help="Optional Tx gain (dB)", default=None)
+    parser.add_option("--txChan", type="int", dest="txChan", help="Transmitter channel (def=0)", default=0)
+    parser.add_option("--freq", type="float", dest="freq", help="Optional Tx and Rx freq (Hz)", default=None)
+    parser.add_option("--txBw", type="float", dest="txBw", help="Optional Tx filter bw (Hz)", default=None)
+    parser.add_option("--waveFreq", type="float", dest="waveFreq", help="Baseband waveform freq (Hz)", default=None)
+    parser.add_option("--clockRate", type="float", dest="clockRate", help="Optional clock rate (Hz)", default=None)
+    (options, args) = parser.parse_args()
+    siggen_app(
+        args=options.args,
+        rate=options.rate,
+        ampl=options.ampl,
+        freq=options.freq,
+        txBw=options.txBw,
+        txAnt=options.txAnt,
+        txGain=options.txGain,
+        txChan=options.txChan,
+        clockRate=options.clockRate,
+        waveFreq=options.waveFreq,
+    )
+
+if __name__ == '__main__': main()
diff --git a/python3/CMakeLists.txt b/python3/CMakeLists.txt
new file mode 100644
index 0000000..554bdc2
--- /dev/null
+++ b/python3/CMakeLists.txt
@@ -0,0 +1,95 @@
+########################################################################
+# Project setup
+########################################################################
+cmake_minimum_required(VERSION 2.8)
+project(SoapySDRPython3 CXX)
+enable_testing()
+
+find_package(SoapySDR NO_MODULE REQUIRED)
+list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR})
+
+########################################################################
+# Find SWIG
+########################################################################
+find_package(SWIG)
+message(STATUS "SWIG_FOUND: ${SWIG_FOUND} - ${SWIG_VERSION}")
+
+########################################################################
+# Find python interp
+########################################################################
+find_package(Python3Interp)
+message(STATUS "PYTHON3INTERP_FOUND: ${PYTHON3INTERP_FOUND}")
+message(STATUS "PYTHON3_EXECUTABLE: ${PYTHON3_EXECUTABLE}")
+
+########################################################################
+# Determine install directory
+########################################################################
+execute_process(
+    COMMAND ${PYTHON3_EXECUTABLE} -c
+    "from distutils.sysconfig import get_python_lib; print(get_python_lib(plat_specific=True, prefix=''))"
+    OUTPUT_STRIP_TRAILING_WHITESPACE
+    OUTPUT_VARIABLE PYTHON3_INSTALL_DIR
+)
+set(PYTHON3_INSTALL_DIR ${PYTHON3_INSTALL_DIR} CACHE STRING "python3 install prefix")
+message(STATUS "PYTHON3_INSTALL_DIR: \${prefix}/${PYTHON3_INSTALL_DIR}")
+
+########################################################################
+# Find Python libs
+########################################################################
+find_package(Python3Libs)
+message(STATUS "PYTHON3LIBS_FOUND: ${PYTHON3LIBS_FOUND}")
+message(STATUS "PYTHON3_INCLUDE_DIRS: ${PYTHON3_INCLUDE_DIRS}")
+message(STATUS "PYTHON3_LIBRARIES: ${PYTHON3_LIBRARIES}")
+
+#on windows, we require a pythonxx_d.lib in debug mode
+#require that the PYTHON_DEBUG_LIBRARY flag is set
+#or the build assumes that the debug library DNE
+set(PYTHON3_DEBUG_OK TRUE)
+if(WIN32 AND NOT PYTHON3_DEBUG_LIBRARY AND "${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
+    message(WARNING "WIN32 Debug mode requires PYTHON3_DEBUG_LIBRARY")
+    set(PYTHON3_DEBUG_OK FALSE)
+endif()
+
+########################################################################
+## Feature registration
+########################################################################
+include(FeatureSummary)
+include(CMakeDependentOption)
+cmake_dependent_option(ENABLE_PYTHON3 "Enable python bindings" ON "ENABLE_LIBRARY;SWIG_FOUND;PYTHON3INTERP_FOUND;PYTHON3LIBS_FOUND;PYTHON3_DEBUG_OK" OFF)
+add_feature_info(Python3 ENABLE_PYTHON3 "python3 bindings")
+if (NOT ENABLE_PYTHON3)
+    return()
+endif()
+
+########################################################################
+# Build Module
+########################################################################
+include(UseSWIG)
+include_directories(${SoapySDR_INCLUDE_DIRS})
+include_directories(${PYTHON3_INCLUDE_DIRS})
+
+configure_file(
+    ${CMAKE_CURRENT_SOURCE_DIR}/../python/SoapySDR.i
+    ${CMAKE_CURRENT_BINARY_DIR}/SoapySDR.i
+ at ONLY)
+
+set(CMAKE_SWIG_FLAGS -c++ -threads)
+set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/SoapySDR.i PROPERTIES CPLUSPLUS ON)
+
+SWIG_ADD_MODULE(SoapySDR3 python ${CMAKE_CURRENT_BINARY_DIR}/SoapySDR.i)
+SWIG_LINK_LIBRARIES(SoapySDR3 ${SoapySDR_LIBRARIES} ${PYTHON3_LIBRARIES})
+
+set_target_properties(${SWIG_MODULE_SoapySDR3_REAL_NAME} PROPERTIES OUTPUT_NAME _SoapySDR)
+
+########################################################################
+# Install Module
+########################################################################
+install(
+    TARGETS ${SWIG_MODULE_SoapySDR3_REAL_NAME}
+    DESTINATION ${PYTHON3_INSTALL_DIR}
+)
+
+install(
+    FILES ${CMAKE_CURRENT_BINARY_DIR}/SoapySDR.py
+    DESTINATION ${PYTHON3_INSTALL_DIR}
+)
diff --git a/python3/FindPython3Interp.cmake b/python3/FindPython3Interp.cmake
new file mode 100644
index 0000000..72d5777
--- /dev/null
+++ b/python3/FindPython3Interp.cmake
@@ -0,0 +1,67 @@
+# - Find python interpreter
+# This module finds if Python interpreter is installed and determines where the
+# executables are. This code sets the following variables:
+#
+#  PYTHONINTERP3_FOUND - Was the Python executable found
+#  PYTHON3_EXECUTABLE  - path to the Python interpreter
+#
+
+#=============================================================================
+# Copyright 2005-2009 Kitware, Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above copyright
+#   notice, this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright
+#   notice, this list of conditions and the following disclaimer in the
+#   documentation and/or other materials provided with the distribution.
+#
+# * Neither the names of Kitware, Inc., the Insight Software Consortium,
+#   nor the names of their contributors may be used to endorse or promote
+#   products derived from this software without specific prior written
+#   permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+FIND_PROGRAM(PYTHON3_EXECUTABLE
+  NAMES python3.2mu python3.2m python3.2u python3.2 python3.1 python3.0 python3
+  PATHS
+  [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\3.2\\InstallPath]
+  [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\3.1\\InstallPath]
+  [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\3.0\\InstallPath]
+  )
+
+FIND_PROGRAM(PYTHON3_DBG_EXECUTABLE
+  NAMES python3.2dmu python3.2dm python3.2du python3.2d python3.1-dbg python3.0-dbg python3-dbg
+  PATHS
+  [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\3.2\\InstallPath]
+  [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\3.1\\InstallPath]
+  [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\3.0\\InstallPath]
+  )
+
+
+# handle the QUIETLY and REQUIRED arguments and set PYTHONINTERP_FOUND to TRUE if
+# all listed variables are TRUE
+INCLUDE(FindPackageHandleStandardArgs)
+FIND_PACKAGE_HANDLE_STANDARD_ARGS(Python3Interp DEFAULT_MSG PYTHON3_EXECUTABLE)
+FIND_PACKAGE_HANDLE_STANDARD_ARGS(Python3InterpDbg DEFAULT_MSG PYTHON3_DBG_EXECUTABLE)
+
+MARK_AS_ADVANCED(PYTHON3_EXECUTABLE)
+MARK_AS_ADVANCED(PYTHON3_DBG_EXECUTABLE)
+
+
diff --git a/python3/FindPython3Libs.cmake b/python3/FindPython3Libs.cmake
new file mode 100644
index 0000000..8994f0c
--- /dev/null
+++ b/python3/FindPython3Libs.cmake
@@ -0,0 +1,246 @@
+# - Find python libraries
+# This module finds if Python is installed and determines where the
+# include files and libraries are. It also determines what the name of
+# the library is. This code sets the following variables:
+#
+#  PYTHONLIBS3_FOUND       - have the Python libs been found
+#  PYTHON3_LIBRARIES       - path to the python library
+#  PYTHON3_INCLUDE_DIRS    - path to where Python.h is found
+#  PYTHON3_DEBUG_LIBRARIES - path to the debug library
+#
+
+#=============================================================================
+# Copyright 2001-2009 Kitware, Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above copyright
+#   notice, this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright
+#   notice, this list of conditions and the following disclaimer in the
+#   documentation and/or other materials provided with the distribution.
+#
+# * Neither the names of Kitware, Inc., the Insight Software Consortium,
+#   nor the names of their contributors may be used to endorse or promote
+#   products derived from this software without specific prior written
+#   permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+INCLUDE(CMakeFindFrameworks)
+# Is there a python3 framework? How do we search for it?
+# Search for the python framework on Apple.
+# CMAKE_FIND_FRAMEWORKS(Python)
+
+FOREACH(_CURRENT_VERSION 3.5 3.4 3.3 3.2 3.1 3.0)
+  IF(_CURRENT_VERSION GREATER 3.1)
+      SET(_32FLAGS "m" "u" "mu" "dm" "du" "dmu" "")
+  ELSE()
+      SET(_32FLAGS "")
+  ENDIF()
+  FOREACH(_COMPILATION_FLAGS ${_32FLAGS})
+      STRING(REPLACE "." "" _CURRENT_VERSION_NO_DOTS ${_CURRENT_VERSION})
+      IF(WIN32)
+        IF(_CURRENT_VERSION GREATER 3.1)
+            FIND_LIBRARY(PYTHON3_DEBUG_LIBRARY
+                NAMES python${_CURRENT_VERSION_NO_DOTS}d${_COMPILATION_FLAGS} python
+              PATHS
+              [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\${_CURRENT_VERSION}\\InstallPath]/libs/Debug
+              [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\${_CURRENT_VERSION}\\InstallPath]/libs )
+        ELSE()
+            FIND_LIBRARY(PYTHON3_DEBUG_LIBRARY
+                NAMES python${_CURRENT_VERSION_NO_DOTS}${_COMPILATION_FLAGS}_d python
+              PATHS
+              [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\${_CURRENT_VERSION}\\InstallPath]/libs/Debug
+              [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\${_CURRENT_VERSION}\\InstallPath]/libs )
+        ENDIF()
+      ENDIF(WIN32)
+
+      FIND_LIBRARY(PYTHON3_LIBRARY
+          NAMES python${_CURRENT_VERSION_NO_DOTS}${_COMPILATION_FLAGS} python${_CURRENT_VERSION}${_COMPILATION_FLAGS}
+        PATHS
+          [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\${_CURRENT_VERSION}\\InstallPath]/libs
+          # Avoid finding the .dll in the PATH.  We want the .lib.
+          NO_SYSTEM_ENVIRONMENT_PATH
+      )
+      # Look for the static library in the Python config directory
+      FIND_LIBRARY(PYTHON3_LIBRARY
+        NAMES python${_CURRENT_VERSION_NO_DOTS}${_COMPILATION_FLAGS} python${_CURRENT_VERSION}${_COMPILATION_FLAGS}
+        # Avoid finding the .dll in the PATH.  We want the .lib.
+        NO_SYSTEM_ENVIRONMENT_PATH
+        # This is where the static library is usually located
+        PATH_SUFFIXES python${_CURRENT_VERSION}/config
+      )
+
+      IF(_CURRENT_VERSION GREATER 3.1)
+          FIND_LIBRARY(PYTHON3_DEBUG_LIBRARY
+              NAMES python${_CURRENT_VERSION_NO_DOTS}d${_COMPILATION_FLAGS} python${_CURRENT_VERSION}d${_COMPILATION_FLAGS}
+            PATHS
+              [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\${_CURRENT_VERSION}\\InstallPath]/libs
+              # Avoid finding the .dll in the PATH.  We want the .lib.
+              NO_SYSTEM_ENVIRONMENT_PATH
+          )
+          # Look for the static library in the Python config directory
+          FIND_LIBRARY(PYTHON3_DEBUG_LIBRARY
+            NAMES python${_CURRENT_VERSION_NO_DOTS}d${_COMPILATION_FLAGS} python${_CURRENT_VERSION}d${_COMPILATION_FLAGS}
+            # Avoid finding the .dll in the PATH.  We want the .lib.
+            NO_SYSTEM_ENVIRONMENT_PATH
+            # This is where the static library is usually located
+            PATH_SUFFIXES python${_CURRENT_VERSION}/config
+          )
+      ENDIF()
+
+#  SET(PYTHON_FRAMEWORK_INCLUDES)
+#  IF(Python_FRAMEWORKS AND NOT PYTHON_INCLUDE_DIR)
+#    FOREACH(dir ${Python_FRAMEWORKS})
+#      SET(PYTHON_FRAMEWORK_INCLUDES ${PYTHON_FRAMEWORK_INCLUDES}
+#        ${dir}/Versions/${_CURRENT_VERSION}/include/python${_CURRENT_VERSION})
+#    ENDFOREACH(dir)
+#  ENDIF(Python_FRAMEWORKS AND NOT PYTHON_INCLUDE_DIR)
+
+      FIND_PATH(PYTHON3_INCLUDE_DIR
+        NAMES Python.h
+        PATHS
+          ${PYTHON_FRAMEWORK_INCLUDES}
+          [HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore\\${_CURRENT_VERSION}\\InstallPath]/include
+        PATH_SUFFIXES
+        python${_CURRENT_VERSION}${_COMPILATION_FLAGS}
+      )
+
+      # For backward compatibility, set PYTHON_INCLUDE_PATH, but make it internal.
+      SET(PYTHON_INCLUDE_PATH "${PYTHON_INCLUDE_DIR}" CACHE INTERNAL 
+        "Path to where Python.h is found (deprecated)")
+  ENDFOREACH(_COMPILATION_FLAGS)
+ENDFOREACH(_CURRENT_VERSION)
+
+MARK_AS_ADVANCED(
+  PYTHON3_DEBUG_LIBRARY
+  PYTHON3_LIBRARY
+  PYTHON3_INCLUDE_DIR
+)
+
+# We use PYTHON3_INCLUDE_DIR, PYTHON3_LIBRARY and PYTHON3_DEBUG_LIBRARY for the
+# cache entries because they are meant to specify the location of a single
+# library. We now set the variables listed by the documentation for this
+# module.
+SET(PYTHON3_INCLUDE_DIRS "${PYTHON3_INCLUDE_DIR}")
+SET(PYTHON3_LIBRARIES "${PYTHON3_LIBRARY}")
+SET(PYTHON3_DEBUG_LIBRARIES "${PYTHON3_DEBUG_LIBRARY}")
+
+#when all else fails, use the python3-config executable
+if (NOT PYTHON3_LIBRARIES OR NOT PYTHON3_INCLUDE_DIRS)
+    find_program(PYTHON3_CONFIG_EXECUTABLE python3-config)
+    if(PYTHON3_CONFIG_EXECUTABLE)
+        execute_process(
+            COMMAND ${PYTHON3_CONFIG_EXECUTABLE}  --includes
+            OUTPUT_STRIP_TRAILING_WHITESPACE
+            OUTPUT_VARIABLE PYTHON3_INCLUDE_DIRS)
+        string(REGEX REPLACE "^[-I]" "" PYTHON3_INCLUDE_DIRS "${PYTHON3_INCLUDE_DIRS}")
+        string(REGEX REPLACE "[ ]-I" " " PYTHON3_INCLUDE_DIRS "${PYTHON3_INCLUDE_DIRS}")
+        separate_arguments(PYTHON3_INCLUDE_DIRS)
+        execute_process(
+            COMMAND ${PYTHON3_CONFIG_EXECUTABLE}  --ldflags
+            OUTPUT_STRIP_TRAILING_WHITESPACE
+            OUTPUT_VARIABLE PYTHON3_LIBRARIES)
+    endif(PYTHON3_CONFIG_EXECUTABLE)
+endif()
+
+INCLUDE(FindPackageHandleStandardArgs)
+FIND_PACKAGE_HANDLE_STANDARD_ARGS(Python3Libs DEFAULT_MSG PYTHON3_LIBRARIES PYTHON3_INCLUDE_DIRS)
+
+
+# PYTHON_ADD_MODULE(<name> src1 src2 ... srcN) is used to build modules for python.
+# PYTHON_WRITE_MODULES_HEADER(<filename>) writes a header file you can include 
+# in your sources to initialize the static python modules
+
+GET_PROPERTY(_TARGET_SUPPORTS_SHARED_LIBS
+  GLOBAL PROPERTY TARGET_SUPPORTS_SHARED_LIBS)
+
+FUNCTION(PYTHON3_ADD_MODULE _NAME )
+  OPTION(PYTHON3_ENABLE_MODULE_${_NAME} "Add module ${_NAME}" TRUE)
+  OPTION(PYTHON3_MODULE_${_NAME}_BUILD_SHARED
+    "Add module ${_NAME} shared" ${_TARGET_SUPPORTS_SHARED_LIBS})
+
+  # Mark these options as advanced
+  MARK_AS_ADVANCED(PYTHON3_ENABLE_MODULE_${_NAME}
+    PYTHON3_MODULE_${_NAME}_BUILD_SHARED)
+
+  IF(PYTHON3_ENABLE_MODULE_${_NAME})
+    IF(PYTHON3_MODULE_${_NAME}_BUILD_SHARED)
+      SET(PY_MODULE_TYPE MODULE)
+    ELSE(PYTHON3_MODULE_${_NAME}_BUILD_SHARED)
+      SET(PY_MODULE_TYPE STATIC)
+      SET_PROPERTY(GLOBAL  APPEND  PROPERTY  PY_STATIC_MODULES_LIST ${_NAME})
+    ENDIF(PYTHON3_MODULE_${_NAME}_BUILD_SHARED)
+
+    SET_PROPERTY(GLOBAL  APPEND  PROPERTY  PY_MODULES_LIST ${_NAME})
+    ADD_LIBRARY(${_NAME} ${PY_MODULE_TYPE} ${ARGN})
+#    TARGET_LINK_LIBRARIES(${_NAME} ${PYTHON_LIBRARIES})
+
+  ENDIF(PYTHON3_ENABLE_MODULE_${_NAME})
+ENDFUNCTION(PYTHON3_ADD_MODULE)
+
+FUNCTION(PYTHON3_WRITE_MODULES_HEADER _filename)
+
+  GET_PROPERTY(PY_STATIC_MODULES_LIST  GLOBAL  PROPERTY PY_STATIC_MODULES_LIST)
+
+  GET_FILENAME_COMPONENT(_name "${_filename}" NAME)
+  STRING(REPLACE "." "_" _name "${_name}")
+  STRING(TOUPPER ${_name} _nameUpper)
+
+  SET(_filenameTmp "${_filename}.in")
+  FILE(WRITE ${_filenameTmp} "/*Created by cmake, do not edit, changes will be lost*/\n")
+  FILE(APPEND ${_filenameTmp} 
+"#ifndef ${_nameUpper}
+#define ${_nameUpper}
+
+#include <Python.h>
+
+#ifdef __cplusplus
+extern \"C\" {
+#endif /* __cplusplus */
+
+")
+
+  FOREACH(_currentModule ${PY_STATIC_MODULES_LIST})
+    FILE(APPEND ${_filenameTmp} "extern void init${PYTHON_MODULE_PREFIX}${_currentModule}(void);\n\n")
+  ENDFOREACH(_currentModule ${PY_STATIC_MODULES_LIST})
+
+  FILE(APPEND ${_filenameTmp} 
+"#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+")
+
+
+  FOREACH(_currentModule ${PY_STATIC_MODULES_LIST})
+    FILE(APPEND ${_filenameTmp} "int ${_name}_${_currentModule}(void) \n{\n  static char name[]=\"${PYTHON_MODULE_PREFIX}${_currentModule}\"; return PyImport_AppendInittab(name, init${PYTHON_MODULE_PREFIX}${_currentModule});\n}\n\n")
+  ENDFOREACH(_currentModule ${PY_STATIC_MODULES_LIST})
+
+  FILE(APPEND ${_filenameTmp} "void ${_name}_LoadAllPythonModules(void)\n{\n")
+  FOREACH(_currentModule ${PY_STATIC_MODULES_LIST})
+    FILE(APPEND ${_filenameTmp} "  ${_name}_${_currentModule}();\n")
+  ENDFOREACH(_currentModule ${PY_STATIC_MODULES_LIST})
+  FILE(APPEND ${_filenameTmp} "}\n\n")
+  FILE(APPEND ${_filenameTmp} "#ifndef EXCLUDE_LOAD_ALL_FUNCTION\nvoid CMakeLoadAllPythonModules(void)\n{\n  ${_name}_LoadAllPythonModules();\n}\n#endif\n\n#endif\n")
+  
+# with CONFIGURE_FILE() cmake complains that you may not use a file created using FILE(WRITE) as input file for CONFIGURE_FILE()
+  EXECUTE_PROCESS(COMMAND ${CMAKE_COMMAND} -E copy_if_different "${_filenameTmp}" "${_filename}" OUTPUT_QUIET ERROR_QUIET)
+
+ENDFUNCTION(PYTHON3_WRITE_MODULES_HEADER)
+
diff --git a/python3/README.md b/python3/README.md
new file mode 100644
index 0000000..48b70bf
--- /dev/null
+++ b/python3/README.md
@@ -0,0 +1,11 @@
+# Build Python version 3 bindings only
+
+The python3/ directory is essentially a copy of the top level python/ directory,
+however it looks solely for the Python version 3 executable and development files.
+This directory is only activated when the top level python/ directory
+is configured to build for a Python version 2 environment.
+
+The purpose of the python3/ directory is allow for building both
+Python version 2 and Python version 3 language bindings
+on systems that support both Python installs simultaneously.
+This also allows the debian packaging to build debs for both versions as well.
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
new file mode 100644
index 0000000..674f300
--- /dev/null
+++ b/tests/CMakeLists.txt
@@ -0,0 +1,21 @@
+########################################################################
+## Feature registration
+########################################################################
+include(FeatureSummary)
+include(CMakeDependentOption)
+cmake_dependent_option(ENABLE_TESTS "Enable library unit tests" ON "ENABLE_LIBRARY" OFF)
+add_feature_info(Tests ENABLE_TESTS "library unit tests")
+if (NOT ENABLE_TESTS)
+    return()
+endif()
+
+########################################################################
+# Unit tests
+########################################################################
+add_executable(TestTimeConversion TestTimeConversion.cpp)
+target_link_libraries(TestTimeConversion SoapySDR)
+add_test(TestTimeConversion TestTimeConversion)
+
+add_executable(TestFormatParser TestFormatParser.cpp)
+target_link_libraries(TestFormatParser SoapySDR)
+add_test(TestFormatParser TestFormatParser)
diff --git a/tests/TestFormatParser.cpp b/tests/TestFormatParser.cpp
new file mode 100644
index 0000000..5b86629
--- /dev/null
+++ b/tests/TestFormatParser.cpp
@@ -0,0 +1,46 @@
+// Copyright (c) 2015-2015 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#include <SoapySDR/Formats.hpp>
+#include <cstdlib>
+#include <cstdio>
+
+int main(void)
+{
+    #define formatCheck(formatStr, expectedSize) \
+    { \
+        size_t bytes = SoapySDR::formatToSize(formatStr); \
+        printf("%s -> %d bytes/element\t", formatStr, int(bytes)); \
+        if (bytes != expectedSize) \
+        { \
+            printf("FAIL: expected %d bytes!\n", int(expectedSize)); \
+            return EXIT_FAILURE; \
+        } \
+        else printf("OK\n"); \
+    }
+
+    formatCheck(SOAPY_SDR_CF64, 16);
+    formatCheck(SOAPY_SDR_CF32, 8);
+    formatCheck(SOAPY_SDR_CS32, 8);
+    formatCheck(SOAPY_SDR_CU32, 8);
+    formatCheck(SOAPY_SDR_CS16, 4);
+    formatCheck(SOAPY_SDR_CU16, 4);
+    formatCheck(SOAPY_SDR_CS12, 3);
+    formatCheck(SOAPY_SDR_CU12, 3);
+    formatCheck(SOAPY_SDR_CS8, 2);
+    formatCheck(SOAPY_SDR_CU8, 2);
+    formatCheck(SOAPY_SDR_CS4, 1);
+    formatCheck(SOAPY_SDR_CU4, 1);
+
+    formatCheck(SOAPY_SDR_F64, 8);
+    formatCheck(SOAPY_SDR_F32, 4);
+    formatCheck(SOAPY_SDR_S32, 4);
+    formatCheck(SOAPY_SDR_U32, 4);
+    formatCheck(SOAPY_SDR_S16, 2);
+    formatCheck(SOAPY_SDR_U16, 2);
+    formatCheck(SOAPY_SDR_S8, 1);
+    formatCheck(SOAPY_SDR_U8, 1);
+
+    printf("DONE!\n");
+    return EXIT_SUCCESS;
+}
diff --git a/tests/TestTimeConversion.cpp b/tests/TestTimeConversion.cpp
new file mode 100644
index 0000000..7ff6604
--- /dev/null
+++ b/tests/TestTimeConversion.cpp
@@ -0,0 +1,90 @@
+// Copyright (c) 2015-2015 Josh Blum
+// SPDX-License-Identifier: BSL-1.0
+
+#include <SoapySDR/Time.hpp>
+#include <cstdlib>
+#include <cstdio>
+#include <cmath>
+
+static bool loopbackTimeToTicks(const long long timeNs, const double rate)
+{
+    const long long ticks = SoapySDR::timeNsToTicks(timeNs, rate);
+    const long long outTime = SoapySDR::ticksToTimeNs(ticks, rate);
+    //we expect error because timeNs specifies a sub-tick
+    if (std::abs(timeNs - outTime)/1e9 < rate) return true;
+    printf("FAIL: loopbackTimeToTicks(%lld, %f)\n", timeNs, rate);
+    printf("    ticks = %lld, outTime = %lld\n", ticks, outTime);
+    printf("    error = %f secs\n", std::abs(timeNs - outTime)/1e9);
+    return false;
+}
+
+static bool loopbackTicksToTime(const long long ticks, const double rate)
+{
+    const long long timeNs = SoapySDR::ticksToTimeNs(ticks, rate);
+    const long long outTicks = SoapySDR::timeNsToTicks(timeNs, rate);
+    if (std::abs(ticks - outTicks) == 0) return true;
+    printf("FAIL: loopbackTicksToTime(%lld, %f)\n", ticks, rate);
+    printf("    timeNs = %lld, outTicks = %lld\n", timeNs, outTicks);
+    printf("    error = %d ticks\n", int(std::abs(ticks - outTicks)));
+    return false;
+}
+
+//http://stackoverflow.com/questions/8120062/generate-random-64-bit-integer
+static unsigned rand256(void)
+{
+    static unsigned const limit = RAND_MAX - RAND_MAX % 256;
+    unsigned result = std::rand();
+    while ( result >= limit )
+    {
+        result = std::rand();
+    }
+    return result % 256;
+}
+
+static unsigned long long rand64bits(void)
+{
+    unsigned long long results = 0ULL;
+    for ( int count = 8; count > 0; -- count)
+    {
+        results = 256U * results + rand256();
+    }
+    return results;
+}
+
+int main(void)
+{
+    //test that random times can make it through the conversion
+    printf("Test random times...\n");
+    for (size_t i = 0; i < 100; i++)
+    {
+        const long long timeNs = rand64bits();
+        if (not loopbackTimeToTicks(timeNs, 1e9)) return EXIT_FAILURE;
+        if (not loopbackTimeToTicks(-timeNs, 1e9)) return EXIT_FAILURE;
+        if (not loopbackTimeToTicks(timeNs, 52e6)) return EXIT_FAILURE;
+        if (not loopbackTimeToTicks(-timeNs, 52e6)) return EXIT_FAILURE;
+        if (not loopbackTimeToTicks(timeNs, 61.44e6)) return EXIT_FAILURE;
+        if (not loopbackTimeToTicks(-timeNs, 61.44e6)) return EXIT_FAILURE;
+        if (not loopbackTimeToTicks(timeNs, 100e6/3)) return EXIT_FAILURE;
+        if (not loopbackTimeToTicks(-timeNs, 100e6/3)) return EXIT_FAILURE;
+    }
+    printf("OK\n");
+
+    //test random ticks for several different rates
+    printf("Test random ticks...\n");
+    for (size_t i = 0; i < 100; i++)
+    {
+        const long long ticks = rand64bits() >> 8; //room for max rate
+        if (not loopbackTicksToTime(ticks, 1e9)) return EXIT_FAILURE;
+        if (not loopbackTicksToTime(-ticks, 1e9)) return EXIT_FAILURE;
+        if (not loopbackTicksToTime(ticks, 52e6)) return EXIT_FAILURE;
+        if (not loopbackTicksToTime(-ticks, 52e6)) return EXIT_FAILURE;
+        if (not loopbackTicksToTime(ticks, 61.44e6)) return EXIT_FAILURE;
+        if (not loopbackTicksToTime(-ticks, 61.44e6)) return EXIT_FAILURE;
+        if (not loopbackTicksToTime(ticks, 100e6/3)) return EXIT_FAILURE;
+        if (not loopbackTicksToTime(-ticks, 100e6/3)) return EXIT_FAILURE;
+    }
+    printf("OK\n");
+
+    printf("DONE!\n");
+    return EXIT_SUCCESS;
+}

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



More information about the pkg-hamradio-commits mailing list