diff --git a/.gitignore b/.gitignore index 02040c4..7fd86ff 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,4 @@ test/test_uldaq lasp/device/lasp_daq.cxx lasp/c/lasp_config.h compile_commands.json +.cache diff --git a/.gitmodules b/.gitmodules index 36b3976..e9c585f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -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 diff --git a/CMakeLists.txt b/CMakeLists.txt index 672bee0..1698e70 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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") diff --git a/DebugTrace-cpp b/DebugTrace-cpp new file mode 160000 index 0000000..9b143ea --- /dev/null +++ b/DebugTrace-cpp @@ -0,0 +1 @@ +Subproject commit 9b143ea40a34d6268d671ea54cd0ba80396b6363 diff --git a/gsl-lite b/gsl-lite new file mode 160000 index 0000000..4720a29 --- /dev/null +++ b/gsl-lite @@ -0,0 +1 @@ +Subproject commit 4720a2980a30da085b4ddb4a0ea2a71af7351a48 diff --git a/lasp/CMakeLists.txt b/lasp/CMakeLists.txt index 23c6865..447105e 100644 --- a/lasp/CMakeLists.txt +++ b/lasp/CMakeLists.txt @@ -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) diff --git a/lasp/c/lasp_config.h.in b/lasp/c/lasp_config.h.in index ab55a71..14b6039 100644 --- a/lasp/c/lasp_config.h.in +++ b/lasp/c/lasp_config.h.in @@ -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 diff --git a/lasp/c/lasp_types.h b/lasp/c/lasp_types.h index 4e58c6a..b937eb3 100644 --- a/lasp/c/lasp_types.h +++ b/lasp/c/lasp_types.h @@ -38,11 +38,11 @@ typedef double d; /* Shortcut for double */ #error LASP_FLOAT_SIZE should be either 32 or 64 #endif - -#include #ifdef __cplusplus +#include typedef std::complex c; #else +#include #if LASP_FLOAT_SIZE == 32 typedef float complex c; #else diff --git a/lasp/cmake/FindCython.cmake b/lasp/cmake/FindCython.cmake deleted file mode 100644 index f1c4316..0000000 --- a/lasp/cmake/FindCython.cmake +++ /dev/null @@ -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 ) diff --git a/lasp/cmake/UseCython.cmake b/lasp/cmake/UseCython.cmake deleted file mode 100644 index 06fdf0c..0000000 --- a/lasp/cmake/UseCython.cmake +++ /dev/null @@ -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( ... ) -# -# To create a standalone executable, the function -# -# cython_add_standalone_executable( [MAIN_MODULE src1] ... ) -# -# 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 -# is assumed to be the MAIN_MODULE. -# -# Where is the name of the resulting Python module and -# ... are source files to be compiled into the module, e.g. *.pyx, -# *.py, *.c, *.cxx, etc. A CMake target is created with 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( 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() diff --git a/lasp/device/CMakeLists.txt b/lasp/device/CMakeLists.txt index 72b79d3..cf5f999 100644 --- a/lasp/device/CMakeLists.txt +++ b/lasp/device/CMakeLists.txt @@ -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") diff --git a/lasp/device/lasp_common_decls.pxd b/lasp/device/lasp_common_decls.pxd deleted file mode 100644 index 53b197e..0000000 --- a/lasp/device/lasp_common_decls.pxd +++ /dev/null @@ -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 + diff --git a/lasp/device/lasp_cppdaq.cpp b/lasp/device/lasp_cppdaq.cpp deleted file mode 100644 index af4997e..0000000 --- a/lasp/device/lasp_cppdaq.cpp +++ /dev/null @@ -1,198 +0,0 @@ -#include "lasp_cppdaq.h" -#include -#include -#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 Daq::getDeviceInfo() { - vector devs; -#ifdef LASP_HAS_ULDAQ - fillUlDaqDeviceInfo(devs); -#endif - -#ifdef LASP_HAS_RTAUDIO - fillRtAudioDeviceInfo(devs); -#endif - - return devs; -} - -vector DaqApi::getAvailableApis() { - - vector 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= 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); -} diff --git a/lasp/device/lasp_cppdaq.h b/lasp/device/lasp_cppdaq.h deleted file mode 100644 index c342fc0..0000000 --- a/lasp/device/lasp_cppdaq.h +++ /dev/null @@ -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 -#endif - -#ifdef LASP_HAS_ULDAQ -#include -#endif - -#include "lasp_cppqueue.h" -#include "string" -#include "vector" -#include -#include -#include - -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 boolvec; -typedef vector dvec; -typedef vector usvec; -typedef std::lock_guard 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 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 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 availableDataTypes; - int prefDataTypeIndex = 0; - - vector availableSampleRates; - int prefSampleRateIndex = -1; - - vector 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 inchannel_sensitivities; - vector inchannel_names; - vector inchannel_metadata; - - vector outchannel_sensitivities; - vector outchannel_names; - vector 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 getDeviceInfo(); - - static Daq *createDaq(const DeviceInfo &, const DaqConfiguration &config); - - Daq(const DeviceInfo &devinfo, const DaqConfiguration &config); - - virtual void start(SafeQueue *inqueue, - SafeQueue *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 diff --git a/lasp/device/lasp_cppqueue.h b/lasp/device/lasp_cppqueue.h deleted file mode 100644 index d76760e..0000000 --- a/lasp/device/lasp_cppqueue.h +++ /dev/null @@ -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 -#include -#include - -// A threadsafe-queue. -template -class SafeQueue { - std::queue _queue; - mutable std::mutex _mutex; - std::condition_variable _cv; -public: - SafeQueue(): _queue(), _mutex() , _cv() - {} - - ~SafeQueue(){} - - void enqueue(T t) { - std::lock_guard lock(_mutex); - _queue.push(t); - _cv.notify_one(); - } - - T dequeue() { - std::unique_lock 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 lock(_mutex); - return _queue.size()==0; - } - size_t size() const { - std::unique_lock lock(_mutex); - return _queue.size(); - } -}; - - -#endif // THREADSAFE_QUEUE_H -////////////////////////////////////////////////////////////////////// diff --git a/lasp/device/lasp_cpprtaudio.cpp b/lasp/device/lasp_cpprtaudio.cpp deleted file mode 100644 index d630008..0000000 --- a/lasp/device/lasp_cpprtaudio.cpp +++ /dev/null @@ -1,391 +0,0 @@ -#include "lasp_cpprtaudio.h" -#include -#include -#include -#include -#include - -#if MS_WIN64 -typedef uint8_t u_int8_t; -#endif - -using std::atomic; - -void fillRtAudioDeviceInfo(vector &devinfolist) { - - vector 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 *inqueue = NULL; - SafeQueue *outqueue = NULL; - SafeQueue *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(); - } - - } - - friend int mycallback(void *outputBuffer, void *inputBuffer, - unsigned int nFrames, - double streamTime, - RtAudioStreamStatus status, - void *userData); - - - void start(SafeQueue *inqueue, SafeQueue *outqueue) { - this->inqueue = inqueue; - this->outqueue = outqueue; - if(monitorOutput) { - this->outDelayqueue = new SafeQueue(); - - } - - 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 *inqueue = daq->inqueue; - SafeQueue *outqueue = daq->outqueue; - SafeQueue *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; -} diff --git a/lasp/device/lasp_cpprtaudio.h b/lasp/device/lasp_cpprtaudio.h deleted file mode 100644 index cbe7dca..0000000 --- a/lasp/device/lasp_cpprtaudio.h +++ /dev/null @@ -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 &devinfolist); - -#endif // RTAUDIO_H - - - diff --git a/lasp/device/lasp_cppthread.h b/lasp/device/lasp_cppthread.h deleted file mode 100644 index 2f9311a..0000000 --- a/lasp/device/lasp_cppthread.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once -#ifndef LASP_CPPTHREAD_H -#define LASP_CPPTHREAD_H -#include -#include -#include -// This is a small wrapper around the std library thread. - -template -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 diff --git a/lasp/device/lasp_cppuldaq.cpp b/lasp/device/lasp_cppuldaq.cpp deleted file mode 100644 index b68a861..0000000 --- a/lasp/device/lasp_cppuldaq.cpp +++ /dev/null @@ -1,636 +0,0 @@ -#include "lasp_cppuldaq.h" -#include "lasp_config.h" -#include "lasp_tracer.h" -#include -#include -#include -#include -#include -#include -#include - -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 stopThread; - DaqDeviceHandle handle = 0; - - std::thread *thread = NULL; - SafeQueue *inqueue = NULL; - SafeQueue *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 *inqueue, SafeQueue *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( - 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 *inqueue = td->inqueue; - SafeQueue *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( - 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( - 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 &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(&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); -} diff --git a/lasp/device/lasp_cppuldaq.h b/lasp/device/lasp_cppuldaq.h deleted file mode 100644 index 59f8cea..0000000 --- a/lasp/device/lasp_cppuldaq.h +++ /dev/null @@ -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 &devinfolist); - -#endif // ULDAQ_H - - diff --git a/lasp/device/lasp_daq.cpp b/lasp/device/lasp_daq.cpp new file mode 100644 index 0000000..4acd372 --- /dev/null +++ b/lasp/device/lasp_daq.cpp @@ -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::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); +} diff --git a/lasp/device/lasp_daq.h b/lasp/device/lasp_daq.h new file mode 100644 index 0000000..db462d6 --- /dev/null +++ b/lasp/device/lasp_daq.h @@ -0,0 +1,169 @@ +#pragma once +#include "lasp_config.h" +#include "lasp_daqconfig.h" +#include "lasp_deviceinfo.h" +#include "lasp_types.h" +#include +#include +#include +#include + +/** + * @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 _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 &raw_vec() { return _data; } + us size_bytes() const { return _data.size(); } +}; + +/** + * @brief Typed DaqData, in which the type is specified + * + * @tparam T + */ +template class TypedDaqData : public DaqData { + T *data() { return static_cast(_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>; +using DaqCallback = + std::function; + +/** + * @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 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 inCallback, + std::optional 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); + } +}; diff --git a/lasp/device/lasp_daq.pxd b/lasp/device/lasp_daq.pxd deleted file mode 100644 index fa8cc8d..0000000 --- a/lasp/device/lasp_daq.pxd +++ /dev/null @@ -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) diff --git a/lasp/device/lasp_daq.pyx b/lasp/device/lasp_daq.pyx deleted file mode 100644 index f1d094d..0000000 --- a/lasp/device/lasp_daq.pyx +++ /dev/null @@ -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 = 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 = ( sd.nFramesPerBlock)/(8*sd.samplerate); - # Sleep time in microseconds - us sleeptime_us = (sleeptime*1e6); - - us nblocks_buffer = max(1, (QUEUE_BUFFER_TIME * sd.samplerate / - sd.nFramesPerBlock)) - - with gil: - import_array() - npy_format = cnp.NPY_FLOAT64 - callback = 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 = malloc(sizeof(double)*nBytesPerChan*noutchannels) - memset(outbuffer, 0, sizeof(double)*nBytesPerChan*noutchannels) - sd.outQueue.enqueue( outbuffer) - sd.ready.store(True) - - while not sd.stopThread.load(): - with gil: - if sd.outQueue: - while sd.outQueue.size() < nblocks_buffer: - outbuffer = malloc(sizeof(double)*nBytesPerChan*noutchannels) - - npy_output = 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( outbuffer) - - if sd.inQueue and not sd.inQueue.empty(): - # Waiting indefinitely on the queue... - inbuffer = sd.inQueue.dequeue() - if inbuffer == NULL: - logging.debug('Received empty buffer on input, stopping thread...\n') - return - - npy_input = 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 = 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 = 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 = 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 = audiocallback - - # Increase reference count to the callback - Py_INCREF( audiocallback) - - with nogil: - self.sd.thread = new CPPThread[void*, void (*)(void*)](audioCallbackPythonThreadFunction, - 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( sd.pyCallback) - sd.pyCallback = NULL - - free(sd) - diff --git a/lasp/device/lasp_daqconfig.cpp b/lasp/device/lasp_daqconfig.cpp new file mode 100644 index 0000000..afcd4c7 --- /dev/null +++ b/lasp/device/lasp_daqconfig.cpp @@ -0,0 +1,97 @@ +#include "lasp_daqconfig.h" +#include "lasp_deviceinfo.h" +#include +#include + +#define MAX_DEV_COUNT_PER_API 20 + +using std::vector; + + +vector DaqApi::getAvailableApis() { + + vector 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; +} + diff --git a/lasp/device/lasp_daqconfig.h b/lasp/device/lasp_daqconfig.h new file mode 100644 index 0000000..5b233a1 --- /dev/null +++ b/lasp/device/lasp_daqconfig.h @@ -0,0 +1,250 @@ +#pragma once +#include "lasp_config.h" +#include "lasp_types.h" +#include +#include + +using std::string; +using std::to_string; + +using boolvec = std::vector; +using dvec = std::vector; +using usvec = std::vector; + +/** + * @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 + 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 getAvailableApis(); +}; + +#if LASP_HAS_ULDAQ == 1 +const DaqApi uldaqapi("UlDaq", 0); +#endif +#if LASP_HAS_RTAUDIO == 1 +#include +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 inchannel_sensitivities; + std::vector inchannel_names; + std::vector inchannel_metadata; + + std::vector outchannel_sensitivities; + std::vector outchannel_names; + std::vector 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; +}; + diff --git a/lasp/device/lasp_daqconfig.pyx b/lasp/device/lasp_daqconfig.pyx deleted file mode 100644 index 3c9c437..0000000 --- a/lasp/device/lasp_daqconfig.pyx +++ /dev/null @@ -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') - diff --git a/lasp/device/lasp_deviceinfo.cpp b/lasp/device/lasp_deviceinfo.cpp new file mode 100644 index 0000000..c3a8be1 --- /dev/null +++ b/lasp/device/lasp_deviceinfo.cpp @@ -0,0 +1,139 @@ +#include "lasp_deviceinfo.h" +#include +#include + +#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(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(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::getDeviceInfo() { + std::vector devs; +#if LASP_HAS_ULDAQ ==1 + fillUlDaqDeviceInfo(devs); +#endif + +#if LASP_HAS_RTAUDIO == 1 + fillRtAudioDeviceInfo(devs); +#endif + + return devs; +} + diff --git a/lasp/device/lasp_deviceinfo.h b/lasp/device/lasp_deviceinfo.h new file mode 100644 index 0000000..3a7a07f --- /dev/null +++ b/lasp/device/lasp_deviceinfo.h @@ -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 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 getDeviceInfo(); +}; + diff --git a/lasp/device/lasp_deviceinfo.pxd b/lasp/device/lasp_deviceinfo.pxd deleted file mode 100644 index e4dc635..0000000 --- a/lasp/device/lasp_deviceinfo.pxd +++ /dev/null @@ -1,5 +0,0 @@ -include "lasp_common_decls.pxd" - -cdef class DeviceInfo: - cdef: - cppDeviceInfo devinfo diff --git a/lasp/device/lasp_deviceinfo.pyx b/lasp/device/lasp_deviceinfo.pyx deleted file mode 100644 index 95bce55..0000000 --- a/lasp/device/lasp_deviceinfo.pyx +++ /dev/null @@ -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 - diff --git a/lasp/device/lasp_devicepybind.cpp b/lasp/device/lasp_devicepybind.cpp new file mode 100644 index 0000000..481c318 --- /dev/null +++ b/lasp/device/lasp_devicepybind.cpp @@ -0,0 +1,236 @@ +#if 0 +#include "lasp_devicepybind.h" +#include +#include +#include +#include +#include +#include + +using std::cerr; +namespace py = pybind11; + +PYBIND11_MODULE(lasp_daq, m) { + + m.doc() = "Lasp DAQ interface"; + + /// DataType + py::class_ 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(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_ 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_ 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_ daq(m, "Daq"); + daq.def(py::init()); + /* 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::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 *inQueue = input ? &_inQueue : nullptr; + SafeQueue *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( + 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 diff --git a/lasp/device/lasp_devicepybind.h b/lasp/device/lasp_devicepybind.h new file mode 100644 index 0000000..ef4a790 --- /dev/null +++ b/lasp/device/lasp_devicepybind.h @@ -0,0 +1,44 @@ +#pragma once +#include "lasp_daq.h" +#include +#include + +#include +namespace py = pybind11; + +namespace std { +class thread; +}; + +class PyDaq { + Daq *_daq = nullptr; + std::thread *_daqThread = nullptr; + + SafeQueue _inQueue; + SafeQueue _outQueue; + + py::function _audioCallback; + + std::atomic _stopThread; + std::atomic _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 getDeviceInfo(); + const Daq &daq() const { return *_daq; } + +private: + void threadFcn(); +}; diff --git a/lasp/device/lasp_rtaudio.pyx b/lasp/device/lasp_rtaudio.pyx deleted file mode 100644 index 02a1fcb..0000000 --- a/lasp/device/lasp_rtaudio.pyx +++ /dev/null @@ -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 = (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( &(( 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 = 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 = 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 = 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 = 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 = 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= 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 = malloc(sizeof(PyStreamData)) - if self.sd == NULL: - raise MemoryError('Could not allocate stream: memory error.') - - self.sd.pyCallback = avstream._audioCallback - # Increase reference count to the callback - Py_INCREF( 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 = 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 = 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, - 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, - 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( stream.pyCallback) - stream.pyCallback = NULL - # fprintf(stderr, "End cleanup callback...\n") - free(stream) diff --git a/lasp/device/lasp_rtaudiodaq.cpp b/lasp/device/lasp_rtaudiodaq.cpp new file mode 100644 index 0000000..8ebea84 --- /dev/null +++ b/lasp/device/lasp_rtaudiodaq.cpp @@ -0,0 +1,369 @@ +#if LASP_HAS_RTAUDIO == 1 +#include "lasp_rtaudiodaq.h" +#include +#include +#include +#include +#include + +using std::cerr; +using std::endl; +using std::atomic; + +void fillRtAudioDeviceInfo(vector &devinfolist) { + + vector 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 *inqueue = NULL; + SafeQueue *outqueue = NULL; + SafeQueue *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(); + } + } + + friend int mycallback(void *outputBuffer, void *inputBuffer, + unsigned int nFrames, double streamTime, + RtAudioStreamStatus status, void *userData); + + void start(SafeQueue *inqueue, SafeQueue *outqueue) { + this->inqueue = inqueue; + this->outqueue = outqueue; + if (monitorOutput) { + this->outDelayqueue = new SafeQueue(); + } + + 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 *inqueue = daq->inqueue; + SafeQueue *outqueue = daq->outqueue; + SafeQueue *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 diff --git a/lasp/device/lasp_rtaudiodaq.h b/lasp/device/lasp_rtaudiodaq.h new file mode 100644 index 0000000..47a8cbb --- /dev/null +++ b/lasp/device/lasp_rtaudiodaq.h @@ -0,0 +1,14 @@ +#pragma once +#include "lasp_daq.h" +#include + +std::unique_ptr 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 &devinfolist); + diff --git a/lasp/device/lasp_uldaq.cpp b/lasp/device/lasp_uldaq.cpp new file mode 100644 index 0000000..a804161 --- /dev/null +++ b/lasp/device/lasp_uldaq.cpp @@ -0,0 +1,550 @@ +#include "lasp_uldaq.h" +#include "lasp_daqconfig.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 _stopThread{false}; + + const us _nFramesPerBlock; + + void threadFcn(std::optional inCallback, + std::optional 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 inCallback, + std::optional 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 inCallback, + std::optional 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 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( + &buf[totalOffset + channel * nFramesPerBlock]), + nFramesPerBlock * sizeof(double)); + } + if (monitorOutput) { + + cv[0] = gsl::span( + reinterpret_cast( + &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(&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(&buf[buffer_mid_idx]), + nFramesPerBlock * sizeof(double))}; + cb(cv, dtype_descr); + botenqueued = true; + } + } else { + botenqueued = false; + if (!topenqueued) { + + ChannelView cv{gsl::span(reinterpret_cast(&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 inCallback, + std::optional outCallback) { + + DEBUGTRACE_ENTER; + std::unique_ptr ibh; + std::unique_ptr obh; + + if (neninchannels() > 0) { + ibh = std::make_unique(*this, inCallback.value()); + } + if (nenoutchannels() > 0) { + obh = std::make_unique(*this, outCallback.value()); + } + + const double sleeptime = + static_cast(_nFramesPerBlock) / (16 * samplerate()); + const us sleeptime_us = static_cast(sleeptime * 1e6); + + while (!_stopThread) { + if (ibh) { + (*ibh)(); + } + if (obh) { + (*obh)(); + } + std::this_thread::sleep_for(std::chrono::microseconds(sleeptime_us)); + } +} + +std::unique_ptr createUlDaqDevice(const DeviceInfo &devinfo, + const DaqConfiguration &config) { + return std::make_unique(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 &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(&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); + } +} diff --git a/lasp/device/lasp_uldaq.h b/lasp/device/lasp_uldaq.h new file mode 100644 index 0000000..4dd7489 --- /dev/null +++ b/lasp/device/lasp_uldaq.h @@ -0,0 +1,14 @@ +#pragma once +#include "lasp_daq.h" + +std::unique_ptr 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 &devinfolist); + +