First work on going to Pybind11. Rewritten the code for the UlDaq significantly. Much cleaner, but still contains bugs.

This commit is contained in:
Anne de Jong 2022-05-23 17:26:29 +02:00
parent 9e03f5e944
commit f635cac209
38 changed files with 2163 additions and 3812 deletions

1
.gitignore vendored
View File

@ -31,3 +31,4 @@ test/test_uldaq
lasp/device/lasp_daq.cxx
lasp/c/lasp_config.h
compile_commands.json
.cache

6
.gitmodules vendored
View File

@ -1,3 +1,9 @@
[submodule "STL-Threadsafe"]
path = STL-Threadsafe
url = https://github.com/miachm/STL-Threadsafe
[submodule "gsl-lite"]
path = gsl-lite
url = https://github.com/gsl-lite/gsl-lite
[submodule "DebugTrace-cpp"]
path = DebugTrace-cpp
url = https://github.com/MasatoKokubo/DebugTrace-cpp

View File

@ -5,11 +5,13 @@ cmake_policy(SET CMP0079 NEW)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/lasp/cmake")
include("BuildType")
# This is used for code completion in vim
set(CMAKE_EXPORT_COMPILE_COMMANDS=ON)
project(LASP LANGUAGES C CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_C_STANDARD 11)
# Whether we want to use blas yes or no
option(LASP_USE_BLAS "Use external blas library for math" ON)
option(LASP_ARM "Compile subset of code for ARM real time (Bela board)" OFF)
@ -24,6 +26,11 @@ set(LASP_TRACERNAME "defaulttracer" CACHE STRING "Name of tracer variable contai
set(LASP_FFT_BACKEND "FFTW" CACHE STRING "FFT Library backend")
set_property(CACHE LASP_FFT_BACKEND PROPERTY STRINGS "FFTW" "FFTPack" "None")
set(PYBIND11_FINDPYTHON ON)
find_package(pybind11 CONFIG REQUIRED)
# Required for PYBIND11
set(POSITION_INDEPENDENT_CODE True)
if(CMAKE_BUILD_TYPE STREQUAL Debug)
set(LASP_DEBUG True)
else()
@ -48,8 +55,6 @@ endif()
add_definitions(-DLASP_MAX_NFFT=33554432) # 2**25
# ####################################### End of user-adjustable variables section
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_C_STANDARD 11)
if(LASP_FFT_BACKEND STREQUAL "FFTW")
find_library(FFTW_LIBRARY NAMES fftw3 fftw)
@ -87,7 +92,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
add_definitions(-DHAS_RTAUDIO_WIN_WASAPI_API)
else() # Linux compile
set(win32 false)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c11 -Werror=incompatible-pointer-types")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror=incompatible-pointer-types")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -Wfatal-errors")
include_directories(/usr/local/include/rtaudio)
@ -98,34 +103,10 @@ else() # Linux compile
# well.
endif()
set(CYTHON_FLAGS "--fast-fail")
if(LASP_DEBUG)
set(LASP_DEBUG_CYTHON=True)
message("Building debug code")
# This will produce html files
# set(CYTHON_ANNOTATE ON)
# Add the __FILENAME__ macro
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D__FILENAME__='\"$(subst ${CMAKE_SOURCE_DIR}/,,$(abspath $<))\"'")
else()
message("Building LASP for release")
set(LASP_DEBUG_CYTHON=False)
set(CYTHON_ANNOTATE OFF)
set(CYTHON_NO_DOCSTRINGS ON)
# Strip unnecessary symbols
# set(CMAKE_SHARED_LINKER_FLAGS "-Wl,--gc-sections")
# set(CMAKE_MODULE_LINKER_FLAGS "-Wl,--gc-sections")
endif(LASP_DEBUG)
# The last argument here takes care of calling SIGABRT when an integer overflow
# occures.
############################## General compilation flags (independent of debug mode, windows or linux)
set(CYTHON_EXTRA_C_FLAGS "-Wno-sign-compare -Wno-cpp -Wno-implicit-fallthrough -Wno-incompatible-pointer-types -Wno-strict-aliasing")
set(CYTHON_EXTRA_CXX_FLAGS "-Wno-sign-compare -Wno-cpp -Wno-implicit-fallthrough -Wno-strict-aliasing")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra \
-Wno-type-limits")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-type-limits")
# Debug make flags
set(CMAKE_C_FLAGS_DEBUG "-g" )
@ -137,8 +118,8 @@ set(CMAKE_C_FLAGS_RELEASE "-O2 -mfpmath=sse -march=x86-64 -mtune=native \
# Python searching.
set(Python_ADDITIONAL_VERSIONS "3.8")
set(python_version_windll "38")
# set(Python_ADDITIONAL_VERSIONS "3.8")
# set(python_version_windll "38")
find_package(PythonLibs REQUIRED )
find_package(PythonInterp REQUIRED)
@ -152,7 +133,10 @@ endif()
include_directories(lasp/c)
include_directories(STL-Threadsafe/include)
include_directories(gsl-lite/include)
include_directories(DebugTrace-cpp/include)
add_subdirectory(lasp)
add_subdirectory(gsl-lite)
add_subdirectory(test)
# set(SETUP_PY_IN "${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in")

1
DebugTrace-cpp Submodule

@ -0,0 +1 @@
Subproject commit 9b143ea40a34d6268d671ea54cd0ba80396b6363

1
gsl-lite Submodule

@ -0,0 +1 @@
Subproject commit 4720a2980a30da085b4ddb4a0ea2a71af7351a48

View File

@ -3,22 +3,12 @@ configure_file(config.pxi.in config.pxi)
# This is used for code completion in vim
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
set(CYTHON_EXECUTABLE "cython3")
include(UseCython)
include(FindNumpy)
include_directories(
${PYTHON_NUMPY_INCLUDE_DIR}
.
c
)
add_subdirectory(c)
add_subdirectory(device)
set_source_files_properties(wrappers.c PROPERTIES COMPILE_FLAGS "${CMAKE_C_FLAGS} ${CYTHON_EXTRA_C_FLAGS}")
cython_add_module(wrappers wrappers.pyx)
target_link_libraries(wrappers lasp_lib ${LASP_THREADING_LIBRARIES})
if(win32)
target_link_libraries(wrappers python${python_version_windll})
endif(win32)
# set_source_files_properties(wrappers.c PROPERTIES COMPILE_FLAGS "${CMAKE_C_FLAGS} ${CYTHON_EXTRA_C_FLAGS}")
# cython_add_module(wrappers wrappers.pyx)
# target_link_libraries(wrappers lasp_lib ${LASP_THREADING_LIBRARIES})
# if(win32)
# target_link_libraries(wrappers python${python_version_windll})
# endif(win32)

View File

@ -11,6 +11,8 @@
/* Debug flag */
#cmakedefine01 LASP_DEBUG
#if LASP_DEBUG == 1
#define TRACER 1
#define TRACERNAME @LASP_TRACERNAME@
@ -23,9 +25,10 @@
#define FFTW 1
#define FFTPack 2
#define None 0
#cmakedefine LASP_FFT_BACKEND @LASP_FFT_BACKEND@
#cmakedefine LASP_HAS_RTAUDIO
#cmakedefine LASP_HAS_ULDAQ
#cmakedefine01 LASP_HAS_RTAUDIO
#cmakedefine01 LASP_HAS_ULDAQ
#cmakedefine01 LASP_DOUBLE_PRECISION
#cmakedefine01 LASP_USE_BLAS

View File

@ -38,11 +38,11 @@ typedef double d; /* Shortcut for double */
#error LASP_FLOAT_SIZE should be either 32 or 64
#endif
#include <complex.h>
#ifdef __cplusplus
#include <complex>
typedef std::complex<d> c;
#else
#include <complex.h>
#if LASP_FLOAT_SIZE == 32
typedef float complex c;
#else

View File

@ -1,44 +0,0 @@
# Find the Cython compiler.
#
# This code sets the following variables:
#
# CYTHON_EXECUTABLE
#
# See also UseCython.cmake
#=============================================================================
# Copyright 2011 Kitware, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#=============================================================================
# Use the Cython executable that lives next to the Python executable
# if it is a local installation.
find_package( PythonInterp )
if( PYTHONINTERP_FOUND )
get_filename_component( _python_path ${PYTHON_EXECUTABLE} PATH )
find_program( CYTHON_EXECUTABLE
NAMES cython cython.bat cython3 cython2
HINTS ${_python_path}
)
else()
find_program( CYTHON_EXECUTABLE
NAMES cython cython.bat cython3
)
endif()
include( FindPackageHandleStandardArgs )
FIND_PACKAGE_HANDLE_STANDARD_ARGS( Cython REQUIRED_VARS CYTHON_EXECUTABLE )
mark_as_advanced( CYTHON_EXECUTABLE )

View File

@ -1,310 +0,0 @@
# Define a function to create Cython modules.
#
# For more information on the Cython project, see http://cython.org/.
# "Cython is a language that makes writing C extensions for the Python language
# as easy as Python itself."
#
# This file defines a CMake function to build a Cython Python module.
# To use it, first include this file.
#
# include( UseCython )
#
# Then call cython_add_module to create a module.
#
# cython_add_module( <module_name> <src1> <src2> ... <srcN> )
#
# To create a standalone executable, the function
#
# cython_add_standalone_executable( <executable_name> [MAIN_MODULE src1] <src1> <src2> ... <srcN> )
#
# To avoid dependence on Python, set the PYTHON_LIBRARY cache variable to point
# to a static library. If a MAIN_MODULE source is specified,
# the "if __name__ == '__main__':" from that module is used as the C main() method
# for the executable. If MAIN_MODULE, the source with the same basename as
# <executable_name> is assumed to be the MAIN_MODULE.
#
# Where <module_name> is the name of the resulting Python module and
# <src1> <src2> ... are source files to be compiled into the module, e.g. *.pyx,
# *.py, *.c, *.cxx, etc. A CMake target is created with name <module_name>. This can
# be used for target_link_libraries(), etc.
#
# The sample paths set with the CMake include_directories() command will be used
# for include directories to search for *.pxd when running the Cython complire.
#
# Cache variables that effect the behavior include:
#
# CYTHON_ANNOTATE
# CYTHON_NO_DOCSTRINGS
# CYTHON_FLAGS
#
# Source file properties that effect the build process are
#
# CYTHON_IS_CXX
#
# If this is set of a *.pyx file with CMake set_source_files_properties()
# command, the file will be compiled as a C++ file.
#
# See also FindCython.cmake
#=============================================================================
# Copyright 2011 Kitware, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#=============================================================================
# Configuration options.
set( CYTHON_ANNOTATE OFF
CACHE BOOL "Create an annotated .html file when compiling *.pyx." )
set( CYTHON_NO_DOCSTRINGS OFF
CACHE BOOL "Strip docstrings from the compiled module." )
set( CYTHON_FLAGS "" CACHE STRING
"Extra flags to the cython compiler." )
mark_as_advanced( CYTHON_ANNOTATE CYTHON_NO_DOCSTRINGS CYTHON_FLAGS )
find_package( Cython REQUIRED )
set( CYTHON_CXX_EXTENSION "cxx" )
set( CYTHON_C_EXTENSION "c" )
# Create a *.c or *.cxx file from a *.pyx file.
# Input the generated file basename. The generate file will put into the variable
# placed in the "generated_file" argument. Finally all the *.py and *.pyx files.
function( compile_pyx _name generated_file )
# Default to assuming all files are C.
set( cxx_arg "" )
set( extension ${CYTHON_C_EXTENSION} )
set( pyx_lang "C" )
set( comment "Compiling Cython C source for ${_name}..." )
set( cython_include_directories "" )
set( pxd_dependencies "" )
set( pxi_dependencies "" )
set( c_header_dependencies "" )
set( pyx_locations "" )
foreach( pyx_file ${ARGN} )
get_filename_component( pyx_file_basename "${pyx_file}" NAME_WE )
# Determine if it is a C or C++ file.
get_source_file_property( property_is_cxx ${pyx_file} CYTHON_IS_CXX )
if( ${property_is_cxx} )
set( cxx_arg "--cplus" )
set( extension ${CYTHON_CXX_EXTENSION} )
set( pyx_lang "CXX" )
set( comment "Compiling Cython CXX source for ${_name}..." )
endif()
# Get the include directories.
get_source_file_property( pyx_location ${pyx_file} LOCATION )
get_filename_component( pyx_path ${pyx_location} PATH )
message(${pyx_path})
get_directory_property( cmake_include_directories DIRECTORY ${pyx_path} INCLUDE_DIRECTORIES )
list( APPEND cython_include_directories ${cmake_include_directories} )
list( APPEND pyx_locations "${pyx_location}" )
# Determine dependencies.
# Add the pxd file will the same name as the given pyx file.
unset( corresponding_pxd_file CACHE )
find_file( corresponding_pxd_file ${pyx_file_basename}.pxd
PATHS "${pyx_path}" ${cmake_include_directories}
NO_DEFAULT_PATH )
if( corresponding_pxd_file )
list( APPEND pxd_dependencies "${corresponding_pxd_file}" )
endif()
# pxd files to check for additional dependencies.
set( pxds_to_check "${pyx_file}" "${pxd_dependencies}" )
set( pxds_checked "" )
set( number_pxds_to_check 1 )
while( ${number_pxds_to_check} GREATER 0 )
foreach( pxd ${pxds_to_check} )
list( APPEND pxds_checked "${pxd}" )
list( REMOVE_ITEM pxds_to_check "${pxd}" )
# check for C header dependencies
file( STRINGS "${pxd}" extern_from_statements
REGEX "cdef[ ]+extern[ ]+from.*$" )
foreach( statement ${extern_from_statements} )
# Had trouble getting the quote in the regex
string( REGEX REPLACE "cdef[ ]+extern[ ]+from[ ]+[\"]([^\"]+)[\"].*" "\\1" header "${statement}" )
unset( header_location CACHE )
find_file( header_location ${header} PATHS ${cmake_include_directories} )
if( header_location )
list( FIND c_header_dependencies "${header_location}" header_idx )
if( ${header_idx} LESS 0 )
list( APPEND c_header_dependencies "${header_location}" )
endif()
endif()
endforeach()
# check for pxd dependencies
# Look for cimport statements.
set( module_dependencies "" )
file( STRINGS "${pxd}" cimport_statements REGEX cimport )
foreach( statement ${cimport_statements} )
if( ${statement} MATCHES from )
string( REGEX REPLACE "from[ ]+([^ ]+).*" "\\1" module "${statement}" )
else()
string( REGEX REPLACE "cimport[ ]+([^ ]+).*" "\\1" module "${statement}" )
endif()
list( APPEND module_dependencies ${module} )
endforeach()
list( REMOVE_DUPLICATES module_dependencies )
# Add the module to the files to check, if appropriate.
foreach( module ${module_dependencies} )
unset( pxd_location CACHE )
find_file( pxd_location ${module}.pxd
PATHS "${pyx_path}" ${cmake_include_directories} NO_DEFAULT_PATH )
if( pxd_location )
list( FIND pxds_checked ${pxd_location} pxd_idx )
if( ${pxd_idx} LESS 0 )
list( FIND pxds_to_check ${pxd_location} pxd_idx )
if( ${pxd_idx} LESS 0 )
list( APPEND pxds_to_check ${pxd_location} )
list( APPEND pxd_dependencies ${pxd_location} )
endif() # if it is not already going to be checked
endif() # if it has not already been checked
endif() # if pxd file can be found
endforeach() # for each module dependency discovered
endforeach() # for each pxd file to check
list( LENGTH pxds_to_check number_pxds_to_check )
endwhile()
# Look for included pxi files
file(STRINGS "${pyx_file}" include_statements REGEX "include +['\"]([^'\"]+).*")
foreach(statement ${include_statements})
string(REGEX REPLACE "include +['\"]([^'\"]+).*" "\\1" pxi_file "${statement}")
unset(pxi_location CACHE)
find_file(pxi_location ${pxi_file}
PATHS "${pyx_path}" ${cmake_include_directories} NO_DEFAULT_PATH)
if (pxi_location)
list(APPEND pxi_dependencies ${pxi_location})
endif()
endforeach() # for each include statement found
endforeach() # pyx_file
# Set additional flags.
if( CYTHON_ANNOTATE )
set( annotate_arg "--annotate" )
endif()
if( CYTHON_NO_DOCSTRINGS )
set( no_docstrings_arg "--no-docstrings" )
endif()
if( "${CMAKE_BUILD_TYPE}" STREQUAL "Debug" OR
"${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo" )
set( cython_debug_arg "--gdb" )
endif()
if( "${PYTHONLIBS_VERSION_STRING}" MATCHES "^2." )
set( version_arg "-2" )
elseif( "${PYTHONLIBS_VERSION_STRING}" MATCHES "^3." )
set( version_arg "-3" )
else()
set( version_arg )
endif()
# Include directory arguments.
list( REMOVE_DUPLICATES cython_include_directories )
set( include_directory_arg "" )
foreach( _include_dir ${cython_include_directories} )
set( include_directory_arg ${include_directory_arg} "-I" "${_include_dir}" )
endforeach()
# Determining generated file name.
set( _generated_file "${CMAKE_CURRENT_BINARY_DIR}/${_name}.${extension}" )
set_source_files_properties( ${_generated_file} PROPERTIES GENERATED TRUE )
set( ${generated_file} ${_generated_file} PARENT_SCOPE )
list( REMOVE_DUPLICATES pxd_dependencies )
list( REMOVE_DUPLICATES c_header_dependencies )
# Add the command to run the compiler.
add_custom_command( OUTPUT ${_generated_file}
COMMAND ${CYTHON_EXECUTABLE}
ARGS ${cxx_arg} ${include_directory_arg} ${version_arg}
${annotate_arg} ${no_docstrings_arg} ${cython_debug_arg} ${CYTHON_FLAGS}
--output-file ${_generated_file} ${pyx_locations}
DEPENDS ${pyx_locations} ${pxd_dependencies} ${pxi_dependencies}
IMPLICIT_DEPENDS ${pyx_lang} ${c_header_dependencies}
COMMENT ${comment}
)
# Remove their visibility to the user.
set( corresponding_pxd_file "" CACHE INTERNAL "" )
set( header_location "" CACHE INTERNAL "" )
set( pxd_location "" CACHE INTERNAL "" )
endfunction()
# cython_add_module( <name> src1 src2 ... srcN )
# Build the Cython Python module.
function( cython_add_module _name )
set( pyx_module_sources "" )
set( other_module_sources "" )
foreach( _file ${ARGN} )
if( ${_file} MATCHES ".*\\.py[x]?$" )
list( APPEND pyx_module_sources ${_file} )
else()
list( APPEND other_module_sources ${_file} )
endif()
endforeach()
compile_pyx( ${_name} generated_file ${pyx_module_sources} )
include_directories( ${PYTHON_INCLUDE_DIRS} )
python_add_module( ${_name} ${generated_file} ${other_module_sources} )
if( APPLE )
set_target_properties( ${_name} PROPERTIES LINK_FLAGS "-undefined dynamic_lookup" )
else()
target_link_libraries( ${_name} ${PYTHON_LIBRARIES} )
endif()
endfunction()
include( CMakeParseArguments )
# cython_add_standalone_executable( _name [MAIN_MODULE src3.py] src1 src2 ... srcN )
# Creates a standalone executable the given sources.
function( cython_add_standalone_executable _name )
set( pyx_module_sources "" )
set( other_module_sources "" )
set( main_module "" )
cmake_parse_arguments( cython_arguments "" "MAIN_MODULE" "" ${ARGN} )
include_directories( ${PYTHON_INCLUDE_DIRS} )
foreach( _file ${cython_arguments_UNPARSED_ARGUMENTS} )
if( ${_file} MATCHES ".*\\.py[x]?$" )
get_filename_component( _file_we ${_file} NAME_WE )
if( "${_file_we}" STREQUAL "${_name}" )
set( main_module "${_file}" )
elseif( NOT "${_file}" STREQUAL "${cython_arguments_MAIN_MODULE}" )
set( PYTHON_MODULE_${_file_we}_static_BUILD_SHARED OFF )
compile_pyx( "${_file_we}_static" generated_file "${_file}" )
list( APPEND pyx_module_sources "${generated_file}" )
endif()
else()
list( APPEND other_module_sources ${_file} )
endif()
endforeach()
if( cython_arguments_MAIN_MODULE )
set( main_module ${cython_arguments_MAIN_MODULE} )
endif()
if( NOT main_module )
message( FATAL_ERROR "main module not found." )
endif()
get_filename_component( main_module_we "${main_module}" NAME_WE )
set( CYTHON_FLAGS ${CYTHON_FLAGS} --embed )
compile_pyx( "${main_module_we}_static" generated_file ${main_module} )
add_executable( ${_name} ${generated_file} ${pyx_module_sources} ${other_module_sources} )
target_link_libraries( ${_name} ${PYTHON_LIBRARIES} ${pyx_module_libs} )
endfunction()

View File

@ -1,40 +1,13 @@
set(cpp_daq_files lasp_cppdaq.cpp)
set(cpp_daq_linklibs ${LASP_THREADING_LIBRARIES})
include_directories(../c)
file(GLOB device_files *.cpp)
# set(cpp_daq_linklibs ${LASP_THREADING_LIBRARIES})
if(LASP_HAS_RTAUDIO)
include_directories(/usr/include/rtaudio)
list(APPEND cpp_daq_files lasp_cpprtaudio.cpp)
list(PREPEND cpp_daq_linklibs rtaudio)
endif()
if(LASP_HAS_ULDAQ)
list(APPEND cpp_daq_files lasp_cppuldaq.cpp)
list(PREPEND cpp_daq_linklibs uldaq)
endif()
if(win32)
list(APPEND cpp_daq_linklibs python${python_version_windll})
endif(win32)
# if(win32)
# list(APPEND lasp_device_linklibs python${python_version_windll})
# endif(win32)
add_library(cpp_daq ${cpp_daq_files})
target_link_libraries(cpp_daq ${cpp_daq_linklibs})
add_library(lasp_device_lib OBJECT ${device_files})
foreach(cython_file lasp_daq lasp_deviceinfo lasp_daqconfig)
# pybind11_add_module(lasp_device lasp_pydevicepybid.cpp)
set_source_files_properties(${cython_file}.pyx PROPERTIES
CYTHON_IS_CXX TRUE)
# target_link_libraries(lasp_device PRIVATE lasp_device_lib)
set_source_files_properties(${cython_file}.cxx PROPERTIES
COMPILE_FLAGS "${CMAKE_CXX_FLAGS} ${CYTHON_EXTRA_CXX_FLAGS}")
cython_add_module(${cython_file} ${cython_file}.pyx)
target_link_libraries(${cython_file} cpp_daq ${cpp_daq_linklibs} lasp_lib)
endforeach()
# This is the way to make this variable work in all CMakeLists files. It is
# also used in the testing directory. But better to already link cpp_daq with
# linklibs.
# set(cpp_daq_linklibs "${cpp_daq_linklibs}" CACHE INTERNAL "cpp_daq_linklibs")

View File

@ -1,152 +0,0 @@
import sys
include "config.pxi"
cimport cython
from .lasp_avtype import AvType
from libcpp.string cimport string
from libcpp.vector cimport vector
from libc.stdlib cimport malloc, free
from libc.stdio cimport printf, fprintf, stderr
from libc.string cimport memcpy, memset
from cpython.ref cimport PyObject,Py_INCREF, Py_DECREF
cdef extern from "lasp_cppthread.h" nogil:
cdef cppclass CPPThread[T,F]:
CPPThread(F threadfunction, T data)
void join()
void CPPsleep_ms(unsigned int ms)
void CPPsleep_us(unsigned int us)
cdef extern from "lasp_cppqueue.h" nogil:
cdef cppclass SafeQueue[T]:
SafeQueue()
void enqueue(T t)
T dequeue()
size_t size() const
bool empty() const
cdef extern from "atomic" namespace "std" nogil:
cdef cppclass atomic[T]:
T load()
void store(T)
cdef extern from "lasp_pyarray.h":
PyObject* data_to_ndarray(void* data,
int n_rows,int n_cols,
int typenum,
bool transfer_ownership,
bool F_contiguous) with gil
ctypedef size_t us
ctypedef vector[bool] boolvec
ctypedef vector[double] dvec
ctypedef vector[us] usvec
cdef extern from "lasp_cppdaq.h" nogil:
cdef cppclass DaqApi:
string apiname
unsigned apicode
unsigned api_specific_subcode
@staticmethod
vector[DaqApi] getAvailableApis();
cdef cppclass DataType:
string name
unsigned sw
bool is_floating
DataType dtype_fl64
bool operator==(const DataType&)
DataType dtype_fl64
DataType dtype_fl32
DataType dtype_int8
DataType dtype_int16
DataType dtype_int32
DataType dtype_invalid
cdef cppclass cppDeviceInfo "DeviceInfo":
DaqApi api
string device_name
int api_specific_devindex
vector[DataType] availableDataTypes
int prefDataTypeIndex
vector[double] availableSampleRates
int prefSampleRateIndex
vector[us] availableFramesPerBlock
unsigned prefFramesPerBlockIndex
dvec availableInputRanges
int prefInputRangeIndex
unsigned ninchannels
unsigned noutchannels
string serialize()
cppDeviceInfo deserialize(string)
bool hasInputIEPE
bool hasInputACCouplingSwitch
bool hasInputTrigger
cdef cppclass cppDaqConfiguration "DaqConfiguration":
DaqApi api
string device_name
boolvec eninchannels
boolvec enoutchannels
vector[double] inchannel_sensitivities
vector[string] inchannel_names
vector[string] inchannel_metadata
vector[string] outchannel_names
vector[double] outchannel_sensitivities
vector[string] outchannel_metadata
us sampleRateIndex
us dataTypeIndex
us framesPerBlockIndex
bool monitorOutput
vector[us] input_qty_idx
boolvec inputIEPEEnabled
boolvec inputACCouplingMode
usvec inputRangeIndices
cppDaqConfiguration()
cppDaqConfiguration(cppDeviceInfo& devinfo)
int getHighestInChannel()
int getHighestOutChannel()
int getLowestInChannel()
int getLowestOutChannel()
cdef cppclass cppDaq "Daq":
void start(SafeQueue[void*] *inQueue,
SafeQueue[void*] *outQueue) except +
void stop() except +
double samplerate()
us neninchannels(bool include_monitorchannel)
us nenoutchannels()
DataType dataType()
us framesPerBlock()
bool isRunning()
bool duplexMode()
@staticmethod
cppDaq* createDaq(cppDeviceInfo&, cppDaqConfiguration&) except +
@staticmethod
vector[cppDeviceInfo] getDeviceInfo() except +

View File

@ -1,198 +0,0 @@
#include "lasp_cppdaq.h"
#include <algorithm>
#include <cassert>
#include "lasp_config.h"
#define MAX_DEV_COUNT_PER_API 20
#ifdef LASP_HAS_ULDAQ
#include "lasp_cppuldaq.h"
#endif
#ifdef LASP_HAS_RTAUDIO
#include "lasp_cpprtaudio.h"
#endif
vector<DeviceInfo> Daq::getDeviceInfo() {
vector<DeviceInfo> devs;
#ifdef LASP_HAS_ULDAQ
fillUlDaqDeviceInfo(devs);
#endif
#ifdef LASP_HAS_RTAUDIO
fillRtAudioDeviceInfo(devs);
#endif
return devs;
}
vector<DaqApi> DaqApi::getAvailableApis() {
vector<DaqApi> apis;
apis.resize(6);
#ifdef LASP_HAS_ULDAQ
apis.at(uldaqapi.apicode) = uldaqapi;
#endif
#ifdef LASP_HAS_RTAUDIO
apis.at(rtaudioAlsaApi.apicode) = rtaudioAlsaApi;
apis.at(rtaudioPulseaudioApi.apicode) = rtaudioPulseaudioApi;
apis.at(rtaudioWasapiApi.apicode) = rtaudioWasapiApi;
apis.at(rtaudioDsApi.apicode) = rtaudioDsApi;
apis.at(rtaudioAsioApi.apicode) = rtaudioAsioApi;
#endif
return apis;
}
DaqConfiguration::DaqConfiguration(const DeviceInfo &device) {
api = device.api;
device_name = device.device_name;
eninchannels.resize(device.ninchannels, false);
enoutchannels.resize(device.noutchannels, false);
inchannel_sensitivities.resize(device.ninchannels, 1.0);
inchannel_metadata.resize(device.ninchannels, "");
for (us i = 0; i < eninchannels.size(); i++) {
std::stringstream chname;
chname << "Unnamed input channel " << i;
inchannel_names.push_back(chname.str());
}
outchannel_metadata.resize(device.noutchannels, "");
outchannel_sensitivities.resize(device.noutchannels, 1.0);
for (us i = 0; i < enoutchannels.size(); i++) {
std::stringstream chname;
chname << "Unnamed output channel " << i;
outchannel_names.push_back(chname.str());
}
sampleRateIndex = device.prefSampleRateIndex;
dataTypeIndex = device.prefDataTypeIndex;
framesPerBlockIndex = device.prefFramesPerBlockIndex;
monitorOutput = false;
inputIEPEEnabled.resize(device.ninchannels, false);
inputACCouplingMode.resize(device.ninchannels, false);
inputRangeIndices.resize(device.ninchannels, device.prefInputRangeIndex);
assert(match(device));
}
bool DaqConfiguration::match(const DeviceInfo& dev) const {
return (dev.device_name == device_name && dev.api == api);
}
int DaqConfiguration::getHighestInChannel() const {
for(int i=eninchannels.size()-1; i>-1;i--) {
if(eninchannels.at(i)) return i;
}
return -1;
}
int DaqConfiguration::getHighestOutChannel() const {
for(us i=enoutchannels.size()-1; i>=0;i--) {
if(enoutchannels.at(i)) return i;
}
return -1;
}
int DaqConfiguration::getLowestInChannel() const {
for(us i=0; i<eninchannels.size();i++) {
if(eninchannels.at(i)) return i;
}
return -1;
}
int DaqConfiguration::getLowestOutChannel() const {
for(us i=0; i<enoutchannels.size();i++) {
if(enoutchannels.at(i)) return i;
}
return -1;
}
Daq *Daq::createDaq(const DeviceInfo& devinfo,
const DaqConfiguration &config) {
if(!config.match(devinfo)) {
throw runtime_error("DaqConfiguration does not match device info");
}
// Some basic sanity checks
if ((devinfo.ninchannels != config.eninchannels.size())) {
/* cerr << "devinfo.ninchannels: " << devinfo.ninchannels << endl; */
/* cerr << "config.eninchannels.size(): " << config.eninchannels.size() << endl; */
throw runtime_error("Invalid length of enabled input channels specified");
}
if ((devinfo.noutchannels != config.enoutchannels.size())) {
throw runtime_error("outvalid length of enabled output channels specified");
}
int apicode = devinfo.api.apicode;
if(devinfo.api == DaqApi()) {
throw std::runtime_error(string("Unable to match API: ") + devinfo.api.apiname);
}
#ifdef LASP_HAS_ULDAQ
else if (devinfo.api == uldaqapi) {
return createUlDaqDevice(devinfo, config);
}
#endif
#ifdef LASP_HAS_RTAUDIO
else if(apicode >= 1 && apicode <= 5) {
return createRtAudioDevice(devinfo, config);
}
#endif
else {
throw std::runtime_error(string("Unable to match API: ") +
devinfo.api.apiname);
}
}
Daq::Daq(const DeviceInfo &devinfo, const DaqConfiguration &config)
: DaqConfiguration(config), DeviceInfo(devinfo) {
if (monitorOutput && !(nenoutchannels() > 0)) {
throw runtime_error(
"Output monitoring only possible when at least one output channel is enabled. Please make sure to enable at least one output channel");
}
}
double Daq::samplerate() const {
mutexlock lock(mutex);
assert(sampleRateIndex < availableSampleRates.size());
return availableSampleRates.at(sampleRateIndex);
}
DataType Daq::dataType() const {
mutexlock lock(mutex);
assert((us)dataTypeIndex < availableDataTypes.size());
return availableDataTypes.at(dataTypeIndex);
}
double Daq::inputRangeForChannel(us ch) const {
if (!(ch < ninchannels)) {
throw runtime_error("Invalid channel number");
}
mutexlock lock(mutex);
assert(inputRangeIndices.size() == eninchannels.size());
return availableInputRanges.at(inputRangeIndices.at(ch));
}
us Daq::neninchannels(bool include_monitorchannel) const {
mutexlock lock(mutex);
us inch = std::count(eninchannels.begin(), eninchannels.end(), true);
if (monitorOutput && include_monitorchannel) {
inch++;
}
return inch;
}
us Daq::nenoutchannels() const {
mutexlock lock(mutex);
return std::count(enoutchannels.begin(), enoutchannels.end(), true);
}

View File

@ -1,332 +0,0 @@
#ifndef LASP_CPPDAQ_H
#define LASP_CPPDAQ_H
#include "lasp_config.h"
#include "lasp_types.h"
#ifdef LASP_HAS_RTAUDIO
#include <RtAudio.h>
#endif
#ifdef LASP_HAS_ULDAQ
#include <uldaq.h>
#endif
#include "lasp_cppqueue.h"
#include "string"
#include "vector"
#include <iostream>
#include <mutex>
#include <sstream>
using std::cerr;
using std::cout;
using std::endl;
using std::getline;
using std::runtime_error;
using std::string;
using std::vector;
using std::to_string;
typedef vector<bool> boolvec;
typedef vector<double> dvec;
typedef vector<us> usvec;
typedef std::lock_guard<std::mutex> mutexlock;
class DataType {
public:
string name;
unsigned sw;
bool is_floating;
DataType(const char *name, unsigned sw, bool is_floating)
: name(name), sw(sw), is_floating(is_floating) {}
DataType() : name("invalid data type"), sw(0), is_floating(false) {}
bool operator==(const DataType &o) {
return (name == o.name && sw == o.sw && is_floating == o.is_floating);
}
};
const DataType dtype_invalid;
const DataType dtype_fl32("32-bits floating point", 4, true);
const DataType dtype_fl64("64-bits floating point", 8, true);
const DataType dtype_int8("8-bits integers", 1, false);
const DataType dtype_int24("24-bits integers", 1, false);
const DataType dtype_int16("16-bits integers", 2, false);
const DataType dtype_int32("32-bits integers", 4, false);
const std::vector<DataType> dataTypes = {
dtype_int8, dtype_int16,dtype_int24, dtype_int32, dtype_fl32, dtype_fl64,
};
class DaqApi {
public:
string apiname = "Invalid API";
int apicode = -1;
unsigned api_specific_subcode = 0;
DaqApi(string apiname, unsigned apicode, unsigned api_specific_subcode = 0)
: apiname(apiname), apicode(apicode),
api_specific_subcode(api_specific_subcode) {}
DaqApi() {}
bool operator==(const DaqApi &other) const {
return (apiname == other.apiname && apicode == other.apicode &&
api_specific_subcode == other.api_specific_subcode);
}
operator string() const { return apiname + ", code: " + to_string(apicode); }
static vector<DaqApi> getAvailableApis();
};
#ifdef LASP_HAS_ULDAQ
const DaqApi uldaqapi("UlDaq", 0);
#endif
#ifdef LASP_HAS_RTAUDIO
const DaqApi rtaudioAlsaApi("RtAudio Linux ALSA", 1, RtAudio::Api::LINUX_ALSA);
const DaqApi rtaudioPulseaudioApi("RtAudio Linux Pulseaudio", 2,
RtAudio::Api::LINUX_PULSE);
const DaqApi rtaudioWasapiApi("RtAudio Windows Wasapi", 3,
RtAudio::Api::WINDOWS_WASAPI);
const DaqApi rtaudioDsApi("RtAudio Windows DirectSound", 4,
RtAudio::Api::WINDOWS_DS);
const DaqApi rtaudioAsioApi("RtAudio Windows ASIO", 5,
RtAudio::Api::WINDOWS_ASIO);
#endif
// Structure containing device info parameters
class DeviceInfo {
public:
DaqApi api;
string device_name = "";
int api_specific_devindex = -1;
vector<DataType> availableDataTypes;
int prefDataTypeIndex = 0;
vector<double> availableSampleRates;
int prefSampleRateIndex = -1;
vector<us> availableFramesPerBlock;
unsigned prefFramesPerBlockIndex = 0;
dvec availableInputRanges;
int prefInputRangeIndex = 0;
unsigned ninchannels = 0;
unsigned noutchannels = 0;
bool hasInputIEPE = false;
bool hasInputACCouplingSwitch = false;
bool hasInputTrigger = false;
/* DeviceInfo(): */
/* datatype(dtype_invalid) { } */
double prefSampleRate() const {
if (((us)prefSampleRateIndex < availableSampleRates.size()) &&
(prefSampleRateIndex >= 0)) {
return availableSampleRates.at(prefSampleRateIndex);
} else {
throw std::runtime_error("No prefered sample rate available");
}
}
operator string() const {
std::stringstream str;
str << api.apiname + " " << api_specific_devindex << endl
<< " number of input channels: " << ninchannels << endl
<< " number of output channels: " << noutchannels << endl;
return str.str();
}
string serialize() const {
// Simple serializer for this object, used because we found a bit late that
// this object needs to be send over the wire. We do not want to make this
// implementation in Python, as these objects are created here, in the C++
// code. The Python wrapper is just a readonly wrapper.
std::stringstream str;
str << api.apiname << "\t";
str << api.apicode << "\t";
str << api.api_specific_subcode << "\t";
str << device_name << "\t";
str << availableDataTypes.size() << "\t";
for(const DataType& dtype: availableDataTypes) {
// WARNING: THIS GOES COMPLETELY WRONG WHEN NAMES contain A TAB!!!
str << dtype.name << "\t";
str << dtype.sw << "\t";
str << dtype.is_floating << "\t";
}
str << prefDataTypeIndex << "\t";
str << availableSampleRates.size() << "\t";
for(const double& fs: availableSampleRates) {
// WARNING: THIS GOES COMPLETELY WRONG WHEN NAMES contain A TAB!!!
str << fs << "\t";
}
str << prefSampleRateIndex << "\t";
str << availableFramesPerBlock.size() << "\t";
for(const us& fb: availableFramesPerBlock) {
// WARNING: THIS GOES COMPLETELY WRONG WHEN NAMES contain A TAB!!!
str << fb << "\t";
}
str << prefFramesPerBlockIndex << "\t";
str << availableInputRanges.size() << "\t";
for(const double& ir: availableInputRanges) {
// WARNING: THIS GOES COMPLETELY WRONG WHEN NAMES contain A TAB!!!
str << ir << "\t";
}
str << prefInputRangeIndex << "\t";
str << ninchannels << "\t";
str << noutchannels << "\t";
str << int(hasInputIEPE) << "\t";
str << int(hasInputACCouplingSwitch) << "\t";
str << int(hasInputTrigger) << "\t";
return str.str();
}
static DeviceInfo deserialize(const string& dstr) {
DeviceInfo devinfo;
std::stringstream str(dstr);
string tmp;
us N;
// Lambda functions for deserializing
auto nexts = [&]() { getline(str, tmp, '\t'); return tmp; };
auto nexti = [&]() { getline(str, tmp, '\t'); return std::atoi(tmp.c_str()); };
auto nextf = [&]() { getline(str, tmp, '\t'); return std::atof(tmp.c_str()); };
// Api
string apiname = nexts();
auto apicode = nexti();
auto api_specific_subcode = nexti();
DaqApi api(apiname, apicode, api_specific_subcode);
devinfo.api = api;
devinfo.device_name = nexts();
N = us(nexti());
for(us i=0;i<N; i++) {
DataType dtype;
dtype.name = nexts();
dtype.sw =nexti();
dtype.is_floating = bool(nexti());
devinfo.availableDataTypes.push_back(dtype);
}
devinfo.prefDataTypeIndex = nexti();
N = us(nexti());
for(us i=0;i<N; i++) {
devinfo.availableSampleRates.push_back(nextf());
}
devinfo.prefSampleRateIndex = nexti();
N = us(nexti());
for(us i=0;i<N; i++) {
devinfo.availableFramesPerBlock.push_back(nexti());
}
devinfo.prefFramesPerBlockIndex = nexti();
N = us(nexti());
for(us i=0;i<N; i++) {
devinfo.availableInputRanges.push_back(nexti());
}
devinfo.prefInputRangeIndex = nexti();
devinfo.ninchannels = nexti();
devinfo.noutchannels = nexti();
devinfo.hasInputIEPE = bool(nexti());
devinfo.hasInputACCouplingSwitch = bool(nexti());
devinfo.hasInputTrigger = bool(nexti());
return devinfo;
}
};
// Device configuration parameters
class DaqConfiguration {
public:
DaqApi api;
string device_name;
boolvec eninchannels; // Enabled input channelsvice(const DeviceInfo& devinfo,
boolvec enoutchannels; // Enabled output channels
vector<double> inchannel_sensitivities;
vector<string> inchannel_names;
vector<string> inchannel_metadata;
vector<double> outchannel_sensitivities;
vector<string> outchannel_names;
vector<string> outchannel_metadata;
us sampleRateIndex = 0; // Index in list of sample rates
us dataTypeIndex = 0; // Required datatype for output, should be
// present in the list
us framesPerBlockIndex = 0;
bool monitorOutput = false;
boolvec inputIEPEEnabled;
boolvec inputACCouplingMode;
usvec inputRangeIndices;
// Create a default configuration, with all channels disabled on both
// input and output, and default channel names
DaqConfiguration(const DeviceInfo &device);
DaqConfiguration() {}
bool match(const DeviceInfo &devinfo) const;
int getHighestInChannel() const;
int getHighestOutChannel() const;
int getLowestInChannel() const;
int getLowestOutChannel() const;
};
class Daq;
class Daq : public DaqConfiguration, public DeviceInfo {
mutable std::mutex mutex;
public:
static vector<DeviceInfo> getDeviceInfo();
static Daq *createDaq(const DeviceInfo &, const DaqConfiguration &config);
Daq(const DeviceInfo &devinfo, const DaqConfiguration &config);
virtual void start(SafeQueue<void *> *inqueue,
SafeQueue<void *> *outqueue) = 0;
virtual void stop() = 0;
virtual bool isRunning() const = 0;
virtual ~Daq(){};
us neninchannels(bool include_monitorchannel = true) const;
us nenoutchannels() const;
double samplerate() const;
double inputRangeForChannel(us ch) const;
DataType dataType() const;
us framesPerBlock() const {
mutexlock lock(mutex);
return availableFramesPerBlock.at(framesPerBlockIndex);
}
bool duplexMode() const {
return (neninchannels(false) > 0 && nenoutchannels() > 0);
}
};
#endif // LASP_CPPDAQ_H

View File

@ -1,56 +0,0 @@
// threadsafe_queue.h
//
// Author: J.A. de Jong
//
// Description:
// Implementation of a thread-safe queue, based on STL queue
//////////////////////////////////////////////////////////////////////
#pragma once
#ifndef THREADSAFE_QUEUE_H
#define THREADSAFE_QUEUE_H
#include <queue>
#include <mutex>
#include <condition_variable>
// A threadsafe-queue.
template <class T>
class SafeQueue {
std::queue<T> _queue;
mutable std::mutex _mutex;
std::condition_variable _cv;
public:
SafeQueue(): _queue(), _mutex() , _cv()
{}
~SafeQueue(){}
void enqueue(T t) {
std::lock_guard<std::mutex> lock(_mutex);
_queue.push(t);
_cv.notify_one();
}
T dequeue() {
std::unique_lock<std::mutex> lock(_mutex);
while(_queue.empty())
{
// release lock as long as the wait and reaquire it afterwards.
_cv.wait(lock);
}
T val = _queue.front();
_queue.pop();
return val;
}
bool empty() const {
std::unique_lock<std::mutex> lock(_mutex);
return _queue.size()==0;
}
size_t size() const {
std::unique_lock<std::mutex> lock(_mutex);
return _queue.size();
}
};
#endif // THREADSAFE_QUEUE_H
//////////////////////////////////////////////////////////////////////

View File

@ -1,391 +0,0 @@
#include "lasp_cpprtaudio.h"
#include <RtAudio.h>
#include <atomic>
#include <cassert>
#include <cstring>
#include <thread>
#if MS_WIN64
typedef uint8_t u_int8_t;
#endif
using std::atomic;
void fillRtAudioDeviceInfo(vector<DeviceInfo> &devinfolist) {
vector<RtAudio::Api> apis;
RtAudio::getCompiledApi(apis);
for(auto api: apis) {
RtAudio rtaudio(api);
us count = rtaudio.getDeviceCount();
for(us devno = 0; devno< count;devno++) {
RtAudio::DeviceInfo devinfo = rtaudio.getDeviceInfo(devno);
if(!devinfo.probed) {
// Device capabilities not successfully probed. Continue to next
continue;
}
DeviceInfo d;
switch(api){
case RtAudio::LINUX_ALSA:
d.api = rtaudioAlsaApi;
break;
case RtAudio::LINUX_PULSE:
d.api = rtaudioPulseaudioApi;
break;
case RtAudio::WINDOWS_WASAPI:
d.api = rtaudioWasapiApi;
break;
case RtAudio::WINDOWS_DS:
d.api = rtaudioDsApi;
break;
case RtAudio::WINDOWS_ASIO:
d.api = rtaudioAsioApi;
break;
default:
cerr << "Not implemented RtAudio API, skipping." << endl;
continue;
break;
}
d.device_name = devinfo.name;
d.api_specific_devindex = devno;
for(us j=0; j<devinfo.sampleRates.size();j++){
us rate = devinfo.sampleRates[j];
d.availableSampleRates.push_back((double) rate);
if(devinfo.preferredSampleRate == rate) {
d.prefSampleRateIndex = j;
}
}
d.noutchannels = devinfo.outputChannels;
d.ninchannels = devinfo.inputChannels;
d.availableInputRanges = {1.0};
RtAudioFormat formats = devinfo.nativeFormats;
if(formats & RTAUDIO_SINT8) {
d.availableDataTypes.push_back(dtype_int8);
}
if(formats & RTAUDIO_SINT16) {
d.availableDataTypes.push_back(dtype_int16);
}
if(formats & RTAUDIO_SINT32) {
d.availableDataTypes.push_back(dtype_int24);
}
if(formats & RTAUDIO_SINT32) {
d.availableDataTypes.push_back(dtype_fl32);
}
if(formats & RTAUDIO_FLOAT64) {
d.availableDataTypes.push_back(dtype_fl64);
}
if(d.availableDataTypes.size() == 0) {
std::cerr << "RtAudio: No data types found in device!" << endl;
}
d.prefDataTypeIndex = d.availableDataTypes.size() - 1;
d.availableFramesPerBlock.push_back(512);
d.availableFramesPerBlock.push_back(1024);
d.availableFramesPerBlock.push_back(2048);
d.availableFramesPerBlock.push_back(4096);
d.availableFramesPerBlock.push_back(8192);
d.prefFramesPerBlockIndex = 1;
devinfolist.push_back(d);
}
}
}
int mycallback(void *outputBuffer, void *inputBuffer,
unsigned int nFrames,
double streamTime,
RtAudioStreamStatus status,
void *userData);
void myerrorcallback(RtAudioError::Type,const string& errorText);
class AudioDaq: public Daq {
SafeQueue<void*> *inqueue = NULL;
SafeQueue<void*> *outqueue = NULL;
SafeQueue<void*> *outDelayqueue = NULL;
RtAudio* rtaudio = NULL;
RtAudio::StreamParameters* instreamparams = nullptr;
RtAudio::StreamParameters* outstreamparams = nullptr;
us nFramesPerBlock;
public:
AudioDaq(const DeviceInfo& devinfo,
const DaqConfiguration& config):
Daq(devinfo, config) {
nFramesPerBlock = this->framesPerBlock();
if(neninchannels(false) > 0) {
instreamparams = new RtAudio::StreamParameters();
instreamparams->nChannels = getHighestInChannel() + 1;
if(instreamparams->nChannels < 1) {
throw runtime_error("Invalid input number of channels");
}
instreamparams->firstChannel = 0;
instreamparams->deviceId = devinfo.api_specific_devindex;
}
if(nenoutchannels() > 0) {
outstreamparams = new RtAudio::StreamParameters();
outstreamparams->nChannels = getHighestOutChannel() + 1;
if(outstreamparams->nChannels < 1) {
throw runtime_error("Invalid output number of channels");
}
outstreamparams->firstChannel = 0;
outstreamparams->deviceId = devinfo.api_specific_devindex;
}
RtAudio::StreamOptions streamoptions;
streamoptions.flags = RTAUDIO_NONINTERLEAVED | RTAUDIO_HOG_DEVICE;
streamoptions.numberOfBuffers = 2;
streamoptions.streamName = "RtAudio stream";
streamoptions.priority = 0;
RtAudioFormat format;
DataType dtype = dataType();
if(dtype == dtype_fl32) {
format = RTAUDIO_FLOAT32;
} else if(dtype == dtype_fl64) {
format = RTAUDIO_FLOAT64;
} else if(dtype == dtype_int8) {
format = RTAUDIO_SINT8;
} else if(dtype == dtype_int16) {
format = RTAUDIO_SINT16;
} else if(dtype == dtype_int32) {
format = RTAUDIO_SINT32;
} else {
throw runtime_error("Invalid data type");
}
try {
rtaudio = new RtAudio((RtAudio::Api) devinfo.api.api_specific_subcode);
if(!rtaudio) {
throw runtime_error("RtAudio allocation failed");
}
rtaudio->openStream(
outstreamparams,
instreamparams,
format,
(us) samplerate(),
(unsigned*) &nFramesPerBlock,
&mycallback,
(void*) this,
&streamoptions,
&myerrorcallback
);
} catch(RtAudioError& e) {
if(rtaudio) delete rtaudio;
if(instreamparams) delete instreamparams;
if(outstreamparams) delete outstreamparams;
throw;
}
if(monitorOutput) {
outDelayqueue = new SafeQueue<void*>();
}
}
friend int mycallback(void *outputBuffer, void *inputBuffer,
unsigned int nFrames,
double streamTime,
RtAudioStreamStatus status,
void *userData);
void start(SafeQueue<void*> *inqueue, SafeQueue<void*> *outqueue) {
this->inqueue = inqueue;
this->outqueue = outqueue;
if(monitorOutput) {
this->outDelayqueue = new SafeQueue<void*>();
}
if(isRunning()){
throw runtime_error("Stream already running");
}
if(neninchannels(false) > 0 && !inqueue) {
throw runtime_error("inqueue argument not given");
}
if(nenoutchannels() > 0 && !outqueue) {
throw runtime_error("outqueue argument not given");
}
assert(rtaudio);
rtaudio->startStream();
}
void stop() {
if(!isRunning()) {
cerr << "Stream is already stopped" << endl;
}
else {
assert(rtaudio);
rtaudio->stopStream();
}
if(inqueue) {
inqueue = nullptr;
}
if(outqueue) {
outqueue = nullptr;
}
if(outDelayqueue) {
delete outDelayqueue;
outDelayqueue = nullptr;
}
}
bool isRunning() const {return (rtaudio && rtaudio->isStreamRunning());}
~AudioDaq() {
assert(rtaudio);
if(isRunning()) {
stop();
}
if(rtaudio->isStreamOpen()) {
rtaudio->closeStream();
}
if(rtaudio) delete rtaudio;
if(outDelayqueue) delete outDelayqueue;
if(instreamparams) delete instreamparams;
if(outstreamparams) delete outstreamparams;
}
};
Daq* createRtAudioDevice(const DeviceInfo& devinfo,
const DaqConfiguration& config) {
AudioDaq *daq = NULL;
try {
daq = new AudioDaq(devinfo, config);
} catch (runtime_error &e) {
if (daq)
delete daq;
throw;
}
return daq;
}
int mycallback(
void *outputBuffervoid,
void *inputBuffervoid,
unsigned int nFrames,
double streamTime,
RtAudioStreamStatus status,
void *userData) {
u_int8_t* inputBuffer = (u_int8_t*) inputBuffervoid;
u_int8_t* outputBuffer = (u_int8_t*) outputBuffervoid;
AudioDaq* daq = (AudioDaq*) userData;
DataType dtype = daq->dataType();
us neninchannels_inc_mon = daq->neninchannels();
us nenoutchannels = daq->nenoutchannels();
bool monitorOutput = daq->monitorOutput;
us bytesperchan = dtype.sw*nFrames;
us monitorOffset = ((us) monitorOutput)*bytesperchan;
SafeQueue<void*> *inqueue = daq->inqueue;
SafeQueue<void*> *outqueue = daq->outqueue;
SafeQueue<void*> *outDelayqueue = daq->outDelayqueue;
const boolvec& eninchannels = daq->eninchannels;
const boolvec& enoutchannels = daq->enoutchannels;
if(inputBuffer || monitorOutput) {
u_int8_t *inbuffercpy = (u_int8_t*) malloc(bytesperchan*neninchannels_inc_mon);
if(inputBuffer) {
us j=0; // OUR buffer channel counter
us i=0; // RtAudio channel counter
for(int ch=daq->getLowestInChannel();ch<=daq->getHighestInChannel();ch++) {
if(eninchannels[ch]) {
memcpy(
&(inbuffercpy[monitorOffset+j*bytesperchan]),
&(inputBuffer[i*bytesperchan]),
bytesperchan);
j++;
}
i++;
}
}
if(monitorOutput) {
assert(outDelayqueue);
if(!daq->outDelayqueue->empty()) {
void* dat = daq->outDelayqueue->dequeue();
memcpy((void*) inbuffercpy, dat, bytesperchan);
free(dat);
} else {
cerr << "Warning: output delay queue appears empty!" << endl;
memset(inbuffercpy, 0, bytesperchan);
}
}
assert(inqueue);
inqueue->enqueue(inbuffercpy);
}
if(outputBuffer) {
assert(outqueue);
if(!outqueue->empty()) {
u_int8_t* outbuffercpy = (u_int8_t*) outqueue->dequeue();
us j=0; // OUR buffer channel counter
us i=0; // RtAudio channel counter
for(us ch=0;ch<=daq->getHighestOutChannel();ch++) {
/* cerr << "Copying from queue... " << endl; */
if(enoutchannels[ch]) {
memcpy(
&(outputBuffer[i*bytesperchan]),
&(outbuffercpy[j*bytesperchan]),
bytesperchan);
j++;
}
else {
/* cerr << "unused output channel in list" << endl; */
memset(
&(outputBuffer[i*bytesperchan]),0,bytesperchan);
}
i++;
}
if(!monitorOutput) {
free(outbuffercpy);
} else {
assert(outDelayqueue);
outDelayqueue->enqueue((void*) outbuffercpy);
}
}
else {
cerr << "RtAudio backend: stream output buffer underflow!" << endl;
}
}
return 0;
}
void myerrorcallback(RtAudioError::Type,const string& errorText) {
cerr << errorText << endl;
}

View File

@ -1,13 +0,0 @@
#ifndef RTAUDIO_H
#define RTAUDIO_H
#include "lasp_cppdaq.h"
Daq* createRtAudioDevice(const DeviceInfo& devinfo,
const DaqConfiguration& config);
void fillRtAudioDeviceInfo(vector<DeviceInfo> &devinfolist);
#endif // RTAUDIO_H

View File

@ -1,34 +0,0 @@
#pragma once
#ifndef LASP_CPPTHREAD_H
#define LASP_CPPTHREAD_H
#include <chrono>
#include <thread>
#include <assert.h>
// This is a small wrapper around the std library thread.
template <class T, typename F>
class CPPThread {
std::thread _thread;
public:
CPPThread(F threadfcn, T data) :
_thread(threadfcn, data) { }
void join() {
assert(_thread.joinable());
_thread.join();
}
/* ~CPPThread() { */
/* } */
};
void CPPsleep_ms(unsigned int ms) {
std::this_thread::sleep_for(std::chrono::milliseconds(ms));
}
void CPPsleep_us(unsigned int us) {
std::this_thread::sleep_for(std::chrono::microseconds(us));
}
#endif // LASP_CPPTHREAD_H

View File

@ -1,636 +0,0 @@
#include "lasp_cppuldaq.h"
#include "lasp_config.h"
#include "lasp_tracer.h"
#include <atomic>
#include <cassert>
#include <chrono>
#include <iostream>
#include <thread>
#include <uldaq.h>
#include <vector>
using std::atomic;
/* using std::this_thread; */
const us MAX_DEV_COUNT_PER_API = 100;
const us UL_ERR_MSG_LEN = 512;
inline void showErr(UlError err) {
if (err != ERR_NO_ERROR) {
char errmsg[UL_ERR_MSG_LEN];
ulGetErrMsg(err, errmsg);
std::cerr << "UlError: " << errmsg << std::endl;
}
}
class DT9837A;
void threadfcn(DT9837A *td);
class DT9837A : public Daq {
atomic<bool> stopThread;
DaqDeviceHandle handle = 0;
std::thread *thread = NULL;
SafeQueue<void *> *inqueue = NULL;
SafeQueue<void *> *outqueue = NULL;
double *inbuffer = NULL;
double *outbuffer = NULL;
us nFramesPerBlock;
public:
DT9837A(const DeviceInfo &devinfo, const DaqConfiguration &config)
: Daq(devinfo, config) {
// Some sanity checks
if (eninchannels.size() != 4) {
throw runtime_error("Invalid length of enabled inChannels vector");
}
if (enoutchannels.size() != 1) {
throw runtime_error("Invalid length of enabled outChannels vector");
}
stopThread = false;
nFramesPerBlock = availableFramesPerBlock[framesPerBlockIndex];
if (nFramesPerBlock < 24 || nFramesPerBlock > 8192) {
throw runtime_error("Unsensible number of samples per block chosen");
}
if (samplerate() < 10000 || samplerate() > 51000) {
throw runtime_error("Invalid sample rate");
}
DaqDeviceDescriptor devdescriptors[MAX_DEV_COUNT_PER_API];
DaqDeviceDescriptor descriptor;
DaqDeviceInterface interfaceType = ANY_IFC;
UlError err;
us numdevs = MAX_DEV_COUNT_PER_API;
err = ulGetDaqDeviceInventory(interfaceType, devdescriptors, (unsigned*) &numdevs);
if (err != ERR_NO_ERROR) {
throw runtime_error("Device inventarization failed");
}
if ((us) api_specific_devindex >= numdevs) {
throw runtime_error("Device number {deviceno} too high {err}. This could "
"happen when the device is currently not connected");
}
descriptor = devdescriptors[api_specific_devindex];
// get a handle to the DAQ device associated with the first descriptor
handle = ulCreateDaqDevice(descriptor);
if (handle == 0) {
throw runtime_error("Unable to create a handle to the specified DAQ "
"device. Is the device currently in use? Please make sure to set "
"the DAQ configuration in duplex mode if simultaneous input and "
"output is required.");
}
err = ulConnectDaqDevice(handle);
if (err != ERR_NO_ERROR) {
showErr(err);
ulReleaseDaqDevice(handle);
handle = 0;
throw runtime_error("Unable to connect to device: {err}");
}
for (us ch = 0; ch < 4; ch++) {
err = ulAISetConfigDbl(handle, AI_CFG_CHAN_SENSOR_SENSITIVITY, ch, 1.0);
showErr(err);
if (err != ERR_NO_ERROR) {
throw runtime_error("Fatal: could normalize channel sensitivity");
}
CouplingMode cm = inputACCouplingMode[ch] ? CM_AC : CM_DC;
err = ulAISetConfig(handle, AI_CFG_CHAN_COUPLING_MODE, ch, cm);
if (err != ERR_NO_ERROR) {
showErr(err);
throw runtime_error("Fatal: could not set AC/DC coupling mode");
}
IepeMode iepe = inputIEPEEnabled[ch] ? IEPE_ENABLED : IEPE_DISABLED;
err = ulAISetConfig(handle, AI_CFG_CHAN_IEPE_MODE, ch, iepe);
if (err != ERR_NO_ERROR) {
showErr(err);
throw runtime_error("Fatal: could not set IEPE mode");
}
}
}
DT9837A(const DT9837A &) = delete;
~DT9837A() {
UlError err;
if (isRunning()) {
stop();
}
if (handle) {
err = ulDisconnectDaqDevice(handle);
showErr(err);
err = ulReleaseDaqDevice(handle);
showErr(err);
}
}
bool isRunning() const { return bool(thread); }
void start(SafeQueue<void *> *inqueue, SafeQueue<void *> *outqueue) {
if (isRunning()) {
throw runtime_error("Thread is already running");
}
bool hasinput = neninchannels() > 0;
bool hasoutput = nenoutchannels() > 0;
if (hasinput && !inqueue) {
throw runtime_error("Inqueue not given, while input is enabled");
}
if (hasoutput && !outqueue) {
throw runtime_error("outqueue not given, while output is enabled");
}
if (hasinput) {
assert(!inbuffer);
inbuffer =
new double[neninchannels() * nFramesPerBlock * 2]; // Watch the 2!
}
if (hasoutput) {
assert(!outbuffer);
outbuffer =
new double[nenoutchannels() * nFramesPerBlock * 2]; // Watch the 2!
}
this->inqueue = inqueue;
this->outqueue = outqueue;
stopThread = false;
thread = new std::thread(threadfcn, this);
}
void stop() {
if (!isRunning()) {
throw runtime_error("No data acquisition running");
}
assert(thread);
stopThread = true;
thread->join();
delete thread;
thread = NULL;
outqueue = NULL;
inqueue = NULL;
if (inbuffer) {
delete inbuffer;
inbuffer = nullptr;
}
if (outbuffer) {
delete outbuffer;
outbuffer = nullptr;
}
}
friend void threadfcn(DT9837A *);
};
/**
* @brief Create an empty buffer and fill it with zeros.
*
* @param size The number of elements in the array
*
* @return Pointer to the array
*/
static double* createZeroBuffer(size_t size) {
double* buf = static_cast<double *>(
malloc(sizeof(double) * size));
for (us sample = 0; sample < size; sample++) {
buf[sample] = 0;
}
return buf;
}
/**
* @brief Copy samples from one linear array to the next.
*
* @param[in] from Buffer to copy from
* @param[out] to Buffer to copy to
* @param startFrom The position to start in the from-buffer
* @param startTo The position to start in the to-buffer
* @param N The number of samples to copy.
*/
static inline void copySamples(double* from,double* to,
const us startFrom,const us startTo,const us N) {
for (us sample = 0; sample < N; sample++) {
to[startTo + sample] = from[startFrom + sample];
}
}
void threadfcn(DT9837A *td) {
std::cerr << "Starting DAQ Thread fcn" << endl;
const us nenoutchannels = td->nenoutchannels();
us neninchannels = td->neninchannels();
const us nFramesPerBlock = td->nFramesPerBlock;
const bool hasinput = neninchannels > 0;
const bool hasoutput = nenoutchannels > 0;
bool monitorOutput = td->monitorOutput;
double *inbuffer = td->inbuffer;
double *outbuffer = td->outbuffer;
SafeQueue<void *> *inqueue = td->inqueue;
SafeQueue<void *> *outqueue = td->outqueue;
ScanStatus inscanstat;
ScanStatus outscanstat;
TransferStatus inxstat, outxstat;
double samplerate = td->samplerate();
const double sleeptime = ((double)nFramesPerBlock) / (4 * samplerate);
const us sleeptime_us = (us)(sleeptime * 1e6);
/* cerr << "Sleep time in loop: " << sleeptime_us << "us." << endl; */
if (sleeptime_us < 10) {
cerr << "ERROR: Too small buffer size (nFramesPerBlock) chosen!" << endl;
return;
}
const us buffer_mid_idx_in = neninchannels * nFramesPerBlock;
const us buffer_mid_idx_out = nenoutchannels * nFramesPerBlock;
DaqDeviceHandle handle = td->handle;
assert(handle);
DaqInScanFlag inscanflags = DAQINSCAN_FF_DEFAULT;
AOutScanFlag outscanflags = AOUTSCAN_FF_DEFAULT;
ScanOption scanoptions = SO_CONTINUOUS;
UlError err = ERR_NO_ERROR;
DaqInChanDescriptor *indesc = NULL;
bool topinenqueued = true;
// Start with true here, to not copy here the first time
bool botinenqueued = true;
bool topoutenqueued = true;
bool botoutenqueued = true;
size_t inTotalCount = 0;
size_t outTotalCount = 0;
// initialize output, if any
if (hasoutput) {
assert(nenoutchannels == 1);
assert(outqueue);
// Initialize the buffer with zeros, before pushing any data.
for (us sample = 0; sample < 2 * nFramesPerBlock; sample++) {
outbuffer[sample] = 0;
}
cerr << "Starting output DAC" << endl;
err = ulAOutScan(handle, 0, 0, BIP10VOLTS,
/* BIP60VOLTS, */
2 * td->nFramesPerBlock, // Watch the 2 here!
&samplerate, scanoptions, outscanflags, outbuffer);
if (err != ERR_NO_ERROR) {
showErr(err);
goto exit;
}
}
// Initialize input, if any
if (hasinput) {
indesc = new DaqInChanDescriptor[neninchannels];
us j = 0;
for (us chin = 0; chin < 4; chin++) {
if (td->eninchannels[chin] == true) {
indesc[j].type = DAQI_ANALOG_SE;
indesc[j].channel = chin;
double rangeval = td->inputRangeForChannel(chin);
Range rangenum;
if (abs(rangeval - 1.0) < 1e-8) {
rangenum = BIP1VOLTS;
} else if (abs(rangeval - 10.0) < 1e-8) {
rangenum = BIP10VOLTS;
} else {
std::cerr << "Fatal: input range value is invalid" << endl;
goto exit;
}
indesc[j].range = rangenum;
j++;
}
}
// Overwrite last channel
if (monitorOutput) {
indesc[j].type = DAQI_DAC;
indesc[j].channel = 0;
indesc[j].range = BIP10VOLTS;
j++;
}
assert(j == neninchannels);
cerr << "Starting input ADC" << endl;
err = ulDaqInScan(handle, indesc, neninchannels,
2 * td->nFramesPerBlock, // Watch the 2 here!
&samplerate, scanoptions, inscanflags, inbuffer);
if (err != ERR_NO_ERROR) {
showErr(err);
goto exit;
}
}
// Runs scan status on output, to catch up with position
if (hasoutput) {
err = ulAOutScanStatus(handle, &outscanstat, &outxstat);
if (err != ERR_NO_ERROR) {
showErr(err);
goto exit;
}
outTotalCount = outxstat.currentScanCount;
assert(outscanstat == SS_RUNNING);
}
/* std::cerr << "Entering while loop" << endl; */
/* std::cerr << "hasinput: " << hasinput << endl; */
while (!td->stopThread && err == ERR_NO_ERROR) {
/* std::cerr << "While..." << endl; */
if (hasoutput) {
err = ulAOutScanStatus(handle, &outscanstat, &outxstat);
if (err != ERR_NO_ERROR) {
showErr(err);
goto exit;
}
assert(outscanstat == SS_RUNNING);
if (outxstat.currentScanCount > outTotalCount + 2 * nFramesPerBlock) {
cerr << "***** WARNING: Missing output sample blocks, DAQ Scan count="
<< outxstat.currentScanCount
<< " while loop count = " << outTotalCount
<< ", probably due to too small buffer size. *****" << endl;
}
outTotalCount = outxstat.currentScanCount;
/* std::cerr << "Samples scanned: " << outxstat.currentTotalCount <<
* endl;
*/
if (outxstat.currentIndex < buffer_mid_idx_out) {
topoutenqueued = false;
if (!botoutenqueued) {
/* cerr << "Copying output buffer to bottom" << endl; */
double *bufcpy;
assert(nenoutchannels > 0);
if (!outqueue->empty()) {
bufcpy = (double *)outqueue->dequeue();
} else {
cerr << "******* WARNING: OUTPUTQUEUE UNDERFLOW, FILLING SIGNAL "
"QUEUE WITH ZEROS ***********"
<< endl;
bufcpy = createZeroBuffer(nFramesPerBlock*nenoutchannels);
}
copySamples(bufcpy, outbuffer, 0, buffer_mid_idx_out, nFramesPerBlock);
free(bufcpy);
botoutenqueued = true;
}
} else {
botoutenqueued = false;
if (!topoutenqueued) {
/* cerr << "Copying output buffer to top" << endl; */
double *bufcpy;
assert(nenoutchannels > 0);
if (!outqueue->empty()) {
bufcpy = (double *)outqueue->dequeue();
} else {
cerr << "******* WARNING: OUTPUTQUEUE UNDERFLOW, FILLING SIGNAL "
"QUEUE WITH ZEROS ***********"
<< endl;
bufcpy = createZeroBuffer(nFramesPerBlock*nenoutchannels);
}
copySamples(bufcpy, outbuffer, 0, 0, nFramesPerBlock);
free(bufcpy);
topoutenqueued = true;
}
}
}
if (hasinput) {
err = ulDaqInScanStatus(handle, &inscanstat, &inxstat);
if (err != ERR_NO_ERROR) {
showErr(err);
goto exit;
}
assert(inscanstat == SS_RUNNING);
if (inxstat.currentScanCount > inTotalCount + 2 * nFramesPerBlock) {
cerr << "***** ERROR: Missing input sample blocks, count="
<< inxstat.currentScanCount
<< ", probably due to too small buffer size. Exiting thread. "
"*****"
<< endl;
break;
}
inTotalCount = inxstat.currentScanCount;
if ((us) inxstat.currentIndex < buffer_mid_idx_in) {
topinenqueued = false;
if (!botinenqueued) {
/* cerr << "Copying in buffer bot" << endl; */
double *bufcpy = static_cast<double *>(
malloc(sizeof(double) * nFramesPerBlock * neninchannels));
us monitoroffset = monitorOutput ? 1 : 0;
assert(neninchannels > 0);
for (us channel = 0; channel < (neninchannels - monitoroffset);
channel++) {
for (us sample = 0; sample < nFramesPerBlock; sample++) {
bufcpy[(monitoroffset + channel) * nFramesPerBlock + sample] =
inbuffer[buffer_mid_idx_in + sample * neninchannels +
channel];
}
}
if (monitorOutput) {
// Monitor output goes to first channel, that is
// our convention
us channel = neninchannels - 1;
for (us sample = 0; sample < nFramesPerBlock; sample++) {
bufcpy[sample] = inbuffer[buffer_mid_idx_in +
sample * neninchannels + channel];
}
}
inqueue->enqueue((void *)bufcpy);
botinenqueued = true;
}
} else {
botinenqueued = false;
if (!topinenqueued) {
double *bufcpy = static_cast<double *>(
malloc(sizeof(double) * nFramesPerBlock * neninchannels));
us monitoroffset = monitorOutput ? 1 : 0;
assert(neninchannels > 0);
for (us channel = 0; channel < (neninchannels - monitoroffset);
channel++) {
for (us sample = 0; sample < nFramesPerBlock; sample++) {
bufcpy[(monitoroffset + channel) * nFramesPerBlock + sample] =
inbuffer[sample * neninchannels + channel];
}
}
if (monitorOutput) {
// Monitor output goes to first channel, that is
// our convention
us channel = neninchannels - 1;
for (us sample = 0; sample < nFramesPerBlock; sample++) {
bufcpy[sample] = inbuffer[sample * neninchannels + channel];
}
}
/* cerr << "Copying in buffer top" << endl; */
inqueue->enqueue((void *)bufcpy);
topinenqueued = true;
}
}
}
std::this_thread::sleep_for(std::chrono::microseconds(sleeptime_us));
} // End of while loop
/* std::cerr << "Exit of while loop" << endl; */
exit:
if (hasoutput) {
ulAOutScanStop(handle);
if (err != ERR_NO_ERROR) {
showErr(err);
}
}
if (hasinput) {
ulDaqInScanStop(handle);
if (err != ERR_NO_ERROR) {
showErr(err);
}
}
if (indesc)
delete indesc;
std::cerr << "Exit of DAQ thread fcn" << endl;
}
Daq *createUlDaqDevice(const DeviceInfo &devinfo,
const DaqConfiguration &config) {
DT9837A *daq = NULL;
try {
daq = new DT9837A(devinfo, config);
} catch (runtime_error &e) {
if (daq)
delete daq;
throw;
}
return daq;
}
void fillUlDaqDeviceInfo(vector<DeviceInfo> &devinfolist) {
fsTRACE(15);
UlError err;
unsigned int numdevs = MAX_DEV_COUNT_PER_API;
DaqDeviceDescriptor devdescriptors[MAX_DEV_COUNT_PER_API];
DaqDeviceDescriptor descriptor;
DaqDeviceInterface interfaceType = ANY_IFC;
err = ulGetDaqDeviceInventory(interfaceType, devdescriptors,static_cast<unsigned*>(&numdevs));
if (err != ERR_NO_ERROR) {
throw std::runtime_error("UlDaq device inventarization failed");
}
for (unsigned i = 0; i < numdevs; i++) {
descriptor = devdescriptors[i];
DeviceInfo devinfo;
devinfo.api = uldaqapi;
string name, interface;
if (string(descriptor.productName) == "DT9837A") {
if (descriptor.devInterface == USB_IFC) {
name = "USB - ";
} else if (descriptor.devInterface == BLUETOOTH_IFC) {
/* devinfo. */
name = "Bluetooth - ";
} else if (descriptor.devInterface == ETHERNET_IFC) {
/* devinfo. */
name = "Ethernet - ";
}
name += string(descriptor.productName) + " ";
name += string(descriptor.uniqueId);
devinfo.device_name = name;
devinfo.api_specific_devindex = i;
devinfo.availableDataTypes.push_back(dtype_fl64);
devinfo.prefDataTypeIndex = 0;
devinfo.availableSampleRates.push_back(8000);
devinfo.availableSampleRates.push_back(10000);
devinfo.availableSampleRates.push_back(11025);
devinfo.availableSampleRates.push_back(16000);
devinfo.availableSampleRates.push_back(20000);
devinfo.availableSampleRates.push_back(22050);
devinfo.availableSampleRates.push_back(24000);
devinfo.availableSampleRates.push_back(32000);
devinfo.availableSampleRates.push_back(44056);
devinfo.availableSampleRates.push_back(44100);
devinfo.availableSampleRates.push_back(47250);
devinfo.availableSampleRates.push_back(48000);
devinfo.availableSampleRates.push_back(50000);
devinfo.availableSampleRates.push_back(50400);
devinfo.availableSampleRates.push_back(51000);
devinfo.prefSampleRateIndex = 11;
devinfo.availableFramesPerBlock.push_back(512);
devinfo.availableFramesPerBlock.push_back(1024);
devinfo.availableFramesPerBlock.push_back(2048);
devinfo.availableFramesPerBlock.push_back(4096);
devinfo.availableFramesPerBlock.push_back(8192);
devinfo.prefFramesPerBlockIndex = 2;
devinfo.availableInputRanges = {1.0, 10.0};
devinfo.prefInputRangeIndex = 0;
devinfo.ninchannels = 4;
devinfo.noutchannels = 1;
devinfo.hasInputIEPE = true;
devinfo.hasInputACCouplingSwitch = true;
devinfo.hasInputTrigger = true;
// Finally, this devinfo is pushed back in list
devinfolist.push_back(devinfo);
}
}
feTRACE(15);
}

View File

@ -1,13 +0,0 @@
#ifndef ULDAQ_H
#define ULDAQ_H
#include "lasp_cppqueue.h"
#include "lasp_cppdaq.h"
Daq* createUlDaqDevice(const DeviceInfo& devinfo,
const DaqConfiguration& config);
void fillUlDaqDeviceInfo(vector<DeviceInfo> &devinfolist);
#endif // ULDAQ_H

102
lasp/device/lasp_daq.cpp Normal file
View File

@ -0,0 +1,102 @@
#include "debugtrace.hpp"
DEBUGTRACE_VARIABLES;
#include "lasp_daq.h"
#if LASP_HAS_ULDAQ == 1
#include "lasp_uldaq.h"
#endif
#if LASP_HAS_RTAUDIO == 1
#include "lasp_rtaudiodaq.h"
#endif
using std::runtime_error;
DaqData::DaqData(const us nchannels, const us nframes,
const DataTypeDescriptor::DataType dtype)
: nchannels(nchannels), nframes(nframes), dtype(dtype),
dtype_descr(dtype_map.at(dtype)) {
static_assert(sizeof(char) == 1, "Invalid char size");
const DataTypeDescriptor &desc = dtype_map.at(dtype);
_data.resize(nframes * nchannels * desc.sw);
}
std::unique_ptr<Daq> Daq::createDaq(const DeviceInfo &devinfo,
const DaqConfiguration &config) {
DEBUGTRACE_ENTER;
if (!config.match(devinfo)) {
throw std::runtime_error("DaqConfiguration does not match device info");
}
// Some basic sanity checks
if ((devinfo.ninchannels != config.eninchannels.size())) {
/* cerr << "devinfo.ninchannels: " << devinfo.ninchannels << endl; */
/* cerr << "config.eninchannels.size(): " << config.eninchannels.size() <<
* endl; */
throw runtime_error("Invalid length of enabled input channels specified");
}
if ((devinfo.noutchannels != config.enoutchannels.size())) {
throw runtime_error("outvalid length of enabled output channels specified");
}
int apicode = devinfo.api.apicode;
if (devinfo.api == DaqApi()) {
throw std::runtime_error(string("Unable to match API: ") +
devinfo.api.apiname);
}
#if LASP_HAS_ULDAQ == 1
else if (devinfo.api == uldaqapi) {
return createUlDaqDevice(devinfo, config);
}
#endif
#if LASP_HAS_RTAUDIO == 1
else if (apicode >= 1 && apicode <= 5) {
return createRtAudioDevice(devinfo, config);
}
#endif
else {
throw std::runtime_error(string("Unable to match API: ") +
devinfo.api.apiname);
}
}
Daq::Daq(const DeviceInfo &devinfo, const DaqConfiguration &config)
: DaqConfiguration(config), DeviceInfo(devinfo) {
DEBUGTRACE_ENTER;
if (monitorOutput && !(nenoutchannels() > 0)) {
throw runtime_error(
"Output monitoring only possible when at least one output channel is "
"enabled. Please make sure to enable at least one output channel, or "
"disable monitoring output.");
}
}
double Daq::samplerate() const {
return availableSampleRates.at(sampleRateIndex);
}
DataTypeDescriptor::DataType Daq::dataType() const {
return availableDataTypes.at(dataTypeIndex);
}
double Daq::inputRangeForChannel(us ch) const {
if (!(ch < ninchannels)) {
throw runtime_error("Invalid channel number");
}
return availableInputRanges.at(inputRangeIndices.at(ch));
}
us Daq::neninchannels(bool include_monitorchannel) const {
us inch = std::count(eninchannels.begin(), eninchannels.end(), true);
if (monitorOutput && include_monitorchannel) {
inch += nenoutchannels();
}
return inch;
}
us Daq::nenoutchannels() const {
return std::count(enoutchannels.begin(), enoutchannels.end(), true);
}

169
lasp/device/lasp_daq.h Normal file
View File

@ -0,0 +1,169 @@
#pragma once
#include "lasp_config.h"
#include "lasp_daqconfig.h"
#include "lasp_deviceinfo.h"
#include "lasp_types.h"
#include <functional>
#include <gsl/gsl-lite.hpp>
#include <memory>
#include <optional>
/**
* @brief Data coming from / going to DAQ. Non-interleaved format, which means
* data in buffer is ordered by channel: _ptr[sample+channel*nsamples]
*/
class DaqData {
protected:
/**
* @brief Storage for actual data.
*/
std::vector<int8_t> _data;
public:
const us nchannels;
const us nframes;
const DataTypeDescriptor::DataType dtype;
const DataTypeDescriptor dtype_descr;
DaqData(const us nchannels, const us nframes,
const DataTypeDescriptor::DataType dtype_descr);
virtual ~DaqData() = default;
/**
* @brief Return reference to internal vector
*
* @return Reference to vector of data storage.
*/
std::vector<int8_t> &raw_vec() { return _data; }
us size_bytes() const { return _data.size(); }
};
/**
* @brief Typed DaqData, in which the type is specified
*
* @tparam T
*/
template <typename T> class TypedDaqData : public DaqData {
T *data() { return static_cast<T *>(_data.data()); }
};
/**
* @brief Callback of DAQ. Called with arguments of a vector of data
* spans for each channel, and a datatype descriptor. Callback should return
* false for a stop request.
*/
using ChannelView = std::vector<gsl::span<uint8_t>>;
using DaqCallback =
std::function<bool(ChannelView channel_data,
const DataTypeDescriptor &dtype_descr)>;
/**
* @brief Base cass for all DAQ instances
*/
class Daq : public DaqConfiguration, public DeviceInfo {
protected:
Daq(const DeviceInfo &devinfo, const DaqConfiguration &config);
Daq(const Daq &) = delete;
public:
/**
* @brief Create a Daq based on given device info and configuration
*
* @param devinfo Device information of device to be used.
* @param config Configuation to apply to the deviec
*
* @return Pointer to Daq device created.
*/
static std::unique_ptr<Daq> createDaq(const DeviceInfo &devinfo,
const DaqConfiguration &config);
/**
* @brief Start the Daq.
*
* @param cb Callback function that is called with input data as argument,
* and which expects output data as a return value. If no output data is
* required, the return value of the function should be nullptr. If no input
* data is presented, the function is called with a nullptr as argument.
*/
virtual void start(std::optional<DaqCallback> inCallback,
std::optional<DaqCallback> outCallback);
/**
* @brief Stop the Daq device. Throws an exception if the device is not
* running at the time this method is called.
*/
virtual void stop() = 0;
/**
* @brief Returns whether the data stream is running Y/N
*
* @return true if running
*/
virtual bool isRunning() const = 0;
virtual ~Daq(){};
/**
* @brief Returns the number of enabled input channels
*
* @param include_monitorchannel if true, all channels that are internally
* monitored are added to the list.
*
* @return number of enabled input channels
*/
us neninchannels(bool include_monitorchannel = true) const;
/**
* @brief Returns the number of enabled output channels
*
* @return Number of enabled output channels
*/
us nenoutchannels() const;
/**
* @brief Returns current sample rate
*
* @return Sample rate in [Hz]
*/
double samplerate() const;
/**
* @brief Returns the input range for each channel. Means the minimum from
* the absolute value of the minumum and maximum value that is allowed to
* pass unclipped.
*
* @param ch The channel index from the input channels.
*
* @return Maximum offset from 0 before clipping.
*/
double inputRangeForChannel(us ch) const;
/**
* @brief Returns datatype (enum) corresponding to the datatype of the
* samples.
*
* @return That as stated aboce
*/
DataTypeDescriptor::DataType dataType() const;
/**
* @brief More elaborate description of the datatypes.
*
* @return A DataTypeDescriptor
*/
DataTypeDescriptor dtypeDescr() const;
/**
* @brief The number of frames that is send in a block of DaqData.
*
* @return The number of frames per block
*/
us framesPerBlock() const {
return availableFramesPerBlock.at(framesPerBlockIndex);
}
/**
* @brief Whether the device runs in duplex mode (both input and output), or
* false if only input / only output.
*
* @return true if duplex
*/
bool duplexMode() const {
return (neninchannels(false) > 0 && nenoutchannels() > 0);
}
};

View File

@ -1,13 +0,0 @@
include "lasp_common_decls.pxd"
ctypedef struct PyStreamData
cdef class Daq:
cdef:
PyStreamData *sd
cppDaq* daq_device
cdef public:
double samplerate
unsigned int nFramesPerBlock
cdef cleanupStream(self, PyStreamData* sd)

View File

@ -1,329 +0,0 @@
cimport cython
from ..lasp_common import AvType
from .lasp_deviceinfo cimport DeviceInfo
from .lasp_daqconfig cimport DaqConfiguration
from numpy cimport import_array
from cpython.ref cimport PyObject,Py_INCREF, Py_DECREF
import numpy as np
import logging
__all__ = ['Daq']
cdef cnp.NPY_TYPES getCNumpyDataType(DataType& dt):
if(dt == dtype_fl32):
return cnp.NPY_FLOAT32
elif(dt == dtype_fl64):
return cnp.NPY_FLOAT64
elif(dt == dtype_int8):
return cnp.NPY_INT8
elif(dt == dtype_int16):
return cnp.NPY_INT16
elif(dt == dtype_int32):
return cnp.NPY_INT32
else:
raise ValueError('Unknown data type')
cdef getNumpyDataType(DataType& dt):
if(dt == dtype_fl32):
return np.dtype(np.float32)
elif(dt == dtype_fl64):
return np.dtype(np.float64)
elif(dt == dtype_int8):
return np.dtype(np.int8)
elif(dt == dtype_int16):
return np.dtype(np.int16)
elif(dt == dtype_int32):
return np.dtype(np.int32)
else:
raise ValueError('Unknown data type')
DEF QUEUE_BUFFER_TIME = 0.5
ctypedef struct PyStreamData:
PyObject* pyCallback
# Flag used to pass the stopThread.
atomic[bool] stopThread
# Flag to indicate that the signal generator queue has been filled for the
# first time.
atomic[bool] ready
# Number of frames per block
unsigned nFramesPerBlock
# Number of bytes per channel
unsigned int nBytesPerChan
unsigned ninchannels
unsigned noutchannels
double samplerate
cnp.NPY_TYPES npy_format
# If either of these queue pointers are NULL, it means the stream does not have an
# input, or output.
SafeQueue[void*] *inQueue
SafeQueue[void*] *outQueue
CPPThread[void*, void (*)(void*)] *thread
cdef void audioCallbackPythonThreadFunction(void* voidsd) nogil:
cdef:
PyStreamData* sd = <PyStreamData*> voidsd
double* inbuffer = NULL
double* outbuffer = NULL
unsigned noutchannels= sd.noutchannels
unsigned ninchannels= sd.ninchannels
unsigned nBytesPerChan= sd.nBytesPerChan
unsigned nFramesPerBlock= sd.nFramesPerBlock
double sleeptime = (<double> sd.nFramesPerBlock)/(8*sd.samplerate);
# Sleep time in microseconds
us sleeptime_us = <us> (sleeptime*1e6);
us nblocks_buffer = <us> max(1, (QUEUE_BUFFER_TIME * sd.samplerate /
sd.nFramesPerBlock))
with gil:
import_array()
npy_format = cnp.NPY_FLOAT64
callback = <object> sd.pyCallback
# print(f'Number of input channels: {ninchannels}')
# print(f'Number of out channels: {noutchannels}')
# fprintf(stderr, 'Sleep time: %d us\n', sleeptime_us)
if sd.outQueue:
for i in range(nblocks_buffer):
outbuffer = <double*> malloc(sizeof(double)*nBytesPerChan*noutchannels)
memset(outbuffer, 0, sizeof(double)*nBytesPerChan*noutchannels)
sd.outQueue.enqueue(<double*> outbuffer)
sd.ready.store(True)
while not sd.stopThread.load():
with gil:
if sd.outQueue:
while sd.outQueue.size() < nblocks_buffer:
outbuffer = <double*> malloc(sizeof(double)*nBytesPerChan*noutchannels)
npy_output = <object> data_to_ndarray(
outbuffer,
nFramesPerBlock,
noutchannels,
sd.npy_format,
False, # Do not transfer ownership to the temporary
# Numpy container
True) # F-contiguous
try:
rval = callback(None, npy_output, nFramesPerBlock)
except Exception as e:
logging.error('exception in Cython callback for audio output: ', str(e))
return
sd.outQueue.enqueue(<double*> outbuffer)
if sd.inQueue and not sd.inQueue.empty():
# Waiting indefinitely on the queue...
inbuffer = <double*> sd.inQueue.dequeue()
if inbuffer == NULL:
logging.debug('Received empty buffer on input, stopping thread...\n')
return
npy_input = <object> data_to_ndarray(
inbuffer,
nFramesPerBlock,
ninchannels,
sd.npy_format,
True, # Do transfer ownership
True) # F-contiguous is True: data is Fortran-cont.
try:
rval = callback(npy_input, None, nFramesPerBlock)
except Exception as e:
logging.error('exception in cython callback for audio input: ', str(e))
return
CPPsleep_us(sleeptime_us);
# Outputbuffer is free'ed by the audiothread, so should not be touched
# here.
outbuffer = NULL
# Inputbuffer memory is owned by Numpy, so should not be free'ed
inbuffer = NULL
cdef class Daq:
def __cinit__(self, DeviceInfo pydevinfo, DaqConfiguration pydaqconfig):
"""
Acquires a daq handle, and opens the device
"""
cdef:
cppDaqConfiguration* daqconfig
cppDeviceInfo* devinfo
vector[cppDeviceInfo] devinfos
self.daq_device = NULL
self.sd = NULL
daqconfig = &(pydaqconfig.config)
devinfo = &(pydevinfo.devinfo)
try:
self.daq_device = cppDaq.createDaq(devinfo[0], daqconfig[0])
except Exception as e:
raise
self.nFramesPerBlock = self.daq_device.framesPerBlock()
self.samplerate = self.daq_device.samplerate()
if self.nFramesPerBlock > 8192 or self.nFramesPerBlock < 512:
del self.daq_device
raise ValueError('Invalid number of nFramesPerBlock')
def __dealloc__(self):
# fprintf(stderr, "UlDaq.__dealloc__\n")
if self.sd is not NULL:
logging.debug("UlDaq.__dealloc__: stopping stream.")
self.stop()
if self.daq_device is not NULL:
del self.daq_device
self.daq_device = NULL
def getNumpyDataType(self):
cdef:
DataType dt = self.daq_device.dataType()
return getNumpyDataType(dt)
def isRunning(self):
return self.sd is not NULL
@staticmethod
def getDeviceInfo():
cdef:
vector[cppDeviceInfo] devinfos = cppDaq.getDeviceInfo()
pydevinfo = {}
for i in range(devinfos.size()):
d = DeviceInfo()
d.devinfo = <cppDeviceInfo> devinfos[i]
if d.api not in pydevinfo.keys():
pydevinfo[d.api] = [d]
else:
pydevinfo[d.api].append(d)
return pydevinfo
@cython.nonecheck(True)
def start(self, audiocallback):
"""
Opens a stream with specified parameters
Args:
avstream: AvStream instance
Returns: None
"""
if self.sd is not NULL:
assert self.daq_device is not NULL
raise RuntimeError('Stream is already opened.')
cdef:
cppDaq* daq = self.daq_device
unsigned nFramesPerBlock = self.nFramesPerBlock
DataType dtype = self.daq_device.dataType()
double samplerate = self.samplerate
# All set, allocate the stream!
self.sd = <PyStreamData*> malloc(sizeof(PyStreamData))
if self.sd == NULL:
del daq
raise MemoryError('Could not allocate stream: memory error.')
self.sd.stopThread.store(False)
self.sd.ready.store(False)
self.sd.inQueue = NULL
self.sd.outQueue = NULL
self.sd.thread = NULL
self.sd.samplerate = <double> samplerate
self.sd.ninchannels = daq.neninchannels(True)
self.sd.noutchannels = daq.nenoutchannels()
self.sd.nBytesPerChan = nFramesPerBlock*dtype.sw
self.sd.nFramesPerBlock = nFramesPerBlock
self.sd.npy_format = getCNumpyDataType(dtype)
if daq.neninchannels(True) > 0:
self.sd.inQueue = new SafeQueue[void*]()
if daq.nenoutchannels() > 0:
self.sd.outQueue = new SafeQueue[void*]()
self.sd.pyCallback = <PyObject*> audiocallback
# Increase reference count to the callback
Py_INCREF(<object> audiocallback)
with nogil:
self.sd.thread = new CPPThread[void*, void (*)(void*)](audioCallbackPythonThreadFunction,
<void*> self.sd)
while not self.sd.ready.load():
# Allow stream stome time to start
CPPsleep_ms(100)
self.daq_device.start(
self.sd.inQueue,
self.sd.outQueue)
return self.daq_device.samplerate()
def stop(self):
if self.sd is NULL:
raise RuntimeError('Stream is not opened')
self.cleanupStream(self.sd)
self.sd = NULL
cdef cleanupStream(self, PyStreamData* sd):
with nogil:
if sd.thread:
sd.stopThread.store(True)
if sd.inQueue:
# If waiting in the input queue, hereby we let it run.
sd.inQueue.enqueue(NULL)
sd.thread.join()
del sd.thread
sd.thread = NULL
if sd.inQueue:
while not sd.inQueue.empty():
free(sd.inQueue.dequeue())
del sd.inQueue
if sd.outQueue:
while not sd.outQueue.empty():
free(sd.outQueue.dequeue())
del sd.outQueue
sd.outQueue = NULL
logging.debug("End cleanup stream queues...\n")
if sd.pyCallback:
Py_DECREF(<object> sd.pyCallback)
sd.pyCallback = NULL
free(sd)

View File

@ -0,0 +1,97 @@
#include "lasp_daqconfig.h"
#include "lasp_deviceinfo.h"
#include <algorithm>
#include <cassert>
#define MAX_DEV_COUNT_PER_API 20
using std::vector;
vector<DaqApi> DaqApi::getAvailableApis() {
vector<DaqApi> apis;
apis.resize(6);
#if LASP_HAS_ULDAQ == 1
apis.at(uldaqapi.apicode) = uldaqapi;
#endif
#if LASP_HAS_RTAUDIO == 1
apis.at(rtaudioAlsaApi.apicode) = rtaudioAlsaApi;
apis.at(rtaudioPulseaudioApi.apicode) = rtaudioPulseaudioApi;
apis.at(rtaudioWasapiApi.apicode) = rtaudioWasapiApi;
apis.at(rtaudioDsApi.apicode) = rtaudioDsApi;
apis.at(rtaudioAsioApi.apicode) = rtaudioAsioApi;
#endif
return apis;
}
DaqConfiguration::DaqConfiguration(const DeviceInfo &device) {
api = device.api;
device_name = device.device_name;
eninchannels.resize(device.ninchannels, false);
enoutchannels.resize(device.noutchannels, false);
inchannel_sensitivities.resize(device.ninchannels, 1.0);
inchannel_metadata.resize(device.ninchannels, "");
for (us i = 0; i < eninchannels.size(); i++) {
std::stringstream chname;
chname << "Unnamed input channel " << i;
inchannel_names.push_back(chname.str());
}
outchannel_metadata.resize(device.noutchannels, "");
outchannel_sensitivities.resize(device.noutchannels, 1.0);
for (us i = 0; i < enoutchannels.size(); i++) {
std::stringstream chname;
chname << "Unnamed output channel " << i;
outchannel_names.push_back(chname.str());
}
sampleRateIndex = device.prefSampleRateIndex;
dataTypeIndex = device.prefDataTypeIndex;
framesPerBlockIndex = device.prefFramesPerBlockIndex;
monitorOutput = false;
inputIEPEEnabled.resize(device.ninchannels, false);
inputACCouplingMode.resize(device.ninchannels, false);
inputRangeIndices.resize(device.ninchannels, device.prefInputRangeIndex);
assert(match(device));
}
bool DaqConfiguration::match(const DeviceInfo &dev) const {
return (dev.device_name == device_name && dev.api == api);
}
int DaqConfiguration::getHighestInChannel() const {
for (int i = eninchannels.size() - 1; i > -1; i--) {
if (eninchannels.at(i))
return i;
}
return -1;
}
int DaqConfiguration::getHighestOutChannel() const {
for (us i = enoutchannels.size() - 1; i >= 0; i--) {
if (enoutchannels.at(i))
return i;
}
return -1;
}
int DaqConfiguration::getLowestInChannel() const {
for (us i = 0; i < eninchannels.size(); i++) {
if (eninchannels.at(i))
return i;
}
return -1;
}
int DaqConfiguration::getLowestOutChannel() const {
for (us i = 0; i < enoutchannels.size(); i++) {
if (enoutchannels.at(i))
return i;
}
return -1;
}

View File

@ -0,0 +1,250 @@
#pragma once
#include "lasp_config.h"
#include "lasp_types.h"
#include <map>
#include <vector>
using std::string;
using std::to_string;
using boolvec = std::vector<bool>;
using dvec = std::vector<double>;
using usvec = std::vector<us>;
/**
* @brief Descriptor for data types containing more detailed information.
*/
class DataTypeDescriptor {
public:
/**
* @brief Basic data types coming from a DAQ that we can deal with. The naming
* will be self-explainging.
*/
enum class DataType {
dtype_fl32 = 0,
dtype_fl64 = 1,
dtype_int8 = 2,
dtype_int16 = 3,
dtype_int32 = 4
};
/**
* @brief Name of the datatype
*/
string name;
/**
* @brief Sample width of a single sample, in bytes
*/
unsigned sw;
/**
* @brief Whether the datatype is a floating point Y/N
*/
bool is_floating;
/**
* @brief The number from the enumeration
*/
DataType dtype;
/**
* @brief Down-cast a DataTypeDescriptor to a datatype
*
* @return The descriptor as an enum
*/
operator DataType() { return dtype; }
/**
* @brief Compare two data type descriptors. Returns true if the DataType
* enumerator is the same.
*
* @param o
*
* @return
*/
bool operator==(const DataTypeDescriptor &o) noexcept {
return dtype == o.dtype;
}
};
const DataTypeDescriptor dtype_desc_fl32 = {
"32-bits floating point", 4, true,
DataTypeDescriptor::DataType::dtype_fl32};
const DataTypeDescriptor dtype_desc_fl64 = {
"64-bits floating point", 8, true,
DataTypeDescriptor::DataType::dtype_fl64};
const DataTypeDescriptor dtype_desc_int8 = {
"8-bits integer", 1, false, DataTypeDescriptor::DataType::dtype_int8};
const DataTypeDescriptor dtype_desc_int16 = {
"16-bits integer", 2, false, DataTypeDescriptor::DataType::dtype_int16};
const DataTypeDescriptor dtype_desc_int32 = {
"32-bits integer", 4, false, DataTypeDescriptor::DataType::dtype_int32};
const std::map<DataTypeDescriptor::DataType, const DataTypeDescriptor>
dtype_map = {
{DataTypeDescriptor::DataType::dtype_fl32, dtype_desc_fl32},
{DataTypeDescriptor::DataType::dtype_fl64, dtype_desc_fl64},
{DataTypeDescriptor::DataType::dtype_int8, dtype_desc_int8},
{DataTypeDescriptor::DataType::dtype_int16, dtype_desc_int16},
{DataTypeDescriptor::DataType::dtype_int32, dtype_desc_int32},
};
class DaqApi {
public:
string apiname = "Invalid API";
int apicode = -1;
unsigned api_specific_subcode = 0;
DaqApi(string apiname, unsigned apicode, unsigned api_specific_subcode = 0)
: apiname(apiname), apicode(apicode),
api_specific_subcode(api_specific_subcode) {}
DaqApi() {}
bool operator==(const DaqApi &other) const {
return (apiname == other.apiname && apicode == other.apicode &&
api_specific_subcode == other.api_specific_subcode);
}
operator string() const { return apiname + ", code: " + to_string(apicode); }
static std::vector<DaqApi> getAvailableApis();
};
#if LASP_HAS_ULDAQ == 1
const DaqApi uldaqapi("UlDaq", 0);
#endif
#if LASP_HAS_RTAUDIO == 1
#include <RtAudio.h>
const DaqApi rtaudioAlsaApi("RtAudio Linux ALSA", 1, RtAudio::Api::LINUX_ALSA);
const DaqApi rtaudioPulseaudioApi("RtAudio Linux Pulseaudio", 2,
RtAudio::Api::LINUX_PULSE);
const DaqApi rtaudioWasapiApi("RtAudio Windows Wasapi", 3,
RtAudio::Api::WINDOWS_WASAPI);
const DaqApi rtaudioDsApi("RtAudio Windows DirectSound", 4,
RtAudio::Api::WINDOWS_DS);
const DaqApi rtaudioAsioApi("RtAudio Windows ASIO", 5,
RtAudio::Api::WINDOWS_ASIO);
#endif
class DeviceInfo;
/**
* @brief Configuration of a DAQ device
*/
class DaqConfiguration {
public:
/**
* @brief The API of the DAQ system this configuration applies to.
*/
DaqApi api;
/**
* @brief The internal device name this DAQ configuration applies to.
*/
string device_name;
/**
* @brief Enabled input channels
*/
boolvec eninchannels;
/**
* @brief Enabled 'normal' output channels, i.e. no internal loopbacks.
*/
boolvec enoutchannels;
std::vector<double> inchannel_sensitivities;
std::vector<string> inchannel_names;
std::vector<string> inchannel_metadata;
std::vector<double> outchannel_sensitivities;
std::vector<string> outchannel_names;
std::vector<string> outchannel_metadata;
/**
* @brief Index in list of sample rates that are available for the device.
*/
us sampleRateIndex = 0; //
/**
* @brief Required datatype for output, should be present in the list
*/
us dataTypeIndex = 0;
/**
* @brief The index in the array of frames per block that can be used for the
* device.
*/
us framesPerBlockIndex = 0;
/**
* @brief If set to true and if the device has this capability, the output
* channels are added as input channels as well.
*/
bool monitorOutput = false;
/**
* @brief If the device is capable, enable IEPE constant current power supply
* for given channel number.
*/
boolvec inputIEPEEnabled;
/**
* @brief If the device is capable, here we can define whether the channel
* should enable hardware AC-coupling.
*/
boolvec inputACCouplingMode;
/**
* @brief Stores the index of the used input range for each of the channels.
*/
usvec inputRangeIndices;
/**
* @brief Create a default configuration, with all channels disabled on both
* input and output, and default channel names
*
* @param deviceinfo DeviceInfo structure
*/
DaqConfiguration(const DeviceInfo &DeviceInfo);
DaqConfiguration() = delete;
/**
* @brief Check to see whether the DAQ configuration matches with the device.
* This means, some basic checks are done on channels, sampling rate, etc,
* and that the name corresponds with the device name.
*
* @param devinfo The DeviceInfo to check
*
* @return true if it matches. Otherwise false.
*/
bool match(const DeviceInfo &devinfo) const;
/**
* @brief Get the highest channel number from the list of enabled input
* channels.
*
* @return Index to the highest input channel. -1 if no input channels are
* enabled.
*/
int getHighestInChannel() const;
/**
* @brief Get the highest channel number from the list of enabled output
* channels.
*
* @return Index to the highest input channel. -1 if no output channels are
* enabled.
*/
int getHighestOutChannel() const;
/**
* @brief Get the lowest channel number from the list of enabled input
* channels.
*
* @return Index to the lowest input channel. -1 if no input channels are
* enabled.
*/
int getLowestInChannel() const;
/**
* @brief Get the lowest channel number from the list of enabled output
* channels.
*
* @return Index to the lowest input channel. -1 if no output channels are
* enabled.
*/
int getLowestOutChannel() const;
};

View File

@ -1,295 +0,0 @@
# -*- coding: utf-8 -*-
"""!
Author: J.A. de Jong - ASCEE
Description:
Data Acquistiion (DAQ) device descriptors, and the DAQ devices themselves
"""
__all__ = ['DaqConfiguration', 'DaqConfigurations']
from ..lasp_common import lasp_shelve, SIQtys, Qty
from .lasp_device_common import DaqChannel
import json
cdef class DaqConfigurations:
cdef public:
object input_config, output_config, duplex_mode
def __init__(self, duplex_mode, DaqConfiguration input_config,
DaqConfiguration output_config):
self.input_config = input_config
self.output_config = output_config
self.duplex_mode = duplex_mode
def to_json(self):
return json.dumps(
dict(
duplex_mode = self.duplex_mode,
input_config = self.input_config.to_json(),
output_config = self.output_config.to_json(),
)
)
@staticmethod
def from_json(daq_configs_json):
configs_dict = json.loads(daq_configs_json)
input_config = DaqConfiguration.from_json(configs_dict['input_config'])
output_config = DaqConfiguration.from_json(configs_dict['output_config'])
return DaqConfigurations(configs_dict['duplex_mode'],
input_config,
output_config)
@staticmethod
def loadAllConfigs():
"""
Returns a dictionary of all configurations presets. The dictionary keys
are the names of the configurations
"""
with lasp_shelve() as sh:
configs_json = sh.load('daqconfigs', {})
configs = {}
for name, val in configs_json.items():
configs[name] = DaqConfigurations.from_json(val)
return configs
@staticmethod
def loadConfigs(name: str):
"""
Load a configuration preset, containing input config and output config
"""
with lasp_shelve() as sh:
configs_json = sh.load('daqconfigs', {})
return DaqConfigurations.from_json(configs_json[name])
def saveConfigs(self, name):
with lasp_shelve() as sh:
configs_json = sh.load('daqconfigs', {})
configs_json[name] = self.to_json()
sh.store('daqconfigs', configs_json)
@staticmethod
def deleteConfigs(name):
with lasp_shelve() as sh:
configs_json = sh.load('daqconfigs', {})
del configs_json[name]
sh.store('daqconfigs', configs_json)
def constructDaqConfig(dict_data):
return DaqConfiguration.from_dict(dict_data)
cdef class DaqConfiguration:
"""
Initialize a device descriptor
"""
def __cinit__(self):
pass
def __str__(self):
return str(self.to_json())
@staticmethod
def fromDeviceInfo(DeviceInfo devinfo):
cdef:
cppDaqConfiguration cconfig
d = DaqConfiguration()
cconfig = cppDaqConfiguration(devinfo.devinfo)
d.config = cconfig
return d
@staticmethod
def from_json(jsonstring):
config_dict = json.loads(jsonstring)
return DaqConfiguration.from_dict(config_dict)
def __reduce__(self):
return (constructDaqConfig, (self.to_dict(),))
@staticmethod
def from_dict(pydict):
cdef:
cppDaqConfiguration config
vector[DaqApi] apis = DaqApi.getAvailableApis()
config.api = apis[pydict['apicode']]
config.device_name = pydict['device_name'].encode('utf-8')
config.eninchannels = pydict['eninchannels']
config.enoutchannels = pydict['enoutchannels']
config.inchannel_names = [inchname.encode('utf-8') for inchname in
pydict['inchannel_names']]
config.outchannel_names = [outchname.encode('utf-8') for outchname in
pydict['outchannel_names']]
config.inchannel_sensitivities = pydict['inchannel_sensitivities']
config.outchannel_sensitivities = pydict['outchannel_sensitivities']
config.sampleRateIndex = pydict['sampleRateIndex']
config.framesPerBlockIndex = pydict['framesPerBlockIndex']
config.dataTypeIndex = pydict['dataTypeIndex']
config.monitorOutput = pydict['monitorOutput']
config.inputIEPEEnabled = pydict['inputIEPEEnabled']
config.inputACCouplingMode = pydict['inputACCouplingMode']
config.inputRangeIndices = pydict['inputRangeIndices']
config.inchannel_metadata = [inchmeta.encode('utf-8') for inchmeta in
pydict['inchannel_metadata']]
config.outchannel_metadata = [outchmeta.encode('utf-8') for outchmeta in
pydict['outchannel_metadata']]
pydaqcfg = DaqConfiguration()
pydaqcfg.config = config
return pydaqcfg
def to_dict(self):
return dict(
apicode = self.config.api.apicode,
device_name = self.config.device_name.decode('utf-8'),
eninchannels = self.eninchannels(),
enoutchannels = self.enoutchannels(),
inchannel_names = [name.decode('utf-8') for name in
self.config.inchannel_names],
outchannel_names = [name.decode('utf-8') for name in
self.config.outchannel_names],
inchannel_sensitivities = [sens for sens in
self.config.inchannel_sensitivities],
outchannel_sensitivities = [sens for sens in
self.config.outchannel_sensitivities],
inchannel_metadata = [inchmeta.decode('utf-8') for inchmeta in
self.config.inchannel_metadata],
outchannel_metadata = [outchmeta.decode('utf-8') for outchmeta in
self.config.outchannel_metadata],
sampleRateIndex = self.config.sampleRateIndex,
dataTypeIndex = self.config.dataTypeIndex,
framesPerBlockIndex = self.config.framesPerBlockIndex,
monitorOutput = self.config.monitorOutput,
inputIEPEEnabled = self.config.inputIEPEEnabled,
inputACCouplingMode = self.config.inputACCouplingMode,
inputRangeIndices = self.config.inputRangeIndices,
)
def to_json(self):
return json.dumps(self.to_dict())
def getInChannel(self, i:int):
return DaqChannel(
channel_enabled=self.config.eninchannels.at(i),
channel_name=self.config.inchannel_names.at(i).decode('utf-8'),
sensitivity=self.config.inchannel_sensitivities.at(i),
range_index=self.config.inputRangeIndices.at(i),
ACCoupling_enabled=self.config.inputACCouplingMode.at(i),
IEPE_enabled=self.config.inputIEPEEnabled.at(i),
channel_metadata=self.config.inchannel_metadata.at(i).decode('utf-8'),
)
def getOutChannel(self, i:int):
return DaqChannel(
channel_enabled=self.config.enoutchannels.at(i),
channel_name=self.config.outchannel_names.at(i).decode('utf-8'),
channel_metadata=self.config.outchannel_metadata.at(i).decode('utf-8'),
)
def setInChannel(self, i:int, ch: DaqChannel):
self.config.eninchannels[i] = ch.channel_enabled
self.config.inchannel_names[i] = ch.channel_name.encode('utf-8')
self.config.inchannel_sensitivities[i] = ch.sensitivity
self.config.inputRangeIndices[i] = ch.range_index
self.config.inputACCouplingMode[i] = ch.ACCoupling_enabled
self.config.inputIEPEEnabled[i] = ch.IEPE_enabled
self.config.inchannel_metadata[i] = ch.channel_metadata.encode('utf-8')
def setOutChannel(self, i:int, ch: DaqChannel):
self.config.enoutchannels[i] = ch.channel_enabled
self.config.outchannel_names[i] = ch.channel_name.encode('utf-8')
self.config.outchannel_metadata[i] = ch.channel_metadata.encode('utf-8')
def getEnabledInChannels(self, include_monitor=True):
inch = []
for i, enabled in enumerate(self.config.eninchannels):
if enabled:
inch.append(self.getInChannel(i))
if include_monitor:
outch = self.getEnabledOutChannels()
if len(outch) > 0 and self.monitorOutput:
inch.insert(0, outch[0])
return inch
def getEnabledOutChannels(self):
outch = []
for i, enabled in enumerate(self.config.enoutchannels):
if enabled:
outch.append(self.getOutChannel(i))
return outch
@property
def api(self):
return self.config.api.apiname.decode('utf-8')
@api.setter
def api(self, apitxt):
cdef:
vector[DaqApi] apis = DaqApi.getAvailableApis()
for api in apis:
if api.apiname.decode('utf-8') == apitxt:
self.config.api = api
return
raise RuntimeError(f'Api {apitxt} unavailable')
def eninchannels(self):
return self.config.eninchannels
def enoutchannels(self):
return self.config.enoutchannels
@property
def sampleRateIndex(self):
return self.config.sampleRateIndex
@sampleRateIndex.setter
def sampleRateIndex(self, int idx):
self.config.sampleRateIndex = idx
@property
def dataTypeIndex(self):
return self.config.dataTypeIndex
@dataTypeIndex.setter
def dataTypeIndex(self, int idx):
self.config.dataTypeIndex = idx
@property
def framesPerBlockIndex(self):
return self.config.framesPerBlockIndex
@framesPerBlockIndex.setter
def framesPerBlockIndex(self, int idx):
self.config.framesPerBlockIndex = idx
@property
def monitorOutput(self):
return self.config.monitorOutput
@monitorOutput.setter
def monitorOutput(self, bool idx):
self.config.monitorOutput = idx
@property
def device_name(self):
return self.config.device_name.decode('utf-8')
@device_name.setter
def device_name(self, idx):
self.config.device_name = idx.encode('utf-8')

View File

@ -0,0 +1,139 @@
#include "lasp_deviceinfo.h"
#include <algorithm>
#include <cassert>
#define MAX_DEV_COUNT_PER_API 20
#if LASP_HAS_ULDAQ == 1
#include "lasp_uldaq.h"
#endif
#if LASP_HAS_RTAUDIO == 1
#include "lasp_rtaudiodaq.h"
#endif
string DeviceInfo::serialize() const {
// Simple serializer for this object, used because we found a bit late
// that this object needs to be send over the wire. We do not want to make
// this implementation in Python, as these objects are created here, in
// the C++ code. The Python wrapper is just a readonly wrapper.
std::stringstream str;
str << api.apiname << "\t";
str << api.apicode << "\t";
str << api.api_specific_subcode << "\t";
str << device_name << "\t";
str << availableDataTypes.size() << "\t";
for (const auto &dtype : availableDataTypes) {
// WARNING: THIS GOES COMPLETELY WRONG WHEN NAMES contain A TAB!!!
str << static_cast<int>(dtype) << "\t";
}
str << prefDataTypeIndex << "\t";
str << availableSampleRates.size() << "\t";
for (const double &fs : availableSampleRates) {
// WARNING: THIS GOES COMPLETELY WRONG WHEN NAMES contain A TAB!!!
str << fs << "\t";
}
str << prefSampleRateIndex << "\t";
str << availableFramesPerBlock.size() << "\t";
for (const us &fb : availableFramesPerBlock) {
// WARNING: THIS GOES COMPLETELY WRONG WHEN NAMES contain A TAB!!!
str << fb << "\t";
}
str << prefFramesPerBlockIndex << "\t";
str << availableInputRanges.size() << "\t";
for (const double &ir : availableInputRanges) {
// WARNING: THIS GOES COMPLETELY WRONG WHEN NAMES contain A TAB!!!
str << ir << "\t";
}
str << prefInputRangeIndex << "\t";
str << ninchannels << "\t";
str << noutchannels << "\t";
str << int(hasInputIEPE) << "\t";
str << int(hasInputACCouplingSwitch) << "\t";
str << int(hasInputTrigger) << "\t";
return str.str();
}
DeviceInfo DeviceInfo::deserialize(const string &dstr) {
DeviceInfo devinfo;
std::stringstream str(dstr);
string tmp;
us N;
// Lambda functions for deserializing
auto nexts = [&]() {
getline(str, tmp, '\t');
return tmp;
};
auto nexti = [&]() {
getline(str, tmp, '\t');
return std::atoi(tmp.c_str());
};
auto nextf = [&]() {
getline(str, tmp, '\t');
return std::atof(tmp.c_str());
};
// Api
string apiname = nexts();
auto apicode = nexti();
auto api_specific_subcode = nexti();
DaqApi api(apiname, apicode, api_specific_subcode);
devinfo.api = api;
devinfo.device_name = nexts();
N = us(nexti());
for (us i = 0; i < N; i++) {
devinfo.availableDataTypes.push_back(
static_cast<DataTypeDescriptor::DataType>(nexti()));
}
devinfo.prefDataTypeIndex = nexti();
N = us(nexti());
for (us i = 0; i < N; i++) {
devinfo.availableSampleRates.push_back(nextf());
}
devinfo.prefSampleRateIndex = nexti();
N = us(nexti());
for (us i = 0; i < N; i++) {
devinfo.availableFramesPerBlock.push_back(nexti());
}
devinfo.prefFramesPerBlockIndex = nexti();
N = us(nexti());
for (us i = 0; i < N; i++) {
devinfo.availableInputRanges.push_back(nexti());
}
devinfo.prefInputRangeIndex = nexti();
devinfo.ninchannels = nexti();
devinfo.noutchannels = nexti();
devinfo.hasInputIEPE = bool(nexti());
devinfo.hasInputACCouplingSwitch = bool(nexti());
devinfo.hasInputTrigger = bool(nexti());
return devinfo;
}
std::vector<DeviceInfo> DeviceInfo::getDeviceInfo() {
std::vector<DeviceInfo> devs;
#if LASP_HAS_ULDAQ ==1
fillUlDaqDeviceInfo(devs);
#endif
#if LASP_HAS_RTAUDIO == 1
fillRtAudioDeviceInfo(devs);
#endif
return devs;
}

View File

@ -0,0 +1,134 @@
#pragma once
#include "lasp_config.h"
#include "lasp_types.h"
#include "lasp_daqconfig.h"
/**
* @brief Structure containing device info parameters
*/
class DeviceInfo {
public:
/**
* @brief Backend API corresponding to device
*/
DaqApi api;
/**
* @brief The name of the device
*/
string device_name = "";
/**
* @brief Specific for the device (Sub-API). Important for the RtAudio
* backend, as RtAudio is able to handle different API's.
*/
int api_specific_devindex = -1;
/**
* @brief The available data types the device can output
*/
std::vector<DataTypeDescriptor::DataType> availableDataTypes;
/**
* @brief The device's prefferd data type.
*/
int prefDataTypeIndex = 0;
/**
* @brief Available sample rates the device can run on
*/
dvec availableSampleRates;
/**
* @brief Preferred setting for the sample rate.
*/
int prefSampleRateIndex = -1;
/**
* @brief Available latency-setting (number of frames passed in each
* callback).
*/
usvec availableFramesPerBlock;
/**
* @brief Preffered number of frames per callback.
*/
us prefFramesPerBlockIndex = 0;
/**
* @brief Available ranges for the input, i.e. +/- 1V and/or +/- 10 V etc.
*/
dvec availableInputRanges;
/**
* @brief Its preffered range
*/
int prefInputRangeIndex = 0;
/**
* @brief The number of input channels available for the device
*/
unsigned ninchannels = 0;
/**
* @brief The number of output channels available for the device
*/
unsigned noutchannels = 0;
/**
* @brief Whether the device is capable to provide IEPE constant current
* power supply.
*/
bool hasInputIEPE = false;
/**
* @brief Whether the device is capable of enabling a hardware AC-coupling
*/
bool hasInputACCouplingSwitch = false;
/**
* @brief Whether the device is able to trigger on input
*/
bool hasInputTrigger = false;
double prefSampleRate() const {
if (((us)prefSampleRateIndex < availableSampleRates.size()) &&
(prefSampleRateIndex >= 0)) {
return availableSampleRates.at(prefSampleRateIndex);
} else {
throw std::runtime_error("No prefered sample rate available");
}
}
/**
* @brief String representation of DeviceInfo
*
* @return string
*/
operator string() const {
using std::endl;
std::stringstream str;
str << api.apiname + " " << api_specific_devindex << endl
<< " number of input channels: " << ninchannels << endl
/**
* @brief
*/
<< " number of output channels: " << noutchannels << endl;
return str.str();
}
/**
* @brief For backwards-compatibility: serialize device info to string
*
* @return Serialize-string
*/
string serialize() const;
/**
* @brief Create a device info structure from serialized string
*
* @param dstr String to deserialize from
*
* @return resulting DeviceInfo
*/
static DeviceInfo deserialize(const string& dstr);
/**
* @brief Create a list of DeviceInfo's that are at call time avalable
*
* @return The device info's for each found device.
*/
static std::vector<DeviceInfo> getDeviceInfo();
};

View File

@ -1,5 +0,0 @@
include "lasp_common_decls.pxd"
cdef class DeviceInfo:
cdef:
cppDeviceInfo devinfo

View File

@ -1,128 +0,0 @@
# -*- coding: utf-8 -*-
"""!
Author: J.A. de Jong - ASCEE
Description:
DeviceInfo C++ object wrapper
"""
__all__ = ['DeviceInfo']
def pickle(dat):
dev = DeviceInfo()
# print('DESERIALIZE****')
dev.devinfo = dev.devinfo.deserialize(dat)
return dev
cdef class DeviceInfo:
def __cinit__(self):
pass
def __init__(self):
pass
def __reduce__(self):
serialized = self.devinfo.serialize()
# print('SERIALIZE****')
return (pickle, (serialized,))
@property
def api(self): return self.devinfo.api.apiname.decode('utf-8')
@property
def name(self): return self.devinfo.device_name.decode('utf-8')
def __repr__(self):
return self.api + ': ' + self.name
@property
def ninchannels(self): return self.devinfo.ninchannels
@property
def noutchannels(self): return self.devinfo.noutchannels
@property
def availableSampleRates(self): return self.devinfo.availableSampleRates
@property
def availableFramesPerBlock(self):
return self.devinfo.availableFramesPerBlock
@property
def availableDataTypes(self):
pydtypes = []
for datatype in self.devinfo.availableDataTypes:
pydtypes.append(datatype.name.decode('utf-8'))
return pydtypes
@property
def prefSampleRateIndex(self):
return self.devinfo.prefSampleRateIndex
@property
def prefSampleRate(self):
return self.availableSampleRates[
self.prefSampleRateIndex]
@property
def prefFramesPerBlockIndex(self):
return self.devinfo.prefFramesPerBlockIndex
@property
def prefFramesPerBlock(self):
return self.availableFramesPerBlock[
self.prefFramesPerBlockIndex]
@property
def prefDataTypeIndex(self):
return self.devinfo.prefDataTypeIndex
@property
def prefDataType(self):
return self.availableDataTypes[
self.prefDataTypeIndex]
@property
def hasInputIEPE(self):
return self.devinfo.hasInputIEPE
@property
def hasInputACCouplingSwitch(self):
return self.devinfo.hasInputACCouplingSwitch
@property
def hasInputTrigger(self):
return self.devinfo.hasInputTrigger
@property
def inputRanges(self):
return self.devinfo.availableInputRanges
@staticmethod
def getDeviceInfo():
"""
Returns device information objects (DeviceInfo) for all available
devices
"""
cdef:
vector[cppDeviceInfo] devinfos
us numdevs, devno
cppDeviceInfo* devinfo
devinfos = cppDaq.getDeviceInfo()
numdevs = devinfos.size()
pydevinfos = []
for devno in range(numdevs):
devinfo = &(devinfos[devno])
d = DeviceInfo()
d.devinfo = devinfo[0]
pydevinfos.append(d)
return pydevinfos
@property
def availableInputRanges(self):
return self.devinfo.availableInputRanges

View File

@ -0,0 +1,236 @@
#if 0
#include "lasp_devicepybind.h"
#include <algorithm>
#include <assert.h>
#include <numpy/arrayobject.h>
#include <pybind11/numpy.h>
#include <stdint.h>
#include <thread>
using std::cerr;
namespace py = pybind11;
PYBIND11_MODULE(lasp_daq, m) {
m.doc() = "Lasp DAQ interface";
/// DataType
py::class_<DataType> datatype(m, "DataType");
datatype.def_readonly("name", &DataType::name);
datatype.def_readonly("sw", &DataType::sw);
datatype.def_readonly("is_floating", &DataType::is_floating);
m.attr("dtype_fl32") = dtype_fl32;
m.attr("dtype_fl64") = dtype_fl64;
m.attr("dtype_int8") = dtype_int8;
m.attr("dtype_int16") = dtype_int16;
m.attr("dtype_int24") = dtype_int24;
m.attr("dtype_int32") = dtype_int32;
/// DaqApi
py::class_<DaqApi> daqapi(m, "DaqApi");
daqapi.def_readonly("apiname", &DaqApi::apiname);
daqapi.def_readonly("apicode", &DaqApi::apicode);
daqapi.def_readonly("api_specific_subcode", &DaqApi::api_specific_subcode);
daqapi.def("__str__", [](const DaqApi &d) { return std::string(d); });
/// DeviceInfo
py::class_<DeviceInfo> devinfo(m, "DeviceInfo");
devinfo.def_readonly("api", &DeviceInfo::api);
devinfo.def_readonly("device_name", &DeviceInfo::device_name);
devinfo.def_readonly("availableDataTypes", &DeviceInfo::availableDataTypes);
devinfo.def_readonly("prefDataTypeIndex", &DeviceInfo::prefDataTypeIndex);
devinfo.def_readonly("availableSampleRates",
&DeviceInfo::availableSampleRates);
devinfo.def_readonly("prefSampleRateIndex", &DeviceInfo::prefSampleRateIndex);
devinfo.def_readonly("availableFramesPerBlock",
&DeviceInfo::availableFramesPerBlock);
devinfo.def_readonly("prefFramesPerBlockIndex",
&DeviceInfo::prefFramesPerBlockIndex);
devinfo.def_readonly("availableInputRanges",
&DeviceInfo::availableInputRanges);
devinfo.def_readonly("prefInputRangeIndex", &DeviceInfo::prefInputRangeIndex);
devinfo.def_readonly("ninchannels", &DeviceInfo::ninchannels);
devinfo.def_readonly("noutchannels", &DeviceInfo::noutchannels);
devinfo.def_readonly("hasInputIEPE", &DeviceInfo::hasInputIEPE);
devinfo.def_readonly("hasInputACCouplingSwitch",
&DeviceInfo::hasInputACCouplingSwitch);
devinfo.def("serialize", &DeviceInfo::serialize);
devinfo.def_static("deserialize", &DeviceInfo::deserialize);
/// DaqConfiguration
py::class_<DaqConfiguration> daqconfig(m, "DaqConfiguration");
daqconfig.def(py::init<>());
daqconfig.def_readwrite("eninchannels", &DaqConfiguration::eninchannels);
daqconfig.def_readwrite("enoutchannels", &DaqConfiguration::enoutchannels);
daqconfig.def_readwrite("inchannel_sensitivities",
&DaqConfiguration::inchannel_sensitivities);
daqconfig.def_readwrite("inchannel_metadata",
&DaqConfiguration::inchannel_metadata);
daqconfig.def_readwrite("inchannel_names",
&DaqConfiguration::inchannel_names);
daqconfig.def_readwrite("outchannel_sensitivities",
&DaqConfiguration::outchannel_sensitivities);
daqconfig.def_readwrite("outchannel_names",
&DaqConfiguration::outchannel_names);
daqconfig.def_readwrite("inchannel_metadata",
&DaqConfiguration::inchannel_metadata);
daqconfig.def_readwrite("sampleRateIndex",
&DaqConfiguration::sampleRateIndex);
daqconfig.def_readwrite("dataTypeIndex",
&DaqConfiguration::dataTypeIndex);
daqconfig.def_readwrite("framesPerBlockIndex",
&DaqConfiguration::framesPerBlockIndex);
daqconfig.def_readwrite("monitorOutput",
&DaqConfiguration::monitorOutput);
daqconfig.def_readwrite("inputIEPEEnabled",
&DaqConfiguration::inputIEPEEnabled);
daqconfig.def_readwrite("inputACCouplingMode",
&DaqConfiguration::inputACCouplingMode);
daqconfig.def_readwrite("inputRangeIndices",
&DaqConfiguration::inputRangeIndices);
daqconfig.def("match", &DaqConfiguration::match);
/// Daq
py::class_<PyDaq> daq(m, "Daq");
daq.def(py::init<DeviceInfo,DaqConfiguration>());
/* daq.def("start */
/* daqconfig.def(py::init< */
}
const d QUEUE_BUFFER_TIME = 0.1;
PyDaq::PyDaq(const DeviceInfo &devinfo, const DaqConfiguration &config) {
_daq = Daq::createDaq(devinfo, config);
_stopThread = false;
_threadReady = false;
}
PyDaq::~PyDaq() {
if (_daqThread) {
}
}
/* py::array arrayFromBuffer(void *buf, us n_rows, us n_cols, DataType dtype) { */
/* // https://github.com/pybind/pybind11/issues/27 */
/* py::size_t dims[] = {n_rows, n_cols}; */
/* py::buffer_info; */
/* switch (dtype) { */
/* case dtype_int8: */
/* break; */
/* case dtype_int16: */
/* break; */
/* case dtype_int24: */
/* break; */
/* case dtype_fl32: */
/* break; */
/* case dtype_fl64: */
/* break; */
/* } */
/* /1* buffer_info = py::buffer_info( *1/ */
/* /1* py::buffer_info(buf, sizeof(float), *1/ */
/* /1* py::format_descriptor<float>::format(), *1/ */
/* /1* 2, *1/ */
/* /1* dims, (float*) buf));; *1/ */
/* /1* } *1/ */
/* py::array res; */
/* } */
void PyDaq::start(py::function audioCallback) {
// Number of input channels, including monitor channel
const us neninchannels = _daq->neninchannels(true);
const us nenoutchannels = _daq->nenoutchannels();
const bool input = neninchannels > 0;
const bool output = nenoutchannels > 0;
SafeQueue<void *> *inQueue = input ? &_inQueue : nullptr;
SafeQueue<void *> *outQueue = output ? &_outQueue : nullptr;
_daq->start(inQueue, outQueue);
_audioCallback = audioCallback;
_daqThread = new std::thread(&PyDaq::threadFcn, this);
// Wait unitil thread is ready before returning
do {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
} while (!_threadReady);
}
void PyDaq::stop() {
_daq->stop();
_stopThread = true;
_threadReady = false;
_daqThread->join();
delete _daqThread;
_daqThread = nullptr;
}
void PyDaq::threadFcn() {
assert(_daq);
void *inbuffer = nullptr;
void *outbuffer = nullptr;
const double sleeptime = _daq->framesPerBlock() / (8 * _daq->samplerate());
// Sleep time in microseconds
const us sleeptime_us = (us)(sleeptime * 1e6);
const us nblocks_buffer = (us)std::max<d>(
1, QUEUE_BUFFER_TIME * _daq->samplerate() / _daq->framesPerBlock());
auto descr = _daq->dtypeDescr();
const us nBytesPerChan = _daq->framesPerBlock() * descr.sw;
_threadReady = true;
// Number of input channels, including monitor channel
const us neninchannels = _daq->neninchannels(true);
const us nenoutchannels = _daq->nenoutchannels();
const bool input = neninchannels > 0;
const bool output = nenoutchannels > 0;
cerr << "Check if it is required to call import_array()" << endl;
/* { */
/* py::gil_scoped_acquire aq; */
/* import_array(void); */
/* } */
_threadReady = true;
if (output) {
for (us i = 0; i < nblocks_buffer; i++) {
outbuffer = new uint8_t[nBytesPerChan * nenoutchannels];
memset(outbuffer, 0, nBytesPerChan * nenoutchannels);
_outQueue.enqueue(outbuffer);
}
outbuffer = nullptr;
}
while (!_stopThread) {
if (output) {
py::gil_scoped_acquire gil;
while (_outQueue.size() < nblocks_buffer) {
/* py::array( */
}
}
} // End of while loop, stopthread
}
#endif

View File

@ -0,0 +1,44 @@
#pragma once
#include "lasp_daq.h"
#include <atomic>
#include <vector>
#include <pybind11/pybind11.h>
namespace py = pybind11;
namespace std {
class thread;
};
class PyDaq {
Daq *_daq = nullptr;
std::thread *_daqThread = nullptr;
SafeQueue<void *> _inQueue;
SafeQueue<void *> _outQueue;
py::function _audioCallback;
std::atomic<bool> _stopThread;
std::atomic<bool> _threadReady;
public:
PyDaq(const DeviceInfo &devinfo, const DaqConfiguration &config);
~PyDaq();
/**
* @brief Start the Daq data acquisition
* @param audioCallback Python callback which is called with in and/or out
data of the daq
* as arguments
*/
void start(py::function audioCallback);
void stop();
bool isRunning() { return _daq->isRunning(); }
static std::vector<DeviceInfo> getDeviceInfo();
const Daq &daq() const { return *_daq; }
private:
void threadFcn();
};

View File

@ -1,777 +0,0 @@
include "lasp_common_decls.pxd"
__all__ = ['RtAudio', 'get_numpy_dtype_from_format_string',
'get_sampwidth_from_format_string']
cdef extern from "RtAudio.h" nogil:
ctypedef unsigned long RtAudioStreamStatus
RtAudioStreamStatus RTAUDIO_INPUT_OVERFLOW
RtAudioStreamStatus RTAUDIO_OUTPUT_UNDERFLOW
cdef cppclass RtAudioError:
ctypedef enum Type:
WARNING
DEBUG_WARNING
UNSPECIFIED
NO_DEVICES_FOUND
INVALID_DEVICE
MEMORY_ERROR
INVALID_PARAMETER
INVALID_USE
DRIVER_ERROR
SYSTEM_ERROR
THREAD_ERROR
ctypedef unsigned long RtAudioStreamFlags
RtAudioStreamFlags RTAUDIO_NONINTERLEAVED
RtAudioStreamFlags RTAUDIO_MINIMIZE_LATENCY
RtAudioStreamFlags RTAUDIO_HOG_DEVICE
RtAudioStreamFlags RTAUDIO_SCHEDULE_REALTIME
RtAudioStreamFlags RTAUDIO_ALSA_USE_DEFAULT
RtAudioStreamFlags RTAUDIO_JACK_DONT_CONNECT
ctypedef unsigned long RtAudioFormat
RtAudioFormat RTAUDIO_SINT8
RtAudioFormat RTAUDIO_SINT16
RtAudioFormat RTAUDIO_SINT24
RtAudioFormat RTAUDIO_SINT32
RtAudioFormat RTAUDIO_FLOAT32
RtAudioFormat RTAUDIO_FLOAT64
ctypedef int (*RtAudioCallback)(void* outputBuffer,
void* inputBuffer,
unsigned int nFramesPerBlock,
double streamTime,
RtAudioStreamStatus status,
void* userData)
ctypedef void (*RtAudioErrorCallback)(RtAudioError.Type _type,
const string& errortxt)
cdef cppclass cppRtAudio "RtAudio":
enum Api:
UNSPECIFIED
LINUX_ALSA
LINUX_PULSE
MACOSX_CORE
WINDOWS_WASAPI
WINDOWS_ASIO
WINDOWS_DS
cppclass DeviceInfo:
bool probed
string name
unsigned int outputChannels
unsigned int inputChannels
unsigned int duplexChannels
bool isDefaultOutput
bool isDefaultInput
vector[unsigned int] sampleRates
unsigned int preferredSampleRate
RtAudioFormat nativeFormats
cppclass StreamOptions:
StreamOptions()
RtAudioStreamFlags flags
unsigned int numberOfBuffers
string streamName
int priority
cppclass StreamParameters:
StreamParameters()
unsigned int deviceId
unsigned int nChannels
unsigned int firstChannel
@staticmethod
void getCompiledApi(vector[cppRtAudio.Api]& apis)
@staticmethod
cppRtAudio.Api getCompiledApiByName(string& name)
@staticmethod
string getApiDisplayName(cppRtAudio.Api api)
@staticmethod
string getApiName(cppRtAudio.Api api)
# RtAudio() except +
cppRtAudio() except +
cppRtAudio(cppRtAudio.Api api) except +
# ~RtAudio() Destructors should not be listed
unsigned int getDeviceCount()
DeviceInfo getDeviceInfo(unsigned int device)
unsigned int getDefaultOutputDevice()
unsigned int getDefaultInputDevice()
void openStream(StreamParameters* outputParameters,
StreamParameters* intputParameters,
RtAudioFormat _format,
unsigned int sampleRate,
unsigned int* bufferFrames,
RtAudioCallback callback,
void* userData,
void* StreamOptions,
RtAudioErrorCallback) except +
void closeStream()
void startStream() except +
void stopStream() except +
void abortStream() except +
bool isStreamOpen()
bool isStreamRunning()
double getStreamTime()
void setStreamTime(double) except +
long getStreamLatency()
unsigned int getStreamSampleRate()
void showWarnings(bool value)
_formats_strkey = {
'8-bit integers': (RTAUDIO_SINT8, 1, np.int8),
'16-bit integers': (RTAUDIO_SINT16, 2, np.int16),
'24-bit integers': (RTAUDIO_SINT24, 3),
'32-bit integers': (RTAUDIO_SINT32, 4, np.int32),
'32-bit floats': (RTAUDIO_FLOAT32, 4, np.float32),
'64-bit floats': (RTAUDIO_FLOAT64, 8, np.float64),
}
_formats_rtkey = {
RTAUDIO_SINT8: ('8-bit integers', 1, cnp.NPY_INT8),
RTAUDIO_SINT16: ('16-bit integers', 2, cnp.NPY_INT16),
RTAUDIO_SINT24: ('24-bit integers', 3),
RTAUDIO_SINT32: ('32-bit integers', 4, cnp.NPY_INT32),
RTAUDIO_FLOAT32: ('32-bit floats', 4, cnp.NPY_FLOAT32),
RTAUDIO_FLOAT64: ('64-bit floats', 8, cnp.NPY_FLOAT64),
}
def get_numpy_dtype_from_format_string(format_string):
return _formats_strkey[format_string][-1]
def get_sampwidth_from_format_string(format_string):
return _formats_strkey[format_string][-2]
# It took me quite a long time to fully understand Cython's idiosyncrasies
# concerning C(++) callbacks, the GIL and passing Python objects as pointers
# into C(++) functions. But finally, here it is!
# cdef void fromNPYToBuffer(cnp.ndarray arr,
# void* buf):
# """
# Copy a Python numpy array over to a buffer
# No checks, just memcpy! Careful!
# """
# memcpy(buf, arr.data, arr.size*arr.itemsize)
ctypedef struct PyStreamData:
PyObject* pyCallback
RtAudioFormat sampleformat
# Flag used to pass the stopThread.
atomic[bool] stopThread
# Number of frames per block
unsigned nFramesPerBlock
# Number of bytes per channel
unsigned int nBytesPerChan
# Number of blocks to delay the output before adding to the input
unsigned int outputDelayBlocks
# The structures as used by RtAudio
cppRtAudio.StreamParameters inputParams
cppRtAudio.StreamParameters outputParams
bool* inputChannelsEnabled
bool* outputChannelsEnabled
unsigned ninputchannels_forwarded
unsigned noutputchannels_forwarded
# If these queue pointers are NULL, it means the stream does not have an
# input, or output.
SafeQueue[void*] *outputDelayQueue
SafeQueue[void*] *inputQueue
SafeQueue[void*] *outputQueue
CPPThread[void*, void (*)(void*)] *thread
cdef int audioCallback(void* outputbuffer,
void* inputbuffer,
unsigned int nFramesPerBlock,
double streamTime,
RtAudioStreamStatus status,
void* userData) nogil:
"""
Calls the Python callback function and converts data
"""
cdef:
int rval = 0
PyStreamData* stream
void* outputbuffercpy = NULL
void* inputbuffercpy = NULL
unsigned j, i
unsigned bytesperchan
unsigned noutputchannels_forwarded, ninputchannels_forwarded
bint ch_en
stream = <PyStreamData*>(userData)
bytesperchan = stream.nBytesPerChan
ninputchannels_forwarded = stream.ninputchannels_forwarded
noutputchannels_forwarded = stream.noutputchannels_forwarded
# with gil:
# print(f'bytesperchan: {bytesperchan}')
# print(f'ninputchannels_forwarded:: {ninputchannels_forwarded}')
# print(f'noutputchannels_forwarded:: {noutputchannels_forwarded}')
# fprintf(stderr, "Stream heartbeat...\n")
# Returning 2 means aborting the stream immediately
if status == RTAUDIO_INPUT_OVERFLOW:
fprintf(stderr, 'Input overflow.\n')
stream.stopThread.store(True)
return 2
elif status == RTAUDIO_OUTPUT_UNDERFLOW:
fprintf(stderr, 'Output underflow.\n')
# stream.stopThread.store(True)
return 0
if nFramesPerBlock != stream.nFramesPerBlock:
printf('Number of frames mismath in callback data!\n')
stream.stopThread.store(True)
return 2
if inputbuffer:
# fprintf(stderr, "enter copying input buffer code\n")
# with gil:
# assert stream.inputQueue is not NULL
inputbuffercpy = malloc(bytesperchan*ninputchannels_forwarded)
if not inputbuffercpy:
fprintf(stderr, "Error allocating buffer\n")
return 2
if stream.outputDelayQueue:
if stream.outputDelayQueue.size() > stream.outputDelayBlocks:
outputbuffercpy = stream.outputDelayQueue.dequeue()
memcpy(inputbuffercpy, outputbuffercpy,
bytesperchan*noutputchannels_forwarded)
# Cleanup buffer
free(outputbuffercpy)
outputbuffercpy = NULL
else:
memset(inputbuffercpy, 0,
bytesperchan*noutputchannels_forwarded)
# Starting channel for copying input channels according to the channel
# map
j = stream.noutputchannels_forwarded
else:
j = 0
i = 0
for i in range(stream.inputParams.nChannels):
ch_en = stream.inputChannelsEnabled[i]
if ch_en:
copyChannel(inputbuffercpy, inputbuffer, bytesperchan, j, i)
j+=1
i+=1
stream.inputQueue.enqueue(inputbuffercpy)
if outputbuffer:
# with gil:
# assert stream.outputQueue
# fprintf(stderr, "enter copying output buffer code\n")
if stream.outputQueue.empty():
fprintf(stderr, 'Stream output buffer underflow, zero-ing buffer...\n')
memset(outputbuffer, 0, stream.outputParams.nChannels*bytesperchan)
else:
outputbuffercpy = stream.outputQueue.dequeue()
# fprintf(stderr, 'Copying data to stream output buffer...\n')
j = 0
i = 0
for i in range(stream.outputParams.nChannels):
ch_en = stream.outputChannelsEnabled[i]
if ch_en:
copyChannel(outputbuffer, outputbuffercpy, bytesperchan, i, j)
j+=1
else:
# If channel is not enabled, we set the data to zero
memset(<void*> &((<char*> outputbuffer)[bytesperchan*i]), 0, bytesperchan)
pass
i+=1
if stream.outputDelayQueue:
# fprintf(stderr, "Adding to delay queue\n")
stream.outputDelayQueue.enqueue(outputbuffercpy)
else:
free(outputbuffercpy)
outputbuffercpy = NULL
return 0
cdef void audioCallbackPythonThreadFunction(void* voidstream) nogil:
cdef:
PyStreamData* stream
cnp.NPY_TYPES npy_format
void* inputbuffer = NULL
void* outputbuffer = NULL
unsigned noutputchannels_forwarded
unsigned ninputchannels_forwarded
unsigned nBytesPerChan
unsigned nFramesPerBlock
stream = <PyStreamData*> voidstream
ninputchannels_forwarded = stream.ninputchannels_forwarded
noutputchannels_forwarded = stream.noutputchannels_forwarded
nBytesPerChan = stream.nBytesPerChan
nFramesPerBlock = stream.nFramesPerBlock
with gil:
npy_format = _formats_rtkey[stream.sampleformat][2]
callback = <object> stream.pyCallback
print(f'noutputchannels_forwarded: {noutputchannels_forwarded}')
print(f'ninputchannels_forwarded: {ninputchannels_forwarded}')
print(f'nBytesPerChan: {nBytesPerChan}')
print(f'nFramesPerBlock: {nFramesPerBlock}')
while True:
if stream.stopThread.load() == True:
printf('Stopping thread...\n')
return
if stream.inputQueue:
# fprintf(stderr, "Waiting on input queue\n")
inputbuffer = stream.inputQueue.dequeue()
if not inputbuffer:
printf('Stopping thread...\n')
return
if stream.outputQueue:
# fprintf(stderr, 'Allocating output buffer...\n')
outputbuffer = malloc(nBytesPerChan*noutputchannels_forwarded)
# memset(outputbuffer, 0, nBytesPerChan*noutputchannels_forwarded)
with gil:
# Obtain stream information
npy_input = None
npy_output = None
if stream.inputQueue:
# assert(inputbuffer)
try:
# print(f'========ninputchannels_forwarded: {ninputchannels_forwarded}')
# print(f'========nFramesPerBlock: {nFramesPerBlock}')
# print(f'========npy_format: {npy_format}')
npy_input = <object> data_to_ndarray(
inputbuffer,
nFramesPerBlock,
ninputchannels_forwarded,
npy_format,
True, # Do transfer ownership
True) # F-contiguous is True: data is Fortran-cont.
# fprintf(stderr, "Copying array...\n")
# fprintf(stderr, "End Copying array...\n")
except Exception as e:
print('exception in cython callback for audio input: ', str(e))
return
if stream.outputQueue:
# fprintf(stderr, 'Copying output buffer to Numpy...\n')
try:
npy_output = <object> data_to_ndarray(
outputbuffer,
nFramesPerBlock,
noutputchannels_forwarded,
npy_format,
False, # Do not transfer ownership
True) # F-contiguous
except Exception as e:
print('exception in Cython callback for audio output: ', str(e))
return
try:
# fprintf(stderr, "Python callback...\n")
rval = callback(npy_input,
npy_output,
nFramesPerBlock,
)
# fprintf(stderr, "Return from Python callback...\n")
except Exception as e:
print('Exception in Cython callback: ', str(e))
return
if stream.outputQueue:
# fprintf(stderr, 'Enqueuing output buffer...\n')
stream.outputQueue.enqueue(outputbuffer)
if not stream.inputQueue:
# fprintf(stderr, 'No input queue!\n')
while stream.outputQueue.size() > 10 and not stream.stopThread.load():
# printf('Sleeping...\n')
# No input queue to wait on, so we relax a bit here.
CPPsleep_ms(1);
# Outputbuffer is free'ed by the audiothread, so should not be touched
# here.
outputbuffer = NULL
# Inputbuffer memory is owned by Numpy, so should not be free'ed
inputbuffer = NULL
cdef void errorCallback(RtAudioError.Type _type,const string& errortxt) nogil:
fprintf(stderr, 'RtAudio error callback called: ')
fprintf(stderr, errortxt.c_str())
fprintf(stderr, '\n')
cdef class RtAudio:
cdef:
cppRtAudio* _rtaudio
PyStreamData* sd
int api
def __cinit__(self, pyapi):
if pyapi.apiname != 'RtAudio':
raise RuntimeError('RtAudio constructor called with invalid Api instance')
cdef:
cppRtAudio.Api api = <cppRtAudio.Api> pyapi.internalnr
self._rtaudio = new cppRtAudio(api)
self.sd = NULL
self._rtaudio.showWarnings(True)
self.api = api
def __dealloc__(self):
if self.sd is not NULL:
# fprintf(stderr, 'Force closing stream...')
if self._rtaudio.isStreamRunning():
self._rtaudio.stopStream()
self._rtaudio.closeStream()
self.cleanupStream(self.sd)
self.sd = NULL
del self._rtaudio
@staticmethod
def getApis():
cdef:
vector[cppRtAudio.Api] apis
cppRtAudio.getCompiledApi(apis)
apilist = []
for api in apis:
apilist.append(
DAQApi(
backendname= 'RtAudio',
apiname = cppRtAudio.getApiName(api).decode('utf-8'),
internalnr=<int> api))
return apilist
cpdef unsigned int getDefaultOutputDevice(self):
return self._rtaudio.getDefaultOutputDevice()
cpdef unsigned int getDefaultInputDevice(self):
return self._rtaudio.getDefaultInputDevice()
def getDeviceInfo(self):
"""
Return device information of the current device
"""
sampleformats = []
cdef:
cppRtAudio.DeviceInfo devinfo
unsigned devcount, i
devinfo_py = []
devcount = self._rtaudio.getDeviceCount()
for i in range(devcount):
devinfo = self._rtaudio.getDeviceInfo(i)
nf = devinfo.nativeFormats
for format_ in [ RTAUDIO_SINT8, RTAUDIO_SINT16, RTAUDIO_SINT24,
RTAUDIO_SINT32, RTAUDIO_FLOAT32, RTAUDIO_FLOAT64]:
if nf & format_:
sampleformats.append(_formats_rtkey[format_][0])
devinfo_py.append(DeviceInfo(
api = self.api,
index = i,
probed = devinfo.probed,
name = devinfo.name.decode('utf-8'),
outputchannels = devinfo.outputChannels,
inputchannels = devinfo.inputChannels,
duplexchannels = devinfo.duplexChannels,
samplerates = devinfo.sampleRates,
sampleformats = sampleformats,
prefsamplerate = devinfo.preferredSampleRate))
return devinfo_py
@cython.nonecheck(True)
def start(self,
avstream
):
"""
Opening a stream with specified parameters
Args:
avstream: AvStream instance
Returns: None
"""
if self.sd is not NULL:
raise RuntimeError('Stream is already opened.')
daqconfig = avstream.daqconfig
avtype = avstream.avtype
device = avstream.device
cdef:
bint duplex_mode = daqconfig.duplex_mode
bint monitorOutput = daqconfig.monitor_gen
unsigned int outputDelayBlocks = daqconfig.outputDelayBlocks
cppRtAudio.StreamParameters *rtOutputParams_ptr = NULL
cppRtAudio.StreamParameters *rtInputParams_ptr = NULL
cppRtAudio.StreamOptions streamoptions
size_t sw
unsigned int nFramesPerBlock = int(daqconfig.nFramesPerBlock)
int firstinputchannel, firstoutputchannel
int lastinputchannel, lastoutputchannel
unsigned int ninputchannels_forwarded=0
unsigned int ninputchannels_rtaudio=0
unsigned int noutputchannels_rtaudio=0
unsigned int noutputchannels_forwarded=0
unsigned int samplerate
int i
bint input_stream=False, output_stream=False
bool* inputChannelsEnabled
bool* outputChannelsEnabled
if daqconfig.nFramesPerBlock > 8192 or daqconfig.nFramesPerBlock < 512:
raise ValueError('Invalid number of nFramesPerBlock')
if daqconfig.outputDelayBlocks < 0 or daqconfig.outputDelayBlocks > 10:
raise ValueError('Invalid number of outputDelayBlocks')
try:
# Determine sample rate and sample format, determine whether we are an
# input or an output stream, or both
if avtype == AvType.audio_input or duplex_mode:
# Here, we override the sample format in case of duplex mode.
sampleformat = daqconfig.en_input_sample_format
samplerate = int(daqconfig.en_input_rate)
input_stream = True
if duplex_mode:
output_stream = True
else:
sampleformat = daqconfig.en_output_sample_format
samplerate = int(daqconfig.en_output_rate)
output_stream = True
print(f'Is input stream: {input_stream}')
print(f'Is output stream: {output_stream}')
sw = get_sampwidth_from_format_string(sampleformat)
print(f'samplewidth: {sw}')
# All set, allocate the stream!
self.sd = <PyStreamData*> malloc(sizeof(PyStreamData))
if self.sd == NULL:
raise MemoryError('Could not allocate stream: memory error.')
self.sd.pyCallback = <PyObject*> avstream._audioCallback
# Increase reference count to the callback
Py_INCREF(<object> avstream._audioCallback)
self.sd.sampleformat = _formats_strkey[sampleformat][0]
self.sd.stopThread.store(False)
self.sd.inputQueue = NULL
self.sd.outputQueue = NULL
self.sd.outputDelayQueue = NULL
self.sd.thread = NULL
self.sd.outputDelayBlocks = outputDelayBlocks
self.sd.ninputchannels_forwarded = 0
self.sd.noutputchannels_forwarded = 0
self.sd.inputChannelsEnabled = NULL
self.sd.outputChannelsEnabled = NULL
# Create channel maps for input channels, set RtAudio input stream
# parameters
if input_stream:
firstinputchannel = daqconfig.firstEnabledInputChannelNumber()
lastinputchannel = daqconfig.lastEnabledInputChannelNumber()
ninputchannels_rtaudio = lastinputchannel-firstinputchannel+1
# print(firstinputchannel)
# print(lastinputchannel)
# print(ninputchannels_rtaudio)
if lastinputchannel < 0 or ninputchannels_rtaudio < 1:
raise ValueError('Not enough input channels selected')
input_ch = daqconfig.input_channel_configs
inputChannelsEnabled = <bool*> malloc(sizeof(bool)*ninputchannels_rtaudio)
self.sd.inputChannelsEnabled = inputChannelsEnabled
for i in range(firstinputchannel, lastinputchannel+1):
ch_en = input_ch[i].channel_enabled
if ch_en:
ninputchannels_forwarded += 1
inputChannelsEnabled[i] = ch_en
rtInputParams_ptr = &self.sd.inputParams
rtInputParams_ptr.deviceId = device.index
rtInputParams_ptr.nChannels = ninputchannels_rtaudio
rtInputParams_ptr.firstChannel = firstinputchannel
self.sd.inputQueue = new SafeQueue[void*]()
self.sd.ninputchannels_forwarded = ninputchannels_forwarded
# Create channel maps for output channels
if output_stream:
firstoutputchannel = daqconfig.firstEnabledOutputChannelNumber()
lastoutputchannel = daqconfig.lastEnabledOutputChannelNumber()
noutputchannels_rtaudio = lastoutputchannel-firstoutputchannel+1
# print(firstoutputchannel)
# print(lastoutputchannel)
# print(noutputchannels_rtaudio)
if lastoutputchannel < 0 or noutputchannels_rtaudio < 1:
raise ValueError('Not enough output channels selected')
output_ch = daqconfig.output_channel_configs
outputChannelsEnabled = <bool*> malloc(sizeof(bool)*noutputchannels_rtaudio)
self.sd.outputChannelsEnabled = outputChannelsEnabled
for i in range(firstoutputchannel, lastoutputchannel+1):
ch_en = output_ch[i].channel_enabled
if ch_en:
noutputchannels_forwarded += 1
outputChannelsEnabled[i] = ch_en
rtOutputParams_ptr = &self.sd.outputParams
rtOutputParams_ptr.deviceId = device.index
rtOutputParams_ptr.nChannels = noutputchannels_rtaudio
rtOutputParams_ptr.firstChannel = firstoutputchannel
self.sd.outputQueue = new SafeQueue[void*]()
self.sd.noutputchannels_forwarded = noutputchannels_forwarded
if monitorOutput and duplex_mode:
self.sd.outputDelayQueue = new SafeQueue[void*]()
self.sd.ninputchannels_forwarded += noutputchannels_forwarded
streamoptions.flags = RTAUDIO_HOG_DEVICE
streamoptions.flags |= RTAUDIO_NONINTERLEAVED
streamoptions.numberOfBuffers = 4
streamoptions.streamName = "LASP Audio stream".encode('utf-8')
streamoptions.priority = 1
self._rtaudio.openStream(rtOutputParams_ptr,
rtInputParams_ptr,
_formats_strkey[sampleformat][0],
samplerate,
&nFramesPerBlock,
audioCallback,
<void*> self.sd,
&streamoptions, # Stream options
errorCallback # Error callback
)
self.sd.nBytesPerChan = nFramesPerBlock*sw
self.sd.nFramesPerBlock = nFramesPerBlock
self._rtaudio.startStream()
except Exception as e:
print('Exception occured in stream opening: ', e)
self.cleanupStream(self.sd)
self.sd = NULL
raise e
with nogil:
self.sd.thread = new CPPThread[void*, void (*)(void*)](audioCallbackPythonThreadFunction,
<void*> self.sd)
# Allow it to start
CPPsleep_ms(500)
pass
return nFramesPerBlock, samplerate
def stop(self):
if self.sd is NULL:
raise RuntimeError('Stream is not running')
try:
self._rtaudio.stopStream()
self._rtaudio.closeStream()
except Exception as e:
print(e)
pass
self.cleanupStream(self.sd)
self.sd = NULL
cdef cleanupStream(self, PyStreamData* stream):
if stream == NULL:
return
with nogil:
if stream.thread:
stream.stopThread.store(True)
if stream.inputQueue:
# If waiting in the input queue, hereby we let it run.
stream.inputQueue.enqueue(NULL)
# printf('Joining thread...\n')
# HERE WE SHOULD RELEASE THE GIL, as exiting the thread function
# will require the GIL, which is locked by this thread!
stream.thread.join()
# printf('Thread joined!\n')
del stream.thread
stream.thread = NULL
if stream.inputChannelsEnabled:
free(stream.inputChannelsEnabled)
if stream.outputChannelsEnabled:
free(stream.outputChannelsEnabled)
if stream.outputQueue:
while not stream.outputQueue.empty():
free(stream.outputQueue.dequeue())
del stream.outputQueue
if stream.inputQueue:
while not stream.inputQueue.empty():
free(stream.inputQueue.dequeue())
del stream.inputQueue
if stream.outputDelayQueue:
while not stream.outputDelayQueue.empty():
free(stream.outputDelayQueue.dequeue())
del stream.outputDelayQueue
fprintf(stderr, "End cleanup stream queues...\n")
if stream.pyCallback:
Py_DECREF(<object> stream.pyCallback)
stream.pyCallback = NULL
# fprintf(stderr, "End cleanup callback...\n")
free(stream)

View File

@ -0,0 +1,369 @@
#if LASP_HAS_RTAUDIO == 1
#include "lasp_rtaudiodaq.h"
#include <RtAudio.h>
#include <atomic>
#include <cassert>
#include <cstdint>
#include <thread>
using std::cerr;
using std::endl;
using std::atomic;
void fillRtAudioDeviceInfo(vector<DeviceInfo> &devinfolist) {
vector<RtAudio::Api> apis;
RtAudio::getCompiledApi(apis);
for (auto api : apis) {
RtAudio rtaudio(api);
us count = rtaudio.getDeviceCount();
for (us devno = 0; devno < count; devno++) {
RtAudio::DeviceInfo devinfo = rtaudio.getDeviceInfo(devno);
if (!devinfo.probed) {
// Device capabilities not successfully probed. Continue to next
continue;
}
DeviceInfo d;
switch (api) {
case RtAudio::LINUX_ALSA:
d.api = rtaudioAlsaApi;
break;
case RtAudio::LINUX_PULSE:
d.api = rtaudioPulseaudioApi;
break;
case RtAudio::WINDOWS_WASAPI:
d.api = rtaudioWasapiApi;
break;
case RtAudio::WINDOWS_DS:
d.api = rtaudioDsApi;
break;
case RtAudio::WINDOWS_ASIO:
d.api = rtaudioAsioApi;
break;
default:
cerr << "Not implemented RtAudio API, skipping." << endl;
continue;
break;
}
d.device_name = devinfo.name;
d.api_specific_devindex = devno;
for (us j = 0; j < devinfo.sampleRates.size(); j++) {
us rate = devinfo.sampleRates[j];
d.availableSampleRates.push_back((double)rate);
if (devinfo.preferredSampleRate == rate) {
d.prefSampleRateIndex = j;
}
}
d.noutchannels = devinfo.outputChannels;
d.ninchannels = devinfo.inputChannels;
d.availableInputRanges = {1.0};
RtAudioFormat formats = devinfo.nativeFormats;
if (formats & RTAUDIO_SINT8) {
d.availableDataTypes.push_back(dtype_int8);
}
if (formats & RTAUDIO_SINT16) {
d.availableDataTypes.push_back(dtype_int16);
}
if (formats & RTAUDIO_SINT32) {
d.availableDataTypes.push_back(dtype_int24);
}
if (formats & RTAUDIO_SINT32) {
d.availableDataTypes.push_back(dtype_fl32);
}
if (formats & RTAUDIO_FLOAT64) {
d.availableDataTypes.push_back(dtype_fl64);
}
if (d.availableDataTypes.size() == 0) {
std::cerr << "RtAudio: No data types found in device!" << endl;
}
d.prefDataTypeIndex = d.availableDataTypes.size() - 1;
d.availableFramesPerBlock.push_back(512);
d.availableFramesPerBlock.push_back(1024);
d.availableFramesPerBlock.push_back(2048);
d.availableFramesPerBlock.push_back(4096);
d.availableFramesPerBlock.push_back(8192);
d.prefFramesPerBlockIndex = 1;
devinfolist.push_back(d);
}
}
}
int mycallback(void *outputBuffer, void *inputBuffer, unsigned int nFrames,
double streamTime, RtAudioStreamStatus status, void *userData);
void myerrorcallback(RtAudioError::Type, const string &errorText);
class AudioDaq : public Daq {
SafeQueue<void *> *inqueue = NULL;
SafeQueue<void *> *outqueue = NULL;
SafeQueue<void *> *outDelayqueue = NULL;
RtAudio *rtaudio = NULL;
RtAudio::StreamParameters *instreamparams = nullptr;
RtAudio::StreamParameters *outstreamparams = nullptr;
us nFramesPerBlock;
public:
AudioDaq(const DeviceInfo &devinfo, const DaqConfiguration &config)
: Daq(devinfo, config) {
nFramesPerBlock = this->framesPerBlock();
if (neninchannels(false) > 0) {
instreamparams = new RtAudio::StreamParameters();
instreamparams->nChannels = getHighestInChannel() + 1;
if (instreamparams->nChannels < 1) {
throw runtime_error("Invalid input number of channels");
}
instreamparams->firstChannel = 0;
instreamparams->deviceId = devinfo.api_specific_devindex;
}
if (nenoutchannels() > 0) {
outstreamparams = new RtAudio::StreamParameters();
outstreamparams->nChannels = getHighestOutChannel() + 1;
if (outstreamparams->nChannels < 1) {
throw runtime_error("Invalid output number of channels");
}
outstreamparams->firstChannel = 0;
outstreamparams->deviceId = devinfo.api_specific_devindex;
}
RtAudio::StreamOptions streamoptions;
streamoptions.flags = RTAUDIO_NONINTERLEAVED | RTAUDIO_HOG_DEVICE;
streamoptions.numberOfBuffers = 2;
streamoptions.streamName = "RtAudio stream";
streamoptions.priority = 0;
RtAudioFormat format;
const DataType dtype = DataType();
switch (dtype) {
case dtype_fl32:
format = RTAUDIO_FLOAT32;
break;
case dtype_fl64:
format = RTAUDIO_FLOAT64;
break;
case dtype_int8:
format = RTAUDIO_SINT8;
break;
case dtype_int16:
format = RTAUDIO_SINT16;
break;
case dtype_int32:
format = RTAUDIO_SINT32;
break;
default:
throw runtime_error("Invalid data type");
break;
}
try {
rtaudio = new RtAudio((RtAudio::Api)devinfo.api.api_specific_subcode);
if (!rtaudio) {
throw runtime_error("RtAudio allocation failed");
}
rtaudio->openStream(outstreamparams, instreamparams, format,
(us)samplerate(), (unsigned *)&nFramesPerBlock,
&mycallback, (void *)this, &streamoptions,
&myerrorcallback);
} catch (RtAudioError &e) {
if (rtaudio)
delete rtaudio;
if (instreamparams)
delete instreamparams;
if (outstreamparams)
delete outstreamparams;
throw;
}
if (monitorOutput) {
outDelayqueue = new SafeQueue<void *>();
}
}
friend int mycallback(void *outputBuffer, void *inputBuffer,
unsigned int nFrames, double streamTime,
RtAudioStreamStatus status, void *userData);
void start(SafeQueue<void *> *inqueue, SafeQueue<void *> *outqueue) {
this->inqueue = inqueue;
this->outqueue = outqueue;
if (monitorOutput) {
this->outDelayqueue = new SafeQueue<void *>();
}
if (isRunning()) {
throw runtime_error("Stream already running");
}
if (neninchannels(false) > 0 && !inqueue) {
throw runtime_error("inqueue argument not given");
}
if (nenoutchannels() > 0 && !outqueue) {
throw runtime_error("outqueue argument not given");
}
assert(rtaudio);
rtaudio->startStream();
}
void stop() {
if (!isRunning()) {
cerr << "Stream is already stopped" << endl;
} else {
assert(rtaudio);
rtaudio->stopStream();
}
if (inqueue) {
inqueue = nullptr;
}
if (outqueue) {
outqueue = nullptr;
}
if (outDelayqueue) {
delete outDelayqueue;
outDelayqueue = nullptr;
}
}
bool isRunning() const { return (rtaudio && rtaudio->isStreamRunning()); }
~AudioDaq() {
assert(rtaudio);
if (isRunning()) {
stop();
}
if (rtaudio->isStreamOpen()) {
rtaudio->closeStream();
}
if (rtaudio)
delete rtaudio;
if (outDelayqueue)
delete outDelayqueue;
if (instreamparams)
delete instreamparams;
if (outstreamparams)
delete outstreamparams;
}
};
Daq *createRtAudioDevice(const DeviceInfo &devinfo,
const DaqConfiguration &config) {
AudioDaq *daq = NULL;
try {
daq = new AudioDaq(devinfo, config);
} catch (runtime_error &e) {
if (daq)
delete daq;
throw;
}
return daq;
}
int mycallback(void *outputBuffervoid, void *inputBuffervoid,
unsigned int nFrames, double streamTime,
RtAudioStreamStatus status, void *userData) {
u_int8_t *inputBuffer = (u_int8_t *)inputBuffervoid;
u_int8_t *outputBuffer = (u_int8_t *)outputBuffervoid;
AudioDaq *daq = (AudioDaq *)userData;
DataType dtype = daq->dataType();
us neninchannels_inc_mon = daq->neninchannels();
us nenoutchannels = daq->nenoutchannels();
bool monitorOutput = daq->monitorOutput;
us bytesperchan = dtype_map.at(dtype).sw * nFrames;
us monitorOffset = ((us)monitorOutput) * bytesperchan;
SafeQueue<void *> *inqueue = daq->inqueue;
SafeQueue<void *> *outqueue = daq->outqueue;
SafeQueue<void *> *outDelayqueue = daq->outDelayqueue;
const boolvec &eninchannels = daq->eninchannels;
const boolvec &enoutchannels = daq->enoutchannels;
if (inputBuffer || monitorOutput) {
u_int8_t *inbuffercpy =
(u_int8_t *)malloc(bytesperchan * neninchannels_inc_mon);
if (inputBuffer) {
us j = 0; // OUR buffer channel counter
us i = 0; // RtAudio channel counter
for (int ch = daq->getLowestInChannel(); ch <= daq->getHighestInChannel();
ch++) {
if (eninchannels[ch]) {
memcpy(&(inbuffercpy[monitorOffset + j * bytesperchan]),
&(inputBuffer[i * bytesperchan]), bytesperchan);
j++;
}
i++;
}
}
if (monitorOutput) {
assert(outDelayqueue);
if (!daq->outDelayqueue->empty()) {
void *dat = daq->outDelayqueue->dequeue();
memcpy((void *)inbuffercpy, dat, bytesperchan);
free(dat);
} else {
cerr << "Warning: output delay queue appears empty!" << endl;
memset(inbuffercpy, 0, bytesperchan);
}
}
assert(inqueue);
inqueue->enqueue(inbuffercpy);
}
if (outputBuffer) {
assert(outqueue);
if (!outqueue->empty()) {
u_int8_t *outbuffercpy = (u_int8_t *)outqueue->dequeue();
us j = 0; // OUR buffer channel counter
us i = 0; // RtAudio channel counter
for (us ch = 0; ch <= daq->getHighestOutChannel(); ch++) {
/* cerr << "Copying from queue... " << endl; */
if (enoutchannels[ch]) {
memcpy(&(outputBuffer[i * bytesperchan]),
&(outbuffercpy[j * bytesperchan]), bytesperchan);
j++;
} else {
/* cerr << "unused output channel in list" << endl; */
memset(&(outputBuffer[i * bytesperchan]), 0, bytesperchan);
}
i++;
}
if (!monitorOutput) {
free(outbuffercpy);
} else {
assert(outDelayqueue);
outDelayqueue->enqueue((void *)outbuffercpy);
}
} else {
cerr << "RtAudio backend: stream output buffer underflow!" << endl;
}
}
return 0;
}
void myerrorcallback(RtAudioError::Type, const string &errorText) {
cerr << errorText << endl;
}
#endif // LASP_HAS_RTAUDIO == 1

View File

@ -0,0 +1,14 @@
#pragma once
#include "lasp_daq.h"
#include <memory>
std::unique_ptr<Daq> createRtAudioDevice(const DeviceInfo& devinfo,
const DaqConfiguration& config);
/**
* @brief Append RtAudio backend devices to the list
*
* @param devinfolist List to append to
*/
void fillRtAudioDeviceInfo(std::vector<DeviceInfo> &devinfolist);

550
lasp/device/lasp_uldaq.cpp Normal file
View File

@ -0,0 +1,550 @@
#include "lasp_uldaq.h"
#include "lasp_daqconfig.h"
#include <algorithm>
#include <atomic>
#include <cassert>
#include <chrono>
#include <gsl/gsl-lite.hpp>
#include <iostream>
#include <memory>
#include <mutex>
#include <span>
#include <stdexcept>
#include <thread>
#include <uldaq.h>
#include <vector>
using namespace std::literals::chrono_literals;
using std::atomic;
using std::cerr;
using std::endl;
using std::runtime_error;
#include "debugtrace.hpp"
DEBUGTRACE_VARIABLES;
const us MAX_DEV_COUNT_PER_API = 100;
/**
* @brief Reserve some space for an error message from UlDaq
*/
const us UL_ERR_MSG_LEN = 512;
/**
* @brief Show the error to default error stream and return a string
* corresponding to the error
*
* @param err Error string
*/
string showErr(UlError err) {
string errstr;
errstr.reserve(UL_ERR_MSG_LEN);
if (err != ERR_NO_ERROR) {
char errmsg[UL_ERR_MSG_LEN];
errstr = "UlDaq API Error: ";
ulGetErrMsg(err, errmsg);
errstr += errmsg;
std::cerr << errstr << std::endl;
return errstr;
}
return errstr;
}
class DT9837A : public Daq {
DaqDeviceHandle _handle = 0;
std::mutex _daqmutex;
std::thread _thread;
atomic<bool> _stopThread{false};
const us _nFramesPerBlock;
void threadFcn(std::optional<DaqCallback> inCallback,
std::optional<DaqCallback> outcallback);
public:
DT9837A(const DeviceInfo &devinfo, const DaqConfiguration &config);
~DT9837A() {
UlError err;
if (isRunning()) {
stop();
}
if (_handle) {
err = ulDisconnectDaqDevice(_handle);
showErr(err);
err = ulReleaseDaqDevice(_handle);
showErr(err);
}
}
bool isRunning() const override final { return _thread.joinable(); }
virtual void start(std::optional<DaqCallback> inCallback,
std::optional<DaqCallback> outCallback) override final;
void stop() override final {
DEBUGTRACE_ENTER;
if (!isRunning()) {
throw runtime_error("No data acquisition running");
}
_stopThread = true;
if (_thread.joinable()) {
_thread.join();
}
_stopThread = false;
}
friend class InBufHandler;
friend class OutBufHandler;
};
void DT9837A::start(std::optional<DaqCallback> inCallback,
std::optional<DaqCallback> outCallback) {
DEBUGTRACE_ENTER;
if (isRunning()) {
throw runtime_error("DAQ is already running");
}
if (neninchannels() > 0) {
if (!inCallback)
throw runtime_error("DAQ requires a callback for input data");
}
if (nenoutchannels() > 0) {
if (!outCallback)
throw runtime_error("DAQ requires a callback for output data");
}
assert(neninchannels() + nenoutchannels() > 0);
_thread = std::thread(&DT9837A::threadFcn, this, inCallback, outCallback);
}
class BufHandler {
protected:
DaqDeviceHandle _handle;
const DataTypeDescriptor dtype_descr;
us nchannels, nFramesPerBlock;
DaqCallback cb;
double samplerate;
dvec buf;
bool topenqueued, botenqueued;
us increment = 0;
us totalFramesCount = 0;
long long buffer_mid_idx;
public:
BufHandler(DaqDeviceHandle handle, const DataTypeDescriptor dtype_descr,
const us nchannels, const us nFramesPerBlock, DaqCallback cb,
const double samplerate)
: _handle(handle), dtype_descr(dtype_descr), nchannels(nchannels),
nFramesPerBlock(nFramesPerBlock), cb(cb), samplerate(samplerate),
buf(2 * nchannels *
nFramesPerBlock, // Watch the two here, the top and the bottom!
0),
buffer_mid_idx(nchannels * nFramesPerBlock) {
assert(nchannels > 0);
}
};
class InBufHandler : public BufHandler {
bool monitorOutput;
public:
InBufHandler(DT9837A &daq, DaqCallback cb)
: BufHandler(daq._handle, daq.dtypeDescr(), daq.neninchannels(),
daq._nFramesPerBlock, cb, daq.samplerate())
{
DEBUGTRACE_ENTER;
assert(_handle != 0);
monitorOutput = daq.monitorOutput;
DaqInScanFlag inscanflags = DAQINSCAN_FF_DEFAULT;
ScanOption scanoptions = SO_CONTINUOUS;
UlError err = ERR_NO_ERROR;
std::vector<DaqInChanDescriptor> indescs;
boolvec eninchannels = daq.eninchannels;
// Initialize input, if any
for (us chin = 0; chin < 4; chin++) {
if (eninchannels[chin] == true) {
DaqInChanDescriptor indesc;
indesc.type = DAQI_ANALOG_SE;
indesc.channel = chin;
double rangeval = daq.inputRangeForChannel(chin);
Range rangenum;
if (fabs(rangeval - 1.0) < 1e-8) {
rangenum = BIP1VOLTS;
} else if (fabs(rangeval - 10.0) < 1e-8) {
rangenum = BIP10VOLTS;
} else {
std::cerr << "Fatal: input range value is invalid" << endl;
return;
}
indesc.range = rangenum;
indescs.push_back(indesc);
}
}
// Overwrite last channel
if (monitorOutput) {
DaqInChanDescriptor indesc;
indesc.type = DAQI_DAC;
indesc.channel = 0;
indesc.range = BIP10VOLTS;
indescs.push_back(indesc);
}
assert(indescs.size() == nchannels);
DEBUGTRACE_MESSAGE("Starting input scan");
err = ulDaqInScan(_handle, indescs.data(), nchannels,
2 * nFramesPerBlock, // Watch the 2 here!
&samplerate, scanoptions, inscanflags, buf.data());
if (err != ERR_NO_ERROR) {
showErr(err);
throw std::runtime_error("Could not start input DAQ");
}
botenqueued = false;
topenqueued = false;
}
void operator()() {
ChannelView cv(nchannels);
auto runCallback = ([&](us totalOffset) {
us monitoroffset = monitorOutput ? 1 : 0;
for (us channel = monitoroffset; channel < (nchannels - monitoroffset);
channel++) {
cv[channel] =
gsl::span(reinterpret_cast<uint8_t *>(
&buf[totalOffset + channel * nFramesPerBlock]),
nFramesPerBlock * sizeof(double));
}
if (monitorOutput) {
cv[0] = gsl::span(
reinterpret_cast<uint8_t *>(
&buf[totalOffset + (nchannels - 1) * nFramesPerBlock]),
nFramesPerBlock * sizeof(double));
}
cb(cv, dtype_descr);
});
ScanStatus status;
TransferStatus transferStatus;
UlError err = ulDaqInScanStatus(_handle, &status, &transferStatus);
if (err != ERR_NO_ERROR) {
showErr(err);
return;
}
increment = transferStatus.currentTotalCount - totalFramesCount;
totalFramesCount += increment;
if (increment > nFramesPerBlock) {
cerr << "Error: overrun for input of DAQ!" << endl;
return;
}
assert(status == SS_RUNNING);
if (transferStatus.currentIndex < (long long)buffer_mid_idx) {
topenqueued = false;
if (!botenqueued) {
runCallback(nchannels * nFramesPerBlock);
botenqueued = true;
}
} else {
botenqueued = false;
if (!topenqueued) {
runCallback(0);
topenqueued = true;
}
}
}
~InBufHandler() {
// At exit of the function, stop scanning.
DEBUGTRACE_ENTER;
UlError err = ulDaqInScanStop(_handle);
if (err != ERR_NO_ERROR) {
showErr(err);
}
}
};
class OutBufHandler : public BufHandler {
public:
OutBufHandler(DT9837A &daq, DaqCallback cb)
: BufHandler(daq._handle, daq.dtypeDescr(), daq.neninchannels(),
daq._nFramesPerBlock, cb, daq.samplerate()) {
DEBUGTRACE_MESSAGE("Starting output scan");
AOutScanFlag outscanflags = AOUTSCAN_FF_DEFAULT;
ScanOption scanoptions = SO_CONTINUOUS;
UlError err =
ulAOutScan(_handle, 0, 0, BIP10VOLTS,
2 * nFramesPerBlock, // Watch the 2 here!
&samplerate, scanoptions, outscanflags, buf.data());
if (err != ERR_NO_ERROR) {
showErr(err);
throw runtime_error("Unable to start output on DAQ");
}
botenqueued = false, topenqueued = true;
// Run callback to first fill top part
ChannelView cv{gsl::span(reinterpret_cast<uint8_t *>(&buf[0]),
nFramesPerBlock * sizeof(double))};
cb(cv, dtype_descr);
}
void operator()() {
assert(_handle != 0);
UlError err = ERR_NO_ERROR;
ScanStatus status;
TransferStatus transferStatus;
err = ulAOutScanStatus(_handle, &status, &transferStatus);
if (err != ERR_NO_ERROR) {
showErr(err);
return;
}
if (status != SS_RUNNING) {
return;
}
increment = transferStatus.currentTotalCount - totalFramesCount;
totalFramesCount += increment;
if (increment > nFramesPerBlock) {
cerr << "Error: underrun for output of DAQ!" << endl;
return;
}
if (transferStatus.currentIndex < buffer_mid_idx) {
topenqueued = false;
if (!botenqueued) {
ChannelView cv{
gsl::span(reinterpret_cast<uint8_t *>(&buf[buffer_mid_idx]),
nFramesPerBlock * sizeof(double))};
cb(cv, dtype_descr);
botenqueued = true;
}
} else {
botenqueued = false;
if (!topenqueued) {
ChannelView cv{gsl::span(reinterpret_cast<uint8_t *>(&buf[0]),
nFramesPerBlock * sizeof(double))};
cb(cv, dtype_descr);
topenqueued = true;
}
}
}
~OutBufHandler() {
DEBUGTRACE_ENTER;
UlError err = ulAOutScanStop(_handle);
if (err != ERR_NO_ERROR) {
showErr(err);
}
}
};
void DT9837A::threadFcn(std::optional<DaqCallback> inCallback,
std::optional<DaqCallback> outCallback) {
DEBUGTRACE_ENTER;
std::unique_ptr<InBufHandler> ibh;
std::unique_ptr<OutBufHandler> obh;
if (neninchannels() > 0) {
ibh = std::make_unique<InBufHandler>(*this, inCallback.value());
}
if (nenoutchannels() > 0) {
obh = std::make_unique<OutBufHandler>(*this, outCallback.value());
}
const double sleeptime =
static_cast<double>(_nFramesPerBlock) / (16 * samplerate());
const us sleeptime_us = static_cast<us>(sleeptime * 1e6);
while (!_stopThread) {
if (ibh) {
(*ibh)();
}
if (obh) {
(*obh)();
}
std::this_thread::sleep_for(std::chrono::microseconds(sleeptime_us));
}
}
std::unique_ptr<Daq> createUlDaqDevice(const DeviceInfo &devinfo,
const DaqConfiguration &config) {
return std::make_unique<DT9837A>(devinfo, config);
}
DT9837A::DT9837A(const DeviceInfo &devinfo, const DaqConfiguration &config)
: Daq(devinfo, config),
_nFramesPerBlock(availableFramesPerBlock.at(framesPerBlockIndex)) {
// Some sanity checks
if (eninchannels.size() != 4) {
throw runtime_error("Invalid length of enabled inChannels vector");
}
if (enoutchannels.size() != 1) {
throw runtime_error("Invalid length of enabled outChannels vector");
}
if (_nFramesPerBlock < 24 || _nFramesPerBlock > 8192) {
throw runtime_error("Unsensible number of samples per block chosen");
}
if (samplerate() < 10000 || samplerate() > 51000) {
throw runtime_error("Invalid sample rate");
}
DaqDeviceDescriptor devdescriptors[MAX_DEV_COUNT_PER_API];
DaqDeviceDescriptor descriptor;
DaqDeviceInterface interfaceType = ANY_IFC;
UlError err;
us numdevs = MAX_DEV_COUNT_PER_API;
err = ulGetDaqDeviceInventory(interfaceType, devdescriptors,
(unsigned *)&numdevs);
if (err != ERR_NO_ERROR) {
throw runtime_error("Device inventarization failed");
}
if ((us)api_specific_devindex >= numdevs) {
throw runtime_error("Device number {deviceno} too high {err}. This could "
"happen when the device is currently not connected");
}
descriptor = devdescriptors[api_specific_devindex];
// get a handle to the DAQ device associated with the first descriptor
_handle = ulCreateDaqDevice(descriptor);
if (_handle == 0) {
throw runtime_error(
"Unable to create a handle to the specified DAQ "
"device. Is the device currently in use? Please make sure to set "
"the DAQ configuration in duplex mode if simultaneous input and "
"output is required.");
}
err = ulConnectDaqDevice(_handle);
if (err != ERR_NO_ERROR) {
ulReleaseDaqDevice(_handle);
_handle = 0;
throw runtime_error(string("Unable to connect to device: " + showErr(err)));
}
for (us ch = 0; ch < 4; ch++) {
err = ulAISetConfigDbl(_handle, AI_CFG_CHAN_SENSOR_SENSITIVITY, ch, 1.0);
showErr(err);
if (err != ERR_NO_ERROR) {
throw runtime_error("Fatal: could normalize channel sensitivity");
}
CouplingMode cm = inputACCouplingMode[ch] ? CM_AC : CM_DC;
err = ulAISetConfig(_handle, AI_CFG_CHAN_COUPLING_MODE, ch, cm);
if (err != ERR_NO_ERROR) {
showErr(err);
throw runtime_error("Fatal: could not set AC/DC coupling mode");
}
IepeMode iepe = inputIEPEEnabled[ch] ? IEPE_ENABLED : IEPE_DISABLED;
err = ulAISetConfig(_handle, AI_CFG_CHAN_IEPE_MODE, ch, iepe);
if (err != ERR_NO_ERROR) {
showErr(err);
throw runtime_error("Fatal: could not set IEPE mode");
}
}
}
void fillUlDaqDeviceInfo(std::vector<DeviceInfo> &devinfolist) {
DEBUGTRACE_ENTER;
UlError err;
unsigned int numdevs = MAX_DEV_COUNT_PER_API;
DaqDeviceDescriptor devdescriptors[MAX_DEV_COUNT_PER_API];
DaqDeviceDescriptor descriptor;
DaqDeviceInterface interfaceType = ANY_IFC;
err = ulGetDaqDeviceInventory(interfaceType, devdescriptors,
static_cast<unsigned *>(&numdevs));
if (err != ERR_NO_ERROR) {
throw runtime_error("UlDaq device inventarization failed");
}
for (unsigned i = 0; i < numdevs; i++) {
descriptor = devdescriptors[i];
DeviceInfo devinfo;
devinfo.api = uldaqapi;
string name, interface;
if (string(descriptor.productName) != "DT9837A") {
throw runtime_error("Unknown UlDAQ type");
}
switch (descriptor.devInterface) {
case USB_IFC:
name = "USB - ";
break;
case BLUETOOTH_IFC:
/* devinfo. */
name = "Bluetooth - ";
break;
case ETHERNET_IFC:
/* devinfo. */
name = "Ethernet - ";
break;
default:
name = "Uknown interface = ";
}
name += string(descriptor.productName) + " " + string(descriptor.uniqueId);
devinfo.device_name = std::move(name);
devinfo.api_specific_devindex = i;
devinfo.availableDataTypes.push_back(
DataTypeDescriptor::DataType::dtype_fl64);
devinfo.prefDataTypeIndex = 0;
devinfo.availableSampleRates = {8000, 10000, 11025, 16000, 20000,
22050, 24000, 32000, 44056, 44100,
47250, 48000, 50000, 50400, 51000};
devinfo.prefSampleRateIndex = 11;
devinfo.availableFramesPerBlock = {512, 1024, 2048, 4096, 8192};
devinfo.availableInputRanges = {1.0, 10.0};
devinfo.prefInputRangeIndex = 0;
devinfo.ninchannels = 4;
devinfo.noutchannels = 1;
devinfo.hasInputIEPE = true;
devinfo.hasInputACCouplingSwitch = true;
devinfo.hasInputTrigger = true;
// Finally, this devinfo is pushed back in list
devinfolist.push_back(devinfo);
}
}

14
lasp/device/lasp_uldaq.h Normal file
View File

@ -0,0 +1,14 @@
#pragma once
#include "lasp_daq.h"
std::unique_ptr<Daq> createUlDaqDevice(const DeviceInfo& devinfo,
const DaqConfiguration& config);
/**
* @brief Fill device info list with UlDaq specific devices, if any.
*
* @param devinfolist Info list to append to
*/
void fillUlDaqDeviceInfo(std::vector<DeviceInfo> &devinfolist);