diff --git a/.gitignore b/.gitignore index 7fd86ff..c6e64f4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,34 +1,22 @@ *.a -*.cxx *.pyc +*.so dist +src/lasp.egg-info +test/.ipynb_checkpoints +src/lasp/lasp_config.h +_deps +compile_commands.json CMakeFiles CMakeCache.txt cmake_install.cmake -lasp/*.cpp Makefile build -*.html __pycache__ cython_debug -lasp/config.pxi -lasp/wrappers.c -lasp/resources_rc.py -*.so -test/test_bf -test/test_fft -test/test_math -test/test_workers -test/test_uldaq doc -LASP.egg-info -lasp_octave_fir.* -lasp/ui_* -resources_rc.py .ropeproject +.ipynb_checkpoints .spyproject -test/test_uldaq -lasp/device/lasp_daq.cxx -lasp/c/lasp_config.h -compile_commands.json .cache +_skbuild diff --git a/.gitmodules b/.gitmodules index 36b3976..4844c33 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,33 @@ [submodule "STL-Threadsafe"] - path = STL-Threadsafe + path = third_party/STL-Threadsafe url = https://github.com/miachm/STL-Threadsafe +[submodule "gsl-lite"] + path = third_party/gsl-lite + url = https://github.com/gsl-lite/gsl-lite +[submodule "DebugTrace-cpp"] + path = third_party/DebugTrace-cpp + url = https://github.com/MasatoKokubo/DebugTrace-cpp +[submodule "armadillo-code"] + path = third_party/armadillo-code + url = https://gitlab.com/conradsnicta/armadillo-code +[submodule "third_party/tomlplusplus"] + path = third_party/tomlplusplus + url = https://github.com/marzer/tomlplusplus +[submodule "third_party/lockfree"] + path = third_party/boost/lockfree + url = https://github.com/boostorg/lockfree +[submodule "third_party/carma"] + path = third_party/carma + url = https://github.com/RUrlus/carma +[submodule "third_party/thread-pool"] + path = third_party/thread-pool + url = https://github.com/bshoshany/thread-pool +[submodule "third_party/rtaudio"] + path = third_party/rtaudio + url = https://github.com/thestk/rtaudio +[submodule "third_party/boost/core"] + path = third_party/boost/core + url = https://github.com/boostorg/core +[submodule "third_party/uldaq"] + path = third_party/uldaq + url = https://github.com/mccdaq/uldaq diff --git a/CMakeLists.txt b/CMakeLists.txt index 672bee0..57cc137 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,183 +1,97 @@ -cmake_minimum_required (VERSION 3.12) +cmake_minimum_required (VERSION 3.16) +project(LASP LANGUAGES C CXX VERSION 1.0) + + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED) + +option(LASP_DOUBLE_PRECISION "Compile as double precision floating point" ON) +option(LASP_HAS_RTAUDIO "Compile with RtAudio Daq backend" ON) +option(LASP_HAS_ULDAQ "Compile with UlDaq backend" ON) +option(LASP_BUILD_TUNED "Tune build for current machine" OFF) +option(LASP_WITH_OPENMP "Use OpenMP parallelization" ON) +set(LASP_MAX_NFFT "33554432" CACHE STRING "Max FFT size") + +# Use ccache if available +find_program(CCACHE_PROGRAM ccache) +if(CCACHE_PROGRAM) + set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE_PROGRAM}") + set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK "${CCACHE_PROGRAM}") +endif() # To allow linking to static libs from other directories cmake_policy(SET CMP0079 NEW) -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/lasp/cmake") -include("BuildType") - # This is used for code completion in vim -set(CMAKE_EXPORT_COMPILE_COMMANDS=ON) -project(LASP LANGUAGES C CXX) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -# Whether we want to use blas yes or no -option(LASP_USE_BLAS "Use external blas library for math" ON) -option(LASP_ARM "Compile subset of code for ARM real time (Bela board)" OFF) -option(LASP_DOUBLE_PRECISION "Compile as double precision floating point" ON) -option(LASP_PARALLEL "Parallel processing" ON) -option(LASP_HAS_RTAUDIO "Compile with RtAudio Daq backend" ON) -option(LASP_HAS_ULDAQ "Compile with UlDaq backend" ON) +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake") +include("BuildType") +include("QueryPythonForPybind11") + +# Find the pybind11 package +find_pybind11_python_first() + +find_package(OpenMP REQUIRED COMPONENTS CXX) -set(LASP_MAX_NUM_CHANNELS 16 CACHE STRING "Maximum number of audio channels that is compiled in in code") -set(LASP_MAX_NUM_THREADS 30 CACHE STRING "The maximum number of simultaneous threads in parallel processing") -set(LASP_TRACERNAME "defaulttracer" CACHE STRING "Name of tracer variable containing level") 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" "Armadillo") -if(CMAKE_BUILD_TYPE STREQUAL Debug) +set(PY_FULL_VERSION ${PROJECT_VERSION}${PY_VERSION_SUFFIX}) + +# Required for PYBIND11 +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + +if(${CMAKE_BUILD_TYPE} STREQUAL "Debug") set(LASP_DEBUG True) + # This does not work nicely with RtAudio. However, if we compile it + # ourselves as a third_party ref, this works nicely together. + add_definitions(-D_GLIBCXX_DEBUG) + else() set(LASP_DEBUG False) endif() -if(LASP_PARALLEL) - add_definitions(-D_REENTRANT) - set(LASP_THREADING_LIBRARIES pthread) -else() - set(LASP_THREADING_LIBRARIES "") +# Tune for current machine +if(LASP_BUILD_TUNED) + if(CMAKE_BUILD_TYPE STREQUAL "Debug") + message(FATAL_ERROR + "Cannot build optimized and tuned code when debug is switched on") + endif() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native -mtune=native") endif() -if(LASP_USE_BLAS) - # link openblas - set(BLA_VENDOR OpenBLAS) - find_package(BLAS REQUIRED) -endif() - - -# Reasonable maximum to the nfft size, at 48kHz this is 700s of data... -add_definitions(-DLASP_MAX_NFFT=33554432) # 2**25 - -# ####################################### End of user-adjustable variables section -set(CMAKE_CXX_STANDARD 11) -set(CMAKE_C_STANDARD 11) +# ###################################### Find and link to OpenBLAS +set(BLA_VENDOR OpenBLAS) +find_package(BLAS REQUIRED) +# ###################################### Find and link to FFTW if(LASP_FFT_BACKEND STREQUAL "FFTW") - find_library(FFTW_LIBRARY NAMES fftw3 fftw) - set(FFT_LIBRARIES "${FFTW_LIBRARY}") -elseif(LASP_FFT_BACKEND STREQUAL "FFTPack") - include_directories(fftpack) - set(FFT_LIBRARIES fftpack) - add_subdirectory(fftpack) + find_library(fftw3 REQUIRED NAMES fftw fftw3) + set(LASP_FFT_LIBS "fftw3") +elseif(LASP_FFT_BACKEND STREQUAL "Armadillo") endif() -# General make flags -set(CMAKE_POSITION_INDEPENDENT_CODE ON) -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wno-type-limits \ --Werror=implicit-function-declaration -Wno-unused-parameter \ --Werror=return-type -Wfatal-errors") - -if(CMAKE_SYSTEM_NAME STREQUAL "Windows") - set(win32 true) - set(home $ENV{USERPROFILE}) - # set(miniconda_dir ${home}\\Miniconda3) - - message("Building for Windows") - include_directories( - ..\\rtaudio - C:\\mingw\\mingw64\\include\\OpenBLAS - link_directories(${home}\\miniconda3\\Library\\include) - ) - set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH} $miniconda_dir\\Lib\\cmake") - # include( - add_definitions(-DMS_WIN64) - link_directories(C:\\mingw\\mingw64\\lib) - link_directories(C:\\mingw\\mingw64\\bin) - link_directories(..\\rtaudio) - link_directories(${home}\\Miniconda3) - add_definitions(-DHAS_RTAUDIO_WIN_WASAPI_API) -else() # Linux compile - set(win32 false) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c11 -Werror=incompatible-pointer-types") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -Wfatal-errors") - include_directories(/usr/local/include/rtaudio) - include_directories(/usr/include/rtaudio) - link_directories(/usr/local/lib) - - # This should become optional later on, and be added to the windows list as - # well. - -endif() - -set(CYTHON_FLAGS "--fast-fail") -if(LASP_DEBUG) - set(LASP_DEBUG_CYTHON=True) - message("Building debug code") - # This will produce html files - # set(CYTHON_ANNOTATE ON) - # Add the __FILENAME__ macro - # set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D__FILENAME__='\"$(subst ${CMAKE_SOURCE_DIR}/,,$(abspath $<))\"'") +# ###################################### OpenMP related +if(LASP_WITH_OPENMP) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") 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) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unknown-pragmas") +endif() -# The last argument here takes care of calling SIGABRT when an integer overflow -# occures. -############################## General compilation flags (independent of debug mode, windows or linux) -set(CYTHON_EXTRA_C_FLAGS "-Wno-sign-compare -Wno-cpp -Wno-implicit-fallthrough -Wno-incompatible-pointer-types -Wno-strict-aliasing") -set(CYTHON_EXTRA_CXX_FLAGS "-Wno-sign-compare -Wno-cpp -Wno-implicit-fallthrough -Wno-strict-aliasing") - - -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra \ --Wno-type-limits") - -# Debug make flags -set(CMAKE_C_FLAGS_DEBUG "-g" ) - -set(CMAKE_C_FLAGS_RELEASE "-O2 -mfpmath=sse -march=x86-64 -mtune=native \ +# ###################################### Compilation flags +set(CMAKE_C_FLAGS_RELEASE "-O3 -flto -mfpmath=sse -march=x86-64 -mtune=native \ -fdata-sections -ffunction-sections -fomit-frame-pointer -finline-functions") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-type-limits") + # ############################# End compilation flags +include_directories(/usr/lib/python3.10/site-packages/numpy/core/include) +# ####################################### End of user-adjustable variables section +include(OSSpecific) +include(rtaudio) +include(uldaq) +# +add_subdirectory(src/lasp) -# Python searching. -set(Python_ADDITIONAL_VERSIONS "3.8") -set(python_version_windll "38") -find_package(PythonLibs REQUIRED ) -find_package(PythonInterp REQUIRED) - -# Add FFTpack dir if used as FFT backend -if(LASP_FFTPACK_BACKEND) - add_subdirectory(fftpack) - include_directories( - fftpack - ) -endif() - -include_directories(lasp/c) -include_directories(STL-Threadsafe/include) -add_subdirectory(lasp) -add_subdirectory(test) - -# set(SETUP_PY_IN "${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in") -set(SETUP_PY "${CMAKE_CURRENT_BINARY_DIR}/setup.py") - -set(DEPS "${CMAKE_CURRENT_SOURCE_DIR}/*.py" - "${CMAKE_CURRENT_SOURCE_DIR}/lasp/*.py" - "wrappers" - "lasp_daq") - -set(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/build/timestamp") - -# configure_file(${SETUP_PY_IN} ${SETUP_PY}) -add_custom_command(OUTPUT ${OUTPUT} - COMMAND ${PYTHON_EXECUTABLE} ${SETUP_PY} build - COMMAND ${CMAKE_COMMAND} -E touch ${OUTPUT} - DEPENDS ${DEPS}) - -add_custom_target(target ALL DEPENDS ${OUTPUT}) - -if(DEFINED INSTALL_DEBUG) - set(EXTRA_SETUP_ARG --user -e) -else() - set(EXTRA_SETUP_ARG "") -endif() - - -install(CODE "execute_process(COMMAND ${PYTHON_EXECUTABLE} -m pip install ${EXTRA_SETUP_ARG} .)") diff --git a/Doxyfile b/Doxyfile index d06c7aa..bcd6a6f 100644 --- a/Doxyfile +++ b/Doxyfile @@ -1,4 +1,4 @@ -# Doxyfile 1.8.14 +# Doxyfile 1.8.17 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. @@ -17,10 +17,10 @@ # Project related configuration options #--------------------------------------------------------------------------- -# This tag specifies the encoding used for all characters in the config file -# that follow. The default is UTF-8 which is also the encoding used for all text -# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv -# built into libc) for the transcoding. See +# This tag specifies the encoding used for all characters in the configuration +# file that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See # https://www.gnu.org/software/libiconv/ for the list of possible encodings. # The default value is: UTF-8. @@ -38,20 +38,20 @@ PROJECT_NAME = LASP # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = +PROJECT_NUMBER = 1.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a # quick idea about the purpose of the project. Keep the description short. -PROJECT_BRIEF = +PROJECT_BRIEF = "Library for Acoustic Signal Processing" # With the PROJECT_LOGO tag one can specify a logo or an icon that is included # in the documentation. The maximum height of the logo should not exceed 55 # pixels and the maximum width should not exceed 200 pixels. Doxygen will copy # the logo to the output directory. -PROJECT_LOGO = +PROJECT_LOGO = /home/anne/wip/mycode/lasp/img/LASP_200px.png # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. If a relative path is @@ -162,7 +162,7 @@ FULL_PATH_NAMES = YES # will be relative from the directory where doxygen is started. # This tag requires that the tag FULL_PATH_NAMES is set to YES. -STRIP_FROM_PATH = +STRIP_FROM_PATH = # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the # path mentioned in the documentation of a class, which tells the reader which @@ -171,7 +171,7 @@ STRIP_FROM_PATH = # specify the list of include paths that are normally passed to the compiler # using the -I flag. -STRIP_FROM_INC_PATH = +STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but # less readable) file names. This can be useful is your file systems doesn't @@ -189,6 +189,16 @@ SHORT_NAMES = NO JAVADOC_AUTOBRIEF = NO +# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line +# such as +# /*************** +# as being the beginning of a Javadoc-style comment "banner". If set to NO, the +# Javadoc-style will behave just like regular comments and it will not be +# interpreted by doxygen. +# The default value is: NO. + +JAVADOC_BANNER = NO + # If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first # line (until the first dot) of a Qt-style comment as the brief description. If # set to NO, the Qt-style will behave just like regular Qt-style comments (thus @@ -209,6 +219,14 @@ QT_AUTOBRIEF = NO MULTILINE_CPP_IS_BRIEF = NO +# By default Python docstrings are displayed as preformatted text and doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES + # If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the # documentation from any documented member that it re-implements. # The default value is: YES. @@ -232,20 +250,18 @@ TAB_SIZE = 4 # the documentation. An alias has the form: # name=value # For example adding -# "sideeffect=@par Side Effects:\n" +# "sideeffect=@par Side Effects:^^" # will allow you to put the command \sideeffect (or @sideeffect) in the # documentation, which will result in a user-defined paragraph with heading -# "Side Effects:". You can put \n's in the value part of an alias to insert -# newlines (in the resulting output). You can put ^^ in the value part of an -# alias to insert a newline as if a physical newline was in the original file. +# "Side Effects:". Note that you cannot put \n's in the value part of an alias +# to insert newlines (in the resulting output). You can put ^^ in the value part +# of an alias to insert a newline as if a physical newline was in the original +# file. When you need a literal { or } or , in the value part of an alias you +# have to escape them by means of a backslash (\), this can lead to conflicts +# with the commands \{ and \} for these it is advised to use the version @{ and +# @} or use a double escape (\\{ and \\}) -ALIASES = - -# This tag can be used to specify a number of word-keyword mappings (TCL only). -# A mapping has the form "name=value". For example adding "class=itcl::class" -# will allow you to use the command class in the itcl::class meaning. - -TCL_SUBST = +ALIASES = # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources # only. Doxygen will then generate output that is more tailored for C. For @@ -253,7 +269,7 @@ TCL_SUBST = # members will be omitted, etc. # The default value is: NO. -OPTIMIZE_OUTPUT_FOR_C = YES +OPTIMIZE_OUTPUT_FOR_C = NO # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or # Python sources only. Doxygen will then generate output that is more tailored @@ -275,28 +291,40 @@ OPTIMIZE_FOR_FORTRAN = NO OPTIMIZE_OUTPUT_VHDL = NO +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = NO + # Doxygen selects the parser to use depending on the extension of the files it # parses. With this tag you can assign which parser to use for a given # extension. Doxygen has a built-in mapping, but you can override or extend it # using this tag. The format is ext=language, where ext is a file extension, and -# language is one of the parsers supported by doxygen: IDL, Java, Javascript, -# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: -# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: -# Fortran. In the later case the parser tries to guess whether the code is fixed -# or free formatted code, this is the default for Fortran type files), VHDL. For -# instance to make doxygen treat .inc files as Fortran files (default is PHP), -# and .f files as C (default is Fortran), use: inc=Fortran f=C. +# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice, +# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# tries to guess whether the code is fixed or free formatted code, this is the +# default for Fortran type files). For instance to make doxygen treat .inc files +# as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. # # Note: For files without extension you can use no_extension as a placeholder. # # Note that for custom extensions you also need to set FILE_PATTERNS otherwise -# the files are not read by doxygen. +# the files are not read by doxygen. When specifying no_extension you should add +# * to the FILE_PATTERNS. +# +# Note see also the list of default file extension mappings. -EXTENSION_MAPPING = +EXTENSION_MAPPING = # If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments # according to the Markdown format, which allows for more readable -# documentation. See http://daringfireball.net/projects/markdown/ for details. +# documentation. See https://daringfireball.net/projects/markdown/ for details. # The output of markdown processing is further processed by doxygen, so you can # mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in # case of backward compatibilities issues. @@ -308,7 +336,7 @@ MARKDOWN_SUPPORT = YES # to that level are automatically included in the table of contents, even if # they do not have an id attribute. # Note: This feature currently applies only to Markdown headings. -# Minimum value: 0, maximum value: 99, default value: 0. +# Minimum value: 0, maximum value: 99, default value: 5. # This tag requires that the tag MARKDOWN_SUPPORT is set to YES. TOC_INCLUDE_HEADINGS = 0 @@ -424,6 +452,19 @@ TYPEDEF_HIDES_STRUCT = NO LOOKUP_CACHE_SIZE = 0 +# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use +# during processing. When set to 0 doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which effectively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +NUM_PROC_THREADS = 1 + #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- @@ -444,6 +485,12 @@ EXTRACT_ALL = YES EXTRACT_PRIVATE = NO +# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual +# methods of a class will be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIV_VIRTUAL = NO + # If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal # scope will be included in the documentation. # The default value is: NO. @@ -481,6 +528,13 @@ EXTRACT_LOCAL_METHODS = NO EXTRACT_ANON_NSPACES = NO +# If this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = YES + # If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all # undocumented members inside documented classes or files. If set to NO these # members will be included in the various overviews, but no documentation @@ -498,8 +552,8 @@ HIDE_UNDOC_MEMBERS = NO HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend -# (class|struct|union) declarations. If set to NO, these declarations will be -# included in the documentation. +# declarations. If set to NO, these declarations will be included in the +# documentation. # The default value is: NO. HIDE_FRIEND_COMPOUNDS = NO @@ -518,11 +572,18 @@ HIDE_IN_BODY_DOCS = NO INTERNAL_DOCS = NO -# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file -# names in lower-case letters. If set to YES, upper-case letters are also -# allowed. This is useful if you have classes or files whose names only differ -# in case and if your file system supports case sensitive file names. Windows -# and Mac users are advised to set this option to NO. +# With the correct setting of option CASE_SENSE_NAMES doxygen will better be +# able to match the capabilities of the underlying filesystem. In case the +# filesystem is case sensitive (i.e. it supports files in the same directory +# whose names only differ in casing), the option must be set to YES to properly +# deal with such files in case they appear in the input. For filesystems that +# are not case sensitive the option should be be set to NO to properly deal with +# output files written for symbols that only differ in casing, such as for two +# classes, one named CLASS and the other named Class, and to also support +# references to files without having to specify the exact matching casing. On +# Windows (including Cygwin) and MacOS, users should typically set this option +# to NO, whereas on Linux or other Unix flavors it should typically be set to +# YES. # The default value is: system dependent. CASE_SENSE_NAMES = NO @@ -532,7 +593,7 @@ CASE_SENSE_NAMES = NO # scope will be hidden. # The default value is: NO. -HIDE_SCOPE_NAMES = YES +HIDE_SCOPE_NAMES = NO # If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will # append additional text to a page's title, such as Class Reference. If set to @@ -541,6 +602,12 @@ HIDE_SCOPE_NAMES = YES HIDE_COMPOUND_REFERENCE= NO +# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class +# will show which file needs to be included to use the class. +# The default value is: YES. + +SHOW_HEADERFILE = YES + # If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of # the files that are included by a file in the documentation of that file. # The default value is: YES. @@ -649,7 +716,7 @@ GENERATE_DEPRECATEDLIST= YES # sections, marked by \if ... \endif and \cond # ... \endcond blocks. -ENABLED_SECTIONS = +ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the # initial value of a variable or macro / define can have for it to appear in the @@ -691,20 +758,21 @@ SHOW_NAMESPACES = YES # by doxygen. Whatever the program writes to standard output is used as the file # version. For an example see the documentation. -FILE_VERSION_FILTER = +FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed # by doxygen. The layout file controls the global structure of the generated # output files in an output format independent way. To create the layout file # that represents doxygen's defaults, run doxygen with the -l option. You can # optionally specify a file name after the option, if omitted DoxygenLayout.xml -# will be used as the name of the layout file. +# will be used as the name of the layout file. See also section "Changing the +# layout of pages" for information. # # Note that if you run doxygen from a directory containing a file called # DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE # tag is left empty. -LAYOUT_FILE = +LAYOUT_FILE = # The CITE_BIB_FILES tag can be used to specify one or more bib files containing # the reference definitions. This must be a list of .bib files. The .bib @@ -714,7 +782,7 @@ LAYOUT_FILE = # LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the # search path. See also \cite for info how to create references. -CITE_BIB_FILES = +CITE_BIB_FILES = #--------------------------------------------------------------------------- # Configuration options related to warning and progress messages @@ -744,23 +812,35 @@ WARNINGS = YES WARN_IF_UNDOCUMENTED = YES # If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for -# potential errors in the documentation, such as not documenting some parameters -# in a documented function, or documenting parameters that don't exist or using -# markup commands wrongly. +# potential errors in the documentation, such as documenting some parameters in +# a documented function twice, or documenting parameters that don't exist or +# using markup commands wrongly. # The default value is: YES. WARN_IF_DOC_ERROR = YES +# If WARN_IF_INCOMPLETE_DOC is set to YES, doxygen will warn about incomplete +# function parameter documentation. If set to NO, doxygen will accept that some +# parameters have no documentation without warning. +# The default value is: YES. + +WARN_IF_INCOMPLETE_DOC = YES + # This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that # are documented, but have no documentation for their parameters or return -# value. If set to NO, doxygen will only warn about wrong or incomplete -# parameter documentation, but not about the absence of documentation. +# value. If set to NO, doxygen will only warn about wrong parameter +# documentation, but not about the absence of documentation. If EXTRACT_ALL is +# set to YES then this flag will automatically be disabled. See also +# WARN_IF_INCOMPLETE_DOC # The default value is: NO. WARN_NO_PARAMDOC = NO # If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when -# a warning is encountered. +# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS +# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the doxygen process doxygen will return with a non-zero status. +# Possible values are: NO, YES and FAIL_ON_WARNINGS. # The default value is: NO. WARN_AS_ERROR = NO @@ -777,9 +857,12 @@ WARN_FORMAT = "$file:$line: $text" # The WARN_LOGFILE tag can be used to specify a file to which warning and error # messages should be written. If left blank the output is written to standard -# error (stderr). +# error (stderr). In case the file specified cannot be opened for writing the +# warning and error messages are written to standard error. When as file - is +# specified the warning and error messages are written to standard output +# (stdout). -WARN_LOGFILE = +WARN_LOGFILE = #--------------------------------------------------------------------------- # Configuration options related to the input files @@ -791,13 +874,13 @@ WARN_LOGFILE = # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. -INPUT = lasp +INPUT = src # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses # libiconv (or the iconv built into libc) for the transcoding. See the libiconv -# documentation (see: https://www.gnu.org/software/libiconv/) for the list of -# possible encodings. +# documentation (see: +# https://www.gnu.org/software/libiconv/) for the list of possible encodings. # The default value is: UTF-8. INPUT_ENCODING = UTF-8 @@ -810,11 +893,15 @@ INPUT_ENCODING = UTF-8 # need to set EXTENSION_MAPPING for the extension otherwise the files are not # read by doxygen. # +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. +# # If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, # *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, -# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, -# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, -# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf. +# *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, +# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C +# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, +# *.vhdl, *.ucf, *.qsf and *.ice. FILE_PATTERNS = *.c \ *.cc \ @@ -874,7 +961,7 @@ RECURSIVE = YES # Note that relative paths are relative to the directory from which doxygen is # run. -EXCLUDE = +EXCLUDE = # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded @@ -890,24 +977,24 @@ EXCLUDE_SYMLINKS = NO # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories for example use the pattern */test/* -EXCLUDE_PATTERNS = +EXCLUDE_PATTERNS = # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, -# AClass::ANamespace, ANamespace::*Test +# ANamespace::AClass, ANamespace::*Test # # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories use the pattern */test/* -EXCLUDE_SYMBOLS = +EXCLUDE_SYMBOLS = # The EXAMPLE_PATH tag can be used to specify one or more files or directories # that contain example code fragments that are included (see the \include # command). -EXAMPLE_PATH = +EXAMPLE_PATH = # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and @@ -927,7 +1014,7 @@ EXAMPLE_RECURSIVE = NO # that contain images that are to be included in the documentation (see the # \image command). -IMAGE_PATH = +IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program @@ -948,7 +1035,7 @@ IMAGE_PATH = # need to set EXTENSION_MAPPING for the extension otherwise the files are not # properly processed by doxygen. -INPUT_FILTER = +INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. Doxygen will compare the file name with each pattern and apply the @@ -961,7 +1048,7 @@ INPUT_FILTER = # need to set EXTENSION_MAPPING for the extension otherwise the files are not # properly processed by doxygen. -FILTER_PATTERNS = +FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER) will also be used to filter the input files that are used for @@ -976,14 +1063,14 @@ FILTER_SOURCE_FILES = NO # *.ext= (so without naming a filter). # This tag requires that the tag FILTER_SOURCE_FILES is set to YES. -FILTER_SOURCE_PATTERNS = +FILTER_SOURCE_PATTERNS = # If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that # is part of the input, its contents will be placed on the main page # (index.html). This can be useful if you have a project on for instance GitHub # and want to reuse the introduction page also for the doxygen output. -USE_MDFILE_AS_MAINPAGE = +USE_MDFILE_AS_MAINPAGE = #--------------------------------------------------------------------------- # Configuration options related to source browsing @@ -1012,7 +1099,7 @@ INLINE_SOURCES = NO STRIP_CODE_COMMENTS = YES # If the REFERENCED_BY_RELATION tag is set to YES then for each documented -# function all documented functions referencing it will be listed. +# entity all documented functions referencing it will be listed. # The default value is: NO. REFERENCED_BY_RELATION = NO @@ -1049,7 +1136,7 @@ SOURCE_TOOLTIPS = YES # # To use it do the following: # - Install the latest version of global -# - Enable SOURCE_BROWSER and USE_HTAGS in the config file +# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file # - Make sure the INPUT points to the root of the source tree # - Run doxygen as normal # @@ -1082,20 +1169,13 @@ VERBATIM_HEADERS = YES ALPHABETICAL_INDEX = YES -# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in -# which the alphabetical index list will be split. -# Minimum value: 1, maximum value: 20, default value: 5. -# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. - -COLS_IN_ALPHA_INDEX = 5 - # In case all classes in a project start with a common prefix, all classes will # be put under the same header in the alphabetical index. The IGNORE_PREFIX tag # can be used to specify a prefix (or a list of prefixes) that should be ignored # while generating the index headers. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. -IGNORE_PREFIX = +IGNORE_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the HTML output @@ -1139,7 +1219,7 @@ HTML_FILE_EXTENSION = .html # of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_HEADER = +HTML_HEADER = # The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each # generated HTML page. If the tag is left blank doxygen will generate a standard @@ -1149,7 +1229,7 @@ HTML_HEADER = # that doxygen normally uses. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_FOOTER = +HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading style # sheet that is used by each HTML page. It can be used to fine-tune the look of @@ -1161,7 +1241,7 @@ HTML_FOOTER = # obsolete. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_STYLESHEET = +HTML_STYLESHEET = # The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined # cascading style sheets that are included after the standard style sheets @@ -1174,7 +1254,7 @@ HTML_STYLESHEET = # list). For an example see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_EXTRA_STYLESHEET = +HTML_EXTRA_STYLESHEET = # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note @@ -1184,11 +1264,11 @@ HTML_EXTRA_STYLESHEET = # files will be copied as-is; there are no commands or markers available. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_EXTRA_FILES = +HTML_EXTRA_FILES = # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the style sheet and background images according to -# this color. Hue is specified as an angle on a colorwheel, see +# this color. Hue is specified as an angle on a color-wheel, see # https://en.wikipedia.org/wiki/Hue for more information. For instance the value # 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 # purple, and 360 is red again. @@ -1198,7 +1278,7 @@ HTML_EXTRA_FILES = HTML_COLORSTYLE_HUE = 220 # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors -# in the HTML output. For a value of 0 the output will use grayscales only. A +# in the HTML output. For a value of 0 the output will use gray-scales only. A # value of 255 will produce the most vivid colors. # Minimum value: 0, maximum value: 255, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. @@ -1227,9 +1307,9 @@ HTML_TIMESTAMP = NO # If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML # documentation will contain a main index with vertical navigation menus that -# are dynamically created via Javascript. If disabled, the navigation index will +# are dynamically created via JavaScript. If disabled, the navigation index will # consists of multiple levels of tabs that are statically embedded in every HTML -# page. Disable this option to support browsers that do not have Javascript, +# page. Disable this option to support browsers that do not have JavaScript, # like the Qt help browser. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. @@ -1259,13 +1339,14 @@ HTML_INDEX_NUM_ENTRIES = 100 # If the GENERATE_DOCSET tag is set to YES, additional index files will be # generated that can be used as input for Apple's Xcode 3 integrated development -# environment (see: https://developer.apple.com/tools/xcode/), introduced with -# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a -# Makefile in the HTML output directory. Running make will produce the docset in -# that directory and running make install will install the docset in +# environment (see: +# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To +# create a documentation set, doxygen will generate a Makefile in the HTML +# output directory. Running make will produce the docset in that directory and +# running make install will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at -# startup. See https://developer.apple.com/tools/creatingdocsetswithdoxygen.html -# for more information. +# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.html for more information. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. @@ -1279,6 +1360,13 @@ GENERATE_DOCSET = NO DOCSET_FEEDNAME = "Doxygen generated docs" +# This tag determines the URL of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDURL = + # This tag specifies a string that should uniquely identify the documentation # set bundle. This should be a reverse domain-name style string, e.g. # com.mycompany.MyDocSet. Doxygen will append .docset to the name. @@ -1304,8 +1392,12 @@ DOCSET_PUBLISHER_NAME = Publisher # If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three # additional HTML index files: index.hhp, index.hhc, and index.hhk. The # index.hhp is a project file that can be read by Microsoft's HTML Help Workshop -# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on -# Windows. +# on Windows. In the beginning of 2021 Microsoft took the original page, with +# a.o. the download links, offline the HTML help workshop was already many years +# in maintenance mode). You can download the HTML help workshop from the web +# archives at Installation executable (see: +# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo +# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe). # # The HTML Help Workshop contains a compiler that can convert all HTML output # generated by doxygen into a single compiled HTML file (.chm). Compiled HTML @@ -1324,7 +1416,7 @@ GENERATE_HTMLHELP = NO # written to the html output directory. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. -CHM_FILE = +CHM_FILE = # The HHC_LOCATION tag can be used to specify the location (absolute path # including file name) of the HTML help compiler (hhc.exe). If non-empty, @@ -1332,10 +1424,10 @@ CHM_FILE = # The file has to be specified with full path. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. -HHC_LOCATION = +HHC_LOCATION = # The GENERATE_CHI flag controls if a separate .chi index file is generated -# (YES) or that it should be included in the master .chm file (NO). +# (YES) or that it should be included in the main .chm file (NO). # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. @@ -1345,7 +1437,7 @@ GENERATE_CHI = NO # and project file content. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. -CHM_INDEX_ENCODING = +CHM_INDEX_ENCODING = # The BINARY_TOC flag controls whether a binary table of contents is generated # (YES) or a normal table of contents (NO) in the .chm file. Furthermore it @@ -1376,11 +1468,12 @@ GENERATE_QHP = NO # the HTML output folder. # This tag requires that the tag GENERATE_QHP is set to YES. -QCH_FILE = +QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help # Project output. For more information please see Qt Help Project / Namespace -# (see: http://doc.qt.io/qt-4.8/qthelpproject.html#namespace). +# (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_QHP is set to YES. @@ -1388,7 +1481,8 @@ QHP_NAMESPACE = org.doxygen.Project # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt # Help Project output. For more information please see Qt Help Project / Virtual -# Folders (see: http://doc.qt.io/qt-4.8/qthelpproject.html#virtual-folders). +# Folders (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). # The default value is: doc. # This tag requires that the tag GENERATE_QHP is set to YES. @@ -1396,31 +1490,33 @@ QHP_VIRTUAL_FOLDER = doc # If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom # filter to add. For more information please see Qt Help Project / Custom -# Filters (see: http://doc.qt.io/qt-4.8/qthelpproject.html#custom-filters). +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. -QHP_CUST_FILTER_NAME = +QHP_CUST_FILTER_NAME = # The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see Qt Help Project / Custom -# Filters (see: http://doc.qt.io/qt-4.8/qthelpproject.html#custom-filters). +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. -QHP_CUST_FILTER_ATTRS = +QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this # project's filter section matches. Qt Help Project / Filter Attributes (see: -# http://doc.qt.io/qt-4.8/qthelpproject.html#filter-attributes). +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). # This tag requires that the tag GENERATE_QHP is set to YES. -QHP_SECT_FILTER_ATTRS = +QHP_SECT_FILTER_ATTRS = -# The QHG_LOCATION tag can be used to specify the location of Qt's -# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the -# generated .qhp file. +# The QHG_LOCATION tag can be used to specify the location (absolute path +# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to +# run qhelpgenerator on the generated .qhp file. # This tag requires that the tag GENERATE_QHP is set to YES. -QHG_LOCATION = +QHG_LOCATION = # If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be # generated, together with the HTML files, they form an Eclipse help plugin. To @@ -1460,16 +1556,28 @@ DISABLE_INDEX = NO # to work a browser that supports JavaScript, DHTML, CSS and frames is required # (i.e. any modern browser). Windows users are probably better off using the # HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can -# further fine-tune the look of the index. As an example, the default style -# sheet generated by doxygen has an example that shows how to put an image at -# the root of the tree instead of the PROJECT_NAME. Since the tree basically has -# the same information as the tab index, you could consider setting -# DISABLE_INDEX to YES when enabling this option. +# further fine tune the look of the index (see "Fine-tuning the output"). As an +# example, the default style sheet generated by doxygen has an example that +# shows how to put an image at the root of the tree instead of the PROJECT_NAME. +# Since the tree basically has the same information as the tab index, you could +# consider setting DISABLE_INDEX to YES when enabling this option. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_TREEVIEW = YES +# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the +# FULL_SIDEBAR option determines if the side bar is limited to only the treeview +# area (value NO) or if it should extend to the full height of the window (value +# YES). Setting this to YES gives a layout similar to +# https://docs.readthedocs.io with more room for contents, but less room for the +# project logo, title, and description. If either GENERATE_TREEVIEW or +# DISABLE_INDEX is set to NO, this option has no effect. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FULL_SIDEBAR = NO + # The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that # doxygen will group on one line in the generated HTML documentation. # @@ -1494,6 +1602,24 @@ TREEVIEW_WIDTH = 250 EXT_LINKS_IN_WINDOW = NO +# If the OBFUSCATE_EMAILS tag is set to YES, doxygen will obfuscate email +# addresses. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +OBFUSCATE_EMAILS = YES + +# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg +# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see +# https://inkscape.org) to generate formulas as SVG images instead of PNGs for +# the HTML output. These images will generally look nicer at scaled resolutions. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). +# The default value is: png. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FORMULA_FORMAT = png + # Use this tag to change the font size of LaTeX formulas included as images in # the HTML documentation. When you change the font size after a successful # doxygen run you need to manually remove any form_*.png images from the HTML @@ -1514,8 +1640,14 @@ FORMULA_FONTSIZE = 10 FORMULA_TRANSPARENT = YES +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. + +FORMULA_MACROFILE = + # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see -# https://www.mathjax.org) which uses client side Javascript for the rendering +# https://www.mathjax.org) which uses client side JavaScript for the rendering # instead of using pre-rendered bitmaps. Use this if you do not have LaTeX # installed or if you want to formulas look prettier in the HTML output. When # enabled you may also need to install MathJax separately and configure the path @@ -1525,11 +1657,29 @@ FORMULA_TRANSPARENT = YES USE_MATHJAX = NO +# With MATHJAX_VERSION it is possible to specify the MathJax version to be used. +# Note that the different versions of MathJax have different requirements with +# regards to the different settings, so it is possible that also other MathJax +# settings have to be changed when switching between the different MathJax +# versions. +# Possible values are: MathJax_2 and MathJax_3. +# The default value is: MathJax_2. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_VERSION = MathJax_2 + # When MathJax is enabled you can set the default output format to be used for -# the MathJax output. See the MathJax site (see: -# http://docs.mathjax.org/en/latest/output.html) for more details. +# the MathJax output. For more details about the output format see MathJax +# version 2 (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3 +# (see: +# http://docs.mathjax.org/en/latest/web/components/output.html). # Possible values are: HTML-CSS (which is slower, but has the best -# compatibility), NativeMML (i.e. MathML) and SVG. +# compatibility. This is the name for Mathjax version 2, for MathJax version 3 +# this will be translated into chtml), NativeMML (i.e. MathML. Only supported +# for NathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This +# is the name for Mathjax version 3, for MathJax version 2 this will be +# translated into HTML-CSS) and SVG. # The default value is: HTML-CSS. # This tag requires that the tag USE_MATHJAX is set to YES. @@ -1542,26 +1692,33 @@ MATHJAX_FORMAT = HTML-CSS # MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax # Content Delivery Network so you can quickly see the result without installing # MathJax. However, it is strongly recommended to install a local copy of -# MathJax from https://www.mathjax.org before deployment. -# The default value is: https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/. +# MathJax from https://www.mathjax.org before deployment. The default value is: +# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2 +# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3 # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_RELPATH = https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/ # The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax # extension names that should be enabled during MathJax rendering. For example +# for MathJax version 2 (see +# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions): # MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# For example for MathJax version 3 (see +# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html): +# MATHJAX_EXTENSIONS = ams # This tag requires that the tag USE_MATHJAX is set to YES. -MATHJAX_EXTENSIONS = +MATHJAX_EXTENSIONS = # The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces # of code that will be used on startup of the MathJax code. See the MathJax site -# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an # example see the documentation. # This tag requires that the tag USE_MATHJAX is set to YES. -MATHJAX_CODEFILE = +MATHJAX_CODEFILE = # When the SEARCHENGINE tag is enabled doxygen will generate a search box for # the HTML output. The underlying search engine uses javascript and DHTML and @@ -1585,7 +1742,7 @@ MATHJAX_CODEFILE = SEARCHENGINE = YES # When the SERVER_BASED_SEARCH tag is enabled the search engine will be -# implemented using a web server instead of a web client using Javascript. There +# implemented using a web server instead of a web client using JavaScript. There # are two flavors of web server based searching depending on the EXTERNAL_SEARCH # setting. When disabled, doxygen will generate a PHP script for searching and # an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing @@ -1604,7 +1761,8 @@ SERVER_BASED_SEARCH = NO # # Doxygen ships with an example indexer (doxyindexer) and search engine # (doxysearch.cgi) which are based on the open source search engine library -# Xapian (see: https://xapian.org/). +# Xapian (see: +# https://xapian.org/). # # See the section "External Indexing and Searching" for details. # The default value is: NO. @@ -1617,11 +1775,12 @@ EXTERNAL_SEARCH = NO # # Doxygen ships with an example indexer (doxyindexer) and search engine # (doxysearch.cgi) which are based on the open source search engine library -# Xapian (see: https://xapian.org/). See the section "External Indexing and -# Searching" for details. +# Xapian (see: +# https://xapian.org/). See the section "External Indexing and Searching" for +# details. # This tag requires that the tag SEARCHENGINE is set to YES. -SEARCHENGINE_URL = +SEARCHENGINE_URL = # When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed # search data is written to a file for indexing by an external tool. With the @@ -1637,7 +1796,7 @@ SEARCHDATA_FILE = searchdata.xml # projects and redirect the results back to the right project. # This tag requires that the tag SEARCHENGINE is set to YES. -EXTERNAL_SEARCH_ID = +EXTERNAL_SEARCH_ID = # The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen # projects other than the one defined by this configuration file, but that are @@ -1647,7 +1806,7 @@ EXTERNAL_SEARCH_ID = # EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ... # This tag requires that the tag SEARCHENGINE is set to YES. -EXTRA_SEARCH_MAPPINGS = +EXTRA_SEARCH_MAPPINGS = #--------------------------------------------------------------------------- # Configuration options related to the LaTeX output @@ -1669,21 +1828,35 @@ LATEX_OUTPUT = latex # The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be # invoked. # -# Note that when enabling USE_PDFLATEX this option is only used for generating -# bitmaps for formulas in the HTML output, but not in the Makefile that is -# written to the output directory. -# The default file is: latex. +# Note that when not enabling USE_PDFLATEX the default is latex when enabling +# USE_PDFLATEX the default is pdflatex and when in the later case latex is +# chosen this is overwritten by pdflatex. For specific output languages the +# default can have been set differently, this depends on the implementation of +# the output language. # This tag requires that the tag GENERATE_LATEX is set to YES. LATEX_CMD_NAME = latex # The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate # index for LaTeX. +# Note: This tag is used in the Makefile / make.bat. +# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file +# (.tex). # The default file is: makeindex. # This tag requires that the tag GENERATE_LATEX is set to YES. MAKEINDEX_CMD_NAME = makeindex +# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to +# generate index for LaTeX. In case there is no backslash (\) as first character +# it will be automatically added in the LaTeX code. +# Note: This tag is used in the generated output file (.tex). +# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat. +# The default value is: makeindex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_MAKEINDEX_CMD = makeindex + # If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX # documents. This may be useful for small projects and may help to save some # trees in general. @@ -1711,34 +1884,36 @@ PAPER_TYPE = a4 # If left blank no extra packages will be included. # This tag requires that the tag GENERATE_LATEX is set to YES. -EXTRA_PACKAGES = +EXTRA_PACKAGES = -# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the -# generated LaTeX document. The header should contain everything until the first -# chapter. If it is left blank doxygen will generate a standard header. See -# section "Doxygen usage" for information on how to let doxygen write the -# default header to a separate file. +# The LATEX_HEADER tag can be used to specify a user-defined LaTeX header for +# the generated LaTeX document. The header should contain everything until the +# first chapter. If it is left blank doxygen will generate a standard header. It +# is highly recommended to start with a default header using +# doxygen -w latex new_header.tex new_footer.tex new_stylesheet.sty +# and then modify the file new_header.tex. See also section "Doxygen usage" for +# information on how to generate the default header that doxygen normally uses. # -# Note: Only use a user-defined header if you know what you are doing! The -# following commands have a special meaning inside the header: $title, -# $datetime, $date, $doxygenversion, $projectname, $projectnumber, -# $projectbrief, $projectlogo. Doxygen will replace $title with the empty -# string, for the replacement values of the other commands the user is referred -# to HTML_HEADER. +# Note: Only use a user-defined header if you know what you are doing! +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. The following +# commands have a special meaning inside the header (and footer): For a +# description of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_LATEX is set to YES. -LATEX_HEADER = +LATEX_HEADER = -# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the -# generated LaTeX document. The footer should contain everything after the last -# chapter. If it is left blank doxygen will generate a standard footer. See +# The LATEX_FOOTER tag can be used to specify a user-defined LaTeX footer for +# the generated LaTeX document. The footer should contain everything after the +# last chapter. If it is left blank doxygen will generate a standard footer. See # LATEX_HEADER for more information on how to generate a default footer and what -# special commands can be used inside the footer. -# -# Note: Only use a user-defined footer if you know what you are doing! +# special commands can be used inside the footer. See also section "Doxygen +# usage" for information on how to generate the default footer that doxygen +# normally uses. Note: Only use a user-defined footer if you know what you are +# doing! # This tag requires that the tag GENERATE_LATEX is set to YES. -LATEX_FOOTER = +LATEX_FOOTER = # The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined # LaTeX style sheets that are included after the standard style sheets created @@ -1749,7 +1924,7 @@ LATEX_FOOTER = # list). # This tag requires that the tag GENERATE_LATEX is set to YES. -LATEX_EXTRA_STYLESHEET = +LATEX_EXTRA_STYLESHEET = # The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the LATEX_OUTPUT output @@ -1757,7 +1932,7 @@ LATEX_EXTRA_STYLESHEET = # markers available. # This tag requires that the tag GENERATE_LATEX is set to YES. -LATEX_EXTRA_FILES = +LATEX_EXTRA_FILES = # If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is # prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will @@ -1768,9 +1943,11 @@ LATEX_EXTRA_FILES = PDF_HYPERLINKS = YES -# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate -# the PDF file directly from the LaTeX files. Set this option to YES, to get a -# higher quality PDF documentation. +# If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as +# specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX +# files. Set this option to YES, to get a higher quality PDF documentation. +# +# See also section LATEX_CMD_NAME for selecting the engine. # The default value is: YES. # This tag requires that the tag GENERATE_LATEX is set to YES. @@ -1778,8 +1955,7 @@ USE_PDFLATEX = YES # If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode # command to the generated LaTeX files. This will instruct LaTeX to keep running -# if errors occur, instead of asking the user for help. This option is also used -# when generating formulas in HTML. +# if errors occur, instead of asking the user for help. # The default value is: NO. # This tag requires that the tag GENERATE_LATEX is set to YES. @@ -1792,16 +1968,6 @@ LATEX_BATCHMODE = NO LATEX_HIDE_INDICES = NO -# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source -# code with syntax highlighting in the LaTeX output. -# -# Note that which sources are shown also depends on other settings such as -# SOURCE_BROWSER. -# The default value is: NO. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_SOURCE_CODE = NO - # The LATEX_BIB_STYLE tag can be used to specify the style to use for the # bibliography, e.g. plainnat, or ieeetr. See # https://en.wikipedia.org/wiki/BibTeX and \cite for more info. @@ -1818,6 +1984,14 @@ LATEX_BIB_STYLE = plain LATEX_TIMESTAMP = NO +# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute) +# path from which the emoji images will be read. If a relative path is entered, +# it will be relative to the LATEX_OUTPUT directory. If left blank the +# LATEX_OUTPUT directory will be used. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EMOJI_DIRECTORY = + #--------------------------------------------------------------------------- # Configuration options related to the RTF output #--------------------------------------------------------------------------- @@ -1857,32 +2031,22 @@ COMPACT_RTF = NO RTF_HYPERLINKS = NO -# Load stylesheet definitions from file. Syntax is similar to doxygen's config -# file, i.e. a series of assignments. You only have to provide replacements, -# missing definitions are set to their default value. +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# configuration file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. # # See also section "Doxygen usage" for information on how to generate the # default style sheet that doxygen normally uses. # This tag requires that the tag GENERATE_RTF is set to YES. -RTF_STYLESHEET_FILE = +RTF_STYLESHEET_FILE = # Set optional variables used in the generation of an RTF document. Syntax is -# similar to doxygen's config file. A template extensions file can be generated -# using doxygen -e rtf extensionFile. +# similar to doxygen's configuration file. A template extensions file can be +# generated using doxygen -e rtf extensionFile. # This tag requires that the tag GENERATE_RTF is set to YES. -RTF_EXTENSIONS_FILE = - -# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code -# with syntax highlighting in the RTF output. -# -# Note that which sources are shown also depends on other settings such as -# SOURCE_BROWSER. -# The default value is: NO. -# This tag requires that the tag GENERATE_RTF is set to YES. - -RTF_SOURCE_CODE = NO +RTF_EXTENSIONS_FILE = #--------------------------------------------------------------------------- # Configuration options related to the man page output @@ -1917,7 +2081,7 @@ MAN_EXTENSION = .3 # MAN_EXTENSION with the initial . removed. # This tag requires that the tag GENERATE_MAN is set to YES. -MAN_SUBDIR = +MAN_SUBDIR = # If the MAN_LINKS tag is set to YES and doxygen generates man output, then it # will generate one additional man file for each entity documented in the real @@ -1955,6 +2119,13 @@ XML_OUTPUT = xml XML_PROGRAMLISTING = YES +# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include +# namespace members in file scope as well, matching the HTML output. +# The default value is: NO. +# This tag requires that the tag GENERATE_XML is set to YES. + +XML_NS_MEMB_FILE_SCOPE = NO + #--------------------------------------------------------------------------- # Configuration options related to the DOCBOOK output #--------------------------------------------------------------------------- @@ -1973,15 +2144,6 @@ GENERATE_DOCBOOK = NO DOCBOOK_OUTPUT = docbook -# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the -# program listings (including syntax highlighting and cross-referencing -# information) to the DOCBOOK output. Note that enabling this will significantly -# increase the size of the DOCBOOK output. -# The default value is: NO. -# This tag requires that the tag GENERATE_DOCBOOK is set to YES. - -DOCBOOK_PROGRAMLISTING = NO - #--------------------------------------------------------------------------- # Configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- @@ -2030,7 +2192,7 @@ PERLMOD_PRETTY = YES # overwrite each other's variables. # This tag requires that the tag GENERATE_PERLMOD is set to YES. -PERLMOD_MAKEVAR_PREFIX = +PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the preprocessor @@ -2071,7 +2233,7 @@ SEARCH_INCLUDES = YES # preprocessor. # This tag requires that the tag SEARCH_INCLUDES is set to YES. -INCLUDE_PATH = +INCLUDE_PATH = # You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard # patterns (like *.h and *.hpp) to filter out the header-files in the @@ -2079,7 +2241,7 @@ INCLUDE_PATH = # used. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -INCLUDE_FILE_PATTERNS = +INCLUDE_FILE_PATTERNS = # The PREDEFINED tag can be used to specify one or more macro names that are # defined before the preprocessor is started (similar to the -D option of e.g. @@ -2089,7 +2251,7 @@ INCLUDE_FILE_PATTERNS = # recursively expanded use the := operator instead of the = operator. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -PREDEFINED = +PREDEFINED = # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this # tag can be used to specify a list of macro names that should be expanded. The @@ -2098,7 +2260,7 @@ PREDEFINED = # definition found in the source code. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -EXPAND_AS_DEFINED = +EXPAND_AS_DEFINED = # If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will # remove all references to function-like macros that are alone on a line, have @@ -2127,13 +2289,13 @@ SKIP_FUNCTION_MACROS = YES # the path). If a tag file is not located in the directory in which doxygen is # run, you must also specify the path to the tagfile here. -TAGFILES = +TAGFILES = # When a file name is specified after GENERATE_TAGFILE, doxygen will create a # tag file that is based on the input files it reads. See section "Linking to # external documentation" for more information about the usage of tag files. -GENERATE_TAGFILE = +GENERATE_TAGFILE = # If the ALLEXTERNALS tag is set to YES, all external class will be listed in # the class index. If set to NO, only the inherited external classes will be @@ -2156,40 +2318,16 @@ EXTERNAL_GROUPS = YES EXTERNAL_PAGES = YES -# The PERL_PATH should be the absolute path and name of the perl script -# interpreter (i.e. the result of 'which perl'). -# The default file (with absolute path) is: /usr/bin/perl. - -PERL_PATH = /usr/bin/perl - #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- -# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram -# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to -# NO turns the diagrams off. Note that this option also works with HAVE_DOT -# disabled, but it is recommended to install and use dot, since it yields more -# powerful graphs. -# The default value is: YES. - -CLASS_DIAGRAMS = YES - -# You can define message sequence charts within doxygen comments using the \msc -# command. Doxygen will then run the mscgen tool (see: -# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the -# documentation. The MSCGEN_PATH tag allows you to specify the directory where -# the mscgen tool resides. If left empty the tool is assumed to be found in the -# default search path. - -MSCGEN_PATH = - # You can include diagrams made with dia in doxygen documentation. Doxygen will # then run dia to produce the diagram and insert it in the documentation. The # DIA_PATH tag allows you to specify the directory where the dia binary resides. # If left empty dia is assumed to be found in the default search path. -DIA_PATH = +DIA_PATH = # If set to YES the inheritance and collaboration graphs will hide inheritance # and usage relations if the target is undocumented or is not a class. @@ -2204,7 +2342,7 @@ HIDE_UNDOC_RELATIONS = YES # set to NO # The default value is: NO. -HAVE_DOT = NO +HAVE_DOT = YES # The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed # to run in parallel. When set to 0 doxygen will base this on the number of @@ -2238,13 +2376,16 @@ DOT_FONTSIZE = 10 # the path where dot can find it using this tag. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_FONTPATH = +DOT_FONTPATH = -# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for -# each documented class showing the direct and indirect inheritance relations. -# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO. +# If the CLASS_GRAPH tag is set to YES (or GRAPH) then doxygen will generate a +# graph for each documented class showing the direct and indirect inheritance +# relations. In case HAVE_DOT is set as well dot will be used to draw the graph, +# otherwise the built-in generator will be used. If the CLASS_GRAPH tag is set +# to TEXT the direct and indirect inheritance relations will be shown as texts / +# links. +# Possible values are: NO, YES, TEXT and GRAPH. # The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. CLASS_GRAPH = YES @@ -2281,10 +2422,32 @@ UML_LOOK = NO # but if the number exceeds 15, the total amount of fields shown is limited to # 10. # Minimum value: 0, maximum value: 100, default value: 10. -# This tag requires that the tag HAVE_DOT is set to YES. +# This tag requires that the tag UML_LOOK is set to YES. UML_LIMIT_NUM_FIELDS = 10 +# If the DOT_UML_DETAILS tag is set to NO, doxygen will show attributes and +# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS +# tag is set to YES, doxygen will add type and arguments for attributes and +# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, doxygen +# will not generate fields with class member information in the UML graphs. The +# class diagrams will look similar to the default class diagrams but using UML +# notation for the relationships. +# Possible values are: NO, YES and NONE. +# The default value is: NO. +# This tag requires that the tag UML_LOOK is set to YES. + +DOT_UML_DETAILS = NO + +# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters +# to display on a single line. If the actual line length exceeds this threshold +# significantly it will wrapped across multiple lines. Some heuristics are apply +# to avoid ugly line breaks. +# Minimum value: 0, maximum value: 1000, default value: 17. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_WRAP_THRESHOLD = 17 + # If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and # collaboration graphs will show the relations between templates and their # instances. @@ -2351,6 +2514,13 @@ GRAPHICAL_HIERARCHY = YES DIRECTORY_GRAPH = YES +# The DIR_GRAPH_MAX_DEPTH tag can be used to limit the maximum number of levels +# of child directories generated in directory dependency graphs by dot. +# Minimum value: 1, maximum value: 25, default value: 1. +# This tag requires that the tag DIRECTORY_GRAPH is set to YES. + +DIR_GRAPH_MAX_DEPTH = 1 + # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images # generated by dot. For an explanation of the image formats see the section # output formats in the documentation of the dot tool (Graphviz (see: @@ -2382,44 +2552,44 @@ INTERACTIVE_SVG = NO # found. If left blank, it is assumed the dot tool can be found in the path. # This tag requires that the tag HAVE_DOT is set to YES. -DOT_PATH = +DOT_PATH = # The DOTFILE_DIRS tag can be used to specify one or more directories that # contain dot files that are included in the documentation (see the \dotfile # command). # This tag requires that the tag HAVE_DOT is set to YES. -DOTFILE_DIRS = +DOTFILE_DIRS = # The MSCFILE_DIRS tag can be used to specify one or more directories that # contain msc files that are included in the documentation (see the \mscfile # command). -MSCFILE_DIRS = +MSCFILE_DIRS = # The DIAFILE_DIRS tag can be used to specify one or more directories that # contain dia files that are included in the documentation (see the \diafile # command). -DIAFILE_DIRS = +DIAFILE_DIRS = # When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the -# path where java can find the plantuml.jar file. If left blank, it is assumed -# PlantUML is not used or called during a preprocessing step. Doxygen will -# generate a warning when it encounters a \startuml command in this case and -# will not generate output for the diagram. +# path where java can find the plantuml.jar file or to the filename of jar file +# to be used. If left blank, it is assumed PlantUML is not used or called during +# a preprocessing step. Doxygen will generate a warning when it encounters a +# \startuml command in this case and will not generate output for the diagram. -PLANTUML_JAR_PATH = +PLANTUML_JAR_PATH = # When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a # configuration file for plantuml. -PLANTUML_CFG_FILE = +PLANTUML_CFG_FILE = # When using plantuml, the specified paths are searched for files specified by # the !include statement in a plantuml block. -PLANTUML_INCLUDE_PATH = +PLANTUML_INCLUDE_PATH = # The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes # that will be shown in the graph. If the number of nodes in a graph becomes @@ -2469,14 +2639,18 @@ DOT_MULTI_TARGETS = NO # If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page # explaining the meaning of the various boxes and arrows in the dot generated # graphs. +# Note: This tag requires that UML_LOOK isn't set, i.e. the doxygen internal +# graphical representation for inheritance and collaboration diagrams is used. # The default value is: YES. # This tag requires that the tag HAVE_DOT is set to YES. GENERATE_LEGEND = YES -# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot +# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate # files that are used to generate the various graphs. +# +# Note: This setting is not only used for dot files but also for msc temporary +# files. # The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. DOT_CLEANUP = YES diff --git a/README.md b/README.md index 5d28a0e..89892a4 100644 --- a/README.md +++ b/README.md @@ -31,31 +31,56 @@ Future features (wish-list) For now, the source code is well-documented but it requires some additional documentation (the math behind it). This will be published -in a sister repository in a later stage. +in a sister repository (https://code.ascee.nl/ascee/lasp-doc). If you have any question(s), please feel free to contact us: info@ascee.nl. # Building from source -## Dependencies +Two commands that install all requirements (for Ubuntu / Linux Mint) -Optional dependencies, which can be turned ON/OFF using CMake: +- `pip install scipy numpy build scikit-build appdirs` +- `sudo apt install libusb-dev -- Build tools, including [http://cmake.org](CMake). -- FFTW (For really fast FFT's) +## Runtime dependencies (Linux) + +- FFTW (For really fast FFT's). If compiled with Ffftpack, this library is not + required. - libUlDAQ, for the Measurement Computing DT9837A USB DAQ box - GNU Autotools, for compiling libUlDAQ - RtAudio, for Audio DAQ backends -- Cython, for wrapping the C-code to Python. +- libusb +- BLAS (OpenBLAS, other). +- RtAudio (optional) +- UlDaq (optional) + +## Editable install + +In the root directory of the repository, run: + +- `pip3 isntall --user --prefix=~/.local -e .` +- `cmake .` +- `make -j` + + + +## Build dependencies + +Optional dependencies, which can be turned ON/OFF using CMake: + +- Build tools: compiler [http://cmake.org](CMake), the Python packages: + - Scipy + - Numpy + - py-build-cmake + - appdirs + These can all be installed using: + - The following Python packages need also be available: - `Scipy` (which includes Numpy). Install with `sudo apt install python3-scipy`, or `pacman -S scipy`. - `appdirs`, which can be grabbed from [https://pypi.org](Pypi) -## Installation of dependencies - - ## Compilation of LASP diff --git a/STL-Threadsafe b/STL-Threadsafe deleted file mode 160000 index 08b2d9e..0000000 --- a/STL-Threadsafe +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 08b2d9e7f487121088a817071d1d42b2736996e9 diff --git a/cmake/BuildType.cmake b/cmake/BuildType.cmake new file mode 100644 index 0000000..c5cd1e2 --- /dev/null +++ b/cmake/BuildType.cmake @@ -0,0 +1,11 @@ +# Set a default build type if none was specified + +set(default_build_type "Release") + +if(NOT CMAKE_BUILD_TYPE) + message(STATUS "Setting build type to '${default_build_type}' as none was specified.") + set(CMAKE_BUILD_TYPE "${default_build_type}" CACHE STRING "Choose the type of build." FORCE) + + # Set the possible values of build type for cmake-gui +endif() +set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release") diff --git a/lasp/cmake/FindNumpy.cmake b/cmake/FindNumpy.cmake similarity index 100% rename from lasp/cmake/FindNumpy.cmake rename to cmake/FindNumpy.cmake diff --git a/cmake/OSSpecific.cmake b/cmake/OSSpecific.cmake new file mode 100644 index 0000000..170c023 --- /dev/null +++ b/cmake/OSSpecific.cmake @@ -0,0 +1,29 @@ +if(WIN32) + set(home $ENV{USERPROFILE}) + + # set(miniconda_dir ${home}\\Miniconda3) + message("Building for Windows") + include_directories( + ..\\rtaudio + C:\\mingw\\mingw64\\include\\OpenBLAS + link_directories(${home}\\miniconda3\\Library\\include) + ) + set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH} $miniconda_dir\\Lib\\cmake") + # include( + add_definitions(-DMS_WIN64) + link_directories(C:\\mingw\\mingw64\\lib) + link_directories(C:\\mingw\\mingw64\\bin) + link_directories(..\\rtaudio) + link_directories(${home}\\Miniconda3) + add_definitions(-DHAS_RTAUDIO_WIN_WASAPI_API) +else() # Linux compile + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wfatal-errors") + include_directories(/usr/local/include/rtaudio) + include_directories(/usr/include/rtaudio) + link_directories(/usr/local/lib) + # This should become optional later on, and be added to the windows list as + # well. + +endif() +# The last argument here takes care of calling SIGABRT when an integer overflow +# occures. diff --git a/cmake/QueryPythonForPybind11.cmake b/cmake/QueryPythonForPybind11.cmake new file mode 100644 index 0000000..8be6181 --- /dev/null +++ b/cmake/QueryPythonForPybind11.cmake @@ -0,0 +1,26 @@ +# First tries to find Python 3, then tries to import the pybind11 module to +# query the CMake config location, and finally imports pybind11 using +# find_package(pybind11 REQUIRED CONFIG). +function(find_pybind11_python_first) + + # Query Python to see if it knows where the headers are + find_package(Python3 REQUIRED COMPONENTS Interpreter Development) + if (NOT pybind11_ROOT OR NOT EXISTS ${pybind11_ROOT}) + execute_process(COMMAND ${Python3_EXECUTABLE} + -m pybind11 --cmakedir + OUTPUT_VARIABLE PY_BUILD_PYBIND11_CMAKE + OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE PY_BUILD_CMAKE_PYBIND11_RESULT) + # If it was successful + if (PY_BUILD_CMAKE_PYBIND11_RESULT EQUAL 0) + message(STATUS "Found pybind11: ${PY_BUILD_PYBIND11_CMAKE}") + set(pybind11_ROOT ${PY_BUILD_PYBIND11_CMAKE} + CACHE PATH "Path to the pybind11 CMake configuration." FORCE) + else() + unset(pybind11_ROOT CACHE) + endif() + endif() + + find_package(pybind11 REQUIRED CONFIG) + +endfunction() diff --git a/lasp/cmake/ReplicatePythonSourceTree.cmake b/cmake/ReplicatePythonSourceTree.cmake similarity index 100% rename from lasp/cmake/ReplicatePythonSourceTree.cmake rename to cmake/ReplicatePythonSourceTree.cmake diff --git a/cmake/rtaudio.cmake b/cmake/rtaudio.cmake new file mode 100644 index 0000000..ca36779 --- /dev/null +++ b/cmake/rtaudio.cmake @@ -0,0 +1,12 @@ +# ###################################### RtAudio +if(LASP_HAS_RTAUDIO) + message("Building RtAudio backend") + if(WIN32) + set(RTAUDIO_API_WASAPI TRUE CACHE BOOL "Build for WASAPI" FORCE) + else() + set(RTAUDIO_API_PULSE TRUE CACHE BOOL "Build with PulseAudio backend" FORCE) + set(RTAUDIO_API_ALSA OFF CACHE BOOL "Do not build with Alsa backend" FORCE) + endif() + set(RTAUDIO_BUILD_STATIC_LIBS ON CACHE BOOL "Build static libs for RtAudio" FORCE) + add_subdirectory(third_party/rtaudio) +endif() diff --git a/cmake/uldaq.cmake b/cmake/uldaq.cmake new file mode 100644 index 0000000..cc0febb --- /dev/null +++ b/cmake/uldaq.cmake @@ -0,0 +1,30 @@ +# ###################################### UlDAQ +if(LASP_HAS_ULDAQ) + message("Building UlDAQ") + + if(NOT WIN32) + find_package(PkgConfig REQUIRED) + pkg_check_modules(libusb-1.0 REQUIRED libusb-1.0) + endif() + + # This is rather coarse! + file(GLOB ULDAQ_FILES1 third_party/uldaq/src/*.cpp) + file(GLOB ULDAQ_FILES2 third_party/uldaq/src/*/*.cpp) + file(GLOB ULDAQ_FILES3 third_party/uldaq/src/*/*/*.cpp) + file(GLOB ULDAQ_FILES4 third_party/uldaq/src/usb/fw/*c) + + add_library(uldaq STATIC ${ULDAQ_FILES1} ${ULDAQ_FILES2} + ${ULDAQ_FILES3} ${ULDAQ_FILES4}) + target_compile_options(uldaq PUBLIC -Wno-unused -Wno-empty-body + -Wno-missing-field-initializers) + + if(NOT WIN32) + # message("libUSB libs: ${libusb-1.0_LIBRARIES}") + target_link_libraries(uldaq ${libusb-1.0_LIBRARIES}) + + # Rules to match UlDAQ Usb devices with libusb + install(FILES third_party/uldaq/rules/50-uldaq.rules DESTINATION + /etc/udev/rules.d PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ + GROUP_WRITE WORLD_READ) + endif() +endif() diff --git a/examples/DaqConfiguration.ipynb b/examples/DaqConfiguration.ipynb new file mode 100644 index 0000000..b1580fd --- /dev/null +++ b/examples/DaqConfiguration.ipynb @@ -0,0 +1,129 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#!make -j -C ~/wip/mycode/lasp" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import lasp\n", + "ds = lasp.DeviceInfo.getDeviceInfo()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "for i, d in enumerate(ds):\n", + " print(f'{i}: ' + d.device_name)\n", + "d = ds[0]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Create a configuration using a device as prototype\n", + "config = lasp.DaqConfiguration(d)\n", + "\n", + "# Enable some channels\n", + "#config.outchannel_config[0].enabled = True\n", + "#config.outchannel_config[1].enabled = True" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Print info\n", + "print('Out channels:',d.noutchannels)\n", + "print('In channels:',d.ninchannels)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Serialize configuration to TOML\n", + "toml = config.toTOML()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(toml)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Read in configuration from serialized data\n", + "d2 = lasp.DaqConfiguration.fromTOML(toml)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Print it\n", + "print(d2.toTOML())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Check\n", + "toml == d2.toTOML()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.10" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/DaqConfigurations.ipynb b/examples/DaqConfigurations.ipynb new file mode 100644 index 0000000..2cdcd21 --- /dev/null +++ b/examples/DaqConfigurations.ipynb @@ -0,0 +1,119 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Assemble a set of configurations (one for input, one for output)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#!make -j -C ~/wip/mycode/lasp" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from lasp import *" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import lasp\n", + "print(lasp.__version__)\n", + "ds = lasp.DeviceInfo.getDeviceInfo()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "for i, d in enumerate(ds):\n", + " print(f'{i}: ' + d.device_name)\n", + "di = ds[0]\n", + "do = ds[1]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "iconfig = lasp.DaqConfiguration(di)\n", + "oconfig = lasp.DaqConfiguration(do)\n", + "ds = lasp.DaqConfigurations(False, iconfig, oconfig)\n", + "oconfig.outchannel_config[0].enabled = True\n", + "oconfig.outchannel_config[1].enabled = True\n", + "iconfig.inchannel_config[0].enabled = True\n", + "iconfig.inchannel_config[1].enabled = True" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Save configuration to a preset\n", + "# ds.save('test')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Load all configurations present\n", + "ds.loadAll()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Print some stuff\n", + "print('Out channels:',do.noutchannels)\n", + "print('In channels:',di.ninchannels)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.10" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/test_SLM.ipynb b/examples/test_SLM.ipynb new file mode 100644 index 0000000..3c62b5d --- /dev/null +++ b/examples/test_SLM.ipynb @@ -0,0 +1,200 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "%pylab is deprecated, use %matplotlib inline and import the required libraries.\n", + "Populating the interactive namespace from numpy and matplotlib\n" + ] + } + ], + "source": [ + "from lasp import SLM\n", + "import numpy as np\n", + "%pylab inline" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2022-09-03 17:21:56+0200 DebugTrace-cpp 2.0.0a2 (g++ 12.2.0)\n", + "2022-09-03 17:21:56+0200 \n", + "2022-09-03 17:21:56+0200 Enter SeriesBiquad (lasp_biquadbank.cpp: 12)\n", + "2022-09-03 17:21:56+0200 Leave SeriesBiquad (lasp_biquadbank.cpp)\n", + "2022-09-03 17:21:56+0200 \n", + "2022-09-03 17:21:56+0200 Enter SeriesBiquad (lasp_biquadbank.cpp: 12)\n", + "2022-09-03 17:21:56+0200 Leave SeriesBiquad (lasp_biquadbank.cpp)\n" + ] + } + ], + "source": [ + "fs = 44100.\n", + "Lref = 1\n", + "ds = 1\n", + "tau = 1/8\n", + "\n", + "bp_coefs = np.zeros((6,2), order='F')\n", + "bp_coefs[0,:] = 1\n", + "bp_coefs[3,:] = 1\n", + "\n", + "slm = SLM.fromBiquads(\n", + " fs, \n", + " Lref,\n", + " ds, tau, bp_coefs\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[1., 1.],\n", + " [0., 0.],\n", + " [0., 0.],\n", + " [1., 1.],\n", + " [0., 0.],\n", + " [0., 0.]])" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bp_coefs" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "x = np.zeros(20100, dtype=float)\n", + "x[:] = 1\n", + "\n", + "res = slm.run(x)\n", + "print(res.shape)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[,\n", + " ]" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXoAAAD6CAYAAACvZ4z8AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/YYfK9AAAACXBIWXMAAAsTAAALEwEAmpwYAAAfnElEQVR4nO3deZCc9X3n8fe3j7lHtzSSkJBkLlkcEswgwCbAsJgI1jZZh2zAtap1YqItOzh2xVVZO/zBOsdWUuV12btmnWDD2lljz3qNMcYhxrAeoWDMoRESCB0gJIE0OkZC0mh67u7nu390Sx5pDvU53fPo86rqmunn6X5+n+luPnr49dNPm7sjIiLhFSl3ABERKS0VvYhIyKnoRURCTkUvIhJyKnoRkZCLlTvAWObMmeNLly7N6769vb3U19cXN1ARKFf2KjETKFeulCs3hebq6Og46u5zx1zp7hV3aW5u9ny1t7fnfd9SUq7sVWImd+XKlXLlptBcwEYfp1M1dSMiEnIqehGRkFPRi4iEnIpeRCTkVPQiIiFX8qI3szVmttPMdpnZl0o9noiInKmkRW9mUeAh4A5gBXCvma0o5ZgiInKmUn9gajWwy913A5hZG3AXsK3E44rIeShIpUilkqRSSYJUklQqRZAcTl8PUgSpFH0nDnNgzw5SqWGCVAoPkgSpFEHmPkGQhFQqffsgiaeSeCqFexJPBQRBCjyFB57+6QEEKTwIcA8wT//0IAUegAd4EJz+HU/B6ds67ilwZ+D4+7y071dcv/aviv64mJfwfPRmdjewxt3vy1xfC1zn7vePcdt1wDqApqam5ra2trzGTCQSNDQ05B+6RJQre5WYCcKXy4MgU37DBMnh9M/UcKbYhvHUEJ5KQjAMqeH0zyAFQQrzZKawkliQwvzUJX094kmC5DDxiGOevh7J3CZ66jopIh4QJb0uSpKop4gSEPUkUVLESKVvR0DUAyKkL1FSRPHM78Fvf9rU/n6NLp/Jttbv5nXf1tbWDndvGWtdxZwCwd0fBh4GaGlp8VtuuSWv7axfv55871tKypW9SswEpcnlQcDgQB8DfQkGB3oZGuhjeKCP4YFekoP9JAd7SQ31EQz1py/D/fjwAAz3YcP9WHIAeo7TUB0hkhokGgwRCYaJ+jDRYIiYDxPz9PW4DxMn/bOKJHGSJSvGpEdIEiVJjKSlazlFlKTF0vVt0XRdW4zAYqQiMYJILUmLMWxRAovhkRhBJI5bFLcoWASPROHU9TN+j2AWTa/PLLdRv8ewSJRDXV0sWLjo9HqLRIlEY+nbRdO3iWSWWzROJPrb25xad2qZWQSLRDLLY1gkQiQSyfyMnv55enuZ9dFobMS69M/nn3+e1ltvZV4Jno9SF30nsHjE9UWZZSJTigcByaEBjh7ax2DfSQZ6exjuO8lQfw/JgR5SAwmCwQTBYC8MJbChXiLDvUSTfUSTfcRTfVQF/VQF/dQE/dQwQLUPUWPD1AA1eWQa9DgDVsWgVzE0VE3SqkhanJTFSUbiDMYa6I9UEUTiBJEqgkgVHv3thczFYtUQq8Zi1Visikismki8mki8hmi8mkismmhVNbF4DdF4FdFYnGismmgsRixWlV4WryKeWReLxYlFo7xQwf9gX1uBuSxSurdMS130rwKXmNky0gV/D/DJEo8pMkqQStFz4ig9x4/Q33OMwcRxhnqPk+rrJtXfjQ+cxAa7iQ71EBvuIZ5MUJ1MUBskqPc+6r2X2ywFL2Yxlht91NBvNQxYLYORWoYitfTFZtATW0gyVo/HavBYLR6vhVgNFq/F4rVEq+uIVNUSraojWl1LrLqOeHU9VbX1VNXUUV1TT3VtPdU1dVRHo1RTuf8HJJWjpEXv7kkzux94BogCj7r7m6UcU8JvaHCAE0cP0nPsMP3dXQyefJ9k4ihB3/tY/3GiA8epGjpBzXA3damTNPpJpnkv082ZPsF2E15Lwurpj9QzEK2nr2oW3fGlpOINBNXTONabZM7CpURqGonWNBKvbSBe20h1XSPVddOpqW+krmE61TV1NEQiVN5svpyvSj5H7+5PA0+XehyZ2oYGBzh+pJOTRzo58c7LvHpsC8mew1iii/jAUWoG36cheZwZwTGm08s8GHMus8+rOWnTSESn0R+bRqJ2AQeqZ+I1M6FuFtH6WcTrZ1JVP4PqhpnUTZtJ3bTZNDTOoCEWm7Cc169fz3Xac5YpqGLejJXw6u/t4eiB3XQffpf+o/tInejEejqp7j9Mw2AXM1NHmU03TUATcAnAvvR9E17LicgMemKzeL92GYdqryWom0ukcR7xxjlUN86lbsYcGmY2MW3WPOpq6qgr358qUpFU9FKwvkQ3Xfve5kTn2wwc2Q3H36U6sY/pAweYHRxhOr0s5sx35U/QwLHIHHqq5/F+7eW81bCASGMTVdOb2Hekh5YP38qseRfQUNegKRCRAqnoJSuJk8c5+M4bdO/bxnDXTuLd79LY38mc5EFm083SEbft82oOR+fTXbOQI3XXEDQuJD5zEbWzL2R60xJmL1jCjPpGZowzVvf69Sxcelnp/yiR84SKXk7zIKDrwB4Ov/0afQd3YO+/TUPPHuYO7WMex9JTKkDKjcOReRyrWsA7jb/D29OXEJu7jMami5h74WXMnLOAZSU8VExEcqOiP0/1JbrZ/9ZrnHzzX3j5zf9NQ/dOFg3tpolemjK3OUk9B2OLeXf6at6ZdTHV85cz+8IVzF/2QRbW1LGwrH+BiGRLRX8eGOhLsHfrbzix6yViB1+jKbGdC4KDXGrOpUCv17Avvowds2+DeZfTeOFVNH3gSmbNXcg07ZmLTHkq+pDxIOC9t1/n8Nb1eGcHc7q3siS5l+UWAHCY2RyoW87+OR+lZtFKDiQi3HHXH7I8Gi1zchEpFRX9FBekUuzZ9ipHtv6Kqv2/YWnvZpZwkiWkp1721izn1fk3U7t0NYsu/zBNC5ecnpqB9BufEZW8SKip6KegA3t2sO/Vp6ja+ysu6tvCRfRyEXDA5vHO9BvYdeGHmH/lLSy++Cqu0tSLyHlPRT8FDPQl2PnS0wxsf4aFR19ksR9gIeli3zGzlciyG1m06jYWXniJ3iAVkVFU9BWqp/sYO//1x0R2PMXynpdZaYP0exVv1a6kc8l/4IKWj7LooitZqD12ETkHFX0FSZw8zrb/932q3/oZH+zbRIslOcoM3phzB7VXfpxLr1vDytr6cscUkSlGRV9mqWSSbb/+GYMdj3F59wZW2xAHbB6b5v8BM5p/n0ubb2WO3iwVkQKo6Mvk0L5d7PnFN7m486dcyXFOUs/rc+5k+vVruaz5Vk3JiEjRqOgnkQcBW194iuRL/8hVvS8yF3ij7jr2r/okK27+A66r0XkXRaT4VPSTYHhokO7tz/Huhs9xZfAex5nGKxesZenv3s+qJTp5l4iUloq+hAb6e9ny1EMs3v5t7vIu9kSW8Oqq/8qVv/spbtCbqiIySVT0JZAcHuK1p77Fkte/znUcY2dsOU8u+GM+9qm/YJneWBWRSVbyojezvUAPkAKS7t5S6jHLxYOALb/6P8x48W+5NtjHzthyum75Bpd/6KMc3LBBpxoQkbKYrD36Vnc/OkljlcX+XVs59uM/Y9VAB/tsIa/d8N9Z9ZG1mI6eEZEyM3cv7QDpPfqWcxW9ma0D1gE0NTU1t7W15TVeIpGgoWHyvnwuOTzI8Bs/5tbuJxgixvrZ91K34k6isXhZc2WrEnNVYiZQrlwpV24KzdXa2tox7oyJu5f0AuwBNgEdwLps7tPc3Oz5am9vz/u+uXp78wu+5yuXuz84zTd+9ff8SOfeisiVi0rMVYmZ3JUrV8qVm0JzARt9nE4teOrGzJ4D5o+x6gF3fxK40d07zWwe8KyZ7XD3DYWOW06pZJJXHnuQ5t3f4oRN5/WbH6G59e5yxxIRGVPBRe/ut51jfWfmZ5eZPQGsBqZs0R/r6uTgd+7lhqEtbGq8mYv+6NtcNbvp3HcUESmTkr5TaGb1ZtZ46nfgdmBrKccspbc2Pc/Q/7yJiwa38crKv+bqP/8p01XyIlLhSn3UTRPwhJmdGusH7v6LEo9ZEhv/+dtc+cqXOWYz2P+Jn7J65Y3ljiQikpWSFr277wZWlnKMyfDSY3/F9W//N7ZVX8mCP/m/LJi7oNyRRESypk/GTsCDgJe+/TluOPh9NtXfxIr726jRqQtEZIpR0Y/Dg4CXH76fGw49xsuzf4+WzzxCNKaHS0SmHjXXOF569Ivpkp/zCVZ/9hF9wlVEpiy11xhe+sHfcMP+R3ll5ke59jPfUcmLyJSmBjvL5ud+yOqdX+W1+htp/tPv6URkIjLlqehH2L31ZS7918/zTvxiln+2TXPyIhIKKvqMxMnjxB//FAmrZ+YfP05tfWO5I4mIFIWKnvQRNju+cx8Lg4Mcuf0h5ixcUu5IIiJFo6IHNj71D7ScfI5Xlv4nLv/QneWOIyJSVOd90R89tI9LX/sbdsRXsHrt35Y7johI0Z33Rf/uY5+j1gep/f2H9OariITSeV30r69/nOaedjqW3seS5deUO46ISEmct0WfSiZp3PAVOq2Ja+59sNxxRERK5rwt+o4nv8my4F0OXftlqmvqyh1HRKRkzsuiHxzoY+kb32BnbDnXrPmP5Y4jIlJS52XRb37qW8zjGEM3/aXOYyMioXfetVxyeIhF2/6Rt2KXcsWNHyt3HBGRkitK0ZvZo2bWZWZbz1q+xsx2mtkuM/tSMcYq1OZffo8L/DCJa/9Me/Micl4oVtN9F1gzcoGZRYGHgDuAFcC9ZraiSOPlrX7z/2K/LWDVbZ8sdxQRkUlRlKJ39w3AsbMWrwZ2uftudx8C2oC7ijFevvZu38gHh99k/wf+vU4/LCLnDXP34mzIbCnwc3e/InP9bmCNu9+Xub4WuM7d7x/n/uuAdQBNTU3NbW1teeVIJBI0NDSMuS758j9wU9+z/Gr1I9TUz8hr+/maKFc5VWKuSswEypUr5cpNoblaW1s73L1lzJXufs4L8BywdYzLXSNusxTYOuL63cB3RlxfC3wzm/Gam5s9X+3t7WMuHxzo9xMPLvCNX70r720XYrxc5VaJuSoxk7ty5Uq5clNoLmCjj9OpWZ3cxd1vy+MfmE5g8YjrizLLymL7i0+xkl6iq+4pVwQRkbIo5WEnrwKXmNkyM6sC7gF+VsLxJjS05XFOUscHP/zxckUQESmLYh1e+UPgN8BlZrbfzD7t7kngfuAZYDvwI3d/sxjj5WpocIDLTjzPzuk36XQHInLeKcp5ed393nGWPw08XYwxCrHzpae5kj7iV/27ckcREZl058Unhnq3PcOgx7ns+n9b7igiIpPuvCj6+Ud+zVs1V+gLv0XkvBT6oj+0bxdLg330Lr653FFERMoi9EX/3sb0WwRNV+tLv0Xk/BT6oue9lzhBA0uWj/2BMRGRsAt90Td1v867tZfr3DYict4KddF3HzvCkmAffU364m8ROX+Fuuj3blkPQOMlHy5vEBGRMgp10ffteYWUG8uu+p1yRxERKZtQF331+9s5EFlAfeOMckcRESmbUBf9vL5dHKm/uNwxRETKKrRF39tzgoXBIQZnfbDcUUREyiq0Rb9/5yYi5tQsuqrcUUREyiq0Rd/97hYA5l18dZmTiIiUV2iLPjjyNkMeY/6Fl5U7iohIWYW26Kt63uNQtIlorCin3BcRmbJCW/TTB/ZzvPqCcscQESm7UBa9BwHzkwcZaLiw3FFERMquWN8Z+6iZdZnZ1rOW7zWzN8xss5ltLMZY2Xi/q5N6G8BnfWCyhhQRqVjF2qP/LrBmnHWt7r7K3SftPMFH39sBQG3TRZM1pIhIxTJ3L86GzJYCP3f3K0Ys2wu0uPvRLO6/DlgH0NTU1NzW1pZXjkQiQaqzg7sOfo2nVnydxnnL8tpOsSUSCRoaGsodY5RKzFWJmUC5cqVcuSk0V2tra8e4O9TuXpQLsBTYetayPcAmoANYl+22mpubPV/t7e3+m+9/xf3BaX7i6KG8t1Ns7e3t5Y4wpkrMVYmZ3JUrV8qVm0JzARt9nE7N6thDM3sOmD/Gqgfc/ckJ7nqju3ea2TzgWTPb4e4bshmzID2HGPQ402bOLflQIiKVLquid/fb8tm4u3dmfnaZ2RPAaqDkRR/rO8zRyCwuiITyoCIRkZyUrAnNrN7MGk/9DtwObJ34XsVRO9DFydicyRhKRKTiFevwyh8CvwEuM7P9ZvZpoAl4wcy2AK8A/+zuvyjGeOcybfgofdWathERgSynbs7F3e8dZ9XKYmw/V7OCY3TWNZVjaBGRihO6SezhoYH0h6UaVPQiIhDGou87CUC0flaZk4iIVIbQFX1qIF308QYVvYgIhLDog8EEAFWNejNWRARCWPQM9gBQN312mYOIiFSG0BV9ZCg9dVM/Y16Zk4iIVIbQFX10OD11o9MfiIikha7o48Mn6fcqauoq7+x0IiLlELqir0ol6DGVvIjIKSEs+n76IvXljiEiUjHCV/Q+wFCkttwxREQqRjiLPlpX7hgiIhUjdEVfHQwyrKIXETktdEVfywCpmIpeROSU0BV9javoRURGCl3R1zGAx3XUjYjIKaEq+iCVopZBvEpFLyJySsFFb2aLzazdzLaZ2Ztm9vkR69aY2U4z22VmXyp0rHMZ6E8QMcdU9CIipxVjjz4JfNHdVwDXA39qZivMLAo8BNwBrADuNbMVRRhvXH2J9AnNrFqfjBUROaXg74x194PAwczvPWa2HbgAmA7scvfdAGbWBtwFbCt0zPEMZr5dKqI9ehGR08zdi7cxs6XABuAK4HZgjbvfl1m3FrjO3e8f577rgHUATU1NzW1tbTmP39O1h49t+wJPLvwi0y+9Kb8/okQSiQQNDZX3fxqVmKsSM4Fy5Uq5clNortbW1g53bxlzpbuf8wI8B2wd43LXiNs0AB3AJzLX7wa+M2L9WuCb2YzX3Nzs+XjrtQ3uD07z1375WF73L6X29vZyRxhTJeaqxEzuypUr5cpNobmAjT5Op2Y1dePut0203sziwOPAY+7+k8ziTmDxiJstyiwrmdTQAACRWFUphxERmVKKcdSNAY8A2939ayNWvQpcYmbLzKwKuAf4WaHjTSQ5nCn6qupSDiMiMqUU46ibD5OelrnVzDZnLne6exK4H3gG2A78yN3fLMJ44wqGhwCIxWtKOYyIyJRSjKNuXgBsnHVPA08XOka2guFBAKJVKnoRkVNC9cnYVKboYyp6EZHTQlX0nkzP0avoRUR+K1RFf2rqJq6iFxE5LVRF70lN3YiInC2URR+v1nfGioicEsqir9Jx9CIip4Wq6EkNA1ClPXoRkdNCVvSDJD1CNFbwxwNEREIjVEVvqWGGC/8MmIhIqISq6AmSpIiWO4WISEUJVdGbp0iNfTYGEZHzVqiKHg8IQvYniYgUKlyt6CkVvYjIWULViqY5ehGRUcJV9B6QslD9SSIiBQtXK2rqRkRklFC1ounNWBGRUULViqY9ehGRUQr+GKmZLQb+CWgCHHjY3b+RWbcX6AFSQNLdWwodb8IsKnoRkVGKcb6AJPBFd99kZo1Ah5k96+7bMutb3f1oEcY5J03diIiMZu5e3A2aPQl8092fzezRt2RT9Ga2DlgH0NTU1NzW1pbz2HUv/DUzk1103vI/cr5vqSUSCRoaGsodY5RKzFWJmUC5cqVcuSk0V2tra8e4sybuXrQLsBR4D5iWub4H2AR0AOuy3U5zc7PnY/PffcR3/JeVed231Nrb28sdYUyVmKsSM7krV66UKzeF5gI2+jidmtXUjZk9B8wfY9UD7v5k5jYNwOPAF9z9ZGb9je7eaWbzgGfNbIe7b8ju36fcaepGRGS0rIre3W+baL2ZxUmX/GPu/pMR9+vM/OwysyeA1UAJiz6pohcROUvBrWhmBjwCbHf3r41YXp95cxYzqwduB7YWOt5EIh4Q6JOxIiJnKMZRNx8G1gJvmNnmzLK/BHYAT6T/HSAG/MDdf1GE8cZlaOpGRORsBRe9u78A454EfmWh28+FeQpX0YuInCFUrRjRm7EiIqOEqhWNADedplhEZKRQFX1Ep0AQERklVK2Y3qMP1Z8kIlKwULWiueP6cnARkTOEq+jLHUBEpAKFqujBKe4p2kREpr5QFb3haL9eRORMoSp6QHP0IiJnCVnRa+JGRORsoSp67cuLiIwWqqIHcFPdi4iMFLKi19SNiMjZQlX0OupGRGS0cBW96zh6EZGzharo07RHLyIyUgiLXkRERirGd8bWmNkrZrbFzN40s6+MWLfGzHaa2S4z+1KhY50zi+boRURGKcYe/SBwq7uvBFYBa8zsejOLAg8BdwArgHvNbEURxpuQq+dFRM5QcNF7WiJzNZ65OLAa2OXuu919CGgD7ip0vHOkKe3mRUSmIHMvvBwze+8dwMXAQ+7+n83sbmCNu9+Xuc1a4Dp3v3+cbawD1gE0NTU1t7W15Zzj4vV/wu7YJQQ3/kWef0npJBIJGhoayh1jlErMVYmZQLlypVy5KTRXa2trh7u3jLUuls0GzOw5YP4Yqx5w9yfdPQWsMrMZwBNmdkWuId39YeBhgJaWFr/lllty3QQHnjcsEiGf+5ba+vXrlStLlZgJlCtXypWbUubKqujd/bYsb3fCzNqBNcCvgcUjVi8COnNOmDNN0ouIjFSMo27mZvbkMbNa4CPADuBV4BIzW2ZmVcA9wM8KHW9imqMXETlbVnv057AA+F5mnj4C/Mjdfw5gZvcDzwBR4FF3f7MI441Ln4wVERmt4KJ399eBq8dZ9zTwdKFj5EZTNyIiI4Xqk7GqeBGR0UJV9OCg89GLiJwhVEVvaI5eRORsoSr6NO3Ri4iMFKqiN+3Pi4iMEqqiB3Dt0YuInCF0RS8iImcKVdFr6kZEZLQQFr2mbkRERgpV0QO4jqMXETlD6IpeRETOFKqi1xy9iMhooSt6HV4pInKmUBV9mopeRGSkUBW9Kl5EZLRQFX367JXlziAiUllCVfSaoxcRGS1URZ+mohcRGangrxI0sxpgA1Cd2d6P3f3BzLq9QA+QApLu3lLoeBNm0eGVIiKjFOPLwQeBW909YWZx4AUz+xd3fymzvtXdjxZhnHPSvryIyGjmXry9YDOrA14APuPuL2f26FuyKXozWwesA2hqampua2vLefyr2+/l5ZrfoeqGz+Z831JLJBI0NDSUO8YolZirEjOBcuVKuXJTaK7W1taOcWdN3L3gCxAFNgMJ4O9HLN8DbAI6gHXZbq+5udnz0f3gfH/27/4wr/uWWnt7e7kjjKkSc1ViJnflypVy5abQXMBGH6dTs5q6MbPngPljrHrA3Z909xSwysxmAE+Y2RXuvhW40d07zWwe8KyZ7XD3Ddn/G5Ubc305uIjI2bIqene/LcvbnTCzdmANsNXdOzPLu8zsCWA16TduS0IVLyIyWsGHV5rZ3MyePGZWC3wE2GFm9WbWmFleD9wObC10vCwSlX4IEZEppBhH3SwAvmdmUdL/cPzI3X9uZh8gPY1zapwfuPsvijDeuHR4pYjIaAUXvbu/Dlw9xvLdwMpCt58LfTJWRGS0EH4yVkRERlLRi4iEXKiKXlM3IiKjha7odRy9iMiZQlX0aSp6EZGRQlX0qngRkdGKcRx9xXhj+s301y0tdwwRkYoSqj36lj9/nOnLby13DBGRihKqohcRkdFU9CIiIaeiFxEJORW9iEjIqehFREJORS8iEnIqehGRkFPRi4iEnKW/PLyymNkR4N087z4HOFrEOMWiXNmrxEygXLlSrtwUmmuJu88da0VFFn0hzGyju7eUO8fZlCt7lZgJlCtXypWbUubS1I2ISMip6EVEQi6MRf9wuQOMQ7myV4mZQLlypVy5KVmu0M3Ri4jImcK4Ry8iIiOo6EVEQk5FLyIScip6EZGQC03Rm9kaM9tpZrvM7EslHmuxmbWb2TYze9PMPj9i3V4ze8PMNpvZxmzyFTN7ruNPRi4zuyyT59TlpJl9IZ+8xchlZo+aWZeZbc1mu/lkySfjWLnyea0VM9cEj1XRnrciPlY5v86K/FhN9FyV9bWFu0/5CxAF3gE+AFQBW4AVJRxvAXBN5vdG4K1T4wF7gTnZ5it29lzGn8xcZ2U5RPrj2mV5vICbgGuAraV4jPLNOE6unF5rxc41VqZiPm/FfKxyfZ2V4LEa87mqhNdWWPboVwO73H23uw8BbcBdpRrM3Q+6+6bM7z3AduCCPPNNRvbxxihHrn8DvOPuE53LqKS53H0DcCzLMfPJklfGsXLl8Voraq5xHqtcxy1qpixzZfM6K2quCZ6rsr+2wlL0FwD7Rlzfz7n/YygKM1sKXA28nFnkwC/NrMPM1mWRr9jZcxl/MnOdcg/wwzzzljJXMR+jkmTM8rU2WbmK9byV83VWslxnPVdlf23Fsg0uo5lZA/A48AV3P5lZfKO7d5rZPOBZM9sxybHKPf64zKwK+Djw5RGLKzZvJcn2tZbZ050MFfu8Zfs6K9VjdfZzZWalGCYnYdmj7wQWj7i+KLOsZMwsTvrJfMzdf3Jqubt3Zn52AU+Q/l+tifIVNXuO409arow7gE3ufjjPvKXKNdF288lS1Iw5vtYmJVcRn7dyvs6Knmuc56r8r61zTeJPhQvp/zPZDSzjt29QXF7C8Qz4J+DrZy2vBxpH/P4isGaifMXMnuv4k5VrRL424I8q4fEClnLmm55Fe4wKyThGrpxea6XINUamoj1vxXyscn2dFTvXBM9V2V9bJSnCclyAO0m/y/0O8ECJx7qR9Jzf68DmzOVO0u+Eb8lc3hyZY6J8xcqez/iTkSuzrXrgfWB6IXmLkYv03O1BYJj0HOeni/0Y5ZNxrFz5vNaKmWucTEV93or1WOXzOivyYzXmc1UJry2d1ExEJOTCMkcvIiLjUNGLiIScil5EJORU9CIiIaeiFxEJORW9iEjIqehFRELu/wPGJjOfGNP05wAAAABJRU5ErkJggg==\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "plot(res)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[1.],\n", + " [1.]])" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "slm.Pm" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([-0.11480531, -0.11480531])" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "res[-1,:]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/test_SeriesBiquad.ipynb b/examples/test_SeriesBiquad.ipynb new file mode 100644 index 0000000..77b49e5 --- /dev/null +++ b/examples/test_SeriesBiquad.ipynb @@ -0,0 +1,82 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from lasp import SeriesBiquad\n", + "import numpy as np" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "coefs = np.array([1.,0,0,1.,-.9,0])\n", + "bq = SeriesBiquad(coefs)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "x = np.zeros(10, dtype=float)\n", + "x[0] = 1\n", + "\n", + "x2 = bq.filter(x)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "x2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "type(x2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.10" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/test_input.ipynb b/examples/test_input.ipynb new file mode 100644 index 0000000..ffeab5b --- /dev/null +++ b/examples/test_input.ipynb @@ -0,0 +1,184 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "740b4091", + "metadata": {}, + "source": [ + "# Test DAQ input on a device" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "ac06df04", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "make: Entering directory '/home/anne/wip/mycode/lasp'\n", + "make[1]: Entering directory '/home/anne/wip/mycode/lasp'\n", + "make[2]: Entering directory '/home/anne/wip/mycode/lasp'\n", + "\u001b[35m\u001b[1mConsolidate compiler generated dependencies of target lasp_dsp_lib\u001b[0m\n", + "make[2]: Leaving directory '/home/anne/wip/mycode/lasp'\n", + "[ 42%] Built target lasp_dsp_lib\n", + "make[2]: Entering directory '/home/anne/wip/mycode/lasp'\n", + "\u001b[35m\u001b[1mConsolidate compiler generated dependencies of target lasp_device_lib\u001b[0m\n", + "make[2]: Leaving directory '/home/anne/wip/mycode/lasp'\n", + "[ 67%] Built target lasp_device_lib\n", + "make[2]: Entering directory '/home/anne/wip/mycode/lasp'\n", + "\u001b[35m\u001b[1mConsolidate compiler generated dependencies of target lasp_cpp\u001b[0m\n", + "make[2]: Leaving directory '/home/anne/wip/mycode/lasp'\n", + "make[2]: Entering directory '/home/anne/wip/mycode/lasp'\n", + "[ 71%] \u001b[32mBuilding CXX object src/lasp/CMakeFiles/lasp_cpp.dir/pybind11/lasp_dsp_pybind.cpp.o\u001b[0m\n", + "[ 75%] \u001b[32m\u001b[1mLinking CXX shared module lasp_cpp.cpython-310-x86_64-linux-gnu.so\u001b[0m\n", + "make[2]: Leaving directory '/home/anne/wip/mycode/lasp'\n", + "[100%] Built target lasp_cpp\n", + "make[1]: Leaving directory '/home/anne/wip/mycode/lasp'\n", + "make: Leaving directory '/home/anne/wip/mycode/lasp'\n" + ] + } + ], + "source": [ + "# !make -j -C ~/wip/mycode/lasp" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ce0dd691", + "metadata": {}, + "outputs": [], + "source": [ + "import lasp\n", + "# Get handle to stream manager\n", + "mgr = lasp.StreamMgr.getInstance()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3cd242a8", + "metadata": {}, + "outputs": [], + "source": [ + "ds = mgr.getDeviceInfo()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a5d878e9", + "metadata": {}, + "outputs": [], + "source": [ + "# Search for a device\n", + "for i, d in enumerate(ds):\n", + " print(f'{i}: ' + d.device_name)\n", + "d = ds[0]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a456054e", + "metadata": {}, + "outputs": [], + "source": [ + "# Check the available block sizes\n", + "d.availableFramesPerBlock" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6d5b281e", + "metadata": {}, + "outputs": [], + "source": [ + "# Create a configuration and enable some input channels\n", + "config = lasp.DaqConfiguration(d)\n", + "config.inchannel_config[0].enabled = True\n", + "config.inchannel_config[1].enabled = True\n", + "# Choose a different number of frames per block\n", + "config.framesPerBlockIndex = 4" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1ead3995", + "metadata": {}, + "outputs": [], + "source": [ + "print('Out channels:',d.noutchannels)\n", + "print('In channels:',d.ninchannels)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12db8306", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# Start a stream with a configuration\n", + "mgr.startStream(config)\n", + "\n", + "def cb(data):\n", + " # raise RuntimeError('hh')\n", + " print(data.shape)\n", + "i = lasp.InDataHandler(mgr, cb)\n", + "import time\n", + "#time.sleep(4)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4d9f0103", + "metadata": {}, + "outputs": [], + "source": [ + "mgr.stopStream(lasp.StreamMgr.StreamType.input)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "61b7282f", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "del i" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.10" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/test_output.ipynb b/examples/test_output.ipynb new file mode 100644 index 0000000..738de7e --- /dev/null +++ b/examples/test_output.ipynb @@ -0,0 +1,138 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "740b4091", + "metadata": {}, + "source": [ + "# Test DAQ output on a device" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ac06df04", + "metadata": {}, + "outputs": [], + "source": [ + "# !make -j -C ~/wip/mycode/lasp" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ce0dd691", + "metadata": {}, + "outputs": [], + "source": [ + "import lasp\n", + "# Get handle to stream manager\n", + "mgr = lasp.StreamMgr.getInstance()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3cd242a8", + "metadata": {}, + "outputs": [], + "source": [ + "ds = mgr.getDeviceInfo()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a5d878e9", + "metadata": {}, + "outputs": [], + "source": [ + "# Search for a device\n", + "for i, d in enumerate(ds):\n", + " print(f'{i}: ' + d.device_name)\n", + "d = ds[1]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6d5b281e", + "metadata": {}, + "outputs": [], + "source": [ + "# Create a configuration and enable some input channels\n", + "config = lasp.DaqConfiguration(d)\n", + "config.outchannel_config[0].enabled = True\n", + "config.outchannel_config[1].enabled = True\n", + "config.setAllInputEnabled(False)\n", + "# Choose a different number of frames per block\n", + "config.framesPerBlockIndex = 4" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1ead3995", + "metadata": {}, + "outputs": [], + "source": [ + "print('Out channels:',d.noutchannels)\n", + "print('In channels:',d.ninchannels)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12db8306", + "metadata": {}, + "outputs": [], + "source": [ + "# Start a stream with a configuration\n", + "mgr.startStream(config)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "da5e8b81-bcd6-4587-8f59-74811a263eee", + "metadata": {}, + "outputs": [], + "source": [ + "siggen = lasp.Sine(1000)\n", + "siggen.setLevel(-20)\n", + "mgr.setSiggen(siggen)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4d9f0103", + "metadata": {}, + "outputs": [], + "source": [ + "mgr.stopStream(lasp.StreamMgr.StreamType.output)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.10" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/test_ppm.ipynb b/examples/test_ppm.ipynb new file mode 100644 index 0000000..4cef664 --- /dev/null +++ b/examples/test_ppm.ipynb @@ -0,0 +1,619 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "740b4091", + "metadata": {}, + "source": [ + "# Test PPM" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "ac06df04", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "make: Entering directory '/home/anne/wip/mycode/lasp'\n", + "make[1]: Entering directory '/home/anne/wip/mycode/lasp'\n", + "make[2]: Entering directory '/home/anne/wip/mycode/lasp'\n", + "make[2]: Leaving directory '/home/anne/wip/mycode/lasp'\n", + "[ 44%] Built target lasp_dsp_lib\n", + "make[2]: Entering directory '/home/anne/wip/mycode/lasp'\n", + "make[2]: Leaving directory '/home/anne/wip/mycode/lasp'\n", + "[ 68%] Built target lasp_device_lib\n", + "make[2]: Entering directory '/home/anne/wip/mycode/lasp'\n", + "make[2]: Leaving directory '/home/anne/wip/mycode/lasp'\n", + "[100%] Built target lasp_cpp\n", + "make[1]: Leaving directory '/home/anne/wip/mycode/lasp'\n", + "make: Leaving directory '/home/anne/wip/mycode/lasp'\n" + ] + } + ], + "source": [ + "!make -j -C ~/wip/mycode/lasp" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "ce0dd691", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2022-10-03 13:39:03+0200 DebugTrace-cpp 2.0.0a2 (g++ 9.4.0)\n", + "2022-10-03 13:39:03+0200 \n", + "2022-10-03 13:39:03+0200 Enter getInstance (lasp_streammgr.cpp: 40)\n", + "2022-10-03 13:39:03+0200 | Enter StreamMgr (lasp_streammgr.cpp: 45)\n", + "2022-10-03 13:39:03+0200 | | Enter rescanDAQDevices (lasp_streammgr.cpp: 60)\n", + "2022-10-03 13:39:03+0200 | | | Enter rescanDAQDevices_impl (lasp_streammgr.cpp: 80)\n", + "2022-10-03 13:39:04+0200 | | | | Enter fillRtAudioDeviceInfo (lasp_rtaudiodaq.cpp: 20)\n", + "2022-10-03 13:39:04+0200 | | | | Leave fillRtAudioDeviceInfo (lasp_rtaudiodaq.cpp)\n", + "2022-10-03 13:39:04+0200 | | | Leave rescanDAQDevices_impl (lasp_streammgr.cpp)\n", + "2022-10-03 13:39:04+0200 | | Leave rescanDAQDevices (lasp_streammgr.cpp)\n", + "2022-10-03 13:39:04+0200 | Leave StreamMgr (lasp_streammgr.cpp)\n", + "2022-10-03 13:39:04+0200 Leave getInstance (lasp_streammgr.cpp)\n" + ] + } + ], + "source": [ + "import lasp\n", + "# Get handle to stream manager\n", + "mgr = lasp.StreamMgr.getInstance()" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "3cd242a8", + "metadata": {}, + "outputs": [], + "source": [ + "ds = mgr.getDeviceInfo()" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "a5d878e9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0: Monitor of Starship/Matisse HD Audio Controller Analog Stereo\n", + "1: Starship/Matisse HD Audio Controller Analog Stereo\n", + "2: Baffin HDMI/DP Audio [Radeon RX 550 640SP / RX 560/560X] Digital Stereo (HDMI)\n", + "3: Monitor of Baffin HDMI/DP Audio [Radeon RX 550 640SP / RX 560/560X] Digital Stereo (HDMI)\n" + ] + } + ], + "source": [ + "# Search for a device\n", + "for i, d in enumerate(ds):\n", + " print(f'{i}: ' + d.device_name)\n", + "d = ds[0]" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "6d5b281e", + "metadata": {}, + "outputs": [], + "source": [ + "# Create a configuration and enable some input channels\n", + "config = lasp.DaqConfiguration(d)\n", + "config.inchannel_config[0].enabled = True\n", + "config.inchannel_config[1].enabled = True\n", + "# Choose a different number of frames per block\n", + "config.framesPerBlockIndex = 4" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "1ead3995", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Out channels: 0\n", + "In channels: 2\n" + ] + } + ], + "source": [ + "print('Out channels:',d.noutchannels)\n", + "print('In channels:',d.ninchannels)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "12db8306", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2022-10-03 13:39:06+0200 \n", + "2022-10-03 13:39:06+0200 Enter startStream (lasp_streammgr.cpp: 209)\n", + "2022-10-03 13:39:06+0200 | Enter createDaq (lasp_daq.cpp: 18)\n", + "2022-10-03 13:39:06+0200 | | Enter Daq (lasp_daq.cpp: 37)\n", + "2022-10-03 13:39:06+0200 | | Leave Daq (lasp_daq.cpp)\n", + "2022-10-03 13:39:06+0200 | | \n", + "2022-10-03 13:39:06+0200 | | Enter RtAudioDaq (lasp_rtaudiodaq.cpp: 135)\n", + "2022-10-03 13:39:06+0200 | | | Enter samplerate (lasp_daq.cpp: 58)\n", + "2022-10-03 13:39:06+0200 | | | Leave samplerate (lasp_daq.cpp)\n", + "2022-10-03 13:39:07+0200 | | Leave RtAudioDaq (lasp_rtaudiodaq.cpp)\n", + "2022-10-03 13:39:07+0200 | Leave createDaq (lasp_daq.cpp)\n", + "2022-10-03 13:39:07+0200 | isInput = true\n", + "2022-10-03 13:39:07+0200 | isOutput = false\n", + "2022-10-03 13:39:07+0200 | \n", + "2022-10-03 13:39:07+0200 | Enter start (lasp_rtaudiodaq.cpp: 215)\n", + "2022-10-03 13:39:07+0200 | Leave start (lasp_rtaudiodaq.cpp)\n", + "2022-10-03 13:39:07+0200 Leave startStream (lasp_streammgr.cpp)\n" + ] + } + ], + "source": [ + "# Start a stream with a configuration\n", + "mgr.startStream(config)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "5323356f-4239-4f0b-ad58-0025501bf7a3", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2022-10-03 13:39:09+0200 \n", + "2022-10-03 13:39:09+0200 Enter InDataHandler (lasp_streammgr.cpp: 14)\n", + "2022-10-03 13:39:09+0200 Leave InDataHandler (lasp_streammgr.cpp)\n", + "2022-10-03 13:39:09+0200 \n", + "2022-10-03 13:39:09+0200 Enter ThreadedInDataHandler (lasp_threadedindatahandler.cpp: 13)\n", + "2022-10-03 13:39:09+0200 Leave ThreadedInDataHandler (lasp_threadedindatahandler.cpp)\n", + "2022-10-03 13:39:09+0200 \n", + "2022-10-03 13:39:09+0200 Enter PPMHandler (lasp_ppm.cpp: 14)\n", + "2022-10-03 13:39:09+0200 | Enter start (lasp_streammgr.cpp: 16)\n", + "2022-10-03 13:39:09+0200 | | Enter reset (lasp_ppm.cpp: 65)\n", + "2022-10-03 13:39:09+0200 | | | Enter samplerate (lasp_daq.cpp: 58)\n", + "2022-10-03 13:39:09+0200 | | | Leave samplerate (lasp_daq.cpp)\n", + "2022-10-03 13:39:09+0200 | | | fs = 44100.000000\n", + "2022-10-03 13:39:09+0200 | | Leave reset (lasp_ppm.cpp)\n", + "2022-10-03 13:39:09+0200 | Leave start (lasp_streammgr.cpp)\n", + "2022-10-03 13:39:09+0200 Leave PPMHandler (lasp_ppm.cpp)\n", + "2022-10-03 13:39:09+0200 \n", + "2022-10-03 13:39:09+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:09+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:09+0200 \n", + "2022-10-03 13:39:09+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:09+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:09+0200 \n", + "2022-10-03 13:39:09+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:09+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:10+0200 \n", + "2022-10-03 13:39:10+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:10+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:10+0200 \n", + "2022-10-03 13:39:10+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:10+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:10+0200 \n", + "2022-10-03 13:39:10+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:10+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:10+0200 \n", + "2022-10-03 13:39:10+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:10+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:11+0200 \n", + "2022-10-03 13:39:11+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:11+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:11+0200 \n", + "2022-10-03 13:39:11+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:11+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:11+0200 \n", + "2022-10-03 13:39:11+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:11+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:11+0200 \n", + "2022-10-03 13:39:11+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:11+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:11+0200 \n", + "2022-10-03 13:39:11+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:11+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:12+0200 \n", + "2022-10-03 13:39:12+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:12+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:12+0200 \n", + "2022-10-03 13:39:12+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:12+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:12+0200 \n", + "2022-10-03 13:39:12+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:12+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:12+0200 \n", + "2022-10-03 13:39:12+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:12+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:12+0200 \n", + "2022-10-03 13:39:12+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:12+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:13+0200 \n", + "2022-10-03 13:39:13+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:13+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:13+0200 \n", + "2022-10-03 13:39:13+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:13+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:13+0200 \n", + "2022-10-03 13:39:13+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:13+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:13+0200 \n", + "2022-10-03 13:39:13+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:13+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:14+0200 \n", + "2022-10-03 13:39:14+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:14+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:14+0200 \n", + "2022-10-03 13:39:14+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:14+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:14+0200 \n", + "2022-10-03 13:39:14+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:14+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:14+0200 \n", + "2022-10-03 13:39:14+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:14+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:14+0200 \n", + "2022-10-03 13:39:14+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:14+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:15+0200 \n", + "2022-10-03 13:39:15+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:15+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:15+0200 \n", + "2022-10-03 13:39:15+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:15+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n" + ] + } + ], + "source": [ + "ppm = lasp.PPMHandler(mgr)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "169c52c4-687f-4371-af04-19a414feb73d", + "metadata": {}, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'a' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m/tmp/ipykernel_24491/2167009006.py\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0ma\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;31mNameError\u001b[0m: name 'a' is not defined" + ] + } + ], + "source": [ + "a" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "688d41ae-a3b1-4a31-b1e3-278bb4eaae4c", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "4d9f0103", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Monitor of Starship/Matisse HD Audio Controller Analog Stereo\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2022-10-03 13:39:15+0200 \n", + "2022-10-03 13:39:15+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:15+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:15+0200 \n", + "2022-10-03 13:39:15+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:15+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:15+0200 \n", + "2022-10-03 13:39:15+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:15+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:16+0200 \n", + "2022-10-03 13:39:16+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:16+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n" + ] + } + ], + "source": [ + "daq = mgr.getDaq(lasp.StreamMgr.StreamType.input)\n", + "print(daq)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "26277ef0-3389-4581-9cc5-2c500e789dfa", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2022-10-03 13:39:16+0200 \n", + "2022-10-03 13:39:16+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:16+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n" + ] + }, + { + "ename": "RuntimeError", + "evalue": "Mat::init(): requested size is not compatible with column vector layout", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mRuntimeError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m/tmp/ipykernel_24664/1896157402.py\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0mtime\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msleep\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m0.1\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0mt\u001b[0m \u001b[0;34m+=\u001b[0m \u001b[0;36m0.1\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 6\u001b[0;31m \u001b[0mlevel\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mclip\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mppm\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mgetCurrentValue\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 7\u001b[0m \u001b[0;31m#print('\\r {\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mRuntimeError\u001b[0m: Mat::init(): requested size is not compatible with column vector layout" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2022-10-03 13:39:16+0200 \n", + "2022-10-03 13:39:16+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:16+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:17+0200 \n", + "2022-10-03 13:39:17+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:17+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:17+0200 \n", + "2022-10-03 13:39:17+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:17+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:17+0200 \n", + "2022-10-03 13:39:17+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:17+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:17+0200 \n", + "2022-10-03 13:39:17+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:17+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:17+0200 \n", + "2022-10-03 13:39:17+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:17+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:18+0200 \n", + "2022-10-03 13:39:18+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:18+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:18+0200 \n", + "2022-10-03 13:39:18+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:18+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:18+0200 \n", + "2022-10-03 13:39:18+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:18+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:18+0200 \n", + "2022-10-03 13:39:18+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:18+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:19+0200 \n", + "2022-10-03 13:39:19+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:19+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:19+0200 \n", + "2022-10-03 13:39:19+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:19+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:19+0200 \n", + "2022-10-03 13:39:19+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:19+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:19+0200 \n", + "2022-10-03 13:39:19+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:19+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:20+0200 \n", + "2022-10-03 13:39:20+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:20+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:20+0200 \n", + "2022-10-03 13:39:20+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:20+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:20+0200 \n", + "2022-10-03 13:39:20+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:20+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:20+0200 \n", + "2022-10-03 13:39:20+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:20+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:20+0200 \n", + "2022-10-03 13:39:20+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:20+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:20+0200 \n", + "2022-10-03 13:39:20+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:20+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:21+0200 \n", + "2022-10-03 13:39:21+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:21+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:21+0200 \n", + "2022-10-03 13:39:21+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:21+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:21+0200 \n", + "2022-10-03 13:39:21+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:21+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:21+0200 \n", + "2022-10-03 13:39:21+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:21+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:22+0200 \n", + "2022-10-03 13:39:22+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:22+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:22+0200 \n", + "2022-10-03 13:39:22+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:22+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:22+0200 \n", + "2022-10-03 13:39:22+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:22+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:22+0200 \n", + "2022-10-03 13:39:22+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:22+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:23+0200 \n", + "2022-10-03 13:39:23+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:23+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:23+0200 \n", + "2022-10-03 13:39:23+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:23+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:23+0200 \n", + "2022-10-03 13:39:23+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:23+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:23+0200 \n", + "2022-10-03 13:39:23+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:23+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:23+0200 \n", + "2022-10-03 13:39:23+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:23+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:23+0200 \n", + "2022-10-03 13:39:23+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:23+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:24+0200 \n", + "2022-10-03 13:39:24+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:24+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:24+0200 \n", + "2022-10-03 13:39:24+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:24+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:24+0200 \n", + "2022-10-03 13:39:24+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:24+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:24+0200 \n", + "2022-10-03 13:39:24+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:24+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:24+0200 \n", + "2022-10-03 13:39:24+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:24+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:25+0200 \n", + "2022-10-03 13:39:25+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:25+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:25+0200 \n", + "2022-10-03 13:39:25+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:25+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:25+0200 \n", + "2022-10-03 13:39:25+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:25+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:25+0200 \n", + "2022-10-03 13:39:25+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:25+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:25+0200 \n", + "2022-10-03 13:39:25+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:25+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:26+0200 \n", + "2022-10-03 13:39:26+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:26+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:26+0200 \n", + "2022-10-03 13:39:26+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:26+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:26+0200 \n", + "2022-10-03 13:39:26+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:26+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:26+0200 \n", + "2022-10-03 13:39:26+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:26+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:26+0200 \n", + "2022-10-03 13:39:26+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:26+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:26+0200 \n", + "2022-10-03 13:39:26+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:26+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:27+0200 \n", + "2022-10-03 13:39:27+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:27+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:27+0200 \n", + "2022-10-03 13:39:27+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:27+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:27+0200 \n", + "2022-10-03 13:39:27+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:27+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n", + "2022-10-03 13:39:27+0200 \n", + "2022-10-03 13:39:27+0200 Enter inCallback_threaded (lasp_ppm.cpp: 18)\n", + "2022-10-03 13:39:27+0200 Leave inCallback_threaded (lasp_ppm.cpp)\n" + ] + } + ], + "source": [ + "import time\n", + "t=0\n", + "while t < 1:\n", + " time.sleep(0.1)\n", + " t += 0.1\n", + " level, clip = ppm.getCurrentValue()\n", + " #print('\\r {" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "30b77ce0-40ee-4b88-89fa-83b7a62e493c", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2022-10-03 13:39:27+0200 \n", + "2022-10-03 13:39:27+0200 Enter stopAllStreams (lasp_streammgr.cpp: 202)\n", + "2022-10-03 13:39:27+0200 | Enter ~Daq (lasp_daq.cpp: 14)\n", + "2022-10-03 13:39:27+0200 | Leave ~Daq (lasp_daq.cpp)\n", + "2022-10-03 13:39:27+0200 Leave stopAllStreams (lasp_streammgr.cpp)\n" + ] + } + ], + "source": [ + "mgr.stopAllStreams()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "78cc8353-0e0b-4d96-a263-f4ad3fe4ee4c", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.10" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/test_record.ipynb b/examples/test_record.ipynb new file mode 100644 index 0000000..ad545a5 --- /dev/null +++ b/examples/test_record.ipynb @@ -0,0 +1,506 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "2406f830", + "metadata": {}, + "source": [ + "# Test DAQ input on a device" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "e3d3280f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "make: Entering directory '/home/anne/wip/mycode/lasp'\n", + "make[1]: Entering directory '/home/anne/wip/mycode/lasp'\n", + "make[2]: Entering directory '/home/anne/wip/mycode/lasp'\n", + "make[2]: Leaving directory '/home/anne/wip/mycode/lasp'\n", + "[ 42%] Built target lasp_dsp_lib\n", + "make[2]: Entering directory '/home/anne/wip/mycode/lasp'\n", + "make[2]: Leaving directory '/home/anne/wip/mycode/lasp'\n", + "[ 67%] Built target lasp_device_lib\n", + "make[2]: Entering directory '/home/anne/wip/mycode/lasp'\n", + "make[2]: Leaving directory '/home/anne/wip/mycode/lasp'\n", + "[100%] Built target lasp_cpp\n", + "make[1]: Leaving directory '/home/anne/wip/mycode/lasp'\n", + "make: Leaving directory '/home/anne/wip/mycode/lasp'\n" + ] + } + ], + "source": [ + " !make -j -C ~/wip/mycode/lasp" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "d12f103c", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2022-09-27 22:08:01+0200 DebugTrace-cpp 2.0.0a2 (g++ 12.2.0)\n", + "2022-09-27 22:08:01+0200 \n", + "2022-09-27 22:08:01+0200 Enter getInstance (lasp_streammgr.cpp: 40)\n", + "2022-09-27 22:08:01+0200 | Enter StreamMgr (lasp_streammgr.cpp: 45)\n", + "2022-09-27 22:08:01+0200 | | Enter rescanDAQDevices (lasp_streammgr.cpp: 60)\n", + "2022-09-27 22:08:01+0200 | | | Enter rescanDAQDevices_impl (lasp_streammgr.cpp: 80)\n", + "2022-09-27 22:08:01+0200 | | | | Enter fillRtAudioDeviceInfo (lasp_rtaudiodaq.cpp: 20)\n", + "\n", + "RtApiAlsa::getDeviceInfo: snd_pcm_open error for device (hw:1,0), Device or resource busy.\n", + "\n", + "2022-09-27 22:08:01+0200 | | | | Leave fillRtAudioDeviceInfo (lasp_rtaudiodaq.cpp)\n", + "2022-09-27 22:08:01+0200 | | | Leave rescanDAQDevices_impl (lasp_streammgr.cpp)\n", + "2022-09-27 22:08:01+0200 | | Leave rescanDAQDevices (lasp_streammgr.cpp)\n", + "2022-09-27 22:08:01+0200 | Leave StreamMgr (lasp_streammgr.cpp)\n", + "2022-09-27 22:08:01+0200 Leave getInstance (lasp_streammgr.cpp)\n" + ] + } + ], + "source": [ + "import lasp\n", + "# Get handle to stream manager\n", + "mgr = lasp.StreamMgr.getInstance()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "f44b1946", + "metadata": {}, + "outputs": [], + "source": [ + "ds = mgr.getDeviceInfo()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "db0f3bb7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0: Monitor of Starship/Matisse HD Audio Controller Analog Stereo\n", + "1: Starship/Matisse HD Audio Controller Analog Stereo\n", + "2: GP108 High Definition Audio Controller Digital Stereo (HDMI)\n", + "3: Monitor of GP108 High Definition Audio Controller Digital Stereo (HDMI)\n", + "4: default\n", + "5: hw:HDA NVidia,3\n", + "6: hw:HDA NVidia,7\n", + "7: hw:HDA NVidia,8\n", + "8: hw:HDA NVidia,9\n", + "9: hw:HDA NVidia,10\n", + "10: hw:HD-Audio Generic,0\n", + "11: hw:HD-Audio Generic,1\n", + "12: hw:HD-Audio Generic,2\n" + ] + } + ], + "source": [ + "# Search for a device\n", + "for i, d in enumerate(ds):\n", + " print(f'{i}: ' + d.device_name)\n", + "d = ds[0]" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "f97c13d5", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[512, 1024, 2048, 4096, 8192]" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Check the available block sizes\n", + "d.availableFramesPerBlock" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "5c84d454", + "metadata": {}, + "outputs": [], + "source": [ + "# Create a configuration and enable some input channels\n", + "config = lasp.DaqConfiguration(d)\n", + "config.inchannel_config[0].enabled = True\n", + "config.inchannel_config[1].enabled = True\n", + "# Choose a different number of frames per block\n", + "config.framesPerBlockIndex = 4" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "a020f86f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Out channels: 0\n", + "In channels: 2\n" + ] + } + ], + "source": [ + "print('Out channels:',d.noutchannels)\n", + "print('In channels:',d.ninchannels)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "034b5f5e", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2022-09-27 22:08:02+0200 \n", + "2022-09-27 22:08:02+0200 Enter startStream (lasp_streammgr.cpp: 209)\n", + "2022-09-27 22:08:02+0200 | Enter createDaq (lasp_daq.cpp: 18)\n", + "2022-09-27 22:08:02+0200 | | Enter Daq (lasp_daq.cpp: 37)\n", + "2022-09-27 22:08:02+0200 | | Leave Daq (lasp_daq.cpp)\n", + "2022-09-27 22:08:02+0200 | | \n", + "2022-09-27 22:08:02+0200 | | Enter RtAudioDaq (lasp_rtaudiodaq.cpp: 135)\n", + "2022-09-27 22:08:02+0200 | | | Enter samplerate (lasp_daq.cpp: 58)\n", + "2022-09-27 22:08:02+0200 | | | Leave samplerate (lasp_daq.cpp)\n", + "2022-09-27 22:08:02+0200 | | Leave RtAudioDaq (lasp_rtaudiodaq.cpp)\n", + "2022-09-27 22:08:02+0200 | Leave createDaq (lasp_daq.cpp)\n", + "2022-09-27 22:08:02+0200 | isInput = true\n", + "2022-09-27 22:08:02+0200 | isOutput = false\n", + "2022-09-27 22:08:02+0200 | \n", + "2022-09-27 22:08:02+0200 | Enter start (lasp_rtaudiodaq.cpp: 215)\n", + "2022-09-27 22:08:02+0200 | Leave start (lasp_rtaudiodaq.cpp)\n", + "2022-09-27 22:08:02+0200 Leave startStream (lasp_streammgr.cpp)\n" + ] + } + ], + "source": [ + "# Start a stream with a configuration\n", + "mgr.startStream(config)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "59326eda-f249-4a9b-9191-a784b01694b9", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2022-09-27 22:08:02+0200 \n", + "2022-09-27 22:08:02+0200 Enter InDataHandler (lasp_streammgr.cpp: 14)\n", + "2022-09-27 22:08:02+0200 Leave InDataHandler (lasp_streammgr.cpp)\n", + "2022-09-27 22:08:02+0200 \n", + "2022-09-27 22:08:02+0200 Enter ThreadedInDataHandler (lasp_threadedindatahandler.cpp: 13)\n", + "2022-09-27 22:08:02+0200 Leave ThreadedInDataHandler (lasp_threadedindatahandler.cpp)\n", + "2022-09-27 22:08:02+0200 \n", + "2022-09-27 22:08:02+0200 Enter PyIndataHandler (lasp_pyindatahandler.cpp: 78)\n", + "2022-09-27 22:08:02+0200 | Enter start (lasp_streammgr.cpp: 16)\n", + "2022-09-27 22:08:02+0200 | Leave start (lasp_streammgr.cpp)\n", + "2022-09-27 22:08:02+0200 Leave PyIndataHandler (lasp_pyindatahandler.cpp)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "." + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2022-09-27 22:08:02+0200 \n", + "2022-09-27 22:08:02+0200 Enter samplerate (lasp_daq.cpp: 58)\n", + "2022-09-27 22:08:02+0200 Leave samplerate (lasp_daq.cpp)\n", + "2022-09-27 22:08:02+0200 \n", + "2022-09-27 22:08:02+0200 Enter samplerate (lasp_daq.cpp: 58)\n", + "2022-09-27 22:08:02+0200 Leave samplerate (lasp_daq.cpp)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + ".....1....2.....3....4....5.....6....7.....8....9....10" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2022-09-27 22:08:12+0200 \n", + "2022-09-27 22:08:12+0200 Enter ~PyIndataHandler (lasp_pyindatahandler.cpp: 87)\n", + "2022-09-27 22:08:12+0200 Leave ~PyIndataHandler (lasp_pyindatahandler.cpp)\n", + "2022-09-27 22:08:12+0200 \n", + "2022-09-27 22:08:12+0200 Enter ~ThreadedInDataHandler (lasp_threadedindatahandler.cpp: 38)\n", + "2022-09-27 22:08:12+0200 Leave ~ThreadedInDataHandler (lasp_threadedindatahandler.cpp)\n", + "2022-09-27 22:08:12+0200 \n", + "2022-09-27 22:08:12+0200 Enter ~InDataHandler (lasp_streammgr.cpp: 27)\n", + "2022-09-27 22:08:12+0200 Leave ~InDataHandler (lasp_streammgr.cpp)\n" + ] + } + ], + "source": [ + "rec = lasp.Recording('test', mgr, 10.0)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "5defa804", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2022-09-27 22:08:12+0200 \n", + "2022-09-27 22:08:12+0200 Enter stopStream (lasp_streammgr.cpp: 287)\n", + "2022-09-27 22:08:12+0200 | Enter ~Daq (lasp_daq.cpp: 14)\n", + "2022-09-27 22:08:12+0200 | Leave ~Daq (lasp_daq.cpp)\n", + "2022-09-27 22:08:12+0200 Leave stopStream (lasp_streammgr.cpp)\n" + ] + } + ], + "source": [ + "mgr.stopStream(lasp.StreamMgr.StreamType.input)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "d12f8c4a", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2022-09-27 22:08:12+0200 \n", + "2022-09-27 22:08:12+0200 Enter stopAllStreams (lasp_streammgr.cpp: 202)\n", + "2022-09-27 22:08:12+0200 Leave stopAllStreams (lasp_streammgr.cpp)\n" + ] + } + ], + "source": [ + "mgr.stopAllStreams()" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "83b1713b", + "metadata": {}, + "outputs": [], + "source": [ + "def cb(data):\n", + " # raise RuntimeError('hh')\n", + " print(data.shape)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "ff7c1e03", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2022-09-27 22:08:12+0200 \n", + "2022-09-27 22:08:12+0200 Enter InDataHandler (lasp_streammgr.cpp: 14)\n", + "2022-09-27 22:08:12+0200 Leave InDataHandler (lasp_streammgr.cpp)\n", + "2022-09-27 22:08:12+0200 \n", + "2022-09-27 22:08:12+0200 Enter ThreadedInDataHandler (lasp_threadedindatahandler.cpp: 13)\n", + "2022-09-27 22:08:12+0200 Leave ThreadedInDataHandler (lasp_threadedindatahandler.cpp)\n", + "2022-09-27 22:08:12+0200 \n", + "2022-09-27 22:08:12+0200 Enter PyIndataHandler (lasp_pyindatahandler.cpp: 78)\n", + "2022-09-27 22:08:12+0200 | Enter start (lasp_streammgr.cpp: 16)\n", + "2022-09-27 22:08:12+0200 | Leave start (lasp_streammgr.cpp)\n", + "2022-09-27 22:08:12+0200 Leave PyIndataHandler (lasp_pyindatahandler.cpp)\n" + ] + } + ], + "source": [ + "i = lasp.InDataHandler(mgr, cb)\n", + "import time\n", + "#time.sleep(4)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "9ba54238", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2022-09-27 22:08:12+0200 \n", + "2022-09-27 22:08:12+0200 Enter ~PyIndataHandler (lasp_pyindatahandler.cpp: 87)\n", + "2022-09-27 22:08:12+0200 Leave ~PyIndataHandler (lasp_pyindatahandler.cpp)\n", + "2022-09-27 22:08:12+0200 \n", + "2022-09-27 22:08:12+0200 Enter ~ThreadedInDataHandler (lasp_threadedindatahandler.cpp: 38)\n", + "2022-09-27 22:08:12+0200 Leave ~ThreadedInDataHandler (lasp_threadedindatahandler.cpp)\n", + "2022-09-27 22:08:12+0200 \n", + "2022-09-27 22:08:12+0200 Enter ~InDataHandler (lasp_streammgr.cpp: 27)\n", + "2022-09-27 22:08:12+0200 Leave ~InDataHandler (lasp_streammgr.cpp)\n" + ] + } + ], + "source": [ + "del i" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "0f0edc36", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "numpy.ndarray" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import numpy as np\n", + "a = np.array([1,2,3])\n", + "type(a)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "ee546114-a879-4ebe-a6eb-e2354b2247fd", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "dtype('int64')" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "a.dtype" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "5d033d6d-3052-42ce-9441-faa917ece6a9", + "metadata": {}, + "outputs": [], + "source": [ + "from lasp import DaqChannel" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "bb99ec99-b921-4fb2-bd79-87db8dfcdb03", + "metadata": {}, + "outputs": [], + "source": [ + "d = DaqChannel()" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "ad67d273-45aa-4358-9405-de5924f087a2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "d.qty.value" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "53ab5c8f-f904-4f9f-b591-6a5dd1581ddd", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/fftpack/CMakeLists.txt b/fftpack/CMakeLists.txt deleted file mode 100644 index 77d4bca..0000000 --- a/fftpack/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -# We borrow Numpy's implementation for doing the Fast Fourier Transform. -# This FFT code appears to be faster than KISSFFT. -add_library(fftpack - fftpack.c -) -# Ling fft to math -target_link_libraries(fftpack m) diff --git a/fftpack/fftpack.c b/fftpack/fftpack.c deleted file mode 100644 index eaf172c..0000000 --- a/fftpack/fftpack.c +++ /dev/null @@ -1,1501 +0,0 @@ -/* - * fftpack.c : A set of FFT routines in C. - * Algorithmically based on Fortran-77 FFTPACK by Paul N. Swarztrauber (Version 4, 1985). -*/ -#define NPY_VISIBILITY_HIDDEN -/* #define NPY_NO_DEPRECATED_API NPY_API_VERSION */ - -#include -#include - - -#define DOUBLE -#ifdef DOUBLE -#define Treal double -#else -#define Treal float -#endif - - -#define ref(u,a) u[a] - -#define MAXFAC 13 /* maximum number of factors in factorization of n */ -#define NSPECIAL 4 /* number of factors for which we have special-case routines */ - -#ifdef __cplusplus -extern "C" { -#endif - - -/* ---------------------------------------------------------------------- - passf2, passf3, passf4, passf5, passf. Complex FFT passes fwd and bwd. ------------------------------------------------------------------------ */ - -static void passf2(int ido, int l1, const Treal cc[], Treal ch[], const Treal wa1[], int isign) - /* isign==+1 for backward transform */ - { - int i, k, ah, ac; - Treal ti2, tr2; - if (ido <= 2) { - for (k=0; k= l1) { - for (j=1; j idp) idlj -= idp; - war = wa[idlj - 2]; - wai = wa[idlj-1]; - for (ik=0; ik= l1) { - for (j=1; j= l1) { - for (k=0; k= l1) { - for (j=1; j= l1) { - for (k=0; k= l1) { - for (j=1; j= l1) { - for (j=1; j 5) { - wa[i1-1] = wa[i-1]; - wa[i1] = wa[i]; - } - } - l1 = l2; - } - } /* cffti1 */ - - -NPY_VISIBILITY_HIDDEN void npy_cffti(int n, Treal wsave[]) - { - int iw1, iw2; - if (n == 1) return; - iw1 = 2*n; - iw2 = iw1 + 2*n; - cffti1(n, wsave+iw1, (int*)(wsave+iw2)); - } /* npy_cffti */ - - /* ------------------------------------------------------------------- -rfftf1, rfftb1, npy_rfftf, npy_rfftb, rffti1, npy_rffti. Treal FFTs. ----------------------------------------------------------------------- */ - -static void rfftf1(int n, Treal c[], Treal ch[], const Treal wa[], const int ifac[MAXFAC+2]) - { - int i; - int k1, l1, l2, na, kh, nf, ip, iw, ix2, ix3, ix4, ido, idl1; - Treal *cinput, *coutput; - nf = ifac[1]; - na = 1; - l2 = n; - iw = n-1; - for (k1 = 1; k1 <= nf; ++k1) { - kh = nf - k1; - ip = ifac[kh + 2]; - l1 = l2 / ip; - ido = n / l2; - idl1 = ido*l1; - iw -= (ip - 1)*ido; - na = !na; - if (na) { - cinput = ch; - coutput = c; - } else { - cinput = c; - coutput = ch; - } - switch (ip) { - case 4: - ix2 = iw + ido; - ix3 = ix2 + ido; - radf4(ido, l1, cinput, coutput, &wa[iw], &wa[ix2], &wa[ix3]); - break; - case 2: - radf2(ido, l1, cinput, coutput, &wa[iw]); - break; - case 3: - ix2 = iw + ido; - radf3(ido, l1, cinput, coutput, &wa[iw], &wa[ix2]); - break; - case 5: - ix2 = iw + ido; - ix3 = ix2 + ido; - ix4 = ix3 + ido; - radf5(ido, l1, cinput, coutput, &wa[iw], &wa[ix2], &wa[ix3], &wa[ix4]); - break; - default: - if (ido == 1) - na = !na; - if (na == 0) { - radfg(ido, ip, l1, idl1, c, ch, &wa[iw]); - na = 1; - } else { - radfg(ido, ip, l1, idl1, ch, c, &wa[iw]); - na = 0; - } - } - l2 = l1; - } - if (na == 1) return; - for (i = 0; i < n; i++) c[i] = ch[i]; - } /* rfftf1 */ - - -static void rfftb1(int n, Treal c[], Treal ch[], const Treal wa[], const int ifac[MAXFAC+2]) - { - int i; - int k1, l1, l2, na, nf, ip, iw, ix2, ix3, ix4, ido, idl1; - Treal *cinput, *coutput; - nf = ifac[1]; - na = 0; - l1 = 1; - iw = 0; - for (k1=1; k1<=nf; k1++) { - ip = ifac[k1 + 1]; - l2 = ip*l1; - ido = n / l2; - idl1 = ido*l1; - if (na) { - cinput = ch; - coutput = c; - } else { - cinput = c; - coutput = ch; - } - switch (ip) { - case 4: - ix2 = iw + ido; - ix3 = ix2 + ido; - radb4(ido, l1, cinput, coutput, &wa[iw], &wa[ix2], &wa[ix3]); - na = !na; - break; - case 2: - radb2(ido, l1, cinput, coutput, &wa[iw]); - na = !na; - break; - case 3: - ix2 = iw + ido; - radb3(ido, l1, cinput, coutput, &wa[iw], &wa[ix2]); - na = !na; - break; - case 5: - ix2 = iw + ido; - ix3 = ix2 + ido; - ix4 = ix3 + ido; - radb5(ido, l1, cinput, coutput, &wa[iw], &wa[ix2], &wa[ix3], &wa[ix4]); - na = !na; - break; - default: - radbg(ido, ip, l1, idl1, cinput, coutput, &wa[iw]); - if (ido == 1) na = !na; - } - l1 = l2; - iw += (ip - 1)*ido; - } - if (na == 0) return; - for (i=0; in_cols == x->n_rows,SIZEINEQUAL); - dbgassert(A->n_rows == b->n_rows,SIZEINEQUAL); - - #if LASP_USE_BLAS == 1 - dbgassert(false,"Untested function. Is not functional for strides"); - /* typedef enum CBLAS_ORDER {CblasRowMajor=101, CblasColMajor=102} CBLAS_ORDER; */ - /* typedef enum CBLAS_TRANSPOSE {CblasNoTrans=111, CblasTrans=112, CblasConjTrans=113, CblasConjNoTrans=114} CBLAS_TRANSPOSE; */ - /* - void cblas_zgemv(OPENBLAS_CONST enum CBLAS_ORDER order, - OPENBLAS_CONST enum CBLAS_TRANSPOSE trans, - OPENBLAS_CONST blasint m, - OPENBLAS_CONST blasint n, - OPENBLAS_CONST double *alpha, - OPENBLAS_CONST double *a, - OPENBLAS_CONST blasint lda, - OPENBLAS_CONST double *x, - OPENBLAS_CONST blasint incx, - OPENBLAS_CONST double *beta, - double *y, - OPENBLAS_CONST blasint incy); - */ - c alpha = 1.0; - c beta = 0.0; - cblas_zgemv(CblasColMajor, - CblasNoTrans, - A->n_rows, - A->n_cols, - (d*) &alpha, /* alpha */ - (d*) A->_data, /* A */ - A->n_rows, /* lda */ - (d*) x->_data, /* */ - 1, - (d*) &beta, /* beta */ - (d*) b->_data, - 1); - - - - #else - size_t i,j; - - vc_set(b,0.0); - - iVARTRACE(20,A->n_cols); - iVARTRACE(20,A->n_rows); - - for(j=0;jn_cols;j++){ - for(i=0;in_rows;i++) { - - c* Aij = getcmatval(A,i,j); - b->_data[i] += *Aij * *getvcval(x,j); - - } - - } - - - #endif -} - -/// The code below here is not yet worked on. Should be improved to -/// directly couple to openblas, instead of using lapacke.h -#if 0 - -/* These functions can be directly linked to openBLAS */ -#define lapack_complex_double double _Complex -#define lapack_complex_float float _Complex - -#define LAPACK_ROW_MAJOR 101 -#define LAPACK_COL_MAJOR 102 - -#define LAPACK_WORK_MEMORY_ERROR -1010 -#define LAPACK_TRANSPOSE_MEMORY_ERROR -1011 - -typedef int lapack_int; - -int LAPACKE_cgelss( int matrix_layout, int m, int n, - int nrhs, lapack_complex_float* a, - int lda, lapack_complex_float* b, - int ldb, float* s, float rcond, - int* rank ); -int LAPACKE_zgelss( int matrix_layout, int m, int n, - int nrhs, lapack_complex_double* a, - int lda, lapack_complex_double* b, - int ldb, double* s, double rcond, - int* rank ); - -lapack_int LAPACKE_zgels( int matrix_layout, char trans, lapack_int m, - lapack_int n, lapack_int nrhs, - lapack_complex_double* a, lapack_int lda, - lapack_complex_double* b, lapack_int ldb ); - - - - -#if LASP_FLOAT == 64 - -#define lapack_gelss LAPACKE_zgelss -#define lapack_gels LAPACKE_zgels -#else - -#define lapack_gelss LAPACKE_cgelss -#endif - -#define max(a,b) ((a)>(b)?(a):(b)) - - -/* int lsq_solve(const cmat* A,const vc* b,vc* x){ */ - -/* int rv; */ -/* /\* M: number of rows of matrix *\/ */ -/* /\* N: Number of columns *\/ */ -/* /\* Norm: L2|b-A*x| *\/ */ -/* /\* NRHS: Number of right hand sides: Number of columns of matrix B *\/ */ - -/* assert(A->n_rows>=A->n_cols); */ -/* assert(x->size == A->n_cols); */ -/* assert(b->size == A->n_rows); */ - -/* int info; */ - -/* size_t lda = max(1,A->n_rows); */ -/* size_t ldb = max(lda,A->n_cols); */ - -/* /\* Make explicit copy of matrix A data, as it will be overwritten */ -/* * by lapack_gels *\/ */ -/* c* A_data = Pool_allocatec(&lsq_solve_pool,A->n_rows*A->n_cols); */ -/* c_copy(A_data,A->data,A->n_cols*A->n_rows); */ - -/* c* work_data = Pool_allocatec(&lsq_solve_pool,b->size); */ -/* c_copy(work_data,b->data,b->size); */ - -/* /\* Lapack documentation says: *\/ */ -/* /\* if TRANS = 'N' and m >= n, rows 1 to n of B contain the least */ -/* squares solution vectors; the residual sum of squares for the */ -/* solution in each column is given by the sum of squares of the */ -/* modulus of elements N+1 to M in that column; */ -/* *\/ */ - - -/* /\* We always assume one RHS column *\/ */ -/* const int nrhs = 1; */ - -/* /\* General Least Squares Solve *\/ */ -/* info = lapack_gels(LAPACK_COL_MAJOR, /\* Column-major ordering *\/ */ -/* 'N', */ -/* A->n_rows, /\* Number of rows in matrix *\/ */ -/* A->n_cols, /\* Number of columns *\/ */ -/* nrhs, /\* nrhs, which is number_mics *\/ */ -/* A_data, /\* The A-matrix *\/ */ -/* lda, /\* lda: the leading dimension of matrix A *\/ */ -/* work_data, /\* The b-matrix *\/ */ -/* ldb); /\* ldb: the leading dimension of b: max(1,M,N) *\/ */ - -/* if(info==0){ */ -/* c_copy(x->data,work_data,x->size); */ -/* rv = SUCCESS; */ -/* } */ -/* else { */ -/* memset(x->data,0,x->size); */ -/* WARN("LAPACK INFO VALUE"); */ -/* printf("%i\n", info ); */ -/* TRACE(15,"Solving least squares problem failed\n"); */ - -/* rv = FAILURE; */ -/* } */ - -/* return rv; */ - -/* } */ - -/* d c_normdiff(const cmat* A,const cmat* B) { */ - -/* TRACE(15,"c_normdif"); */ - -/* dbgassert(A->n_cols==B->n_cols,"Number of columns of A and B " */ -/* "should be equal"); */ -/* dbgassert(A->n_rows==B->n_rows,"Number of rows of A and B " */ -/* "should be equal"); */ - -/* size_t size = A->n_cols*A->n_rows; */ - -/* vc diff_temp = vc_al[MAX_MATRIX_SIZE]; */ - -/* c_copy(diff_temp,A->data,size); */ - -/* c alpha = -1.0; */ - -/* /\* This routine computes y <- alpha*x + beta*y *\/ */ - - -/* /\* void cblas_zaxpy(OPENBLAS_CONST blasint n, *\/ */ -/* /\* OPENBLAS_CONST double *alpha, *\/ */ -/* /\* OPENBLAS_CONST double *x, *\/ */ -/* /\* OPENBLAS_CONST blasint incx, *\/ */ -/* /\* double *y, *\/ */ -/* /\* OPENBLAS_CONST blasint incy); *\/ */ - -/* cblas_zaxpy(size, */ -/* (d*) &alpha, */ -/* (d*) B->data, */ -/* 1, */ -/* (d*) diff_temp, */ -/* 1 ); */ - -/* return c_norm(diff_temp,size); */ -/* } */ -#endif /* if 0 */ - -////////////////////////////////////////////////////////////////////// diff --git a/lasp/c/lasp_alg.h b/lasp/c/lasp_alg.h deleted file mode 100644 index bc41074..0000000 --- a/lasp/c/lasp_alg.h +++ /dev/null @@ -1,192 +0,0 @@ -// lasp_alg.h -// -// Author: J.A. de Jong - ASCEE -// -// Description: -// (Linear) algebra routines on matrices and vectors -////////////////////////////////////////////////////////////////////// -#pragma once -#ifndef LASP_ALG_H -#define LASP_ALG_H -#include "lasp_config.h" -#include "lasp_mat.h" - -/** - * Compute the dot product of two vectors of floats - * - * @param a First vector - * @param b Second second vector - * @return dot product as float - */ -static inline d vd_dot(const vd * a,const vd* b) { - dbgassert(a && b,NULLPTRDEREF); - assert_vx(a); - assert_vx(b); - dbgassert(a->n_rows == b->n_rows,SIZEINEQUAL); - return d_dot(getvdval(a,0),getvdval(b,0),a->n_rows); -} - -/** - * y = fac * y - * - * @param y - * @param fac scale factor - */ -static inline void dmat_scale(dmat* y,const c fac){ - dbgassert(y,NULLPTRDEREF); - for(us col=0;coln_cols;col++) { - d_scale(getdmatval(y,0,col),fac,y->n_rows); - } - -} -/** - * y = fac * y - * - * @param y - * @param fac scale factor - */ -static inline void cmat_scale(cmat* y,const c fac){ - dbgassert(y,NULLPTRDEREF); - dbgassert(y,NULLPTRDEREF); - for(us col=0;coln_cols;col++) { - c_scale(getcmatval(y,0,col),fac,y->n_rows); - } -} - -/** - * x = x + fac*y - * - * @param x - * @param y - * @param fac - */ -static inline void dmat_add_dmat(dmat* x,dmat* y,d fac) { - dbgassert(x && y,NULLPTRDEREF); - dbgassert(x->n_cols == y->n_cols,SIZEINEQUAL); - dbgassert(x->n_rows == y->n_rows,SIZEINEQUAL); - for(us col=0;coln_cols;col++) { - d_add_to(getdmatval(x,0,col), - getdmatval(y,0,col),fac,x->n_rows); - } -} -/** - * x = x + fac*y - * - * @param[in,out] x - * @param[in] y - * @param[in] fac - */ -static inline void cmat_add_cmat(cmat* x,cmat* y,c fac) { - dbgassert(x && y,NULLPTRDEREF); - dbgassert(x->n_cols == y->n_cols,SIZEINEQUAL); - dbgassert(x->n_rows == y->n_rows,SIZEINEQUAL); - for(us col=0;coln_cols;col++) { - c_add_to(getcmatval(x,0,col), - getcmatval(y,0,col),fac,x->n_rows); - } -} - -/** - * Compute the element-wise (Hadamard) product of a and b, and store - * in result - * - * @param[out] result - * @param[in] a - * @param[in] b - */ -static inline void vd_elem_prod(vd* result,const vd* a,const vd* b) { - dbgassert(result && a && b,NULLPTRDEREF); - assert_equalsize(a,b); - d_elem_prod_d(getvdval(result,0), - getvdval(a,0), - getvdval(b,0),a->n_rows); -} -/** - * Compute the element-wise (Hadamard) product of a and b, and store - * in result - * - * @param[out] result - * @param[in] a - * @param[in] b - */ -static inline void vc_hadamard(vc* result,const vc* a,const vc* b) { - fsTRACE(15); - dbgassert(result && a && b,NULLPTRDEREF); - assert_equalsize(a,b); - assert_equalsize(a,result); - - c_hadamard(getvcval(result,0), - getvcval(a,0), - getvcval(b,0), - a->n_rows); - - check_overflow_vx(*result); - check_overflow_vx(*a); - check_overflow_vx(*b); - feTRACE(15); -} - -/** - * Compute the element-wise (Hadamard) product of a and b, and store - * in result - * - * @param[out] result - * @param[in] a - * @param[in] b - */ -static inline void cmat_hadamard(cmat* result, - const cmat* a, - const cmat* b) { - fsTRACE(15); - dbgassert(result && a && b,NULLPTRDEREF); - - dbgassert(result->n_rows==a->n_rows,SIZEINEQUAL); - dbgassert(result->n_cols==a->n_cols,SIZEINEQUAL); - dbgassert(b->n_rows==a->n_rows,SIZEINEQUAL); - dbgassert(b->n_cols==a->n_cols,SIZEINEQUAL); - - for(us col=0;coln_cols;col++) { - c_hadamard(getcmatval(result,0,col), - getcmatval(a,0,col), - getcmatval(b,0,col), - a->n_rows); - } - - feTRACE(15); -} - - -/** - * Compute the matrix vector product for complex-valued types: b = A*x. - * - * @param[in] A Matrix A - * @param[in] x Vector x - * @param[out] b Result of computation - */ -void cmv_dot(const cmat* A, - const vc* restrict x, - vc* restrict b); - -int lsq_solve(const cmat* A, - const vc* restrict b, - vc* restrict x); - -/** - * Compute the norm of the difference of two complex matrices - * - * @param A - * @param B - */ -d cmat_normdiff(const cmat* A,const cmat* B); - -/** - * Computes the Kronecker product of a kron b, stores result in result. - * - * @param a a - * @param b b - * @param result a kron b - */ -void kronecker_product(const cmat* a,const cmat* b,cmat* result); - -#endif // LASP_ALG_H -////////////////////////////////////////////////////////////////////// diff --git a/lasp/c/lasp_alloc.h b/lasp/c/lasp_alloc.h deleted file mode 100644 index f6a8a55..0000000 --- a/lasp/c/lasp_alloc.h +++ /dev/null @@ -1,33 +0,0 @@ -// lasp_alloc.h -// -// Author: J.A. de Jong - ASCEE -// -// Description: -// memory allocation functions. -////////////////////////////////////////////////////////////////////// -#pragma once -#ifndef LASP_ALLOC_H -#define LASP_ALLOC_H -#include -#include "lasp_tracer.h" -/** - * Reserved words for memory allocation. Can be changed to something - * else when required. For example for debugging purposes. - */ -static inline void* a_malloc(size_t nbytes) { -#if LASP_DEBUG - if(nbytes == 0) { - FATAL("Tried to allocate 0 bytes"); - } -#endif - void* ptr = malloc(nbytes); - if(!ptr) { - FATAL("Memory allocation failed. Exiting"); - } - return ptr; -} -#define a_free free -#define a_realloc realloc - -#endif // LASP_ALLOC_H -////////////////////////////////////////////////////////////////////// diff --git a/lasp/c/lasp_aps.c b/lasp/c/lasp_aps.c deleted file mode 100644 index 75b83a5..0000000 --- a/lasp/c/lasp_aps.c +++ /dev/null @@ -1,291 +0,0 @@ -// lasp_aps.c -// -// Author: J.A. de Jong -ASCEE -// -// Description: -// -////////////////////////////////////////////////////////////////////// -#define TRACERPLUS (-5) -#include "lasp_aps.h" -#include "lasp_ps.h" -#include "lasp_alg.h" -#include "lasp_dfifo.h" - -/* Multiplication factor for the maximum size of the fifo queue. This - * factor is multiplied by nfft to obtain the maximum size of the - * fifo. */ -#define FIFO_SIZE_MULT (5) - -typedef struct AvPowerSpectra_s { - us nfft, nchannels; - us overlap; /* Number of samples to overlap */ - - us naverages; /* Counter that counts the number of - * averages taken for the computation - * of this averaged power spectra. */ - - dmat buffer; /**< Buffer storage of some of the - * previous samples. Number of rows is - * equal to nfft. */ - - - dFifo* fifo; /* Sample fifo storage */ - - cmat* ps_storage; /**< Here we store the averaged - * results for each Cross-power - * spectra computed so far. */ - - cmat ps_result; - - cmat ps_single; /**< This is the work area for a - * PowerSpectra computation on a - * single block */ - - - vd weighting; /**< This array stores the time weighting - * coefficients for a running - * spectrogram. The vector length is - * zero for a full averaged (not - * running spectrogram). */ - - us oldest_block; /**< Index of oldest block in - * Spectrogram mode */ - - - PowerSpectra* ps; /**< Pointer to underlying - * PowerSpectra calculator. */ - -} AvPowerSpectra; - -void AvPowerSpectra_free(AvPowerSpectra* aps) { - fsTRACE(15); - - PowerSpectra_free(aps->ps); - dFifo_free(aps->fifo); - dmat_free(&aps->buffer); - - us nweight = aps->weighting.n_rows; - if(nweight > 0) { - for(us blockno = 0; blockno < nweight; blockno++) { - cmat_free(&aps->ps_storage[blockno]); - } - a_free(aps->ps_storage); - vd_free(&aps->weighting); - } - - cmat_free(&aps->ps_single); - cmat_free(&aps->ps_result); - a_free(aps); - - feTRACE(15); -} - -AvPowerSpectra* AvPowerSpectra_alloc(const us nfft, - const us nchannels, - const d overlap_percentage, - const WindowType wt, - const vd* weighting) { - - fsTRACE(15); - - /* Check nfft */ - if(nfft==0 || nfft % 2 != 0 || nfft > LASP_MAX_NFFT) { - WARN("Invalid nfft"); - feTRACE(15); - return NULL; - } - - /* Check overlap percentage */ - if(overlap_percentage < 0. || overlap_percentage >= 100.) { - WARN("Invalid overlap percentage"); - feTRACE(15); - return NULL; - } - - /* Compute and check overlap offset */ - us overlap = (us) (overlap_percentage*((d) nfft)/100); - if(overlap == nfft) { - WARN("Overlap percentage results in full overlap, decreasing overlap."); - overlap--; - } - - PowerSpectra* ps = PowerSpectra_alloc(nfft,wt); - if(!ps) { - WARN(ALLOCFAILED "ps"); - feTRACE(15); - return NULL; - } - - AvPowerSpectra* aps = a_malloc(sizeof(AvPowerSpectra)); - - aps->nchannels = nchannels; - aps->nfft = nfft; - aps->ps = ps; - aps->naverages = 0; - aps->overlap = overlap; - aps->buffer = dmat_alloc(nfft,nchannels); - aps->oldest_block = 0; - if(weighting) { - - us nweight = weighting->n_rows; - iVARTRACE(15,nweight); - /* Allocate vectors and matrices */ - aps->ps_storage = a_malloc(nweight*sizeof(cmat)); - for(us blockno = 0; blockno < nweight; blockno++) { - aps->ps_storage[blockno] = cmat_alloc(nfft/2+1,nchannels*nchannels); - cmat_set(&aps->ps_storage[blockno],0); - } - - /* Allocate space and copy weighting coefficients */ - aps->weighting = vd_alloc(weighting->n_rows); - vd_copy(&aps->weighting,weighting); - } - else { - TRACE(15,"no weighting"); - aps->weighting.n_rows = 0; - } - - aps->ps_result = cmat_alloc(nfft/2+1,nchannels*nchannels); - aps->ps_single = cmat_alloc(nfft/2+1,nchannels*nchannels); - cmat_set(&aps->ps_result,0); - - aps->fifo = dFifo_create(nchannels,FIFO_SIZE_MULT*nfft); - - feTRACE(15); - return aps; -} - - -us AvPowerSpectra_getAverages(const AvPowerSpectra* ps) { - return ps->naverages; -} - -/** - * Helper function that adds a block of time data to the APS - * - * @param aps AvPowerSpectra handle - * @param block Time data block. Size should be exactly nfft*nchannels. - */ -static void AvPowerSpectra_addBlock(AvPowerSpectra* aps, - const dmat* block) { - fsTRACE(15); - - dbgassert(aps && block,NULLPTRDEREF); - dbgassert(block->n_rows == aps->nfft,"Invalid block n_rows"); - dbgassert(block->n_cols == aps->nchannels,"Invalid block n_cols"); - - const us nfft = aps->nfft; - iVARTRACE(15,nfft); - - cmat* ps_single = &aps->ps_single; - cmat* ps_storage = aps->ps_storage; - cmat* ps_result = &aps->ps_result; - - PowerSpectra_compute(aps->ps, - block, - ps_single); - - vd weighting = aps->weighting; - us nweight = weighting.n_rows; - - if(nweight == 0) { - - /* Overall mode */ - c naverages = (++aps->naverages); - - /* Scale previous result */ - cmat_scale(ps_result, - (naverages-1)/naverages); - - /* Add new result, scaled properly */ - cmat_add_cmat(ps_result, - ps_single,1/naverages); - - } - else { - cmat_set(ps_result,0); - - us* oldest_block = &aps->oldest_block; - /* uVARTRACE(20,*oldest_block); */ - - cmat_copy(&ps_storage[*oldest_block],ps_single); - - uVARTRACE(16,*oldest_block); - if(aps->naverages < nweight) { - ++(aps->naverages); - } - - /* Update pointer to oldest block */ - (*oldest_block)++; - *oldest_block %= nweight; - - us block_index_weight = *oldest_block; - - for(us block = 0; block < aps->naverages; block++) { - /* Add new result, scaled properly */ - - c weight_fac = *getvdval(&weighting, - aps->naverages-1-block); - - /* cVARTRACE(20,weight_fac); */ - cmat_add_cmat(ps_result, - &ps_storage[block_index_weight], - weight_fac); - - block_index_weight++; - block_index_weight %= nweight; - - - } - - - - } - - feTRACE(15); -} - - -cmat* AvPowerSpectra_addTimeData(AvPowerSpectra* aps, - const dmat* timedata) { - - fsTRACE(15); - - dbgassert(aps && timedata,NULLPTRDEREF); - const us nchannels = aps->nchannels; - const us nfft = aps->nfft; - - dbgassert(timedata->n_cols == nchannels,"Invalid time data"); - dbgassert(timedata->n_rows > 0,"Invalid time data. " - "Should at least have one row"); - - /* dFifo handle */ - dFifo* fifo = aps->fifo; - dFifo_push(fifo,timedata); - - /* Temporary storage buffer */ - dmat* buffer = &aps->buffer; - - /* Pop samples from the fifo while there are still at - * least nfft samples available */ - while (dFifo_size(fifo) >= nfft) { - int popped = dFifo_pop(fifo, - buffer, - aps->overlap); /* Keep 'overlap' - * number of samples - * in the queue */ - - dbgassert((us) popped == nfft,"Bug in dFifo"); - /* Process the block of time data */ - AvPowerSpectra_addBlock(aps,buffer); - - } - - feTRACE(15); - return &aps->ps_result; -} - - - - -////////////////////////////////////////////////////////////////////// diff --git a/lasp/c/lasp_aps.h b/lasp/c/lasp_aps.h deleted file mode 100644 index 973037a..0000000 --- a/lasp/c/lasp_aps.h +++ /dev/null @@ -1,85 +0,0 @@ -// aps.h -// -// Author: J.A. de Jong - ASCEE -// -// Description: -// -////////////////////////////////////////////////////////////////////// -#pragma once -#ifndef LASP_APS_H -#define LASP_APS_H -#include "lasp_types.h" -#include "lasp_mat.h" -#include "lasp_window.h" - -typedef enum { - Linear=0, - Exponential=1 -} TimeWeighting; - - -typedef struct AvPowerSpectra_s AvPowerSpectra; - -/** - * Allocates a AvPowerSpectra object which is used to compute an - * averaged power spectra from given input time samples. - * - * @param[in] nfft Size of the fft, - * - * @param[in] nchannels Number of channels of time data to allocate - * for. - * - * @param[in] overlap_percentage. Should be 0<= overlap_pct < 100. The - * overlap percentage will be coerced, as the offset will be an - * integer number of samples at all cases. Therefore, the real overlap - * percentage can be obtained later on - * - * @param wt - * - * @return - */ -AvPowerSpectra* AvPowerSpectra_alloc(const us nfft, - const us nchannels, - const d overlap_percentage, - const WindowType wt, - const vd* spectrogram_weighting); - - -/** - * Computes the real overlap percentage, from the integer overlap - * offset. - * - * @param aps - */ -d AvPowerSpectra_realoverlap_pct(const AvPowerSpectra* aps); - -/** - * Return the current number of averages taken to obtain the result. - * - * @return The number of averages taken. - */ -us AvPowerSpectra_getAverages(const AvPowerSpectra*); - -/** - * Add time data to this Averaged Power Spectra. Remembers the part - * that should be overlapped with the next time data. - * - * @param aps AvPowerSpectra handle - * - * @param timedata Pointer to timedata buffer. Number of rows SHOULD - * *at least* be nfft. Number of columns should exactly be nchannels. - * - * @return pointer to the averaged power spectra buffer that is being - * written to. Pointer is valid until AvPowerSpectra_free() is called. - */ -cmat* AvPowerSpectra_addTimeData(AvPowerSpectra* aps, - const dmat* timedata); - -/** - * Free storage of the AvPowerSpectra - */ -void AvPowerSpectra_free(AvPowerSpectra*); - - -#endif // LASP_APS_H -////////////////////////////////////////////////////////////////////// diff --git a/lasp/c/lasp_assert.c b/lasp/c/lasp_assert.c deleted file mode 100644 index 3105e36..0000000 --- a/lasp/c/lasp_assert.c +++ /dev/null @@ -1,29 +0,0 @@ -// lasp_assert.c -// -// Author: J.A. de Jong -ASCEE -// -// Description: -// -////////////////////////////////////////////////////////////////////// -#include "lasp_config.h" -#include "lasp_assert.h" - -#ifdef LASP_DEBUG -#include -#include -#include "lasp_types.h" -#define MAX_MSG 200 - -void DBG_AssertFailedExtImplementation(const char* filename, - us linenr, - const char* extendedinfo) -{ - char scratchpad[MAX_MSG]; - - sprintf(scratchpad,"ASSERT: file %s line %lu: (%s)\n", - filename, linenr, extendedinfo); - printf("%s\n", scratchpad); - abort(); -} - -#endif diff --git a/lasp/c/lasp_assert.h b/lasp/c/lasp_assert.h deleted file mode 100644 index 2524c43..0000000 --- a/lasp/c/lasp_assert.h +++ /dev/null @@ -1,44 +0,0 @@ -// ascee_assert.h -// -// Author: J.A. de Jong - ASCEE -// -// Description: -// Basic tools for debugging using assert statements including text. -////////////////////////////////////////////////////////////////////// -#pragma once -#ifndef LASP_ASSERT_H -#define LASP_ASSERT_H - -#define OUTOFBOUNDSMATR "Out of bounds access on matrix row" -#define OUTOFBOUNDSMATC "Out of bounds access on matrix column" -#define OUTOFBOUNDSVEC "Out of bounds access on vector" -#define SIZEINEQUAL "Array sizes not equal" -#define ALLOCFAILED "Memory allocation failure in: " -#define NULLPTRDEREF "Null pointer dereference in: " - -#ifdef LASP_DEBUG -#include "lasp_types.h" - - -void DBG_AssertFailedExtImplementation(const char* file, - const us line, - const char* string); - -#define dbgassert(assertion, assert_string) \ - if (!(assertion)) \ - { \ - DBG_AssertFailedExtImplementation(__FILE__, __LINE__, assert_string ); \ - } - -#define assertvalidptr(ptr) dbgassert(ptr,NULLPTRDEREF) - -#else // LASP_DEBUG not defined - -#define dbgassert(assertion, assert_string) - -#define assertvalidptr(ptr) dbgassert(ptr,NULLPTRDEREF) - -#endif // LASP_DEBUG - -#endif // LASP_ASSERT_H -////////////////////////////////////////////////////////////////////// diff --git a/lasp/c/lasp_bem.c b/lasp/c/lasp_bem.c deleted file mode 100644 index 7d14399..0000000 --- a/lasp/c/lasp_bem.c +++ /dev/null @@ -1,20 +0,0 @@ -// bem.c -// -// Author: J.A. de Jong -ASCEE -// -// Description: -// -////////////////////////////////////////////////////////////////////// -#include "types.h" - -typedef struct { - umat elem; - vd nodes; - ElemType elemtype; -} BemMesh; - -int Ms_osc_free(BemMesh* mesh,cmat* Ms) { - - -} -////////////////////////////////////////////////////////////////////// diff --git a/lasp/c/lasp_decimation.c b/lasp/c/lasp_decimation.c deleted file mode 100644 index e6ab6aa..0000000 --- a/lasp/c/lasp_decimation.c +++ /dev/null @@ -1,221 +0,0 @@ -// lasp_decimation.c -// -// Author: J.A. de Jong -ASCEE -// -// Description: -// Implementation of the decimator -////////////////////////////////////////////////////////////////////// -#include "lasp_decimation.h" -#include "lasp_firfilterbank.h" -#include "lasp_tracer.h" -#include "lasp_alloc.h" -#include "lasp_dfifo.h" - -// The Maximum number of taps in a decimation filter -#define DEC_FILTER_MAX_TAPS (128) - -// The FFT length -#define DEC_FFT_LEN (1024) - -typedef struct { - DEC_FAC df; - us dec_fac; - us ntaps; - d h[DEC_FILTER_MAX_TAPS]; -} DecFilter; - -static __thread DecFilter DecFilters[] = { - {DEC_FAC_4,4,128, -#include "dec_filter_4.c" - } -}; - - -typedef struct Decimator_s { - us nchannels; - us dec_fac; - Firfilterbank** fbs; - dFifo* output_fifo; -} Decimator; - - -Decimator* Decimator_create(us nchannels,DEC_FAC df) { - fsTRACE(15); - - /* Find the filter */ - const us nfilters = sizeof(DecFilters)/sizeof(DecFilter); - DecFilter* filter = DecFilters; - bool found = false; - for(us filterno = 0;filterno < nfilters; filterno++) { - if(filter->df == df) { - TRACE(15,"Found filter"); - found = true; - break; - } - filter++; - } - if(!found) { - WARN("Decimation factor not found in list of filters"); - return NULL; - } - - /* Create the filterbanks */ - Decimator* dec = a_malloc(sizeof(Decimator)); - dec->fbs = a_malloc(sizeof(Firfilterbank*)*nchannels); - dec->nchannels = nchannels; - dec->dec_fac = filter->dec_fac; - - dmat h = dmat_foreign_data(filter->ntaps,1,filter->h,false); - - for(us channelno=0;channelnofbs[channelno] = Firfilterbank_create(&h,DEC_FFT_LEN); - } - - dmat_free(&h); - - /* Create input and output fifo's */ - dec->output_fifo = dFifo_create(nchannels,DEC_FFT_LEN); - - feTRACE(15); - return dec; -} - -static void lasp_downsample(dmat* downsampled, - const dmat* filtered, - us dec_fac) { - fsTRACE(15); - dbgassert(downsampled && filtered,NULLPTRDEREF); - dbgassert(filtered->n_rows/dec_fac == downsampled->n_rows, - "Incompatible number of rows"); - dbgassert(downsampled->n_cols == filtered->n_cols, - "Incompatible number of rows"); - - dbgassert(dec_fac> 0,"Invalid dec_fac"); - - for(us col=0;coln_cols;col++) { - /* Low-level BLAS copy. */ - d_copy(getdmatval(downsampled,0,col), - getdmatval(filtered,0,col), - downsampled->n_rows, /* number */ - 1, /* incx out */ - dec_fac); /* incx in */ - } - - check_overflow_xmat(*downsampled); - check_overflow_xmat(*filtered); - - feTRACE(15); -} - -dmat Decimator_decimate(Decimator* dec,const dmat* samples) { - - fsTRACE(15); - dbgassert(dec && samples,NULLPTRDEREF); - const us nchannels = dec->nchannels; - const us dec_fac = dec->dec_fac; - dbgassert(samples->n_cols == nchannels,"Invalid number of " - "channels in samples"); - dbgassert(samples->n_rows > 0,"Number of rows should be >0") - - /* Not downsampled, but filtered result */ - dmat filtered; - - /* Filter each channel and store result in filtered. In first - * iteration the right size for filtered is allocated. */ - - for(us chan=0;chanfbs[chan], - &samples_channel); - - dbgassert(filtered_res.n_cols == 1,"Bug in Firfilterbank"); - - vd_free(&samples_channel); - - if(filtered_res.n_rows > 0) { - if(chan==0) { - /* Allocate space for result */ - filtered = dmat_alloc(filtered_res.n_rows, - nchannels); - - } - dmat filtered_col = dmat_submat(&filtered, - 0,chan, - filtered_res.n_rows, - 1); - - dbgassert(filtered_res.n_rows == filtered_col.n_rows, - "Not all Firfilterbank's have same output number" - " of rows!"); - - dmat_copy_rows(&filtered_col, - &filtered_res, - 0,0,filtered_res.n_rows); - - dmat_free(&filtered_res); - dmat_free(&filtered_col); - } - else { - filtered = dmat_alloc(0, nchannels); - } - } - if(filtered.n_rows > 0) { - /* Push filtered result into output fifo */ - dFifo_push(dec->output_fifo, - &filtered); - } - dmat_free(&filtered); - - - /* Now, downsample stuff */ - dmat downsampled; - uVARTRACE(15,dec_fac); - us fifo_size = dFifo_size(dec->output_fifo); - if((fifo_size / dec_fac) > 0) { - - filtered = dmat_alloc((fifo_size/dec_fac)*dec_fac, - nchannels); - - int nsamples = dFifo_pop(dec->output_fifo, - &filtered,0); - - dbgassert((us) nsamples % dec_fac == 0 && nsamples > 0, - "BUG"); - dbgassert((us) nsamples == (fifo_size/dec_fac)*dec_fac,"BUG"); - - downsampled = dmat_alloc(nsamples/dec_fac,nchannels); - /* Do the downsampling work */ - lasp_downsample(&downsampled,&filtered,dec_fac); - - dmat_free(&filtered); - } - else { - TRACE(15,"Empty downsampled"); - downsampled = dmat_alloc(0,0); - } - - feTRACE(15); - /* return filtered; */ - return downsampled; -} - - -void Decimator_free(Decimator* dec) { - fsTRACE(15); - dbgassert(dec,NULLPTRDEREF); - dFifo_free(dec->output_fifo); - - for(us chan=0;channchannels;chan++) { - Firfilterbank_free(dec->fbs[chan]); - } - - a_free(dec->fbs); - a_free(dec); - - feTRACE(15); -} -////////////////////////////////////////////////////////////////////// - diff --git a/lasp/c/lasp_decimation.h b/lasp/c/lasp_decimation.h deleted file mode 100644 index 1115502..0000000 --- a/lasp/c/lasp_decimation.h +++ /dev/null @@ -1,54 +0,0 @@ -// lasp_decimation.h -// -// Author: J.A. de Jong - ASCEE -// -// Description: Sample decimator, works on a whole block of -// (uninterleaved) time samples at once. Decimates (downsamples and -// low-pass filters by a factor given integer factor. -////////////////////////////////////////////////////////////////////// -#pragma once -#ifndef LASP_DECIMATION_H -#define LASP_DECIMATION_H -#include "lasp_types.h" -#include "lasp_mat.h" - -typedef struct Decimator_s Decimator; - -typedef enum DEC_FAC_e { - DEC_FAC_4 = 0, // Decimate by a factor of 4 -} DEC_FAC; - -/** - * Create a decimation filter for a given number of channels and - * decimation factor - * - * @param nchannels Number of channels - - * @param d Decimation factor. Should be one of the implemented - * ones. (TODO: add more. At this point we have only a decimation - * factor of 4 implemented) - * - * @return Decimator handle. NULL on error - */ -Decimator* Decimator_create(us nchannels,DEC_FAC d); - -/** - * Decimate given samples - * - * @param samples - * - * @return Decimated samples. Can be an empty array. - */ -dmat Decimator_decimate(Decimator* dec,const dmat* samples); - - -d Decimator_get_cutoff(Decimator*); -/** - * Free memory corresponding to Decimator - * - * @param dec Decimator handle. - */ -void Decimator_free(Decimator* dec); - -#endif // LASP_DECIMATION_H -////////////////////////////////////////////////////////////////////// diff --git a/lasp/c/lasp_dfifo.c b/lasp/c/lasp_dfifo.c deleted file mode 100644 index d5e2e0e..0000000 --- a/lasp/c/lasp_dfifo.c +++ /dev/null @@ -1,145 +0,0 @@ -// lasp_dfifo.c -// -// Author: J.A. de Jong -ASCEE -// -// Description: -// Implementation of the dFifo queue -////////////////////////////////////////////////////////////////////// -#define TRACERPLUS (-10) -#include "lasp_dfifo.h" - -typedef struct dFifo_s { - dmat queue; - us start_row; - us end_row; -} dFifo; -us dFifo_size(dFifo* fifo) { - fsTRACE(15); - dbgassert(fifo,NULLPTRDEREF); - dbgassert(fifo->start_row <= fifo->end_row,"BUG"); - feTRACE(15); - return fifo->end_row-fifo->start_row; -} - -/** - * Change the max size of the dFifo to a new max size specified. Max size - * should be larger than fifo size. Resets start row to 0 - * - * @param fifo - * @param new_size - */ -static void dFifo_change_maxsize(dFifo* fifo,const us new_max_size) { - fsTRACE(15); - dmat old_queue = fifo->queue; - - dbgassert(new_max_size >= dFifo_size(fifo),"BUG"); - const us size = dFifo_size(fifo); - - dmat new_queue = dmat_alloc(new_max_size,old_queue.n_cols); - if(size > 0) { - dmat_copy_rows(&new_queue, - &old_queue, - 0, - fifo->start_row, - size); - } - - dmat_free(&old_queue); - fifo->queue = new_queue; - fifo->end_row -= fifo->start_row; - fifo->start_row = 0; - - feTRACE(15); -} - -dFifo* dFifo_create(const us nchannels, - const us init_size) { - - fsTRACE(15); - dFifo* fifo = a_malloc(sizeof(dFifo)); - fifo->queue = dmat_alloc(init_size,nchannels); - fifo->start_row = 0; - fifo->end_row = 0; - feTRACE(15); - return fifo; -} -void dFifo_free(dFifo* fifo) { - fsTRACE(15); - dmat_free(&fifo->queue); - a_free(fifo); - feTRACE(15); -} -void dFifo_push(dFifo* fifo,const dmat* data) { - fsTRACE(15); - dbgassert(fifo && data, NULLPTRDEREF); - dbgassert(data->n_cols == fifo->queue.n_cols, - "Invalid number of columns in data"); - - - const us added_size = data->n_rows; - - dmat queue = fifo->queue; - const us max_size = queue.n_rows; - - us* end_row = &fifo->end_row; - - if(added_size + dFifo_size(fifo) > max_size) { - dFifo_change_maxsize(fifo,2*(max_size+added_size)); - - /* Now the stack of this function is not valid anymore. Best - * thing to do is restart the function. */ - dFifo_push(fifo,data); - feTRACE(15); - return; - } - else if(*end_row + added_size > max_size) { - dFifo_change_maxsize(fifo,max_size); - /* Now the stack of this function is not valid anymore. Best - * thing to do is restart the function. */ - dFifo_push(fifo,data); - feTRACE(15); - return; - - } - - /* Now, copy samples */ - dmat_copy_rows(&queue, /* to */ - data, /* from */ - *end_row, /* startrow_to */ - 0, /* startrow_from */ - added_size); /* n_rows */ - - /* Increase the size */ - *end_row += added_size; - - feTRACE(15); -} -int dFifo_pop(dFifo* fifo,dmat* data,const us keep) { - fsTRACE(15); - dbgassert(fifo && data,NULLPTRDEREF); - dbgassert(data->n_cols == fifo->queue.n_cols, - "Invalid number of columns in data"); - dbgassert(keep < data->n_rows, "Number of samples to keep should" - " be smaller than requested number of samples"); - - us* start_row = &fifo->start_row; - us cur_size = dFifo_size(fifo); - us requested = data->n_rows; - - us obtained = requested > cur_size ? cur_size : requested; - dbgassert(obtained > keep,"Number of samples to keep should be" - " smaller than requested number of samples"); - - - dmat_copy_rows(data, - &fifo->queue, - 0, - *start_row, - obtained); - - *start_row += obtained - keep; - - feTRACE(15); - return (int) obtained; -} -////////////////////////////////////////////////////////////////////// diff --git a/lasp/c/lasp_dfifo.h b/lasp/c/lasp_dfifo.h deleted file mode 100644 index 98f198f..0000000 --- a/lasp/c/lasp_dfifo.h +++ /dev/null @@ -1,73 +0,0 @@ -// lasp_dfifo.h -// -// Author: J.A. de Jong - ASCEE -// -// Description: -// API of a contiguous fifo buffer of samples. -////////////////////////////////////////////////////////////////////// -#pragma once -#ifndef LASP_DFIFO_H -#define LASPDFIFO_H -#include "lasp_types.h" -#include "lasp_mat.h" - -typedef struct dFifo_s dFifo; - -/** - * Create a fifo buffer - * - * @param nchannels Number of channels to store for - * @param max_size Maximum size of the queue. - * - * @return Pointer to fifo queue (no null ptr) - */ -dFifo* dFifo_create(const us nchannels, - const us init_size); - - -/** - * Pushes samples into the fifo. - * - * @param fifo dFifo handle - * - * @param data data to push. Number of columns should be equal to - * nchannels. - */ -void dFifo_push(dFifo* fifo,const dmat* data); - -/** - * Pop samples from the queue - * - * @param[in] fifo dFifo handle - - * @param[out] data Pointer to dmat where popped data will be - * stored. Should have nchannels number of columns. If n_rows is - * larger than current storage, the queue is emptied. - - * @param[in] keep Keeps a number of samples for the next dFifo_pop(). If - * keep=0, then no samples will be left. Keep should be smaller than - * the number of rows in data. - * - * @return Number of samples obtained in data. - */ -int dFifo_pop(dFifo* fifo,dmat* data,const us keep); - -/** - * Returns current size of the fifo - * - * @param[in] fifo dFifo handle - * - * @return Current size - */ -us dFifo_size(dFifo* fifo); - -/** - * Free a dFifo object - * - * @param[in] fifo dFifo handle. - */ -void dFifo_free(dFifo* fifo); - -#endif // LASP_DFIFO_H -////////////////////////////////////////////////////////////////////// - diff --git a/lasp/c/lasp_eq.c b/lasp/c/lasp_eq.c deleted file mode 100644 index 1cf462f..0000000 --- a/lasp/c/lasp_eq.c +++ /dev/null @@ -1,70 +0,0 @@ -#include "lasp_eq.h" -#include "lasp_assert.h" - -typedef struct Eq { - Sosfilterbank* fb; - us nfilters; - vd ampl_values; -} Eq; - -Eq* Eq_create(Sosfilterbank* fb) { - fsTRACE(15); - assertvalidptr(fb); - Eq* eq = a_malloc(sizeof(Eq)); - eq->fb = fb; - eq->nfilters = Sosfilterbank_getFilterbankSize(fb); - eq->ampl_values = vd_alloc(eq->nfilters); - vd_set(&(eq->ampl_values), 1.0); - feTRACE(15); - return eq; -} -vd Eq_equalize(Eq* eq,const vd* input_data) { - fsTRACE(15); - assertvalidptr(eq); - assert_vx(input_data); - vd result = vd_alloc(input_data->n_rows); - dmat_set(&result, 0); - dmat filtered = Sosfilterbank_filter(eq->fb, input_data); - - for(us filter=0;filternfilters;filter++) { - d ampl = *getvdval(&(eq->ampl_values), filter); - /// TODO: Replace this code with something more fast from BLAS. - for(us sample=0;samplen_rows == eq->nfilters, "Invalid levels size"); - for(us ch=0;chnfilters;ch++){ - d level = *getvdval(levels, ch); - *getvdval(&(eq->ampl_values), ch) = d_pow(10, level/20); - } - - feTRACE(15); -} - -us Eq_getNLevels(const Eq* eq) { - fsTRACE(15); - assertvalidptr(eq); - feTRACE(15); - return eq->nfilters; -} - -void Eq_free(Eq* eq) { - fsTRACE(15); - assertvalidptr(eq); - assertvalidptr(eq->fb); - Sosfilterbank_free(eq->fb); - vd_free(&(eq->ampl_values)); - feTRACE(15); -} diff --git a/lasp/c/lasp_eq.h b/lasp/c/lasp_eq.h deleted file mode 100644 index 1afefff..0000000 --- a/lasp/c/lasp_eq.h +++ /dev/null @@ -1,59 +0,0 @@ -// lasp_eq.h -// -// Author: J.A. de Jong - ASCEE -// -// Description: Implementation of an equalizer using the Second Order Sections -// filter bank implementation. Applies all filterbanks in parallel and -// recombines to create a single output signal. -#pragma once -#ifndef LASP_EQ_H -#define LASP_EQ_H -#include "lasp_sosfilterbank.h" -typedef struct Eq Eq; - -/** - * Initialize an equalizer using the given Filterbank. Note: takes over pointer - * ownership of fb! Sets levels of all filterbanks initially to 0 dB. - * - * @param[in] fb Filterbank to be used in equalizer - * @return Equalizer handle, NULL on error - * */ -Eq* Eq_create(Sosfilterbank* fb); - -/** - * Equalize a given piece of data using current settings of the levels. - * - * @param[in] eq Equalizer handle - * @param[in] input_data Input data to equalize - * @return Equalized data. Newly allocated vector. Ownership is transferred. - * */ -vd Eq_equalize(Eq* eq,const vd* input_data); - -/** - * Returns number of channels of the equalizer. Note: takes over pointer - * ownership of fb! - * - * @param[in] eq Equalizer handle - * */ -us Eq_getNLevels(const Eq* eq); - -/** - * Set amplification values for each filter in the equalizer. - * - * @param[in] eq Equalizer handle - * @param[in] levels: Vector with level values for each channel. Should have - * length equal to the number of filters in the filterbank. - * */ -void Eq_setLevels(Eq* eq, const vd* levels); - -/** - * Cleans up an existing Equalizer - * - * @param[in] eq Equalizer handle - */ -void Eq_free(Eq* eq); - - -#endif // LASP_EQ_H -// ////////////////////////////////////////////////////////////////////// - diff --git a/lasp/c/lasp_fft.c b/lasp/c/lasp_fft.c deleted file mode 100644 index 310897d..0000000 --- a/lasp/c/lasp_fft.c +++ /dev/null @@ -1,263 +0,0 @@ -// lasp_fft.c -// -// Author: J.A. de Jong - ASCEE -// -// Description: -// -////////////////////////////////////////////////////////////////////// -#define TRACERPLUS (-5) -#include "lasp_types.h" -#include "lasp_config.h" -#include "lasp_tracer.h" -#include "lasp_fft.h" - -#if LASP_FFT_BACKEND == FFTPack -#include "fftpack.h" -typedef struct Fft_s { - us nfft; - vd fft_work; // Storage memory for fftpack -} Fft_s; -#elif LASP_FFT_BACKEND == FFTW -#include - -typedef struct Fft_s { - us nfft; - fftw_plan forward_plan; - fftw_plan reverse_plan; - c* complex_storage; - d* real_storage; -} Fft_s; -#else -#error "Cannot compile lasp_ffc.c, no FFT backend specified. Should either be FFTPack, or FFTW" -#endif - -void load_fft_wisdom(const char* wisdom) { -#if LASP_FFT_BACKEND == FFTPack -#elif LASP_FFT_BACKEND == FFTW - if(wisdom) { - int rv= fftw_import_wisdom_from_string(wisdom); - if(rv != 1) { - fprintf(stderr, "Error loading FFTW wisdom"); - } - } -#endif -} - -char* store_fft_wisdom() { -#if LASP_FFT_BACKEND == FFTPack - return NULL; -#elif LASP_FFT_BACKEND == FFTW - return fftw_export_wisdom_to_string(); -#endif -} - -Fft* Fft_create(const us nfft) { - fsTRACE(15); - if(nfft == 0) { - WARN("nfft should be > 0"); - return NULL; - } - - Fft* fft = a_malloc(sizeof(Fft)); - - fft->nfft = nfft; - -#if LASP_FFT_BACKEND == FFTPack - /* Initialize foreign fft lib */ - fft->fft_work = vd_alloc(2*nfft+15); - npy_rffti(nfft,getvdval(&fft->fft_work,0)); - check_overflow_vx(fft->fft_work); -#elif LASP_FFT_BACKEND == FFTW - fft->complex_storage = fftw_malloc(sizeof(c) * (nfft/2 + 1)); - fft->real_storage = fftw_malloc(sizeof(d) * nfft); - - fft->forward_plan = fftw_plan_dft_r2c_1d(nfft, - fft->real_storage, - fft->complex_storage, - FFTW_MEASURE); - fft->reverse_plan = fftw_plan_dft_c2r_1d(nfft, - fft->complex_storage, - fft->real_storage, - FFTW_MEASURE); - -#endif - - /* print_vd(&fft->fft_work); */ - - feTRACE(15); - return fft; -} -void Fft_free(Fft* fft) { - fsTRACE(15); - dbgassert(fft,NULLPTRDEREF); -#if LASP_FFT_BACKEND == FFTPack - vd_free(&fft->fft_work); -#elif LASP_FFT_BACKEND == FFTW - fftw_free(fft->complex_storage); - fftw_free(fft->real_storage); - fftw_destroy_plan(fft->forward_plan); - fftw_destroy_plan(fft->reverse_plan); -#endif - a_free(fft); - feTRACE(15); -} - -us Fft_nfft(const Fft* fft) {return fft->nfft;} - -void Fft_ifft_single(const Fft* fft,const vc* freqdata,vd* result) { - fsTRACE(15); - dbgassert(fft && freqdata && result,NULLPTRDEREF); - const us nfft = fft->nfft; - dbgassert(result->n_rows == nfft, - "Invalid size for time data rows." - " Should be equal to nfft"); - - dbgassert(freqdata->n_rows == (nfft/2+1),"Invalid number of rows in" - " result array"); - - - d* result_ptr = getvdval(result,0); - -#if LASP_FFT_BACKEND == FFTPack - d* freqdata_ptr = (d*) getvcval(freqdata,0); - /* Copy freqdata, to fft_result. */ - d_copy(&result_ptr[1],&freqdata_ptr[2],nfft-1,1,1); - result_ptr[0] = freqdata_ptr[0]; - - /* Perform inplace backward transform */ - npy_rfftb(nfft, - result_ptr, - getvdval(&fft->fft_work,0)); - - -#elif LASP_FFT_BACKEND == FFTW - c* freqdata_ptr = (c*) getvcval(freqdata,0); - - c_copy(fft->complex_storage, freqdata_ptr,nfft/2+1); - - fftw_execute(fft->reverse_plan); - - d_copy(result_ptr, fft->real_storage, nfft, 1, 1); - -#endif - check_overflow_vx(*result); - - /* Scale by dividing by nfft. Checked with numpy implementation - * that this indeed needs to be done for FFTpack. */ - d_scale(result_ptr,1/((d) nfft),nfft); - feTRACE(15); -} -void Fft_ifft(const Fft* fft,const cmat* freqdata,dmat* timedata) { - fsTRACE(15); - - dbgassert(fft && timedata && freqdata,NULLPTRDEREF); - - const us nchannels = timedata->n_cols; - dbgassert(timedata->n_cols == freqdata->n_cols, - "Number of columns in timedata and result" - " should be equal."); - - for(us col=0;colnfft; - assert_vx(timedata); - assert_vx(result); - dbgassert(timedata->n_rows == nfft, - "Invalid size for time data rows." - " Should be equal to nfft"); - - dbgassert(result->n_rows == (nfft/2+1),"Invalid number of rows in" - " result array"); - - -#if LASP_FFT_BACKEND == FFTPack - d* result_ptr = (d*) getvcval(result,0); - - /* Fftpack stores the data a bit strange, the resulting array - * has the DC value at 0,the first cosine at 1, the first sine - * at 2 etc. 1 - * resulting matrix, as for the complex data, the imaginary - * part of the DC component equals zero. */ - - /* Copy timedata, as it will be overwritten in the fft pass. */ - d_copy(&result_ptr[1],getvdval(timedata,0),nfft,1,1); - - - /* Perform fft */ - npy_rfftf(nfft,&result_ptr[1], - getvdval(&fft->fft_work,0)); - - /* Set real part of DC component to first index of the rfft - * routine */ - result_ptr[0] = result_ptr[1]; - - result_ptr[1] = 0; /* Set imaginary part of DC component - * to zero */ - - /* For an even fft, the imaginary part of the Nyquist frequency - * bin equals zero.*/ - if(islikely(nfft%2 == 0)) { - result_ptr[nfft+1] = 0; - } - check_overflow_vx(fft->fft_work); -#elif LASP_FFT_BACKEND == FFTW - - d* timedata_ptr = getvdval(timedata,0); - c* result_ptr = getvcval(result,0); - d_copy(fft->real_storage,timedata_ptr, nfft, 1, 1); - - fftw_execute(fft->forward_plan); - - c_copy(result_ptr, fft->complex_storage, nfft/2+1); - -#endif - check_overflow_vx(*result); - feTRACE(15); - -} -void Fft_fft(const Fft* fft,const dmat* timedata,cmat* result) { - fsTRACE(15); - - dbgassert(fft && timedata && result,NULLPTRDEREF); - - const us nchannels = timedata->n_cols; - dbgassert(timedata->n_cols == result->n_cols, - "Number of columns in timedata and result" - " should be equal."); - - for(us col=0;coln_rows; - const us nfilters = h->n_cols; - - if(P > nfft/2) { - WARN("Filter order should be <= nfft/2"); - return NULL; - } - - Fft* fft = Fft_create(nfft); - if(!fft) { - WARN("Fft allocation failed"); - return NULL; - } - - Firfilterbank* fb = a_malloc(sizeof(Firfilterbank)); - - fb->nfft = nfft; - fb->P_m_1 = P-1; - fb->fft = fft; - fb->filters = cmat_alloc(nfft/2+1,nfilters); - - fb->output_fifo = dFifo_create(nfilters,FIFO_SIZE_MULT*nfft); - fb->input_fifo = dFifo_create(1,FIFO_SIZE_MULT*nfft); - - // Initialize the input fifo with zeros. - // dmat init_zero = dmat_alloc(nfft - P, 1); - // dmat_set(&init_zero,0); - // dFifo_push(fb->input_fifo, &init_zero); - // dmat_free(&init_zero); - - /* Create a temporary buffer which is going to be FFT'th to - * contain the filter transfer functions. - */ - dmat temp = dmat_alloc(nfft,nfilters); - dmat_set(&temp,0); - dmat_copy_rows(&temp,h,0,0,h->n_rows); - - /* Fft the FIR impulse responses */ - Fft_fft(fb->fft,&temp,&fb->filters); - - dmat_free(&temp); - - feTRACE(15); - return fb; -} -void Firfilterbank_free(Firfilterbank* fb) { - fsTRACE(15); - dbgassert(fb,NULLPTRDEREF); - cmat_free(&fb->filters); - dFifo_free(fb->input_fifo); - dFifo_free(fb->output_fifo); - Fft_free(fb->fft); - a_free(fb); - feTRACE(15); -} -dmat Firfilterbank_filter(Firfilterbank* fb, - const vd* x) { - - fsTRACE(15); - dbgassert(fb && x ,NULLPTRDEREF); - - dFifo* input_fifo = fb->input_fifo; - dFifo* output_fifo = fb->output_fifo; - - const us nfft = fb->nfft; - const us nfilters = fb->filters.n_cols; - - /* Push samples to the input fifo */ - dFifo_push(fb->input_fifo,x); - - dmat input_block = dmat_alloc(nfft,1); - - /* FFT'th filter coefficients */ - cmat input_fft = cmat_alloc(nfft/2+1,1); - - /* Output of the fast convolution */ - cmat output_fft = cmat_alloc(nfft/2+1,nfilters); - - /* Inverse FFT'th output */ - dmat output_block = dmat_alloc(nfft,nfilters); - - while (dFifo_size(input_fifo) >= nfft) { - - us nsamples = dFifo_pop(input_fifo, - &input_block, - fb->P_m_1 /* save P-1 samples */ - ); - - dbgassert(nsamples == nfft,"BUG in dFifo"); - - Fft_fft(fb->fft,&input_block,&input_fft); - - vc input_fft_col = cmat_column(&input_fft,0); - - for(us col=0;colfilters,col); - - vc_hadamard(&output_fft_col, - &input_fft_col, - &filter_col); - - vc_free(&output_fft_col); - vc_free(&filter_col); - - } - - vc_free(&input_fft_col); - - Fft_ifft(fb->fft,&output_fft,&output_block); - - dmat valid_samples = dmat_submat(&output_block, - fb->P_m_1,0, /* startrow, startcol */ - nfft-fb->P_m_1, /* Number of rows */ - output_block.n_cols); - - /* Push the valid samples to the output FIFO */ - dFifo_push(fb->output_fifo,&valid_samples); - dmat_free(&valid_samples); - - } - - dmat_free(&input_block); - cmat_free(&input_fft); - cmat_free(&output_fft); - dmat_free(&output_block); - - us samples_done = dFifo_size(output_fifo); - uVARTRACE(15,samples_done); - dmat filtered_result = dmat_alloc(samples_done,nfilters); - if(samples_done) { - us samples_done2 = dFifo_pop(output_fifo,&filtered_result,0); - dbgassert(samples_done2 == samples_done,"BUG in dFifo"); - } - feTRACE(15); - return filtered_result; -} - - -////////////////////////////////////////////////////////////////////// diff --git a/lasp/c/lasp_firfilterbank.h b/lasp/c/lasp_firfilterbank.h deleted file mode 100644 index e0f646b..0000000 --- a/lasp/c/lasp_firfilterbank.h +++ /dev/null @@ -1,59 +0,0 @@ -// lasp_firfilterbank.h -// -// Author: J.A. de Jong - ASCEE -// -// Description: Implemententation of a discrete FIR filterbank using fast -// convolution and the overlap-save (overlap-scrap method). Multiple -// filters can be applied to the same input data (*filterbank*). -// Implementation is computationally efficient, as the forward FFT is -// performed only over the input data, and the backwards transfer for -// each filter in the filterbank. -////////////////////////////////////////////////////////////////////// -#pragma once -#ifndef LASP_FIRFILTERBANK_H -#define LASP_FIRFILTERBANK_H -#include "lasp_types.h" -#include "lasp_mat.h" -typedef struct Firfilterbank_s Firfilterbank; - -/** - * Initializes a fast convolution filter bank and returns a Firfilterbank - * handle. The nfft will be chosen to be at least four times the - * length of the FIR filters. - * - * @param h: matrix with filter coefficients of each of the - * filters. First axis is the axis of the filter coefficients, second - * axis is the filter number. Maximum length of the filter is nfft/2. - * - * @param nfft: FTT length for fast convolution. For good performance, - * nfft should be chosen as the nearest power of 2, approximately four - * times the filter lengths. For the lowest possible latency, it is - * better to set nfft at twice the filter length. - * - * @return Firfilterbank handle, NULL on error. - */ -Firfilterbank* Firfilterbank_create(const dmat* h,const us nfft); - -/** - * Filters x using h, returns y - * - * @param x Input time sequence block. Should have at least one sample. - - * @return Filtered output in an allocated array. The number of - * columns in this array equals the number of filters in the - * filterbank. The number of output samples is equal to the number of - * input samples in x. - */ -dmat Firfilterbank_filter(Firfilterbank* fb, - const vd* x); - -/** - * Cleans up an existing filter bank. - * - * @param f Filter handle - */ -void Firfilterbank_free(Firfilterbank* f); - - -#endif // LASP_FIRFILTERBANK_H -////////////////////////////////////////////////////////////////////// diff --git a/lasp/c/lasp_mat.c b/lasp/c/lasp_mat.c deleted file mode 100644 index bece9d3..0000000 --- a/lasp/c/lasp_mat.c +++ /dev/null @@ -1,54 +0,0 @@ -// lasp_mat.c -// -// Author: J.A. de Jong -ASCEE -// -// Description: -// -////////////////////////////////////////////////////////////////////// -#define TRACERPLUS (-10) -#include "lasp_mat.h" -#include "lasp_assert.h" -#include "lasp_tracer.h" - -#include - -#if LASP_DEBUG == 1 -void print_dmat(const dmat* m) { - fsTRACE(50); - size_t row,col; - for(row=0;rown_rows;row++){ - indent_trace(); - for(col=0;coln_cols;col++){ - d val = *getdmatval(m,row,col); - printf("%c%2.2e ", val<0?'-':' ' ,d_abs(val)); - - } - printf("\n"); - - } - feTRACE(50); -} -void print_cmat(const cmat* m) { - fsTRACE(50); - size_t row,col; - for(row=0;rown_rows;row++){ - indent_trace(); - for(col=0;coln_cols;col++){ - c val = *getcmatval(m,row,col); - - d rval = creal(val); - d ival = cimag(val); - - printf("%c%2.2e%c%2.2ei ",rval< 0 ?'-': ' ', - d_abs(rval),ival<0 ? '-' : '+',d_abs(ival) ) ; - - } - printf("\n"); - - } - feTRACE(50); -} - -#endif // LASP_DEBUG == 1 - -////////////////////////////////////////////////////////////////////// diff --git a/lasp/c/lasp_mat.h b/lasp/c/lasp_mat.h deleted file mode 100644 index 3a349ae..0000000 --- a/lasp/c/lasp_mat.h +++ /dev/null @@ -1,597 +0,0 @@ -// lasp_mat.h -// -// Author: J.A. de Jong - ASCEE -// -// Description: Basic routines for allocating, setting, freeing and -// copying of matrices and vectors. -////////////////////////////////////////////////////////////////////// -#pragma once -#ifndef LASP_MAT_H -#define LASP_MAT_H -#include "lasp_math_raw.h" -#include "lasp_alloc.h" -#include "lasp_assert.h" -#include "lasp_tracer.h" -#include "lasp_assert.h" - -/// Dense matrix of floating point values -typedef struct { - us n_rows; - us n_cols; - bool _foreign_data; - us colstride; - d* _data; -} dmat; - -/// Dense matrix of complex floating point values -typedef struct { - us n_rows; - us n_cols; - bool _foreign_data; - us colstride; - c* _data; -} cmat; - -typedef dmat vd; -typedef cmat vc; - -#define assert_equalsize(a,b) \ - dbgassert((a)->n_rows == (b)->n_rows,SIZEINEQUAL); \ - dbgassert((a)->n_cols == (b)->n_cols,SIZEINEQUAL); - -#define is_vx(vx) ((vx)->n_cols == 1) -#define assert_vx(vx) dbgassert(is_vx(vx),"Not a vector!") - -#define setvecval(vec,index,val) \ - assert_vx(vec); \ - dbgassert((((us) index) < (vec)->n_rows),OUTOFBOUNDSVEC); \ - (vec)->_data[index] = val; - -#define setmatval(mat,row,col,val) \ - dbgassert((((us) row) <= (mat)->n_rows),OUTOFBOUNDSMATR); \ - dbgassert((((us) col) <= (mat)->n_cols),OUTOFBOUNDSMATC); \ - (mat)->_data[(col)*(mat)->colstride+(row)] = val; - -/** - * Return pointer to a value from a vector - * - * @param mat The vector - * @param row The row - */ -/** - * Return a value from a matrix of floating points - * - * @param mat The matrix - * @param row The row - * @param col The column - */ -static inline d* getdmatval(const dmat* mat,us row,us col){ - dbgassert(mat,NULLPTRDEREF); - dbgassert(row < mat->n_rows,OUTOFBOUNDSMATR); - dbgassert(col < mat->n_cols,OUTOFBOUNDSMATC); - return &mat->_data[col*mat->colstride+row]; -} -/** - * Return a value from a matrix of complex floating points - * - * @param mat The matrix - * @param row The row - * @param col The column - */ -static inline c* getcmatval(const cmat* mat,const us row,const us col){ - dbgassert(mat,NULLPTRDEREF); - dbgassert(row < mat->n_rows,OUTOFBOUNDSMATR); - dbgassert(col < mat->n_cols,OUTOFBOUNDSMATC); - return &mat->_data[col*mat->colstride+row]; -} - -static inline d* getvdval(const vd* vec,us row){ - dbgassert(vec,NULLPTRDEREF); - assert_vx(vec); - return getdmatval(vec,row,0); -} - -/** - * Return pointer to a value from a complex vector - * - * @param mat The vector - * @param row The row - */ -static inline c* getvcval(const vc* vec,us row){ - dbgassert(vec,NULLPTRDEREF); - assert_vx(vec); - return getcmatval(vec,row,0); -} - - - -#ifdef LASP_DEBUG -#define OVERFLOW_MAGIC_NUMBER (-10e-45) - -#define check_overflow_xmat(xmat) \ - TRACE(15,"Checking overflow " #xmat); \ - if(!(xmat)._foreign_data) { \ - dbgassert((xmat)._data[((xmat).n_cols-1)*(xmat).colstride+(xmat).n_rows] \ - == OVERFLOW_MAGIC_NUMBER, \ - "Buffer overflow detected on" #xmat ); \ - } \ - -#define check_overflow_vx check_overflow_xmat - -#else -#define check_overflow_vx(vx) -#define check_overflow_xmat(xmat) -#endif - -/** - * Sets all values in a matrix to the value - * - * @param mat The matrix to set - * @param value - */ -static inline void dmat_set(dmat* mat,const d value){ - dbgassert(mat,NULLPTRDEREF); - if(islikely(mat->n_cols * mat->n_rows > 0)) { - for(us col=0;coln_cols;col++) { - d_set(getdmatval(mat,0,col),value,mat->n_rows); - } - } -} -#define vd_set dmat_set - -/** - * Sets all values in a matrix to the value - * - * @param mat The matrix to set - * @param value - */ -static inline void cmat_set(cmat* mat,const c value){ - dbgassert(mat,NULLPTRDEREF); - if(islikely(mat->n_cols * mat->n_rows > 0)) { - for(us col=0;coln_cols;col++) { - c_set(getcmatval(mat,0,col),value,mat->n_rows); - } - } -} -#define vc_set cmat_set - -/** - * Allocate data for a matrix of floating points - * - * @param n_rows Number of rows - * @param n_cols Number of columns - * @param p Memory pool - * - * @return dmat with allocated data - */ -static inline dmat dmat_alloc(us n_rows, - us n_cols) { - dmat result = { n_rows, n_cols, - false, - n_rows, // The column stride - NULL}; - - #ifdef LASP_DEBUG - result._data = (d*) a_malloc((n_rows*n_cols+1)*sizeof(d)); - result._data[n_rows*n_cols] = OVERFLOW_MAGIC_NUMBER; - #else - result._data = (d*) a_malloc((n_rows*n_cols)*sizeof(d)); - #endif // LASP_DEBUG - - #ifdef LASP_DEBUG - dmat_set(&result,NAN); - #endif // LASP_DEBUG - - return result; -} - -/** - * Allocate a matrix of complex floating points - * - * @param n_rows Number of rows - * @param n_cols Number of columns - * @param p Memory pool - * - * @return cmat with allocated data - */ -static inline cmat cmat_alloc(const us n_rows, - const us n_cols) { - cmat result = { n_rows, n_cols, false, n_rows, NULL}; - - #ifdef LASP_DEBUG - result._data = (c*) a_malloc((n_rows*n_cols+1)*sizeof(c)); - result._data[n_rows*n_cols] = OVERFLOW_MAGIC_NUMBER; - #else - result._data = (c*) a_malloc((n_rows*n_cols)*sizeof(c)); - #endif // LASP_DEBUG - - #ifdef LASP_DEBUG - cmat_set(&result,NAN+I*NAN); - #endif // LASP_DEBUG - return result; -} - -/** - * Allocate data for a float vector. - * - * @param size Size of the vector - * - * @return vd with allocated data - */ -static inline vd vd_alloc(us size) { - return dmat_alloc(size,1); -} - -/** - * Allocate data for a complex vector. - * - * @param size Size of the vector - * - * @return vc with allocated data - */ -static inline vc vc_alloc(us size) { - return cmat_alloc(size,1); -} - - -/** - * Creates a dmat from foreign data. Does not copy the data, but only - * initializes the row pointers. Assumes column-major ordering for the - * data. Please do not keep this one alive after the data has been - * destroyed. Assumes the column colstride equals to n_rows. - * - * @param n_rows Number of rows - * @param n_cols Number of columns - * @param data - * - * @return - */ -static inline dmat dmat_foreign(dmat* other) { - dbgassert(other,NULLPTRDEREF); - dmat result = {other->n_rows, - other->n_cols, - true, - other->colstride, - other->_data}; - return result; -} -/** - * Create a dmat from foreign data. Assumes the colstride of the data is - * n_rows. - * - * @param n_rows Number of rows - * @param n_cols Number of columns - * @param data Pointer to data storage - * - * @return dmat - */ -static inline dmat dmat_foreign_data(us n_rows, - us n_cols, - d* data, - bool own_data) { - - dbgassert(data,NULLPTRDEREF); - dmat result = {n_rows, - n_cols, - !own_data, - n_rows, - data}; - return result; -} -/** - * Create a cmat from foreign data. Assumes the colstride of the data is - * n_rows. - * - * @param n_rows Number of rows - * @param n_cols Number of columns - * @param data Pointer to data storage - * - * @return dmat - */ -static inline cmat cmat_foreign_data(us n_rows, - us n_cols, - c* data, - bool own_data) { - - dbgassert(data,NULLPTRDEREF); - cmat result = {n_rows, - n_cols, - !own_data, - n_rows, - data}; - return result; -} - -/** - * Creates a cmat from foreign data. Does not copy the data, but only - * initializes the row pointers. Assumes column-major ordering for the - * data. Please do not keep this one alive after the data has been - * destroyed. Assumes the column colstride equals to n_rows. - * - * @param n_rows - * @param n_cols - * @param data - * - * @return - */ -static inline cmat cmat_foreign(cmat* other) { - dbgassert(other,NULLPTRDEREF); - cmat result = {other->n_rows, - other->n_cols, - true, - other->colstride, - other->_data}; - return result; -} - - - -/** - * Free's data of dmat. Safe to run on sub-matrices as well. - * - * @param m Matrix to free - */ -static inline void dmat_free(dmat* m) { - dbgassert(m,NULLPTRDEREF); - if(!(m->_foreign_data)) a_free(m->_data); -} -#define vd_free dmat_free - -/** - * Free's data of dmat. Safe to run on sub-matrices as well. - * - * @param m Matrix to free - */ -static inline void cmat_free(cmat* m) { - dbgassert(m,NULLPTRDEREF); - if(!(m->_foreign_data)) a_free(m->_data); -} -#define vc_free cmat_free - -/** - * Copy some rows from one matrix to another - * - * @param to Matrix to copy to - * @param from Matrix to copy from - * @param startrow_from Starting row where to get the values - * @param startrow_to Starting row where to insert the values - * @param nrows Number of rows to copy - */ -static inline void dmat_copy_rows(dmat* to,const dmat* from, - us startrow_to, - us startrow_from, - us nrows) { - us col,ncols = to->n_cols; - dbgassert(to && from,NULLPTRDEREF); - dbgassert(to->n_cols == from->n_cols,SIZEINEQUAL); - dbgassert(startrow_from+nrows <= from->n_rows,OUTOFBOUNDSMATR); - dbgassert(startrow_to+nrows <= to->n_rows,OUTOFBOUNDSMATR); - - for(col=0;coln_rows,OUTOFBOUNDSMATR); - dbgassert(n_cols+startcol <= parent->n_cols,OUTOFBOUNDSMATC); - - dmat result = { n_rows,n_cols, - true, // Foreign data = true - parent->n_rows, // This is the colstride to get to - // the next column. - getdmatval(parent,startrow,startcol)}; - - return result; -} -/** - * Allocate a sub-matrix view of the parent - * - * @param parent Parent matrix - * @param startrow Startrow - * @param startcol Start column - * @param n_rows Number of rows in sub-matrix - * @param n_cols Number of columns in sub-matrix - * - * @return submatrix view - */ -static inline cmat cmat_submat(cmat* parent, - const us startrow, - const us startcol, - const us n_rows, - const us n_cols) { - dbgassert(false,"untested"); - dbgassert(parent,NULLPTRDEREF); - dbgassert(n_rows+startrow <= parent->n_rows,OUTOFBOUNDSMATR); - dbgassert(n_cols+startcol <= parent->n_cols,OUTOFBOUNDSMATC); - - - cmat result = { n_rows,n_cols, - true, // Foreign data = true - parent->n_rows, // This is the colstride to get to - // the next column. - getcmatval(parent,startrow,startcol)}; - - return result; -} - -/** - * Copy contents of one matrix to another. Sizes should be equal - * - * @param to - * @param from - */ -static inline void dmat_copy(dmat* to,const dmat* from) { - dbgassert(to && from,NULLPTRDEREF); - dbgassert(to->n_rows==from->n_rows,SIZEINEQUAL); - dbgassert(to->n_cols==from->n_cols,SIZEINEQUAL); - for(us col=0;coln_cols;col++) { - d_copy(getdmatval(to,0,col), - getdmatval(from,0,col), - to->n_rows,1,1); - } -} - -/** - * Allocate a new array, with size based on other. - * - * @param[in] from: Array to copy - */ -static inline dmat dmat_alloc_from_dmat(const dmat* from) { - assertvalidptr(from); - dmat thecopy = dmat_alloc(from->n_rows, from->n_cols); - return thecopy; -} - -/** - * Copy contents of one matrix to another. Sizes should be equal - * - * @param to - * @param from - */ -static inline void cmat_copy(cmat* to,const cmat* from) { - dbgassert(to && from,NULLPTRDEREF); - dbgassert(to->n_rows==from->n_rows,SIZEINEQUAL); - dbgassert(to->n_cols==from->n_cols,SIZEINEQUAL); - for(us col=0;coln_cols;col++) { - c_copy(getcmatval(to,0,col), - getcmatval(from,0,col), - to->n_rows); - } -} - -/** - * Copy contents of one vector to another - * - * @param to : Vector to write to - * @param from : Vector to read from - */ -static inline void vd_copy(vd* to,const vd* from) { - dbgassert(to && from,NULLPTRDEREF); - assert_vx(to); - assert_vx(from); - dmat_copy(to,from); -} -/** - * Copy contents of one vector to another - * - * @param to : Vector to write to - * @param from : Vector to read from - */ -static inline void vc_copy(vc* to,const vc* from) { - dbgassert(to && from,NULLPTRDEREF); - assert_vx(to); - assert_vx(from); - cmat_copy(to,from); -} - - -/** - * Get a reference to a column of a matrix as a vector - * - * @param x Matrix - * @param col Column number - * - * @return vector with reference to column - */ -static inline vd dmat_column(dmat* x,us col) { - vd res = {x->n_rows,1,true,x->colstride,getdmatval(x,0,col)}; - return res; -} - -/** - * Get a reference to a column of a matrix as a vector - * - * @param x Matrix - * @param col Column number - * - * @return vector with reference to column - */ -static inline vc cmat_column(cmat* x,us col) { - vc res = {x->n_rows,1,true,x->colstride,getcmatval(x,0,col)}; - return res; -} - -/** - * Compute the complex conjugate of b and store result in a - * - * @param a - * @param b - */ -static inline void cmat_conj(cmat* a,const cmat* b) { - fsTRACE(15); - dbgassert(a && b,NULLPTRDEREF); - dbgassert(a->n_cols == b->n_cols,SIZEINEQUAL); - dbgassert(a->n_rows == b->n_rows,SIZEINEQUAL); - for(us col=0;coln_cols;col++) { - carray_conj(getcmatval(a,0,col),getcmatval(b,0,col),a->n_rows); - } - feTRACE(15); -} - -/** - * Take the complex conjugate of x, in place - * - * @param x - */ -static inline void cmat_conj_inplace(cmat* x) { - dbgassert(x,NULLPTRDEREF); - for(us col=0;coln_cols;col++) { - c_conj_inplace(getcmatval(x,0,col),x->n_rows); - } -} - -/** - * Computes the maximum value for each row, returns a vector with maximum - * values for each column in the matrix. - * - * @param x - */ -static inline vd dmat_max(const dmat x) { - vd max_vals = vd_alloc(x.n_cols); - d max_val = -d_inf; - for(us j=0; j< x.n_cols; j++) { - for(us i=0; i< x.n_rows; i++) { - max_val = *getdmatval(&x, i, j) < max_val? max_val : *getdmatval(&x, i,j); - } - *getvdval(&max_vals, j) = max_val; - } - return max_vals; -} - - -#if LASP_DEBUG == 1 - -void print_cmat(const cmat* m); -void print_dmat(const dmat* m); -#define print_vc(x) assert_vx(x) print_cmat(x) -#define print_vd(x) assert_vx(x) print_dmat(x) - -#else -#define print_cmat(m) -#define print_dmat(m) -#define print_vc(m) -#define print_vd(m) - -#endif // LASP_DEBUG == 1 - -#endif // LASP_MAT_H -////////////////////////////////////////////////////////////////////// diff --git a/lasp/c/lasp_math_raw.c b/lasp/c/lasp_math_raw.c deleted file mode 100644 index 02385f2..0000000 --- a/lasp/c/lasp_math_raw.c +++ /dev/null @@ -1,152 +0,0 @@ -// lasp_math_raw.c -// -// last-edit-by: J.A. de Jong -// -// Description: -// Operations working on raw arrays of floating point numbers -////////////////////////////////////////////////////////////////////// -#define TRACERPLUS (-5) -#include "lasp_math_raw.h" -#if LASP_USE_BLAS == 1 -#include -#endif - -#ifdef __MKL_CBLAS_H__ -/* Symbol not present in MKL blas */ -#define blasint CBLAS_INDEX -#else -#endif - -void d_elem_prod_d(d res[], - const d arr1[], - const d arr2[], - const us size) { - - #if LASP_USE_BLAS == 1 - - #if LASP_DEBUG - - if(arr1 == arr2) { - DBGWARN("d_elem_prod_d: Array 1 and array 2 point to the same" - " memory. This results in pointer aliasing, for which" - " testing is still to be done. Results might be" - " unrealiable."); - } - - #endif - - - #if LASP_DOUBLE_PRECISION - #define elem_prod_fun cblas_dsbmv - #else - #define elem_prod_fun cblas_ssbmv - #endif - /* These parameters do not matter for this specific case */ - const CBLAS_ORDER mat_order= CblasColMajor; - const CBLAS_UPLO uplo = CblasLower; - - /* Extra multiplication factor */ - const d alpha = 1.0; - - /* void cblas_dsbmv(OPENBLAS_CONST enum CBLAS_ORDER order, */ - /* OPENBLAS_CONST enum CBLAS_UPLO Uplo, */ - /* OPENBLAS_CONST blasint N, */ - /* OPENBLAS_CONST blasint K, */ - /* OPENBLAS_CONST double alpha, */ - /* OPENBLAS_CONST double *A, */ - /* OPENBLAS_CONST blasint lda, */ - /* OPENBLAS_CONST double *X, */ - /* OPENBLAS_CONST blasint incX, */ - /* OPENBLAS_CONST double beta, */ - /* double *Y, */ - /* OPENBLAS_CONST blasint incY); */ - - elem_prod_fun(mat_order, - uplo, - (blasint) size, - 0, // Just the diagonal; 0 super-diagonal bands - alpha, /* Multiplication factor alpha */ - arr1, - 1, /* LDA */ - arr2, /* x */ - 1, /* incX = 1 */ - 0.0, /* Beta */ - res, /* The Y matrix to write to */ - 1); /* incY */ - #undef elem_prod_fun - - #else /* No blas routines, routine is very simple, but here we - * go! */ - DBGWARN("Performing slow non-blas vector-vector multiplication"); - for(us i=0;i -#include - -#if LASP_USE_BLAS == 1 -#include - -#elif LASP_USE_BLAS == 0 -#else -#error "LASP_USE_BLAS should be set to either 0 or 1" -#endif - -#if LASP_DOUBLE_PRECISION == 1 -#define c_real creal -#define c_imag cimag -#define d_abs fabs -#define c_abs cabs -#define c_conj conj -#define d_atan2 atan2 -#define d_acos acos -#define d_sqrt sqrt -#define c_exp cexp -#define d_sin sin -#define d_cos cos -#define d_pow pow -#define d_log10 log10 -#define d_ln log -#define d_epsilon (DBL_EPSILON) - -#else // LASP_DOUBLE_PRECISION not defined -#define c_conj conjf -#define c_real crealf -#define c_imag cimagf -#define d_abs fabsf -#define c_abs cabsf -#define d_atan2 atan2f -#define d_acos acosf -#define d_sqrt sqrtf -#define c_exp cexpf -#define d_sin sinf -#define d_cos cosf -#define d_pow powf -#define d_log10 log10f -#define d_ln logf -#define d_epsilon (FLT_EPSILON) -#endif // LASP_DOUBLE_PRECISION - -/// Positive infinite -#define d_inf (INFINITY) - -#ifdef M_PI -static const d number_pi = M_PI; -#else -static const d number_pi = 3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679; -#endif - -/** - * Set all elements in an array equal to val - * - * @param to - * @param val - * @param size - */ -static inline void d_set(d to[],d val,us size) { - for(us i=0;ib?a:b; -} - - -/** - * Return the dot product of two arrays, one of them complex-valued, - * the other real-valued - * - * @param a the complex-valued array - * @param b the real-valued array - * @param size the size of the arrays. *Should be equal-sized!* - * - * @return the dot product - */ -static inline c cd_dot(const c a[],const d b[],us size){ - c result = 0; - us i; - for(i=0;i max) max=a[i]; - } - return max; -} -/** - * Compute the minimum of an array - * - * @param a array - * @param size size of the array - * @return minimum - */ -static inline d d_min(const d a[],us size){ - us i; - d min = a[0]; - for(i=1;i min) min=a[i]; - } - return min; -} - -/** - * Compute the \f$ L_2 \f$ norm of an array of doubles - * - * @param a Array - * @param size Size of array - */ -static inline d d_norm(const d a[],us size){ -#if LASP_USE_BLAS == 1 - return cblas_dnrm2(size,a,1); -#else - d norm = 0; - us i; - for(i=0;i - -/* #ifdef linux */ -#define _GNU_SOURCE -#include -#include -#ifndef MS_WIN64 -#include -#endif -/* #endif */ - -typedef struct { - void* job_ptr; - bool running; - bool ready; -} Job; - -typedef struct JobQueue_s { -#ifdef LASP_PARALLEL - pthread_mutex_t mutex; - pthread_cond_t cv_plus; /**< Condition variable for the - * "workers". */ - pthread_cond_t cv_minus; /**< Condition variable for the - * main thread. */ -#endif - Job* jobs; /**< Pointer to job vector */ - us max_jobs; /**< Stores the maximum number of - * items */ -} JobQueue; - -static us count_jobs(JobQueue* jq) { - fsTRACE(15); - us njobs = 0; - for(us i=0;imax_jobs;i++){ - if(jq->jobs[i].ready) - njobs++; - } - return njobs; -} -static Job* get_ready_job(JobQueue* jq) { - fsTRACE(15); - Job* j = jq->jobs; - for(us i=0;imax_jobs;i++){ - if(j->ready && !j->running) - return j; - j++; - } - return NULL; -} -void print_job_queue(JobQueue* jq) { - fsTRACE(15); - for(us i=0;imax_jobs;i++) { - printf("Job %zu", i); - if(jq->jobs[i].ready) - printf(" available"); - if(jq->jobs[i].running) - printf(" running"); - - printf(" - ptr %zu\n", (us) jq->jobs[i].job_ptr); - - } - feTRACE(15); -} - -#ifdef LASP_PARALLEL -#define LOCK_MUTEX \ - /* Lock the mutex to let the threads wait initially */ \ - int rv = pthread_mutex_lock(&jq->mutex); \ - if(rv !=0) { \ - WARN("Mutex lock failed"); \ - } -#define UNLOCK_MUTEX \ - rv = pthread_mutex_unlock(&jq->mutex); \ - if(rv !=0) { \ - WARN("Mutex unlock failed"); \ - } - -#else -#define LOCK_MUTEX -#define UNLOCK_MUTEX -#endif // LASP_PARALLEL - -JobQueue* JobQueue_alloc(const us max_jobs) { - TRACE(15,"JobQueue_alloc"); - /* if(max_jobs > LASP_MAX_NUM_CHANNELS) { */ - /* WARN("Max jobs restricted to LASP_MAX_NUM_CHANNELS"); */ - /* return NULL; */ - /* } */ - JobQueue* jq = a_malloc(sizeof(JobQueue)); - - - if(!jq) { - WARN("Allocation of JobQueue failed"); - return NULL; - } - jq->max_jobs = max_jobs; - - jq->jobs = a_malloc(max_jobs*sizeof(Job)); - if(!jq->jobs) { - WARN("Allocation of JobQueue jobs failed"); - return NULL; - } - - Job* j = jq->jobs; - for(us jindex=0;jindexjob_ptr = NULL; - j->ready = false; - j->running = false; - j++; - } - -#ifdef LASP_PARALLEL - /* Initialize thread mutex */ - int rv = pthread_mutex_init(&jq->mutex,NULL); - if(rv !=0) { - WARN("Mutex initialization failed"); - return NULL; - } - rv = pthread_cond_init(&jq->cv_plus,NULL); - if(rv !=0) { - WARN("Condition variable initialization failed"); - return NULL; - } - - rv = pthread_cond_init(&jq->cv_minus,NULL); - if(rv !=0) { - WARN("Condition variable initialization failed"); - return NULL; - } - -#endif // LASP_PARALLEL - /* print_job_queue(jq); */ - return jq; -} - -void JobQueue_free(JobQueue* jq) { - - TRACE(15,"JobQueue_free"); - dbgassert(jq,NULLPTRDEREF "jq in JobQueue_free"); - - int rv; - - if(count_jobs(jq) != 0) { - WARN("Job queue not empty!"); - } - - a_free(jq->jobs); - -#ifdef LASP_PARALLEL - /* Destroy the mutexes and condition variables */ - rv = pthread_mutex_destroy(&jq->mutex); - if(rv != 0){ - WARN("Mutex destroy failed. Do not know what to do."); - } - - rv = pthread_cond_destroy(&jq->cv_plus); - if(rv != 0){ - WARN("Condition variable destruction failed. " - "Do not know what to do."); - } - - rv = pthread_cond_destroy(&jq->cv_minus); - if(rv != 0){ - WARN("Condition variable destruction failed. " - "Do not know what to do."); - } -#endif // LASP_PARALLEL - -} - -int JobQueue_push(JobQueue* jq,void* job_ptr) { - - TRACE(15,"JobQueue_push"); - dbgassert(jq,NULLPTRDEREF "jq in JobQueue_push"); - - /* print_job_queue(jq); */ - /* uVARTRACE(15,(us) job_ptr); */ - - LOCK_MUTEX; - - us max_jobs = jq->max_jobs; - -#ifdef LASP_PARALLEL - /* Check if queue is full */ - while(count_jobs(jq) == max_jobs) { - - WARN("Queue full. Wait until some jobs are done."); - rv = pthread_cond_wait(&jq->cv_minus,&jq->mutex); - if(rv !=0) { - WARN("Condition variable wait failed"); - } - } -#else - /* If job queue is full, not in parallel, we just fail to add something - * without waiting*/ - if(count_jobs(jq) == max_jobs) { - return LASP_FAILURE; - } -#endif // LASP_PARALLEL - - dbgassert(count_jobs(jq) != max_jobs, - "Queue cannot be full!"); - - /* Queue is not full try to find a place, fill it */ - Job* j = jq->jobs; - us i; - for(i=0;iready == false ) { - dbgassert(j->job_ptr==NULL,"Job ptr should be 0"); - dbgassert(j->ready==false,"Job cannot be assigned"); - break; - } - j++; - } - dbgassert(i!=jq->max_jobs,"Should have found a job!"); - - j->job_ptr = job_ptr; - j->ready = true; - -#ifdef LASP_PARALLEL - /* Notify worker threads that a new job has arrived */ - if(count_jobs(jq) == max_jobs) { - /* Notify ALL threads. Action required! */ - rv = pthread_cond_broadcast(&jq->cv_plus); - if(rv !=0) { - WARN("Condition variable broadcast failed"); - } - - } else { - /* Notify some thread that there has been some change to - * the Queue */ - rv = pthread_cond_signal(&jq->cv_plus); - if(rv !=0) { - WARN("Condition variable signal failed"); - } - } -#endif // LASP_PARALLEL - - /* print_job_queue(jq); */ - - UNLOCK_MUTEX; - - return LASP_SUCCESS; -} -void* JobQueue_assign(JobQueue* jq) { - - TRACE(15,"JobQueue_assign"); - - LOCK_MUTEX; - - Job* j; -#ifdef LASP_PARALLEL - /* Wait until a job is available */ - while ((j=get_ready_job(jq))==NULL) { - - TRACE(15,"JobQueue_assign: no ready job"); - pthread_cond_wait(&jq->cv_plus,&jq->mutex); - - } -#else - if(count_jobs(jq) == 0) { return NULL; } - else { j = get_ready_job(jq); } -#endif // LASP_PARALLEL - - TRACE(16,"JobQueue_assign: found ready job. Assigned to:"); -#ifdef LASP_DEBUG -#ifdef LASP_PARALLEL - pthread_t thisthread = pthread_self(); - iVARTRACE(16,thisthread); -#endif -#endif - - /* print_job_queue(jq); */ - /* Find a job from the queue, assign it and return it */ - j->running = true; - -#ifdef LASP_PARALLEL - if(count_jobs(jq) > 1) { - /* Signal different thread that there is more work to do */ - rv = pthread_cond_signal(&jq->cv_plus); - if(rv !=0) { - WARN("Condition variable broadcast failed"); - } - } -#endif - - UNLOCK_MUTEX; - - TRACE(15,"End JobQueue_assign"); - - return j->job_ptr; -} -void JobQueue_done(JobQueue* jq,void* job_ptr) { - - TRACE(15,"JobQueue_done"); - dbgassert(jq,NULLPTRDEREF "jq in JobQueue_done"); - - LOCK_MUTEX; - - /* print_job_queue(jq); */ - - /* Find the job from the queue, belonging to the job_ptr */ - Job* j=jq->jobs; - us i; - for(i=0;imax_jobs;i++) { - iVARTRACE(10,i); - if(j->ready && j->running && j->job_ptr == job_ptr) { - TRACE(15,"Found the job that has been done:"); - j->ready = false; - j->job_ptr = NULL; - j->running = false; - break; - } - j++; - } - - /* print_job_queue(jq); */ - -#ifdef LASP_PARALLEL - /* Job done, broadcast this */ - rv = pthread_cond_signal(&jq->cv_minus); - if(rv !=0) { - WARN("Condition variable broadcast failed"); - } -#endif - - UNLOCK_MUTEX; -} - -#ifdef LASP_PARALLEL -void JobQueue_wait_alldone(JobQueue* jq) { - TRACE(15,"JobQueue_wait_alldone"); - dbgassert(jq,NULLPTRDEREF "jq in JobQueue_wait_alldone"); - - LOCK_MUTEX; - - /* Wait until number of jobs is 0 */ - while (count_jobs(jq)!=0) { - - if(rv !=0) { - WARN("Condition variable broadcast failed"); - } - - pthread_cond_wait(&jq->cv_minus,&jq->mutex); - } - - UNLOCK_MUTEX; - -} -#endif - - -////////////////////////////////////////////////////////////////////// diff --git a/lasp/c/lasp_mq.h b/lasp/c/lasp_mq.h deleted file mode 100644 index e183229..0000000 --- a/lasp/c/lasp_mq.h +++ /dev/null @@ -1,78 +0,0 @@ -// mq.h -// -// Author: J.A. de Jong - ASCEE -// -// Description: -// Multithreaded job queue implementation -////////////////////////////////////////////////////////////////////// -#pragma once -#ifndef MQ_H -#define MQ_H -#include "lasp_types.h" - -typedef struct JobQueue_s JobQueue; - -/** - * Allocate a new job queue. - * - * @param max_msg Maximum number of jobs that can be put in the - * queue. - * - * @return Pointer to new JobQueue instance. NULL on error. - */ -JobQueue* JobQueue_alloc(const us max_msg); - -/** - * Free an existing job queue. If it is not empty and threads are - * still waiting for jobs, the behaviour is undefined. So please - * make sure all threads are done before free'ing the queue. - * - * @param jq: JobQueue to free - */ -void JobQueue_free(JobQueue* jq); - -/** - * Pops a job from the queue. Waits indefinitely until some job is - * available. If in parallel mode. In serial mode, it returns NULL if no job - * is available (LASP_PARALLEL compilation flag not set). - * - * @param jq: JobQueue handle - * @return Pointer to the job, NULL on error. - */ -void* JobQueue_assign(JobQueue* jq); - -/** - * Tell the queue the job that has been popped is done. Only after - * this function call, the job is really removed from the queue. - * - * @param jq: JobQueue handle - * @param job - */ -void JobQueue_done(JobQueue* jq,void* job); - -/** - * A push on the job queue will notify one a single thread that is - * blocked waiting in the JobQueue_assign() function. If the job - * queue is full, however all waiters will be signaled and the - * function will block until there is some space in the job queue. - * - * @param jp JobQueue - * @param job_ptr Pointer to job to be done - * @return 0 on success. - */ -int JobQueue_push(JobQueue* jq,void* job_ptr); - -/** - * Wait until the job queue is empty. Please use this function with - * caution, as it will block indefinitely in case the queue never gets - * empty. The purpose of this function is to let the main thread wait - * until all task workers are finished. - * - */ - -#ifdef LASP_PARALLEL -void JobQueue_wait_alldone(JobQueue*); -#endif // LASP_PARALLEL - -#endif // MQ_H -////////////////////////////////////////////////////////////////////// diff --git a/lasp/c/lasp_nprocs.c b/lasp/c/lasp_nprocs.c deleted file mode 100644 index 4467499..0000000 --- a/lasp/c/lasp_nprocs.c +++ /dev/null @@ -1,18 +0,0 @@ -#include "lasp_nprocs.h" -#ifdef MS_WIN64 -#include -#else -// Used for obtaining the number of processors -#include -#endif - -us getNumberOfProcs() { -#if MS_WIN64 -// https://stackoverflow.com/questions/150355/programmatically-find-the-number-of-cores-on-a-machine - SYSTEM_INFO sysinfo; - GetSystemInfo(&sysinfo); - return sysinfo.dwNumberOfProcessors; -#else // Linux, easy - return get_nprocs(); -#endif -} diff --git a/lasp/c/lasp_nprocs.h b/lasp/c/lasp_nprocs.h deleted file mode 100644 index 7ba5458..0000000 --- a/lasp/c/lasp_nprocs.h +++ /dev/null @@ -1,18 +0,0 @@ -// lasp_nprocs.h -// -// Author: J.A. de Jong - ASCEE -// -// Description: Implemententation of a function to determine the number -// of processors. -////////////////////////////////////////////////////////////////////// -#pragma once -#ifndef LASP_NPROCS_H -#define LASP_NPROCS_H -#include "lasp_types.h" - -/** - * @return The number of SMP processors - */ -us getNumberOfProcs(); - -#endif // LASP_NPROCS_H \ No newline at end of file diff --git a/lasp/c/lasp_ps.c b/lasp/c/lasp_ps.c deleted file mode 100644 index c8e664d..0000000 --- a/lasp/c/lasp_ps.c +++ /dev/null @@ -1,181 +0,0 @@ -// lasp_ps.c -// -// Author: J.A. de Jong -ASCEE -// -// Description: -// -////////////////////////////////////////////////////////////////////// -#define TRACERPLUS (-5) -#include "lasp_ps.h" -#include "lasp_fft.h" -#include "lasp_alloc.h" -#include "lasp_alg.h" -#include "lasp_assert.h" - -typedef struct PowerSpectra_s { - - vd window; - d win_pow; /**< The power of the window */ - Fft* fft; /**< Fft routines storage */ -} PowerSpectra; - -PowerSpectra* PowerSpectra_alloc(const us nfft, - const WindowType wt) { - - fsTRACE(15); - int rv; - - /* Check nfft */ - if(nfft % 2 != 0) { - WARN("nfft should be even"); - return NULL; - } - - /* ALlocate space */ - Fft* fft = Fft_create(nfft); - if(fft == NULL) { - WARN("Fft allocation failed"); - return NULL; - } - - PowerSpectra* ps = a_malloc(sizeof(PowerSpectra)); - if(!ps) { - WARN("Allocation of PowerSpectra memory failed"); - Fft_free(fft); - return NULL; - } - ps->fft = fft; - - /* Allocate vectors and matrices */ - ps->window = vd_alloc(nfft); - - rv = window_create(wt,&ps->window,&ps->win_pow); - check_overflow_vx(ps->window); - if(rv!=0) { - WARN("Error creating window function, continuing anyway"); - } - feTRACE(15); - return ps; -} - -void PowerSpectra_free(PowerSpectra* ps) { - fsTRACE(15); - Fft_free(ps->fft); - vd_free(&ps->window); - a_free(ps); - feTRACE(15); -} - - -void PowerSpectra_compute(const PowerSpectra* ps, - const dmat * timedata, - cmat * result) { - - fsTRACE(15); - - dbgassert(ps && timedata && result,NULLPTRDEREF); - - const us nchannels = timedata->n_cols; - const us nfft = Fft_nfft(ps->fft); - uVARTRACE(15,nchannels); - const d win_pow = ps->win_pow; - dVARTRACE(15,win_pow); - - /* Sanity checks for the matrices */ - dbgassert(timedata->n_rows == nfft,"timedata n_rows " - "should be equal to nfft"); - - dbgassert(result->n_rows == nfft/2+1,"result n_rows " - "should be equal to nfft/2+1"); - - dbgassert(result->n_cols == nchannels*nchannels,"result n_cols " - "should be equal to nchannels*nchannels"); - - - /* Multiply time data with the window and store result in - * timedata_work. */ - dmat timedata_work = dmat_alloc(nfft,nchannels); - for(us i=0;iwindow); - - vd_free(&column); - vd_free(&column_work); - } - check_overflow_xmat(timedata_work); - /* print_dmat(&timedata_work); */ - - /* Compute fft of the time data */ - cmat fft_work = cmat_alloc(nfft/2+1,nchannels); - Fft_fft(ps->fft, - &timedata_work, - &fft_work); - - dmat_free(&timedata_work); - - TRACE(15,"fft done"); - - /* Scale fft such that power is easily comxputed */ - const c scale_fac = d_sqrt(2/win_pow)/nfft; - - /* POWER SPECTRAL DENSITY? Scale fft such that power is easily computed - * - Multiply power spectral density by 2 except at f=0 and f=fNq - * - Divide by energy of window function = nfft * window_power - * - .. sqrt(factors) because it is applied to output fft instead of psd */ - // const c scale_fac = d_sqrt(2/(nfft*win_pow)); - - cmat_scale(&fft_work,scale_fac); - TRACE(15,"scale done"); - - for(us i=0;i< nchannels;i++) { - /* Multiply DC term by 1/sqrt(2) */ - *getcmatval(&fft_work,0,i) *= 1/d_sqrt(2.)+0*I; - - /* Multiply Nyquist term by 1/sqrt(2) */ - *getcmatval(&fft_work,nfft/2,i) *= 1/d_sqrt(2.)+0*I; - } - check_overflow_xmat(fft_work); - - /* print_cmat(&fft_work); */ - TRACE(15,"Nyquist and DC correction done"); - - vc j_vec_conj = vc_alloc(nfft/2+1); - - /* Compute Cross-power spectra and store result */ - for(us i =0; i -#include - -#if LASP_DOUBLE_PRECISION == 1 -#define LASP_NUMPY_FLOAT_TYPE NPY_FLOAT64 -#define LASP_NUMPY_COMPLEX_TYPE NPY_COMPLEX128 -#else -#define LASP_NUMPY_FLOAT_TYPE NPY_FLOAT32 -#endif -#include - -/** - * Function passed to Python to use for cleanup of - * foreignly obtained data. - **/ -#define LASP_CAPSULE_NAME "pyarray_data_destructor" -static inline void capsule_cleanup(PyObject *capsule) { - void *memory = PyCapsule_GetPointer(capsule, LASP_CAPSULE_NAME); - free(memory); -} - -/** - * Create a numpy array from a raw data pointer. - * - * @param data pointer to data, assumes d* for data - * @param transfer_ownership If set to true, the created Numpy array will be - * responsible for freeing the data. - * @param F_contiguous It set to true, the data is assumed to be column-major - * ordered in memory (which means we can find element d[r,c] by d[n_cols*r+c], - * if set to false. Data is assumed to be row-major ordered (which means we - * find element d[r,c] by d[n_rows*c+r] - * - * @return Numpy array - */ -static inline PyObject *data_to_ndarray(void *data, int n_rows, int n_cols, - int typenum, bool transfer_ownership, - bool F_contiguous) { - - /* fprintf(stderr, "Enter data_to_ndarray\n"); */ - assert(data); - - PyArray_Descr *descr = PyArray_DescrFromType(typenum); - if (!descr) - return NULL; - - npy_intp dims[2] = {n_rows, n_cols}; - npy_intp strides[2]; - - int flags = 0; - if (F_contiguous) { - flags |= NPY_ARRAY_FARRAY; - strides[0] = descr->elsize; - strides[1] = descr->elsize * n_rows; - } else { - strides[0] = descr->elsize * n_rows; - strides[1] = descr->elsize; - } - - PyArrayObject *arr = - (PyArrayObject *)PyArray_NewFromDescr(&PyArray_Type, - descr, // Description - 2, // nd - dims, // dimensions - strides, // strides - data, // Data pointer - flags, // Flags - NULL // obj - ); - - if (!arr) { - fprintf(stderr, "arr = 0!"); - return NULL; - } - - if (transfer_ownership) { - // The default destructor of Python cannot free the data, as it is allocated - // with malloc. Therefore, with this code, we tell Numpy/Python to use - // the capsule_cleanup constructor. See: - // https://stackoverflow.com/questions/54269956/crash-of-jupyter-due-to-the-use-of-pyarray-enableflags/54278170#54278170 - // Note that in general it was disadvised to build all C code with MinGW on - // Windows. We do it anyway, see if we find any problems on the way. - /* PyObject *capsule = PyCapsule_New(data, LASP_CAPSULE_NAME, capsule_cleanup); */ - /* int res = PyArray_SetBaseObject(arr, capsule); */ - /* if (res != 0) { */ - /* fprintf(stderr, "Failed to set base object of array!"); */ - /* return NULL; */ - /* } */ - - PyArray_ENABLEFLAGS(arr, NPY_OWNDATA); - } - - return (PyObject *)arr; -} - -#undef LASP_CAPSULE_NAME -#endif // LASP_PYARRAY_H diff --git a/lasp/c/lasp_python.h b/lasp/c/lasp_python.h deleted file mode 100644 index 27dc8d9..0000000 --- a/lasp/c/lasp_python.h +++ /dev/null @@ -1,59 +0,0 @@ -// ascee_python.h -// -// Author: J.A. de Jong - ASCEE - Redu-Sone -// -// Description: -// Some routines to generate numpy arrays from matrices and vectors. -////////////////////////////////////////////////////////////////////// -#pragma once -#ifndef LASP_PYTHON_H -#define LASP_PYTHON_H - -#ifdef __cplusplus -#error "Cannot compile this file with C++" -#endif -#include "lasp_types.h" -#include "lasp_mat.h" -#include "lasp_pyarray.h" - -/** - * @brief Create a Numpy array from a dmat structure - * - * @param mat Pointer to the dmat - * @param transfer_ownership If set to true, the created Numpy array will - * obtain ownership of the data and calls free() on destruction. The dmat will - * have the foreign_data flag set, such that it won't free the data. - * - * @return - */ -static inline PyObject* dmat_to_ndarray(dmat* mat,bool transfer_ownership) { - dbgassert(mat,NULLPTRDEREF); - dbgassert(mat->_data,NULLPTRDEREF); - /* fprintf(stderr, "Enter dmat_to_ndarray\n"); */ - - // Dimensions given in wrong order, as mat is - // Fortran-contiguous. Later on we transpose the result. This is - // more easy than using the PyArray_New syntax. - PyObject* arr = data_to_ndarray(mat->_data, - mat->n_rows, - mat->n_cols, - LASP_NUMPY_FLOAT_TYPE, - transfer_ownership, - true); // Fortran-contiguous - - if(transfer_ownership) { - mat->_foreign_data = true; - } - - if(!arr) { - WARN("Array creation failure"); - feTRACE(15); - return NULL; - } - /* fprintf(stderr, "Exit dmat_to_ndarray\n"); */ - - return arr; -} - -#endif // LASP_PYTHON_H -////////////////////////////////////////////////////////////////////// diff --git a/lasp/c/lasp_siggen.c b/lasp/c/lasp_siggen.c deleted file mode 100644 index 702a527..0000000 --- a/lasp/c/lasp_siggen.c +++ /dev/null @@ -1,472 +0,0 @@ -// lasp_siggen.c -// -// Author: J.A. de Jong -ASCEE -// -// Description: -// Signal generator implementation -////////////////////////////////////////////////////////////////////// -/* #define TRACERPLUS (-5) */ -#include "lasp_siggen.h" -#include "lasp_alloc.h" -#include "lasp_assert.h" -#include "lasp_mat.h" - -/** The fixed number of Newton iterations t.b.d. for tuning the sweep start and - * stop frequency in logarithmic sweeps */ -#define NITER_NEWTON 20 - -/** The number of Bytes of space for the signal-specific data in the Siggen - * structure */ -#define PRIVATE_SIZE 64 - -typedef enum { - SINEWAVE = 0, - NOISE, - SWEEP, -} SignalType; - -typedef struct Siggen { - SignalType signaltype; - d fs; // Sampling frequency [Hz] - d level_amp; - char private_data[PRIVATE_SIZE]; -} Siggen; - -typedef struct { - d curtime; - d omg; -} SinewaveSpecific; - -typedef struct { - us N; - vd data; - us index; -} PeriodicSpecific; - -typedef struct { - d V1, V2, S; - int phase; - Sosfilterbank* colorfilter; -} NoiseSpecific; - -static d level_amp(d level_dB){ - return pow(10, level_dB/20); -} - -Siggen* Siggen_create(SignalType type, const d fs,const d level_dB) { - - fsTRACE(15); - - Siggen* siggen = a_malloc(sizeof(Siggen)); - siggen->signaltype = type; - siggen->fs = fs; - siggen->level_amp = level_amp(level_dB); - - feTRACE(15); - return siggen; -} - -Siggen* Siggen_Sinewave_create(const d fs, const d freq,const d level_dB) { - fsTRACE(15); - - Siggen* sine = Siggen_create(SINEWAVE, fs, level_dB); - dbgassert(sizeof(SinewaveSpecific) <= sizeof(sine->private_data), - "Allocated memory too small"); - SinewaveSpecific* sp = (SinewaveSpecific*) sine->private_data; - sp->curtime = 0; - sp->omg = 2*number_pi*freq; - - feTRACE(15); - return sine; -} - -Siggen* Siggen_Noise_create(const d fs, const d level_dB, Sosfilterbank* colorfilter) { - fsTRACE(15); - - Siggen* noise = Siggen_create(NOISE, fs, level_dB); - dbgassert(sizeof(NoiseSpecific) <= sizeof(noise->private_data), - "Allocated memory too small"); - NoiseSpecific* wn = (NoiseSpecific*) noise->private_data; - wn->phase = 0; - wn->V1 = 0; - wn->V2 = 0; - wn->S = 0; - wn->colorfilter = colorfilter; - - feTRACE(15); - return noise; -} - -Siggen* Siggen_Sweep_create(const d fs,const d fl_,const d fu_, - const d Ts,const d Tq, const us flags, const d level_dB) { - fsTRACE(15); - - Siggen* sweep = Siggen_create(SWEEP, fs, level_dB); - dbgassert(sizeof(PeriodicSpecific) <= sizeof(sweep->private_data), - "Allocated memory too small"); - - bool forward_sweep = flags & SWEEP_FLAG_FORWARD; - bool backward_sweep = flags & SWEEP_FLAG_BACKWARD; - - // Set pointer to inplace data storage - dbgassert(!(forward_sweep && backward_sweep), "Both forward and backward flag set"); - - PeriodicSpecific* sp = (PeriodicSpecific*) sweep->private_data; - if(fl_ < 0 || fu_ < 0 || Ts <= 0) { - return NULL; - } - - - const d Dt = 1/fs; // Deltat - - // Estimate N, the number of samples in the sweep part (non-quiescent part): - const us N = (us) (Ts*fs); - const us Nq = (us) (Tq*fs); - iVARTRACE(15, N); - sp->data = vd_alloc(N+Nq); - vd* data = &(sp->data); - /* Set the last part, the quiescent tail to zero */ - dmat_set(data,0.0); - - sp->N = N+Nq; - sp->index = 0; - - // Obtain flags and expand - d phase = 0; - d fl, fu; - - /* Swap fl and fu for a backward sweep */ - if(backward_sweep) { - fu = fl_; - fl = fu_; - } - else { - /* Case of continuous sweep, or forward sweep */ - fl = fl_; - fu = fu_; - } - - /* Linear sweep */ - if(flags & SWEEP_FLAG_LINEAR) { - TRACE(15, "linear sweep"); - - if(forward_sweep || backward_sweep) { - /* Forward or backward sweep */ - TRACE(15, "Forward or backward sweep"); - us K = (us) (Dt*(fl*N+0.5*(N-1)*(fu-fl))); - d eps_num = ((d) K)/Dt - fl*N-0.5*(N-1)*(fu-fl); - d eps = eps_num/(0.5*(N-1)); - iVARTRACE(15, K); - dVARTRACE(15, eps); - - for(us n = 0; n= 0, "BUG"); - - phase += 2*number_pi*Dt*fn; - /* dVARTRACE(17, phase); */ - /* setvecval(data, n, fn); */ - /* setvecval(data, n, phase); */ - } - /* This should be a very small number!! */ - dVARTRACE(15, phase); - } - - } - else if(flags & SWEEP_FLAG_EXPONENTIAL) { - - TRACE(15, "exponential sweep"); - if(forward_sweep || backward_sweep) { - /* Forward or backward sweep */ - TRACE(15, "Forward or backward sweep"); - d k1 = (fu/fl); - us K = (us) (Dt*fl*(k1-1)/(d_pow(k1,1.0/N)-1)); - d k = k1; - - /* Iterate k to the right solution */ - d E; - for(us iter=0;iter< 10; iter++) { - E = 1 + K/(Dt*fl)*(d_pow(k,1.0/N)-1) - k; - d dEdk = K/(Dt*fl)*d_pow(k,1.0/N)/(N*k)-1; - k -= E/dEdk; - } - - iVARTRACE(15, K); - dVARTRACE(15, k1); - dVARTRACE(15, k); - dVARTRACE(15, E); - - for(us n = 0; n= 0, "BUG"); - - phase += 2*number_pi*Dt*fn; - while(phase > 2*number_pi) phase -= 2*number_pi; - /* dVARTRACE(17, phase); */ - /* setvecval(data, n, fn); */ - /* setvecval(data, n, phase); */ - } - /* This should be a very small number!! */ - dVARTRACE(15, phase); - - } - } - - feTRACE(15); - return sweep; -} - -static void Siggen_periodic_free(PeriodicSpecific* ps) { - assertvalidptr(ps); - fsTRACE(15); - vd_free(&(ps->data)); - feTRACE(15); -} -us Siggen_getN(const Siggen* siggen) { - fsTRACE(15); - assertvalidptr(siggen); - - switch(siggen->signaltype) { - case SINEWAVE: - break; - case NOISE: - break; - case SWEEP: - return ((PeriodicSpecific*) siggen->private_data)->N; - break; - default: - dbgassert(false, "Not implementend signal type"); - - } - - feTRACE(15); - return 0; -} -void Siggen_setLevel(Siggen* siggen, const d new_level_dB) { - fsTRACE(15); - - siggen->level_amp = d_pow(10, new_level_dB/20); - - feTRACE(15); -} - -void Siggen_free(Siggen* siggen) { - fsTRACE(15); - assertvalidptr(siggen); - NoiseSpecific* sp; - - switch(siggen->signaltype) { - case SWEEP: - /* Sweep specific stuff here */ - Siggen_periodic_free((PeriodicSpecific*) siggen->private_data); - break; - case SINEWAVE: - /* Sweep specific stuff here */ - break; - case NOISE: - sp = (NoiseSpecific*) siggen->private_data; - if(sp->colorfilter) { - Sosfilterbank_free(sp->colorfilter); - } - - } - - a_free(siggen); - feTRACE(15); -} - -static void Sinewave_genSignal(Siggen* siggen, SinewaveSpecific* sine, vd* samples) { - fsTRACE(10); - assertvalidptr(sine); - d ts = 1/siggen->fs; - d omg = sine->omg; - - d curtime = sine->curtime; - for(us i =0; i< samples->n_rows; i++) { - setvecval(samples, i, siggen->level_amp*sin(omg*curtime)); - curtime = curtime + ts; - } - sine->curtime = curtime; - feTRACE(10); -} - -static void Periodic_genSignal(Siggen* siggen, PeriodicSpecific* sweep, vd* samples) { - fsTRACE(10); - - for(us i=0; in_rows; i++) { - d* data = getvdval(&(sweep->data), sweep->index); - setvecval(samples, i, siggen->level_amp*(*data)); - sweep->index++; - sweep->index %= sweep->N; - } - - feTRACE(10); -} - - -static void noise_genSignal(Siggen* siggen, NoiseSpecific* wn, vd* samples) { - fsTRACE(10); - d X; - d S = wn->S; - d V1 = wn->V1; - d V2 = wn->V2; - - int phase = wn->phase; - - for(us i =0; i< samples->n_rows; i++) { - - if(wn->phase == 0) { - do { - d U1 = (d)rand() / RAND_MAX; - d U2 = (d)rand() / RAND_MAX; - - V1 = 2 * U1 - 1; - V2 = 2 * U2 - 1; - S = V1 * V1 + V2 * V2; - } while(S >= 1 || S == 0); - - X = V1 * sqrt(-2 * d_ln(S) / S); - } else - X = V2 * sqrt(-2 * d_ln(S) / S); - - phase = 1 - phase; - - setvecval(samples, i, siggen->level_amp*X); - } - if(wn->colorfilter){ - vd filtered = Sosfilterbank_filter(wn->colorfilter, - samples); - dmat_copy(samples, &filtered); - vd_free(&filtered); - } - wn->S = S; - wn->V1 = V1; - wn->V2 = V2; - wn->phase = phase; - feTRACE(10); -} - -void Siggen_genSignal(Siggen* siggen,vd* samples) { - - fsTRACE(10); - assertvalidptr(siggen); - assert_vx(samples); - - switch(siggen->signaltype) { - case SINEWAVE: - Sinewave_genSignal(siggen, - (SinewaveSpecific*) siggen->private_data, - samples); - - break; - case NOISE: - noise_genSignal(siggen, - (NoiseSpecific*) siggen->private_data, - samples); - break; - case SWEEP: - Periodic_genSignal(siggen, - (PeriodicSpecific*) siggen->private_data, - samples); - break; - default: - dbgassert(false, "Not implementend signal type"); - - } - - feTRACE(10); -} - - - -////////////////////////////////////////////////////////////////////// diff --git a/lasp/c/lasp_siggen.h b/lasp/c/lasp_siggen.h deleted file mode 100644 index 5be6cee..0000000 --- a/lasp/c/lasp_siggen.h +++ /dev/null @@ -1,100 +0,0 @@ -// lasp_siggen.h -// -// Author: J.A. de Jong - ASCEE -// -// Description: -// Header file for signal generation routines -// -////////////////////////////////////////////////////////////////////// -#pragma once -#ifndef LASP_SIGGEN_H -#define LASP_SIGGEN_H -#include "lasp_mat.h" -#include "lasp_sosfilterbank.h" - -// Define this flag to repeat a forward sweep only, or backward only. If not -// set, we do a continuous sweep -#define SWEEP_FLAG_FORWARD 1 -#define SWEEP_FLAG_BACKWARD 2 - -// Types of sweeps -#define SWEEP_FLAG_LINEAR 4 -#define SWEEP_FLAG_EXPONENTIAL 8 - -typedef struct Siggen Siggen; - -/** - * Create a sine wave signal generator - * - * @param[in] fs: Sampling frequency [Hz] - * @param[in] level: Relative level in [dB], should be between -inf and 0 - * @param[freq] Sine wave frequency [Hz] - */ -Siggen* Siggen_Sinewave_create(const d fs,const d freq,const d level_dB); - -/** - * Create a Noise signal generator. If no Sosfilterbank is provided, it will - * create white noise. Otherwise, the noise is 'colored' using the filterbank - * given in the constructor. Note that the pointer to this filterbank is - * *STOLEN*!. - * - * @param[in] fs: Sampling frequency [Hz] - * @param[in] level_dB: Relative level [dB] - * @param[in] - * - * @return Siggen* handle - */ -Siggen* Siggen_Noise_create(const d fs, const d level_dB, Sosfilterbank* colorfilter); - -/** - * Set the level of the signal generator - * @param[in] Siggen* Signal generator handle - * - * @param[in] new_level_dB The new level, in dBFS - */ -void Siggen_setLevel(Siggen*, const d new_level_dB); - - -/** - * Obtain the repetition period for a periodic excitation. - * @param[in] Siggen* Signal generator handle - * - * @param[out] N The amount of samples in one period, returns 0 if the signal - * does not repeat. - */ -us Siggen_getN(const Siggen*); - -/** - * Create a forward sweep - * - * @param[in] fs: Sampling frequency [Hz] - * @param[in] fl: Lower frequency [Hz] - * @param[in] fl: Upper frequency [Hz] - * @param[in] Ts: Sweep time [s] - * @param[in] Tq: Quescent tail time [s]. Choose this value long enough to - * avoid temporal aliasing in case of measuring impulse responses. - * @param[in] sweep_flags: Sweep period [s] - * @param[in] level: Relative level in [dB], should be between -inf and 0 - * @return Siggen* handle - */ -Siggen* Siggen_Sweep_create(const d fs,const d fl,const d fu, - const d Ts, const d Tq, const us sweep_flags, - const d level); - -/** - * Obtain a new piece of signal - * - * @param[in] Siggen* Signal generator handle - * @param[out] samples Samples to fill. Vector should be pre-allocated, but - * values will be overwritten. - */ -void Siggen_genSignal(Siggen*,vd* samples); -/** - * Free Siggen data - * - * @param[in] Siggen* Signal generator private data - */ -void Siggen_free(Siggen*); - -#endif //LASP_SIGGEN_H -////////////////////////////////////////////////////////////////////// diff --git a/lasp/c/lasp_signals.h b/lasp/c/lasp_signals.h deleted file mode 100644 index 71205f0..0000000 --- a/lasp/c/lasp_signals.h +++ /dev/null @@ -1,32 +0,0 @@ -// lasp_signals.h -// -// Author: J.A. de Jong - ASCEE -// -// Description: -// Several signal functions -////////////////////////////////////////////////////////////////////// -#pragma once -#ifndef LASP_SIGNALS_H -#define LASP_SIGNALS_H -#include "lasp_mat.h" - -/** - * Compute the signal power, that is \f$ \frac{1}{N} \sum_{i=0}^{N-1} - * v_i^2 \f$ - * - * @param[in] signal Signal to compute the power of. - * @return the signal power - */ -static inline d signal_power(vd* signal) { - d res = 0; - for(us i=0;in_rows;i++) { - res+= d_pow(*getvdval(signal,i),2); - } - res /= signal->n_rows; - return res; -} - - - -#endif // LASP_SIGNALS_H -////////////////////////////////////////////////////////////////////// diff --git a/lasp/c/lasp_slm.c b/lasp/c/lasp_slm.c deleted file mode 100644 index ccbdb82..0000000 --- a/lasp/c/lasp_slm.c +++ /dev/null @@ -1,293 +0,0 @@ -#define TRACERPLUS (-5) -#include "lasp_slm.h" -#include "lasp_assert.h" -#include "lasp_tracer.h" - -typedef struct Slm { - Sosfilterbank *prefilter; /// Pre-filter, A, or C. If NULL, not used. - Sosfilterbank *bandpass; /// Filterbank. If NULL, not used - Sosfilterbank **splowpass; /// Used for time-weighting of the squared signal - d ref_level; /// Reference value for computing decibels - us downsampling_fac; /// Every x'th sample is returned. - us cur_offset; /// Storage for offset point in input arrays - vd Pm; /// Storage for the computing the mean of the square of the signal. - vd Pmax; /// Storage for maximum computed signal power so far. - vd Ppeak; /// Storage for computing peak powers so far. - us N; /// Counter for the number of time samples counted that came in - -} Slm; - -Slm *Slm_create(Sosfilterbank *prefilter, Sosfilterbank *bandpass, const d fs, - const d tau, const d ref_level, us *downsampling_fac) { - fsTRACE(15); - assertvalidptr(downsampling_fac); - - Slm *slm = NULL; - if (ref_level <= 0) { - WARN("Invalid reference level"); - return NULL; - } else if (fs <= 0) { - WARN("Invalid sampling frequency"); - return NULL; - } - - slm = (Slm *)a_malloc(sizeof(Slm)); - slm->ref_level = ref_level; - slm->prefilter = prefilter; - slm->bandpass = bandpass; - - /// Compute the downsampling factor. This one is chosen based on the - /// lowpass filter. Which has a -3 dB point of f = 1/(tau*2*pi). See LASP - /// documentation for the computation of its minus 20 dB point. We set the - /// reduction in its 'sampling frequency' such that its noise is at a level - /// of 20 dB less than its 'signal'. - us ds_fac; - if (tau > 0) { - // A reasonable 'framerate' for the sound level meter, based on the - // filtering time constant. - d fs_slm = 10 / tau; - dVARTRACE(15, fs_slm); - if(fs_slm < 30) { - fs_slm = 30; - } - ds_fac = (us)(fs / fs_slm); - if (ds_fac == 0) { - // If we get 0, it should be 1 - ds_fac++; - } - } else { - ds_fac = 1; - } - slm->downsampling_fac = ds_fac; - *downsampling_fac = ds_fac; - slm->cur_offset = 0; - - /// Create the single pole lowpass - us filterbank_size; - if (bandpass) { - filterbank_size = Sosfilterbank_getFilterbankSize(bandpass); - } else { - filterbank_size = 1; - } - - if (tau > 0) { - - vd lowpass_sos = vd_alloc(6); - d b0 = 1.0 / (1 + 2 * tau * fs); - *getvdval(&lowpass_sos, 0) = b0; - *getvdval(&lowpass_sos, 1) = b0; - *getvdval(&lowpass_sos, 2) = 0; - *getvdval(&lowpass_sos, 3) = 1; - *getvdval(&lowpass_sos, 4) = (1 - 2 * tau * fs) * b0; - *getvdval(&lowpass_sos, 5) = 0; - - slm->splowpass = a_malloc(filterbank_size * sizeof(Sosfilterbank *)); - for (us ch = 0; ch < filterbank_size; ch++) { - /// Allocate a filterbank with one channel and one section. - slm->splowpass[ch] = Sosfilterbank_create(0, 1, 1); - Sosfilterbank_setFilter(slm->splowpass[ch], 0, lowpass_sos); - } - vd_free(&lowpass_sos); - } else { - /// No low-pass filtering. Tau set to zero - slm->splowpass = NULL; - } - - /// Initialize statistics gatherers - slm->Ppeak = vd_alloc(filterbank_size); - slm->Pmax = vd_alloc(filterbank_size); - slm->Pm = vd_alloc(filterbank_size); - slm->N = 0; - vd_set(&(slm->Ppeak), 0); - vd_set(&(slm->Pmax), 0); - vd_set(&(slm->Pm), 0); - feTRACE(15); - return slm; -} - -dmat Slm_run(Slm *slm, vd *input_data) { - fsTRACE(15); - assertvalidptr(slm); - assert_vx(input_data); - - /// First step: run the input data through the pre-filter - vd prefiltered; - if (slm->prefilter) - prefiltered = Sosfilterbank_filter(slm->prefilter, input_data); - else { - prefiltered = dmat_foreign(input_data); - } - dmat bandpassed; - if (slm->bandpass) { - bandpassed = Sosfilterbank_filter(slm->bandpass, &prefiltered); - } else { - bandpassed = dmat_foreign(&prefiltered); - } - us filterbank_size = bandpassed.n_cols; - - /// Next step: square all values. We do this in-place. Then we filter for - /// each channel. - d ref_level = slm->ref_level; - d *tmp; - - /// Pre-calculate the size of the output data - us downsampling_fac = slm->downsampling_fac; - us samples_bandpassed = bandpassed.n_rows; - iVARTRACE(15, samples_bandpassed); - iVARTRACE(15, downsampling_fac); - us cur_offset = slm->cur_offset; - - /// Compute the number of samples output - us nsamples_output = samples_bandpassed; - if (downsampling_fac > 1) { - nsamples_output = (samples_bandpassed - cur_offset) / downsampling_fac; - if(nsamples_output > samples_bandpassed) { - // This means overflow of unsigned number calculations - nsamples_output = 0; - } - while(nsamples_output * downsampling_fac + cur_offset < samples_bandpassed) { - nsamples_output++; - } - } - - iVARTRACE(15, nsamples_output); - iVARTRACE(15, cur_offset); - dmat levels = dmat_alloc(nsamples_output, filterbank_size); - us N, ch; - - for (ch = 0; ch < bandpassed.n_cols; ch++) { - iVARTRACE(15, ch); - vd chan = dmat_column(&bandpassed, ch); - /// Inplace squaring of the signal - for (us sample = 0; sample < bandpassed.n_rows; sample++) { - tmp = getdmatval(&bandpassed, sample, ch); - *tmp = *tmp * *tmp; - *getvdval(&(slm->Ppeak), ch) = d_max(*getvdval(&(slm->Ppeak), ch), *tmp); - } - - // Now that all data for the channel is squared, we can run it through - // the low-pass filter - cur_offset = slm->cur_offset; - - /// Apply single-pole lowpass filter for current filterbank channel - TRACE(15, "Start filtering"); - vd power_filtered; - if (slm->splowpass) { - power_filtered = Sosfilterbank_filter(slm->splowpass[ch], &chan); - } else { - power_filtered = dmat_foreign(&chan); - } - TRACE(15, "Filtering done"); - dbgassert(chan.n_rows == power_filtered.n_rows, "BUG"); - - /// Output resulting levels at a lower interval - us i = 0; - N = slm->N; - d *Pm = getvdval(&(slm->Pm), ch); - while (cur_offset < samples_bandpassed) { - iVARTRACE(10, i); - iVARTRACE(10, cur_offset); - - /// Filtered power. - const d P = *getvdval(&power_filtered, cur_offset); - dVARTRACE(15, P); - - /// Compute maximum, compare to current maximum - *getvdval(&(slm->Pmax), ch) = d_max(*getvdval(&(slm->Pmax), ch), P); - - /// Update mean power - d Nd = (d) N; - *Pm = (*Pm*Nd + P ) / (Nd+1); - N++; - dVARTRACE(15, *Pm); - - /// Compute level - d level = 10 * d_log10((P + d_epsilon ) / ref_level / ref_level); - - *getdmatval(&levels, i++, ch) = level; - cur_offset = cur_offset + downsampling_fac; - } - iVARTRACE(15, cur_offset); - iVARTRACE(15, i); - dbgassert(i == nsamples_output, "BUG"); - - vd_free(&power_filtered); - vd_free(&chan); - } - /// Update sample counter - dbgassert(ch >0, "BUG"); - slm->N = N; - slm->cur_offset = cur_offset - samples_bandpassed; - - vd_free(&prefiltered); - dmat_free(&bandpassed); - feTRACE(15); - return levels; -} - - -static inline vd levels_from_power(const vd* power,const d ref_level){ - fsTRACE(15); - - vd levels = dmat_alloc_from_dmat(power); - for(us i=0; i< levels.n_rows; i++) { - *getvdval(&levels, i) = 10 * d_log10( - (*getvdval(power, i) + d_epsilon) / ref_level / ref_level); - - } - feTRACE(15); - return levels; -} - -vd Slm_Lpeak(Slm* slm) { - fsTRACE(15); - assertvalidptr(slm); - vd Lpeak = levels_from_power(&(slm->Ppeak), slm->ref_level); - feTRACE(15); - return Lpeak; -} - -vd Slm_Lmax(Slm* slm) { - fsTRACE(15); - assertvalidptr(slm); - vd Lmax = levels_from_power(&(slm->Pmax), slm->ref_level); - feTRACE(15); - return Lmax; -} - -vd Slm_Leq(Slm* slm) { - fsTRACE(15); - assertvalidptr(slm); - print_vd(&(slm->Pm)); - vd Leq = levels_from_power(&(slm->Pm), slm->ref_level); - feTRACE(15); - return Leq; -} - -void Slm_free(Slm *slm) { - fsTRACE(15); - assertvalidptr(slm); - if (slm->prefilter) { - Sosfilterbank_free(slm->prefilter); - } - - us filterbank_size; - if (slm->bandpass) { - filterbank_size = Sosfilterbank_getFilterbankSize(slm->bandpass); - Sosfilterbank_free(slm->bandpass); - } else { - filterbank_size = 1; - } - if (slm->splowpass) { - for (us ch = 0; ch < filterbank_size; ch++) { - Sosfilterbank_free(slm->splowpass[ch]); - } - a_free(slm->splowpass); - } - vd_free(&(slm->Ppeak)); - vd_free(&(slm->Pmax)); - vd_free(&(slm->Pm)); - a_free(slm); - - feTRACE(15); -} diff --git a/lasp/c/lasp_slm.h b/lasp/c/lasp_slm.h deleted file mode 100644 index 479b383..0000000 --- a/lasp/c/lasp_slm.h +++ /dev/null @@ -1,96 +0,0 @@ -// lasp_slm.h -// -// Author: J.A. de Jong - ASCEE -// -// Description: Multi-purpose implementation of a real time Sound Level Meter, -// can be used for full-signal filtering, (fractional) octave band filtering, -// etc. -// ////////////////////////////////////////////////////////////////////// -#pragma once -#ifndef LASP_SLM_H -#define LASP_SLM_H -#include "lasp_types.h" -#include "lasp_mat.h" -#include "lasp_sosfilterbank.h" - -typedef struct Slm Slm; - -#define TAU_FAST (1.0/8.0) -#define TAU_SLOW (1.0) -#define TAU_IMPULSE (35e-3) - -/** - * Initializes a Sound level meter. NOTE: Sound level meter takes over - * ownership of pointers to the filterbanks for prefiltering and band-pass - * filtering! After passing them to the constructor of the Slm, they should not - * be touched anymore. - * - * @param[in] weighting: Sosfiterbank handle, used to pre-filter data. This is - * in most cases an A-weighting filter, or C-weighting. If NULL, no - * pre-filtering is done. That can be the case in situations of Z-weighting. - * @param[in] fb: Sosfiterbank handle, bandpass filters. - * @param[in] fs: Sampling frequency in [Hz], used for computing the - * downsampling factor and size of output arrays. - * @param[in] tau: Time constant of single pole low pass filter for squared data. - * Three pre-defined values can be used: TAU_FAST, for fast filtering, - * TAU_SLOW, for slow filtering, TAU_IMPULSE for impulse filtering - * @param[in] ref_level: Reference level when computing dB's. I.e. P_REF_AIR for - * sound pressure levels in air - * @param[out] downsampling_fac: Here, the used downsampling factor is stored - * which is used on returning data. If the value is for example 10, the - * 'sampling' frequency of output data from `Slm_run` is 4800 is fs is set to - * 48000. This downsampling factor is a function of the used time weighting. - * - * @return Slm: Handle of the sound level meter, NULL on error. - */ -Slm* Slm_create(Sosfilterbank* weighting,Sosfilterbank* bandpass, - const d fs, - const d tau, - const d ref_level, - us* downsampling_fac); - -/** - * Run the sound level meter on a piece of time data. - * - * @param[in] slm: Slm handle - * @param[in] input_data: Vector of input data samples. - * - * @return Output result of Sound Level values in [dB], for each bank in the filter - * bank. - */ -dmat Slm_run(Slm* slm, - vd* input_data); -/** - * Cleans up an existing Slm - * - * @param f slm handle - */ -void Slm_free(Slm* f); - -/** - * Returns the (raw) peak level computed so far. - * - * @param[in] slm: Slm handle - * @return Vector of peak level for each channel in the filterbank. - */ -vd Slm_Lpeak(Slm* slm); - -/** - * Returns the equivalent level computed so far. - * - * @param[in] slm: Slm handle - * @return Vector of equivalent levels for each channel in the filterbank. - */ -vd Slm_Leq(Slm* slm); - -/** - * Returns the maximum level computed so far. - * - * @param[in] slm: Slm handle - * @return Vector of maximum levels for each channel in the filterbank. - */ -vd Slm_Lmax(Slm* slm); - - -#endif // LASP_SLM_H -////////////////////////////////////////////////////////////////////// diff --git a/lasp/c/lasp_sosfilterbank.c b/lasp/c/lasp_sosfilterbank.c deleted file mode 100644 index 7b42147..0000000 --- a/lasp/c/lasp_sosfilterbank.c +++ /dev/null @@ -1,264 +0,0 @@ -#define TRACERPLUS (-5) -#include "lasp_sosfilterbank.h" -#include "lasp_mq.h" -#include "lasp_worker.h" -#include "lasp_nprocs.h" - - - -typedef struct Sosfilterbank { - /// The filter_coefs matrix contains filter coefficients for a SOS filter. - us filterbank_size; - us nsections; - - /// The filter coefficients for each of the filters in the Filterbank - /// The *first* axis is the filter no, the second axis contains the - /// filter coefficients, in the order, b_0, b_1, b_2, a_0, a_1, a_2, which - /// corresponds to the transfer function - /// b_0 + b_1 z^-1 + b_2 z^-2 - /// H[z] = ------------------------- - /// a_0 + a_1 z^-1 + a_2 z^-2 - dmat sos; /// sos[filter_no, coeff] - - /// Storage for the current state of the output, first axis correspond to - /// the filter number axis, the second axis contains state coefficients - dmat state; -#ifdef LASP_PARALLEL - JobQueue* jq; - Workers* workers; -#endif // LASP_PARALLEL - - -} Sosfilterbank; - -us Sosfilterbank_getFilterbankSize(const Sosfilterbank* fb) { - fsTRACE(15); - assertvalidptr(fb); - return fb->filterbank_size; - feTRACE(15); - -} - -int filter_single(void* worker_data,void* job); - -static inline us min(us a, us b){ - return ab?a:b; -} - -Sosfilterbank* Sosfilterbank_create( - const us nthreads_, - const us filterbank_size, - const us nsections) { - fsTRACE(15); - - dbgassert(filterbank_size <= MAX_SOS_FILTER_BANK_SIZE, - "Illegal filterbank size. Max size is " - annestr(MAX_SOS_FILTER_BANK_SIZE)); - - Sosfilterbank* fb = (Sosfilterbank*) a_malloc(sizeof(Sosfilterbank)); - - fb->filterbank_size = filterbank_size; - dbgassert(nsections < MAX_SOS_FILTER_BANK_NSECTIONS,"Illegal number of sections"); - fb->nsections = nsections; - - /// Allocate filter coefficients matrix - fb->sos = dmat_alloc(filterbank_size, nsections*6); - fb->state = dmat_alloc(filterbank_size, nsections*2); - dmat_set(&(fb->state), 0); - - /// Set all filter coefficients to unit impulse response - vd imp_response = vd_alloc(6*nsections); - vd_set(&imp_response,0); - for(us section = 0;section < nsections; section++) { - // Set b0 coefficient to 1 - setvecval(&imp_response, 0 + 6*section, 1); - // Set a0 coefficient to 1 - setvecval(&imp_response, 3 + 6*section, 1); - } - - // Initialize all filters with a simple impulse response, single pass - for(us filter_no = 0; filter_no < filterbank_size; filter_no++) { - Sosfilterbank_setFilter(fb,filter_no,imp_response); - } - // Check if coefficients are properly initialized - // print_dmat(&(fb->sos)); - vd_free(&imp_response); - -#ifdef LASP_PARALLEL - fb->jq = NULL; - fb->workers = NULL; - dbgassert(nthreads_ <= LASP_MAX_NUM_THREADS, "Illegal number of threads"); - us nthreads; - us nprocs = getNumberOfProcs(); - - if(nthreads_ == 0) { - nthreads = min(max(nprocs/2,1), filterbank_size); - } else { - nthreads = nthreads_; - } - iVARTRACE(15, nthreads); - - if(nthreads > 1) { - if(!(fb->jq = JobQueue_alloc(filterbank_size))) { - Sosfilterbank_free(fb); - feTRACE(15); - return NULL; - } - - if(!(fb->workers = Workers_create(nthreads, - fb->jq, - NULL, - &filter_single, - NULL, - NULL))) { - Sosfilterbank_free(fb); - feTRACE(15); - return NULL; - } - } - -#endif // LASP_PARALLEL - feTRACE(15); - return fb; -} - -void Sosfilterbank_setFilter(Sosfilterbank* fb,const us filter_no, - const vd filter_coefs) { - fsTRACE(15); - assertvalidptr(fb); - assert_vx(&filter_coefs); - iVARTRACE(15, filter_coefs.n_rows); - iVARTRACE(15, filter_no); - dbgassert(filter_no < fb->filterbank_size, "Illegal filter number"); - dbgassert(filter_coefs.n_rows == fb->nsections * 6, - "Illegal filter coefficient length"); - - dmat *sos = &fb->sos; - /* dmat *state = &fb->state; */ - us nsections = fb->nsections; - - for(us index=0;indexsos)); - dmat_free(&(fb->state)); - -#ifdef LASP_PARALLEL - if(fb->workers) Workers_free(fb->workers); - if(fb->jq) JobQueue_free(fb->jq); -#endif // LASP_PARALLEL - - a_free(fb); - feTRACE(15); - -} - -typedef struct { - us filter_no; - us nsections; - us nsamples; - dmat sos; - dmat state; - dmat ys; -} Job; - -int filter_single(void* worker_data, void* job_) { - fsTRACE(15); - Job* job = (Job*) job_; - - us nsections = job->nsections; - us nsamples = job->nsamples; - dmat sos = job->sos; - - - for(us section=0;sectionstate),job->filter_no,section*2); - d w2 = *getdmatval(&(job->state),job->filter_no,section*2+1); - - d b0 = *getdmatval(&sos,job->filter_no,section*6+0); - d b1 = *getdmatval(&sos,job->filter_no,section*6+1); - d b2 = *getdmatval(&sos,job->filter_no,section*6+2); - /* d a0 = *getdmatval(&sos,job->filter_no,section*6+3); */ - d a1 = *getdmatval(&sos,job->filter_no,section*6+4); - d a2 = *getdmatval(&sos,job->filter_no,section*6+5); - - d* y = getdmatval(&(job->ys), 0, job->filter_no); - - for(us sample=0;samplestate),job->filter_no,section*2) = w1; - *getdmatval(&(job->state),job->filter_no,section*2+1) = w2; - - } - - feTRACE(15); - return 0; -} - - -dmat Sosfilterbank_filter(Sosfilterbank* fb,const vd* xs) { - - fsTRACE(15); - assertvalidptr(fb); - assert_vx(xs); - dmat state = fb->state; - dmat sos = fb->sos; - - us nsections = fb->nsections; - us filterbank_size = fb->filterbank_size; - us nsamples = xs->n_rows; - - dmat ys = dmat_alloc(nsamples, filterbank_size); - /// Copy input signal to output array - for(us filter=0;filterworkers) { - assertvalidptr(fb->jq); - JobQueue_push(fb->jq, &(jobs[filter])); - } else { - -#endif // LASP_PARALLEL - /* No workers, we have to do it ourselves */ - filter_single(NULL,(void*) &(jobs[filter])); -#ifdef LASP_PARALLEL - } -#endif // LASP_PARALLEL - } -#ifdef LASP_PARALLEL - if(fb->workers) { - JobQueue_wait_alldone(fb->jq); - } -#endif // LASP_PARALLEL - feTRACE(15); - return ys; -} diff --git a/lasp/c/lasp_sosfilterbank.h b/lasp/c/lasp_sosfilterbank.h deleted file mode 100644 index 67c1815..0000000 --- a/lasp/c/lasp_sosfilterbank.h +++ /dev/null @@ -1,81 +0,0 @@ -// lasp_sosfilterbank.h -// -// Author: J.A. de Jong - ASCEE -// -// Description: Implemententation of a discrete parallel filterbank using -// cascaded second order sections (sos) for each filter in the filterbank. In -// parallel mode, the filters are allocated over a set of Worker threads that -// actually perform the computation. In serial mode, all filters in the bank -// are computed in series. -////////////////////////////////////////////////////////////////////// -#pragma once -#ifndef LASP_FILTERBANK_H -#define LASP_FILTERBANK_H -#include "lasp_config.h" -#include "lasp_types.h" -#include "lasp_mat.h" - -#define MAX_SOS_FILTER_BANK_SIZE 40 -#define MAX_SOS_FILTER_BANK_NSECTIONS 10 - -typedef struct Sosfilterbank Sosfilterbank; - -/** - * Initializes a Sosfilterbank. Sets all coefficients in such a way that the - * filter effectively does nothing (unit impulse response). - * @param[in] nthreads: The number of threads used in computation should be 1 - * <= nthreads <= LASP_MAX_NUM_THREADS. If set to 0, a sensible value is - * computed based on the number of filterbanks. - * @param[in] filterbank_size: The number of parallel filters in the bank - * - * @return Sosfilterbank handle - */ -Sosfilterbank* Sosfilterbank_create(const us nthreads, - const us filterbank_size, - const us nsections); - -/** - * Returns the number of channels in the filterbank (the filberbank size). - * - * @param[in] fb: Filterbank handle - * @return The number of filters in the bank - * */ -us Sosfilterbank_getFilterbankSize(const Sosfilterbank* fb); - -/** - * Initialize the filter coeficients in the filterbank - * - * @param fb: Filterbank handle - * @param filter_no: Filter number in the bank - * @param coefss: Array of filter coefficients. Should have a length of - * nsections x 6, for each of the sections, it contains (b0, b1, b2, a0, - * a1, a2), where b are the numerator coefficients and a are the denominator - * coefficients. - * - */ -void Sosfilterbank_setFilter(Sosfilterbank* fb,const us filter_no, - const vd coefs); - -/** - * Filters x using h, returns y - * - * @param x Input time sequence block. Should have at least one sample. - - * @return Filtered output in an allocated array. The number of - * columns in this array equals the number of filters in the - * filterbank. The number of output samples is equal to the number of - * input samples in x (and is equal to the number of rows in the output). - */ -dmat Sosfilterbank_filter(Sosfilterbank* fb, - const vd* x); - -/** - * Cleans up an existing filter bank. - * - * @param f Filterbank handle - */ -void Sosfilterbank_free(Sosfilterbank* f); - - -#endif // LASP_FILTERBANK_H -////////////////////////////////////////////////////////////////////// diff --git a/lasp/c/lasp_tracer.c b/lasp/c/lasp_tracer.c deleted file mode 100644 index d404776..0000000 --- a/lasp/c/lasp_tracer.c +++ /dev/null @@ -1,78 +0,0 @@ -// lasp_tracer.c -// -// last-edit-by: J.A. de Jong -// -// Description: -// Debug tracing code implementation -////////////////////////////////////////////////////////////////////// -#include "lasp_config.h" -#if TRACER == 1 -#include -#include "lasp_tracer.h" -#include "lasp_types.h" - -#ifdef _REENTRANT - -static __thread us ASCEE_FN_LEVEL = 0; - -static int TRACERNAME = DEFAULTTRACERLEVEL; - - -void setTracerLevel(int level) { - __sync_lock_test_and_set(&TRACERNAME, level); -} -int getTracerLevel() { - return __sync_fetch_and_add(&TRACERNAME, 0); -} - -#else - -int TRACERNAME; -static us ASCEE_FN_LEVEL = 0; - -/* setTracerLevel and getTracerLevel are defined as macros in - * tracer.h */ -#endif - -void indent_trace() { - for(us i=0;i -#include -#include -#ifdef __cplusplus -extern "C" { -#endif - -static inline void clearScreen() { - printf("\033c\n"); -} - -// Some console colors -#define RESET "\033[0m" -#define BLACK "\033[30m" /* Black */ -#define RED "\033[31m" /* Red */ -#define GREEN "\033[32m" /* Green */ -#define YELLOW "\033[33m" /* Yellow */ -#define BLUE "\033[34m" /* Blue */ -#define MAGENTA "\033[35m" /* Magenta */ -#define CYAN "\033[36m" /* Cyan */ -#define WHITE "\033[37m" /* White */ -#define BOLDBLACK "\033[1m\033[30m" /* Bold Black */ -#define BOLDRED "\033[1m\033[31m" /* Bold Red */ -#define BOLDGREEN "\033[1m\033[32m" /* Bold Green */ -#define BOLDYELLOW "\033[1m\033[33m" /* Bold Yellow */ -#define BOLDBLUE "\033[1m\033[34m" /* Bold Blue */ -#define BOLDMAGENTA "\033[1m\033[35m" /* Bold Magenta */ -#define BOLDCYAN "\033[1m\033[36m" /* Bold Cyan */ -#define BOLDWHITE "\033[1m\033[37m" /* Bold White */ - -// Not so interesting part -#define rawstr(x) #x -#define namestr(x) rawstr(x) -#define annestr(x) namestr(x) -#define FILEWITHOUTPATH ( strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__ ) -// #define POS annestr(FILEWITHOUTPATH) ":" # __LINE__ << ": " -// End not so interesting part - -/** - * Produce a debug warning - */ -#define DBGWARN(a) \ - printf(RED); \ - printf("%s(%d): ", \ - __FILE__, \ - __LINE__ \ - ); \ - printf(a); \ - printf(RESET "\n"); - -/** - * Produce a runtime warning - */ -#define WARN(a) \ - printf(RED); \ - printf("WARNING: "); \ - printf(a); \ - printf(RESET "\n"); - -/** - * Fatal error, abort execution - */ -#define FATAL(a) \ - WARN(a); \ - abort(); - - - - -// **************************************** Tracer code -#ifndef TRACERPLUS -#define TRACERPLUS (0) -#endif - -// If PP variable TRACER is not defined, we automatically set it on. -#ifndef TRACER -#define TRACER 1 -#endif - - -#if TRACER == 1 -#ifndef TRACERNAME - -#ifdef __GNUC__ -#warning TRACERNAME name not set, sol TRACERNAME set to 'defaulttracer' -#else -#pragma message("TRACERNAME name not set, sol TRACERNAME set to defaulttracer") -#endif - -#define TRACERNAME defaulttracer -#endif // ifndef TRACERNAME - -/** - * Indent the rule for tracing visibility. - */ -void indent_trace(); - -// Define this preprocessor definition to overwrite -// Use -O flag for compiler to remove the dead functions! -// In that case all cout's for TRACE() are removed from code -#ifndef DEFAULTTRACERLEVEL -#define DEFAULTTRACERLEVEL (15) -#endif - -#ifdef _REENTRANT -/** - * Set the tracer level at runtime - * - * @param level - */ -void setTracerLevel(int level); - -/** - * Obtain the tracer level - * - * @return level - */ -int getTracerLevel(); - -#else // Not reentrant -extern int TRACERNAME; -#define setTracerLevel(a) TRACERNAME = a; -static inline int getTracerLevel() { return TRACERNAME;} -#endif - -#include "lasp_types.h" - -// Use this preprocessor command to introduce one TRACERNAME integer per unit -/* Introduce one static logger */ -// We trust that the compiler will eliminate 'dead code', which means -// that if variable BUILDINTRACERLEVEL is set, the inner if statement -// will not be reached. -void trace_impl(const char* pos,int line,const char * string); -void fstrace_impl(const char* file,int pos,const char* fn); -void fetrace_impl(const char* file,int pos,const char* fn); -void dvartrace_impl(const char* pos,int line,const char* varname,d var); -void cvartrace_impl(const char* pos,int line,const char* varname,c var); -void ivartrace_impl(const char* pos,int line,const char* varname,int var); -void uvartrace_impl(const char* pos,int line,const char* varname,size_t var); - -/** - * Print a trace string - */ -#define TRACE(level,trace_string) \ - if (level+TRACERPLUS>=getTracerLevel()) \ - { \ - trace_impl(FILEWITHOUTPATH,__LINE__,trace_string ); \ - } - -#define SFSG TRACE(100,"SFSG") - -/** - * Print start of function string - */ -#define fsTRACE(level) \ - if (level+TRACERPLUS>=getTracerLevel()) \ - { \ - fstrace_impl(FILEWITHOUTPATH,__LINE__, __FUNCTION__ ); \ - } - -/** - * Print end of function string - */ -#define feTRACE(level) \ - if (level+TRACERPLUS>=getTracerLevel()) \ - { \ - fetrace_impl(FILEWITHOUTPATH,__LINE__, __FUNCTION__ ); \ - } - - -/** - * Trace an int variable - */ -#define iVARTRACE(level,trace_var) \ - if (level+TRACERPLUS>=getTracerLevel()) \ - { \ - ivartrace_impl(FILEWITHOUTPATH,__LINE__,#trace_var,trace_var); \ - } - -/** - * Trace an unsigned int variable - */ -#define uVARTRACE(level,trace_var) \ - if (level+TRACERPLUS>=getTracerLevel()) \ - { \ - uvartrace_impl(FILEWITHOUTPATH,__LINE__,#trace_var,trace_var); \ - } -/** - * Trace a floating point value - */ -#define dVARTRACE(level,trace_var) \ - if (level+TRACERPLUS>=getTracerLevel()) \ - { \ - dvartrace_impl(FILEWITHOUTPATH,__LINE__,#trace_var,trace_var); \ - } -/** - * Trace a complex floating point value - */ -#define cVARTRACE(level,trace_var) \ - if (level+TRACERPLUS>=getTracerLevel()) \ - { \ - cvartrace_impl(FILEWITHOUTPATH,__LINE__,#trace_var,trace_var); \ - } - - -#else // TRACER !=1 -#define TRACE(l,a) -#define fsTRACE(l) -#define feTRACE(l) -#define setTracerLevel(a) -#define getTracerLevel() - -#define iVARTRACE(level,trace_var) -#define uVARTRACE(level,trace_var) -#define dVARTRACE(level,trace_var) -#define cVARTRACE(level,trace_var) - - -#endif // ######################################## TRACER ==1 - - -#ifdef __cplusplus -} -#endif -#endif // LASP_TRACER_H -////////////////////////////////////////////////////////////////////// diff --git a/lasp/c/lasp_window.c b/lasp/c/lasp_window.c deleted file mode 100644 index 9f11123..0000000 --- a/lasp/c/lasp_window.c +++ /dev/null @@ -1,111 +0,0 @@ -// window.c -// -// Author: J.A. de Jong -ASCEE -// -// Description: -// -////////////////////////////////////////////////////////////////////// - -#include "lasp_window.h" -#include "lasp_signals.h" -#include -/** - * Compute the Hann window - * - * @param i index - * @param N Number of indices - */ -static d hann(us i,us N) { - dbgassert(in_rows; - d (*win_fun)(us,us); - switch (wintype) { - case Hann: { - win_fun = hann; - break; - } - case Hamming: { - win_fun = hamming; - break; - } - case Rectangular: { - win_fun = rectangle; - break; - } - case Bartlett: { - win_fun = bartlett; - break; - } - case Blackman: { - win_fun = blackman; - break; - } - default: - DBGWARN("BUG: Unknown window function"); - abort(); - break; - } - us index; - for(index=0;index -#include "lasp_assert.h" -#include "lasp_tracer.h" - -typedef struct Workers_s { - JobQueue* jq; - worker_alloc_function w_alloc_fcn; - worker_function fn; - worker_free_function w_free_fcn; - - pthread_mutex_t global_data_mutex; - void* global_data; - - pthread_t worker_threads[LASP_MAX_NUM_THREADS]; - us num_workers; -} Workers; - -static void* threadfcn(void* data); - -Workers* Workers_create(const us num_workers, - JobQueue* jq, - worker_alloc_function init_fn, - worker_function fn, - worker_free_function free_fn, - void* thread_global_data) { - - TRACE(15,"Workers_create"); - - if(num_workers > LASP_MAX_NUM_THREADS) { - WARN("Number of workers too high in Workers_create"); - return NULL; - } - - /* dbgassert(init_fn,NULLPTRDEREF "init_fn"); */ - dbgassert(fn,NULLPTRDEREF "fn"); - /* dbgassert(free_fn,NULLPTRDEREF "free_fn"); */ - - Workers* w = a_malloc(sizeof(Workers)); - if(!w){ - WARN(ALLOCFAILED "Workers_create"); - return NULL; - } - - w->jq = jq; - w->w_alloc_fcn = init_fn; - w->fn = fn; - w->w_free_fcn = free_fn; - w->global_data = thread_global_data; - w->num_workers = num_workers; - - /* Initialize thread mutex */ - int rv = pthread_mutex_init(&w->global_data_mutex,NULL); - if(rv !=0) { - WARN("Mutex initialization failed"); - return NULL; - } - - - /* Create the threads */ - pthread_t* thread = w->worker_threads; - for(us i = 0; i < num_workers; i++) { - TRACE(15,"Creating thread"); - int rv = pthread_create(thread, - NULL, /* Thread attributes */ - threadfcn, /* Function */ - w); /* Data */ - if(rv!=0) { - WARN("Thread creation failed"); - return NULL; - } - thread++; - } - - return w; - -} - -void Workers_free(Workers* w) { - TRACE(15,"Workers_free"); - dbgassert(w,NULLPTRDEREF "w in Workers_free"); - dbgassert(w->jq,NULLPTRDEREF "w->jq in Workers_free"); - - for(us i=0;inum_workers;i++) { - /* Push the special NULL job. This will make the worker - * threads stop their execution. */ - JobQueue_push(w->jq,NULL); - } - - JobQueue_wait_alldone(w->jq); - - /* Join the threads */ - pthread_t* thread = w->worker_threads; - for(us i=0;inum_workers;i++) { - void* retval; - if(pthread_join(*thread,&retval)!=0) { - WARN("Error joining thread!"); - } - if((retval) != NULL) { - WARN("Thread returned with error status"); - } - thread++; - } - - /* Destroy the global data mutex */ - int rv = pthread_mutex_destroy(&w->global_data_mutex); - if(rv != 0){ - WARN("Mutex destroy failed. Do not know what to do."); - } - - /* All threads joined */ - a_free(w); - -} - -static void* threadfcn(void* thread_global_data) { - - TRACE(15,"Started worker thread function"); - /* dbgassert(thread_global_data,NULLPTRDEREF "thread_data in" */ - /* " threadfcn"); */ - Workers* w = (Workers*) thread_global_data; - - JobQueue* jq = w->jq; - worker_alloc_function walloc = w->w_alloc_fcn; - worker_free_function wfree = w->w_free_fcn; - worker_function worker_fn = w->fn; - void* global_data = w->global_data; - - dbgassert(jq,NULLPTRDEREF "jq in threadfcn"); - /* dbgassert(walloc,NULLPTRDEREF "walloc in threadfcn"); */ - /* dbgassert(wfree,NULLPTRDEREF "wfree in threadfcn"); */ - - int rv = pthread_mutex_lock(&w->global_data_mutex); - if(rv !=0) { - WARN("Global data mutex lock failed"); - pthread_exit((void*) 1); - } - void* w_data = NULL; - if(walloc) { - w_data = walloc(global_data); - if(!w_data) { - WARN(ALLOCFAILED); - pthread_exit((void*) 1); - } - } - - rv = pthread_mutex_unlock(&w->global_data_mutex); - if(rv !=0) { - WARN("Global data mutex unlock failed"); - pthread_exit((void*) 1); - } - - void* job = NULL; - TRACE(20,"Worker ready"); - while (true) { - - TRACE(10,"--------------- START CYCLE -------------"); - job = JobQueue_assign(jq); - - /* Kill the thread for the special NULL job */ - if(!job) break; - - /* Run the worker function */ - rv = worker_fn(w_data,job); - if(rv!=0) { - WARN("An error occured during execution of worker function"); - JobQueue_done(jq,job); - break; - } - - JobQueue_done(jq,job); - TRACE(10,"--------------- CYCLE COMPLETE -------------"); - } - - JobQueue_done(jq,job); - - /* Call the cleanup function */ - if(wfree) { - wfree(w_data); - } - TRACE(15,"Exiting thread. Goodbye"); - pthread_exit((void*) NULL); - - /* This return statement is never reached, but added to have a proper return - * type from this function. */ - return NULL; -} - -#endif // LASP_PARALLEL - -////////////////////////////////////////////////////////////////////// diff --git a/lasp/c/lasp_worker.h b/lasp/c/lasp_worker.h deleted file mode 100644 index 9780e14..0000000 --- a/lasp/c/lasp_worker.h +++ /dev/null @@ -1,65 +0,0 @@ -// worker.h -// -// Author: J.A. de Jong - ASCEE -// -// Description: Provides a clean interface to a pool of worker -// threads. This class is used to easily interface with worker threads -// by just providing a simple function to be called by a worker thread -// on the push of a new job. -////////////////////////////////////////////////////////////////////// -#pragma once -#ifndef LASP_WORKER_H -#define LASP_WORKER_H -#include "lasp_config.h" -#include "lasp_types.h" - -#ifdef LASP_PARALLEL -typedef struct Workers_s Workers; -typedef struct JobQueue_s JobQueue; - -typedef void* (*worker_alloc_function)(void* global_data); -typedef int (*worker_function)(void* worker_data,void* job); -typedef void (*worker_free_function)(void* worker_data); - -/** - * Create a pool of worker threads, that pull jobs from the job queue - * and perform the action. - * - * @param num_workers Number of worker threads to create - * - * @param jq JobQueue. JobQueue where jobs for the workers are - * pushed. Should stay valid as long as the Workers are alive. - * - * @param worker_alloc_function Function pointer to the function that - * will be called right after the thread has been created. The worker - * alloc function will get a pointer to the thread global data. This - * data will be given to each thread during initialization. Using a - * mutex to avoid race conditions on this global data. - - * @param fn Worker function that performs the action on the - * data. Will be called every time a job is available from the - * JobQueue. Should have a return code of 0 on success. - * - * @param worker_free_function Cleanup function that is called on - * exit. - * - * @return Pointer to Workers handle. NULL on error. - */ -Workers* Workers_create(const us num_workers, - JobQueue* jq, - worker_alloc_function init_fn, - worker_function fn, - worker_free_function free_fn, - void* thread_global_data); - -/** - * Free the pool of workers. - * - * @param w - */ -void Workers_free(Workers* w); - -#endif // LASP_PARALLEL - -#endif // LASP_WORKER_H -////////////////////////////////////////////////////////////////////// diff --git a/lasp/cmake/BuildType.cmake b/lasp/cmake/BuildType.cmake deleted file mode 100644 index caeee6d..0000000 --- a/lasp/cmake/BuildType.cmake +++ /dev/null @@ -1,13 +0,0 @@ -# Set a default build type if none was specified -set(default_build_type "Release") -if(EXISTS "${CMAKE_SOURCE_DIR}/.git") - set(default_build_type "Debug") -endif() - -if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) - message(STATUS "Setting build type to '${default_build_type}' as none was specified.") - set(CMAKE_BUILD_TYPE "${default_build_type}" CACHE - STRING "Choose the type of build." FORCE) - # Set the possible values of build type for cmake-gui - set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release") -endif() diff --git a/lasp/cmake/FindCython.cmake b/lasp/cmake/FindCython.cmake deleted file mode 100644 index f1c4316..0000000 --- a/lasp/cmake/FindCython.cmake +++ /dev/null @@ -1,44 +0,0 @@ -# Find the Cython compiler. -# -# This code sets the following variables: -# -# CYTHON_EXECUTABLE -# -# See also UseCython.cmake - -#============================================================================= -# Copyright 2011 Kitware, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -#============================================================================= - -# Use the Cython executable that lives next to the Python executable -# if it is a local installation. -find_package( PythonInterp ) -if( PYTHONINTERP_FOUND ) - get_filename_component( _python_path ${PYTHON_EXECUTABLE} PATH ) - find_program( CYTHON_EXECUTABLE - NAMES cython cython.bat cython3 cython2 - HINTS ${_python_path} - ) -else() - find_program( CYTHON_EXECUTABLE - NAMES cython cython.bat cython3 - ) -endif() - - -include( FindPackageHandleStandardArgs ) -FIND_PACKAGE_HANDLE_STANDARD_ARGS( Cython REQUIRED_VARS CYTHON_EXECUTABLE ) - -mark_as_advanced( CYTHON_EXECUTABLE ) diff --git a/lasp/cmake/UseCython.cmake b/lasp/cmake/UseCython.cmake deleted file mode 100644 index 06fdf0c..0000000 --- a/lasp/cmake/UseCython.cmake +++ /dev/null @@ -1,310 +0,0 @@ -# Define a function to create Cython modules. -# -# For more information on the Cython project, see http://cython.org/. -# "Cython is a language that makes writing C extensions for the Python language -# as easy as Python itself." -# -# This file defines a CMake function to build a Cython Python module. -# To use it, first include this file. -# -# include( UseCython ) -# -# Then call cython_add_module to create a module. -# -# cython_add_module( ... ) -# -# To create a standalone executable, the function -# -# cython_add_standalone_executable( [MAIN_MODULE src1] ... ) -# -# To avoid dependence on Python, set the PYTHON_LIBRARY cache variable to point -# to a static library. If a MAIN_MODULE source is specified, -# the "if __name__ == '__main__':" from that module is used as the C main() method -# for the executable. If MAIN_MODULE, the source with the same basename as -# is assumed to be the MAIN_MODULE. -# -# Where is the name of the resulting Python module and -# ... are source files to be compiled into the module, e.g. *.pyx, -# *.py, *.c, *.cxx, etc. A CMake target is created with name . This can -# be used for target_link_libraries(), etc. -# -# The sample paths set with the CMake include_directories() command will be used -# for include directories to search for *.pxd when running the Cython complire. -# -# Cache variables that effect the behavior include: -# -# CYTHON_ANNOTATE -# CYTHON_NO_DOCSTRINGS -# CYTHON_FLAGS -# -# Source file properties that effect the build process are -# -# CYTHON_IS_CXX -# -# If this is set of a *.pyx file with CMake set_source_files_properties() -# command, the file will be compiled as a C++ file. -# -# See also FindCython.cmake - -#============================================================================= -# Copyright 2011 Kitware, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -#============================================================================= - -# Configuration options. -set( CYTHON_ANNOTATE OFF - CACHE BOOL "Create an annotated .html file when compiling *.pyx." ) -set( CYTHON_NO_DOCSTRINGS OFF - CACHE BOOL "Strip docstrings from the compiled module." ) -set( CYTHON_FLAGS "" CACHE STRING - "Extra flags to the cython compiler." ) -mark_as_advanced( CYTHON_ANNOTATE CYTHON_NO_DOCSTRINGS CYTHON_FLAGS ) - -find_package( Cython REQUIRED ) - - -set( CYTHON_CXX_EXTENSION "cxx" ) -set( CYTHON_C_EXTENSION "c" ) - -# Create a *.c or *.cxx file from a *.pyx file. -# Input the generated file basename. The generate file will put into the variable -# placed in the "generated_file" argument. Finally all the *.py and *.pyx files. -function( compile_pyx _name generated_file ) - # Default to assuming all files are C. - set( cxx_arg "" ) - set( extension ${CYTHON_C_EXTENSION} ) - set( pyx_lang "C" ) - set( comment "Compiling Cython C source for ${_name}..." ) - - set( cython_include_directories "" ) - set( pxd_dependencies "" ) - set( pxi_dependencies "" ) - set( c_header_dependencies "" ) - set( pyx_locations "" ) - - foreach( pyx_file ${ARGN} ) - get_filename_component( pyx_file_basename "${pyx_file}" NAME_WE ) - - # Determine if it is a C or C++ file. - get_source_file_property( property_is_cxx ${pyx_file} CYTHON_IS_CXX ) - if( ${property_is_cxx} ) - set( cxx_arg "--cplus" ) - set( extension ${CYTHON_CXX_EXTENSION} ) - set( pyx_lang "CXX" ) - set( comment "Compiling Cython CXX source for ${_name}..." ) - endif() - - # Get the include directories. - get_source_file_property( pyx_location ${pyx_file} LOCATION ) - get_filename_component( pyx_path ${pyx_location} PATH ) - message(${pyx_path}) - get_directory_property( cmake_include_directories DIRECTORY ${pyx_path} INCLUDE_DIRECTORIES ) - list( APPEND cython_include_directories ${cmake_include_directories} ) - list( APPEND pyx_locations "${pyx_location}" ) - - # Determine dependencies. - # Add the pxd file will the same name as the given pyx file. - unset( corresponding_pxd_file CACHE ) - find_file( corresponding_pxd_file ${pyx_file_basename}.pxd - PATHS "${pyx_path}" ${cmake_include_directories} - NO_DEFAULT_PATH ) - if( corresponding_pxd_file ) - list( APPEND pxd_dependencies "${corresponding_pxd_file}" ) - endif() - - # pxd files to check for additional dependencies. - set( pxds_to_check "${pyx_file}" "${pxd_dependencies}" ) - set( pxds_checked "" ) - set( number_pxds_to_check 1 ) - while( ${number_pxds_to_check} GREATER 0 ) - foreach( pxd ${pxds_to_check} ) - list( APPEND pxds_checked "${pxd}" ) - list( REMOVE_ITEM pxds_to_check "${pxd}" ) - - # check for C header dependencies - file( STRINGS "${pxd}" extern_from_statements - REGEX "cdef[ ]+extern[ ]+from.*$" ) - foreach( statement ${extern_from_statements} ) - # Had trouble getting the quote in the regex - string( REGEX REPLACE "cdef[ ]+extern[ ]+from[ ]+[\"]([^\"]+)[\"].*" "\\1" header "${statement}" ) - unset( header_location CACHE ) - find_file( header_location ${header} PATHS ${cmake_include_directories} ) - if( header_location ) - list( FIND c_header_dependencies "${header_location}" header_idx ) - if( ${header_idx} LESS 0 ) - list( APPEND c_header_dependencies "${header_location}" ) - endif() - endif() - endforeach() - - # check for pxd dependencies - - # Look for cimport statements. - set( module_dependencies "" ) - file( STRINGS "${pxd}" cimport_statements REGEX cimport ) - foreach( statement ${cimport_statements} ) - if( ${statement} MATCHES from ) - string( REGEX REPLACE "from[ ]+([^ ]+).*" "\\1" module "${statement}" ) - else() - string( REGEX REPLACE "cimport[ ]+([^ ]+).*" "\\1" module "${statement}" ) - endif() - list( APPEND module_dependencies ${module} ) - endforeach() - list( REMOVE_DUPLICATES module_dependencies ) - # Add the module to the files to check, if appropriate. - foreach( module ${module_dependencies} ) - unset( pxd_location CACHE ) - find_file( pxd_location ${module}.pxd - PATHS "${pyx_path}" ${cmake_include_directories} NO_DEFAULT_PATH ) - if( pxd_location ) - list( FIND pxds_checked ${pxd_location} pxd_idx ) - if( ${pxd_idx} LESS 0 ) - list( FIND pxds_to_check ${pxd_location} pxd_idx ) - if( ${pxd_idx} LESS 0 ) - list( APPEND pxds_to_check ${pxd_location} ) - list( APPEND pxd_dependencies ${pxd_location} ) - endif() # if it is not already going to be checked - endif() # if it has not already been checked - endif() # if pxd file can be found - endforeach() # for each module dependency discovered - endforeach() # for each pxd file to check - list( LENGTH pxds_to_check number_pxds_to_check ) - endwhile() - - # Look for included pxi files - file(STRINGS "${pyx_file}" include_statements REGEX "include +['\"]([^'\"]+).*") - foreach(statement ${include_statements}) - string(REGEX REPLACE "include +['\"]([^'\"]+).*" "\\1" pxi_file "${statement}") - unset(pxi_location CACHE) - find_file(pxi_location ${pxi_file} - PATHS "${pyx_path}" ${cmake_include_directories} NO_DEFAULT_PATH) - if (pxi_location) - list(APPEND pxi_dependencies ${pxi_location}) - endif() - endforeach() # for each include statement found - - endforeach() # pyx_file - - # Set additional flags. - if( CYTHON_ANNOTATE ) - set( annotate_arg "--annotate" ) - endif() - - if( CYTHON_NO_DOCSTRINGS ) - set( no_docstrings_arg "--no-docstrings" ) - endif() - - if( "${CMAKE_BUILD_TYPE}" STREQUAL "Debug" OR - "${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo" ) - set( cython_debug_arg "--gdb" ) - endif() - - if( "${PYTHONLIBS_VERSION_STRING}" MATCHES "^2." ) - set( version_arg "-2" ) - elseif( "${PYTHONLIBS_VERSION_STRING}" MATCHES "^3." ) - set( version_arg "-3" ) - else() - set( version_arg ) - endif() - - # Include directory arguments. - list( REMOVE_DUPLICATES cython_include_directories ) - set( include_directory_arg "" ) - foreach( _include_dir ${cython_include_directories} ) - set( include_directory_arg ${include_directory_arg} "-I" "${_include_dir}" ) - endforeach() - - # Determining generated file name. - set( _generated_file "${CMAKE_CURRENT_BINARY_DIR}/${_name}.${extension}" ) - set_source_files_properties( ${_generated_file} PROPERTIES GENERATED TRUE ) - set( ${generated_file} ${_generated_file} PARENT_SCOPE ) - - list( REMOVE_DUPLICATES pxd_dependencies ) - list( REMOVE_DUPLICATES c_header_dependencies ) - - # Add the command to run the compiler. - add_custom_command( OUTPUT ${_generated_file} - COMMAND ${CYTHON_EXECUTABLE} - ARGS ${cxx_arg} ${include_directory_arg} ${version_arg} - ${annotate_arg} ${no_docstrings_arg} ${cython_debug_arg} ${CYTHON_FLAGS} - --output-file ${_generated_file} ${pyx_locations} - DEPENDS ${pyx_locations} ${pxd_dependencies} ${pxi_dependencies} - IMPLICIT_DEPENDS ${pyx_lang} ${c_header_dependencies} - COMMENT ${comment} - ) - - # Remove their visibility to the user. - set( corresponding_pxd_file "" CACHE INTERNAL "" ) - set( header_location "" CACHE INTERNAL "" ) - set( pxd_location "" CACHE INTERNAL "" ) -endfunction() - -# cython_add_module( src1 src2 ... srcN ) -# Build the Cython Python module. -function( cython_add_module _name ) - set( pyx_module_sources "" ) - set( other_module_sources "" ) - foreach( _file ${ARGN} ) - if( ${_file} MATCHES ".*\\.py[x]?$" ) - list( APPEND pyx_module_sources ${_file} ) - else() - list( APPEND other_module_sources ${_file} ) - endif() - endforeach() - compile_pyx( ${_name} generated_file ${pyx_module_sources} ) - include_directories( ${PYTHON_INCLUDE_DIRS} ) - python_add_module( ${_name} ${generated_file} ${other_module_sources} ) - if( APPLE ) - set_target_properties( ${_name} PROPERTIES LINK_FLAGS "-undefined dynamic_lookup" ) - else() - target_link_libraries( ${_name} ${PYTHON_LIBRARIES} ) - endif() -endfunction() - -include( CMakeParseArguments ) -# cython_add_standalone_executable( _name [MAIN_MODULE src3.py] src1 src2 ... srcN ) -# Creates a standalone executable the given sources. -function( cython_add_standalone_executable _name ) - set( pyx_module_sources "" ) - set( other_module_sources "" ) - set( main_module "" ) - cmake_parse_arguments( cython_arguments "" "MAIN_MODULE" "" ${ARGN} ) - include_directories( ${PYTHON_INCLUDE_DIRS} ) - foreach( _file ${cython_arguments_UNPARSED_ARGUMENTS} ) - if( ${_file} MATCHES ".*\\.py[x]?$" ) - get_filename_component( _file_we ${_file} NAME_WE ) - if( "${_file_we}" STREQUAL "${_name}" ) - set( main_module "${_file}" ) - elseif( NOT "${_file}" STREQUAL "${cython_arguments_MAIN_MODULE}" ) - set( PYTHON_MODULE_${_file_we}_static_BUILD_SHARED OFF ) - compile_pyx( "${_file_we}_static" generated_file "${_file}" ) - list( APPEND pyx_module_sources "${generated_file}" ) - endif() - else() - list( APPEND other_module_sources ${_file} ) - endif() - endforeach() - - if( cython_arguments_MAIN_MODULE ) - set( main_module ${cython_arguments_MAIN_MODULE} ) - endif() - if( NOT main_module ) - message( FATAL_ERROR "main module not found." ) - endif() - get_filename_component( main_module_we "${main_module}" NAME_WE ) - set( CYTHON_FLAGS ${CYTHON_FLAGS} --embed ) - compile_pyx( "${main_module_we}_static" generated_file ${main_module} ) - add_executable( ${_name} ${generated_file} ${pyx_module_sources} ${other_module_sources} ) - target_link_libraries( ${_name} ${PYTHON_LIBRARIES} ${pyx_module_libs} ) -endfunction() diff --git a/lasp/config.pxi.in b/lasp/config.pxi.in deleted file mode 100644 index cc6bd92..0000000 --- a/lasp/config.pxi.in +++ /dev/null @@ -1,10 +0,0 @@ -import numpy as np -cimport numpy as cnp - -# Do this, our segfaults will be your destination -cnp.import_array() - -DEF LASP_DOUBLE_PRECISION = "@LASP_DOUBLE_PRECISION@" -DEF LASP_DEBUG_CYTHON = "@LASP_DEBUG@" - -from libcpp cimport bool diff --git a/lasp/device/CMakeLists.txt b/lasp/device/CMakeLists.txt deleted file mode 100644 index 72b79d3..0000000 --- a/lasp/device/CMakeLists.txt +++ /dev/null @@ -1,40 +0,0 @@ -set(cpp_daq_files lasp_cppdaq.cpp) -set(cpp_daq_linklibs ${LASP_THREADING_LIBRARIES}) -include_directories(../c) - -if(LASP_HAS_RTAUDIO) - include_directories(/usr/include/rtaudio) - list(APPEND cpp_daq_files lasp_cpprtaudio.cpp) - list(PREPEND cpp_daq_linklibs rtaudio) -endif() -if(LASP_HAS_ULDAQ) - list(APPEND cpp_daq_files lasp_cppuldaq.cpp) - list(PREPEND cpp_daq_linklibs uldaq) -endif() -if(win32) - list(APPEND cpp_daq_linklibs python${python_version_windll}) -endif(win32) - -add_library(cpp_daq ${cpp_daq_files}) -target_link_libraries(cpp_daq ${cpp_daq_linklibs}) - -foreach(cython_file lasp_daq lasp_deviceinfo lasp_daqconfig) - - set_source_files_properties(${cython_file}.pyx PROPERTIES - 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") diff --git a/lasp/device/__init__.py b/lasp/device/__init__.py deleted file mode 100644 index 6fde552..0000000 --- a/lasp/device/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from .lasp_device_common import * -from .lasp_deviceinfo import * -from .lasp_daqconfig import * -from .lasp_daq import * - diff --git a/lasp/device/lasp_common_decls.pxd b/lasp/device/lasp_common_decls.pxd deleted file mode 100644 index 53b197e..0000000 --- a/lasp/device/lasp_common_decls.pxd +++ /dev/null @@ -1,152 +0,0 @@ -import sys -include "config.pxi" -cimport cython -from .lasp_avtype import AvType -from libcpp.string cimport string -from libcpp.vector cimport vector -from libc.stdlib cimport malloc, free -from libc.stdio cimport printf, fprintf, stderr -from libc.string cimport memcpy, memset -from cpython.ref cimport PyObject,Py_INCREF, Py_DECREF - -cdef extern from "lasp_cppthread.h" nogil: - cdef cppclass CPPThread[T,F]: - CPPThread(F threadfunction, T data) - void join() - - void CPPsleep_ms(unsigned int ms) - void CPPsleep_us(unsigned int us) - -cdef extern from "lasp_cppqueue.h" nogil: - cdef cppclass SafeQueue[T]: - SafeQueue() - void enqueue(T t) - T dequeue() - size_t size() const - bool empty() const - -cdef extern from "atomic" namespace "std" nogil: - cdef cppclass atomic[T]: - T load() - void store(T) - -cdef extern from "lasp_pyarray.h": - PyObject* data_to_ndarray(void* data, - int n_rows,int n_cols, - int typenum, - bool transfer_ownership, - bool F_contiguous) with gil - - -ctypedef size_t us -ctypedef vector[bool] boolvec -ctypedef vector[double] dvec -ctypedef vector[us] usvec - -cdef extern from "lasp_cppdaq.h" nogil: - - cdef cppclass DaqApi: - string apiname - unsigned apicode - unsigned api_specific_subcode - - @staticmethod - vector[DaqApi] getAvailableApis(); - - cdef cppclass DataType: - string name - unsigned sw - bool is_floating - DataType dtype_fl64 - bool operator==(const DataType&) - - DataType dtype_fl64 - DataType dtype_fl32 - DataType dtype_int8 - DataType dtype_int16 - DataType dtype_int32 - DataType dtype_invalid - - cdef cppclass cppDeviceInfo "DeviceInfo": - DaqApi api - string device_name - - int api_specific_devindex - - vector[DataType] availableDataTypes - int prefDataTypeIndex - - vector[double] availableSampleRates - int prefSampleRateIndex - - vector[us] availableFramesPerBlock - unsigned prefFramesPerBlockIndex - - dvec availableInputRanges - int prefInputRangeIndex - - unsigned ninchannels - unsigned noutchannels - - string serialize() - - cppDeviceInfo deserialize(string) - bool hasInputIEPE - bool hasInputACCouplingSwitch - bool hasInputTrigger - - cdef cppclass cppDaqConfiguration "DaqConfiguration": - DaqApi api - string device_name - - boolvec eninchannels - boolvec enoutchannels - - vector[double] inchannel_sensitivities - vector[string] inchannel_names - vector[string] inchannel_metadata - - vector[string] outchannel_names - vector[double] outchannel_sensitivities - vector[string] outchannel_metadata - - us sampleRateIndex - - us dataTypeIndex - - us framesPerBlockIndex - - bool monitorOutput - vector[us] input_qty_idx - - boolvec inputIEPEEnabled - boolvec inputACCouplingMode - - usvec inputRangeIndices - - cppDaqConfiguration() - cppDaqConfiguration(cppDeviceInfo& devinfo) - - int getHighestInChannel() - int getHighestOutChannel() - - int getLowestInChannel() - int getLowestOutChannel() - - cdef cppclass cppDaq "Daq": - void start(SafeQueue[void*] *inQueue, - SafeQueue[void*] *outQueue) except + - void stop() except + - double samplerate() - us neninchannels(bool include_monitorchannel) - us nenoutchannels() - DataType dataType() - us framesPerBlock() - bool isRunning() - bool duplexMode() - - @staticmethod - cppDaq* createDaq(cppDeviceInfo&, cppDaqConfiguration&) except + - - @staticmethod - vector[cppDeviceInfo] getDeviceInfo() except + diff --git a/lasp/device/lasp_cppdaq.cpp b/lasp/device/lasp_cppdaq.cpp deleted file mode 100644 index af4997e..0000000 --- a/lasp/device/lasp_cppdaq.cpp +++ /dev/null @@ -1,198 +0,0 @@ -#include "lasp_cppdaq.h" -#include -#include -#include "lasp_config.h" - -#define MAX_DEV_COUNT_PER_API 20 - -#ifdef LASP_HAS_ULDAQ -#include "lasp_cppuldaq.h" -#endif -#ifdef LASP_HAS_RTAUDIO -#include "lasp_cpprtaudio.h" -#endif - -vector Daq::getDeviceInfo() { - vector devs; -#ifdef LASP_HAS_ULDAQ - fillUlDaqDeviceInfo(devs); -#endif - -#ifdef LASP_HAS_RTAUDIO - fillRtAudioDeviceInfo(devs); -#endif - - return devs; -} - -vector DaqApi::getAvailableApis() { - - vector apis; - apis.resize(6); -#ifdef LASP_HAS_ULDAQ - apis.at(uldaqapi.apicode) = uldaqapi; -#endif -#ifdef LASP_HAS_RTAUDIO - apis.at(rtaudioAlsaApi.apicode) = rtaudioAlsaApi; - apis.at(rtaudioPulseaudioApi.apicode) = rtaudioPulseaudioApi; - apis.at(rtaudioWasapiApi.apicode) = rtaudioWasapiApi; - apis.at(rtaudioDsApi.apicode) = rtaudioDsApi; - apis.at(rtaudioAsioApi.apicode) = rtaudioAsioApi; -#endif - return apis; -} - - -DaqConfiguration::DaqConfiguration(const DeviceInfo &device) { - - api = device.api; - device_name = device.device_name; - - eninchannels.resize(device.ninchannels, false); - enoutchannels.resize(device.noutchannels, false); - - inchannel_sensitivities.resize(device.ninchannels, 1.0); - inchannel_metadata.resize(device.ninchannels, ""); - for (us i = 0; i < eninchannels.size(); i++) { - std::stringstream chname; - chname << "Unnamed input channel " << i; - inchannel_names.push_back(chname.str()); - - } - - outchannel_metadata.resize(device.noutchannels, ""); - outchannel_sensitivities.resize(device.noutchannels, 1.0); - for (us i = 0; i < enoutchannels.size(); i++) { - std::stringstream chname; - chname << "Unnamed output channel " << i; - outchannel_names.push_back(chname.str()); - } - sampleRateIndex = device.prefSampleRateIndex; - dataTypeIndex = device.prefDataTypeIndex; - framesPerBlockIndex = device.prefFramesPerBlockIndex; - - monitorOutput = false; - - inputIEPEEnabled.resize(device.ninchannels, false); - inputACCouplingMode.resize(device.ninchannels, false); - inputRangeIndices.resize(device.ninchannels, device.prefInputRangeIndex); - - assert(match(device)); -} - -bool DaqConfiguration::match(const DeviceInfo& dev) const { - return (dev.device_name == device_name && dev.api == api); -} - -int DaqConfiguration::getHighestInChannel() const { - for(int i=eninchannels.size()-1; i>-1;i--) { - if(eninchannels.at(i)) return i; - } - return -1; -} - -int DaqConfiguration::getHighestOutChannel() const { - for(us i=enoutchannels.size()-1; i>=0;i--) { - if(enoutchannels.at(i)) return i; - } - return -1; -} -int DaqConfiguration::getLowestInChannel() const { - for(us i=0; i= 1 && apicode <= 5) { - return createRtAudioDevice(devinfo, config); - } -#endif - else { - throw std::runtime_error(string("Unable to match API: ") + - devinfo.api.apiname); - } -} - - -Daq::Daq(const DeviceInfo &devinfo, const DaqConfiguration &config) - : DaqConfiguration(config), DeviceInfo(devinfo) { - - if (monitorOutput && !(nenoutchannels() > 0)) { - throw runtime_error( - "Output monitoring only possible when at least one output channel is enabled. Please make sure to enable at least one output channel"); - } - } - - -double Daq::samplerate() const { - mutexlock lock(mutex); - assert(sampleRateIndex < availableSampleRates.size()); - return availableSampleRates.at(sampleRateIndex); -} - - -DataType Daq::dataType() const { - mutexlock lock(mutex); - assert((us)dataTypeIndex < availableDataTypes.size()); - return availableDataTypes.at(dataTypeIndex); -} - - -double Daq::inputRangeForChannel(us ch) const { - if (!(ch < ninchannels)) { - throw runtime_error("Invalid channel number"); - } - mutexlock lock(mutex); - assert(inputRangeIndices.size() == eninchannels.size()); - return availableInputRanges.at(inputRangeIndices.at(ch)); -} - - -us Daq::neninchannels(bool include_monitorchannel) const { - mutexlock lock(mutex); - us inch = std::count(eninchannels.begin(), eninchannels.end(), true); - if (monitorOutput && include_monitorchannel) { - inch++; - } - return inch; -} - - -us Daq::nenoutchannels() const { - mutexlock lock(mutex); - return std::count(enoutchannels.begin(), enoutchannels.end(), true); -} diff --git a/lasp/device/lasp_cppdaq.h b/lasp/device/lasp_cppdaq.h deleted file mode 100644 index c342fc0..0000000 --- a/lasp/device/lasp_cppdaq.h +++ /dev/null @@ -1,332 +0,0 @@ -#ifndef LASP_CPPDAQ_H -#define LASP_CPPDAQ_H -#include "lasp_config.h" -#include "lasp_types.h" - -#ifdef LASP_HAS_RTAUDIO -#include -#endif - -#ifdef LASP_HAS_ULDAQ -#include -#endif - -#include "lasp_cppqueue.h" -#include "string" -#include "vector" -#include -#include -#include - -using std::cerr; -using std::cout; -using std::endl; -using std::getline; -using std::runtime_error; -using std::string; -using std::vector; -using std::to_string; - -typedef vector boolvec; -typedef vector dvec; -typedef vector usvec; -typedef std::lock_guard mutexlock; - -class DataType { - public: - string name; - unsigned sw; - bool is_floating; - - DataType(const char *name, unsigned sw, bool is_floating) - : name(name), sw(sw), is_floating(is_floating) {} - DataType() : name("invalid data type"), sw(0), is_floating(false) {} - bool operator==(const DataType &o) { - return (name == o.name && sw == o.sw && is_floating == o.is_floating); - } -}; - -const DataType dtype_invalid; -const DataType dtype_fl32("32-bits floating point", 4, true); -const DataType dtype_fl64("64-bits floating point", 8, true); -const DataType dtype_int8("8-bits integers", 1, false); -const DataType dtype_int24("24-bits integers", 1, false); -const DataType dtype_int16("16-bits integers", 2, false); -const DataType dtype_int32("32-bits integers", 4, false); - -const std::vector dataTypes = { - dtype_int8, dtype_int16,dtype_int24, dtype_int32, dtype_fl32, dtype_fl64, -}; - -class DaqApi { - public: - string apiname = "Invalid API"; - int apicode = -1; - unsigned api_specific_subcode = 0; - - DaqApi(string apiname, unsigned apicode, unsigned api_specific_subcode = 0) - : apiname(apiname), apicode(apicode), - api_specific_subcode(api_specific_subcode) {} - DaqApi() {} - bool operator==(const DaqApi &other) const { - return (apiname == other.apiname && apicode == other.apicode && - api_specific_subcode == other.api_specific_subcode); - } - operator string() const { return apiname + ", code: " + to_string(apicode); } - static vector getAvailableApis(); -}; - -#ifdef LASP_HAS_ULDAQ -const DaqApi uldaqapi("UlDaq", 0); -#endif -#ifdef LASP_HAS_RTAUDIO -const DaqApi rtaudioAlsaApi("RtAudio Linux ALSA", 1, RtAudio::Api::LINUX_ALSA); -const DaqApi rtaudioPulseaudioApi("RtAudio Linux Pulseaudio", 2, - RtAudio::Api::LINUX_PULSE); -const DaqApi rtaudioWasapiApi("RtAudio Windows Wasapi", 3, - RtAudio::Api::WINDOWS_WASAPI); -const DaqApi rtaudioDsApi("RtAudio Windows DirectSound", 4, - RtAudio::Api::WINDOWS_DS); -const DaqApi rtaudioAsioApi("RtAudio Windows ASIO", 5, - RtAudio::Api::WINDOWS_ASIO); -#endif - -// Structure containing device info parameters -class DeviceInfo { - public: - DaqApi api; - string device_name = ""; - - int api_specific_devindex = -1; - - vector availableDataTypes; - int prefDataTypeIndex = 0; - - vector availableSampleRates; - int prefSampleRateIndex = -1; - - vector availableFramesPerBlock; - unsigned prefFramesPerBlockIndex = 0; - - dvec availableInputRanges; - int prefInputRangeIndex = 0; - - unsigned ninchannels = 0; - unsigned noutchannels = 0; - - bool hasInputIEPE = false; - bool hasInputACCouplingSwitch = false; - bool hasInputTrigger = false; - - /* DeviceInfo(): */ - /* datatype(dtype_invalid) { } */ - - double prefSampleRate() const { - if (((us)prefSampleRateIndex < availableSampleRates.size()) && - (prefSampleRateIndex >= 0)) { - return availableSampleRates.at(prefSampleRateIndex); - } else { - throw std::runtime_error("No prefered sample rate available"); - } - } - - operator string() const { - std::stringstream str; - str << api.apiname + " " << api_specific_devindex << endl - << " number of input channels: " << ninchannels << endl - << " number of output channels: " << noutchannels << endl; - return str.str(); - } - - string serialize() const { - // Simple serializer for this object, used because we found a bit late that - // this object needs to be send over the wire. We do not want to make this - // implementation in Python, as these objects are created here, in the C++ - // code. The Python wrapper is just a readonly wrapper. - std::stringstream str; - - str << api.apiname << "\t"; - str << api.apicode << "\t"; - str << api.api_specific_subcode << "\t"; - str << device_name << "\t"; - - str << availableDataTypes.size() << "\t"; - for(const DataType& dtype: availableDataTypes) { - // WARNING: THIS GOES COMPLETELY WRONG WHEN NAMES contain A TAB!!! - str << dtype.name << "\t"; - str << dtype.sw << "\t"; - str << dtype.is_floating << "\t"; - } - str << prefDataTypeIndex << "\t"; - - str << availableSampleRates.size() << "\t"; - for(const double& fs: availableSampleRates) { - // WARNING: THIS GOES COMPLETELY WRONG WHEN NAMES contain A TAB!!! - str << fs << "\t"; - } - str << prefSampleRateIndex << "\t"; - - str << availableFramesPerBlock.size() << "\t"; - for(const us& fb: availableFramesPerBlock) { - // WARNING: THIS GOES COMPLETELY WRONG WHEN NAMES contain A TAB!!! - str << fb << "\t"; - } - str << prefFramesPerBlockIndex << "\t"; - - str << availableInputRanges.size() << "\t"; - for(const double& ir: availableInputRanges) { - // WARNING: THIS GOES COMPLETELY WRONG WHEN NAMES contain A TAB!!! - str << ir << "\t"; - } - str << prefInputRangeIndex << "\t"; - - str << ninchannels << "\t"; - str << noutchannels << "\t"; - str << int(hasInputIEPE) << "\t"; - str << int(hasInputACCouplingSwitch) << "\t"; - str << int(hasInputTrigger) << "\t"; - - return str.str(); - } - - static DeviceInfo deserialize(const string& dstr) { - DeviceInfo devinfo; - - std::stringstream str(dstr); - string tmp; - us N; - // Lambda functions for deserializing - auto nexts = [&]() { getline(str, tmp, '\t'); return tmp; }; - auto nexti = [&]() { getline(str, tmp, '\t'); return std::atoi(tmp.c_str()); }; - auto nextf = [&]() { getline(str, tmp, '\t'); return std::atof(tmp.c_str()); }; - - // Api - string apiname = nexts(); - auto apicode = nexti(); - auto api_specific_subcode = nexti(); - DaqApi api(apiname, apicode, api_specific_subcode); - devinfo.api = api; - - devinfo.device_name = nexts(); - - N = us(nexti()); - for(us i=0;i inchannel_sensitivities; - vector inchannel_names; - vector inchannel_metadata; - - vector outchannel_sensitivities; - vector outchannel_names; - vector outchannel_metadata; - - us sampleRateIndex = 0; // Index in list of sample rates - - us dataTypeIndex = 0; // Required datatype for output, should be - // present in the list - - us framesPerBlockIndex = 0; - - bool monitorOutput = false; - - boolvec inputIEPEEnabled; - boolvec inputACCouplingMode; - - usvec inputRangeIndices; - - // Create a default configuration, with all channels disabled on both - // input and output, and default channel names - DaqConfiguration(const DeviceInfo &device); - DaqConfiguration() {} - - bool match(const DeviceInfo &devinfo) const; - - int getHighestInChannel() const; - int getHighestOutChannel() const; - - int getLowestInChannel() const; - int getLowestOutChannel() const; -}; - -class Daq; -class Daq : public DaqConfiguration, public DeviceInfo { - - mutable std::mutex mutex; - - public: - static vector getDeviceInfo(); - - static Daq *createDaq(const DeviceInfo &, const DaqConfiguration &config); - - Daq(const DeviceInfo &devinfo, const DaqConfiguration &config); - - virtual void start(SafeQueue *inqueue, - SafeQueue *outqueue) = 0; - - virtual void stop() = 0; - - virtual bool isRunning() const = 0; - - virtual ~Daq(){}; - us neninchannels(bool include_monitorchannel = true) const; - us nenoutchannels() const; - - double samplerate() const; - double inputRangeForChannel(us ch) const; - DataType dataType() const; - - us framesPerBlock() const { - mutexlock lock(mutex); - return availableFramesPerBlock.at(framesPerBlockIndex); - } - bool duplexMode() const { - return (neninchannels(false) > 0 && nenoutchannels() > 0); - } -}; - -#endif // LASP_CPPDAQ_H diff --git a/lasp/device/lasp_cppqueue.h b/lasp/device/lasp_cppqueue.h deleted file mode 100644 index d76760e..0000000 --- a/lasp/device/lasp_cppqueue.h +++ /dev/null @@ -1,56 +0,0 @@ -// threadsafe_queue.h -// -// Author: J.A. de Jong -// -// Description: -// Implementation of a thread-safe queue, based on STL queue -////////////////////////////////////////////////////////////////////// -#pragma once -#ifndef THREADSAFE_QUEUE_H -#define THREADSAFE_QUEUE_H -#include -#include -#include - -// A threadsafe-queue. -template -class SafeQueue { - std::queue _queue; - mutable std::mutex _mutex; - std::condition_variable _cv; -public: - SafeQueue(): _queue(), _mutex() , _cv() - {} - - ~SafeQueue(){} - - void enqueue(T t) { - std::lock_guard lock(_mutex); - _queue.push(t); - _cv.notify_one(); - } - - T dequeue() { - std::unique_lock lock(_mutex); - while(_queue.empty()) - { - // release lock as long as the wait and reaquire it afterwards. - _cv.wait(lock); - } - T val = _queue.front(); - _queue.pop(); - return val; - } - bool empty() const { - std::unique_lock lock(_mutex); - return _queue.size()==0; - } - size_t size() const { - std::unique_lock lock(_mutex); - return _queue.size(); - } -}; - - -#endif // THREADSAFE_QUEUE_H -////////////////////////////////////////////////////////////////////// diff --git a/lasp/device/lasp_cpprtaudio.cpp b/lasp/device/lasp_cpprtaudio.cpp deleted file mode 100644 index d630008..0000000 --- a/lasp/device/lasp_cpprtaudio.cpp +++ /dev/null @@ -1,391 +0,0 @@ -#include "lasp_cpprtaudio.h" -#include -#include -#include -#include -#include - -#if MS_WIN64 -typedef uint8_t u_int8_t; -#endif - -using std::atomic; - -void fillRtAudioDeviceInfo(vector &devinfolist) { - - vector apis; - RtAudio::getCompiledApi(apis); - - for(auto api: apis) { - RtAudio rtaudio(api); - us count = rtaudio.getDeviceCount(); - for(us devno = 0; devno< count;devno++) { - - RtAudio::DeviceInfo devinfo = rtaudio.getDeviceInfo(devno); - if(!devinfo.probed) { - // Device capabilities not successfully probed. Continue to next - continue; - } - DeviceInfo d; - switch(api){ - case RtAudio::LINUX_ALSA: - d.api = rtaudioAlsaApi; - break; - case RtAudio::LINUX_PULSE: - d.api = rtaudioPulseaudioApi; - break; - case RtAudio::WINDOWS_WASAPI: - d.api = rtaudioWasapiApi; - break; - case RtAudio::WINDOWS_DS: - d.api = rtaudioDsApi; - break; - case RtAudio::WINDOWS_ASIO: - d.api = rtaudioAsioApi; - break; - default: - cerr << "Not implemented RtAudio API, skipping." << endl; - continue; - break; - } - - d.device_name = devinfo.name; - d.api_specific_devindex = devno; - - for(us j=0; j *inqueue = NULL; - SafeQueue *outqueue = NULL; - SafeQueue *outDelayqueue = NULL; - - RtAudio* rtaudio = NULL; - RtAudio::StreamParameters* instreamparams = nullptr; - RtAudio::StreamParameters* outstreamparams = nullptr; - - us nFramesPerBlock; - - public: - AudioDaq(const DeviceInfo& devinfo, - const DaqConfiguration& config): - Daq(devinfo, config) { - - nFramesPerBlock = this->framesPerBlock(); - - if(neninchannels(false) > 0) { - instreamparams = new RtAudio::StreamParameters(); - instreamparams->nChannels = getHighestInChannel() + 1; - if(instreamparams->nChannels < 1) { - throw runtime_error("Invalid input number of channels"); - } - instreamparams->firstChannel = 0; - instreamparams->deviceId = devinfo.api_specific_devindex; - } - - if(nenoutchannels() > 0) { - outstreamparams = new RtAudio::StreamParameters(); - outstreamparams->nChannels = getHighestOutChannel() + 1; - if(outstreamparams->nChannels < 1) { - throw runtime_error("Invalid output number of channels"); - } - outstreamparams->firstChannel = 0; - outstreamparams->deviceId = devinfo.api_specific_devindex; - } - - RtAudio::StreamOptions streamoptions; - streamoptions.flags = RTAUDIO_NONINTERLEAVED | RTAUDIO_HOG_DEVICE; - - streamoptions.numberOfBuffers = 2; - streamoptions.streamName = "RtAudio stream"; - streamoptions.priority = 0; - - RtAudioFormat format; - DataType dtype = dataType(); - if(dtype == dtype_fl32) { - format = RTAUDIO_FLOAT32; - } else if(dtype == dtype_fl64) { - format = RTAUDIO_FLOAT64; - } else if(dtype == dtype_int8) { - format = RTAUDIO_SINT8; - } else if(dtype == dtype_int16) { - format = RTAUDIO_SINT16; - } else if(dtype == dtype_int32) { - format = RTAUDIO_SINT32; - } else { - throw runtime_error("Invalid data type"); - } - - try { - rtaudio = new RtAudio((RtAudio::Api) devinfo.api.api_specific_subcode); - if(!rtaudio) { - throw runtime_error("RtAudio allocation failed"); - } - rtaudio->openStream( - outstreamparams, - instreamparams, - format, - (us) samplerate(), - (unsigned*) &nFramesPerBlock, - &mycallback, - (void*) this, - &streamoptions, - &myerrorcallback - ); - } catch(RtAudioError& e) { - if(rtaudio) delete rtaudio; - if(instreamparams) delete instreamparams; - if(outstreamparams) delete outstreamparams; - throw; - } - if(monitorOutput) { - outDelayqueue = new SafeQueue(); - } - - } - - friend int mycallback(void *outputBuffer, void *inputBuffer, - unsigned int nFrames, - double streamTime, - RtAudioStreamStatus status, - void *userData); - - - void start(SafeQueue *inqueue, SafeQueue *outqueue) { - this->inqueue = inqueue; - this->outqueue = outqueue; - if(monitorOutput) { - this->outDelayqueue = new SafeQueue(); - - } - - if(isRunning()){ - throw runtime_error("Stream already running"); - } - - if(neninchannels(false) > 0 && !inqueue) { - throw runtime_error("inqueue argument not given"); - } - if(nenoutchannels() > 0 && !outqueue) { - throw runtime_error("outqueue argument not given"); - } - assert(rtaudio); - rtaudio->startStream(); - - } - - void stop() { - - if(!isRunning()) { - cerr << "Stream is already stopped" << endl; - } - else { - assert(rtaudio); - rtaudio->stopStream(); - } - if(inqueue) { - inqueue = nullptr; - } - if(outqueue) { - outqueue = nullptr; - } - if(outDelayqueue) { - delete outDelayqueue; - outDelayqueue = nullptr; - } - } - bool isRunning() const {return (rtaudio && rtaudio->isStreamRunning());} - - ~AudioDaq() { - assert(rtaudio); - if(isRunning()) { - stop(); - } - if(rtaudio->isStreamOpen()) { - rtaudio->closeStream(); - } - - if(rtaudio) delete rtaudio; - if(outDelayqueue) delete outDelayqueue; - if(instreamparams) delete instreamparams; - if(outstreamparams) delete outstreamparams; - - } -}; - - -Daq* createRtAudioDevice(const DeviceInfo& devinfo, - const DaqConfiguration& config) { - - AudioDaq *daq = NULL; - - try { - daq = new AudioDaq(devinfo, config); - - } catch (runtime_error &e) { - if (daq) - delete daq; - throw; - } - return daq; -} - - -int mycallback( - void *outputBuffervoid, - void *inputBuffervoid, - unsigned int nFrames, - double streamTime, - RtAudioStreamStatus status, - void *userData) { - - u_int8_t* inputBuffer = (u_int8_t*) inputBuffervoid; - u_int8_t* outputBuffer = (u_int8_t*) outputBuffervoid; - - AudioDaq* daq = (AudioDaq*) userData; - DataType dtype = daq->dataType(); - us neninchannels_inc_mon = daq->neninchannels(); - us nenoutchannels = daq->nenoutchannels(); - - bool monitorOutput = daq->monitorOutput; - us bytesperchan = dtype.sw*nFrames; - us monitorOffset = ((us) monitorOutput)*bytesperchan; - - SafeQueue *inqueue = daq->inqueue; - SafeQueue *outqueue = daq->outqueue; - SafeQueue *outDelayqueue = daq->outDelayqueue; - - const boolvec& eninchannels = daq->eninchannels; - const boolvec& enoutchannels = daq->enoutchannels; - - if(inputBuffer || monitorOutput) { - - u_int8_t *inbuffercpy = (u_int8_t*) malloc(bytesperchan*neninchannels_inc_mon); - if(inputBuffer) { - us j=0; // OUR buffer channel counter - us i=0; // RtAudio channel counter - for(int ch=daq->getLowestInChannel();ch<=daq->getHighestInChannel();ch++) { - if(eninchannels[ch]) { - memcpy( - &(inbuffercpy[monitorOffset+j*bytesperchan]), - &(inputBuffer[i*bytesperchan]), - bytesperchan); - j++; - } - i++; - } - } - if(monitorOutput) { - assert(outDelayqueue); - - if(!daq->outDelayqueue->empty()) { - void* dat = daq->outDelayqueue->dequeue(); - memcpy((void*) inbuffercpy, dat, bytesperchan); - free(dat); - } else { - cerr << "Warning: output delay queue appears empty!" << endl; - memset(inbuffercpy, 0, bytesperchan); - } - } - assert(inqueue); - inqueue->enqueue(inbuffercpy); - - } - - if(outputBuffer) { - assert(outqueue); - if(!outqueue->empty()) { - u_int8_t* outbuffercpy = (u_int8_t*) outqueue->dequeue(); - us j=0; // OUR buffer channel counter - us i=0; // RtAudio channel counter - for(us ch=0;ch<=daq->getHighestOutChannel();ch++) { - /* cerr << "Copying from queue... " << endl; */ - if(enoutchannels[ch]) { - memcpy( - &(outputBuffer[i*bytesperchan]), - &(outbuffercpy[j*bytesperchan]), - bytesperchan); - j++; - } - else { - /* cerr << "unused output channel in list" << endl; */ - memset( - &(outputBuffer[i*bytesperchan]),0,bytesperchan); - } - i++; - } - if(!monitorOutput) { - free(outbuffercpy); - } else { - assert(outDelayqueue); - outDelayqueue->enqueue((void*) outbuffercpy); - } - } - else { - cerr << "RtAudio backend: stream output buffer underflow!" << endl; - } - - - } - - return 0; -} -void myerrorcallback(RtAudioError::Type,const string& errorText) { - cerr << errorText << endl; -} diff --git a/lasp/device/lasp_cpprtaudio.h b/lasp/device/lasp_cpprtaudio.h deleted file mode 100644 index cbe7dca..0000000 --- a/lasp/device/lasp_cpprtaudio.h +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef RTAUDIO_H -#define RTAUDIO_H -#include "lasp_cppdaq.h" - -Daq* createRtAudioDevice(const DeviceInfo& devinfo, - const DaqConfiguration& config); - -void fillRtAudioDeviceInfo(vector &devinfolist); - -#endif // RTAUDIO_H - - - diff --git a/lasp/device/lasp_cppthread.h b/lasp/device/lasp_cppthread.h deleted file mode 100644 index 2f9311a..0000000 --- a/lasp/device/lasp_cppthread.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once -#ifndef LASP_CPPTHREAD_H -#define LASP_CPPTHREAD_H -#include -#include -#include -// This is a small wrapper around the std library thread. - -template -class CPPThread { - std::thread _thread; - public: - CPPThread(F threadfcn, T data) : - _thread(threadfcn, data) { } - - void join() { - assert(_thread.joinable()); - _thread.join(); - } - /* ~CPPThread() { */ - - /* } */ - -}; - -void CPPsleep_ms(unsigned int ms) { - std::this_thread::sleep_for(std::chrono::milliseconds(ms)); -} -void CPPsleep_us(unsigned int us) { - std::this_thread::sleep_for(std::chrono::microseconds(us)); -} - - -#endif // LASP_CPPTHREAD_H diff --git a/lasp/device/lasp_cppuldaq.cpp b/lasp/device/lasp_cppuldaq.cpp deleted file mode 100644 index b68a861..0000000 --- a/lasp/device/lasp_cppuldaq.cpp +++ /dev/null @@ -1,636 +0,0 @@ -#include "lasp_cppuldaq.h" -#include "lasp_config.h" -#include "lasp_tracer.h" -#include -#include -#include -#include -#include -#include -#include - -using std::atomic; - -/* using std::this_thread; */ -const us MAX_DEV_COUNT_PER_API = 100; -const us UL_ERR_MSG_LEN = 512; - -inline void showErr(UlError err) { - if (err != ERR_NO_ERROR) { - char errmsg[UL_ERR_MSG_LEN]; - ulGetErrMsg(err, errmsg); - std::cerr << "UlError: " << errmsg << std::endl; - } -} - -class DT9837A; -void threadfcn(DT9837A *td); - -class DT9837A : public Daq { - - atomic stopThread; - DaqDeviceHandle handle = 0; - - std::thread *thread = NULL; - SafeQueue *inqueue = NULL; - SafeQueue *outqueue = NULL; - - double *inbuffer = NULL; - double *outbuffer = NULL; - - us nFramesPerBlock; - - public: - DT9837A(const DeviceInfo &devinfo, const DaqConfiguration &config) - : Daq(devinfo, config) { - - // Some sanity checks - if (eninchannels.size() != 4) { - throw runtime_error("Invalid length of enabled inChannels vector"); - } - - if (enoutchannels.size() != 1) { - throw runtime_error("Invalid length of enabled outChannels vector"); - } - - stopThread = false; - - nFramesPerBlock = availableFramesPerBlock[framesPerBlockIndex]; - - if (nFramesPerBlock < 24 || nFramesPerBlock > 8192) { - throw runtime_error("Unsensible number of samples per block chosen"); - } - - if (samplerate() < 10000 || samplerate() > 51000) { - throw runtime_error("Invalid sample rate"); - } - - DaqDeviceDescriptor devdescriptors[MAX_DEV_COUNT_PER_API]; - DaqDeviceDescriptor descriptor; - DaqDeviceInterface interfaceType = ANY_IFC; - - UlError err; - - us numdevs = MAX_DEV_COUNT_PER_API; - err = ulGetDaqDeviceInventory(interfaceType, devdescriptors, (unsigned*) &numdevs); - if (err != ERR_NO_ERROR) { - throw runtime_error("Device inventarization failed"); - } - - if ((us) api_specific_devindex >= numdevs) { - throw runtime_error("Device number {deviceno} too high {err}. This could " - "happen when the device is currently not connected"); - } - - descriptor = devdescriptors[api_specific_devindex]; - - // get a handle to the DAQ device associated with the first descriptor - handle = ulCreateDaqDevice(descriptor); - - if (handle == 0) { - throw runtime_error("Unable to create a handle to the specified DAQ " - "device. Is the device currently in use? Please make sure to set " - "the DAQ configuration in duplex mode if simultaneous input and " - "output is required."); - } - - err = ulConnectDaqDevice(handle); - if (err != ERR_NO_ERROR) { - showErr(err); - ulReleaseDaqDevice(handle); - handle = 0; - throw runtime_error("Unable to connect to device: {err}"); - } - - for (us ch = 0; ch < 4; ch++) { - - err = ulAISetConfigDbl(handle, AI_CFG_CHAN_SENSOR_SENSITIVITY, ch, 1.0); - showErr(err); - if (err != ERR_NO_ERROR) { - throw runtime_error("Fatal: could normalize channel sensitivity"); - } - - CouplingMode cm = inputACCouplingMode[ch] ? CM_AC : CM_DC; - err = ulAISetConfig(handle, AI_CFG_CHAN_COUPLING_MODE, ch, cm); - if (err != ERR_NO_ERROR) { - showErr(err); - throw runtime_error("Fatal: could not set AC/DC coupling mode"); - } - - IepeMode iepe = inputIEPEEnabled[ch] ? IEPE_ENABLED : IEPE_DISABLED; - err = ulAISetConfig(handle, AI_CFG_CHAN_IEPE_MODE, ch, iepe); - if (err != ERR_NO_ERROR) { - showErr(err); - throw runtime_error("Fatal: could not set IEPE mode"); - } - } - } - - DT9837A(const DT9837A &) = delete; - - ~DT9837A() { - UlError err; - if (isRunning()) { - stop(); - } - - if (handle) { - err = ulDisconnectDaqDevice(handle); - showErr(err); - err = ulReleaseDaqDevice(handle); - showErr(err); - } - } - - bool isRunning() const { return bool(thread); } - - void start(SafeQueue *inqueue, SafeQueue *outqueue) { - if (isRunning()) { - throw runtime_error("Thread is already running"); - } - - bool hasinput = neninchannels() > 0; - bool hasoutput = nenoutchannels() > 0; - - if (hasinput && !inqueue) { - throw runtime_error("Inqueue not given, while input is enabled"); - } - - if (hasoutput && !outqueue) { - throw runtime_error("outqueue not given, while output is enabled"); - } - - if (hasinput) { - assert(!inbuffer); - inbuffer = - new double[neninchannels() * nFramesPerBlock * 2]; // Watch the 2! - } - if (hasoutput) { - assert(!outbuffer); - outbuffer = - new double[nenoutchannels() * nFramesPerBlock * 2]; // Watch the 2! - } - this->inqueue = inqueue; - this->outqueue = outqueue; - - stopThread = false; - thread = new std::thread(threadfcn, this); - } - - void stop() { - if (!isRunning()) { - throw runtime_error("No data acquisition running"); - } - assert(thread); - - stopThread = true; - thread->join(); - delete thread; - thread = NULL; - - outqueue = NULL; - inqueue = NULL; - if (inbuffer) { - delete inbuffer; - inbuffer = nullptr; - } - if (outbuffer) { - delete outbuffer; - outbuffer = nullptr; - } - } - - friend void threadfcn(DT9837A *); -}; - -/** - * @brief Create an empty buffer and fill it with zeros. - * - * @param size The number of elements in the array - * - * @return Pointer to the array - */ -static double* createZeroBuffer(size_t size) { - - double* buf = static_cast( - malloc(sizeof(double) * size)); - - for (us sample = 0; sample < size; sample++) { - buf[sample] = 0; - } - return buf; -} -/** - * @brief Copy samples from one linear array to the next. - * - * @param[in] from Buffer to copy from - * @param[out] to Buffer to copy to - * @param startFrom The position to start in the from-buffer - * @param startTo The position to start in the to-buffer - * @param N The number of samples to copy. - */ -static inline void copySamples(double* from,double* to, - const us startFrom,const us startTo,const us N) { - - for (us sample = 0; sample < N; sample++) { - to[startTo + sample] = from[startFrom + sample]; - } -} - -void threadfcn(DT9837A *td) { - - std::cerr << "Starting DAQ Thread fcn" << endl; - - const us nenoutchannels = td->nenoutchannels(); - us neninchannels = td->neninchannels(); - const us nFramesPerBlock = td->nFramesPerBlock; - - const bool hasinput = neninchannels > 0; - const bool hasoutput = nenoutchannels > 0; - - bool monitorOutput = td->monitorOutput; - - double *inbuffer = td->inbuffer; - double *outbuffer = td->outbuffer; - - SafeQueue *inqueue = td->inqueue; - SafeQueue *outqueue = td->outqueue; - - ScanStatus inscanstat; - ScanStatus outscanstat; - TransferStatus inxstat, outxstat; - - double samplerate = td->samplerate(); - - const double sleeptime = ((double)nFramesPerBlock) / (4 * samplerate); - const us sleeptime_us = (us)(sleeptime * 1e6); - /* cerr << "Sleep time in loop: " << sleeptime_us << "us." << endl; */ - if (sleeptime_us < 10) { - cerr << "ERROR: Too small buffer size (nFramesPerBlock) chosen!" << endl; - return; - } - - const us buffer_mid_idx_in = neninchannels * nFramesPerBlock; - const us buffer_mid_idx_out = nenoutchannels * nFramesPerBlock; - - DaqDeviceHandle handle = td->handle; - assert(handle); - - DaqInScanFlag inscanflags = DAQINSCAN_FF_DEFAULT; - AOutScanFlag outscanflags = AOUTSCAN_FF_DEFAULT; - ScanOption scanoptions = SO_CONTINUOUS; - UlError err = ERR_NO_ERROR; - - DaqInChanDescriptor *indesc = NULL; - - bool topinenqueued = true; - // Start with true here, to not copy here the first time - bool botinenqueued = true; - - bool topoutenqueued = true; - bool botoutenqueued = true; - - size_t inTotalCount = 0; - size_t outTotalCount = 0; - - // initialize output, if any - if (hasoutput) { - assert(nenoutchannels == 1); - assert(outqueue); - - // Initialize the buffer with zeros, before pushing any data. - for (us sample = 0; sample < 2 * nFramesPerBlock; sample++) { - outbuffer[sample] = 0; - } - - cerr << "Starting output DAC" << endl; - err = ulAOutScan(handle, 0, 0, BIP10VOLTS, - /* BIP60VOLTS, */ - 2 * td->nFramesPerBlock, // Watch the 2 here! - &samplerate, scanoptions, outscanflags, outbuffer); - - if (err != ERR_NO_ERROR) { - showErr(err); - goto exit; - } - } - - // Initialize input, if any - if (hasinput) { - indesc = new DaqInChanDescriptor[neninchannels]; - us j = 0; - for (us chin = 0; chin < 4; chin++) { - if (td->eninchannels[chin] == true) { - indesc[j].type = DAQI_ANALOG_SE; - indesc[j].channel = chin; - - double rangeval = td->inputRangeForChannel(chin); - Range rangenum; - if (abs(rangeval - 1.0) < 1e-8) { - rangenum = BIP1VOLTS; - } else if (abs(rangeval - 10.0) < 1e-8) { - rangenum = BIP10VOLTS; - } else { - std::cerr << "Fatal: input range value is invalid" << endl; - goto exit; - } - indesc[j].range = rangenum; - j++; - } - } - // Overwrite last channel - if (monitorOutput) { - indesc[j].type = DAQI_DAC; - indesc[j].channel = 0; - indesc[j].range = BIP10VOLTS; - j++; - } - assert(j == neninchannels); - - cerr << "Starting input ADC" << endl; - err = ulDaqInScan(handle, indesc, neninchannels, - 2 * td->nFramesPerBlock, // Watch the 2 here! - &samplerate, scanoptions, inscanflags, inbuffer); - if (err != ERR_NO_ERROR) { - showErr(err); - goto exit; - } - } - - // Runs scan status on output, to catch up with position - if (hasoutput) { - err = ulAOutScanStatus(handle, &outscanstat, &outxstat); - if (err != ERR_NO_ERROR) { - showErr(err); - goto exit; - } - outTotalCount = outxstat.currentScanCount; - assert(outscanstat == SS_RUNNING); - } - - /* std::cerr << "Entering while loop" << endl; */ - /* std::cerr << "hasinput: " << hasinput << endl; */ - while (!td->stopThread && err == ERR_NO_ERROR) { - /* std::cerr << "While..." << endl; */ - if (hasoutput) { - err = ulAOutScanStatus(handle, &outscanstat, &outxstat); - if (err != ERR_NO_ERROR) { - showErr(err); - goto exit; - } - assert(outscanstat == SS_RUNNING); - - if (outxstat.currentScanCount > outTotalCount + 2 * nFramesPerBlock) { - cerr << "***** WARNING: Missing output sample blocks, DAQ Scan count=" - << outxstat.currentScanCount - << " while loop count = " << outTotalCount - << ", probably due to too small buffer size. *****" << endl; - } - outTotalCount = outxstat.currentScanCount; - - /* std::cerr << "Samples scanned: " << outxstat.currentTotalCount << - * endl; - */ - if (outxstat.currentIndex < buffer_mid_idx_out) { - topoutenqueued = false; - if (!botoutenqueued) { - /* cerr << "Copying output buffer to bottom" << endl; */ - double *bufcpy; - assert(nenoutchannels > 0); - if (!outqueue->empty()) { - bufcpy = (double *)outqueue->dequeue(); - } else { - cerr << "******* WARNING: OUTPUTQUEUE UNDERFLOW, FILLING SIGNAL " - "QUEUE WITH ZEROS ***********" - << endl; - bufcpy = createZeroBuffer(nFramesPerBlock*nenoutchannels); - } - copySamples(bufcpy, outbuffer, 0, buffer_mid_idx_out, nFramesPerBlock); - free(bufcpy); - botoutenqueued = true; - } - } else { - botoutenqueued = false; - if (!topoutenqueued) { - /* cerr << "Copying output buffer to top" << endl; */ - double *bufcpy; - assert(nenoutchannels > 0); - if (!outqueue->empty()) { - bufcpy = (double *)outqueue->dequeue(); - } else { - cerr << "******* WARNING: OUTPUTQUEUE UNDERFLOW, FILLING SIGNAL " - "QUEUE WITH ZEROS ***********" - << endl; - bufcpy = createZeroBuffer(nFramesPerBlock*nenoutchannels); - } - copySamples(bufcpy, outbuffer, 0, 0, nFramesPerBlock); - free(bufcpy); - topoutenqueued = true; - } - } - } - - if (hasinput) { - err = ulDaqInScanStatus(handle, &inscanstat, &inxstat); - if (err != ERR_NO_ERROR) { - showErr(err); - goto exit; - } - assert(inscanstat == SS_RUNNING); - if (inxstat.currentScanCount > inTotalCount + 2 * nFramesPerBlock) { - cerr << "***** ERROR: Missing input sample blocks, count=" - << inxstat.currentScanCount - << ", probably due to too small buffer size. Exiting thread. " - "*****" - << endl; - break; - } - inTotalCount = inxstat.currentScanCount; - - if ((us) inxstat.currentIndex < buffer_mid_idx_in) { - topinenqueued = false; - if (!botinenqueued) { - /* cerr << "Copying in buffer bot" << endl; */ - double *bufcpy = static_cast( - malloc(sizeof(double) * nFramesPerBlock * neninchannels)); - us monitoroffset = monitorOutput ? 1 : 0; - assert(neninchannels > 0); - for (us channel = 0; channel < (neninchannels - monitoroffset); - channel++) { - for (us sample = 0; sample < nFramesPerBlock; sample++) { - bufcpy[(monitoroffset + channel) * nFramesPerBlock + sample] = - inbuffer[buffer_mid_idx_in + sample * neninchannels + - channel]; - } - } - if (monitorOutput) { - // Monitor output goes to first channel, that is - // our convention - us channel = neninchannels - 1; - for (us sample = 0; sample < nFramesPerBlock; sample++) { - bufcpy[sample] = inbuffer[buffer_mid_idx_in + - sample * neninchannels + channel]; - } - } - inqueue->enqueue((void *)bufcpy); - botinenqueued = true; - } - } else { - botinenqueued = false; - if (!topinenqueued) { - double *bufcpy = static_cast( - malloc(sizeof(double) * nFramesPerBlock * neninchannels)); - us monitoroffset = monitorOutput ? 1 : 0; - assert(neninchannels > 0); - for (us channel = 0; channel < (neninchannels - monitoroffset); - channel++) { - for (us sample = 0; sample < nFramesPerBlock; sample++) { - bufcpy[(monitoroffset + channel) * nFramesPerBlock + sample] = - inbuffer[sample * neninchannels + channel]; - } - } - if (monitorOutput) { - // Monitor output goes to first channel, that is - // our convention - us channel = neninchannels - 1; - for (us sample = 0; sample < nFramesPerBlock; sample++) { - bufcpy[sample] = inbuffer[sample * neninchannels + channel]; - } - } - - /* cerr << "Copying in buffer top" << endl; */ - inqueue->enqueue((void *)bufcpy); - topinenqueued = true; - } - } - } - - std::this_thread::sleep_for(std::chrono::microseconds(sleeptime_us)); - - } // End of while loop - /* std::cerr << "Exit of while loop" << endl; */ - -exit: - - if (hasoutput) { - ulAOutScanStop(handle); - if (err != ERR_NO_ERROR) { - showErr(err); - } - } - - if (hasinput) { - ulDaqInScanStop(handle); - if (err != ERR_NO_ERROR) { - showErr(err); - } - } - - if (indesc) - delete indesc; - std::cerr << "Exit of DAQ thread fcn" << endl; -} - -Daq *createUlDaqDevice(const DeviceInfo &devinfo, - const DaqConfiguration &config) { - - DT9837A *daq = NULL; - - try { - daq = new DT9837A(devinfo, config); - - } catch (runtime_error &e) { - if (daq) - delete daq; - throw; - } - return daq; -} - - -void fillUlDaqDeviceInfo(vector &devinfolist) { - - fsTRACE(15); - - UlError err; - unsigned int numdevs = MAX_DEV_COUNT_PER_API; - - DaqDeviceDescriptor devdescriptors[MAX_DEV_COUNT_PER_API]; - DaqDeviceDescriptor descriptor; - DaqDeviceInterface interfaceType = ANY_IFC; - - err = ulGetDaqDeviceInventory(interfaceType, devdescriptors,static_cast(&numdevs)); - - if (err != ERR_NO_ERROR) { - throw std::runtime_error("UlDaq device inventarization failed"); - } - - for (unsigned i = 0; i < numdevs; i++) { - - descriptor = devdescriptors[i]; - - DeviceInfo devinfo; - devinfo.api = uldaqapi; - string name, interface; - - if (string(descriptor.productName) == "DT9837A") { - if (descriptor.devInterface == USB_IFC) { - name = "USB - "; - } else if (descriptor.devInterface == BLUETOOTH_IFC) { - /* devinfo. */ - name = "Bluetooth - "; - } else if (descriptor.devInterface == ETHERNET_IFC) { - /* devinfo. */ - name = "Ethernet - "; - } - - name += string(descriptor.productName) + " "; - name += string(descriptor.uniqueId); - devinfo.device_name = name; - - devinfo.api_specific_devindex = i; - devinfo.availableDataTypes.push_back(dtype_fl64); - devinfo.prefDataTypeIndex = 0; - - devinfo.availableSampleRates.push_back(8000); - devinfo.availableSampleRates.push_back(10000); - devinfo.availableSampleRates.push_back(11025); - devinfo.availableSampleRates.push_back(16000); - devinfo.availableSampleRates.push_back(20000); - devinfo.availableSampleRates.push_back(22050); - devinfo.availableSampleRates.push_back(24000); - devinfo.availableSampleRates.push_back(32000); - devinfo.availableSampleRates.push_back(44056); - devinfo.availableSampleRates.push_back(44100); - devinfo.availableSampleRates.push_back(47250); - devinfo.availableSampleRates.push_back(48000); - devinfo.availableSampleRates.push_back(50000); - devinfo.availableSampleRates.push_back(50400); - devinfo.availableSampleRates.push_back(51000); - - devinfo.prefSampleRateIndex = 11; - - devinfo.availableFramesPerBlock.push_back(512); - devinfo.availableFramesPerBlock.push_back(1024); - devinfo.availableFramesPerBlock.push_back(2048); - devinfo.availableFramesPerBlock.push_back(4096); - devinfo.availableFramesPerBlock.push_back(8192); - devinfo.prefFramesPerBlockIndex = 2; - - devinfo.availableInputRanges = {1.0, 10.0}; - devinfo.prefInputRangeIndex = 0; - - devinfo.ninchannels = 4; - devinfo.noutchannels = 1; - - devinfo.hasInputIEPE = true; - devinfo.hasInputACCouplingSwitch = true; - devinfo.hasInputTrigger = true; - - // Finally, this devinfo is pushed back in list - devinfolist.push_back(devinfo); - } - } - - feTRACE(15); -} diff --git a/lasp/device/lasp_cppuldaq.h b/lasp/device/lasp_cppuldaq.h deleted file mode 100644 index 59f8cea..0000000 --- a/lasp/device/lasp_cppuldaq.h +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef ULDAQ_H -#define ULDAQ_H -#include "lasp_cppqueue.h" -#include "lasp_cppdaq.h" - -Daq* createUlDaqDevice(const DeviceInfo& devinfo, - const DaqConfiguration& config); - -void fillUlDaqDeviceInfo(vector &devinfolist); - -#endif // ULDAQ_H - - diff --git a/lasp/device/lasp_daq.pxd b/lasp/device/lasp_daq.pxd deleted file mode 100644 index fa8cc8d..0000000 --- a/lasp/device/lasp_daq.pxd +++ /dev/null @@ -1,13 +0,0 @@ -include "lasp_common_decls.pxd" - -ctypedef struct PyStreamData - -cdef class Daq: - cdef: - PyStreamData *sd - cppDaq* daq_device - cdef public: - double samplerate - unsigned int nFramesPerBlock - - cdef cleanupStream(self, PyStreamData* sd) diff --git a/lasp/device/lasp_daq.pyx b/lasp/device/lasp_daq.pyx deleted file mode 100644 index f1d094d..0000000 --- a/lasp/device/lasp_daq.pyx +++ /dev/null @@ -1,329 +0,0 @@ -cimport cython -from ..lasp_common import AvType -from .lasp_deviceinfo cimport DeviceInfo -from .lasp_daqconfig cimport DaqConfiguration -from numpy cimport import_array - -from cpython.ref cimport PyObject,Py_INCREF, Py_DECREF -import numpy as np -import logging - -__all__ = ['Daq'] - -cdef cnp.NPY_TYPES getCNumpyDataType(DataType& dt): - if(dt == dtype_fl32): - return cnp.NPY_FLOAT32 - elif(dt == dtype_fl64): - return cnp.NPY_FLOAT64 - elif(dt == dtype_int8): - return cnp.NPY_INT8 - elif(dt == dtype_int16): - return cnp.NPY_INT16 - elif(dt == dtype_int32): - return cnp.NPY_INT32 - else: - raise ValueError('Unknown data type') - -cdef getNumpyDataType(DataType& dt): - if(dt == dtype_fl32): - return np.dtype(np.float32) - elif(dt == dtype_fl64): - return np.dtype(np.float64) - elif(dt == dtype_int8): - return np.dtype(np.int8) - elif(dt == dtype_int16): - return np.dtype(np.int16) - elif(dt == dtype_int32): - return np.dtype(np.int32) - else: - raise ValueError('Unknown data type') - -DEF QUEUE_BUFFER_TIME = 0.5 - -ctypedef struct PyStreamData: - PyObject* pyCallback - - # Flag used to pass the stopThread. - atomic[bool] stopThread - - # Flag to indicate that the signal generator queue has been filled for the - # first time. - atomic[bool] ready - - # Number of frames per block - unsigned nFramesPerBlock - - # Number of bytes per channel - unsigned int nBytesPerChan - - unsigned ninchannels - unsigned noutchannels - - double samplerate - - cnp.NPY_TYPES npy_format - - # If either of these queue pointers are NULL, it means the stream does not have an - # input, or output. - SafeQueue[void*] *inQueue - SafeQueue[void*] *outQueue - CPPThread[void*, void (*)(void*)] *thread - -cdef void audioCallbackPythonThreadFunction(void* voidsd) nogil: - cdef: - PyStreamData* sd = voidsd - - double* inbuffer = NULL - double* outbuffer = NULL - - unsigned noutchannels= sd.noutchannels - unsigned ninchannels= sd.ninchannels - unsigned nBytesPerChan= sd.nBytesPerChan - unsigned nFramesPerBlock= sd.nFramesPerBlock - - double sleeptime = ( sd.nFramesPerBlock)/(8*sd.samplerate); - # Sleep time in microseconds - us sleeptime_us = (sleeptime*1e6); - - us nblocks_buffer = max(1, (QUEUE_BUFFER_TIME * sd.samplerate / - sd.nFramesPerBlock)) - - with gil: - import_array() - npy_format = cnp.NPY_FLOAT64 - callback = sd.pyCallback - # print(f'Number of input channels: {ninchannels}') - # print(f'Number of out channels: {noutchannels}') - # fprintf(stderr, 'Sleep time: %d us\n', sleeptime_us) - - if sd.outQueue: - for i in range(nblocks_buffer): - outbuffer = malloc(sizeof(double)*nBytesPerChan*noutchannels) - memset(outbuffer, 0, sizeof(double)*nBytesPerChan*noutchannels) - sd.outQueue.enqueue( outbuffer) - sd.ready.store(True) - - while not sd.stopThread.load(): - with gil: - if sd.outQueue: - while sd.outQueue.size() < nblocks_buffer: - outbuffer = malloc(sizeof(double)*nBytesPerChan*noutchannels) - - npy_output = data_to_ndarray( - outbuffer, - nFramesPerBlock, - noutchannels, - sd.npy_format, - False, # Do not transfer ownership to the temporary - # Numpy container - True) # F-contiguous - try: - rval = callback(None, npy_output, nFramesPerBlock) - - except Exception as e: - logging.error('exception in Cython callback for audio output: ', str(e)) - return - - sd.outQueue.enqueue( outbuffer) - - if sd.inQueue and not sd.inQueue.empty(): - # Waiting indefinitely on the queue... - inbuffer = sd.inQueue.dequeue() - if inbuffer == NULL: - logging.debug('Received empty buffer on input, stopping thread...\n') - return - - npy_input = data_to_ndarray( - inbuffer, - nFramesPerBlock, - ninchannels, - sd.npy_format, - True, # Do transfer ownership - True) # F-contiguous is True: data is Fortran-cont. - - try: - rval = callback(npy_input, None, nFramesPerBlock) - - except Exception as e: - logging.error('exception in cython callback for audio input: ', str(e)) - return - - CPPsleep_us(sleeptime_us); - - # Outputbuffer is free'ed by the audiothread, so should not be touched - # here. - outbuffer = NULL - - # Inputbuffer memory is owned by Numpy, so should not be free'ed - inbuffer = NULL - -cdef class Daq: - - def __cinit__(self, DeviceInfo pydevinfo, DaqConfiguration pydaqconfig): - - """ - Acquires a daq handle, and opens the device - - """ - cdef: - cppDaqConfiguration* daqconfig - cppDeviceInfo* devinfo - vector[cppDeviceInfo] devinfos - - self.daq_device = NULL - self.sd = NULL - - daqconfig = &(pydaqconfig.config) - devinfo = &(pydevinfo.devinfo) - - try: - self.daq_device = cppDaq.createDaq(devinfo[0], daqconfig[0]) - except Exception as e: - raise - self.nFramesPerBlock = self.daq_device.framesPerBlock() - self.samplerate = self.daq_device.samplerate() - - if self.nFramesPerBlock > 8192 or self.nFramesPerBlock < 512: - del self.daq_device - raise ValueError('Invalid number of nFramesPerBlock') - - def __dealloc__(self): - # fprintf(stderr, "UlDaq.__dealloc__\n") - if self.sd is not NULL: - logging.debug("UlDaq.__dealloc__: stopping stream.") - self.stop() - - if self.daq_device is not NULL: - del self.daq_device - self.daq_device = NULL - - def getNumpyDataType(self): - cdef: - DataType dt = self.daq_device.dataType() - return getNumpyDataType(dt) - - - def isRunning(self): - return self.sd is not NULL - - @staticmethod - def getDeviceInfo(): - cdef: - vector[cppDeviceInfo] devinfos = cppDaq.getDeviceInfo() - pydevinfo = {} - for i in range(devinfos.size()): - d = DeviceInfo() - d.devinfo = devinfos[i] - if d.api not in pydevinfo.keys(): - pydevinfo[d.api] = [d] - else: - pydevinfo[d.api].append(d) - - return pydevinfo - - @cython.nonecheck(True) - def start(self, audiocallback): - """ - Opens a stream with specified parameters - - Args: - avstream: AvStream instance - - Returns: None - """ - if self.sd is not NULL: - assert self.daq_device is not NULL - raise RuntimeError('Stream is already opened.') - - cdef: - cppDaq* daq = self.daq_device - unsigned nFramesPerBlock = self.nFramesPerBlock - DataType dtype = self.daq_device.dataType() - double samplerate = self.samplerate - - - # All set, allocate the stream! - self.sd = malloc(sizeof(PyStreamData)) - if self.sd == NULL: - del daq - raise MemoryError('Could not allocate stream: memory error.') - - - self.sd.stopThread.store(False) - self.sd.ready.store(False) - - self.sd.inQueue = NULL - self.sd.outQueue = NULL - - self.sd.thread = NULL - self.sd.samplerate = samplerate - - self.sd.ninchannels = daq.neninchannels(True) - self.sd.noutchannels = daq.nenoutchannels() - self.sd.nBytesPerChan = nFramesPerBlock*dtype.sw - self.sd.nFramesPerBlock = nFramesPerBlock - self.sd.npy_format = getCNumpyDataType(dtype) - - if daq.neninchannels(True) > 0: - self.sd.inQueue = new SafeQueue[void*]() - if daq.nenoutchannels() > 0: - self.sd.outQueue = new SafeQueue[void*]() - - self.sd.pyCallback = audiocallback - - # Increase reference count to the callback - Py_INCREF( audiocallback) - - with nogil: - self.sd.thread = new CPPThread[void*, void (*)(void*)](audioCallbackPythonThreadFunction, - self.sd) - - while not self.sd.ready.load(): - # Allow stream stome time to start - CPPsleep_ms(100) - - self.daq_device.start( - self.sd.inQueue, - self.sd.outQueue) - - return self.daq_device.samplerate() - - def stop(self): - if self.sd is NULL: - raise RuntimeError('Stream is not opened') - - self.cleanupStream(self.sd) - self.sd = NULL - - cdef cleanupStream(self, PyStreamData* sd): - - with nogil: - if sd.thread: - sd.stopThread.store(True) - - if sd.inQueue: - # If waiting in the input queue, hereby we let it run. - sd.inQueue.enqueue(NULL) - - sd.thread.join() - del sd.thread - sd.thread = NULL - - if sd.inQueue: - while not sd.inQueue.empty(): - free(sd.inQueue.dequeue()) - del sd.inQueue - - if sd.outQueue: - while not sd.outQueue.empty(): - free(sd.outQueue.dequeue()) - del sd.outQueue - sd.outQueue = NULL - logging.debug("End cleanup stream queues...\n") - - if sd.pyCallback: - Py_DECREF( sd.pyCallback) - sd.pyCallback = NULL - - free(sd) - diff --git a/lasp/device/lasp_daqconfig.pxd b/lasp/device/lasp_daqconfig.pxd deleted file mode 100644 index 60bcdeb..0000000 --- a/lasp/device/lasp_daqconfig.pxd +++ /dev/null @@ -1,6 +0,0 @@ -include "lasp_common_decls.pxd" -from .lasp_deviceinfo cimport DeviceInfo - -cdef class DaqConfiguration: - cdef: - cppDaqConfiguration config diff --git a/lasp/device/lasp_daqconfig.pyx b/lasp/device/lasp_daqconfig.pyx deleted file mode 100644 index 3c9c437..0000000 --- a/lasp/device/lasp_daqconfig.pyx +++ /dev/null @@ -1,295 +0,0 @@ -# -*- coding: utf-8 -*- -"""! -Author: J.A. de Jong - ASCEE - -Description: - -Data Acquistiion (DAQ) device descriptors, and the DAQ devices themselves - -""" -__all__ = ['DaqConfiguration', 'DaqConfigurations'] -from ..lasp_common import lasp_shelve, SIQtys, Qty -from .lasp_device_common import DaqChannel -import json - -cdef class DaqConfigurations: - cdef public: - object input_config, output_config, duplex_mode - - def __init__(self, duplex_mode, DaqConfiguration input_config, - DaqConfiguration output_config): - - self.input_config = input_config - self.output_config = output_config - self.duplex_mode = duplex_mode - - def to_json(self): - return json.dumps( - dict( - duplex_mode = self.duplex_mode, - input_config = self.input_config.to_json(), - output_config = self.output_config.to_json(), - ) - ) - - @staticmethod - def from_json(daq_configs_json): - configs_dict = json.loads(daq_configs_json) - input_config = DaqConfiguration.from_json(configs_dict['input_config']) - output_config = DaqConfiguration.from_json(configs_dict['output_config']) - return DaqConfigurations(configs_dict['duplex_mode'], - input_config, - output_config) - - @staticmethod - def loadAllConfigs(): - """ - Returns a dictionary of all configurations presets. The dictionary keys - are the names of the configurations - - """ - with lasp_shelve() as sh: - configs_json = sh.load('daqconfigs', {}) - configs = {} - for name, val in configs_json.items(): - configs[name] = DaqConfigurations.from_json(val) - return configs - - @staticmethod - def loadConfigs(name: str): - """ - Load a configuration preset, containing input config and output config - """ - - with lasp_shelve() as sh: - configs_json = sh.load('daqconfigs', {}) - return DaqConfigurations.from_json(configs_json[name]) - - def saveConfigs(self, name): - with lasp_shelve() as sh: - configs_json = sh.load('daqconfigs', {}) - configs_json[name] = self.to_json() - sh.store('daqconfigs', configs_json) - - @staticmethod - def deleteConfigs(name): - with lasp_shelve() as sh: - configs_json = sh.load('daqconfigs', {}) - del configs_json[name] - sh.store('daqconfigs', configs_json) - -def constructDaqConfig(dict_data): - return DaqConfiguration.from_dict(dict_data) - -cdef class DaqConfiguration: - """ - Initialize a device descriptor - """ - def __cinit__(self): - pass - - def __str__(self): - return str(self.to_json()) - - @staticmethod - def fromDeviceInfo(DeviceInfo devinfo): - cdef: - cppDaqConfiguration cconfig - - d = DaqConfiguration() - cconfig = cppDaqConfiguration(devinfo.devinfo) - d.config = cconfig - return d - - @staticmethod - def from_json(jsonstring): - config_dict = json.loads(jsonstring) - return DaqConfiguration.from_dict(config_dict) - - def __reduce__(self): - return (constructDaqConfig, (self.to_dict(),)) - - @staticmethod - def from_dict(pydict): - cdef: - cppDaqConfiguration config - vector[DaqApi] apis = DaqApi.getAvailableApis() - - config.api = apis[pydict['apicode']] - config.device_name = pydict['device_name'].encode('utf-8') - config.eninchannels = pydict['eninchannels'] - config.enoutchannels = pydict['enoutchannels'] - - config.inchannel_names = [inchname.encode('utf-8') for inchname in - pydict['inchannel_names']] - - config.outchannel_names = [outchname.encode('utf-8') for outchname in - pydict['outchannel_names']] - - config.inchannel_sensitivities = pydict['inchannel_sensitivities'] - config.outchannel_sensitivities = pydict['outchannel_sensitivities'] - - config.sampleRateIndex = pydict['sampleRateIndex'] - config.framesPerBlockIndex = pydict['framesPerBlockIndex'] - config.dataTypeIndex = pydict['dataTypeIndex'] - config.monitorOutput = pydict['monitorOutput'] - config.inputIEPEEnabled = pydict['inputIEPEEnabled'] - config.inputACCouplingMode = pydict['inputACCouplingMode'] - config.inputRangeIndices = pydict['inputRangeIndices'] - config.inchannel_metadata = [inchmeta.encode('utf-8') for inchmeta in - pydict['inchannel_metadata']] - config.outchannel_metadata = [outchmeta.encode('utf-8') for outchmeta in - pydict['outchannel_metadata']] - - pydaqcfg = DaqConfiguration() - pydaqcfg.config = config - - return pydaqcfg - - def to_dict(self): - return dict( - apicode = self.config.api.apicode, - device_name = self.config.device_name.decode('utf-8'), - - eninchannels = self.eninchannels(), - enoutchannels = self.enoutchannels(), - - inchannel_names = [name.decode('utf-8') for name in - self.config.inchannel_names], - - outchannel_names = [name.decode('utf-8') for name in - self.config.outchannel_names], - - inchannel_sensitivities = [sens for sens in - self.config.inchannel_sensitivities], - outchannel_sensitivities = [sens for sens in - self.config.outchannel_sensitivities], - - inchannel_metadata = [inchmeta.decode('utf-8') for inchmeta in - self.config.inchannel_metadata], - outchannel_metadata = [outchmeta.decode('utf-8') for outchmeta in - self.config.outchannel_metadata], - - sampleRateIndex = self.config.sampleRateIndex, - dataTypeIndex = self.config.dataTypeIndex, - framesPerBlockIndex = self.config.framesPerBlockIndex, - monitorOutput = self.config.monitorOutput, - - inputIEPEEnabled = self.config.inputIEPEEnabled, - inputACCouplingMode = self.config.inputACCouplingMode, - inputRangeIndices = self.config.inputRangeIndices, - ) - - def to_json(self): - return json.dumps(self.to_dict()) - - def getInChannel(self, i:int): - return DaqChannel( - channel_enabled=self.config.eninchannels.at(i), - channel_name=self.config.inchannel_names.at(i).decode('utf-8'), - sensitivity=self.config.inchannel_sensitivities.at(i), - range_index=self.config.inputRangeIndices.at(i), - ACCoupling_enabled=self.config.inputACCouplingMode.at(i), - IEPE_enabled=self.config.inputIEPEEnabled.at(i), - channel_metadata=self.config.inchannel_metadata.at(i).decode('utf-8'), - ) - def getOutChannel(self, i:int): - return DaqChannel( - channel_enabled=self.config.enoutchannels.at(i), - channel_name=self.config.outchannel_names.at(i).decode('utf-8'), - channel_metadata=self.config.outchannel_metadata.at(i).decode('utf-8'), - ) - - def setInChannel(self, i:int, ch: DaqChannel): - self.config.eninchannels[i] = ch.channel_enabled - self.config.inchannel_names[i] = ch.channel_name.encode('utf-8') - self.config.inchannel_sensitivities[i] = ch.sensitivity - self.config.inputRangeIndices[i] = ch.range_index - self.config.inputACCouplingMode[i] = ch.ACCoupling_enabled - self.config.inputIEPEEnabled[i] = ch.IEPE_enabled - self.config.inchannel_metadata[i] = ch.channel_metadata.encode('utf-8') - - def setOutChannel(self, i:int, ch: DaqChannel): - self.config.enoutchannels[i] = ch.channel_enabled - self.config.outchannel_names[i] = ch.channel_name.encode('utf-8') - self.config.outchannel_metadata[i] = ch.channel_metadata.encode('utf-8') - - def getEnabledInChannels(self, include_monitor=True): - inch = [] - for i, enabled in enumerate(self.config.eninchannels): - if enabled: - inch.append(self.getInChannel(i)) - if include_monitor: - outch = self.getEnabledOutChannels() - if len(outch) > 0 and self.monitorOutput: - inch.insert(0, outch[0]) - - return inch - - def getEnabledOutChannels(self): - outch = [] - for i, enabled in enumerate(self.config.enoutchannels): - if enabled: - outch.append(self.getOutChannel(i)) - return outch - - @property - def api(self): - return self.config.api.apiname.decode('utf-8') - - @api.setter - def api(self, apitxt): - cdef: - vector[DaqApi] apis = DaqApi.getAvailableApis() - for api in apis: - if api.apiname.decode('utf-8') == apitxt: - self.config.api = api - return - raise RuntimeError(f'Api {apitxt} unavailable') - - def eninchannels(self): - return self.config.eninchannels - - def enoutchannels(self): - return self.config.enoutchannels - - @property - def sampleRateIndex(self): - return self.config.sampleRateIndex - - @sampleRateIndex.setter - def sampleRateIndex(self, int idx): - self.config.sampleRateIndex = idx - - @property - def dataTypeIndex(self): - return self.config.dataTypeIndex - - @dataTypeIndex.setter - def dataTypeIndex(self, int idx): - self.config.dataTypeIndex = idx - - @property - def framesPerBlockIndex(self): - return self.config.framesPerBlockIndex - - @framesPerBlockIndex.setter - def framesPerBlockIndex(self, int idx): - self.config.framesPerBlockIndex = idx - - @property - def monitorOutput(self): - return self.config.monitorOutput - - @monitorOutput.setter - def monitorOutput(self, bool idx): - self.config.monitorOutput = idx - - @property - def device_name(self): - return self.config.device_name.decode('utf-8') - - @device_name.setter - def device_name(self, idx): - self.config.device_name = idx.encode('utf-8') - diff --git a/lasp/device/lasp_deviceinfo.pxd b/lasp/device/lasp_deviceinfo.pxd deleted file mode 100644 index e4dc635..0000000 --- a/lasp/device/lasp_deviceinfo.pxd +++ /dev/null @@ -1,5 +0,0 @@ -include "lasp_common_decls.pxd" - -cdef class DeviceInfo: - cdef: - cppDeviceInfo devinfo diff --git a/lasp/device/lasp_deviceinfo.pyx b/lasp/device/lasp_deviceinfo.pyx deleted file mode 100644 index 95bce55..0000000 --- a/lasp/device/lasp_deviceinfo.pyx +++ /dev/null @@ -1,128 +0,0 @@ -# -*- coding: utf-8 -*- -"""! -Author: J.A. de Jong - ASCEE - -Description: - -DeviceInfo C++ object wrapper - -""" -__all__ = ['DeviceInfo'] - -def pickle(dat): - dev = DeviceInfo() - # print('DESERIALIZE****') - dev.devinfo = dev.devinfo.deserialize(dat) - return dev - -cdef class DeviceInfo: - def __cinit__(self): - pass - - def __init__(self): - pass - - def __reduce__(self): - serialized = self.devinfo.serialize() - # print('SERIALIZE****') - return (pickle, (serialized,)) - - @property - def api(self): return self.devinfo.api.apiname.decode('utf-8') - - @property - def name(self): return self.devinfo.device_name.decode('utf-8') - - def __repr__(self): - return self.api + ': ' + self.name - - @property - def ninchannels(self): return self.devinfo.ninchannels - - @property - def noutchannels(self): return self.devinfo.noutchannels - - @property - def availableSampleRates(self): return self.devinfo.availableSampleRates - - @property - def availableFramesPerBlock(self): - return self.devinfo.availableFramesPerBlock - - @property - def availableDataTypes(self): - pydtypes = [] - for datatype in self.devinfo.availableDataTypes: - pydtypes.append(datatype.name.decode('utf-8')) - return pydtypes - - @property - def prefSampleRateIndex(self): - return self.devinfo.prefSampleRateIndex - - @property - def prefSampleRate(self): - return self.availableSampleRates[ - self.prefSampleRateIndex] - - @property - def prefFramesPerBlockIndex(self): - return self.devinfo.prefFramesPerBlockIndex - - @property - def prefFramesPerBlock(self): - return self.availableFramesPerBlock[ - self.prefFramesPerBlockIndex] - - @property - def prefDataTypeIndex(self): - return self.devinfo.prefDataTypeIndex - - @property - def prefDataType(self): - return self.availableDataTypes[ - self.prefDataTypeIndex] - @property - def hasInputIEPE(self): - return self.devinfo.hasInputIEPE - - @property - def hasInputACCouplingSwitch(self): - return self.devinfo.hasInputACCouplingSwitch - - @property - def hasInputTrigger(self): - return self.devinfo.hasInputTrigger - - @property - def inputRanges(self): - return self.devinfo.availableInputRanges - - @staticmethod - def getDeviceInfo(): - """ - Returns device information objects (DeviceInfo) for all available - devices - """ - cdef: - vector[cppDeviceInfo] devinfos - us numdevs, devno - cppDeviceInfo* devinfo - - devinfos = cppDaq.getDeviceInfo() - numdevs = devinfos.size() - - pydevinfos = [] - for devno in range(numdevs): - devinfo = &(devinfos[devno]) - - d = DeviceInfo() - d.devinfo = devinfo[0] - - pydevinfos.append(d) - return pydevinfos - - @property - def availableInputRanges(self): - return self.devinfo.availableInputRanges - diff --git a/lasp/device/lasp_rtaudio.pyx b/lasp/device/lasp_rtaudio.pyx deleted file mode 100644 index 02a1fcb..0000000 --- a/lasp/device/lasp_rtaudio.pyx +++ /dev/null @@ -1,777 +0,0 @@ -include "lasp_common_decls.pxd" - -__all__ = ['RtAudio', 'get_numpy_dtype_from_format_string', - 'get_sampwidth_from_format_string'] - -cdef extern from "RtAudio.h" nogil: - ctypedef unsigned long RtAudioStreamStatus - RtAudioStreamStatus RTAUDIO_INPUT_OVERFLOW - RtAudioStreamStatus RTAUDIO_OUTPUT_UNDERFLOW - - cdef cppclass RtAudioError: - ctypedef enum Type: - WARNING - DEBUG_WARNING - UNSPECIFIED - NO_DEVICES_FOUND - INVALID_DEVICE - MEMORY_ERROR - INVALID_PARAMETER - INVALID_USE - DRIVER_ERROR - SYSTEM_ERROR - THREAD_ERROR - - ctypedef unsigned long RtAudioStreamFlags - RtAudioStreamFlags RTAUDIO_NONINTERLEAVED - RtAudioStreamFlags RTAUDIO_MINIMIZE_LATENCY - RtAudioStreamFlags RTAUDIO_HOG_DEVICE - RtAudioStreamFlags RTAUDIO_SCHEDULE_REALTIME - RtAudioStreamFlags RTAUDIO_ALSA_USE_DEFAULT - RtAudioStreamFlags RTAUDIO_JACK_DONT_CONNECT - - ctypedef unsigned long RtAudioFormat - RtAudioFormat RTAUDIO_SINT8 - RtAudioFormat RTAUDIO_SINT16 - RtAudioFormat RTAUDIO_SINT24 - RtAudioFormat RTAUDIO_SINT32 - RtAudioFormat RTAUDIO_FLOAT32 - RtAudioFormat RTAUDIO_FLOAT64 - - ctypedef int (*RtAudioCallback)(void* outputBuffer, - void* inputBuffer, - unsigned int nFramesPerBlock, - double streamTime, - RtAudioStreamStatus status, - void* userData) - - ctypedef void (*RtAudioErrorCallback)(RtAudioError.Type _type, - const string& errortxt) - - cdef cppclass cppRtAudio "RtAudio": - enum Api: - UNSPECIFIED - LINUX_ALSA - LINUX_PULSE - MACOSX_CORE - WINDOWS_WASAPI - WINDOWS_ASIO - WINDOWS_DS - - cppclass DeviceInfo: - bool probed - string name - unsigned int outputChannels - unsigned int inputChannels - unsigned int duplexChannels - bool isDefaultOutput - bool isDefaultInput - vector[unsigned int] sampleRates - unsigned int preferredSampleRate - RtAudioFormat nativeFormats - - cppclass StreamOptions: - StreamOptions() - RtAudioStreamFlags flags - unsigned int numberOfBuffers - string streamName - int priority - cppclass StreamParameters: - StreamParameters() - unsigned int deviceId - unsigned int nChannels - unsigned int firstChannel - - @staticmethod - void getCompiledApi(vector[cppRtAudio.Api]& apis) - - @staticmethod - cppRtAudio.Api getCompiledApiByName(string& name) - - @staticmethod - string getApiDisplayName(cppRtAudio.Api api) - - @staticmethod - string getApiName(cppRtAudio.Api api) - - # RtAudio() except + - cppRtAudio() except + - cppRtAudio(cppRtAudio.Api api) except + - - # ~RtAudio() Destructors should not be listed - - unsigned int getDeviceCount() - DeviceInfo getDeviceInfo(unsigned int device) - unsigned int getDefaultOutputDevice() - unsigned int getDefaultInputDevice() - void openStream(StreamParameters* outputParameters, - StreamParameters* intputParameters, - RtAudioFormat _format, - unsigned int sampleRate, - unsigned int* bufferFrames, - RtAudioCallback callback, - void* userData, - void* StreamOptions, - RtAudioErrorCallback) except + - void closeStream() - void startStream() except + - void stopStream() except + - void abortStream() except + - bool isStreamOpen() - bool isStreamRunning() - double getStreamTime() - void setStreamTime(double) except + - long getStreamLatency() - unsigned int getStreamSampleRate() - void showWarnings(bool value) - - -_formats_strkey = { - '8-bit integers': (RTAUDIO_SINT8, 1, np.int8), - '16-bit integers': (RTAUDIO_SINT16, 2, np.int16), - '24-bit integers': (RTAUDIO_SINT24, 3), - '32-bit integers': (RTAUDIO_SINT32, 4, np.int32), - '32-bit floats': (RTAUDIO_FLOAT32, 4, np.float32), - '64-bit floats': (RTAUDIO_FLOAT64, 8, np.float64), -} -_formats_rtkey = { - RTAUDIO_SINT8: ('8-bit integers', 1, cnp.NPY_INT8), - RTAUDIO_SINT16: ('16-bit integers', 2, cnp.NPY_INT16), - RTAUDIO_SINT24: ('24-bit integers', 3), - RTAUDIO_SINT32: ('32-bit integers', 4, cnp.NPY_INT32), - RTAUDIO_FLOAT32: ('32-bit floats', 4, cnp.NPY_FLOAT32), - RTAUDIO_FLOAT64: ('64-bit floats', 8, cnp.NPY_FLOAT64), -} - -def get_numpy_dtype_from_format_string(format_string): - return _formats_strkey[format_string][-1] -def get_sampwidth_from_format_string(format_string): - return _formats_strkey[format_string][-2] - - -# It took me quite a long time to fully understand Cython's idiosyncrasies -# concerning C(++) callbacks, the GIL and passing Python objects as pointers -# into C(++) functions. But finally, here it is! - -# cdef void fromNPYToBuffer(cnp.ndarray arr, -# void* buf): -# """ -# Copy a Python numpy array over to a buffer -# No checks, just memcpy! Careful! -# """ -# memcpy(buf, arr.data, arr.size*arr.itemsize) - - - -ctypedef struct PyStreamData: - PyObject* pyCallback - RtAudioFormat sampleformat - - # Flag used to pass the stopThread. - atomic[bool] stopThread - - # Number of frames per block - unsigned nFramesPerBlock - # Number of bytes per channel - unsigned int nBytesPerChan - - # Number of blocks to delay the output before adding to the input - unsigned int outputDelayBlocks - - # The structures as used by RtAudio - cppRtAudio.StreamParameters inputParams - cppRtAudio.StreamParameters outputParams - - bool* inputChannelsEnabled - bool* outputChannelsEnabled - - unsigned ninputchannels_forwarded - unsigned noutputchannels_forwarded - - # If these queue pointers are NULL, it means the stream does not have an - # input, or output. - SafeQueue[void*] *outputDelayQueue - SafeQueue[void*] *inputQueue - SafeQueue[void*] *outputQueue - CPPThread[void*, void (*)(void*)] *thread - - -cdef int audioCallback(void* outputbuffer, - void* inputbuffer, - unsigned int nFramesPerBlock, - double streamTime, - RtAudioStreamStatus status, - void* userData) nogil: - """ - Calls the Python callback function and converts data - - """ - cdef: - int rval = 0 - PyStreamData* stream - void* outputbuffercpy = NULL - void* inputbuffercpy = NULL - unsigned j, i - unsigned bytesperchan - unsigned noutputchannels_forwarded, ninputchannels_forwarded - bint ch_en - - stream = (userData) - bytesperchan = stream.nBytesPerChan - ninputchannels_forwarded = stream.ninputchannels_forwarded - noutputchannels_forwarded = stream.noutputchannels_forwarded - - # with gil: - # print(f'bytesperchan: {bytesperchan}') - # print(f'ninputchannels_forwarded:: {ninputchannels_forwarded}') - # print(f'noutputchannels_forwarded:: {noutputchannels_forwarded}') - # fprintf(stderr, "Stream heartbeat...\n") - - - # Returning 2 means aborting the stream immediately - if status == RTAUDIO_INPUT_OVERFLOW: - fprintf(stderr, 'Input overflow.\n') - stream.stopThread.store(True) - return 2 - elif status == RTAUDIO_OUTPUT_UNDERFLOW: - fprintf(stderr, 'Output underflow.\n') - # stream.stopThread.store(True) - return 0 - - if nFramesPerBlock != stream.nFramesPerBlock: - printf('Number of frames mismath in callback data!\n') - stream.stopThread.store(True) - return 2 - - if inputbuffer: - # fprintf(stderr, "enter copying input buffer code\n") - # with gil: - # assert stream.inputQueue is not NULL - inputbuffercpy = malloc(bytesperchan*ninputchannels_forwarded) - if not inputbuffercpy: - fprintf(stderr, "Error allocating buffer\n") - return 2 - - if stream.outputDelayQueue: - if stream.outputDelayQueue.size() > stream.outputDelayBlocks: - outputbuffercpy = stream.outputDelayQueue.dequeue() - memcpy(inputbuffercpy, outputbuffercpy, - bytesperchan*noutputchannels_forwarded) - - # Cleanup buffer - free(outputbuffercpy) - outputbuffercpy = NULL - else: - memset(inputbuffercpy, 0, - bytesperchan*noutputchannels_forwarded) - - # Starting channel for copying input channels according to the channel - # map - j = stream.noutputchannels_forwarded - else: - j = 0 - i = 0 - for i in range(stream.inputParams.nChannels): - ch_en = stream.inputChannelsEnabled[i] - if ch_en: - copyChannel(inputbuffercpy, inputbuffer, bytesperchan, j, i) - j+=1 - i+=1 - - stream.inputQueue.enqueue(inputbuffercpy) - - if outputbuffer: - # with gil: - # assert stream.outputQueue - # fprintf(stderr, "enter copying output buffer code\n") - if stream.outputQueue.empty(): - fprintf(stderr, 'Stream output buffer underflow, zero-ing buffer...\n') - memset(outputbuffer, 0, stream.outputParams.nChannels*bytesperchan) - else: - outputbuffercpy = stream.outputQueue.dequeue() - # fprintf(stderr, 'Copying data to stream output buffer...\n') - j = 0 - i = 0 - for i in range(stream.outputParams.nChannels): - ch_en = stream.outputChannelsEnabled[i] - if ch_en: - copyChannel(outputbuffer, outputbuffercpy, bytesperchan, i, j) - j+=1 - else: - # If channel is not enabled, we set the data to zero - memset( &(( outputbuffer)[bytesperchan*i]), 0, bytesperchan) - pass - i+=1 - - if stream.outputDelayQueue: - # fprintf(stderr, "Adding to delay queue\n") - stream.outputDelayQueue.enqueue(outputbuffercpy) - else: - free(outputbuffercpy) - - outputbuffercpy = NULL - - return 0 - - -cdef void audioCallbackPythonThreadFunction(void* voidstream) nogil: - cdef: - PyStreamData* stream - cnp.NPY_TYPES npy_format - void* inputbuffer = NULL - void* outputbuffer = NULL - unsigned noutputchannels_forwarded - unsigned ninputchannels_forwarded - unsigned nBytesPerChan - unsigned nFramesPerBlock - - stream = voidstream - ninputchannels_forwarded = stream.ninputchannels_forwarded - noutputchannels_forwarded = stream.noutputchannels_forwarded - nBytesPerChan = stream.nBytesPerChan - nFramesPerBlock = stream.nFramesPerBlock - - with gil: - npy_format = _formats_rtkey[stream.sampleformat][2] - callback = stream.pyCallback - print(f'noutputchannels_forwarded: {noutputchannels_forwarded}') - print(f'ninputchannels_forwarded: {ninputchannels_forwarded}') - print(f'nBytesPerChan: {nBytesPerChan}') - print(f'nFramesPerBlock: {nFramesPerBlock}') - - while True: - if stream.stopThread.load() == True: - printf('Stopping thread...\n') - return - - if stream.inputQueue: - # fprintf(stderr, "Waiting on input queue\n") - inputbuffer = stream.inputQueue.dequeue() - if not inputbuffer: - printf('Stopping thread...\n') - return - - if stream.outputQueue: - # fprintf(stderr, 'Allocating output buffer...\n') - outputbuffer = malloc(nBytesPerChan*noutputchannels_forwarded) - # memset(outputbuffer, 0, nBytesPerChan*noutputchannels_forwarded) - - with gil: - - # Obtain stream information - npy_input = None - npy_output = None - - if stream.inputQueue: - # assert(inputbuffer) - try: - # print(f'========ninputchannels_forwarded: {ninputchannels_forwarded}') - # print(f'========nFramesPerBlock: {nFramesPerBlock}') - # print(f'========npy_format: {npy_format}') - - npy_input = data_to_ndarray( - inputbuffer, - nFramesPerBlock, - ninputchannels_forwarded, - npy_format, - True, # Do transfer ownership - True) # F-contiguous is True: data is Fortran-cont. - # fprintf(stderr, "Copying array...\n") - # fprintf(stderr, "End Copying array...\n") - - except Exception as e: - print('exception in cython callback for audio input: ', str(e)) - return - - if stream.outputQueue: - # fprintf(stderr, 'Copying output buffer to Numpy...\n') - try: - npy_output = data_to_ndarray( - outputbuffer, - nFramesPerBlock, - noutputchannels_forwarded, - npy_format, - False, # Do not transfer ownership - True) # F-contiguous - - except Exception as e: - print('exception in Cython callback for audio output: ', str(e)) - return - - try: - # fprintf(stderr, "Python callback...\n") - rval = callback(npy_input, - npy_output, - nFramesPerBlock, - ) - # fprintf(stderr, "Return from Python callback...\n") - - except Exception as e: - print('Exception in Cython callback: ', str(e)) - return - - if stream.outputQueue: - # fprintf(stderr, 'Enqueuing output buffer...\n') - - stream.outputQueue.enqueue(outputbuffer) - if not stream.inputQueue: - # fprintf(stderr, 'No input queue!\n') - while stream.outputQueue.size() > 10 and not stream.stopThread.load(): - # printf('Sleeping...\n') - # No input queue to wait on, so we relax a bit here. - CPPsleep_ms(1); - - # Outputbuffer is free'ed by the audiothread, so should not be touched - # here. - outputbuffer = NULL - - # Inputbuffer memory is owned by Numpy, so should not be free'ed - inputbuffer = NULL - - -cdef void errorCallback(RtAudioError.Type _type,const string& errortxt) nogil: - fprintf(stderr, 'RtAudio error callback called: ') - fprintf(stderr, errortxt.c_str()) - fprintf(stderr, '\n') - - -cdef class RtAudio: - cdef: - cppRtAudio* _rtaudio - PyStreamData* sd - int api - - def __cinit__(self, pyapi): - if pyapi.apiname != 'RtAudio': - raise RuntimeError('RtAudio constructor called with invalid Api instance') - cdef: - cppRtAudio.Api api = pyapi.internalnr - - self._rtaudio = new cppRtAudio(api) - self.sd = NULL - self._rtaudio.showWarnings(True) - self.api = api - - def __dealloc__(self): - if self.sd is not NULL: - # fprintf(stderr, 'Force closing stream...') - if self._rtaudio.isStreamRunning(): - self._rtaudio.stopStream() - self._rtaudio.closeStream() - - self.cleanupStream(self.sd) - self.sd = NULL - del self._rtaudio - - @staticmethod - def getApis(): - cdef: - vector[cppRtAudio.Api] apis - cppRtAudio.getCompiledApi(apis) - - apilist = [] - for api in apis: - apilist.append( - DAQApi( - backendname= 'RtAudio', - apiname = cppRtAudio.getApiName(api).decode('utf-8'), - internalnr= api)) - - return apilist - - cpdef unsigned int getDefaultOutputDevice(self): - return self._rtaudio.getDefaultOutputDevice() - - cpdef unsigned int getDefaultInputDevice(self): - return self._rtaudio.getDefaultInputDevice() - - def getDeviceInfo(self): - """ - Return device information of the current device - """ - sampleformats = [] - - cdef: - cppRtAudio.DeviceInfo devinfo - unsigned devcount, i - - devinfo_py = [] - - devcount = self._rtaudio.getDeviceCount() - - for i in range(devcount): - - devinfo = self._rtaudio.getDeviceInfo(i) - - nf = devinfo.nativeFormats - for format_ in [ RTAUDIO_SINT8, RTAUDIO_SINT16, RTAUDIO_SINT24, - RTAUDIO_SINT32, RTAUDIO_FLOAT32, RTAUDIO_FLOAT64]: - if nf & format_: - sampleformats.append(_formats_rtkey[format_][0]) - - devinfo_py.append(DeviceInfo( - api = self.api, - index = i, - probed = devinfo.probed, - name = devinfo.name.decode('utf-8'), - outputchannels = devinfo.outputChannels, - inputchannels = devinfo.inputChannels, - duplexchannels = devinfo.duplexChannels, - samplerates = devinfo.sampleRates, - sampleformats = sampleformats, - prefsamplerate = devinfo.preferredSampleRate)) - - return devinfo_py - - @cython.nonecheck(True) - def start(self, - avstream - ): - """ - Opening a stream with specified parameters - - Args: - avstream: AvStream instance - - Returns: None - """ - - if self.sd is not NULL: - raise RuntimeError('Stream is already opened.') - - daqconfig = avstream.daqconfig - avtype = avstream.avtype - device = avstream.device - - cdef: - bint duplex_mode = daqconfig.duplex_mode - bint monitorOutput = daqconfig.monitor_gen - unsigned int outputDelayBlocks = daqconfig.outputDelayBlocks - cppRtAudio.StreamParameters *rtOutputParams_ptr = NULL - cppRtAudio.StreamParameters *rtInputParams_ptr = NULL - cppRtAudio.StreamOptions streamoptions - size_t sw - unsigned int nFramesPerBlock = int(daqconfig.nFramesPerBlock) - int firstinputchannel, firstoutputchannel - int lastinputchannel, lastoutputchannel - unsigned int ninputchannels_forwarded=0 - unsigned int ninputchannels_rtaudio=0 - unsigned int noutputchannels_rtaudio=0 - unsigned int noutputchannels_forwarded=0 - unsigned int samplerate - int i - bint input_stream=False, output_stream=False - bool* inputChannelsEnabled - bool* outputChannelsEnabled - - if daqconfig.nFramesPerBlock > 8192 or daqconfig.nFramesPerBlock < 512: - raise ValueError('Invalid number of nFramesPerBlock') - - if daqconfig.outputDelayBlocks < 0 or daqconfig.outputDelayBlocks > 10: - raise ValueError('Invalid number of outputDelayBlocks') - - try: - - # Determine sample rate and sample format, determine whether we are an - # input or an output stream, or both - if avtype == AvType.audio_input or duplex_mode: - # Here, we override the sample format in case of duplex mode. - sampleformat = daqconfig.en_input_sample_format - samplerate = int(daqconfig.en_input_rate) - input_stream = True - if duplex_mode: - output_stream = True - else: - sampleformat = daqconfig.en_output_sample_format - samplerate = int(daqconfig.en_output_rate) - output_stream = True - - print(f'Is input stream: {input_stream}') - print(f'Is output stream: {output_stream}') - - sw = get_sampwidth_from_format_string(sampleformat) - print(f'samplewidth: {sw}') - - # All set, allocate the stream! - self.sd = malloc(sizeof(PyStreamData)) - if self.sd == NULL: - raise MemoryError('Could not allocate stream: memory error.') - - self.sd.pyCallback = avstream._audioCallback - # Increase reference count to the callback - Py_INCREF( avstream._audioCallback) - - self.sd.sampleformat = _formats_strkey[sampleformat][0] - self.sd.stopThread.store(False) - self.sd.inputQueue = NULL - self.sd.outputQueue = NULL - self.sd.outputDelayQueue = NULL - - self.sd.thread = NULL - - self.sd.outputDelayBlocks = outputDelayBlocks - self.sd.ninputchannels_forwarded = 0 - self.sd.noutputchannels_forwarded = 0 - self.sd.inputChannelsEnabled = NULL - self.sd.outputChannelsEnabled = NULL - - # Create channel maps for input channels, set RtAudio input stream - # parameters - if input_stream: - firstinputchannel = daqconfig.firstEnabledInputChannelNumber() - lastinputchannel = daqconfig.lastEnabledInputChannelNumber() - ninputchannels_rtaudio = lastinputchannel-firstinputchannel+1 - - # print(firstinputchannel) - # print(lastinputchannel) - # print(ninputchannels_rtaudio) - - if lastinputchannel < 0 or ninputchannels_rtaudio < 1: - raise ValueError('Not enough input channels selected') - input_ch = daqconfig.input_channel_configs - - inputChannelsEnabled = malloc(sizeof(bool)*ninputchannels_rtaudio) - self.sd.inputChannelsEnabled = inputChannelsEnabled - - for i in range(firstinputchannel, lastinputchannel+1): - ch_en = input_ch[i].channel_enabled - if ch_en: - ninputchannels_forwarded += 1 - inputChannelsEnabled[i] = ch_en - - rtInputParams_ptr = &self.sd.inputParams - rtInputParams_ptr.deviceId = device.index - rtInputParams_ptr.nChannels = ninputchannels_rtaudio - rtInputParams_ptr.firstChannel = firstinputchannel - - self.sd.inputQueue = new SafeQueue[void*]() - self.sd.ninputchannels_forwarded = ninputchannels_forwarded - - - # Create channel maps for output channels - if output_stream: - firstoutputchannel = daqconfig.firstEnabledOutputChannelNumber() - lastoutputchannel = daqconfig.lastEnabledOutputChannelNumber() - noutputchannels_rtaudio = lastoutputchannel-firstoutputchannel+1 - - # print(firstoutputchannel) - # print(lastoutputchannel) - # print(noutputchannels_rtaudio) - - if lastoutputchannel < 0 or noutputchannels_rtaudio < 1: - raise ValueError('Not enough output channels selected') - output_ch = daqconfig.output_channel_configs - - outputChannelsEnabled = malloc(sizeof(bool)*noutputchannels_rtaudio) - self.sd.outputChannelsEnabled = outputChannelsEnabled - for i in range(firstoutputchannel, lastoutputchannel+1): - ch_en = output_ch[i].channel_enabled - if ch_en: - noutputchannels_forwarded += 1 - outputChannelsEnabled[i] = ch_en - - rtOutputParams_ptr = &self.sd.outputParams - rtOutputParams_ptr.deviceId = device.index - rtOutputParams_ptr.nChannels = noutputchannels_rtaudio - rtOutputParams_ptr.firstChannel = firstoutputchannel - - self.sd.outputQueue = new SafeQueue[void*]() - self.sd.noutputchannels_forwarded = noutputchannels_forwarded - - if monitorOutput and duplex_mode: - self.sd.outputDelayQueue = new SafeQueue[void*]() - self.sd.ninputchannels_forwarded += noutputchannels_forwarded - - - streamoptions.flags = RTAUDIO_HOG_DEVICE - streamoptions.flags |= RTAUDIO_NONINTERLEAVED - streamoptions.numberOfBuffers = 4 - streamoptions.streamName = "LASP Audio stream".encode('utf-8') - streamoptions.priority = 1 - - self._rtaudio.openStream(rtOutputParams_ptr, - rtInputParams_ptr, - _formats_strkey[sampleformat][0], - samplerate, - &nFramesPerBlock, - audioCallback, - self.sd, - &streamoptions, # Stream options - errorCallback # Error callback - ) - - self.sd.nBytesPerChan = nFramesPerBlock*sw - self.sd.nFramesPerBlock = nFramesPerBlock - - self._rtaudio.startStream() - - except Exception as e: - print('Exception occured in stream opening: ', e) - self.cleanupStream(self.sd) - self.sd = NULL - raise e - - with nogil: - self.sd.thread = new CPPThread[void*, void (*)(void*)](audioCallbackPythonThreadFunction, - self.sd) - # Allow it to start - CPPsleep_ms(500) - pass - - return nFramesPerBlock, samplerate - - def stop(self): - if self.sd is NULL: - raise RuntimeError('Stream is not running') - - try: - self._rtaudio.stopStream() - self._rtaudio.closeStream() - except Exception as e: - print(e) - pass - self.cleanupStream(self.sd) - self.sd = NULL - - cdef cleanupStream(self, PyStreamData* stream): - if stream == NULL: - return - - with nogil: - if stream.thread: - stream.stopThread.store(True) - if stream.inputQueue: - # If waiting in the input queue, hereby we let it run. - stream.inputQueue.enqueue(NULL) - # printf('Joining thread...\n') - # HERE WE SHOULD RELEASE THE GIL, as exiting the thread function - # will require the GIL, which is locked by this thread! - stream.thread.join() - # printf('Thread joined!\n') - del stream.thread - stream.thread = NULL - - if stream.inputChannelsEnabled: - free(stream.inputChannelsEnabled) - if stream.outputChannelsEnabled: - free(stream.outputChannelsEnabled) - - if stream.outputQueue: - while not stream.outputQueue.empty(): - free(stream.outputQueue.dequeue()) - del stream.outputQueue - if stream.inputQueue: - while not stream.inputQueue.empty(): - free(stream.inputQueue.dequeue()) - del stream.inputQueue - if stream.outputDelayQueue: - while not stream.outputDelayQueue.empty(): - free(stream.outputDelayQueue.dequeue()) - del stream.outputDelayQueue - fprintf(stderr, "End cleanup stream queues...\n") - - if stream.pyCallback: - Py_DECREF( stream.pyCallback) - stream.pyCallback = NULL - # fprintf(stderr, "End cleanup callback...\n") - free(stream) diff --git a/lasp/lasp_avstream.py b/lasp/lasp_avstream.py deleted file mode 100644 index 73fb668..0000000 --- a/lasp/lasp_avstream.py +++ /dev/null @@ -1,625 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Author: J.A. de Jong - -Description: Controlling an audio stream in a different process. -""" -import logging -import multiprocessing as mp -# import cv2 as cv -import signal -import time -from dataclasses import dataclass -from enum import Enum, auto, unique -from typing import List - -import numpy as np - -from .device import Daq, DaqChannel, DaqConfiguration, DeviceInfo -from .filter import highpass -from .lasp_atomic import Atomic -from .lasp_common import AvType -from .lasp_multiprocessingpatch import apply_patch -from .wrappers import SosFilterBank - -apply_patch() - -__all__ = ["StreamManager", "ignoreSigInt", "StreamStatus"] - - -def ignoreSigInt(): - """ - Ignore sigint signal. Should be set on all processes to let the main - process control this signal. - """ - signal.signal(signal.SIGINT, signal.SIG_IGN) - - -@dataclass -class StreamMetaData: - # Sample rate [Hz] - fs: float - - # Input channels - in_ch: List[DaqChannel] - - # Output channels - out_ch: List[DaqChannel] - - # blocksize - blocksize: int - - # The data type of input and output blocks. - dtype: np.dtype - - -@unique -class StreamMsg(Enum): - """ - First part, control messages that can be send to the stream - """ - - startStream = auto() - stopStream = auto() - stopAllStreams = auto() - getStreamMetaData = auto() - endProcess = auto() - scanDaqDevices = auto() - """ - Second part, status messages that are send back on all listeners - """ - # "Normal messages" - deviceList = auto() - streamStarted = auto() - streamStopped = auto() - streamMetaData = auto() - streamData = auto() - - # Error messages - # Some error occured, which mostly leads to a stop of the stream - streamError = auto() - # An error occured, but we recovered - streamTemporaryError = auto() - # A fatal error occured. This leads to serious errors in the application - streamFatalError = auto() - - -class AudioStream: - """ - Audio stream. - """ - - def __init__( - self, - avtype: AvType, - devices: list, - daqconfig: DaqConfiguration, - processCallback: callable, - ): - """ - Initializes the audio stream and tries to start it. - - avtype: AvType - devices: List of device information - daqconfig: DaqConfiguration to used to generate audio stream backend - processCallback: callback function that will be called from a different - thread, with arguments (AudioStream, in - """ - logging.debug("AudioStream()") - - # self.running = Atomic(False) - # self.aframectr = Atomic(0) - self.running = False - self.aframectr = 0 - self.avtype = avtype - - api_devices = devices[daqconfig.api] - self.processCallback = processCallback - - matching_devices = [ - device for device in api_devices if device.name == daqconfig.device_name - ] - - if len(matching_devices) == 0: - raise RuntimeError(f"Could not find device {daqconfig.device_name}") - - # TODO: We pick te first one, what to do if we have multiple matches? - # Is that even possible? - device = matching_devices[0] - - self.daq = Daq(device, daqconfig) - en_in_ch = daqconfig.getEnabledInChannels(include_monitor=True) - en_out_ch = daqconfig.getEnabledOutChannels() - - if en_in_ch == 0 and en_out_ch == 0: - raise RuntimeError("No enabled input / output channels") - elif en_out_ch == 0 and avtype in (AvType.audio_duplex, AvType.audio_output): - raise RuntimeError("No enabled output channels") - elif en_in_ch == 0 and avtype in (AvType.audio_input, AvType.audio_duplex): - raise RuntimeError("No enabled input channels") - - logging.debug("Ready to start device...") - samplerate = self.daq.start(self.streamCallback) - - # Create required Highpass filters for incoming data - self.hpfs = [None] * len(en_in_ch) - for i, ch in enumerate(en_in_ch): - # Simple filter with a single bank and one section - if ch.highpass > 0: - fb = SosFilterBank(1, 1) - hpf = highpass(samplerate, ch.highpass, Q=np.sqrt(2) / 2) - fb.setFilter(0, hpf[None, :]) - self.hpfs[i] = fb - - self.streammetadata = StreamMetaData( - fs=samplerate, - in_ch=en_in_ch, - out_ch=en_out_ch, - blocksize=self.daq.nFramesPerBlock, - dtype=self.daq.getNumpyDataType(), - ) - self.running = True - - def streamCallback(self, indata, outdata, nframes): - """ - This is called (from a separate thread) for each block - of audio data. - """ - if not self.running: - return 1 - self.aframectr += 1 - - # TODO: Fix this. This gives bug on Windows, the threading lock does - # give a strange erro. - try: - if not self.running: - return 1 - except Exception as e: - print(e) - - if indata is not None: - indata_filtered = np.empty_like(indata) - nchannels = indata.shape[1] - - for i in range(nchannels): - # Filter each channel to the optional high-pass, which could also - # be an empty filter - if self.hpfs[i] is not None: - indata_float = indata[:, [i]].astype(np.float) - filtered_ch_float = self.hpfs[i].filter_(indata_float) - - indata_filtered[:, i] = filtered_ch_float.astype( - self.streammetadata.dtype - )[:, 0] - else: - # One-to-one copy - indata_filtered[:, i] = indata[:, i] - else: - indata_filtered = indata - - # rv = self.processCallback(self, indata, outdata) - rv = self.processCallback(self, indata_filtered, outdata) - if rv != 0: - self.running <<= False - return rv - - def stop(self): - """ - Stop the DAQ stream. Should be called only once. - """ - daq = self.daq - self.daq = None - self.running <<= False - daq.stop() - - self.streammetadata = None - - -class AvStreamProcess(mp.Process): - """ - Different process on which all audio streams are running. - """ - - def __init__(self, pipe, msg_qlist, indata_qlist, outq): - """ - - Args: - pipe: Message control pipe on which commands are received. - msg_qlist: List of queues on which stream status and events are - sent. Here, everything is send, except for the captured data - itself. - indata_qlist: List of queues on which captured data from a DAQ is - send. This one gets all events, but also captured data. - outq: On this queue, the stream process receives data to be send as - output to the devices. - - """ - super().__init__() - - self.pipe = pipe - self.msg_qlist = msg_qlist - self.indata_qlist = indata_qlist - self.outq = outq - - self.devices = {} - self.daqconfigs = None - - # In, out, duplex - self.streams = {t: None for t in list(AvType)} - - # When this is set, a kill on the main process will also kill the - # siggen process. Highly wanted feature - self.daemon = True - - def run(self): - """ - The actual function running in a different process. - """ - # First things first, ignore interrupt signals for THIS process - # https://stackoverflow.com/questions/21104997/keyboard-interrupt-with-pythons-multiprocessing - signal.signal(signal.SIGINT, signal.SIG_IGN) - - # Check for devices - self.rescanDaqDevices() - - while True: - try: - msg, data = self.pipe.recv() - except OSError: - logging.error("Error with pipe, terminating process") - self.stopAllStreams() - self.terminate() - logging.debug(f"Streamprocess obtained message {msg}") - - if msg == StreamMsg.scanDaqDevices: - self.rescanDaqDevices() - - elif msg == StreamMsg.stopAllStreams: - self.stopAllStreams() - - elif msg == StreamMsg.endProcess: - self.stopAllStreams() - # and.. exit! - return - - elif msg == StreamMsg.getStreamMetaData: - (avtype, ) = data - duplex = self.streams[AvType.audio_duplex] - stream = duplex if duplex else self.streams[avtype] - - self.sendAllQueues( - StreamMsg.streamMetaData, avtype, stream.streammetadata - ) - - elif msg == StreamMsg.startStream: - avtype, daqconfig = data - self.startStream(avtype, daqconfig) - - elif msg == StreamMsg.stopStream: - (avtype,) = data - self.stopStream(avtype) - - def startStream(self, avtype: AvType, daqconfig: DaqConfiguration): - """ - Start a stream, based on type and configuration - - """ - self.stopRequiredExistingStreams(avtype) - # Empty the queue from existing stuff (puts the signal generator - # directly in action!). - if avtype in (AvType.audio_duplex, AvType.audio_output): - while not self.outq.empty(): - self.outq.get() - try: - stream = AudioStream(avtype, self.devices, daqconfig, self.streamCallback) - self.streams[avtype] = stream - self.sendAllQueues(StreamMsg.streamStarted, avtype, stream.streammetadata) - - except Exception as e: - self.sendAllQueues( - StreamMsg.streamError, avtype, f"Error starting stream: {str(e)}" - ) - return - - def stopStream(self, avtype: AvType): - """ - Stop an existing stream, and sets the attribute in the list of streams - to None - - Args: - stream: AudioStream instance - """ - stream = self.streams[avtype] - if stream is not None: - try: - stream.stop() - self.sendAllQueues(StreamMsg.streamStopped, stream.avtype) - except Exception as e: - self.sendAllQueues( - StreamMsg.streamError, - stream.avtype, - f"Error occured in stopping stream: {str(e)}", - ) - self.streams[avtype] = None - - def stopRequiredExistingStreams(self, avtype: AvType): - """ - Stop all existing streams that conflict with the current avtype - """ - if avtype == AvType.audio_input: - # For a new input, duplex and input needs to be stopped - stream_to_stop = (AvType.audio_input, AvType.audio_duplex) - elif avtype == AvType.audio_output: - # For a new output, duplex and output needs to be stopped - stream_to_stop = (AvType.audio_output, AvType.audio_duplex) - elif avtype == AvType.audio_duplex: - # All others have to stop - stream_to_stop = list(AvType) # All of them - else: - raise ValueError("BUG") - - for stream in stream_to_stop: - if stream is not None: - self.stopStream(stream) - - def stopAllStreams(self): - """ - Stops all streams - """ - for key in self.streams.keys(): - self.stopStream(key) - - def isStreamRunning(self, avtype: AvType = None): - """ - Check whether a stream is running - - Args: - avtype: The stream type to check whether it is still running. If - None, it checks all streams. - - Returns: - True if a stream is running, otherwise false - """ - if avtype is None: - avtype = list(AvType) - else: - avtype = (avtype,) - for t in avtype: - if self.streams[t] is not None and self.streams[t].running(): - return True - - return False - - def rescanDaqDevices(self): - """ - Rescan the available DaQ devices. - - """ - if self.isStreamRunning(): - self.sendAllQueues( - StreamMsg.streamError, - None, - "A stream is running, cannot rescan DAQ devices.", - ) - return - - self.devices = Daq.getDeviceInfo() - self.sendAllQueues(StreamMsg.deviceList, self.devices) - - def streamCallback(self, audiostream, indata, outdata): - """This is called (from a separate thread) for each audio block.""" - # logging.debug('streamCallback()') - if outdata is not None: - if not self.outq.empty(): - newdata = self.outq.get() - if newdata.shape[0] != outdata.shape[0] or newdata.ndim != 1: - msgtxt = "Invalid output data obtained from queue" - logging.fatal(msgtxt) - self.sendAllQueues( - StreamMsg.streamFatalError, audiostream.avtype, msgtxt - ) - return 1 - outdata[:, :] = newdata[:, None] - else: - msgtxt = "Signal generator buffer underflow. Signal generator cannot keep up with data generation." - # logging.error(msgtxt) - self.sendAllQueues( - StreamMsg.streamTemporaryError, audiostream.avtype, msgtxt - ) - outdata[:, :] = 0 - - if indata is not None: - self.sendInQueues(StreamMsg.streamData, indata) - - return 0 - - # Wrapper functions that safe some typing, they do not require an - # explanation. - def sendInQueues(self, msg, *data): - # logging.debug('sendInQueues()') - try: - for q in self.indata_qlist: - # Fan out the input data to all queues in the queue list - q.put((msg, data)) - except ValueError: - logging.error("Error with data queue, terminating process") - self.stopAllStreams() - self.terminate() - - def sendAllQueues(self, msg, *data): - """ - Destined for all queues, including capture data queues - """ - self.sendInQueues(msg, *data) - try: - for q in self.msg_qlist: - # Fan out the input data to all queues in the queue list - q.put((msg, data)) - except ValueError: - logging.error("Error with data queue, terminating process") - self.stopAllStreams() - self.terminate() - - -@dataclass -class StreamStatus: - lastStatus: StreamMsg = StreamMsg.streamStopped - errorTxt: str = None - streammetadata: StreamMetaData = None - - -class StreamManager: - """ - Audio and video data stream manager, to which queus can be added - """ - - def __init__(self): - """Open a stream for audio in/output and video input. For audio output,""" - - # Initialize streamstatus - self.streamstatus = {t: StreamStatus() for t in list(AvType)} - - self.devices = None - - # Multiprocessing manager, pipe, output queue, input queue, - self.manager = mp.managers.SyncManager() - - # Start this manager and ignore interrupts - # https://stackoverflow.com/questions/21104997/keyboard-interrupt-with-pythons-multiprocessing - self.manager.start(ignoreSigInt) - - # List of queues for all entities that require 'microphone' or input - # data. We need a local list, to manage listener queues, as the queues - # which are in the manager list get a new object id. The local list is - # used to find the index in the manager queues list upon deletion by - # 'removeListener()' - self.indata_qlist = self.manager.list([]) - self.indata_qlist_local = [] - - self.msg_qlist = self.manager.list([]) - self.msg_qlist_local = [] - - # Queue used for signal generator data - self.outq = self.manager.Queue() - - # Messaging pipe - self.pipe, child_pipe = mp.Pipe(duplex=True) - - # This is the queue on which this class listens for stream process - # messages. - self.our_msgqueue = self.addMsgQueueListener() - - # Create the stream process - self.streamProcess = AvStreamProcess( - child_pipe, self.msg_qlist, self.indata_qlist, self.outq - ) - self.streamProcess.start() - - def scanDaqDevices(self): - """ - Output the message to the stream process to rescan the list of devices - """ - self.sendPipe(StreamMsg.scanDaqDevices, None) - - def getStreamStatus(self, avtype: AvType): - """ - Sends a request for the stream status over the pipe, for given AvType - """ - self.sendPipe(StreamMsg.getStreamMetaData, avtype) - - def getOutputQueue(self): - """ - Returns the output queue object. - - Note, should (of course) only be used by one signal generator at the time! - """ - return self.outq - - def addMsgQueueListener(self): - """ - Add a listener queue to the list of message queues, and return the - queue. - - Returns: - listener queue - """ - newqueue = self.manager.Queue() - self.msg_qlist.append(newqueue) - self.msg_qlist_local.append(newqueue) - return newqueue - - def removeMsgQueueListener(self, queue): - """ - Remove an input listener queue from the message queue list. - """ - # Uses a local queue list to find the index, based on the queue - idx = self.msg_qlist_local.index(queue) - del self.msg_qlist_local[idx] - del self.msg_qlist[idx] - - def addInQueueListener(self): - """ - Add a listener queue to the list of queues, and return the queue. - - Returns: - listener queue - """ - newqueue = self.manager.Queue() - self.indata_qlist.append(newqueue) - self.indata_qlist_local.append(newqueue) - return newqueue - - def removeInQueueListener(self, queue): - """ - Remove an input listener queue from the queue list. - """ - # Uses a local queue list to find the index, based on the queue - idx = self.indata_qlist_local.index(queue) - del self.indata_qlist[idx] - del self.indata_qlist_local[idx] - - def startStream(self, avtype: AvType, daqconfig: DaqConfiguration): - """ - Start the stream, which means the callbacks are called with stream - data (audio/video) - - Args: - wait: Wait until the stream starts talking before returning from - this function. - - """ - logging.debug("Starting stream...") - self.sendPipe(StreamMsg.startStream, avtype, daqconfig) - - def stopStream(self, avtype: AvType): - logging.debug(f"StreamManager::stopStream({avtype})") - self.sendPipe(StreamMsg.stopStream, avtype) - - def stopAllStreams(self): - self.sendPipe(StreamMsg.stopAllStreams) - - def cleanup(self): - """ - Stops the stream if it is still running, and after that, it stops the - stream process. - - This method SHOULD always be called before removing a AvStream object. - Otherwise things will wait forever... - - """ - self.sendPipe(StreamMsg.endProcess, None) - logging.debug("Joining stream process...") - self.streamProcess.join() - logging.debug("Joining stream process done") - - def hasVideo(self): - """ - Stub, TODO: for future - """ - return False - - def sendPipe(self, msg, *data): - """ - Send a message with data over the control pipe - """ - self.pipe.send((msg, data)) diff --git a/lasp/lasp_multiprocessingpatch.py b/lasp/lasp_multiprocessingpatch.py deleted file mode 100644 index fcd8e79..0000000 --- a/lasp/lasp_multiprocessingpatch.py +++ /dev/null @@ -1,57 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Author: J.A. de Jong - -Description: MonkeyPatch required to let the Multiprocessing library work properly. -Should be applied prior to running any other multiprocessing code. Comes from -Stackoverflow and is mainly used for managing a list of queues that can be -shared between processes. - -For more information, see: -https://stackoverflow.com/questions/46779860/multiprocessing-managers-and-custom-classes -""" -from multiprocessing import managers -import logging -from functools import wraps -from inspect import signature - -orig_AutoProxy = managers.AutoProxy - -__all__ = ['apply_patch'] - - -@wraps(managers.AutoProxy) -def AutoProxy(*args, incref=True, manager_owned=False, **kwargs): - # Create the autoproxy without the manager_owned flag, then - # update the flag on the generated instance. If the manager_owned flag - # is set, `incref` is disabled, so set it to False here for the same - # result. - autoproxy_incref = False if manager_owned else incref - proxy = orig_AutoProxy(*args, incref=autoproxy_incref, **kwargs) - proxy._owned_by_manager = manager_owned - return proxy - - -def apply_patch(): - if "manager_owned" in signature(managers.AutoProxy).parameters: - return - - logging.debug("Patching multiprocessing.managers.AutoProxy to add manager_owned") - managers.AutoProxy = AutoProxy - - # re-register any types already registered to SyncManager without a custom - # proxy type, as otherwise these would all be using the old unpatched AutoProxy - SyncManager = managers.SyncManager - registry = managers.SyncManager._registry - for typeid, (callable, exposed, method_to_typeid, proxytype) in registry.items(): - if proxytype is not orig_AutoProxy: - continue - create_method = hasattr(managers.SyncManager, typeid) - SyncManager.register( - typeid, - callable=callable, - exposed=exposed, - method_to_typeid=method_to_typeid, - create_method=create_method, - ) - diff --git a/lasp/lasp_record.py b/lasp/lasp_record.py deleted file mode 100644 index a9badef..0000000 --- a/lasp/lasp_record.py +++ /dev/null @@ -1,318 +0,0 @@ -#!/usr/bin/python3.8 -# -*- coding: utf-8 -*- -""" -Read data from stream and record sound and video at the same time -""" -import dataclasses, logging, os, time, h5py -from .lasp_avstream import StreamManager, StreamMetaData, StreamMsg -from .lasp_common import AvType - - -@dataclasses.dataclass -class RecordStatus: - curT: float - done: bool - - -class Recording: - """ - Class used to perform a recording. - """ - def __init__(self, fn: str, streammgr: StreamManager, - rectime: float = None, wait: bool = True, - progressCallback=None, - startDelay: float=0): - """ - Start a recording. Blocks if wait is set to True. - - Args: - fn: Filename to record to. Extension is automatically added if not - provided. - stream: AvStream instance to record from. Should have input - channels! - rectime: Recording time [s], None for infinite, in seconds. If set - to None, or np.inf, the recording continues indefintely. - progressCallback: callable that is called with an instance of - RecordStatus instance as argument. - startDelay: Optional delay added before the recording is *actually* - started in [s]. - """ - ext = '.h5' - if ext not in fn: - fn += ext - - self.smgr = streammgr - self.metadata = None - - assert startDelay >= 0 - self.startDelay = startDelay - # Flag used to indicate that we have passed the start delay - self.startDelay_passed = False - self.rectime = rectime - self.fn = fn - - self.video_frame_positions = [] - self.curT_rounded_to_seconds = 0 - - # Counter of the number of blocks - self.ablockno = 0 - self.vframeno = 0 - - self.progressCallback = progressCallback - self.wait = wait - - self.f = h5py.File(self.fn, 'w') - - # This flag is used to delete the file on finish(), and can be used - # when a recording is canceled. - self.deleteFile = False - - try: - # Input queue - self.inq = streammgr.addInQueueListener() - - except RuntimeError: - # Cleanup stuff, something is going wrong when starting the stream - try: - self.f.close() - except Exception as e: - logging.error( - 'Error preliminary closing measurement file {fn}: {str(e)}') - - self.__deleteFile() - raise - - # Try to obtain stream metadata - streammgr.getStreamStatus(AvType.audio_input) - - self.ad = None - - logging.debug('Starting record....') - # TODO: Fix this later when we want video - # if stream.hasVideo(): - # stream.addCallback(self.aCallback, AvType.audio_input) - self.stop = False - - if self.wait: - logging.debug('Stop recording with CTRL-C') - try: - while not self.stop: - self.handleQueue() - time.sleep(0.01) - except KeyboardInterrupt: - logging.debug("Keyboard interrupt on record") - finally: - self.finish() - - def handleQueue(self): - """ - This method should be called to grab data from the input queue, which - is filled by the stream, and put it into a file. It should be called at - a regular interval to prevent overflowing of the queue. It is called - within the start() method of the recording, if block is set to True. - Otherwise, it should be called from its parent at regular intervals. - For example, in Qt this can be done using a QTimer. - - - """ - # logging.debug('handleQueue()') - while self.inq.qsize() > 0: - msg, data = self.inq.get() - # logging.debug(f'Obtained message: {msg}') - if msg == StreamMsg.streamData: - samples, = data - self.__addTimeData(samples) - elif msg == StreamMsg.streamStarted: - logging.debug(f'handleQueue obtained message {msg}') - avtype, metadata = data - if metadata is None: - raise RuntimeError('BUG: no stream metadata') - if avtype in (AvType.audio_duplex, AvType.audio_input): - self.processStreamMetaData(metadata) - elif msg == StreamMsg.streamMetaData: - logging.debug(f'handleQueue obtained message {msg}') - avtype, metadata = data - if metadata is not None: - self.processStreamMetaData(metadata) - elif msg == StreamMsg.streamTemporaryError: - pass - else: - logging.debug(f'handleQueue obtained message {msg}') - # An error occured, we do not remove the file, but we stop. - self.stop = True - logging.debug(f'Stream message: {msg}. Recording stopped unexpectedly') - raise RuntimeError('Recording stopped unexpectedly') - - - def processStreamMetaData(self, md: StreamMetaData): - """ - Stream metadata has been catched. This is used to set all metadata in - the measurement file - - """ - logging.debug('Recording::processStreamMetaData()') - if self.metadata is not None: - # Metadata already obtained. We check whether the new metadata is - # compatible. Otherwise an error occurs - if md != self.metadata: - raise RuntimeError('BUG: Incompatible stream metadata!') - return - - # The 'Audio' dataset as specified in lasp_measurement, where data is - # sent to. We use gzip as compression, this gives moderate a moderate - # compression to the data. - f = self.f - blocksize = md.blocksize - nchannels = len(md.in_ch) - self.ad = f.create_dataset('audio', - (1, blocksize, nchannels), - dtype=md.dtype, - maxshape=( - None, # This means, we can add blocks - # indefinitely - blocksize, - nchannels), - compression='gzip' - ) - - # TODO: This piece of code is not up-to-date and should be changed at a - # later instance once we really want to record video simultaneously - # with audio. - # if smgr.hasVideo(): - # video_x, video_y = smgr.video_x, smgr.video_y - # self.vd = f.create_dataset('video', - # (1, video_y, video_x, 3), - # dtype='uint8', - # maxshape=( - # None, video_y, video_x, 3), - # compression='gzip' - # ) - - # Set the bunch of attributes - f.attrs['samplerate'] = md.fs - f.attrs['nchannels'] = nchannels - f.attrs['blocksize'] = blocksize - f.attrs['sensitivity'] = [ch.sensitivity for ch in md.in_ch] - f.attrs['channelNames'] = [ch.channel_name for ch in md.in_ch] - f.attrs['time'] = time.time() - self.blocksize = blocksize - self.fs = md.fs - - # Measured physical quantity metadata - f.attrs['qtys'] = [ch.qty.to_json() for ch in md.in_ch] - self.metadata = md - - def setDelete(self, val: bool): - """ - Set the delete flag. If set, measurement file is deleted at the end of - the recording. Typically used for cleaning up after canceling a - recording. - """ - self.deleteFile = val - - def finish(self): - """ - This method should be called to finish and a close a recording file, - remove the queue from the stream, etc. - - """ - logging.debug('Recording::finish()') - smgr = self.smgr - - # TODO: Fix when video - # if smgr.hasVideo(): - # smgr.removeCallback(self.vCallback, AvType.video_input) - # self.f['video_frame_positions'] = self.video_frame_positions - - try: - smgr.removeInQueueListener(self.inq) - except Exception as e: - logging.error(f'Could not remove queue from smgr: {e}') - - try: - # Close the recording file - self.f.close() - except Exception as e: - logging.error(f'Error closing file: {e}') - - logging.debug('Recording ended') - if self.deleteFile: - self.__deleteFile() - - def __deleteFile(self): - """ - Cleanup the recording file. - """ - try: - os.remove(self.fn) - except Exception as e: - logging.error(f'Error deleting file: {self.fn}') - - def __addTimeData(self, indata): - """ - Called by handleQueue() and adds new time data to the storage file. - """ - # logging.debug('Recording::__addTimeData()') - - if self.stop: - # Stop flag is raised. We stop recording here. - return - - # The current time that is recorded and stored into the file, without - # the new data - if not self.metadata: - # We obtained stream data, but metadata is not yet available. - # Therefore, we request it explicitly and then we return - logging.info('Requesting stream metadata') - self.smgr.getStreamStatus(AvType.audio_input) - self.smgr.getStreamStatus(AvType.audio_duplex) - return - - curT = self.ablockno*self.blocksize/self.fs - - # Increase the block counter - self.ablockno += 1 - - if curT < self.startDelay and not self.startDelay_passed: - # Start delay has not been passed - return - elif curT >= 0 and not self.startDelay_passed: - # Start delay passed, switch the flag! - self.startDelay_passed = True - # Reset the audio block counter and the time - self.ablockno = 1 - curT = 0 - - recstatus = RecordStatus( - curT=curT, - done=False) - - if self.progressCallback is not None: - self.progressCallback(recstatus) - - curT_rounded_to_seconds = int(curT) - if curT_rounded_to_seconds > self.curT_rounded_to_seconds: - self.curT_rounded_to_seconds = curT_rounded_to_seconds - print(f'{curT_rounded_to_seconds}', end='', flush=True) - else: - print('.', end='', flush=True) - - if self.rectime is not None and curT > self.rectime: - # We are done! - if self.progressCallback is not None: - recstatus.done = True - self.progressCallback(recstatus) - self.stop = True - return - - # Add the data to the file, and resize the audio data blocks - self.ad.resize(self.ablockno, axis=0) - self.ad[self.ablockno-1, :, :] = indata - - - # def _vCallback(self, frame, framectr): - # self.video_frame_positions.append(self.ablockno()) - # vframeno = self.vframeno - # self.vd.resize(vframeno+1, axis=0) - # self.vd[vframeno, :, :] = frame - # self.vframeno += 1 diff --git a/lasp/lasp_siggen.py b/lasp/lasp_siggen.py deleted file mode 100644 index a8cfada..0000000 --- a/lasp/lasp_siggen.py +++ /dev/null @@ -1,369 +0,0 @@ -#!/usr/bin/env python3.6 -# -*- coding: utf-8 -*- -""" -Author: J.A. de Jong - ASCEE - -Description: Signal generator code - -""" -import multiprocessing as mp -import dataclasses -import logging -from typing import Tuple -import numpy as np - -from .filter import PinkNoise -from .lasp_octavefilter import SosOctaveFilterBank, SosThirdOctaveFilterBank -from .filter import OctaveBankDesigner, PinkNoise, ThirdOctaveBankDesigner -from .lasp_avstream import StreamManager, ignoreSigInt -from .wrappers import Siggen as pyxSiggen, Equalizer -from enum import Enum, unique, auto - -QUEUE_BUFFER_TIME = 0.5 # The amount of time used in the queues for buffering -# of data, larger is more stable, but also enlarges latency - -__all__ = ["SignalType", "NoiseType", "SiggenMessage", "SiggenData", "Siggen"] - - -@unique -class SignalType(Enum): - Periodic = 0 - Noise = 1 - Sweep = 2 - Meas = 3 - - -@unique -class NoiseType(Enum): - white = "White noise" - pink = "Pink noise" - - def __str__(self): - return str(self.value) - - @staticmethod - def fillComboBox(combo): - for type_ in list(NoiseType): - combo.addItem(str(type_)) - - @staticmethod - def getCurrent(cb): - return list(NoiseType)[cb.currentIndex()] - - -class SiggenWorkerDone(Exception): - def __str__(self): - return "Done generating signal" - - -@unique -class SiggenMessage(Enum): - """ - Different messages that can be send to the signal generator over the pipe - connection. - """ - endProcess = auto() # Stop and quit the signal generator - adjustVolume = auto() # Adjust the volume - newEqSettings = auto() # Forward new equalizer settings - newSiggenData = auto() # Forward new equalizer settings - ready = auto() # Send out once, once the signal generator is ready with - # pre-generating data. - mute = auto() # Mute / unmute siggen - - # These messages are send back to the main thread over the pipe - error = auto() - done = auto() - - -@dataclasses.dataclass -class SiggenData: - """ - Metadata used to create a Signal Generator - """ - fs: float # Sample rate [Hz] - - # Number of frames "samples" to send in one block - nframes_per_block: int - - # The data type to output - dtype: np.dtype - - # Muted? - muted: bool - - # Level of output signal [dBFS]el - level_dB: float - - # Signal type specific data, i.e. - signaltype: SignalType - signaltypedata: Tuple = None - - # Settings for the equalizer etc - eqdata: object = None # Equalizer data - -class SiggenProcess(mp.Process): - """ - Main function running in a different process, is responsible for generating - new signal data. Uses the signal queue to push new generated signal data - on. - """ - def __init__(self, siggendata, dataq, pipe): - - """ - Args: - siggendata: The signal generator data to start with. - dataq: The queue to put generated signal on - pipe: Control and status messaging pipe - """ - super().__init__() - - # When this is set, a kill on the main process will also kill the - # siggen process. Highly wanted feature - self.daemon = True - - self.dataq = dataq - self.siggendata = siggendata - self.pipe = pipe - self.eq = None - self.siggen = None - - fs = self.siggendata.fs - nframes_per_block = siggendata.nframes_per_block - self.nblocks_buffer = max( - 1, int(QUEUE_BUFFER_TIME * fs/ nframes_per_block) - ) - - def newSiggen(self, siggendata: SiggenData): - """ - Create a signal generator based on parameters specified in global - function data. - - Args: - siggendata: SiggenData. Metadata to create a new signal generator. - """ - - logging.debug('newSiggen') - - fs = siggendata.fs - nframes_per_block = siggendata.nframes_per_block - level_dB = siggendata.level_dB - signaltype = siggendata.signaltype - signaltypedata = siggendata.signaltypedata - - # Muted state - self.muted = siggendata.muted - - if signaltype == SignalType.Periodic: - freq, = signaltypedata - siggen = pyxSiggen.sineWave(fs, freq, level_dB) - elif signaltype == SignalType.Noise: - noisetype, zerodBpoint = signaltypedata - if noisetype == NoiseType.white: - sos_colorfilter = None - elif noisetype == NoiseType.pink: - sos_colorfilter = PinkNoise(fs, zerodBpoint).flatten() - else: - raise ValueError(f"Unknown noise type") - - siggen = pyxSiggen.noise(fs, level_dB, sos_colorfilter) - - elif signaltype == SignalType.Sweep: - fl, fu, Ts, Tq, sweep_flags = signaltypedata - siggen = pyxSiggen.sweep(fs, fl, fu, Ts, Tq, sweep_flags, level_dB) - - else: - raise ValueError(f"Not implemented signal type: {signaltype}") - - logging.debug('newSiggen') - return siggen - - def generate(self): - """ - Generate a single block of data and put it on the data queue - """ - signal = self.siggen.genSignal(self.siggendata.nframes_per_block) - dtype = self.siggendata.dtype - if self.eq is not None: - signal = self.eq.equalize(signal) - if np.issubdtype(dtype, np.integer): - bitdepth_fixed = dtype.itemsize * 8 - signal *= 2 ** (bitdepth_fixed - 1) - 1 - if self.muted: - # Mute it - signal *= 0 - try: - self.dataq.put(signal.astype(dtype)) - except ValueError: - # As of Python 3.8, a value error on a Queue means that the oter - # end of the process died. - logging.error("Error with data queue, terminating process") - self.terminate() - - def newEqualizer(self, eqdata): - """ - Create an equalizer object from equalizer data - - Args: - eqdata: dictionary containing equalizer data. TODO: document the - requiring fields. - """ - if eqdata is None: - return None - eq_type = eqdata['type'] - eq_levels = eqdata['levels'] - fs = self.siggendata.fs - - if eq_type == 'three': - fb = SosThirdOctaveFilterBank(fs) - elif eq_type == 'one': - fb = SosOctaveFilterBank(fs) - - eq = Equalizer(fb._fb) - if eq_levels is not None: - eq.setLevels(eq_levels) - return eq - - def sendPipe(self, msgtype, msg): - try: - self.pipe.send((msgtype, msg)) - except OSError: - logging.error("Error with pipe, terminating process") - self.terminate() - - def run(self): - # The main function of the actual process - # First things first - ignoreSigInt() - - try: - self.siggen = self.newSiggen(self.siggendata) - except Exception as e: - self.sendPipe(SiggenMessage.error, str(e)) - - try: - self.eq = self.newEqualizer(self.siggendata.eqdata) - except Exception as e: - self.sendPipe(SiggenMessage.error, str(e)) - - # Pre-generate blocks of signal data - while self.dataq.qsize() < self.nblocks_buffer: - self.generate() - - self.sendPipe(SiggenMessage.ready, None) - - while True: - # Wait here for a while, to check for messages to consume - if self.pipe.poll(timeout=QUEUE_BUFFER_TIME / 4): - try: - msg, data = self.pipe.recv() - except OSError: - logging.error("Error with pipe, terminating process") - self.terminate() - - if msg == SiggenMessage.endProcess: - logging.debug("Signal generator caught 'endProcess' message. Exiting.") - return 0 - elif msg == SiggenMessage.mute: - self.muted = data - elif msg == SiggenMessage.adjustVolume: - level_dB = data - logging.debug(f"Signal generator caught 'adjustVolume' message. New volume = {level_dB:.1f} dB FS") - self.siggen.setLevel(level_dB) - elif msg == SiggenMessage.newEqSettings: - eqdata = data - self.eq = self.newEqualizer(eqdata) - elif msg == SiggenMessage.newSiggenData: - siggendata = data - self.siggen = self.newSiggen(siggendata) - else: - self.pipe.send( - SiggenMessage.error, "BUG: Generator caught unknown message. Quiting" - ) - while self.dataq.qsize() < self.nblocks_buffer: - # Generate new data and put it in the queue! - try: - self.generate() - except SiggenWorkerDone: - self.sendPipe(SiggenMessage.done, None) - return 0 - - return 1 - - -class Siggen: - """ - Signal generator class, generates signal data in a different process to - unload the work in the calling thread. - """ - - def __init__(self, dataq, siggendata: SiggenData): - """""" - - self.pipe, client_end = mp.Pipe(duplex=True) - - self.stopped = False - - self.process = SiggenProcess(siggendata, dataq, client_end) - self.process.start() - - if not self.process.is_alive(): - raise RuntimeError('Unexpected signal generator exception') - - # Block waiting here for signal generator to be ready - msg, data = self.pipe.recv() - if msg == SiggenMessage.ready: - logging.debug('Signal generator ready') - elif msg == SiggenMessage.error: - e = data - raise RuntimeError(f'Signal generator exception: {str(e)}') - else: - # Done, or something - if msg == SiggenMessage.done: - self.stopped = True - - self.handle_msgs() - - def setLevel(self, new_level): - """ - Set a new signal level to the generator - - Args: - new_level: The new level in [dBFS] - """ - self.pipe.send((SiggenMessage.adjustVolume, new_level)) - - def mute(self, mute): - self.pipe.send((SiggenMessage.mute, mute)) - - def setEqData(self, eqdata): - self.pipe.send((SiggenMessage.newEqSettings, eqdata)) - - def setSiggenData(self, siggendata: SiggenData): - """ - Updates the whole signal generator, based on new signal generator data. - """ - self.pipe.send((SiggenMessage.newSiggenData, siggendata)) - - def handle_msgs(self): - while self.pipe.poll(): - msg, data = self.pipe.recv() - if msg == SiggenMessage.error: - raise RuntimeError( - f"Error in initialization of signal generator: {data}" - ) - # elif msg == SiggenMessage.done: - # self.stop() - - - def cleanup(self): - logging.debug('Siggen::stop()') - self.pipe.send((SiggenMessage.endProcess, None)) - self.pipe.close() - - logging.debug('Joining siggen process') - self.process.join() - logging.debug('Joining siggen process done') - self.process.close() - - self.process = None - diff --git a/lasp/wrappers.pyx b/lasp/wrappers.pyx deleted file mode 100644 index 444bf49..0000000 --- a/lasp/wrappers.pyx +++ /dev/null @@ -1,798 +0,0 @@ -""" -This file contains the Cython wrapper functions to C implementations. -""" -include "config.pxi" -from libc.stdio cimport printf -from numpy cimport import_array - -import_array() - - -IF LASP_DOUBLE_PRECISION == "ON": - ctypedef double d - ctypedef double complex c - NUMPY_FLOAT_TYPE = np.float64 - NUMPY_COMPLEX_TYPE = np.complex128 - CYTHON_NUMPY_FLOAT_t = cnp.NPY_FLOAT64 - CYTHON_NUMPY_COMPLEX_t = cnp.NPY_COMPLEX128 - -ELSE: - ctypedef float d - ctypedef float complex c - NUMPY_FLOAT_TYPE = np.float32 - NUMPY_COMPLEX_TYPE = np.complex64 - CYTHON_NUMPY_FLOAT_t = cnp.NPY_FLOAT32 - CYTHON_NUMPY_COMPLEX_t = cnp.NPY_COMPLEX64 - -ctypedef size_t us - -cdef extern from "lasp_tracer.h": - void setTracerLevel(int) - void TRACE(int,const char*) - void fsTRACE(int) - void feTRACE(int) - void clearScreen() - - -cdef extern from "lasp_mat.h" nogil: - ctypedef struct dmat: - us n_cols - us n_rows - d* _data - bint _foreign_data - ctypedef struct cmat: - pass - ctypedef cmat vc - ctypedef dmat vd - - dmat dmat_foreign_data(us n_rows, - us n_cols, - d* data, - bint own_data) - cmat cmat_foreign_data(us n_rows, - us n_cols, - c* data, - bint own_data) - - cmat cmat_alloc(us n_rows,us n_cols) - dmat dmat_alloc(us n_rows,us n_cols) - vd vd_foreign(const us size,d* data) - void vd_free(vd*) - - void dmat_free(dmat*) - void cmat_free(cmat*) - void cmat_copy(cmat* to,cmat* from_) - - -cdef extern from "numpy/arrayobject.h": - void PyArray_ENABLEFLAGS(cnp.ndarray arr, int flags) - - -cdef extern from "lasp_python.h": - object dmat_to_ndarray(dmat*,bint transfer_ownership) - -__all__ = ['AvPowerSpectra', 'SosFilterBank', 'FilterBank', 'Siggen', - 'sweep_flag_forward', 'sweep_flag_backward', 'sweep_flag_linear', - 'sweep_flag_exponential', - 'load_fft_wisdom', 'store_fft_wisdom'] - -setTracerLevel(15) - -cdef extern from "cblas.h": - int openblas_get_num_threads() - void openblas_set_num_threads(int) - -# If we touch this variable: we get segfaults when running from -# Spyder! -# openblas_set_num_threads(8) -# print("Number of threads: ", -# openblas_get_num_threads()) - -def cls(): - clearScreen() -# cls() - -cdef extern from "lasp_fft.h": - void c_load_fft_wisdom "load_fft_wisdom" (const char* wisdom) - char* c_store_fft_wisdom "store_fft_wisdom" () - ctypedef struct c_Fft "Fft" - c_Fft* Fft_create(us nfft) - void Fft_free(c_Fft*) - void Fft_fft(c_Fft*,dmat * timedate,cmat * res) nogil - void Fft_ifft(c_Fft*,cmat * freqdata,dmat* timedata) nogil - us Fft_nfft(c_Fft*) - -def load_fft_wisdom(const unsigned char[::1] wisdom): - c_load_fft_wisdom( &wisdom[0]) - -from cpython cimport PyBytes_FromString -from libc.stdlib cimport free - -def store_fft_wisdom(): - cdef char* wisdom = c_store_fft_wisdom() - - if wisdom != NULL: - try: - bts = PyBytes_FromString(wisdom) - finally: - free(wisdom) - return bts - else: - return None - -cdef class Fft: - cdef: - c_Fft* _fft - - def __cinit__(self, us nfft): - self._fft = Fft_create(nfft) - if self._fft == NULL: - raise RuntimeError('Fft allocation failed') - - def __dealloc__(self): - if self._fft!=NULL: - Fft_free(self._fft) - - def fft(self,d[::1,:] timedata): - - cdef us nfft = Fft_nfft(self._fft) - cdef us nchannels = timedata.shape[1] - assert timedata.shape[0] ==nfft - - result = np.empty((nfft//2+1,nchannels), - dtype=NUMPY_COMPLEX_TYPE, - order='F') - - # result[:,:] = np.nan+1j*np.nan - cdef c[::1,:] result_view = result - cdef cmat r = cmat_foreign_data(result.shape[0], - result.shape[1], - &result_view[0,0], - False) - - cdef dmat t = dmat_foreign_data(timedata.shape[0], - timedata.shape[1], - &timedata[0,0], - False) - with nogil: - Fft_fft(self._fft,&t,&r) - - dmat_free(&t) - cmat_free(&r) - - return result - - def ifft(self,c[::1,:] freqdata): - - cdef us nfft = Fft_nfft(self._fft) - cdef us nchannels = freqdata.shape[1] - assert freqdata.shape[0] == nfft//2+1 - - - # result[:,:] = np.nan+1j*np.nan - - cdef cmat f = cmat_foreign_data(freqdata.shape[0], - freqdata.shape[1], - &freqdata[0,0], - False) - - timedata = np.empty((nfft,nchannels), - dtype=NUMPY_FLOAT_TYPE, - order='F') - - cdef d[::1,:] timedata_view = timedata - cdef dmat t = dmat_foreign_data(timedata.shape[0], - timedata.shape[1], - &timedata_view[0,0], - False) - - with nogil: - Fft_ifft(self._fft,&f,&t) - - dmat_free(&t) - cmat_free(&f) - - return timedata - -cdef extern from "lasp_window.h": - ctypedef enum WindowType: - Hann - Hamming - Rectangular - Bartlett - Blackman - -# Export these constants to Python -class Window: - hann = Hann - hamming = Hamming - rectangular = Rectangular - bartlett = Bartlett - blackman = Blackman - -cdef extern from "lasp_ps.h": - ctypedef struct c_PowerSpectra "PowerSpectra" - c_PowerSpectra* PowerSpectra_alloc(const us nfft, - const WindowType wt) - - void PowerSpectra_compute(const c_PowerSpectra* ps, - const dmat * timedata, - cmat * result) nogil - - - void PowerSpectra_free(c_PowerSpectra*) - -cdef class PowerSpectra: - cdef: - c_PowerSpectra* _ps - - def __cinit__(self, us nfft,us window=Window.rectangular): - self._ps = PowerSpectra_alloc(nfft, window) - if self._ps == NULL: - raise RuntimeError('PowerSpectra allocation failed') - - def compute(self,d[::1,:] timedata): - cdef: - us nchannels = timedata.shape[1] - us nfft = timedata.shape[0] - int rv - dmat td - cmat result_mat - - - td = dmat_foreign_data(nfft, - nchannels, - &timedata[0,0], - False) - - # The array here is created in such a way that the strides - # increase with increasing dimension. This is required for - # interoperability with the C-code, that stores all - # cross-spectra in a 2D matrix, where the first axis is the - # frequency axis, and the second axis corresponds to a certain - # cross-spectrum, as C_ij(f) = result[freq,i+j*nchannels] - - result = np.empty((nfft//2+1,nchannels,nchannels), - dtype = NUMPY_COMPLEX_TYPE, - order='F') - - cdef c[::1,:,:] result_view = result - - result_mat = cmat_foreign_data(nfft//2+1, - nchannels*nchannels, - &result_view[0,0,0], - False) - - - with nogil: - PowerSpectra_compute(self._ps,&td,&result_mat) - - dmat_free(&td) - cmat_free(&result_mat) - - return result - - - def __dealloc__(self): - if self._ps != NULL: - PowerSpectra_free(self._ps) - - -cdef extern from "lasp_aps.h": - ctypedef struct c_AvPowerSpectra "AvPowerSpectra" - c_AvPowerSpectra* AvPowerSpectra_alloc(const us nfft, - const us nchannels, - d overlap_percentage, - const WindowType wt, - const vd* weighting) - - cmat* AvPowerSpectra_addTimeData(const c_AvPowerSpectra* ps, - const dmat * timedata) nogil - - - void AvPowerSpectra_free(c_AvPowerSpectra*) - us AvPowerSpectra_getAverages(const c_AvPowerSpectra*); - -cdef class AvPowerSpectra: - cdef: - c_AvPowerSpectra* aps - us nfft, nchannels - - def __cinit__(self,us nfft, - us nchannels, - d overlap_percentage, - us window=Window.hann, - d[:] weighting = np.array([])): - - - cdef vd weighting_vd - cdef vd* weighting_ptr = NULL - if(weighting.size != 0): - weighting_vd = dmat_foreign_data(weighting.size,1, - &weighting[0],False) - weighting_ptr = &weighting_vd - - self.aps = AvPowerSpectra_alloc(nfft, - nchannels, - overlap_percentage, - window, - weighting_ptr) - self.nchannels = nchannels - self.nfft = nfft - - if self.aps == NULL: - raise RuntimeError('AvPowerSpectra allocation failed') - - def __dealloc__(self): - if self.aps: - AvPowerSpectra_free(self.aps) - def getAverages(self): - return AvPowerSpectra_getAverages(self.aps) - - def addTimeData(self,d[::1,:] timedata): - """! - Adds time data, returns current result - """ - cdef: - us nsamples = timedata.shape[0] - us nchannels = timedata.shape[1] - dmat td - cmat* result_ptr - - if nchannels != self.nchannels: - raise RuntimeError('Invalid number of channels') - - td = dmat_foreign_data(nsamples, - nchannels, - &timedata[0,0], - False) - - result = np.empty((self.nfft//2+1,nchannels,nchannels), - dtype = NUMPY_COMPLEX_TYPE, - order='F') - - cdef c[::1,:,:] result_view = result - - cdef cmat res = cmat_foreign_data(self.nfft//2+1, - nchannels*nchannels, - &result_view[0,0,0], - False) - with nogil: - result_ptr = AvPowerSpectra_addTimeData(self.aps, - &td) - - # The array here is created in such a way that the strides - # increase with increasing dimension. This is required for - # interoperability with the C-code, that stores all - # cross-spectra in a 2D matrix, where the first axis is the - # frequency axis, and the second axis corresponds to a certain - # cross-spectrum, as C_ij(f) = result[freq,i+j*nchannels] - - # Copy result - cmat_copy(&res,result_ptr) - - cmat_free(&res) - dmat_free(&td) - - return result - -cdef extern from "lasp_firfilterbank.h": - ctypedef struct c_Firfilterbank "Firfilterbank" - c_Firfilterbank* Firfilterbank_create(const dmat* h,const us nfft) nogil - dmat Firfilterbank_filter(c_Firfilterbank* fb,const vd* x) nogil - void Firfilterbank_free(c_Firfilterbank* fb) nogil - - -cdef class FilterBank: - cdef: - c_Firfilterbank* fb - def __cinit__(self,d[::1,:] h, us nfft): - cdef dmat hmat = dmat_foreign_data(h.shape[0], - h.shape[1], - &h[0,0], - False) - - self.fb = Firfilterbank_create(&hmat,nfft) - dmat_free(&hmat) - if not self.fb: - raise RuntimeError('Error creating FilberBank') - - def __dealloc__(self): - if self.fb: - Firfilterbank_free(self.fb) - - def filter_(self,d[::1, :] input_): - assert input_.shape[1] == 1 - cdef dmat input_vd = dmat_foreign_data(input_.shape[0],1, - &input_[0, 0],False) - - - cdef dmat output - with nogil: - output = Firfilterbank_filter(self.fb,&input_vd) - - # Steal the pointer from output - result = dmat_to_ndarray(&output,True) - - dmat_free(&output) - vd_free(&input_vd) - - return result - -cdef extern from "lasp_sosfilterbank.h": - ctypedef struct c_Sosfilterbank "Sosfilterbank" - c_Sosfilterbank* Sosfilterbank_create(const us nthreads, const us filterbank_size, const us nsections) nogil - void Sosfilterbank_setFilter(c_Sosfilterbank* fb,const us filter_no, const vd filter_coefs) - dmat Sosfilterbank_filter(c_Sosfilterbank* fb,const vd* x) nogil - void Sosfilterbank_free(c_Sosfilterbank* fb) nogil - - -cdef class SosFilterBank: - cdef: - c_Sosfilterbank* fb - us nsections - def __cinit__(self,const us filterbank_size, const us nsections): - self.nsections = nsections - - self.fb = Sosfilterbank_create(0, filterbank_size,nsections) - - def setFilter(self,us filter_no, d[:, ::1] sos): - """ - - Args: - filter_no: Filter number of the filterbank to set the - filter for - sos: Second axis are the filter coefficients, first axis - is the section, second axis are the coefficients for that section. - Storage is in agreement with specification from Scipy: first axis - is the section, second axis are the coefficients for that section. - - """ - if sos.shape[0] != self.nsections: - raise RuntimeError(f'Invalid number of sections in filter data, should be {self.nsections}.') - elif sos.shape[1] != 6: - raise RuntimeError('Illegal number of filter coefficients in section. Should be 6.') - cdef dmat coefs = dmat_foreign_data(sos.size,1, - &sos[0, 0],False # No copying - ) - Sosfilterbank_setFilter(self.fb,filter_no, coefs) - - def __dealloc__(self): - if self.fb: - Sosfilterbank_free(self.fb) - - def filter_(self,d[::1, :] input_): - # Only single channel input - assert input_.shape[1] == 1 - cdef dmat input_vd = dmat_foreign_data(input_.shape[0],1, - &input_[0, 0],False) - - - cdef dmat output - with nogil: - output = Sosfilterbank_filter(self.fb,&input_vd) - #printf('Came back from filter\n') - # Steal the pointer from output - result = dmat_to_ndarray(&output,True) - #printf('Converted to array\n') - dmat_free(&output) - vd_free(&input_vd) - #printf('Ready to return\n') - return result - -cdef extern from "lasp_decimation.h": - ctypedef struct c_Decimator "Decimator" - ctypedef enum DEC_FAC: - DEC_FAC_4 - - c_Decimator* Decimator_create(us nchannels,DEC_FAC d) nogil - dmat Decimator_decimate(c_Decimator* dec,const dmat* samples) nogil - void Decimator_free(c_Decimator* dec) nogil - - -cdef extern from "lasp_slm.h" nogil: - ctypedef struct c_Slm "Slm" - d TAU_FAST, TAU_SLOW, TAU_IMPULSE - c_Slm* Slm_create(c_Sosfilterbank* prefilter, - c_Sosfilterbank* bandpass, - d fs, d tau, d ref_level, - us* downsampling_fac) - dmat Slm_run(c_Slm* slm,vd* input_data) - void Slm_free(c_Slm* slm) - vd Slm_Leq(c_Slm*) - vd Slm_Lmax(c_Slm*) - vd Slm_Lpeak(c_Slm*) - - -tau_fast = TAU_FAST -tau_slow = TAU_SLOW -tau_impulse = TAU_IMPULSE - -cdef class Slm: - cdef: - c_Slm* c_slm - public us downsampling_fac - - def __cinit__(self, d[::1] sos_prefilter, - d[:, ::1] sos_bandpass, - d fs, d tau, d ref_level): - - cdef: - us prefilter_nsections - us bandpass_nsections - us bandpass_nchannels - c_Sosfilterbank* prefilter = NULL - c_Sosfilterbank* bandpass = NULL - vd coefs_vd - d[:] coefs - - if sos_prefilter is not None: - assert sos_prefilter.size % 6 == 0 - prefilter_nsections = sos_prefilter.size // 6 - prefilter = Sosfilterbank_create(0, 1,prefilter_nsections) - coefs = sos_prefilter - coefs_vd = dmat_foreign_data(prefilter_nsections*6,1, - &coefs[0],False) - Sosfilterbank_setFilter(prefilter, 0, coefs_vd) - - if prefilter is NULL: - raise RuntimeError('Error creating pre-filter') - - if sos_bandpass is not None: - assert sos_bandpass.shape[1] % 6 == 0 - bandpass_nsections = sos_bandpass.shape[1] // 6 - bandpass_nchannels = sos_bandpass.shape[0] - bandpass = Sosfilterbank_create(0, - bandpass_nchannels, - bandpass_nsections) - if bandpass == NULL: - if prefilter: - Sosfilterbank_free(prefilter) - raise RuntimeError('Error creating bandpass filter') - - - for i in range(bandpass_nchannels): - coefs = sos_bandpass[i, :] - coefs_vd = dmat_foreign_data(bandpass_nsections*6,1, - &coefs[0],False) - - Sosfilterbank_setFilter(bandpass, i, coefs_vd) - - self.c_slm = Slm_create(prefilter, bandpass, - fs, tau, ref_level, - &self.downsampling_fac) - if self.c_slm is NULL: - Sosfilterbank_free(prefilter) - Sosfilterbank_free(bandpass) - raise RuntimeError('Error creating sound level meter') - - def run(self, d[:, ::1] data): - assert data.shape[1] == 1 - cdef vd data_vd = dmat_foreign_data(data.shape[0], 1, - &data[0,0], False) - cdef dmat res - with nogil: - res = Slm_run(self.c_slm, &data_vd) - result = dmat_to_ndarray(&res,True) - return result - - def Leq(self): - cdef vd res - res = Slm_Leq(self.c_slm) - # True below, means transfer ownership of allocated data to Numpy - return dmat_to_ndarray(&res,True) - - def Lmax(self): - cdef vd res - res = Slm_Lmax(self.c_slm) - # True below, means transfer ownership of allocated data to Numpy - return dmat_to_ndarray(&res,True) - - def Lpeak(self): - cdef vd res - res = Slm_Lpeak(self.c_slm) - # True below, means transfer ownership of allocated data to Numpy - return dmat_to_ndarray(&res,True) - - def __dealloc__(self): - if self.c_slm: - Slm_free(self.c_slm) - - - - -cdef class Decimator: - cdef: - c_Decimator* dec - us nchannels - def __cinit__(self, us nchannels,us dec_fac): - assert dec_fac == 4, 'Invalid decimation factor' - self.nchannels = nchannels - self.dec = Decimator_create(nchannels,DEC_FAC_4) - if not self.dec: - raise RuntimeError('Error creating decimator') - - def decimate(self,d[::1,:] samples): - assert samples.shape[1] == self.nchannels,'Invalid number of channels' - if samples.shape[0] == 0: - return np.zeros((0, self.nchannels)) - - cdef dmat d_samples = dmat_foreign_data(samples.shape[0], - samples.shape[1], - &samples[0,0], - False) - - cdef dmat res = Decimator_decimate(self.dec,&d_samples) - result = dmat_to_ndarray(&res,True) - dmat_free(&res) - return result - - def __dealloc__(self): - if self.dec != NULL: - Decimator_free(self.dec) - - -cdef extern from "lasp_siggen.h": - ctypedef struct c_Siggen "Siggen" - us SWEEP_FLAG_FORWARD, SWEEP_FLAG_BACKWARD, SWEEP_FLAG_LINEAR - us SWEEP_FLAG_EXPONENTIAL - - c_Siggen* Siggen_Noise_create(d fs, d level_dB, c_Sosfilterbank* - colorfilter) - c_Siggen* Siggen_Sinewave_create(d fs, d freq, d level_dB) - c_Siggen* Siggen_Sweep_create(d fs, d fl, - d fu, d Ts,d Tq, us sweep_flags, - d level_dB) - void Siggen_setLevel(c_Siggen*,d new_level_dB) - us Siggen_getN(const c_Siggen*) - void Siggen_genSignal(c_Siggen*, vd* samples) nogil - void Siggen_free(c_Siggen*) - -# Sweep flags -sweep_flag_forward = SWEEP_FLAG_FORWARD -sweep_flag_backward = SWEEP_FLAG_BACKWARD - -sweep_flag_linear = SWEEP_FLAG_LINEAR -sweep_flag_exponential = SWEEP_FLAG_EXPONENTIAL - -from .filter import PinkNoise - -cdef class Siggen: - - cdef c_Siggen *_siggen - - def __cinit__(self): - self._siggen = NULL - - def __dealloc__(self): - if self._siggen: - Siggen_free(self._siggen) - - def setLevel(self,d level_dB): - Siggen_setLevel(self._siggen, level_dB) - - def genSignal(self, us nsamples): - output = np.empty(nsamples, dtype=np.float) - assert self._siggen != NULL - - cdef d[:] output_view = output - - cdef dmat output_dmat = dmat_foreign_data(nsamples, - 1, - &output_view[0], - False) - with nogil: - Siggen_genSignal(self._siggen, - &output_dmat) - - return output - - def getN(self): - return Siggen_getN(self._siggen) - - def progress(self): - """ - TODO: Should be implemented to return the current position in the - generator. - - """ - return None - - @staticmethod - def sineWave(d fs,d freq,d level_dB): - cdef c_Siggen* c_siggen = Siggen_Sinewave_create(fs, freq, level_dB) - siggen = Siggen() - siggen._siggen = c_siggen - return siggen - - - @staticmethod - def noise(d fs, d level_dB, d[::1] colorfilter_coefs=None): - cdef: - c_Sosfilterbank* colorfilter = NULL - if colorfilter_coefs is not None: - assert colorfilter_coefs.size % 6 == 0 - colorfilter_nsections = colorfilter_coefs.size // 6 - colorfilter = Sosfilterbank_create(0, 1,colorfilter_nsections) - coefs = colorfilter_coefs - coefs_vd = dmat_foreign_data(colorfilter_nsections*6,1, - &colorfilter_coefs[0],False) - Sosfilterbank_setFilter(colorfilter, 0, coefs_vd) - - if colorfilter is NULL: - raise RuntimeError('Error creating pre-filter') - cdef c_Siggen* c_siggen = Siggen_Noise_create(fs, level_dB, colorfilter) - siggen = Siggen() - siggen._siggen = c_siggen - return siggen - - - @staticmethod - def sweep(d fs, d fl, d fu, d Ts, d Tq, us sweep_flags, d level_dB): - cdef c_Siggen* c_siggen = Siggen_Sweep_create(fs, - fl, - fu, - Ts, - Tq, - sweep_flags, - level_dB) - if c_siggen == NULL: - raise ValueError('Failed creating signal generator') - siggen = Siggen() - siggen._siggen = c_siggen - return siggen - -cdef extern from "lasp_eq.h" nogil: - ctypedef struct c_Eq "Eq" - c_Eq* Eq_create(c_Sosfilterbank* fb) - vd Eq_equalize(c_Eq* eq,const vd* input_data) - us Eq_getNLevels(const c_Eq* eq) - void Eq_setLevels(c_Eq* eq, const vd* levels) - void Eq_free(c_Eq* eq) - -cdef class Equalizer: - cdef: - c_Eq* ceq - - def __cinit__(self, SosFilterBank cdef_fb): - """ - Initialize equalizer using given filterbank. Note: Steals pointer of - underlying c_Sosfilterbank!! - """ - self.ceq = Eq_create(cdef_fb.fb) - # Set this pointer to NULL, such that the underlying c_SosfilterBank is - # not deallocated. - cdef_fb.fb = NULL - - def getNLevels(self): - return Eq_getNLevels(self.ceq) - - def setLevels(self,d[:] new_levels): - cdef dmat dmat_new_levels = dmat_foreign_data(new_levels.shape[0], - 1, - &new_levels[0], - False) - Eq_setLevels(self.ceq, &dmat_new_levels) - dmat_free(&dmat_new_levels) - - def equalize(self, d[::1] input_data): - cdef: - vd res - cdef dmat input_dmat = dmat_foreign_data(input_data.size, - 1, - &input_data[0], - False) - with nogil: - res = Eq_equalize(self.ceq, &input_dmat) - - # Steal the pointer from output - py_res = dmat_to_ndarray(&res,True)[:,0] - - dmat_free(&res) - vd_free(&input_dmat) - - return py_res - - def __dealloc__(self): - if self.ceq: - Eq_free(self.ceq) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..771e14f --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,28 @@ +[project] # Project metadata +name = "lasp" +readme = "README.md" +requires-python = ">=3.8" +license = { "file" = "LICENSE" } +authors = [{ "name" = "J.A. de Jong et al.", "email" = "info@ascee.nl" }] +keywords = ["DSP", "DAQ", "Signal processing"] + +classifiers = [ + "Topic :: Scientific/Engineering", + "Programming Language :: Python :: 3.8", + "Operating System :: POSIX :: Linux", + "Operating System :: Microsoft :: Windows", +] + +# urls = { "Documentation" = "https://" } +dependencies = ["numpy", "scipy", "appdirs", "h5py", "appdirs", "dataclasses_json", ] +dynamic = ["version", "description"] + +[build-system] # How pip and other frontends should build this project +requires = [ + "setuptools>=42", + "wheel", + "scikit-build", + "cmake", +] +build-backend = "setuptools.build_meta" + diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..8a234a3 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +addopts = "--ignore=third_party" diff --git a/scripts/lasp_record b/scripts/lasp_record index 9d2edef..e2395e1 100755 --- a/scripts/lasp_record +++ b/scripts/lasp_record @@ -26,12 +26,11 @@ FORMAT = "[%(filename)s:%(lineno)s - %(funcName)20s() ] %(message)s" logging.basicConfig(format=FORMAT, level=numeric_level) import multiprocessing -from lasp.device import DaqConfigurations -from lasp import AvType, StreamManager, Recording# configureLogging +from lasp import StreamMgr, Recording, DaqConfiguration def main(args): try: - streammgr = StreamManager() + streammgr = StreamMgr.getInstance() configs = DaqConfigurations.loadAllConfigs() config_keys = [key for key in configs.keys()] diff --git a/setup.py b/setup.py index 2ce4fda..65fef76 100644 --- a/setup.py +++ b/setup.py @@ -1,28 +1,15 @@ -#!/usr/bin/env python3.8 -# -*- coding: utf-8 -*- -""" -@author: J.A. de Jong - ASCEE -""" -from setuptools import setup, find_packages +from skbuild import setup setup( - name="LASP", - version="1.0", - packages=find_packages(), - long_description=open("./README.md", 'r').read(), - long_description_content_type="text/markdown", - # ext_modules=[CMakeExtension('lasp/wrappers.so'), - # ], - #package_data={'lasp': ['wrappers.so']}, - author='J.A. de Jong - ASCEE', - author_email="j.a.dejong@ascee.nl", - install_requires=['matplotlib>=1.0', - 'scipy>=1.0', 'numpy>=1.0', 'h5py', - 'dataclasses_json', 'cython', - ], - license='MIT', - description="Library for Acoustic Signal Processing", - keywords="", - url="https://www.ascee.nl/lasp/", # project home page - + name="lasp", + version="1.0", + description="LASP Library of Acoustic Signal Processing", + author='J.A. de Jong (ASCEE / Redu-Sone)', + author_email='info@ascee.nl', + license="MIT", + packages=['lasp'], + package_dir= {'': 'src'}, + cmake_install_dir='src/lasp', + # cmake_install_target='src', + python_requires='>=3.8', ) diff --git a/src/lasp/CMakeLists.txt b/src/lasp/CMakeLists.txt new file mode 100644 index 0000000..831094e --- /dev/null +++ b/src/lasp/CMakeLists.txt @@ -0,0 +1,45 @@ +# src/lasp/CMakeLists.txt + +# Armadillo +add_definitions(-DARMA_DONT_USE_WRAPPER) + +configure_file(lasp_config.h.in lasp_config.h) +include_directories(${CMAKE_CURRENT_BINARY_DIR}) +include_directories(SYSTEM + ../../third_party/carma/extern/pybind11/include) +include_directories(SYSTEM + ../../third_party/carma/extern/armadillo-code/include) +include_directories(SYSTEM ../../third_party/carma/include) + +include_directories(../../third_party/DebugTrace-cpp/include) +include_directories(../../third_party/boost/core/include) +include_directories(../../third_party/boost/lockfree/include) +include_directories(../../third_party/gsl-lite/include) +include_directories(../../third_party/tomlplusplus/include) +include_directories(../../third_party/thread-pool) + +if(LASP_HAS_RTAUDIO) + include_directories(../../third_party/rtaudio) +endif() +if(LASP_HAS_ULDAQ) + include_directories(../../third_party/uldaq/src) +endif() + +add_subdirectory(device) +add_subdirectory(dsp) + +pybind11_add_module(lasp_cpp MODULE lasp_cpp.cpp + pybind11/lasp_deviceinfo.cpp + pybind11/lasp_daqconfig.cpp + pybind11//lasp_dsp_pybind.cpp + pybind11/lasp_streammgr.cpp + pybind11/lasp_daq.cpp + pybind11/lasp_deviceinfo.cpp + pybind11/lasp_pyindatahandler.cpp + pybind11/lasp_siggen.cpp + ) + +target_link_libraries(lasp_cpp PRIVATE lasp_device_lib lasp_dsp_lib + ${OpenMP_CXX_LIBRARIES} ${LASP_FFT_LIBS}) + +install(TARGETS lasp_cpp DESTINATION .) diff --git a/src/lasp/__init__.py b/src/lasp/__init__.py new file mode 100644 index 0000000..d48bd0d --- /dev/null +++ b/src/lasp/__init__.py @@ -0,0 +1,19 @@ +# Comments are what is imported, state of 6-8-2021 +""" +LASP: Library for Acoustic Signal Processing + +""" +from .lasp_cpp import * +import lasp.lasp_cpp +from .lasp_common import * +__version__ = lasp_cpp.__version__ + +# from .lasp_imptube import * # TwoMicImpedanceTube +from .lasp_measurement import * # Measurement, scaleBlockSens +from .lasp_octavefilter import * +from .lasp_slm import * # SLM, Dummy +from .lasp_record import * # RecordStatus, Recording +from .lasp_daqconfigs import * +# from .lasp_siggen import * # SignalType, NoiseType, SiggenMessage, SiggenData, Siggen +# from .lasp_weighcal import * # WeighCal +# from .tools import * # SmoothingType, smoothSpectralData, SmoothingWidth diff --git a/src/lasp/device/CMakeLists.txt b/src/lasp/device/CMakeLists.txt new file mode 100644 index 0000000..052d997 --- /dev/null +++ b/src/lasp/device/CMakeLists.txt @@ -0,0 +1,31 @@ +# src/lasp/device/CMakeLists.txt + +add_library(lasp_device_lib OBJECT + lasp_daq.cpp + lasp_daqconfig.cpp + lasp_daqdata.cpp + lasp_deviceinfo.cpp + lasp_rtaudiodaq.cpp + lasp_streammgr.cpp + lasp_uldaq.cpp + ) + +# Callback requires certain arguments that are not used by code. This disables +# a compiler warning about it. +set_source_files_properties(lasp_rtaudiodaq.cpp PROPERTIES COMPILE_OPTIONS + "-Wno-unused") + +target_include_directories(lasp_device_lib PUBLIC ../dsp) +target_include_directories(lasp_device_lib PUBLIC ../c) +target_include_directories(lasp_device_lib INTERFACE + ${CMAKE_CURRENT_SOURCE_DIR}) + +if(LASP_HAS_ULDAQ) + target_link_libraries(lasp_device_lib uldaq) +endif() +if(LASP_HAS_RTAUDIO) + target_link_libraries(lasp_device_lib rtaudio) +endif() + +target_link_libraries(lasp_device_lib lasp_dsp_lib) + diff --git a/src/lasp/device/lasp_daq.cpp b/src/lasp/device/lasp_daq.cpp new file mode 100644 index 0000000..1892426 --- /dev/null +++ b/src/lasp/device/lasp_daq.cpp @@ -0,0 +1,101 @@ +/* #define DEBUGTRACE_ENABLED */ +#include "debugtrace.hpp" +#include "lasp_daqconfig.h" + +#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; + +Daq::~Daq() { DEBUGTRACE_ENTER; } + +std::unique_ptr Daq::createDaq(const DeviceInfo &devinfo, + const DaqConfiguration &config) { + DEBUGTRACE_ENTER; + +#if LASP_HAS_ULDAQ == 1 + if (devinfo.api.apicode == LASP_ULDAQ_APICODE) { + return createUlDaqDevice(devinfo, config); + } +#endif +#if LASP_HAS_RTAUDIO == 1 + // See lasp_daqconfig.h:114 ALSA, up to + if (devinfo.api.apicode == LASP_RTAUDIO_APICODE) { + return createRtAudioDevice(devinfo, config); + } +#endif + throw std::runtime_error(string("Unable to match Device API: ") + + devinfo.api.apiname); +} + +Daq::Daq(const DeviceInfo &devinfo, const DaqConfiguration &config) + : DaqConfiguration(config), DeviceInfo(devinfo) { + DEBUGTRACE_ENTER; + + if (!hasInternalOutputMonitor && monitorOutput) { + throw std::runtime_error( + "Output monitor flag set, but device does not have output monitor"); + } + + if (!config.match(devinfo)) { + throw std::runtime_error("DaqConfiguration does not match device info"); + } + if (neninchannels(false) > ninchannels) { + throw std::runtime_error( + "Number of enabled input channels is higher than device capability"); + } + if (nenoutchannels() > noutchannels) { + throw std::runtime_error( + "Number of enabled output channels is higher than device capability"); + } + } + +double Daq::samplerate() const { + DEBUGTRACE_ENTER; + return availableSampleRates.at(sampleRateIndex); +} + +DataTypeDescriptor::DataType Daq::dataType() const { + return availableDataTypes.at(dataTypeIndex); +} +DataTypeDescriptor Daq::dtypeDescr() const { return dtype_map.at(dataType()); } + +double Daq::inputRangeForChannel(us ch) const { + if (!(ch < ninchannels)) { + throw runtime_error("Invalid channel number"); + } + return availableInputRanges.at(inchannel_config[ch].rangeIndex); +} + +us Daq::neninchannels(bool include_monitorchannel) const { + boolvec eninchannels = this->eninchannels(include_monitorchannel); + return std::count(eninchannels.cbegin(), eninchannels.cend(), true); +} + +us Daq::nenoutchannels() const { + boolvec enchannels = this->enoutchannels(); + return std::count(enchannels.cbegin(), enchannels.cend(), true); +} + +boolvec Daq::eninchannels(bool include_monitor) const { + boolvec res; + if (hasInternalOutputMonitor && include_monitor) { + res.push_back(monitorOutput); + } + + for (auto &ch : inchannel_config) { + res.push_back(ch.enabled); + } + return res; +} +boolvec Daq::enoutchannels() const { + boolvec res; + for (auto &ch : outchannel_config) { + res.push_back(ch.enabled); + } + return res; +} diff --git a/src/lasp/device/lasp_daq.h b/src/lasp/device/lasp_daq.h new file mode 100644 index 0000000..fe57355 --- /dev/null +++ b/src/lasp/device/lasp_daq.h @@ -0,0 +1,207 @@ +#pragma once +#include "lasp_config.h" +#include "lasp_daqdata.h" +#include "lasp_deviceinfo.h" +#include "lasp_types.h" +#include +#include + +/** + * \defgroup device Device interfacing + * \addtogroup device + * @{ + * @brief Callback of DAQ for input data. Callback should return + * false for a stop request. + */ +using InDaqCallback = std::function; + +/** + * @brief + */ +using OutDaqCallback = std::function; + +/** + * @brief Base cass for all DAQ (Data Acquisition) interfaces. A DAQ can be a + * custom device, or for example a sound card. + */ +class Daq : public DaqConfiguration, public DeviceInfo { + +protected: + Daq(const DeviceInfo &devinfo, const DaqConfiguration &config); + Daq(const Daq &) = delete; + +public: + /** + * @brief Information regarding a stream. + */ + class StreamStatus { + public: + enum class StreamError { + noError, + inputXRun, + outputXRun, + driverError, + systemError, + threadError, + logicError, + apiSpecificError + }; + + /** + * @brief Map between error types and messages + */ + inline static const std::map errorMessages{ + {StreamError::noError, "No error"}, + {StreamError::inputXRun, "Input buffer overrun"}, + {StreamError::outputXRun, "Output buffer underrun"}, + {StreamError::driverError, "Driver error"}, + {StreamError::systemError, "System error"}, + {StreamError::threadError, "Thread error"}, + {StreamError::logicError, "Logic error (probably a bug)"}, + }; + bool isRunning = false; + /** + * @brief Check if stream has error + * + * @return true if there is an error. + */ + bool error() const { return errorType != StreamError::noError; }; + + StreamError errorType{StreamError::noError}; + + std::string errorMsg() const { return errorMessages.at(errorType); } + + /** + * @brief Returns true if everything is OK with a certain stream and the + * stream is running. + * + * @return as described above. + */ + bool runningOK() const { return isRunning && !error(); } + }; + + /** + * @brief Create a Daq based on given device info and configuration + * + * @param devinfo Device information of device to be used. + * @param config Configuation to apply to the deviec + * + * @return Pointer to Daq device created. + */ + static std::unique_ptr createDaq(const DeviceInfo &devinfo, + const DaqConfiguration &config); + + /** + * @brief Start the Daq. + * + * @param inCallback Function that is called with input data as argument, + * and which expects output data as a return value. If no input data is + * required, this callback should be unset. + * @param outCallback Function that is called when output data is required. + * If the stream does not require output data, this callback should be unset. + */ + virtual void start(InDaqCallback inCallback, OutDaqCallback outCallback) = 0; + + /** + * @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; + + virtual ~Daq() = 0; + + /** + * @brief Returns the number of enabled input channels + * + * @param include_monitorchannels if true, all channels that are internally + * monitored are added to the list. + * + * @return number of enabled input channels + */ + us neninchannels(bool include_monitorchannels = true) const; + + /** + * @brief Returns the number of enabled output channels + * + * @return Number of enabled output channels + */ + us nenoutchannels() const; + + /** + * @brief Create a vector of boolean values of the enabled input channels. + * + * @param include_monitor If set to true and a monitor channel is available, + * the first index in the array will correspond to the monitor channel, and + * whether it is enabled + * + * @return Boolean vector + */ + boolvec eninchannels(bool include_monitor = true) const; + + /** + * @brief Create an array of booleans for each enabled output channel. + * + * @return Boolean vector + */ + boolvec enoutchannels() 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 Get stream status corresponding to current DAQ. + * + * @return StreamStatus object. + */ + virtual StreamStatus getStreamStatus() const = 0; + + /** + * @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() > 0 && nenoutchannels() > 0); + } +}; +/** @} */ diff --git a/src/lasp/device/lasp_daqconfig.cpp b/src/lasp/device/lasp_daqconfig.cpp new file mode 100644 index 0000000..bb6d33b --- /dev/null +++ b/src/lasp/device/lasp_daqconfig.cpp @@ -0,0 +1,237 @@ +/* #define DEBUGTRACE_ENABLED */ +#include "debugtrace.hpp" + +#include "lasp_daqconfig.h" +#include "lasp_deviceinfo.h" +#include +#include +#include + +using std::vector; + +vector DaqApi::getAvailableApis() { + + vector apis; +#if LASP_HAS_ULDAQ == 1 + apis.push_back(uldaqapi); +#endif +#if LASP_HAS_RTAUDIO == 1 + apis.push_back(rtaudioAlsaApi); + apis.push_back(rtaudioPulseaudioApi); + apis.push_back(rtaudioWasapiApi); + apis.push_back(rtaudioDsApi); + apis.push_back(rtaudioAsioApi); +#endif + return apis; +} + +DaqConfiguration::DaqConfiguration(const DeviceInfo &device) { + + api = device.api; + device_name = device.device_name; + + inchannel_config.resize(device.ninchannels); + outchannel_config.resize(device.noutchannels); + us i = 0; + for (auto &inch : inchannel_config) { + inch.name = "Unnamed input channel " + std::to_string(i); + i++; + } + + i = 0; + for (auto &outch : outchannel_config) { + outch.name = "Unnamed output channel " + std::to_string(i); + i++; + } + + sampleRateIndex = device.prefSampleRateIndex; + dataTypeIndex = device.prefDataTypeIndex; + framesPerBlockIndex = device.prefFramesPerBlockIndex; + + monitorOutput = false; + + 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 = inchannel_config.size() - 1; i > -1; i--) { + if (inchannel_config.at(i).enabled) + return i; + } + return -1; +} + +int DaqConfiguration::getHighestOutChannel() const { + for (us i = outchannel_config.size() - 1; i >= 0; i--) { + if (outchannel_config.at(i).enabled) + return i; + } + return -1; +} +int DaqConfiguration::getLowestInChannel() const { + for (us i = 0; i < inchannel_config.size(); i++) { + if (inchannel_config.at(i).enabled) + return i; + } + return -1; +} +int DaqConfiguration::getLowestOutChannel() const { + for (us i = 0; i < outchannel_config.size(); i++) { + if (outchannel_config.at(i).enabled) + return i; + } + return -1; +} +vector DaqConfiguration::enabledInChannels() const { + vector res; + if(monitorOutput) { + DaqChannel ch; + ch.name = "Internal output monitor (loopback)"; + ch.enabled = true; + ch.sensitivity = 1; + ch.qty = DaqChannel::Qty::Number; + res.emplace_back(std::move(ch)); + + } + for(auto& ch: inchannel_config) { + if(ch.enabled) { res.push_back(ch);} + } + return res; +} + +#include "toml++/toml.h" +#include + +toml::table daqChannelToTOML(const DaqChannel &ch) { + DEBUGTRACE_ENTER; + toml::table tbl; + tbl.emplace("enabled", ch.enabled); + tbl.emplace("name", ch.name); + tbl.emplace("sensitivity", ch.sensitivity); + tbl.emplace("IEPEEnabled", ch.IEPEEnabled); + tbl.emplace("ACCouplingMode", ch.ACCouplingMode); + tbl.emplace("rangeIndex", ch.rangeIndex); + tbl.emplace("qty", static_cast(ch.qty)); + tbl.emplace("digitalHighpassCutOn", ch.digitalHighPassCutOn); + + return tbl; +} + +string DaqConfiguration::toTOML() const { + + DEBUGTRACE_ENTER; + toml::table apitbl{ + {"apiname", api.apiname}, // Api name + {"apicode", api.apicode}, // Api code + {"api_specific_subcode", api.api_specific_subcode} // Subcode + }; + + toml::table tbl{{"daqapi", apitbl}}; + + tbl.emplace("device_name", device_name); + tbl.emplace("sampleRateIndex", sampleRateIndex); + tbl.emplace("dataTypeIndex", dataTypeIndex); + tbl.emplace("framesPerBlockIndex", framesPerBlockIndex); + tbl.emplace("monitorOutput", monitorOutput); + + toml::array inchannel_config_tbl; + for (const auto &ch : inchannel_config) { + inchannel_config_tbl.emplace_back(daqChannelToTOML(ch)); + } + tbl.emplace("inchannel_config", inchannel_config_tbl); + + toml::array outchannel_config_tbl; + for (const auto &ch : outchannel_config) { + outchannel_config_tbl.emplace_back(daqChannelToTOML(ch)); + } + tbl.emplace("outchannel_config", outchannel_config_tbl); + + std::stringstream str; + + str << tbl; + + return str.str(); +} + +template T getValue(R &tbl, const char* key) { + using std::runtime_error; + + DEBUGTRACE_ENTER; + + // Yeah, weird syntax quirck of C++ + std::optional val = tbl[key].template value(); + if (!val) { + throw runtime_error(string("Error while parsing Daq configuration. Table " + "does not contain key: ") + + key); + } + return val.value(); +} +template DaqChannel TOMLToDaqChannel(T& node) { + DEBUGTRACE_ENTER; + DaqChannel d; + toml::table& tbl = *node.as_table(); + d.enabled = getValue(tbl, "enabled"); + d.name = getValue(tbl, "name"); + d.sensitivity = getValue(tbl, "sensitivity"); + d.IEPEEnabled = getValue(tbl, "IEPEEnabled"); + d.ACCouplingMode = getValue(tbl, "ACCouplingMode"); + d.rangeIndex = getValue(tbl, "rangeIndex"); + d.qty = static_cast(getValue(tbl, "qty")); + d.digitalHighPassCutOn = getValue(tbl, "digitalHighpassCutOn"); + + return d; +} + +DaqConfiguration DaqConfiguration::fromTOML(const std::string &tomlstr) { + + DEBUGTRACE_ENTER; + + using std::runtime_error; + + try { + toml::table tbl = toml::parse(tomlstr); + DaqConfiguration config; + + auto daqapi = tbl["daqapi"]; + DaqApi api; + api.apicode = getValue(daqapi, "apicode"); + api.api_specific_subcode = getValue(daqapi, "api_specific_subcode"); + api.apiname = getValue(daqapi, "apiname"); + config.api = api; + + config.device_name = getValue(tbl, "device_name"); + config.sampleRateIndex = getValue(tbl, "sampleRateIndex"); + config.dataTypeIndex = getValue(tbl, "dataTypeIndex"); + config.framesPerBlockIndex = getValue(tbl, "framesPerBlockIndex"); + config.monitorOutput = getValue(tbl, "monitorOutput"); + + if(toml::array* in_arr = tbl["inchannel_config"].as_array()) { + for(auto& el: *in_arr) { + config.inchannel_config.push_back(TOMLToDaqChannel(el)); + } + + } else { + throw runtime_error("inchannel_config is not an array"); + } + + if(toml::array* out_arr = tbl["outchannel_config"].as_array()) { + for(auto& el: *out_arr) { + config.outchannel_config.push_back(TOMLToDaqChannel(el)); + } + } else { + throw runtime_error("outchannel_config is not an array"); + } + + + return config; + + } catch (const toml::parse_error &e) { + throw std::runtime_error(string("Error parsing TOML DaqConfiguration: ") + + e.what()); + } +} diff --git a/src/lasp/device/lasp_daqconfig.h b/src/lasp/device/lasp_daqconfig.h new file mode 100644 index 0000000..dc5248d --- /dev/null +++ b/src/lasp/device/lasp_daqconfig.h @@ -0,0 +1,363 @@ +#pragma once +#include "lasp_config.h" +#include "lasp_types.h" +#include +#include + +/** \addtogroup device + * @{ + */ + +using std::string; +using std::to_string; + +using boolvec = std::vector; +using dvec = std::vector; +using usvec = std::vector; + +/** + * @brief Descriptor for data types containing more detailed information. + */ +class DataTypeDescriptor { +public: + /** + * @brief Basic data types coming from a DAQ that we can deal with. The naming + * will be self-explainging. + */ + enum class DataType { + dtype_fl32 = 0, + dtype_fl64 = 1, + dtype_int8 = 2, + dtype_int16 = 3, + dtype_int32 = 4 + }; + + /** + * @brief Name of the datatype + */ + string name; + /** + * @brief Sample width of a single sample, in bytes + */ + unsigned sw; + /** + * @brief Whether the datatype is a floating point Y/N + */ + bool is_floating; + + /** + * @brief The number from the enumeration + */ + DataType dtype; + + /** + * @brief Down-cast a DataTypeDescriptor to a datatype + * + * @return The descriptor as an enum + */ + operator DataType() { return dtype; } + + /** + * @brief Compare two data type descriptors. Returns true if the DataType + * enumerator is the same. + * + * @param o + * + * @return + */ + bool operator==(const DataTypeDescriptor &o) noexcept { + return dtype == o.dtype; + } +}; + +const DataTypeDescriptor dtype_desc_fl32 = { + .name = "32-bits floating point", + .sw = 4, + .is_floating = true, + .dtype = DataTypeDescriptor::DataType::dtype_fl32}; +const DataTypeDescriptor dtype_desc_fl64 = { + .name = "64-bits floating point", + .sw = 8, + .is_floating = true, + .dtype = DataTypeDescriptor::DataType::dtype_fl64}; +const DataTypeDescriptor dtype_desc_int8 = { + .name = "8-bits integer", + .sw = 1, + .is_floating = false, + .dtype = DataTypeDescriptor::DataType::dtype_int8}; +const DataTypeDescriptor dtype_desc_int16 = { + .name = "16-bits integer", + .sw = 2, + .is_floating = false, + .dtype = DataTypeDescriptor::DataType::dtype_int16}; +const DataTypeDescriptor dtype_desc_int32 = { + .name = "32-bits integer", + .sw = 4, + .is_floating = false, + .dtype = DataTypeDescriptor::DataType::dtype_int32}; + +const std::map + dtype_map = { + {DataTypeDescriptor::DataType::dtype_fl32, dtype_desc_fl32}, + {DataTypeDescriptor::DataType::dtype_fl64, dtype_desc_fl64}, + {DataTypeDescriptor::DataType::dtype_int8, dtype_desc_int8}, + {DataTypeDescriptor::DataType::dtype_int16, dtype_desc_int16}, + {DataTypeDescriptor::DataType::dtype_int32, dtype_desc_int32}, +}; + +/** + * @brief Class that specifies API related information. An API configuration is + * part of the DAQConfiguration and used to address a certain physical device. + * For that, a specific backend needs to be compiled in. Examples of API's are + * RtAudio and UlDaq. + */ +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; } + static std::vector getAvailableApis(); +}; + +#if LASP_HAS_ULDAQ == 1 +const us LASP_ULDAQ_APICODE = 0; +const DaqApi uldaqapi("UlDaq", 0); +#endif +#if LASP_HAS_RTAUDIO == 1 +#include "RtAudio.h" +const us LASP_RTAUDIO_APICODE = 1; +const DaqApi rtaudioAlsaApi("RtAudio Linux ALSA", 1, RtAudio::Api::LINUX_ALSA); +const DaqApi rtaudioPulseaudioApi("RtAudio Linux Pulseaudio", 1, + RtAudio::Api::LINUX_PULSE); +const DaqApi rtaudioWasapiApi("RtAudio Windows Wasapi", 1, + RtAudio::Api::WINDOWS_WASAPI); +const DaqApi rtaudioDsApi("RtAudio Windows DirectSound", 1, + RtAudio::Api::WINDOWS_DS); +const DaqApi rtaudioAsioApi("RtAudio Windows ASIO", 1, + RtAudio::Api::WINDOWS_ASIO); +#endif + +class DeviceInfo; + +/** + * @brief Stores channel configuration data for each channel. I.e. the physical + * quantity measured, sensitivity, device-specific channel settings, a channel + * name and others. + */ +class DaqChannel { + +public: + /** + * @brief Possible physical quantities that are recorded. + */ + enum class Qty { Number, AcousticPressure, Voltage, UserDefined }; + + DaqChannel() {} + + /** + * @brief Whether the channel is enabled. + */ + bool enabled = false; + + string name = ""; + /** + * @brief The conversion between recorded physical unit and stored / + * outputed number, i.e. Number/Volt or Number/Pa. Converting stored numbers + * to physical qty, *divide* by the sensitivity. + */ + double sensitivity = 1; + /** + * @brief For input-only: enable IEPE constant current power supply for + * this channel. Only for hardware that is capable of doing so. + */ + bool IEPEEnabled = false; + /** + * @brief Whether to enable HW AC-coupling for this channel. + */ + bool ACCouplingMode = false; + /** + * @brief Index in possible ranges for input / output + */ + int rangeIndex = 0; + /** + * @brief The physical quantity that is inputed / outputed + */ + Qty qty = Qty::Number; + + /** + * @brief Whether to enable a digital high pass on the signal before + * passing the result in case of input, or outputing the result in case of + * output. + */ + double digitalHighPassCutOn = -1; + + /** + * @brief Compare two channels to eachother. They are equal when the name, + * sensitivity and quantity are the same. + * + * @param other The DaqChannel to compare with + * + * @return true if equal + */ + bool operator==(const DaqChannel& other) const { + return other.name == name && other.sensitivity == sensitivity && + other.qty == qty; + } +}; + +/** + * @brief Configuration of a DAQ device + */ +class DaqConfiguration { +public: + /** + * @brief Export the class to TOML markup language. + * + * @return String with TOML exported data. + */ + std::string toTOML() const; + + /** + * @brief Load in a DAQConfiguration from TOML. + * + * @param toml String containing TOML data + * + * @return DaqConfiguration object + */ + static DaqConfiguration fromTOML(const std::string &toml); + + DaqApi api; + /** + * @brief The internal device name this DAQ configuration applies to. + */ + string device_name; + + /** + * @brief Channel configuration for input channels + */ + std::vector inchannel_config; + + /** + * @brief Return list of enabled input channels + * + * @return That. + */ + std::vector enabledInChannels() const; + + /** + * @brief Channel configuration for output channels + */ + std::vector outchannel_config; + + /** + * @brief Index in list of sample rates that are available for the device. + */ + int sampleRateIndex = 0; // + + /** + * @brief Required datatype for output, should be present in the list + */ + int dataTypeIndex = 0; + + /** + * @brief The index in the array of frames per block that can be used for the + * device. + */ + int 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 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() {} + + /** + * @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; + + /** + * @brief Set all input channels to enabled / disabled state. + * + * @param val true if enabled, false if disabled. + */ + void setAllInputEnabled(bool val) { + for(auto& ch: inchannel_config) { + ch.enabled = val; + } + } + + /** + * @brief Set all output channels to enabled / disabled state. + * + * @param val true if enabled, false if disabled. + */ + void setAllOutputEnabled(bool val) { + for(auto& ch: outchannel_config) { + ch.enabled = val; + } + } +}; +/** + * @} + */ diff --git a/src/lasp/device/lasp_daqdata.cpp b/src/lasp/device/lasp_daqdata.cpp new file mode 100644 index 0000000..1e26091 --- /dev/null +++ b/src/lasp/device/lasp_daqdata.cpp @@ -0,0 +1,106 @@ +/* #define DEBUGTRACE_ENABLED */ +#include "lasp_daqdata.h" +#include "debugtrace.hpp" +#include "lasp_mathtypes.h" +#include + +DEBUGTRACE_VARIABLES; + +DaqData::DaqData(const us nchannels, const us nframes, + const DataTypeDescriptor::DataType dtype) + : nchannels(nchannels), nframes(nframes), dtype(dtype), + dtype_descr(dtype_map.at(dtype)), sw(dtype_descr.sw) { + static_assert(sizeof(char) == 1, "Invalid char size"); + + const DataTypeDescriptor &desc = dtype_map.at(dtype); + _data.resize(nframes * nchannels * desc.sw); +} + +void DaqData::copyInFromRaw(const std::vector &ptrs) { + us ch = 0; + assert(ptrs.size() == nchannels); + for (auto ptr : ptrs) { + std::copy(ptr, ptr + sw * nframes, &_data[sw * ch * nframes]); + ch++; + } +} + +void DaqData::copyToRaw(const us channel, uint8_t *ptr) { + std::copy(raw_ptr(0, channel), + raw_ptr(nframes, channel), ptr); +} + +template d convertToFloat(const int_type val) { + if constexpr (std::is_integral::value) { + return static_cast(val) / std::numeric_limits::max(); + } else { + return static_cast(val); + } +} +template +inline vd channelToFloat(const byte_t *vals, const us nframes) { + vd res(nframes); + for (us i = 0; i < nframes; i++) { + res(i) = convertToFloat(reinterpret_cast(vals)[i]); + } + return res; +} +template +inline dmat allToFloat(const byte_t *vals, const us nframes, const us nchannels) { + dmat res(nframes, nchannels); + for (us j = 0; j < nchannels; j++) { + for (us i = 0; i < nframes; i++) { + res(i, j) = convertToFloat( + reinterpret_cast(vals)[i + j * nframes]); + } + } + return res; +} + +vd DaqData::toFloat(const us channel_no) const { + using DataType = DataTypeDescriptor::DataType; + switch (dtype) { + case (DataType::dtype_int8): { + return channelToFloat(raw_ptr(0, channel_no), nframes); + } break; + case (DataType::dtype_int16): { + return channelToFloat(raw_ptr(0, channel_no), nframes); + + } break; + case (DataType::dtype_int32): { + return channelToFloat(raw_ptr(0, channel_no), nframes); + } break; + case (DataType::dtype_fl32): { + return channelToFloat(raw_ptr(0, channel_no), nframes); + } break; + case (DataType::dtype_fl64): { + return channelToFloat(raw_ptr(0, channel_no), nframes); + } break; + default: + throw std::runtime_error("BUG"); + } // End of switch +} + +dmat DaqData::toFloat() const { + dmat result(nframes, nchannels); + using DataType = DataTypeDescriptor::DataType; + switch (dtype) { + case (DataType::dtype_int8): { + return allToFloat(raw_ptr(0), nframes, nchannels); + } break; + case (DataType::dtype_int16): { + return allToFloat(raw_ptr(0), nframes, nchannels); + } break; + case (DataType::dtype_int32): { + return allToFloat(raw_ptr(0), nframes, nchannels); + } break; + case (DataType::dtype_fl32): { + return allToFloat(raw_ptr(0), nframes, nchannels); + } break; + case (DataType::dtype_fl64): { + return allToFloat(raw_ptr(0), nframes, nchannels); + } break; + default: + throw std::runtime_error("BUG"); + } // End of switch +} diff --git a/src/lasp/device/lasp_daqdata.h b/src/lasp/device/lasp_daqdata.h new file mode 100644 index 0000000..84e91ee --- /dev/null +++ b/src/lasp/device/lasp_daqdata.h @@ -0,0 +1,168 @@ +#pragma once +#include "lasp_daqconfig.h" +#include "lasp_types.h" +#include +#include +#include +#include + +/** \addtogroup device + * @{ + */ +class Daq; + +using byte_t = char; + +/** + * @brief Data coming from / going to DAQ. **Non-interleaved format**, which + * means data in buffer is ordered by channel: _ptr[frame+channel*nframes] + */ +class DaqData { +protected: + /** + * @brief Storage for the actual data. + */ + std::vector _data; + +public: + /** + * @brief The number of channels + */ + us nchannels; + + /** + * @brief The number of frames in this block of data. + */ + us nframes; + + /** + * @brief The data type corresponding to a sample + */ + DataTypeDescriptor::DataType dtype; + + /** + * @brief The data type description corresponding to a sample + */ + DataTypeDescriptor dtype_descr; + + /** + * @brief The number of bytes per sample (sample width, sw) + */ + us sw; + + /** + * @brief Initialize an empty frame of data + * + * @param nchannels The number of channels + * @param nframes The number of frames + * @param dtype The data type + */ + DaqData(const us nchannels, const us nframes, + const DataTypeDescriptor::DataType dtype); + /** + * @brief Initialize using no allocation + */ + DaqData() + : DaqData(0, 0, DataTypeDescriptor::DataType::dtype_int8) {} + virtual ~DaqData() = default; + DaqData &operator=(const DaqData &) = default; + + /** + * @brief Return pointer to the raw data corresponding to a certain sample. + * + * @param frame The frame number + * @param channel The channel number + * + * @return Pointer to sample, not casted to final type + */ + byte_t *raw_ptr(const us frame = 0, const us channel = 0) { + return &(_data.data()[sw * (frame + channel * nframes)]); + } + const byte_t *raw_ptr(const us frame = 0, const us channel = 0) const { + return &(_data.data()[sw * (frame + channel * nframes)]); + } + + void setSlow(const us frame, const us channel, const int8_t *val) { + for (us i = 0; i < sw; i++) { + _data.at(i + sw * (frame + channel * nframes)) = val[i]; + } + } + + /** + * @brief Return the total number of bytes + * + * @return Number of bytes of data. + */ + us size_bytes() const { return _data.size(); } + + /** + * @brief Copy data from a set of raw pointers of *uninterleaved* data. + * Overwrites any existing available data. + * + * @param ptrs Pointers to data from channels + */ + void copyInFromRaw(const std::vector &ptrs); + + /** + * @brief Copy contents of DaqData for a certain channel to a raw pointer. + * + * @param channel The channel to copy. + * @param ptr The pointer where data is copied to. + */ + void copyToRaw(const us channel, uint8_t *ptr); + + /** + * @brief Convert samples to floating point values and return a nframes x + * nchannels array of floats. For data that is not already floating-point, + * the data is scaled back from MAX_INT to +1.0. + * + * @return Array of floats + */ + arma::Mat toFloat() const; + + /** + * @brief Convert samples to floating point values and return a nframes + * column vector of floats. For data that is not already floating-point, + * the data is scaled back from MAX_INT to +1.0. + * + * @param channel The channel to convert + * + * @return Array of floats + */ + arma::Col toFloat(const us channel_no) const; +}; + +template class TypedDaqData : public DaqData { +public: + TypedDaqData(const us nchannels, const us nframes, + const DataTypeDescriptor::DataType dtype_descr) + : DaqData(nchannels, nframes, dtype_descr) {} + + T &operator[](const us i) { return _data[sw * i]; } + + /** + * @brief Reference of sample, casted to the right type. Same as raw_ptr(), + * but then as reference, and corresponding to right type. + * + * @param frame Frame number + * @param channel Channel number + * + * @return + */ + T &operator()(const us frame = 0, const us channel = 0) { + return reinterpret_cast(*raw_ptr(frame, channel)); + } + + /** + * @brief Copy out the data for a certain channel to a buffer + * + * @param channel The channel to copy + * @param buffer The buffer to copy to. *Make sure* it is allocated with at + * least `sw*nframes` bytes. + */ + void copyToRaw(const us channel, T *buffer) { + DaqData::copyToRaw(channel, reinterpret_cast(buffer)); + } + +}; +/** @} */ diff --git a/lasp/device/lasp_device_common.py b/src/lasp/device/lasp_device_common.py similarity index 100% rename from lasp/device/lasp_device_common.py rename to src/lasp/device/lasp_device_common.py diff --git a/src/lasp/device/lasp_deviceinfo.cpp b/src/lasp/device/lasp_deviceinfo.cpp new file mode 100644 index 0000000..ea382bc --- /dev/null +++ b/src/lasp/device/lasp_deviceinfo.cpp @@ -0,0 +1,27 @@ +#include "lasp_deviceinfo.h" +#include +#include + +#define MAX_DEV_COUNT_PER_API 20 + +#if LASP_HAS_ULDAQ == 1 +#include "lasp_uldaq.h" +#endif +#if LASP_HAS_RTAUDIO == 1 +#include "lasp_rtaudiodaq.h" +#endif + + +std::vector DeviceInfo::getDeviceInfo() { + std::vector devs; +#if LASP_HAS_ULDAQ ==1 + fillUlDaqDeviceInfo(devs); +#endif + +#if LASP_HAS_RTAUDIO == 1 + fillRtAudioDeviceInfo(devs); +#endif + + return devs; +} + diff --git a/src/lasp/device/lasp_deviceinfo.h b/src/lasp/device/lasp_deviceinfo.h new file mode 100644 index 0000000..de3a168 --- /dev/null +++ b/src/lasp/device/lasp_deviceinfo.h @@ -0,0 +1,129 @@ +#pragma once +#include "lasp_config.h" +#include "lasp_types.h" +#include "lasp_daqconfig.h" + +/** \addtogroup device + * @{ + */ + +/** + * @brief Structure containing device info parameters + */ +class DeviceInfo { +public: + /** + * @brief Backend API corresponding to device + */ + DaqApi api; + /** + * @brief The name of the device + */ + string device_name = ""; + + /** + * @brief Specific for the device (Sub-API). Important for the RtAudio + * backend, as RtAudio is able to handle different API's. + */ + int api_specific_devindex = -1; + + /** + * @brief The available data types the device can output + */ + std::vector availableDataTypes; + /** + * @brief The device's prefferd data type. + */ + int prefDataTypeIndex = 0; + + /** + * @brief Available sample rates the device can run on + */ + dvec availableSampleRates; + /** + * @brief Preferred setting for the sample rate. + */ + int prefSampleRateIndex = -1; + + /** + * @brief Available latency-setting (number of frames passed in each + * callback). + */ + usvec availableFramesPerBlock; + /** + * @brief Preffered number of frames per callback. + */ + us prefFramesPerBlockIndex = 0; + + /** + * @brief Available ranges for the input, i.e. +/- 1V and/or +/- 10 V etc. + */ + dvec availableInputRanges; + /** + * @brief Its preffered range + */ + int prefInputRangeIndex = 0; + + /** + * @brief The number of input channels available for the device + */ + unsigned ninchannels = 0; + /** + * @brief The number of output channels available for the device + */ + unsigned noutchannels = 0; + + /** + * @brief Whether the device is capable to provide IEPE constant current + * power supply. + */ + bool hasInputIEPE = false; + /** + * @brief Whether the device is capable of enabling a hardware AC-coupling + */ + bool hasInputACCouplingSwitch = false; + /** + * @brief Whether the device is able to trigger on input + */ + bool hasInputTrigger = false; + + double prefSampleRate() const { + if (((us)prefSampleRateIndex < availableSampleRates.size()) && + (prefSampleRateIndex >= 0)) { + return availableSampleRates.at(prefSampleRateIndex); + } else { + throw std::runtime_error("No prefered sample rate available"); + } + } + + /** + * @brief Whether the device has an internal monitor of the output signal. + */ + bool hasInternalOutputMonitor = false; + + /** + * @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 Create a list of DeviceInfo's that are at call time avalable + * + * @return The device info's for each found device. + */ + static std::vector getDeviceInfo(); +}; + +/** @} */ diff --git a/src/lasp/device/lasp_rtaudiodaq.cpp b/src/lasp/device/lasp_rtaudiodaq.cpp new file mode 100644 index 0000000..46e30fe --- /dev/null +++ b/src/lasp/device/lasp_rtaudiodaq.cpp @@ -0,0 +1,387 @@ +/* #define DEBUGTRACE_ENABLED */ +#include "debugtrace.hpp" + +#include "lasp_rtaudiodaq.h" +#if LASP_HAS_RTAUDIO == 1 +#include "lasp_daq.h" +#include "RtAudio.h" +#include +#include + +using std::atomic; +using std::cerr; +using std::endl; +using std::runtime_error; +using std::vector; + +DEBUGTRACE_VARIABLES; + +void fillRtAudioDeviceInfo(vector &devinfolist) { + DEBUGTRACE_ENTER; + + vector apis; + RtAudio::getCompiledApi(apis); + + for (auto api : apis) { + RtAudio rtaudio(api); + us count = rtaudio.getDeviceCount(); + for (us devno = 0; devno < count; devno++) { + + RtAudio::DeviceInfo devinfo = rtaudio.getDeviceInfo(devno); + if (!devinfo.probed) { + // Device capabilities not successfully probed. Continue to next + continue; + } + // "Our device info struct" + 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( + DataTypeDescriptor::DataType::dtype_int8); + } + if (formats & RTAUDIO_SINT16) { + d.availableDataTypes.push_back( + DataTypeDescriptor::DataType::dtype_int16); + } + /* if (formats & RTAUDIO_SINT32) { */ + /* d.availableDataTypes.push_back(DataTypeDescriptor::DataType::dtype_int24); + */ + /* } */ + if (formats & RTAUDIO_SINT32) { + d.availableDataTypes.push_back( + DataTypeDescriptor::DataType::dtype_fl32); + } + if (formats & RTAUDIO_FLOAT64) { + d.availableDataTypes.push_back( + DataTypeDescriptor::DataType::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 = {512, 1024, 2048, 4096, 8192}; + d.prefFramesPerBlockIndex = 1; + + devinfolist.push_back(d); + } + } +} + +static int mycallback(void *outputBuffer, void *inputBuffer, + unsigned int nFrames, double streamTime, + RtAudioStreamStatus status, void *userData); + +static void myerrorcallback(RtAudioError::Type, const string &errorText); + +class RtAudioDaq : public Daq { + + RtAudio rtaudio; + const us nFramesPerBlock; + + RtAudioDaq(const RtAudioDaq &) = delete; + RtAudioDaq &operator=(const RtAudioDaq &) = delete; + + InDaqCallback _incallback; + OutDaqCallback _outcallback; + + std::atomic _streamStatus{}; + +public: + RtAudioDaq(const DeviceInfo &devinfo, const DaqConfiguration &config) + : Daq(devinfo, config), + rtaudio(static_cast(devinfo.api.api_specific_subcode)), + nFramesPerBlock(Daq::framesPerBlock()) { + + DEBUGTRACE_ENTER; + + // We make sure not to run RtAudio in duplex mode. This seems to be buggy + // and untested. Better to use a hardware-type loopback into the system. + if (duplexMode()) { + throw runtime_error("RtAudio backend cannot run in duplex mode."); + } + assert(!monitorOutput); + + std::unique_ptr inParams, outParams; + + if (neninchannels() > 0) { + + inParams = std::make_unique(); + + // +1 to get the count. + inParams->nChannels = getHighestInChannel() + 1; + if (inParams->nChannels < 1) { + throw runtime_error("Invalid input number of channels"); + } + inParams->firstChannel = 0; + inParams->deviceId = devinfo.api_specific_devindex; + + } else { + + outParams = std::make_unique(); + + outParams->nChannels = getHighestOutChannel() + 1; + if (outParams->nChannels < 1) { + throw runtime_error("Invalid output number of channels"); + } + outParams->firstChannel = 0; + outParams->deviceId = devinfo.api_specific_devindex; + } + + RtAudio::StreamOptions streamoptions; + streamoptions.flags = RTAUDIO_HOG_DEVICE | RTAUDIO_NONINTERLEAVED; + + streamoptions.numberOfBuffers = 2; + streamoptions.streamName = "RtAudio stream"; + streamoptions.priority = 0; + + RtAudioFormat format; + using Dtype = DataTypeDescriptor::DataType; + const Dtype dtype = dataType(); + switch (dtype) { + case Dtype::dtype_fl32: + format = RTAUDIO_FLOAT32; + break; + case Dtype::dtype_fl64: + format = RTAUDIO_FLOAT64; + break; + case Dtype::dtype_int8: + format = RTAUDIO_SINT8; + break; + case Dtype::dtype_int16: + format = RTAUDIO_SINT16; + break; + case Dtype::dtype_int32: + format = RTAUDIO_SINT32; + break; + default: + throw runtime_error("Invalid data type specified for DAQ stream."); + break; + } + + // Copy here, as it is used to return the *actual* number of frames per + // block. + unsigned int nFramesPerBlock_copy = nFramesPerBlock; + + // Final step: open the stream. + rtaudio.openStream(outParams.get(), inParams.get(), format, + static_cast(samplerate()), &nFramesPerBlock_copy, + mycallback, (void *)this, &streamoptions, + &myerrorcallback); + } + + virtual void start(InDaqCallback inCallback, + OutDaqCallback outCallback) override final { + + DEBUGTRACE_ENTER; + + assert(!monitorOutput); + + if (StreamStatus().runningOK()) { + throw runtime_error("Stream already running"); + } + + // Logical XOR + if (inCallback && outCallback) { + throw runtime_error("Either input or output stream possible for RtAudio. " + "Stream duplex mode not provided."); + } + + if (inCallback) { + _incallback = inCallback; + if (neninchannels() == 0) { + throw runtime_error( + "Input callback given, but stream does not provide input data"); + } + } + if (outCallback) { + _outcallback = outCallback; + if (nenoutchannels() == 0) { + throw runtime_error( + "Output callback given, but stream does not provide output data"); + } + } + + // Start the stream. Throws on error. + rtaudio.startStream(); + + // If we are here, we are running without errors. + StreamStatus status; + status.isRunning = true; + _streamStatus = status; + } + + StreamStatus getStreamStatus() const override final { return _streamStatus; } + + void stop() override final { + DEBUGTRACE_ENTER; + if (getStreamStatus().runningOK()) { + rtaudio.stopStream(); + } + StreamStatus s = _streamStatus; + s.isRunning = false; + s.errorType = StreamStatus::StreamError::noError; + _streamStatus = s; + } + + int streamCallback(void *outputBuffer, void *inputBuffer, + unsigned int nFrames, double streamTime, + RtAudioStreamStatus status) { + + /* DEBUGTRACE_ENTER; */ + + using se = StreamStatus::StreamError; + + int rval = 0; + auto stopWithError = [&](se e) { + DEBUGTRACE_PRINT("stopWithError"); + StreamStatus stat = _streamStatus; + stat.errorType = e; + stat.isRunning = false; + _streamStatus = stat; + rval = 1; + }; + + switch (status) { + case RTAUDIO_INPUT_OVERFLOW: + stopWithError(se::inputXRun); + return 1; + break; + case RTAUDIO_OUTPUT_UNDERFLOW: + stopWithError(se::outputXRun); + return 1; + break; + default: + break; + } + + const auto &dtype_descr = DataTypeDescriptor(); + const auto dtype = dataType(); + us neninchannels = this->neninchannels(); + us nenoutchannels = this->nenoutchannels(); + us sw = dtype_descr.sw; + if (nFrames != nFramesPerBlock) { + cerr << "RtAudio backend error: nFrames does not match block size!" + << endl; + stopWithError(se::logicError); + return 1; + } + + if (inputBuffer) { + assert(_incallback); + std::vector ptrs; + ptrs.reserve(neninchannels); + + us i = 0; + for (int ch = getLowestInChannel(); ch <= getHighestInChannel(); ch++) { + if (inchannel_config.at(ch).enabled) { + ptrs.push_back(static_cast(inputBuffer) + + sw * i * nFramesPerBlock); + i++; + } + } + DaqData d{neninchannels, nFramesPerBlock, dtype}; + d.copyInFromRaw(ptrs); + + bool ret = _incallback(d); + if (!ret) { + stopWithError(se::noError); + return 1; + } + } + + if (outputBuffer) { + assert(_outcallback); + std::vector ptrs; + ptrs.reserve(nenoutchannels); + + /* outCallback */ + us i = 0; + for (int ch = getLowestOutChannel(); ch <= getHighestOutChannel(); ch++) { + + if (outchannel_config.at(ch).enabled) { + + ptrs.push_back(static_cast(outputBuffer) + + sw * i * nFramesPerBlock); + + i++; + } + } + DaqData d{nenoutchannels, nFramesPerBlock, dtype}; + + bool ret = _outcallback(d); + if (!ret) { + stopWithError(se::noError); + return 1; + } + us j = 0; + for (auto ptr : ptrs) { + d.copyToRaw(j, ptr); + j++; + } + } + + return rval; + } + + // RtAudio documentation says: if a stream is open, it will be stopped and + // closed automatically on deletion. Therefore the destructor here is a + // default one. + ~RtAudioDaq() = default; +}; + +std::unique_ptr createRtAudioDevice(const DeviceInfo &devinfo, + const DaqConfiguration &config) { + return std::make_unique(devinfo, config); +} + +void myerrorcallback(RtAudioError::Type, const string &errorText) { + cerr << "RtAudio backend stream error: " << errorText << endl; +} +int mycallback(void *outputBuffer, void *inputBuffer, unsigned int nFrames, + double streamTime, RtAudioStreamStatus status, void *userData) { + + return static_cast(userData)->streamCallback( + outputBuffer, inputBuffer, nFrames, streamTime, status); +} + +#endif // LASP_HAS_RTAUDIO == 1 diff --git a/src/lasp/device/lasp_rtaudiodaq.h b/src/lasp/device/lasp_rtaudiodaq.h new file mode 100644 index 0000000..47a8cbb --- /dev/null +++ b/src/lasp/device/lasp_rtaudiodaq.h @@ -0,0 +1,14 @@ +#pragma once +#include "lasp_daq.h" +#include + +std::unique_ptr createRtAudioDevice(const DeviceInfo& devinfo, + const DaqConfiguration& config); + +/** + * @brief Append RtAudio backend devices to the list + * + * @param devinfolist List to append to + */ +void fillRtAudioDeviceInfo(std::vector &devinfolist); + diff --git a/src/lasp/device/lasp_streammgr.cpp b/src/lasp/device/lasp_streammgr.cpp new file mode 100644 index 0000000..cdf9118 --- /dev/null +++ b/src/lasp/device/lasp_streammgr.cpp @@ -0,0 +1,363 @@ +/* #define DEBUGTRACE_ENABLED */ +#include "lasp_streammgr.h" +#include "debugtrace.hpp" +#include "lasp_thread.h" +#include +#include +#include +#include + +using std::cerr; +using std::endl; +using rte = std::runtime_error; + +InDataHandler::InDataHandler(StreamMgr &mgr) : _mgr(mgr) { DEBUGTRACE_ENTER; } +void InDataHandler::start() { + DEBUGTRACE_ENTER; + _mgr.addInDataHandler(*this); +} +void InDataHandler::stop() { +#if LASP_DEBUG == 1 + stopCalled = true; +#endif + _mgr.removeInDataHandler(*this); +} +InDataHandler::~InDataHandler() { + + DEBUGTRACE_ENTER; +#if LASP_DEBUG == 1 + if (!stopCalled) { + cerr << "************ BUG: Stop function not called while arriving at " + "InDataHandler's destructor. Fix this by calling " + "InDataHandler::stop() from the derived class' destructor." + << endl; + } +#endif +} + +StreamMgr &StreamMgr::getInstance() { + + DEBUGTRACE_ENTER; + static StreamMgr mgr; + return mgr; +} +StreamMgr::StreamMgr() { + DEBUGTRACE_ENTER; +#if LASP_DEBUG == 1 + _main_thread_id = std::this_thread::get_id(); +#endif + // Trigger a scan for the available devices, in the background. + rescanDAQDevices(true); +} +#if LASP_DEBUG == 1 +void StreamMgr::checkRightThread() const { + assert(std::this_thread::get_id() == _main_thread_id); +} +#endif + +void StreamMgr::rescanDAQDevices(bool background, + std::function callback) { + DEBUGTRACE_ENTER; + + checkRightThread(); + if(!_devices_mtx.try_lock()) { + throw rte("A background DAQ device scan is probably already running"); + } + _devices_mtx.unlock(); + + if (_inputStream || _outputStream) { + throw rte("Rescanning DAQ devices only possible when no stream is running"); + } + _devices.clear(); + auto &pool = getPool(); + if (background) { + pool.push_task(&StreamMgr::rescanDAQDevices_impl, this, callback); + } else { + rescanDAQDevices_impl(callback); + } +} +void StreamMgr::rescanDAQDevices_impl(std::function callback) { + DEBUGTRACE_ENTER; + std::scoped_lock lck(_devices_mtx); + _devices = DeviceInfo::getDeviceInfo(); + if (callback) { + callback(); + } +} +bool StreamMgr::inCallback(const DaqData &data) { + + /* DEBUGTRACE_ENTER; */ + + std::scoped_lock lck(_inDataHandler_mtx); + + for (auto &handler : _inDataHandlers) { + + bool res = handler->inCallback(data); + if (!res) + return false; + + } + return true; +} + +void StreamMgr::setSiggen(std::shared_ptr siggen) { + + DEBUGTRACE_ENTER; + checkRightThread(); + + std::scoped_lock lck(_siggen_mtx); + + // If not set to nullptr, and a stream is running, we update the signal + // generator by resetting it. + if (isStreamRunningOK(StreamType::output) && siggen) { + const Daq *daq = getDaq(StreamType::output); + assert(daq != nullptr); + // Reset the signal generator. + siggen->reset(daq->samplerate()); + } + _siggen = siggen; +} + +#define DEBUG_FILLDATA 0 +/** + * @brief Converts from double precision floating point to output signal in + * non-interleaving format. + * + * @tparam T + * @param data + * @param signal + * + * @return + */ +template bool fillData(DaqData &data, const vd &signal) { + /* DEBUGTRACE_ENTER; */ + assert(data.nframes == signal.size()); + + T *res = reinterpret_cast(data.raw_ptr()); + if (std::is_floating_point()) { + for (us ch = 0; ch < data.nchannels; ch++) { + for (us frame = 0; frame < data.nframes; frame++) { +#if DEBUG_FILLDATA == 1 + DEBUGTRACE_PRINT("SLOW flt"); + data.setSlow(frame, ch, + reinterpret_cast(&signal[frame])); +#else + res[ch * data.nframes + frame] = signal[frame]; +#endif + } + } + } else { + for (us ch = 0; ch < data.nchannels; ch++) { + for (us frame = 0; frame < data.nframes; frame++) { + const T val = (signal[frame] * std::numeric_limits::max()); +#if DEBUG_FILLDATA == 1 + data.setSlow(frame, ch, reinterpret_cast(&val)); +#else + res[ch * data.nframes + frame] = val; +#endif + } + } + } + + return true; +} +bool StreamMgr::outCallback(DaqData &data) { + + /* DEBUGTRACE_ENTER; */ + + std::scoped_lock lck(_siggen_mtx); + + if (_siggen) { + vd signal = _siggen->genSignal(data.nframes); + switch (data.dtype) { + case (DataTypeDescriptor::DataType::dtype_fl32): + fillData(data, signal); + break; + case (DataTypeDescriptor::DataType::dtype_fl64): + fillData(data, signal); + break; + case (DataTypeDescriptor::DataType::dtype_int8): + fillData(data, signal); + break; + case (DataTypeDescriptor::DataType::dtype_int16): + fillData(data, signal); + break; + case (DataTypeDescriptor::DataType::dtype_int32): + fillData(data, signal); + break; + } + } else { + // Set all values to 0. + std::fill(data.raw_ptr(), data.raw_ptr() + data.size_bytes(), 0); + } + return true; +} + +StreamMgr::~StreamMgr() { + DEBUGTRACE_ENTER; + checkRightThread(); + stopAllStreams(); +} +void StreamMgr::stopAllStreams() { + DEBUGTRACE_ENTER; + checkRightThread(); + _inputStream.reset(); + _outputStream.reset(); +} + +void StreamMgr::startStream(const DaqConfiguration &config) { + DEBUGTRACE_ENTER; + checkRightThread(); + + bool isInput = std::count_if(config.inchannel_config.cbegin(), + config.inchannel_config.cend(), + [](auto &i) { return i.enabled; }); + + // Find the first device that matches with the configuration + + DeviceInfo devinfo; + { + std::scoped_lock lck(_devices_mtx); + + auto devinfo2 = std::find_if( + _devices.cbegin(), _devices.cend(), + [&config](const DeviceInfo &d) { return config.match(d); }); + if (devinfo2 == std::cend(_devices)) { + throw rte("Could not find a device with name " + config.device_name + + " in list of devices."); + } + devinfo = *devinfo2; + } + + isInput |= config.monitorOutput && devinfo.hasInternalOutputMonitor; + + bool isOutput = std::count_if(config.outchannel_config.cbegin(), + config.outchannel_config.cend(), + [](auto &i) { return i.enabled; }); + + bool isDuplex = isInput && isOutput; + + if (!isInput && !isOutput) { + throw rte("Neither input, nor output channels enabled for " + "stream. Cannot start."); + } + + if ((isDuplex || isInput) && _inputStream) { + throw rte("Error: an input stream is already running. Please " + "first stop existing stream"); + } else if (isOutput && _outputStream) { + throw rte("Error: output stream is already running. Please " + "first stop existing stream"); + } + + InDaqCallback inCallback; + OutDaqCallback outCallback; + + using namespace std::placeholders; + std::unique_ptr daq = Daq::createDaq(devinfo, config); + + if (isInput) { + inCallback = std::bind(&StreamMgr::inCallback, this, _1); + } + + if (isOutput) { + if (_siggen) { + DEBUGTRACE_PRINT("Resetting _siggen with new samplerate of "); + DEBUGTRACE_PRINT(daq->samplerate()); + _siggen->reset(daq->samplerate()); + } + outCallback = std::bind(&StreamMgr::outCallback, this, _1); + } + DEBUGTRACE_PRINT(isInput); + DEBUGTRACE_PRINT(isOutput); + + daq->start(inCallback, outCallback); + + if (isInput) { + _inputStream = std::move(daq); + for(auto& handler: _inDataHandlers) { + handler->reset(_inputStream.get()); + } + if (_inputStream->duplexMode()) { + assert(!_outputStream); + } + } else { + assert(isOutput); + _outputStream = std::move(daq); + } +} +void StreamMgr::stopStream(const StreamType t) { + DEBUGTRACE_ENTER; + checkRightThread(); + switch (t) { + case (StreamType::input): { + if (!_inputStream) { + throw rte("Input stream is not running"); + } + /// Kills input stream + _inputStream = nullptr; + /// Send reset to all in data handlers + for(auto& handler: _inDataHandlers) { + handler->reset(nullptr); + } + } break; + case (StreamType::output): { + if (_inputStream && _inputStream->duplexMode()) { + _inputStream = nullptr; + } else { + if (!_outputStream) { + throw rte("Output stream is not running"); + } + _outputStream = nullptr; + } // end else + } break; + default: + throw rte("BUG"); + break; + } +} + +void StreamMgr::addInDataHandler(InDataHandler &handler) { + DEBUGTRACE_ENTER; + checkRightThread(); + std::scoped_lock lck(_inDataHandler_mtx); + _inDataHandlers.push_back(&handler); + if(_inputStream) { + handler.reset(_inputStream.get()); + } else { + handler.reset(nullptr); + } +} +void StreamMgr::removeInDataHandler(InDataHandler &handler) { + DEBUGTRACE_ENTER; + checkRightThread(); + std::scoped_lock lck(_inDataHandler_mtx); + _inDataHandlers.remove(&handler); +} + +Daq::StreamStatus StreamMgr::getStreamStatus(const StreamType type) const { + /* DEBUGTRACE_ENTER; */ + + checkRightThread(); + // Default constructor, says stream is not running, but also no errors + Daq::StreamStatus s; + + const Daq *daq = getDaq(type); + if (daq) { + s = daq->getStreamStatus(); + } + return s; +} + +const Daq *StreamMgr::getDaq(StreamType type) const { + if (type == StreamType::input) { + return _inputStream.get(); + } else { + // Output stream. If input runs in duplex mode, this is also the output + // stream. In that case, we return the outputstram + if (_inputStream && _inputStream->duplexMode()) { + return _inputStream.get(); + } + return _outputStream.get(); + } +} diff --git a/src/lasp/device/lasp_streammgr.h b/src/lasp/device/lasp_streammgr.h new file mode 100644 index 0000000..cc16cb2 --- /dev/null +++ b/src/lasp/device/lasp_streammgr.h @@ -0,0 +1,258 @@ +#pragma once +#include "lasp_daq.h" +#include "lasp_siggen.h" +#include +#include +#include +#include + +/** \addtogroup device + * @{ + */ +class StreamMgr; + +class InDataHandler { + + protected: + StreamMgr &_mgr; +#if LASP_DEBUG == 1 + std::atomic stopCalled{false}; +#endif + + public: + virtual ~InDataHandler(); + + /** + * @brief When constructed, the handler is added to the stream manager, which + * will call the handlers's inCallback() until stop() is called. + * + * @param mgr Stream manager. + */ + InDataHandler(StreamMgr &mgr); + + /** + * @brief This function is called when input data from a DAQ is available. + * + * @param daqdata Input data from DAQ + * + * @return true if no error. False to stop the stream from running. + */ + virtual bool inCallback(const DaqData &daqdata) = 0; + + /** + * @brief Reset in-data handler. + * + * @param daq New DAQ configuration of inCallback(). If nullptr is given, + * it means that the stream is stopped. + */ + virtual void reset(const Daq* daq = nullptr) = 0; + + /** + * @brief This function should be called from the constructor of the + * implementation of InDataHandler. It will start the stream's calling of + * inCallback(). + */ + void start(); + + /** + * @brief This function should be called from the destructor of derived + * classes, to disable the calls to inCallback(), such that proper + * destruction of the object is allowed and no other threads call methods + * from the object. It removes the inCallback() from the callback list of the + * StreamMgr(). **Failing to call this function results in deadlocks, errors + * like "pure virtual function called", or other**. + */ + void stop(); +}; + +/** + * @brief Stream manager. Used to manage the input and output streams. + * Implemented as a singleton: only one stream manager can be in existance for + * a given program. The thread that instantiates a stream manager is the only + * thread that should interact with the stream manager. If this is not true, + * undefined behavior can be expected. If LASP is compiled in debug mode, this + * fact is asserted. + */ +class StreamMgr { +#if LASP_DEBUG == 1 + std::thread::id _main_thread_id; +#endif + + /** + * @brief Storage for streams. + */ + std::unique_ptr _inputStream, _outputStream; + + /** + * @brief All indata handlers are called when input data is available. Note + * that they can be called from different threads and should take care of + * thread-safety. + */ + std::list _inDataHandlers; + std::mutex _inDataHandler_mtx; + + /** + * @brief Signal generator in use to generate output data. Currently + * implemented as to generate the same data for all output channels. + */ + std::shared_ptr _siggen; + std::mutex _siggen_mtx; + + std::mutex _devices_mtx; + std::vector _devices; + + StreamMgr(); + + friend class InDataHandler; + friend class Siggen; + + // Singleton, no public destructor + ~StreamMgr(); + + public: + enum class StreamType : us { + /** + * @brief Input stream + */ + input = 1 << 0, + /** + * @brief Output stream + */ + output = 1 << 1, + }; + + StreamMgr(const StreamMgr &) = delete; + StreamMgr &operator=(const StreamMgr &) = delete; + + /** + * @brief Get access to stream manager instance + * + * @return Reference to stream manager. + */ + static StreamMgr &getInstance(); + + /** + * @brief Obtain a list of devices currently available. When the StreamMgr is + * first created, this + * + * @return A copy of the internal stored list of devices + */ + std::vector getDeviceInfo() const { + std::scoped_lock lck(const_cast(_devices_mtx)); + return _devices; + } + + /** + * @brief Triggers a background scan of the DAQ devices, which updates the + * internally stored list of devices. Throws a runtime error when a + * background thread is already scanning for devices. + * + * @param background Perform searching for DAQ devices in the background. If + * set to true, the function returns immediately. + * @param callback Function to call when complete. + */ + void + rescanDAQDevices(bool background = false, + std::function callback = std::function()); + + /** + * @brief Start a stream based on given configuration. + * + * @param config Configuration of a device + */ + void startStream(const DaqConfiguration &config); + + /** + * @brief Check if a certain stream is running. If running with no errors, it + * returns true. If an error occured, or the stream is not running, it gives + * false. + * + * @param type The type of stream to check for. + */ + bool isStreamRunningOK(const StreamType type) const { + return getStreamStatus(type).runningOK(); + } + bool isStreamRunning(const StreamType type) const { + switch(type) { + case(StreamType::input): + return bool(_inputStream); + break; + case(StreamType::output): + return bool(_outputStream); + break; + } + return false; + } + + /** + * @brief Get the streamstatus object corresponding to a given stream. + * + * @param type Type of stream, input, inputType, etc. + * + * @return status object. + */ + Daq::StreamStatus getStreamStatus(const StreamType type) const; + + /** + * @brief Get DAQ pointer for a given stream. Gives a nullptr if stream is + * not running. + * + * @param type The stream type to get a DAQ ptr for. + * + * @return Pointer to DAQ + */ + const Daq *getDaq(StreamType type) const; + + /** + * @brief Stop stream of given type (input / output/ duplex); + * + * @param stype The stream type to stop. + */ + void stopStream(const StreamType stype); + + /** + * @brief Stop and delete all streams. Also called on destruction of the + * StreamMgr. + */ + void stopAllStreams(); + + /** + * @brief Set active signal generator for output streams. Only one `Siggen' + * is active at the same time. Siggen controls its own data race protection + * using a mutex. + * + * @param s New Siggen pointer + */ + void setSiggen(std::shared_ptr s); + + private: + bool inCallback(const DaqData &data); + bool outCallback(DaqData &data); + + void removeInDataHandler(InDataHandler &handler); + + /** + * @brief Add an input data handler. The handler's inCallback() function is + * called with data when available. This function should *NOT* be called by + * the user, instead, an InDataHandler can only be created with the StreamMgr + * as argument. + * + * @param handler The handler to add. + */ + void addInDataHandler(InDataHandler &handler); + + /** + * @brief Do the actual rescanning. + * + * @param callback + */ + void rescanDAQDevices_impl(std::function callback); + +#if LASP_DEBUG == 1 + void checkRightThread() const; +#else + void checkRightThread() const {} +#endif +}; + +/** @} */ diff --git a/src/lasp/device/lasp_uldaq.cpp b/src/lasp/device/lasp_uldaq.cpp new file mode 100644 index 0000000..24fb61a --- /dev/null +++ b/src/lasp/device/lasp_uldaq.cpp @@ -0,0 +1,660 @@ +/* #define DEBUGTRACE_ENABLED */ +#include "debugtrace.hpp" +#include "lasp_config.h" + +#if LASP_HAS_ULDAQ == 1 +#include "lasp_daqconfig.h" +#include "lasp_uldaq.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std::literals::chrono_literals; +using std::atomic; +using std::cerr; +using std::endl; +using rte = 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 << "\b\n**************** UlDAQ backend error **********\n"; + std::cerr << errstr << std::endl; + std::cerr << "***********************************************\n\n"; + return errstr; + } + return errstr; +} + +class DT9837A : public Daq { + + DaqDeviceHandle _handle = 0; + std::mutex _daqmutex; + + std::thread _thread; + atomic _stopThread{false}; + atomic _streamStatus; + + const us _nFramesPerBlock; + + void threadFcn(InDaqCallback inCallback, OutDaqCallback outcallback); + +public: + DaqDeviceHandle getHandle() const { return _handle; } + 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 { + DEBUGTRACE_ENTER; + return _thread.joinable(); + } + virtual void start(InDaqCallback inCallback, + OutDaqCallback outCallback) override final; + + virtual StreamStatus getStreamStatus() const override { + return _streamStatus; + } + + void stop() override final { + DEBUGTRACE_ENTER; + StreamStatus status = _streamStatus; + if (!isRunning()) { + throw rte("No data acquisition running"); + } + + _stopThread = true; + if (_thread.joinable()) { + _thread.join(); + } + _stopThread = false; + status.isRunning = false; + _streamStatus = status; + } + + friend class InBufHandler; + friend class OutBufHandler; +}; + +void DT9837A::start(InDaqCallback inCallback, OutDaqCallback outCallback) { + DEBUGTRACE_ENTER; + if (isRunning()) { + throw rte("DAQ is already running"); + } + if (neninchannels() > 0) { + if (!inCallback) + throw rte("DAQ requires a callback for input data"); + } + if (nenoutchannels() > 0) { + if (!outCallback) + throw rte("DAQ requires a callback for output data"); + } + assert(neninchannels() + nenoutchannels() > 0); + _thread = std::thread(&DT9837A::threadFcn, this, inCallback, outCallback); +} + +class BufHandler { +protected: + DT9837A &daq; + const DataTypeDescriptor dtype_descr; + us nchannels, nFramesPerBlock; + double samplerate; + std::vector buf; + bool topenqueued, botenqueued; + + us increment = 0; + + us totalFramesCount = 0; + long long buffer_mid_idx; + +public: + BufHandler(DT9837A &daq, const us nchannels) + : daq(daq), dtype_descr(daq.dtypeDescr()), nchannels(nchannels), + nFramesPerBlock(daq.framesPerBlock()), samplerate(daq.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; + InDaqCallback cb; + +public: + InBufHandler(DT9837A &daq, InDaqCallback cb) + : BufHandler(daq, daq.neninchannels()), cb(cb) + + { + DEBUGTRACE_ENTER; + assert(daq.getHandle() != 0); + + monitorOutput = daq.monitorOutput; + + DaqInScanFlag inscanflags = DAQINSCAN_FF_DEFAULT; + ScanOption scanoptions = SO_CONTINUOUS; + UlError err = ERR_NO_ERROR; + + std::vector indescs; + boolvec eninchannels_without_mon = daq.eninchannels(false); + + // Initialize input, if any + for (us chin = 0; chin < 4; chin++) { + if (eninchannels_without_mon[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); + } + } + + // Add possibly last channel as monitor + if (monitorOutput) { + DaqInChanDescriptor indesc; + indesc.type = DAQI_DAC; + indesc.channel = 0; + /// The output only has a range of 10V, therefore the monitor of the + /// output also has to be set to this value. + indesc.range = BIP10VOLTS; + indescs.push_back(indesc); + } + assert(indescs.size() == nchannels); + + DEBUGTRACE_MESSAGE("Starting input scan"); + + err = ulDaqInScan(daq.getHandle(), indescs.data(), nchannels, + 2 * nFramesPerBlock, // Watch the 2 here! + &samplerate, scanoptions, inscanflags, buf.data()); + if (err != ERR_NO_ERROR) { + showErr(err); + throw rte("Could not start input DAQ"); + } + } + void start() { + + ScanStatus status; + TransferStatus transferStatus; + UlError err = ulDaqInScanStatus(daq.getHandle(), &status, &transferStatus); + if (err != ERR_NO_ERROR) { + showErr(err); + throw rte("Unable to start input on DAQ"); + } + + totalFramesCount = transferStatus.currentTotalCount; + topenqueued = true; + botenqueued = true; + } + + /** + * @brief InBufHandler::operator()() + * + * @return true on success + */ + bool operator()() { + + /* DEBUGTRACE_ENTER; */ + + bool ret = true; + + auto runCallback = ([&](us totalOffset) { + /* DEBUGTRACE_ENTER; */ + + TypedDaqData data(nchannels, nFramesPerBlock, + DataTypeDescriptor::DataType::dtype_fl64); + + us monitorOffset = monitorOutput ? 1 : 0; + /* /// Put the output monitor in front */ + if (monitorOutput) { + for (us sample = 0; sample < nFramesPerBlock; sample++) { + data(sample, 0) = + buf[totalOffset + sample * nchannels + (nchannels - 1)]; + } + } + + for (us channel = 0; channel < nchannels-monitorOffset; channel++) { + /* DEBUGTRACE_PRINT(channel); */ + for (us frame = 0; frame < nFramesPerBlock; frame++) { + data(frame, channel + monitorOffset) = + buf[totalOffset + (frame * nchannels) + channel]; + } + } + return cb(data); + }); + + ScanStatus status; + TransferStatus transferStatus; + + UlError err = ulDaqInScanStatus(daq.getHandle(), &status, &transferStatus); + if (err != ERR_NO_ERROR) { + showErr(err); + return false; + } + + increment = transferStatus.currentTotalCount - totalFramesCount; + totalFramesCount += increment; + + if (increment > nFramesPerBlock) { + cerr << "Error: overrun for input of DAQ!" << endl; + return false; + } + assert(status == SS_RUNNING); + + if (transferStatus.currentIndex < (long long)buffer_mid_idx) { + topenqueued = false; + if (!botenqueued) { + ret = runCallback(nchannels * nFramesPerBlock); + botenqueued = true; + } + } else { + botenqueued = false; + if (!topenqueued) { + ret = runCallback(0); + topenqueued = true; + } + } + return ret; + } + ~InBufHandler() { + // At exit of the function, stop scanning. + DEBUGTRACE_ENTER; + UlError err = ulDaqInScanStop(daq.getHandle()); + if (err != ERR_NO_ERROR) { + showErr(err); + } + } +}; + +class OutBufHandler : public BufHandler { + OutDaqCallback cb; + +public: + OutBufHandler(DT9837A &daq, OutDaqCallback cb) + : BufHandler(daq, daq.nenoutchannels()), cb(cb) { + + DEBUGTRACE_MESSAGE("Starting output scan"); + DEBUGTRACE_PRINT(nchannels); + AOutScanFlag outscanflags = AOUTSCAN_FF_DEFAULT; + ScanOption scanoptions = SO_CONTINUOUS; + UlError err = + ulAOutScan(daq.getHandle(), 0, 0, BIP10VOLTS, + 2 * nFramesPerBlock, // Watch the 2 here! + &samplerate, scanoptions, outscanflags, buf.data()); + + if (err != ERR_NO_ERROR) { + showErr(err); + throw rte("Unable to start output on DAQ"); + } + } + void start() { + + ScanStatus status; + TransferStatus transferStatus; + + UlError err = ulAOutScanStatus(daq.getHandle(), &status, &transferStatus); + if (err != ERR_NO_ERROR) { + showErr(err); + throw rte("Unable to start output on DAQ"); + } + if (status != SS_RUNNING) { + throw rte("Unable to start output on DAQ"); + } + totalFramesCount = transferStatus.currentTotalCount; + topenqueued = true; + botenqueued = true; + } + /** + * @brief OutBufHandler::operator() + * + * @return true on success + */ + bool operator()() { + + /* DEBUGTRACE_ENTER; */ + bool res = true; + assert(daq.getHandle() != 0); + + UlError err = ERR_NO_ERROR; + + ScanStatus status; + TransferStatus transferStatus; + + err = ulAOutScanStatus(daq.getHandle(), &status, &transferStatus); + if (err != ERR_NO_ERROR) { + showErr(err); + return false; + } + if (status != SS_RUNNING) { + return false; + } + increment = transferStatus.currentTotalCount - totalFramesCount; + totalFramesCount += increment; + + if (increment > nFramesPerBlock) { + cerr << "Error: underrun for output of DAQ!" << endl; + return false; + } + + if (transferStatus.currentIndex < buffer_mid_idx) { + topenqueued = false; + if (!botenqueued) { + TypedDaqData d(1, nFramesPerBlock, + DataTypeDescriptor::DataType::dtype_fl64); + res = cb(d); + d.copyToRaw(0, &buf[buffer_mid_idx]); + + botenqueued = true; + } + } else { + botenqueued = false; + if (!topenqueued) { + TypedDaqData d(1, nFramesPerBlock, + DataTypeDescriptor::DataType::dtype_fl64); + res = cb(d); + d.copyToRaw(0, buf.data()); + + topenqueued = true; + } + } + return res; + } + + ~OutBufHandler() { + DEBUGTRACE_ENTER; + UlError err = ulAOutScanStop(daq.getHandle()); + if (err != ERR_NO_ERROR) { + showErr(err); + } + } +}; + +void DT9837A::threadFcn(InDaqCallback inCallback, OutDaqCallback outCallback) { + + DEBUGTRACE_ENTER; + + /* cerr << "******************\n" */ + /* "Todo: the current way of handling timing in this DAQ thread is not " */ + /* "really robust, due " */ + /* "to input / output callbacks that can be too time-consuming. We have " */ + /* "to fix the " */ + /* "sleep_for to properly deal with longer callbacks." */ + /* "\n*****************" */ + /* << endl; */ + try { + + std::unique_ptr obh; + std::unique_ptr ibh; + + StreamStatus status = _streamStatus; + status.isRunning = true; + _streamStatus = status; + + if (nenoutchannels() > 0) { + assert(outCallback); + obh = std::make_unique(*this, outCallback); + } + if (neninchannels() > 0) { + assert(inCallback); + ibh = std::make_unique(*this, inCallback); + } + if (obh) + obh->start(); + if (ibh) + ibh->start(); + + const double sleeptime_s = + static_cast(_nFramesPerBlock) / (16 * samplerate()); + const us sleeptime_us = static_cast(sleeptime_s * 1e6); + + while (!_stopThread) { + if (ibh) { + if (!(*ibh)()) { + _stopThread = true; + } + } + if (obh) { + if (!(*obh)()) { + _stopThread = true; + } + } else { + std::this_thread::sleep_for(std::chrono::microseconds(sleeptime_us)); + } + } + } catch (rte &e) { + + StreamStatus status = _streamStatus; + status.isRunning = false; + status.errorType = StreamStatus::StreamError::systemError; + _streamStatus = status; + + cerr << "\n******************\n"; + cerr << "Catched error in UlDAQ thread: " << e.what() << endl; + cerr << "\n******************\n"; + } + StreamStatus status = _streamStatus; + + status.isRunning = false; + _streamStatus = status; + _stopThread = false; +} + +std::unique_ptr createUlDaqDevice(const DeviceInfo &devinfo, + const DaqConfiguration &config) { + return std::make_unique(devinfo, config); +} + +DT9837A::DT9837A(const DeviceInfo &devinfo, const DaqConfiguration &config) + : Daq(devinfo, config), + _nFramesPerBlock(availableFramesPerBlock.at(framesPerBlockIndex)) { + + // Some sanity checks + if (inchannel_config.size() != 4) { + throw rte("Invalid length of enabled inChannels vector"); + } + + if (outchannel_config.size() != 1) { + throw rte("Invalid length of enabled outChannels vector"); + } + + if (_nFramesPerBlock < 24 || _nFramesPerBlock > 8192) { + throw rte("Unsensible number of samples per block chosen"); + } + + if (samplerate() < 10000 || samplerate() > 51000) { + throw rte("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 rte("Device inventarization failed"); + } + + if ((us)api_specific_devindex >= numdevs) { + throw rte("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 rte("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 rte(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 rte("Fatal: could normalize channel sensitivity"); + } + + CouplingMode cm = inchannel_config.at(ch).ACCouplingMode ? CM_AC : CM_DC; + err = ulAISetConfig(_handle, AI_CFG_CHAN_COUPLING_MODE, ch, cm); + if (err != ERR_NO_ERROR) { + showErr(err); + throw rte("Fatal: could not set AC/DC coupling mode"); + } + + IepeMode iepe = + inchannel_config.at(ch).IEPEEnabled ? IEPE_ENABLED : IEPE_DISABLED; + err = ulAISetConfig(_handle, AI_CFG_CHAN_IEPE_MODE, ch, iepe); + if (err != ERR_NO_ERROR) { + showErr(err); + throw rte("Fatal: could not set IEPE mode"); + } + } +} +void fillUlDaqDeviceInfo(std::vector &devinfolist) { + + DEBUGTRACE_ENTER; + + UlError err; + unsigned int numdevs = MAX_DEV_COUNT_PER_API; + + DaqDeviceDescriptor devdescriptors[MAX_DEV_COUNT_PER_API]; + DaqDeviceDescriptor descriptor; + DaqDeviceInterface interfaceType = ANY_IFC; + + err = ulGetDaqDeviceInventory(interfaceType, devdescriptors, + static_cast(&numdevs)); + + if (err != ERR_NO_ERROR) { + throw rte("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 rte("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; + + devinfo.hasInternalOutputMonitor = true; + + // Finally, this devinfo is pushed back in list + devinfolist.push_back(devinfo); + } +} +#endif // LASP_HAS_ULDAQ diff --git a/src/lasp/device/lasp_uldaq.h b/src/lasp/device/lasp_uldaq.h new file mode 100644 index 0000000..4dd7489 --- /dev/null +++ b/src/lasp/device/lasp_uldaq.h @@ -0,0 +1,14 @@ +#pragma once +#include "lasp_daq.h" + +std::unique_ptr createUlDaqDevice(const DeviceInfo& devinfo, + const DaqConfiguration& config); + +/** + * @brief Fill device info list with UlDaq specific devices, if any. + * + * @param devinfolist Info list to append to + */ +void fillUlDaqDeviceInfo(std::vector &devinfolist); + + diff --git a/src/lasp/dsp/CMakeLists.txt b/src/lasp/dsp/CMakeLists.txt new file mode 100644 index 0000000..61a8f59 --- /dev/null +++ b/src/lasp/dsp/CMakeLists.txt @@ -0,0 +1,27 @@ +# src/lasp/dsp/CMakeLists.txt + +set(lasp_dsp_files + lasp_filter.cpp + lasp_siggen.cpp + lasp_siggen_impl.cpp + lasp_window.cpp + lasp_fft.cpp + lasp_rtaps.cpp + lasp_avpowerspectra.cpp + lasp_biquadbank.cpp + lasp_thread.cpp + lasp_timebuffer.cpp + lasp_slm.cpp + lasp_threadedindatahandler.cpp + lasp_ppm.cpp + ) + + + +add_library(lasp_dsp_lib STATIC ${lasp_dsp_files}) +target_compile_definitions(lasp_dsp_lib PUBLIC "-DARMA_DONT_USE_WRAPPER") + +target_link_libraries(lasp_dsp_lib PRIVATE ${BLAS_LIBRARIES}) +target_include_directories(lasp_dsp_lib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +target_include_directories(lasp_dsp_lib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../device) + diff --git a/src/lasp/dsp/lasp_avpowerspectra.cpp b/src/lasp/dsp/lasp_avpowerspectra.cpp new file mode 100644 index 0000000..e8bc71d --- /dev/null +++ b/src/lasp/dsp/lasp_avpowerspectra.cpp @@ -0,0 +1,131 @@ +/* #define DEBUGTRACE_ENABLED */ +#include "lasp_avpowerspectra.h" +#include "lasp_mathtypes.h" +#include "debugtrace.hpp" +#include +#include + +using rte = std::runtime_error; +using std::cerr; +using std::endl; + +PowerSpectra::PowerSpectra(const us nfft, const Window::WindowType w) + : PowerSpectra(Window::create(w, nfft)) {} + +PowerSpectra::PowerSpectra(const vd &window) + : nfft(window.size()), _fft(nfft), _window(window) { + + d win_pow = arma::sum(window % window) / window.size(); + + /* Scale fft such that power is easily computed */ + _scale_fac = 2.0 / (win_pow * (d)nfft * (d)nfft); + + DEBUGTRACE_PRINT(nfft); + DEBUGTRACE_PRINT(win_pow); +} + +arma::Cube PowerSpectra::compute(const dmat &input) { + + /// Run very often. Silence this one. + /* DEBUGTRACE_ENTER; */ + + dmat input_tmp = input; + + // Multiply each column of the inputs element-wise with the window. + input_tmp.each_col() %= _window; + + cmat rfft = _fft.fft(input_tmp); + + arma::cx_cube output(rfft.n_rows, input.n_cols, input.n_cols); + for (us i = 0; i < input.n_cols; i++) { + /// This one can be run in parallel without any problem. Note that it is + /// the inner loop that is run in parallel. +#pragma omp parallel for + for (us j = 0; j < input.n_cols; j++) { + output.slice(j).col(i) = + _scale_fac * (rfft.col(i) % arma::conj(rfft.col(j))); + + output.slice(j).col(i)(0) /= 2; + output.slice(j).col(i)(nfft / 2) /= 2; + } + } + return output; +} + +AvPowerSpectra::AvPowerSpectra(const us nfft, const Window::WindowType w, + const d overlap_percentage, + const d time_constant_times_fs) + : _ps(nfft, w) { + + DEBUGTRACE_ENTER; + if (overlap_percentage >= 100 || overlap_percentage < 0) { + throw rte("Overlap percentage should be >= 0 and < 100"); + } + + _overlap_keep = (nfft * overlap_percentage) / 100; + if (_overlap_keep >= nfft) { + throw rte("Overlap is too high. Results in no jump. Please " + "choose a smaller overlap percentage or a higher nfft"); + } + if (time_constant_times_fs < 0) { + _mode = Mode::Averaging; + } else if (time_constant_times_fs == 0) { + _mode = Mode::Spectrogram; + } else { + _mode = Mode::Leaking; + _alpha = d_exp(-static_cast((nfft - _overlap_keep)/time_constant_times_fs)); + DEBUGTRACE_PRINT(_alpha); + } +} + +std::optional AvPowerSpectra::compute(const dmat &timedata) { + + /* DEBUGTRACE_ENTER; */ + /* DEBUGTRACE_PRINT(timedata.n_rows); */ + /* DEBUGTRACE_PRINT(_ps.nfft); */ + + _timeBuf.push(timedata); + if (_est.n_cols == 0) { + _est = arma::cx_cube(_ps.nfft / 2 + 1, timedata.n_cols, timedata.n_cols, + arma::fill::zeros); + } + + std::optional res; + + us i = 0; + while (auto samples = _timeBuf.pop(_ps.nfft, _overlap_keep)) { + /* DEBUGTRACE_PRINT((int)_mode); */ + switch (_mode) { + case (Mode::Spectrogram): { + _est = _ps.compute(samples.value()); + } break; + case (Mode::Averaging): { + n_averages++; + if (n_averages == 1) { + _est = _ps.compute(samples.value()); + } else { + _est = _est * (static_cast(n_averages - 1) / n_averages) + + _ps.compute(samples.value()) / n_averages; + } + } break; + case (Mode::Leaking): { + /* DEBUGTRACE_PRINT("Leaking mode"); */ + if (arma::size(_est) == arma::size(0, 0, 0)) { + _est = _ps.compute(samples.value()); + } else { + _est = _alpha * _est + (1 - _alpha) * _ps.compute(samples.value()); + } + } break; + } // end switch mode + i++; + } + if (i > 0) { + return std::make_optional(_est); + } + return std::nullopt; +} +std::optional AvPowerSpectra::get_est() { + if (_est.n_cols > 0) + return _est; + return std::nullopt; +} diff --git a/src/lasp/dsp/lasp_avpowerspectra.h b/src/lasp/dsp/lasp_avpowerspectra.h new file mode 100644 index 0000000..ed12540 --- /dev/null +++ b/src/lasp/dsp/lasp_avpowerspectra.h @@ -0,0 +1,164 @@ +#pragma once +#include "lasp_fft.h" +#include "lasp_mathtypes.h" +#include "lasp_timebuffer.h" +#include "lasp_window.h" +#include +#include + +/** \defgroup dsp Digital Signal Processing utilities + * These are classes and functions used for processing raw signal data, to + * obtain statistics. + * + * + * @{ + */ + +/** + * @brief Computes single-sided cross-power spectra for a group of channels. + * Only a single block of length fft, no averaging. This class should normally + * not be used. Please refer to AvPowerSpectra instead. + */ +class PowerSpectra { +public: + /** + * @brief The FFT length + */ + us nfft; + +private: + /** + * @brief Instance to compute actual FFT's + */ + Fft _fft; + + vd _window; + d _scale_fac; + +public: + /** + * @brief Initalize power spectra computer + * + * @param nfft The fft length + * @param w The window type + */ + PowerSpectra(const us nfft, const Window::WindowType w); + + /** + * @brief Initalize power spectra computer + * + * @param window Uses the window length to compute fft length, and uses the + * window shape as the actual window. + */ + PowerSpectra(const vd &window); + + /** + * @brief Computes the spectra. Data is normalized by the (spectral) power of + * the window, to compensate for the effect of the window. + * + * @param input Input data, first axis is assumed the sample, second axis the + * channel. Input first dimension should be equal to nfft, otherwise a + * runtime error is thrown. + * + * @return Cross-power-spectra. Cubic array with size indices C_fij, where + * f is the frequency index, i is the row and j is the column. An element + * can be accessed by: C(f, i, j). Storage is such that frequency components + * are contiguous. + */ + arma::Cube compute(const dmat &input); +}; + +/** + * @brief Estimate cross-power spectra using Welch' method of spectral + * estimation. The exact amount of overlap in Welch' method is rounded up to a + * certain amount of samples. + */ +class AvPowerSpectra { + + enum class Mode { + Averaging = 0, // Averaging all time date + Leaking = 1, // Exponential weighting of an "instantaneous cps" + Spectrogram = 2 // Instantenous spectrum, no averaging + }; + + Mode _mode; + d _alpha; // Only valid in case of 'Leaking' + us n_averages = 0; // Only valid in case of 'Averaging' + + PowerSpectra _ps; + /** + * @brief Current estimate of cross-spectral density + */ + arma::cx_cube _est; + + /** + * @brief Buffer of storage of time data. + */ + TimeBuffer _timeBuf; + /** + * @brief The amount of samples to keep in the overlap + */ + us _overlap_keep; + +public: + /** + * @brief Initalize averaged power spectra computer. If a time constant is + * given > 0, it is used in a kind of exponential weighting. + * + * @param nfft The fft length + * @param w The window type. + * @param overlap_percentage A number 0 < overlap_percentage <= 100. It + * determines the amount of overlap used in Welch' method. A typical value is + * 50 %, i.e. 50. + * @param fs_tau Value should either be < 0, indicating that the + * estimate is averages over all time data. + * For a value = 0 the instantaneous power spectrum is returned, which can be + * interpreted as the spectrogram. For a value > 0 a exponential forgetting is + * used, where the value is used as the time constant such that the decay + * follows approximately the trend exp(-n/fs_tau), where n is the + * sample number in the power spectra. To choose 'fast' time weighting, set + * time_constant to the value of fs*0.125, where fs denotes the sampling + * frequency. + **/ + AvPowerSpectra(const us nfft = 2048, + const Window::WindowType w = Window::WindowType::Hann, + const d overlap_percentage = 50., + const d fs_tau = -1); + + AvPowerSpectra(const AvPowerSpectra &) = delete; + AvPowerSpectra &operator=(const AvPowerSpectra &) = delete; + + void reset() { _est.reset(); } + + /** + * @brief Compute an update of the power spectra based on given time data. + * Note that the number of channels is determined from the first time this + * function is called. If a later call has an incompatible number of + * channels, a runtime error is thrown. + * + * @param timedata + * + * @return Optionally, a copy of the latest estimate of the power spectra. An + * update is only given if the amount of new time data is enough to compute a + * new estimate. It can be checked by operator bool(). + * + */ + std::optional compute(const dmat &timedata); + + /** + * @brief Returns the latest estimate of cps (cross-power spectra. + * + * @return Pointer (reference, not owning!) to spectral estimate date, or + * nullptr, in case nothing could yet be computed. + */ + std::optional get_est(); + + /** + * @brief The overlap is rounded to a certain amount of time samples. This + * function returns that value. + * + * @return The amount of samples in overlapping. + */ + us exactOverlapSamples() const { return _ps.nfft - _overlap_keep; } +}; +/** @} */ diff --git a/src/lasp/dsp/lasp_biquadbank.cpp b/src/lasp/dsp/lasp_biquadbank.cpp new file mode 100644 index 0000000..6ee8060 --- /dev/null +++ b/src/lasp/dsp/lasp_biquadbank.cpp @@ -0,0 +1,138 @@ +/* #define DEBUGTRACE_ENABLED */ +#include "lasp_biquadbank.h" +#include "debugtrace.hpp" +#include "lasp_thread.h" +#include + +using std::cerr; +using std::endl; +using rte = std::runtime_error; + +SeriesBiquad::SeriesBiquad(const vd &filter_coefs) { + DEBUGTRACE_ENTER; + if (filter_coefs.n_cols != 1) { + throw rte("Expected filter coefficients for a single SeriesBiquad as a " + "single column with length 6 x n_filters"); + } + + if (filter_coefs.n_rows % 6 != 0) { + cerr << "Number of rows given: " << filter_coefs.n_rows << endl; + throw rte("filter_coefs should be multiple of 6, given: " + + std::to_string(filter_coefs.n_rows)); + } + us nfilters = filter_coefs.n_rows / 6; + + /// Initialize state to zero + state = dmat(2, nfilters, arma::fill::zeros); + + sos.resize(6, nfilters); + for (us i = 0; i < nfilters; i++) { + sos.col(i) = filter_coefs.subvec(6 * i, 6 * (i + 1) - 1); + } + + /// Check if third row in this matrix equals unity. + if (!arma::approx_equal(sos.row(3), arma::rowvec(nfilters, arma::fill::ones), + "absdiff", 1e-9)) { + std::cerr << "Read row: " << sos.row(3) << endl; + + throw rte( + "Filter coefficients should have fourth element (a0) equal to 1.0"); + } +} +std::unique_ptr SeriesBiquad::clone() const { + // sos.as_col() concatenates all columns, exactly what we want. + return std::make_unique(sos.as_col()); +} +void SeriesBiquad::reset() { + DEBUGTRACE_ENTER; + state.zeros(); +} +void SeriesBiquad::filter(vd &inout) { + + DEBUGTRACE_ENTER; + + /// Implementation is based on Proakis & Manolakis - Digital Signal + /// Processing, Fourth Edition, p. 550 + for (us filterno = 0; filterno < sos.n_cols; filterno++) { + d b0 = sos(0, filterno); + d b1 = sos(1, filterno); + d b2 = sos(2, filterno); + d a1 = sos(4, filterno); + d a2 = sos(5, filterno); + + d w1 = state(0, filterno); + d w2 = state(1, filterno); + + for (us sample = 0; sample < inout.size(); sample++) { + d w0 = inout(sample) - a1 * w1 - a2 * w2; + d yn = b0 * w0 + b1 * w1 + b2 * w2; + w2 = w1; + w1 = w0; + inout(sample) = yn; + } + + state(0, filterno) = w1; + state(1, filterno) = w2; + } +} + +BiquadBank::BiquadBank(const dmat &filters, const vd *gains) { + DEBUGTRACE_ENTER; + /** + * @brief Make sure the pool is created once, such that all threads are ready + * for use. + */ + getPool(); + + for (us i = 0; i < filters.n_cols; i++) { + _filters.emplace_back(filters.col(i)); + } + + if (gains != nullptr) { + setGains(*gains); + } else { + _gains = vd(_filters.size(), arma::fill::ones); + } +} +void BiquadBank::setGains(const vd &gains) { + DEBUGTRACE_ENTER; + const us nfilters = _filters.size(); + if (gains.size() != nfilters) { + throw rte("Invalid number of gain values given."); + } + _gains = gains; +} + +void BiquadBank::filter(vd &inout) { + + dmat res(inout.n_rows, _filters.size()); + std::vector> futs; + + auto &pool = getPool(); + for (us i = 0; i < res.n_cols; i++) { + futs.emplace_back(pool.submit( + [&](us i) { + // Copy column + vd col = inout; + _filters[i].filter(col); + return col; + }, // Launch a task to filter. + i // Column i as argument to the lambda function above. + )); + } + + // Zero-out in-out and sum-up the filtered values + inout.zeros(); + for (us i = 0; i < res.n_cols; i++) { + inout += futs[i].get() * _gains[i]; + } +} +void BiquadBank::reset() { + DEBUGTRACE_ENTER; + for (auto &f : _filters) { + f.reset(); + } +} +std::unique_ptr BiquadBank::clone() const { + return std::make_unique(_filters, _gains); +} diff --git a/src/lasp/dsp/lasp_biquadbank.h b/src/lasp/dsp/lasp_biquadbank.h new file mode 100644 index 0000000..0fd6fab --- /dev/null +++ b/src/lasp/dsp/lasp_biquadbank.h @@ -0,0 +1,95 @@ +#pragma once +#include "lasp_filter.h" + +/** + * \addtogroup dsp + * @{ + */ + +/** + * @brief A set of Biquad filters in series. + */ +class SeriesBiquad : public Filter { + + /// The filter coefficients for each of the filters in the Filterbank + /// The *first* axis is the filter no, the second axis contains the + /// filter coefficients, in the order, b_0, b_1, b_2, a_0, a_1, a_2, which + /// corresponds to the transfer function + /// b_0 + b_1 z^-1 + b_2 z^-2 + /// H[z] = ------------------------- + /// a_0 + a_1 z^-1 + a_2 z^-2 + /// + dmat sos; /// sos[coef, filter_no] + /// + /// Storage for the current state of the output, first axis correspond to + /// the state axis, the second axis to the series filter no. For each filter + /// in series, two state coefficients are remembered. + dmat state; + +public: + /** + * @brief Initalize a SeriesBiquad filter. + * + * @param filter_coefs Filter coefficients, should be given in the order as + * [b0, b1, b2, a0==1, a1, a2, b0,...] + */ + SeriesBiquad(const vd &filter_coefs); + + virtual void filter(vd &inout) override final; + virtual ~SeriesBiquad() override {} + void reset() override final; + std::unique_ptr clone() const override final; +}; + +/** + * @brief Multiple biquad filters in parallel, each multiplied with a gain + * value, and finally all added together. This class can be used to create + * a graphic equalizer. + */ +class BiquadBank : public Filter { + std::vector _filters; + vd _gains; + + +public: + /** + * @brief Initialize biquadbank. + * + * @param filters Filters for each filter in the bank. First axis isis the + * coefficient index, second axis is the filter index. + * @param gains Gain values. Given as pointer, if not given (nulltpr), gains + * are initialized with unity gain. + */ + BiquadBank(const dmat &filters, const vd *gains = nullptr); + + /** + * @brief Construct biquad bank from already given set of series biquad filters and gain values + * + * @param filters The filters to set + * @param gains The gain values for each filter + */ + BiquadBank(std::vector filters,vd gains): + _filters(std::move(filters)),_gains(std::move(gains)) {} + + /** + * @brief Set new gain values for each filter in the BiquadBank + * + * @param gains Vector of gain values. Should be of same length as the number + * of filters installed. + */ + void setGains(const vd& gains); + + /** + * @brief Returns the number of Filters + * + * @return The number of filters + */ + us nfilters() const {return _filters.size();} + + virtual void filter(vd &inout) override final; + + void reset() override final; + std::unique_ptr clone() const override final; + +}; +/** @} */ diff --git a/src/lasp/dsp/lasp_fft.cpp b/src/lasp/dsp/lasp_fft.cpp new file mode 100644 index 0000000..20a74f9 --- /dev/null +++ b/src/lasp/dsp/lasp_fft.cpp @@ -0,0 +1,187 @@ +// lasp_fft.c +// +// Author: J.A. de Jong - ASCEE +// +// Description: +// FFt implementation +// +////////////////////////////////////////////////////////////////////// +#include +#include +/* #define DEBUGTRACE_ENABLED */ +#include "lasp_fft.h" +#include "debugtrace.hpp" +#include "lasp_config.h" +using rte = std::runtime_error; + +#if LASP_FFT_BACKEND == Armadillo +#include "fftpack.h" +class Fft_impl { + public: + us nfft; + Fft_impl(const us nfft) : nfft(nfft) { + throw runtime_error( + "This code does not output correct results, as it computes the full " + "FFT. It needs to be reworked to single-sided spectra."); + + DEBUGTRACE_ENTER; + } + + vd ifft(const vc &f) { return arma::ifft(f); } + vc fft(const vd &time) { return arma::fft(time); } +}; +#elif LASP_FFT_BACKEND == FFTW +#include + +/** + * @brief FFTW implementation + */ +class Fft_impl { + public: + us nfft; + fftw_plan forward_plan = nullptr; + fftw_plan reverse_plan = nullptr; + fftw_complex *frequencyDomain = nullptr; + d *timeDomain = nullptr; + + Fft_impl(const us nfft) : nfft(nfft) { + + timeDomain = (d *)fftw_malloc(sizeof(d) * nfft); + + frequencyDomain = + (fftw_complex *)fftw_malloc(sizeof(fftw_complex) * (nfft / 2 + 1)); + + forward_plan = + fftw_plan_dft_r2c_1d(nfft, timeDomain, frequencyDomain, FFTW_MEASURE); + + reverse_plan = + fftw_plan_dft_c2r_1d(nfft, frequencyDomain, timeDomain, FFTW_MEASURE); + if(!forward_plan || !reverse_plan || !timeDomain || !frequencyDomain) { + throw rte("Error allocating FFT"); + } + + }; + + ~Fft_impl() { + fftw_destroy_plan(forward_plan); + fftw_destroy_plan(reverse_plan); + fftw_free(frequencyDomain); + fftw_free(timeDomain); + } + + vc fft(const vd &time) { + if(time.n_rows != nfft) { + throw rte("Invalid size of input vector, should be equal to nfft"); + } + + vc res(nfft / 2 + 1); + memcpy(timeDomain, time.memptr(), sizeof(d) * nfft); + + fftw_execute(forward_plan); + + memcpy(reinterpret_cast(res.memptr()), frequencyDomain, + sizeof(c) * (nfft / 2 + 1)); + + return res; + } + vd ifft(const vc &f) { + DEBUGTRACE_ENTER; + if(f.n_rows != nfft/2+1) { + throw rte("Invalid size of input vector, should be equal to nfft/2+1"); + } + memcpy(frequencyDomain, + reinterpret_cast(const_cast(f.memptr())), + (nfft / 2 + 1) * sizeof(c)); + + fftw_execute(reverse_plan); + + vd res(nfft); + memcpy(res.memptr(), timeDomain, nfft * sizeof(d)); + + /* Scale by dividing by nfft. Checked with numpy implementation + * that this indeed needs to be done for FFTW. */ + res *= 1.0 / nfft; + + return res; + } +}; +#else +#error \ + "Cannot compile lasp_ffc.c, no FFT backend specified. Should either be FFTPack, or FFTW" +#endif + +Fft::Fft(const us nfft) { + DEBUGTRACE_ENTER; + if (nfft == 0) { + throw rte("Invalid nfft: 0"); + } + if (nfft >= LASP_MAX_NFFT) { + throw rte("Invalid nfft, should be smaller than: " + + std::to_string(LASP_MAX_NFFT)); + } + _impl = std::make_unique(nfft); +} +Fft::~Fft() { } + +us Fft::nfft() const { assert(_impl); return _impl->nfft; } + +vc Fft::fft(const vd &timedata) { assert(_impl); return _impl->fft(timedata); } + +cmat Fft::fft(const dmat &freqdata) { + DEBUGTRACE_ENTER; + assert(_impl); + cmat res(_impl->nfft/2+1, freqdata.n_cols); + /// * WARNING *. This was source of a serious bug. It is not possible to run + /// FFT's and IFFT's on the same _impl, as it overwrites the same memory. + /// Uncommenting the line below results in faulty results. + /// #pragma omp parallel for + for (us colno = 0; colno < freqdata.n_cols; colno++) { + res.col(colno) = _impl->fft(freqdata.col(colno)); + } + return res; +} + +vd Fft::ifft(const vc &freqdata) { + DEBUGTRACE_ENTER; + assert(_impl); + return _impl->ifft(freqdata); +} +dmat Fft::ifft(const cmat &freqdata) { + dmat res(_impl->nfft, freqdata.n_cols); + /// * WARNING *. This was source of a serious bug. It is not possible to run + /// FFT's and IFFT's on the same _impl, as it overwrites the same memory. + /// Uncommenting the line below results in faulty results. + /// #pragma omp parallel for + + for (us colno = 0; colno < freqdata.n_cols; colno++) { + res.col(colno) = _impl->ifft(freqdata.col(colno)); + } + return res; +} + +void Fft::load_fft_wisdom(const std::string& wisdom) { +#if LASP_FFT_BACKEND == Armadillo +#elif LASP_FFT_BACKEND == FFTW + if (wisdom.length() > 0) { + int rv = fftw_import_wisdom_from_string(wisdom.c_str()); + if (rv != 1) { + throw rte("Error loading FFTW wisdom"); + } + } +#endif +} + +std::string Fft::store_fft_wisdom() { +#if LASP_FFT_BACKEND == Armadillo + return ""; +#elif LASP_FFT_BACKEND == FFTW + // It is not possible to let FFTW directly return a C++ string. We have to + // put it in this container by copying in. Not a good solution if this has to + // happen often. + // Fortunately, this function is only called at the end of the program. + char* wis = fftw_export_wisdom_to_string(); + std::string res {wis}; + free(wis); + return res; +#endif +} diff --git a/src/lasp/dsp/lasp_fft.h b/src/lasp/dsp/lasp_fft.h new file mode 100644 index 0000000..9556dee --- /dev/null +++ b/src/lasp/dsp/lasp_fft.h @@ -0,0 +1,99 @@ +// lasp_fft.h +// +// Author: J.A. de Jong - ASCEE +// +// Description: FFT class +// Interface to the FFT library, multiple channel FFT's +////////////////////////////////////////////////////////////////////// +#pragma once +#include +#include "lasp_mathtypes.h" + +/** + * \addtogroup dsp + * @{ + */ + +class Fft_impl; + +/** + * @brief Perform forward FFT's on real time data. Computes single-sided spectra, + * equivalent to Numpy's rfft and irfft functions. But then faster as it can + * use a fast FFT backend, such as FFTW. + */ +class Fft { + std::unique_ptr _impl; + + public: + /** + * @brief Initialize FFT + * + * @param nfft The length of nfft + */ + Fft(const us nfft); + ~Fft(); + + Fft(const Fft&) = delete; + Fft& operator=(const Fft&) = delete; + + /** + * @brief Return nfft + * + * @return nfft NFFT (lenght of the DFT transform) + */ + us nfft() const; + + /** + * Compute the fft for a single channel of data. + * + * @param[in] timedata Input time data, should have size nfft + * @return Result complex vector + * */ + vc fft(const vd& timedata); + + /** + * Compute the fft of the data matrix, first axis is assumed to be + * the time axis. + * + * @param[in] timedata Input time data, should have size nfft. First axis + * is time, second axis is channel + * @returns Result complex array + */ + cmat fft(const dmat& timedata); + + /** + * Perform inverse fft on a single channel. + * + * @param[in] freqdata Frequency domain input data, to be iFft'th. Should + * have size nfft/2+1 + * @returns timedata: iFft't data, size nfft. + */ + vd ifft(const vc& freqdata); + + /** + * Perform inverse FFT + * + * @param freqdata Frequency domain data + * @return timedata Time domain result + */ + dmat ifft(const cmat& freqdata); + + /** + * @brief Load FFT wisdom from a wisdom string. Function does nothing if + * FFT backend is not FFTW + * + * @param wisdom Wisdom string content. + */ + static void load_fft_wisdom(const std::string& wisdom); + + /** + * @brief Return a string containing FFT wisdom storage. String is empty + * for backend != FFTW + * + * @return FFT wisdom string + */ + static std::string store_fft_wisdom(); + +}; + +/** @} */ diff --git a/src/lasp/dsp/lasp_filter.cpp b/src/lasp/dsp/lasp_filter.cpp new file mode 100644 index 0000000..a714238 --- /dev/null +++ b/src/lasp/dsp/lasp_filter.cpp @@ -0,0 +1,4 @@ +#include "lasp_filter.h" + + + diff --git a/src/lasp/dsp/lasp_filter.h b/src/lasp/dsp/lasp_filter.h new file mode 100644 index 0000000..8cf4a12 --- /dev/null +++ b/src/lasp/dsp/lasp_filter.h @@ -0,0 +1,33 @@ +#pragma once +#include +#include +#include "lasp_types.h" +#include "lasp_mathtypes.h" +/** + * @brief Filter used to pre-filter a double-precision floating point data + * stream. + */ +class Filter { +public: + /** + * @brief Filter input, and provides output in same array as input + * + * @param inout Vector of input / output samples. + */ + virtual void filter(vd &inout) = 0; + virtual ~Filter() = 0; + /** + * @brief Reset filter state to 0 (history was all-zero). + */ + virtual void reset() = 0; + + /** + * @brief Clone a filter, to generate a copy. + * + * @return Copy of filter with state set to zero. + */ + virtual std::unique_ptr clone() const = 0; +}; + + +inline Filter::~Filter() {} diff --git a/src/lasp/dsp/lasp_mathtypes.h b/src/lasp/dsp/lasp_mathtypes.h new file mode 100644 index 0000000..8d3102f --- /dev/null +++ b/src/lasp/dsp/lasp_mathtypes.h @@ -0,0 +1,52 @@ +#pragma once +#include +#include "lasp_types.h" +#include + +#if LASP_DOUBLE_PRECISION == 1 +#define c_real creal +#define c_imag cimag +#define d_abs fabs +#define c_abs cabs +#define c_conj conj +#define d_atan2 atan2 +#define d_acos acos +#define d_sqrt sqrt +#define c_exp cexp +#define d_exp exp +#define d_sin sin +#define d_cos cos +#define d_pow pow +#define d_log10 log10 +#define d_ln log +#define d_epsilon (DBL_EPSILON) + +#else // LASP_DOUBLE_PRECISION not defined +#define c_conj conjf +#define c_real crealf +#define c_imag cimagf +#define d_abs fabsf +#define c_abs cabsf +#define d_atan2 atan2f +#define d_acos acosf +#define d_sqrt sqrtf +#define c_exp cexpf +#define d_exp expf +#define d_sin sinf +#define d_cos cosf +#define d_pow powf +#define d_log10 log10f +#define d_ln logf +#define d_epsilon (FLT_EPSILON) + +#endif // LASP_DOUBLE_PRECISION + +using vd = arma::Col; +using vrd = arma::Row; +using vc = arma::Col; +using vrc = arma::Row; +using dmat = arma::Mat; +using cmat = arma::Mat; +using cube = arma::Cube; + +const d number_pi = arma::datum::pi; diff --git a/src/lasp/dsp/lasp_ppm.cpp b/src/lasp/dsp/lasp_ppm.cpp new file mode 100644 index 0000000..bbfa8da --- /dev/null +++ b/src/lasp/dsp/lasp_ppm.cpp @@ -0,0 +1,98 @@ +/* #define DEBUGTRACE_ENABLED */ +#include "lasp_ppm.h" +#include "debugtrace.hpp" +#include + +using std::cerr; +using std::endl; + +using Lck = std::scoped_lock; +using rte = std::runtime_error; + +PPMHandler::PPMHandler(StreamMgr &mgr, const d decay_dBps) + : ThreadedInDataHandler(mgr), _decay_dBps(decay_dBps) { + DEBUGTRACE_ENTER; + std::scoped_lock lck(_mtx); + start(); + } +bool PPMHandler::inCallback_threaded(const DaqData &d) { + /* DEBUGTRACE_ENTER; */ + std::scoped_lock lck(_mtx); + dmat data = d.toFloat(); + + const us nchannels = _cur_max.size(); + + vrd maxabs = arma::max(arma::abs(data)); + + arma::uvec clip_indices = arma::find(maxabs > clip_point); + arma::uvec clip(nchannels, arma::fill::zeros); + clip.elem(clip_indices).fill(1); + + arma::uvec update_max_idx = arma::find(maxabs > _cur_max); + arma::uvec update_max(nchannels, arma::fill::zeros); + update_max.elem(update_max_idx).fill(1); + + assert(_cur_max.size() == _clip_time.size()); + + for (us i = 0; i < nchannels; i++) { + if (clip(i)) { + /// Reset clip counter + _clip_time(i) = 0; + } else if (_clip_time(i) > clip_indication_time) { + /// Reset to 'unclipped' + _clip_time(i) = -1; + } else if (_clip_time(i) >= 0) { + /// Add a bit of clip time + _clip_time(i) += _dt; + } + + /* cerr << "maxabs(i)" << maxabs(i) << endl; */ + /* cerr << "curmax(i)" << _cur_max(i) << endl; */ + if (update_max(i)) { + _cur_max(i) = maxabs(i); + } else { + _cur_max(i) *= _alpha; + } + } + return true; +} + +std::tuple PPMHandler::getCurrentValue() const { + + /* DEBUGTRACE_ENTER; */ + std::scoped_lock lck(_mtx); + + arma::uvec clips(_clip_time.size(), arma::fill::zeros); + clips.elem(arma::find(_clip_time >= 0)).fill(1); + + return {20 * arma::log10(_cur_max + arma::datum::eps).as_col(), clips}; +} + +void PPMHandler::reset(const Daq *daq) { + + DEBUGTRACE_ENTER; + std::scoped_lock lck(_mtx); + + if (daq) { + + _cur_max = vrd(daq->neninchannels(), arma::fill::zeros); + + _clip_time = vd(daq->neninchannels(), arma::fill::value(-1)); + const d fs = daq->samplerate(); + DEBUGTRACE_PRINT(fs); + _dt = daq->framesPerBlock() / fs; + + _alpha = std::max(d_pow(10, -_dt * _decay_dBps / (20)), 0); + DEBUGTRACE_PRINT(_alpha); + + } else { + _cur_max.clear(); + _clip_time.clear(); + } +} + +PPMHandler::~PPMHandler() { + DEBUGTRACE_ENTER; + std::scoped_lock lck(_mtx); + stop(); +} diff --git a/src/lasp/dsp/lasp_ppm.h b/src/lasp/dsp/lasp_ppm.h new file mode 100644 index 0000000..cbc7776 --- /dev/null +++ b/src/lasp/dsp/lasp_ppm.h @@ -0,0 +1,88 @@ +// lasp_ppm.h +// +// Author: J.A. de Jong - ASCEE +// +// Description: Peak Programme Meter +#pragma once +#include +#include "lasp_filter.h" +#include "lasp_mathtypes.h" +#include "lasp_threadedindatahandler.h" + +/** + * \addtogroup dsp + * @{ + * + * \addtogroup rt + * @{ + */ + + +/** + * @brief Digital Peak Programme Meter (PPM). Let the latest maximum flow away + * with a certain amount of dB/s. If a new peak is found, it goes up again. + * Also detects clipping. + * */ +class PPMHandler: public ThreadedInDataHandler { + + /** + * @brief Assuming full scale of a signal is +/- 1.0. If a value is found + */ + static inline const d clip_point = 0.98; + + /** + * @brief How long it takes in [s] after a clip event has happened, that we + * are actually still in 'clip' mode. + */ + static inline const d clip_indication_time = 3.0; + + const d _decay_dBps; + + /** + * @brief Inverse of block sampling frequency [s]: (framesPerBlock/fs) + */ + d _dt; + + /** + * @brief 1st order low_pass value of levels, based on _decay_dBps; + */ + d _alpha; + + mutable std::mutex _mtx; + /** + * @brief Current maximum values + */ + vrd _cur_max; + + /** + * @brief How long ago the last clip has happened. Negative in case no clip + * has happened. + */ + vd _clip_time; + + public: + /** + * @brief Constructs Peak Programme Meter + * + * @param mgr Stream Mgr to operate on + * @param decay_dBps The level decay in units dB/s, after a peak has been + * hit. + */ + PPMHandler(StreamMgr& mgr,const d decay_dBps = 20.0); + ~PPMHandler(); + + /** + * @brief Get the current values of the PPM. Returns an array of levels in dB + * and a vector of True's. + * + * @return Current levels in [dB], clipping indication + */ + std::tuple getCurrentValue() const; + + bool inCallback_threaded(const DaqData& ) override final; + void reset(const Daq*) override final; + +}; + +/** @} */ +/** @} */ diff --git a/src/lasp/dsp/lasp_rtaps.cpp b/src/lasp/dsp/lasp_rtaps.cpp new file mode 100644 index 0000000..7cfea0c --- /dev/null +++ b/src/lasp/dsp/lasp_rtaps.cpp @@ -0,0 +1,65 @@ +#define DEBUGTRACE_ENABLED +#include "lasp_rtaps.h" +#include "debugtrace.hpp" +#include + +using std::cerr; +using std::endl; + +bool RtAps::inCallback_threaded(const DaqData &data) { + + /* DEBUGTRACE_ENTER; */ + + std::scoped_lock lck(_mtx); + dmat fltdata = data.toFloat(); + const us nchannels = fltdata.n_cols; + + if (_filterPrototype) { + + // Adjust number of filters, if necessary + if (nchannels > _freqWeightingFilter.size()) { + while (nchannels > _freqWeightingFilter.size()) { + _freqWeightingFilter.emplace_back(_filterPrototype->clone()); + } + + for (auto &filter : _freqWeightingFilter) { + filter->reset(); + } + } + + // Apply filtering +#pragma omp parallel for + for (us i = 0; i < nchannels; i++) { + vd col = fltdata.col(i); + _freqWeightingFilter.at(i)->filter(col); + fltdata.col(i) = col; + } + } // End of if(_filterPrototype) + + std::optional res = _ps.compute(fltdata); + + if (res.has_value()) { + /* DEBUGTRACE_PRINT("Data ready!"); */ + _latest_est = std::make_unique(std::move(res.value())); + } + + return true; +} +void RtAps::reset(const Daq *daq) { // Explicitly say + // to GCC that + // the argument is + // not used. + + DEBUGTRACE_ENTER; + std::scoped_lock lck(_mtx); + _ps.reset(); + _latest_est.reset(); +} + +std::unique_ptr RtAps::getCurrentValue() { + + /* DEBUGTRACE_ENTER; */ + std::scoped_lock lck(_mtx); + + return std::move(_latest_est); +} diff --git a/src/lasp/dsp/lasp_rtaps.h b/src/lasp/dsp/lasp_rtaps.h new file mode 100644 index 0000000..fb2b160 --- /dev/null +++ b/src/lasp/dsp/lasp_rtaps.h @@ -0,0 +1,70 @@ +// lasp_threadedaps.h +// +// Author: J.A. de Jong - ASCEE +// +// Description: Real Time Spectrum Viewer +#pragma once +#include "lasp_avpowerspectra.h" +#include "lasp_filter.h" +#include "lasp_mathtypes.h" +#include "lasp_threadedindatahandler.h" +#include +#include + +/** + * \addtogroup dsp + * @{ + * + * \addtogroup rt + * @{ + */ + +class RtAps : public ThreadedInDataHandler { + + std::mutex _mtx; + std::unique_ptr _filterPrototype; + std::vector> _freqWeightingFilter; + bool _data_ready = false; + std::unique_ptr _latest_est; + + AvPowerSpectra _ps; + + public: + /** + * @brief Initialize RtAps. + * + * @param mgr StreamMgr singleton reference + * @param freqWeightingFilter Optionally: the frequency weighting filter. + * Nullptr should be given for Z-weighting. + * @param For all other arguments, see constructor of AvPowerSpectra + */ + RtAps(StreamMgr &mgr, const Filter *freqWeightingFilter, const us nfft = 2048, + const Window::WindowType w = Window::WindowType::Hann, + const d overlap_percentage = 50., const d time_constant = -1) + : ThreadedInDataHandler(mgr), + _ps(nfft, w, overlap_percentage, time_constant) { + + std::scoped_lock lck(_mtx); + if (freqWeightingFilter != nullptr) { + _filterPrototype = freqWeightingFilter->clone(); + } + start(); + } + ~RtAps() { + std::scoped_lock lck(_mtx); + stop(); + } + + /** + * @brief Get the latest estimate of the power spectra + * + * @return Optionally, if available, the latest values + */ + std::unique_ptr getCurrentValue(); + + bool inCallback_threaded(const DaqData &) override final; + void reset(const Daq *) override final; +}; + +/** @} */ +/** @} */ diff --git a/src/lasp/dsp/lasp_siggen.cpp b/src/lasp/dsp/lasp_siggen.cpp new file mode 100644 index 0000000..aa747e7 --- /dev/null +++ b/src/lasp/dsp/lasp_siggen.cpp @@ -0,0 +1,52 @@ +/* #define DEBUGTRACE_ENABLED */ +#include "debugtrace.hpp" +#include "lasp_siggen.h" +#include "lasp_mathtypes.h" +#include +#include +using std::cerr; +using std::endl; + +inline d level_amp(d level_dB) { return pow(10, level_dB / 20); } + +using mutexlock = std::scoped_lock; + +vd Siggen::genSignal(const us nframes) { + + DEBUGTRACE_ENTER; + mutexlock lck(_mtx); + + DEBUGTRACE_PRINT(nframes); + vd signal(nframes, arma::fill::value(_dc_offset)); + + if (!_muted) { + vd signal_dynamic = _level_linear * genSignalUnscaled(nframes); + if (_filter) { + _filter->filter(signal_dynamic); + } + signal += signal_dynamic; + } + + return signal; +} +void Siggen::setFilter(std::shared_ptr filter) { + DEBUGTRACE_ENTER; + mutexlock lck(_mtx); + _filter = filter; +} +void Siggen::setDCOffset(const d offset) { + DEBUGTRACE_ENTER; + mutexlock lck(_mtx); + _dc_offset = offset; +} +void Siggen::setLevel(const d level, bool dB) { + DEBUGTRACE_ENTER; + mutexlock lck(_mtx); + _level_linear = dB ? level_amp(level) : level; +} +void Siggen::reset(const d newFs) { + DEBUGTRACE_ENTER; + mutexlock lck(_mtx); + _fs = newFs; + resetImpl(); +} diff --git a/src/lasp/dsp/lasp_siggen.h b/src/lasp/dsp/lasp_siggen.h new file mode 100644 index 0000000..969ade9 --- /dev/null +++ b/src/lasp/dsp/lasp_siggen.h @@ -0,0 +1,91 @@ +#pragma once +#include +#include "lasp_types.h" +#include "lasp_mathtypes.h" +#include "lasp_filter.h" + +class StreamMgr; +class DaqData; + +/** + * \addtogroup dsp + * @{ + */ +/** + * \defgroup siggen Signal generators + * @{ + */ + +/** + * @brief Signal generation abstract base class. Implementation is required for + * resetImpl(), genSignalUnscaled() and destructor. + */ +class Siggen { +private: + std::shared_ptr _filter; + d _dc_offset = 0, _level_linear = 1; + bool _muted = false; + +protected: + std::mutex _mtx; + d _fs = 0; + + virtual void resetImpl() = 0; + virtual vd genSignalUnscaled(const us nframes) = 0; + +public: + virtual ~Siggen() = default; + + /** + * @brief Set a post-filter on the signal. For example to EQ the signal, or + * otherwise to shape the spectrum. + * + * @param f The filter to install. + */ + void setFilter(std::shared_ptr f); + + /** + * @brief Set a linear DC offset value to the signal + * + * @param offset + */ + void setDCOffset(d offset); + + /** + * @brief Mute the signal. Passes through the DC offset. No lock is hold. If + * it just works one block later, than that is just the case. + * + * @param mute if tre + */ + void setMute(bool mute = true) { _muted = mute; } + + /** + * @brief Set the level of the signal generator + * + * @param level The new level. If dB == true, it is treated as a level, and + * pow(10, level/20) is installed as the linear gain. + * @param dB if false, level is treated as linear gain value. + */ + void setLevel(const d level, bool dB=true); + + /** + * @brief Reset the signal generator. Should be called whenever the output is + * based on a different sampling frequency. Note that derived classes from + * this class should call the base class! + * + * @param newFs New sampling frequency to use. + */ + void reset(const d newFs); + + /** + * @brief Called whenever the implementation needs to create new samples. + * + * @param nframes + * + * @return Array of samples with length nframes + */ + vd genSignal(const us nframes); +}; + +/** @} */ +/** @} */ diff --git a/src/lasp/dsp/lasp_siggen_impl.cpp b/src/lasp/dsp/lasp_siggen_impl.cpp new file mode 100644 index 0000000..870ce63 --- /dev/null +++ b/src/lasp/dsp/lasp_siggen_impl.cpp @@ -0,0 +1,258 @@ +// lasp_siggen_impl.cpp +// +// Author: J.A. de Jong -ASCEE +// +// Description: +// Signal generators implementation +////////////////////////////////////////////////////////////////////// +#define DEBUGTRACE_ENABLED +#include "debugtrace.hpp" +#include "lasp_siggen_impl.h" +#include "debugtrace.hpp" +#include "lasp_mathtypes.h" + +using rte = std::runtime_error; + +DEBUGTRACE_VARIABLES; + +/** The fixed number of Newton iterations t.b.d. for tuning the sweep start and + * stop frequency in logarithmic sweeps */ +#define NITER_NEWTON 20 + +Noise::Noise(){DEBUGTRACE_ENTER} + +vd Noise::genSignalUnscaled(us nframes) { + return arma::randn(nframes); +} +void Noise::resetImpl() {} + +Sine::Sine(const d freq) : omg(2 * arma::datum::pi * freq) { DEBUGTRACE_ENTER;} + +vd Sine::genSignalUnscaled(const us nframes) { + /* DEBUGTRACE_ENTER; */ + const d pi = arma::datum::pi; + vd phase_vec = + arma::linspace(phase, phase + omg * (nframes - 1) / _fs, nframes); + phase += omg * nframes / _fs; + while (phase > 2 * arma::datum::pi) { + phase -= 2 * pi; + } + return arma::sin(phase_vec); +} + +vd Periodic::genSignalUnscaled(const us nframes) { + + vd res(nframes); + if(_signal.size() == 0) { + throw rte("No signal defined while calling"); + } + for(us i=0;i= 0, "BUG"); */ + + phase += 2 * number_pi * Dt * fn; + } + /* This should be a very small number!! */ + /* dVARTRACE(15, phase); */ + } + } else if (flags & LogSweep) { + + DEBUGTRACE_PRINT("Log sweep"); + if (forward_sweep || backward_sweep) { + /* Forward or backward sweep */ + DEBUGTRACE_PRINT("Forward or backward sweep"); + d k1 = (fu / fl); + us K = (us)(Dt * fl * (k1 - 1) / (d_pow(k1, 1.0 / N) - 1)); + d k = k1; + + /* Iterate k to the right solution */ + d E; + for (us iter = 0; iter < 10; iter++) { + E = 1 + K / (Dt * fl) * (d_pow(k, 1.0 / N) - 1) - k; + d dEdk = K / (Dt * fl) * d_pow(k, 1.0 / N) / (N * k) - 1; + k -= E / dEdk; + } + + DEBUGTRACE_PRINT(K); + DEBUGTRACE_PRINT(k1); + DEBUGTRACE_PRINT(k); + DEBUGTRACE_PRINT(E); + + for (us n = 0; n < Ns; n++) { + _signal[n] = d_sin(phase); + d fn = fl * d_pow(k, ((d)n) / N); + phase += 2 * number_pi * Dt * fn; + } + } else { + + DEBUGTRACE_PRINT("Continuous sweep"); + + const us Nf = N / 2; + const us Nb = N - Nf; + const d k1 = (fu / fl); + const d phif1 = + 2 * number_pi * Dt * fl * (k1 - 1) / (d_pow(k1, 1.0 / Nf) - 1); + const us K = (us)(phif1 / (2 * number_pi) + + Dt * fu * (1 / k1 - 1) / (d_pow(1 / k1, 1.0 / Nb) - 1)); + + d E; + d k = k1; + + /* Newton iterations to converge k to the value such that the sweep is + * continuous */ + for (us iter = 0; iter < NITER_NEWTON; iter++) { + E = (k - 1) / (d_pow(k, 1.0 / Nf) - 1) + + (k - 1) / (1 - d_pow(k, -1.0 / Nb)) - K / Dt / fl; + DEBUGTRACE_PRINT(E); + + /* All parts of the derivative of above error E to k */ + d dEdk1 = 1 / (d_pow(k, 1.0 / Nf) - 1); + d dEdk2 = (1 / k - 1) / (d_pow(k, -1.0 / Nb) - 1); + d dEdk3 = -1 / (k * (d_pow(k, -1.0 / Nb) - 1)); + d dEdk4 = d_pow(k, -1.0 / Nb) * (1 / k - 1) / + (Nb * d_pow(d_pow(k, -1.0 / Nb) - 1, 2)); + d dEdk5 = -d_pow(k, 1.0 / Nf) * (k - 1) / + (Nf * k * d_pow(d_pow(k, 1.0 / Nf) - 1, 2)); + d dEdk = dEdk1 + dEdk2 + dEdk3 + dEdk4 + dEdk5; + + /* Iterate! */ + k -= E / dEdk; + + } + + DEBUGTRACE_PRINT(K); + DEBUGTRACE_PRINT(k1); + DEBUGTRACE_PRINT(k); + DEBUGTRACE_PRINT(E); + + for (us n = 0; n <= Ns; n++) { + /* iVARTRACE(17, n); */ + if (n < Ns) { + _signal[n] = d_sin(phase); + } + + d fn; + if (n <= Nf) { + fn = fl * d_pow(k, ((d)n) / Nf); + } else { + fn = fl * k * d_pow(1 / k, ((d)n - Nf) / Nb); + } + /* dbgassert(fn >= 0, "BUG"); */ + + phase += 2 * number_pi * Dt * fn; + while (phase > 2 * number_pi) + phase -= 2 * number_pi; + /* dVARTRACE(17, phase); */ + } + /* This should be a very small number!! */ + DEBUGTRACE_PRINT(phase); + } + } +} diff --git a/src/lasp/dsp/lasp_siggen_impl.h b/src/lasp/dsp/lasp_siggen_impl.h new file mode 100644 index 0000000..e2a2d86 --- /dev/null +++ b/src/lasp/dsp/lasp_siggen_impl.h @@ -0,0 +1,107 @@ +#pragma once +#include "lasp_siggen.h" +#include "lasp_types.h" +/** + * \addtogroup dsp + * @{ + */ + +/** + * \addtogroup siggen + * @{ + */ + +/** + * @brief Generate a random signal (noise) + */ +class Noise : public Siggen { + d level_linear; + virtual vd genSignalUnscaled(const us nframes) override; + void resetImpl() override; + public: + + /** + * @brief Constructs a noise generator. If no filter is used, the output will + * be white noise. By default, the output will be standard deviation = 1 + * noise, which clips the output for standard audio devices, so make sure the + * level is set properly. + */ + Noise(); + ~Noise() = default; + +}; + +/** + * @brief Generate a sine wave + */ +class Sine : public Siggen { + d phase = 0; + d omg; + protected: + + void resetImpl() override final { phase=0; } + virtual vd genSignalUnscaled(const us nframes) override final; + + public: + + /** + * @brief Create a sine wave generator + * + * @param freq_Hz The frequency in Hz + */ + Sine(const d freq_Hz); + ~Sine() = default; + void setFreq(const d newFreq) { omg = 2*arma::datum::pi*newFreq; } ; +}; + +/** + * @brief Base class for all periodic signals (that are exactly periodic based + * on the sampling frequency). Note that the sine wave generator is not exactly + * periodic as the frequency can be any floating point value. + */ +class Periodic: public Siggen { + protected: + vd _signal { 1, arma::fill::zeros}; + us _cur_pos = 0; + public: + + virtual vd genSignalUnscaled(const us nframes) override final; + ~Periodic() = default; + +}; + +/** + * @brief Sweep signal + */ +class Sweep : public Periodic { + d fl_, fu_, Ts, Tq; + us index; + us flags; + + void resetImpl() override; + + public: + + static constexpr int ForwardSweep = 1 << 0; + static constexpr int BackwardSweep = 1 << 1; + static constexpr int LinearSweep = 1 << 2; + static constexpr int LogSweep = 1 << 3; + + /** + * Create a sweep signal + * + * @param[in] fl: Lower frequency [Hz] + * @param[in] fu: Upper frequency [Hz] + * @param[in] Ts: Sweep time [s] + * @param[in] Tq: Quescent tail time [s]. Choose this value long enough to + * avoid temporal aliasing in case of measuring impulse responses. + * @param[in] sweep_flags: Sweep period [s] + */ + Sweep(const d fl, const d fu, const d Ts, const d Tq, + const us sweep_flags); + + ~Sweep() = default; + +}; +/** @} */ +/** @} */ diff --git a/src/lasp/dsp/lasp_slm.cpp b/src/lasp/dsp/lasp_slm.cpp new file mode 100644 index 0000000..853cca5 --- /dev/null +++ b/src/lasp/dsp/lasp_slm.cpp @@ -0,0 +1,209 @@ +#define DEBUGTRACE_ENABLED +#include "debugtrace.hpp" +#include "lasp_slm.h" +#include "lasp_thread.h" +#include +#include +#include +#include + +using std::cerr; +using std::endl; +using rte = std::runtime_error; +using std::unique_ptr; + +SLM::SLM(const d fs, const d Lref, const us downsampling_fac, const d tau, + std::unique_ptr pre_filter, + std::vector> bandpass) + : _pre_filter(std::move(pre_filter)), _bandpass(std::move(bandpass)), + _alpha(exp(-1 / (fs * tau))), + _sp_storage(_bandpass.size(), arma::fill::zeros), // Storage for + // components of + // single pole low pass + // filter + Lrefsq(Lref*Lref), // Reference level + downsampling_fac(downsampling_fac), + + // Initalize mean square + Pm(_bandpass.size(), arma::fill::zeros), + + // Initalize max + Pmax(_bandpass.size(), arma::fill::zeros), + + // Initalize peak + Ppeak(_bandpass.size(), arma::fill::zeros) + +{ + DEBUGTRACE_ENTER; + + // Make sure thread pool is running + getPool(); + + if (Lref <= 0) { + throw rte("Invalid reference level"); + } + if (tau <= 0) { + throw rte("Invalid time constant for Single pole lowpass filter"); + } + if (fs <= 0) { + throw rte("Invalid sampling frequency"); + } +} +SLM::~SLM() {} + +/** + * @brief Create set bandpass filters from filter coefficients + * + * @param coefs + * + * @return + */ +std::vector> createBandPass(const dmat &coefs) { + DEBUGTRACE_ENTER; + std::vector> bf; + for (us colno = 0; colno < coefs.n_cols; colno++) { + bf.emplace_back(std::make_unique(coefs.col(colno))); + } + return bf; +} +us SLM::suggestedDownSamplingFac(const d fs,const d tau) { + if(tau<0) throw rte("Invalid time weighting time constant"); + if(fs<=0) throw rte("Invalid sampling frequency"); + // A reasonable 'framerate' for the sound level meter, based on the + // filtering time constant. + if (tau > 0) { + d fs_slm = 10 / tau; + if(fs_slm < 30) { + fs_slm = 30; + } + return std::max((us) 1, static_cast(fs / fs_slm)); + } else { + return 1; + } +} + +SLM SLM::fromBiquads(const d fs, const d Lref, const us downsampling_fac, + const d tau, const vd &pre_filter_coefs, + const dmat &bandpass_coefs) { + DEBUGTRACE_ENTER; + + return SLM(fs, Lref, downsampling_fac, tau, + std::make_unique(pre_filter_coefs), + createBandPass(bandpass_coefs)); +} +SLM SLM::fromBiquads(const d fs, const d Lref, const us downsampling_fac, + const d tau, const dmat &bandpass_coefs) { + + DEBUGTRACE_ENTER; + + return SLM(fs, Lref, downsampling_fac, tau, + nullptr, // Pre-filter + createBandPass(bandpass_coefs) // Bandpass coefficients + ); +} + +vd SLM::run_single(vd work,const us i) { + + // Filter input in-place + _bandpass[i]->filter(work); + + /* cerr << "Filter done" << endl; */ + + // Square input --> Signal powers + /* work.transform([](d j) { return j * j; }); */ + work %= work; + + // Compute peak level, that is before single-pole low pass filter + Ppeak(i) = std::max(Ppeak(i), arma::max(work)); + + // Create copy of N, as we run this in multiple threads. + us N_local = N; + DEBUGTRACE_PRINT(N); + + // Obtain storage of single_pole low pass filter + d cur_storage = _sp_storage(i); + + for (us j = 0; j < work.n_rows; j++) { + // Update mean square of signal, work is here still signal power + Pm(i) = (Pm(i) * static_cast(N_local) + work(j)) / + (static_cast(N_local) + 1); + + N_local++; + + cur_storage = _alpha * cur_storage + (1 - _alpha) * work(j); + + // Now work is single-pole lowpassed signal power + work(j) = cur_storage; + } + + // And update storage of low-pass filter + _sp_storage(i) = cur_storage; + + Pmax(i) = std::max(Pmax(i), arma::max(work)); + + // Convert to levels in dB + work = 10*arma::log10((work+arma::datum::eps)/Lrefsq); + + return work; +} + +dmat SLM::run(const vd &input_orig) { + + DEBUGTRACE_ENTER; + vd input = input_orig; + + // _pre_filter filters in-place + if (_pre_filter) { + _pre_filter->filter(input); + } + + // Fan out over multiple threads, as it is typically a heavy load + dmat res(input.n_rows, _bandpass.size()); + + // Perform operations in-place. + +#pragma omp parallel for + for (us i = 0; i < _bandpass.size(); i++) { + res.col(i) = run_single(input, i); + /* DEBUGTRACE_PRINT(_bandpass.size()); */ + /* DEBUGTRACE_PRINT(res.n_cols); */ + /* DEBUGTRACE_PRINT(res.n_rows); */ + /* DEBUGTRACE_PRINT(futs.size()); */ + + // Update the total number of samples harvested so far. NOTE: *This should be + // done AFTER the threads are done!!!* + } + N += input.n_rows; + + // Downsample, if applicable + if (downsampling_fac > 1) { + dmat res_ds; + us rowno = 0; + while (cur_offset < res.n_rows) { + res_ds.insert_rows(rowno, res.row(cur_offset)); + rowno++; + + cur_offset += downsampling_fac; + } + cur_offset -= res.n_rows; + // Instead, return a downsampled version + return res_ds; + } + + return res; +} +void SLM::reset() { + Pm.zeros(); + Pmax.zeros(); + Ppeak.zeros(); + for (auto &f : _bandpass) { + f.reset(); + } + if (_pre_filter) { + _pre_filter->reset(); + } + _sp_storage.zeros(); + cur_offset = 0; + + N = 0; +} diff --git a/src/lasp/dsp/lasp_slm.h b/src/lasp/dsp/lasp_slm.h new file mode 100644 index 0000000..f4afc0e --- /dev/null +++ b/src/lasp/dsp/lasp_slm.h @@ -0,0 +1,170 @@ +#pragma once +#include "lasp_biquadbank.h" +#include "lasp_filter.h" +#include +#include + +/** + * \ingroup dsp + * @{ + */ + +/** + * @brief Sound Level Meter implementation that gives a result for each + * channel. A channel is the result of a filtered signal + */ +class SLM { + /** + * @brief A, C or Z weighting, depending on the pre-filter installed. + */ + std::unique_ptr _pre_filter; + + /** + * @brief Bandpass filters for each channel + */ + std::vector> _bandpass; + /** + * @brief Storage for the single-pole low-pass filter coefficient based on + * the Fast / Slow time constant. < 0 means the filter is disabled. + */ + d _alpha = -1; + vd _sp_storage; + + d Lrefsq; /// Square of reference value for computing decibels + us downsampling_fac; /// Every x'th sample is returned. + us cur_offset = 0; /// Storage for offset point in input arrays + /// +public: + /** + * @brief Public storage for the mean of the square of the signal. + */ + vd Pm; + /** + * @brief Public storage for the maximum signal power, after single pole + * low-pass filter. + */ + vd Pmax; /// Storage for maximum computed signal power so far. + /** + * @brief Public storage for the peak signal power, before single pole + * low-pass filter. + */ + vd Ppeak; + + us N = 0; /// Counter for the number of time samples counted that came + /// in; + + /** + * @brief Initialize a Sound Level Meter + * + * @param fs Sampling frequency [Hz] + * @param Lref Level reference, used to scale to proper decibel units (dB + * SPL / dBV, etc) + * @param downsampling_fac Every 1/downsampling_fac value is returned from + * compute() + * @param tau Time consant of level meter + * @param pre_filter The pre-filter (Typically an A/C frequency weighting + * filter) + * @param bandpass The parallel set of bandpass filters. + */ + SLM(const d fs, const d Lref, const us downsampling_fac, const d tau, + std::unique_ptr pre_filter, + std::vector> bandpass); + + /** + * @brief Convenience function to create a Sound Level meter from Biquad + * filters only. + * + * @param fs Sampling frequency [Hz] + * @param Lref Level reference, used to scale to proper decibel units (dB + * SPL / dBV, etc) + * @param downsampling_fac Every 1/downsampling_fac value is returned from + * compute() + * @param tau Time consant of level meter + * @param pre_filter_coefs Biquad filter coefficients for pre-filter + * @param bandpass_coefs Biquad filter coeffiecients for bandpass filter + * + * @return Sound Level Meter object + */ + static SLM fromBiquads(const d fs, const d Lref, const us downsampling_fac, + const d tau, const vd &pre_filter_coefs, + const dmat &bandpass_coefs); + /** + * @brief Convenience function to create a Sound Level meter from Biquad + * filters only. No pre-filter, only bandpass. + * + * @param fs Sampling frequency [Hz] + * @param Lref Level reference, used to scale to proper decibel units (dB + * SPL / dBV, etc) + * @param downsampling_fac Every 1/downsampling_fac value is returned from + * compute() + * @param tau Time consant of level meter + * @param bandpass_coefs Biquad filter coefficients for bandpass filter. First axis isis the coefficient index, second axis is the filter index. + + * + * @return Sound Level Meter object + */ + static SLM fromBiquads(const d fs, const d Lref, const us downsampling_fac, + const d tau, const dmat &bandpass_coefs); + + ~SLM(); + + /** + * @brief Reset state related to samples acquired. All filters reset to zero. + * Start again from no history. + */ + void reset(); + + SLM(const SLM &o) = delete; + SLM &operator=(const SLM &o) = delete; + SLM(SLM &&o) = default; + + /** + * @brief Run the sound level meter on given input data. Return downsampled + * level data for each filterbank channel. + * + * @param input Raw input data + * + * @return Filtered level data for each filtered channel. + */ + dmat run(const vd &input); + + /** + * @brief Calculates peak levels measured for each filter channel. The peak + * level is just the highest instantaneous measured power value. + * + * @return vector of peak level values + */ + vd Lpeak() const { return 10 * arma::log10(Ppeak / Lrefsq); }; + /** + * @brief Calculates equivalent (time-averaged) levels measured for each + * filter channel + * + * @return vector of equivalent level values + */ + vd Leq() const { return 10 * arma::log10(Pm / Lrefsq); }; + /** + * @brief Calculates max levels measured for each filter channel. The max + * value is the maximum time-filtered (Fast / Slow) power level. + * + * @return vector of max level values + */ + vd Lmax() const { return 10 * arma::log10(Pmax / Lrefsq); }; + + /** + * @brief Comput a 'suggested' downsampling factor, i.e. a lower frame rate + * at which sound level meter values are returned from the computation. This + * is possible since the signal power is low-pas filtered with a single pole + * low pass filter. It can remove computational burden, especially for + * plotting, to have a value > 10. + * + * @param fs Sampling frequency of signal [Hz] + * @param tw Time weighting of SLM low pass filter + * + * @return Suggested downsampling factor, no unit. [-] + */ + static us suggestedDownSamplingFac(const d fs,const d tw); + +private: + vd run_single(vd input, const us filter_no); +}; +/** @} */ diff --git a/src/lasp/dsp/lasp_thread.cpp b/src/lasp/dsp/lasp_thread.cpp new file mode 100644 index 0000000..76bc400 --- /dev/null +++ b/src/lasp/dsp/lasp_thread.cpp @@ -0,0 +1,25 @@ +/* #define DEBUGTRACE_ENABLED */ +#include "lasp_thread.h" +#include "BS_thread_pool.hpp" +#include "debugtrace.hpp" +#include + +/** + * @brief It seems to work much better in cooperation with Pybind11 when this + * singleton is implemented with a unique_ptr. + */ +std::unique_ptr _static_storage_threadpool; + +void destroyThreadPool() { + DEBUGTRACE_ENTER; + _static_storage_threadpool = nullptr; +} + +BS::thread_pool &getPool() { + /* DEBUGTRACE_ENTER; */ + if (!_static_storage_threadpool) { + DEBUGTRACE_PRINT("Creating new thread pool"); + _static_storage_threadpool = std::make_unique(); + } + return *_static_storage_threadpool; +} diff --git a/src/lasp/dsp/lasp_thread.h b/src/lasp/dsp/lasp_thread.h new file mode 100644 index 0000000..8a6c0ca --- /dev/null +++ b/src/lasp/dsp/lasp_thread.h @@ -0,0 +1,19 @@ +#pragma once +#include "BS_thread_pool.hpp" + +/** + * @brief Return reference to global (singleton) thread pool. The threadpool is + * created using the default argument, which results in exactly + * hardware_concurrency() amount of threads. + * + * @return Thread pool ref. + */ +BS::thread_pool& getPool(); + + +/** + * @brief The global thread pool is stored in a unique_ptr, so in normal C++ + * code the thread pool is deleted at the end of main(). However this does not + * hold when LASP code is run + */ +void destroyThreadPool(); diff --git a/src/lasp/dsp/lasp_threadedindatahandler.cpp b/src/lasp/dsp/lasp_threadedindatahandler.cpp new file mode 100644 index 0000000..8d3682c --- /dev/null +++ b/src/lasp/dsp/lasp_threadedindatahandler.cpp @@ -0,0 +1,65 @@ +/* #define DEBUGTRACE_ENABLED */ +#include "lasp_threadedindatahandler.h" +#include "debugtrace.hpp" +#include "lasp_thread.h" +#include +#include + +using namespace std::literals::chrono_literals; + +ThreadedInDataHandler::ThreadedInDataHandler(StreamMgr &mgr) + : InDataHandler(mgr) { + + DEBUGTRACE_ENTER; + + // Initialize thread pool, if not already done + getPool(); +} + +bool ThreadedInDataHandler::inCallback(const DaqData &daqdata) { + /* DEBUGTRACE_ENTER; */ + + if (!_lastCallbackResult) { + return false; + } + dataqueue.push(daqdata); + + auto &pool = getPool(); + + if (!_thread_running && (!_stopThread) && _lastCallbackResult) { + pool.push_task(&ThreadedInDataHandler::threadFcn, this); + } + + return _lastCallbackResult; +} + +ThreadedInDataHandler::~ThreadedInDataHandler() { + + DEBUGTRACE_ENTER; + _stopThread = true; + + // Then wait in steps for the thread to stop running. + while (_thread_running) { + std::this_thread::sleep_for(10us); + } +} + +void ThreadedInDataHandler::threadFcn() { + + /* DEBUGTRACE_ENTER; */ + _thread_running = true; + + DaqData d{}; + + if (dataqueue.pop(d) && !_stopThread) { + + // Call inCallback_threaded + if (inCallback_threaded(d) == false) { + _lastCallbackResult = false; + _thread_running = false; + return; + } + } + _lastCallbackResult = true; + _thread_running = false; +} diff --git a/src/lasp/dsp/lasp_threadedindatahandler.h b/src/lasp/dsp/lasp_threadedindatahandler.h new file mode 100644 index 0000000..0161d11 --- /dev/null +++ b/src/lasp/dsp/lasp_threadedindatahandler.h @@ -0,0 +1,67 @@ +#pragma once +#include +#include "lasp_streammgr.h" + +const us RINGBUFFER_SIZE = 1024; + + +/** + * \addtogroup dsp + * @{ + * + * \defgroup rt Real time signal handlers + * @{ + */ + + +/** + * @brief Threaded in data handler. Buffers inCallback data and calls a + * callback with the same signature on a different thread. + */ +class ThreadedInDataHandler: public InDataHandler { + /** + * @brief The queue used to push elements to the handling thread. + */ + boost::lockfree::spsc_queue> + dataqueue; + + std::atomic _thread_running{false}; + std::atomic _stopThread{false}; + std::atomic _lastCallbackResult{true}; + + void threadFcn(); + + public: + /** + * @brief Initialize a ThreadedInDataHandler + * + * @param mgr StreamMgr singleton reference + */ + ThreadedInDataHandler(StreamMgr& mgr); + ~ThreadedInDataHandler(); + + /** + * @brief Pushes a copy of the daqdata to the thread queue and returns + * + * @param daqdata the daq info to push + * + * @return true, to continue with sampling. + */ + virtual bool inCallback(const DaqData &daqdata) override final; + + /** + * @brief This function should be overridden with an actual implementation, + * of what should happen on a different thread. + * + * @param DaqData Input daq data + * + * @return true on succes. False when an error occured. + */ + virtual bool inCallback_threaded(const DaqData&) = 0; + +}; + + +/** @} */ +/** @} */ diff --git a/src/lasp/dsp/lasp_timebuffer.cpp b/src/lasp/dsp/lasp_timebuffer.cpp new file mode 100644 index 0000000..ffcf29d --- /dev/null +++ b/src/lasp/dsp/lasp_timebuffer.cpp @@ -0,0 +1,89 @@ +/* #define DEBUGTRACE_ENABLED */ +#include "debugtrace.hpp" +#include "lasp_timebuffer.h" +#include +#include +#include +#include +#include +#include + +using rte = std::runtime_error; + +class TimeBufferImp { + /** + * @brief Storage in a double-ended queue of armadillo row vectors. + */ + std::deque _storage; + +public: + void reset() { + DEBUGTRACE_ENTER; + _storage.clear(); + } + void push(const dmat &mat) { + DEBUGTRACE_ENTER; +#if LASP_DEBUG==1 + if(!_storage.empty()) { + if(mat.n_cols != _storage.front().n_cols) { + throw rte("Invalid number of channels in mat"); + } + } +#endif + for (us i = 0; i < mat.n_rows; i++) { + _storage.push_back(mat.row(i)); + } + } + + std::optional pop(const us nsamples, us keep) { + + DEBUGTRACE_ENTER; + + if (keep > nsamples) + throw rte("keep should be <= nsamples"); + + if (nsamples <= n_frames()) { + + assert(!_storage.empty()); + + dmat res(nsamples, _storage.front().n_cols); + + for (us i = 0; i < nsamples; i++) { + + if (i + keep >= nsamples) { + // Suppose keep == 0, then we never arrive here + // Suppose keep == 1, then storage[0] is copyied over. + // Suppose keep == 2, then storage[0[ and storage[1] is copyied over. + // Etc. + res.row(i) = _storage[i + keep - nsamples]; + } else { + + // Just pop elements and copy over + res.row(i) = _storage.front(); + _storage.pop_front(); + } + } + return res; + } + + // If nsamples is too much for what we have, we just return nothing. + return std::nullopt; + } + + /** + * @brief Counts the number of available frames in the queue + * + * @return + */ + us n_frames() const { return _storage.size(); } +}; + +TimeBuffer::TimeBuffer() : _imp(std::make_unique()) {} +std::optional TimeBuffer::pop(const us n_rows,const us keep) { + return _imp->pop(n_rows, keep); +} +TimeBuffer::~TimeBuffer(){} +void TimeBuffer::reset() { + _imp->reset(); +} +void TimeBuffer::push(const dmat &dat) { _imp->push(dat); } diff --git a/src/lasp/dsp/lasp_timebuffer.h b/src/lasp/dsp/lasp_timebuffer.h new file mode 100644 index 0000000..baa4f07 --- /dev/null +++ b/src/lasp/dsp/lasp_timebuffer.h @@ -0,0 +1,40 @@ +#pragma once +#include "lasp_mathtypes.h" +#include +#include + +class TimeBufferImp; +/** + * @brief Implementation of a buffer of time samples, where + */ +class TimeBuffer { + std::unique_ptr _imp; + +public: + TimeBuffer(); + ~TimeBuffer(); + /** + * @brief Put samples in the buffer. Number of channels should match other + * frames, otherwise things go wrong. + * + * @param mat Samples to push, axes should be as mat(frame, channel). + */ + void push(const dmat &mat); + + /** + * @brief Reset (empties) the time buffer. + */ + void reset(); + + /** + * @brief Try top pop frames from the buffer. + * + * @param nframes The number of rows + * @param keep The number of frames to copy, but also to keep in the buffer + * (usage: overlap) + * + * @return An optional container, containing a matrix of time samples for + * each channel + */ + std::optional pop(const us nframes, const us keep = 0); +}; diff --git a/lasp/c/lasp_types.h b/src/lasp/dsp/lasp_types.h similarity index 64% rename from lasp/c/lasp_types.h rename to src/lasp/dsp/lasp_types.h index 4e58c6a..692a9f5 100644 --- a/lasp/c/lasp_types.h +++ b/src/lasp/dsp/lasp_types.h @@ -7,8 +7,6 @@ // needed. ////////////////////////////////////////////////////////////////////// #pragma once -#ifndef LASP_TYPES_H -#define LASP_TYPES_H #include "lasp_config.h" #include @@ -26,39 +24,30 @@ #include // true, false #include #endif + typedef size_t us; /* Size type I always use */ // To change the whole code to 32-bit floating points, change this to // float. #if LASP_FLOAT_SIZE == 32 -typedef float d; /* Shortcut for double */ +typedef float d; /* Shortcut for floating point - single precision */ #elif LASP_FLOAT_SIZE == 64 -typedef double d; /* Shortcut for double */ +typedef double d; /* Shortcut for floating point - double precision */ #else #error LASP_FLOAT_SIZE should be either 32 or 64 #endif - -#include #ifdef __cplusplus -typedef std::complex c; + #include + typedef std::complex c; #else -#if LASP_FLOAT_SIZE == 32 -typedef float complex c; -#else -typedef double complex c; + #include + #if LASP_FLOAT_SIZE == 32 + typedef float complex c; + #else + typedef double complex c; #endif + #endif -/// I need these numbers so often, that they can be in the global -/// namespace. -#define LASP_SUCCESS 0 -#define LASP_INTERRUPTED (-3) -#define LASP_MALLOC_FAILED (-1) -#define LASP_FAILURE (-2) - - -#endif // LASP_TYPES_H -////////////////////////////////////////////////////////////////////// - diff --git a/src/lasp/dsp/lasp_window.cpp b/src/lasp/dsp/lasp_window.cpp new file mode 100644 index 0000000..9dedb95 --- /dev/null +++ b/src/lasp/dsp/lasp_window.cpp @@ -0,0 +1,63 @@ +// lasp_window.cpp +// +// Author: J.A. de Jong - ASCEE +// +/* #define DEBUGTRACE_ENABLED */ +#include "debugtrace.hpp" +#include "lasp_window.h" + +using rte = std::runtime_error; +using std::cerr; +using std::endl; + +// Safe some typing. Linspace form 0 up to (and NOT including N). +#define lin0N arma::linspace(0, N - 1, N) + +vd Window::hann(const us N) { + return arma::pow(arma::sin((arma::datum::pi/N) * lin0N), 2); +} +vd Window::hamming(const us N) { + d alpha = 25.0 / 46.0; + return alpha - (1 - alpha) * arma::cos(2 * number_pi * lin0N / N); +} +vd Window::blackman(const us N) { + d a0 = 7938. / 18608.; + d a1 = 9240. / 18608.; + d a2 = 1430. / 18608.; + return a0 - a1 * d_cos((2 * number_pi/N) * lin0N) + + a2 * d_cos((4 * number_pi / N)* lin0N ); +} + +vd Window::rectangular(const us N) { return arma::ones(N); } + +vd Window::bartlett(const us N) { + return 1 - arma::abs(2 * (lin0N - (N - 1) / 2.) / N); +} +vd Window::create(const WindowType w, const us N) { + + switch (w) { + case WindowType::Hann: { + return hann(N); + break; + } + case WindowType::Hamming: { + return hamming(N); + break; + } + case WindowType::Rectangular: { + return rectangular(N); + break; + } + case WindowType::Bartlett: { + return bartlett(N); + break; + } + case WindowType::Blackman: { + return blackman(N); + break; + } + default: + abort(); + break; + } +} diff --git a/src/lasp/dsp/lasp_window.h b/src/lasp/dsp/lasp_window.h new file mode 100644 index 0000000..81f043e --- /dev/null +++ b/src/lasp/dsp/lasp_window.h @@ -0,0 +1,105 @@ +#pragma once +#include "lasp_mathtypes.h" + +/** + * @brief Window (aka taper) functions of a certain type + */ +class Window { + + public: + enum class WindowType { + Hann = 0, + Hamming = 1, + Rectangular = 2, + Bartlett = 3, + Blackman = 4, + + }; + /** + * @brief Convert a window type enum to its equivalent text. + * + * @param wt The window type to convert + * + * @return Text string + */ + static std::string toText(const WindowType wt) { + switch(wt) { + case(WindowType::Hann): { + return "Hann"; + } + break; + case(WindowType::Hamming): { + return "Hamming"; + } + break; + case(WindowType::Rectangular): { + return "Rectangular"; + } + break; + case(WindowType::Bartlett): { + return "Bartlett"; + } + break; + case(WindowType::Blackman): { + return "Blackman"; + } + break; + } + throw std::runtime_error("Not implemenented window type"); + } + + /** + * @brief Dispatcher: create a window based on enum type and len + * + * @param w Window type + * @param len Length of the window (typically, integer power of two). + * + * @return Window vector of values + */ + static vd create(const WindowType w,const us len); + + /** + * @brief Hann window + * + * @param len Length of the window (typically, integer power of two). + * + * @return vector of values + */ + static vd hann(const us len); + + /** + * @brief Hamming window + * + * @param len Length of the window (typically, integer power of two). + * + * @return vector of values + */ + static vd hamming(const us len); + /** + * @brief Rectangular (boxcar) window. + * + * @param len Length of the window (typically, integer power of two). + * + * @return vector of values + */ + static vd rectangular(const us len); + + /** + * @brief Bartlett window. + * + * @param len Length of the window (typically, integer power of two). + * + * @return vector of values + */ + static vd bartlett(const us len); + + /** + * @brief Blackman window. + * + * @param len Length of the window (typically, integer power of two). + * + * @return vector of values + */ + static vd blackman(const us len); + +}; diff --git a/lasp/filter/__init__.py b/src/lasp/filter/__init__.py similarity index 100% rename from lasp/filter/__init__.py rename to src/lasp/filter/__init__.py diff --git a/lasp/filter/biquad.py b/src/lasp/filter/biquad.py similarity index 100% rename from lasp/filter/biquad.py rename to src/lasp/filter/biquad.py diff --git a/lasp/filter/colorednoise.py b/src/lasp/filter/colorednoise.py similarity index 100% rename from lasp/filter/colorednoise.py rename to src/lasp/filter/colorednoise.py diff --git a/lasp/filter/decimation_fir.py b/src/lasp/filter/decimation_fir.py similarity index 100% rename from lasp/filter/decimation_fir.py rename to src/lasp/filter/decimation_fir.py diff --git a/lasp/filter/filterbank_design.py b/src/lasp/filter/filterbank_design.py similarity index 81% rename from lasp/filter/filterbank_design.py rename to src/lasp/filter/filterbank_design.py index efdf720..7773917 100644 --- a/lasp/filter/filterbank_design.py +++ b/src/lasp/filter/filterbank_design.py @@ -10,12 +10,14 @@ Resulting filters are supposed to be standard compliant. See test/octave_fir_test.py for a testing """ -from .fir_design import bandpass_fir_design, freqResponse as firFreqResponse -import numpy as np +import warnings +import numpy as np # For designing second-order sections from scipy.signal import butter +from .fir_design import bandpass_fir_design +from .fir_design import freqResponse as firFreqResponse __all__ = ['OctaveBankDesigner', 'ThirdOctaveBankDesigner'] @@ -34,7 +36,7 @@ class FilterBankDesigner: self.fs = fs # Constant G, according to standard - self.G = 10**(3/10) + self.G = 10**(3 / 10) # Reference frequency for all filter banks self.fr = 1000. @@ -61,11 +63,13 @@ class FilterBankDesigner: # Interpolate limites to frequency array as given llim_full = np.interp(freq, freqlim, llim, left=-np.inf, right=-np.inf) - ulim_full = np.interp(freq, freqlim, ulim, - left=ulim[0], right=ulim[-1]) + ulim_full = np.interp(freq, + freqlim, + ulim, + left=ulim[0], + right=ulim[-1]) - return bool(np.all(llim_full <= h_dB) and - np.all(ulim_full >= h_dB)) + return bool(np.all(llim_full <= h_dB) and np.all(ulim_full >= h_dB)) def band_limits(self, x, filter_class): raise NotImplementedError() @@ -81,7 +85,8 @@ class FilterBankDesigner: if self.nominal_txt(x) == nom_txt: return x raise ValueError( - f'Could not find a nominal frequency corresponding to {nom_txt}. Hint: use \'5k\' instead of \'5000\'.') + f'Could not find a nominal frequency corresponding to {nom_txt}. Hint: use \'5k\' instead of \'5000\'.' + ) def sanitize_input(self, input_): if isinstance(input_, int): @@ -94,12 +99,11 @@ class FilterBankDesigner: # This is the "code" to create an array xl = self.sanitize_input(input_[0]) xu = self.sanitize_input(input_[2]) - return np.asarray(list(range(xl, xu+1))) + return np.asarray(list(range(xl, xu + 1))) else: x = [self.sanitize_input(xi) for xi in input_] return np.asarray(x) - def getxs(self, nom_txt_start, nom_txt_end): """Returns a list of all filter designators, for given start end end nominal frequencies. @@ -113,7 +117,8 @@ class FilterBankDesigner: """ xstart = self.nominal_txt_tox(nom_txt_start) xend = self.nominal_txt_tox(nom_txt_end) - return list(range(xstart, xend+1)) + return list(range(xstart, xend + 1)) + def fm(self, x): """Returns the exact midband frequency of the bandpass filter. @@ -123,7 +128,7 @@ class FilterBankDesigner: x = self.sanitize_input(x) # Exact midband frequency - return self.G**(x/self.b)*self.fr + return self.G**(x / self.b) * self.fr def fl(self, x): """Returns the exact cut-on frequency of the bandpass filter. @@ -132,7 +137,7 @@ class FilterBankDesigner: x: Midband designator """ x = self.sanitize_input(x) - return self.fm(x)*self.G**(-1/(2*self.b)) + return self.fm(x) * self.G**(-1 / (2 * self.b)) def fu(self, x): """Returns the exact cut-off frequency of the bandpass filter. @@ -141,7 +146,7 @@ class FilterBankDesigner: x: Midband designator """ x = self.sanitize_input(x) - return self.fm(x)*self.G**(1/(2*self.b)) + return self.fm(x) * self.G**(1 / (2 * self.b)) def createFirFilter(self, x): """Create a FIR filter for band designator b and sampling frequency fs. @@ -155,8 +160,8 @@ class FilterBankDesigner: # For designing the filter, the lower and upper frequencies need to be # slightly adjusted to fall within the limits for a class 1 filter. - fl = self.fl(x)*self.firFac_l(x) - fu = self.fu(x)*self.firFac_u(x) + fl = self.fl(x) * self.firFac_l(x) + fu = self.fu(x) * self.firFac_u(x) return bandpass_fir_design(self.firFilterLength, fd, fl, fu) @@ -171,15 +176,15 @@ class FilterBankDesigner: SOS_ORDER = 5 fs = self.fs - fl = self.fl(x)*self.sosFac_l(x) - fu = self.fu(x)*self.sosFac_u(x) + fl = self.fl(x) * self.sosFac_l(x) + fu = self.fu(x) * self.sosFac_u(x) - fnyq = fs/2 + fnyq = fs / 2 # Normalized upper and lower frequencies of the bandpass - fl_n = fl/fnyq + fl_n = fl / fnyq x = self.sanitize_input(x) - fu_n = fu/fnyq + fu_n = fu / fnyq return butter(SOS_ORDER, [fl_n, fu_n], output='sos', btype='band') @@ -200,9 +205,11 @@ class FilterBankDesigner: return firFreqResponse(fd, freq, fir) - - def getNarrowBandFromOctaveBand(self, xl, xu, - levels_in_bands, npoints=500, + def getNarrowBandFromOctaveBand(self, + xl, + xu, + levels_in_bands, + npoints=500, method='flat', scale='lin'): """Create a narrow band spectrum based on a spectrum in (fractional) @@ -256,7 +263,7 @@ class FilterBankDesigner: power_cur = 10**(levels_in_bands[i] / 10) power_narrow = power_cur / indices_cur[0].size - level_narrow = 10*np.log10(power_narrow) + level_narrow = 10 * np.log10(power_narrow) levels_narrow[indices_cur] = level_narrow return freq, levels_narrow @@ -292,7 +299,7 @@ class FilterBankDesigner: levels_in_bands = [] nom_txt = [] - for x in range(xl, xu+1): + for x in range(xl, xu + 1): fl = self.fl(x) fu = self.fu(x) if x != xu: @@ -301,7 +308,7 @@ class FilterBankDesigner: indices_cur = np.where((freq >= fl) & (freq <= fu)) power_cur = np.sum(10**(levels_narrow[indices_cur] / 10)) - levels_in_bands.append(10*np.log10(power_cur)) + levels_in_bands.append(10 * np.log10(power_cur)) nom_txt.append(self.nominal_txt(x)) freq_in_bands.append(self.fm(x)) @@ -341,9 +348,9 @@ class OctaveBankDesigner(FilterBankDesigner): b = 1 # Exact midband frequency - fm = self.G**(x/self.b)*self.fr + fm = self.G**(x / self.b) * self.fr - G_power_values_pos = [0, 1/8, 1/4, 3/8, 1/2, 1/2, 1, 2, 3, 4] + G_power_values_pos = [0, 1 / 8, 1 / 4, 3 / 8, 1 / 2, 1 / 2, 1, 2, 3, 4] G_power_values_neg = [-i for i in G_power_values_pos] G_power_values_neg.reverse() G_power_values = G_power_values_neg[:-1] + G_power_values_pos @@ -351,41 +358,43 @@ class OctaveBankDesigner(FilterBankDesigner): mininf = -1e300 if filter_class == 1: - lower_limits_pos = [-0.3, -0.4, - - 0.6, -1.3, -5.0, -5.0] + 4*[mininf] + lower_limits_pos = [-0.3, -0.4, -0.6, -1.3, -5.0, -5.0 + ] + 4 * [mininf] elif filter_class == 0: - lower_limits_pos = [-0.15, -0.2, - - 0.4, -1.1, -4.5, -4.5] + 4*[mininf] + lower_limits_pos = [-0.15, -0.2, -0.4, -1.1, -4.5, -4.5 + ] + 4 * [mininf] lower_limits_neg = lower_limits_pos[:] lower_limits_neg.reverse() lower_limits = np.asarray(lower_limits_neg[:-1] + lower_limits_pos) if filter_class == 1: - upper_limits_pos = [0.3]*5 + [-2, -17.5, -42, -61, -70] + upper_limits_pos = [0.3] * 5 + [-2, -17.5, -42, -61, -70] if filter_class == 0: - upper_limits_pos = [0.15]*5 + [-2.3, -18, -42.5, -62, -75] + upper_limits_pos = [0.15] * 5 + [-2.3, -18, -42.5, -62, -75] upper_limits_neg = upper_limits_pos[:] upper_limits_neg.reverse() upper_limits = np.asarray(upper_limits_neg[:-1] + upper_limits_pos) - freqs = fm*self.G**np.asarray(G_power_values) + freqs = fm * self.G**np.asarray(G_power_values) return freqs, lower_limits, upper_limits def nominal_txt(self, x): """Returns textual repressentation of corresponding to the nominal frequency.""" - nominals = {4: '16k', - 3: '8k', - 2: '4k', - 1: '2k', - 0: '1k', - -1: '500', - -2: '250', - -3: '125', - -4: '63', - -5: '31.5', - -6: '16'} + nominals = { + 4: '16k', + 3: '8k', + 2: '4k', + 1: '2k', + 0: '1k', + -1: '500', + -2: '250', + -3: '125', + -4: '63', + -5: '31.5', + -6: '16' + } assert len(nominals) == len(self.xs) return nominals[x] @@ -397,7 +406,7 @@ class OctaveBankDesigner(FilterBankDesigner): return .995 elif x in (3, 1): return .99 - elif x in(-6, -4, -2, 2, 0): + elif x in (-6, -4, -2, 2, 0): return .98 else: return .96 @@ -460,20 +469,24 @@ class OctaveBankDesigner(FilterBankDesigner): class ThirdOctaveBankDesigner(FilterBankDesigner): - def __init__(self, fs): + def __init__(self, fs: float): + """ + Initialize ThirdOctaveBankDesigner, a filter bank designer for + one-third octave bands + + Args: + fs: Sampling frequency in [Hz] + + """ super().__init__(fs) self.xs = list(range(-16, 14)) # Text corresponding to the nominal frequency - self._nominal_txt = ['25', '31.5', '40', - '50', '63', '80', - '100', '125', '160', - '200', '250', '315', - '400', '500', '630', - '800', '1k', '1.25k', - '1.6k', '2k', '2.5k', - '3.15k', '4k', '5k', - '6.3k', '8k', '10k', - '12.5k', '16k', '20k'] + self._nominal_txt = [ + '25', '31.5', '40', '50', '63', '80', '100', '125', '160', '200', + '250', '315', '400', '500', '630', '800', '1k', '1.25k', '1.6k', + '2k', '2.5k', '3.15k', '4k', '5k', '6.3k', '8k', '10k', '12.5k', + '16k', '20k' + ] assert len(self.xs) == len(self._nominal_txt) @@ -491,8 +504,7 @@ class ThirdOctaveBankDesigner(FilterBankDesigner): elif type(x) == list: index_start = x[0] - self.xs[0] index_stop = x[-1] - self.xs[0] - return self._nominal_txt[index_start:index_stop+1] - + return self._nominal_txt[index_start:index_stop + 1] def band_limits(self, x, filter_class=0): """Returns the third octave band filter limits for filter designator x. @@ -508,13 +520,17 @@ class ThirdOctaveBankDesigner(FilterBankDesigner): in *deciBell*, upper limits in *deciBell*, respectively. """ - fm = self.G**(x/self.b)*self.fr + fm = self.G**(x / self.b) * self.fr plusinf = 20 - f_ratio_pos = [1., 1.02667, 1.05575, 1.08746, 1.12202, 1.12202, - 1.29437, 1.88173, 3.05365, 5.39195, plusinf] + f_ratio_pos = [ + 1., 1.02667, 1.05575, 1.08746, 1.12202, 1.12202, 1.29437, 1.88173, + 3.05365, 5.39195, plusinf + ] - f_ratio_neg = [0.97402, 0.94719, 0.91958, 0.89125, 0.89125, - 0.77257, 0.53143, 0.32748, 0.18546, 1/plusinf] + f_ratio_neg = [ + 0.97402, 0.94719, 0.91958, 0.89125, 0.89125, 0.77257, 0.53143, + 0.32748, 0.18546, 1 / plusinf + ] f_ratio_neg.reverse() f_ratio = f_ratio_neg + f_ratio_pos @@ -522,9 +538,9 @@ class ThirdOctaveBankDesigner(FilterBankDesigner): mininf = -1e300 if filter_class == 1: - upper_limits_pos = [.3]*5 + [-2, -17.5, -42, -61, -70, -70] + upper_limits_pos = [.3] * 5 + [-2, -17.5, -42, -61, -70, -70] elif filter_class == 0: - upper_limits_pos = [.15]*5 + [-2.3, -18, -42.5, -62, -75, -75] + upper_limits_pos = [.15] * 5 + [-2.3, -18, -42.5, -62, -75, -75] else: raise ValueError('Filter class should either be 0 or 1') @@ -533,17 +549,21 @@ class ThirdOctaveBankDesigner(FilterBankDesigner): upper_limits = np.array(upper_limits_neg[:-1] + upper_limits_pos) if filter_class == 1: - lower_limits_pos = [-.3, -.4, -.6, -1.3, -5, -5, mininf, mininf, - mininf, mininf, mininf] + lower_limits_pos = [ + -.3, -.4, -.6, -1.3, -5, -5, mininf, mininf, mininf, mininf, + mininf + ] elif filter_class == 0: - lower_limits_pos = [-.15, -.2, -.4, -1.1, -4.5, -4.5, mininf, mininf, - mininf, mininf, mininf] + lower_limits_pos = [ + -.15, -.2, -.4, -1.1, -4.5, -4.5, mininf, mininf, mininf, + mininf, mininf + ] lower_limits_neg = lower_limits_pos[:] lower_limits_neg.reverse() lower_limits = np.array(lower_limits_neg[:-1] + lower_limits_pos) - freqs = fm*np.array(f_ratio) + freqs = fm * np.array(f_ratio) return freqs, lower_limits, upper_limits @@ -584,13 +604,17 @@ class ThirdOctaveBankDesigner(FilterBankDesigner): filter.""" # Idea: correct for frequency warping: if np.isclose(self.fs, 48000): + return 1.00 + elif np.isclose(self.fs, 41000): + warnings.warn( + f'Frequency {self.fs} might not result in correct filters') + return 1.00 elif np.isclose(self.fs, 32768): return 1.00 else: raise ValueError('Unimplemented sampling frequency for SOS' 'filter design') - def sosFac_u(self, x): """Right side percentage of change in cut-on frequency for designing the filter.""" diff --git a/lasp/filter/fir_design.py b/src/lasp/filter/fir_design.py similarity index 100% rename from lasp/filter/fir_design.py rename to src/lasp/filter/fir_design.py diff --git a/lasp/filter/soundpressureweighting.py b/src/lasp/filter/soundpressureweighting.py similarity index 100% rename from lasp/filter/soundpressureweighting.py rename to src/lasp/filter/soundpressureweighting.py diff --git a/lasp/lasp_atomic.py b/src/lasp/lasp_atomic.py similarity index 72% rename from lasp/lasp_atomic.py rename to src/lasp/lasp_atomic.py index b95ad30..6897683 100644 --- a/lasp/lasp_atomic.py +++ b/src/lasp/lasp_atomic.py @@ -22,16 +22,27 @@ from threading import Lock class Atomic: + """ + Implementation of atomic operations on integers and booleans. + + """ def __init__(self, val): + self.checkType(val) self._val = val self._lock = Lock() + def checkType(self, val): + if not (type(val) == bool or type(val) == int): + raise RuntimeError("Invalid type for Atomic") + def __iadd__(self, toadd): + self.checkType(toadd) with self._lock: self._val += toadd return self def __isub__(self, toadd): + self.checkType(toadd) with self._lock: self._val -= toadd return self @@ -41,6 +52,7 @@ class Atomic: return self._val def __ilshift__(self, other): + self.checkType(other) with self._lock: self._val = other return self diff --git a/lasp/lasp_common.py b/src/lasp/lasp_common.py similarity index 89% rename from lasp/lasp_common.py rename to src/lasp/lasp_common.py index 3ce3c3d..773ea41 100644 --- a/lasp/lasp_common.py +++ b/src/lasp/lasp_common.py @@ -2,11 +2,11 @@ import shelve, logging, sys, appdirs, os, platform import numpy as np -from .wrappers import Window as wWindow from collections import namedtuple from dataclasses import dataclass from dataclasses_json import dataclass_json from enum import Enum, unique, auto +from .lasp_cpp import DaqChannel """ Common definitions used throughout the code. @@ -14,7 +14,7 @@ Common definitions used throughout the code. __all__ = [ 'P_REF', 'FreqWeighting', 'TimeWeighting', 'getTime', 'getFreq', 'Qty', - 'SIQtys', 'Window', + 'SIQtys', 'lasp_shelve', 'this_lasp_shelve', 'W_REF', 'U_REF', 'I_REF', 'dBFS_REF', 'AvType' ] @@ -39,6 +39,7 @@ U_REF = 5e-8 # 50 nano meter / s # hence this is the reference level as specified below. dBFS_REF = 0.5*2**0.5 # Which level would be -3.01 dBFS + @unique class AvType(Enum): """Specificying the type of data, for adding and removing callbacks from @@ -52,7 +53,6 @@ class AvType(Enum): # Both input as well as output audio_duplex = (2, 'duplex') - # video = 4 @dataclass_json @@ -67,6 +67,7 @@ class Qty: # yet able to compute `dBPa's' level_ref_name: object level_ref_value: object + cpp_enum: DaqChannel.Qty def __str__(self): return f'{self.name} [{self.unit_symb}]' @@ -83,7 +84,6 @@ class Qty: - @unique class SIQtys(Enum): N = Qty(name='Number', @@ -91,14 +91,16 @@ class SIQtys(Enum): unit_symb='-', level_unit=('dBFS',), level_ref_name=('Relative to full scale sine wave',), - level_ref_value=(dBFS_REF,) + level_ref_value=(dBFS_REF,), + cpp_enum = DaqChannel.Qty.Number ) AP = Qty(name='Acoustic Pressure', unit_name='Pascal', unit_symb='Pa', level_unit=('dB SPL','dBPa'), level_ref_name=('2 micropascal', '1 pascal',), - level_ref_value=(P_REF, 1) + level_ref_value=(P_REF, 1), + cpp_enum = DaqChannel.Qty.AcousticPressure ) V = Qty(name='Voltage', @@ -107,28 +109,34 @@ class SIQtys(Enum): level_unit=('dBV',), # dBV level_ref_name=('1V',), level_ref_value=(1.0,), + cpp_enum = DaqChannel.Qty.Voltage ) - @staticmethod - def fillComboBox(cb): - """ - Fill to a combobox - - Args: - cb: QComboBox to fill - """ - cb.clear() - for ty in SIQtys: - cb.addItem(f'{ty.value.unit_name}') - cb.setCurrentIndex(0) - @staticmethod def default(): return SIQtys.N.value @staticmethod - def getCurrent(cb): - return list(SIQtys)[cb.currentIndex()] + def fromCppEnum(enum): + """ + Convert enumeration index from - say - a measurement file back into + physical quantity information. + """ + for qty in SIQtys: + if qty.value.cpp_enum == enum: + return qty.value + raise RuntimeError(f'Qty corresponding to enum {enum} not found') + + @staticmethod + def fromInt(val): + """ + Convert integer index from - say - a measurement file back into + physical quantity information. + """ + for qty in SIQtys: + if qty.value.cpp_enum.value == val: + return qty.value + raise RuntimeError(f'Qty corresponding to integer {val} not found') @dataclass @@ -268,33 +276,6 @@ class this_lasp_shelve(Shelve): node = platform.node() return os.path.join(lasp_appdir, f'{node}_config.shelve') - -@unique -class Window(Enum): - hann = (wWindow.hann, 'Hann') - hamming = (wWindow.hamming, 'Hamming') - rectangular = (wWindow.rectangular, 'Rectangular') - bartlett = (wWindow.bartlett, 'Bartlett') - blackman = (wWindow.blackman, 'Blackman') - - @staticmethod - def fillComboBox(cb): - """ - Fill Windows to a combobox - - Args: - cb: QComboBox to fill - """ - cb.clear() - for w in list(Window): - cb.addItem(w.value[1], w) - cb.setCurrentIndex(0) - - @staticmethod - def getCurrent(cb): - return list(Window)[cb.currentIndex()] - - class TimeWeighting: none = (-1, 'Raw (no time weighting)') uufast = (1e-4, '0.1 ms') diff --git a/lasp/c/lasp_config.h.in b/src/lasp/lasp_config.h.in similarity index 81% rename from lasp/c/lasp_config.h.in rename to src/lasp/lasp_config.h.in index ab55a71..5a0f28e 100644 --- a/lasp/c/lasp_config.h.in +++ b/src/lasp/lasp_config.h.in @@ -9,8 +9,13 @@ #ifndef LASP_CONFIG_H #define LASP_CONFIG_H +const int LASP_VERSION_MAJOR = @CMAKE_PROJECT_VERSION_MAJOR@; +const int LASP_VERSION_MINOR = @CMAKE_PROJECT_VERSION_MINOR@; + /* Debug flag */ #cmakedefine01 LASP_DEBUG + + #if LASP_DEBUG == 1 #define TRACER 1 #define TRACERNAME @LASP_TRACERNAME@ @@ -23,12 +28,14 @@ #define FFTW 1 #define FFTPack 2 #define None 0 + #cmakedefine LASP_FFT_BACKEND @LASP_FFT_BACKEND@ -#cmakedefine LASP_HAS_RTAUDIO -#cmakedefine LASP_HAS_ULDAQ +#cmakedefine01 LASP_HAS_RTAUDIO +#cmakedefine01 LASP_HAS_ULDAQ #cmakedefine01 LASP_DOUBLE_PRECISION #cmakedefine01 LASP_USE_BLAS + /* Single / double precision */ #ifdef LASP_DOUBLE_PRECISION #define LASP_FLOAT_SIZE 64 @@ -45,6 +52,9 @@ /* Audio-specific */ #cmakedefine LASP_MAX_NUM_CHANNELS @LASP_MAX_NUM_CHANNELS@ +/* For FFT's */ +#cmakedefine LASP_MAX_NFFT @LASP_MAX_NFFT@ + /* Platform-specific */ #ifdef _WIN32 #define MS_WIN64 diff --git a/lasp/lasp_config.py b/src/lasp/lasp_config.py similarity index 53% rename from lasp/lasp_config.py rename to src/lasp/lasp_config.py index cd5dbff..46e7af8 100644 --- a/lasp/lasp_config.py +++ b/src/lasp/lasp_config.py @@ -11,12 +11,12 @@ LASP_NUMPY_FLOAT_TYPE = np.float64 def zeros(shape): - return np.zeros(shape, dtype=LASP_NUMPY_FLOAT_TYPE) + return np.zeros(shape, dtype=LASP_NUMPY_FLOAT_TYPE, order='F') def ones(shape): - return np.ones(shape, dtype=LASP_NUMPY_FLOAT_TYPE) + return np.ones(shape, dtype=LASP_NUMPY_FLOAT_TYPE, order='F') def empty(shape): - return np.empty(shape, dtype=LASP_NUMPY_FLOAT_TYPE) + return np.empty(shape, dtype=LASP_NUMPY_FLOAT_TYPE, order='F') diff --git a/src/lasp/lasp_cpp.cpp b/src/lasp/lasp_cpp.cpp new file mode 100644 index 0000000..91ce669 --- /dev/null +++ b/src/lasp/lasp_cpp.cpp @@ -0,0 +1,55 @@ +#include "lasp_config.h" +#include + +/*! \mainpage + * + * \section intro_sec Introduction + * + * Welcome to the LASP (Library for Acoustic Signal Processing) code + * documentation. The code comprises a part which is written in C++, a part + * that is written in Python, and a part that functions as glue, which is + * Pybind11 C++ glue code. An example of such a file is the current one. + * + * \section Installation + * + * For the installation manual, please refer to the README.md of the Git + * repository. It is recommended to install the software from source. + * + * + * */ + +/** + * \defgroup pybind Pybind11 wrapper code + * @{ + * + */ +namespace py = pybind11; + +void init_dsp(py::module &m); +void init_deviceinfo(py::module &m); +void init_daqconfiguration(py::module &m); +void init_daq(py::module &m); +void init_streammgr(py::module &m); +void init_datahandler(py::module &m); +void init_siggen(py::module &m); + +PYBIND11_MODULE(lasp_cpp, m) { + + init_dsp(m); + init_deviceinfo(m); + init_daqconfiguration(m); + init_daq(m); + init_streammgr(m); + init_datahandler(m); + init_siggen(m); + + // We store the version number of the code via CMake, and create an + // attribute in the C++ code. + m.attr("__version__") = std::to_string(LASP_VERSION_MAJOR) + "." + + std::to_string(LASP_VERSION_MINOR); + + + m.attr("LASP_VERSION_MAJOR") = LASP_VERSION_MAJOR; + m.attr("LASP_VERSION_MINOR") = LASP_VERSION_MINOR; +} +/** @} */ diff --git a/src/lasp/lasp_daqconfigs.py b/src/lasp/lasp_daqconfigs.py new file mode 100644 index 0000000..fe80f4f --- /dev/null +++ b/src/lasp/lasp_daqconfigs.py @@ -0,0 +1,121 @@ +# -*- coding: utf-8 -*- +from .lasp_cpp import DaqConfiguration, LASP_VERSION_MAJOR + +"""! +Author: J.A. de Jong - ASCEE + +Description: + +Data Acquistiion (DAQ) device descriptors, and the DAQ devices themselves + +""" +__all__ = ["DaqConfigurations"] +import json + +from .lasp_common import Qty, SIQtys, lasp_shelve +from .lasp_cpp import DaqChannel, DaqConfiguration + + +class DaqConfigurations: + """ + DaqConfigurations stores a set containing an input configuration and an + output configuration. + """ + + def __init__( + self, + duplex_mode: bool, + input_config: DaqConfiguration, + output_config: DaqConfiguration, + ): + """ + Initialize set of DaqConfigurations. + + Args: + duplex_mode: If true, the input configuration is used for output as + well. This makes only sense when the device is capable of having + simultaneous input / output. + input_config: The configuration settings for the input + output_config: The configuration settoutgs for the output + """ + + self.input_config = input_config + self.output_config = output_config + self.duplex_mode = duplex_mode + + @staticmethod + def getNames(): + """ + Get a list of all names of DaqConfigurations sets. + + Returns: + list of names + + """ + with lasp_shelve() as sh: + configs_ser = sh.load(f"daqconfigs_v{LASP_VERSION_MAJOR}", {}) + return list(configs_ser.keys()) + + @staticmethod + def loadAll(): + """ + Returns a dictionary of all configurations presets. The dictionary keys + are the names of the configurations + + Returns: + all configurations, as a dictionary + + """ + with lasp_shelve() as sh: + configs_ser = sh.load(f"daqconfigs_v{LASP_VERSION_MAJOR}", {}) + configs = {} + for name, val in configs_ser.items(): + configs[name] = DaqConfigurations.load(name) + return configs + + @staticmethod + def load(name: str): + """ + Load a single configuration preset, containing input config and output config + + Args: + name: The name of the configuration to load. + + """ + + with lasp_shelve() as sh: + configs_str = sh.load(f"daqconfigs_v{LASP_VERSION_MAJOR}", {}) + config_str = configs_str[name] + + duplex_mode = config_str[0] + input_config = DaqConfiguration.fromTOML(config_str[1]) + output_config = DaqConfiguration.fromTOML(config_str[2]) + return DaqConfigurations(duplex_mode, input_config, output_config) + + def save(self, name: str): + """ + Save the current set of configurations to the shelve store. + + Args: + name: The name of the configuration set. + """ + with lasp_shelve() as sh: + + # Convert to TOML + input_str = self.input_config.toTOML() + output_str = self.output_config.toTOML() + + configs_str = sh.load(f"daqconfigs_v{LASP_VERSION_MAJOR}", {}) + configs_str[name] = [self.duplex_mode, input_str, output_str] + sh.store(f"daqconfigs_v{LASP_VERSION_MAJOR}", configs_str) + + @staticmethod + def delete(name: str): + """ + Delete a DaqConfigurations set from the store. + + """ + with lasp_shelve() as sh: + configs_str = sh.load(f"daqconfigs_v{LASP_VERSION_MAJOR}", {}) + del configs_str[name] + sh.store(f"daqconfigs_v{LASP_VERSION_MAJOR}", configs_str) diff --git a/lasp/lasp_imptube.py b/src/lasp/lasp_imptube.py similarity index 100% rename from lasp/lasp_imptube.py rename to src/lasp/lasp_imptube.py diff --git a/lasp/lasp_logging.py b/src/lasp/lasp_logging.py similarity index 100% rename from lasp/lasp_logging.py rename to src/lasp/lasp_logging.py diff --git a/lasp/lasp_measurement.py b/src/lasp/lasp_measurement.py similarity index 95% rename from lasp/lasp_measurement.py rename to src/lasp/lasp_measurement.py index fb7dd52..64f1109 100644 --- a/lasp/lasp_measurement.py +++ b/src/lasp/lasp_measurement.py @@ -16,9 +16,13 @@ is assumed to be acoustic data. 'sensitivity': (Optionally) the stored sensitivity of the record channels. This can be a single value, or a list of sensitivities for each channel. Both representations are allowed. + +For measurement files of LASP < v1.0 'qtys' : (Optionally): list of quantities that is recorded for each channel', if this array is not found. Quantities are defaulted to 'Number / Full scale' +For measurement files of LASP >= 1.0 + - Datasets: 'audio': 3-dimensional array of blocks of audio data. The first axis is the @@ -46,8 +50,7 @@ from .lasp_config import LASP_NUMPY_FLOAT_TYPE from scipy.io import wavfile import os, time, wave, logging from .lasp_common import SIQtys, Qty, getFreq -from .device import DaqChannel -from .wrappers import AvPowerSpectra, Window, PowerSpectra +from .lasp_cpp import Window, DaqChannel, LASP_VERSION_MAJOR def getSampWidth(dtype): @@ -209,6 +212,13 @@ class Measurement: self.N = (self.nblocks * self.blocksize) self.T = self.N / self.samplerate + try: + self.version_major = f.attrs['LASP_VERSION_MAJOR'] + self.version_minor = f.attrs['LASP_VERSION_MINOR'] + except KeyError: + self.version_major = 0 + self.version_minor = 1 + # Due to a previous bug, the channel names were not stored # consistently, i.e. as 'channel_names' and later camelcase. try: @@ -240,6 +250,7 @@ class Measurement: self._time = f.attrs['time'] + self._qtys = None try: qtys_json = f.attrs['qtys'] # Load quantity data @@ -247,9 +258,20 @@ class Measurement: except KeyError: # If quantity data is not available, this is an 'old' # measurement file. - logging.debug(f'Physical quantity data not available in measurement file. Assuming {SIQtys.default}') - self._qtys = [SIQtys.default() for i in range(self.nchannels)] + pass + try: + qtys_enum_idx = f.attrs['qtys_enum_idx'] + self._qtys = [SIQtys.fromInt(idx) for idx in qtys_enum_idx] + except KeyError: + pass + + if self._qtys is None: + self._qtys = [SIQtys.default() for i in range(self.nchannels)] + logging.debug(f'Physical quantity data not available in measurement file. Assuming {SIQtys.default}') + + + logging.debug(f'Physical quantity data not available in measurement file. Assuming {SIQtys.default}') def setAttribute(self, atrname, value): """ Set an attribute in the measurement file, and keep a local copy in @@ -277,14 +299,15 @@ class Measurement: @property def channelConfig(self): - chcfg = [DaqChannel(channel_enabled=True, - channel_name=chname, - sensitivity=sens,) - for chname, sens in zip( - self.channelNames, - self.sensitivity)] - for i, qty in enumerate(self.qtys): - chcfg[i].qty = qty + chcfg = [] + for chname, sens, qty in zip(self.channelNames, self.sensitivity, + self.qtys): + ch = DaqChannel() + ch.enabled = True + ch.name = chname + ch.sensitivity = sens + ch.qty = qty.cpp_enum + chcfg.append(ch) return chcfg @channelConfig.setter @@ -293,9 +316,9 @@ class Measurement: sens = [] qtys = [] for ch in chcfg: - chname.append(ch.channel_name) + chname.append(ch.name) sens.append(ch.sensitivity) - qtys.append(ch.qty) + qtys.append(SIQtys.fromInt(ch.qty)) self.channelNames = chname self.sensitivity = sens diff --git a/lasp/lasp_octavefilter.py b/src/lasp/lasp_octavefilter.py similarity index 98% rename from lasp/lasp_octavefilter.py rename to src/lasp/lasp_octavefilter.py index b2f3a6b..56f1e89 100644 --- a/lasp/lasp_octavefilter.py +++ b/src/lasp/lasp_octavefilter.py @@ -12,8 +12,6 @@ __all__ = ['FirOctaveFilterBank', 'FirThirdOctaveFilterBank', from .filter.filterbank_design import (OctaveBankDesigner, ThirdOctaveBankDesigner) -from .wrappers import (Decimator, FilterBank as pyxFilterBank, - SosFilterBank as pyxSosFilterBank) import numpy as np diff --git a/lasp/lasp_playback.py b/src/lasp/lasp_playback.py similarity index 100% rename from lasp/lasp_playback.py rename to src/lasp/lasp_playback.py diff --git a/src/lasp/lasp_record.py b/src/lasp/lasp_record.py new file mode 100644 index 0000000..3dacecf --- /dev/null +++ b/src/lasp/lasp_record.py @@ -0,0 +1,285 @@ +#!/usr/bin/python3.8 +# -*- coding: utf-8 -*- +""" +Read data from stream and record sound and video at the same time +""" +import dataclasses, logging, os, time, h5py, threading +import numpy as np + +from .lasp_atomic import Atomic +from .lasp_cpp import (LASP_VERSION_MAJOR, LASP_VERSION_MINOR, InDataHandler, + StreamMgr) + + +@dataclasses.dataclass +class RecordStatus: + curT: float = 0 + done: bool = False + + +class Recording: + """ + Class used to perform a recording. + """ + + def __init__( + self, + fn: str, + streammgr: StreamMgr, + rectime: float = None, + wait: bool = True, + progressCallback=None, + startDelay: float = 0, + ): + """ + Start a recording. Blocks if wait is set to True. + + Args: + fn: Filename to record to. Extension is automatically added if not + provided. + stream: AvStream instance to record from. Should have input + channels! + rectime: Recording time [s], None for infinite, in seconds. If set + to None, or np.inf, the recording continues indefintely. + progressCallback: callable that is called with an instance of + RecordStatus instance as argument. + startDelay: Optional delay added before the recording is *actually* + started in [s]. + """ + ext = ".h5" + if ext not in fn: + fn += ext + + self.smgr = streammgr + self.metadata = None + + if startDelay < 0: + raise RuntimeError("Invalid start delay value. Should be >= 0") + + self.startDelay = startDelay + + # Flag used to indicate that we have passed the start delay + self.startDelay_passed = False + + # The amount of seconds (float) that is to be recorded + self.rectime = rectime + + # The file name to store data to + self.fn = fn + + self.curT_rounded_to_seconds = 0 + + # Counter of the number of blocks + self.ablockno = Atomic(0) + + # Stop flag, set when recording is finished. + self.stop = Atomic(False) + + # Mutex, on who is working with the H5py data + self.file_mtx = threading.Lock() + + self.progressCallback = progressCallback + + # Open the file + self.f = h5py.File(self.fn, "w") + + # This flag is used to delete the file on finish(), and can be used + # when a recording is canceled. + self.deleteFile = False + + # Try to obtain stream metadata + streamstatus = streammgr.getStreamStatus(StreamMgr.StreamType.input) + if not streamstatus.runningOK(): + raise RuntimeError( + "Stream is not running properly. Please first start the stream" + ) + + self.ad = None + + logging.debug("Starting record....") + + self.indh = InDataHandler(streammgr, self.inCallback, self.resetCallback) + + if wait: + logging.debug("Stop recording with CTRL-C") + try: + while not self.stop(): + time.sleep(0.01) + except KeyboardInterrupt: + logging.debug("Keyboard interrupt on record") + finally: + self.finish() + + def resetCallback(self, daq): + """ + Function called with initial stream data. + """ + with self.file_mtx: + in_ch = daq.enabledInChannels() + blocksize = daq.framesPerBlock() + self.blocksize = blocksize + self.nchannels = daq.neninchannels() + self.fs = daq.samplerate() + + f = self.f + + f.attrs["LASP_VERSION_MAJOR"] = LASP_VERSION_MAJOR + f.attrs["LASP_VERSION_MINOR"] = LASP_VERSION_MINOR + + # Set the bunch of attributes + f.attrs["samplerate"] = daq.samplerate() + f.attrs["nchannels"] = daq.neninchannels() + f.attrs["blocksize"] = blocksize + f.attrs["sensitivity"] = [ch.sensitivity for ch in in_ch] + f.attrs["channelNames"] = [ch.name for ch in in_ch] + + # Add the start delay here, as firstFrames() is called right after the + # constructor is called. + f.attrs["time"] = time.time() + self.startDelay + + # In V2, we do not store JSON metadata anymore, but just an enumeration + # index to a physical quantity. + f.attrs["qtys_enum_idx"] = [ch.qty.value for ch in in_ch] + + # Measured physical quantity metadata + # f.attrs['qtys'] = [ch.qty.to_json() for ch in in_ch] + + def firstFrames(self, adata): + """ + Set up the dataset in which to store the audio data. This will create + the attribute `self.ad` + + Args: + adata: Numpy array with data from DAQ + + """ + + # The array data type cannot + # datatype = daq.dataType() + dtype = np.dtype(adata.dtype) + + self.ad = self.f.create_dataset( + "audio", + (1, self.blocksize, self.nchannels), + dtype=dtype, + maxshape=( + None, # This means, we can add blocks + # indefinitely + self.blocksize, + self.nchannels, + ), + compression="gzip", + ) + + def inCallback(self, adata): + """ + This method should be called to grab data from the input queue, which + is filled by the stream, and put it into a file. It should be called at + a regular interval to prevent overflowing of the queue. It is called + within the start() method of the recording, if block is set to True. + Otherwise, it should be called from its parent at regular intervals. + For example, in Qt this can be done using a QTimer. + + + """ + if self.stop(): + # Stop flag is raised. We do not add any data anymore. + return + + with self.file_mtx: + + if self.ad is None: + self.firstFrames(adata) + + self.__addTimeData(adata) + return True + + def setDelete(self, val: bool): + """ + Set the delete flag. If set, measurement file is deleted at the end of + the recording. Typically used for cleaning up after canceling a + recording. + """ + self.deleteFile = val + + def finish(self): + """ + This method should be called to finish and a close a recording file, + remove the queue from the stream, etc. + + """ + logging.debug("Recording::finish()") + + self.stop <<= True + + with self.file_mtx: + + # Remove indata handler, which also should remove callback function + # from StreamMgr. + self.indh = None + + try: + # Close the recording file + self.f.close() + except Exception as e: + logging.error(f"Error closing file: {e}") + + logging.debug("Recording ended") + if self.deleteFile: + self.__deleteFile() + + def __deleteFile(self): + """ + Cleanup the recording file. + """ + try: + os.remove(self.fn) + except Exception as e: + logging.error(f"Error deleting file: {self.fn}: {str(e)}") + + def __addTimeData(self, indata): + """ + Called by handleQueue() and adds new time data to the storage file. + """ + # logging.debug('Recording::__addTimeData()') + + curT = self.ablockno() * self.blocksize / self.fs + + # Increase the block counter + self.ablockno += 1 + + if curT < self.startDelay and not self.startDelay_passed: + # Start delay has not been passed + return + elif curT >= 0 and not self.startDelay_passed: + # Start delay passed, switch the flag! + self.startDelay_passed = True + + # Reset the audio block counter and the recording time + self.ablockno = Atomic(1) + curT = 0 + + ablockno = self.ablockno() + recstatus = RecordStatus(curT=curT, done=False) + + if self.progressCallback is not None: + self.progressCallback(recstatus) + + curT_rounded_to_seconds = int(curT) + if curT_rounded_to_seconds > self.curT_rounded_to_seconds: + self.curT_rounded_to_seconds = curT_rounded_to_seconds + print(f"{curT_rounded_to_seconds}", end="", flush=True) + else: + print(".", end="", flush=True) + + if self.rectime is not None and curT > self.rectime: + # We are done! + if self.progressCallback is not None: + recstatus.done = True + self.progressCallback(recstatus) + self.stop <<= True + return + + # Add the data to the file, and resize the audio data blocks + self.ad.resize(ablockno, axis=0) + self.ad[ablockno - 1, :, :] = indata diff --git a/lasp/lasp_reverb.py b/src/lasp/lasp_reverb.py similarity index 95% rename from lasp/lasp_reverb.py rename to src/lasp/lasp_reverb.py index dbc829d..9ac5831 100644 --- a/lasp/lasp_reverb.py +++ b/src/lasp/lasp_reverb.py @@ -58,14 +58,14 @@ class ReverbTime: # Solve the least-squares problem, by creating a matrix of A = np.hstack([x, np.ones(x.shape)]) - print(A.shape) - print(points.shape) + # print(A.shape) + # print(points.shape) # derivative is dB/s of increase/decrease sol, residuals, rank, s = np.linalg.lstsq(A, points) - print(f'sol: {sol}') + # print(f'sol: {sol}') # Derivative of the decay in dB/s derivative = sol[0][0] @@ -79,5 +79,5 @@ class ReverbTime: 'const': const, 'derivative': derivative, 'T60': T60} - print(res) + # print(res) return res diff --git a/lasp/lasp_slm.py b/src/lasp/lasp_slm.py similarity index 85% rename from lasp/lasp_slm.py rename to src/lasp/lasp_slm.py index 14642c5..3fae3b9 100644 --- a/lasp/lasp_slm.py +++ b/src/lasp/lasp_slm.py @@ -4,7 +4,7 @@ Sound level meter implementation @author: J.A. de Jong - ASCEE """ -from .wrappers import Slm as pyxSlm +from .lasp_cpp import cppSLM import numpy as np from .lasp_common import (TimeWeighting, FreqWeighting, P_REF) from .filter import SPLFilterDesigner @@ -33,8 +33,8 @@ class SLM: def __init__(self, fs, fbdesigner=None, - tw=TimeWeighting.fast, - fw=FreqWeighting.A, + tw: TimeWeighting =TimeWeighting.fast, + fw: FreqWeighting =FreqWeighting.A, xmin = None, xmax = None, include_overall=True, @@ -50,7 +50,7 @@ class SLM: fs: Sampling frequency [Hz] tw: Time Weighting to apply fw: Frequency weighting to apply - xmin: Filter designator of lowest band + xmin: Filter designator of lowest band. xmax: Filter designator of highest band include_overall: If true, a non-functioning filter is added which is used to compute the overall level. @@ -96,36 +96,42 @@ class SLM: assert fbdesigner.fs == fs sos_firstx = fbdesigner.createSOSFilter(self.xs[0]).flatten() self.nom_txt.append(fbdesigner.nominal_txt(self.xs[0])) - sos = np.empty((nfilters, sos_firstx.size), dtype=float, order='C') - sos[0, :] = sos_firstx + sos = np.empty((sos_firstx.size, nfilters), dtype=float, order='C') + sos[:, 0] = sos_firstx for i, x in enumerate(self.xs[1:]): - sos[i+1, :] = fbdesigner.createSOSFilter(x).flatten() + sos[:, i+1] = fbdesigner.createSOSFilter(x).flatten() self.nom_txt.append(fbdesigner.nominal_txt(x)) if include_overall: # Create a unit impulse response filter, every third index equals # 1, so b0 = 1 and a0 is 1 (by definition) # a0 = 1, b0 = 1, rest is zero - sos[-1,:] = sos_overall + sos[:,-1] = sos_overall self.nom_txt.append('overall') else: # No filterbank, means we do only compute the overall values. This # means that in case of include_overall, it creates two overall # channels. That would be confusing, so we do not allow it. - sos = sos_overall[np.newaxis,:] + sos = sos_overall[:,np.newaxis] self.nom_txt.append('overall') - self.slm = pyxSlm(prefilter, sos, - fs, tw[0], level_ref_value) + # Downsampling factor, determine from single pole low pass filter time + # constant, such that aliasing is ~ allowed at 20 dB lower value + # and + dsfac = cppSLM.suggestedDownSamplingFac(fs, tw[0]) - dsfac = self.slm.downsampling_fac - if dsfac > 0: - # Not unfiltered data - self.fs_slm = fs / self.slm.downsampling_fac + if prefilter is not None: + self.slm = cppSLM.fromBiquads(fs, level_ref_value, dsfac, + tw[0], + prefilter, sos) else: - self.fs_slm = fs + self.slm = cppSLM.fromBiquads(fs, level_ref_value, dsfac, + tw[0], + sos) + + self.fs_slm = fs / dsfac # Initialize counter to 0 self.N = 0 @@ -189,7 +195,6 @@ class SLM: output['mid'] = self.fbdesigner.fm(list(self.xs)) logging.debug(list(self.xs)) logging.debug(output['mid']) - # Indiced at 0, as pyxSLM always returns 2D arrays. if self.include_overall and self.fbdesigner is not None: output['overall'] = dat[-1,0] diff --git a/lasp/lasp_weighcal.py b/src/lasp/lasp_weighcal.py similarity index 100% rename from lasp/lasp_weighcal.py rename to src/lasp/lasp_weighcal.py diff --git a/lasp/plot/__init__.py b/src/lasp/plot/__init__.py similarity index 100% rename from lasp/plot/__init__.py rename to src/lasp/plot/__init__.py diff --git a/lasp/plot/bar.py b/src/lasp/plot/bar.py similarity index 100% rename from lasp/plot/bar.py rename to src/lasp/plot/bar.py diff --git a/src/lasp/pybind11/lasp_daq.cpp b/src/lasp/pybind11/lasp_daq.cpp new file mode 100644 index 0000000..fa91280 --- /dev/null +++ b/src/lasp/pybind11/lasp_daq.cpp @@ -0,0 +1,42 @@ +#include "lasp_daq.h" +#include +#include +#include + +using std::cerr; +namespace py = pybind11; + +void init_daq(py::module &m) { + + /// Daq + py::class_ daq(m, "Daq"); + + /// Daq::StreamStatus + py::class_ ss(daq, "StreamStatus"); + ss.def("error", &Daq::StreamStatus::error); + ss.def("runningOK", &Daq::StreamStatus::runningOK); + ss.def_readonly("isRunning", &Daq::StreamStatus::isRunning); + + /// Daq::StreamStatus::StreamError + py::enum_(ss, "StreamError") + .value("noError", Daq::StreamStatus::StreamError::noError) + .value("inputXRun", Daq::StreamStatus::StreamError::inputXRun) + .value("outputXRun", Daq::StreamStatus::StreamError::outputXRun) + .value("driverError", Daq::StreamStatus::StreamError::driverError) + .value("systemError", Daq::StreamStatus::StreamError::systemError) + .value("threadError", Daq::StreamStatus::StreamError::threadError) + .value("logicError", Daq::StreamStatus::StreamError::logicError) + .value("apiSpecificError", + Daq::StreamStatus::StreamError::apiSpecificError); + + ss.def("errorMsg", &Daq::StreamStatus::errorMsg); + + /// Daq + daq.def("neninchannels", &Daq::neninchannels, py::arg("include_monitor") = true); + daq.def("nenoutchannels", &Daq::nenoutchannels); + daq.def("samplerate", &Daq::samplerate); + daq.def("dataType", &Daq::dataType); + daq.def("framesPerBlock", &Daq::framesPerBlock); + daq.def("getStreamStatus", &Daq::getStreamStatus); + +} diff --git a/src/lasp/pybind11/lasp_daqconfig.cpp b/src/lasp/pybind11/lasp_daqconfig.cpp new file mode 100644 index 0000000..2442531 --- /dev/null +++ b/src/lasp/pybind11/lasp_daqconfig.cpp @@ -0,0 +1,99 @@ +#include "lasp_daqconfig.h" +#include "lasp_deviceinfo.h" +#include +#include +#include +#include +#include + +using std::cerr; +namespace py = pybind11; + +void init_daqconfiguration(py::module &m) { + + /// DataType + py::class_ dtype_desc(m, "DataTypeDescriptor"); + + dtype_desc.def_readonly("name", &DataTypeDescriptor::name); + dtype_desc.def_readonly("sw", &DataTypeDescriptor::sw); + dtype_desc.def_readonly("is_floating", &DataTypeDescriptor::is_floating); + dtype_desc.def_readonly("dtype", &DataTypeDescriptor::dtype); + + py::enum_(dtype_desc, "DataType") + .value("dtype_fl32", DataTypeDescriptor::DataType::dtype_fl32) + .value("dtype_fl64", DataTypeDescriptor::DataType::dtype_fl64) + .value("dtype_int8", DataTypeDescriptor::DataType::dtype_int8) + .value("dtype_int16", DataTypeDescriptor::DataType::dtype_int16) + .value("dtype_int32", DataTypeDescriptor::DataType::dtype_int32); + + dtype_desc.def_static("toStr", [](const DataTypeDescriptor::DataType d) { + return dtype_map.at(d).name; + }); + dtype_desc.def_static("fromStr", [](const std::string &dtype_str) { + decltype(dtype_map.cbegin()) d = std::find_if( + dtype_map.cbegin(), dtype_map.cend(), + [&dtype_str](const auto &d) { return d.second.name == dtype_str; }); + if (d != dtype_map.cend()) { + return d->first; + } + throw std::runtime_error(dtype_str + " not found in list of data types"); + }); + + dtype_desc.def_readonly("dtype", &DataTypeDescriptor::dtype); + + /// DaqApi + py::class_ daqapi(m, "DaqApi"); + daqapi.def_readonly("apiname", &DaqApi::apiname); + daqapi.def_readonly("apicode", &DaqApi::apicode); + daqapi.def_readonly("api_specific_subcode", &DaqApi::api_specific_subcode); + daqapi.def("__str__", [](const DaqApi &d) { return std::string(d); }); + + /// DaqChannel, DaqConfiguration + py::class_ daqconfig(m, "DaqConfiguration"); + + py::class_ daqchannel(m, "DaqChannel"); + daqchannel.def(py::init<>()); + daqchannel.def_readwrite("enabled", &DaqChannel::enabled); + daqchannel.def_readwrite("name", &DaqChannel::name); + daqchannel.def_readwrite("sensitivity", &DaqChannel::sensitivity); + daqchannel.def_readwrite("ACCouplingMode", &DaqChannel::ACCouplingMode); + daqchannel.def_readwrite("IEPEEnabled", &DaqChannel::IEPEEnabled); + daqchannel.def_readwrite("digitalHighpassCutOn", + &DaqChannel::digitalHighPassCutOn); + daqchannel.def_readwrite("rangeIndex", &DaqChannel::rangeIndex); + + py::enum_(daqchannel, "Qty") + .value("Number", DaqChannel::Qty::Number) + .value("AcousticPressure", DaqChannel::Qty::AcousticPressure) + .value("Voltage", DaqChannel::Qty::Voltage) + .value("UserDefined", DaqChannel::Qty::UserDefined); + daqchannel.def_readwrite("qty", &DaqChannel::qty); + + daqchannel.def("__eq__", [](const DaqChannel& a, const DaqChannel& b) { return a==b;}); + + /// DaqConfiguration + daqconfig.def(py::init<>()); + daqconfig.def(py::init()); + daqconfig.def_readwrite("api", &DaqConfiguration::api); + daqconfig.def_readwrite("device_name", &DaqConfiguration::device_name); + + 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("match", &DaqConfiguration::match); + + daqconfig.def_static("fromTOML", &DaqConfiguration::fromTOML); + daqconfig.def("toTOML", &DaqConfiguration::toTOML); + daqconfig.def_readwrite("inchannel_config", + &DaqConfiguration::inchannel_config); + daqconfig.def_readwrite("outchannel_config", + &DaqConfiguration::outchannel_config); + daqconfig.def("setAllInputEnabled", &DaqConfiguration::setAllInputEnabled); + daqconfig.def("setAllOutputEnabled", &DaqConfiguration::setAllOutputEnabled); + daqconfig.def("enabledInChannels", &DaqConfiguration::enabledInChannels); +} diff --git a/src/lasp/pybind11/lasp_deviceinfo.cpp b/src/lasp/pybind11/lasp_deviceinfo.cpp new file mode 100644 index 0000000..3d0ad4a --- /dev/null +++ b/src/lasp/pybind11/lasp_deviceinfo.cpp @@ -0,0 +1,40 @@ +#include +#include +#include +#include "lasp_deviceinfo.h" + +using std::cerr; +namespace py = pybind11; + +void init_deviceinfo(py::module& m) { + + /// DeviceInfo + py::class_ devinfo(m, "DeviceInfo"); + devinfo.def("__str__", [](const DeviceInfo& d) {return d.device_name;}); + 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); + +} + diff --git a/src/lasp/pybind11/lasp_dsp_pybind.cpp b/src/lasp/pybind11/lasp_dsp_pybind.cpp new file mode 100644 index 0000000..f301a3e --- /dev/null +++ b/src/lasp/pybind11/lasp_dsp_pybind.cpp @@ -0,0 +1,105 @@ +#include +#include "lasp_avpowerspectra.h" +#include "lasp_biquadbank.h" +#include "lasp_fft.h" +#include "lasp_streammgr.h" +#include "lasp_filter.h" +#include "lasp_slm.h" +#include "lasp_window.h" +#include +#include + +using std::cerr; +namespace py = pybind11; + +/** + * \ingroup pybind + * @{ + * + */ + +/** + * @brief Initialize DSP code + * + * @param m The Python module to add classes and methods to + */ +void init_dsp(py::module &m) { + + py::class_ fft(m, "Fft"); + fft.def(py::init()); + fft.def("fft", py::overload_cast(&Fft::fft)); + fft.def("fft", py::overload_cast(&Fft::fft)); + + fft.def("ifft", py::overload_cast(&Fft::ifft)); + fft.def("ifft", py::overload_cast(&Fft::ifft)); + fft.def_static("load_fft_wisdom", &Fft::load_fft_wisdom); + fft.def_static("store_fft_wisdom", &Fft::store_fft_wisdom); + + /// Window + py::class_ w(m, "Window"); + + py::enum_(w, "WindowType") + .value("Hann", Window::WindowType::Hann) + .value("Hamming", Window::WindowType::Hamming) + .value("Bartlett", Window::WindowType::Bartlett) + .value("Blackman", Window::WindowType::Bartlett) + .value("Rectangular", Window::WindowType::Rectangular); + + w.def_static("toTxt", &Window::toText); + + py::class_> filter(m, "Filter"); + + /// SeriesBiquad + py::class_> sbq(m, "SeriesBiquad", + filter); + sbq.def(py::init()); + sbq.def("filter", [](SeriesBiquad &s, const vd &input) { + vd res = input; + s.filter(res); + return res; + }); + + /// BiquadBank + py::class_> bqb(m, "BiquadBank"); + bqb.def(py::init()); + bqb.def("setGains", &BiquadBank::setGains); + bqb.def("filter", &BiquadBank::filter); + + /// PowerSpectra + py::class_ ps(m, "PowerSpectra"); + ps.def(py::init()); + ps.def("compute", &PowerSpectra::compute); + + /// AvPowerSpectra + py::class_ aps(m, "AvPowerSpectra"); + aps.def(py::init(), + py::arg("nfft") = 2048, + py::arg("windowType") = Window::WindowType::Hann, + py::arg("overlap_percentage") = 50.0, py::arg("time_constant") = -1); + + aps.def("compute", [](AvPowerSpectra &aps, const dmat &timedata) { + std::optional res = aps.compute(timedata); + return res.value_or(arma::cx_cube(0, 0, 0)); + }); + + py::class_ slm(m, "cppSLM"); + + slm.def_static( + "fromBiquads", + py::overload_cast( + &SLM::fromBiquads)); + slm.def_static( + "fromBiquads", + py::overload_cast(&SLM::fromBiquads)); + slm.def("run", &SLM::run); + slm.def_readonly("Pm", &SLM::Pm); + slm.def_readonly("Pmax", &SLM::Pmax); + slm.def_readonly("Ppeak", &SLM::Ppeak); + slm.def("Lpeak", &SLM::Lpeak); + slm.def("Leq", &SLM::Leq); + slm.def("Lmax", &SLM::Lmax); + slm.def_static("suggestedDownSamplingFac", &SLM::suggestedDownSamplingFac); + +} +/** @} */ diff --git a/src/lasp/pybind11/lasp_pyindatahandler.cpp b/src/lasp/pybind11/lasp_pyindatahandler.cpp new file mode 100644 index 0000000..694b700 --- /dev/null +++ b/src/lasp/pybind11/lasp_pyindatahandler.cpp @@ -0,0 +1,183 @@ +#include +#include +#define DEBUGTRACE_ENABLED +#include "debugtrace.hpp" +#include "lasp_ppm.h" +#include "lasp_rtaps.h" +#include "lasp_streammgr.h" +#include "lasp_threadedindatahandler.h" +#include +#include +#include + +using namespace std::literals::chrono_literals; +using std::cerr; +using std::endl; + +namespace py = pybind11; + +/** + * @brief Generate a Numpy array from daqdata, does *NOT* create a copy of the + * data!. Instead, it shares the data from the DaqData container. + * + * @tparam T The type of the stored sample + * @param d The daqdata to convert + * + * @return Numpy array + */ +template py::array_t getPyArray(const DaqData &d) { + // https://github.com/pybind/pybind11/issues/323 + // + // When a valid object is passed as 'base', it tells pybind not to take + // ownership of the data, because 'base' will own it. In fact 'packet' will + // own it, but - psss! - , we don't tell it to pybind... Alos note that ANY + // valid object is good for this purpose, so I chose "str"... + + py::str dummyDataOwner; + /* + * Signature: + array_t(ShapeContainer shape, + StridesContainer strides, + const T *ptr = nullptr, + handle base = handle()); + */ + + return py::array_t( + py::array::ShapeContainer({d.nframes, d.nchannels}), // Shape + + py::array::StridesContainer( // Strides + {sizeof(T), + sizeof(T) * d.nframes}), // Strides (in bytes) for each index + + reinterpret_cast( + const_cast(d).raw_ptr()), // Pointer to buffer + + dummyDataOwner // As stated above, now Numpy does not take ownership of + // the data pointer. + ); +} + +/** + * @brief Wraps the InDataHandler such that it calls a Python callback with a + * buffer of sample data. The Python callback is called from a different + * thread, using a Numpy array as argument. + */ +class PyIndataHandler : public ThreadedInDataHandler { + /** + * @brief The callback functions that is called. + */ + py::function cb, reset_callback; + +public: + PyIndataHandler(StreamMgr &mgr, py::function cb, py::function reset_callback) + : ThreadedInDataHandler(mgr), cb(cb), reset_callback(reset_callback) { + + DEBUGTRACE_ENTER; + /// TODO: Note that if start() throws an exception, which means that the + /// destructor of PyIndataHandler is not called and the thread destructor + /// calls terminate(). It is a kind of rude way to crash, but it is also + /// *very* unlikely to happen, as start() does only add a reference to this + /// handler to a list in the stream mgr. + start(); + } + ~PyIndataHandler() { + DEBUGTRACE_ENTER; + stop(); + } + void reset(const Daq *daq) override final { reset_callback(daq); } + + /** + * @brief Reads from the buffer + */ + bool inCallback_threaded(const DaqData &d) override final { + + /* DEBUGTRACE_ENTER; */ + + using DataType = DataTypeDescriptor::DataType; + + py::gil_scoped_acquire acquire; + try { + py::object bool_val; + switch (d.dtype) { + case (DataType::dtype_int8): { + bool_val = cb(getPyArray(d)); + } break; + case (DataType::dtype_int16): { + bool_val = cb(getPyArray(d)); + } break; + case (DataType::dtype_int32): { + bool_val = cb(getPyArray(d)); + } break; + case (DataType::dtype_fl32): { + bool_val = cb(getPyArray(d)); + } break; + case (DataType::dtype_fl64): { + bool_val = cb(getPyArray(d)); + } break; + default: + throw std::runtime_error("BUG"); + } // End of switch + + bool res = bool_val.cast(); + if (!res) + return false; + } catch (py::error_already_set &e) { + cerr << "ERROR: Python raised exception from callback function: "; + cerr << e.what() << endl; + return false; + } catch (py::cast_error &e) { + cerr << e.what() << endl; + cerr << "ERROR: Python callback does not return boolean value." << endl; + return false; + } + return true; + } +}; + + +void init_datahandler(py::module &m) { + py::class_ h(m, "InDataHandler"); + h.def(py::init()); + + /// Peak Programme Meter + py::class_ ppm(m, "PPMHandler"); + ppm.def(py::init()); + ppm.def(py::init()); + + ppm.def("getCurrentValue", [](const PPMHandler &ppm) { + std::tuple tp = ppm.getCurrentValue(); + + return py::make_tuple(carma::col_to_arr(std::get<0>(tp)), + carma::col_to_arr(std::get<1>(tp))); + }); + + /// Real time Aps + /// + py::class_ rtaps(m, "RtAps"); + rtaps.def(py::init(), + py::arg("streammgr"), // StreamMgr + py::arg("preFilter").none(true), + /// Below list of arguments *SHOULD* be same as for + + /// AvPowerSpectra constructor! + py::arg("nfft") = 2048, // + py::arg("windowType") = Window::WindowType::Hann, // + py::arg("overlap_percentage") = 50.0, // + py::arg("time_constant") = -1 // + ); + + rtaps.def("getCurrentValue", [](RtAps &rt) { + std::unique_ptr val = rt.getCurrentValue(); + if (val) { + return carma::cube_to_arr(std::move(*val)); + } + return carma::cube_to_arr(cube(1, 0, 0)); + }); +} diff --git a/src/lasp/pybind11/lasp_siggen.cpp b/src/lasp/pybind11/lasp_siggen.cpp new file mode 100644 index 0000000..5b1eaab --- /dev/null +++ b/src/lasp/pybind11/lasp_siggen.cpp @@ -0,0 +1,56 @@ +#include +#include "lasp_avpowerspectra.h" +#include "lasp_biquadbank.h" +#include "lasp_fft.h" +#include "lasp_siggen.h" +#include "lasp_siggen_impl.h" +#include "lasp_slm.h" +#include "lasp_window.h" +#include +#include + +using std::cerr; +namespace py = pybind11; + +/** + * \ingroup pybind + * @{ + * + */ + +/** + * @brief Initialize Siggen wrappers + * + * @param m The Python module to add classes and methods to + */ +void init_siggen(py::module &m) { + + /// Siggen + py::class_> siggen(m, "Siggen"); + siggen.def("setLevel", &Siggen::setLevel, + "Set the level of the signal generator"); + siggen.def("setDCOffset", &Siggen::setDCOffset); + siggen.def("setMute", &Siggen::setMute); + siggen.def("setLevel", &Siggen::setLevel); + siggen.def("setLevel", &Siggen::setLevel, py::arg("newLevel"), py::arg("dB") = true); + siggen.def("genSignal", &Siggen::genSignal); + siggen.def("setFilter", &Siggen::setFilter); + + py::class_> noise(m, "Noise", siggen); + noise.def(py::init<>()); + + py::class_> sine(m, "Sine", siggen); + sine.def(py::init()); + sine.def("setFreq", &Sine::setFreq); + + py::class_> periodic(m, "Periodic", siggen); + + py::class_> sweep(m, "Sweep", periodic); + sweep.def(py::init()); + sweep.def_readonly_static("ForwardSweep", &Sweep::ForwardSweep); + sweep.def_readonly_static("BackwardSweep", &Sweep::BackwardSweep); + sweep.def_readonly_static("LinearSweep", &Sweep::LinearSweep); + sweep.def_readonly_static("LogSweep", &Sweep::LogSweep); + +} +/** @} */ diff --git a/src/lasp/pybind11/lasp_streammgr.cpp b/src/lasp/pybind11/lasp_streammgr.cpp new file mode 100644 index 0000000..cf6c08f --- /dev/null +++ b/src/lasp/pybind11/lasp_streammgr.cpp @@ -0,0 +1,42 @@ +#include "lasp_streammgr.h" +#include +#include +#include +#include +#include + +using std::cerr; +namespace py = pybind11; + +void init_streammgr(py::module &m) { + + /// The stream manager is a singleton, and the lifetime is managed elsewhere. + // It should not be deleted. + py::class_> smgr( + m, "StreamMgr"); + + py::enum_(smgr, "StreamType") + .value("input", StreamMgr::StreamType::input) + .value("output", StreamMgr::StreamType::output); + + smgr.def("startStream", &StreamMgr::startStream); + smgr.def("stopStream", &StreamMgr::stopStream); + smgr.def_static("getInstance", []() { + return std::unique_ptr(&StreamMgr::getInstance()); + }); + smgr.def("stopAllStreams", &StreamMgr::stopAllStreams); + + smgr.def("setSiggen", &StreamMgr::setSiggen); + smgr.def("getDeviceInfo", &StreamMgr::getDeviceInfo); + smgr.def("getStreamStatus", &StreamMgr::getStreamStatus); + smgr.def("isStreamRunningOK", &StreamMgr::isStreamRunningOK); + smgr.def("isStreamRunning", &StreamMgr::isStreamRunning); + smgr.def("getDaq", &StreamMgr::getDaq, py::return_value_policy::reference); + smgr.def("rescanDAQDevices", [](StreamMgr& smgr, bool background) { + // A pure C++ callback is the second argument to rescanDAQDevices, which + // cannot be wrapped to Pybind11. Only the one without callback is + // forwarded here to Python code. + smgr.rescanDAQDevices(background); + }, + py::arg("background")=false); +} diff --git a/lasp/tools/__init__.py b/src/lasp/tools/__init__.py similarity index 100% rename from lasp/tools/__init__.py rename to src/lasp/tools/__init__.py diff --git a/lasp/tools/tools.py b/src/lasp/tools/tools.py similarity index 100% rename from lasp/tools/tools.py rename to src/lasp/tools/tools.py diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 1159d75..98f23a5 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,15 +1,15 @@ include_directories(${CMAKE_SOURCE_DIR}/lasp/c) include_directories(${CMAKE_SOURCE_DIR}/lasp/device) -add_executable(test_bf test_bf.c) -add_executable(test_workers test_workers.c) -add_executable(test_fft test_fft.c) -add_executable(test_math test_math.c) +# add_executable(test_bf test_bf.c) +# add_executable(test_workers test_workers.c) +# add_executable(test_fft test_fft.c) +# add_executable(test_math test_math.c) -target_link_libraries(test_bf lasp_lib ${LASP_THREADING_LIBRARIES}) -target_link_libraries(test_fft lasp_lib ${LASP_THREADING_LIBRARIES}) -target_link_libraries(test_workers lasp_lib ${LASP_THREADING_LIBRARIES}) -target_link_libraries(test_math lasp_lib ${LASP_THREADING_LIBRARIES}) +# target_link_libraries(test_bf lasp_lib ${LASP_THREADING_LIBRARIES}) +# target_link_libraries(test_fft lasp_lib ${LASP_THREADING_LIBRARIES}) +# target_link_libraries(test_workers lasp_lib ${LASP_THREADING_LIBRARIES}) +# target_link_libraries(test_math lasp_lib ${LASP_THREADING_LIBRARIES}) if(LASP_ULDAQ) add_executable(test_uldaq test_uldaq.cpp) diff --git a/test/bandpass_test_1.py b/test/bandpass_test_1.py deleted file mode 100644 index 499cc34..0000000 --- a/test/bandpass_test_1.py +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -"""! -Author: J.A. de Jong - ASCEE - -""" -import numpy as np -from lasp.filter.bandpass_limits import (third_octave_band_limits, - octave_band_limits, G, fr) - -from lasp.filter.bandpass_fir import ThirdOctaveBankDesigner, \ - OctaveBankDesigner -import matplotlib.pyplot as plt - -# Adjust these settings -b = 1 # or three -zoom = False # or True - -if b == 3: - bands = ThirdOctaveBankDesigner() -elif b == 1: - bands = OctaveBankDesigner() -else: - raise ValueError('b should be 1 or 3') - - -for x in bands.xs: - fig = plt.figure() - ax = fig.add_subplot(111) - fs = 48000. - dec = np.prod(bands.decimation(x)) - fd = fs/dec - fc = fd/2/1.4 - - freq = np.logspace(np.log10(1), np.log10(fd), 5000) - H = bands.freqResponse(fs, x, freq) - dBH = 20*np.log10(np.abs(H)) - ax.semilogx(freq, dBH) - - if b == 1: - freq, ulim, llim = octave_band_limits(x) - else: - freq, ulim, llim = third_octave_band_limits(x) - - ax.semilogx(freq, llim) - ax.semilogx(freq, ulim) - ax.set_title(f'x = {x}, fnom = {bands.nominal_txt(x)}') - - if zoom: - ax.set_xlim(bands.fl(x)/1.1, bands.fu(x)*1.1) - ax.set_ylim(-15, 1) - else: - ax.set_ylim(-75, 1) - ax.set_xlim(10, fd) - - ax.axvline(fd/2) - if dec > 1: - ax.axvline(fc, color='red') - - ax.legend(['Filter frequency response', - 'Lower limit from standard', - 'Upper limit from standard', - 'Nyquist frequency after decimation', - 'Decimation filter cut-off frequency'], fontsize=8) - -plt.show() diff --git a/test/fft_test.py b/test/fft_test.py deleted file mode 100644 index 58b6e25..0000000 --- a/test/fft_test.py +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Created on Mon Jan 15 19:45:33 2018 - -@author: anne -""" -import numpy as np -from lasp.wrappers import Fft - -nfft=9 -print('nfft:',nfft) -print(nfft) -nchannels = 4 - -t = np.linspace(0,1,nfft+1)[:-1] -# print(t) -#x1 = 1+np.sin(2*np.pi*t)+3.2*np.cos(2*np.pi*t)+np.sin(7*np.pi*t) -#x1 = np.sin(2*np.pi*t) -x1 = 1+0*t -x = np.vstack([x1.T]*nchannels).T -# Using transpose to get the strides right -x = np.random.randn(nchannels,nfft).T -# x.strides = (8,nfft*8)x -# print("signal:",x) - -X = np.fft.rfft(x,axis=0) -print('Numpy fft') -print(X) - -fft = Fft(nfft) -Y = fft.fft(x) -print('Beamforming fft') -print(Y) - -x2 = fft.ifft(Y) -print('normdiff:',np.linalg.norm(x2-x)) -print('end python script') diff --git a/test/ps_test.py b/test/ps_test.py deleted file mode 100644 index ed7859f..0000000 --- a/test/ps_test.py +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Created on Mon Jan 15 19:45:33 2018 - -@author: anne -""" -import numpy as np -from beamforming import Fft, PowerSpectra, cls -cls -nfft=2048 -print('nfft:',nfft) -#print(nfft) -nchannels = 2 - -t = np.linspace(0,1,nfft+1) -# print(t) -x1 = (np.cos(4*np.pi*t[:-1])+3.2*np.sin(6*np.pi*t[:-1]))[:,np.newaxis]+10 -x = np.vstack([x1.T]*nchannels).T -# Using transpose to get the strides right -x = np.random.randn(nchannels,nfft).T -print("strides: ",x.strides) -# x.strides = (8,nfft*8)x -# print("signal:",x) - -xms = np.sum(x**2,axis=0)/nfft -print('Total signal power time domain: ', xms) - -X = np.fft.rfft(x,axis=0) -# X =np.fft.fft(x) -#X =np.fft.rfft(x) - -# print(X) -Xs = 2*X/nfft -Xs[np.where(np.abs(Xs) < 1e-10)] = 0 -Xs[0] /= np.sqrt(2) -Xs[-1] /= np.sqrt(2) -# print('single sided amplitude spectrum:\n',Xs) - - -power = Xs*np.conj(Xs)/2 - -# print('Frequency domain signal power\n', power) -print('Total signal power', np.sum(power,axis=0).real) - -pstest = PowerSpectra(nfft,nchannels) -ps = pstest.compute(x) - -fft = Fft(nfft,nchannels) -fft.fft(x) - -ps[np.where(np.abs(ps) < 1e-10)] = 0+0j - -print('our ps: \n' , ps) - -print('Our total signal power: ',np.sum(ps,axis=0).real) diff --git a/test/test_aps.py b/test/test_aps.py new file mode 100644 index 0000000..ed86a17 --- /dev/null +++ b/test/test_aps.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Mon Jan 15 19:45:33 2018 + +@author: anne +""" +import numpy as np +from lasp import AvPowerSpectra, Window + +import matplotlib.pyplot as plt +# plt.close('all') +def test_aps1(): + + nfft = 16384 + fs = 48000 + tend = 10 + t = np.linspace(0, (tend*fs)//fs, int(fs*tend), endpoint=False) + + w = Window.WindowType.Hann + # w = Window.WindowType.Rectangular + aps = AvPowerSpectra(nfft, w, 50, -1) + + sig = np.random.randn(int(fs*tend)) + + cps = aps.compute(sig) + + pow1 = np.sum(sig**2)/sig.size + pow2 = np.sum((cps[:,0,0]).real) + + # Check for Parseval + assert np.isclose(pow2 - pow1,0, atol=1e-2) + +def test_aps2(): + """ + Test whether we are able to estimate a simple transfer function of a gain + of 2 between two channels + """ + + nfft = 16384 + fs = 48000 + tend = 10 + t = np.linspace(0, (tend*fs)//fs, int(fs*tend), endpoint=False) + + # w = Window.WindowType.Hann + w = Window.WindowType.Rectangular + aps = AvPowerSpectra(nfft, w, 50, -1) + + sig1 = np.random.randn(int(fs*tend)) + sig2 = 2*sig1 + + sig = np.concatenate((sig1[None,:], sig2[None,:])).T + + cps = aps.compute(sig) + + H = cps[:,0,1]/cps[:,0,0] + + # Check if transfer function is obtained + assert np.all(np.isclose(H,2)) + +if __name__ == '__main__': + test_aps1() diff --git a/test/test_bars.py b/test/test_bars.py deleted file mode 100644 index 0dbc824..0000000 --- a/test/test_bars.py +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -import sys -from lasp.plot import BarScene -from PySide import QtGui -from PySide.QtCore import QTimer -from lasp.lasp_gui_tools import Branding, ASCEEColors -import numpy as np -import PySide.QtOpenGL as gl - - -def main(): - app = QtGui.QApplication(sys.argv) # A new instance of QApplication - app.setFont(Branding.font()) - pix = QtGui.QPixmap(':img/img/lasp_logo_640.png') - splash = QtGui.QSplashScreen(pixmap=pix) - splash.show() - mw = QtGui.QGraphicsView() - glwidget = gl.QGLWidget() - mw.setViewport(glwidget) - bs = BarScene(None, np.array([10, 20, 300]), 2, ylim=(0, 1)) - mw.setScene(bs) - - bs.set_ydata(np.array([[.1, .2], - [.7, .8], - [.9, 1]])) - - # timer = QTimer. - print(ASCEEColors.bggreen.getRgb()) - mw.show() # Show the form - splash.finish(mw) - app.exec_() # and execute the app - - -if __name__ == '__main__': - main() # run the main function diff --git a/test/test_bf.c b/test/test_bf.c deleted file mode 100644 index 5375747..0000000 --- a/test/test_bf.c +++ /dev/null @@ -1,46 +0,0 @@ -// test_bf.c -// -// Author: J.A. de Jong -ASCEE -// -// Description: -// -////////////////////////////////////////////////////////////////////// -#include "lasp_mat.h" - -int main() { - - iVARTRACE(15,getTracerLevel()); - /* vd vec1 = vd_alloc(3); */ - /* vd_set(&vec1,2); */ - - /* vd vec2 = vd_alloc(3); */ - /* vd_set(&vec2,3); */ - - /* print_vd(&vec1); */ - - /* vd res = vd_alloc(3); */ - /* d_elem_prod_d(res.data,vec1.data,vec2.data,3); */ - - /* print_vd(&res); */ - - - vc vc1 = vc_alloc(3); - vc_set(&vc1,2+2I); - print_vc(&vc1); - - vc vc2 = vc_alloc(3); - vc_set(&vc2,2-2I); - setvecval(&vc2,0,10); - print_vc(&vc2); - - - vc res2 = vc_alloc(3); - c_hadamard(res2._data,vc1._data,vc2._data,3); - - print_vc(&res2); - - - -} - -////////////////////////////////////////////////////////////////////// diff --git a/test/test_biquadbank.py b/test/test_biquadbank.py new file mode 100644 index 0000000..a920610 --- /dev/null +++ b/test/test_biquadbank.py @@ -0,0 +1,41 @@ +#!/usr/bin/python3 +import numpy as np +from lasp import SeriesBiquad, AvPowerSpectra +from lasp.filter import SPLFilterDesigner + +import matplotlib.pyplot as plt +from scipy.signal import sosfreqz +# plt.close('all') + +# def test_cppslm2(): +# """ +# Generate a sine wave, now A-weighted +# """ +fs = 48000 +omg = 2*np.pi*1000 + +filt = SPLFilterDesigner(fs).A_Sos_design() + +bq = SeriesBiquad(filt.flatten()) + +tend=10 +t = np.linspace(0, int((tend*fs)//fs), int(tend*fs), endpoint=False) + +in_ = np.random.randn(t.size) +out = bq.filter(in_) + + +from scipy.signal import welch +nfft = 48000 +freq, H1 = welch(out[:,0], + # scaling + fs=fs,nfft=nfft) + +freq, H2 = welch(in_, + # scaling + fs=fs,nfft=nfft) +# plt.figure() +plt.semilogx(freq,10*np.log10(np.abs(H1/H2))) + +omg, H_ex = sosfreqz(filt) +plt.semilogx(omg/(2*np.pi)*fs, 20*np.log10(np.abs(H_ex)+1e-80)) diff --git a/test/test_cppslm.py b/test/test_cppslm.py new file mode 100644 index 0000000..5890b8d --- /dev/null +++ b/test/test_cppslm.py @@ -0,0 +1,92 @@ +#!/usr/bin/python3 +import numpy as np +from lasp import cppSLM +from lasp.filter import SPLFilterDesigner +import matplotlib.pyplot as plt + +def test_cppslm1(): + """ + Generate a sine wave + """ + fs = 48000 + omg = 2*np.pi*1000 + + slm = cppSLM.fromBiquads(fs, 2e-5, 1, 0.125, [1.,0,0,1,0,0]) + + t = np.linspace(0, 10, 10*fs, endpoint=False) + + # Input signal with an rms of 1 Pa + in_ = np.sin(omg*t)*np.sqrt(2) + + # Compute overall RMS + rms = np.sqrt(np.sum(in_**2)/in_.size) + # Compute overall level + level = 20*np.log10(rms/2e-5) + + # Output of SLM + out = slm.run(in_) + + # Output of SLM should be close to theoretical + # level, at least for reasonable time constants + # (Fast, Slow etc) + assert(np.isclose(out[-1,0], level)) + + +def test_cppslm2(): + """ + Generate a sine wave, now A-weighted + """ + fs = 48000 + omg = 2*np.pi*1000 + + filt = SPLFilterDesigner(fs).A_Sos_design() + slm = cppSLM.fromBiquads(fs, 2e-5, 0, 0.125, filt.flatten(), [1.,0,0,1,0,0]) + + t = np.linspace(0, 10, 10*fs, endpoint=False) + + # Input signal with an rms of 1 Pa + in_ = np.sin(omg*t) *np.sqrt(2) + + # Compute overall RMS + rms = np.sqrt(np.sum(in_**2)/in_.size) + # Compute overall level + level = 20*np.log10(rms/2e-5) + + # Output of SLM + out = slm.run(in_) + + # Output of SLM should be close to theoretical + # level, at least for reasonable time constants + # (Fast, Slow etc) + assert np.isclose(out[-1,0], level, atol=1e-2) + +def test_cppslm3(): + fs = 48000 + omg = 2*np.pi*1000 + + filt = SPLFilterDesigner(fs).A_Sos_design() + slm = cppSLM.fromBiquads(fs, 2e-5, 0, 0.125, filt.flatten(), [1.,0,0,1,0,0]) + t = np.linspace(0, 10, 10*fs, endpoint=False) + + in_ = 10*np.sin(omg*t) * np.sqrt(2)+np.random.randn() + # Compute overall RMS + rms = np.sqrt(np.sum(in_**2)/in_.size) + # Compute overall level + level = 20*np.log10(rms/2e-5) + + + # Output of SLM + out = slm.run(in_) + + Lpeak = 20*np.log10(np.max(np.abs(in_)/2e-5)) + Lpeak + slm.Lpeak() + assert np.isclose(out[-1,0], slm.Leq()[0][0], atol=1e-2) + assert np.isclose(Lpeak, slm.Lpeak()[0][0], atol=2e0) + + + +if __name__ == '__main__': + test_cppslm1() + test_cppslm2() + test_cppslm3() diff --git a/test/test_fft.c b/test/test_fft.c deleted file mode 100644 index 2cb5311..0000000 --- a/test/test_fft.c +++ /dev/null @@ -1,33 +0,0 @@ -// test_bf.c -// -// Author: J.A. de Jong -ASCEE -// -// Description: -// -////////////////////////////////////////////////////////////////////// - -#include "lasp_fft.h" -#include "lasp_tracer.h" - - -int main() { - - setTracerLevel(0); - - iVARTRACE(15,getTracerLevel()); - - Fft* fft = Fft_create(100000); - - /* Fft_fft(fft,NULL,NULL); */ - - Fft_free(fft); - - return 0; -} - - - - -////////////////////////////////////////////////////////////////////// - - diff --git a/test/test_fft.py b/test/test_fft.py new file mode 100644 index 0000000..2f3b087 --- /dev/null +++ b/test/test_fft.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Mon Jan 15 19:45:33 2018 + +@author: anne +""" +import numpy as np +from lasp import Fft, getFreq + +def test_forward_fft(): + """ + Test that our FFT implementation equals Numpy's rfft implementation + """ + nfft = 2048 + t = np.linspace(0, 1.0, nfft, endpoint=False) + freq = 10 + omg = 2*np.pi*freq + sig = np.cos(omg*t)+10 + sig = np.random.randn(nfft) + fft_lasp = Fft(nfft) + + res_lasp = fft_lasp.fft(sig)[:,0] + res_npy = np.fft.rfft(sig) + assert(np.isclose(np.linalg.norm(res_lasp- res_npy), 0)) + +def test_backward_fft(): + """ + Test that our backward FFT implementation equals Numpy's rfft implementation + """ + nfft = 2048 + freq = getFreq(nfft, nfft) + + # Sig = np.zeros(nfft//2+1, dtype=complex) + Sigr = np.random.randn(nfft//2+1) + Sigi = np.random.randn(nfft//2+1) + + Sig = Sigr + 1j*Sigi + + fft_lasp = Fft(nfft) + sig_lasp = fft_lasp.ifft(Sig)[:,0] + sig_py = np.fft.irfft(Sig) + + assert(np.isclose(np.linalg.norm(sig_py- sig_lasp), 0)) + +if __name__ == '__main__': + test_forward_fft() + # test_backward_fft()() \ No newline at end of file diff --git a/test/test_math.c b/test/test_math.c deleted file mode 100644 index 404eeac..0000000 --- a/test/test_math.c +++ /dev/null @@ -1,50 +0,0 @@ -// test_bf.c -// -// Author: J.A. de Jong -ASCEE -// -// Description: -// -////////////////////////////////////////////////////////////////////// - -#include "lasp_fft.h" -#include "lasp_mat.h" -#include "lasp_tracer.h" -#include "lasp_alg.h" - -int main() { - - iVARTRACE(15,getTracerLevel()); - - c a[5]; - c_set(a,1-I,5); - - /* print_vc(&a); */ - - vc b = vc_alloc(5); - vc_set(&b,2); - - - printf("b:\n"); - print_vc(&b); - - - vc c1 = vc_alloc(5); - /* vc_set(&c1,10); */ - /* c_add_to(c1.ptr,a.ptr,1,3); */ - c_hadamard(c1._data,a,b._data,5); - - - - printf("c1:\n"); - print_vc(&c1); - - vc_free(&b); - - - - return 0; -} - -////////////////////////////////////////////////////////////////////// - - diff --git a/test/test_ps.py b/test/test_ps.py new file mode 100644 index 0000000..d7aced2 --- /dev/null +++ b/test/test_ps.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Testing code for power spectra +""" +import numpy as np +from lasp import PowerSpectra, Window +# import matplotlib.pyplot as plt +# plt.close('all') + +def test_ps(): + """ + Check Parsevall for single-sided power spectra + """ + nfft = 2048 + t = np.linspace(0, 1.0, nfft, endpoint=False) + + ps = PowerSpectra(nfft, Window.WindowType.Rectangular) + + sig = np.random.randn(nfft) + freq = 4 + omg = 2*np.pi*freq + # sig = 8*np.cos(omg*t) + + + cps = ps.compute(sig) + + pow1 = np.sum(sig**2)/sig.size + pow2 = np.sum((cps[:,0,0]).real) + + # print(pow1) + # print(pow2) + # plt.plot(cps[:,0,0]) + assert np.isclose(pow2 - pow1,0, atol=1e-1) + +if __name__ == '__main__': + test_ps() + diff --git a/test/test_rtaudio.py b/test/test_rtaudio.py deleted file mode 100644 index 582986c..0000000 --- a/test/test_rtaudio.py +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/python3 -import numpy as np -from lasp_rtaudio import RtAudio, SampleFormat, Format_SINT32, Format_FLOAT64 -import time - - -nframes = 0 -samplerate = 48000 -omg = 2*np.pi*1000 - -def mycallback(input_, nframes, streamtime): - t = np.linspace(streamtime, streamtime + nframes/samplerate, - nframes)[np.newaxis,:] - outp = 0.1*np.sin(omg*t) - return outp, 0 - -if __name__ == '__main__': - pa = RtAudio() - count = pa.getDeviceCount() - # dev = pa.getDeviceInfo(0) - for i in range(count): - dev = pa.getDeviceInfo(i) - print(dev) - - outputparams = {'deviceid': 0, 'nchannels': 1, 'firstchannel': 0} - pa.openStream(outputparams, None , Format_FLOAT64,samplerate, 512, mycallback) - pa.startStream() - - input() - - pa.stopStream() - pa.closeStream() - - - diff --git a/test/test_slm.py b/test/test_slm.py new file mode 100644 index 0000000..1596262 --- /dev/null +++ b/test/test_slm.py @@ -0,0 +1,37 @@ +#!/usr/bin/python3 +import numpy as np +from lasp import SLM + + + +nframes = 0 +samplerate = 48000 +omg = 2*np.pi*1000 + + + +# def mycallback(input_, nframes, streamtime): +# t = np.linspace(streamtime, streamtime + nframes/samplerate, +# nframes)[np.newaxis,:] +# outp = 0.1*np.sin(omg*t) +# return outp, 0 + +# if __name__ == '__main__': +# pa = RtAudio() +# count = pa.getDeviceCount() +# # dev = pa.getDeviceInfo(0) +# for i in range(count): +# dev = pa.getDeviceInfo(i) +# print(dev) + +# outputparams = {'deviceid': 0, 'nchannels': 1, 'firstchannel': 0} +# pa.openStream(outputparams, None , Format_FLOAT64,samplerate, 512, mycallback) +# pa.startStream() + +# input() + +# pa.stopStream() +# pa.closeStream() + + + diff --git a/test/test_uldaq.cpp b/test/test_uldaq.cpp deleted file mode 100644 index b72b8fb..0000000 --- a/test/test_uldaq.cpp +++ /dev/null @@ -1,89 +0,0 @@ -#include "lasp_cppdaq.h" -#include -#include -#include -#include -using std::cout; -using std::cerr; -using std::endl; - - -int main() { - - /* boolvec inChannels = {true, false, false, false}; */ - auto devinfos = Daq::getDeviceInfo(); - DeviceInfo devinfo; - us i; - bool found = false; - for(i=0;i inqueue; - SafeQueue outqueue; - - double totalTime = 5; - double t = 0; - double freq = 1000; - us nblocks = ((us) totalTime*samplerate/samplesPerBlock) + 10; - - for(us i=0;i(malloc(sizeof(double)*samplesPerBlock)); - for(us sample=0;samplestart(&inqueue, &outqueue); - - std::this_thread::sleep_for(std::chrono::seconds((int) totalTime)); - - daq->stop(); - - while(!inqueue.empty()) { - double* buf = (double*) inqueue.dequeue(); - for(us i=0;ineninchannels();ch++) { - cout << buf[ch*samplesPerBlock+i] << " "; - } - cout << endl; - } - free(buf); - } - while(!outqueue.empty()){ - void* dat = outqueue.dequeue(); - free(dat); - } - - return 0; -} - - diff --git a/test/test_workers.c b/test/test_workers.c deleted file mode 100644 index fa33a3b..0000000 --- a/test/test_workers.c +++ /dev/null @@ -1,79 +0,0 @@ -// test_bf.c -// -// Author: J.A. de Jong -ASCEE -// -// Description: -// -////////////////////////////////////////////////////////////////////// -#include "lasp_config.h" -#include "lasp_tracer.h" -#include "lasp_assert.h" -#include - -#ifdef LASP_PARALLEL -#include "lasp_worker.h" -#include "lasp_mq.h" - -static void* walloc(void*); -static int worker(void*,void*); -static void wfree(void*); -#endif // LASP_PARALLEL - - -int main() { - - fsTRACE(15); - - iVARTRACE(15,getTracerLevel()); - -#ifdef LASP_PARALLEL - us njobs = 4; - JobQueue* jq = JobQueue_alloc(njobs); - dbgassert(jq,NULLPTRDEREF); - - Workers* w = Workers_create(njobs, - jq, - walloc, - worker, - wfree, - (void*) 101); - dbgassert(jq,NULLPTRDEREF); - - for(us i=0; i< njobs; i++) { - iVARTRACE(15,i); - JobQueue_push(jq,(void*) i+1); - } - - JobQueue_wait_alldone(jq); - Workers_free(w); - JobQueue_free(jq); - -#endif // LASP_PARALLEL - return 0; -} -#ifdef LASP_PARALLEL -static void* walloc(void* data) { - TRACE(15,"WALLOC"); - uVARTRACE(15,(us) data); - return (void*) 1; -} - -static int worker(void* w_data,void* tj) { - - TRACE(15,"worker"); - - sleep(4); - - return 0; - -} -static void wfree(void* w_data) { - TRACE(15,"wfree"); -} - -#endif // LASP_PARALLEL - - -////////////////////////////////////////////////////////////////////// - - diff --git a/third_party/DebugTrace-cpp b/third_party/DebugTrace-cpp new file mode 160000 index 0000000..9b143ea --- /dev/null +++ b/third_party/DebugTrace-cpp @@ -0,0 +1 @@ +Subproject commit 9b143ea40a34d6268d671ea54cd0ba80396b6363 diff --git a/third_party/armadillo-code b/third_party/armadillo-code new file mode 160000 index 0000000..b572743 --- /dev/null +++ b/third_party/armadillo-code @@ -0,0 +1 @@ +Subproject commit b5727433d342afca53aca0ee16ecf1caaa661821 diff --git a/third_party/boost/core b/third_party/boost/core new file mode 160000 index 0000000..b407b5d --- /dev/null +++ b/third_party/boost/core @@ -0,0 +1 @@ +Subproject commit b407b5d87df30f75ca501c1b6f2930c0913d4ca7 diff --git a/third_party/boost/lockfree b/third_party/boost/lockfree new file mode 160000 index 0000000..fdd4d06 --- /dev/null +++ b/third_party/boost/lockfree @@ -0,0 +1 @@ +Subproject commit fdd4d0632dd0904f6e9c656c45397fe8ef985bc9 diff --git a/third_party/carma b/third_party/carma new file mode 160000 index 0000000..5673bc7 --- /dev/null +++ b/third_party/carma @@ -0,0 +1 @@ +Subproject commit 5673bc713e9515a00fba0c7378375fc075bdc693 diff --git a/third_party/gsl-lite b/third_party/gsl-lite new file mode 160000 index 0000000..4720a29 --- /dev/null +++ b/third_party/gsl-lite @@ -0,0 +1 @@ +Subproject commit 4720a2980a30da085b4ddb4a0ea2a71af7351a48 diff --git a/third_party/rtaudio b/third_party/rtaudio new file mode 160000 index 0000000..46b01b5 --- /dev/null +++ b/third_party/rtaudio @@ -0,0 +1 @@ +Subproject commit 46b01b5b134f33d8ddc3dab76829d4b1350e0522 diff --git a/third_party/thread-pool b/third_party/thread-pool new file mode 160000 index 0000000..67fad04 --- /dev/null +++ b/third_party/thread-pool @@ -0,0 +1 @@ +Subproject commit 67fad04348b91cf93bdfad7495d298f54825602c diff --git a/third_party/tomlplusplus b/third_party/tomlplusplus new file mode 160000 index 0000000..b1ecdf0 --- /dev/null +++ b/third_party/tomlplusplus @@ -0,0 +1 @@ +Subproject commit b1ecdf0ed86faeeaebe13d0db16ad04c20519527 diff --git a/third_party/uldaq b/third_party/uldaq new file mode 160000 index 0000000..1d84041 --- /dev/null +++ b/third_party/uldaq @@ -0,0 +1 @@ +Subproject commit 1d8404159c0fb6d2665461b80acca5bbef5c610a