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/device/lasp_daq.cxx
|
||||||
lasp/c/lasp_config.h
|
lasp/c/lasp_config.h
|
||||||
compile_commands.json
|
compile_commands.json
|
||||||
|
.cache
|
||||||
|
6
.gitmodules
vendored
6
.gitmodules
vendored
@ -1,3 +1,9 @@
|
|||||||
[submodule "STL-Threadsafe"]
|
[submodule "STL-Threadsafe"]
|
||||||
path = STL-Threadsafe
|
path = STL-Threadsafe
|
||||||
url = https://github.com/miachm/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")
|
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/lasp/cmake")
|
||||||
include("BuildType")
|
include("BuildType")
|
||||||
|
|
||||||
|
|
||||||
# This is used for code completion in vim
|
# This is used for code completion in vim
|
||||||
set(CMAKE_EXPORT_COMPILE_COMMANDS=ON)
|
set(CMAKE_EXPORT_COMPILE_COMMANDS=ON)
|
||||||
project(LASP LANGUAGES C CXX)
|
project(LASP LANGUAGES C CXX)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
set(CMAKE_C_STANDARD 11)
|
||||||
|
|
||||||
# Whether we want to use blas yes or no
|
# Whether we want to use blas yes or no
|
||||||
option(LASP_USE_BLAS "Use external blas library for math" ON)
|
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)
|
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(LASP_FFT_BACKEND "FFTW" CACHE STRING "FFT Library backend")
|
||||||
set_property(CACHE LASP_FFT_BACKEND PROPERTY STRINGS "FFTW" "FFTPack" "None")
|
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)
|
if(CMAKE_BUILD_TYPE STREQUAL Debug)
|
||||||
set(LASP_DEBUG True)
|
set(LASP_DEBUG True)
|
||||||
else()
|
else()
|
||||||
@ -48,8 +55,6 @@ endif()
|
|||||||
add_definitions(-DLASP_MAX_NFFT=33554432) # 2**25
|
add_definitions(-DLASP_MAX_NFFT=33554432) # 2**25
|
||||||
|
|
||||||
# ####################################### End of user-adjustable variables section
|
# ####################################### End of user-adjustable variables section
|
||||||
set(CMAKE_CXX_STANDARD 11)
|
|
||||||
set(CMAKE_C_STANDARD 11)
|
|
||||||
|
|
||||||
if(LASP_FFT_BACKEND STREQUAL "FFTW")
|
if(LASP_FFT_BACKEND STREQUAL "FFTW")
|
||||||
find_library(FFTW_LIBRARY NAMES fftw3 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)
|
add_definitions(-DHAS_RTAUDIO_WIN_WASAPI_API)
|
||||||
else() # Linux compile
|
else() # Linux compile
|
||||||
set(win32 false)
|
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_C_FLAGS "${CMAKE_C_FLAGS} -fPIC")
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -Wfatal-errors")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -Wfatal-errors")
|
||||||
include_directories(/usr/local/include/rtaudio)
|
include_directories(/usr/local/include/rtaudio)
|
||||||
@ -98,34 +103,10 @@ else() # Linux compile
|
|||||||
# well.
|
# well.
|
||||||
|
|
||||||
endif()
|
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
|
# The last argument here takes care of calling SIGABRT when an integer overflow
|
||||||
# occures.
|
# 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
|
# Debug make flags
|
||||||
set(CMAKE_C_FLAGS_DEBUG "-g" )
|
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.
|
# Python searching.
|
||||||
set(Python_ADDITIONAL_VERSIONS "3.8")
|
# set(Python_ADDITIONAL_VERSIONS "3.8")
|
||||||
set(python_version_windll "38")
|
# set(python_version_windll "38")
|
||||||
find_package(PythonLibs REQUIRED )
|
find_package(PythonLibs REQUIRED )
|
||||||
find_package(PythonInterp REQUIRED)
|
find_package(PythonInterp REQUIRED)
|
||||||
|
|
||||||
@ -152,7 +133,10 @@ endif()
|
|||||||
|
|
||||||
include_directories(lasp/c)
|
include_directories(lasp/c)
|
||||||
include_directories(STL-Threadsafe/include)
|
include_directories(STL-Threadsafe/include)
|
||||||
|
include_directories(gsl-lite/include)
|
||||||
|
include_directories(DebugTrace-cpp/include)
|
||||||
add_subdirectory(lasp)
|
add_subdirectory(lasp)
|
||||||
|
add_subdirectory(gsl-lite)
|
||||||
add_subdirectory(test)
|
add_subdirectory(test)
|
||||||
|
|
||||||
# set(SETUP_PY_IN "${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in")
|
# 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
|
# This is used for code completion in vim
|
||||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
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(c)
|
||||||
add_subdirectory(device)
|
add_subdirectory(device)
|
||||||
|
|
||||||
set_source_files_properties(wrappers.c PROPERTIES COMPILE_FLAGS "${CMAKE_C_FLAGS} ${CYTHON_EXTRA_C_FLAGS}")
|
# set_source_files_properties(wrappers.c PROPERTIES COMPILE_FLAGS "${CMAKE_C_FLAGS} ${CYTHON_EXTRA_C_FLAGS}")
|
||||||
cython_add_module(wrappers wrappers.pyx)
|
# cython_add_module(wrappers wrappers.pyx)
|
||||||
target_link_libraries(wrappers lasp_lib ${LASP_THREADING_LIBRARIES})
|
# target_link_libraries(wrappers lasp_lib ${LASP_THREADING_LIBRARIES})
|
||||||
if(win32)
|
# if(win32)
|
||||||
target_link_libraries(wrappers python${python_version_windll})
|
# target_link_libraries(wrappers python${python_version_windll})
|
||||||
endif(win32)
|
# endif(win32)
|
||||||
|
@ -11,6 +11,8 @@
|
|||||||
|
|
||||||
/* Debug flag */
|
/* Debug flag */
|
||||||
#cmakedefine01 LASP_DEBUG
|
#cmakedefine01 LASP_DEBUG
|
||||||
|
|
||||||
|
|
||||||
#if LASP_DEBUG == 1
|
#if LASP_DEBUG == 1
|
||||||
#define TRACER 1
|
#define TRACER 1
|
||||||
#define TRACERNAME @LASP_TRACERNAME@
|
#define TRACERNAME @LASP_TRACERNAME@
|
||||||
@ -23,9 +25,10 @@
|
|||||||
#define FFTW 1
|
#define FFTW 1
|
||||||
#define FFTPack 2
|
#define FFTPack 2
|
||||||
#define None 0
|
#define None 0
|
||||||
|
|
||||||
#cmakedefine LASP_FFT_BACKEND @LASP_FFT_BACKEND@
|
#cmakedefine LASP_FFT_BACKEND @LASP_FFT_BACKEND@
|
||||||
#cmakedefine LASP_HAS_RTAUDIO
|
#cmakedefine01 LASP_HAS_RTAUDIO
|
||||||
#cmakedefine LASP_HAS_ULDAQ
|
#cmakedefine01 LASP_HAS_ULDAQ
|
||||||
#cmakedefine01 LASP_DOUBLE_PRECISION
|
#cmakedefine01 LASP_DOUBLE_PRECISION
|
||||||
#cmakedefine01 LASP_USE_BLAS
|
#cmakedefine01 LASP_USE_BLAS
|
||||||
|
|
||||||
|
@ -38,11 +38,11 @@ typedef double d; /* Shortcut for double */
|
|||||||
#error LASP_FLOAT_SIZE should be either 32 or 64
|
#error LASP_FLOAT_SIZE should be either 32 or 64
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
#include <complex.h>
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
#include <complex>
|
||||||
typedef std::complex<d> c;
|
typedef std::complex<d> c;
|
||||||
#else
|
#else
|
||||||
|
#include <complex.h>
|
||||||
#if LASP_FLOAT_SIZE == 32
|
#if LASP_FLOAT_SIZE == 32
|
||||||
typedef float complex c;
|
typedef float complex c;
|
||||||
#else
|
#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)
|
file(GLOB device_files *.cpp)
|
||||||
set(cpp_daq_linklibs ${LASP_THREADING_LIBRARIES})
|
# set(cpp_daq_linklibs ${LASP_THREADING_LIBRARIES})
|
||||||
include_directories(../c)
|
|
||||||
|
|
||||||
if(LASP_HAS_RTAUDIO)
|
# if(win32)
|
||||||
include_directories(/usr/include/rtaudio)
|
# list(APPEND lasp_device_linklibs python${python_version_windll})
|
||||||
list(APPEND cpp_daq_files lasp_cpprtaudio.cpp)
|
# endif(win32)
|
||||||
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)
|
|
||||||
|
|
||||||
add_library(cpp_daq ${cpp_daq_files})
|
add_library(lasp_device_lib OBJECT ${device_files})
|
||||||
target_link_libraries(cpp_daq ${cpp_daq_linklibs})
|
|
||||||
|
|
||||||
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
|
# target_link_libraries(lasp_device PRIVATE lasp_device_lib)
|
||||||
CYTHON_IS_CXX TRUE)
|
|
||||||
|
|
||||||
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