Compare commits
62 Commits
Author | SHA1 | Date | |
---|---|---|---|
5a051d21a1 | |||
6b0442fe90 | |||
e542f805e9 | |||
d28875cdcc | |||
f6ea790071 | |||
7cd3dcffa8 | |||
cdc3ffb84a | |||
838a0f7cc1 | |||
bf5d006aef | |||
a443be6e39 | |||
27da426d66 | |||
fa51d6e81c | |||
832302bba2 | |||
dd3aa5a0d6 | |||
a1781fa66c | |||
1cc6b9106d | |||
9bcc2e4173 | |||
c8a4ded750 | |||
c7f8ac7122 | |||
afffa0b2ca | |||
a1802686d1 | |||
96b3fd5371 | |||
74bfdef921 | |||
e3bcfa30ce | |||
41e748c2f5 | |||
d24b5fb00b | |||
92fb5c1d76 | |||
35dc6885aa | |||
b10564dc49 | |||
3738012c3e | |||
a91640cd8d | |||
0bf621e45c | |||
1f7deca3fd | |||
1fb98412b2 | |||
d50dd35745 | |||
1765042d20 | |||
46d1eda94d | |||
3005f17400 | |||
33439354f8 | |||
da023273d8 | |||
84db689e56 | |||
83c7aa6ade | |||
3c16e33453 | |||
e973f14884 | |||
e24cac2805 | |||
d0d494fcb2 | |||
15cd62baf8 | |||
ab080910fc | |||
6799ee9287 | |||
f9cf059c90 | |||
3ec15ec645 | |||
48d262fbf0 | |||
204e431d79 | |||
bf06402b11 | |||
26eef040a4 | |||
b61e836f35 | |||
0841dbd73b | |||
5e8e40db7a | |||
3b2f2f7c41 | |||
878da3369b | |||
e9f500d460 | |||
6bda124196 |
@ -12,7 +12,7 @@ jobs:
|
|||||||
- lasp_dist:/dist
|
- lasp_dist:/dist
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Cleanup old dist files and copy new to /dist dir
|
- name: Cleanup old dist files and copy new to /dist dir
|
||||||
run: |-
|
run: |-
|
||||||
rm /dist/*
|
rm -f /dist/*
|
||||||
cp -v dist/* /dist
|
cp -v dist/* /dist
|
||||||
|
|
||||||
Release-Ubuntu:
|
Release-Ubuntu:
|
||||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -21,3 +21,6 @@ acme_log.log
|
|||||||
.venv
|
.venv
|
||||||
.py-build-cmake_cache
|
.py-build-cmake_cache
|
||||||
cpp_src/lasp_config.h
|
cpp_src/lasp_config.h
|
||||||
|
.cache
|
||||||
|
.vscode
|
||||||
|
build
|
||||||
|
@ -1,17 +1,28 @@
|
|||||||
cmake_minimum_required(VERSION 3.16)
|
cmake_minimum_required(VERSION 3.16)
|
||||||
project(LASP LANGUAGES C CXX VERSION 1.1)
|
project(LASP LANGUAGES C CXX VERSION 1.6.3)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED)
|
set(CMAKE_CXX_STANDARD_REQUIRED)
|
||||||
|
|
||||||
option(LASP_DOUBLE_PRECISION "Compile as double precision floating point" ON)
|
# Experimental: support for Raspberry Pi (64-bit architectures)
|
||||||
|
if(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "aarch64")
|
||||||
|
set(RPI TRUE)
|
||||||
|
else()
|
||||||
|
set(RPI FALSE)
|
||||||
|
endif()
|
||||||
|
|
||||||
# Setting defaults for PortAudio and RtAudio backend, depending on Linux /
|
# Setting defaults for PortAudio and RtAudio backend, depending on Linux /
|
||||||
# Windows.
|
# Windows.
|
||||||
|
set(DEFAULT_DOUBLE_PRECISION ON)
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
set(DEFAULT_RTAUDIO OFF)
|
set(DEFAULT_RTAUDIO OFF)
|
||||||
set(DEFAULT_PORTAUDIO ON)
|
set(DEFAULT_PORTAUDIO ON)
|
||||||
set(DEFAULT_ULDAQ OFF)
|
set(DEFAULT_ULDAQ OFF)
|
||||||
|
elseif(${RPI})
|
||||||
|
set(DEFAULT_RTAUDIO OFF)
|
||||||
|
set(DEFAULT_PORTAUDIO ON)
|
||||||
|
set(DEFAULT_ULDAQ OFF)
|
||||||
|
set(DEFAULT_DOUBLE_PRECISION OFF)
|
||||||
else()
|
else()
|
||||||
set(DEFAULT_RTAUDIO OFF)
|
set(DEFAULT_RTAUDIO OFF)
|
||||||
set(DEFAULT_PORTAUDIO ON)
|
set(DEFAULT_PORTAUDIO ON)
|
||||||
@ -19,6 +30,7 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
||||||
|
option(LASP_DOUBLE_PRECISION "Compile as double precision floating point" ${DEFAULT_DOUBLE_PRECISION})
|
||||||
option(LASP_HAS_RTAUDIO "Compile with RtAudio Daq backend" ${DEFAULT_RTAUDIO})
|
option(LASP_HAS_RTAUDIO "Compile with RtAudio Daq backend" ${DEFAULT_RTAUDIO})
|
||||||
option(LASP_HAS_PORTAUDIO "Compile with PortAudio Daq backend" ${DEFAULT_PORTAUDIO})
|
option(LASP_HAS_PORTAUDIO "Compile with PortAudio Daq backend" ${DEFAULT_PORTAUDIO})
|
||||||
if(LASP_HAS_PORTAUDIO AND LASP_HAS_RTAUDIO)
|
if(LASP_HAS_PORTAUDIO AND LASP_HAS_RTAUDIO)
|
||||||
@ -44,7 +56,6 @@ cmake_policy(SET CMP0079 NEW)
|
|||||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
|
|
||||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake")
|
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake")
|
||||||
include_directories(/usr/include/python3.8)
|
|
||||||
include("BuildType")
|
include("BuildType")
|
||||||
include("QueryPythonForPybind11")
|
include("QueryPythonForPybind11")
|
||||||
|
|
||||||
@ -105,13 +116,20 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
# ###################################### Compilation flags
|
# ###################################### Compilation flags
|
||||||
set(CMAKE_C_FLAGS_RELEASE "-O3 -flto -mfpmath=sse -march=x86-64 -mtune=native \
|
set(CMAKE_C_FLAGS_RELEASE "-O3 -flto -mtune=native \
|
||||||
-fdata-sections -ffunction-sections -fomit-frame-pointer -finline-functions")
|
-fdata-sections -ffunction-sections -fomit-frame-pointer -finline-functions")
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-type-limits -Werror=return-type")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-type-limits -Werror=return-type")
|
||||||
|
|
||||||
|
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -flto -mtune=native \
|
||||||
|
-fdata-sections -ffunction-sections -fomit-frame-pointer -finline-functions")
|
||||||
|
|
||||||
|
if(NOT ${RPI})
|
||||||
|
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -mfpmath=sse -march=x86-64")
|
||||||
|
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -mfpmath=sse -march=x86-64")
|
||||||
|
endif()
|
||||||
|
set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g -Wall ")
|
||||||
|
|
||||||
# ############################# End compilation flags
|
# ############################# End compilation flags
|
||||||
include_directories(/usr/lib/python3.10/site-packages/numpy/core/include)
|
|
||||||
|
|
||||||
# ####################################### End of user-adjustable variables section
|
# ####################################### End of user-adjustable variables section
|
||||||
include(OSSpecific)
|
include(OSSpecific)
|
||||||
|
112
README.md
112
README.md
@ -1,4 +1,5 @@
|
|||||||
# Library for Acoustic Signal Processing
|
Library for Acoustic Signal Processing
|
||||||
|
======================================
|
||||||
|
|
||||||
|
|
||||||
Welcome to LASP: Library for Acoustic Signal Processing. LASP is a C++ library
|
Welcome to LASP: Library for Acoustic Signal Processing. LASP is a C++ library
|
||||||
@ -43,19 +44,18 @@ in a sister repository [lasp-doc](https://code.ascee.nl/ascee/lasp-doc).
|
|||||||
|
|
||||||
If you have any question(s), please feel free to contact us: [email](info@ascee.nl).
|
If you have any question(s), please feel free to contact us: [email](info@ascee.nl).
|
||||||
|
|
||||||
|
|
||||||
# Installation - Linux (Ubuntu-based)
|
# Installation - Linux (Ubuntu-based)
|
||||||
|
|
||||||
## From wheel (recommended for non-developers)
|
## Prerequisites
|
||||||
|
|
||||||
### Prerequisites
|
|
||||||
|
|
||||||
Run the following on the command line to install all prerequisites on
|
Run the following on the command line to install all prerequisites on
|
||||||
Debian-based Linux:
|
Debian-based Linux, x86-64:
|
||||||
|
|
||||||
- `sudo apt install python3-pip libfftw3-3 libopenblas-base libusb-1.0-0
|
- `sudo apt install python3-pip libfftw3-3 libopenblas-base libusb-1.0-0 libpulse0`
|
||||||
libpulse0`
|
|
||||||
|
|
||||||
### Download and install LASP
|
|
||||||
|
## Installation from wheel (recommended for non-developers)
|
||||||
|
|
||||||
Go to: [LASP releases](https://code.ascee.nl/ASCEE/lasp/releases/latest/) and
|
Go to: [LASP releases](https://code.ascee.nl/ASCEE/lasp/releases/latest/) and
|
||||||
download the latest `.whl`. Then run:
|
download the latest `.whl`. Then run:
|
||||||
@ -84,35 +84,65 @@ If building RtAudio with the Jack Audio Connection Kit (JACK) backend, you will
|
|||||||
- `$ cd lasp`
|
- `$ cd lasp`
|
||||||
- `pip install -e .`
|
- `pip install -e .`
|
||||||
|
|
||||||
# Installation - (x86_64) Windows (with WinPython), build with MSYS2 (NOT YET UPDATED!!)
|
# Building and installation for Raspberry Pi (Raspberry Pi OS)
|
||||||
|
|
||||||
|
Run the following on the command line to install all prerequisites on
|
||||||
|
Raspberry Pi OS:
|
||||||
|
|
||||||
|
- `sudo apt install libfftw3-dev libopenblas64-dev libhdf5-dev libclalsadrv-dev`
|
||||||
|
|
||||||
|
In a virtualenv: install `build`
|
||||||
|
|
||||||
|
- `$ pip install build`
|
||||||
|
|
||||||
|
Then run:
|
||||||
|
|
||||||
|
- `$ git clone --recursive https://code.ascee.nl/ASCEE/lasp.git`
|
||||||
|
- `$ cd lasp`
|
||||||
|
- `$ pyproject-build`
|
||||||
|
|
||||||
|
Which will generate a `whl` in the `dist` folder, that is redistributable for Raspberry Pis that run Raspberry Pi OS.
|
||||||
|
|
||||||
|
When installing the `whl`, it appears that H5PY takes quite some time to install. To follow this process, run it it verbose mode.
|
||||||
|
|
||||||
|
|
||||||
|
# Installation - (x86_64) Windows (with WinPython), build with MSYS2
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
- Download and install [WinPython](https://winpython.github.io)
|
- Download and install [WinPython](https://winpython.github.io)
|
||||||
|
|
||||||
|
## From wheel
|
||||||
|
|
||||||
|
- Download latest wheel from [LASP releases](https://code.ascee.nl/ASCEE/lasp/releases/latest/) and
|
||||||
|
download the latest `.whl`. Then install with `pip`.
|
||||||
|
|
||||||
|
## From source
|
||||||
|
|
||||||
- Download and install [MSYS2](https://msys2.org). Make sure to install the
|
- Download and install [MSYS2](https://msys2.org). Make sure to install the
|
||||||
x86_64 version.
|
x86_64 version.
|
||||||
- Download and install [Git for Windows](https://git-scm.com)
|
|
||||||
|
|
||||||
- When unzipping WinPython, make sure to choose a proper and simple path, i.e.
|
- When unzipping WinPython, make sure to choose a proper and simple path, i.e.
|
||||||
C:\winpython
|
C:\winpython
|
||||||
- Append C:\winpython\ to the PATH environment variable.
|
- Download and install [Git for Windows](https://git-scm.com)
|
||||||
- Run Python and install Pybind11
|
- Open an MSYS2 **MINGW64** terminal, and install some tools we require:
|
||||||
- `python -m pip install pybind11`
|
- `$ pacman -S git`
|
||||||
|
- Create a new virtualenv:
|
||||||
|
- `$ /c/winpython/<py-distr-dir>/python.exe -m venv venv`
|
||||||
- Open a msys2 **MINGW64** terminal. And run:
|
- Add the venv-python to the path (eases a lot of commands)
|
||||||
|
- `$ export PATH=$PATH:~/venv/Scripts`
|
||||||
- `pacman -S git`
|
- Install `build`:
|
||||||
|
- `$ pip install build`
|
||||||
- Then clone the LASP repo:
|
- Clone LASP:
|
||||||
|
- `$ git clone --recurse-submodules https://code.ascee.nl/ascee/lasp && cd lasp`
|
||||||
- `git clone https://code.ascee.nl/ascee/lasp`
|
- If run for the first time, we have to install the libraries we depend on in
|
||||||
- `cd lasp`
|
MSYS2 (this only has to be done on a fresh MSYS2 installation):
|
||||||
|
- `$ scripts/install_msys2_builddeps.sh`
|
||||||
- Configure MSYS2 further, and run cmake:
|
- Copy over required DLL's to be included in distribution:
|
||||||
|
- `scripts/copy_windows_dlls.sh`
|
||||||
- `scripts/install_msys2_buiddeps.sh`
|
- And... build!
|
||||||
- `scripts/configur_cmake_msys2.sh`
|
- `pyproject-build`
|
||||||
|
- Lastly: the generated wheel can be installed in the current virtualenv:
|
||||||
|
- `pip install dist/lasp*.whl`
|
||||||
|
|
||||||
|
|
||||||
# Documentation
|
# Documentation
|
||||||
@ -121,7 +151,7 @@ If building RtAudio with the Jack Audio Connection Kit (JACK) backend, you will
|
|||||||
|
|
||||||
[Online LASP documentation](https://lasp.ascee.nl/).
|
[Online LASP documentation](https://lasp.ascee.nl/).
|
||||||
|
|
||||||
## In directory
|
## In directory (Linux/Debian)
|
||||||
|
|
||||||
`$ sudo apt install doxygen graphviz`
|
`$ sudo apt install doxygen graphviz`
|
||||||
`$ pip install doxypypy`
|
`$ pip install doxypypy`
|
||||||
@ -138,3 +168,25 @@ This will build the documentation. It can be read by:
|
|||||||
|
|
||||||
- See examples directories for IPython notebooks.
|
- See examples directories for IPython notebooks.
|
||||||
- Please refer to the [documentation](https://lasp.ascee.nl/) for features.
|
- Please refer to the [documentation](https://lasp.ascee.nl/) for features.
|
||||||
|
|
||||||
|
|
||||||
|
# Development docs
|
||||||
|
|
||||||
|
## Bumping version number
|
||||||
|
|
||||||
|
When bumping the version number, please update the number in
|
||||||
|
|
||||||
|
- `pyproject.toml`
|
||||||
|
- `CMakeLists.txt`
|
||||||
|
|
||||||
|
Then, create a commit with tag `vX.X.X`, and push it.
|
||||||
|
|
||||||
|
## Updating to latest version (editable mode)
|
||||||
|
|
||||||
|
When updating to the latest version of LASP in editable mode:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
- $ git pull
|
||||||
|
- $ git submodule update
|
||||||
|
- $ pip install -e . -v
|
||||||
|
```
|
@ -2,14 +2,27 @@
|
|||||||
if(LASP_HAS_PORTAUDIO)
|
if(LASP_HAS_PORTAUDIO)
|
||||||
message("Building with Portaudio backend")
|
message("Building with Portaudio backend")
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
|
set(PA_USE_ALSA FALSE CACHE BOOL "Build PortAudio with ALSA backend")
|
||||||
|
set(PA_USE_ASIO TRUE CACHE BOOL "Build PortAudio with ASIO backend")
|
||||||
|
set(PA_USE_DS FALSE CACHE BOOL "Build PortAudio with Directsound backend")
|
||||||
|
set(PA_USE_WMME FALSE CACHE BOOL "Build PortAudio with WMME backend")
|
||||||
|
set(PA_USE_WDMKS FALSE CACHE BOOL "Build PortAudio with WDMKS backend")
|
||||||
else()
|
else()
|
||||||
|
# Unix
|
||||||
set(PA_USE_ALSA TRUE CACHE BOOL "Build PortAudio with ALSA backend")
|
set(PA_USE_ALSA TRUE CACHE BOOL "Build PortAudio with ALSA backend")
|
||||||
set(PA_USE_JACK FALSE CACHE BOOL "Build PortAudio with Jack backend")
|
set(PA_USE_JACK FALSE CACHE BOOL "Build PortAudio with Jack backend")
|
||||||
set(PA_USE_PULSEAUDIO FALSE CACHE BOOL "Build PortAudio with PulseAudio backend")
|
set(PA_USE_PULSEAUDIO FALSE CACHE BOOL "Build PortAudio with PulseAudio backend")
|
||||||
# set(PA_ALSA_DYNAMIC FALSE CACHE BOOL "Build static library of ALSA")
|
|
||||||
set(PA_BUILD_SHARED_LIBS FALSE CACHE BOOL "Build static library")
|
set(PA_BUILD_SHARED_LIBS FALSE CACHE BOOL "Build static library")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_subdirectory(third_party/portaudio)
|
add_subdirectory(third_party/portaudio)
|
||||||
include_directories(third_party/portaudio/include)
|
include_directories(third_party/portaudio/include)
|
||||||
link_directories(third_party/portaudio)
|
link_directories(third_party/portaudio)
|
||||||
|
|
||||||
|
if(PA_USE_ALSA)
|
||||||
|
add_definitions(-DLASP_HAS_PA_ALSA=1)
|
||||||
|
else()
|
||||||
|
add_definitions(-DLASP_HAS_PA_ALSA=0)
|
||||||
|
endif()
|
||||||
|
|
||||||
endif()
|
endif()
|
||||||
|
@ -38,18 +38,26 @@ pybind11_add_module(lasp_cpp MODULE lasp_cpp.cpp
|
|||||||
target_link_libraries(lasp_cpp PRIVATE lasp_device_lib lasp_dsp_lib
|
target_link_libraries(lasp_cpp PRIVATE lasp_device_lib lasp_dsp_lib
|
||||||
${OpenMP_CXX_LIBRARIES} ${LASP_FFT_LIBS} ${TARGET_OS_LINKLIBS})
|
${OpenMP_CXX_LIBRARIES} ${LASP_FFT_LIBS} ${TARGET_OS_LINKLIBS})
|
||||||
|
|
||||||
|
target_compile_definitions(lasp_cpp PRIVATE
|
||||||
|
MODULE_NAME=$<TARGET_FILE_BASE_NAME:lasp_cpp>
|
||||||
|
VERSION_INFO="${PY_FULL_VERSION}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Hide all symbols by default (including external libraries on Linux)
|
||||||
|
if(CMAKE_BUILD_TYPE STREQUAL "Release")
|
||||||
|
set_target_properties(lasp_cpp PROPERTIES
|
||||||
|
CXX_VISIBILITY_PRESET "hidden"
|
||||||
|
VISIBILITY_INLINES_HIDDEN true)
|
||||||
|
if (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||||
|
target_link_options(lasp_cpp PRIVATE "LINKER:--exclude-libs,ALL")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
if(DEFINED PY_BUILD_CMAKE_MODULE_NAME)
|
if(DEFINED PY_BUILD_CMAKE_MODULE_NAME)
|
||||||
# Install the Python module
|
# Install the Python module
|
||||||
install(TARGETS lasp_cpp
|
install(TARGETS lasp_cpp
|
||||||
EXCLUDE_FROM_ALL
|
EXCLUDE_FROM_ALL
|
||||||
COMPONENT python_modules
|
COMPONENT python_modules
|
||||||
DESTINATION ${PY_BUILD_CMAKE_MODULE_NAME})
|
DESTINATION ${PY_BUILD_CMAKE_MODULE_NAME})
|
||||||
# Install the debug file for the Python module (Windows only)
|
|
||||||
if (WIN32)
|
|
||||||
install(FILES $<TARGET_PDB_FILE:_add_module>
|
|
||||||
EXCLUDE_FROM_ALL
|
|
||||||
COMPONENT python_modules
|
|
||||||
DESTINATION ${PY_BUILD_CMAKE_MODULE_NAME}
|
|
||||||
OPTIONAL)
|
|
||||||
endif()
|
|
||||||
endif()
|
endif()
|
@ -1,4 +1,4 @@
|
|||||||
/* #define DEBUGTRACE_ENABLED */
|
// #define DEBUGTRACE_ENABLED
|
||||||
#include "debugtrace.hpp"
|
#include "debugtrace.hpp"
|
||||||
#include "lasp_daqconfig.h"
|
#include "lasp_daqconfig.h"
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
/* #define DEBUGTRACE_ENABLED */
|
// #define DEBUGTRACE_ENABLED
|
||||||
#include "lasp_indatahandler.h"
|
#include "lasp_indatahandler.h"
|
||||||
#include "debugtrace.hpp"
|
#include "debugtrace.hpp"
|
||||||
#include "lasp_streammgr.h"
|
#include "lasp_streammgr.h"
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
InDataHandler::InDataHandler(SmgrHandle mgr, const InCallbackType cb,
|
InDataHandler::InDataHandler(SmgrHandle mgr, const InCallbackType cb,
|
||||||
const InResetType resetfcn)
|
const ResetCallbackType resetfcn)
|
||||||
: _mgr(mgr), inCallback(cb), reset(resetfcn)
|
: _mgr(mgr), inCallback(cb), reset(resetfcn)
|
||||||
#if LASP_DEBUG == 1
|
#if LASP_DEBUG == 1
|
||||||
,
|
,
|
||||||
@ -29,20 +29,22 @@ void InDataHandler::start() {
|
|||||||
}
|
}
|
||||||
void InDataHandler::stop() {
|
void InDataHandler::stop() {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
checkRightThread();
|
// checkRightThread();
|
||||||
#if LASP_DEBUG == 1
|
#if LASP_DEBUG == 1
|
||||||
stopCalled = true;
|
stopCalled = true;
|
||||||
#endif
|
#endif
|
||||||
if (SmgrHandle handle = _mgr.lock()) {
|
if (SmgrHandle smgr = _mgr.lock()) {
|
||||||
handle->removeInDataHandler(*this);
|
smgr->removeInDataHandler(*this);
|
||||||
|
} else {
|
||||||
|
DEBUGTRACE_PRINT("No stream manager alive anymore!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
InDataHandler::~InDataHandler() {
|
InDataHandler::~InDataHandler() {
|
||||||
|
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
checkRightThread();
|
|
||||||
#if LASP_DEBUG == 1
|
#if LASP_DEBUG == 1
|
||||||
|
// checkRightThread();
|
||||||
if (!stopCalled) {
|
if (!stopCalled) {
|
||||||
std::cerr << "************ BUG: Stop function not called while arriving at "
|
std::cerr << "************ BUG: Stop function not called while arriving at "
|
||||||
"InDataHandler's destructor. Fix this by calling "
|
"InDataHandler's destructor. Fix this by calling "
|
||||||
|
@ -22,7 +22,7 @@ using InCallbackType = std::function<void(const DaqData &)>;
|
|||||||
/**
|
/**
|
||||||
* @brief Function definition for the reset callback.
|
* @brief Function definition for the reset callback.
|
||||||
*/
|
*/
|
||||||
using InResetType = std::function<void(const Daq *)>;
|
using ResetCallbackType = std::function<void(const Daq *)>;
|
||||||
|
|
||||||
class InDataHandler {
|
class InDataHandler {
|
||||||
|
|
||||||
@ -38,7 +38,7 @@ protected:
|
|||||||
public:
|
public:
|
||||||
~InDataHandler();
|
~InDataHandler();
|
||||||
const InCallbackType inCallback;
|
const InCallbackType inCallback;
|
||||||
const InResetType reset;
|
const ResetCallbackType reset;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief When constructed, the handler is added to the stream manager, which
|
* @brief When constructed, the handler is added to the stream manager, which
|
||||||
@ -50,7 +50,7 @@ public:
|
|||||||
* changes state.
|
* changes state.
|
||||||
*/
|
*/
|
||||||
InDataHandler(SmgrHandle mgr, InCallbackType cb,
|
InDataHandler(SmgrHandle mgr, InCallbackType cb,
|
||||||
InResetType resetfcn);
|
ResetCallbackType resetfcn);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Adds the current InDataHandler to the list of handlers in the
|
* @brief Adds the current InDataHandler to the list of handlers in the
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
/* #define DEBUGTRACE_ENABLED */
|
// #define DEBUGTRACE_ENABLED
|
||||||
#include "lasp_streammgr.h"
|
#include "lasp_streammgr.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
@ -8,12 +8,15 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
#include "debugtrace.hpp"
|
#include "debugtrace.hpp"
|
||||||
#include "lasp_biquadbank.h"
|
#include "lasp_biquadbank.h"
|
||||||
#include "lasp_indatahandler.h"
|
#include "lasp_indatahandler.h"
|
||||||
#include "lasp_thread.h"
|
#include "lasp_thread.h"
|
||||||
|
|
||||||
|
using namespace std::literals::chrono_literals;
|
||||||
|
|
||||||
using std::cerr;
|
using std::cerr;
|
||||||
using std::endl;
|
using std::endl;
|
||||||
using rte = std::runtime_error;
|
using rte = std::runtime_error;
|
||||||
@ -27,7 +30,7 @@ using rte = std::runtime_error;
|
|||||||
std::weak_ptr<StreamMgr> _mgr;
|
std::weak_ptr<StreamMgr> _mgr;
|
||||||
std::mutex _mgr_mutex;
|
std::mutex _mgr_mutex;
|
||||||
|
|
||||||
using Lck = std::scoped_lock<std::mutex>;
|
using Lck = std::scoped_lock<std::recursive_mutex>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The only way to obtain a stream manager, can only be called from the
|
* @brief The only way to obtain a stream manager, can only be called from the
|
||||||
@ -38,11 +41,11 @@ using Lck = std::scoped_lock<std::mutex>;
|
|||||||
SmgrHandle StreamMgr::getInstance() {
|
SmgrHandle StreamMgr::getInstance() {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
|
|
||||||
|
std::scoped_lock<std::mutex> lck(_mgr_mutex);
|
||||||
auto mgr = _mgr.lock();
|
auto mgr = _mgr.lock();
|
||||||
if (!mgr) {
|
if (!mgr) {
|
||||||
// Double Check Locking Pattern, if two threads would simultaneously
|
// Double Check Locking Pattern, if two threads would simultaneously
|
||||||
// instantiate the singleton instance.
|
// instantiate the singleton instance.
|
||||||
Lck lck(_mgr_mutex);
|
|
||||||
|
|
||||||
auto mgr = _mgr.lock();
|
auto mgr = _mgr.lock();
|
||||||
if (mgr) {
|
if (mgr) {
|
||||||
@ -84,37 +87,45 @@ void StreamMgr::rescanDAQDevices(bool background,
|
|||||||
std::function<void()> callback) {
|
std::function<void()> callback) {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
DEBUGTRACE_PRINT(background);
|
DEBUGTRACE_PRINT(background);
|
||||||
|
if (_scanningDevices) {
|
||||||
|
throw rte("A background device scan is already busy");
|
||||||
|
}
|
||||||
|
|
||||||
|
Lck lck(_mtx);
|
||||||
checkRightThread();
|
checkRightThread();
|
||||||
if (_inputStream || _outputStream) {
|
if (_inputStream || _outputStream) {
|
||||||
throw rte("Rescanning DAQ devices only possible when no stream is running");
|
throw rte("Rescanning DAQ devices only possible when no stream is running");
|
||||||
}
|
}
|
||||||
if (!_devices_mtx.try_lock()) {
|
|
||||||
throw rte("A background DAQ device scan is probably already running");
|
|
||||||
}
|
|
||||||
_devices_mtx.unlock();
|
|
||||||
|
|
||||||
std::scoped_lock lck(_devices_mtx);
|
|
||||||
_devices.clear();
|
_devices.clear();
|
||||||
if (!background) {
|
if (!background) {
|
||||||
|
_scanningDevices = true;
|
||||||
rescanDAQDevices_impl(callback);
|
rescanDAQDevices_impl(callback);
|
||||||
} else {
|
} else {
|
||||||
DEBUGTRACE_PRINT("Rescanning DAQ devices on different thread...");
|
DEBUGTRACE_PRINT("Rescanning DAQ devices on different thread...");
|
||||||
|
_scanningDevices = true;
|
||||||
_pool.push_task(&StreamMgr::rescanDAQDevices_impl, this, callback);
|
_pool.push_task(&StreamMgr::rescanDAQDevices_impl, this, callback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void StreamMgr::rescanDAQDevices_impl(std::function<void()> callback) {
|
void StreamMgr::rescanDAQDevices_impl(std::function<void()> callback) {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
std::scoped_lock lck(_devices_mtx);
|
assert(!_inputStream && !_outputStream);
|
||||||
|
Lck lck(_mtx);
|
||||||
|
// Alsa spits out annoying messages that are not useful
|
||||||
|
{
|
||||||
|
|
||||||
_devices = DeviceInfo::getDeviceInfo();
|
_devices = DeviceInfo::getDeviceInfo();
|
||||||
|
}
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_scanningDevices = false;
|
||||||
}
|
}
|
||||||
void StreamMgr::inCallback(const DaqData &data) {
|
void StreamMgr::inCallback(const DaqData &data) {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
|
|
||||||
std::scoped_lock<std::mutex> lck(_inDataHandler_mtx);
|
Lck lck(_mtx);
|
||||||
|
|
||||||
assert(_inputFilters.size() == data.nchannels);
|
assert(_inputFilters.size() == data.nchannels);
|
||||||
|
|
||||||
@ -139,12 +150,13 @@ void StreamMgr::inCallback(const DaqData &data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DEBUGTRACE_PRINT("Calling incallback for handlers (filtered)...");
|
||||||
for (auto &handler : _inDataHandlers) {
|
for (auto &handler : _inDataHandlers) {
|
||||||
handler->inCallback(input_filtered);
|
handler->inCallback(input_filtered);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
/// No input filters
|
/// No input filters
|
||||||
|
DEBUGTRACE_PRINT("Calling incallback for handlers...");
|
||||||
for (auto &handler : _inDataHandlers) {
|
for (auto &handler : _inDataHandlers) {
|
||||||
handler->inCallback(data);
|
handler->inCallback(data);
|
||||||
}
|
}
|
||||||
@ -155,8 +167,7 @@ void StreamMgr::setSiggen(std::shared_ptr<Siggen> siggen) {
|
|||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
checkRightThread();
|
checkRightThread();
|
||||||
|
|
||||||
std::scoped_lock<std::mutex> lck(_siggen_mtx);
|
Lck lck(_mtx);
|
||||||
|
|
||||||
// If not set to nullptr, and a stream is running, we update the signal
|
// If not set to nullptr, and a stream is running, we update the signal
|
||||||
// generator by resetting it.
|
// generator by resetting it.
|
||||||
if (isStreamRunningOK(StreamType::output) && siggen) {
|
if (isStreamRunningOK(StreamType::output) && siggen) {
|
||||||
@ -213,9 +224,9 @@ bool fillData(DaqData &data, const vd &signal) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
void StreamMgr::outCallback(DaqData &data) {
|
void StreamMgr::outCallback(DaqData &data) {
|
||||||
/* DEBUGTRACE_ENTER; */
|
DEBUGTRACE_ENTER;
|
||||||
|
|
||||||
std::scoped_lock<std::mutex> lck(_siggen_mtx);
|
Lck lck(_mtx);
|
||||||
|
|
||||||
if (_siggen) {
|
if (_siggen) {
|
||||||
vd signal = _siggen->genSignal(data.nframes);
|
vd signal = _siggen->genSignal(data.nframes);
|
||||||
@ -244,7 +255,17 @@ void StreamMgr::outCallback(DaqData &data) {
|
|||||||
|
|
||||||
StreamMgr::~StreamMgr() {
|
StreamMgr::~StreamMgr() {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
|
while (_scanningDevices) {
|
||||||
|
std::this_thread::sleep_for(10us);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if LASP_DEBUG == 1
|
||||||
|
{ // Careful, this lock needs to be released to make sure the streams can
|
||||||
|
// obtain a lock to the stream manager.
|
||||||
|
Lck lck(_mtx);
|
||||||
checkRightThread();
|
checkRightThread();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
// Stream manager now handled by shared pointer. Each indata handler gets a
|
// Stream manager now handled by shared pointer. Each indata handler gets a
|
||||||
// shared pointer to the stream manager, and stores a weak pointer to it.
|
// shared pointer to the stream manager, and stores a weak pointer to it.
|
||||||
// Hence, we do not have to do any cleanup here. It also makes sure that the
|
// Hence, we do not have to do any cleanup here. It also makes sure that the
|
||||||
@ -260,13 +281,21 @@ StreamMgr::~StreamMgr() {
|
|||||||
}
|
}
|
||||||
void StreamMgr::stopAllStreams() {
|
void StreamMgr::stopAllStreams() {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
|
{
|
||||||
|
Lck lck(_mtx);
|
||||||
checkRightThread();
|
checkRightThread();
|
||||||
|
}
|
||||||
|
// No lock here!
|
||||||
_inputStream.reset();
|
_inputStream.reset();
|
||||||
_outputStream.reset();
|
_outputStream.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void StreamMgr::startStream(const DaqConfiguration &config) {
|
void StreamMgr::startStream(const DaqConfiguration &config) {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
|
if (_scanningDevices) {
|
||||||
|
throw rte("DAQ device scan is busy. Cannot start stream.");
|
||||||
|
}
|
||||||
|
Lck lck(_mtx);
|
||||||
checkRightThread();
|
checkRightThread();
|
||||||
|
|
||||||
bool isInput = std::count_if(config.inchannel_config.cbegin(),
|
bool isInput = std::count_if(config.inchannel_config.cbegin(),
|
||||||
@ -278,8 +307,6 @@ void StreamMgr::startStream(const DaqConfiguration &config) {
|
|||||||
[](auto &i) { return i.enabled; }) > 0;
|
[](auto &i) { return i.enabled; }) > 0;
|
||||||
|
|
||||||
// Find the first device that matches with the configuration
|
// Find the first device that matches with the configuration
|
||||||
std::scoped_lock lck(_devices_mtx);
|
|
||||||
|
|
||||||
DeviceInfo *devinfo = nullptr;
|
DeviceInfo *devinfo = nullptr;
|
||||||
|
|
||||||
// Match configuration to a device in the list of devices
|
// Match configuration to a device in the list of devices
|
||||||
@ -404,37 +431,49 @@ void StreamMgr::startStream(const DaqConfiguration &config) {
|
|||||||
void StreamMgr::stopStream(const StreamType t) {
|
void StreamMgr::stopStream(const StreamType t) {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
checkRightThread();
|
checkRightThread();
|
||||||
|
bool resetHandlers = false;
|
||||||
|
std::unique_ptr<Daq> *streamToStop = nullptr;
|
||||||
|
|
||||||
|
{ // Mutex locked in this scope
|
||||||
|
Lck lck(_mtx);
|
||||||
if (t == StreamType::input) {
|
if (t == StreamType::input) {
|
||||||
if (!_inputStream) {
|
if (!_inputStream) {
|
||||||
throw rte("Input stream is not running");
|
throw rte("Input stream is not running");
|
||||||
}
|
}
|
||||||
/// Kills input stream
|
streamToStop = std::addressof(_inputStream);
|
||||||
_inputStream.reset();
|
resetHandlers = true;
|
||||||
/// Send reset to all in data handlers
|
|
||||||
for (auto &handler : _inDataHandlers) {
|
|
||||||
handler->reset(nullptr);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
/// t == output
|
/// t == output
|
||||||
|
|
||||||
/// Kill input stream in case that one is a duplex stream
|
/// Kill input stream in case that one is a duplex stream
|
||||||
if (_inputStream && _inputStream->duplexMode()) {
|
if (_inputStream && _inputStream->duplexMode()) {
|
||||||
_inputStream.reset();
|
streamToStop = std::addressof(_inputStream);
|
||||||
} else {
|
} else {
|
||||||
if (!_outputStream) {
|
if (!_outputStream) {
|
||||||
throw rte("Output stream is not running");
|
throw rte("Output stream is not running");
|
||||||
}
|
}
|
||||||
_outputStream.reset();
|
streamToStop = std::addressof(_outputStream);
|
||||||
} // end else
|
} // end else
|
||||||
}
|
}
|
||||||
|
} // End of mutex lock. When stopping stream, mutex should be unlocked.
|
||||||
|
|
||||||
|
// If we arrive here, we should have a stream to stop.
|
||||||
|
assert(streamToStop != nullptr);
|
||||||
|
streamToStop->reset();
|
||||||
|
|
||||||
|
/// Send reset to all in data handlers
|
||||||
|
if (resetHandlers) {
|
||||||
|
Lck lck(_mtx);
|
||||||
|
for (auto &handler : _inDataHandlers) {
|
||||||
|
handler->reset(nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void StreamMgr::addInDataHandler(InDataHandler *handler) {
|
void StreamMgr::addInDataHandler(InDataHandler *handler) {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
|
Lck lck(_mtx);
|
||||||
checkRightThread();
|
checkRightThread();
|
||||||
assert(handler);
|
assert(handler);
|
||||||
std::scoped_lock<std::mutex> lck(_inDataHandler_mtx);
|
|
||||||
handler->reset(_inputStream.get());
|
handler->reset(_inputStream.get());
|
||||||
|
|
||||||
if (std::find(_inDataHandlers.cbegin(), _inDataHandlers.cend(), handler) !=
|
if (std::find(_inDataHandlers.cbegin(), _inDataHandlers.cend(), handler) !=
|
||||||
@ -449,16 +488,17 @@ void StreamMgr::addInDataHandler(InDataHandler *handler) {
|
|||||||
|
|
||||||
void StreamMgr::removeInDataHandler(InDataHandler &handler) {
|
void StreamMgr::removeInDataHandler(InDataHandler &handler) {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
checkRightThread();
|
Lck lck(_mtx);
|
||||||
std::scoped_lock<std::mutex> lck(_inDataHandler_mtx);
|
// checkRightThread();
|
||||||
_inDataHandlers.remove(&handler);
|
_inDataHandlers.remove(&handler);
|
||||||
|
|
||||||
DEBUGTRACE_PRINT(_inDataHandlers.size());
|
DEBUGTRACE_PRINT(_inDataHandlers.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
Daq::StreamStatus StreamMgr::getStreamStatus(const StreamType type) const {
|
Daq::StreamStatus StreamMgr::getStreamStatus(const StreamType type) const {
|
||||||
/* DEBUGTRACE_ENTER; */
|
DEBUGTRACE_ENTER;
|
||||||
|
|
||||||
|
Lck lck(_mtx);
|
||||||
checkRightThread();
|
checkRightThread();
|
||||||
// Default constructor, says stream is not running, but also no errors
|
// Default constructor, says stream is not running, but also no errors
|
||||||
|
|
||||||
@ -471,6 +511,7 @@ Daq::StreamStatus StreamMgr::getStreamStatus(const StreamType type) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Daq *StreamMgr::getDaq(StreamType type) const {
|
const Daq *StreamMgr::getDaq(StreamType type) const {
|
||||||
|
Lck lck(_mtx);
|
||||||
checkRightThread();
|
checkRightThread();
|
||||||
|
|
||||||
if (type == StreamType::input) {
|
if (type == StreamType::input) {
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "lasp_daq.h"
|
|
||||||
#include "lasp_siggen.h"
|
|
||||||
#include "lasp_thread.h"
|
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
|
#include "lasp_daq.h"
|
||||||
|
#include "lasp_siggen.h"
|
||||||
|
#include "lasp_thread.h"
|
||||||
|
|
||||||
/** \addtogroup device
|
/** \addtogroup device
|
||||||
* @{
|
* @{
|
||||||
*/
|
*/
|
||||||
class StreamMgr;
|
class StreamMgr;
|
||||||
class InDataHandler;
|
class InDataHandler;
|
||||||
|
|
||||||
|
|
||||||
class SeriesBiquad;
|
class SeriesBiquad;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -25,12 +25,15 @@ class SeriesBiquad;
|
|||||||
* fact is asserted.
|
* fact is asserted.
|
||||||
*/
|
*/
|
||||||
class StreamMgr {
|
class StreamMgr {
|
||||||
|
mutable std::recursive_mutex _mtx;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Storage for streams.
|
* @brief Storage for streams.
|
||||||
*/
|
*/
|
||||||
std::unique_ptr<Daq> _inputStream, _outputStream;
|
std::unique_ptr<Daq> _inputStream, _outputStream;
|
||||||
|
|
||||||
|
std::atomic<bool> _scanningDevices{false};
|
||||||
|
|
||||||
GlobalThreadPool _pool;
|
GlobalThreadPool _pool;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -39,22 +42,18 @@ class StreamMgr {
|
|||||||
* thread-safety.
|
* thread-safety.
|
||||||
*/
|
*/
|
||||||
std::list<InDataHandler *> _inDataHandlers;
|
std::list<InDataHandler *> _inDataHandlers;
|
||||||
mutable std::mutex _inDataHandler_mtx;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Signal generator in use to generate output data. Currently
|
* @brief Signal generator in use to generate output data. Currently
|
||||||
* implemented as to generate the same data for all output channels.
|
* implemented as to generate the same data for all output channels.
|
||||||
*/
|
*/
|
||||||
std::shared_ptr<Siggen> _siggen;
|
std::shared_ptr<Siggen> _siggen;
|
||||||
std::mutex _siggen_mtx;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Filters on input stream. For example, a digital high pass filter.
|
* @brief Filters on input stream. For example, a digital high pass filter.
|
||||||
*/
|
*/
|
||||||
std::vector<std::unique_ptr<SeriesBiquad>> _inputFilters;
|
std::vector<std::unique_ptr<SeriesBiquad>> _inputFilters;
|
||||||
|
|
||||||
|
|
||||||
mutable std::recursive_mutex _devices_mtx;
|
|
||||||
/**
|
/**
|
||||||
* @brief Current storage for the device list
|
* @brief Current storage for the device list
|
||||||
*/
|
*/
|
||||||
@ -67,9 +66,7 @@ class StreamMgr {
|
|||||||
friend class InDataHandler;
|
friend class InDataHandler;
|
||||||
friend class Siggen;
|
friend class Siggen;
|
||||||
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
~StreamMgr();
|
~StreamMgr();
|
||||||
|
|
||||||
enum class StreamType : us {
|
enum class StreamType : us {
|
||||||
@ -100,9 +97,10 @@ class StreamMgr {
|
|||||||
* @return A copy of the internal stored list of devices
|
* @return A copy of the internal stored list of devices
|
||||||
*/
|
*/
|
||||||
DeviceInfoList getDeviceInfo() const {
|
DeviceInfoList getDeviceInfo() const {
|
||||||
std::scoped_lock lck(_devices_mtx);
|
std::scoped_lock lck(_mtx);
|
||||||
DeviceInfoList d2;
|
DeviceInfoList d2;
|
||||||
for (const auto &dev : _devices) {
|
for (const auto &dev : _devices) {
|
||||||
|
assert(dev != nullptr);
|
||||||
d2.push_back(dev->clone());
|
d2.push_back(dev->clone());
|
||||||
}
|
}
|
||||||
return d2;
|
return d2;
|
||||||
@ -118,8 +116,8 @@ class StreamMgr {
|
|||||||
* set to true, the function returns immediately.
|
* set to true, the function returns immediately.
|
||||||
* @param callback Function to call when complete.
|
* @param callback Function to call when complete.
|
||||||
*/
|
*/
|
||||||
void
|
void rescanDAQDevices(
|
||||||
rescanDAQDevices(bool background = false,
|
bool background = false,
|
||||||
std::function<void()> callback = std::function<void()>());
|
std::function<void()> callback = std::function<void()>());
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -197,7 +195,6 @@ private:
|
|||||||
void inCallback(const DaqData &data);
|
void inCallback(const DaqData &data);
|
||||||
void outCallback(DaqData &data);
|
void outCallback(DaqData &data);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Add an input data handler. The handler's inCallback() function is
|
* @brief Add an input data handler. The handler's inCallback() function is
|
||||||
* called with data when available. This function should *NOT* be called by
|
* called with data when available. This function should *NOT* be called by
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
/* #define DEBUGTRACE_ENABLED */
|
// #define DEBUGTRACE_ENABLED
|
||||||
#include "debugtrace.hpp"
|
#include "debugtrace.hpp"
|
||||||
#include "lasp_config.h"
|
#include "lasp_config.h"
|
||||||
|
|
||||||
@ -16,6 +16,33 @@ using std::endl;
|
|||||||
using std::string;
|
using std::string;
|
||||||
using std::to_string;
|
using std::to_string;
|
||||||
|
|
||||||
|
#if LASP_HAS_PA_ALSA
|
||||||
|
#include <alsa/asoundlib.h>
|
||||||
|
void empty_handler(const char *file, int line, const char *function, int err,
|
||||||
|
const char *fmt, ...) {
|
||||||
|
|
||||||
|
// cerr << "Test empty error handler...\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Temporarily set the ALSA eror handler to something that does nothing, to
|
||||||
|
// prevent ALSA from spitting out all kinds of misconfiguration errors.
|
||||||
|
class MuteErrHandler {
|
||||||
|
private:
|
||||||
|
snd_lib_error_handler_t _default_handler;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit MuteErrHandler() {
|
||||||
|
_default_handler = snd_lib_error;
|
||||||
|
snd_lib_error_set_handler(empty_handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
~MuteErrHandler() { snd_lib_error_set_handler(_default_handler); }
|
||||||
|
};
|
||||||
|
#else
|
||||||
|
// Does nothin in case of no ALSA
|
||||||
|
class MuteErrHandler {};
|
||||||
|
#endif
|
||||||
|
|
||||||
inline void throwIfError(PaError e) {
|
inline void throwIfError(PaError e) {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
if (e != paNoError) {
|
if (e != paNoError) {
|
||||||
@ -44,6 +71,7 @@ class OurPaDeviceInfo : public DeviceInfo {
|
|||||||
void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist) {
|
void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist) {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
bool shouldPaTerminate = false;
|
bool shouldPaTerminate = false;
|
||||||
|
MuteErrHandler guard;
|
||||||
try {
|
try {
|
||||||
PaError err = Pa_Initialize();
|
PaError err = Pa_Initialize();
|
||||||
/// PortAudio says that Pa_Terminate() should not be called whenever there
|
/// PortAudio says that Pa_Terminate() should not be called whenever there
|
||||||
@ -93,6 +121,7 @@ void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist) {
|
|||||||
d.api = portaudioALSAApi;
|
d.api = portaudioALSAApi;
|
||||||
break;
|
break;
|
||||||
case paASIO:
|
case paASIO:
|
||||||
|
hasDuplexMode = true;
|
||||||
d.api = portaudioASIOApi;
|
d.api = portaudioASIOApi;
|
||||||
break;
|
break;
|
||||||
case paDirectSound:
|
case paDirectSound:
|
||||||
@ -137,6 +166,9 @@ void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist) {
|
|||||||
d.ninchannels = deviceInfo->maxInputChannels;
|
d.ninchannels = deviceInfo->maxInputChannels;
|
||||||
d.noutchannels = deviceInfo->maxOutputChannels;
|
d.noutchannels = deviceInfo->maxOutputChannels;
|
||||||
|
|
||||||
|
// Duplex mode, only for ALSA devices
|
||||||
|
d.hasDuplexMode = hasDuplexMode;
|
||||||
|
|
||||||
devinfolist.push_back(std::make_unique<OurPaDeviceInfo>(d));
|
devinfolist.push_back(std::make_unique<OurPaDeviceInfo>(d));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -344,18 +376,12 @@ PortAudioDaq::PortAudioDaq(const OurPaDeviceInfo &devinfo_gen,
|
|||||||
void PortAudioDaq::start(InDaqCallback inCallback, OutDaqCallback outCallback) {
|
void PortAudioDaq::start(InDaqCallback inCallback, OutDaqCallback outCallback) {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
assert(_stream);
|
assert(_stream);
|
||||||
|
MuteErrHandler guard;
|
||||||
|
|
||||||
if (Pa_IsStreamActive(_stream)) {
|
if (Pa_IsStreamActive(_stream)) {
|
||||||
throw rte("Stream is already running");
|
throw rte("Stream is already running");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logical XOR
|
|
||||||
if (inCallback && outCallback) {
|
|
||||||
throw rte(
|
|
||||||
"Either input or output stream possible for PortAudio. "
|
|
||||||
"Stream duplex mode not provided.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (neninchannels() > 0) {
|
if (neninchannels() > 0) {
|
||||||
if (!inCallback) {
|
if (!inCallback) {
|
||||||
throw rte(
|
throw rte(
|
||||||
@ -426,6 +452,7 @@ Daq::StreamStatus PortAudioDaq::getStreamStatus() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
PortAudioDaq::~PortAudioDaq() {
|
PortAudioDaq::~PortAudioDaq() {
|
||||||
|
DEBUGTRACE_ENTER;
|
||||||
PaError err;
|
PaError err;
|
||||||
assert(_stream);
|
assert(_stream);
|
||||||
if (Pa_IsStreamActive(_stream)) {
|
if (Pa_IsStreamActive(_stream)) {
|
||||||
@ -449,7 +476,7 @@ int PortAudioDaq::memberPaCallback(const void *inputBuffer, void *outputBuffer,
|
|||||||
unsigned long framesPerBuffer,
|
unsigned long framesPerBuffer,
|
||||||
const PaStreamCallbackTimeInfo *timeInfo,
|
const PaStreamCallbackTimeInfo *timeInfo,
|
||||||
PaStreamCallbackFlags statusFlags) {
|
PaStreamCallbackFlags statusFlags) {
|
||||||
// DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
typedef Daq::StreamStatus::StreamError se;
|
typedef Daq::StreamStatus::StreamError se;
|
||||||
if (statusFlags & paPrimingOutput) {
|
if (statusFlags & paPrimingOutput) {
|
||||||
// Initial output buffers generated. So nothing with input yet
|
// Initial output buffers generated. So nothing with input yet
|
||||||
|
@ -16,6 +16,7 @@ set(lasp_dsp_files
|
|||||||
lasp_threadedindatahandler.cpp
|
lasp_threadedindatahandler.cpp
|
||||||
lasp_ppm.cpp
|
lasp_ppm.cpp
|
||||||
lasp_clip.cpp
|
lasp_clip.cpp
|
||||||
|
lasp_freqsmooth.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,12 +2,9 @@
|
|||||||
#include "lasp_avpowerspectra.h"
|
#include "lasp_avpowerspectra.h"
|
||||||
#include "debugtrace.hpp"
|
#include "debugtrace.hpp"
|
||||||
#include "lasp_mathtypes.h"
|
#include "lasp_mathtypes.h"
|
||||||
#include <cmath>
|
#include <stdexcept>
|
||||||
#include <optional>
|
|
||||||
|
|
||||||
using rte = std::runtime_error;
|
using rte = std::runtime_error;
|
||||||
using std::cerr;
|
|
||||||
using std::endl;
|
|
||||||
|
|
||||||
PowerSpectra::PowerSpectra(const us nfft, const Window::WindowType w)
|
PowerSpectra::PowerSpectra(const us nfft, const Window::WindowType w)
|
||||||
: PowerSpectra(Window::create(w, nfft)) {}
|
: PowerSpectra(Window::create(w, nfft)) {}
|
||||||
|
@ -3,8 +3,6 @@
|
|||||||
#include "lasp_mathtypes.h"
|
#include "lasp_mathtypes.h"
|
||||||
#include "lasp_timebuffer.h"
|
#include "lasp_timebuffer.h"
|
||||||
#include "lasp_window.h"
|
#include "lasp_window.h"
|
||||||
#include <memory>
|
|
||||||
#include <optional>
|
|
||||||
|
|
||||||
/** \defgroup dsp Digital Signal Processing utilities
|
/** \defgroup dsp Digital Signal Processing utilities
|
||||||
* These are classes and functions used for processing raw signal data, to
|
* These are classes and functions used for processing raw signal data, to
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
#include "lasp_biquadbank.h"
|
#include "lasp_biquadbank.h"
|
||||||
#include "debugtrace.hpp"
|
#include "debugtrace.hpp"
|
||||||
#include "lasp_thread.h"
|
#include "lasp_thread.h"
|
||||||
#include <cassert>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
using std::cerr;
|
using std::cerr;
|
||||||
|
133
cpp_src/dsp/lasp_freqsmooth.cpp
Normal file
133
cpp_src/dsp/lasp_freqsmooth.cpp
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
// #define DEBUGTRACE_ENABLED
|
||||||
|
#include "lasp_freqsmooth.h"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
#include "debugtrace.hpp"
|
||||||
|
|
||||||
|
using rte = std::runtime_error;
|
||||||
|
|
||||||
|
vd freqSmooth(const vd& freq, const vd& X, const unsigned w,
|
||||||
|
bool power_correct) {
|
||||||
|
DEBUGTRACE_ENTER;
|
||||||
|
if (freq.size() < 2) {
|
||||||
|
throw rte("Invalid frequency size. Should be > 2");
|
||||||
|
}
|
||||||
|
if (freq.size() != X.size()) {
|
||||||
|
throw rte("Sizes of freq and X do not match");
|
||||||
|
}
|
||||||
|
if (freq.size() > std::numeric_limits<long>::max() / 2) {
|
||||||
|
throw rte("Frequency size limit for smoothing is 2^30");
|
||||||
|
}
|
||||||
|
if (w == 0) {
|
||||||
|
throw rte("Invalid number of octaves");
|
||||||
|
}
|
||||||
|
const us Nfreq = freq.size();
|
||||||
|
|
||||||
|
// Smoothing width in unit of number of octaves
|
||||||
|
const d Delta = 1 / d(w);
|
||||||
|
|
||||||
|
// Minimum frequency and maximum frequency to smooth on (frequency range that
|
||||||
|
// is interpolated to a log scale)
|
||||||
|
d freq_min;
|
||||||
|
const d freq_max = freq(Nfreq - 1);
|
||||||
|
const bool firstFreqEqZero = (d_abs(freq(0)) < 1e-15);
|
||||||
|
|
||||||
|
// AC-signal power
|
||||||
|
d ac_pwr;
|
||||||
|
if (firstFreqEqZero) {
|
||||||
|
freq_min = freq(1);
|
||||||
|
if (power_correct) {
|
||||||
|
ac_pwr = arma::sum(X.subvec(1, Nfreq - 1));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
freq_min = freq(0);
|
||||||
|
if (power_correct) {
|
||||||
|
ac_pwr = arma::sum(X);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DEBUGTRACE_PRINT(freq_min);
|
||||||
|
DEBUGTRACE_PRINT(freq_max);
|
||||||
|
const vd freq_log =
|
||||||
|
arma::logspace(d_log10(freq_min), d_log10(freq_max), 10 * Nfreq);
|
||||||
|
DEBUGTRACE_PRINT("freq_log = ");
|
||||||
|
|
||||||
|
const long Nfreq_sm = freq_log.size();
|
||||||
|
|
||||||
|
// Interpolate X to logscale
|
||||||
|
vd X_log;
|
||||||
|
DEBUGTRACE_PRINT("X_log = :");
|
||||||
|
arma::interp1(freq, X, freq_log, X_log, "*linear");
|
||||||
|
|
||||||
|
// First and last point are not interpolated well, could be minimally out of
|
||||||
|
// the interpolation range, due to roundoff errors. Armadillo sets these
|
||||||
|
// points to nan, so we have to manually "interpolate" them.
|
||||||
|
X_log(Nfreq_sm - 1) = X(X.size() - 1);
|
||||||
|
if (firstFreqEqZero) {
|
||||||
|
X_log(0) = X(1);
|
||||||
|
} else {
|
||||||
|
X_log(0) = X(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate space for smoothed X on log scale
|
||||||
|
vd Xsm_log(freq_log.size());
|
||||||
|
const d beta = d_log10(Nfreq_sm) / d_log10(2) / (Nfreq_sm - 1);
|
||||||
|
// int rounds down
|
||||||
|
const long mu = int(Delta / d(2) / beta);
|
||||||
|
DEBUGTRACE_PRINT(mu);
|
||||||
|
|
||||||
|
// Long is at least 32 bits. So +/- 2M points length
|
||||||
|
for (long k = 0; k < Nfreq_sm; k++) {
|
||||||
|
// const d fcur = freq_log(k);
|
||||||
|
long idx_start = std::max(k - mu, 0l);
|
||||||
|
long idx_stop = std::min(k + mu, Nfreq_sm - 1);
|
||||||
|
|
||||||
|
// Make window smaller at the sides (close to the end of the array)
|
||||||
|
if (idx_start == 0 || idx_stop == Nfreq_sm - 1) {
|
||||||
|
const long mu_edge = std::min(k - idx_start, idx_stop - k);
|
||||||
|
idx_start = k - mu_edge;
|
||||||
|
idx_stop = k + mu_edge;
|
||||||
|
}
|
||||||
|
assert(idx_stop < Nfreq_sm);
|
||||||
|
assert(idx_start < Nfreq_sm);
|
||||||
|
|
||||||
|
DEBUGTRACE_PRINT(idx_start)
|
||||||
|
DEBUGTRACE_PRINT(idx_stop);
|
||||||
|
|
||||||
|
Xsm_log(k) = arma::mean(X_log.subvec(idx_start, idx_stop));
|
||||||
|
}
|
||||||
|
DEBUGTRACE_PRINT("Xsm_log:");
|
||||||
|
// std::cerr << Xsm_log << std::endl;
|
||||||
|
|
||||||
|
// Back-interpolate to a linear scale, and be wary of nans at the start end
|
||||||
|
// and range. Also interpolates power
|
||||||
|
vd Xsm(Nfreq);
|
||||||
|
if (firstFreqEqZero) {
|
||||||
|
vd Xsm_gt0;
|
||||||
|
arma::interp1(freq_log, Xsm_log, freq.subvec(1, Nfreq - 1), Xsm_gt0,
|
||||||
|
"*linear");
|
||||||
|
Xsm(0) = X(0);
|
||||||
|
Xsm.subvec(1, Nfreq - 1) = Xsm_gt0;
|
||||||
|
Xsm(1) = Xsm_log(1);
|
||||||
|
Xsm(Nfreq - 1) = Xsm_log(Nfreq_sm - 1);
|
||||||
|
|
||||||
|
// Final step: power-correct smoothed spectrum
|
||||||
|
if (power_correct) {
|
||||||
|
d new_acpwr = arma::sum(Xsm.subvec(1, Nfreq - 1));
|
||||||
|
Xsm.subvec(1, Nfreq - 1) *= ac_pwr / new_acpwr;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
arma::interp1(freq_log, Xsm_log, freq, Xsm, "*linear");
|
||||||
|
Xsm(0) = X(0);
|
||||||
|
Xsm(Nfreq - 1) = Xsm_log(Nfreq_sm - 1);
|
||||||
|
|
||||||
|
// Final step: power-correct smoothed spectrum
|
||||||
|
if (power_correct) {
|
||||||
|
d new_acpwr = arma::sum(Xsm);
|
||||||
|
Xsm *= ac_pwr / new_acpwr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Xsm;
|
||||||
|
}
|
28
cpp_src/dsp/lasp_freqsmooth.h
Normal file
28
cpp_src/dsp/lasp_freqsmooth.h
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "lasp_mathtypes.h"
|
||||||
|
#include "lasp_types.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \addtogroup dsp
|
||||||
|
* @{
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Apply frequency domain smoothing to a Frequency domain (single
|
||||||
|
* sided)signal power spectrum
|
||||||
|
*
|
||||||
|
* @param freq Frequency range
|
||||||
|
* @param X Signal pwr
|
||||||
|
* @param w Parameter determining the smoothing with. 1 = 1/1 octave, 3 = 1/3th
|
||||||
|
* octave and so on
|
||||||
|
* @param power_correct Apply a correction to the whole spectrum to make the
|
||||||
|
* signal power equal to the unsmoothed signal power.
|
||||||
|
* @return vd Smoothed spectrum
|
||||||
|
*/
|
||||||
|
vd freqSmooth(const vd& freq, const vd& X, const unsigned w,
|
||||||
|
bool power_correct = false);
|
||||||
|
|
||||||
|
/** @} */
|
@ -37,7 +37,7 @@ void RtAps::inCallback(const DaqData &data) {
|
|||||||
cerr << "**** Error: sensitivity size does not match! *****" << endl;
|
cerr << "**** Error: sensitivity size does not match! *****" << endl;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fltdata.each_row() %= _sens.as_row();
|
fltdata.each_row() /= _sens.as_row();
|
||||||
|
|
||||||
if (_filterPrototype) {
|
if (_filterPrototype) {
|
||||||
|
|
||||||
|
@ -4,12 +4,9 @@
|
|||||||
//
|
//
|
||||||
// Description: Real Time Signal Viewer.
|
// Description: Real Time Signal Viewer.
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "lasp_avpowerspectra.h"
|
|
||||||
#include "lasp_filter.h"
|
|
||||||
#include "lasp_mathtypes.h"
|
#include "lasp_mathtypes.h"
|
||||||
#include "lasp_threadedindatahandler.h"
|
#include "lasp_threadedindatahandler.h"
|
||||||
#include "lasp_timebuffer.h"
|
#include "lasp_timebuffer.h"
|
||||||
#include <memory>
|
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -10,12 +10,12 @@ using rte = std::runtime_error;
|
|||||||
|
|
||||||
inline d level_amp(d level_dB) { return pow(10, level_dB / 20); }
|
inline d level_amp(d level_dB) { return pow(10, level_dB / 20); }
|
||||||
|
|
||||||
using mutexlock = std::scoped_lock<std::mutex>;
|
using slock = std::scoped_lock<std::recursive_mutex>;
|
||||||
|
|
||||||
vd Siggen::genSignal(const us nframes) {
|
vd Siggen::genSignal(const us nframes) {
|
||||||
|
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
mutexlock lck(_mtx);
|
slock lck(_mtx);
|
||||||
|
|
||||||
DEBUGTRACE_PRINT(nframes);
|
DEBUGTRACE_PRINT(nframes);
|
||||||
vd signal(nframes, arma::fill::value(_dc_offset));
|
vd signal(nframes, arma::fill::value(_dc_offset));
|
||||||
@ -52,7 +52,7 @@ vd Siggen::genSignal(const us nframes) {
|
|||||||
return signal;
|
return signal;
|
||||||
}
|
}
|
||||||
void Siggen::setInterruptPeriod(const d newPeriod) {
|
void Siggen::setInterruptPeriod(const d newPeriod) {
|
||||||
mutexlock lck(_mtx);
|
slock lck(_mtx);
|
||||||
if (newPeriod == 0) {
|
if (newPeriod == 0) {
|
||||||
throw rte("Interruption period cannot be 0");
|
throw rte("Interruption period cannot be 0");
|
||||||
}
|
}
|
||||||
@ -65,7 +65,7 @@ void Siggen::setInterruptPeriod(const d newPeriod) {
|
|||||||
void Siggen::setFilter(const std::string &name,
|
void Siggen::setFilter(const std::string &name,
|
||||||
std::shared_ptr<Filter> filter) {
|
std::shared_ptr<Filter> filter) {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
mutexlock lck(_mtx);
|
slock lck(_mtx);
|
||||||
if (filter) {
|
if (filter) {
|
||||||
_filters[name] = filter;
|
_filters[name] = filter;
|
||||||
} else if (_filters.find(name) != _filters.end()) {
|
} else if (_filters.find(name) != _filters.end()) {
|
||||||
@ -74,17 +74,17 @@ void Siggen::setFilter(const std::string &name,
|
|||||||
}
|
}
|
||||||
void Siggen::setDCOffset(const d offset) {
|
void Siggen::setDCOffset(const d offset) {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
mutexlock lck(_mtx);
|
slock lck(_mtx);
|
||||||
_dc_offset = offset;
|
_dc_offset = offset;
|
||||||
}
|
}
|
||||||
void Siggen::setLevel(const d level, bool dB) {
|
void Siggen::setLevel(const d level, bool dB) {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
mutexlock lck(_mtx);
|
slock lck(_mtx);
|
||||||
_level_linear = dB ? level_amp(level) : level;
|
_level_linear = dB ? level_amp(level) : level;
|
||||||
}
|
}
|
||||||
void Siggen::reset(const d newFs) {
|
void Siggen::reset(const d newFs) {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
mutexlock lck(_mtx);
|
slock lck(_mtx);
|
||||||
_fs = newFs;
|
_fs = newFs;
|
||||||
for (auto &f : _filters) {
|
for (auto &f : _filters) {
|
||||||
assert(f.second);
|
assert(f.second);
|
||||||
|
@ -27,7 +27,7 @@ private:
|
|||||||
bool _muted = false;
|
bool _muted = false;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::mutex _mtx;
|
mutable std::recursive_mutex _mtx;
|
||||||
d _fs = 0;
|
d _fs = 0;
|
||||||
/**
|
/**
|
||||||
* @brief Interuption of period the signal. If set, the signal will be
|
* @brief Interuption of period the signal. If set, the signal will be
|
||||||
|
@ -7,11 +7,14 @@
|
|||||||
//////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////
|
||||||
/* #define DEBUGTRACE_ENABLED */
|
/* #define DEBUGTRACE_ENABLED */
|
||||||
#include "lasp_siggen_impl.h"
|
#include "lasp_siggen_impl.h"
|
||||||
#include "debugtrace.hpp"
|
|
||||||
#include "lasp_mathtypes.h"
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
|
||||||
|
#include "debugtrace.hpp"
|
||||||
|
#include "lasp_mathtypes.h"
|
||||||
|
|
||||||
using rte = std::runtime_error;
|
using rte = std::runtime_error;
|
||||||
|
using slock = std::scoped_lock<std::recursive_mutex>;
|
||||||
|
|
||||||
DEBUGTRACE_VARIABLES;
|
DEBUGTRACE_VARIABLES;
|
||||||
|
|
||||||
@ -30,6 +33,7 @@ Sine::Sine(const d freq) : omg(2 * arma::datum::pi * freq) { DEBUGTRACE_ENTER; }
|
|||||||
|
|
||||||
vd Sine::genSignalUnscaled(const us nframes) {
|
vd Sine::genSignalUnscaled(const us nframes) {
|
||||||
/* DEBUGTRACE_ENTER; */
|
/* DEBUGTRACE_ENTER; */
|
||||||
|
slock lck(_mtx);
|
||||||
const d pi = arma::datum::pi;
|
const d pi = arma::datum::pi;
|
||||||
vd phase_vec =
|
vd phase_vec =
|
||||||
arma::linspace(phase, phase + omg * (nframes - 1) / _fs, nframes);
|
arma::linspace(phase, phase + omg * (nframes - 1) / _fs, nframes);
|
||||||
@ -41,8 +45,8 @@ vd Sine::genSignalUnscaled(const us nframes) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
vd Periodic::genSignalUnscaled(const us nframes) {
|
vd Periodic::genSignalUnscaled(const us nframes) {
|
||||||
|
|
||||||
vd res(nframes);
|
vd res(nframes);
|
||||||
|
slock lck(_mtx);
|
||||||
if (_signal.size() == 0) {
|
if (_signal.size() == 0) {
|
||||||
throw rte("No signal defined while calling");
|
throw rte("No signal defined while calling");
|
||||||
}
|
}
|
||||||
@ -74,8 +78,8 @@ Sweep::Sweep(const d fl, const d fu, const d Ts, const d Tq, const us flags)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Sweep::resetImpl() {
|
void Sweep::resetImpl() {
|
||||||
|
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
|
slock lck(_mtx);
|
||||||
|
|
||||||
_cur_pos = 0;
|
_cur_pos = 0;
|
||||||
|
|
||||||
@ -166,7 +170,6 @@ void Sweep::resetImpl() {
|
|||||||
/* dVARTRACE(15, phase); */
|
/* dVARTRACE(15, phase); */
|
||||||
}
|
}
|
||||||
} else if (flags & LogSweep) {
|
} else if (flags & LogSweep) {
|
||||||
|
|
||||||
DEBUGTRACE_PRINT("Log sweep");
|
DEBUGTRACE_PRINT("Log sweep");
|
||||||
if (forward_sweep || backward_sweep) {
|
if (forward_sweep || backward_sweep) {
|
||||||
/* Forward or backward sweep */
|
/* Forward or backward sweep */
|
||||||
@ -194,7 +197,6 @@ void Sweep::resetImpl() {
|
|||||||
phase += 2 * number_pi * Dt * fn;
|
phase += 2 * number_pi * Dt * fn;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
DEBUGTRACE_PRINT("Continuous sweep");
|
DEBUGTRACE_PRINT("Continuous sweep");
|
||||||
|
|
||||||
const us Nf = Ns / 2;
|
const us Nf = Ns / 2;
|
||||||
@ -249,8 +251,7 @@ void Sweep::resetImpl() {
|
|||||||
/* dbgassert(fn >= 0, "BUG"); */
|
/* dbgassert(fn >= 0, "BUG"); */
|
||||||
|
|
||||||
phase += 2 * number_pi * Dt * fn;
|
phase += 2 * number_pi * Dt * fn;
|
||||||
while (phase > 2 * number_pi)
|
while (phase > 2 * number_pi) phase -= 2 * number_pi;
|
||||||
phase -= 2 * number_pi;
|
|
||||||
/* dVARTRACE(17, phase); */
|
/* dVARTRACE(17, phase); */
|
||||||
}
|
}
|
||||||
/* This should be a very small number!! */
|
/* This should be a very small number!! */
|
||||||
@ -260,6 +261,5 @@ void Sweep::resetImpl() {
|
|||||||
else {
|
else {
|
||||||
// Either log or linear sweep had to be given as flags.
|
// Either log or linear sweep had to be given as flags.
|
||||||
assert(false);
|
assert(false);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,8 +18,8 @@ class Noise : public Siggen {
|
|||||||
d level_linear;
|
d level_linear;
|
||||||
virtual vd genSignalUnscaled(const us nframes) override;
|
virtual vd genSignalUnscaled(const us nframes) override;
|
||||||
void resetImpl() override;
|
void resetImpl() override;
|
||||||
public:
|
|
||||||
|
|
||||||
|
public:
|
||||||
/**
|
/**
|
||||||
* @brief Constructs a noise generator. If no filter is used, the output will
|
* @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
|
* be white noise. By default, the output will be standard deviation = 1
|
||||||
@ -28,7 +28,6 @@ class Noise : public Siggen {
|
|||||||
*/
|
*/
|
||||||
Noise();
|
Noise();
|
||||||
~Noise() = default;
|
~Noise() = default;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -37,13 +36,12 @@ class Noise : public Siggen {
|
|||||||
class Sine : public Siggen {
|
class Sine : public Siggen {
|
||||||
d phase = 0;
|
d phase = 0;
|
||||||
d omg;
|
d omg;
|
||||||
protected:
|
|
||||||
|
|
||||||
|
protected:
|
||||||
void resetImpl() override final { phase = 0; }
|
void resetImpl() override final { phase = 0; }
|
||||||
virtual vd genSignalUnscaled(const us nframes) override final;
|
virtual vd genSignalUnscaled(const us nframes) override final;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Create a sine wave generator
|
* @brief Create a sine wave generator
|
||||||
*
|
*
|
||||||
@ -87,7 +85,6 @@ class Sweep : public Periodic {
|
|||||||
void resetImpl() override;
|
void resetImpl() override;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
static constexpr int ForwardSweep = 1 << 0;
|
static constexpr int ForwardSweep = 1 << 0;
|
||||||
static constexpr int BackwardSweep = 1 << 1;
|
static constexpr int BackwardSweep = 1 << 1;
|
||||||
static constexpr int LinearSweep = 1 << 2;
|
static constexpr int LinearSweep = 1 << 2;
|
||||||
@ -103,11 +100,11 @@ class Sweep : public Periodic {
|
|||||||
* avoid temporal aliasing in case of measuring impulse responses.
|
* avoid temporal aliasing in case of measuring impulse responses.
|
||||||
* @param[in] sweep_flags: Sweep period [s]
|
* @param[in] sweep_flags: Sweep period [s]
|
||||||
*/
|
*/
|
||||||
Sweep(const d fl, const d fu, const d Ts, const d Tq,
|
Sweep(const d fl, const d fu, const d Ts, const d Tq, const us sweep_flags);
|
||||||
const us sweep_flags);
|
|
||||||
|
|
||||||
~Sweep() = default;
|
~Sweep() = default;
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
/** @} */
|
/** @} */
|
||||||
/** @} */
|
/** @} */
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
/* #define DEBUGTRACE_ENABLED */
|
// #define DEBUGTRACE_ENABLED
|
||||||
#include "lasp_threadedindatahandler.h"
|
#include "lasp_threadedindatahandler.h"
|
||||||
#include "debugtrace.hpp"
|
|
||||||
#include "lasp_daqdata.h"
|
|
||||||
#include "lasp_thread.h"
|
|
||||||
#include <future>
|
#include <future>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
|
#include "debugtrace.hpp"
|
||||||
|
#include "lasp_daqdata.h"
|
||||||
|
#include "lasp_thread.h"
|
||||||
|
|
||||||
using namespace std::literals::chrono_literals;
|
using namespace std::literals::chrono_literals;
|
||||||
using lck = std::scoped_lock<std::mutex>;
|
using lck = std::scoped_lock<std::mutex>;
|
||||||
using rte = std::runtime_error;
|
using rte = std::runtime_error;
|
||||||
@ -53,57 +55,74 @@ public:
|
|||||||
|
|
||||||
ThreadedInDataHandlerBase::ThreadedInDataHandlerBase(SmgrHandle mgr,
|
ThreadedInDataHandlerBase::ThreadedInDataHandlerBase(SmgrHandle mgr,
|
||||||
InCallbackType cb,
|
InCallbackType cb,
|
||||||
InResetType reset)
|
ResetCallbackType reset)
|
||||||
: _indatahandler(
|
: _queue(std::make_unique<SafeQueue>()),
|
||||||
mgr,
|
inCallback(cb),
|
||||||
std::bind(&ThreadedInDataHandlerBase::_inCallbackFromInDataHandler, this,
|
resetCallback(reset),
|
||||||
_1),
|
_smgr(mgr) {
|
||||||
reset),
|
|
||||||
_queue(std::make_unique<SafeQueue>()), inCallback(cb) {
|
|
||||||
|
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
|
|
||||||
}
|
}
|
||||||
void ThreadedInDataHandlerBase::startThread() {
|
void ThreadedInDataHandlerBase::startThread() {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
_thread_can_safely_run = true;
|
if (_indatahandler) {
|
||||||
_indatahandler.start();
|
throw rte("BUG: ThreadedIndataHandler already started");
|
||||||
|
}
|
||||||
|
SmgrHandle smgr = _smgr.lock();
|
||||||
|
if (!smgr) {
|
||||||
|
cerr << "Stream manager destructed" << endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_indatahandler = std::make_unique<InDataHandler>(
|
||||||
|
smgr,
|
||||||
|
std::bind(&ThreadedInDataHandlerBase::_inCallbackFromInDataHandler, this,
|
||||||
|
_1),
|
||||||
|
resetCallback);
|
||||||
|
|
||||||
|
_thread_allowed_to_run = true;
|
||||||
|
_indatahandler->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ThreadedInDataHandlerBase::_inCallbackFromInDataHandler(
|
void ThreadedInDataHandlerBase::_inCallbackFromInDataHandler(
|
||||||
const DaqData &daqdata) {
|
const DaqData &daqdata) {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
std::scoped_lock lck(_mtx);
|
|
||||||
|
|
||||||
// Early return in case object is under DESTRUCTION
|
// Early return in case object is under DESTRUCTION
|
||||||
if (!_thread_can_safely_run)
|
if (!_thread_allowed_to_run) return;
|
||||||
return;
|
|
||||||
|
|
||||||
_queue->push(daqdata);
|
_queue->push(daqdata);
|
||||||
if (!_thread_running) {
|
if (!_thread_running) {
|
||||||
DEBUGTRACE_PRINT("Pushing new thread in pool");
|
DEBUGTRACE_PRINT("Pushing new thread in pool");
|
||||||
|
_thread_running = true;
|
||||||
_pool.push_task(&ThreadedInDataHandlerBase::threadFcn, this);
|
_pool.push_task(&ThreadedInDataHandlerBase::threadFcn, this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ThreadedInDataHandlerBase::stopThread() {
|
void ThreadedInDataHandlerBase::stopThread() {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
// Make sure inCallback is no longer called
|
if (!_indatahandler) {
|
||||||
_thread_can_safely_run = false;
|
throw rte("BUG: ThreadedIndataHandler not running");
|
||||||
_indatahandler.stop();
|
}
|
||||||
|
|
||||||
std::scoped_lock lck(_mtx);
|
// Stop the existing thread
|
||||||
|
_thread_allowed_to_run = false;
|
||||||
|
|
||||||
|
// Make sure no new data arrives
|
||||||
|
_indatahandler->stop();
|
||||||
|
_indatahandler.reset();
|
||||||
|
|
||||||
|
DEBUGTRACE_PRINT("Indatahandler stopped. Waiting for thread to finish...");
|
||||||
// Then wait in steps for the thread to stop running.
|
// Then wait in steps for the thread to stop running.
|
||||||
while (_thread_running) {
|
while (_thread_running) {
|
||||||
std::this_thread::sleep_for(10us);
|
std::this_thread::sleep_for(10us);
|
||||||
}
|
}
|
||||||
|
DEBUGTRACE_PRINT("Thread stopped");
|
||||||
|
// Kill the handler
|
||||||
|
DEBUGTRACE_PRINT("Handler resetted");
|
||||||
}
|
}
|
||||||
|
|
||||||
ThreadedInDataHandlerBase::~ThreadedInDataHandlerBase() {
|
ThreadedInDataHandlerBase::~ThreadedInDataHandlerBase() {
|
||||||
|
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
if (_thread_can_safely_run) {
|
if (_thread_allowed_to_run) {
|
||||||
stopThread();
|
stopThread();
|
||||||
cerr << "*** BUG: InDataHandlers have not been all stopped, while "
|
cerr << "*** BUG: InDataHandlers have not been all stopped, while "
|
||||||
"StreamMgr destructor is called. This is a misuse BUG."
|
"StreamMgr destructor is called. This is a misuse BUG."
|
||||||
@ -113,12 +132,9 @@ ThreadedInDataHandlerBase::~ThreadedInDataHandlerBase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ThreadedInDataHandlerBase::threadFcn() {
|
void ThreadedInDataHandlerBase::threadFcn() {
|
||||||
|
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
_thread_running = true;
|
|
||||||
|
|
||||||
while (!_queue->empty() && _thread_can_safely_run) {
|
|
||||||
|
|
||||||
|
while (!_queue->empty() && _thread_allowed_to_run) {
|
||||||
// Call inCallback_threaded
|
// Call inCallback_threaded
|
||||||
inCallback(_queue->pop());
|
inCallback(_queue->pop());
|
||||||
}
|
}
|
||||||
|
@ -29,21 +29,27 @@ class ThreadedInDataHandlerBase {
|
|||||||
* @brief The queue used to push elements to the handling thread.
|
* @brief The queue used to push elements to the handling thread.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
InDataHandler _indatahandler;
|
|
||||||
std::unique_ptr<SafeQueue> _queue;
|
std::unique_ptr<SafeQueue> _queue;
|
||||||
|
|
||||||
mutable std::recursive_mutex _mtx;
|
|
||||||
|
|
||||||
std::atomic<bool> _thread_running{false};
|
|
||||||
std::atomic<bool> _thread_can_safely_run{false};
|
|
||||||
|
|
||||||
GlobalThreadPool _pool;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Function pointer that is called when new DaqData arrives.
|
* @brief Function pointer that is called when new DaqData arrives.
|
||||||
*/
|
*/
|
||||||
const InCallbackType inCallback;
|
const InCallbackType inCallback;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Function pointer that is called when reset() is called.
|
||||||
|
*/
|
||||||
|
const ResetCallbackType resetCallback;
|
||||||
|
|
||||||
|
std::weak_ptr<StreamMgr> _smgr;
|
||||||
|
|
||||||
|
std::unique_ptr<InDataHandler> _indatahandler;
|
||||||
|
|
||||||
|
std::atomic<bool> _thread_running{false};
|
||||||
|
std::atomic<bool> _thread_allowed_to_run{false};
|
||||||
|
|
||||||
|
GlobalThreadPool _pool;
|
||||||
|
|
||||||
void threadFcn();
|
void threadFcn();
|
||||||
|
|
||||||
|
|
||||||
@ -58,7 +64,7 @@ class ThreadedInDataHandlerBase {
|
|||||||
void _inCallbackFromInDataHandler(const DaqData &daqdata);
|
void _inCallbackFromInDataHandler(const DaqData &daqdata);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ThreadedInDataHandlerBase(SmgrHandle mgr, InCallbackType cb, InResetType reset);
|
ThreadedInDataHandlerBase(SmgrHandle mgr, InCallbackType cb, ResetCallbackType reset);
|
||||||
~ThreadedInDataHandlerBase();
|
~ThreadedInDataHandlerBase();
|
||||||
/**
|
/**
|
||||||
* @brief This method should be called from the derived class' constructor,
|
* @brief This method should be called from the derived class' constructor,
|
||||||
|
@ -11,7 +11,7 @@ using std::cerr;
|
|||||||
using std::endl;
|
using std::endl;
|
||||||
|
|
||||||
// Safe some typing. Linspace form 0 up to (and NOT including N).
|
// Safe some typing. Linspace form 0 up to (and NOT including N).
|
||||||
#define lin0N arma::linspace(0, N - 1, N)
|
#define lin0N arma::linspace<vd>(0, N - 1, N)
|
||||||
|
|
||||||
vd Window::hann(const us N) {
|
vd Window::hann(const us N) {
|
||||||
return arma::pow(arma::sin((arma::datum::pi/N) * lin0N), 2);
|
return arma::pow(arma::sin((arma::datum::pi/N) * lin0N), 2);
|
||||||
@ -24,8 +24,8 @@ vd Window::blackman(const us N) {
|
|||||||
d a0 = 7938. / 18608.;
|
d a0 = 7938. / 18608.;
|
||||||
d a1 = 9240. / 18608.;
|
d a1 = 9240. / 18608.;
|
||||||
d a2 = 1430. / 18608.;
|
d a2 = 1430. / 18608.;
|
||||||
return a0 - a1 * d_cos((2 * number_pi/N) * lin0N) +
|
return a0 - a1 * arma::cos((2 * number_pi/N) * lin0N) +
|
||||||
a2 * d_cos((4 * number_pi / N)* lin0N );
|
a2 * arma::cos((4 * number_pi / N)* lin0N );
|
||||||
}
|
}
|
||||||
|
|
||||||
vd Window::rectangular(const us N) { return arma::ones(N); }
|
vd Window::rectangular(const us N) { return arma::ones(N); }
|
||||||
|
@ -44,6 +44,12 @@ void init_siggen(py::module &m);
|
|||||||
|
|
||||||
PYBIND11_MODULE(lasp_cpp, m) {
|
PYBIND11_MODULE(lasp_cpp, m) {
|
||||||
|
|
||||||
|
#if LASP_DOUBLE_PRECISION == 1
|
||||||
|
m.attr("LASP_DOUBLE_PRECISION") = true;
|
||||||
|
#else
|
||||||
|
m.attr("LASP_DOUBLE_PRECISION") = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
init_dsp(m);
|
init_dsp(m);
|
||||||
init_deviceinfo(m);
|
init_deviceinfo(m);
|
||||||
init_daqconfiguration(m);
|
init_daqconfiguration(m);
|
||||||
@ -51,6 +57,5 @@ PYBIND11_MODULE(lasp_cpp, m) {
|
|||||||
init_streammgr(m);
|
init_streammgr(m);
|
||||||
init_datahandler(m);
|
init_datahandler(m);
|
||||||
init_siggen(m);
|
init_siggen(m);
|
||||||
|
|
||||||
}
|
}
|
||||||
/** @} */
|
/** @} */
|
@ -1,13 +1,16 @@
|
|||||||
|
#include <pybind11/pybind11.h>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
#include "arma_npy.h"
|
#include "arma_npy.h"
|
||||||
#include "lasp_avpowerspectra.h"
|
#include "lasp_avpowerspectra.h"
|
||||||
#include "lasp_biquadbank.h"
|
#include "lasp_biquadbank.h"
|
||||||
#include "lasp_fft.h"
|
#include "lasp_fft.h"
|
||||||
#include "lasp_filter.h"
|
#include "lasp_filter.h"
|
||||||
|
#include "lasp_freqsmooth.h"
|
||||||
#include "lasp_slm.h"
|
#include "lasp_slm.h"
|
||||||
#include "lasp_streammgr.h"
|
#include "lasp_streammgr.h"
|
||||||
#include "lasp_window.h"
|
#include "lasp_window.h"
|
||||||
#include <iostream>
|
|
||||||
#include <pybind11/pybind11.h>
|
|
||||||
|
|
||||||
using std::cerr;
|
using std::cerr;
|
||||||
using std::endl;
|
using std::endl;
|
||||||
@ -27,7 +30,6 @@ using rte = std::runtime_error;
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
void init_dsp(py::module &m) {
|
void init_dsp(py::module &m) {
|
||||||
|
|
||||||
py::class_<Fft> fft(m, "Fft");
|
py::class_<Fft> fft(m, "Fft");
|
||||||
fft.def(py::init<us>());
|
fft.def(py::init<us>());
|
||||||
fft.def("fft", [](Fft &f, dpyarray dat) {
|
fft.def("fft", [](Fft &f, dpyarray dat) {
|
||||||
@ -114,9 +116,10 @@ void init_dsp(py::module &m) {
|
|||||||
|
|
||||||
aps.def("compute", [](AvPowerSpectra &aps, dpyarray timedata) {
|
aps.def("compute", [](AvPowerSpectra &aps, dpyarray timedata) {
|
||||||
std::optional<ccube> res;
|
std::optional<ccube> res;
|
||||||
|
dmat timedata_mat = NpyToMat<d, false>(timedata);
|
||||||
{
|
{
|
||||||
py::gil_scoped_release release;
|
py::gil_scoped_release release;
|
||||||
res = aps.compute(NpyToMat<d, false>(timedata));
|
res = aps.compute(timedata_mat);
|
||||||
}
|
}
|
||||||
|
|
||||||
return CubeToNpy<c>(res.value_or(ccube(0, 0, 0)));
|
return CubeToNpy<c>(res.value_or(ccube(0, 0, 0)));
|
||||||
@ -151,5 +154,12 @@ void init_dsp(py::module &m) {
|
|||||||
slm.def("Lmax", [](const SLM &slm) { return ColToNpy<d>(slm.Lmax()); });
|
slm.def("Lmax", [](const SLM &slm) { return ColToNpy<d>(slm.Lmax()); });
|
||||||
slm.def("Lpeak", [](const SLM &slm) { return ColToNpy<d>(slm.Lpeak()); });
|
slm.def("Lpeak", [](const SLM &slm) { return ColToNpy<d>(slm.Lpeak()); });
|
||||||
slm.def_static("suggestedDownSamplingFac", &SLM::suggestedDownSamplingFac);
|
slm.def_static("suggestedDownSamplingFac", &SLM::suggestedDownSamplingFac);
|
||||||
|
|
||||||
|
// Frequency smoother
|
||||||
|
m.def("freqSmooth", [](dpyarray freq, dpyarray X, unsigned w) {
|
||||||
|
vd freqa = NpyToCol<d, false>(freq);
|
||||||
|
vd Xa = NpyToCol<d, false>(X);
|
||||||
|
return ColToNpy(freqSmooth(freqa, Xa, w));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
/** @} */
|
/** @} */
|
||||||
|
@ -1,4 +1,10 @@
|
|||||||
/* #define DEBUGTRACE_ENABLED */
|
// #define DEBUGTRACE_ENABLED
|
||||||
|
#include <pybind11/pybind11.h>
|
||||||
|
#include <pybind11/pytypes.h>
|
||||||
|
|
||||||
|
#include <armadillo>
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
#include "arma_npy.h"
|
#include "arma_npy.h"
|
||||||
#include "debugtrace.hpp"
|
#include "debugtrace.hpp"
|
||||||
#include "lasp_clip.h"
|
#include "lasp_clip.h"
|
||||||
@ -9,14 +15,12 @@
|
|||||||
#include "lasp_rtsignalviewer.h"
|
#include "lasp_rtsignalviewer.h"
|
||||||
#include "lasp_streammgr.h"
|
#include "lasp_streammgr.h"
|
||||||
#include "lasp_threadedindatahandler.h"
|
#include "lasp_threadedindatahandler.h"
|
||||||
#include <armadillo>
|
|
||||||
#include <atomic>
|
|
||||||
#include <chrono>
|
|
||||||
#include <pybind11/pybind11.h>
|
|
||||||
|
|
||||||
using namespace std::literals::chrono_literals;
|
using namespace std::literals::chrono_literals;
|
||||||
using std::cerr;
|
using std::cerr;
|
||||||
using std::endl;
|
using std::endl;
|
||||||
|
using rte = std::runtime_error;
|
||||||
|
using Lck = std::scoped_lock<std::recursive_mutex>;
|
||||||
|
|
||||||
namespace py = pybind11;
|
namespace py = pybind11;
|
||||||
|
|
||||||
@ -104,7 +108,9 @@ class PyIndataHandler : public ThreadedInDataHandler<PyIndataHandler> {
|
|||||||
/**
|
/**
|
||||||
* @brief The callback functions that is called.
|
* @brief The callback functions that is called.
|
||||||
*/
|
*/
|
||||||
py::function cb, reset_callback;
|
py::object _cb, _reset_callback;
|
||||||
|
std::atomic<bool> _done{false};
|
||||||
|
std::recursive_mutex _mtx;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
@ -117,19 +123,26 @@ public:
|
|||||||
* is called, when a stream stops, this pointer / handle will dangle.
|
* is called, when a stream stops, this pointer / handle will dangle.
|
||||||
*/
|
*/
|
||||||
PyIndataHandler(SmgrHandle mgr, py::function cb, py::function reset_callback)
|
PyIndataHandler(SmgrHandle mgr, py::function cb, py::function reset_callback)
|
||||||
: ThreadedInDataHandler(mgr), cb(cb), reset_callback(reset_callback) {
|
: ThreadedInDataHandler(mgr),
|
||||||
|
_cb(py::weakref(cb)),
|
||||||
|
_reset_callback(py::weakref(reset_callback)) {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
|
// cerr << "Thread ID: " << std::this_thread::get_id() << endl;
|
||||||
/// Start should be called externally, as at constructor time no virtual
|
/// Start should be called externally, as at constructor time no virtual
|
||||||
/// functions should be called.
|
/// functions should be called.
|
||||||
py::gil_scoped_release release;
|
if (_cb().is_none() || _reset_callback().is_none()) {
|
||||||
|
throw rte("cb or reset_callback is none!");
|
||||||
|
}
|
||||||
startThread();
|
startThread();
|
||||||
}
|
}
|
||||||
~PyIndataHandler() {
|
~PyIndataHandler() {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
|
// cerr << "Thread ID: " << std::this_thread::get_id() << endl;
|
||||||
/// Callback cannot be called, which results in a deadlock on the GIL
|
/// Callback cannot be called, which results in a deadlock on the GIL
|
||||||
/// without this release.
|
/// without this release.
|
||||||
py::gil_scoped_release release;
|
py::gil_scoped_release release;
|
||||||
|
DEBUGTRACE_PRINT("Gil released");
|
||||||
|
_done = true;
|
||||||
stopThread();
|
stopThread();
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@ -137,13 +150,23 @@ public:
|
|||||||
*
|
*
|
||||||
* @param daq Daq device, or nullptr in case no input stream is running.
|
* @param daq Daq device, or nullptr in case no input stream is running.
|
||||||
*/
|
*/
|
||||||
void reset(const Daq *daq) {
|
void reset(const Daq *daqi) {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
|
// cerr << "Thread ID: " << std::this_thread::get_id() << endl;
|
||||||
|
if (_done) return;
|
||||||
|
{
|
||||||
try {
|
try {
|
||||||
py::gil_scoped_acquire acquire;
|
py::object reset_callback = _reset_callback();
|
||||||
if (daq) {
|
if (reset_callback.is_none()) {
|
||||||
reset_callback(daq);
|
DEBUGTRACE_PRINT("reset_callback is none, weakref killed");
|
||||||
|
_done = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (daqi != nullptr) {
|
||||||
|
assert(reset_callback);
|
||||||
|
reset_callback(daqi);
|
||||||
} else {
|
} else {
|
||||||
|
assert(reset_callback);
|
||||||
reset_callback(py::none());
|
reset_callback(py::none());
|
||||||
}
|
}
|
||||||
} catch (py::error_already_set &e) {
|
} catch (py::error_already_set &e) {
|
||||||
@ -155,63 +178,95 @@ public:
|
|||||||
abort();
|
abort();
|
||||||
/* throw std::runtime_error(e.what()); */
|
/* throw std::runtime_error(e.what()); */
|
||||||
} catch (std::exception &e) {
|
} catch (std::exception &e) {
|
||||||
cerr << "Caught unknown exception in reset callback:" << e.what() << endl;
|
cerr << "Caught unknown exception in reset callback:" << e.what()
|
||||||
|
<< endl;
|
||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
}
|
} // end of GIL scope
|
||||||
|
} // end of function reset()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Calls the Python callback method / function with a Numpy array of
|
* @brief Calls the Python callback method / function with a Numpy array of
|
||||||
* stream data.
|
* stream data.
|
||||||
*/
|
*/
|
||||||
void inCallback(const DaqData &d) {
|
void inCallback(const DaqData &d) {
|
||||||
|
DEBUGTRACE_ENTER;
|
||||||
/* DEBUGTRACE_ENTER; */
|
// cerr << "=== Enter incallback for thread ID: " << std::this_thread::get_id() << endl;
|
||||||
|
|
||||||
using DataType = DataTypeDescriptor::DataType;
|
using DataType = DataTypeDescriptor::DataType;
|
||||||
|
if (_done) {
|
||||||
try {
|
DEBUGTRACE_PRINT("Early stop, done");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
DEBUGTRACE_PRINT("================ TRYING TO OBTAIN GIL in inCallback...");
|
||||||
py::gil_scoped_acquire acquire;
|
py::gil_scoped_acquire acquire;
|
||||||
py::object bool_val;
|
try {
|
||||||
|
py::object py_bool;
|
||||||
|
py::object cb = _cb();
|
||||||
|
if (cb.is_none()) {
|
||||||
|
DEBUGTRACE_PRINT("cb is none, weakref killed");
|
||||||
|
_done = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
switch (d.dtype) {
|
switch (d.dtype) {
|
||||||
case (DataType::dtype_int8): {
|
case (DataType::dtype_int8): {
|
||||||
bool_val = cb(getPyArrayNoCpy<int8_t>(d));
|
py_bool = cb(getPyArrayNoCpy<int8_t>(d));
|
||||||
} break;
|
} break;
|
||||||
case (DataType::dtype_int16): {
|
case (DataType::dtype_int16): {
|
||||||
bool_val = cb(getPyArrayNoCpy<int16_t>(d));
|
py_bool = cb(getPyArrayNoCpy<int16_t>(d));
|
||||||
} break;
|
} break;
|
||||||
case (DataType::dtype_int32): {
|
case (DataType::dtype_int32): {
|
||||||
bool_val = cb(getPyArrayNoCpy<int32_t>(d));
|
py_bool = cb(getPyArrayNoCpy<int32_t>(d));
|
||||||
} break;
|
} break;
|
||||||
case (DataType::dtype_fl32): {
|
case (DataType::dtype_fl32): {
|
||||||
bool_val = cb(getPyArrayNoCpy<float>(d));
|
py_bool = cb(getPyArrayNoCpy<float>(d));
|
||||||
} break;
|
} break;
|
||||||
case (DataType::dtype_fl64): {
|
case (DataType::dtype_fl64): {
|
||||||
bool_val = cb(getPyArrayNoCpy<double>(d));
|
py_bool = cb(getPyArrayNoCpy<double>(d));
|
||||||
} break;
|
} break;
|
||||||
default:
|
default:
|
||||||
throw std::runtime_error("BUG");
|
throw std::runtime_error("BUG");
|
||||||
} // End of switch
|
} // End of switch
|
||||||
|
|
||||||
bool res = bool_val.cast<bool>();
|
bool res = py_bool.cast<bool>();
|
||||||
|
if (res == false) {
|
||||||
|
DEBUGTRACE_PRINT("Setting callbacks to None")
|
||||||
|
_done = true;
|
||||||
|
|
||||||
|
// By doing this, we remove the references, but in the mean time this
|
||||||
|
// might also trigger removing Python objects. Including itself, as
|
||||||
|
// there is no reference to it anymore. The consequence is that the
|
||||||
|
// current object might be destroyed from this thread. However, if we
|
||||||
|
// do not remove these references and in lasp_record.py finish() is
|
||||||
|
// not called, we end up with not-garbage collected recordings in
|
||||||
|
// memory. This is also not good. How can we force Python to not yet
|
||||||
|
// destroy this object?
|
||||||
|
// cb.reset();
|
||||||
|
// reset_callback.reset();
|
||||||
|
}
|
||||||
} catch (py::error_already_set &e) {
|
} catch (py::error_already_set &e) {
|
||||||
cerr << "ERROR: Python raised exception from callback function: ";
|
cerr << "ERROR (BUG): Python raised exception from callback function: ";
|
||||||
cerr << e.what() << endl;
|
cerr << e.what() << endl;
|
||||||
abort();
|
abort();
|
||||||
} catch (py::cast_error &e) {
|
} catch (py::cast_error &e) {
|
||||||
cerr << e.what() << endl;
|
cerr << e.what() << endl;
|
||||||
cerr << "ERROR: Python callback does not return boolean value." << endl;
|
cerr << "ERROR (BUG): Python callback does not return boolean value."
|
||||||
|
<< endl;
|
||||||
abort();
|
abort();
|
||||||
} catch (std::exception &e) {
|
} catch (std::exception &e) {
|
||||||
cerr << "Caught unknown exception in Python callback:" << e.what()
|
cerr << "Caught unknown exception in Python callback:" << e.what()
|
||||||
<< endl;
|
<< endl;
|
||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
} // End of scope in which the GIL is acquired
|
||||||
|
// cerr << "=== LEAVE incallback for thread ID: " << std::this_thread::get_id() << endl;
|
||||||
|
|
||||||
|
} // End of function inCallback()
|
||||||
};
|
};
|
||||||
|
|
||||||
void init_datahandler(py::module &m) {
|
void init_datahandler(py::module &m) {
|
||||||
|
|
||||||
/// The C++ class is PyIndataHandler, but for Python, it is called
|
/// The C++ class is PyIndataHandler, but for Python, it is called
|
||||||
/// InDataHandler
|
/// InDataHandler
|
||||||
py::class_<PyIndataHandler> pyidh(m, "InDataHandler");
|
py::class_<PyIndataHandler> pyidh(m, "InDataHandler");
|
||||||
|
@ -5,7 +5,7 @@ requires-python = ">=3.10"
|
|||||||
description = "Library for Acoustic Signal Processing"
|
description = "Library for Acoustic Signal Processing"
|
||||||
license = { "file" = "LICENSE" }
|
license = { "file" = "LICENSE" }
|
||||||
authors = [{ "name" = "J.A. de Jong", "email" = "j.a.dejong@ascee.nl" }]
|
authors = [{ "name" = "J.A. de Jong", "email" = "j.a.dejong@ascee.nl" }]
|
||||||
version = "1.4.2"
|
version = "1.6.8"
|
||||||
|
|
||||||
keywords = ["DSP", "DAQ", "Signal processing"]
|
keywords = ["DSP", "DAQ", "Signal processing"]
|
||||||
|
|
||||||
@ -23,8 +23,7 @@ classifiers = [
|
|||||||
urls = { "Documentation" = "https://lasp.ascee.nl" }
|
urls = { "Documentation" = "https://lasp.ascee.nl" }
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"scipy",
|
"scipy>=1.13.1",
|
||||||
"numpy",
|
|
||||||
"matplotlib>=3.7.2",
|
"matplotlib>=3.7.2",
|
||||||
"appdirs",
|
"appdirs",
|
||||||
"dataclasses_json",
|
"dataclasses_json",
|
||||||
@ -60,10 +59,3 @@ install_components = ["python_modules"]
|
|||||||
# This might not work properly on Windows. Comment this out when testing on
|
# This might not work properly on Windows. Comment this out when testing on
|
||||||
# Windows.
|
# Windows.
|
||||||
mode = "symlink"
|
mode = "symlink"
|
||||||
|
|
||||||
[tool.commitizen]
|
|
||||||
name = "cz_conventional_commits"
|
|
||||||
tag_format = "v$version"
|
|
||||||
version_scheme = "semver"
|
|
||||||
version_provider = "pep621"
|
|
||||||
update_changelog_on_bump = true
|
|
||||||
|
@ -10,11 +10,11 @@ from .lasp_cpp import *
|
|||||||
|
|
||||||
# from .lasp_imptube import * # TwoMicImpedanceTube
|
# from .lasp_imptube import * # TwoMicImpedanceTube
|
||||||
from .lasp_measurement import * # Measurement, scaleBlockSens
|
from .lasp_measurement import * # Measurement, scaleBlockSens
|
||||||
from .lasp_octavefilter import *
|
from .lasp_octavefilter import * # OverallFilterBank, SosOctaveFilterBank, SosThirdOctaveFilterBank
|
||||||
from .lasp_slm import * # SLM, Dummy
|
from .lasp_slm import * # SLM, Dummy
|
||||||
from .lasp_record import * # RecordStatus, Recording
|
from .lasp_record import * # RecordStatus, Recording
|
||||||
from .lasp_daqconfigs import *
|
from .lasp_daqconfigs import * # DaqConfigurations
|
||||||
from .lasp_measurementset import *
|
from .lasp_measurementset import * # MeasurementSet
|
||||||
|
|
||||||
# from .lasp_siggen import * # SignalType, NoiseType, SiggenMessage, SiggenData, Siggen
|
# from .lasp_siggen import * # SignalType, NoiseType, SiggenMessage, SiggenData, Siggen
|
||||||
# from .lasp_weighcal import * # WeighCal
|
# from .lasp_weighcal import * # WeighCal
|
||||||
|
@ -11,7 +11,9 @@ __all__ = ['freqResponse', 'bandpass_fir_design', 'lowpass_fir_design',
|
|||||||
'arbitrary_fir_design']
|
'arbitrary_fir_design']
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from scipy.signal import freqz, hann, firwin2
|
from scipy.signal import freqz, firwin2
|
||||||
|
from scipy.signal.windows import hann
|
||||||
|
from ..lasp_config import empty
|
||||||
|
|
||||||
|
|
||||||
def freqResponse(fs, freq, coefs_b, coefs_a=1.):
|
def freqResponse(fs, freq, coefs_b, coefs_a=1.):
|
||||||
@ -44,7 +46,7 @@ def bandpass_fir_design(L, fs, fl, fu, window=hann):
|
|||||||
Omg2 = 2*np.pi*fu/fs
|
Omg2 = 2*np.pi*fu/fs
|
||||||
Omg1 = 2*np.pi*fl/fs
|
Omg1 = 2*np.pi*fl/fs
|
||||||
|
|
||||||
fir = np.empty(L, dtype=float)
|
fir = empty(L, dtype=float)
|
||||||
|
|
||||||
# First Create ideal band-pass filter
|
# First Create ideal band-pass filter
|
||||||
fir[L//2] = (Omg2-Omg1)/np.pi
|
fir[L//2] = (Omg2-Omg1)/np.pi
|
||||||
@ -64,7 +66,7 @@ def lowpass_fir_design(L, fs, fc, window=hann):
|
|||||||
" than upper cut-off"
|
" than upper cut-off"
|
||||||
|
|
||||||
Omgc = 2*np.pi*fc/fs
|
Omgc = 2*np.pi*fc/fs
|
||||||
fir = np.empty(L, dtype=float)
|
fir = empty(L, dtype=float)
|
||||||
|
|
||||||
# First Create ideal band-pass filter
|
# First Create ideal band-pass filter
|
||||||
fir[L//2] = Omgc/np.pi
|
fir[L//2] = Omgc/np.pi
|
||||||
|
@ -33,7 +33,7 @@ class Atomic:
|
|||||||
|
|
||||||
def checkType(self, val):
|
def checkType(self, val):
|
||||||
if not (type(val) == bool or type(val) == int):
|
if not (type(val) == bool or type(val) == int):
|
||||||
raise RuntimeError("Invalid type for Atomic")
|
raise ValueError("Invalid type for Atomic")
|
||||||
|
|
||||||
def __iadd__(self, toadd):
|
def __iadd__(self, toadd):
|
||||||
self.checkType(toadd)
|
self.checkType(toadd)
|
||||||
|
@ -121,7 +121,7 @@ class SIQtys(Enum):
|
|||||||
unit_name='Pascal',
|
unit_name='Pascal',
|
||||||
unit_symb='Pa',
|
unit_symb='Pa',
|
||||||
level_unit=('dB SPL','dBPa'),
|
level_unit=('dB SPL','dBPa'),
|
||||||
level_ref_name=('2 micropascal', '1 pascal',),
|
level_ref_name=('20 micropascal', '1 pascal',),
|
||||||
level_ref_value=(P_REF, 1),
|
level_ref_value=(P_REF, 1),
|
||||||
cpp_enum = DaqChannel.Qty.AcousticPressure
|
cpp_enum = DaqChannel.Qty.AcousticPressure
|
||||||
)
|
)
|
||||||
|
@ -6,17 +6,38 @@ Author: J.A. de Jong - ASCEE
|
|||||||
Description: LASP configuration
|
Description: LASP configuration
|
||||||
"""
|
"""
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
from .lasp_cpp import LASP_DOUBLE_PRECISION
|
||||||
|
|
||||||
|
if LASP_DOUBLE_PRECISION:
|
||||||
LASP_NUMPY_FLOAT_TYPE = np.float64
|
LASP_NUMPY_FLOAT_TYPE = np.float64
|
||||||
|
LASP_NUMPY_COMPLEX_TYPE = np.complex128
|
||||||
|
else:
|
||||||
|
LASP_NUMPY_FLOAT_TYPE = np.float32
|
||||||
|
LASP_NUMPY_COMPLEX_TYPE = np.float64
|
||||||
|
|
||||||
|
|
||||||
def zeros(shape):
|
def zeros(shape, dtype=float, order='F'):
|
||||||
return np.zeros(shape, dtype=LASP_NUMPY_FLOAT_TYPE, order='F')
|
if dtype == float:
|
||||||
|
return np.zeros(shape, dtype=LASP_NUMPY_FLOAT_TYPE, order=order)
|
||||||
|
elif dtype == complex:
|
||||||
|
return np.zeros(shape, dtype=LASP_NUMPY_COMPLEX_TYPE, order=order)
|
||||||
|
else:
|
||||||
|
raise RuntimeError(f"Unknown dtype: {dtype}")
|
||||||
|
|
||||||
|
|
||||||
def ones(shape):
|
def ones(shape, dtype=float, order='F'):
|
||||||
return np.ones(shape, dtype=LASP_NUMPY_FLOAT_TYPE, order='F')
|
if dtype == float:
|
||||||
|
return np.ones(shape, dtype=LASP_NUMPY_FLOAT_TYPE, order=order)
|
||||||
|
elif dtype == complex:
|
||||||
|
return np.ones(shape, dtype=LASP_NUMPY_COMPLEX_TYPE, order=order)
|
||||||
|
else:
|
||||||
|
raise RuntimeError(f"Unknown dtype: {dtype}")
|
||||||
|
|
||||||
|
def empty(shape, dtype=float, order='F'):
|
||||||
|
if dtype == float:
|
||||||
|
return np.empty(shape, dtype=LASP_NUMPY_FLOAT_TYPE, order=order)
|
||||||
|
elif dtype == complex:
|
||||||
|
return np.empty(shape, dtype=LASP_NUMPY_COMPLEX_TYPE, order=order)
|
||||||
|
else:
|
||||||
|
raise RuntimeError(f"Unknown dtype: {dtype}")
|
||||||
|
|
||||||
def empty(shape):
|
|
||||||
return np.empty(shape, dtype=LASP_NUMPY_FLOAT_TYPE, order='F')
|
|
||||||
|
@ -1,173 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""!
|
|
||||||
Author: J.A. de Jong - ASCEE
|
|
||||||
|
|
||||||
Description: Two-microphone impedance tube methods
|
|
||||||
"""
|
|
||||||
__all__ = ['TwoMicImpedanceTube']
|
|
||||||
# from lrftubes import Air
|
|
||||||
from .lasp_measurement import Measurement
|
|
||||||
from numpy import pi, sqrt, exp
|
|
||||||
import numpy as np
|
|
||||||
from scipy.interpolate import UnivariateSpline
|
|
||||||
# from lrftubes import PrsDuct
|
|
||||||
from functools import lru_cache
|
|
||||||
|
|
||||||
class TwoMicImpedanceTube:
|
|
||||||
def __init__(self, mnormal: Measurement,
|
|
||||||
mswitched: Measurement,
|
|
||||||
s: float,
|
|
||||||
d1: float,
|
|
||||||
d2: float,
|
|
||||||
fl: float = None,
|
|
||||||
fu: float = None,
|
|
||||||
periodic_method=False,
|
|
||||||
# mat= Air(),
|
|
||||||
D_imptube = 50e-3,
|
|
||||||
thermoviscous = True,
|
|
||||||
**kwargs):
|
|
||||||
|
|
||||||
"""
|
|
||||||
Initialize two-microphone impedance tube methods
|
|
||||||
|
|
||||||
Args:
|
|
||||||
mnormal: Measurement in normal configuration
|
|
||||||
mswitched: Measurement in normal configuration
|
|
||||||
s: Microphone distance
|
|
||||||
fl: Lower evaluation frequency
|
|
||||||
fu: Lower evaluation frequency
|
|
||||||
|
|
||||||
kwargs: tuple with extra arguments, of which:
|
|
||||||
N: Period length of periodic excitation *obligatory*
|
|
||||||
chan0: Measurement channel index of mic 0
|
|
||||||
chan1: Measurement channel index of mic 1
|
|
||||||
|
|
||||||
"""
|
|
||||||
self.mnormal = mnormal
|
|
||||||
self.mswitched = mswitched
|
|
||||||
|
|
||||||
self.mat = mat
|
|
||||||
self.s = s
|
|
||||||
self.d1 = d1
|
|
||||||
self.d2 = d2
|
|
||||||
|
|
||||||
self.fl = fl
|
|
||||||
if fl is None:
|
|
||||||
ksmin = 0.1*pi
|
|
||||||
kmin = ksmin/s
|
|
||||||
self.fl = kmin*mat.c0/2/pi
|
|
||||||
|
|
||||||
self.fu = fu
|
|
||||||
if fu is None:
|
|
||||||
ksmax = 0.8*pi
|
|
||||||
kmax = ksmax/s
|
|
||||||
self.fu = kmax*mat.c0/2/pi
|
|
||||||
|
|
||||||
self.thermoviscous = thermoviscous
|
|
||||||
self.D_imptube = D_imptube
|
|
||||||
|
|
||||||
self.periodic_method = periodic_method
|
|
||||||
self.channels = [kwargs.pop('chan0', 0), kwargs.pop('chan1', 1)]
|
|
||||||
# Compute calibration correction
|
|
||||||
if periodic_method:
|
|
||||||
self.N = kwargs.pop('N')
|
|
||||||
freq, C1 = mnormal.periodicCPS(self.N, channels=self.channels)
|
|
||||||
freq, C2 = mswitched.periodicCPS(self.N, channels=self.channels)
|
|
||||||
else:
|
|
||||||
self.nfft = kwargs.pop('nfft', 16000)
|
|
||||||
freq, C1 = mnormal.CPS(nfft=self.nfft, channels=self.channels)
|
|
||||||
freq, C2 = mswitched.CPS(nfft=self.nfft, channels=self.channels)
|
|
||||||
|
|
||||||
# Store this, as it is often used for just a single sample.
|
|
||||||
self.C1 = C1
|
|
||||||
self.freq = freq
|
|
||||||
|
|
||||||
self.il = np.where(self.freq<= self.fl)[0][-1]
|
|
||||||
self.ul = np.where(self.freq > self.fu)[0][0]
|
|
||||||
|
|
||||||
# Calibration correction factor
|
|
||||||
# self.K = 0*self.freq + 1.0
|
|
||||||
K = sqrt(C2[:,0,1]*C1[:,0,0]/(C2[:,1,1]*C1[:,1,0]))
|
|
||||||
# self.K = UnivariateSpline(self.freq, K.real)(self.freq) +\
|
|
||||||
# 1j*UnivariateSpline(self.freq, K.imag)(self.freq)
|
|
||||||
self.K = K
|
|
||||||
|
|
||||||
def cut_to_limits(self, ar):
|
|
||||||
return ar[self.il:self.ul]
|
|
||||||
|
|
||||||
def getFreq(self):
|
|
||||||
"""
|
|
||||||
Array of frequencies, cut to limits of validity
|
|
||||||
"""
|
|
||||||
return self.cut_to_limits(self.freq)
|
|
||||||
|
|
||||||
@lru_cache
|
|
||||||
def G_AB(self, meas):
|
|
||||||
if meas is self.mnormal:
|
|
||||||
C = self.C1
|
|
||||||
freq = self.freq
|
|
||||||
else:
|
|
||||||
if self.periodic_method:
|
|
||||||
freq, C = meas.periodicCPS(self.N, self.channels)
|
|
||||||
else:
|
|
||||||
freq, C = meas.CPS(nfft=self.nfft, channels=self.channels)
|
|
||||||
|
|
||||||
# Microphone transfer function
|
|
||||||
G_AB = self.K*C[:,1,0]/C[:,0,0]
|
|
||||||
return self.getFreq(), self.cut_to_limits(G_AB)
|
|
||||||
|
|
||||||
def k(self, freq):
|
|
||||||
"""
|
|
||||||
Wave number, or thermoviscous wave number
|
|
||||||
"""
|
|
||||||
if self.thermoviscous:
|
|
||||||
D = self.D_imptube
|
|
||||||
S = pi/4*D**2
|
|
||||||
d = PrsDuct(0, S=S, rh=D/4, cs='circ')
|
|
||||||
d.mat = self.mat
|
|
||||||
omg = 2*pi*freq
|
|
||||||
G, Z = d.GammaZc(omg)
|
|
||||||
return G
|
|
||||||
else:
|
|
||||||
return 2*pi*freq/self.mat.c0
|
|
||||||
|
|
||||||
|
|
||||||
def R(self, meas):
|
|
||||||
freq, G_AB = self.G_AB(meas)
|
|
||||||
s = self.s
|
|
||||||
k = self.k(freq)
|
|
||||||
d1 = self.d1
|
|
||||||
RpA = (G_AB - exp(-1j*k*s))/(exp(1j*k*s)-G_AB)
|
|
||||||
|
|
||||||
R = RpA*exp(2*1j*k*(s+d1))
|
|
||||||
|
|
||||||
return freq, R
|
|
||||||
|
|
||||||
def alpha(self, meas):
|
|
||||||
"""
|
|
||||||
Acoustic absorption coefficient
|
|
||||||
"""
|
|
||||||
freq, R = self.R(meas)
|
|
||||||
return freq, 1 - np.abs(R)**2
|
|
||||||
|
|
||||||
def z(self, meas):
|
|
||||||
"""
|
|
||||||
Acoustic impedance at the position of the sample, in front of the sample
|
|
||||||
"""
|
|
||||||
freq, R = self.R(meas)
|
|
||||||
return freq, self.mat.z0*(1+R)/(1-R)
|
|
||||||
|
|
||||||
def zs(self, meas):
|
|
||||||
"""
|
|
||||||
Sample impedance jump, assuming a cavity behind the sample with
|
|
||||||
thickness d2
|
|
||||||
"""
|
|
||||||
freq, R = self.R(meas)
|
|
||||||
z0 = self.mat.z0
|
|
||||||
k = 2*pi*freq/self.mat.c0
|
|
||||||
d2 = self.d2
|
|
||||||
|
|
||||||
zs = 2*z0*(1-R*exp(2*1j*k*d2))/((R-1)*(exp(2*d2*k*1j)-1))
|
|
||||||
return freq, zs
|
|
||||||
|
|
@ -1,6 +1,24 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from weakref import WeakValueDictionary
|
||||||
|
import h5py as h5
|
||||||
|
import uuid
|
||||||
|
import pathlib
|
||||||
|
import glob
|
||||||
|
import itertools
|
||||||
|
import numpy as np
|
||||||
|
from enum import Enum, unique
|
||||||
|
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 .lasp_version import LASP_VERSION_MAJOR, LASP_VERSION_MINOR
|
||||||
|
from .lasp_cpp import Window, DaqChannel, AvPowerSpectra
|
||||||
|
from typing import List
|
||||||
|
from functools import lru_cache
|
||||||
|
from .lasp_config import ones
|
||||||
"""!
|
"""!
|
||||||
Author: J.A. de Jong - ASCEE
|
Author: J.A. de Jong - ASCEE
|
||||||
|
|
||||||
@ -46,28 +64,11 @@ The video dataset can possibly be not present in the data.
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__all__ = ["Measurement", "scaleBlockSens", "MeasurementType"]
|
|
||||||
from contextlib import contextmanager
|
|
||||||
from weakref import WeakValueDictionary
|
|
||||||
import h5py as h5
|
|
||||||
import uuid
|
|
||||||
import pathlib
|
|
||||||
import glob
|
|
||||||
import itertools
|
|
||||||
import numpy as np
|
|
||||||
from enum import Enum, unique
|
|
||||||
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 .lasp_version import LASP_VERSION_MAJOR, LASP_VERSION_MINOR
|
|
||||||
from .lasp_cpp import Window, DaqChannel, AvPowerSpectra
|
|
||||||
from typing import List
|
|
||||||
from functools import lru_cache
|
|
||||||
|
|
||||||
|
__all__ = ["Measurement", "scaleBlockSens", "MeasurementType"]
|
||||||
# Measurement file extension
|
# Measurement file extension
|
||||||
MEXT = 'h5'
|
MEXT = "h5"
|
||||||
DOTMEXT = f'.{MEXT}'
|
DOTMEXT = f".{MEXT}"
|
||||||
|
|
||||||
|
|
||||||
@unique
|
@unique
|
||||||
@ -223,7 +224,7 @@ class IterData(IterRawData):
|
|||||||
|
|
||||||
def __init__(self, fa, channels, sensitivity, **kwargs):
|
def __init__(self, fa, channels, sensitivity, **kwargs):
|
||||||
super().__init__(fa, channels, **kwargs)
|
super().__init__(fa, channels, **kwargs)
|
||||||
self.sens = np.asarray(sensitivity)[self.channels]
|
self.sens = np.asarray(sensitivity, dtype=LASP_NUMPY_FLOAT_TYPE)[self.channels]
|
||||||
assert self.sens.ndim == 1
|
assert self.sens.ndim == 1
|
||||||
|
|
||||||
def __next__(self):
|
def __next__(self):
|
||||||
@ -235,7 +236,8 @@ class Measurement:
|
|||||||
"""Provides access to measurement data stored in the h5 measurement file
|
"""Provides access to measurement data stored in the h5 measurement file
|
||||||
format."""
|
format."""
|
||||||
|
|
||||||
# Store a dict of open measurements, with uuid string as a key. We store them as a weak ref.
|
# Store a dict of open measurements, with uuid string as a key. We store
|
||||||
|
# them as a weak ref.
|
||||||
uuid_s = WeakValueDictionary()
|
uuid_s = WeakValueDictionary()
|
||||||
|
|
||||||
def __init__(self, fn):
|
def __init__(self, fn):
|
||||||
@ -277,7 +279,7 @@ class Measurement:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
# Try to catch UUID (Universally Unique IDentifier)
|
# Try to catch UUID (Universally Unique IDentifier)
|
||||||
self._UUID = f.attrs['UUID']
|
self._UUID = f.attrs["UUID"]
|
||||||
# Flag indicating we have to add a new UUID
|
# Flag indicating we have to add a new UUID
|
||||||
create_new_uuid = False
|
create_new_uuid = False
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@ -290,17 +292,17 @@ class Measurement:
|
|||||||
# The last filename is a filename that *probably* is the reference measurement with
|
# The last filename is a filename that *probably* is the reference measurement with
|
||||||
# given UUID. If it is not, we will search for it in the same directory as `this` measurement.
|
# given UUID. If it is not, we will search for it in the same directory as `this` measurement.
|
||||||
# If we cannot find it there, we will give up, and remove the field corresponding to this reference measurement type.
|
# If we cannot find it there, we will give up, and remove the field corresponding to this reference measurement type.
|
||||||
refMeas_list = f.attrs['refMeas']
|
refMeas_list = f.attrs["refMeas"]
|
||||||
|
|
||||||
# Build a tuple string from it
|
# Build a tuple string from it
|
||||||
self._refMeas = {}
|
self._refMeas = {}
|
||||||
for (key, val, name) in refMeas_list:
|
for key, val, name in refMeas_list:
|
||||||
self._refMeas[MeasurementType(int(key))] = (val, name)
|
self._refMeas[MeasurementType(int(key))] = (val, name)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
self._refMeas = {}
|
self._refMeas = {}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._type_int = f.attrs['type_int']
|
self._type_int = f.attrs["type_int"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
self._type_int = 0
|
self._type_int = 0
|
||||||
|
|
||||||
@ -329,10 +331,10 @@ class Measurement:
|
|||||||
try:
|
try:
|
||||||
sens = f.attrs["sensitivity"]
|
sens = f.attrs["sensitivity"]
|
||||||
self._sens = (
|
self._sens = (
|
||||||
sens * np.ones(self.nchannels) if isinstance(sens, float) else sens
|
sens * ones(self.nchannels) if isinstance(sens, float) else sens
|
||||||
)
|
)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
self._sens = np.ones(self.nchannels)
|
self._sens = ones(self.nchannels)
|
||||||
|
|
||||||
# The time is cached AND ALWAYS ASSUMED TO BE AN IMMUTABLE OBJECT.
|
# The time is cached AND ALWAYS ASSUMED TO BE AN IMMUTABLE OBJECT.
|
||||||
# It is also cached. Changing the measurement timestamp should not
|
# It is also cached. Changing the measurement timestamp should not
|
||||||
@ -367,7 +369,9 @@ class Measurement:
|
|||||||
self.genNewUUID()
|
self.genNewUUID()
|
||||||
else:
|
else:
|
||||||
if self.UUID in Measurement.uuid_s.keys():
|
if self.UUID in Measurement.uuid_s.keys():
|
||||||
raise RuntimeError(f"Measurement '{self.name}' is already opened. Cannot open measurement twice. Note: this error can happen when measurements are manually copied.")
|
raise RuntimeError(
|
||||||
|
f"Measurement '{self.name}' is already opened. Cannot open measurement twice. Note: this error can happen when measurements are manually copied."
|
||||||
|
)
|
||||||
|
|
||||||
# Store weak reference to 'self' in list of UUID's. They are removed when no file is open anymore
|
# Store weak reference to 'self' in list of UUID's. They are removed when no file is open anymore
|
||||||
Measurement.uuid_s[self._UUID] = self
|
Measurement.uuid_s[self._UUID] = self
|
||||||
@ -396,7 +400,7 @@ class Measurement:
|
|||||||
"""
|
"""
|
||||||
Create new UUID for measurement and store in file.
|
Create new UUID for measurement and store in file.
|
||||||
"""
|
"""
|
||||||
self.setAttribute('UUID', str(uuid.uuid1()))
|
self.setAttribute("UUID", str(uuid.uuid1()))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def UUID(self):
|
def UUID(self):
|
||||||
@ -423,40 +427,54 @@ class Measurement:
|
|||||||
# Try to find it in the dictionary of of open measurements
|
# Try to find it in the dictionary of of open measurements
|
||||||
if required_uuid in Measurement.uuid_s.keys():
|
if required_uuid in Measurement.uuid_s.keys():
|
||||||
m = Measurement.uuid_s[required_uuid]
|
m = Measurement.uuid_s[required_uuid]
|
||||||
logging.info(f'Returned reference measurement {m.name} from list of open measurements')
|
logging.debug(
|
||||||
|
f"Returned reference measurement {m.name} from list of open measurements"
|
||||||
|
)
|
||||||
|
|
||||||
# Not found in list of openend measurements. See if we can open it using its last stored file name we know of
|
# Not found in list of openend measurements. See if we can open it using its last stored file name we know of
|
||||||
if m is None:
|
if m is None:
|
||||||
try:
|
try:
|
||||||
m = Measurement(possible_name)
|
m = Measurement(possible_name)
|
||||||
if m.UUID == required_uuid:
|
if m.UUID == required_uuid:
|
||||||
logging.info(f'Opened reference measurement {m.name} by name')
|
logging.info(f"Opened reference measurement {m.name} by name")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f'Could not find reference measurement using file name: {possible_name}')
|
logging.error(
|
||||||
|
f"Could not find reference measurement using file name: {possible_name}"
|
||||||
|
)
|
||||||
|
|
||||||
# Last resort, see if we can find the right measurement in the same folder
|
# Last resort, see if we can find the right measurement in the same folder
|
||||||
if m is None:
|
if m is None:
|
||||||
try:
|
try:
|
||||||
folder, _ = os.path.split(self.fn)
|
folder, _ = os.path.split(self.fn)
|
||||||
m = Measurement.fromFolderWithUUID(required_uuid, folder, skip=[self.name])
|
m = Measurement.fromFolderWithUUID(
|
||||||
logging.info('Found reference measurement in folder with correct UUID. Updating name of reference measurement')
|
required_uuid, folder, skip=[self.name]
|
||||||
|
)
|
||||||
|
logging.info(
|
||||||
|
"Found reference measurement in folder with correct UUID. Updating name of reference measurement"
|
||||||
|
)
|
||||||
# Update the measurement file name in the list, such that next time it
|
# Update the measurement file name in the list, such that next time it
|
||||||
# can be opened just by its name.
|
# can be opened just by its name.
|
||||||
self.setRefMeas(m)
|
self.setRefMeas(m)
|
||||||
except:
|
except:
|
||||||
logging.error("Could not find the reference measurement. Is it deleted?")
|
logging.error(
|
||||||
|
"Could not find the reference measurement. Is it deleted?"
|
||||||
|
)
|
||||||
|
|
||||||
# Well, we found it. Now make sure the reference measurement actually has the right type (User could have marked it as a NotSpecific for example in the mean time).
|
# Well, we found it. Now make sure the reference measurement actually has the right type (User could have marked it as a NotSpecific for example in the mean time).
|
||||||
if m is not None:
|
if m is not None:
|
||||||
if m.measurementType() != mtype:
|
if m.measurementType() != mtype:
|
||||||
m.removeRefMeas(mtype)
|
m.removeRefMeas(mtype)
|
||||||
raise RuntimeError(f"Reference measurement for {self.name} is not a proper reference (anymore).")
|
raise RuntimeError(
|
||||||
|
f"Reference measurement for {self.name} is not a proper reference (anymore)."
|
||||||
|
)
|
||||||
|
|
||||||
# Whow, we passed all security checks, here we go!
|
# Whow, we passed all security checks, here we go!
|
||||||
return m
|
return m
|
||||||
else:
|
else:
|
||||||
# Nope, not there.
|
# Nope, not there.
|
||||||
raise RuntimeError(f"Could not find the reference measurement for '{self.name}'. Is it deleted?")
|
raise RuntimeError(
|
||||||
|
f"Could not find the reference measurement for '{self.name}'. Is it deleted?"
|
||||||
|
)
|
||||||
|
|
||||||
def removeRefMeas(self, mtype: MeasurementType):
|
def removeRefMeas(self, mtype: MeasurementType):
|
||||||
"""
|
"""
|
||||||
@ -477,9 +495,12 @@ class Measurement:
|
|||||||
with self.file("r+") as f:
|
with self.file("r+") as f:
|
||||||
# Update attribute in file. Stored as a flat list of string tuples:
|
# Update attribute in file. Stored as a flat list of string tuples:
|
||||||
# [(ref_value1, uuid_1, name_1), (ref_value2, uuid_2, name_2), ...]
|
# [(ref_value1, uuid_1, name_1), (ref_value2, uuid_2, name_2), ...]
|
||||||
reflist = list((str(key.value), val1, val2) for key, (val1, val2) in self._refMeas.items())
|
reflist = list(
|
||||||
|
(str(key.value), val1, val2)
|
||||||
|
for key, (val1, val2) in self._refMeas.items()
|
||||||
|
)
|
||||||
# print(reflist)
|
# print(reflist)
|
||||||
f.attrs['refMeas'] = reflist
|
f.attrs["refMeas"] = reflist
|
||||||
|
|
||||||
def setRefMeas(self, m: Measurement):
|
def setRefMeas(self, m: Measurement):
|
||||||
"""
|
"""
|
||||||
@ -489,19 +510,21 @@ class Measurement:
|
|||||||
"""
|
"""
|
||||||
mtype = m.measurementType()
|
mtype = m.measurementType()
|
||||||
if mtype == MeasurementType.NotSpecific:
|
if mtype == MeasurementType.NotSpecific:
|
||||||
raise ValueError('Measurement to be set as reference is not a reference measurement')
|
raise ValueError(
|
||||||
|
"Measurement to be set as reference is not a reference measurement"
|
||||||
|
)
|
||||||
|
|
||||||
self._refMeas[mtype] = (m.UUID, m.name)
|
self._refMeas[mtype] = (m.UUID, m.name)
|
||||||
self.__storeReafMeas()
|
self.__storeReafMeas()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def fromFolderWithUUID(uuid_str: str, folder: str='', skip=[]):
|
def fromFolderWithUUID(uuid_str: str, folder: str = "", skip=[]):
|
||||||
"""
|
"""
|
||||||
Returns Measurement object from a given UUID string. It first tries to find whether there
|
Returns Measurement object from a given UUID string. It first tries to find whether there
|
||||||
is an uuid in the static list of weak references. If not, it will try to open files in
|
is an uuid in the static list of weak references. If not, it will try to open files in
|
||||||
the current file path.
|
the current file path.
|
||||||
"""
|
"""
|
||||||
for fn in glob.glob(str(pathlib.Path(folder)) + f'/*{DOTMEXT}'):
|
for fn in glob.glob(str(pathlib.Path(folder)) + f"/*{DOTMEXT}"):
|
||||||
# Do not try to open this file in case it is in the 'skip' list.
|
# Do not try to open this file in case it is in the 'skip' list.
|
||||||
if len(list(filter(lambda a: a in fn, skip))) > 0:
|
if len(list(filter(lambda a: a in fn, skip))) > 0:
|
||||||
continue
|
continue
|
||||||
@ -512,9 +535,11 @@ class Measurement:
|
|||||||
# Update 'last_fn' attribute in dict of stored reference measurements
|
# Update 'last_fn' attribute in dict of stored reference measurements
|
||||||
return m
|
return m
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f'Possible measurement file {fn} returned error {e} when opening.')
|
logging.error(
|
||||||
|
f"Possible measurement file {fn} returned error {e} when opening."
|
||||||
|
)
|
||||||
|
|
||||||
raise RuntimeError(f'Measurement with UUID {uuid_str} could not be found.')
|
raise RuntimeError(f"Measurement with UUID {uuid_str} could not be found.")
|
||||||
|
|
||||||
def setAttribute(self, attrname: str, value):
|
def setAttribute(self, attrname: str, value):
|
||||||
"""
|
"""
|
||||||
@ -534,7 +559,7 @@ class Measurement:
|
|||||||
"""
|
"""
|
||||||
Returns True when a measurement is flagged as being of a certaint "MeasurementType"
|
Returns True when a measurement is flagged as being of a certaint "MeasurementType"
|
||||||
"""
|
"""
|
||||||
if (type_.value & self._type_int):
|
if type_.value & self._type_int:
|
||||||
return True
|
return True
|
||||||
elif type_.value == self._type_int == 0:
|
elif type_.value == self._type_int == 0:
|
||||||
return True
|
return True
|
||||||
@ -544,7 +569,7 @@ class Measurement:
|
|||||||
"""
|
"""
|
||||||
Set the measurement type to given type
|
Set the measurement type to given type
|
||||||
"""
|
"""
|
||||||
self.setAttribute('type_int', type_.value)
|
self.setAttribute("type_int", type_.value)
|
||||||
|
|
||||||
def measurementType(self):
|
def measurementType(self):
|
||||||
"""
|
"""
|
||||||
@ -885,7 +910,7 @@ class Measurement:
|
|||||||
"""
|
"""
|
||||||
if isinstance(sens, float):
|
if isinstance(sens, float):
|
||||||
# Put all sensitivities equal
|
# Put all sensitivities equal
|
||||||
sens = sens * np.ones(self.nchannels)
|
sens = sens * ones(self.nchannels)
|
||||||
elif isinstance(sens, list):
|
elif isinstance(sens, list):
|
||||||
sens = np.asarray(sens)
|
sens = np.asarray(sens)
|
||||||
|
|
||||||
@ -998,9 +1023,9 @@ class Measurement:
|
|||||||
happen that a Measurement object is created twice for the same backing file, which we do not allow.
|
happen that a Measurement object is created twice for the same backing file, which we do not allow.
|
||||||
"""
|
"""
|
||||||
# See if the base part of the filename is referring to a file that is already open
|
# See if the base part of the filename is referring to a file that is already open
|
||||||
with h5.File(fn, 'r') as f:
|
with h5.File(fn, "r") as f:
|
||||||
try:
|
try:
|
||||||
theuuid = f.attrs['UUID']
|
theuuid = f.attrs["UUID"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# No UUID stored in measurement. This is an old measurement that did not have UUID's
|
# No UUID stored in measurement. This is an old measurement that did not have UUID's
|
||||||
# We create a new UUID here such that the file is opened from the filesystem
|
# We create a new UUID here such that the file is opened from the filesystem
|
||||||
@ -1131,11 +1156,8 @@ class Measurement:
|
|||||||
if data.ndim != 2:
|
if data.ndim != 2:
|
||||||
data = data[:, np.newaxis]
|
data = data[:, np.newaxis]
|
||||||
|
|
||||||
try:
|
if not (isinstance(sensitivity, np.ndarray) and sensitivity.ndim >= 1):
|
||||||
len(sensitivity)
|
sensitivity = np.asarray(sensitivity)[np.newaxis]
|
||||||
except:
|
|
||||||
raise ValueError("Sensitivity should be given as array-like data type")
|
|
||||||
sensitivity = np.asarray(sensitivity)
|
|
||||||
|
|
||||||
nchannels = data.shape[1]
|
nchannels = data.shape[1]
|
||||||
if nchannels != sensitivity.shape[0]:
|
if nchannels != sensitivity.shape[0]:
|
||||||
@ -1148,7 +1170,7 @@ class Measurement:
|
|||||||
raise RuntimeError("Illegal length of channelNames list given")
|
raise RuntimeError("Illegal length of channelNames list given")
|
||||||
|
|
||||||
if qtys is None:
|
if qtys is None:
|
||||||
qtys = [SIQtys.AP] * nchannels
|
qtys = [SIQtys.fromInt(1)] * nchannels # Acoustic pressure is default
|
||||||
else:
|
else:
|
||||||
if len(qtys) != nchannels:
|
if len(qtys) != nchannels:
|
||||||
raise RuntimeError("Illegal length of qtys list given")
|
raise RuntimeError("Illegal length of qtys list given")
|
||||||
@ -1161,7 +1183,7 @@ class Measurement:
|
|||||||
hf.attrs["nchannels"] = nchannels
|
hf.attrs["nchannels"] = nchannels
|
||||||
|
|
||||||
# Add physical quantity indices
|
# Add physical quantity indices
|
||||||
hf.attrs['qtys_enum_idx'] = [qty.toInt() for qty in qtys]
|
hf.attrs["qtys_enum_idx"] = [qty.toInt() for qty in qtys]
|
||||||
|
|
||||||
# Add channel names in case given
|
# Add channel names in case given
|
||||||
if channelNames is not None:
|
if channelNames is not None:
|
||||||
@ -1199,7 +1221,7 @@ class Measurement:
|
|||||||
nchannels = 1
|
nchannels = 1
|
||||||
nframes = len(data)
|
nframes = len(data)
|
||||||
data = data[:, np.newaxis]
|
data = data[:, np.newaxis]
|
||||||
sensitivity = np.ones(nchannels)
|
sensitivity = ones(nchannels)
|
||||||
|
|
||||||
with h5.File(newfn, "w") as hf:
|
with h5.File(newfn, "w") as hf:
|
||||||
hf.attrs["samplerate"] = samplerate
|
hf.attrs["samplerate"] = samplerate
|
||||||
@ -1217,4 +1239,3 @@ class Measurement:
|
|||||||
ad[0] = data
|
ad[0] = data
|
||||||
|
|
||||||
return Measurement(newfn)
|
return Measurement(newfn)
|
||||||
|
|
||||||
|
@ -3,7 +3,8 @@ Provides class MeasurementSet, a class used to perform checks and adjustments
|
|||||||
on a group of measurements at the same time.
|
on a group of measurements at the same time.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
__all__ = ['MeasurementSet']
|
|
||||||
|
__all__ = ["MeasurementSet"]
|
||||||
from .lasp_measurement import Measurement, MeasurementType
|
from .lasp_measurement import Measurement, MeasurementType
|
||||||
from typing import List
|
from typing import List
|
||||||
import time
|
import time
|
||||||
@ -13,28 +14,35 @@ class MeasurementSet(list):
|
|||||||
"""
|
"""
|
||||||
Group of measurements that have some correspondence to one another. Class
|
Group of measurements that have some correspondence to one another. Class
|
||||||
is used to operate on multiple measurements at once.
|
is used to operate on multiple measurements at once.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, mlist: List[Measurement] = []):
|
def __init__(self, mlist: List[Measurement] = []):
|
||||||
"""
|
"""
|
||||||
Initialize a measurement set
|
Initialize a measurement set
|
||||||
|
|
||||||
Args:
|
Arg:
|
||||||
mlist: Measurement list
|
mlist: Measurement list
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if any([not isinstance(i, Measurement) for i in mlist]):
|
|
||||||
raise TypeError('Object in list should be of Measurement type')
|
|
||||||
|
|
||||||
|
if any([not isinstance(i, Measurement) for i in mlist]):
|
||||||
|
raise TypeError("Object in list should be of Measurement type")
|
||||||
|
|
||||||
|
# Sort by time stamp, otherwise the order is random
|
||||||
|
mlist.sort(key=lambda x: x.time, reverse=True)
|
||||||
super().__init__(mlist)
|
super().__init__(mlist)
|
||||||
|
|
||||||
def getNewestReferenceMeasurement(self, mtype: MeasurementType):
|
def getNewestReferenceMeasurement(self, mtype: MeasurementType):
|
||||||
"""Return the newest (in time) measurement in the current list of a certain type. Returns None in case no measurement could be found.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
mtype (MeasurementType): The type required.
|
|
||||||
"""
|
"""
|
||||||
|
Get the NEWEST ref. measurement of a current type, in the current set.
|
||||||
|
|
||||||
|
Arg:
|
||||||
|
mtype (MeasurementType): The type required.
|
||||||
|
|
||||||
|
Return:
|
||||||
|
- The newest (in time) measurement in the current list of a certain type.
|
||||||
|
- None, in case no measurement could be found.
|
||||||
|
"""
|
||||||
|
|
||||||
mnewest = None
|
mnewest = None
|
||||||
for m in self:
|
for m in self:
|
||||||
if m.measurementType() == mtype:
|
if m.measurementType() == mtype:
|
||||||
@ -45,14 +53,33 @@ class MeasurementSet(list):
|
|||||||
mnewest = m
|
mnewest = m
|
||||||
return mnewest
|
return mnewest
|
||||||
|
|
||||||
|
def getReferenceMeasurements(self, mtype: MeasurementType):
|
||||||
|
"""Get ALL ref. measurements of a certain type, in the current set.
|
||||||
|
|
||||||
|
Arg:
|
||||||
|
mtype (MeasurementType): The type of which to list
|
||||||
|
|
||||||
|
Return:
|
||||||
|
A new measurement set, including all measurements of a certain type
|
||||||
|
"""
|
||||||
|
|
||||||
|
return [m for m in self if m.measurementType() == mtype]
|
||||||
|
|
||||||
def getNewestReferenceMeasurements(self):
|
def getNewestReferenceMeasurements(self):
|
||||||
"""Returns a dictionary with newest measurement of each type that is not specific returns None in case no measurement is found."""
|
"""
|
||||||
|
Get the NEWEST ref. measurement of all types, in the current set.
|
||||||
|
|
||||||
|
Return:
|
||||||
|
- A dictionary with the newest measurement of each type that is not specific
|
||||||
|
- None, in case no measurement is found.
|
||||||
|
"""
|
||||||
|
|
||||||
newest = {}
|
newest = {}
|
||||||
for m in self:
|
for m in self:
|
||||||
mtype = m.measurementType()
|
mtype = m.measurementType()
|
||||||
if mtype == MeasurementType.NotSpecific:
|
if mtype == MeasurementType.NotSpecific:
|
||||||
continue
|
continue
|
||||||
if not mtype in newest:
|
if mtype not in newest:
|
||||||
newest[mtype] = m
|
newest[mtype] = m
|
||||||
else:
|
else:
|
||||||
if m.time > newest[mtype].time:
|
if m.time > newest[mtype].time:
|
||||||
@ -60,8 +87,17 @@ class MeasurementSet(list):
|
|||||||
return newest
|
return newest
|
||||||
|
|
||||||
def newestReferenceOlderThan(self, secs):
|
def newestReferenceOlderThan(self, secs):
|
||||||
"""Returns a dictionary of references with the newest reference, that is still
|
"""
|
||||||
older than `secs` seconds. """
|
Get a dictionary of reference measurements which are older than a
|
||||||
|
specified threshold. Only one of each type is returned.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
- secs: time threshold, in seconds
|
||||||
|
|
||||||
|
Return:
|
||||||
|
- a dictionary of references with the newest reference, that is still
|
||||||
|
older than `secs` seconds
|
||||||
|
"""
|
||||||
curtime = time.time()
|
curtime = time.time()
|
||||||
newest = self.getNewestReferenceMeasurements()
|
newest = self.getNewestReferenceMeasurements()
|
||||||
newest_older_than = {}
|
newest_older_than = {}
|
||||||
@ -70,28 +106,31 @@ class MeasurementSet(list):
|
|||||||
newest_older_than[key] = m
|
newest_older_than[key] = m
|
||||||
return newest_older_than
|
return newest_older_than
|
||||||
|
|
||||||
|
|
||||||
def measTimeSame(self):
|
def measTimeSame(self):
|
||||||
"""
|
"""
|
||||||
Returns True if all measurements have the same measurement
|
Returns True if all measurements have the same measurement length and
|
||||||
time (recorded time)
|
sample rate
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if len(self) > 0:
|
if len(self) > 0:
|
||||||
first = self[0].N
|
firstN = self[0].N # samples
|
||||||
return all([first == meas.N for meas in self])
|
firstFS = self[0].samplerate # sample rate
|
||||||
|
sameN = all([firstN == meas.N for meas in self])
|
||||||
|
sameFS = all([firstFS == meas.samplerate for meas in self])
|
||||||
|
return sameN and sameFS
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def measSimilar(self):
|
def measSimilar(self):
|
||||||
"""
|
"""
|
||||||
Similar means: channel metadata is the same, and the measurement time
|
Similar means: channel metadata is the same, and the measurement time
|
||||||
is the same. It means that the recorded data is, of course, different.
|
is the same.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
True if measChannelsSame() and measTimeSame() else False
|
- True if measChannelsSame() and measTimeSame()
|
||||||
|
- False otherwise
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return self.measTimeSame() and self.measChannelsSame()
|
return self.measTimeSame() and self.measChannelsSame()
|
||||||
|
|
||||||
def measChannelsSame(self):
|
def measChannelsSame(self):
|
||||||
@ -101,9 +140,9 @@ class MeasurementSet(list):
|
|||||||
a set of measurements, simultaneously. If the channel data is the same
|
a set of measurements, simultaneously. If the channel data is the same
|
||||||
(name, sensitivity, ...) it returns True.
|
(name, sensitivity, ...) it returns True.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if len(self) > 0:
|
if len(self) > 0:
|
||||||
first = self[0].channelConfig
|
first = self[0].channelConfig
|
||||||
return all([first == meas.channelConfig for meas in self])
|
return all([first == meas.channelConfig for meas in self])
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -6,12 +6,10 @@ Author: J.A. de Jong - ASCEE
|
|||||||
Provides the implementations of (fractional) octave filter banks
|
Provides the implementations of (fractional) octave filter banks
|
||||||
|
|
||||||
"""
|
"""
|
||||||
__all__ = ['FirOctaveFilterBank', 'FirThirdOctaveFilterBank',
|
|
||||||
'OverallFilterBank', 'SosOctaveFilterBank',
|
|
||||||
'SosThirdOctaveFilterBank']
|
|
||||||
|
|
||||||
from .filter.filterbank_design import (OctaveBankDesigner,
|
__all__ = ["OverallFilterBank", "SosOctaveFilterBank", "SosThirdOctaveFilterBank"]
|
||||||
ThirdOctaveBankDesigner)
|
|
||||||
|
from .filter.filterbank_design import OctaveBankDesigner, ThirdOctaveBankDesigner
|
||||||
from .lasp_cpp import BiquadBank
|
from .lasp_cpp import BiquadBank
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
@ -19,7 +17,7 @@ import numpy as np
|
|||||||
class OverallFilterBank:
|
class OverallFilterBank:
|
||||||
"""
|
"""
|
||||||
Dummy type filter bank. Does nothing special, only returns output in a
|
Dummy type filter bank. Does nothing special, only returns output in a
|
||||||
sensible way
|
way compatible with SosFilterBank.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, fs):
|
def __init__(self, fs):
|
||||||
@ -51,173 +49,13 @@ class OverallFilterBank:
|
|||||||
t = np.linspace(tstart, tend, Ncur, endpoint=False)
|
t = np.linspace(tstart, tend, Ncur, endpoint=False)
|
||||||
self.N += Ncur
|
self.N += Ncur
|
||||||
|
|
||||||
output['Overall'] = {'t': t, 'data': data, 'x': 0}
|
output["Overall"] = {"t": t, "data": data, "x": 0}
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def decimation(self, x):
|
def decimation(self, x):
|
||||||
return [1]
|
return [1]
|
||||||
|
|
||||||
|
|
||||||
class FirFilterBank:
|
|
||||||
"""
|
|
||||||
Single channel (fractional) octave filter bank implementation, based on FIR
|
|
||||||
filters and sample rate decimation.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, fs, xmin, xmax):
|
|
||||||
"""
|
|
||||||
Initialize a OctaveFilterBank object.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
fs: Sampling frequency of base signal
|
|
||||||
|
|
||||||
"""
|
|
||||||
assert np.isclose(fs, 48000), "Only sampling frequency" \
|
|
||||||
" available is 48 kHz"
|
|
||||||
|
|
||||||
self.fs = fs
|
|
||||||
self.xs = list(range(xmin, xmax + 1))
|
|
||||||
|
|
||||||
maxdecimation = self.designer.firDecimation(self.xs[0])
|
|
||||||
self.decimators = []
|
|
||||||
for dec in maxdecimation:
|
|
||||||
self.decimators.append(Decimator(1, dec))
|
|
||||||
|
|
||||||
xs_d1 = []
|
|
||||||
xs_d4 = []
|
|
||||||
xs_d16 = []
|
|
||||||
xs_d64 = []
|
|
||||||
xs_d256 = []
|
|
||||||
|
|
||||||
self.filterbanks = []
|
|
||||||
# Sort the x values in categories according to the required decimation
|
|
||||||
for x in self.xs:
|
|
||||||
dec = self.designer.firDecimation(x)
|
|
||||||
if len(dec) == 1 and dec[0] == 1:
|
|
||||||
xs_d1.append(x)
|
|
||||||
elif len(dec) == 1 and dec[0] == 4:
|
|
||||||
xs_d4.append(x)
|
|
||||||
elif len(dec) == 2:
|
|
||||||
xs_d16.append(x)
|
|
||||||
elif len(dec) == 3:
|
|
||||||
xs_d64.append(x)
|
|
||||||
elif len(dec) == 4:
|
|
||||||
xs_d256.append(x)
|
|
||||||
else:
|
|
||||||
raise ValueError(f'No decimation found for x={x}')
|
|
||||||
|
|
||||||
xs_all = [xs_d1, xs_d4, xs_d16, xs_d64, xs_d256]
|
|
||||||
for xs in xs_all:
|
|
||||||
nominals_txt = []
|
|
||||||
if len(xs) > 0:
|
|
||||||
firs = np.empty((self.designer.firFilterLength, len(xs)), order='F')
|
|
||||||
for i, x in enumerate(xs):
|
|
||||||
# These are the filters that do not require lasp_decimation
|
|
||||||
# prior to filtering
|
|
||||||
nominals_txt.append(self.designer.nominal_txt(x))
|
|
||||||
firs[:, i] = self.designer.createFirFilter(x)
|
|
||||||
filterbank = {'fb': pyxFilterBank(firs, 1024),
|
|
||||||
'xs': xs,
|
|
||||||
'nominals': nominals_txt}
|
|
||||||
self.filterbanks.append(filterbank)
|
|
||||||
|
|
||||||
# Sample input counter.
|
|
||||||
self.N = 0
|
|
||||||
|
|
||||||
self.dec = [1, 4, 16, 64, 256]
|
|
||||||
|
|
||||||
# Filter output counters
|
|
||||||
# These intial delays are found 'experimentally' using a toneburst
|
|
||||||
# response.
|
|
||||||
self.Nf = [915, 806, 780, 582, 338]
|
|
||||||
|
|
||||||
def filterd(self, dec_stage, data):
|
|
||||||
"""
|
|
||||||
Filter data for a given decimation stage
|
|
||||||
|
|
||||||
Args:
|
|
||||||
dec_stage: decimation stage
|
|
||||||
data: Pre-filtered data
|
|
||||||
"""
|
|
||||||
output = {}
|
|
||||||
if data.shape[0] == 0:
|
|
||||||
return output
|
|
||||||
|
|
||||||
filtered = self.filterbanks[dec_stage]['fb'].filter_(data)
|
|
||||||
Nf = filtered.shape[0]
|
|
||||||
if Nf > 0:
|
|
||||||
dec = self.dec[dec_stage]
|
|
||||||
fd = self.fs/dec
|
|
||||||
|
|
||||||
oldNf = self.Nf[dec_stage]
|
|
||||||
tstart = oldNf/fd
|
|
||||||
tend = tstart + Nf/fd
|
|
||||||
|
|
||||||
t = np.linspace(tstart, tend, Nf, endpoint=False)
|
|
||||||
self.Nf[dec_stage] += Nf
|
|
||||||
for i, nom_txt in enumerate(self.filterbanks[dec_stage]['nominals']):
|
|
||||||
x = self.designer.nominal_txt_tox(nom_txt)
|
|
||||||
output[nom_txt] = {'t': t, 'data': filtered[:, [i]], 'x': x}
|
|
||||||
|
|
||||||
return output
|
|
||||||
|
|
||||||
def filter_(self, data):
|
|
||||||
"""
|
|
||||||
Filter input data
|
|
||||||
"""
|
|
||||||
assert data.ndim == 2
|
|
||||||
assert data.shape[1] == 1, "invalid number of channels, should be 1"
|
|
||||||
|
|
||||||
if data.shape[0] == 0:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
# Output given as a dictionary with x as the key
|
|
||||||
output = {}
|
|
||||||
output_unsorted = {}
|
|
||||||
self.N += data.shape[0]
|
|
||||||
|
|
||||||
output_unsorted = {**output_unsorted, **self.filterd(0, data)}
|
|
||||||
|
|
||||||
for i in range(len(self.decimators)):
|
|
||||||
dec_stage = i+1
|
|
||||||
if data.shape[0] > 0:
|
|
||||||
# Apply a decimation stage
|
|
||||||
data = self.decimators[i].decimate(data)
|
|
||||||
output_unsorted = {**output_unsorted,
|
|
||||||
**self.filterd(dec_stage, data)}
|
|
||||||
|
|
||||||
# Create sorted output
|
|
||||||
for x in self.xs:
|
|
||||||
nom_txt = self.designer.nominal_txt(x)
|
|
||||||
output[nom_txt] = output_unsorted[nom_txt]
|
|
||||||
|
|
||||||
return output
|
|
||||||
|
|
||||||
def decimation(self, x):
|
|
||||||
return self.designer.firDecimation(x)
|
|
||||||
|
|
||||||
|
|
||||||
class FirOctaveFilterBank(FirFilterBank):
|
|
||||||
"""
|
|
||||||
Filter bank which uses FIR filtering for each octave frequency band
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, fs, xmin, xmax):
|
|
||||||
self.designer = OctaveBankDesigner(fs)
|
|
||||||
FirFilterBank.__init__(self, fs, xmin, xmax)
|
|
||||||
|
|
||||||
|
|
||||||
class FirThirdOctaveFilterBank(FirFilterBank):
|
|
||||||
"""
|
|
||||||
Filter bank which uses FIR filtering for each one-third octave frequency
|
|
||||||
band.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, fs, xmin, xmax):
|
|
||||||
self.designer = ThirdOctaveBankDesigner(fs)
|
|
||||||
FirFilterBank.__init__(self, fs, xmin, xmax)
|
|
||||||
|
|
||||||
|
|
||||||
class SosFilterBank:
|
class SosFilterBank:
|
||||||
def __init__(self, fs, xmin, xmax):
|
def __init__(self, fs, xmin, xmax):
|
||||||
"""
|
"""
|
||||||
@ -245,7 +83,7 @@ class SosFilterBank:
|
|||||||
for i, x in enumerate(self.xs):
|
for i, x in enumerate(self.xs):
|
||||||
channel = self.designer.createSOSFilter(x)
|
channel = self.designer.createSOSFilter(x)
|
||||||
if sos is None:
|
if sos is None:
|
||||||
sos = np.empty((channel.size, len(self.xs)))
|
sos = empty((channel.size, len(self.xs)))
|
||||||
sos[:, i] = channel.flatten()
|
sos[:, i] = channel.flatten()
|
||||||
|
|
||||||
self._fb = BiquadBank(sos)
|
self._fb = BiquadBank(sos)
|
||||||
|
@ -7,9 +7,15 @@ import dataclasses, logging, os, time, h5py, threading
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from .lasp_atomic import Atomic
|
from .lasp_atomic import Atomic
|
||||||
|
from enum import Enum, auto, unique
|
||||||
from .lasp_cpp import InDataHandler, StreamMgr
|
from .lasp_cpp import InDataHandler, StreamMgr
|
||||||
from .lasp_version import LASP_VERSION_MAJOR, LASP_VERSION_MINOR
|
from .lasp_version import LASP_VERSION_MAJOR, LASP_VERSION_MINOR
|
||||||
import uuid
|
import uuid
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
# logger.setLevel(logging.DEBUG)
|
||||||
|
logger.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass
|
@dataclasses.dataclass
|
||||||
@ -18,6 +24,16 @@ class RecordStatus:
|
|||||||
done: bool = False
|
done: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
class RecordingState(Enum):
|
||||||
|
"""Enumeration for the recording state"""
|
||||||
|
|
||||||
|
Waiting = auto()
|
||||||
|
Recording = auto()
|
||||||
|
AllDataStored = auto()
|
||||||
|
Finished = auto()
|
||||||
|
Error = auto()
|
||||||
|
|
||||||
|
|
||||||
class Recording:
|
class Recording:
|
||||||
"""
|
"""
|
||||||
Class used to perform a recording. Recording data can come in from a
|
Class used to perform a recording. Recording data can come in from a
|
||||||
@ -49,87 +65,104 @@ class Recording:
|
|||||||
startDelay: Optional delay added before the recording is *actually*
|
startDelay: Optional delay added before the recording is *actually*
|
||||||
started in [s].
|
started in [s].
|
||||||
"""
|
"""
|
||||||
|
logger.debug("__init__()")
|
||||||
ext = ".h5"
|
ext = ".h5"
|
||||||
if ext not in fn:
|
if ext not in fn:
|
||||||
fn += ext
|
fn += ext
|
||||||
|
|
||||||
self.smgr = streammgr
|
if os.path.exists(fn):
|
||||||
self.metadata = None
|
raise RuntimeError("Recording file name already exists / is in use")
|
||||||
|
|
||||||
|
self._smgr = streammgr
|
||||||
|
self._metadata = None
|
||||||
|
|
||||||
|
self._recState = RecordingState.Waiting
|
||||||
|
|
||||||
if startDelay < 0:
|
if startDelay < 0:
|
||||||
raise RuntimeError("Invalid start delay value. Should be >= 0")
|
raise RuntimeError("Invalid start delay value. Should be >= 0")
|
||||||
|
|
||||||
self.startDelay = startDelay
|
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
|
# The amount of seconds (float) that is to be recorded
|
||||||
self.rectime = rectime
|
self._requiredRecordingLength = rectime
|
||||||
|
|
||||||
# The file name to store data to
|
# The file name to store data to
|
||||||
self.fn = fn
|
self._fn = fn
|
||||||
|
|
||||||
self.curT_rounded_to_seconds = 0
|
# Counter of the number of blocks that have been recorded
|
||||||
|
self._recordedBlocks = 0
|
||||||
|
|
||||||
# Counter of the number of blocks
|
# Counter of the overall number of blocks that have passed (including
|
||||||
self.ablockno = Atomic(0)
|
# the blocks that passed during waiting prior to recording)
|
||||||
|
self._allBlocks = 0
|
||||||
|
|
||||||
# Stop flag, set when recording is finished.
|
# Stop flag, set when recording is finished.
|
||||||
self.stop = Atomic(False)
|
self._stop = Atomic(False)
|
||||||
|
|
||||||
# Mutex, on who is working with the H5py data
|
# Mutex, on who is working with the H5py data and the class settings
|
||||||
self.file_mtx = threading.Lock()
|
self._rec_mutex = threading.RLock()
|
||||||
|
|
||||||
self.progressCallback = progressCallback
|
self._progressCallback = progressCallback
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Open the file
|
# Open the file
|
||||||
self.f = h5py.File(self.fn, "w", "stdio")
|
self._h5file = h5py.File(self._fn, "w", "stdio")
|
||||||
self.f.flush()
|
self._h5file.flush()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error creating measurement file {e}")
|
logger.error(f"Error creating measurement file {e}")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
# This flag is used to delete the file on finish(), and can be used
|
# This flag is used to delete the file on finish(), and can be used
|
||||||
# when a recording is canceled.
|
# when a recording is canceled. It is set to True at start, as the file will be deleted when no data is in it.
|
||||||
self.deleteFile = False
|
self._deleteFile = True
|
||||||
|
|
||||||
# Try to obtain stream metadata
|
# Try to obtain stream metadata
|
||||||
streamstatus = streammgr.getStreamStatus(StreamMgr.StreamType.input)
|
streamstatus = streammgr.getStreamStatus(StreamMgr.StreamType.input)
|
||||||
if not streamstatus.runningOK():
|
if not streamstatus.runningOK():
|
||||||
raise RuntimeError(
|
raise RuntimeError("Stream is not running properly. Cannot start recording")
|
||||||
"Stream is not running properly. Please first start the stream"
|
|
||||||
|
# Audio dataset
|
||||||
|
self._ad = None
|
||||||
|
|
||||||
|
logger.debug("Starting record....")
|
||||||
|
|
||||||
|
# In the PyInDataHandler, a weak reference is stored to the python
|
||||||
|
# methods reset and incallback. One way or another, the weak ref is gone
|
||||||
|
# on the callback thread. If we store an "extra" ref to this method over
|
||||||
|
# here, the weak ref stays alive. We do not know whether this is a bug
|
||||||
|
# or a feature, but in any case storing this extra ref to inCallback
|
||||||
|
# solves the problem.
|
||||||
|
self._incalback_cpy = self.inCallback
|
||||||
|
self._indataHandler = InDataHandler(
|
||||||
|
streammgr, self._incalback_cpy, self.resetCallback
|
||||||
)
|
)
|
||||||
|
|
||||||
self.ad = None
|
|
||||||
|
|
||||||
logging.debug("Starting record....")
|
|
||||||
|
|
||||||
self.indh = InDataHandler(streammgr, self.inCallback, self.resetCallback)
|
|
||||||
|
|
||||||
if wait:
|
if wait:
|
||||||
logging.debug("Stop recording with CTRL-C")
|
logger.debug("Stop recording with CTRL-C")
|
||||||
try:
|
try:
|
||||||
while not self.stop():
|
while not self._stop():
|
||||||
time.sleep(0.01)
|
time.sleep(0.01)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
logging.debug("Keyboard interrupt on record")
|
logger.debug("Keyboard interrupt on record")
|
||||||
finally:
|
finally:
|
||||||
self.finish()
|
self.finish()
|
||||||
|
|
||||||
|
def curT(self):
|
||||||
|
"""Return currently recorded time as float"""
|
||||||
|
|
||||||
def resetCallback(self, daq):
|
def resetCallback(self, daq):
|
||||||
"""
|
"""
|
||||||
Function called with initial stream data.
|
Function called with initial stream data.
|
||||||
"""
|
"""
|
||||||
with self.file_mtx:
|
logger.debug(f"resetCallback({daq})")
|
||||||
|
with self._rec_mutex:
|
||||||
in_ch = daq.enabledInChannels()
|
in_ch = daq.enabledInChannels()
|
||||||
blocksize = daq.framesPerBlock()
|
blocksize = daq.framesPerBlock()
|
||||||
self.blocksize = blocksize
|
self._blocksize = blocksize
|
||||||
self.nchannels = daq.neninchannels()
|
self._nchannels = daq.neninchannels()
|
||||||
self.fs = daq.samplerate()
|
self._fs = daq.samplerate()
|
||||||
|
|
||||||
f = self.f
|
f = self._h5file
|
||||||
|
|
||||||
f.attrs["LASP_VERSION_MAJOR"] = LASP_VERSION_MAJOR
|
f.attrs["LASP_VERSION_MAJOR"] = LASP_VERSION_MAJOR
|
||||||
f.attrs["LASP_VERSION_MINOR"] = LASP_VERSION_MINOR
|
f.attrs["LASP_VERSION_MINOR"] = LASP_VERSION_MINOR
|
||||||
@ -145,7 +178,7 @@ class Recording:
|
|||||||
# Add the start delay here, as firstFrames() is called right after the
|
# Add the start delay here, as firstFrames() is called right after the
|
||||||
# constructor is called. time.time() returns a floating point
|
# constructor is called. time.time() returns a floating point
|
||||||
# number of seconds after epoch.
|
# number of seconds after epoch.
|
||||||
f.attrs["time"] = time.time() + self.startDelay
|
f.attrs["time"] = time.time() + self._startDelay
|
||||||
|
|
||||||
# In V2, we do not store JSON metadata anymore, but just an enumeration
|
# In V2, we do not store JSON metadata anymore, but just an enumeration
|
||||||
# index to a physical quantity.
|
# index to a physical quantity.
|
||||||
@ -156,34 +189,6 @@ class Recording:
|
|||||||
# f.attrs['qtys'] = [ch.qty.to_json() for ch in in_ch]
|
# f.attrs['qtys'] = [ch.qty.to_json() for ch in in_ch]
|
||||||
f.flush()
|
f.flush()
|
||||||
|
|
||||||
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",
|
|
||||||
)
|
|
||||||
self.f.flush()
|
|
||||||
|
|
||||||
def inCallback(self, adata):
|
def inCallback(self, adata):
|
||||||
"""
|
"""
|
||||||
This method is called when a block of audio data from the stream is
|
This method is called when a block of audio data from the stream is
|
||||||
@ -192,18 +197,91 @@ class Recording:
|
|||||||
When returning False, it will stop the stream.
|
When returning False, it will stop the stream.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if self.stop():
|
logger.debug(f"inCallback()")
|
||||||
logging.debug("Stop flag set, early return in inCallback")
|
if self._stop():
|
||||||
|
logger.debug("Stop flag set, early return in inCallback")
|
||||||
# Stop flag is raised. We do not add any data anymore.
|
# Stop flag is raised. We do not add any data anymore.
|
||||||
|
return False
|
||||||
|
|
||||||
|
with self._rec_mutex:
|
||||||
|
|
||||||
|
self._allBlocks += 1
|
||||||
|
|
||||||
|
match self._recState:
|
||||||
|
case RecordingState.Waiting:
|
||||||
|
if self._allBlocks * self._blocksize / self._fs > self._startDelay:
|
||||||
|
self._recState = RecordingState.Recording
|
||||||
|
|
||||||
|
case RecordingState.Recording:
|
||||||
|
if self._ad is None:
|
||||||
|
self.__addFirstFramesToFile(adata)
|
||||||
|
else:
|
||||||
|
self.__addTimeDataToFile(adata)
|
||||||
|
|
||||||
|
# Increase the block counter
|
||||||
|
self._recordedBlocks += 1
|
||||||
|
|
||||||
|
recstatus = RecordStatus(curT=self.recordedTime, done=False)
|
||||||
|
|
||||||
|
if (
|
||||||
|
self._requiredRecordingLength is not None
|
||||||
|
and self.recordedTime >= self._requiredRecordingLength
|
||||||
|
):
|
||||||
|
self._recState = RecordingState.AllDataStored
|
||||||
|
self._stop <<= True
|
||||||
|
recstatus.done = True
|
||||||
|
|
||||||
|
if self._progressCallback is not None:
|
||||||
|
self._progressCallback(recstatus)
|
||||||
|
|
||||||
|
case RecordingState.AllDataStored:
|
||||||
|
return False
|
||||||
|
|
||||||
|
case RecordingState.Finished:
|
||||||
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
with self.file_mtx:
|
@property
|
||||||
|
def recordedTime(self):
|
||||||
|
"""Return recorded time (not rounded) as float"""
|
||||||
|
with self._rec_mutex:
|
||||||
|
if self._ad is None:
|
||||||
|
return 0.0
|
||||||
|
return self._recordedBlocks * self._blocksize / self._fs
|
||||||
|
|
||||||
if self.ad is None:
|
def __addFirstFramesToFile(self, adata):
|
||||||
self.firstFrames(adata)
|
"""
|
||||||
|
Set up the dataset in which to store the audio data. This will create
|
||||||
|
the attribute `self.ad` and flip around the _deleteFile flag.
|
||||||
|
|
||||||
self.__addTimeData(adata)
|
Args:
|
||||||
return True
|
adata: Numpy array with data from DAQ
|
||||||
|
|
||||||
|
"""
|
||||||
|
with self._rec_mutex:
|
||||||
|
# The array data type cannot
|
||||||
|
# datatype = daq.dataType()
|
||||||
|
dtype = np.dtype(adata.dtype)
|
||||||
|
|
||||||
|
assert self._ad is None
|
||||||
|
|
||||||
|
self._ad = self._h5file.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",
|
||||||
|
)
|
||||||
|
self._ad[0, :, :] = adata
|
||||||
|
|
||||||
|
self._h5file.flush()
|
||||||
|
self._deleteFile = False
|
||||||
|
|
||||||
def setDelete(self, val: bool):
|
def setDelete(self, val: bool):
|
||||||
"""
|
"""
|
||||||
@ -211,8 +289,8 @@ class Recording:
|
|||||||
the recording. Typically used for cleaning up after canceling a
|
the recording. Typically used for cleaning up after canceling a
|
||||||
recording.
|
recording.
|
||||||
"""
|
"""
|
||||||
with self.file_mtx:
|
with self._rec_mutex:
|
||||||
self.deleteFile = val
|
self._deleteFile = val
|
||||||
|
|
||||||
def finish(self):
|
def finish(self):
|
||||||
"""
|
"""
|
||||||
@ -220,88 +298,57 @@ class Recording:
|
|||||||
remove the queue from the stream, etc.
|
remove the queue from the stream, etc.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
logging.debug("Recording::finish()")
|
logger.debug("Recording::finish()")
|
||||||
|
|
||||||
self.stop <<= True
|
self._stop <<= True
|
||||||
|
|
||||||
|
with self._rec_mutex:
|
||||||
|
|
||||||
|
if self._recState == RecordingState.Finished:
|
||||||
|
raise RuntimeError("Recording has already finished")
|
||||||
|
|
||||||
with self.file_mtx:
|
|
||||||
self.f.flush()
|
|
||||||
# Remove indata handler, which also should remove callback function
|
# Remove indata handler, which also should remove callback function
|
||||||
# from StreamMgr. This, however does not have to happen
|
# from StreamMgr. This, however does not have to happen
|
||||||
# instantaneously. For which we have to implement extra mutex
|
# instantaneously. For which we have to implement extra mutex
|
||||||
# guards in this class
|
# guards in this class
|
||||||
del self.indh
|
del self._indataHandler
|
||||||
self.indh = None
|
|
||||||
|
self._h5file.flush()
|
||||||
|
|
||||||
# Remove handle to dataset otherwise the h5 file is not closed
|
# Remove handle to dataset otherwise the h5 file is not closed
|
||||||
# properly.
|
# properly.
|
||||||
del self.ad
|
del self._ad
|
||||||
self.ad = None
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Close the recording file
|
# Close the recording file
|
||||||
self.f.close()
|
self._h5file.close()
|
||||||
del self.f
|
del self._h5file
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error closing file: {e}")
|
logger.error(f"Error closing file: {e}")
|
||||||
|
|
||||||
logging.debug("Recording ended")
|
logger.debug("Recording ended")
|
||||||
if self.deleteFile:
|
if self._deleteFile:
|
||||||
self.__deleteFile()
|
self.__deleteFile()
|
||||||
|
self._recState = RecordingState.Finished
|
||||||
|
|
||||||
def __deleteFile(self):
|
def __deleteFile(self):
|
||||||
"""
|
"""
|
||||||
Cleanup the recording file.
|
Cleanup the recording file.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
os.remove(self.fn)
|
os.remove(self._fn)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error deleting file: {self.fn}: {str(e)}")
|
logger.error(f"Error deleting file: {self._fn}: {str(e)}")
|
||||||
|
|
||||||
def __addTimeData(self, indata):
|
def __addTimeDataToFile(self, indata):
|
||||||
"""
|
"""
|
||||||
Called by handleQueue() and adds new time data to the storage file.
|
Called by handleQueue() and adds new time data to the storage file.
|
||||||
"""
|
"""
|
||||||
# logging.debug('Recording::__addTimeData()')
|
with self._rec_mutex:
|
||||||
|
|
||||||
curT = self.ablockno() * self.blocksize / self.fs
|
ablockno = self._recordedBlocks
|
||||||
|
|
||||||
# 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
|
# Add the data to the file, and resize the audio data blocks
|
||||||
self.ad.resize(ablockno, axis=0)
|
self._ad.resize(ablockno + 1, axis=0)
|
||||||
self.ad[ablockno - 1, :, :] = indata
|
self._ad[ablockno, :, :] = indata
|
||||||
self.f.flush()
|
self._h5file.flush()
|
||||||
|
@ -6,6 +6,7 @@ Description:
|
|||||||
Reverberation time estimation tool using least squares
|
Reverberation time estimation tool using least squares
|
||||||
"""
|
"""
|
||||||
from .lasp_common import getTime
|
from .lasp_common import getTime
|
||||||
|
from .lasp_config import ones
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
@ -56,7 +57,7 @@ class ReverbTime:
|
|||||||
x = self._t[istart:istop][:, np.newaxis]
|
x = self._t[istart:istop][:, np.newaxis]
|
||||||
|
|
||||||
# Solve the least-squares problem, by creating a matrix of
|
# Solve the least-squares problem, by creating a matrix of
|
||||||
A = np.hstack([x, np.ones(x.shape)])
|
A = np.hstack([x, ones(x.shape)])
|
||||||
|
|
||||||
# print(A.shape)
|
# print(A.shape)
|
||||||
# print(points.shape)
|
# print(points.shape)
|
||||||
|
@ -5,6 +5,7 @@ Sound level meter implementation
|
|||||||
@author: J.A. de Jong - ASCEE
|
@author: J.A. de Jong - ASCEE
|
||||||
"""
|
"""
|
||||||
from .lasp_cpp import cppSLM
|
from .lasp_cpp import cppSLM
|
||||||
|
from .lasp_config import empty
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from .lasp_common import (TimeWeighting, FreqWeighting, P_REF)
|
from .lasp_common import (TimeWeighting, FreqWeighting, P_REF)
|
||||||
from .filter import SPLFilterDesigner
|
from .filter import SPLFilterDesigner
|
||||||
@ -101,7 +102,7 @@ class SLM:
|
|||||||
assert fbdesigner.fs == fs
|
assert fbdesigner.fs == fs
|
||||||
sos_firstx = fbdesigner.createSOSFilter(self.xs[0]).flatten()
|
sos_firstx = fbdesigner.createSOSFilter(self.xs[0]).flatten()
|
||||||
self.nom_txt.append(fbdesigner.nominal_txt(self.xs[0]))
|
self.nom_txt.append(fbdesigner.nominal_txt(self.xs[0]))
|
||||||
sos = np.empty((sos_firstx.size, nfilters), dtype=float, order='C')
|
sos = empty((sos_firstx.size, nfilters), dtype=float, order='C')
|
||||||
sos[:, 0] = sos_firstx
|
sos[:, 0] = sos_firstx
|
||||||
|
|
||||||
for i, x in enumerate(self.xs[1:]):
|
for i, x in enumerate(self.xs[1:]):
|
||||||
|
@ -47,7 +47,7 @@ class WeighCal:
|
|||||||
|
|
||||||
P = 2048 # Filter length (number of taps)
|
P = 2048 # Filter length (number of taps)
|
||||||
|
|
||||||
self._firs = np.empty((P, self.nchannels))
|
self._firs = empty((P, self.nchannels))
|
||||||
self._fbs = []
|
self._fbs = []
|
||||||
for chan in range(self.nchannels):
|
for chan in range(self.nchannels):
|
||||||
fir = arbitrary_fir_design(fs, P, freq_design,
|
fir = arbitrary_fir_design(fs, P, freq_design,
|
||||||
|
@ -20,6 +20,8 @@ from enum import Enum, unique
|
|||||||
import copy
|
import copy
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from numpy import log2, pi, sin
|
from numpy import log2, pi, sin
|
||||||
|
from ..lasp_cpp import freqSmooth
|
||||||
|
from ..lasp_config import zeros
|
||||||
|
|
||||||
|
|
||||||
@unique
|
@unique
|
||||||
@ -152,7 +154,7 @@ def smoothCalcMatrix(freq, sw: SmoothingWidth):
|
|||||||
return Q
|
return Q
|
||||||
|
|
||||||
|
|
||||||
def smoothSpectralData(freq, M, sw: SmoothingWidth,
|
def smoothSpectralData_old(freq, M, sw: SmoothingWidth,
|
||||||
st: SmoothingType = SmoothingType.levels):
|
st: SmoothingType = SmoothingType.levels):
|
||||||
"""
|
"""
|
||||||
Apply fractional octave smoothing to data in the frequency domain.
|
Apply fractional octave smoothing to data in the frequency domain.
|
||||||
@ -220,6 +222,45 @@ def smoothSpectralData(freq, M, sw: SmoothingWidth,
|
|||||||
|
|
||||||
return Psm
|
return Psm
|
||||||
|
|
||||||
|
def smoothSpectralData(freq, M, sw: SmoothingWidth,
|
||||||
|
st: SmoothingType = SmoothingType.levels):
|
||||||
|
"""
|
||||||
|
Apply fractional octave smoothing to data in the frequency domain.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
freq: array of frequencies of data points [Hz] - equally spaced
|
||||||
|
M: array of data, either power or dB
|
||||||
|
the smoothing type `st`, the smoothing is applied.
|
||||||
|
sw: smoothing width
|
||||||
|
st: smoothing type = data type of input data
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
freq : array frequencies of data points [Hz]
|
||||||
|
Msm : float smoothed magnitude of data points
|
||||||
|
"""
|
||||||
|
# Safety
|
||||||
|
|
||||||
|
if st == SmoothingType.ps:
|
||||||
|
assert np.min(M) >= 0, 'Power spectrum values cannot be negative'
|
||||||
|
if st == SmoothingType.levels and isinstance(M.dtype, complex):
|
||||||
|
raise RuntimeError('Decibel input should be real-valued')
|
||||||
|
|
||||||
|
# Convert to power
|
||||||
|
if st == SmoothingType.levels:
|
||||||
|
P = 10**(M/10)
|
||||||
|
elif st == SmoothingType.ps:
|
||||||
|
P = M
|
||||||
|
else:
|
||||||
|
raise RuntimeError(f"Incorrect SmoothingType: {st}")
|
||||||
|
|
||||||
|
Psm = freqSmooth(freq, P, sw.value[0])
|
||||||
|
|
||||||
|
# Convert to original format
|
||||||
|
if st == SmoothingType.levels:
|
||||||
|
Psm = 10*np.log10(Psm)
|
||||||
|
|
||||||
|
return Psm
|
||||||
|
|
||||||
|
|
||||||
# %% Test
|
# %% Test
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
#
|
#
|
||||||
cmake . -G"Ninja" -DLASP_HAS_ULDAQ=OFF -DPython3_ROOT_DIR=C:\\winpython\\python-3.10.9.amd64
|
arch=mingw64
|
||||||
arch=ucrt64
|
# DLL's that are required by lasp_cpp
|
||||||
#arch=mingw64
|
|
||||||
|
|
||||||
files_to_cpy="libfftw3-3.dll libgcc_s_seh-1.dll libgfortran-5.dll libgomp-1.dll libopenblas.dll libquadmath-0.dll libstdc++-6.dll libwinpthread-1.dll"
|
files_to_cpy="libfftw3-3.dll libgcc_s_seh-1.dll libgfortran-5.dll libgomp-1.dll libopenblas.dll libquadmath-0.dll libstdc++-6.dll libwinpthread-1.dll"
|
||||||
|
|
||||||
for fn in ${files_to_cpy}; do
|
for fn in ${files_to_cpy}; do
|
||||||
cp /c/msys64/${arch}/bin/${fn} src/lasp
|
cp /c/msys64/${arch}/bin/${fn} python_src/lasp
|
||||||
done
|
done
|
@ -5,8 +5,8 @@
|
|||||||
if [ -z $CI ]; then
|
if [ -z $CI ]; then
|
||||||
PACMAN_OPTIONS="--needed --noconfirm"
|
PACMAN_OPTIONS="--needed --noconfirm"
|
||||||
fi
|
fi
|
||||||
# arch=mingw-w64-x86_64
|
arch=mingw-w64-x86_64
|
||||||
arch=mingw-w64-ucrt-x86_64
|
# arch=mingw-w64-ucrt-x86_64
|
||||||
|
|
||||||
pacman -S ${PACMAN_OPTIONS} make
|
pacman -S ${PACMAN_OPTIONS} make
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ def test_backward_fft():
|
|||||||
nfft = 2048
|
nfft = 2048
|
||||||
freq = getFreq(nfft, nfft)
|
freq = getFreq(nfft, nfft)
|
||||||
|
|
||||||
# Sig = np.zeros(nfft//2+1, dtype=complex)
|
# Sig = zeros(nfft//2+1, dtype=complex)
|
||||||
Sigr = np.random.randn(nfft//2+1)
|
Sigr = np.random.randn(nfft//2+1)
|
||||||
Sigi = np.random.randn(nfft//2+1)
|
Sigi = np.random.randn(nfft//2+1)
|
||||||
|
|
||||||
|
2
third_party/portaudio
vendored
2
third_party/portaudio
vendored
@ -1 +1 @@
|
|||||||
Subproject commit daaf637f6f9fce670031221abfd7dfde92e5cce3
|
Subproject commit 18a606e1f928852bfc29639d9539ae74d37b5dee
|
Loading…
Reference in New Issue
Block a user