First work on going to Pybind11. Rewritten the code for the UlDaq significantly. Much cleaner, but still contains bugs.
This commit is contained in:
parent
9e03f5e944
commit
f635cac209
1
.gitignore
vendored
1
.gitignore
vendored
@ -31,3 +31,4 @@ test/test_uldaq
|
||||
lasp/device/lasp_daq.cxx
|
||||
lasp/c/lasp_config.h
|
||||
compile_commands.json
|
||||
.cache
|
||||
|
6
.gitmodules
vendored
6
.gitmodules
vendored
@ -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
|
||||
|
@ -5,11 +5,13 @@ cmake_policy(SET CMP0079 NEW)
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/lasp/cmake")
|
||||
include("BuildType")
|
||||
|
||||
|
||||
# This is used for code completion in vim
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS=ON)
|
||||
project(LASP LANGUAGES C CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
|
||||
# Whether we want to use blas yes or no
|
||||
option(LASP_USE_BLAS "Use external blas library for math" ON)
|
||||
option(LASP_ARM "Compile subset of code for ARM real time (Bela board)" OFF)
|
||||
@ -24,6 +26,11 @@ set(LASP_TRACERNAME "defaulttracer" CACHE STRING "Name of tracer variable contai
|
||||
set(LASP_FFT_BACKEND "FFTW" CACHE STRING "FFT Library backend")
|
||||
set_property(CACHE LASP_FFT_BACKEND PROPERTY STRINGS "FFTW" "FFTPack" "None")
|
||||
|
||||
set(PYBIND11_FINDPYTHON ON)
|
||||
find_package(pybind11 CONFIG REQUIRED)
|
||||
# Required for PYBIND11
|
||||
set(POSITION_INDEPENDENT_CODE True)
|
||||
|
||||
if(CMAKE_BUILD_TYPE STREQUAL Debug)
|
||||
set(LASP_DEBUG True)
|
||||
else()
|
||||
@ -48,8 +55,6 @@ endif()
|
||||
add_definitions(-DLASP_MAX_NFFT=33554432) # 2**25
|
||||
|
||||
# ####################################### End of user-adjustable variables section
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
|
||||
if(LASP_FFT_BACKEND STREQUAL "FFTW")
|
||||
find_library(FFTW_LIBRARY NAMES fftw3 fftw)
|
||||
@ -87,7 +92,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
||||
add_definitions(-DHAS_RTAUDIO_WIN_WASAPI_API)
|
||||
else() # Linux compile
|
||||
set(win32 false)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c11 -Werror=incompatible-pointer-types")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror=incompatible-pointer-types")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -Wfatal-errors")
|
||||
include_directories(/usr/local/include/rtaudio)
|
||||
@ -98,34 +103,10 @@ else() # Linux compile
|
||||
# well.
|
||||
|
||||
endif()
|
||||
|
||||
set(CYTHON_FLAGS "--fast-fail")
|
||||
if(LASP_DEBUG)
|
||||
set(LASP_DEBUG_CYTHON=True)
|
||||
message("Building debug code")
|
||||
# This will produce html files
|
||||
# set(CYTHON_ANNOTATE ON)
|
||||
# Add the __FILENAME__ macro
|
||||
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D__FILENAME__='\"$(subst ${CMAKE_SOURCE_DIR}/,,$(abspath $<))\"'")
|
||||
else()
|
||||
message("Building LASP for release")
|
||||
set(LASP_DEBUG_CYTHON=False)
|
||||
set(CYTHON_ANNOTATE OFF)
|
||||
set(CYTHON_NO_DOCSTRINGS ON)
|
||||
# Strip unnecessary symbols
|
||||
# set(CMAKE_SHARED_LINKER_FLAGS "-Wl,--gc-sections")
|
||||
# set(CMAKE_MODULE_LINKER_FLAGS "-Wl,--gc-sections")
|
||||
endif(LASP_DEBUG)
|
||||
|
||||
# The last argument here takes care of calling SIGABRT when an integer overflow
|
||||
# occures.
|
||||
############################## General compilation flags (independent of debug mode, windows or linux)
|
||||
set(CYTHON_EXTRA_C_FLAGS "-Wno-sign-compare -Wno-cpp -Wno-implicit-fallthrough -Wno-incompatible-pointer-types -Wno-strict-aliasing")
|
||||
set(CYTHON_EXTRA_CXX_FLAGS "-Wno-sign-compare -Wno-cpp -Wno-implicit-fallthrough -Wno-strict-aliasing")
|
||||
|
||||
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra \
|
||||
-Wno-type-limits")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-type-limits")
|
||||
|
||||
# Debug make flags
|
||||
set(CMAKE_C_FLAGS_DEBUG "-g" )
|
||||
@ -137,8 +118,8 @@ set(CMAKE_C_FLAGS_RELEASE "-O2 -mfpmath=sse -march=x86-64 -mtune=native \
|
||||
|
||||
|
||||
# Python searching.
|
||||
set(Python_ADDITIONAL_VERSIONS "3.8")
|
||||
set(python_version_windll "38")
|
||||
# set(Python_ADDITIONAL_VERSIONS "3.8")
|
||||
# set(python_version_windll "38")
|
||||
find_package(PythonLibs REQUIRED )
|
||||
find_package(PythonInterp REQUIRED)
|
||||
|
||||
@ -152,7 +133,10 @@ endif()
|
||||
|
||||
include_directories(lasp/c)
|
||||
include_directories(STL-Threadsafe/include)
|
||||
include_directories(gsl-lite/include)
|
||||
include_directories(DebugTrace-cpp/include)
|
||||
add_subdirectory(lasp)
|
||||
add_subdirectory(gsl-lite)
|
||||
add_subdirectory(test)
|
||||
|
||||
# set(SETUP_PY_IN "${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in")
|
||||
|
1
DebugTrace-cpp
Submodule
1
DebugTrace-cpp
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 9b143ea40a34d6268d671ea54cd0ba80396b6363
|
1
gsl-lite
Submodule
1
gsl-lite
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 4720a2980a30da085b4ddb4a0ea2a71af7351a48
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -38,11 +38,11 @@ typedef double d; /* Shortcut for double */
|
||||
#error LASP_FLOAT_SIZE should be either 32 or 64
|
||||
#endif
|
||||
|
||||
|
||||
#include <complex.h>
|
||||
#ifdef __cplusplus
|
||||
#include <complex>
|
||||
typedef std::complex<d> c;
|
||||
#else
|
||||
#include <complex.h>
|
||||
#if LASP_FLOAT_SIZE == 32
|
||||
typedef float complex c;
|
||||
#else
|
||||
|
@ -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 )
|
@ -1,310 +0,0 @@
|
||||
# Define a function to create Cython modules.
|
||||
#
|
||||
# For more information on the Cython project, see http://cython.org/.
|
||||
# "Cython is a language that makes writing C extensions for the Python language
|
||||
# as easy as Python itself."
|
||||
#
|
||||
# This file defines a CMake function to build a Cython Python module.
|
||||
# To use it, first include this file.
|
||||
#
|
||||
# include( UseCython )
|
||||
#
|
||||
# Then call cython_add_module to create a module.
|
||||
#
|
||||
# cython_add_module( <module_name> <src1> <src2> ... <srcN> )
|
||||
#
|
||||
# To create a standalone executable, the function
|
||||
#
|
||||
# cython_add_standalone_executable( <executable_name> [MAIN_MODULE src1] <src1> <src2> ... <srcN> )
|
||||
#
|
||||
# To avoid dependence on Python, set the PYTHON_LIBRARY cache variable to point
|
||||
# to a static library. If a MAIN_MODULE source is specified,
|
||||
# the "if __name__ == '__main__':" from that module is used as the C main() method
|
||||
# for the executable. If MAIN_MODULE, the source with the same basename as
|
||||
# <executable_name> is assumed to be the MAIN_MODULE.
|
||||
#
|
||||
# Where <module_name> is the name of the resulting Python module and
|
||||
# <src1> <src2> ... are source files to be compiled into the module, e.g. *.pyx,
|
||||
# *.py, *.c, *.cxx, etc. A CMake target is created with name <module_name>. This can
|
||||
# be used for target_link_libraries(), etc.
|
||||
#
|
||||
# The sample paths set with the CMake include_directories() command will be used
|
||||
# for include directories to search for *.pxd when running the Cython complire.
|
||||
#
|
||||
# Cache variables that effect the behavior include:
|
||||
#
|
||||
# CYTHON_ANNOTATE
|
||||
# CYTHON_NO_DOCSTRINGS
|
||||
# CYTHON_FLAGS
|
||||
#
|
||||
# Source file properties that effect the build process are
|
||||
#
|
||||
# CYTHON_IS_CXX
|
||||
#
|
||||
# If this is set of a *.pyx file with CMake set_source_files_properties()
|
||||
# command, the file will be compiled as a C++ file.
|
||||
#
|
||||
# See also FindCython.cmake
|
||||
|
||||
#=============================================================================
|
||||
# Copyright 2011 Kitware, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#=============================================================================
|
||||
|
||||
# Configuration options.
|
||||
set( CYTHON_ANNOTATE OFF
|
||||
CACHE BOOL "Create an annotated .html file when compiling *.pyx." )
|
||||
set( CYTHON_NO_DOCSTRINGS OFF
|
||||
CACHE BOOL "Strip docstrings from the compiled module." )
|
||||
set( CYTHON_FLAGS "" CACHE STRING
|
||||
"Extra flags to the cython compiler." )
|
||||
mark_as_advanced( CYTHON_ANNOTATE CYTHON_NO_DOCSTRINGS CYTHON_FLAGS )
|
||||
|
||||
find_package( Cython REQUIRED )
|
||||
|
||||
|
||||
set( CYTHON_CXX_EXTENSION "cxx" )
|
||||
set( CYTHON_C_EXTENSION "c" )
|
||||
|
||||
# Create a *.c or *.cxx file from a *.pyx file.
|
||||
# Input the generated file basename. The generate file will put into the variable
|
||||
# placed in the "generated_file" argument. Finally all the *.py and *.pyx files.
|
||||
function( compile_pyx _name generated_file )
|
||||
# Default to assuming all files are C.
|
||||
set( cxx_arg "" )
|
||||
set( extension ${CYTHON_C_EXTENSION} )
|
||||
set( pyx_lang "C" )
|
||||
set( comment "Compiling Cython C source for ${_name}..." )
|
||||
|
||||
set( cython_include_directories "" )
|
||||
set( pxd_dependencies "" )
|
||||
set( pxi_dependencies "" )
|
||||
set( c_header_dependencies "" )
|
||||
set( pyx_locations "" )
|
||||
|
||||
foreach( pyx_file ${ARGN} )
|
||||
get_filename_component( pyx_file_basename "${pyx_file}" NAME_WE )
|
||||
|
||||
# Determine if it is a C or C++ file.
|
||||
get_source_file_property( property_is_cxx ${pyx_file} CYTHON_IS_CXX )
|
||||
if( ${property_is_cxx} )
|
||||
set( cxx_arg "--cplus" )
|
||||
set( extension ${CYTHON_CXX_EXTENSION} )
|
||||
set( pyx_lang "CXX" )
|
||||
set( comment "Compiling Cython CXX source for ${_name}..." )
|
||||
endif()
|
||||
|
||||
# Get the include directories.
|
||||
get_source_file_property( pyx_location ${pyx_file} LOCATION )
|
||||
get_filename_component( pyx_path ${pyx_location} PATH )
|
||||
message(${pyx_path})
|
||||
get_directory_property( cmake_include_directories DIRECTORY ${pyx_path} INCLUDE_DIRECTORIES )
|
||||
list( APPEND cython_include_directories ${cmake_include_directories} )
|
||||
list( APPEND pyx_locations "${pyx_location}" )
|
||||
|
||||
# Determine dependencies.
|
||||
# Add the pxd file will the same name as the given pyx file.
|
||||
unset( corresponding_pxd_file CACHE )
|
||||
find_file( corresponding_pxd_file ${pyx_file_basename}.pxd
|
||||
PATHS "${pyx_path}" ${cmake_include_directories}
|
||||
NO_DEFAULT_PATH )
|
||||
if( corresponding_pxd_file )
|
||||
list( APPEND pxd_dependencies "${corresponding_pxd_file}" )
|
||||
endif()
|
||||
|
||||
# pxd files to check for additional dependencies.
|
||||
set( pxds_to_check "${pyx_file}" "${pxd_dependencies}" )
|
||||
set( pxds_checked "" )
|
||||
set( number_pxds_to_check 1 )
|
||||
while( ${number_pxds_to_check} GREATER 0 )
|
||||
foreach( pxd ${pxds_to_check} )
|
||||
list( APPEND pxds_checked "${pxd}" )
|
||||
list( REMOVE_ITEM pxds_to_check "${pxd}" )
|
||||
|
||||
# check for C header dependencies
|
||||
file( STRINGS "${pxd}" extern_from_statements
|
||||
REGEX "cdef[ ]+extern[ ]+from.*$" )
|
||||
foreach( statement ${extern_from_statements} )
|
||||
# Had trouble getting the quote in the regex
|
||||
string( REGEX REPLACE "cdef[ ]+extern[ ]+from[ ]+[\"]([^\"]+)[\"].*" "\\1" header "${statement}" )
|
||||
unset( header_location CACHE )
|
||||
find_file( header_location ${header} PATHS ${cmake_include_directories} )
|
||||
if( header_location )
|
||||
list( FIND c_header_dependencies "${header_location}" header_idx )
|
||||
if( ${header_idx} LESS 0 )
|
||||
list( APPEND c_header_dependencies "${header_location}" )
|
||||
endif()
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
# check for pxd dependencies
|
||||
|
||||
# Look for cimport statements.
|
||||
set( module_dependencies "" )
|
||||
file( STRINGS "${pxd}" cimport_statements REGEX cimport )
|
||||
foreach( statement ${cimport_statements} )
|
||||
if( ${statement} MATCHES from )
|
||||
string( REGEX REPLACE "from[ ]+([^ ]+).*" "\\1" module "${statement}" )
|
||||
else()
|
||||
string( REGEX REPLACE "cimport[ ]+([^ ]+).*" "\\1" module "${statement}" )
|
||||
endif()
|
||||
list( APPEND module_dependencies ${module} )
|
||||
endforeach()
|
||||
list( REMOVE_DUPLICATES module_dependencies )
|
||||
# Add the module to the files to check, if appropriate.
|
||||
foreach( module ${module_dependencies} )
|
||||
unset( pxd_location CACHE )
|
||||
find_file( pxd_location ${module}.pxd
|
||||
PATHS "${pyx_path}" ${cmake_include_directories} NO_DEFAULT_PATH )
|
||||
if( pxd_location )
|
||||
list( FIND pxds_checked ${pxd_location} pxd_idx )
|
||||
if( ${pxd_idx} LESS 0 )
|
||||
list( FIND pxds_to_check ${pxd_location} pxd_idx )
|
||||
if( ${pxd_idx} LESS 0 )
|
||||
list( APPEND pxds_to_check ${pxd_location} )
|
||||
list( APPEND pxd_dependencies ${pxd_location} )
|
||||
endif() # if it is not already going to be checked
|
||||
endif() # if it has not already been checked
|
||||
endif() # if pxd file can be found
|
||||
endforeach() # for each module dependency discovered
|
||||
endforeach() # for each pxd file to check
|
||||
list( LENGTH pxds_to_check number_pxds_to_check )
|
||||
endwhile()
|
||||
|
||||
# Look for included pxi files
|
||||
file(STRINGS "${pyx_file}" include_statements REGEX "include +['\"]([^'\"]+).*")
|
||||
foreach(statement ${include_statements})
|
||||
string(REGEX REPLACE "include +['\"]([^'\"]+).*" "\\1" pxi_file "${statement}")
|
||||
unset(pxi_location CACHE)
|
||||
find_file(pxi_location ${pxi_file}
|
||||
PATHS "${pyx_path}" ${cmake_include_directories} NO_DEFAULT_PATH)
|
||||
if (pxi_location)
|
||||
list(APPEND pxi_dependencies ${pxi_location})
|
||||
endif()
|
||||
endforeach() # for each include statement found
|
||||
|
||||
endforeach() # pyx_file
|
||||
|
||||
# Set additional flags.
|
||||
if( CYTHON_ANNOTATE )
|
||||
set( annotate_arg "--annotate" )
|
||||
endif()
|
||||
|
||||
if( CYTHON_NO_DOCSTRINGS )
|
||||
set( no_docstrings_arg "--no-docstrings" )
|
||||
endif()
|
||||
|
||||
if( "${CMAKE_BUILD_TYPE}" STREQUAL "Debug" OR
|
||||
"${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo" )
|
||||
set( cython_debug_arg "--gdb" )
|
||||
endif()
|
||||
|
||||
if( "${PYTHONLIBS_VERSION_STRING}" MATCHES "^2." )
|
||||
set( version_arg "-2" )
|
||||
elseif( "${PYTHONLIBS_VERSION_STRING}" MATCHES "^3." )
|
||||
set( version_arg "-3" )
|
||||
else()
|
||||
set( version_arg )
|
||||
endif()
|
||||
|
||||
# Include directory arguments.
|
||||
list( REMOVE_DUPLICATES cython_include_directories )
|
||||
set( include_directory_arg "" )
|
||||
foreach( _include_dir ${cython_include_directories} )
|
||||
set( include_directory_arg ${include_directory_arg} "-I" "${_include_dir}" )
|
||||
endforeach()
|
||||
|
||||
# Determining generated file name.
|
||||
set( _generated_file "${CMAKE_CURRENT_BINARY_DIR}/${_name}.${extension}" )
|
||||
set_source_files_properties( ${_generated_file} PROPERTIES GENERATED TRUE )
|
||||
set( ${generated_file} ${_generated_file} PARENT_SCOPE )
|
||||
|
||||
list( REMOVE_DUPLICATES pxd_dependencies )
|
||||
list( REMOVE_DUPLICATES c_header_dependencies )
|
||||
|
||||
# Add the command to run the compiler.
|
||||
add_custom_command( OUTPUT ${_generated_file}
|
||||
COMMAND ${CYTHON_EXECUTABLE}
|
||||
ARGS ${cxx_arg} ${include_directory_arg} ${version_arg}
|
||||
${annotate_arg} ${no_docstrings_arg} ${cython_debug_arg} ${CYTHON_FLAGS}
|
||||
--output-file ${_generated_file} ${pyx_locations}
|
||||
DEPENDS ${pyx_locations} ${pxd_dependencies} ${pxi_dependencies}
|
||||
IMPLICIT_DEPENDS ${pyx_lang} ${c_header_dependencies}
|
||||
COMMENT ${comment}
|
||||
)
|
||||
|
||||
# Remove their visibility to the user.
|
||||
set( corresponding_pxd_file "" CACHE INTERNAL "" )
|
||||
set( header_location "" CACHE INTERNAL "" )
|
||||
set( pxd_location "" CACHE INTERNAL "" )
|
||||
endfunction()
|
||||
|
||||
# cython_add_module( <name> src1 src2 ... srcN )
|
||||
# Build the Cython Python module.
|
||||
function( cython_add_module _name )
|
||||
set( pyx_module_sources "" )
|
||||
set( other_module_sources "" )
|
||||
foreach( _file ${ARGN} )
|
||||
if( ${_file} MATCHES ".*\\.py[x]?$" )
|
||||
list( APPEND pyx_module_sources ${_file} )
|
||||
else()
|
||||
list( APPEND other_module_sources ${_file} )
|
||||
endif()
|
||||
endforeach()
|
||||
compile_pyx( ${_name} generated_file ${pyx_module_sources} )
|
||||
include_directories( ${PYTHON_INCLUDE_DIRS} )
|
||||
python_add_module( ${_name} ${generated_file} ${other_module_sources} )
|
||||
if( APPLE )
|
||||
set_target_properties( ${_name} PROPERTIES LINK_FLAGS "-undefined dynamic_lookup" )
|
||||
else()
|
||||
target_link_libraries( ${_name} ${PYTHON_LIBRARIES} )
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
include( CMakeParseArguments )
|
||||
# cython_add_standalone_executable( _name [MAIN_MODULE src3.py] src1 src2 ... srcN )
|
||||
# Creates a standalone executable the given sources.
|
||||
function( cython_add_standalone_executable _name )
|
||||
set( pyx_module_sources "" )
|
||||
set( other_module_sources "" )
|
||||
set( main_module "" )
|
||||
cmake_parse_arguments( cython_arguments "" "MAIN_MODULE" "" ${ARGN} )
|
||||
include_directories( ${PYTHON_INCLUDE_DIRS} )
|
||||
foreach( _file ${cython_arguments_UNPARSED_ARGUMENTS} )
|
||||
if( ${_file} MATCHES ".*\\.py[x]?$" )
|
||||
get_filename_component( _file_we ${_file} NAME_WE )
|
||||
if( "${_file_we}" STREQUAL "${_name}" )
|
||||
set( main_module "${_file}" )
|
||||
elseif( NOT "${_file}" STREQUAL "${cython_arguments_MAIN_MODULE}" )
|
||||
set( PYTHON_MODULE_${_file_we}_static_BUILD_SHARED OFF )
|
||||
compile_pyx( "${_file_we}_static" generated_file "${_file}" )
|
||||
list( APPEND pyx_module_sources "${generated_file}" )
|
||||
endif()
|
||||
else()
|
||||
list( APPEND other_module_sources ${_file} )
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
if( cython_arguments_MAIN_MODULE )
|
||||
set( main_module ${cython_arguments_MAIN_MODULE} )
|
||||
endif()
|
||||
if( NOT main_module )
|
||||
message( FATAL_ERROR "main module not found." )
|
||||
endif()
|
||||
get_filename_component( main_module_we "${main_module}" NAME_WE )
|
||||
set( CYTHON_FLAGS ${CYTHON_FLAGS} --embed )
|
||||
compile_pyx( "${main_module_we}_static" generated_file ${main_module} )
|
||||
add_executable( ${_name} ${generated_file} ${pyx_module_sources} ${other_module_sources} )
|
||||
target_link_libraries( ${_name} ${PYTHON_LIBRARIES} ${pyx_module_libs} )
|
||||
endfunction()
|
@ -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")
|
||||
|
@ -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 +
|
@ -1,198 +0,0 @@
|
||||
#include "lasp_cppdaq.h"
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include "lasp_config.h"
|
||||
|
||||
#define MAX_DEV_COUNT_PER_API 20
|
||||
|
||||
#ifdef LASP_HAS_ULDAQ
|
||||
#include "lasp_cppuldaq.h"
|
||||
#endif
|
||||
#ifdef LASP_HAS_RTAUDIO
|
||||
#include "lasp_cpprtaudio.h"
|
||||
#endif
|
||||
|
||||
vector<DeviceInfo> Daq::getDeviceInfo() {
|
||||
vector<DeviceInfo> devs;
|
||||
#ifdef LASP_HAS_ULDAQ
|
||||
fillUlDaqDeviceInfo(devs);
|
||||
#endif
|
||||
|
||||
#ifdef LASP_HAS_RTAUDIO
|
||||
fillRtAudioDeviceInfo(devs);
|
||||
#endif
|
||||
|
||||
return devs;
|
||||
}
|
||||
|
||||
vector<DaqApi> DaqApi::getAvailableApis() {
|
||||
|
||||
vector<DaqApi> apis;
|
||||
apis.resize(6);
|
||||
#ifdef LASP_HAS_ULDAQ
|
||||
apis.at(uldaqapi.apicode) = uldaqapi;
|
||||
#endif
|
||||
#ifdef LASP_HAS_RTAUDIO
|
||||
apis.at(rtaudioAlsaApi.apicode) = rtaudioAlsaApi;
|
||||
apis.at(rtaudioPulseaudioApi.apicode) = rtaudioPulseaudioApi;
|
||||
apis.at(rtaudioWasapiApi.apicode) = rtaudioWasapiApi;
|
||||
apis.at(rtaudioDsApi.apicode) = rtaudioDsApi;
|
||||
apis.at(rtaudioAsioApi.apicode) = rtaudioAsioApi;
|
||||
#endif
|
||||
return apis;
|
||||
}
|
||||
|
||||
|
||||
DaqConfiguration::DaqConfiguration(const DeviceInfo &device) {
|
||||
|
||||
api = device.api;
|
||||
device_name = device.device_name;
|
||||
|
||||
eninchannels.resize(device.ninchannels, false);
|
||||
enoutchannels.resize(device.noutchannels, false);
|
||||
|
||||
inchannel_sensitivities.resize(device.ninchannels, 1.0);
|
||||
inchannel_metadata.resize(device.ninchannels, "");
|
||||
for (us i = 0; i < eninchannels.size(); i++) {
|
||||
std::stringstream chname;
|
||||
chname << "Unnamed input channel " << i;
|
||||
inchannel_names.push_back(chname.str());
|
||||
|
||||
}
|
||||
|
||||
outchannel_metadata.resize(device.noutchannels, "");
|
||||
outchannel_sensitivities.resize(device.noutchannels, 1.0);
|
||||
for (us i = 0; i < enoutchannels.size(); i++) {
|
||||
std::stringstream chname;
|
||||
chname << "Unnamed output channel " << i;
|
||||
outchannel_names.push_back(chname.str());
|
||||
}
|
||||
sampleRateIndex = device.prefSampleRateIndex;
|
||||
dataTypeIndex = device.prefDataTypeIndex;
|
||||
framesPerBlockIndex = device.prefFramesPerBlockIndex;
|
||||
|
||||
monitorOutput = false;
|
||||
|
||||
inputIEPEEnabled.resize(device.ninchannels, false);
|
||||
inputACCouplingMode.resize(device.ninchannels, false);
|
||||
inputRangeIndices.resize(device.ninchannels, device.prefInputRangeIndex);
|
||||
|
||||
assert(match(device));
|
||||
}
|
||||
|
||||
bool DaqConfiguration::match(const DeviceInfo& dev) const {
|
||||
return (dev.device_name == device_name && dev.api == api);
|
||||
}
|
||||
|
||||
int DaqConfiguration::getHighestInChannel() const {
|
||||
for(int i=eninchannels.size()-1; i>-1;i--) {
|
||||
if(eninchannels.at(i)) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int DaqConfiguration::getHighestOutChannel() const {
|
||||
for(us i=enoutchannels.size()-1; i>=0;i--) {
|
||||
if(enoutchannels.at(i)) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
int DaqConfiguration::getLowestInChannel() const {
|
||||
for(us i=0; i<eninchannels.size();i++) {
|
||||
if(eninchannels.at(i)) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
int DaqConfiguration::getLowestOutChannel() const {
|
||||
for(us i=0; i<enoutchannels.size();i++) {
|
||||
if(enoutchannels.at(i)) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
Daq *Daq::createDaq(const DeviceInfo& devinfo,
|
||||
const DaqConfiguration &config) {
|
||||
|
||||
if(!config.match(devinfo)) {
|
||||
throw runtime_error("DaqConfiguration does not match device info");
|
||||
}
|
||||
|
||||
// Some basic sanity checks
|
||||
if ((devinfo.ninchannels != config.eninchannels.size())) {
|
||||
/* cerr << "devinfo.ninchannels: " << devinfo.ninchannels << endl; */
|
||||
/* cerr << "config.eninchannels.size(): " << config.eninchannels.size() << endl; */
|
||||
throw runtime_error("Invalid length of enabled input channels specified");
|
||||
}
|
||||
if ((devinfo.noutchannels != config.enoutchannels.size())) {
|
||||
throw runtime_error("outvalid length of enabled output channels specified");
|
||||
}
|
||||
|
||||
int apicode = devinfo.api.apicode;
|
||||
if(devinfo.api == DaqApi()) {
|
||||
throw std::runtime_error(string("Unable to match API: ") + devinfo.api.apiname);
|
||||
}
|
||||
#ifdef LASP_HAS_ULDAQ
|
||||
else if (devinfo.api == uldaqapi) {
|
||||
return createUlDaqDevice(devinfo, config);
|
||||
}
|
||||
#endif
|
||||
#ifdef LASP_HAS_RTAUDIO
|
||||
else if(apicode >= 1 && apicode <= 5) {
|
||||
return createRtAudioDevice(devinfo, config);
|
||||
}
|
||||
#endif
|
||||
else {
|
||||
throw std::runtime_error(string("Unable to match API: ") +
|
||||
devinfo.api.apiname);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Daq::Daq(const DeviceInfo &devinfo, const DaqConfiguration &config)
|
||||
: DaqConfiguration(config), DeviceInfo(devinfo) {
|
||||
|
||||
if (monitorOutput && !(nenoutchannels() > 0)) {
|
||||
throw runtime_error(
|
||||
"Output monitoring only possible when at least one output channel is enabled. Please make sure to enable at least one output channel");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
double Daq::samplerate() const {
|
||||
mutexlock lock(mutex);
|
||||
assert(sampleRateIndex < availableSampleRates.size());
|
||||
return availableSampleRates.at(sampleRateIndex);
|
||||
}
|
||||
|
||||
|
||||
DataType Daq::dataType() const {
|
||||
mutexlock lock(mutex);
|
||||
assert((us)dataTypeIndex < availableDataTypes.size());
|
||||
return availableDataTypes.at(dataTypeIndex);
|
||||
}
|
||||
|
||||
|
||||
double Daq::inputRangeForChannel(us ch) const {
|
||||
if (!(ch < ninchannels)) {
|
||||
throw runtime_error("Invalid channel number");
|
||||
}
|
||||
mutexlock lock(mutex);
|
||||
assert(inputRangeIndices.size() == eninchannels.size());
|
||||
return availableInputRanges.at(inputRangeIndices.at(ch));
|
||||
}
|
||||
|
||||
|
||||
us Daq::neninchannels(bool include_monitorchannel) const {
|
||||
mutexlock lock(mutex);
|
||||
us inch = std::count(eninchannels.begin(), eninchannels.end(), true);
|
||||
if (monitorOutput && include_monitorchannel) {
|
||||
inch++;
|
||||
}
|
||||
return inch;
|
||||
}
|
||||
|
||||
|
||||
us Daq::nenoutchannels() const {
|
||||
mutexlock lock(mutex);
|
||||
return std::count(enoutchannels.begin(), enoutchannels.end(), true);
|
||||
}
|
@ -1,332 +0,0 @@
|
||||
#ifndef LASP_CPPDAQ_H
|
||||
#define LASP_CPPDAQ_H
|
||||
#include "lasp_config.h"
|
||||
#include "lasp_types.h"
|
||||
|
||||
#ifdef LASP_HAS_RTAUDIO
|
||||
#include <RtAudio.h>
|
||||
#endif
|
||||
|
||||
#ifdef LASP_HAS_ULDAQ
|
||||
#include <uldaq.h>
|
||||
#endif
|
||||
|
||||
#include "lasp_cppqueue.h"
|
||||
#include "string"
|
||||
#include "vector"
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <sstream>
|
||||
|
||||
using std::cerr;
|
||||
using std::cout;
|
||||
using std::endl;
|
||||
using std::getline;
|
||||
using std::runtime_error;
|
||||
using std::string;
|
||||
using std::vector;
|
||||
using std::to_string;
|
||||
|
||||
typedef vector<bool> boolvec;
|
||||
typedef vector<double> dvec;
|
||||
typedef vector<us> usvec;
|
||||
typedef std::lock_guard<std::mutex> mutexlock;
|
||||
|
||||
class DataType {
|
||||
public:
|
||||
string name;
|
||||
unsigned sw;
|
||||
bool is_floating;
|
||||
|
||||
DataType(const char *name, unsigned sw, bool is_floating)
|
||||
: name(name), sw(sw), is_floating(is_floating) {}
|
||||
DataType() : name("invalid data type"), sw(0), is_floating(false) {}
|
||||
bool operator==(const DataType &o) {
|
||||
return (name == o.name && sw == o.sw && is_floating == o.is_floating);
|
||||
}
|
||||
};
|
||||
|
||||
const DataType dtype_invalid;
|
||||
const DataType dtype_fl32("32-bits floating point", 4, true);
|
||||
const DataType dtype_fl64("64-bits floating point", 8, true);
|
||||
const DataType dtype_int8("8-bits integers", 1, false);
|
||||
const DataType dtype_int24("24-bits integers", 1, false);
|
||||
const DataType dtype_int16("16-bits integers", 2, false);
|
||||
const DataType dtype_int32("32-bits integers", 4, false);
|
||||
|
||||
const std::vector<DataType> dataTypes = {
|
||||
dtype_int8, dtype_int16,dtype_int24, dtype_int32, dtype_fl32, dtype_fl64,
|
||||
};
|
||||
|
||||
class DaqApi {
|
||||
public:
|
||||
string apiname = "Invalid API";
|
||||
int apicode = -1;
|
||||
unsigned api_specific_subcode = 0;
|
||||
|
||||
DaqApi(string apiname, unsigned apicode, unsigned api_specific_subcode = 0)
|
||||
: apiname(apiname), apicode(apicode),
|
||||
api_specific_subcode(api_specific_subcode) {}
|
||||
DaqApi() {}
|
||||
bool operator==(const DaqApi &other) const {
|
||||
return (apiname == other.apiname && apicode == other.apicode &&
|
||||
api_specific_subcode == other.api_specific_subcode);
|
||||
}
|
||||
operator string() const { return apiname + ", code: " + to_string(apicode); }
|
||||
static vector<DaqApi> getAvailableApis();
|
||||
};
|
||||
|
||||
#ifdef LASP_HAS_ULDAQ
|
||||
const DaqApi uldaqapi("UlDaq", 0);
|
||||
#endif
|
||||
#ifdef LASP_HAS_RTAUDIO
|
||||
const DaqApi rtaudioAlsaApi("RtAudio Linux ALSA", 1, RtAudio::Api::LINUX_ALSA);
|
||||
const DaqApi rtaudioPulseaudioApi("RtAudio Linux Pulseaudio", 2,
|
||||
RtAudio::Api::LINUX_PULSE);
|
||||
const DaqApi rtaudioWasapiApi("RtAudio Windows Wasapi", 3,
|
||||
RtAudio::Api::WINDOWS_WASAPI);
|
||||
const DaqApi rtaudioDsApi("RtAudio Windows DirectSound", 4,
|
||||
RtAudio::Api::WINDOWS_DS);
|
||||
const DaqApi rtaudioAsioApi("RtAudio Windows ASIO", 5,
|
||||
RtAudio::Api::WINDOWS_ASIO);
|
||||
#endif
|
||||
|
||||
// Structure containing device info parameters
|
||||
class DeviceInfo {
|
||||
public:
|
||||
DaqApi api;
|
||||
string device_name = "";
|
||||
|
||||
int api_specific_devindex = -1;
|
||||
|
||||
vector<DataType> availableDataTypes;
|
||||
int prefDataTypeIndex = 0;
|
||||
|
||||
vector<double> availableSampleRates;
|
||||
int prefSampleRateIndex = -1;
|
||||
|
||||
vector<us> availableFramesPerBlock;
|
||||
unsigned prefFramesPerBlockIndex = 0;
|
||||
|
||||
dvec availableInputRanges;
|
||||
int prefInputRangeIndex = 0;
|
||||
|
||||
unsigned ninchannels = 0;
|
||||
unsigned noutchannels = 0;
|
||||
|
||||
bool hasInputIEPE = false;
|
||||
bool hasInputACCouplingSwitch = false;
|
||||
bool hasInputTrigger = false;
|
||||
|
||||
/* DeviceInfo(): */
|
||||
/* datatype(dtype_invalid) { } */
|
||||
|
||||
double prefSampleRate() const {
|
||||
if (((us)prefSampleRateIndex < availableSampleRates.size()) &&
|
||||
(prefSampleRateIndex >= 0)) {
|
||||
return availableSampleRates.at(prefSampleRateIndex);
|
||||
} else {
|
||||
throw std::runtime_error("No prefered sample rate available");
|
||||
}
|
||||
}
|
||||
|
||||
operator string() const {
|
||||
std::stringstream str;
|
||||
str << api.apiname + " " << api_specific_devindex << endl
|
||||
<< " number of input channels: " << ninchannels << endl
|
||||
<< " number of output channels: " << noutchannels << endl;
|
||||
return str.str();
|
||||
}
|
||||
|
||||
string serialize() const {
|
||||
// Simple serializer for this object, used because we found a bit late that
|
||||
// this object needs to be send over the wire. We do not want to make this
|
||||
// implementation in Python, as these objects are created here, in the C++
|
||||
// code. The Python wrapper is just a readonly wrapper.
|
||||
std::stringstream str;
|
||||
|
||||
str << api.apiname << "\t";
|
||||
str << api.apicode << "\t";
|
||||
str << api.api_specific_subcode << "\t";
|
||||
str << device_name << "\t";
|
||||
|
||||
str << availableDataTypes.size() << "\t";
|
||||
for(const DataType& dtype: availableDataTypes) {
|
||||
// WARNING: THIS GOES COMPLETELY WRONG WHEN NAMES contain A TAB!!!
|
||||
str << dtype.name << "\t";
|
||||
str << dtype.sw << "\t";
|
||||
str << dtype.is_floating << "\t";
|
||||
}
|
||||
str << prefDataTypeIndex << "\t";
|
||||
|
||||
str << availableSampleRates.size() << "\t";
|
||||
for(const double& fs: availableSampleRates) {
|
||||
// WARNING: THIS GOES COMPLETELY WRONG WHEN NAMES contain A TAB!!!
|
||||
str << fs << "\t";
|
||||
}
|
||||
str << prefSampleRateIndex << "\t";
|
||||
|
||||
str << availableFramesPerBlock.size() << "\t";
|
||||
for(const us& fb: availableFramesPerBlock) {
|
||||
// WARNING: THIS GOES COMPLETELY WRONG WHEN NAMES contain A TAB!!!
|
||||
str << fb << "\t";
|
||||
}
|
||||
str << prefFramesPerBlockIndex << "\t";
|
||||
|
||||
str << availableInputRanges.size() << "\t";
|
||||
for(const double& ir: availableInputRanges) {
|
||||
// WARNING: THIS GOES COMPLETELY WRONG WHEN NAMES contain A TAB!!!
|
||||
str << ir << "\t";
|
||||
}
|
||||
str << prefInputRangeIndex << "\t";
|
||||
|
||||
str << ninchannels << "\t";
|
||||
str << noutchannels << "\t";
|
||||
str << int(hasInputIEPE) << "\t";
|
||||
str << int(hasInputACCouplingSwitch) << "\t";
|
||||
str << int(hasInputTrigger) << "\t";
|
||||
|
||||
return str.str();
|
||||
}
|
||||
|
||||
static DeviceInfo deserialize(const string& dstr) {
|
||||
DeviceInfo devinfo;
|
||||
|
||||
std::stringstream str(dstr);
|
||||
string tmp;
|
||||
us N;
|
||||
// Lambda functions for deserializing
|
||||
auto nexts = [&]() { getline(str, tmp, '\t'); return tmp; };
|
||||
auto nexti = [&]() { getline(str, tmp, '\t'); return std::atoi(tmp.c_str()); };
|
||||
auto nextf = [&]() { getline(str, tmp, '\t'); return std::atof(tmp.c_str()); };
|
||||
|
||||
// Api
|
||||
string apiname = nexts();
|
||||
auto apicode = nexti();
|
||||
auto api_specific_subcode = nexti();
|
||||
DaqApi api(apiname, apicode, api_specific_subcode);
|
||||
devinfo.api = api;
|
||||
|
||||
devinfo.device_name = nexts();
|
||||
|
||||
N = us(nexti());
|
||||
for(us i=0;i<N; i++) {
|
||||
DataType dtype;
|
||||
dtype.name = nexts();
|
||||
dtype.sw =nexti();
|
||||
dtype.is_floating = bool(nexti());
|
||||
devinfo.availableDataTypes.push_back(dtype);
|
||||
}
|
||||
|
||||
devinfo.prefDataTypeIndex = nexti();
|
||||
|
||||
N = us(nexti());
|
||||
for(us i=0;i<N; i++) {
|
||||
devinfo.availableSampleRates.push_back(nextf());
|
||||
}
|
||||
devinfo.prefSampleRateIndex = nexti();
|
||||
|
||||
N = us(nexti());
|
||||
for(us i=0;i<N; i++) {
|
||||
devinfo.availableFramesPerBlock.push_back(nexti());
|
||||
}
|
||||
devinfo.prefFramesPerBlockIndex = nexti();
|
||||
|
||||
N = us(nexti());
|
||||
for(us i=0;i<N; i++) {
|
||||
devinfo.availableInputRanges.push_back(nexti());
|
||||
}
|
||||
devinfo.prefInputRangeIndex = nexti();
|
||||
|
||||
devinfo.ninchannels = nexti();
|
||||
devinfo.noutchannels = nexti();
|
||||
devinfo.hasInputIEPE = bool(nexti());
|
||||
devinfo.hasInputACCouplingSwitch = bool(nexti());
|
||||
devinfo.hasInputTrigger = bool(nexti());
|
||||
|
||||
return devinfo;
|
||||
}
|
||||
};
|
||||
|
||||
// Device configuration parameters
|
||||
class DaqConfiguration {
|
||||
public:
|
||||
DaqApi api;
|
||||
string device_name;
|
||||
|
||||
boolvec eninchannels; // Enabled input channelsvice(const DeviceInfo& devinfo,
|
||||
boolvec enoutchannels; // Enabled output channels
|
||||
|
||||
vector<double> inchannel_sensitivities;
|
||||
vector<string> inchannel_names;
|
||||
vector<string> inchannel_metadata;
|
||||
|
||||
vector<double> outchannel_sensitivities;
|
||||
vector<string> outchannel_names;
|
||||
vector<string> outchannel_metadata;
|
||||
|
||||
us sampleRateIndex = 0; // Index in list of sample rates
|
||||
|
||||
us dataTypeIndex = 0; // Required datatype for output, should be
|
||||
// present in the list
|
||||
|
||||
us framesPerBlockIndex = 0;
|
||||
|
||||
bool monitorOutput = false;
|
||||
|
||||
boolvec inputIEPEEnabled;
|
||||
boolvec inputACCouplingMode;
|
||||
|
||||
usvec inputRangeIndices;
|
||||
|
||||
// Create a default configuration, with all channels disabled on both
|
||||
// input and output, and default channel names
|
||||
DaqConfiguration(const DeviceInfo &device);
|
||||
DaqConfiguration() {}
|
||||
|
||||
bool match(const DeviceInfo &devinfo) const;
|
||||
|
||||
int getHighestInChannel() const;
|
||||
int getHighestOutChannel() const;
|
||||
|
||||
int getLowestInChannel() const;
|
||||
int getLowestOutChannel() const;
|
||||
};
|
||||
|
||||
class Daq;
|
||||
class Daq : public DaqConfiguration, public DeviceInfo {
|
||||
|
||||
mutable std::mutex mutex;
|
||||
|
||||
public:
|
||||
static vector<DeviceInfo> getDeviceInfo();
|
||||
|
||||
static Daq *createDaq(const DeviceInfo &, const DaqConfiguration &config);
|
||||
|
||||
Daq(const DeviceInfo &devinfo, const DaqConfiguration &config);
|
||||
|
||||
virtual void start(SafeQueue<void *> *inqueue,
|
||||
SafeQueue<void *> *outqueue) = 0;
|
||||
|
||||
virtual void stop() = 0;
|
||||
|
||||
virtual bool isRunning() const = 0;
|
||||
|
||||
virtual ~Daq(){};
|
||||
us neninchannels(bool include_monitorchannel = true) const;
|
||||
us nenoutchannels() const;
|
||||
|
||||
double samplerate() const;
|
||||
double inputRangeForChannel(us ch) const;
|
||||
DataType dataType() const;
|
||||
|
||||
us framesPerBlock() const {
|
||||
mutexlock lock(mutex);
|
||||
return availableFramesPerBlock.at(framesPerBlockIndex);
|
||||
}
|
||||
bool duplexMode() const {
|
||||
return (neninchannels(false) > 0 && nenoutchannels() > 0);
|
||||
}
|
||||
};
|
||||
|
||||
#endif // LASP_CPPDAQ_H
|
@ -1,56 +0,0 @@
|
||||
// threadsafe_queue.h
|
||||
//
|
||||
// Author: J.A. de Jong
|
||||
//
|
||||
// Description:
|
||||
// Implementation of a thread-safe queue, based on STL queue
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
#pragma once
|
||||
#ifndef THREADSAFE_QUEUE_H
|
||||
#define THREADSAFE_QUEUE_H
|
||||
#include <queue>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
|
||||
// A threadsafe-queue.
|
||||
template <class T>
|
||||
class SafeQueue {
|
||||
std::queue<T> _queue;
|
||||
mutable std::mutex _mutex;
|
||||
std::condition_variable _cv;
|
||||
public:
|
||||
SafeQueue(): _queue(), _mutex() , _cv()
|
||||
{}
|
||||
|
||||
~SafeQueue(){}
|
||||
|
||||
void enqueue(T t) {
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
_queue.push(t);
|
||||
_cv.notify_one();
|
||||
}
|
||||
|
||||
T dequeue() {
|
||||
std::unique_lock<std::mutex> lock(_mutex);
|
||||
while(_queue.empty())
|
||||
{
|
||||
// release lock as long as the wait and reaquire it afterwards.
|
||||
_cv.wait(lock);
|
||||
}
|
||||
T val = _queue.front();
|
||||
_queue.pop();
|
||||
return val;
|
||||
}
|
||||
bool empty() const {
|
||||
std::unique_lock<std::mutex> lock(_mutex);
|
||||
return _queue.size()==0;
|
||||
}
|
||||
size_t size() const {
|
||||
std::unique_lock<std::mutex> lock(_mutex);
|
||||
return _queue.size();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
#endif // THREADSAFE_QUEUE_H
|
||||
//////////////////////////////////////////////////////////////////////
|
@ -1,391 +0,0 @@
|
||||
#include "lasp_cpprtaudio.h"
|
||||
#include <RtAudio.h>
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <thread>
|
||||
|
||||
#if MS_WIN64
|
||||
typedef uint8_t u_int8_t;
|
||||
#endif
|
||||
|
||||
using std::atomic;
|
||||
|
||||
void fillRtAudioDeviceInfo(vector<DeviceInfo> &devinfolist) {
|
||||
|
||||
vector<RtAudio::Api> apis;
|
||||
RtAudio::getCompiledApi(apis);
|
||||
|
||||
for(auto api: apis) {
|
||||
RtAudio rtaudio(api);
|
||||
us count = rtaudio.getDeviceCount();
|
||||
for(us devno = 0; devno< count;devno++) {
|
||||
|
||||
RtAudio::DeviceInfo devinfo = rtaudio.getDeviceInfo(devno);
|
||||
if(!devinfo.probed) {
|
||||
// Device capabilities not successfully probed. Continue to next
|
||||
continue;
|
||||
}
|
||||
DeviceInfo d;
|
||||
switch(api){
|
||||
case RtAudio::LINUX_ALSA:
|
||||
d.api = rtaudioAlsaApi;
|
||||
break;
|
||||
case RtAudio::LINUX_PULSE:
|
||||
d.api = rtaudioPulseaudioApi;
|
||||
break;
|
||||
case RtAudio::WINDOWS_WASAPI:
|
||||
d.api = rtaudioWasapiApi;
|
||||
break;
|
||||
case RtAudio::WINDOWS_DS:
|
||||
d.api = rtaudioDsApi;
|
||||
break;
|
||||
case RtAudio::WINDOWS_ASIO:
|
||||
d.api = rtaudioAsioApi;
|
||||
break;
|
||||
default:
|
||||
cerr << "Not implemented RtAudio API, skipping." << endl;
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
|
||||
d.device_name = devinfo.name;
|
||||
d.api_specific_devindex = devno;
|
||||
|
||||
for(us j=0; j<devinfo.sampleRates.size();j++){
|
||||
us rate = devinfo.sampleRates[j];
|
||||
d.availableSampleRates.push_back((double) rate);
|
||||
if(devinfo.preferredSampleRate == rate) {
|
||||
d.prefSampleRateIndex = j;
|
||||
}
|
||||
}
|
||||
|
||||
d.noutchannels = devinfo.outputChannels;
|
||||
d.ninchannels = devinfo.inputChannels;
|
||||
|
||||
d.availableInputRanges = {1.0};
|
||||
|
||||
RtAudioFormat formats = devinfo.nativeFormats;
|
||||
if(formats & RTAUDIO_SINT8) {
|
||||
d.availableDataTypes.push_back(dtype_int8);
|
||||
}
|
||||
if(formats & RTAUDIO_SINT16) {
|
||||
d.availableDataTypes.push_back(dtype_int16);
|
||||
}
|
||||
if(formats & RTAUDIO_SINT32) {
|
||||
d.availableDataTypes.push_back(dtype_int24);
|
||||
}
|
||||
if(formats & RTAUDIO_SINT32) {
|
||||
d.availableDataTypes.push_back(dtype_fl32);
|
||||
}
|
||||
if(formats & RTAUDIO_FLOAT64) {
|
||||
d.availableDataTypes.push_back(dtype_fl64);
|
||||
}
|
||||
if(d.availableDataTypes.size() == 0) {
|
||||
std::cerr << "RtAudio: No data types found in device!" << endl;
|
||||
}
|
||||
|
||||
d.prefDataTypeIndex = d.availableDataTypes.size() - 1;
|
||||
|
||||
d.availableFramesPerBlock.push_back(512);
|
||||
d.availableFramesPerBlock.push_back(1024);
|
||||
d.availableFramesPerBlock.push_back(2048);
|
||||
d.availableFramesPerBlock.push_back(4096);
|
||||
d.availableFramesPerBlock.push_back(8192);
|
||||
d.prefFramesPerBlockIndex = 1;
|
||||
|
||||
devinfolist.push_back(d);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int mycallback(void *outputBuffer, void *inputBuffer,
|
||||
unsigned int nFrames,
|
||||
double streamTime,
|
||||
RtAudioStreamStatus status,
|
||||
void *userData);
|
||||
|
||||
void myerrorcallback(RtAudioError::Type,const string& errorText);
|
||||
|
||||
class AudioDaq: public Daq {
|
||||
|
||||
SafeQueue<void*> *inqueue = NULL;
|
||||
SafeQueue<void*> *outqueue = NULL;
|
||||
SafeQueue<void*> *outDelayqueue = NULL;
|
||||
|
||||
RtAudio* rtaudio = NULL;
|
||||
RtAudio::StreamParameters* instreamparams = nullptr;
|
||||
RtAudio::StreamParameters* outstreamparams = nullptr;
|
||||
|
||||
us nFramesPerBlock;
|
||||
|
||||
public:
|
||||
AudioDaq(const DeviceInfo& devinfo,
|
||||
const DaqConfiguration& config):
|
||||
Daq(devinfo, config) {
|
||||
|
||||
nFramesPerBlock = this->framesPerBlock();
|
||||
|
||||
if(neninchannels(false) > 0) {
|
||||
instreamparams = new RtAudio::StreamParameters();
|
||||
instreamparams->nChannels = getHighestInChannel() + 1;
|
||||
if(instreamparams->nChannels < 1) {
|
||||
throw runtime_error("Invalid input number of channels");
|
||||
}
|
||||
instreamparams->firstChannel = 0;
|
||||
instreamparams->deviceId = devinfo.api_specific_devindex;
|
||||
}
|
||||
|
||||
if(nenoutchannels() > 0) {
|
||||
outstreamparams = new RtAudio::StreamParameters();
|
||||
outstreamparams->nChannels = getHighestOutChannel() + 1;
|
||||
if(outstreamparams->nChannels < 1) {
|
||||
throw runtime_error("Invalid output number of channels");
|
||||
}
|
||||
outstreamparams->firstChannel = 0;
|
||||
outstreamparams->deviceId = devinfo.api_specific_devindex;
|
||||
}
|
||||
|
||||
RtAudio::StreamOptions streamoptions;
|
||||
streamoptions.flags = RTAUDIO_NONINTERLEAVED | RTAUDIO_HOG_DEVICE;
|
||||
|
||||
streamoptions.numberOfBuffers = 2;
|
||||
streamoptions.streamName = "RtAudio stream";
|
||||
streamoptions.priority = 0;
|
||||
|
||||
RtAudioFormat format;
|
||||
DataType dtype = dataType();
|
||||
if(dtype == dtype_fl32) {
|
||||
format = RTAUDIO_FLOAT32;
|
||||
} else if(dtype == dtype_fl64) {
|
||||
format = RTAUDIO_FLOAT64;
|
||||
} else if(dtype == dtype_int8) {
|
||||
format = RTAUDIO_SINT8;
|
||||
} else if(dtype == dtype_int16) {
|
||||
format = RTAUDIO_SINT16;
|
||||
} else if(dtype == dtype_int32) {
|
||||
format = RTAUDIO_SINT32;
|
||||
} else {
|
||||
throw runtime_error("Invalid data type");
|
||||
}
|
||||
|
||||
try {
|
||||
rtaudio = new RtAudio((RtAudio::Api) devinfo.api.api_specific_subcode);
|
||||
if(!rtaudio) {
|
||||
throw runtime_error("RtAudio allocation failed");
|
||||
}
|
||||
rtaudio->openStream(
|
||||
outstreamparams,
|
||||
instreamparams,
|
||||
format,
|
||||
(us) samplerate(),
|
||||
(unsigned*) &nFramesPerBlock,
|
||||
&mycallback,
|
||||
(void*) this,
|
||||
&streamoptions,
|
||||
&myerrorcallback
|
||||
);
|
||||
} catch(RtAudioError& e) {
|
||||
if(rtaudio) delete rtaudio;
|
||||
if(instreamparams) delete instreamparams;
|
||||
if(outstreamparams) delete outstreamparams;
|
||||
throw;
|
||||
}
|
||||
if(monitorOutput) {
|
||||
outDelayqueue = new SafeQueue<void*>();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
friend int mycallback(void *outputBuffer, void *inputBuffer,
|
||||
unsigned int nFrames,
|
||||
double streamTime,
|
||||
RtAudioStreamStatus status,
|
||||
void *userData);
|
||||
|
||||
|
||||
void start(SafeQueue<void*> *inqueue, SafeQueue<void*> *outqueue) {
|
||||
this->inqueue = inqueue;
|
||||
this->outqueue = outqueue;
|
||||
if(monitorOutput) {
|
||||
this->outDelayqueue = new SafeQueue<void*>();
|
||||
|
||||
}
|
||||
|
||||
if(isRunning()){
|
||||
throw runtime_error("Stream already running");
|
||||
}
|
||||
|
||||
if(neninchannels(false) > 0 && !inqueue) {
|
||||
throw runtime_error("inqueue argument not given");
|
||||
}
|
||||
if(nenoutchannels() > 0 && !outqueue) {
|
||||
throw runtime_error("outqueue argument not given");
|
||||
}
|
||||
assert(rtaudio);
|
||||
rtaudio->startStream();
|
||||
|
||||
}
|
||||
|
||||
void stop() {
|
||||
|
||||
if(!isRunning()) {
|
||||
cerr << "Stream is already stopped" << endl;
|
||||
}
|
||||
else {
|
||||
assert(rtaudio);
|
||||
rtaudio->stopStream();
|
||||
}
|
||||
if(inqueue) {
|
||||
inqueue = nullptr;
|
||||
}
|
||||
if(outqueue) {
|
||||
outqueue = nullptr;
|
||||
}
|
||||
if(outDelayqueue) {
|
||||
delete outDelayqueue;
|
||||
outDelayqueue = nullptr;
|
||||
}
|
||||
}
|
||||
bool isRunning() const {return (rtaudio && rtaudio->isStreamRunning());}
|
||||
|
||||
~AudioDaq() {
|
||||
assert(rtaudio);
|
||||
if(isRunning()) {
|
||||
stop();
|
||||
}
|
||||
if(rtaudio->isStreamOpen()) {
|
||||
rtaudio->closeStream();
|
||||
}
|
||||
|
||||
if(rtaudio) delete rtaudio;
|
||||
if(outDelayqueue) delete outDelayqueue;
|
||||
if(instreamparams) delete instreamparams;
|
||||
if(outstreamparams) delete outstreamparams;
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Daq* createRtAudioDevice(const DeviceInfo& devinfo,
|
||||
const DaqConfiguration& config) {
|
||||
|
||||
AudioDaq *daq = NULL;
|
||||
|
||||
try {
|
||||
daq = new AudioDaq(devinfo, config);
|
||||
|
||||
} catch (runtime_error &e) {
|
||||
if (daq)
|
||||
delete daq;
|
||||
throw;
|
||||
}
|
||||
return daq;
|
||||
}
|
||||
|
||||
|
||||
int mycallback(
|
||||
void *outputBuffervoid,
|
||||
void *inputBuffervoid,
|
||||
unsigned int nFrames,
|
||||
double streamTime,
|
||||
RtAudioStreamStatus status,
|
||||
void *userData) {
|
||||
|
||||
u_int8_t* inputBuffer = (u_int8_t*) inputBuffervoid;
|
||||
u_int8_t* outputBuffer = (u_int8_t*) outputBuffervoid;
|
||||
|
||||
AudioDaq* daq = (AudioDaq*) userData;
|
||||
DataType dtype = daq->dataType();
|
||||
us neninchannels_inc_mon = daq->neninchannels();
|
||||
us nenoutchannels = daq->nenoutchannels();
|
||||
|
||||
bool monitorOutput = daq->monitorOutput;
|
||||
us bytesperchan = dtype.sw*nFrames;
|
||||
us monitorOffset = ((us) monitorOutput)*bytesperchan;
|
||||
|
||||
SafeQueue<void*> *inqueue = daq->inqueue;
|
||||
SafeQueue<void*> *outqueue = daq->outqueue;
|
||||
SafeQueue<void*> *outDelayqueue = daq->outDelayqueue;
|
||||
|
||||
const boolvec& eninchannels = daq->eninchannels;
|
||||
const boolvec& enoutchannels = daq->enoutchannels;
|
||||
|
||||
if(inputBuffer || monitorOutput) {
|
||||
|
||||
u_int8_t *inbuffercpy = (u_int8_t*) malloc(bytesperchan*neninchannels_inc_mon);
|
||||
if(inputBuffer) {
|
||||
us j=0; // OUR buffer channel counter
|
||||
us i=0; // RtAudio channel counter
|
||||
for(int ch=daq->getLowestInChannel();ch<=daq->getHighestInChannel();ch++) {
|
||||
if(eninchannels[ch]) {
|
||||
memcpy(
|
||||
&(inbuffercpy[monitorOffset+j*bytesperchan]),
|
||||
&(inputBuffer[i*bytesperchan]),
|
||||
bytesperchan);
|
||||
j++;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
if(monitorOutput) {
|
||||
assert(outDelayqueue);
|
||||
|
||||
if(!daq->outDelayqueue->empty()) {
|
||||
void* dat = daq->outDelayqueue->dequeue();
|
||||
memcpy((void*) inbuffercpy, dat, bytesperchan);
|
||||
free(dat);
|
||||
} else {
|
||||
cerr << "Warning: output delay queue appears empty!" << endl;
|
||||
memset(inbuffercpy, 0, bytesperchan);
|
||||
}
|
||||
}
|
||||
assert(inqueue);
|
||||
inqueue->enqueue(inbuffercpy);
|
||||
|
||||
}
|
||||
|
||||
if(outputBuffer) {
|
||||
assert(outqueue);
|
||||
if(!outqueue->empty()) {
|
||||
u_int8_t* outbuffercpy = (u_int8_t*) outqueue->dequeue();
|
||||
us j=0; // OUR buffer channel counter
|
||||
us i=0; // RtAudio channel counter
|
||||
for(us ch=0;ch<=daq->getHighestOutChannel();ch++) {
|
||||
/* cerr << "Copying from queue... " << endl; */
|
||||
if(enoutchannels[ch]) {
|
||||
memcpy(
|
||||
&(outputBuffer[i*bytesperchan]),
|
||||
&(outbuffercpy[j*bytesperchan]),
|
||||
bytesperchan);
|
||||
j++;
|
||||
}
|
||||
else {
|
||||
/* cerr << "unused output channel in list" << endl; */
|
||||
memset(
|
||||
&(outputBuffer[i*bytesperchan]),0,bytesperchan);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if(!monitorOutput) {
|
||||
free(outbuffercpy);
|
||||
} else {
|
||||
assert(outDelayqueue);
|
||||
outDelayqueue->enqueue((void*) outbuffercpy);
|
||||
}
|
||||
}
|
||||
else {
|
||||
cerr << "RtAudio backend: stream output buffer underflow!" << endl;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
void myerrorcallback(RtAudioError::Type,const string& errorText) {
|
||||
cerr << errorText << endl;
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
#ifndef RTAUDIO_H
|
||||
#define RTAUDIO_H
|
||||
#include "lasp_cppdaq.h"
|
||||
|
||||
Daq* createRtAudioDevice(const DeviceInfo& devinfo,
|
||||
const DaqConfiguration& config);
|
||||
|
||||
void fillRtAudioDeviceInfo(vector<DeviceInfo> &devinfolist);
|
||||
|
||||
#endif // RTAUDIO_H
|
||||
|
||||
|
||||
|
@ -1,34 +0,0 @@
|
||||
#pragma once
|
||||
#ifndef LASP_CPPTHREAD_H
|
||||
#define LASP_CPPTHREAD_H
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <assert.h>
|
||||
// This is a small wrapper around the std library thread.
|
||||
|
||||
template <class T, typename F>
|
||||
class CPPThread {
|
||||
std::thread _thread;
|
||||
public:
|
||||
CPPThread(F threadfcn, T data) :
|
||||
_thread(threadfcn, data) { }
|
||||
|
||||
void join() {
|
||||
assert(_thread.joinable());
|
||||
_thread.join();
|
||||
}
|
||||
/* ~CPPThread() { */
|
||||
|
||||
/* } */
|
||||
|
||||
};
|
||||
|
||||
void CPPsleep_ms(unsigned int ms) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(ms));
|
||||
}
|
||||
void CPPsleep_us(unsigned int us) {
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(us));
|
||||
}
|
||||
|
||||
|
||||
#endif // LASP_CPPTHREAD_H
|
@ -1,636 +0,0 @@
|
||||
#include "lasp_cppuldaq.h"
|
||||
#include "lasp_config.h"
|
||||
#include "lasp_tracer.h"
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include <uldaq.h>
|
||||
#include <vector>
|
||||
|
||||
using std::atomic;
|
||||
|
||||
/* using std::this_thread; */
|
||||
const us MAX_DEV_COUNT_PER_API = 100;
|
||||
const us UL_ERR_MSG_LEN = 512;
|
||||
|
||||
inline void showErr(UlError err) {
|
||||
if (err != ERR_NO_ERROR) {
|
||||
char errmsg[UL_ERR_MSG_LEN];
|
||||
ulGetErrMsg(err, errmsg);
|
||||
std::cerr << "UlError: " << errmsg << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
class DT9837A;
|
||||
void threadfcn(DT9837A *td);
|
||||
|
||||
class DT9837A : public Daq {
|
||||
|
||||
atomic<bool> stopThread;
|
||||
DaqDeviceHandle handle = 0;
|
||||
|
||||
std::thread *thread = NULL;
|
||||
SafeQueue<void *> *inqueue = NULL;
|
||||
SafeQueue<void *> *outqueue = NULL;
|
||||
|
||||
double *inbuffer = NULL;
|
||||
double *outbuffer = NULL;
|
||||
|
||||
us nFramesPerBlock;
|
||||
|
||||
public:
|
||||
DT9837A(const DeviceInfo &devinfo, const DaqConfiguration &config)
|
||||
: Daq(devinfo, config) {
|
||||
|
||||
// Some sanity checks
|
||||
if (eninchannels.size() != 4) {
|
||||
throw runtime_error("Invalid length of enabled inChannels vector");
|
||||
}
|
||||
|
||||
if (enoutchannels.size() != 1) {
|
||||
throw runtime_error("Invalid length of enabled outChannels vector");
|
||||
}
|
||||
|
||||
stopThread = false;
|
||||
|
||||
nFramesPerBlock = availableFramesPerBlock[framesPerBlockIndex];
|
||||
|
||||
if (nFramesPerBlock < 24 || nFramesPerBlock > 8192) {
|
||||
throw runtime_error("Unsensible number of samples per block chosen");
|
||||
}
|
||||
|
||||
if (samplerate() < 10000 || samplerate() > 51000) {
|
||||
throw runtime_error("Invalid sample rate");
|
||||
}
|
||||
|
||||
DaqDeviceDescriptor devdescriptors[MAX_DEV_COUNT_PER_API];
|
||||
DaqDeviceDescriptor descriptor;
|
||||
DaqDeviceInterface interfaceType = ANY_IFC;
|
||||
|
||||
UlError err;
|
||||
|
||||
us numdevs = MAX_DEV_COUNT_PER_API;
|
||||
err = ulGetDaqDeviceInventory(interfaceType, devdescriptors, (unsigned*) &numdevs);
|
||||
if (err != ERR_NO_ERROR) {
|
||||
throw runtime_error("Device inventarization failed");
|
||||
}
|
||||
|
||||
if ((us) api_specific_devindex >= numdevs) {
|
||||
throw runtime_error("Device number {deviceno} too high {err}. This could "
|
||||
"happen when the device is currently not connected");
|
||||
}
|
||||
|
||||
descriptor = devdescriptors[api_specific_devindex];
|
||||
|
||||
// get a handle to the DAQ device associated with the first descriptor
|
||||
handle = ulCreateDaqDevice(descriptor);
|
||||
|
||||
if (handle == 0) {
|
||||
throw runtime_error("Unable to create a handle to the specified DAQ "
|
||||
"device. Is the device currently in use? Please make sure to set "
|
||||
"the DAQ configuration in duplex mode if simultaneous input and "
|
||||
"output is required.");
|
||||
}
|
||||
|
||||
err = ulConnectDaqDevice(handle);
|
||||
if (err != ERR_NO_ERROR) {
|
||||
showErr(err);
|
||||
ulReleaseDaqDevice(handle);
|
||||
handle = 0;
|
||||
throw runtime_error("Unable to connect to device: {err}");
|
||||
}
|
||||
|
||||
for (us ch = 0; ch < 4; ch++) {
|
||||
|
||||
err = ulAISetConfigDbl(handle, AI_CFG_CHAN_SENSOR_SENSITIVITY, ch, 1.0);
|
||||
showErr(err);
|
||||
if (err != ERR_NO_ERROR) {
|
||||
throw runtime_error("Fatal: could normalize channel sensitivity");
|
||||
}
|
||||
|
||||
CouplingMode cm = inputACCouplingMode[ch] ? CM_AC : CM_DC;
|
||||
err = ulAISetConfig(handle, AI_CFG_CHAN_COUPLING_MODE, ch, cm);
|
||||
if (err != ERR_NO_ERROR) {
|
||||
showErr(err);
|
||||
throw runtime_error("Fatal: could not set AC/DC coupling mode");
|
||||
}
|
||||
|
||||
IepeMode iepe = inputIEPEEnabled[ch] ? IEPE_ENABLED : IEPE_DISABLED;
|
||||
err = ulAISetConfig(handle, AI_CFG_CHAN_IEPE_MODE, ch, iepe);
|
||||
if (err != ERR_NO_ERROR) {
|
||||
showErr(err);
|
||||
throw runtime_error("Fatal: could not set IEPE mode");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DT9837A(const DT9837A &) = delete;
|
||||
|
||||
~DT9837A() {
|
||||
UlError err;
|
||||
if (isRunning()) {
|
||||
stop();
|
||||
}
|
||||
|
||||
if (handle) {
|
||||
err = ulDisconnectDaqDevice(handle);
|
||||
showErr(err);
|
||||
err = ulReleaseDaqDevice(handle);
|
||||
showErr(err);
|
||||
}
|
||||
}
|
||||
|
||||
bool isRunning() const { return bool(thread); }
|
||||
|
||||
void start(SafeQueue<void *> *inqueue, SafeQueue<void *> *outqueue) {
|
||||
if (isRunning()) {
|
||||
throw runtime_error("Thread is already running");
|
||||
}
|
||||
|
||||
bool hasinput = neninchannels() > 0;
|
||||
bool hasoutput = nenoutchannels() > 0;
|
||||
|
||||
if (hasinput && !inqueue) {
|
||||
throw runtime_error("Inqueue not given, while input is enabled");
|
||||
}
|
||||
|
||||
if (hasoutput && !outqueue) {
|
||||
throw runtime_error("outqueue not given, while output is enabled");
|
||||
}
|
||||
|
||||
if (hasinput) {
|
||||
assert(!inbuffer);
|
||||
inbuffer =
|
||||
new double[neninchannels() * nFramesPerBlock * 2]; // Watch the 2!
|
||||
}
|
||||
if (hasoutput) {
|
||||
assert(!outbuffer);
|
||||
outbuffer =
|
||||
new double[nenoutchannels() * nFramesPerBlock * 2]; // Watch the 2!
|
||||
}
|
||||
this->inqueue = inqueue;
|
||||
this->outqueue = outqueue;
|
||||
|
||||
stopThread = false;
|
||||
thread = new std::thread(threadfcn, this);
|
||||
}
|
||||
|
||||
void stop() {
|
||||
if (!isRunning()) {
|
||||
throw runtime_error("No data acquisition running");
|
||||
}
|
||||
assert(thread);
|
||||
|
||||
stopThread = true;
|
||||
thread->join();
|
||||
delete thread;
|
||||
thread = NULL;
|
||||
|
||||
outqueue = NULL;
|
||||
inqueue = NULL;
|
||||
if (inbuffer) {
|
||||
delete inbuffer;
|
||||
inbuffer = nullptr;
|
||||
}
|
||||
if (outbuffer) {
|
||||
delete outbuffer;
|
||||
outbuffer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
friend void threadfcn(DT9837A *);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Create an empty buffer and fill it with zeros.
|
||||
*
|
||||
* @param size The number of elements in the array
|
||||
*
|
||||
* @return Pointer to the array
|
||||
*/
|
||||
static double* createZeroBuffer(size_t size) {
|
||||
|
||||
double* buf = static_cast<double *>(
|
||||
malloc(sizeof(double) * size));
|
||||
|
||||
for (us sample = 0; sample < size; sample++) {
|
||||
buf[sample] = 0;
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
/**
|
||||
* @brief Copy samples from one linear array to the next.
|
||||
*
|
||||
* @param[in] from Buffer to copy from
|
||||
* @param[out] to Buffer to copy to
|
||||
* @param startFrom The position to start in the from-buffer
|
||||
* @param startTo The position to start in the to-buffer
|
||||
* @param N The number of samples to copy.
|
||||
*/
|
||||
static inline void copySamples(double* from,double* to,
|
||||
const us startFrom,const us startTo,const us N) {
|
||||
|
||||
for (us sample = 0; sample < N; sample++) {
|
||||
to[startTo + sample] = from[startFrom + sample];
|
||||
}
|
||||
}
|
||||
|
||||
void threadfcn(DT9837A *td) {
|
||||
|
||||
std::cerr << "Starting DAQ Thread fcn" << endl;
|
||||
|
||||
const us nenoutchannels = td->nenoutchannels();
|
||||
us neninchannels = td->neninchannels();
|
||||
const us nFramesPerBlock = td->nFramesPerBlock;
|
||||
|
||||
const bool hasinput = neninchannels > 0;
|
||||
const bool hasoutput = nenoutchannels > 0;
|
||||
|
||||
bool monitorOutput = td->monitorOutput;
|
||||
|
||||
double *inbuffer = td->inbuffer;
|
||||
double *outbuffer = td->outbuffer;
|
||||
|
||||
SafeQueue<void *> *inqueue = td->inqueue;
|
||||
SafeQueue<void *> *outqueue = td->outqueue;
|
||||
|
||||
ScanStatus inscanstat;
|
||||
ScanStatus outscanstat;
|
||||
TransferStatus inxstat, outxstat;
|
||||
|
||||
double samplerate = td->samplerate();
|
||||
|
||||
const double sleeptime = ((double)nFramesPerBlock) / (4 * samplerate);
|
||||
const us sleeptime_us = (us)(sleeptime * 1e6);
|
||||
/* cerr << "Sleep time in loop: " << sleeptime_us << "us." << endl; */
|
||||
if (sleeptime_us < 10) {
|
||||
cerr << "ERROR: Too small buffer size (nFramesPerBlock) chosen!" << endl;
|
||||
return;
|
||||
}
|
||||
|
||||
const us buffer_mid_idx_in = neninchannels * nFramesPerBlock;
|
||||
const us buffer_mid_idx_out = nenoutchannels * nFramesPerBlock;
|
||||
|
||||
DaqDeviceHandle handle = td->handle;
|
||||
assert(handle);
|
||||
|
||||
DaqInScanFlag inscanflags = DAQINSCAN_FF_DEFAULT;
|
||||
AOutScanFlag outscanflags = AOUTSCAN_FF_DEFAULT;
|
||||
ScanOption scanoptions = SO_CONTINUOUS;
|
||||
UlError err = ERR_NO_ERROR;
|
||||
|
||||
DaqInChanDescriptor *indesc = NULL;
|
||||
|
||||
bool topinenqueued = true;
|
||||
// Start with true here, to not copy here the first time
|
||||
bool botinenqueued = true;
|
||||
|
||||
bool topoutenqueued = true;
|
||||
bool botoutenqueued = true;
|
||||
|
||||
size_t inTotalCount = 0;
|
||||
size_t outTotalCount = 0;
|
||||
|
||||
// initialize output, if any
|
||||
if (hasoutput) {
|
||||
assert(nenoutchannels == 1);
|
||||
assert(outqueue);
|
||||
|
||||
// Initialize the buffer with zeros, before pushing any data.
|
||||
for (us sample = 0; sample < 2 * nFramesPerBlock; sample++) {
|
||||
outbuffer[sample] = 0;
|
||||
}
|
||||
|
||||
cerr << "Starting output DAC" << endl;
|
||||
err = ulAOutScan(handle, 0, 0, BIP10VOLTS,
|
||||
/* BIP60VOLTS, */
|
||||
2 * td->nFramesPerBlock, // Watch the 2 here!
|
||||
&samplerate, scanoptions, outscanflags, outbuffer);
|
||||
|
||||
if (err != ERR_NO_ERROR) {
|
||||
showErr(err);
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize input, if any
|
||||
if (hasinput) {
|
||||
indesc = new DaqInChanDescriptor[neninchannels];
|
||||
us j = 0;
|
||||
for (us chin = 0; chin < 4; chin++) {
|
||||
if (td->eninchannels[chin] == true) {
|
||||
indesc[j].type = DAQI_ANALOG_SE;
|
||||
indesc[j].channel = chin;
|
||||
|
||||
double rangeval = td->inputRangeForChannel(chin);
|
||||
Range rangenum;
|
||||
if (abs(rangeval - 1.0) < 1e-8) {
|
||||
rangenum = BIP1VOLTS;
|
||||
} else if (abs(rangeval - 10.0) < 1e-8) {
|
||||
rangenum = BIP10VOLTS;
|
||||
} else {
|
||||
std::cerr << "Fatal: input range value is invalid" << endl;
|
||||
goto exit;
|
||||
}
|
||||
indesc[j].range = rangenum;
|
||||
j++;
|
||||
}
|
||||
}
|
||||
// Overwrite last channel
|
||||
if (monitorOutput) {
|
||||
indesc[j].type = DAQI_DAC;
|
||||
indesc[j].channel = 0;
|
||||
indesc[j].range = BIP10VOLTS;
|
||||
j++;
|
||||
}
|
||||
assert(j == neninchannels);
|
||||
|
||||
cerr << "Starting input ADC" << endl;
|
||||
err = ulDaqInScan(handle, indesc, neninchannels,
|
||||
2 * td->nFramesPerBlock, // Watch the 2 here!
|
||||
&samplerate, scanoptions, inscanflags, inbuffer);
|
||||
if (err != ERR_NO_ERROR) {
|
||||
showErr(err);
|
||||
goto exit;
|
||||
}
|
||||
}
|
||||
|
||||
// Runs scan status on output, to catch up with position
|
||||
if (hasoutput) {
|
||||
err = ulAOutScanStatus(handle, &outscanstat, &outxstat);
|
||||
if (err != ERR_NO_ERROR) {
|
||||
showErr(err);
|
||||
goto exit;
|
||||
}
|
||||
outTotalCount = outxstat.currentScanCount;
|
||||
assert(outscanstat == SS_RUNNING);
|
||||
}
|
||||
|
||||
/* std::cerr << "Entering while loop" << endl; */
|
||||
/* std::cerr << "hasinput: " << hasinput << endl; */
|
||||
while (!td->stopThread && err == ERR_NO_ERROR) {
|
||||
/* std::cerr << "While..." << endl; */
|
||||
if (hasoutput) {
|
||||
err = ulAOutScanStatus(handle, &outscanstat, &outxstat);
|
||||
if (err != ERR_NO_ERROR) {
|
||||
showErr(err);
|
||||
goto exit;
|
||||
}
|
||||
assert(outscanstat == SS_RUNNING);
|
||||
|
||||
if (outxstat.currentScanCount > outTotalCount + 2 * nFramesPerBlock) {
|
||||
cerr << "***** WARNING: Missing output sample blocks, DAQ Scan count="
|
||||
<< outxstat.currentScanCount
|
||||
<< " while loop count = " << outTotalCount
|
||||
<< ", probably due to too small buffer size. *****" << endl;
|
||||
}
|
||||
outTotalCount = outxstat.currentScanCount;
|
||||
|
||||
/* std::cerr << "Samples scanned: " << outxstat.currentTotalCount <<
|
||||
* endl;
|
||||
*/
|
||||
if (outxstat.currentIndex < buffer_mid_idx_out) {
|
||||
topoutenqueued = false;
|
||||
if (!botoutenqueued) {
|
||||
/* cerr << "Copying output buffer to bottom" << endl; */
|
||||
double *bufcpy;
|
||||
assert(nenoutchannels > 0);
|
||||
if (!outqueue->empty()) {
|
||||
bufcpy = (double *)outqueue->dequeue();
|
||||
} else {
|
||||
cerr << "******* WARNING: OUTPUTQUEUE UNDERFLOW, FILLING SIGNAL "
|
||||
"QUEUE WITH ZEROS ***********"
|
||||
<< endl;
|
||||
bufcpy = createZeroBuffer(nFramesPerBlock*nenoutchannels);
|
||||
}
|
||||
copySamples(bufcpy, outbuffer, 0, buffer_mid_idx_out, nFramesPerBlock);
|
||||
free(bufcpy);
|
||||
botoutenqueued = true;
|
||||
}
|
||||
} else {
|
||||
botoutenqueued = false;
|
||||
if (!topoutenqueued) {
|
||||
/* cerr << "Copying output buffer to top" << endl; */
|
||||
double *bufcpy;
|
||||
assert(nenoutchannels > 0);
|
||||
if (!outqueue->empty()) {
|
||||
bufcpy = (double *)outqueue->dequeue();
|
||||
} else {
|
||||
cerr << "******* WARNING: OUTPUTQUEUE UNDERFLOW, FILLING SIGNAL "
|
||||
"QUEUE WITH ZEROS ***********"
|
||||
<< endl;
|
||||
bufcpy = createZeroBuffer(nFramesPerBlock*nenoutchannels);
|
||||
}
|
||||
copySamples(bufcpy, outbuffer, 0, 0, nFramesPerBlock);
|
||||
free(bufcpy);
|
||||
topoutenqueued = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasinput) {
|
||||
err = ulDaqInScanStatus(handle, &inscanstat, &inxstat);
|
||||
if (err != ERR_NO_ERROR) {
|
||||
showErr(err);
|
||||
goto exit;
|
||||
}
|
||||
assert(inscanstat == SS_RUNNING);
|
||||
if (inxstat.currentScanCount > inTotalCount + 2 * nFramesPerBlock) {
|
||||
cerr << "***** ERROR: Missing input sample blocks, count="
|
||||
<< inxstat.currentScanCount
|
||||
<< ", probably due to too small buffer size. Exiting thread. "
|
||||
"*****"
|
||||
<< endl;
|
||||
break;
|
||||
}
|
||||
inTotalCount = inxstat.currentScanCount;
|
||||
|
||||
if ((us) inxstat.currentIndex < buffer_mid_idx_in) {
|
||||
topinenqueued = false;
|
||||
if (!botinenqueued) {
|
||||
/* cerr << "Copying in buffer bot" << endl; */
|
||||
double *bufcpy = static_cast<double *>(
|
||||
malloc(sizeof(double) * nFramesPerBlock * neninchannels));
|
||||
us monitoroffset = monitorOutput ? 1 : 0;
|
||||
assert(neninchannels > 0);
|
||||
for (us channel = 0; channel < (neninchannels - monitoroffset);
|
||||
channel++) {
|
||||
for (us sample = 0; sample < nFramesPerBlock; sample++) {
|
||||
bufcpy[(monitoroffset + channel) * nFramesPerBlock + sample] =
|
||||
inbuffer[buffer_mid_idx_in + sample * neninchannels +
|
||||
channel];
|
||||
}
|
||||
}
|
||||
if (monitorOutput) {
|
||||
// Monitor output goes to first channel, that is
|
||||
// our convention
|
||||
us channel = neninchannels - 1;
|
||||
for (us sample = 0; sample < nFramesPerBlock; sample++) {
|
||||
bufcpy[sample] = inbuffer[buffer_mid_idx_in +
|
||||
sample * neninchannels + channel];
|
||||
}
|
||||
}
|
||||
inqueue->enqueue((void *)bufcpy);
|
||||
botinenqueued = true;
|
||||
}
|
||||
} else {
|
||||
botinenqueued = false;
|
||||
if (!topinenqueued) {
|
||||
double *bufcpy = static_cast<double *>(
|
||||
malloc(sizeof(double) * nFramesPerBlock * neninchannels));
|
||||
us monitoroffset = monitorOutput ? 1 : 0;
|
||||
assert(neninchannels > 0);
|
||||
for (us channel = 0; channel < (neninchannels - monitoroffset);
|
||||
channel++) {
|
||||
for (us sample = 0; sample < nFramesPerBlock; sample++) {
|
||||
bufcpy[(monitoroffset + channel) * nFramesPerBlock + sample] =
|
||||
inbuffer[sample * neninchannels + channel];
|
||||
}
|
||||
}
|
||||
if (monitorOutput) {
|
||||
// Monitor output goes to first channel, that is
|
||||
// our convention
|
||||
us channel = neninchannels - 1;
|
||||
for (us sample = 0; sample < nFramesPerBlock; sample++) {
|
||||
bufcpy[sample] = inbuffer[sample * neninchannels + channel];
|
||||
}
|
||||
}
|
||||
|
||||
/* cerr << "Copying in buffer top" << endl; */
|
||||
inqueue->enqueue((void *)bufcpy);
|
||||
topinenqueued = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(sleeptime_us));
|
||||
|
||||
} // End of while loop
|
||||
/* std::cerr << "Exit of while loop" << endl; */
|
||||
|
||||
exit:
|
||||
|
||||
if (hasoutput) {
|
||||
ulAOutScanStop(handle);
|
||||
if (err != ERR_NO_ERROR) {
|
||||
showErr(err);
|
||||
}
|
||||
}
|
||||
|
||||
if (hasinput) {
|
||||
ulDaqInScanStop(handle);
|
||||
if (err != ERR_NO_ERROR) {
|
||||
showErr(err);
|
||||
}
|
||||
}
|
||||
|
||||
if (indesc)
|
||||
delete indesc;
|
||||
std::cerr << "Exit of DAQ thread fcn" << endl;
|
||||
}
|
||||
|
||||
Daq *createUlDaqDevice(const DeviceInfo &devinfo,
|
||||
const DaqConfiguration &config) {
|
||||
|
||||
DT9837A *daq = NULL;
|
||||
|
||||
try {
|
||||
daq = new DT9837A(devinfo, config);
|
||||
|
||||
} catch (runtime_error &e) {
|
||||
if (daq)
|
||||
delete daq;
|
||||
throw;
|
||||
}
|
||||
return daq;
|
||||
}
|
||||
|
||||
|
||||
void fillUlDaqDeviceInfo(vector<DeviceInfo> &devinfolist) {
|
||||
|
||||
fsTRACE(15);
|
||||
|
||||
UlError err;
|
||||
unsigned int numdevs = MAX_DEV_COUNT_PER_API;
|
||||
|
||||
DaqDeviceDescriptor devdescriptors[MAX_DEV_COUNT_PER_API];
|
||||
DaqDeviceDescriptor descriptor;
|
||||
DaqDeviceInterface interfaceType = ANY_IFC;
|
||||
|
||||
err = ulGetDaqDeviceInventory(interfaceType, devdescriptors,static_cast<unsigned*>(&numdevs));
|
||||
|
||||
if (err != ERR_NO_ERROR) {
|
||||
throw std::runtime_error("UlDaq device inventarization failed");
|
||||
}
|
||||
|
||||
for (unsigned i = 0; i < numdevs; i++) {
|
||||
|
||||
descriptor = devdescriptors[i];
|
||||
|
||||
DeviceInfo devinfo;
|
||||
devinfo.api = uldaqapi;
|
||||
string name, interface;
|
||||
|
||||
if (string(descriptor.productName) == "DT9837A") {
|
||||
if (descriptor.devInterface == USB_IFC) {
|
||||
name = "USB - ";
|
||||
} else if (descriptor.devInterface == BLUETOOTH_IFC) {
|
||||
/* devinfo. */
|
||||
name = "Bluetooth - ";
|
||||
} else if (descriptor.devInterface == ETHERNET_IFC) {
|
||||
/* devinfo. */
|
||||
name = "Ethernet - ";
|
||||
}
|
||||
|
||||
name += string(descriptor.productName) + " ";
|
||||
name += string(descriptor.uniqueId);
|
||||
devinfo.device_name = name;
|
||||
|
||||
devinfo.api_specific_devindex = i;
|
||||
devinfo.availableDataTypes.push_back(dtype_fl64);
|
||||
devinfo.prefDataTypeIndex = 0;
|
||||
|
||||
devinfo.availableSampleRates.push_back(8000);
|
||||
devinfo.availableSampleRates.push_back(10000);
|
||||
devinfo.availableSampleRates.push_back(11025);
|
||||
devinfo.availableSampleRates.push_back(16000);
|
||||
devinfo.availableSampleRates.push_back(20000);
|
||||
devinfo.availableSampleRates.push_back(22050);
|
||||
devinfo.availableSampleRates.push_back(24000);
|
||||
devinfo.availableSampleRates.push_back(32000);
|
||||
devinfo.availableSampleRates.push_back(44056);
|
||||
devinfo.availableSampleRates.push_back(44100);
|
||||
devinfo.availableSampleRates.push_back(47250);
|
||||
devinfo.availableSampleRates.push_back(48000);
|
||||
devinfo.availableSampleRates.push_back(50000);
|
||||
devinfo.availableSampleRates.push_back(50400);
|
||||
devinfo.availableSampleRates.push_back(51000);
|
||||
|
||||
devinfo.prefSampleRateIndex = 11;
|
||||
|
||||
devinfo.availableFramesPerBlock.push_back(512);
|
||||
devinfo.availableFramesPerBlock.push_back(1024);
|
||||
devinfo.availableFramesPerBlock.push_back(2048);
|
||||
devinfo.availableFramesPerBlock.push_back(4096);
|
||||
devinfo.availableFramesPerBlock.push_back(8192);
|
||||
devinfo.prefFramesPerBlockIndex = 2;
|
||||
|
||||
devinfo.availableInputRanges = {1.0, 10.0};
|
||||
devinfo.prefInputRangeIndex = 0;
|
||||
|
||||
devinfo.ninchannels = 4;
|
||||
devinfo.noutchannels = 1;
|
||||
|
||||
devinfo.hasInputIEPE = true;
|
||||
devinfo.hasInputACCouplingSwitch = true;
|
||||
devinfo.hasInputTrigger = true;
|
||||
|
||||
// Finally, this devinfo is pushed back in list
|
||||
devinfolist.push_back(devinfo);
|
||||
}
|
||||
}
|
||||
|
||||
feTRACE(15);
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
#ifndef ULDAQ_H
|
||||
#define ULDAQ_H
|
||||
#include "lasp_cppqueue.h"
|
||||
#include "lasp_cppdaq.h"
|
||||
|
||||
Daq* createUlDaqDevice(const DeviceInfo& devinfo,
|
||||
const DaqConfiguration& config);
|
||||
|
||||
void fillUlDaqDeviceInfo(vector<DeviceInfo> &devinfolist);
|
||||
|
||||
#endif // ULDAQ_H
|
||||
|
||||
|
102
lasp/device/lasp_daq.cpp
Normal file
102
lasp/device/lasp_daq.cpp
Normal file
@ -0,0 +1,102 @@
|
||||
#include "debugtrace.hpp"
|
||||
|
||||
DEBUGTRACE_VARIABLES;
|
||||
|
||||
#include "lasp_daq.h"
|
||||
#if LASP_HAS_ULDAQ == 1
|
||||
#include "lasp_uldaq.h"
|
||||
#endif
|
||||
#if LASP_HAS_RTAUDIO == 1
|
||||
#include "lasp_rtaudiodaq.h"
|
||||
#endif
|
||||
using std::runtime_error;
|
||||
|
||||
DaqData::DaqData(const us nchannels, const us nframes,
|
||||
const DataTypeDescriptor::DataType dtype)
|
||||
: nchannels(nchannels), nframes(nframes), dtype(dtype),
|
||||
dtype_descr(dtype_map.at(dtype)) {
|
||||
static_assert(sizeof(char) == 1, "Invalid char size");
|
||||
|
||||
const DataTypeDescriptor &desc = dtype_map.at(dtype);
|
||||
_data.resize(nframes * nchannels * desc.sw);
|
||||
}
|
||||
|
||||
|
||||
std::unique_ptr<Daq> Daq::createDaq(const DeviceInfo &devinfo,
|
||||
const DaqConfiguration &config) {
|
||||
DEBUGTRACE_ENTER;
|
||||
|
||||
if (!config.match(devinfo)) {
|
||||
throw std::runtime_error("DaqConfiguration does not match device info");
|
||||
}
|
||||
|
||||
// Some basic sanity checks
|
||||
if ((devinfo.ninchannels != config.eninchannels.size())) {
|
||||
/* cerr << "devinfo.ninchannels: " << devinfo.ninchannels << endl; */
|
||||
/* cerr << "config.eninchannels.size(): " << config.eninchannels.size() <<
|
||||
* endl; */
|
||||
throw runtime_error("Invalid length of enabled input channels specified");
|
||||
}
|
||||
if ((devinfo.noutchannels != config.enoutchannels.size())) {
|
||||
throw runtime_error("outvalid length of enabled output channels specified");
|
||||
}
|
||||
|
||||
int apicode = devinfo.api.apicode;
|
||||
if (devinfo.api == DaqApi()) {
|
||||
throw std::runtime_error(string("Unable to match API: ") +
|
||||
devinfo.api.apiname);
|
||||
}
|
||||
#if LASP_HAS_ULDAQ == 1
|
||||
else if (devinfo.api == uldaqapi) {
|
||||
return createUlDaqDevice(devinfo, config);
|
||||
}
|
||||
#endif
|
||||
#if LASP_HAS_RTAUDIO == 1
|
||||
else if (apicode >= 1 && apicode <= 5) {
|
||||
return createRtAudioDevice(devinfo, config);
|
||||
}
|
||||
#endif
|
||||
else {
|
||||
throw std::runtime_error(string("Unable to match API: ") +
|
||||
devinfo.api.apiname);
|
||||
}
|
||||
}
|
||||
|
||||
Daq::Daq(const DeviceInfo &devinfo, const DaqConfiguration &config)
|
||||
: DaqConfiguration(config), DeviceInfo(devinfo) {
|
||||
DEBUGTRACE_ENTER;
|
||||
|
||||
if (monitorOutput && !(nenoutchannels() > 0)) {
|
||||
throw runtime_error(
|
||||
"Output monitoring only possible when at least one output channel is "
|
||||
"enabled. Please make sure to enable at least one output channel, or "
|
||||
"disable monitoring output.");
|
||||
}
|
||||
}
|
||||
|
||||
double Daq::samplerate() const {
|
||||
return availableSampleRates.at(sampleRateIndex);
|
||||
}
|
||||
|
||||
DataTypeDescriptor::DataType Daq::dataType() const {
|
||||
return availableDataTypes.at(dataTypeIndex);
|
||||
}
|
||||
|
||||
double Daq::inputRangeForChannel(us ch) const {
|
||||
if (!(ch < ninchannels)) {
|
||||
throw runtime_error("Invalid channel number");
|
||||
}
|
||||
return availableInputRanges.at(inputRangeIndices.at(ch));
|
||||
}
|
||||
|
||||
us Daq::neninchannels(bool include_monitorchannel) const {
|
||||
us inch = std::count(eninchannels.begin(), eninchannels.end(), true);
|
||||
if (monitorOutput && include_monitorchannel) {
|
||||
inch += nenoutchannels();
|
||||
}
|
||||
return inch;
|
||||
}
|
||||
|
||||
us Daq::nenoutchannels() const {
|
||||
return std::count(enoutchannels.begin(), enoutchannels.end(), true);
|
||||
}
|
169
lasp/device/lasp_daq.h
Normal file
169
lasp/device/lasp_daq.h
Normal file
@ -0,0 +1,169 @@
|
||||
#pragma once
|
||||
#include "lasp_config.h"
|
||||
#include "lasp_daqconfig.h"
|
||||
#include "lasp_deviceinfo.h"
|
||||
#include "lasp_types.h"
|
||||
#include <functional>
|
||||
#include <gsl/gsl-lite.hpp>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
/**
|
||||
* @brief Data coming from / going to DAQ. Non-interleaved format, which means
|
||||
* data in buffer is ordered by channel: _ptr[sample+channel*nsamples]
|
||||
*/
|
||||
class DaqData {
|
||||
protected:
|
||||
/**
|
||||
* @brief Storage for actual data.
|
||||
*/
|
||||
std::vector<int8_t> _data;
|
||||
|
||||
public:
|
||||
const us nchannels;
|
||||
const us nframes;
|
||||
const DataTypeDescriptor::DataType dtype;
|
||||
const DataTypeDescriptor dtype_descr;
|
||||
|
||||
DaqData(const us nchannels, const us nframes,
|
||||
const DataTypeDescriptor::DataType dtype_descr);
|
||||
virtual ~DaqData() = default;
|
||||
|
||||
/**
|
||||
* @brief Return reference to internal vector
|
||||
*
|
||||
* @return Reference to vector of data storage.
|
||||
*/
|
||||
std::vector<int8_t> &raw_vec() { return _data; }
|
||||
us size_bytes() const { return _data.size(); }
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Typed DaqData, in which the type is specified
|
||||
*
|
||||
* @tparam T
|
||||
*/
|
||||
template <typename T> class TypedDaqData : public DaqData {
|
||||
T *data() { return static_cast<T *>(_data.data()); }
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Callback of DAQ. Called with arguments of a vector of data
|
||||
* spans for each channel, and a datatype descriptor. Callback should return
|
||||
* false for a stop request.
|
||||
*/
|
||||
using ChannelView = std::vector<gsl::span<uint8_t>>;
|
||||
using DaqCallback =
|
||||
std::function<bool(ChannelView channel_data,
|
||||
const DataTypeDescriptor &dtype_descr)>;
|
||||
|
||||
/**
|
||||
* @brief Base cass for all DAQ instances
|
||||
*/
|
||||
class Daq : public DaqConfiguration, public DeviceInfo {
|
||||
|
||||
protected:
|
||||
Daq(const DeviceInfo &devinfo, const DaqConfiguration &config);
|
||||
Daq(const Daq &) = delete;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Create a Daq based on given device info and configuration
|
||||
*
|
||||
* @param devinfo Device information of device to be used.
|
||||
* @param config Configuation to apply to the deviec
|
||||
*
|
||||
* @return Pointer to Daq device created.
|
||||
*/
|
||||
static std::unique_ptr<Daq> createDaq(const DeviceInfo &devinfo,
|
||||
const DaqConfiguration &config);
|
||||
|
||||
/**
|
||||
* @brief Start the Daq.
|
||||
*
|
||||
* @param cb Callback function that is called with input data as argument,
|
||||
* and which expects output data as a return value. If no output data is
|
||||
* required, the return value of the function should be nullptr. If no input
|
||||
* data is presented, the function is called with a nullptr as argument.
|
||||
*/
|
||||
virtual void start(std::optional<DaqCallback> inCallback,
|
||||
std::optional<DaqCallback> outCallback);
|
||||
|
||||
/**
|
||||
* @brief Stop the Daq device. Throws an exception if the device is not
|
||||
* running at the time this method is called.
|
||||
*/
|
||||
virtual void stop() = 0;
|
||||
|
||||
/**
|
||||
* @brief Returns whether the data stream is running Y/N
|
||||
*
|
||||
* @return true if running
|
||||
*/
|
||||
virtual bool isRunning() const = 0;
|
||||
|
||||
virtual ~Daq(){};
|
||||
/**
|
||||
* @brief Returns the number of enabled input channels
|
||||
*
|
||||
* @param include_monitorchannel if true, all channels that are internally
|
||||
* monitored are added to the list.
|
||||
*
|
||||
* @return number of enabled input channels
|
||||
*/
|
||||
us neninchannels(bool include_monitorchannel = true) const;
|
||||
/**
|
||||
* @brief Returns the number of enabled output channels
|
||||
*
|
||||
* @return Number of enabled output channels
|
||||
*/
|
||||
us nenoutchannels() const;
|
||||
|
||||
/**
|
||||
* @brief Returns current sample rate
|
||||
*
|
||||
* @return Sample rate in [Hz]
|
||||
*/
|
||||
double samplerate() const;
|
||||
/**
|
||||
* @brief Returns the input range for each channel. Means the minimum from
|
||||
* the absolute value of the minumum and maximum value that is allowed to
|
||||
* pass unclipped.
|
||||
*
|
||||
* @param ch The channel index from the input channels.
|
||||
*
|
||||
* @return Maximum offset from 0 before clipping.
|
||||
*/
|
||||
double inputRangeForChannel(us ch) const;
|
||||
/**
|
||||
* @brief Returns datatype (enum) corresponding to the datatype of the
|
||||
* samples.
|
||||
*
|
||||
* @return That as stated aboce
|
||||
*/
|
||||
DataTypeDescriptor::DataType dataType() const;
|
||||
/**
|
||||
* @brief More elaborate description of the datatypes.
|
||||
*
|
||||
* @return A DataTypeDescriptor
|
||||
*/
|
||||
DataTypeDescriptor dtypeDescr() const;
|
||||
|
||||
/**
|
||||
* @brief The number of frames that is send in a block of DaqData.
|
||||
*
|
||||
* @return The number of frames per block
|
||||
*/
|
||||
us framesPerBlock() const {
|
||||
return availableFramesPerBlock.at(framesPerBlockIndex);
|
||||
}
|
||||
/**
|
||||
* @brief Whether the device runs in duplex mode (both input and output), or
|
||||
* false if only input / only output.
|
||||
*
|
||||
* @return true if duplex
|
||||
*/
|
||||
bool duplexMode() const {
|
||||
return (neninchannels(false) > 0 && nenoutchannels() > 0);
|
||||
}
|
||||
};
|
@ -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)
|
@ -1,329 +0,0 @@
|
||||
cimport cython
|
||||
from ..lasp_common import AvType
|
||||
from .lasp_deviceinfo cimport DeviceInfo
|
||||
from .lasp_daqconfig cimport DaqConfiguration
|
||||
from numpy cimport import_array
|
||||
|
||||
from cpython.ref cimport PyObject,Py_INCREF, Py_DECREF
|
||||
import numpy as np
|
||||
import logging
|
||||
|
||||
__all__ = ['Daq']
|
||||
|
||||
cdef cnp.NPY_TYPES getCNumpyDataType(DataType& dt):
|
||||
if(dt == dtype_fl32):
|
||||
return cnp.NPY_FLOAT32
|
||||
elif(dt == dtype_fl64):
|
||||
return cnp.NPY_FLOAT64
|
||||
elif(dt == dtype_int8):
|
||||
return cnp.NPY_INT8
|
||||
elif(dt == dtype_int16):
|
||||
return cnp.NPY_INT16
|
||||
elif(dt == dtype_int32):
|
||||
return cnp.NPY_INT32
|
||||
else:
|
||||
raise ValueError('Unknown data type')
|
||||
|
||||
cdef getNumpyDataType(DataType& dt):
|
||||
if(dt == dtype_fl32):
|
||||
return np.dtype(np.float32)
|
||||
elif(dt == dtype_fl64):
|
||||
return np.dtype(np.float64)
|
||||
elif(dt == dtype_int8):
|
||||
return np.dtype(np.int8)
|
||||
elif(dt == dtype_int16):
|
||||
return np.dtype(np.int16)
|
||||
elif(dt == dtype_int32):
|
||||
return np.dtype(np.int32)
|
||||
else:
|
||||
raise ValueError('Unknown data type')
|
||||
|
||||
DEF QUEUE_BUFFER_TIME = 0.5
|
||||
|
||||
ctypedef struct PyStreamData:
|
||||
PyObject* pyCallback
|
||||
|
||||
# Flag used to pass the stopThread.
|
||||
atomic[bool] stopThread
|
||||
|
||||
# Flag to indicate that the signal generator queue has been filled for the
|
||||
# first time.
|
||||
atomic[bool] ready
|
||||
|
||||
# Number of frames per block
|
||||
unsigned nFramesPerBlock
|
||||
|
||||
# Number of bytes per channel
|
||||
unsigned int nBytesPerChan
|
||||
|
||||
unsigned ninchannels
|
||||
unsigned noutchannels
|
||||
|
||||
double samplerate
|
||||
|
||||
cnp.NPY_TYPES npy_format
|
||||
|
||||
# If either of these queue pointers are NULL, it means the stream does not have an
|
||||
# input, or output.
|
||||
SafeQueue[void*] *inQueue
|
||||
SafeQueue[void*] *outQueue
|
||||
CPPThread[void*, void (*)(void*)] *thread
|
||||
|
||||
cdef void audioCallbackPythonThreadFunction(void* voidsd) nogil:
|
||||
cdef:
|
||||
PyStreamData* sd = <PyStreamData*> voidsd
|
||||
|
||||
double* inbuffer = NULL
|
||||
double* outbuffer = NULL
|
||||
|
||||
unsigned noutchannels= sd.noutchannels
|
||||
unsigned ninchannels= sd.ninchannels
|
||||
unsigned nBytesPerChan= sd.nBytesPerChan
|
||||
unsigned nFramesPerBlock= sd.nFramesPerBlock
|
||||
|
||||
double sleeptime = (<double> sd.nFramesPerBlock)/(8*sd.samplerate);
|
||||
# Sleep time in microseconds
|
||||
us sleeptime_us = <us> (sleeptime*1e6);
|
||||
|
||||
us nblocks_buffer = <us> max(1, (QUEUE_BUFFER_TIME * sd.samplerate /
|
||||
sd.nFramesPerBlock))
|
||||
|
||||
with gil:
|
||||
import_array()
|
||||
npy_format = cnp.NPY_FLOAT64
|
||||
callback = <object> sd.pyCallback
|
||||
# print(f'Number of input channels: {ninchannels}')
|
||||
# print(f'Number of out channels: {noutchannels}')
|
||||
# fprintf(stderr, 'Sleep time: %d us\n', sleeptime_us)
|
||||
|
||||
if sd.outQueue:
|
||||
for i in range(nblocks_buffer):
|
||||
outbuffer = <double*> malloc(sizeof(double)*nBytesPerChan*noutchannels)
|
||||
memset(outbuffer, 0, sizeof(double)*nBytesPerChan*noutchannels)
|
||||
sd.outQueue.enqueue(<double*> outbuffer)
|
||||
sd.ready.store(True)
|
||||
|
||||
while not sd.stopThread.load():
|
||||
with gil:
|
||||
if sd.outQueue:
|
||||
while sd.outQueue.size() < nblocks_buffer:
|
||||
outbuffer = <double*> malloc(sizeof(double)*nBytesPerChan*noutchannels)
|
||||
|
||||
npy_output = <object> data_to_ndarray(
|
||||
outbuffer,
|
||||
nFramesPerBlock,
|
||||
noutchannels,
|
||||
sd.npy_format,
|
||||
False, # Do not transfer ownership to the temporary
|
||||
# Numpy container
|
||||
True) # F-contiguous
|
||||
try:
|
||||
rval = callback(None, npy_output, nFramesPerBlock)
|
||||
|
||||
except Exception as e:
|
||||
logging.error('exception in Cython callback for audio output: ', str(e))
|
||||
return
|
||||
|
||||
sd.outQueue.enqueue(<double*> outbuffer)
|
||||
|
||||
if sd.inQueue and not sd.inQueue.empty():
|
||||
# Waiting indefinitely on the queue...
|
||||
inbuffer = <double*> sd.inQueue.dequeue()
|
||||
if inbuffer == NULL:
|
||||
logging.debug('Received empty buffer on input, stopping thread...\n')
|
||||
return
|
||||
|
||||
npy_input = <object> data_to_ndarray(
|
||||
inbuffer,
|
||||
nFramesPerBlock,
|
||||
ninchannels,
|
||||
sd.npy_format,
|
||||
True, # Do transfer ownership
|
||||
True) # F-contiguous is True: data is Fortran-cont.
|
||||
|
||||
try:
|
||||
rval = callback(npy_input, None, nFramesPerBlock)
|
||||
|
||||
except Exception as e:
|
||||
logging.error('exception in cython callback for audio input: ', str(e))
|
||||
return
|
||||
|
||||
CPPsleep_us(sleeptime_us);
|
||||
|
||||
# Outputbuffer is free'ed by the audiothread, so should not be touched
|
||||
# here.
|
||||
outbuffer = NULL
|
||||
|
||||
# Inputbuffer memory is owned by Numpy, so should not be free'ed
|
||||
inbuffer = NULL
|
||||
|
||||
cdef class Daq:
|
||||
|
||||
def __cinit__(self, DeviceInfo pydevinfo, DaqConfiguration pydaqconfig):
|
||||
|
||||
"""
|
||||
Acquires a daq handle, and opens the device
|
||||
|
||||
"""
|
||||
cdef:
|
||||
cppDaqConfiguration* daqconfig
|
||||
cppDeviceInfo* devinfo
|
||||
vector[cppDeviceInfo] devinfos
|
||||
|
||||
self.daq_device = NULL
|
||||
self.sd = NULL
|
||||
|
||||
daqconfig = &(pydaqconfig.config)
|
||||
devinfo = &(pydevinfo.devinfo)
|
||||
|
||||
try:
|
||||
self.daq_device = cppDaq.createDaq(devinfo[0], daqconfig[0])
|
||||
except Exception as e:
|
||||
raise
|
||||
self.nFramesPerBlock = self.daq_device.framesPerBlock()
|
||||
self.samplerate = self.daq_device.samplerate()
|
||||
|
||||
if self.nFramesPerBlock > 8192 or self.nFramesPerBlock < 512:
|
||||
del self.daq_device
|
||||
raise ValueError('Invalid number of nFramesPerBlock')
|
||||
|
||||
def __dealloc__(self):
|
||||
# fprintf(stderr, "UlDaq.__dealloc__\n")
|
||||
if self.sd is not NULL:
|
||||
logging.debug("UlDaq.__dealloc__: stopping stream.")
|
||||
self.stop()
|
||||
|
||||
if self.daq_device is not NULL:
|
||||
del self.daq_device
|
||||
self.daq_device = NULL
|
||||
|
||||
def getNumpyDataType(self):
|
||||
cdef:
|
||||
DataType dt = self.daq_device.dataType()
|
||||
return getNumpyDataType(dt)
|
||||
|
||||
|
||||
def isRunning(self):
|
||||
return self.sd is not NULL
|
||||
|
||||
@staticmethod
|
||||
def getDeviceInfo():
|
||||
cdef:
|
||||
vector[cppDeviceInfo] devinfos = cppDaq.getDeviceInfo()
|
||||
pydevinfo = {}
|
||||
for i in range(devinfos.size()):
|
||||
d = DeviceInfo()
|
||||
d.devinfo = <cppDeviceInfo> devinfos[i]
|
||||
if d.api not in pydevinfo.keys():
|
||||
pydevinfo[d.api] = [d]
|
||||
else:
|
||||
pydevinfo[d.api].append(d)
|
||||
|
||||
return pydevinfo
|
||||
|
||||
@cython.nonecheck(True)
|
||||
def start(self, audiocallback):
|
||||
"""
|
||||
Opens a stream with specified parameters
|
||||
|
||||
Args:
|
||||
avstream: AvStream instance
|
||||
|
||||
Returns: None
|
||||
"""
|
||||
if self.sd is not NULL:
|
||||
assert self.daq_device is not NULL
|
||||
raise RuntimeError('Stream is already opened.')
|
||||
|
||||
cdef:
|
||||
cppDaq* daq = self.daq_device
|
||||
unsigned nFramesPerBlock = self.nFramesPerBlock
|
||||
DataType dtype = self.daq_device.dataType()
|
||||
double samplerate = self.samplerate
|
||||
|
||||
|
||||
# All set, allocate the stream!
|
||||
self.sd = <PyStreamData*> malloc(sizeof(PyStreamData))
|
||||
if self.sd == NULL:
|
||||
del daq
|
||||
raise MemoryError('Could not allocate stream: memory error.')
|
||||
|
||||
|
||||
self.sd.stopThread.store(False)
|
||||
self.sd.ready.store(False)
|
||||
|
||||
self.sd.inQueue = NULL
|
||||
self.sd.outQueue = NULL
|
||||
|
||||
self.sd.thread = NULL
|
||||
self.sd.samplerate = <double> samplerate
|
||||
|
||||
self.sd.ninchannels = daq.neninchannels(True)
|
||||
self.sd.noutchannels = daq.nenoutchannels()
|
||||
self.sd.nBytesPerChan = nFramesPerBlock*dtype.sw
|
||||
self.sd.nFramesPerBlock = nFramesPerBlock
|
||||
self.sd.npy_format = getCNumpyDataType(dtype)
|
||||
|
||||
if daq.neninchannels(True) > 0:
|
||||
self.sd.inQueue = new SafeQueue[void*]()
|
||||
if daq.nenoutchannels() > 0:
|
||||
self.sd.outQueue = new SafeQueue[void*]()
|
||||
|
||||
self.sd.pyCallback = <PyObject*> audiocallback
|
||||
|
||||
# Increase reference count to the callback
|
||||
Py_INCREF(<object> audiocallback)
|
||||
|
||||
with nogil:
|
||||
self.sd.thread = new CPPThread[void*, void (*)(void*)](audioCallbackPythonThreadFunction,
|
||||
<void*> self.sd)
|
||||
|
||||
while not self.sd.ready.load():
|
||||
# Allow stream stome time to start
|
||||
CPPsleep_ms(100)
|
||||
|
||||
self.daq_device.start(
|
||||
self.sd.inQueue,
|
||||
self.sd.outQueue)
|
||||
|
||||
return self.daq_device.samplerate()
|
||||
|
||||
def stop(self):
|
||||
if self.sd is NULL:
|
||||
raise RuntimeError('Stream is not opened')
|
||||
|
||||
self.cleanupStream(self.sd)
|
||||
self.sd = NULL
|
||||
|
||||
cdef cleanupStream(self, PyStreamData* sd):
|
||||
|
||||
with nogil:
|
||||
if sd.thread:
|
||||
sd.stopThread.store(True)
|
||||
|
||||
if sd.inQueue:
|
||||
# If waiting in the input queue, hereby we let it run.
|
||||
sd.inQueue.enqueue(NULL)
|
||||
|
||||
sd.thread.join()
|
||||
del sd.thread
|
||||
sd.thread = NULL
|
||||
|
||||
if sd.inQueue:
|
||||
while not sd.inQueue.empty():
|
||||
free(sd.inQueue.dequeue())
|
||||
del sd.inQueue
|
||||
|
||||
if sd.outQueue:
|
||||
while not sd.outQueue.empty():
|
||||
free(sd.outQueue.dequeue())
|
||||
del sd.outQueue
|
||||
sd.outQueue = NULL
|
||||
logging.debug("End cleanup stream queues...\n")
|
||||
|
||||
if sd.pyCallback:
|
||||
Py_DECREF(<object> sd.pyCallback)
|
||||
sd.pyCallback = NULL
|
||||
|
||||
free(sd)
|
||||
|
97
lasp/device/lasp_daqconfig.cpp
Normal file
97
lasp/device/lasp_daqconfig.cpp
Normal file
@ -0,0 +1,97 @@
|
||||
#include "lasp_daqconfig.h"
|
||||
#include "lasp_deviceinfo.h"
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
|
||||
#define MAX_DEV_COUNT_PER_API 20
|
||||
|
||||
using std::vector;
|
||||
|
||||
|
||||
vector<DaqApi> DaqApi::getAvailableApis() {
|
||||
|
||||
vector<DaqApi> apis;
|
||||
apis.resize(6);
|
||||
#if LASP_HAS_ULDAQ == 1
|
||||
apis.at(uldaqapi.apicode) = uldaqapi;
|
||||
#endif
|
||||
#if LASP_HAS_RTAUDIO == 1
|
||||
apis.at(rtaudioAlsaApi.apicode) = rtaudioAlsaApi;
|
||||
apis.at(rtaudioPulseaudioApi.apicode) = rtaudioPulseaudioApi;
|
||||
apis.at(rtaudioWasapiApi.apicode) = rtaudioWasapiApi;
|
||||
apis.at(rtaudioDsApi.apicode) = rtaudioDsApi;
|
||||
apis.at(rtaudioAsioApi.apicode) = rtaudioAsioApi;
|
||||
#endif
|
||||
return apis;
|
||||
}
|
||||
|
||||
DaqConfiguration::DaqConfiguration(const DeviceInfo &device) {
|
||||
|
||||
api = device.api;
|
||||
device_name = device.device_name;
|
||||
|
||||
eninchannels.resize(device.ninchannels, false);
|
||||
enoutchannels.resize(device.noutchannels, false);
|
||||
|
||||
inchannel_sensitivities.resize(device.ninchannels, 1.0);
|
||||
inchannel_metadata.resize(device.ninchannels, "");
|
||||
for (us i = 0; i < eninchannels.size(); i++) {
|
||||
std::stringstream chname;
|
||||
chname << "Unnamed input channel " << i;
|
||||
inchannel_names.push_back(chname.str());
|
||||
}
|
||||
|
||||
outchannel_metadata.resize(device.noutchannels, "");
|
||||
outchannel_sensitivities.resize(device.noutchannels, 1.0);
|
||||
for (us i = 0; i < enoutchannels.size(); i++) {
|
||||
std::stringstream chname;
|
||||
chname << "Unnamed output channel " << i;
|
||||
outchannel_names.push_back(chname.str());
|
||||
}
|
||||
sampleRateIndex = device.prefSampleRateIndex;
|
||||
dataTypeIndex = device.prefDataTypeIndex;
|
||||
framesPerBlockIndex = device.prefFramesPerBlockIndex;
|
||||
|
||||
monitorOutput = false;
|
||||
|
||||
inputIEPEEnabled.resize(device.ninchannels, false);
|
||||
inputACCouplingMode.resize(device.ninchannels, false);
|
||||
inputRangeIndices.resize(device.ninchannels, device.prefInputRangeIndex);
|
||||
|
||||
assert(match(device));
|
||||
}
|
||||
|
||||
bool DaqConfiguration::match(const DeviceInfo &dev) const {
|
||||
return (dev.device_name == device_name && dev.api == api);
|
||||
}
|
||||
|
||||
int DaqConfiguration::getHighestInChannel() const {
|
||||
for (int i = eninchannels.size() - 1; i > -1; i--) {
|
||||
if (eninchannels.at(i))
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int DaqConfiguration::getHighestOutChannel() const {
|
||||
for (us i = enoutchannels.size() - 1; i >= 0; i--) {
|
||||
if (enoutchannels.at(i))
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
int DaqConfiguration::getLowestInChannel() const {
|
||||
for (us i = 0; i < eninchannels.size(); i++) {
|
||||
if (eninchannels.at(i))
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
int DaqConfiguration::getLowestOutChannel() const {
|
||||
for (us i = 0; i < enoutchannels.size(); i++) {
|
||||
if (enoutchannels.at(i))
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
250
lasp/device/lasp_daqconfig.h
Normal file
250
lasp/device/lasp_daqconfig.h
Normal file
@ -0,0 +1,250 @@
|
||||
#pragma once
|
||||
#include "lasp_config.h"
|
||||
#include "lasp_types.h"
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
using std::string;
|
||||
using std::to_string;
|
||||
|
||||
using boolvec = std::vector<bool>;
|
||||
using dvec = std::vector<double>;
|
||||
using usvec = std::vector<us>;
|
||||
|
||||
/**
|
||||
* @brief Descriptor for data types containing more detailed information.
|
||||
*/
|
||||
class DataTypeDescriptor {
|
||||
public:
|
||||
/**
|
||||
* @brief Basic data types coming from a DAQ that we can deal with. The naming
|
||||
* will be self-explainging.
|
||||
*/
|
||||
enum class DataType {
|
||||
dtype_fl32 = 0,
|
||||
dtype_fl64 = 1,
|
||||
dtype_int8 = 2,
|
||||
dtype_int16 = 3,
|
||||
dtype_int32 = 4
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Name of the datatype
|
||||
*/
|
||||
string name;
|
||||
/**
|
||||
* @brief Sample width of a single sample, in bytes
|
||||
*/
|
||||
unsigned sw;
|
||||
/**
|
||||
* @brief Whether the datatype is a floating point Y/N
|
||||
*/
|
||||
bool is_floating;
|
||||
|
||||
/**
|
||||
* @brief The number from the enumeration
|
||||
*/
|
||||
DataType dtype;
|
||||
|
||||
/**
|
||||
* @brief Down-cast a DataTypeDescriptor to a datatype
|
||||
*
|
||||
* @return The descriptor as an enum
|
||||
*/
|
||||
operator DataType() { return dtype; }
|
||||
|
||||
/**
|
||||
* @brief Compare two data type descriptors. Returns true if the DataType
|
||||
* enumerator is the same.
|
||||
*
|
||||
* @param o
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
bool operator==(const DataTypeDescriptor &o) noexcept {
|
||||
return dtype == o.dtype;
|
||||
}
|
||||
};
|
||||
|
||||
const DataTypeDescriptor dtype_desc_fl32 = {
|
||||
"32-bits floating point", 4, true,
|
||||
DataTypeDescriptor::DataType::dtype_fl32};
|
||||
const DataTypeDescriptor dtype_desc_fl64 = {
|
||||
"64-bits floating point", 8, true,
|
||||
DataTypeDescriptor::DataType::dtype_fl64};
|
||||
const DataTypeDescriptor dtype_desc_int8 = {
|
||||
"8-bits integer", 1, false, DataTypeDescriptor::DataType::dtype_int8};
|
||||
const DataTypeDescriptor dtype_desc_int16 = {
|
||||
"16-bits integer", 2, false, DataTypeDescriptor::DataType::dtype_int16};
|
||||
const DataTypeDescriptor dtype_desc_int32 = {
|
||||
"32-bits integer", 4, false, DataTypeDescriptor::DataType::dtype_int32};
|
||||
|
||||
const std::map<DataTypeDescriptor::DataType, const DataTypeDescriptor>
|
||||
dtype_map = {
|
||||
{DataTypeDescriptor::DataType::dtype_fl32, dtype_desc_fl32},
|
||||
{DataTypeDescriptor::DataType::dtype_fl64, dtype_desc_fl64},
|
||||
{DataTypeDescriptor::DataType::dtype_int8, dtype_desc_int8},
|
||||
{DataTypeDescriptor::DataType::dtype_int16, dtype_desc_int16},
|
||||
{DataTypeDescriptor::DataType::dtype_int32, dtype_desc_int32},
|
||||
};
|
||||
|
||||
class DaqApi {
|
||||
public:
|
||||
string apiname = "Invalid API";
|
||||
int apicode = -1;
|
||||
unsigned api_specific_subcode = 0;
|
||||
|
||||
DaqApi(string apiname, unsigned apicode, unsigned api_specific_subcode = 0)
|
||||
: apiname(apiname), apicode(apicode),
|
||||
api_specific_subcode(api_specific_subcode) {}
|
||||
DaqApi() {}
|
||||
bool operator==(const DaqApi &other) const {
|
||||
return (apiname == other.apiname && apicode == other.apicode &&
|
||||
api_specific_subcode == other.api_specific_subcode);
|
||||
}
|
||||
operator string() const { return apiname + ", code: " + to_string(apicode); }
|
||||
static std::vector<DaqApi> getAvailableApis();
|
||||
};
|
||||
|
||||
#if LASP_HAS_ULDAQ == 1
|
||||
const DaqApi uldaqapi("UlDaq", 0);
|
||||
#endif
|
||||
#if LASP_HAS_RTAUDIO == 1
|
||||
#include <RtAudio.h>
|
||||
const DaqApi rtaudioAlsaApi("RtAudio Linux ALSA", 1, RtAudio::Api::LINUX_ALSA);
|
||||
const DaqApi rtaudioPulseaudioApi("RtAudio Linux Pulseaudio", 2,
|
||||
RtAudio::Api::LINUX_PULSE);
|
||||
const DaqApi rtaudioWasapiApi("RtAudio Windows Wasapi", 3,
|
||||
RtAudio::Api::WINDOWS_WASAPI);
|
||||
const DaqApi rtaudioDsApi("RtAudio Windows DirectSound", 4,
|
||||
RtAudio::Api::WINDOWS_DS);
|
||||
const DaqApi rtaudioAsioApi("RtAudio Windows ASIO", 5,
|
||||
RtAudio::Api::WINDOWS_ASIO);
|
||||
#endif
|
||||
|
||||
|
||||
class DeviceInfo;
|
||||
/**
|
||||
* @brief Configuration of a DAQ device
|
||||
*/
|
||||
class DaqConfiguration {
|
||||
public:
|
||||
/**
|
||||
* @brief The API of the DAQ system this configuration applies to.
|
||||
*/
|
||||
DaqApi api;
|
||||
/**
|
||||
* @brief The internal device name this DAQ configuration applies to.
|
||||
*/
|
||||
string device_name;
|
||||
|
||||
/**
|
||||
* @brief Enabled input channels
|
||||
*/
|
||||
boolvec eninchannels;
|
||||
/**
|
||||
* @brief Enabled 'normal' output channels, i.e. no internal loopbacks.
|
||||
*/
|
||||
boolvec enoutchannels;
|
||||
|
||||
std::vector<double> inchannel_sensitivities;
|
||||
std::vector<string> inchannel_names;
|
||||
std::vector<string> inchannel_metadata;
|
||||
|
||||
std::vector<double> outchannel_sensitivities;
|
||||
std::vector<string> outchannel_names;
|
||||
std::vector<string> outchannel_metadata;
|
||||
|
||||
/**
|
||||
* @brief Index in list of sample rates that are available for the device.
|
||||
*/
|
||||
us sampleRateIndex = 0; //
|
||||
|
||||
/**
|
||||
* @brief Required datatype for output, should be present in the list
|
||||
*/
|
||||
us dataTypeIndex = 0;
|
||||
|
||||
/**
|
||||
* @brief The index in the array of frames per block that can be used for the
|
||||
* device.
|
||||
*/
|
||||
us framesPerBlockIndex = 0;
|
||||
|
||||
/**
|
||||
* @brief If set to true and if the device has this capability, the output
|
||||
* channels are added as input channels as well.
|
||||
*/
|
||||
bool monitorOutput = false;
|
||||
|
||||
/**
|
||||
* @brief If the device is capable, enable IEPE constant current power supply
|
||||
* for given channel number.
|
||||
*/
|
||||
boolvec inputIEPEEnabled;
|
||||
/**
|
||||
* @brief If the device is capable, here we can define whether the channel
|
||||
* should enable hardware AC-coupling.
|
||||
*/
|
||||
boolvec inputACCouplingMode;
|
||||
|
||||
/**
|
||||
* @brief Stores the index of the used input range for each of the channels.
|
||||
*/
|
||||
usvec inputRangeIndices;
|
||||
|
||||
/**
|
||||
* @brief Create a default configuration, with all channels disabled on both
|
||||
* input and output, and default channel names
|
||||
*
|
||||
* @param deviceinfo DeviceInfo structure
|
||||
*/
|
||||
DaqConfiguration(const DeviceInfo &DeviceInfo);
|
||||
DaqConfiguration() = delete;
|
||||
|
||||
/**
|
||||
* @brief Check to see whether the DAQ configuration matches with the device.
|
||||
* This means, some basic checks are done on channels, sampling rate, etc,
|
||||
* and that the name corresponds with the device name.
|
||||
*
|
||||
* @param devinfo The DeviceInfo to check
|
||||
*
|
||||
* @return true if it matches. Otherwise false.
|
||||
*/
|
||||
bool match(const DeviceInfo &devinfo) const;
|
||||
|
||||
/**
|
||||
* @brief Get the highest channel number from the list of enabled input
|
||||
* channels.
|
||||
*
|
||||
* @return Index to the highest input channel. -1 if no input channels are
|
||||
* enabled.
|
||||
*/
|
||||
int getHighestInChannel() const;
|
||||
/**
|
||||
* @brief Get the highest channel number from the list of enabled output
|
||||
* channels.
|
||||
*
|
||||
* @return Index to the highest input channel. -1 if no output channels are
|
||||
* enabled.
|
||||
*/
|
||||
int getHighestOutChannel() const;
|
||||
|
||||
/**
|
||||
* @brief Get the lowest channel number from the list of enabled input
|
||||
* channels.
|
||||
*
|
||||
* @return Index to the lowest input channel. -1 if no input channels are
|
||||
* enabled.
|
||||
*/
|
||||
int getLowestInChannel() const;
|
||||
/**
|
||||
* @brief Get the lowest channel number from the list of enabled output
|
||||
* channels.
|
||||
*
|
||||
* @return Index to the lowest input channel. -1 if no output channels are
|
||||
* enabled.
|
||||
*/
|
||||
int getLowestOutChannel() const;
|
||||
};
|
||||
|
@ -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')
|
||||
|
139
lasp/device/lasp_deviceinfo.cpp
Normal file
139
lasp/device/lasp_deviceinfo.cpp
Normal file
@ -0,0 +1,139 @@
|
||||
#include "lasp_deviceinfo.h"
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
|
||||
#define MAX_DEV_COUNT_PER_API 20
|
||||
|
||||
#if LASP_HAS_ULDAQ == 1
|
||||
#include "lasp_uldaq.h"
|
||||
#endif
|
||||
#if LASP_HAS_RTAUDIO == 1
|
||||
#include "lasp_rtaudiodaq.h"
|
||||
#endif
|
||||
|
||||
string DeviceInfo::serialize() const {
|
||||
// Simple serializer for this object, used because we found a bit late
|
||||
// that this object needs to be send over the wire. We do not want to make
|
||||
// this implementation in Python, as these objects are created here, in
|
||||
// the C++ code. The Python wrapper is just a readonly wrapper.
|
||||
std::stringstream str;
|
||||
|
||||
str << api.apiname << "\t";
|
||||
str << api.apicode << "\t";
|
||||
str << api.api_specific_subcode << "\t";
|
||||
str << device_name << "\t";
|
||||
|
||||
str << availableDataTypes.size() << "\t";
|
||||
for (const auto &dtype : availableDataTypes) {
|
||||
// WARNING: THIS GOES COMPLETELY WRONG WHEN NAMES contain A TAB!!!
|
||||
str << static_cast<int>(dtype) << "\t";
|
||||
}
|
||||
str << prefDataTypeIndex << "\t";
|
||||
|
||||
str << availableSampleRates.size() << "\t";
|
||||
for (const double &fs : availableSampleRates) {
|
||||
// WARNING: THIS GOES COMPLETELY WRONG WHEN NAMES contain A TAB!!!
|
||||
str << fs << "\t";
|
||||
}
|
||||
str << prefSampleRateIndex << "\t";
|
||||
|
||||
str << availableFramesPerBlock.size() << "\t";
|
||||
for (const us &fb : availableFramesPerBlock) {
|
||||
// WARNING: THIS GOES COMPLETELY WRONG WHEN NAMES contain A TAB!!!
|
||||
str << fb << "\t";
|
||||
}
|
||||
str << prefFramesPerBlockIndex << "\t";
|
||||
|
||||
str << availableInputRanges.size() << "\t";
|
||||
for (const double &ir : availableInputRanges) {
|
||||
// WARNING: THIS GOES COMPLETELY WRONG WHEN NAMES contain A TAB!!!
|
||||
str << ir << "\t";
|
||||
}
|
||||
str << prefInputRangeIndex << "\t";
|
||||
|
||||
str << ninchannels << "\t";
|
||||
str << noutchannels << "\t";
|
||||
str << int(hasInputIEPE) << "\t";
|
||||
str << int(hasInputACCouplingSwitch) << "\t";
|
||||
str << int(hasInputTrigger) << "\t";
|
||||
|
||||
return str.str();
|
||||
}
|
||||
|
||||
DeviceInfo DeviceInfo::deserialize(const string &dstr) {
|
||||
DeviceInfo devinfo;
|
||||
|
||||
std::stringstream str(dstr);
|
||||
string tmp;
|
||||
us N;
|
||||
// Lambda functions for deserializing
|
||||
auto nexts = [&]() {
|
||||
getline(str, tmp, '\t');
|
||||
return tmp;
|
||||
};
|
||||
auto nexti = [&]() {
|
||||
getline(str, tmp, '\t');
|
||||
return std::atoi(tmp.c_str());
|
||||
};
|
||||
auto nextf = [&]() {
|
||||
getline(str, tmp, '\t');
|
||||
return std::atof(tmp.c_str());
|
||||
};
|
||||
|
||||
// Api
|
||||
string apiname = nexts();
|
||||
auto apicode = nexti();
|
||||
auto api_specific_subcode = nexti();
|
||||
DaqApi api(apiname, apicode, api_specific_subcode);
|
||||
devinfo.api = api;
|
||||
|
||||
devinfo.device_name = nexts();
|
||||
|
||||
N = us(nexti());
|
||||
for (us i = 0; i < N; i++) {
|
||||
devinfo.availableDataTypes.push_back(
|
||||
static_cast<DataTypeDescriptor::DataType>(nexti()));
|
||||
}
|
||||
|
||||
devinfo.prefDataTypeIndex = nexti();
|
||||
|
||||
N = us(nexti());
|
||||
for (us i = 0; i < N; i++) {
|
||||
devinfo.availableSampleRates.push_back(nextf());
|
||||
}
|
||||
devinfo.prefSampleRateIndex = nexti();
|
||||
|
||||
N = us(nexti());
|
||||
for (us i = 0; i < N; i++) {
|
||||
devinfo.availableFramesPerBlock.push_back(nexti());
|
||||
}
|
||||
devinfo.prefFramesPerBlockIndex = nexti();
|
||||
|
||||
N = us(nexti());
|
||||
for (us i = 0; i < N; i++) {
|
||||
devinfo.availableInputRanges.push_back(nexti());
|
||||
}
|
||||
devinfo.prefInputRangeIndex = nexti();
|
||||
|
||||
devinfo.ninchannels = nexti();
|
||||
devinfo.noutchannels = nexti();
|
||||
devinfo.hasInputIEPE = bool(nexti());
|
||||
devinfo.hasInputACCouplingSwitch = bool(nexti());
|
||||
devinfo.hasInputTrigger = bool(nexti());
|
||||
|
||||
return devinfo;
|
||||
}
|
||||
|
||||
std::vector<DeviceInfo> DeviceInfo::getDeviceInfo() {
|
||||
std::vector<DeviceInfo> devs;
|
||||
#if LASP_HAS_ULDAQ ==1
|
||||
fillUlDaqDeviceInfo(devs);
|
||||
#endif
|
||||
|
||||
#if LASP_HAS_RTAUDIO == 1
|
||||
fillRtAudioDeviceInfo(devs);
|
||||
#endif
|
||||
|
||||
return devs;
|
||||
}
|
||||
|
134
lasp/device/lasp_deviceinfo.h
Normal file
134
lasp/device/lasp_deviceinfo.h
Normal file
@ -0,0 +1,134 @@
|
||||
#pragma once
|
||||
#include "lasp_config.h"
|
||||
#include "lasp_types.h"
|
||||
#include "lasp_daqconfig.h"
|
||||
|
||||
|
||||
/**
|
||||
* @brief Structure containing device info parameters
|
||||
*/
|
||||
class DeviceInfo {
|
||||
public:
|
||||
/**
|
||||
* @brief Backend API corresponding to device
|
||||
*/
|
||||
DaqApi api;
|
||||
/**
|
||||
* @brief The name of the device
|
||||
*/
|
||||
string device_name = "";
|
||||
|
||||
/**
|
||||
* @brief Specific for the device (Sub-API). Important for the RtAudio
|
||||
* backend, as RtAudio is able to handle different API's.
|
||||
*/
|
||||
int api_specific_devindex = -1;
|
||||
|
||||
/**
|
||||
* @brief The available data types the device can output
|
||||
*/
|
||||
std::vector<DataTypeDescriptor::DataType> availableDataTypes;
|
||||
/**
|
||||
* @brief The device's prefferd data type.
|
||||
*/
|
||||
int prefDataTypeIndex = 0;
|
||||
|
||||
/**
|
||||
* @brief Available sample rates the device can run on
|
||||
*/
|
||||
dvec availableSampleRates;
|
||||
/**
|
||||
* @brief Preferred setting for the sample rate.
|
||||
*/
|
||||
int prefSampleRateIndex = -1;
|
||||
|
||||
/**
|
||||
* @brief Available latency-setting (number of frames passed in each
|
||||
* callback).
|
||||
*/
|
||||
usvec availableFramesPerBlock;
|
||||
/**
|
||||
* @brief Preffered number of frames per callback.
|
||||
*/
|
||||
us prefFramesPerBlockIndex = 0;
|
||||
|
||||
/**
|
||||
* @brief Available ranges for the input, i.e. +/- 1V and/or +/- 10 V etc.
|
||||
*/
|
||||
dvec availableInputRanges;
|
||||
/**
|
||||
* @brief Its preffered range
|
||||
*/
|
||||
int prefInputRangeIndex = 0;
|
||||
|
||||
/**
|
||||
* @brief The number of input channels available for the device
|
||||
*/
|
||||
unsigned ninchannels = 0;
|
||||
/**
|
||||
* @brief The number of output channels available for the device
|
||||
*/
|
||||
unsigned noutchannels = 0;
|
||||
|
||||
/**
|
||||
* @brief Whether the device is capable to provide IEPE constant current
|
||||
* power supply.
|
||||
*/
|
||||
bool hasInputIEPE = false;
|
||||
/**
|
||||
* @brief Whether the device is capable of enabling a hardware AC-coupling
|
||||
*/
|
||||
bool hasInputACCouplingSwitch = false;
|
||||
/**
|
||||
* @brief Whether the device is able to trigger on input
|
||||
*/
|
||||
bool hasInputTrigger = false;
|
||||
|
||||
double prefSampleRate() const {
|
||||
if (((us)prefSampleRateIndex < availableSampleRates.size()) &&
|
||||
(prefSampleRateIndex >= 0)) {
|
||||
return availableSampleRates.at(prefSampleRateIndex);
|
||||
} else {
|
||||
throw std::runtime_error("No prefered sample rate available");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief String representation of DeviceInfo
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
operator string() const {
|
||||
using std::endl;
|
||||
std::stringstream str;
|
||||
str << api.apiname + " " << api_specific_devindex << endl
|
||||
<< " number of input channels: " << ninchannels << endl
|
||||
/**
|
||||
* @brief
|
||||
*/
|
||||
<< " number of output channels: " << noutchannels << endl;
|
||||
return str.str();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief For backwards-compatibility: serialize device info to string
|
||||
*
|
||||
* @return Serialize-string
|
||||
*/
|
||||
string serialize() const;
|
||||
/**
|
||||
* @brief Create a device info structure from serialized string
|
||||
*
|
||||
* @param dstr String to deserialize from
|
||||
*
|
||||
* @return resulting DeviceInfo
|
||||
*/
|
||||
static DeviceInfo deserialize(const string& dstr);
|
||||
/**
|
||||
* @brief Create a list of DeviceInfo's that are at call time avalable
|
||||
*
|
||||
* @return The device info's for each found device.
|
||||
*/
|
||||
static std::vector<DeviceInfo> getDeviceInfo();
|
||||
};
|
||||
|
@ -1,5 +0,0 @@
|
||||
include "lasp_common_decls.pxd"
|
||||
|
||||
cdef class DeviceInfo:
|
||||
cdef:
|
||||
cppDeviceInfo devinfo
|
@ -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
|
||||
|
236
lasp/device/lasp_devicepybind.cpp
Normal file
236
lasp/device/lasp_devicepybind.cpp
Normal file
@ -0,0 +1,236 @@
|
||||
#if 0
|
||||
#include "lasp_devicepybind.h"
|
||||
#include <algorithm>
|
||||
#include <assert.h>
|
||||
#include <numpy/arrayobject.h>
|
||||
#include <pybind11/numpy.h>
|
||||
#include <stdint.h>
|
||||
#include <thread>
|
||||
|
||||
using std::cerr;
|
||||
namespace py = pybind11;
|
||||
|
||||
PYBIND11_MODULE(lasp_daq, m) {
|
||||
|
||||
m.doc() = "Lasp DAQ interface";
|
||||
|
||||
/// DataType
|
||||
py::class_<DataType> datatype(m, "DataType");
|
||||
datatype.def_readonly("name", &DataType::name);
|
||||
datatype.def_readonly("sw", &DataType::sw);
|
||||
datatype.def_readonly("is_floating", &DataType::is_floating);
|
||||
|
||||
m.attr("dtype_fl32") = dtype_fl32;
|
||||
m.attr("dtype_fl64") = dtype_fl64;
|
||||
m.attr("dtype_int8") = dtype_int8;
|
||||
m.attr("dtype_int16") = dtype_int16;
|
||||
m.attr("dtype_int24") = dtype_int24;
|
||||
m.attr("dtype_int32") = dtype_int32;
|
||||
|
||||
/// DaqApi
|
||||
py::class_<DaqApi> daqapi(m, "DaqApi");
|
||||
daqapi.def_readonly("apiname", &DaqApi::apiname);
|
||||
daqapi.def_readonly("apicode", &DaqApi::apicode);
|
||||
daqapi.def_readonly("api_specific_subcode", &DaqApi::api_specific_subcode);
|
||||
daqapi.def("__str__", [](const DaqApi &d) { return std::string(d); });
|
||||
|
||||
/// DeviceInfo
|
||||
py::class_<DeviceInfo> devinfo(m, "DeviceInfo");
|
||||
devinfo.def_readonly("api", &DeviceInfo::api);
|
||||
devinfo.def_readonly("device_name", &DeviceInfo::device_name);
|
||||
|
||||
devinfo.def_readonly("availableDataTypes", &DeviceInfo::availableDataTypes);
|
||||
devinfo.def_readonly("prefDataTypeIndex", &DeviceInfo::prefDataTypeIndex);
|
||||
|
||||
devinfo.def_readonly("availableSampleRates",
|
||||
&DeviceInfo::availableSampleRates);
|
||||
devinfo.def_readonly("prefSampleRateIndex", &DeviceInfo::prefSampleRateIndex);
|
||||
|
||||
devinfo.def_readonly("availableFramesPerBlock",
|
||||
&DeviceInfo::availableFramesPerBlock);
|
||||
devinfo.def_readonly("prefFramesPerBlockIndex",
|
||||
&DeviceInfo::prefFramesPerBlockIndex);
|
||||
|
||||
devinfo.def_readonly("availableInputRanges",
|
||||
&DeviceInfo::availableInputRanges);
|
||||
devinfo.def_readonly("prefInputRangeIndex", &DeviceInfo::prefInputRangeIndex);
|
||||
|
||||
devinfo.def_readonly("ninchannels", &DeviceInfo::ninchannels);
|
||||
devinfo.def_readonly("noutchannels", &DeviceInfo::noutchannels);
|
||||
devinfo.def_readonly("hasInputIEPE", &DeviceInfo::hasInputIEPE);
|
||||
devinfo.def_readonly("hasInputACCouplingSwitch",
|
||||
&DeviceInfo::hasInputACCouplingSwitch);
|
||||
|
||||
devinfo.def("serialize", &DeviceInfo::serialize);
|
||||
|
||||
devinfo.def_static("deserialize", &DeviceInfo::deserialize);
|
||||
|
||||
/// DaqConfiguration
|
||||
py::class_<DaqConfiguration> daqconfig(m, "DaqConfiguration");
|
||||
daqconfig.def(py::init<>());
|
||||
daqconfig.def_readwrite("eninchannels", &DaqConfiguration::eninchannels);
|
||||
daqconfig.def_readwrite("enoutchannels", &DaqConfiguration::enoutchannels);
|
||||
|
||||
daqconfig.def_readwrite("inchannel_sensitivities",
|
||||
&DaqConfiguration::inchannel_sensitivities);
|
||||
daqconfig.def_readwrite("inchannel_metadata",
|
||||
&DaqConfiguration::inchannel_metadata);
|
||||
daqconfig.def_readwrite("inchannel_names",
|
||||
&DaqConfiguration::inchannel_names);
|
||||
|
||||
daqconfig.def_readwrite("outchannel_sensitivities",
|
||||
&DaqConfiguration::outchannel_sensitivities);
|
||||
daqconfig.def_readwrite("outchannel_names",
|
||||
&DaqConfiguration::outchannel_names);
|
||||
daqconfig.def_readwrite("inchannel_metadata",
|
||||
&DaqConfiguration::inchannel_metadata);
|
||||
|
||||
daqconfig.def_readwrite("sampleRateIndex",
|
||||
&DaqConfiguration::sampleRateIndex);
|
||||
daqconfig.def_readwrite("dataTypeIndex",
|
||||
&DaqConfiguration::dataTypeIndex);
|
||||
|
||||
daqconfig.def_readwrite("framesPerBlockIndex",
|
||||
&DaqConfiguration::framesPerBlockIndex);
|
||||
daqconfig.def_readwrite("monitorOutput",
|
||||
&DaqConfiguration::monitorOutput);
|
||||
|
||||
daqconfig.def_readwrite("inputIEPEEnabled",
|
||||
&DaqConfiguration::inputIEPEEnabled);
|
||||
daqconfig.def_readwrite("inputACCouplingMode",
|
||||
&DaqConfiguration::inputACCouplingMode);
|
||||
|
||||
daqconfig.def_readwrite("inputRangeIndices",
|
||||
&DaqConfiguration::inputRangeIndices);
|
||||
|
||||
daqconfig.def("match", &DaqConfiguration::match);
|
||||
|
||||
/// Daq
|
||||
py::class_<PyDaq> daq(m, "Daq");
|
||||
daq.def(py::init<DeviceInfo,DaqConfiguration>());
|
||||
/* daq.def("start */
|
||||
/* daqconfig.def(py::init< */
|
||||
}
|
||||
|
||||
const d QUEUE_BUFFER_TIME = 0.1;
|
||||
|
||||
PyDaq::PyDaq(const DeviceInfo &devinfo, const DaqConfiguration &config) {
|
||||
|
||||
_daq = Daq::createDaq(devinfo, config);
|
||||
|
||||
_stopThread = false;
|
||||
_threadReady = false;
|
||||
}
|
||||
PyDaq::~PyDaq() {
|
||||
if (_daqThread) {
|
||||
}
|
||||
}
|
||||
|
||||
/* py::array arrayFromBuffer(void *buf, us n_rows, us n_cols, DataType dtype) { */
|
||||
/* // https://github.com/pybind/pybind11/issues/27 */
|
||||
/* py::size_t dims[] = {n_rows, n_cols}; */
|
||||
/* py::buffer_info; */
|
||||
/* switch (dtype) { */
|
||||
/* case dtype_int8: */
|
||||
/* break; */
|
||||
/* case dtype_int16: */
|
||||
/* break; */
|
||||
/* case dtype_int24: */
|
||||
/* break; */
|
||||
/* case dtype_fl32: */
|
||||
/* break; */
|
||||
/* case dtype_fl64: */
|
||||
/* break; */
|
||||
/* } */
|
||||
/* /1* buffer_info = py::buffer_info( *1/ */
|
||||
/* /1* py::buffer_info(buf, sizeof(float), *1/ */
|
||||
/* /1* py::format_descriptor<float>::format(), *1/ */
|
||||
/* /1* 2, *1/ */
|
||||
/* /1* dims, (float*) buf));; *1/ */
|
||||
/* /1* } *1/ */
|
||||
/* py::array res; */
|
||||
/* } */
|
||||
|
||||
void PyDaq::start(py::function audioCallback) {
|
||||
|
||||
// Number of input channels, including monitor channel
|
||||
const us neninchannels = _daq->neninchannels(true);
|
||||
const us nenoutchannels = _daq->nenoutchannels();
|
||||
const bool input = neninchannels > 0;
|
||||
const bool output = nenoutchannels > 0;
|
||||
|
||||
SafeQueue<void *> *inQueue = input ? &_inQueue : nullptr;
|
||||
SafeQueue<void *> *outQueue = output ? &_outQueue : nullptr;
|
||||
|
||||
_daq->start(inQueue, outQueue);
|
||||
|
||||
_audioCallback = audioCallback;
|
||||
|
||||
_daqThread = new std::thread(&PyDaq::threadFcn, this);
|
||||
|
||||
// Wait unitil thread is ready before returning
|
||||
do {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
} while (!_threadReady);
|
||||
}
|
||||
|
||||
void PyDaq::stop() {
|
||||
_daq->stop();
|
||||
_stopThread = true;
|
||||
_threadReady = false;
|
||||
_daqThread->join();
|
||||
delete _daqThread;
|
||||
_daqThread = nullptr;
|
||||
}
|
||||
|
||||
void PyDaq::threadFcn() {
|
||||
|
||||
assert(_daq);
|
||||
|
||||
void *inbuffer = nullptr;
|
||||
void *outbuffer = nullptr;
|
||||
|
||||
const double sleeptime = _daq->framesPerBlock() / (8 * _daq->samplerate());
|
||||
// Sleep time in microseconds
|
||||
const us sleeptime_us = (us)(sleeptime * 1e6);
|
||||
|
||||
const us nblocks_buffer = (us)std::max<d>(
|
||||
1, QUEUE_BUFFER_TIME * _daq->samplerate() / _daq->framesPerBlock());
|
||||
|
||||
auto descr = _daq->dtypeDescr();
|
||||
const us nBytesPerChan = _daq->framesPerBlock() * descr.sw;
|
||||
|
||||
_threadReady = true;
|
||||
|
||||
// Number of input channels, including monitor channel
|
||||
const us neninchannels = _daq->neninchannels(true);
|
||||
const us nenoutchannels = _daq->nenoutchannels();
|
||||
const bool input = neninchannels > 0;
|
||||
const bool output = nenoutchannels > 0;
|
||||
|
||||
cerr << "Check if it is required to call import_array()" << endl;
|
||||
/* { */
|
||||
/* py::gil_scoped_acquire aq; */
|
||||
/* import_array(void); */
|
||||
|
||||
/* } */
|
||||
_threadReady = true;
|
||||
if (output) {
|
||||
for (us i = 0; i < nblocks_buffer; i++) {
|
||||
outbuffer = new uint8_t[nBytesPerChan * nenoutchannels];
|
||||
memset(outbuffer, 0, nBytesPerChan * nenoutchannels);
|
||||
_outQueue.enqueue(outbuffer);
|
||||
}
|
||||
outbuffer = nullptr;
|
||||
}
|
||||
|
||||
while (!_stopThread) {
|
||||
if (output) {
|
||||
py::gil_scoped_acquire gil;
|
||||
while (_outQueue.size() < nblocks_buffer) {
|
||||
/* py::array( */
|
||||
}
|
||||
}
|
||||
} // End of while loop, stopthread
|
||||
}
|
||||
#endif
|
44
lasp/device/lasp_devicepybind.h
Normal file
44
lasp/device/lasp_devicepybind.h
Normal file
@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
#include "lasp_daq.h"
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
|
||||
#include <pybind11/pybind11.h>
|
||||
namespace py = pybind11;
|
||||
|
||||
namespace std {
|
||||
class thread;
|
||||
};
|
||||
|
||||
class PyDaq {
|
||||
Daq *_daq = nullptr;
|
||||
std::thread *_daqThread = nullptr;
|
||||
|
||||
SafeQueue<void *> _inQueue;
|
||||
SafeQueue<void *> _outQueue;
|
||||
|
||||
py::function _audioCallback;
|
||||
|
||||
std::atomic<bool> _stopThread;
|
||||
std::atomic<bool> _threadReady;
|
||||
|
||||
public:
|
||||
PyDaq(const DeviceInfo &devinfo, const DaqConfiguration &config);
|
||||
~PyDaq();
|
||||
/**
|
||||
* @brief Start the Daq data acquisition
|
||||
* @param audioCallback Python callback which is called with in and/or out
|
||||
data of the daq
|
||||
* as arguments
|
||||
|
||||
*/
|
||||
void start(py::function audioCallback);
|
||||
void stop();
|
||||
bool isRunning() { return _daq->isRunning(); }
|
||||
|
||||
static std::vector<DeviceInfo> getDeviceInfo();
|
||||
const Daq &daq() const { return *_daq; }
|
||||
|
||||
private:
|
||||
void threadFcn();
|
||||
};
|
@ -1,777 +0,0 @@
|
||||
include "lasp_common_decls.pxd"
|
||||
|
||||
__all__ = ['RtAudio', 'get_numpy_dtype_from_format_string',
|
||||
'get_sampwidth_from_format_string']
|
||||
|
||||
cdef extern from "RtAudio.h" nogil:
|
||||
ctypedef unsigned long RtAudioStreamStatus
|
||||
RtAudioStreamStatus RTAUDIO_INPUT_OVERFLOW
|
||||
RtAudioStreamStatus RTAUDIO_OUTPUT_UNDERFLOW
|
||||
|
||||
cdef cppclass RtAudioError:
|
||||
ctypedef enum Type:
|
||||
WARNING
|
||||
DEBUG_WARNING
|
||||
UNSPECIFIED
|
||||
NO_DEVICES_FOUND
|
||||
INVALID_DEVICE
|
||||
MEMORY_ERROR
|
||||
INVALID_PARAMETER
|
||||
INVALID_USE
|
||||
DRIVER_ERROR
|
||||
SYSTEM_ERROR
|
||||
THREAD_ERROR
|
||||
|
||||
ctypedef unsigned long RtAudioStreamFlags
|
||||
RtAudioStreamFlags RTAUDIO_NONINTERLEAVED
|
||||
RtAudioStreamFlags RTAUDIO_MINIMIZE_LATENCY
|
||||
RtAudioStreamFlags RTAUDIO_HOG_DEVICE
|
||||
RtAudioStreamFlags RTAUDIO_SCHEDULE_REALTIME
|
||||
RtAudioStreamFlags RTAUDIO_ALSA_USE_DEFAULT
|
||||
RtAudioStreamFlags RTAUDIO_JACK_DONT_CONNECT
|
||||
|
||||
ctypedef unsigned long RtAudioFormat
|
||||
RtAudioFormat RTAUDIO_SINT8
|
||||
RtAudioFormat RTAUDIO_SINT16
|
||||
RtAudioFormat RTAUDIO_SINT24
|
||||
RtAudioFormat RTAUDIO_SINT32
|
||||
RtAudioFormat RTAUDIO_FLOAT32
|
||||
RtAudioFormat RTAUDIO_FLOAT64
|
||||
|
||||
ctypedef int (*RtAudioCallback)(void* outputBuffer,
|
||||
void* inputBuffer,
|
||||
unsigned int nFramesPerBlock,
|
||||
double streamTime,
|
||||
RtAudioStreamStatus status,
|
||||
void* userData)
|
||||
|
||||
ctypedef void (*RtAudioErrorCallback)(RtAudioError.Type _type,
|
||||
const string& errortxt)
|
||||
|
||||
cdef cppclass cppRtAudio "RtAudio":
|
||||
enum Api:
|
||||
UNSPECIFIED
|
||||
LINUX_ALSA
|
||||
LINUX_PULSE
|
||||
MACOSX_CORE
|
||||
WINDOWS_WASAPI
|
||||
WINDOWS_ASIO
|
||||
WINDOWS_DS
|
||||
|
||||
cppclass DeviceInfo:
|
||||
bool probed
|
||||
string name
|
||||
unsigned int outputChannels
|
||||
unsigned int inputChannels
|
||||
unsigned int duplexChannels
|
||||
bool isDefaultOutput
|
||||
bool isDefaultInput
|
||||
vector[unsigned int] sampleRates
|
||||
unsigned int preferredSampleRate
|
||||
RtAudioFormat nativeFormats
|
||||
|
||||
cppclass StreamOptions:
|
||||
StreamOptions()
|
||||
RtAudioStreamFlags flags
|
||||
unsigned int numberOfBuffers
|
||||
string streamName
|
||||
int priority
|
||||
cppclass StreamParameters:
|
||||
StreamParameters()
|
||||
unsigned int deviceId
|
||||
unsigned int nChannels
|
||||
unsigned int firstChannel
|
||||
|
||||
@staticmethod
|
||||
void getCompiledApi(vector[cppRtAudio.Api]& apis)
|
||||
|
||||
@staticmethod
|
||||
cppRtAudio.Api getCompiledApiByName(string& name)
|
||||
|
||||
@staticmethod
|
||||
string getApiDisplayName(cppRtAudio.Api api)
|
||||
|
||||
@staticmethod
|
||||
string getApiName(cppRtAudio.Api api)
|
||||
|
||||
# RtAudio() except +
|
||||
cppRtAudio() except +
|
||||
cppRtAudio(cppRtAudio.Api api) except +
|
||||
|
||||
# ~RtAudio() Destructors should not be listed
|
||||
|
||||
unsigned int getDeviceCount()
|
||||
DeviceInfo getDeviceInfo(unsigned int device)
|
||||
unsigned int getDefaultOutputDevice()
|
||||
unsigned int getDefaultInputDevice()
|
||||
void openStream(StreamParameters* outputParameters,
|
||||
StreamParameters* intputParameters,
|
||||
RtAudioFormat _format,
|
||||
unsigned int sampleRate,
|
||||
unsigned int* bufferFrames,
|
||||
RtAudioCallback callback,
|
||||
void* userData,
|
||||
void* StreamOptions,
|
||||
RtAudioErrorCallback) except +
|
||||
void closeStream()
|
||||
void startStream() except +
|
||||
void stopStream() except +
|
||||
void abortStream() except +
|
||||
bool isStreamOpen()
|
||||
bool isStreamRunning()
|
||||
double getStreamTime()
|
||||
void setStreamTime(double) except +
|
||||
long getStreamLatency()
|
||||
unsigned int getStreamSampleRate()
|
||||
void showWarnings(bool value)
|
||||
|
||||
|
||||
_formats_strkey = {
|
||||
'8-bit integers': (RTAUDIO_SINT8, 1, np.int8),
|
||||
'16-bit integers': (RTAUDIO_SINT16, 2, np.int16),
|
||||
'24-bit integers': (RTAUDIO_SINT24, 3),
|
||||
'32-bit integers': (RTAUDIO_SINT32, 4, np.int32),
|
||||
'32-bit floats': (RTAUDIO_FLOAT32, 4, np.float32),
|
||||
'64-bit floats': (RTAUDIO_FLOAT64, 8, np.float64),
|
||||
}
|
||||
_formats_rtkey = {
|
||||
RTAUDIO_SINT8: ('8-bit integers', 1, cnp.NPY_INT8),
|
||||
RTAUDIO_SINT16: ('16-bit integers', 2, cnp.NPY_INT16),
|
||||
RTAUDIO_SINT24: ('24-bit integers', 3),
|
||||
RTAUDIO_SINT32: ('32-bit integers', 4, cnp.NPY_INT32),
|
||||
RTAUDIO_FLOAT32: ('32-bit floats', 4, cnp.NPY_FLOAT32),
|
||||
RTAUDIO_FLOAT64: ('64-bit floats', 8, cnp.NPY_FLOAT64),
|
||||
}
|
||||
|
||||
def get_numpy_dtype_from_format_string(format_string):
|
||||
return _formats_strkey[format_string][-1]
|
||||
def get_sampwidth_from_format_string(format_string):
|
||||
return _formats_strkey[format_string][-2]
|
||||
|
||||
|
||||
# It took me quite a long time to fully understand Cython's idiosyncrasies
|
||||
# concerning C(++) callbacks, the GIL and passing Python objects as pointers
|
||||
# into C(++) functions. But finally, here it is!
|
||||
|
||||
# cdef void fromNPYToBuffer(cnp.ndarray arr,
|
||||
# void* buf):
|
||||
# """
|
||||
# Copy a Python numpy array over to a buffer
|
||||
# No checks, just memcpy! Careful!
|
||||
# """
|
||||
# memcpy(buf, arr.data, arr.size*arr.itemsize)
|
||||
|
||||
|
||||
|
||||
ctypedef struct PyStreamData:
|
||||
PyObject* pyCallback
|
||||
RtAudioFormat sampleformat
|
||||
|
||||
# Flag used to pass the stopThread.
|
||||
atomic[bool] stopThread
|
||||
|
||||
# Number of frames per block
|
||||
unsigned nFramesPerBlock
|
||||
# Number of bytes per channel
|
||||
unsigned int nBytesPerChan
|
||||
|
||||
# Number of blocks to delay the output before adding to the input
|
||||
unsigned int outputDelayBlocks
|
||||
|
||||
# The structures as used by RtAudio
|
||||
cppRtAudio.StreamParameters inputParams
|
||||
cppRtAudio.StreamParameters outputParams
|
||||
|
||||
bool* inputChannelsEnabled
|
||||
bool* outputChannelsEnabled
|
||||
|
||||
unsigned ninputchannels_forwarded
|
||||
unsigned noutputchannels_forwarded
|
||||
|
||||
# If these queue pointers are NULL, it means the stream does not have an
|
||||
# input, or output.
|
||||
SafeQueue[void*] *outputDelayQueue
|
||||
SafeQueue[void*] *inputQueue
|
||||
SafeQueue[void*] *outputQueue
|
||||
CPPThread[void*, void (*)(void*)] *thread
|
||||
|
||||
|
||||
cdef int audioCallback(void* outputbuffer,
|
||||
void* inputbuffer,
|
||||
unsigned int nFramesPerBlock,
|
||||
double streamTime,
|
||||
RtAudioStreamStatus status,
|
||||
void* userData) nogil:
|
||||
"""
|
||||
Calls the Python callback function and converts data
|
||||
|
||||
"""
|
||||
cdef:
|
||||
int rval = 0
|
||||
PyStreamData* stream
|
||||
void* outputbuffercpy = NULL
|
||||
void* inputbuffercpy = NULL
|
||||
unsigned j, i
|
||||
unsigned bytesperchan
|
||||
unsigned noutputchannels_forwarded, ninputchannels_forwarded
|
||||
bint ch_en
|
||||
|
||||
stream = <PyStreamData*>(userData)
|
||||
bytesperchan = stream.nBytesPerChan
|
||||
ninputchannels_forwarded = stream.ninputchannels_forwarded
|
||||
noutputchannels_forwarded = stream.noutputchannels_forwarded
|
||||
|
||||
# with gil:
|
||||
# print(f'bytesperchan: {bytesperchan}')
|
||||
# print(f'ninputchannels_forwarded:: {ninputchannels_forwarded}')
|
||||
# print(f'noutputchannels_forwarded:: {noutputchannels_forwarded}')
|
||||
# fprintf(stderr, "Stream heartbeat...\n")
|
||||
|
||||
|
||||
# Returning 2 means aborting the stream immediately
|
||||
if status == RTAUDIO_INPUT_OVERFLOW:
|
||||
fprintf(stderr, 'Input overflow.\n')
|
||||
stream.stopThread.store(True)
|
||||
return 2
|
||||
elif status == RTAUDIO_OUTPUT_UNDERFLOW:
|
||||
fprintf(stderr, 'Output underflow.\n')
|
||||
# stream.stopThread.store(True)
|
||||
return 0
|
||||
|
||||
if nFramesPerBlock != stream.nFramesPerBlock:
|
||||
printf('Number of frames mismath in callback data!\n')
|
||||
stream.stopThread.store(True)
|
||||
return 2
|
||||
|
||||
if inputbuffer:
|
||||
# fprintf(stderr, "enter copying input buffer code\n")
|
||||
# with gil:
|
||||
# assert stream.inputQueue is not NULL
|
||||
inputbuffercpy = malloc(bytesperchan*ninputchannels_forwarded)
|
||||
if not inputbuffercpy:
|
||||
fprintf(stderr, "Error allocating buffer\n")
|
||||
return 2
|
||||
|
||||
if stream.outputDelayQueue:
|
||||
if stream.outputDelayQueue.size() > stream.outputDelayBlocks:
|
||||
outputbuffercpy = stream.outputDelayQueue.dequeue()
|
||||
memcpy(inputbuffercpy, outputbuffercpy,
|
||||
bytesperchan*noutputchannels_forwarded)
|
||||
|
||||
# Cleanup buffer
|
||||
free(outputbuffercpy)
|
||||
outputbuffercpy = NULL
|
||||
else:
|
||||
memset(inputbuffercpy, 0,
|
||||
bytesperchan*noutputchannels_forwarded)
|
||||
|
||||
# Starting channel for copying input channels according to the channel
|
||||
# map
|
||||
j = stream.noutputchannels_forwarded
|
||||
else:
|
||||
j = 0
|
||||
i = 0
|
||||
for i in range(stream.inputParams.nChannels):
|
||||
ch_en = stream.inputChannelsEnabled[i]
|
||||
if ch_en:
|
||||
copyChannel(inputbuffercpy, inputbuffer, bytesperchan, j, i)
|
||||
j+=1
|
||||
i+=1
|
||||
|
||||
stream.inputQueue.enqueue(inputbuffercpy)
|
||||
|
||||
if outputbuffer:
|
||||
# with gil:
|
||||
# assert stream.outputQueue
|
||||
# fprintf(stderr, "enter copying output buffer code\n")
|
||||
if stream.outputQueue.empty():
|
||||
fprintf(stderr, 'Stream output buffer underflow, zero-ing buffer...\n')
|
||||
memset(outputbuffer, 0, stream.outputParams.nChannels*bytesperchan)
|
||||
else:
|
||||
outputbuffercpy = stream.outputQueue.dequeue()
|
||||
# fprintf(stderr, 'Copying data to stream output buffer...\n')
|
||||
j = 0
|
||||
i = 0
|
||||
for i in range(stream.outputParams.nChannels):
|
||||
ch_en = stream.outputChannelsEnabled[i]
|
||||
if ch_en:
|
||||
copyChannel(outputbuffer, outputbuffercpy, bytesperchan, i, j)
|
||||
j+=1
|
||||
else:
|
||||
# If channel is not enabled, we set the data to zero
|
||||
memset(<void*> &((<char*> outputbuffer)[bytesperchan*i]), 0, bytesperchan)
|
||||
pass
|
||||
i+=1
|
||||
|
||||
if stream.outputDelayQueue:
|
||||
# fprintf(stderr, "Adding to delay queue\n")
|
||||
stream.outputDelayQueue.enqueue(outputbuffercpy)
|
||||
else:
|
||||
free(outputbuffercpy)
|
||||
|
||||
outputbuffercpy = NULL
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
cdef void audioCallbackPythonThreadFunction(void* voidstream) nogil:
|
||||
cdef:
|
||||
PyStreamData* stream
|
||||
cnp.NPY_TYPES npy_format
|
||||
void* inputbuffer = NULL
|
||||
void* outputbuffer = NULL
|
||||
unsigned noutputchannels_forwarded
|
||||
unsigned ninputchannels_forwarded
|
||||
unsigned nBytesPerChan
|
||||
unsigned nFramesPerBlock
|
||||
|
||||
stream = <PyStreamData*> voidstream
|
||||
ninputchannels_forwarded = stream.ninputchannels_forwarded
|
||||
noutputchannels_forwarded = stream.noutputchannels_forwarded
|
||||
nBytesPerChan = stream.nBytesPerChan
|
||||
nFramesPerBlock = stream.nFramesPerBlock
|
||||
|
||||
with gil:
|
||||
npy_format = _formats_rtkey[stream.sampleformat][2]
|
||||
callback = <object> stream.pyCallback
|
||||
print(f'noutputchannels_forwarded: {noutputchannels_forwarded}')
|
||||
print(f'ninputchannels_forwarded: {ninputchannels_forwarded}')
|
||||
print(f'nBytesPerChan: {nBytesPerChan}')
|
||||
print(f'nFramesPerBlock: {nFramesPerBlock}')
|
||||
|
||||
while True:
|
||||
if stream.stopThread.load() == True:
|
||||
printf('Stopping thread...\n')
|
||||
return
|
||||
|
||||
if stream.inputQueue:
|
||||
# fprintf(stderr, "Waiting on input queue\n")
|
||||
inputbuffer = stream.inputQueue.dequeue()
|
||||
if not inputbuffer:
|
||||
printf('Stopping thread...\n')
|
||||
return
|
||||
|
||||
if stream.outputQueue:
|
||||
# fprintf(stderr, 'Allocating output buffer...\n')
|
||||
outputbuffer = malloc(nBytesPerChan*noutputchannels_forwarded)
|
||||
# memset(outputbuffer, 0, nBytesPerChan*noutputchannels_forwarded)
|
||||
|
||||
with gil:
|
||||
|
||||
# Obtain stream information
|
||||
npy_input = None
|
||||
npy_output = None
|
||||
|
||||
if stream.inputQueue:
|
||||
# assert(inputbuffer)
|
||||
try:
|
||||
# print(f'========ninputchannels_forwarded: {ninputchannels_forwarded}')
|
||||
# print(f'========nFramesPerBlock: {nFramesPerBlock}')
|
||||
# print(f'========npy_format: {npy_format}')
|
||||
|
||||
npy_input = <object> data_to_ndarray(
|
||||
inputbuffer,
|
||||
nFramesPerBlock,
|
||||
ninputchannels_forwarded,
|
||||
npy_format,
|
||||
True, # Do transfer ownership
|
||||
True) # F-contiguous is True: data is Fortran-cont.
|
||||
# fprintf(stderr, "Copying array...\n")
|
||||
# fprintf(stderr, "End Copying array...\n")
|
||||
|
||||
except Exception as e:
|
||||
print('exception in cython callback for audio input: ', str(e))
|
||||
return
|
||||
|
||||
if stream.outputQueue:
|
||||
# fprintf(stderr, 'Copying output buffer to Numpy...\n')
|
||||
try:
|
||||
npy_output = <object> data_to_ndarray(
|
||||
outputbuffer,
|
||||
nFramesPerBlock,
|
||||
noutputchannels_forwarded,
|
||||
npy_format,
|
||||
False, # Do not transfer ownership
|
||||
True) # F-contiguous
|
||||
|
||||
except Exception as e:
|
||||
print('exception in Cython callback for audio output: ', str(e))
|
||||
return
|
||||
|
||||
try:
|
||||
# fprintf(stderr, "Python callback...\n")
|
||||
rval = callback(npy_input,
|
||||
npy_output,
|
||||
nFramesPerBlock,
|
||||
)
|
||||
# fprintf(stderr, "Return from Python callback...\n")
|
||||
|
||||
except Exception as e:
|
||||
print('Exception in Cython callback: ', str(e))
|
||||
return
|
||||
|
||||
if stream.outputQueue:
|
||||
# fprintf(stderr, 'Enqueuing output buffer...\n')
|
||||
|
||||
stream.outputQueue.enqueue(outputbuffer)
|
||||
if not stream.inputQueue:
|
||||
# fprintf(stderr, 'No input queue!\n')
|
||||
while stream.outputQueue.size() > 10 and not stream.stopThread.load():
|
||||
# printf('Sleeping...\n')
|
||||
# No input queue to wait on, so we relax a bit here.
|
||||
CPPsleep_ms(1);
|
||||
|
||||
# Outputbuffer is free'ed by the audiothread, so should not be touched
|
||||
# here.
|
||||
outputbuffer = NULL
|
||||
|
||||
# Inputbuffer memory is owned by Numpy, so should not be free'ed
|
||||
inputbuffer = NULL
|
||||
|
||||
|
||||
cdef void errorCallback(RtAudioError.Type _type,const string& errortxt) nogil:
|
||||
fprintf(stderr, 'RtAudio error callback called: ')
|
||||
fprintf(stderr, errortxt.c_str())
|
||||
fprintf(stderr, '\n')
|
||||
|
||||
|
||||
cdef class RtAudio:
|
||||
cdef:
|
||||
cppRtAudio* _rtaudio
|
||||
PyStreamData* sd
|
||||
int api
|
||||
|
||||
def __cinit__(self, pyapi):
|
||||
if pyapi.apiname != 'RtAudio':
|
||||
raise RuntimeError('RtAudio constructor called with invalid Api instance')
|
||||
cdef:
|
||||
cppRtAudio.Api api = <cppRtAudio.Api> pyapi.internalnr
|
||||
|
||||
self._rtaudio = new cppRtAudio(api)
|
||||
self.sd = NULL
|
||||
self._rtaudio.showWarnings(True)
|
||||
self.api = api
|
||||
|
||||
def __dealloc__(self):
|
||||
if self.sd is not NULL:
|
||||
# fprintf(stderr, 'Force closing stream...')
|
||||
if self._rtaudio.isStreamRunning():
|
||||
self._rtaudio.stopStream()
|
||||
self._rtaudio.closeStream()
|
||||
|
||||
self.cleanupStream(self.sd)
|
||||
self.sd = NULL
|
||||
del self._rtaudio
|
||||
|
||||
@staticmethod
|
||||
def getApis():
|
||||
cdef:
|
||||
vector[cppRtAudio.Api] apis
|
||||
cppRtAudio.getCompiledApi(apis)
|
||||
|
||||
apilist = []
|
||||
for api in apis:
|
||||
apilist.append(
|
||||
DAQApi(
|
||||
backendname= 'RtAudio',
|
||||
apiname = cppRtAudio.getApiName(api).decode('utf-8'),
|
||||
internalnr=<int> api))
|
||||
|
||||
return apilist
|
||||
|
||||
cpdef unsigned int getDefaultOutputDevice(self):
|
||||
return self._rtaudio.getDefaultOutputDevice()
|
||||
|
||||
cpdef unsigned int getDefaultInputDevice(self):
|
||||
return self._rtaudio.getDefaultInputDevice()
|
||||
|
||||
def getDeviceInfo(self):
|
||||
"""
|
||||
Return device information of the current device
|
||||
"""
|
||||
sampleformats = []
|
||||
|
||||
cdef:
|
||||
cppRtAudio.DeviceInfo devinfo
|
||||
unsigned devcount, i
|
||||
|
||||
devinfo_py = []
|
||||
|
||||
devcount = self._rtaudio.getDeviceCount()
|
||||
|
||||
for i in range(devcount):
|
||||
|
||||
devinfo = self._rtaudio.getDeviceInfo(i)
|
||||
|
||||
nf = devinfo.nativeFormats
|
||||
for format_ in [ RTAUDIO_SINT8, RTAUDIO_SINT16, RTAUDIO_SINT24,
|
||||
RTAUDIO_SINT32, RTAUDIO_FLOAT32, RTAUDIO_FLOAT64]:
|
||||
if nf & format_:
|
||||
sampleformats.append(_formats_rtkey[format_][0])
|
||||
|
||||
devinfo_py.append(DeviceInfo(
|
||||
api = self.api,
|
||||
index = i,
|
||||
probed = devinfo.probed,
|
||||
name = devinfo.name.decode('utf-8'),
|
||||
outputchannels = devinfo.outputChannels,
|
||||
inputchannels = devinfo.inputChannels,
|
||||
duplexchannels = devinfo.duplexChannels,
|
||||
samplerates = devinfo.sampleRates,
|
||||
sampleformats = sampleformats,
|
||||
prefsamplerate = devinfo.preferredSampleRate))
|
||||
|
||||
return devinfo_py
|
||||
|
||||
@cython.nonecheck(True)
|
||||
def start(self,
|
||||
avstream
|
||||
):
|
||||
"""
|
||||
Opening a stream with specified parameters
|
||||
|
||||
Args:
|
||||
avstream: AvStream instance
|
||||
|
||||
Returns: None
|
||||
"""
|
||||
|
||||
if self.sd is not NULL:
|
||||
raise RuntimeError('Stream is already opened.')
|
||||
|
||||
daqconfig = avstream.daqconfig
|
||||
avtype = avstream.avtype
|
||||
device = avstream.device
|
||||
|
||||
cdef:
|
||||
bint duplex_mode = daqconfig.duplex_mode
|
||||
bint monitorOutput = daqconfig.monitor_gen
|
||||
unsigned int outputDelayBlocks = daqconfig.outputDelayBlocks
|
||||
cppRtAudio.StreamParameters *rtOutputParams_ptr = NULL
|
||||
cppRtAudio.StreamParameters *rtInputParams_ptr = NULL
|
||||
cppRtAudio.StreamOptions streamoptions
|
||||
size_t sw
|
||||
unsigned int nFramesPerBlock = int(daqconfig.nFramesPerBlock)
|
||||
int firstinputchannel, firstoutputchannel
|
||||
int lastinputchannel, lastoutputchannel
|
||||
unsigned int ninputchannels_forwarded=0
|
||||
unsigned int ninputchannels_rtaudio=0
|
||||
unsigned int noutputchannels_rtaudio=0
|
||||
unsigned int noutputchannels_forwarded=0
|
||||
unsigned int samplerate
|
||||
int i
|
||||
bint input_stream=False, output_stream=False
|
||||
bool* inputChannelsEnabled
|
||||
bool* outputChannelsEnabled
|
||||
|
||||
if daqconfig.nFramesPerBlock > 8192 or daqconfig.nFramesPerBlock < 512:
|
||||
raise ValueError('Invalid number of nFramesPerBlock')
|
||||
|
||||
if daqconfig.outputDelayBlocks < 0 or daqconfig.outputDelayBlocks > 10:
|
||||
raise ValueError('Invalid number of outputDelayBlocks')
|
||||
|
||||
try:
|
||||
|
||||
# Determine sample rate and sample format, determine whether we are an
|
||||
# input or an output stream, or both
|
||||
if avtype == AvType.audio_input or duplex_mode:
|
||||
# Here, we override the sample format in case of duplex mode.
|
||||
sampleformat = daqconfig.en_input_sample_format
|
||||
samplerate = int(daqconfig.en_input_rate)
|
||||
input_stream = True
|
||||
if duplex_mode:
|
||||
output_stream = True
|
||||
else:
|
||||
sampleformat = daqconfig.en_output_sample_format
|
||||
samplerate = int(daqconfig.en_output_rate)
|
||||
output_stream = True
|
||||
|
||||
print(f'Is input stream: {input_stream}')
|
||||
print(f'Is output stream: {output_stream}')
|
||||
|
||||
sw = get_sampwidth_from_format_string(sampleformat)
|
||||
print(f'samplewidth: {sw}')
|
||||
|
||||
# All set, allocate the stream!
|
||||
self.sd = <PyStreamData*> malloc(sizeof(PyStreamData))
|
||||
if self.sd == NULL:
|
||||
raise MemoryError('Could not allocate stream: memory error.')
|
||||
|
||||
self.sd.pyCallback = <PyObject*> avstream._audioCallback
|
||||
# Increase reference count to the callback
|
||||
Py_INCREF(<object> avstream._audioCallback)
|
||||
|
||||
self.sd.sampleformat = _formats_strkey[sampleformat][0]
|
||||
self.sd.stopThread.store(False)
|
||||
self.sd.inputQueue = NULL
|
||||
self.sd.outputQueue = NULL
|
||||
self.sd.outputDelayQueue = NULL
|
||||
|
||||
self.sd.thread = NULL
|
||||
|
||||
self.sd.outputDelayBlocks = outputDelayBlocks
|
||||
self.sd.ninputchannels_forwarded = 0
|
||||
self.sd.noutputchannels_forwarded = 0
|
||||
self.sd.inputChannelsEnabled = NULL
|
||||
self.sd.outputChannelsEnabled = NULL
|
||||
|
||||
# Create channel maps for input channels, set RtAudio input stream
|
||||
# parameters
|
||||
if input_stream:
|
||||
firstinputchannel = daqconfig.firstEnabledInputChannelNumber()
|
||||
lastinputchannel = daqconfig.lastEnabledInputChannelNumber()
|
||||
ninputchannels_rtaudio = lastinputchannel-firstinputchannel+1
|
||||
|
||||
# print(firstinputchannel)
|
||||
# print(lastinputchannel)
|
||||
# print(ninputchannels_rtaudio)
|
||||
|
||||
if lastinputchannel < 0 or ninputchannels_rtaudio < 1:
|
||||
raise ValueError('Not enough input channels selected')
|
||||
input_ch = daqconfig.input_channel_configs
|
||||
|
||||
inputChannelsEnabled = <bool*> malloc(sizeof(bool)*ninputchannels_rtaudio)
|
||||
self.sd.inputChannelsEnabled = inputChannelsEnabled
|
||||
|
||||
for i in range(firstinputchannel, lastinputchannel+1):
|
||||
ch_en = input_ch[i].channel_enabled
|
||||
if ch_en:
|
||||
ninputchannels_forwarded += 1
|
||||
inputChannelsEnabled[i] = ch_en
|
||||
|
||||
rtInputParams_ptr = &self.sd.inputParams
|
||||
rtInputParams_ptr.deviceId = device.index
|
||||
rtInputParams_ptr.nChannels = ninputchannels_rtaudio
|
||||
rtInputParams_ptr.firstChannel = firstinputchannel
|
||||
|
||||
self.sd.inputQueue = new SafeQueue[void*]()
|
||||
self.sd.ninputchannels_forwarded = ninputchannels_forwarded
|
||||
|
||||
|
||||
# Create channel maps for output channels
|
||||
if output_stream:
|
||||
firstoutputchannel = daqconfig.firstEnabledOutputChannelNumber()
|
||||
lastoutputchannel = daqconfig.lastEnabledOutputChannelNumber()
|
||||
noutputchannels_rtaudio = lastoutputchannel-firstoutputchannel+1
|
||||
|
||||
# print(firstoutputchannel)
|
||||
# print(lastoutputchannel)
|
||||
# print(noutputchannels_rtaudio)
|
||||
|
||||
if lastoutputchannel < 0 or noutputchannels_rtaudio < 1:
|
||||
raise ValueError('Not enough output channels selected')
|
||||
output_ch = daqconfig.output_channel_configs
|
||||
|
||||
outputChannelsEnabled = <bool*> malloc(sizeof(bool)*noutputchannels_rtaudio)
|
||||
self.sd.outputChannelsEnabled = outputChannelsEnabled
|
||||
for i in range(firstoutputchannel, lastoutputchannel+1):
|
||||
ch_en = output_ch[i].channel_enabled
|
||||
if ch_en:
|
||||
noutputchannels_forwarded += 1
|
||||
outputChannelsEnabled[i] = ch_en
|
||||
|
||||
rtOutputParams_ptr = &self.sd.outputParams
|
||||
rtOutputParams_ptr.deviceId = device.index
|
||||
rtOutputParams_ptr.nChannels = noutputchannels_rtaudio
|
||||
rtOutputParams_ptr.firstChannel = firstoutputchannel
|
||||
|
||||
self.sd.outputQueue = new SafeQueue[void*]()
|
||||
self.sd.noutputchannels_forwarded = noutputchannels_forwarded
|
||||
|
||||
if monitorOutput and duplex_mode:
|
||||
self.sd.outputDelayQueue = new SafeQueue[void*]()
|
||||
self.sd.ninputchannels_forwarded += noutputchannels_forwarded
|
||||
|
||||
|
||||
streamoptions.flags = RTAUDIO_HOG_DEVICE
|
||||
streamoptions.flags |= RTAUDIO_NONINTERLEAVED
|
||||
streamoptions.numberOfBuffers = 4
|
||||
streamoptions.streamName = "LASP Audio stream".encode('utf-8')
|
||||
streamoptions.priority = 1
|
||||
|
||||
self._rtaudio.openStream(rtOutputParams_ptr,
|
||||
rtInputParams_ptr,
|
||||
_formats_strkey[sampleformat][0],
|
||||
samplerate,
|
||||
&nFramesPerBlock,
|
||||
audioCallback,
|
||||
<void*> self.sd,
|
||||
&streamoptions, # Stream options
|
||||
errorCallback # Error callback
|
||||
)
|
||||
|
||||
self.sd.nBytesPerChan = nFramesPerBlock*sw
|
||||
self.sd.nFramesPerBlock = nFramesPerBlock
|
||||
|
||||
self._rtaudio.startStream()
|
||||
|
||||
except Exception as e:
|
||||
print('Exception occured in stream opening: ', e)
|
||||
self.cleanupStream(self.sd)
|
||||
self.sd = NULL
|
||||
raise e
|
||||
|
||||
with nogil:
|
||||
self.sd.thread = new CPPThread[void*, void (*)(void*)](audioCallbackPythonThreadFunction,
|
||||
<void*> self.sd)
|
||||
# Allow it to start
|
||||
CPPsleep_ms(500)
|
||||
pass
|
||||
|
||||
return nFramesPerBlock, samplerate
|
||||
|
||||
def stop(self):
|
||||
if self.sd is NULL:
|
||||
raise RuntimeError('Stream is not running')
|
||||
|
||||
try:
|
||||
self._rtaudio.stopStream()
|
||||
self._rtaudio.closeStream()
|
||||
except Exception as e:
|
||||
print(e)
|
||||
pass
|
||||
self.cleanupStream(self.sd)
|
||||
self.sd = NULL
|
||||
|
||||
cdef cleanupStream(self, PyStreamData* stream):
|
||||
if stream == NULL:
|
||||
return
|
||||
|
||||
with nogil:
|
||||
if stream.thread:
|
||||
stream.stopThread.store(True)
|
||||
if stream.inputQueue:
|
||||
# If waiting in the input queue, hereby we let it run.
|
||||
stream.inputQueue.enqueue(NULL)
|
||||
# printf('Joining thread...\n')
|
||||
# HERE WE SHOULD RELEASE THE GIL, as exiting the thread function
|
||||
# will require the GIL, which is locked by this thread!
|
||||
stream.thread.join()
|
||||
# printf('Thread joined!\n')
|
||||
del stream.thread
|
||||
stream.thread = NULL
|
||||
|
||||
if stream.inputChannelsEnabled:
|
||||
free(stream.inputChannelsEnabled)
|
||||
if stream.outputChannelsEnabled:
|
||||
free(stream.outputChannelsEnabled)
|
||||
|
||||
if stream.outputQueue:
|
||||
while not stream.outputQueue.empty():
|
||||
free(stream.outputQueue.dequeue())
|
||||
del stream.outputQueue
|
||||
if stream.inputQueue:
|
||||
while not stream.inputQueue.empty():
|
||||
free(stream.inputQueue.dequeue())
|
||||
del stream.inputQueue
|
||||
if stream.outputDelayQueue:
|
||||
while not stream.outputDelayQueue.empty():
|
||||
free(stream.outputDelayQueue.dequeue())
|
||||
del stream.outputDelayQueue
|
||||
fprintf(stderr, "End cleanup stream queues...\n")
|
||||
|
||||
if stream.pyCallback:
|
||||
Py_DECREF(<object> stream.pyCallback)
|
||||
stream.pyCallback = NULL
|
||||
# fprintf(stderr, "End cleanup callback...\n")
|
||||
free(stream)
|
369
lasp/device/lasp_rtaudiodaq.cpp
Normal file
369
lasp/device/lasp_rtaudiodaq.cpp
Normal file
@ -0,0 +1,369 @@
|
||||
#if LASP_HAS_RTAUDIO == 1
|
||||
#include "lasp_rtaudiodaq.h"
|
||||
#include <RtAudio.h>
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <thread>
|
||||
|
||||
using std::cerr;
|
||||
using std::endl;
|
||||
using std::atomic;
|
||||
|
||||
void fillRtAudioDeviceInfo(vector<DeviceInfo> &devinfolist) {
|
||||
|
||||
vector<RtAudio::Api> apis;
|
||||
RtAudio::getCompiledApi(apis);
|
||||
|
||||
for (auto api : apis) {
|
||||
RtAudio rtaudio(api);
|
||||
us count = rtaudio.getDeviceCount();
|
||||
for (us devno = 0; devno < count; devno++) {
|
||||
|
||||
RtAudio::DeviceInfo devinfo = rtaudio.getDeviceInfo(devno);
|
||||
if (!devinfo.probed) {
|
||||
// Device capabilities not successfully probed. Continue to next
|
||||
continue;
|
||||
}
|
||||
DeviceInfo d;
|
||||
switch (api) {
|
||||
case RtAudio::LINUX_ALSA:
|
||||
d.api = rtaudioAlsaApi;
|
||||
break;
|
||||
case RtAudio::LINUX_PULSE:
|
||||
d.api = rtaudioPulseaudioApi;
|
||||
break;
|
||||
case RtAudio::WINDOWS_WASAPI:
|
||||
d.api = rtaudioWasapiApi;
|
||||
break;
|
||||
case RtAudio::WINDOWS_DS:
|
||||
d.api = rtaudioDsApi;
|
||||
break;
|
||||
case RtAudio::WINDOWS_ASIO:
|
||||
d.api = rtaudioAsioApi;
|
||||
break;
|
||||
default:
|
||||
cerr << "Not implemented RtAudio API, skipping." << endl;
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
|
||||
d.device_name = devinfo.name;
|
||||
d.api_specific_devindex = devno;
|
||||
|
||||
for (us j = 0; j < devinfo.sampleRates.size(); j++) {
|
||||
us rate = devinfo.sampleRates[j];
|
||||
d.availableSampleRates.push_back((double)rate);
|
||||
if (devinfo.preferredSampleRate == rate) {
|
||||
d.prefSampleRateIndex = j;
|
||||
}
|
||||
}
|
||||
|
||||
d.noutchannels = devinfo.outputChannels;
|
||||
d.ninchannels = devinfo.inputChannels;
|
||||
|
||||
d.availableInputRanges = {1.0};
|
||||
|
||||
RtAudioFormat formats = devinfo.nativeFormats;
|
||||
if (formats & RTAUDIO_SINT8) {
|
||||
d.availableDataTypes.push_back(dtype_int8);
|
||||
}
|
||||
if (formats & RTAUDIO_SINT16) {
|
||||
d.availableDataTypes.push_back(dtype_int16);
|
||||
}
|
||||
if (formats & RTAUDIO_SINT32) {
|
||||
d.availableDataTypes.push_back(dtype_int24);
|
||||
}
|
||||
if (formats & RTAUDIO_SINT32) {
|
||||
d.availableDataTypes.push_back(dtype_fl32);
|
||||
}
|
||||
if (formats & RTAUDIO_FLOAT64) {
|
||||
d.availableDataTypes.push_back(dtype_fl64);
|
||||
}
|
||||
if (d.availableDataTypes.size() == 0) {
|
||||
std::cerr << "RtAudio: No data types found in device!" << endl;
|
||||
}
|
||||
|
||||
d.prefDataTypeIndex = d.availableDataTypes.size() - 1;
|
||||
|
||||
d.availableFramesPerBlock.push_back(512);
|
||||
d.availableFramesPerBlock.push_back(1024);
|
||||
d.availableFramesPerBlock.push_back(2048);
|
||||
d.availableFramesPerBlock.push_back(4096);
|
||||
d.availableFramesPerBlock.push_back(8192);
|
||||
d.prefFramesPerBlockIndex = 1;
|
||||
|
||||
devinfolist.push_back(d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int mycallback(void *outputBuffer, void *inputBuffer, unsigned int nFrames,
|
||||
double streamTime, RtAudioStreamStatus status, void *userData);
|
||||
|
||||
void myerrorcallback(RtAudioError::Type, const string &errorText);
|
||||
|
||||
class AudioDaq : public Daq {
|
||||
|
||||
SafeQueue<void *> *inqueue = NULL;
|
||||
SafeQueue<void *> *outqueue = NULL;
|
||||
SafeQueue<void *> *outDelayqueue = NULL;
|
||||
|
||||
RtAudio *rtaudio = NULL;
|
||||
RtAudio::StreamParameters *instreamparams = nullptr;
|
||||
RtAudio::StreamParameters *outstreamparams = nullptr;
|
||||
|
||||
us nFramesPerBlock;
|
||||
|
||||
public:
|
||||
AudioDaq(const DeviceInfo &devinfo, const DaqConfiguration &config)
|
||||
: Daq(devinfo, config) {
|
||||
|
||||
nFramesPerBlock = this->framesPerBlock();
|
||||
|
||||
if (neninchannels(false) > 0) {
|
||||
instreamparams = new RtAudio::StreamParameters();
|
||||
instreamparams->nChannels = getHighestInChannel() + 1;
|
||||
if (instreamparams->nChannels < 1) {
|
||||
throw runtime_error("Invalid input number of channels");
|
||||
}
|
||||
instreamparams->firstChannel = 0;
|
||||
instreamparams->deviceId = devinfo.api_specific_devindex;
|
||||
}
|
||||
|
||||
if (nenoutchannels() > 0) {
|
||||
outstreamparams = new RtAudio::StreamParameters();
|
||||
outstreamparams->nChannels = getHighestOutChannel() + 1;
|
||||
if (outstreamparams->nChannels < 1) {
|
||||
throw runtime_error("Invalid output number of channels");
|
||||
}
|
||||
outstreamparams->firstChannel = 0;
|
||||
outstreamparams->deviceId = devinfo.api_specific_devindex;
|
||||
}
|
||||
|
||||
RtAudio::StreamOptions streamoptions;
|
||||
streamoptions.flags = RTAUDIO_NONINTERLEAVED | RTAUDIO_HOG_DEVICE;
|
||||
|
||||
streamoptions.numberOfBuffers = 2;
|
||||
streamoptions.streamName = "RtAudio stream";
|
||||
streamoptions.priority = 0;
|
||||
|
||||
RtAudioFormat format;
|
||||
const DataType dtype = DataType();
|
||||
switch (dtype) {
|
||||
case dtype_fl32:
|
||||
format = RTAUDIO_FLOAT32;
|
||||
break;
|
||||
case dtype_fl64:
|
||||
format = RTAUDIO_FLOAT64;
|
||||
break;
|
||||
case dtype_int8:
|
||||
format = RTAUDIO_SINT8;
|
||||
break;
|
||||
case dtype_int16:
|
||||
format = RTAUDIO_SINT16;
|
||||
break;
|
||||
case dtype_int32:
|
||||
format = RTAUDIO_SINT32;
|
||||
break;
|
||||
default:
|
||||
throw runtime_error("Invalid data type");
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
rtaudio = new RtAudio((RtAudio::Api)devinfo.api.api_specific_subcode);
|
||||
if (!rtaudio) {
|
||||
throw runtime_error("RtAudio allocation failed");
|
||||
}
|
||||
rtaudio->openStream(outstreamparams, instreamparams, format,
|
||||
(us)samplerate(), (unsigned *)&nFramesPerBlock,
|
||||
&mycallback, (void *)this, &streamoptions,
|
||||
&myerrorcallback);
|
||||
} catch (RtAudioError &e) {
|
||||
if (rtaudio)
|
||||
delete rtaudio;
|
||||
if (instreamparams)
|
||||
delete instreamparams;
|
||||
if (outstreamparams)
|
||||
delete outstreamparams;
|
||||
throw;
|
||||
}
|
||||
if (monitorOutput) {
|
||||
outDelayqueue = new SafeQueue<void *>();
|
||||
}
|
||||
}
|
||||
|
||||
friend int mycallback(void *outputBuffer, void *inputBuffer,
|
||||
unsigned int nFrames, double streamTime,
|
||||
RtAudioStreamStatus status, void *userData);
|
||||
|
||||
void start(SafeQueue<void *> *inqueue, SafeQueue<void *> *outqueue) {
|
||||
this->inqueue = inqueue;
|
||||
this->outqueue = outqueue;
|
||||
if (monitorOutput) {
|
||||
this->outDelayqueue = new SafeQueue<void *>();
|
||||
}
|
||||
|
||||
if (isRunning()) {
|
||||
throw runtime_error("Stream already running");
|
||||
}
|
||||
|
||||
if (neninchannels(false) > 0 && !inqueue) {
|
||||
throw runtime_error("inqueue argument not given");
|
||||
}
|
||||
if (nenoutchannels() > 0 && !outqueue) {
|
||||
throw runtime_error("outqueue argument not given");
|
||||
}
|
||||
assert(rtaudio);
|
||||
rtaudio->startStream();
|
||||
}
|
||||
|
||||
void stop() {
|
||||
|
||||
if (!isRunning()) {
|
||||
cerr << "Stream is already stopped" << endl;
|
||||
} else {
|
||||
assert(rtaudio);
|
||||
rtaudio->stopStream();
|
||||
}
|
||||
if (inqueue) {
|
||||
inqueue = nullptr;
|
||||
}
|
||||
if (outqueue) {
|
||||
outqueue = nullptr;
|
||||
}
|
||||
if (outDelayqueue) {
|
||||
delete outDelayqueue;
|
||||
outDelayqueue = nullptr;
|
||||
}
|
||||
}
|
||||
bool isRunning() const { return (rtaudio && rtaudio->isStreamRunning()); }
|
||||
|
||||
~AudioDaq() {
|
||||
assert(rtaudio);
|
||||
if (isRunning()) {
|
||||
stop();
|
||||
}
|
||||
if (rtaudio->isStreamOpen()) {
|
||||
rtaudio->closeStream();
|
||||
}
|
||||
|
||||
if (rtaudio)
|
||||
delete rtaudio;
|
||||
if (outDelayqueue)
|
||||
delete outDelayqueue;
|
||||
if (instreamparams)
|
||||
delete instreamparams;
|
||||
if (outstreamparams)
|
||||
delete outstreamparams;
|
||||
}
|
||||
};
|
||||
|
||||
Daq *createRtAudioDevice(const DeviceInfo &devinfo,
|
||||
const DaqConfiguration &config) {
|
||||
|
||||
AudioDaq *daq = NULL;
|
||||
|
||||
try {
|
||||
daq = new AudioDaq(devinfo, config);
|
||||
|
||||
} catch (runtime_error &e) {
|
||||
if (daq)
|
||||
delete daq;
|
||||
throw;
|
||||
}
|
||||
return daq;
|
||||
}
|
||||
|
||||
int mycallback(void *outputBuffervoid, void *inputBuffervoid,
|
||||
unsigned int nFrames, double streamTime,
|
||||
RtAudioStreamStatus status, void *userData) {
|
||||
|
||||
u_int8_t *inputBuffer = (u_int8_t *)inputBuffervoid;
|
||||
u_int8_t *outputBuffer = (u_int8_t *)outputBuffervoid;
|
||||
|
||||
AudioDaq *daq = (AudioDaq *)userData;
|
||||
DataType dtype = daq->dataType();
|
||||
us neninchannels_inc_mon = daq->neninchannels();
|
||||
us nenoutchannels = daq->nenoutchannels();
|
||||
|
||||
bool monitorOutput = daq->monitorOutput;
|
||||
us bytesperchan = dtype_map.at(dtype).sw * nFrames;
|
||||
us monitorOffset = ((us)monitorOutput) * bytesperchan;
|
||||
|
||||
SafeQueue<void *> *inqueue = daq->inqueue;
|
||||
SafeQueue<void *> *outqueue = daq->outqueue;
|
||||
SafeQueue<void *> *outDelayqueue = daq->outDelayqueue;
|
||||
|
||||
const boolvec &eninchannels = daq->eninchannels;
|
||||
const boolvec &enoutchannels = daq->enoutchannels;
|
||||
|
||||
if (inputBuffer || monitorOutput) {
|
||||
|
||||
u_int8_t *inbuffercpy =
|
||||
(u_int8_t *)malloc(bytesperchan * neninchannels_inc_mon);
|
||||
if (inputBuffer) {
|
||||
us j = 0; // OUR buffer channel counter
|
||||
us i = 0; // RtAudio channel counter
|
||||
for (int ch = daq->getLowestInChannel(); ch <= daq->getHighestInChannel();
|
||||
ch++) {
|
||||
if (eninchannels[ch]) {
|
||||
memcpy(&(inbuffercpy[monitorOffset + j * bytesperchan]),
|
||||
&(inputBuffer[i * bytesperchan]), bytesperchan);
|
||||
j++;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
if (monitorOutput) {
|
||||
assert(outDelayqueue);
|
||||
|
||||
if (!daq->outDelayqueue->empty()) {
|
||||
void *dat = daq->outDelayqueue->dequeue();
|
||||
memcpy((void *)inbuffercpy, dat, bytesperchan);
|
||||
free(dat);
|
||||
} else {
|
||||
cerr << "Warning: output delay queue appears empty!" << endl;
|
||||
memset(inbuffercpy, 0, bytesperchan);
|
||||
}
|
||||
}
|
||||
assert(inqueue);
|
||||
inqueue->enqueue(inbuffercpy);
|
||||
}
|
||||
|
||||
if (outputBuffer) {
|
||||
assert(outqueue);
|
||||
if (!outqueue->empty()) {
|
||||
u_int8_t *outbuffercpy = (u_int8_t *)outqueue->dequeue();
|
||||
us j = 0; // OUR buffer channel counter
|
||||
us i = 0; // RtAudio channel counter
|
||||
for (us ch = 0; ch <= daq->getHighestOutChannel(); ch++) {
|
||||
/* cerr << "Copying from queue... " << endl; */
|
||||
if (enoutchannels[ch]) {
|
||||
memcpy(&(outputBuffer[i * bytesperchan]),
|
||||
&(outbuffercpy[j * bytesperchan]), bytesperchan);
|
||||
j++;
|
||||
} else {
|
||||
/* cerr << "unused output channel in list" << endl; */
|
||||
memset(&(outputBuffer[i * bytesperchan]), 0, bytesperchan);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (!monitorOutput) {
|
||||
free(outbuffercpy);
|
||||
} else {
|
||||
assert(outDelayqueue);
|
||||
outDelayqueue->enqueue((void *)outbuffercpy);
|
||||
}
|
||||
} else {
|
||||
cerr << "RtAudio backend: stream output buffer underflow!" << endl;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
void myerrorcallback(RtAudioError::Type, const string &errorText) {
|
||||
cerr << errorText << endl;
|
||||
}
|
||||
#endif // LASP_HAS_RTAUDIO == 1
|
14
lasp/device/lasp_rtaudiodaq.h
Normal file
14
lasp/device/lasp_rtaudiodaq.h
Normal file
@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
#include "lasp_daq.h"
|
||||
#include <memory>
|
||||
|
||||
std::unique_ptr<Daq> createRtAudioDevice(const DeviceInfo& devinfo,
|
||||
const DaqConfiguration& config);
|
||||
|
||||
/**
|
||||
* @brief Append RtAudio backend devices to the list
|
||||
*
|
||||
* @param devinfolist List to append to
|
||||
*/
|
||||
void fillRtAudioDeviceInfo(std::vector<DeviceInfo> &devinfolist);
|
||||
|
550
lasp/device/lasp_uldaq.cpp
Normal file
550
lasp/device/lasp_uldaq.cpp
Normal file
@ -0,0 +1,550 @@
|
||||
#include "lasp_uldaq.h"
|
||||
#include "lasp_daqconfig.h"
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
#include <gsl/gsl-lite.hpp>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <span>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
#include <uldaq.h>
|
||||
#include <vector>
|
||||
|
||||
using namespace std::literals::chrono_literals;
|
||||
using std::atomic;
|
||||
using std::cerr;
|
||||
using std::endl;
|
||||
using std::runtime_error;
|
||||
|
||||
#include "debugtrace.hpp"
|
||||
DEBUGTRACE_VARIABLES;
|
||||
|
||||
const us MAX_DEV_COUNT_PER_API = 100;
|
||||
/**
|
||||
* @brief Reserve some space for an error message from UlDaq
|
||||
*/
|
||||
const us UL_ERR_MSG_LEN = 512;
|
||||
|
||||
/**
|
||||
* @brief Show the error to default error stream and return a string
|
||||
* corresponding to the error
|
||||
*
|
||||
* @param err Error string
|
||||
*/
|
||||
string showErr(UlError err) {
|
||||
string errstr;
|
||||
errstr.reserve(UL_ERR_MSG_LEN);
|
||||
if (err != ERR_NO_ERROR) {
|
||||
char errmsg[UL_ERR_MSG_LEN];
|
||||
errstr = "UlDaq API Error: ";
|
||||
ulGetErrMsg(err, errmsg);
|
||||
errstr += errmsg;
|
||||
std::cerr << errstr << std::endl;
|
||||
return errstr;
|
||||
}
|
||||
return errstr;
|
||||
}
|
||||
|
||||
class DT9837A : public Daq {
|
||||
|
||||
DaqDeviceHandle _handle = 0;
|
||||
std::mutex _daqmutex;
|
||||
|
||||
std::thread _thread;
|
||||
atomic<bool> _stopThread{false};
|
||||
|
||||
const us _nFramesPerBlock;
|
||||
|
||||
void threadFcn(std::optional<DaqCallback> inCallback,
|
||||
std::optional<DaqCallback> outcallback);
|
||||
|
||||
public:
|
||||
DT9837A(const DeviceInfo &devinfo, const DaqConfiguration &config);
|
||||
|
||||
~DT9837A() {
|
||||
UlError err;
|
||||
if (isRunning()) {
|
||||
stop();
|
||||
}
|
||||
|
||||
if (_handle) {
|
||||
err = ulDisconnectDaqDevice(_handle);
|
||||
showErr(err);
|
||||
err = ulReleaseDaqDevice(_handle);
|
||||
showErr(err);
|
||||
}
|
||||
}
|
||||
|
||||
bool isRunning() const override final { return _thread.joinable(); }
|
||||
virtual void start(std::optional<DaqCallback> inCallback,
|
||||
std::optional<DaqCallback> outCallback) override final;
|
||||
|
||||
void stop() override final {
|
||||
DEBUGTRACE_ENTER;
|
||||
if (!isRunning()) {
|
||||
throw runtime_error("No data acquisition running");
|
||||
}
|
||||
|
||||
_stopThread = true;
|
||||
if (_thread.joinable()) {
|
||||
_thread.join();
|
||||
}
|
||||
_stopThread = false;
|
||||
}
|
||||
friend class InBufHandler;
|
||||
friend class OutBufHandler;
|
||||
};
|
||||
|
||||
void DT9837A::start(std::optional<DaqCallback> inCallback,
|
||||
std::optional<DaqCallback> outCallback) {
|
||||
DEBUGTRACE_ENTER;
|
||||
if (isRunning()) {
|
||||
throw runtime_error("DAQ is already running");
|
||||
}
|
||||
if (neninchannels() > 0) {
|
||||
if (!inCallback)
|
||||
throw runtime_error("DAQ requires a callback for input data");
|
||||
}
|
||||
if (nenoutchannels() > 0) {
|
||||
if (!outCallback)
|
||||
throw runtime_error("DAQ requires a callback for output data");
|
||||
}
|
||||
assert(neninchannels() + nenoutchannels() > 0);
|
||||
_thread = std::thread(&DT9837A::threadFcn, this, inCallback, outCallback);
|
||||
}
|
||||
|
||||
class BufHandler {
|
||||
protected:
|
||||
DaqDeviceHandle _handle;
|
||||
const DataTypeDescriptor dtype_descr;
|
||||
us nchannels, nFramesPerBlock;
|
||||
DaqCallback cb;
|
||||
double samplerate;
|
||||
dvec buf;
|
||||
bool topenqueued, botenqueued;
|
||||
us increment = 0;
|
||||
us totalFramesCount = 0;
|
||||
long long buffer_mid_idx;
|
||||
|
||||
public:
|
||||
BufHandler(DaqDeviceHandle handle, const DataTypeDescriptor dtype_descr,
|
||||
const us nchannels, const us nFramesPerBlock, DaqCallback cb,
|
||||
const double samplerate)
|
||||
: _handle(handle), dtype_descr(dtype_descr), nchannels(nchannels),
|
||||
nFramesPerBlock(nFramesPerBlock), cb(cb), samplerate(samplerate),
|
||||
buf(2 * nchannels *
|
||||
nFramesPerBlock, // Watch the two here, the top and the bottom!
|
||||
0),
|
||||
buffer_mid_idx(nchannels * nFramesPerBlock) {
|
||||
assert(nchannels > 0);
|
||||
}
|
||||
};
|
||||
class InBufHandler : public BufHandler {
|
||||
bool monitorOutput;
|
||||
public:
|
||||
InBufHandler(DT9837A &daq, DaqCallback cb)
|
||||
: BufHandler(daq._handle, daq.dtypeDescr(), daq.neninchannels(),
|
||||
daq._nFramesPerBlock, cb, daq.samplerate())
|
||||
|
||||
{
|
||||
DEBUGTRACE_ENTER;
|
||||
assert(_handle != 0);
|
||||
|
||||
monitorOutput = daq.monitorOutput;
|
||||
|
||||
DaqInScanFlag inscanflags = DAQINSCAN_FF_DEFAULT;
|
||||
ScanOption scanoptions = SO_CONTINUOUS;
|
||||
UlError err = ERR_NO_ERROR;
|
||||
|
||||
std::vector<DaqInChanDescriptor> indescs;
|
||||
|
||||
boolvec eninchannels = daq.eninchannels;
|
||||
|
||||
// Initialize input, if any
|
||||
for (us chin = 0; chin < 4; chin++) {
|
||||
if (eninchannels[chin] == true) {
|
||||
DaqInChanDescriptor indesc;
|
||||
indesc.type = DAQI_ANALOG_SE;
|
||||
indesc.channel = chin;
|
||||
|
||||
double rangeval = daq.inputRangeForChannel(chin);
|
||||
Range rangenum;
|
||||
if (fabs(rangeval - 1.0) < 1e-8) {
|
||||
rangenum = BIP1VOLTS;
|
||||
} else if (fabs(rangeval - 10.0) < 1e-8) {
|
||||
rangenum = BIP10VOLTS;
|
||||
} else {
|
||||
std::cerr << "Fatal: input range value is invalid" << endl;
|
||||
return;
|
||||
}
|
||||
indesc.range = rangenum;
|
||||
indescs.push_back(indesc);
|
||||
}
|
||||
}
|
||||
// Overwrite last channel
|
||||
if (monitorOutput) {
|
||||
DaqInChanDescriptor indesc;
|
||||
indesc.type = DAQI_DAC;
|
||||
indesc.channel = 0;
|
||||
indesc.range = BIP10VOLTS;
|
||||
indescs.push_back(indesc);
|
||||
}
|
||||
assert(indescs.size() == nchannels);
|
||||
DEBUGTRACE_MESSAGE("Starting input scan");
|
||||
err = ulDaqInScan(_handle, indescs.data(), nchannels,
|
||||
2 * nFramesPerBlock, // Watch the 2 here!
|
||||
&samplerate, scanoptions, inscanflags, buf.data());
|
||||
if (err != ERR_NO_ERROR) {
|
||||
showErr(err);
|
||||
throw std::runtime_error("Could not start input DAQ");
|
||||
}
|
||||
|
||||
botenqueued = false;
|
||||
topenqueued = false;
|
||||
}
|
||||
|
||||
void operator()() {
|
||||
|
||||
ChannelView cv(nchannels);
|
||||
|
||||
auto runCallback = ([&](us totalOffset) {
|
||||
us monitoroffset = monitorOutput ? 1 : 0;
|
||||
for (us channel = monitoroffset; channel < (nchannels - monitoroffset);
|
||||
channel++) {
|
||||
cv[channel] =
|
||||
gsl::span(reinterpret_cast<uint8_t *>(
|
||||
&buf[totalOffset + channel * nFramesPerBlock]),
|
||||
nFramesPerBlock * sizeof(double));
|
||||
}
|
||||
if (monitorOutput) {
|
||||
|
||||
cv[0] = gsl::span(
|
||||
reinterpret_cast<uint8_t *>(
|
||||
&buf[totalOffset + (nchannels - 1) * nFramesPerBlock]),
|
||||
nFramesPerBlock * sizeof(double));
|
||||
}
|
||||
cb(cv, dtype_descr);
|
||||
});
|
||||
|
||||
|
||||
ScanStatus status;
|
||||
TransferStatus transferStatus;
|
||||
|
||||
UlError err = ulDaqInScanStatus(_handle, &status, &transferStatus);
|
||||
if (err != ERR_NO_ERROR) {
|
||||
showErr(err);
|
||||
return;
|
||||
}
|
||||
|
||||
increment = transferStatus.currentTotalCount - totalFramesCount;
|
||||
totalFramesCount += increment;
|
||||
|
||||
if (increment > nFramesPerBlock) {
|
||||
cerr << "Error: overrun for input of DAQ!" << endl;
|
||||
return;
|
||||
}
|
||||
assert(status == SS_RUNNING);
|
||||
|
||||
if (transferStatus.currentIndex < (long long)buffer_mid_idx) {
|
||||
topenqueued = false;
|
||||
if (!botenqueued) {
|
||||
runCallback(nchannels * nFramesPerBlock);
|
||||
botenqueued = true;
|
||||
}
|
||||
} else {
|
||||
botenqueued = false;
|
||||
if (!topenqueued) {
|
||||
runCallback(0);
|
||||
topenqueued = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
~InBufHandler() {
|
||||
// At exit of the function, stop scanning.
|
||||
DEBUGTRACE_ENTER;
|
||||
UlError err = ulDaqInScanStop(_handle);
|
||||
if (err != ERR_NO_ERROR) {
|
||||
showErr(err);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class OutBufHandler : public BufHandler {
|
||||
public:
|
||||
OutBufHandler(DT9837A &daq, DaqCallback cb)
|
||||
: BufHandler(daq._handle, daq.dtypeDescr(), daq.neninchannels(),
|
||||
daq._nFramesPerBlock, cb, daq.samplerate()) {
|
||||
|
||||
DEBUGTRACE_MESSAGE("Starting output scan");
|
||||
AOutScanFlag outscanflags = AOUTSCAN_FF_DEFAULT;
|
||||
ScanOption scanoptions = SO_CONTINUOUS;
|
||||
UlError err =
|
||||
ulAOutScan(_handle, 0, 0, BIP10VOLTS,
|
||||
2 * nFramesPerBlock, // Watch the 2 here!
|
||||
&samplerate, scanoptions, outscanflags, buf.data());
|
||||
if (err != ERR_NO_ERROR) {
|
||||
showErr(err);
|
||||
throw runtime_error("Unable to start output on DAQ");
|
||||
}
|
||||
|
||||
botenqueued = false, topenqueued = true;
|
||||
|
||||
// Run callback to first fill top part
|
||||
ChannelView cv{gsl::span(reinterpret_cast<uint8_t *>(&buf[0]),
|
||||
nFramesPerBlock * sizeof(double))};
|
||||
cb(cv, dtype_descr);
|
||||
}
|
||||
void operator()() {
|
||||
|
||||
assert(_handle != 0);
|
||||
|
||||
UlError err = ERR_NO_ERROR;
|
||||
|
||||
ScanStatus status;
|
||||
TransferStatus transferStatus;
|
||||
|
||||
err = ulAOutScanStatus(_handle, &status, &transferStatus);
|
||||
if (err != ERR_NO_ERROR) {
|
||||
showErr(err);
|
||||
return;
|
||||
}
|
||||
if (status != SS_RUNNING) {
|
||||
return;
|
||||
}
|
||||
increment = transferStatus.currentTotalCount - totalFramesCount;
|
||||
totalFramesCount += increment;
|
||||
|
||||
if (increment > nFramesPerBlock) {
|
||||
cerr << "Error: underrun for output of DAQ!" << endl;
|
||||
return;
|
||||
}
|
||||
|
||||
if (transferStatus.currentIndex < buffer_mid_idx) {
|
||||
topenqueued = false;
|
||||
if (!botenqueued) {
|
||||
|
||||
ChannelView cv{
|
||||
gsl::span(reinterpret_cast<uint8_t *>(&buf[buffer_mid_idx]),
|
||||
nFramesPerBlock * sizeof(double))};
|
||||
cb(cv, dtype_descr);
|
||||
botenqueued = true;
|
||||
}
|
||||
} else {
|
||||
botenqueued = false;
|
||||
if (!topenqueued) {
|
||||
|
||||
ChannelView cv{gsl::span(reinterpret_cast<uint8_t *>(&buf[0]),
|
||||
nFramesPerBlock * sizeof(double))};
|
||||
cb(cv, dtype_descr);
|
||||
|
||||
topenqueued = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
~OutBufHandler() {
|
||||
DEBUGTRACE_ENTER;
|
||||
UlError err = ulAOutScanStop(_handle);
|
||||
if (err != ERR_NO_ERROR) {
|
||||
showErr(err);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void DT9837A::threadFcn(std::optional<DaqCallback> inCallback,
|
||||
std::optional<DaqCallback> outCallback) {
|
||||
|
||||
DEBUGTRACE_ENTER;
|
||||
std::unique_ptr<InBufHandler> ibh;
|
||||
std::unique_ptr<OutBufHandler> obh;
|
||||
|
||||
if (neninchannels() > 0) {
|
||||
ibh = std::make_unique<InBufHandler>(*this, inCallback.value());
|
||||
}
|
||||
if (nenoutchannels() > 0) {
|
||||
obh = std::make_unique<OutBufHandler>(*this, outCallback.value());
|
||||
}
|
||||
|
||||
const double sleeptime =
|
||||
static_cast<double>(_nFramesPerBlock) / (16 * samplerate());
|
||||
const us sleeptime_us = static_cast<us>(sleeptime * 1e6);
|
||||
|
||||
while (!_stopThread) {
|
||||
if (ibh) {
|
||||
(*ibh)();
|
||||
}
|
||||
if (obh) {
|
||||
(*obh)();
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(sleeptime_us));
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Daq> createUlDaqDevice(const DeviceInfo &devinfo,
|
||||
const DaqConfiguration &config) {
|
||||
return std::make_unique<DT9837A>(devinfo, config);
|
||||
}
|
||||
|
||||
DT9837A::DT9837A(const DeviceInfo &devinfo, const DaqConfiguration &config)
|
||||
: Daq(devinfo, config),
|
||||
_nFramesPerBlock(availableFramesPerBlock.at(framesPerBlockIndex)) {
|
||||
|
||||
// Some sanity checks
|
||||
if (eninchannels.size() != 4) {
|
||||
throw runtime_error("Invalid length of enabled inChannels vector");
|
||||
}
|
||||
|
||||
if (enoutchannels.size() != 1) {
|
||||
throw runtime_error("Invalid length of enabled outChannels vector");
|
||||
}
|
||||
|
||||
if (_nFramesPerBlock < 24 || _nFramesPerBlock > 8192) {
|
||||
throw runtime_error("Unsensible number of samples per block chosen");
|
||||
}
|
||||
|
||||
if (samplerate() < 10000 || samplerate() > 51000) {
|
||||
throw runtime_error("Invalid sample rate");
|
||||
}
|
||||
|
||||
DaqDeviceDescriptor devdescriptors[MAX_DEV_COUNT_PER_API];
|
||||
DaqDeviceDescriptor descriptor;
|
||||
DaqDeviceInterface interfaceType = ANY_IFC;
|
||||
|
||||
UlError err;
|
||||
|
||||
us numdevs = MAX_DEV_COUNT_PER_API;
|
||||
err = ulGetDaqDeviceInventory(interfaceType, devdescriptors,
|
||||
(unsigned *)&numdevs);
|
||||
if (err != ERR_NO_ERROR) {
|
||||
throw runtime_error("Device inventarization failed");
|
||||
}
|
||||
|
||||
if ((us)api_specific_devindex >= numdevs) {
|
||||
throw runtime_error("Device number {deviceno} too high {err}. This could "
|
||||
"happen when the device is currently not connected");
|
||||
}
|
||||
|
||||
descriptor = devdescriptors[api_specific_devindex];
|
||||
|
||||
// get a handle to the DAQ device associated with the first descriptor
|
||||
_handle = ulCreateDaqDevice(descriptor);
|
||||
|
||||
if (_handle == 0) {
|
||||
throw runtime_error(
|
||||
"Unable to create a handle to the specified DAQ "
|
||||
"device. Is the device currently in use? Please make sure to set "
|
||||
"the DAQ configuration in duplex mode if simultaneous input and "
|
||||
"output is required.");
|
||||
}
|
||||
|
||||
err = ulConnectDaqDevice(_handle);
|
||||
if (err != ERR_NO_ERROR) {
|
||||
ulReleaseDaqDevice(_handle);
|
||||
_handle = 0;
|
||||
throw runtime_error(string("Unable to connect to device: " + showErr(err)));
|
||||
}
|
||||
|
||||
for (us ch = 0; ch < 4; ch++) {
|
||||
|
||||
err = ulAISetConfigDbl(_handle, AI_CFG_CHAN_SENSOR_SENSITIVITY, ch, 1.0);
|
||||
showErr(err);
|
||||
if (err != ERR_NO_ERROR) {
|
||||
throw runtime_error("Fatal: could normalize channel sensitivity");
|
||||
}
|
||||
|
||||
CouplingMode cm = inputACCouplingMode[ch] ? CM_AC : CM_DC;
|
||||
err = ulAISetConfig(_handle, AI_CFG_CHAN_COUPLING_MODE, ch, cm);
|
||||
if (err != ERR_NO_ERROR) {
|
||||
showErr(err);
|
||||
throw runtime_error("Fatal: could not set AC/DC coupling mode");
|
||||
}
|
||||
|
||||
IepeMode iepe = inputIEPEEnabled[ch] ? IEPE_ENABLED : IEPE_DISABLED;
|
||||
err = ulAISetConfig(_handle, AI_CFG_CHAN_IEPE_MODE, ch, iepe);
|
||||
if (err != ERR_NO_ERROR) {
|
||||
showErr(err);
|
||||
throw runtime_error("Fatal: could not set IEPE mode");
|
||||
}
|
||||
}
|
||||
}
|
||||
void fillUlDaqDeviceInfo(std::vector<DeviceInfo> &devinfolist) {
|
||||
|
||||
DEBUGTRACE_ENTER;
|
||||
|
||||
UlError err;
|
||||
unsigned int numdevs = MAX_DEV_COUNT_PER_API;
|
||||
|
||||
DaqDeviceDescriptor devdescriptors[MAX_DEV_COUNT_PER_API];
|
||||
DaqDeviceDescriptor descriptor;
|
||||
DaqDeviceInterface interfaceType = ANY_IFC;
|
||||
|
||||
err = ulGetDaqDeviceInventory(interfaceType, devdescriptors,
|
||||
static_cast<unsigned *>(&numdevs));
|
||||
|
||||
if (err != ERR_NO_ERROR) {
|
||||
throw runtime_error("UlDaq device inventarization failed");
|
||||
}
|
||||
|
||||
for (unsigned i = 0; i < numdevs; i++) {
|
||||
|
||||
descriptor = devdescriptors[i];
|
||||
|
||||
DeviceInfo devinfo;
|
||||
devinfo.api = uldaqapi;
|
||||
string name, interface;
|
||||
if (string(descriptor.productName) != "DT9837A") {
|
||||
throw runtime_error("Unknown UlDAQ type");
|
||||
}
|
||||
|
||||
switch (descriptor.devInterface) {
|
||||
case USB_IFC:
|
||||
name = "USB - ";
|
||||
break;
|
||||
case BLUETOOTH_IFC:
|
||||
/* devinfo. */
|
||||
name = "Bluetooth - ";
|
||||
break;
|
||||
|
||||
case ETHERNET_IFC:
|
||||
/* devinfo. */
|
||||
name = "Ethernet - ";
|
||||
break;
|
||||
default:
|
||||
name = "Uknown interface = ";
|
||||
}
|
||||
|
||||
name += string(descriptor.productName) + " " + string(descriptor.uniqueId);
|
||||
devinfo.device_name = std::move(name);
|
||||
|
||||
devinfo.api_specific_devindex = i;
|
||||
devinfo.availableDataTypes.push_back(
|
||||
DataTypeDescriptor::DataType::dtype_fl64);
|
||||
devinfo.prefDataTypeIndex = 0;
|
||||
|
||||
devinfo.availableSampleRates = {8000, 10000, 11025, 16000, 20000,
|
||||
22050, 24000, 32000, 44056, 44100,
|
||||
47250, 48000, 50000, 50400, 51000};
|
||||
|
||||
devinfo.prefSampleRateIndex = 11;
|
||||
|
||||
devinfo.availableFramesPerBlock = {512, 1024, 2048, 4096, 8192};
|
||||
|
||||
devinfo.availableInputRanges = {1.0, 10.0};
|
||||
devinfo.prefInputRangeIndex = 0;
|
||||
|
||||
devinfo.ninchannels = 4;
|
||||
devinfo.noutchannels = 1;
|
||||
|
||||
devinfo.hasInputIEPE = true;
|
||||
devinfo.hasInputACCouplingSwitch = true;
|
||||
devinfo.hasInputTrigger = true;
|
||||
|
||||
// Finally, this devinfo is pushed back in list
|
||||
devinfolist.push_back(devinfo);
|
||||
}
|
||||
}
|
14
lasp/device/lasp_uldaq.h
Normal file
14
lasp/device/lasp_uldaq.h
Normal file
@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
#include "lasp_daq.h"
|
||||
|
||||
std::unique_ptr<Daq> createUlDaqDevice(const DeviceInfo& devinfo,
|
||||
const DaqConfiguration& config);
|
||||
|
||||
/**
|
||||
* @brief Fill device info list with UlDaq specific devices, if any.
|
||||
*
|
||||
* @param devinfolist Info list to append to
|
||||
*/
|
||||
void fillUlDaqDeviceInfo(std::vector<DeviceInfo> &devinfolist);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user