Compare commits
222 Commits
Alternativ
...
master
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 | |||
7ce45e9c82 | |||
7c8e6368ba | |||
7430e2c600 | |||
6b337df2a9 | |||
c713806bbe | |||
08010e56dd | |||
292a9d5938 | |||
373dcfb60f | |||
e8408ab53d | |||
0d152f6c14 | |||
46bef007ca | |||
fd8366c362 | |||
6d5899c880 | |||
061beaf88b | |||
e8ba3b86bf | |||
14ab3d9dfe | |||
695a05b262 | |||
514ed1aa32 | |||
0be8dd71d9 | |||
2cd4c616b3 | |||
311a1274bf | |||
936f2d5708 | |||
98d4e8dad2 | |||
87283e4aba | |||
e5c40c6af3 | |||
8c7dbed606 | |||
c610c6350d | |||
44fe7f2689 | |||
f72a635cc7 | |||
e9d7f0561e | |||
17319c4925 | |||
a7b219a1e1 | |||
e4f887dc5b | |||
ee7e5fbba9 | |||
bfa6704360 | |||
152d6d635d | |||
de3ef1b4c1 | |||
5ca899fa0a | |||
ab103b74f7 | |||
a72284880d | |||
67ff10b5e5 | |||
5b76ff007c | |||
592448eea9 | |||
402425ebd1 | |||
07b2d97e7a | |||
db9a1a28a5 | |||
24f849d9ee | |||
bcbb5b0720 | |||
bf6a18bcf8 | |||
749d5354c9 | |||
941b83abe8 | |||
7857c3aed5 | |||
5565b10fb2 | |||
44c3782b46 | |||
abbb3fee2c | |||
02b95f9aa8 | |||
b9c42c1c24 | |||
2206f47cff | |||
03513b6502 | |||
8ef5fd5a64 | |||
bab69c8018 | |||
6be9e14c13 | |||
78181ebca9 | |||
9b44ff7262 | |||
1bf32fe81d | |||
2b060b7708 | |||
2d0d24a35a | |||
6bb15965d6 | |||
31208db325 | |||
7993d81808 | |||
586dfb38b3 | |||
5b1051bf99 | |||
63122b8a42 | |||
df4e2cb573 | |||
0425195ffd | |||
e51463a6cc | |||
628ba898c9 | |||
dbd9c7c1af | |||
77a7e46f9d | |||
376e0dc85c | |||
1bf022d648 | |||
c9243b1143 | |||
8397779a2a | |||
94f0ec1d84 | |||
a29f72a592 | |||
89b303497b | |||
aa0803e2f1 | |||
01a6c35f6e | |||
ff1cfddf97 | |||
d96c591183 | |||
37048c54fa | |||
9f81db8eeb | |||
914da89819 | |||
72716ecd39 | |||
790eb41a26 | |||
2727bb5582 | |||
a70f124f89 | |||
26343fda79 | |||
da80dbf075 | |||
33132e2c9d | |||
fc681f3b6c | |||
ddbb842c14 | |||
30ce35d29b | |||
839ca4f77c | |||
8711c6c57d | |||
4ca8866cb7 | |||
2420e6cb28 | |||
3681e7adac | |||
1a22a33c0f | |||
f160b696fb | |||
6353282e24 | |||
d9a3cfd627 | |||
64a268e277 | |||
77b1848bb4 | |||
88624764e7 | |||
303e15e2d6 | |||
e61d71b08a | |||
ee4b230947 | |||
3904abfcf9 | |||
a58be3ab87 | |||
f9640a5f99 | |||
9b724ab9d5 | |||
21df1bc6cf | |||
028bed9229 | |||
c87a5cec25 | |||
6fc1bd90b1 | |||
dd2bbb5973 | |||
ae3f8043e0 | |||
0d02779f2e | |||
9617da3ad9 | |||
43cf2427ea | |||
9ec2abeced | |||
a1a7b411f1 | |||
34729cf9c0 | |||
93619a344c | |||
318a565e17 | |||
3e6c8cf3b2 | |||
a0bbeea24d | |||
24de84a4f7 | |||
ebf5ac3de4 | |||
ad864ddb4a | |||
3844827505 | |||
fb9920d00a | |||
e09b00d801 | |||
f1348ede80 | |||
bdef0b45f3 | |||
3f0d9f4b00 | |||
ad0076e1c9 | |||
ad62917aab | |||
14126c8b9c | |||
f164aa2e71 | |||
ec2a933e20 | |||
617eded04e | |||
2b22af5d2c | |||
92f5b18481 | |||
8547d0915a | |||
fa8f5e64ad | |||
b3fb7ddb6d | |||
28d540b667 | |||
480b036e02 |
110
.drone.yml
110
.drone.yml
@ -1,110 +0,0 @@
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: archlinux
|
||||
|
||||
clone:
|
||||
depth: 50
|
||||
|
||||
steps:
|
||||
- name: archlinux_build
|
||||
image: archlinux_build:latest
|
||||
pull: if-not-exists
|
||||
volumes:
|
||||
- name: archlinux_ccache
|
||||
path: /root/.ccache
|
||||
commands:
|
||||
# The following command is not required, we included this in the docker
|
||||
# image of archlinux_build
|
||||
# - pacman -S --noconfirm ccache openblas fftw pulseaudio pybind11
|
||||
- git submodule update --init --recursive
|
||||
- cmake .
|
||||
# More than two makes ascee2 irresponsive for now
|
||||
- make -j2
|
||||
|
||||
- name: archlinux_test
|
||||
image: archlinux_build:latest
|
||||
pull: if-not-exists
|
||||
commands:
|
||||
# The following command is not required, we included this in the docker
|
||||
# image of archlinux_build
|
||||
# - pacman -S --noconfirm openblas python-pytest fftw pulseaudio python-pip python-scipy python-h5py
|
||||
- pip install -r requirements.txt
|
||||
- pip install .
|
||||
- pytest
|
||||
|
||||
# - name: release-arch
|
||||
# commands:
|
||||
# -
|
||||
|
||||
volumes:
|
||||
- name: archlinux_ccache
|
||||
host:
|
||||
path: /tmp/archlinux_ccache
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: ubuntu
|
||||
|
||||
clone:
|
||||
depth: 3
|
||||
|
||||
volumes:
|
||||
- name: archlinux_ccache
|
||||
path: /root/.ccache
|
||||
|
||||
steps:
|
||||
- name: ubuntu_build
|
||||
image: ubuntu_build:latest
|
||||
pull: if-not-exists
|
||||
volumes:
|
||||
- name: ubuntu_ccache
|
||||
path: /root/.ccache
|
||||
commands:
|
||||
# The following commands are not required, we included this in the docker
|
||||
# image of ubuntu_build
|
||||
#- apt update
|
||||
#- apt install -y git cmake python3-pybind11 libopenblas-dev python3-pip python3-scipy libusb-1.0-0-dev libpulse-dev python3-h5py fftw-dev
|
||||
- git submodule update --init --recursive
|
||||
- cmake .
|
||||
# More than two makes ascee2 irresponsive for now
|
||||
- make -j2
|
||||
|
||||
- name: ubuntu_test
|
||||
image: ubuntu_build:latest
|
||||
pull: if-not-exists
|
||||
commands:
|
||||
# The following commands are not required, we included this in the docker
|
||||
# image of ubuntu_build
|
||||
#- apt update
|
||||
#- apt install -y python3-pytest fftw pulseaudio python3-pip python3-scipy python3-h5py
|
||||
- pip install -r requirements.txt
|
||||
- pip install .
|
||||
- pytest-3
|
||||
|
||||
volumes:
|
||||
- name: ubuntu_ccache
|
||||
host:
|
||||
path: /tmp/ubuntu_ccache
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: documentation_build
|
||||
|
||||
clone:
|
||||
depth: 3
|
||||
|
||||
|
||||
steps:
|
||||
- name: build_docker_master
|
||||
image: plugins/docker
|
||||
settings:
|
||||
repo: ascee/lasp_ascee_nl
|
||||
tags: latest
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
when:
|
||||
branch: master
|
52
.gitea/workflows/workflow.yml
Normal file
52
.gitea/workflows/workflow.yml
Normal file
@ -0,0 +1,52 @@
|
||||
name: Building, testing and releasing LASP if it has a tag
|
||||
|
||||
on:
|
||||
- push
|
||||
|
||||
jobs:
|
||||
Build-Test-Ubuntu:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ascee/ubuntu_build:latest
|
||||
volumes:
|
||||
- lasp_dist:/dist
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Build and test
|
||||
run: |
|
||||
pip install build pytest
|
||||
python3 -m build
|
||||
pip install dist/lasp*.whl
|
||||
pytest
|
||||
|
||||
- name: Cleanup old dist files and copy new to /dist dir
|
||||
run: |-
|
||||
rm -f /dist/*
|
||||
cp -v dist/* /dist
|
||||
|
||||
Release-Ubuntu:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
volumes:
|
||||
- lasp_dist:/dist
|
||||
needs: Build-Test-Ubuntu
|
||||
if: startsWith(gitea.ref, 'refs/tags/v')
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: setup go
|
||||
uses: https://github.com/actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.18'
|
||||
- name: Release
|
||||
uses: https://gitea.com/actions/release-action@main
|
||||
working-directory: "/"
|
||||
with:
|
||||
files: |-
|
||||
../../../../../dist/**
|
||||
api_key: '${{secrets.RELEASE_TOKEN}}'
|
17
.gitignore
vendored
17
.gitignore
vendored
@ -1,23 +1,26 @@
|
||||
*.a
|
||||
*.pyc
|
||||
*.so
|
||||
*.dll
|
||||
*.pyd
|
||||
.ninja*
|
||||
build.ninja
|
||||
dist
|
||||
src/lasp.egg-info
|
||||
test/.ipynb_checkpoints
|
||||
src/lasp/lasp_config.h
|
||||
_deps
|
||||
compile_commands.json
|
||||
CMakeFiles
|
||||
CMakeCache.txt
|
||||
cmake_install.cmake
|
||||
Makefile
|
||||
build
|
||||
__pycache__
|
||||
cython_debug
|
||||
doc
|
||||
.ropeproject
|
||||
.ipynb_checkpoints
|
||||
.spyproject
|
||||
.cache
|
||||
_skbuild
|
||||
acme_log.log
|
||||
.venv
|
||||
.py-build-cmake_cache
|
||||
cpp_src/lasp_config.h
|
||||
.cache
|
||||
.vscode
|
||||
build
|
||||
|
7
.gitmodules
vendored
7
.gitmodules
vendored
@ -6,7 +6,7 @@
|
||||
url = https://github.com/gsl-lite/gsl-lite
|
||||
[submodule "DebugTrace-cpp"]
|
||||
path = third_party/DebugTrace-cpp
|
||||
url = https://github.com/MasatoKokubo/DebugTrace-cpp
|
||||
url = https://github.com/asceenl/DebugTrace-cpp
|
||||
[submodule "armadillo-code"]
|
||||
path = third_party/armadillo-code
|
||||
url = https://gitlab.com/conradsnicta/armadillo-code
|
||||
@ -30,4 +30,7 @@
|
||||
url = https://github.com/boostorg/core
|
||||
[submodule "third_party/uldaq"]
|
||||
path = third_party/uldaq
|
||||
url = https://github.com/mccdaq/uldaq
|
||||
url = https://github.com/asceenl/uldaq
|
||||
[submodule "third_party/portaudio"]
|
||||
path = third_party/portaudio
|
||||
url = https://github.com/PortAudio/portaudio
|
||||
|
18
.pre-commit-config.yaml
Normal file
18
.pre-commit-config.yaml
Normal file
@ -0,0 +1,18 @@
|
||||
---
|
||||
repos:
|
||||
- repo: https://github.com/commitizen-tools/commitizen
|
||||
rev: 3.5.3
|
||||
hooks:
|
||||
- id: commitizen
|
||||
- id: commitizen-branch
|
||||
stages: [push]
|
||||
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v2.3.0
|
||||
hooks:
|
||||
- id: end-of-file-fixer
|
||||
- id: trailing-whitespace
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 22.10.0
|
||||
hooks:
|
||||
- id: black
|
7
CHANGELOG.md
Normal file
7
CHANGELOG.md
Normal file
@ -0,0 +1,7 @@
|
||||
## v1.0.1 (2023-07-19)
|
||||
|
||||
### Fix
|
||||
|
||||
- Added patch number to semver in pyproject.toml
|
||||
|
||||
## v1.0.0 (2023-07-19)
|
@ -1,13 +1,42 @@
|
||||
cmake_minimum_required (VERSION 3.16)
|
||||
project(LASP LANGUAGES C CXX VERSION 1.0)
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(LASP LANGUAGES C CXX VERSION 1.6.3)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED)
|
||||
|
||||
option(LASP_DOUBLE_PRECISION "Compile as double precision floating point" ON)
|
||||
option(LASP_HAS_RTAUDIO "Compile with RtAudio Daq backend" ON)
|
||||
option(LASP_HAS_ULDAQ "Compile with UlDaq backend" ON)
|
||||
# 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 /
|
||||
# Windows.
|
||||
set(DEFAULT_DOUBLE_PRECISION ON)
|
||||
if(WIN32)
|
||||
set(DEFAULT_RTAUDIO OFF)
|
||||
set(DEFAULT_PORTAUDIO ON)
|
||||
set(DEFAULT_ULDAQ OFF)
|
||||
elseif(${RPI})
|
||||
set(DEFAULT_RTAUDIO OFF)
|
||||
set(DEFAULT_PORTAUDIO ON)
|
||||
set(DEFAULT_ULDAQ OFF)
|
||||
set(DEFAULT_DOUBLE_PRECISION OFF)
|
||||
else()
|
||||
set(DEFAULT_RTAUDIO OFF)
|
||||
set(DEFAULT_PORTAUDIO ON)
|
||||
set(DEFAULT_ULDAQ ON)
|
||||
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_PORTAUDIO "Compile with PortAudio Daq backend" ${DEFAULT_PORTAUDIO})
|
||||
if(LASP_HAS_PORTAUDIO AND LASP_HAS_RTAUDIO)
|
||||
message(FATAL_ERROR "Either PortAudio or RtAudio can be selected as audio backend")
|
||||
endif()
|
||||
option(LASP_HAS_ULDAQ "Compile with UlDaq backend" ${DEFAULT_ULDAQ})
|
||||
option(LASP_BUILD_TUNED "Tune build for current machine (Experimental / untested)" OFF)
|
||||
option(LASP_WITH_OPENMP "Use OpenMP parallelization (Experimental: crashes SHOULD BE EXPECTED)" OFF)
|
||||
set(LASP_MAX_NFFT "33554432" CACHE STRING "Max FFT size")
|
||||
@ -27,7 +56,6 @@ cmake_policy(SET CMP0079 NEW)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake")
|
||||
include_directories(/usr/include/python3.8)
|
||||
include("BuildType")
|
||||
include("QueryPythonForPybind11")
|
||||
|
||||
@ -88,20 +116,29 @@ else()
|
||||
endif()
|
||||
|
||||
# ###################################### 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")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-type-limits")
|
||||
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
|
||||
include_directories(/usr/lib/python3.10/site-packages/numpy/core/include)
|
||||
|
||||
# ####################################### End of user-adjustable variables section
|
||||
include(OSSpecific)
|
||||
include(rtaudio)
|
||||
include(portaudio)
|
||||
include(uldaq)
|
||||
#
|
||||
add_subdirectory(src/lasp)
|
||||
add_definitions(-Dgsl_CONFIG_DEFAULTS_VERSION=1)
|
||||
add_subdirectory(cpp_src)
|
||||
if(LASP_BUILD_CPP_TESTS)
|
||||
add_subdirectory(test)
|
||||
endif()
|
||||
|
11
Dockerfile
11
Dockerfile
@ -1,13 +1,16 @@
|
||||
FROM archlinux
|
||||
FROM archlinux:latest
|
||||
MAINTAINER J.A. de Jong - j.a.dejong@ascee.nl
|
||||
RUN pacman --noconfirm -Sy
|
||||
RUN pacman --noconfirm -S git doxygen graphviz lighttpd python-pip
|
||||
RUN pip install doxypypy
|
||||
RUN pacman --noconfirm -S git doxygen graphviz lighttpd python-pip python-virtualenv
|
||||
WORKDIR /root
|
||||
ENV VIRTUAL_ENV=/root/testenv
|
||||
RUN python3 -m venv $VIRTUAL_ENV
|
||||
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
|
||||
RUN pip install doxypypy
|
||||
RUN git clone https://code.ascee.nl/ascee/lasp
|
||||
WORKDIR /root/lasp
|
||||
RUN doxygen
|
||||
RUN rm -rf /srv//http
|
||||
RUN rm -rf /srv/http
|
||||
RUN mv doc/html /srv/http
|
||||
CMD /usr/bin/lighttpd -D -f /etc/lighttpd/lighttpd.conf
|
||||
|
||||
|
154
README.md
154
README.md
@ -1,7 +1,5 @@
|
||||
# Library for Acoustic Signal Processing
|
||||
|
||||
- Master branch: [![Build Status](https://drone.ascee.nl/api/badges/ASCEE/lasp/status.svg?ref=refs/heads/master)](https://drone.ascee.nl/ASCEE/lasp)
|
||||
- Develop branch: [![Build Status](https://drone.ascee.nl/api/badges/ASCEE/lasp/status.svg?ref=refs/heads/develop)](https://drone.ascee.nl/ASCEE/lasp)
|
||||
Library for Acoustic Signal Processing
|
||||
======================================
|
||||
|
||||
|
||||
Welcome to LASP: Library for Acoustic Signal Processing. LASP is a C++ library
|
||||
@ -9,6 +7,7 @@ with a Python interface which is supposed to acquire and process (multi) sensor
|
||||
|
||||
Current features that are implemented:
|
||||
|
||||
|
||||
- Communication with data acquisition (DAQ) devices, of which:
|
||||
- Internal sound cards via the [RtAudio](http://www.music.mcgill.ca/~gary/rtaudio) backend. Many thanks to Gary P. Scavone et al.
|
||||
- [Measurement Computing](https://www.mccdaq.com) [DT9838A](https://www.mccdaq.com/Products/Sound-Vibration-DAQ/DT9837) signal analyzer.
|
||||
@ -32,54 +31,127 @@ Current features that are implemented:
|
||||
- Spectra data smoothing algorithms
|
||||
- Sensor calibration for microphones
|
||||
|
||||
|
||||
Future features (wish-list)
|
||||
|
||||
|
||||
- Conventional and delay-and-sum beam-forming algorithms
|
||||
- Impedance tube measurement processing
|
||||
|
||||
For now, the source code is well-documented on [lasp.ascee.nl](https://lasp.ascee.nl) but it requires some
|
||||
additional documentation (the math behind it). This is maintained
|
||||
in a sister repository [lasp-doc](https://code.ascee.nl/ascee/lasp-doc). The
|
||||
most recent
|
||||
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: info@ascee.nl.
|
||||
If you have any question(s), please feel free to contact us: [email](info@ascee.nl).
|
||||
|
||||
# Installation
|
||||
|
||||
## Dependencies
|
||||
# Installation - Linux (Ubuntu-based)
|
||||
|
||||
- `$ sudo apt install python3-pybind11 libopenblas-dev python3-pip python3-scipy libusb-1.0-0-dev libpulse-dev cmake-curses-gui python3-h5py`
|
||||
- `$ pip3 install --user -r requirements.txt`
|
||||
## Prerequisites
|
||||
|
||||
Run the following on the command line to install all prerequisites on
|
||||
Debian-based Linux, x86-64:
|
||||
|
||||
- `sudo apt install python3-pip libfftw3-3 libopenblas-base libusb-1.0-0 libpulse0`
|
||||
|
||||
|
||||
## Installation from wheel (recommended for non-developers)
|
||||
|
||||
Go to: [LASP releases](https://code.ascee.nl/ASCEE/lasp/releases/latest/) and
|
||||
download the latest `.whl`. Then run:
|
||||
|
||||
- `pip install lasp-*-linux_x86_64.whl`
|
||||
|
||||
## From source (Ubuntu-based)
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Run the following one-liner:
|
||||
|
||||
- `sudo apt install -y git python3 python3-virtualenv python3-venv libopenblas-dev python3-pip libfftw3-dev libusb-1.0-0-dev libpulse-dev python3-build`
|
||||
|
||||
If building RtAudio with the ALSA backend, you will also require the following packages:
|
||||
|
||||
- libclalsadrv-dev
|
||||
- `sudo apt install libclalsadrv-dev`
|
||||
|
||||
If building RtAudio with the Jack Audio Connection Kit (JACK) backend, you will also require the following packages:
|
||||
|
||||
- libjack-jackd2-dev
|
||||
- `sudo apt install libjack-jackd2-dev`
|
||||
|
||||
## Download & build
|
||||
### Download & build
|
||||
|
||||
- `$ git clone --recursive https://code.ascee.nl/ASCEE/lasp.git`
|
||||
- `$ cd lasp`
|
||||
- `pip install -e .`
|
||||
|
||||
For a release build:
|
||||
# Building and installation for Raspberry Pi (Raspberry Pi OS)
|
||||
|
||||
- `$ cmake .`
|
||||
Run the following on the command line to install all prerequisites on
|
||||
Raspberry Pi OS:
|
||||
|
||||
or optionally for a custom build:
|
||||
- `sudo apt install libfftw3-dev libopenblas64-dev libhdf5-dev libclalsadrv-dev`
|
||||
|
||||
- `$ ccmake .`
|
||||
In a virtualenv: install `build`
|
||||
|
||||
Configure and run:
|
||||
- `$ pip install build`
|
||||
|
||||
- `$ make -j`
|
||||
Then run:
|
||||
|
||||
### Build documentation
|
||||
- `$ git clone --recursive https://code.ascee.nl/ASCEE/lasp.git`
|
||||
- `$ cd lasp`
|
||||
- `$ pyproject-build`
|
||||
|
||||
In directory:
|
||||
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
|
||||
|
||||
- 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
|
||||
x86_64 version.
|
||||
- When unzipping WinPython, make sure to choose a proper and simple path, i.e.
|
||||
C:\winpython
|
||||
- Download and install [Git for Windows](https://git-scm.com)
|
||||
- Open an MSYS2 **MINGW64** terminal, and install some tools we require:
|
||||
- `$ pacman -S git`
|
||||
- Create a new virtualenv:
|
||||
- `$ /c/winpython/<py-distr-dir>/python.exe -m venv venv`
|
||||
- Add the venv-python to the path (eases a lot of commands)
|
||||
- `$ export PATH=$PATH:~/venv/Scripts`
|
||||
- Install `build`:
|
||||
- `$ pip install build`
|
||||
- Clone LASP:
|
||||
- `$ git clone --recurse-submodules https://code.ascee.nl/ascee/lasp && cd lasp`
|
||||
- If run for the first time, we have to install the libraries we depend on in
|
||||
MSYS2 (this only has to be done on a fresh MSYS2 installation):
|
||||
- `$ scripts/install_msys2_builddeps.sh`
|
||||
- Copy over required DLL's to be included in distribution:
|
||||
- `scripts/copy_windows_dlls.sh`
|
||||
- And... build!
|
||||
- `pyproject-build`
|
||||
- Lastly: the generated wheel can be installed in the current virtualenv:
|
||||
- `pip install dist/lasp*.whl`
|
||||
|
||||
|
||||
# Documentation
|
||||
|
||||
## Online
|
||||
|
||||
[Online LASP documentation](https://lasp.ascee.nl/).
|
||||
|
||||
## In directory (Linux/Debian)
|
||||
|
||||
`$ sudo apt install doxygen graphviz`
|
||||
`$ pip install doxypypy`
|
||||
@ -92,21 +164,29 @@ This will build the documentation. It can be read by:
|
||||
|
||||
`$ <YOUR-BROWSER> doc/html/index.html`
|
||||
|
||||
Or via docker:
|
||||
|
||||
`$ docker build -t lasp_ascee_nl:latest .`
|
||||
|
||||
## Install
|
||||
|
||||
For an editable install (while developing):
|
||||
|
||||
- `$ pip3 install --prefix=$HOME/.local -e .`
|
||||
|
||||
To install locally, for a fixed version:
|
||||
|
||||
- `$ pip3 install --prefix=$HOME/.local`
|
||||
|
||||
## Usage
|
||||
# Usage
|
||||
|
||||
- See examples directories for IPython notebooks.
|
||||
- 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
|
||||
```
|
@ -1,29 +1,11 @@
|
||||
if(WIN32)
|
||||
set(home $ENV{USERPROFILE})
|
||||
|
||||
# set(miniconda_dir ${home}\\Miniconda3)
|
||||
message("Building for Windows")
|
||||
include_directories(
|
||||
..\\rtaudio
|
||||
C:\\mingw\\mingw64\\include\\OpenBLAS
|
||||
link_directories(${home}\\miniconda3\\Library\\include)
|
||||
)
|
||||
set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH} $miniconda_dir\\Lib\\cmake")
|
||||
# include(
|
||||
add_definitions(-DMS_WIN64)
|
||||
link_directories(C:\\mingw\\mingw64\\lib)
|
||||
link_directories(C:\\mingw\\mingw64\\bin)
|
||||
link_directories(..\\rtaudio)
|
||||
link_directories(${home}\\Miniconda3)
|
||||
add_definitions(-DHAS_RTAUDIO_WIN_WASAPI_API)
|
||||
else() # Linux compile
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wfatal-errors")
|
||||
include_directories(/usr/local/include/rtaudio)
|
||||
include_directories(/usr/include/rtaudio)
|
||||
link_directories(/usr/local/lib)
|
||||
# This should become optional later on, and be added to the windows list as
|
||||
# well.
|
||||
set(TARGET_OS_LINKLIBS winmm dsound setupapi ole32 uuid winmm)
|
||||
|
||||
message("Building for Windows")
|
||||
else() # Linux compile
|
||||
message("Building for Linux :)")
|
||||
set(TARGET_OS_LINKLIBS "")
|
||||
endif()
|
||||
# The last argument here takes care of calling SIGABRT when an integer overflow
|
||||
# occures.
|
||||
|
||||
|
28
cmake/portaudio.cmake
Normal file
28
cmake/portaudio.cmake
Normal file
@ -0,0 +1,28 @@
|
||||
# ###################################### RtAudio
|
||||
if(LASP_HAS_PORTAUDIO)
|
||||
message("Building with Portaudio backend")
|
||||
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()
|
||||
# Unix
|
||||
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_PULSEAUDIO FALSE CACHE BOOL "Build PortAudio with PulseAudio backend")
|
||||
set(PA_BUILD_SHARED_LIBS FALSE CACHE BOOL "Build static library")
|
||||
endif()
|
||||
|
||||
add_subdirectory(third_party/portaudio)
|
||||
include_directories(third_party/portaudio/include)
|
||||
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()
|
@ -2,12 +2,13 @@
|
||||
if(LASP_HAS_RTAUDIO)
|
||||
message("Building RtAudio backend")
|
||||
if(WIN32)
|
||||
set(RTAUDIO_API_WASAPI TRUE CACHE BOOL "Build for WASAPI" FORCE)
|
||||
set(RTAUDIO_API_WASAPI FALSE CACHE BOOL "Build for WASAPI backend")
|
||||
set(RTAUDIO_API_DS TRUE CACHE BOOL "Build for Directsound backend")
|
||||
else()
|
||||
set(RTAUDIO_API_PULSE TRUE CACHE BOOL "Build with PulseAudio backend" FORCE)
|
||||
set(RTAUDIO_API_ALSA OFF CACHE BOOL "Do not build with Alsa backend" FORCE)
|
||||
set(RTAUDIO_API_JACK OFF CACHE BOOL "Do not build with Jack backend" FORCE)
|
||||
set(RTAUDIO_API_PULSE TRUE CACHE BOOL "Build with PulseAudio backend")
|
||||
set(RTAUDIO_API_ALSA OFF CACHE BOOL "Do not build with Alsa backend")
|
||||
set(RTAUDIO_API_JACK OFF CACHE BOOL "Do not build with Jack backend")
|
||||
endif()
|
||||
set(RTAUDIO_BUILD_STATIC_LIBS ON CACHE BOOL "Build static libs for RtAudio" FORCE)
|
||||
add_subdirectory(third_party/rtaudio)
|
||||
add_subdirectory(${PROJECT_SOURCE_DIR}/third_party/rtaudio)
|
||||
endif()
|
||||
|
63
cpp_src/CMakeLists.txt
Normal file
63
cpp_src/CMakeLists.txt
Normal file
@ -0,0 +1,63 @@
|
||||
# src/lasp/CMakeLists.txt
|
||||
|
||||
# Armadillo, don't build the wrapper lib, but instead directly link to
|
||||
# openblas.
|
||||
add_definitions(-DARMA_DONT_USE_WRAPPER)
|
||||
|
||||
configure_file(lasp_config.h.in lasp_config.h)
|
||||
include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||
include_directories(SYSTEM
|
||||
${PROJECT_SOURCE_DIR}/third_party/armadillo-code/include)
|
||||
|
||||
include_directories(${PROJECT_SOURCE_DIR}/third_party/DebugTrace-cpp/include)
|
||||
include_directories(${PROJECT_SOURCE_DIR}/third_party/gsl-lite/include)
|
||||
include_directories(${PROJECT_SOURCE_DIR}/third_party/tomlplusplus/include)
|
||||
include_directories(${PROJECT_SOURCE_DIR}/third_party/thread-pool)
|
||||
|
||||
if(LASP_HAS_RTAUDIO)
|
||||
include_directories(${PROJECT_SOURCE_DIR}/third_party/rtaudio)
|
||||
endif()
|
||||
if(LASP_HAS_ULDAQ)
|
||||
include_directories(${PROJECT_SOURCE_DIR}/third_party/uldaq/src)
|
||||
endif()
|
||||
|
||||
add_subdirectory(device)
|
||||
add_subdirectory(dsp)
|
||||
|
||||
pybind11_add_module(lasp_cpp MODULE lasp_cpp.cpp
|
||||
pybind11/lasp_deviceinfo.cpp
|
||||
pybind11/lasp_daqconfig.cpp
|
||||
pybind11//lasp_dsp_pybind.cpp
|
||||
pybind11/lasp_streammgr.cpp
|
||||
pybind11/lasp_daq.cpp
|
||||
pybind11/lasp_deviceinfo.cpp
|
||||
pybind11/lasp_pyindatahandler.cpp
|
||||
pybind11/lasp_siggen.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(lasp_cpp PRIVATE lasp_device_lib lasp_dsp_lib
|
||||
${OpenMP_CXX_LIBRARIES} ${LASP_FFT_LIBS} ${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)
|
||||
# Install the Python module
|
||||
install(TARGETS lasp_cpp
|
||||
EXCLUDE_FROM_ALL
|
||||
COMPONENT python_modules
|
||||
DESTINATION ${PY_BUILD_CMAKE_MODULE_NAME})
|
||||
|
||||
endif()
|
@ -1,4 +1,6 @@
|
||||
# src/lasp/device/CMakeLists.txt
|
||||
include_directories(uldaq)
|
||||
include_directories(portaudio)
|
||||
|
||||
add_library(lasp_device_lib OBJECT
|
||||
lasp_daq.cpp
|
||||
@ -7,8 +9,12 @@ add_library(lasp_device_lib OBJECT
|
||||
lasp_deviceinfo.cpp
|
||||
lasp_rtaudiodaq.cpp
|
||||
lasp_streammgr.cpp
|
||||
lasp_indatahandler.cpp
|
||||
lasp_uldaq.cpp
|
||||
lasp_uldaq_impl.cpp
|
||||
uldaq/lasp_uldaq_impl.cpp
|
||||
uldaq/lasp_uldaq_bufhandler.cpp
|
||||
uldaq/lasp_uldaq_common.cpp
|
||||
portaudio/lasp_portaudiodaq.cpp
|
||||
)
|
||||
|
||||
# Callback requires certain arguments that are not used by code. This disables
|
||||
@ -27,6 +33,13 @@ endif()
|
||||
if(LASP_HAS_RTAUDIO)
|
||||
target_link_libraries(lasp_device_lib rtaudio)
|
||||
endif()
|
||||
if(LASP_HAS_PORTAUDIO)
|
||||
target_link_libraries(lasp_device_lib PortAudio)
|
||||
if(WIN32)
|
||||
else()
|
||||
target_link_libraries(lasp_device_lib asound)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
target_link_libraries(lasp_device_lib lasp_dsp_lib)
|
||||
|
@ -1,7 +1,8 @@
|
||||
/* #define DEBUGTRACE_ENABLED */
|
||||
// #define DEBUGTRACE_ENABLED
|
||||
#include "debugtrace.hpp"
|
||||
#include "lasp_daqconfig.h"
|
||||
|
||||
#include "lasp_config.h"
|
||||
#include "lasp_daq.h"
|
||||
#if LASP_HAS_ULDAQ == 1
|
||||
#include "lasp_uldaq.h"
|
||||
@ -9,6 +10,9 @@
|
||||
#if LASP_HAS_RTAUDIO == 1
|
||||
#include "lasp_rtaudiodaq.h"
|
||||
#endif
|
||||
#if LASP_HAS_PORTAUDIO == 1
|
||||
#include "lasp_portaudiodaq.h"
|
||||
#endif
|
||||
using rte = std::runtime_error;
|
||||
|
||||
Daq::~Daq() { DEBUGTRACE_ENTER; }
|
||||
@ -27,6 +31,11 @@ std::unique_ptr<Daq> Daq::createDaq(const DeviceInfo &devinfo,
|
||||
if (devinfo.api.apicode == LASP_RTAUDIO_APICODE) {
|
||||
return createRtAudioDevice(devinfo, config);
|
||||
}
|
||||
#endif
|
||||
#if LASP_HAS_PORTAUDIO == 1
|
||||
if (devinfo.api.apicode == LASP_PORTAUDIO_APICODE) {
|
||||
return createPortAudioDevice(devinfo, config);
|
||||
}
|
||||
#endif
|
||||
throw rte(string("Unable to match Device API: ") + devinfo.api.apiname);
|
||||
}
|
||||
@ -35,31 +44,41 @@ Daq::Daq(const DeviceInfo &devinfo, const DaqConfiguration &config)
|
||||
: DaqConfiguration(config), DeviceInfo(devinfo) {
|
||||
DEBUGTRACE_ENTER;
|
||||
|
||||
if (duplexMode()) {
|
||||
if (neninchannels() == 0) {
|
||||
throw rte("Duplex mode enabled, but no input channels enabled");
|
||||
}
|
||||
|
||||
if (nenoutchannels() == 0) {
|
||||
throw rte("Duplex mode enabled, but no output channels enabled");
|
||||
}
|
||||
if(!duplexMode() && monitorOutput) {
|
||||
throw rte("Duplex mode requires enabling both input and output channels. Please make sure at least one output channel is enabled, or disable hardware output loopback in DAQ configuration.");
|
||||
}
|
||||
|
||||
if (!hasInternalOutputMonitor && monitorOutput) {
|
||||
throw rte(
|
||||
"Output monitor flag set, but device does not have output monitor");
|
||||
"Output monitor flag set, but device does not have hardware output monitor.");
|
||||
}
|
||||
|
||||
if (!config.match(devinfo)) {
|
||||
throw rte("DaqConfiguration does not match device info");
|
||||
}
|
||||
if (neninchannels(false) > devinfo.ninchannels) {
|
||||
|
||||
{
|
||||
const int hich = getHighestEnabledInChannel();
|
||||
if (hich + 1 > devinfo.ninchannels)
|
||||
{
|
||||
throw rte(
|
||||
"Number of enabled input channels is higher than device capability");
|
||||
string("Highest of enabled input channel: ") +
|
||||
to_string(hich) +
|
||||
string(" is higher than device capability, which is: ") +
|
||||
to_string(ninchannels) + ".");
|
||||
}
|
||||
if (nenoutchannels() > devinfo.noutchannels) {
|
||||
}
|
||||
|
||||
{
|
||||
const int hoch = getHighestEnabledOutChannel();
|
||||
if (hoch + 1 > devinfo.noutchannels)
|
||||
{
|
||||
throw rte(
|
||||
"Number of enabled output channels is higher than device capability");
|
||||
string("Highest of enabled output channel: ") +
|
||||
to_string(hoch) +
|
||||
string(" is higher than device capability, which is: ") +
|
||||
to_string(noutchannels) + ".");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,8 @@
|
||||
#include "lasp_types.h"
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
|
||||
/**
|
||||
* \defgroup device Device interfacing
|
||||
@ -13,12 +15,12 @@
|
||||
* @brief Callback of DAQ for input data. Callback should return
|
||||
* false for a stop request.
|
||||
*/
|
||||
using InDaqCallback = std::function<bool(const DaqData &)>;
|
||||
using InDaqCallback = std::function<void(const DaqData &)>;
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*/
|
||||
using OutDaqCallback = std::function<bool(DaqData &)>;
|
||||
using OutDaqCallback = std::function<void(DaqData &)>;
|
||||
|
||||
/**
|
||||
* @brief Base cass for all DAQ (Data Acquisition) interfaces. A DAQ can be a
|
||||
@ -46,6 +48,10 @@ public:
|
||||
logicError,
|
||||
};
|
||||
|
||||
// Below the only members of this class, which are public.
|
||||
bool isRunning = false;
|
||||
StreamError errorType{StreamError::noError};
|
||||
|
||||
/**
|
||||
* @brief Map between error types and messages
|
||||
*/
|
||||
@ -59,7 +65,7 @@ public:
|
||||
{StreamError::logicError, "Logic error (probably a bug)"},
|
||||
};
|
||||
|
||||
bool isRunning = false;
|
||||
|
||||
/**
|
||||
* @brief Check if stream has error
|
||||
*
|
||||
@ -67,8 +73,6 @@ public:
|
||||
*/
|
||||
bool error() const { return errorType != StreamError::noError; };
|
||||
|
||||
StreamError errorType{StreamError::noError};
|
||||
|
||||
std::string errorMsg() const { return errorMessages.at(errorType); }
|
||||
|
||||
/**
|
@ -35,12 +35,14 @@ DaqConfiguration::DaqConfiguration(const DeviceInfo &device) {
|
||||
us i = 0;
|
||||
for (auto &inch : inchannel_config) {
|
||||
inch.name = "Unnamed input channel " + std::to_string(i);
|
||||
inch.rangeIndex = device.prefInputRangeIndex;
|
||||
i++;
|
||||
}
|
||||
|
||||
i = 0;
|
||||
for (auto &outch : outchannel_config) {
|
||||
outch.name = "Unnamed output channel " + std::to_string(i);
|
||||
outch.rangeIndex = device.prefOutputRangeIndex;
|
||||
i++;
|
||||
}
|
||||
|
||||
@ -54,10 +56,12 @@ DaqConfiguration::DaqConfiguration(const DeviceInfo &device) {
|
||||
}
|
||||
|
||||
bool DaqConfiguration::match(const DeviceInfo &dev) const {
|
||||
DEBUGTRACE_ENTER;
|
||||
return (dev.device_name == device_name && dev.api == api);
|
||||
}
|
||||
|
||||
int DaqConfiguration::getHighestEnabledInChannel() const {
|
||||
DEBUGTRACE_ENTER;
|
||||
for (int i = inchannel_config.size() - 1; i > -1; i--) {
|
||||
if (inchannel_config.at(i).enabled)
|
||||
return i;
|
||||
@ -66,13 +70,15 @@ int DaqConfiguration::getHighestEnabledInChannel() const {
|
||||
}
|
||||
|
||||
int DaqConfiguration::getHighestEnabledOutChannel() const {
|
||||
for (us i = outchannel_config.size() - 1; i >= 0; i--) {
|
||||
DEBUGTRACE_ENTER;
|
||||
for (int i = outchannel_config.size() - 1; i > -1; i--) {
|
||||
if (outchannel_config.at(i).enabled)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
int DaqConfiguration::getLowestEnabledInChannel() const {
|
||||
DEBUGTRACE_ENTER;
|
||||
for (us i = 0; i < inchannel_config.size(); i++) {
|
||||
if (inchannel_config.at(i).enabled)
|
||||
return i;
|
@ -137,15 +137,26 @@ const DaqApi uldaqapi("UlDaq", 0);
|
||||
#include "RtAudio.h"
|
||||
const us LASP_RTAUDIO_APICODE = 1;
|
||||
const DaqApi rtaudioAlsaApi("RtAudio Linux ALSA", 1, RtAudio::Api::LINUX_ALSA);
|
||||
const DaqApi rtaudioPulseaudioApi("RtAudio Linux Pulseaudio", 1,
|
||||
const DaqApi rtaudioPulseaudioApi("RtAudio Linux Pulseaudio", LASP_RTAUDIO_APICODE,
|
||||
RtAudio::Api::LINUX_PULSE);
|
||||
const DaqApi rtaudioWasapiApi("RtAudio Windows Wasapi", 1,
|
||||
const DaqApi rtaudioWasapiApi("RtAudio Windows Wasapi", LASP_RTAUDIO_APICODE,
|
||||
RtAudio::Api::WINDOWS_WASAPI);
|
||||
const DaqApi rtaudioDsApi("RtAudio Windows DirectSound", 1,
|
||||
const DaqApi rtaudioDsApi("RtAudio Windows DirectSound", LASP_RTAUDIO_APICODE,
|
||||
RtAudio::Api::WINDOWS_DS);
|
||||
const DaqApi rtaudioAsioApi("RtAudio Windows ASIO", 1,
|
||||
const DaqApi rtaudioAsioApi("RtAudio Windows ASIO", LASP_RTAUDIO_APICODE,
|
||||
RtAudio::Api::WINDOWS_ASIO);
|
||||
#endif
|
||||
#if LASP_HAS_PORTAUDIO == 1
|
||||
const us LASP_PORTAUDIO_APICODE = 2;
|
||||
const DaqApi portaudioALSAApi("PortAudio Linux ALSA", LASP_PORTAUDIO_APICODE, 0);
|
||||
const DaqApi portaudioPulseApi("PortAudio Linux PulseAudio", LASP_PORTAUDIO_APICODE, 1);
|
||||
const DaqApi portaudioASIOApi("PortAudio Windows ASIO", LASP_PORTAUDIO_APICODE, 2);
|
||||
const DaqApi portaudioDSApi("PortAudio Windows DirectSound", LASP_PORTAUDIO_APICODE, 3);
|
||||
const DaqApi portaudioWMMEApi("PortAudio Windows WMME", LASP_PORTAUDIO_APICODE, 4);
|
||||
const DaqApi portaudioWASAPIApi("PortAudio Windows WASAPI", LASP_PORTAUDIO_APICODE, 5);
|
||||
const DaqApi portaudioWDMKS("PortAudio Windows WDMKS", LASP_PORTAUDIO_APICODE, 6);
|
||||
const DaqApi portaudioDirectSoundApi("PortAudio Windows DirectSound", LASP_PORTAUDIO_APICODE, 7);
|
||||
#endif
|
||||
|
||||
class DeviceInfo;
|
||||
|
@ -26,7 +26,9 @@ DaqData::DaqData(const us nframes, const us nchannels,
|
||||
DEBUGTRACE_PRINT(sw);
|
||||
|
||||
assert(sw > 0 && sw <= 8);
|
||||
_data = new (std::align_val_t{8}) byte_t[sw * nchannels * nframes];
|
||||
_data = reinterpret_cast<byte_t *>(
|
||||
new double[(sw * nchannels * nframes) / sizeof(double) + 1]);
|
||||
|
||||
if (!_data) {
|
||||
throw rte("Could not allocate memory for DaqData!");
|
||||
}
|
||||
@ -52,7 +54,7 @@ DaqData::DaqData(DaqData &&o)
|
||||
DaqData::~DaqData() {
|
||||
DEBUGTRACE_ENTER;
|
||||
if (_data)
|
||||
delete[] _data;
|
||||
delete[](reinterpret_cast<double *>(_data));
|
||||
}
|
||||
|
||||
void DaqData::copyInFromRaw(const std::vector<byte_t *> &ptrs) {
|
@ -10,6 +10,9 @@
|
||||
#if LASP_HAS_RTAUDIO == 1
|
||||
#include "lasp_rtaudiodaq.h"
|
||||
#endif
|
||||
#if LASP_HAS_PORTAUDIO == 1
|
||||
#include "lasp_portaudiodaq.h"
|
||||
#endif
|
||||
|
||||
|
||||
DeviceInfoList DeviceInfo::getDeviceInfo() {
|
||||
@ -21,6 +24,9 @@ DeviceInfoList DeviceInfo::getDeviceInfo() {
|
||||
#if LASP_HAS_RTAUDIO == 1
|
||||
fillRtAudioDeviceInfo(devs);
|
||||
#endif
|
||||
#if LASP_HAS_PORTAUDIO == 1
|
||||
fillPortAudioDeviceInfo(devs);
|
||||
#endif
|
||||
|
||||
return devs;
|
||||
}
|
@ -19,6 +19,7 @@ public:
|
||||
* @brief Virtual desctructor. Can be derived class.
|
||||
*/
|
||||
virtual ~DeviceInfo() {}
|
||||
DeviceInfo& operator=(const DeviceInfo&) = delete;
|
||||
|
||||
/**
|
||||
* @brief Clone a device info.
|
||||
@ -70,11 +71,23 @@ public:
|
||||
* @brief Available ranges for the input, i.e. +/- 1V and/or +/- 10 V etc.
|
||||
*/
|
||||
dvec availableInputRanges;
|
||||
|
||||
/**
|
||||
* @brief Its preffered range
|
||||
* @brief Available ranges for the output, i.e. +/- 1V and/or +/- 10 V etc.
|
||||
*/
|
||||
dvec availableOutputRanges;
|
||||
|
||||
/**
|
||||
* @brief Its preffered input range
|
||||
*/
|
||||
int prefInputRangeIndex = 0;
|
||||
|
||||
/**
|
||||
* @brief Its preffered output range
|
||||
*/
|
||||
int prefOutputRangeIndex = 0;
|
||||
|
||||
|
||||
/**
|
||||
* @brief The number of input channels available for the device
|
||||
*/
|
||||
@ -124,13 +137,29 @@ public:
|
||||
bool duplexModeForced = false;
|
||||
|
||||
/**
|
||||
* @brief The physical quantity of the output signal. For 'normal' audio
|
||||
* @brief Indicates whether the device is able to run in duplex mode. If false,
|
||||
* devices cannot run in duplex mode, and the `duplexModeForced` flag is meaningless.
|
||||
*/
|
||||
bool hasDuplexMode = false;
|
||||
|
||||
/**
|
||||
* @brief The physical quantity of the input signal from DAQ. For 'normal' audio
|
||||
* interfaces, this is typically a 'number' between +/- full scale. For some
|
||||
* real DAQ devices however, the input quantity corresponds to a physical signal,
|
||||
* such a Volts.
|
||||
*/
|
||||
|
||||
DaqChannel::Qty physicalInputQty = DaqChannel::Qty::Number;
|
||||
|
||||
/**
|
||||
* @brief The physical quantity of the output signal from DAQ. For 'normal' audio
|
||||
* devices, this is typically a 'number' between +/- full scale. For some
|
||||
* devices however, the output quantity corresponds to a physical signal,
|
||||
* real DAQ devices however, the input quantity corresponds to a physical signal,
|
||||
* such a Volts.
|
||||
*/
|
||||
DaqChannel::Qty physicalOutputQty = DaqChannel::Qty::Number;
|
||||
|
||||
|
||||
/**
|
||||
* @brief String representation of DeviceInfo
|
||||
*
|
61
cpp_src/device/lasp_indatahandler.cpp
Normal file
61
cpp_src/device/lasp_indatahandler.cpp
Normal file
@ -0,0 +1,61 @@
|
||||
// #define DEBUGTRACE_ENABLED
|
||||
#include "lasp_indatahandler.h"
|
||||
#include "debugtrace.hpp"
|
||||
#include "lasp_streammgr.h"
|
||||
#include <thread>
|
||||
|
||||
InDataHandler::InDataHandler(SmgrHandle mgr, const InCallbackType cb,
|
||||
const ResetCallbackType resetfcn)
|
||||
: _mgr(mgr), inCallback(cb), reset(resetfcn)
|
||||
#if LASP_DEBUG == 1
|
||||
,
|
||||
main_thread_id(std::this_thread::get_id())
|
||||
#endif
|
||||
{
|
||||
DEBUGTRACE_ENTER;
|
||||
#if LASP_DEBUG == 1
|
||||
assert(mgr->main_thread_id == main_thread_id);
|
||||
#endif
|
||||
}
|
||||
void InDataHandler::start() {
|
||||
DEBUGTRACE_ENTER;
|
||||
checkRightThread();
|
||||
if (SmgrHandle handle = _mgr.lock()) {
|
||||
handle->addInDataHandler(this);
|
||||
#if LASP_DEBUG == 1
|
||||
assert(handle->main_thread_id == main_thread_id);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
void InDataHandler::stop() {
|
||||
DEBUGTRACE_ENTER;
|
||||
// checkRightThread();
|
||||
#if LASP_DEBUG == 1
|
||||
stopCalled = true;
|
||||
#endif
|
||||
if (SmgrHandle smgr = _mgr.lock()) {
|
||||
smgr->removeInDataHandler(*this);
|
||||
} else {
|
||||
DEBUGTRACE_PRINT("No stream manager alive anymore!");
|
||||
}
|
||||
}
|
||||
|
||||
InDataHandler::~InDataHandler() {
|
||||
|
||||
DEBUGTRACE_ENTER;
|
||||
#if LASP_DEBUG == 1
|
||||
// checkRightThread();
|
||||
if (!stopCalled) {
|
||||
std::cerr << "************ BUG: Stop function not called while arriving at "
|
||||
"InDataHandler's destructor. Fix this by calling "
|
||||
"InDataHandler::stop()."
|
||||
<< std::endl;
|
||||
abort();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#if LASP_DEBUG == 1
|
||||
void InDataHandler::checkRightThread() const {
|
||||
assert(std::this_thread::get_id() == main_thread_id);
|
||||
}
|
||||
#endif
|
79
cpp_src/device/lasp_indatahandler.h
Normal file
79
cpp_src/device/lasp_indatahandler.h
Normal file
@ -0,0 +1,79 @@
|
||||
#pragma once
|
||||
#include "lasp_types.h"
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
|
||||
class StreamMgr;
|
||||
using SmgrHandle = std::shared_ptr<StreamMgr>;
|
||||
|
||||
class DaqData;
|
||||
class Daq;
|
||||
|
||||
/** \addtogroup device
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief The function definition of callbacks with incoming DAQ data
|
||||
*/
|
||||
using InCallbackType = std::function<void(const DaqData &)>;
|
||||
/**
|
||||
* @brief Function definition for the reset callback.
|
||||
*/
|
||||
using ResetCallbackType = std::function<void(const Daq *)>;
|
||||
|
||||
class InDataHandler {
|
||||
|
||||
protected:
|
||||
std::weak_ptr<StreamMgr> _mgr;
|
||||
#if LASP_DEBUG == 1
|
||||
// This is a flag to indicate whether the method stop() is called for the
|
||||
// current handler. It should call the method stop() from the derived class's
|
||||
// destructor.
|
||||
std::atomic<bool> stopCalled{false};
|
||||
#endif
|
||||
|
||||
public:
|
||||
~InDataHandler();
|
||||
const InCallbackType inCallback;
|
||||
const ResetCallbackType reset;
|
||||
|
||||
/**
|
||||
* @brief When constructed, the handler is added to the stream manager, which
|
||||
* will call the handlers's inCallback() until stop() is called.
|
||||
*
|
||||
* @param mgr Stream manager.
|
||||
* @param cb The callback that is stored, and called on new DAQ data
|
||||
* @param resetfcn The callback that is stored, and called when the DAQ
|
||||
* changes state.
|
||||
*/
|
||||
InDataHandler(SmgrHandle mgr, InCallbackType cb,
|
||||
ResetCallbackType resetfcn);
|
||||
|
||||
/**
|
||||
* @brief Adds the current InDataHandler to the list of handlers in the
|
||||
* StreamMgr. After this happens, the reset() method stored in this
|
||||
* object is called back. When the stream is running, right after this,
|
||||
* inCallback() is called with DaqData.
|
||||
*/
|
||||
void start();
|
||||
|
||||
/**
|
||||
* @brief Removes the currend InDataHandler from the list of handlers in the
|
||||
* StreamMgr. From that point on, the object can be safely destroyed. Not
|
||||
* calling stop() before destruction of this object is considered a BUG. I.e.
|
||||
* a class which *uses* an InDataHandler should always call stop() in its
|
||||
* destructor.
|
||||
*/
|
||||
void stop();
|
||||
|
||||
#if LASP_DEBUG == 1
|
||||
const std::thread::id main_thread_id;
|
||||
void checkRightThread() const;
|
||||
#else
|
||||
void checkRightThread() const {}
|
||||
#endif
|
||||
};
|
||||
/** @} */
|
@ -1,5 +1,5 @@
|
||||
#include <mutex>
|
||||
/* #define DEBUGTRACE_ENABLED */
|
||||
#include <mutex>
|
||||
#include "debugtrace.hpp"
|
||||
#include "lasp_mathtypes.h"
|
||||
|
||||
@ -17,37 +17,46 @@ using rte = std::runtime_error;
|
||||
using std::vector;
|
||||
using lck = std::scoped_lock<std::mutex>;
|
||||
|
||||
class RtAudioDeviceInfo : public DeviceInfo {
|
||||
const unsigned RTAUDIO_MAX_CHANNELS = 8;
|
||||
|
||||
class RtAudioDeviceInfo : public DeviceInfo
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Specific for the device (Sub-API). Important for the RtAudio
|
||||
* backend, as RtAudio is able to handle different API's.
|
||||
*/
|
||||
int _api_devindex;
|
||||
virtual std::unique_ptr<DeviceInfo> clone() const override {
|
||||
int ID; // Copy of RtAudio::DeviceInfo::ID
|
||||
virtual std::unique_ptr<DeviceInfo> clone() const override
|
||||
{
|
||||
return std::make_unique<DeviceInfo>(*this);
|
||||
}
|
||||
};
|
||||
|
||||
void fillRtAudioDeviceInfo(DeviceInfoList &devinfolist) {
|
||||
void fillRtAudioDeviceInfo(DeviceInfoList &devinfolist)
|
||||
{
|
||||
DEBUGTRACE_ENTER;
|
||||
|
||||
vector<RtAudio::Api> apis;
|
||||
RtAudio::getCompiledApi(apis);
|
||||
|
||||
for (auto api : apis) {
|
||||
for (auto api : apis)
|
||||
{
|
||||
RtAudio rtaudio(api);
|
||||
us count = rtaudio.getDeviceCount();
|
||||
for (us devno = 0; devno < count; devno++) {
|
||||
const us count = rtaudio.getDeviceCount();
|
||||
|
||||
const auto ids = rtaudio.getDeviceIds();
|
||||
|
||||
for (us i = 0; i < count; i++)
|
||||
{
|
||||
us id = ids.at(i);
|
||||
|
||||
RtAudio::DeviceInfo devinfo = rtaudio.getDeviceInfo(id);
|
||||
|
||||
RtAudio::DeviceInfo devinfo = rtaudio.getDeviceInfo(devno);
|
||||
if (!devinfo.probed) {
|
||||
// Device capabilities not successfully probed. Continue to next
|
||||
continue;
|
||||
}
|
||||
// "Our device info struct"
|
||||
RtAudioDeviceInfo d;
|
||||
switch (api) {
|
||||
switch (api)
|
||||
{
|
||||
case RtAudio::LINUX_ALSA:
|
||||
d.api = rtaudioAlsaApi;
|
||||
break;
|
||||
@ -70,42 +79,49 @@ void fillRtAudioDeviceInfo(DeviceInfoList &devinfolist) {
|
||||
}
|
||||
|
||||
d.device_name = devinfo.name;
|
||||
d._api_devindex = devno;
|
||||
d.ID = id;
|
||||
|
||||
/// When 48k is available we overwrite the default sample rate with the 48
|
||||
/// kHz value, which is our preffered rate,
|
||||
bool rate_48k_found = false;
|
||||
|
||||
for (us j = 0; j < devinfo.sampleRates.size(); j++) {
|
||||
for (us j = 0; j < devinfo.sampleRates.size(); j++)
|
||||
{
|
||||
|
||||
us rate_int = devinfo.sampleRates[j];
|
||||
|
||||
d.availableSampleRates.push_back((double)rate_int);
|
||||
|
||||
if (!rate_48k_found) {
|
||||
if (!rate_48k_found)
|
||||
{
|
||||
|
||||
if (devinfo.preferredSampleRate == rate_int) {
|
||||
if (devinfo.preferredSampleRate == rate_int)
|
||||
{
|
||||
d.prefSampleRateIndex = j;
|
||||
}
|
||||
|
||||
if (rate_int == 48000) {
|
||||
if (rate_int == 48000)
|
||||
{
|
||||
d.prefSampleRateIndex = j;
|
||||
rate_48k_found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
d.noutchannels = devinfo.outputChannels;
|
||||
d.ninchannels = devinfo.inputChannels;
|
||||
d.noutchannels = std::min(devinfo.outputChannels, RTAUDIO_MAX_CHANNELS);
|
||||
d.ninchannels = std::min(devinfo.inputChannels, RTAUDIO_MAX_CHANNELS);
|
||||
|
||||
d.availableInputRanges = {1.0};
|
||||
d.availableOutputRanges = {1.0};
|
||||
|
||||
RtAudioFormat formats = devinfo.nativeFormats;
|
||||
if (formats & RTAUDIO_SINT8) {
|
||||
if (formats & RTAUDIO_SINT8)
|
||||
{
|
||||
d.availableDataTypes.push_back(
|
||||
DataTypeDescriptor::DataType::dtype_int8);
|
||||
}
|
||||
if (formats & RTAUDIO_SINT16) {
|
||||
if (formats & RTAUDIO_SINT16)
|
||||
{
|
||||
d.availableDataTypes.push_back(
|
||||
DataTypeDescriptor::DataType::dtype_int16);
|
||||
}
|
||||
@ -113,15 +129,18 @@ void fillRtAudioDeviceInfo(DeviceInfoList &devinfolist) {
|
||||
/* d.availableDataTypes.push_back(DataTypeDescriptor::DataType::dtype_int24);
|
||||
*/
|
||||
/* } */
|
||||
if (formats & RTAUDIO_SINT32) {
|
||||
if (formats & RTAUDIO_SINT32)
|
||||
{
|
||||
d.availableDataTypes.push_back(
|
||||
DataTypeDescriptor::DataType::dtype_fl32);
|
||||
}
|
||||
if (formats & RTAUDIO_FLOAT64) {
|
||||
if (formats & RTAUDIO_FLOAT64)
|
||||
{
|
||||
d.availableDataTypes.push_back(
|
||||
DataTypeDescriptor::DataType::dtype_fl64);
|
||||
}
|
||||
if (d.availableDataTypes.size() == 0) {
|
||||
if (d.availableDataTypes.size() == 0)
|
||||
{
|
||||
std::cerr << "RtAudio: No data types found in device!" << endl;
|
||||
}
|
||||
|
||||
@ -139,9 +158,8 @@ static int mycallback(void *outputBuffer, void *inputBuffer,
|
||||
unsigned int nFrames, double streamTime,
|
||||
RtAudioStreamStatus status, void *userData);
|
||||
|
||||
static void myerrorcallback(RtAudioError::Type, const string &errorText);
|
||||
|
||||
class RtAudioDaq : public Daq {
|
||||
class RtAudioDaq : public Daq
|
||||
{
|
||||
|
||||
RtAudio rtaudio;
|
||||
const us nFramesPerBlock;
|
||||
@ -177,24 +195,24 @@ public:
|
||||
|
||||
inParams = std::make_unique<RtAudio::StreamParameters>();
|
||||
|
||||
// +1 to get the count.
|
||||
inParams->nChannels = getHighestEnabledInChannel() + 1;
|
||||
if (inParams->nChannels < 1) {
|
||||
throw rte("Invalid input number of channels");
|
||||
}
|
||||
/// RtAudio lacks good bookkeeping when the first channel is not equal to
|
||||
/// 0. For now, our fix is to shift out the channels we want, and let
|
||||
/// RtAudio pass on all channels.
|
||||
inParams->firstChannel = 0;
|
||||
inParams->deviceId = devinfo._api_devindex;
|
||||
|
||||
} else {
|
||||
inParams->nChannels = devinfo.ninchannels;
|
||||
inParams->deviceId = devinfo.ID;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
outParams = std::make_unique<RtAudio::StreamParameters>();
|
||||
|
||||
outParams->nChannels = getHighestEnabledOutChannel() + 1;
|
||||
if (outParams->nChannels < 1) {
|
||||
throw rte("Invalid output number of channels");
|
||||
}
|
||||
/// RtAudio lacks good bookkeeping when the first channel is not equal to
|
||||
/// 0. For now, our fix is to shift out the channels we want, and let
|
||||
/// RtAudio pass on all channels.
|
||||
outParams->firstChannel = 0;
|
||||
outParams->deviceId = devinfo._api_devindex;
|
||||
outParams->nChannels = devinfo.noutchannels;
|
||||
outParams->deviceId = devinfo.ID;
|
||||
}
|
||||
|
||||
RtAudio::StreamOptions streamoptions;
|
||||
@ -207,7 +225,8 @@ public:
|
||||
RtAudioFormat format;
|
||||
using Dtype = DataTypeDescriptor::DataType;
|
||||
const Dtype dtype = dataType();
|
||||
switch (dtype) {
|
||||
switch (dtype)
|
||||
{
|
||||
case Dtype::dtype_fl32:
|
||||
DEBUGTRACE_PRINT("Datatype float32");
|
||||
format = RTAUDIO_FLOAT32;
|
||||
@ -238,36 +257,46 @@ public:
|
||||
unsigned int nFramesPerBlock_copy = nFramesPerBlock;
|
||||
|
||||
// Final step: open the stream.
|
||||
rtaudio.openStream(outParams.get(), inParams.get(), format,
|
||||
RtAudioErrorType err = rtaudio.openStream(outParams.get(), inParams.get(), format,
|
||||
static_cast<us>(samplerate()), &nFramesPerBlock_copy,
|
||||
mycallback, (void *)this, &streamoptions,
|
||||
&myerrorcallback);
|
||||
mycallback, (void *)this, &streamoptions);
|
||||
if (err != RTAUDIO_NO_ERROR)
|
||||
{
|
||||
throw std::runtime_error(string("Error opening stream: ") + rtaudio.getErrorText());
|
||||
}
|
||||
|
||||
if (nFramesPerBlock_copy != nFramesPerBlock) {
|
||||
throw rte("Got different number of frames per block back from RtAudio "
|
||||
"backend. I do not know what to do.");
|
||||
if (nFramesPerBlock_copy != nFramesPerBlock)
|
||||
{
|
||||
throw rte(string("Got different number of frames per block back from RtAudio "
|
||||
"backend: ") +
|
||||
std::to_string(nFramesPerBlock_copy) + ". I do not know what to do.");
|
||||
}
|
||||
}
|
||||
|
||||
virtual void start(InDaqCallback inCallback,
|
||||
OutDaqCallback outCallback) override final {
|
||||
OutDaqCallback outCallback) override final
|
||||
{
|
||||
|
||||
DEBUGTRACE_ENTER;
|
||||
|
||||
assert(!monitorOutput);
|
||||
|
||||
if (getStreamStatus().runningOK()) {
|
||||
if (getStreamStatus().runningOK())
|
||||
{
|
||||
throw rte("Stream already running");
|
||||
}
|
||||
|
||||
// Logical XOR
|
||||
if (inCallback && outCallback) {
|
||||
if (inCallback && outCallback)
|
||||
{
|
||||
throw rte("Either input or output stream possible for RtAudio. "
|
||||
"Stream duplex mode not provided.");
|
||||
}
|
||||
|
||||
if (neninchannels() > 0) {
|
||||
if (!inCallback) {
|
||||
if (neninchannels() > 0)
|
||||
{
|
||||
if (!inCallback)
|
||||
{
|
||||
throw rte(
|
||||
|
||||
"Input callback given, but stream does not provide input data");
|
||||
@ -275,8 +304,10 @@ public:
|
||||
|
||||
_incallback = inCallback;
|
||||
}
|
||||
if (nenoutchannels() > 0) {
|
||||
if (!outCallback) {
|
||||
if (nenoutchannels() > 0)
|
||||
{
|
||||
if (!outCallback)
|
||||
{
|
||||
throw rte(
|
||||
"Output callback given, but stream does not provide output data");
|
||||
}
|
||||
@ -284,7 +315,11 @@ public:
|
||||
}
|
||||
|
||||
// Start the stream. Throws on error.
|
||||
rtaudio.startStream();
|
||||
const auto err = rtaudio.startStream();
|
||||
if (err != RTAUDIO_NO_ERROR)
|
||||
{
|
||||
throw std::runtime_error(string("Error starting stream: ") + rtaudio.getErrorText());
|
||||
}
|
||||
|
||||
// If we are here, we are running without errors.
|
||||
StreamStatus status;
|
||||
@ -294,10 +329,15 @@ public:
|
||||
|
||||
StreamStatus getStreamStatus() const override final { return _streamStatus; }
|
||||
|
||||
void stop() override final {
|
||||
void stop() override final
|
||||
{
|
||||
DEBUGTRACE_ENTER;
|
||||
if (getStreamStatus().runningOK()) {
|
||||
rtaudio.stopStream();
|
||||
if (getStreamStatus().runningOK())
|
||||
{
|
||||
const auto err = rtaudio.stopStream();
|
||||
if(err != RTAUDIO_NO_ERROR) {
|
||||
std::cerr << "Error occured while stopping the stream: " << rtaudio.getErrorText() << endl;
|
||||
}
|
||||
}
|
||||
StreamStatus s = _streamStatus;
|
||||
s.isRunning = false;
|
||||
@ -306,14 +346,16 @@ public:
|
||||
}
|
||||
|
||||
int streamCallback(void *outputBuffer, void *inputBuffer,
|
||||
unsigned int nFrames, RtAudioStreamStatus status) {
|
||||
unsigned int nFrames, RtAudioStreamStatus status)
|
||||
{
|
||||
|
||||
DEBUGTRACE_ENTER;
|
||||
|
||||
using se = StreamStatus::StreamError;
|
||||
|
||||
int rval = 0;
|
||||
auto stopWithError = [&](se e) {
|
||||
auto stopWithError = [&](se e)
|
||||
{
|
||||
DEBUGTRACE_PRINT("stopWithError");
|
||||
StreamStatus stat = _streamStatus;
|
||||
stat.errorType = e;
|
||||
@ -322,7 +364,8 @@ public:
|
||||
rval = 1;
|
||||
};
|
||||
|
||||
switch (status) {
|
||||
switch (status)
|
||||
{
|
||||
case RTAUDIO_INPUT_OVERFLOW:
|
||||
stopWithError(se::inputXRun);
|
||||
return 1;
|
||||
@ -338,44 +381,46 @@ public:
|
||||
const auto &dtype_descr = dtypeDescr();
|
||||
const auto dtype = dataType();
|
||||
|
||||
us neninchannels = this->neninchannels();
|
||||
us nenoutchannels = this->nenoutchannels();
|
||||
us sw = dtype_descr.sw;
|
||||
if (nFrames != nFramesPerBlock) {
|
||||
const us neninchannels = this->neninchannels();
|
||||
const us nenoutchannels = this->nenoutchannels();
|
||||
const us sw = dtype_descr.sw;
|
||||
if (nFrames != nFramesPerBlock)
|
||||
{
|
||||
cerr << "RtAudio backend error: nFrames does not match block size!"
|
||||
<< endl;
|
||||
stopWithError(se::logicError);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (inputBuffer) {
|
||||
if (inputBuffer)
|
||||
{
|
||||
assert(_incallback);
|
||||
std::vector<byte_t *> ptrs;
|
||||
ptrs.reserve(neninchannels);
|
||||
|
||||
const us ch_min = getLowestEnabledInChannel();
|
||||
const us ch_max = getHighestEnabledInChannel();
|
||||
us i = 0;
|
||||
for (us ch = ch_min; ch <= ch_max; ch++) {
|
||||
if (inchannel_config.at(ch).enabled) {
|
||||
assert(ch_min < ninchannels);
|
||||
assert(ch_max < ninchannels);
|
||||
|
||||
/// Only pass on the pointers of the channels we want
|
||||
for (us ch = ch_min; ch <= ch_max; ch++)
|
||||
{
|
||||
if (inchannel_config.at(ch).enabled)
|
||||
{
|
||||
byte_t *ptr =
|
||||
static_cast<byte_t *>(inputBuffer) + sw * i * nFramesPerBlock;
|
||||
DEBUGTRACE_PRINT((us)ptr);
|
||||
static_cast<byte_t *>(inputBuffer) + sw * ch * nFramesPerBlock;
|
||||
ptrs.push_back(ptr);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
DaqData d{nFramesPerBlock, neninchannels, dtype};
|
||||
d.copyInFromRaw(ptrs);
|
||||
|
||||
bool ret = _incallback(d);
|
||||
if (!ret) {
|
||||
stopWithError(se::noError);
|
||||
return 1;
|
||||
}
|
||||
_incallback(d);
|
||||
}
|
||||
|
||||
if (outputBuffer) {
|
||||
if (outputBuffer)
|
||||
{
|
||||
assert(_outcallback);
|
||||
std::vector<byte_t *> ptrs;
|
||||
ptrs.reserve(nenoutchannels);
|
||||
@ -384,24 +429,24 @@ public:
|
||||
|
||||
const us ch_min = getLowestEnabledOutChannel();
|
||||
const us ch_max = getHighestEnabledOutChannel();
|
||||
us i = 0;
|
||||
for (us ch = ch_min; ch <= ch_max; ch++) {
|
||||
if (outchannel_config.at(ch).enabled) {
|
||||
|
||||
assert(ch_min < noutchannels);
|
||||
assert(ch_max < noutchannels);
|
||||
/// Only pass on the pointers of the channels we want
|
||||
for (us ch = ch_min; ch <= ch_max; ch++)
|
||||
{
|
||||
if (outchannel_config.at(ch).enabled)
|
||||
{
|
||||
ptrs.push_back(static_cast<byte_t *>(outputBuffer) +
|
||||
sw * i * nFramesPerBlock);
|
||||
sw * ch * nFramesPerBlock);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
DaqData d{nFramesPerBlock, nenoutchannels, dtype};
|
||||
|
||||
bool ret = _outcallback(d);
|
||||
if (!ret) {
|
||||
stopWithError(se::noError);
|
||||
return 1;
|
||||
}
|
||||
_outcallback(d);
|
||||
// Copy over the buffer
|
||||
us j = 0;
|
||||
for (auto ptr : ptrs) {
|
||||
for (auto ptr : ptrs)
|
||||
{
|
||||
d.copyToRaw(j, ptr);
|
||||
j++;
|
||||
}
|
||||
@ -417,17 +462,16 @@ public:
|
||||
};
|
||||
|
||||
std::unique_ptr<Daq> createRtAudioDevice(const DeviceInfo &devinfo,
|
||||
const DaqConfiguration &config) {
|
||||
const DaqConfiguration &config)
|
||||
{
|
||||
return std::make_unique<RtAudioDaq>(devinfo, config);
|
||||
}
|
||||
|
||||
void myerrorcallback(RtAudioError::Type, const string &errorText) {
|
||||
cerr << "RtAudio backend stream error: " << errorText << endl;
|
||||
}
|
||||
int mycallback(
|
||||
void *outputBuffer, void *inputBuffer, unsigned int nFrames,
|
||||
__attribute__((unused)) double streamTime, // Not used parameter streamTime
|
||||
RtAudioStreamStatus status, void *userData) {
|
||||
RtAudioStreamStatus status, void *userData)
|
||||
{
|
||||
|
||||
return static_cast<RtAudioDaq *>(userData)->streamCallback(
|
||||
outputBuffer, inputBuffer, nFrames, status);
|
35
cpp_src/device/lasp_rtaudiodaq.h
Normal file
35
cpp_src/device/lasp_rtaudiodaq.h
Normal file
@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
#include "lasp_daq.h"
|
||||
#include <memory>
|
||||
|
||||
/** \addtogroup device
|
||||
* @{
|
||||
* \defgroup rtaudio RtAudio backend
|
||||
* This code is used to interface with the RtAudio cross-platform audio
|
||||
* interface.
|
||||
*
|
||||
* \addtogroup rtaudio
|
||||
* @{
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @brief Method called from Daq::createDaq.
|
||||
*
|
||||
* @param devinfo Device info
|
||||
* @param config DAQ Configuration settings
|
||||
*
|
||||
* @return Pointer to Daq instance. Throws Runtime errors on error.
|
||||
*/
|
||||
std::unique_ptr<Daq> createRtAudioDevice(const DeviceInfo& devinfo,
|
||||
const DaqConfiguration& config);
|
||||
|
||||
/**
|
||||
* @brief Append RtAudio backend devices to the list
|
||||
*
|
||||
* @param devinfolist List to append to
|
||||
*/
|
||||
void fillRtAudioDeviceInfo(DeviceInfoList &devinfolist);
|
||||
|
||||
/** @} */
|
||||
/** @} */
|
@ -1,103 +1,136 @@
|
||||
/* #define DEBUGTRACE_ENABLED */
|
||||
// #define DEBUGTRACE_ENABLED
|
||||
#include "lasp_streammgr.h"
|
||||
#include "debugtrace.hpp"
|
||||
#include "lasp_biquadbank.h"
|
||||
#include "lasp_thread.h"
|
||||
#include <algorithm>
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
#include "debugtrace.hpp"
|
||||
#include "lasp_biquadbank.h"
|
||||
#include "lasp_indatahandler.h"
|
||||
#include "lasp_thread.h"
|
||||
|
||||
using namespace std::literals::chrono_literals;
|
||||
|
||||
using std::cerr;
|
||||
using std::endl;
|
||||
using rte = std::runtime_error;
|
||||
|
||||
InDataHandler::InDataHandler(StreamMgr &mgr) : _mgr(mgr) { DEBUGTRACE_ENTER; }
|
||||
void InDataHandler::start() {
|
||||
DEBUGTRACE_ENTER;
|
||||
_mgr.addInDataHandler(*this);
|
||||
}
|
||||
void InDataHandler::stop() {
|
||||
#if LASP_DEBUG == 1
|
||||
stopCalled = true;
|
||||
#endif
|
||||
_mgr.removeInDataHandler(*this);
|
||||
}
|
||||
InDataHandler::~InDataHandler() {
|
||||
/**
|
||||
* @brief The main global handle to a stream, stored in a weak pointer, if it
|
||||
* does not yet exist, via StreamMgr::getInstance, a new stream mgr is created.
|
||||
* It also makes sure that the stream manager is deleted once the latest handle
|
||||
* to it has been destroyed (no global stuff left).
|
||||
*/
|
||||
std::weak_ptr<StreamMgr> _mgr;
|
||||
std::mutex _mgr_mutex;
|
||||
|
||||
using Lck = std::scoped_lock<std::recursive_mutex>;
|
||||
|
||||
/**
|
||||
* @brief The only way to obtain a stream manager, can only be called from the
|
||||
* thread that does it the first time.
|
||||
*
|
||||
* @return Stream manager handle
|
||||
*/
|
||||
SmgrHandle StreamMgr::getInstance() {
|
||||
DEBUGTRACE_ENTER;
|
||||
#if LASP_DEBUG == 1
|
||||
if (!stopCalled) {
|
||||
cerr << "************ BUG: Stop function not called while arriving at "
|
||||
"InDataHandler's destructor. Fix this by calling "
|
||||
"InDataHandler::stop() from the derived class' destructor."
|
||||
<< endl;
|
||||
abort();
|
||||
|
||||
std::scoped_lock<std::mutex> lck(_mgr_mutex);
|
||||
auto mgr = _mgr.lock();
|
||||
if (!mgr) {
|
||||
// Double Check Locking Pattern, if two threads would simultaneously
|
||||
// instantiate the singleton instance.
|
||||
|
||||
auto mgr = _mgr.lock();
|
||||
if (mgr) {
|
||||
return mgr;
|
||||
}
|
||||
|
||||
mgr = SmgrHandle(new StreamMgr());
|
||||
if (!mgr) {
|
||||
throw rte("Fatal: could not allocate stream manager!");
|
||||
}
|
||||
// Update global weak pointer
|
||||
_mgr = mgr;
|
||||
return mgr;
|
||||
}
|
||||
#if LASP_DEBUG == 1
|
||||
// Make sure we never ask for a new SmgrHandle from a different thread.
|
||||
assert(std::this_thread::get_id() == mgr->main_thread_id);
|
||||
#endif
|
||||
}
|
||||
|
||||
StreamMgr &StreamMgr::getInstance() {
|
||||
|
||||
DEBUGTRACE_ENTER;
|
||||
static StreamMgr mgr;
|
||||
return mgr;
|
||||
}
|
||||
StreamMgr::StreamMgr() {
|
||||
DEBUGTRACE_ENTER;
|
||||
|
||||
StreamMgr::StreamMgr()
|
||||
#if LASP_DEBUG == 1
|
||||
_main_thread_id = std::this_thread::get_id();
|
||||
: main_thread_id(std::this_thread::get_id())
|
||||
#endif
|
||||
{
|
||||
DEBUGTRACE_ENTER;
|
||||
// Trigger a scan for the available devices, in the background.
|
||||
rescanDAQDevices(true);
|
||||
}
|
||||
#if LASP_DEBUG == 1
|
||||
void StreamMgr::checkRightThread() const {
|
||||
assert(std::this_thread::get_id() == _main_thread_id);
|
||||
assert(std::this_thread::get_id() == main_thread_id);
|
||||
}
|
||||
#endif
|
||||
|
||||
void StreamMgr::rescanDAQDevices(bool background,
|
||||
std::function<void()> callback) {
|
||||
DEBUGTRACE_ENTER;
|
||||
auto &pool = getPool();
|
||||
|
||||
checkRightThread();
|
||||
if (!_devices_mtx.try_lock()) {
|
||||
throw rte("A background DAQ device scan is probably already running");
|
||||
DEBUGTRACE_PRINT(background);
|
||||
if (_scanningDevices) {
|
||||
throw rte("A background device scan is already busy");
|
||||
}
|
||||
_devices_mtx.unlock();
|
||||
|
||||
Lck lck(_mtx);
|
||||
checkRightThread();
|
||||
if (_inputStream || _outputStream) {
|
||||
throw rte("Rescanning DAQ devices only possible when no stream is running");
|
||||
}
|
||||
|
||||
_devices.clear();
|
||||
/* auto &pool = getPool(); */
|
||||
if (!background) {
|
||||
_scanningDevices = true;
|
||||
rescanDAQDevices_impl(callback);
|
||||
} else {
|
||||
pool.push_task(&StreamMgr::rescanDAQDevices_impl, this, callback);
|
||||
DEBUGTRACE_PRINT("Rescanning DAQ devices on different thread...");
|
||||
_scanningDevices = true;
|
||||
_pool.push_task(&StreamMgr::rescanDAQDevices_impl, this, callback);
|
||||
}
|
||||
}
|
||||
void StreamMgr::rescanDAQDevices_impl(std::function<void()> callback) {
|
||||
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();
|
||||
}
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
bool StreamMgr::inCallback(const DaqData &data) {
|
||||
|
||||
_scanningDevices = false;
|
||||
}
|
||||
void StreamMgr::inCallback(const DaqData &data) {
|
||||
DEBUGTRACE_ENTER;
|
||||
|
||||
std::scoped_lock<std::mutex> lck(_inDataHandler_mtx);
|
||||
Lck lck(_mtx);
|
||||
|
||||
assert(_inputFilters.size() == data.nchannels);
|
||||
|
||||
if (std::count_if(_inputFilters.cbegin(), _inputFilters.cend(),
|
||||
[](const auto &a) { return bool(a); }) > 0) {
|
||||
|
||||
/// Found a filter in vector of input filters. So we have to apply the
|
||||
/// filters to each channel.
|
||||
|
||||
@ -117,33 +150,24 @@ bool StreamMgr::inCallback(const DaqData &data) {
|
||||
}
|
||||
}
|
||||
|
||||
DEBUGTRACE_PRINT("Calling incallback for handlers (filtered)...");
|
||||
for (auto &handler : _inDataHandlers) {
|
||||
bool res = handler->inCallback(input_filtered);
|
||||
if (!res) {
|
||||
return false;
|
||||
handler->inCallback(input_filtered);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
/// No input filters
|
||||
DEBUGTRACE_PRINT("Calling incallback for handlers...");
|
||||
for (auto &handler : _inDataHandlers) {
|
||||
|
||||
bool res = handler->inCallback(data);
|
||||
if (!res) {
|
||||
return false;
|
||||
handler->inCallback(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void StreamMgr::setSiggen(std::shared_ptr<Siggen> siggen) {
|
||||
|
||||
DEBUGTRACE_ENTER;
|
||||
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
|
||||
// generator by resetting it.
|
||||
if (isStreamRunningOK(StreamType::output) && siggen) {
|
||||
@ -166,7 +190,8 @@ void StreamMgr::setSiggen(std::shared_ptr<Siggen> siggen) {
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
template <typename T> bool fillData(DaqData &data, const vd &signal) {
|
||||
template <typename T>
|
||||
bool fillData(DaqData &data, const vd &signal) {
|
||||
/* DEBUGTRACE_ENTER; */
|
||||
assert(data.nframes == signal.size());
|
||||
|
||||
@ -198,11 +223,10 @@ template <typename T> bool fillData(DaqData &data, const vd &signal) {
|
||||
|
||||
return true;
|
||||
}
|
||||
bool 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) {
|
||||
vd signal = _siggen->genSignal(data.nframes);
|
||||
@ -227,52 +251,72 @@ bool StreamMgr::outCallback(DaqData &data) {
|
||||
// Set all values to 0.
|
||||
std::fill(data.raw_ptr(), data.raw_ptr() + data.size_bytes(), 0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
StreamMgr::~StreamMgr() {
|
||||
DEBUGTRACE_ENTER;
|
||||
checkRightThread();
|
||||
stopAllStreams();
|
||||
if (!_inDataHandlers.empty()) {
|
||||
cerr << "*** WARNING: InDataHandlers have not been all stopped, while "
|
||||
"StreamMgr destructor is called. This is a misuse BUG"
|
||||
<< endl;
|
||||
abort();
|
||||
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();
|
||||
}
|
||||
#endif
|
||||
// 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.
|
||||
// Hence, we do not have to do any cleanup here. It also makes sure that the
|
||||
// order in which destructors are called does not matter anymore. As soon as
|
||||
// the stream manager is destructed, the weak pointers loose there ref, and do
|
||||
// not have to removeInDataHandler() anymore.
|
||||
|
||||
// Stop the streams in this phase, otherwise it might happen during the
|
||||
// destruction of the Siggen, in which case we might get calls to pure
|
||||
// virtual methods. This was really a bug.
|
||||
_inputStream.reset();
|
||||
_outputStream.reset();
|
||||
}
|
||||
void StreamMgr::stopAllStreams() {
|
||||
DEBUGTRACE_ENTER;
|
||||
{
|
||||
Lck lck(_mtx);
|
||||
checkRightThread();
|
||||
}
|
||||
// No lock here!
|
||||
_inputStream.reset();
|
||||
_outputStream.reset();
|
||||
}
|
||||
|
||||
void StreamMgr::startStream(const DaqConfiguration &config) {
|
||||
DEBUGTRACE_ENTER;
|
||||
if (_scanningDevices) {
|
||||
throw rte("DAQ device scan is busy. Cannot start stream.");
|
||||
}
|
||||
Lck lck(_mtx);
|
||||
checkRightThread();
|
||||
|
||||
bool isInput = std::count_if(config.inchannel_config.cbegin(),
|
||||
config.inchannel_config.cend(),
|
||||
[](auto &i) { return i.enabled; });
|
||||
[](auto &i) { return i.enabled; }) > 0;
|
||||
|
||||
bool isOutput = std::count_if(config.outchannel_config.cbegin(),
|
||||
config.outchannel_config.cend(),
|
||||
[](auto &i) { return i.enabled; });
|
||||
[](auto &i) { return i.enabled; }) > 0;
|
||||
|
||||
// Find the first device that matches with the configuration
|
||||
std::scoped_lock lck(_devices_mtx);
|
||||
|
||||
DeviceInfo *devinfo = nullptr;
|
||||
bool found = false;
|
||||
|
||||
// Match configuration to a device in the list of devices
|
||||
for (auto &devinfoi : _devices) {
|
||||
if (config.match(*devinfoi)) {
|
||||
devinfo = devinfoi.get();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!devinfo) {
|
||||
if (devinfo == nullptr) {
|
||||
throw rte("Could not find a device with name " + config.device_name +
|
||||
" in list of devices.");
|
||||
}
|
||||
@ -283,19 +327,23 @@ void StreamMgr::startStream(const DaqConfiguration &config) {
|
||||
bool isDuplex = isInput && isOutput;
|
||||
|
||||
if (!isInput && !isOutput) {
|
||||
throw rte("Neither input, nor output channels enabled for "
|
||||
"stream. Cannot start.");
|
||||
throw rte(
|
||||
"Attempted stream start failed, stream does not have any enabled "
|
||||
"channels. Please first enable channels in the channel configuration.");
|
||||
}
|
||||
|
||||
if (isInput && _inputStream) {
|
||||
throw rte("Error: an input stream is already running. Please "
|
||||
throw rte(
|
||||
"Error: an input stream is already running. Please "
|
||||
"first stop existing stream");
|
||||
} else if (isOutput && _outputStream) {
|
||||
throw rte("Error: output stream is already running. Please "
|
||||
throw rte(
|
||||
"Error: output stream is already running. Please "
|
||||
"first stop existing stream");
|
||||
} else if (_inputStream) {
|
||||
if (_inputStream->duplexMode() && isOutput) {
|
||||
throw rte("Error: output stream is already running (in duplex mode). "
|
||||
throw rte(
|
||||
"Error: output stream is already running (in duplex mode). "
|
||||
"Please "
|
||||
"first stop existing stream");
|
||||
}
|
||||
@ -303,13 +351,15 @@ void StreamMgr::startStream(const DaqConfiguration &config) {
|
||||
|
||||
if (_outputStream && isInput && _outputStream->duplexModeForced &&
|
||||
config.match(*_outputStream)) {
|
||||
throw rte("This device is already opened for output. If input is also "
|
||||
throw rte(
|
||||
"This device is already opened for output. If input is also "
|
||||
"required, please enable duplex mode for this device");
|
||||
}
|
||||
|
||||
if (_inputStream && isOutput && _inputStream->duplexModeForced &&
|
||||
config.match(*_inputStream)) {
|
||||
throw rte("This device is already opened for input. If output is also "
|
||||
throw rte(
|
||||
"This device is already opened for input. If output is also "
|
||||
"required, please enable duplex mode for this device");
|
||||
}
|
||||
|
||||
@ -333,10 +383,13 @@ void StreamMgr::startStream(const DaqConfiguration &config) {
|
||||
d fs = daq->samplerate();
|
||||
/// Create input filters
|
||||
_inputFilters.clear();
|
||||
/// No input filter for monitor channel.
|
||||
|
||||
/// No input filter for monitor channel, which comes as the first input
|
||||
/// channel In the list
|
||||
if (config.monitorOutput && devinfo->hasInternalOutputMonitor) {
|
||||
_inputFilters.push_back(nullptr);
|
||||
}
|
||||
|
||||
for (auto &ch : daq->inchannel_config) {
|
||||
if (ch.enabled) {
|
||||
if (ch.digitalHighPassCutOn < 0) {
|
||||
@ -376,61 +429,76 @@ void StreamMgr::startStream(const DaqConfiguration &config) {
|
||||
}
|
||||
}
|
||||
void StreamMgr::stopStream(const StreamType t) {
|
||||
|
||||
DEBUGTRACE_ENTER;
|
||||
checkRightThread();
|
||||
bool resetHandlers = false;
|
||||
std::unique_ptr<Daq> *streamToStop = nullptr;
|
||||
|
||||
{ // Mutex locked in this scope
|
||||
Lck lck(_mtx);
|
||||
if (t == StreamType::input) {
|
||||
if (!_inputStream) {
|
||||
throw rte("Input stream is not running");
|
||||
}
|
||||
/// Kills input stream
|
||||
_inputStream.reset();
|
||||
/// Send reset to all in data handlers
|
||||
for (auto &handler : _inDataHandlers) {
|
||||
handler->reset(nullptr);
|
||||
}
|
||||
streamToStop = std::addressof(_inputStream);
|
||||
resetHandlers = true;
|
||||
} else {
|
||||
/// t == output
|
||||
|
||||
/// Kill input stream in case that one is a duplex stream
|
||||
if (_inputStream && _inputStream->duplexMode()) {
|
||||
_inputStream.reset();
|
||||
streamToStop = std::addressof(_inputStream);
|
||||
} else {
|
||||
if (!_outputStream) {
|
||||
throw rte("Output stream is not running");
|
||||
}
|
||||
_outputStream.reset();
|
||||
streamToStop = std::addressof(_outputStream);
|
||||
} // 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;
|
||||
Lck lck(_mtx);
|
||||
checkRightThread();
|
||||
std::scoped_lock<std::mutex> lck(_inDataHandler_mtx);
|
||||
if (_inputStream) {
|
||||
handler.reset(_inputStream.get());
|
||||
} else {
|
||||
handler.reset(nullptr);
|
||||
}
|
||||
if (std::find(_inDataHandlers.cbegin(), _inDataHandlers.cend(), &handler) !=
|
||||
assert(handler);
|
||||
handler->reset(_inputStream.get());
|
||||
|
||||
if (std::find(_inDataHandlers.cbegin(), _inDataHandlers.cend(), handler) !=
|
||||
_inDataHandlers.cend()) {
|
||||
throw std::runtime_error("Error: handler already added. Probably start() "
|
||||
throw std::runtime_error(
|
||||
"Error: handler already added. Probably start() "
|
||||
"is called more than once on a handler object");
|
||||
}
|
||||
_inDataHandlers.push_back(&handler);
|
||||
_inDataHandlers.push_back(handler);
|
||||
DEBUGTRACE_PRINT(_inDataHandlers.size());
|
||||
}
|
||||
|
||||
void StreamMgr::removeInDataHandler(InDataHandler &handler) {
|
||||
DEBUGTRACE_ENTER;
|
||||
checkRightThread();
|
||||
std::scoped_lock<std::mutex> lck(_inDataHandler_mtx);
|
||||
Lck lck(_mtx);
|
||||
// checkRightThread();
|
||||
_inDataHandlers.remove(&handler);
|
||||
|
||||
DEBUGTRACE_PRINT(_inDataHandlers.size());
|
||||
}
|
||||
|
||||
Daq::StreamStatus StreamMgr::getStreamStatus(const StreamType type) const {
|
||||
/* DEBUGTRACE_ENTER; */
|
||||
DEBUGTRACE_ENTER;
|
||||
|
||||
Lck lck(_mtx);
|
||||
checkRightThread();
|
||||
// Default constructor, says stream is not running, but also no errors
|
||||
|
||||
@ -443,7 +511,7 @@ Daq::StreamStatus StreamMgr::getStreamStatus(const StreamType type) const {
|
||||
}
|
||||
|
||||
const Daq *StreamMgr::getDaq(StreamType type) const {
|
||||
|
||||
Lck lck(_mtx);
|
||||
checkRightThread();
|
||||
|
||||
if (type == StreamType::input) {
|
@ -1,69 +1,18 @@
|
||||
#pragma once
|
||||
#include "lasp_daq.h"
|
||||
#include "lasp_siggen.h"
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
#include "lasp_daq.h"
|
||||
#include "lasp_siggen.h"
|
||||
#include "lasp_thread.h"
|
||||
|
||||
/** \addtogroup device
|
||||
* @{
|
||||
*/
|
||||
class StreamMgr;
|
||||
|
||||
class InDataHandler {
|
||||
|
||||
protected:
|
||||
StreamMgr &_mgr;
|
||||
#if LASP_DEBUG == 1
|
||||
std::atomic<bool> stopCalled{false};
|
||||
#endif
|
||||
|
||||
public:
|
||||
virtual ~InDataHandler();
|
||||
|
||||
/**
|
||||
* @brief When constructed, the handler is added to the stream manager, which
|
||||
* will call the handlers's inCallback() until stop() is called.
|
||||
*
|
||||
* @param mgr Stream manager.
|
||||
*/
|
||||
InDataHandler(StreamMgr &mgr);
|
||||
|
||||
/**
|
||||
* @brief This function is called when input data from a DAQ is available.
|
||||
*
|
||||
* @param daqdata Input data from DAQ
|
||||
*
|
||||
* @return true if no error. False to stop the stream from running.
|
||||
*/
|
||||
virtual bool inCallback(const DaqData &daqdata) = 0;
|
||||
|
||||
/**
|
||||
* @brief Reset in-data handler.
|
||||
*
|
||||
* @param daq New DAQ configuration of inCallback(). If nullptr is given,
|
||||
* it means that the stream is stopped.
|
||||
*/
|
||||
virtual void reset(const Daq *daq = nullptr) = 0;
|
||||
|
||||
/**
|
||||
* @brief This function should be called from the constructor of the
|
||||
* implementation of InDataHandler. It will start the stream's calling of
|
||||
* inCallback().
|
||||
*/
|
||||
void start();
|
||||
|
||||
/**
|
||||
* @brief This function should be called from the destructor of derived
|
||||
* classes, to disable the calls to inCallback(), such that proper
|
||||
* destruction of the object is allowed and no other threads call methods
|
||||
* from the object. It removes the inCallback() from the callback list of the
|
||||
* StreamMgr(). **Failing to call this function results in deadlocks, errors
|
||||
* like "pure virtual function called", or other**.
|
||||
*/
|
||||
void stop();
|
||||
};
|
||||
class InDataHandler;
|
||||
|
||||
class SeriesBiquad;
|
||||
|
||||
@ -76,22 +25,23 @@ class SeriesBiquad;
|
||||
* fact is asserted.
|
||||
*/
|
||||
class StreamMgr {
|
||||
#if LASP_DEBUG == 1
|
||||
std::thread::id _main_thread_id;
|
||||
#endif
|
||||
mutable std::recursive_mutex _mtx;
|
||||
|
||||
/**
|
||||
* @brief Storage for streams.
|
||||
*/
|
||||
std::unique_ptr<Daq> _inputStream, _outputStream;
|
||||
|
||||
std::atomic<bool> _scanningDevices{false};
|
||||
|
||||
GlobalThreadPool _pool;
|
||||
|
||||
/**
|
||||
* @brief All indata handlers are called when input data is available. Note
|
||||
* that they can be called from different threads and should take care of
|
||||
* thread-safety.
|
||||
*/
|
||||
std::list<InDataHandler *> _inDataHandlers;
|
||||
std::mutex _inDataHandler_mtx;
|
||||
|
||||
/**
|
||||
* @brief Signal generator in use to generate output data. Currently
|
||||
@ -104,20 +54,21 @@ class StreamMgr {
|
||||
*/
|
||||
std::vector<std::unique_ptr<SeriesBiquad>> _inputFilters;
|
||||
|
||||
std::mutex _siggen_mtx;
|
||||
|
||||
std::mutex _devices_mtx;
|
||||
/**
|
||||
* @brief Current storage for the device list
|
||||
*/
|
||||
DeviceInfoList _devices;
|
||||
|
||||
// Singleton, no public constructor. Can only be obtained using
|
||||
// getInstance();
|
||||
StreamMgr();
|
||||
|
||||
friend class InDataHandler;
|
||||
friend class Siggen;
|
||||
|
||||
// Singleton, no public destructor
|
||||
public:
|
||||
~StreamMgr();
|
||||
|
||||
public:
|
||||
enum class StreamType : us {
|
||||
/**
|
||||
* @brief Input stream
|
||||
@ -137,7 +88,7 @@ class StreamMgr {
|
||||
*
|
||||
* @return Reference to stream manager.
|
||||
*/
|
||||
static StreamMgr &getInstance();
|
||||
static std::shared_ptr<StreamMgr> getInstance();
|
||||
|
||||
/**
|
||||
* @brief Obtain a list of devices currently available. When the StreamMgr is
|
||||
@ -146,9 +97,10 @@ class StreamMgr {
|
||||
* @return A copy of the internal stored list of devices
|
||||
*/
|
||||
DeviceInfoList getDeviceInfo() const {
|
||||
std::scoped_lock lck(const_cast<std::mutex &>(_devices_mtx));
|
||||
std::scoped_lock lck(_mtx);
|
||||
DeviceInfoList d2;
|
||||
for(const auto& dev: _devices) {
|
||||
for (const auto &dev : _devices) {
|
||||
assert(dev != nullptr);
|
||||
d2.push_back(dev->clone());
|
||||
}
|
||||
return d2;
|
||||
@ -157,14 +109,15 @@ class StreamMgr {
|
||||
/**
|
||||
* @brief Triggers a background scan of the DAQ devices, which updates the
|
||||
* internally stored list of devices. Throws a runtime error when a
|
||||
* background thread is already scanning for devices.
|
||||
* background thread is already scanning for devices, or if a stream is
|
||||
* running.
|
||||
*
|
||||
* @param background Perform searching for DAQ devices in the background. If
|
||||
* set to true, the function returns immediately.
|
||||
* @param callback Function to call when complete.
|
||||
*/
|
||||
void
|
||||
rescanDAQDevices(bool background = false,
|
||||
void rescanDAQDevices(
|
||||
bool background = false,
|
||||
std::function<void()> callback = std::function<void()>());
|
||||
|
||||
/**
|
||||
@ -231,17 +184,16 @@ class StreamMgr {
|
||||
/**
|
||||
* @brief Set active signal generator for output streams. Only one `Siggen'
|
||||
* is active at the same time. Siggen controls its own data race protection
|
||||
* using a mutex.
|
||||
* using a mutex. If no Siggen is there, and an output stream is running, it
|
||||
* will send a default signal of 0.
|
||||
*
|
||||
* @param s New Siggen pointer
|
||||
*/
|
||||
void setSiggen(std::shared_ptr<Siggen> s);
|
||||
|
||||
private:
|
||||
bool inCallback(const DaqData &data);
|
||||
bool outCallback(DaqData &data);
|
||||
|
||||
void removeInDataHandler(InDataHandler &handler);
|
||||
private:
|
||||
void inCallback(const DaqData &data);
|
||||
void outCallback(DaqData &data);
|
||||
|
||||
/**
|
||||
* @brief Add an input data handler. The handler's inCallback() function is
|
||||
@ -251,8 +203,14 @@ private:
|
||||
*
|
||||
* @param handler The handler to add.
|
||||
*/
|
||||
void addInDataHandler(InDataHandler &handler);
|
||||
void addInDataHandler(InDataHandler *handler);
|
||||
|
||||
/**
|
||||
* @brief Remove InDataHandler from the list.
|
||||
*
|
||||
* @param handler
|
||||
*/
|
||||
void removeInDataHandler(InDataHandler &handler);
|
||||
/**
|
||||
* @brief Do the actual rescanning.
|
||||
*
|
||||
@ -261,6 +219,7 @@ private:
|
||||
void rescanDAQDevices_impl(std::function<void()> callback);
|
||||
|
||||
#if LASP_DEBUG == 1
|
||||
const std::thread::id main_thread_id;
|
||||
void checkRightThread() const;
|
||||
#else
|
||||
void checkRightThread() const {}
|
@ -1,18 +1,22 @@
|
||||
/* #define DEBUGTRACE_ENABLED */
|
||||
#include "debugtrace.hpp"
|
||||
#include "lasp_config.h"
|
||||
|
||||
#if LASP_HAS_ULDAQ == 1
|
||||
#include "lasp_uldaq.h"
|
||||
#include "lasp_uldaq_impl.h"
|
||||
#include <uldaq.h>
|
||||
|
||||
|
||||
/**
|
||||
* @brief The maximum number of devices that can be enumerated when calling
|
||||
* ulGetDaqDeviceInventory()
|
||||
*/
|
||||
const us MAX_ULDAQ_DEV_COUNT_PER_API = 100;
|
||||
|
||||
void fillUlDaqDeviceInfo(DeviceInfoList &devinfolist) {
|
||||
|
||||
DEBUGTRACE_ENTER;
|
||||
|
||||
|
||||
UlError err;
|
||||
unsigned int numdevs = MAX_ULDAQ_DEV_COUNT_PER_API;
|
||||
|
||||
@ -20,13 +24,13 @@ void fillUlDaqDeviceInfo(DeviceInfoList &devinfolist) {
|
||||
DaqDeviceDescriptor descriptor;
|
||||
DaqDeviceInterface interfaceType = ANY_IFC;
|
||||
|
||||
err = ulGetDaqDeviceInventory(interfaceType, devdescriptors,
|
||||
static_cast<unsigned *>(&numdevs));
|
||||
err = ulGetDaqDeviceInventory(interfaceType, devdescriptors, &numdevs);
|
||||
|
||||
if (err != ERR_NO_ERROR) {
|
||||
throw rte("UlDaq device inventarization failed");
|
||||
}
|
||||
|
||||
DEBUGTRACE_PRINT(string("Number of devices: ") + std::to_string(numdevs));
|
||||
for (unsigned i = 0; i < numdevs; i++) {
|
||||
|
||||
descriptor = devdescriptors[i];
|
||||
@ -35,7 +39,8 @@ void fillUlDaqDeviceInfo(DeviceInfoList &devinfolist) {
|
||||
devinfo._uldaqDescriptor = descriptor;
|
||||
|
||||
devinfo.api = uldaqapi;
|
||||
string name, interface;
|
||||
{
|
||||
string name;
|
||||
string productname = descriptor.productName;
|
||||
if (productname != "DT9837A") {
|
||||
throw rte("Unknown UlDAQ type: " + productname);
|
||||
@ -58,25 +63,26 @@ void fillUlDaqDeviceInfo(DeviceInfoList &devinfolist) {
|
||||
name = "Uknown interface = ";
|
||||
}
|
||||
|
||||
name += string(descriptor.productName) + " " + string(descriptor.uniqueId);
|
||||
devinfo.device_name = std::move(name);
|
||||
name += productname + " " + string(descriptor.uniqueId);
|
||||
devinfo.device_name = name;
|
||||
}
|
||||
|
||||
devinfo.physicalOutputQty = DaqChannel::Qty::Voltage;
|
||||
devinfo.physicalInputQty = DaqChannel::Qty::Voltage;
|
||||
|
||||
devinfo.availableDataTypes.push_back(
|
||||
DataTypeDescriptor::DataType::dtype_fl64);
|
||||
devinfo.prefDataTypeIndex = 0;
|
||||
|
||||
devinfo.availableSampleRates = {8000, 10000, 11025, 16000, 20000,
|
||||
22050, 24000, 32000, 44056, 44100,
|
||||
47250, 48000, 50000, 50400, 51000};
|
||||
|
||||
devinfo.availableSampleRates = ULDAQ_SAMPLERATES;
|
||||
devinfo.prefSampleRateIndex = 11;
|
||||
|
||||
devinfo.availableFramesPerBlock = {512, 1024, 2048, 4096, 8192};
|
||||
|
||||
devinfo.availableInputRanges = {1.0, 10.0};
|
||||
devinfo.availableOutputRanges = {10.0};
|
||||
devinfo.prefInputRangeIndex = 0;
|
||||
devinfo.prefOutputRangeIndex = 0;
|
||||
|
||||
devinfo.ninchannels = 4;
|
||||
devinfo.noutchannels = 1;
|
||||
@ -87,6 +93,7 @@ void fillUlDaqDeviceInfo(DeviceInfoList &devinfolist) {
|
||||
|
||||
devinfo.hasInternalOutputMonitor = true;
|
||||
|
||||
devinfo.hasDuplexMode = true;
|
||||
devinfo.duplexModeForced = true;
|
||||
|
||||
// Finally, this devinfo is pushed back in list
|
||||
@ -96,7 +103,12 @@ void fillUlDaqDeviceInfo(DeviceInfoList &devinfolist) {
|
||||
|
||||
std::unique_ptr<Daq> createUlDaqDevice(const DeviceInfo &devinfo,
|
||||
const DaqConfiguration &config) {
|
||||
return std::make_unique<DT9837A>(devinfo, config);
|
||||
const UlDaqDeviceInfo *_info =
|
||||
dynamic_cast<const UlDaqDeviceInfo *>(&devinfo);
|
||||
if (_info == nullptr) {
|
||||
throw rte("BUG: Could not cast DeviceInfo to UlDaqDeviceInfo");
|
||||
}
|
||||
return std::make_unique<DT9837A>(*_info, config);
|
||||
}
|
||||
|
||||
#endif // LASP_HAS_ULDAQ
|
@ -1,18 +1,24 @@
|
||||
#pragma once
|
||||
#include "lasp_daq.h"
|
||||
|
||||
/**
|
||||
* @brief The maximum number of devices that can be enumerated when calling
|
||||
* ulGetDaqDeviceInventory()
|
||||
/** \addtogroup device
|
||||
* \defgroup uldaq UlDAQ specific code
|
||||
* This code is used to interface with UlDAQ compatible devices. It is only
|
||||
* tested on Linux.
|
||||
* @{
|
||||
* \addtogroup uldaq
|
||||
* @{
|
||||
*/
|
||||
const us MAX_ULDAQ_DEV_COUNT_PER_API = 100;
|
||||
|
||||
std::unique_ptr<Daq> createUlDaqDevice(const DeviceInfo &devinfo,
|
||||
const DaqConfiguration &config);
|
||||
|
||||
/**
|
||||
* @brief Fill device info list with UlDaq specific devices, if any.
|
||||
* @brief Append device info list with UlDaq specific devices, if any.
|
||||
*
|
||||
* @param devinfolist Info list to append to.
|
||||
*/
|
||||
void fillUlDaqDeviceInfo(DeviceInfoList& devinfolist);
|
||||
|
||||
/** @} */
|
||||
/** @} */
|
561
cpp_src/device/portaudio/lasp_portaudiodaq.cpp
Normal file
561
cpp_src/device/portaudio/lasp_portaudiodaq.cpp
Normal file
@ -0,0 +1,561 @@
|
||||
// #define DEBUGTRACE_ENABLED
|
||||
#include "debugtrace.hpp"
|
||||
#include "lasp_config.h"
|
||||
|
||||
#if LASP_HAS_PORTAUDIO == 1
|
||||
#include <gsl-lite/gsl-lite.hpp>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
#include "lasp_portaudiodaq.h"
|
||||
#include "portaudio.h"
|
||||
|
||||
using rte = std::runtime_error;
|
||||
using std::cerr;
|
||||
using std::endl;
|
||||
using std::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) {
|
||||
DEBUGTRACE_ENTER;
|
||||
if (e != paNoError) {
|
||||
throw rte(string("PortAudio backend error: ") + Pa_GetErrorText(e));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Device info, plus PortAudio stuff
|
||||
*/
|
||||
class OurPaDeviceInfo : public DeviceInfo {
|
||||
public:
|
||||
/**
|
||||
* @brief Store instance to PaDeviceInfo.
|
||||
*/
|
||||
PaDeviceInfo _paDevInfo;
|
||||
|
||||
virtual std::unique_ptr<DeviceInfo> clone() const override final {
|
||||
return std::make_unique<OurPaDeviceInfo>(*this);
|
||||
}
|
||||
OurPaDeviceInfo &operator=(const OurPaDeviceInfo &) = delete;
|
||||
OurPaDeviceInfo(const OurPaDeviceInfo &) = default;
|
||||
OurPaDeviceInfo(const PaDeviceInfo &o) : DeviceInfo(), _paDevInfo(o) {}
|
||||
};
|
||||
|
||||
void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist) {
|
||||
DEBUGTRACE_ENTER;
|
||||
bool shouldPaTerminate = false;
|
||||
MuteErrHandler guard;
|
||||
try {
|
||||
PaError err = Pa_Initialize();
|
||||
/// PortAudio says that Pa_Terminate() should not be called whenever there
|
||||
/// is an error in Pa_Initialize(). This is opposite to what most examples
|
||||
/// of PortAudio show.
|
||||
throwIfError(err);
|
||||
shouldPaTerminate = true;
|
||||
|
||||
auto fin = gsl::finally([&err] {
|
||||
DEBUGTRACE_PRINT("Terminating PortAudio instance");
|
||||
err = Pa_Terminate();
|
||||
if (err != paNoError) {
|
||||
cerr << "Error terminating PortAudio. Do not know what to do." << endl;
|
||||
}
|
||||
});
|
||||
|
||||
const PaHostApiIndex apicount = Pa_GetHostApiCount();
|
||||
if (apicount < 0) {
|
||||
return;
|
||||
}
|
||||
/* const PaDeviceInfo *deviceInfo; */
|
||||
const int numDevices = Pa_GetDeviceCount();
|
||||
if (numDevices < 0) {
|
||||
throw rte("PortAudio could not find any devices");
|
||||
}
|
||||
for (us i = 0; i < (us)numDevices; i++) {
|
||||
/* DEBUGTRACE_PRINT(i); */
|
||||
bool hasDuplexMode = false;
|
||||
|
||||
const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(i);
|
||||
if (!deviceInfo) {
|
||||
throw rte("No device info struct returned");
|
||||
}
|
||||
OurPaDeviceInfo d(*deviceInfo);
|
||||
// We store the name in d.device_name
|
||||
d._paDevInfo.name = nullptr;
|
||||
d.device_name = deviceInfo->name;
|
||||
|
||||
const PaHostApiInfo *hostapiinfo = Pa_GetHostApiInfo(deviceInfo->hostApi);
|
||||
if (hostapiinfo == nullptr) {
|
||||
throw std::runtime_error("Hostapi nullptr!");
|
||||
}
|
||||
switch (hostapiinfo->type) {
|
||||
case paALSA:
|
||||
// Duplex mode for alsa
|
||||
hasDuplexMode = true;
|
||||
d.api = portaudioALSAApi;
|
||||
break;
|
||||
case paASIO:
|
||||
hasDuplexMode = true;
|
||||
d.api = portaudioASIOApi;
|
||||
break;
|
||||
case paDirectSound:
|
||||
d.api = portaudioDirectSoundApi;
|
||||
break;
|
||||
case paMME:
|
||||
d.api = portaudioWMMEApi;
|
||||
break;
|
||||
case paWDMKS:
|
||||
d.api = portaudioWDMKS;
|
||||
break;
|
||||
case paWASAPI:
|
||||
d.api = portaudioWASAPIApi;
|
||||
break;
|
||||
case paPulseAudio:
|
||||
d.api = portaudioPulseApi;
|
||||
break;
|
||||
default:
|
||||
throw rte("Unimplemented portaudio API!");
|
||||
break;
|
||||
}
|
||||
|
||||
d.availableDataTypes = {DataTypeDescriptor::DataType::dtype_int16,
|
||||
DataTypeDescriptor::DataType::dtype_int32,
|
||||
DataTypeDescriptor::DataType::dtype_fl32};
|
||||
|
||||
d.prefDataTypeIndex = 2;
|
||||
|
||||
d.availableSampleRates = {8000.0, 9600.0, 11025.0, 12000.0, 16000.0,
|
||||
22050.0, 24000.0, 32000.0, 44100.0, 48000.0,
|
||||
88200.0, 96000.0, 192000.0};
|
||||
d.prefSampleRateIndex = 9;
|
||||
|
||||
d.availableFramesPerBlock = {512, 1024, 2048, 4096, 8192};
|
||||
d.prefFramesPerBlockIndex = 2;
|
||||
|
||||
d.availableInputRanges = {1.0};
|
||||
// d.prefInputRangeIndex = 0; // Constructor-defined
|
||||
d.availableOutputRanges = {1.0};
|
||||
// d.prefOutputRangeIndex = 0; // Constructor-defined
|
||||
|
||||
d.ninchannels = deviceInfo->maxInputChannels;
|
||||
d.noutchannels = deviceInfo->maxOutputChannels;
|
||||
|
||||
// Duplex mode, only for ALSA devices
|
||||
d.hasDuplexMode = hasDuplexMode;
|
||||
|
||||
devinfolist.push_back(std::make_unique<OurPaDeviceInfo>(d));
|
||||
}
|
||||
}
|
||||
|
||||
catch (rte &e) {
|
||||
if (shouldPaTerminate) {
|
||||
PaError err = Pa_Terminate();
|
||||
if (err != paNoError) {
|
||||
cerr << "Error terminating PortAudio. Do not know what to do." << endl;
|
||||
}
|
||||
}
|
||||
cerr << "PortAudio backend error: " << e.what() << std::endl;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Forward declaration of raw callback. Calls into
|
||||
* PortAudioDaq->memberPaCallback. Undocumented parameters are specified
|
||||
* in memberPaCallback
|
||||
*
|
||||
* @param inputBuffer
|
||||
* @param outputBuffer
|
||||
* @param framesPerBuffer
|
||||
* @param timeInfo
|
||||
* @param statusFlags
|
||||
* @param userData Pointer to PortAudioDaq* instance.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
static int rawPaCallback(const void *inputBuffer, void *outputBuffer,
|
||||
unsigned long framesPerBuffer,
|
||||
const PaStreamCallbackTimeInfo *timeInfo,
|
||||
PaStreamCallbackFlags statusFlags, void *userData);
|
||||
|
||||
class PortAudioDaq : public Daq {
|
||||
PaStream *_stream = nullptr;
|
||||
std::atomic<StreamStatus::StreamError> _streamError =
|
||||
StreamStatus::StreamError::noError;
|
||||
InDaqCallback _incallback;
|
||||
OutDaqCallback _outcallback;
|
||||
|
||||
public:
|
||||
PortAudioDaq(const OurPaDeviceInfo &devinfo_gen,
|
||||
const DaqConfiguration &config);
|
||||
|
||||
void start(InDaqCallback inCallback,
|
||||
OutDaqCallback outCallback) override final;
|
||||
void stop() override final;
|
||||
|
||||
StreamStatus getStreamStatus() const override final;
|
||||
|
||||
/**
|
||||
* @brief Member va
|
||||
*
|
||||
* @param inputBuffer
|
||||
* @param outputBuffer
|
||||
* @param framesPerBuffer
|
||||
* @param timeInfo
|
||||
* @param statusFlags
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
int memberPaCallback(const void *inputBuffer, void *outputBuffer,
|
||||
unsigned long framesPerBuffer,
|
||||
const PaStreamCallbackTimeInfo *timeInfo,
|
||||
PaStreamCallbackFlags statusFlags);
|
||||
~PortAudioDaq();
|
||||
};
|
||||
|
||||
std::unique_ptr<Daq> createPortAudioDevice(const DeviceInfo &devinfo,
|
||||
const DaqConfiguration &config) {
|
||||
DEBUGTRACE_ENTER;
|
||||
const OurPaDeviceInfo *_info =
|
||||
dynamic_cast<const OurPaDeviceInfo *>(&devinfo);
|
||||
if (_info == nullptr) {
|
||||
throw rte("BUG: Could not cast DeviceInfo to OurPaDeviceInfo");
|
||||
}
|
||||
return std::make_unique<PortAudioDaq>(*_info, config);
|
||||
}
|
||||
|
||||
static int rawPaCallback(const void *inputBuffer, void *outputBuffer,
|
||||
unsigned long framesPerBuffer,
|
||||
const PaStreamCallbackTimeInfo *timeInfo,
|
||||
PaStreamCallbackFlags statusFlags, void *userData) {
|
||||
return static_cast<PortAudioDaq *>(userData)->memberPaCallback(
|
||||
inputBuffer, outputBuffer, framesPerBuffer, timeInfo, statusFlags);
|
||||
}
|
||||
|
||||
PortAudioDaq::PortAudioDaq(const OurPaDeviceInfo &devinfo_gen,
|
||||
const DaqConfiguration &config)
|
||||
: Daq(devinfo_gen, config) {
|
||||
DEBUGTRACE_ENTER;
|
||||
bool shouldPaTerminate = false;
|
||||
try {
|
||||
PaError err = Pa_Initialize();
|
||||
/// PortAudio says that Pa_Terminate() should not be called whenever there
|
||||
/// is an error in Pa_Initialize(). This is opposite to what most examples
|
||||
/// of PortAudio show.
|
||||
throwIfError(err);
|
||||
|
||||
// OK, Pa_Initialize successfully finished, it means we have to clean up
|
||||
// with Pa_Terminate in the destructor.
|
||||
shouldPaTerminate = true;
|
||||
|
||||
// Going to find the device in the list. If its there, we have to retrieve
|
||||
// the index, as this is required in the PaStreamParameters struct
|
||||
int devindex = -1;
|
||||
for (int i = 0; i < Pa_GetDeviceCount(); i++) {
|
||||
// DEBUGTRACE_PRINT(i);
|
||||
bool ok = true;
|
||||
const PaDeviceInfo *info = Pa_GetDeviceInfo(i);
|
||||
if (!info) {
|
||||
throw rte("No device structure returned from PortAudio");
|
||||
}
|
||||
ok &= string(info->name) == devinfo_gen.device_name;
|
||||
ok &= info->hostApi == devinfo_gen._paDevInfo.hostApi;
|
||||
ok &= info->maxInputChannels == devinfo_gen._paDevInfo.maxInputChannels;
|
||||
ok &= info->maxOutputChannels == devinfo_gen._paDevInfo.maxOutputChannels;
|
||||
ok &= info->defaultSampleRate == devinfo_gen._paDevInfo.defaultSampleRate;
|
||||
|
||||
if (ok) {
|
||||
devindex = i;
|
||||
}
|
||||
}
|
||||
if (devindex < 0) {
|
||||
throw rte(string("Device not found: ") + string(devinfo_gen.device_name));
|
||||
}
|
||||
|
||||
using Dtype = DataTypeDescriptor::DataType;
|
||||
const Dtype dtype = dataType();
|
||||
// Sample format is bit flag
|
||||
PaSampleFormat format = paNonInterleaved;
|
||||
switch (dtype) {
|
||||
case Dtype::dtype_fl32:
|
||||
DEBUGTRACE_PRINT("Datatype float32");
|
||||
format |= paFloat32;
|
||||
break;
|
||||
case Dtype::dtype_fl64:
|
||||
DEBUGTRACE_PRINT("Datatype float64");
|
||||
throw rte("Invalid data type specified for DAQ stream.");
|
||||
break;
|
||||
case Dtype::dtype_int8:
|
||||
DEBUGTRACE_PRINT("Datatype int8");
|
||||
format |= paInt8;
|
||||
break;
|
||||
case Dtype::dtype_int16:
|
||||
DEBUGTRACE_PRINT("Datatype int16");
|
||||
format |= paInt16;
|
||||
break;
|
||||
case Dtype::dtype_int32:
|
||||
DEBUGTRACE_PRINT("Datatype int32");
|
||||
format |= paInt32;
|
||||
break;
|
||||
default:
|
||||
throw rte("Invalid data type specified for DAQ stream.");
|
||||
break;
|
||||
}
|
||||
|
||||
std::unique_ptr<PaStreamParameters> instreamParams;
|
||||
std::unique_ptr<PaStreamParameters> outstreamParams;
|
||||
|
||||
if (neninchannels() > 0) {
|
||||
instreamParams = std::make_unique<PaStreamParameters>(PaStreamParameters(
|
||||
{.device = devindex,
|
||||
.channelCount = (int)getHighestEnabledInChannel() + 1,
|
||||
.sampleFormat = format,
|
||||
.suggestedLatency = framesPerBlock() / samplerate(),
|
||||
.hostApiSpecificStreamInfo = nullptr}));
|
||||
}
|
||||
if (nenoutchannels() > 0) {
|
||||
outstreamParams = std::make_unique<PaStreamParameters>(PaStreamParameters(
|
||||
{.device = devindex,
|
||||
.channelCount = (int)getHighestEnabledOutChannel() + 1,
|
||||
.sampleFormat = format,
|
||||
.suggestedLatency = framesPerBlock() / samplerate(),
|
||||
.hostApiSpecificStreamInfo = nullptr}));
|
||||
}
|
||||
|
||||
// Next step: check whether we are OK
|
||||
err = Pa_IsFormatSupported(instreamParams.get(), outstreamParams.get(),
|
||||
samplerate());
|
||||
throwIfError(err);
|
||||
|
||||
err = Pa_OpenStream(&_stream, // stream
|
||||
instreamParams.get(), // inputParameters
|
||||
outstreamParams.get(), // outputParameters
|
||||
samplerate(), // yeah,
|
||||
framesPerBlock(), // framesPerBuffer
|
||||
paNoFlag, // streamFlags
|
||||
rawPaCallback, this);
|
||||
throwIfError(err);
|
||||
assert(_stream);
|
||||
} catch (rte &e) {
|
||||
if (shouldPaTerminate) {
|
||||
PaError err = Pa_Terminate();
|
||||
if (err != paNoError) {
|
||||
cerr << "Error terminating PortAudio. Do not know what to do." << endl;
|
||||
}
|
||||
}
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void PortAudioDaq::start(InDaqCallback inCallback, OutDaqCallback outCallback) {
|
||||
DEBUGTRACE_ENTER;
|
||||
assert(_stream);
|
||||
MuteErrHandler guard;
|
||||
|
||||
if (Pa_IsStreamActive(_stream)) {
|
||||
throw rte("Stream is already running");
|
||||
}
|
||||
|
||||
if (neninchannels() > 0) {
|
||||
if (!inCallback) {
|
||||
throw rte(
|
||||
|
||||
"Input callback given, but stream does not provide input data");
|
||||
}
|
||||
|
||||
_incallback = inCallback;
|
||||
}
|
||||
if (nenoutchannels() > 0) {
|
||||
if (!outCallback) {
|
||||
throw rte(
|
||||
"Output callback given, but stream does not provide output data");
|
||||
}
|
||||
_outcallback = outCallback;
|
||||
}
|
||||
|
||||
PaError err = Pa_StartStream(_stream);
|
||||
throwIfError(err);
|
||||
}
|
||||
void PortAudioDaq::stop() {
|
||||
DEBUGTRACE_ENTER;
|
||||
assert(_stream);
|
||||
if (Pa_IsStreamStopped(_stream) > 1) {
|
||||
throw rte("Stream is already stopped");
|
||||
}
|
||||
PaError err = Pa_StopStream(_stream);
|
||||
throwIfError(err);
|
||||
}
|
||||
Daq::StreamStatus PortAudioDaq::getStreamStatus() const {
|
||||
DEBUGTRACE_ENTER;
|
||||
// Stores an error type and whether the
|
||||
Daq::StreamStatus status;
|
||||
using StreamError = Daq::StreamStatus::StreamError;
|
||||
Daq::StreamStatus::StreamError errortype = _streamError.load();
|
||||
|
||||
PaError err = Pa_IsStreamStopped(_stream);
|
||||
if (err > 1) {
|
||||
// Stream is stopped due to an error in the callback. The exact error type
|
||||
// is filled in in the if-statement above
|
||||
return status;
|
||||
} else if (err == 0) {
|
||||
// Still running
|
||||
status.isRunning = true;
|
||||
} else if (err < 0) {
|
||||
// Stream encountered an error.
|
||||
switch (err) {
|
||||
case paInternalError:
|
||||
errortype = StreamError::driverError;
|
||||
break;
|
||||
case paDeviceUnavailable:
|
||||
errortype = StreamError::driverError;
|
||||
break;
|
||||
case paInputOverflowed:
|
||||
errortype = StreamError::inputXRun;
|
||||
break;
|
||||
case paOutputUnderflowed:
|
||||
errortype = StreamError::outputXRun;
|
||||
break;
|
||||
default:
|
||||
errortype = StreamError::driverError;
|
||||
cerr << "Portaudio backend error:" << Pa_GetErrorText(err) << endl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
status.errorType = errortype;
|
||||
return status;
|
||||
}
|
||||
|
||||
PortAudioDaq::~PortAudioDaq() {
|
||||
DEBUGTRACE_ENTER;
|
||||
PaError err;
|
||||
assert(_stream);
|
||||
if (Pa_IsStreamActive(_stream)) {
|
||||
// Stop the stream first
|
||||
stop();
|
||||
}
|
||||
|
||||
err = Pa_CloseStream(_stream);
|
||||
_stream = nullptr;
|
||||
if (err != paNoError) {
|
||||
cerr << "Error closing PortAudio stream. Do not know what to do." << endl;
|
||||
}
|
||||
|
||||
err = Pa_Terminate();
|
||||
if (err != paNoError) {
|
||||
cerr << "Error terminating PortAudio. Do not know what to do." << endl;
|
||||
}
|
||||
}
|
||||
|
||||
int PortAudioDaq::memberPaCallback(const void *inputBuffer, void *outputBuffer,
|
||||
unsigned long framesPerBuffer,
|
||||
const PaStreamCallbackTimeInfo *timeInfo,
|
||||
PaStreamCallbackFlags statusFlags) {
|
||||
DEBUGTRACE_ENTER;
|
||||
typedef Daq::StreamStatus::StreamError se;
|
||||
if (statusFlags & paPrimingOutput) {
|
||||
// Initial output buffers generated. So nothing with input yet
|
||||
return paContinue;
|
||||
}
|
||||
if ((statusFlags & paInputUnderflow) || (statusFlags & paInputOverflow)) {
|
||||
_streamError = se::inputXRun;
|
||||
return paAbort;
|
||||
}
|
||||
if ((statusFlags & paOutputUnderflow) || (statusFlags & paOutputOverflow)) {
|
||||
_streamError = se::outputXRun;
|
||||
return paAbort;
|
||||
}
|
||||
if (framesPerBuffer != framesPerBlock()) {
|
||||
cerr << "Logic error: expected a block size of: " << framesPerBlock()
|
||||
<< endl;
|
||||
_streamError = se::logicError;
|
||||
return paAbort;
|
||||
}
|
||||
|
||||
const us neninchannels = this->neninchannels();
|
||||
const us nenoutchannels = this->nenoutchannels();
|
||||
const auto &dtype_descr = dtypeDescr();
|
||||
const auto dtype = dataType();
|
||||
const us sw = dtype_descr.sw;
|
||||
if (inputBuffer) {
|
||||
assert(_incallback);
|
||||
std::vector<byte_t *> ptrs;
|
||||
ptrs.reserve(neninchannels);
|
||||
|
||||
const us ch_min = getLowestEnabledInChannel();
|
||||
const us ch_max = getHighestEnabledInChannel();
|
||||
assert(ch_min < ninchannels);
|
||||
assert(ch_max < ninchannels);
|
||||
|
||||
/// Only pass on the pointers of the channels we want. inputBuffer is
|
||||
/// noninterleaved, as specified in PortAudioDaq constructor.
|
||||
for (us ch = ch_min; ch <= ch_max; ch++) {
|
||||
if (inchannel_config.at(ch).enabled) {
|
||||
byte_t *ch_ptr =
|
||||
reinterpret_cast<byte_t **>(const_cast<void *>(inputBuffer))[ch];
|
||||
ptrs.push_back(ch_ptr);
|
||||
}
|
||||
}
|
||||
DaqData d{framesPerBuffer, neninchannels, dtype};
|
||||
d.copyInFromRaw(ptrs);
|
||||
|
||||
_incallback(d);
|
||||
}
|
||||
|
||||
if (outputBuffer) {
|
||||
assert(_outcallback);
|
||||
std::vector<byte_t *> ptrs;
|
||||
ptrs.reserve(nenoutchannels);
|
||||
|
||||
/* outCallback */
|
||||
|
||||
const us ch_min = getLowestEnabledOutChannel();
|
||||
const us ch_max = getHighestEnabledOutChannel();
|
||||
assert(ch_min < noutchannels);
|
||||
assert(ch_max < noutchannels);
|
||||
/// Only pass on the pointers of the channels we want
|
||||
for (us ch = ch_min; ch <= ch_max; ch++) {
|
||||
if (outchannel_config.at(ch).enabled) {
|
||||
byte_t *ch_ptr = reinterpret_cast<byte_t **>(outputBuffer)[ch];
|
||||
ptrs.push_back(ch_ptr);
|
||||
}
|
||||
}
|
||||
DaqData d{framesPerBuffer, nenoutchannels, dtype};
|
||||
|
||||
_outcallback(d);
|
||||
// Copy over the buffer
|
||||
us j = 0;
|
||||
for (auto ptr : ptrs) {
|
||||
d.copyToRaw(j, ptr);
|
||||
j++;
|
||||
}
|
||||
}
|
||||
|
||||
return paContinue;
|
||||
}
|
||||
#endif
|
35
cpp_src/device/portaudio/lasp_portaudiodaq.h
Normal file
35
cpp_src/device/portaudio/lasp_portaudiodaq.h
Normal file
@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
#include "lasp_daq.h"
|
||||
#include <memory>
|
||||
|
||||
/** \addtogroup device
|
||||
* @{
|
||||
* \defgroup portaudio PortAudio backend
|
||||
* This code is used to interface with the PortAudio cross-platform audio
|
||||
* interface.
|
||||
*
|
||||
* \addtogroup portaudio
|
||||
* @{
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @brief Method called from Daq::createDaq.
|
||||
*
|
||||
* @param devinfo Device info
|
||||
* @param config DAQ Configuration settings
|
||||
*
|
||||
* @return Pointer to Daq instance. Throws Runtime errors on error.
|
||||
*/
|
||||
std::unique_ptr<Daq> createPortAudioDevice(const DeviceInfo& devinfo,
|
||||
const DaqConfiguration& config);
|
||||
|
||||
/**
|
||||
* @brief Append PortAudio backend devices to the list
|
||||
*
|
||||
* @param devinfolist List to append to
|
||||
*/
|
||||
void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist);
|
||||
|
||||
/** @} */
|
||||
/** @} */
|
245
cpp_src/device/uldaq/lasp_uldaq_bufhandler.cpp
Normal file
245
cpp_src/device/uldaq/lasp_uldaq_bufhandler.cpp
Normal file
@ -0,0 +1,245 @@
|
||||
/* #define DEBUGTRACE_ENABLED */
|
||||
#include "debugtrace.hpp"
|
||||
#include "lasp_config.h"
|
||||
|
||||
#if LASP_HAS_ULDAQ == 1
|
||||
#include "lasp_uldaq_bufhandler.h"
|
||||
#include "lasp_daq.h"
|
||||
|
||||
InBufHandler::InBufHandler(DT9837A &daq, InDaqCallback cb)
|
||||
: BufHandler(daq, daq.neninchannels()), cb(cb)
|
||||
|
||||
{
|
||||
DEBUGTRACE_ENTER;
|
||||
assert(daq.getHandle() != 0);
|
||||
|
||||
monitorOutput = daq.monitorOutput;
|
||||
|
||||
DaqInScanFlag inscanflags = DAQINSCAN_FF_DEFAULT;
|
||||
ScanOption scanoptions = SO_CONTINUOUS;
|
||||
UlError err = ERR_NO_ERROR;
|
||||
|
||||
std::vector<DaqInChanDescriptor> indescs;
|
||||
boolvec eninchannels_without_mon = daq.eninchannels(false);
|
||||
|
||||
// Set ranges for each input. Below asks only channels that are not a
|
||||
// monitor channel (hence the false flag).
|
||||
dvec ranges = daq.inputRangeForEnabledChannels(false);
|
||||
|
||||
us enabled_ch_counter = 0;
|
||||
for (us chin = 0; chin < 4; chin++) {
|
||||
if (eninchannels_without_mon[chin] == true) {
|
||||
DaqInChanDescriptor indesc;
|
||||
indesc.type = DAQI_ANALOG_SE;
|
||||
indesc.channel = chin;
|
||||
|
||||
double rangeval = ranges.at(enabled_ch_counter);
|
||||
Range rangenum;
|
||||
if (fabs(rangeval - 1.0) < 1e-8) {
|
||||
rangenum = BIP1VOLTS;
|
||||
} else if (fabs(rangeval - 10.0) < 1e-8) {
|
||||
rangenum = BIP10VOLTS;
|
||||
} else {
|
||||
throw Daq::StreamException(Daq::StreamStatus::StreamError::logicError);
|
||||
std::cerr << "Fatal: input range value is invalid" << endl;
|
||||
return;
|
||||
}
|
||||
indesc.range = rangenum;
|
||||
indescs.push_back(indesc);
|
||||
enabled_ch_counter++;
|
||||
}
|
||||
}
|
||||
|
||||
// Add possibly last channel as monitor
|
||||
if (monitorOutput) {
|
||||
DaqInChanDescriptor indesc;
|
||||
indesc.type = DAQI_DAC;
|
||||
indesc.channel = 0;
|
||||
/// The output only has a range of 10V, therefore the monitor of the
|
||||
/// output also has to be set to this value.
|
||||
indesc.range = BIP10VOLTS;
|
||||
indescs.push_back(indesc);
|
||||
}
|
||||
assert(indescs.size() == nchannels);
|
||||
|
||||
DEBUGTRACE_MESSAGE("Starting input scan");
|
||||
|
||||
err = ulDaqInScan(daq.getHandle(), indescs.data(), nchannels,
|
||||
2 * nFramesPerBlock, // Watch the 2 here!
|
||||
&samplerate, scanoptions, inscanflags, buf.data());
|
||||
throwOnPossibleUlException(err);
|
||||
}
|
||||
void InBufHandler::start() {
|
||||
|
||||
DEBUGTRACE_ENTER;
|
||||
ScanStatus status;
|
||||
TransferStatus transferStatus;
|
||||
UlError err = ulDaqInScanStatus(daq.getHandle(), &status, &transferStatus);
|
||||
throwOnPossibleUlException(err);
|
||||
|
||||
totalFramesCount = transferStatus.currentTotalCount;
|
||||
topenqueued = true;
|
||||
botenqueued = true;
|
||||
}
|
||||
|
||||
bool InBufHandler::operator()() {
|
||||
|
||||
/* DEBUGTRACE_ENTER; */
|
||||
|
||||
bool ret = true;
|
||||
|
||||
auto runCallback = ([&](us totalOffset) {
|
||||
/* DEBUGTRACE_ENTER; */
|
||||
|
||||
DaqData data(nFramesPerBlock, nchannels, dtype_descr.dtype);
|
||||
|
||||
us monitorOffset = monitorOutput ? 1 : 0;
|
||||
/* /// Put the output monitor in front */
|
||||
if (monitorOutput) {
|
||||
for (us frame = 0; frame < nFramesPerBlock; frame++) {
|
||||
data.value<double>(frame, 0) =
|
||||
buf[totalOffset // Offset to lowest part of the buffer, or not
|
||||
+ (frame * nchannels) // Data is interleaved, so skip each
|
||||
+ (nchannels - 1)] // Monitor comes as last in the channel list,
|
||||
// but we want it first in the output data.
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
// Now, all normal channels
|
||||
for (us channel = 0; channel < nchannels - monitorOffset; channel++) {
|
||||
/* DEBUGTRACE_PRINT(channel); */
|
||||
for (us frame = 0; frame < nFramesPerBlock; frame++) {
|
||||
data.value<double>(frame, channel + monitorOffset) =
|
||||
buf[totalOffset + (frame * nchannels) + channel];
|
||||
}
|
||||
}
|
||||
return cb(data);
|
||||
});
|
||||
|
||||
ScanStatus status;
|
||||
TransferStatus transferStatus;
|
||||
|
||||
UlError err = ulDaqInScanStatus(daq.getHandle(), &status, &transferStatus);
|
||||
throwOnPossibleUlException(err);
|
||||
|
||||
us increment = transferStatus.currentTotalCount - totalFramesCount;
|
||||
totalFramesCount += increment;
|
||||
|
||||
if (increment > nFramesPerBlock) {
|
||||
throw Daq::StreamException(Daq::StreamStatus::StreamError::inputXRun);
|
||||
}
|
||||
assert(status == SS_RUNNING);
|
||||
|
||||
if (transferStatus.currentIndex < (long long)buffer_mid_idx) {
|
||||
topenqueued = false;
|
||||
if (!botenqueued) {
|
||||
runCallback(nchannels * nFramesPerBlock);
|
||||
botenqueued = true;
|
||||
}
|
||||
} else {
|
||||
botenqueued = false;
|
||||
if (!topenqueued) {
|
||||
runCallback(0);
|
||||
topenqueued = true;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
InBufHandler::~InBufHandler() {
|
||||
// At exit of the function, stop scanning.
|
||||
DEBUGTRACE_ENTER;
|
||||
UlError err = ulDaqInScanStop(daq.getHandle());
|
||||
if (err != ERR_NO_ERROR) {
|
||||
showErr(err);
|
||||
}
|
||||
}
|
||||
|
||||
OutBufHandler::OutBufHandler(DT9837A &daq, OutDaqCallback cb)
|
||||
: BufHandler(daq, daq.nenoutchannels()), cb(cb) {
|
||||
|
||||
DEBUGTRACE_MESSAGE("Starting output scan");
|
||||
DEBUGTRACE_PRINT(nchannels);
|
||||
AOutScanFlag outscanflags = AOUTSCAN_FF_DEFAULT;
|
||||
ScanOption scanoptions = SO_CONTINUOUS;
|
||||
UlError err = ulAOutScan(daq.getHandle(), 0, 0, BIP10VOLTS,
|
||||
2 * nFramesPerBlock, // Watch the 2 here!
|
||||
&samplerate, scanoptions, outscanflags, buf.data());
|
||||
|
||||
throwOnPossibleUlException(err);
|
||||
}
|
||||
void OutBufHandler::start() {
|
||||
|
||||
ScanStatus status;
|
||||
TransferStatus transferStatus;
|
||||
|
||||
UlError err = ulAOutScanStatus(daq.getHandle(), &status, &transferStatus);
|
||||
if (err != ERR_NO_ERROR) {
|
||||
showErr(err);
|
||||
throw rte("Unable to start output on DAQ");
|
||||
}
|
||||
if (status != SS_RUNNING) {
|
||||
throw rte("Unable to start output on DAQ");
|
||||
}
|
||||
totalFramesCount = transferStatus.currentTotalCount;
|
||||
topenqueued = true;
|
||||
botenqueued = true;
|
||||
}
|
||||
bool OutBufHandler::operator()() {
|
||||
|
||||
DEBUGTRACE_ENTER;
|
||||
bool res = true;
|
||||
assert(daq.getHandle() != 0);
|
||||
|
||||
UlError err = ERR_NO_ERROR;
|
||||
|
||||
ScanStatus status;
|
||||
TransferStatus transferStatus;
|
||||
|
||||
err = ulAOutScanStatus(daq.getHandle(), &status, &transferStatus);
|
||||
throwOnPossibleUlException(err);
|
||||
if (status != SS_RUNNING) {
|
||||
return false;
|
||||
}
|
||||
us increment = transferStatus.currentTotalCount - totalFramesCount;
|
||||
totalFramesCount += increment;
|
||||
|
||||
if (increment > nFramesPerBlock) {
|
||||
cerr << "totalFramesCount: " << totalFramesCount << ". Detected output underrun" << endl;
|
||||
/* throw Daq::StreamException(Daq::StreamStatus::StreamError::outputXRun); */
|
||||
}
|
||||
|
||||
if (transferStatus.currentIndex < buffer_mid_idx) {
|
||||
topenqueued = false;
|
||||
if (!botenqueued) {
|
||||
DaqData d(nFramesPerBlock, 1,// Only one output channel
|
||||
dtype_descr.dtype);
|
||||
// Receive data, run callback
|
||||
cb(d);
|
||||
d.copyToRaw(0, reinterpret_cast<byte_t *>(&(buf[buffer_mid_idx])));
|
||||
|
||||
botenqueued = true;
|
||||
}
|
||||
} else {
|
||||
botenqueued = false;
|
||||
if (!topenqueued) {
|
||||
DaqData d(nFramesPerBlock, 1,// Only one output channel
|
||||
dtype_descr.dtype);
|
||||
// Receive
|
||||
cb(d);
|
||||
d.copyToRaw(0, reinterpret_cast<byte_t *>(&(buf[0])));
|
||||
|
||||
topenqueued = true;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
OutBufHandler::~OutBufHandler() {
|
||||
DEBUGTRACE_ENTER;
|
||||
UlError err = ulAOutScanStop(daq.getHandle());
|
||||
if (err != ERR_NO_ERROR) {
|
||||
showErr(err);
|
||||
}
|
||||
}
|
||||
#endif
|
@ -1,75 +1,20 @@
|
||||
#pragma once
|
||||
#include "lasp_daq.h"
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
#include <uldaq.h>
|
||||
#include <vector>
|
||||
#include "lasp_types.h"
|
||||
#include "lasp_uldaq_impl.h"
|
||||
#include "lasp_uldaq_common.h"
|
||||
|
||||
using std::atomic;
|
||||
using std::cerr;
|
||||
using std::endl;
|
||||
using rte = std::runtime_error;
|
||||
|
||||
/**
|
||||
* @brief UlDaq-specific device information. Adds a copy of the underlying
|
||||
* DaqDeDaqDeviceDescriptor.
|
||||
|
||||
/** \addtogroup device
|
||||
* @{
|
||||
* \addtogroup uldaq
|
||||
*/
|
||||
class UlDaqDeviceInfo : public DeviceInfo {
|
||||
|
||||
public:
|
||||
DaqDeviceDescriptor _uldaqDescriptor;
|
||||
virtual std::unique_ptr<DeviceInfo> clone() const {
|
||||
return std::make_unique<UlDaqDeviceInfo>(*this);
|
||||
}
|
||||
};
|
||||
|
||||
class DT9837A : public Daq {
|
||||
|
||||
DaqDeviceHandle _handle = 0;
|
||||
std::mutex _daqmutex;
|
||||
|
||||
std::thread _thread;
|
||||
atomic<bool> _stopThread{false};
|
||||
atomic<StreamStatus> _streamStatus;
|
||||
|
||||
const us _nFramesPerBlock;
|
||||
|
||||
void threadFcn(InDaqCallback inCallback, OutDaqCallback outcallback);
|
||||
|
||||
public:
|
||||
DaqDeviceHandle getHandle() const { return _handle; }
|
||||
/**
|
||||
* @brief Create a DT9837A instance.
|
||||
*
|
||||
* @param devinfo DeviceInfo to connect to
|
||||
* @param config DaqConfiguration settings
|
||||
*/
|
||||
DT9837A(const DeviceInfo &devinfo, const DaqConfiguration &config);
|
||||
|
||||
virtual ~DT9837A();
|
||||
|
||||
bool isRunning() const;
|
||||
|
||||
void stop() override final;
|
||||
|
||||
friend class InBufHandler;
|
||||
friend class OutBufHandler;
|
||||
|
||||
virtual void start(InDaqCallback inCallback,
|
||||
OutDaqCallback outCallback) override final;
|
||||
|
||||
virtual StreamStatus getStreamStatus() const override {
|
||||
return _streamStatus;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Helper class for managing input and output samples of the DAQ device.
|
||||
*/
|
||||
|
||||
class DT9837A;
|
||||
class BufHandler {
|
||||
protected:
|
||||
/**
|
||||
@ -88,12 +33,19 @@ protected:
|
||||
* @brief Sampling frequency in Hz
|
||||
*/
|
||||
double samplerate;
|
||||
/**
|
||||
* @brief Storage capacity for the DAQ I/O.
|
||||
*/
|
||||
std::vector<double> buf;
|
||||
/**
|
||||
* @brief Whether the top / bottom part of the buffer are ready to be
|
||||
* @brief Whether the top part of the buffer is enqueued
|
||||
*/
|
||||
bool topenqueued = false;
|
||||
/**
|
||||
* @brief Whether the bottom part of the buffer is enqueued
|
||||
* enqueued
|
||||
*/
|
||||
bool topenqueued, botenqueued;
|
||||
bool botenqueued = false;
|
||||
|
||||
/**
|
||||
* @brief Counter for the total number of frames acquired / sent since the
|
||||
@ -117,6 +69,7 @@ public:
|
||||
0),
|
||||
buffer_mid_idx(nchannels * nFramesPerBlock) {
|
||||
assert(nchannels > 0);
|
||||
assert(nFramesPerBlock > 0);
|
||||
}
|
||||
};
|
||||
/**
|
||||
@ -152,3 +105,5 @@ public:
|
||||
|
||||
~OutBufHandler();
|
||||
};
|
||||
/** @} */
|
||||
/** @} */
|
46
cpp_src/device/uldaq/lasp_uldaq_common.cpp
Normal file
46
cpp_src/device/uldaq/lasp_uldaq_common.cpp
Normal file
@ -0,0 +1,46 @@
|
||||
/* #define DEBUGTRACE_ENABLED */
|
||||
#include "debugtrace.hpp"
|
||||
#include "lasp_config.h"
|
||||
|
||||
#if LASP_HAS_ULDAQ == 1
|
||||
#include "lasp_uldaq_common.h"
|
||||
#include "lasp_daq.h"
|
||||
|
||||
string getErrMsg(UlError err) {
|
||||
string errstr;
|
||||
errstr.reserve(ERR_MSG_LEN);
|
||||
char errmsg[ERR_MSG_LEN];
|
||||
errstr = "UlDaq API Error: ";
|
||||
ulGetErrMsg(err, errmsg);
|
||||
errstr += errmsg;
|
||||
return errstr;
|
||||
}
|
||||
void showErr(string errstr) {
|
||||
std::cerr << "\b\n**************** UlDAQ backend error **********\n";
|
||||
std::cerr << errstr << std::endl;
|
||||
std::cerr << "***********************************************\n\n";
|
||||
}
|
||||
void showErr(UlError err) {
|
||||
if (err != ERR_NO_ERROR)
|
||||
showErr(getErrMsg(err));
|
||||
}
|
||||
|
||||
|
||||
void throwOnPossibleUlException(UlError err) {
|
||||
if (err == ERR_NO_ERROR) {
|
||||
return;
|
||||
}
|
||||
string errstr = getErrMsg(err);
|
||||
showErr(errstr);
|
||||
Daq::StreamStatus::StreamError serr;
|
||||
if ((int)err == 18) {
|
||||
serr = Daq::StreamStatus::StreamError::inputXRun;
|
||||
} else if ((int)err == 19) {
|
||||
serr = Daq::StreamStatus::StreamError::outputXRun;
|
||||
} else {
|
||||
serr = Daq::StreamStatus::StreamError::driverError;
|
||||
}
|
||||
|
||||
throw Daq::StreamException(serr, errstr);
|
||||
}
|
||||
#endif
|
66
cpp_src/device/uldaq/lasp_uldaq_common.h
Normal file
66
cpp_src/device/uldaq/lasp_uldaq_common.h
Normal file
@ -0,0 +1,66 @@
|
||||
#pragma once
|
||||
#include <uldaq.h>
|
||||
#include <string>
|
||||
#include "lasp_deviceinfo.h"
|
||||
|
||||
/** \addtogroup device
|
||||
* @{
|
||||
* \addtogroup uldaq
|
||||
*/
|
||||
/**
|
||||
* @brief Throws an appropriate stream exception based on the UlError number.
|
||||
* The mapping is based on the error numbers as given in uldaq.h. There are a
|
||||
* log of errors definded here (109 in total). Except for some, we will map
|
||||
* most of them to a driver error.
|
||||
*
|
||||
* @param e The backend error code.
|
||||
*/
|
||||
void throwOnPossibleUlException(UlError err);
|
||||
|
||||
/**
|
||||
* @brief Return a string corresponding to the UlDaq API error
|
||||
*
|
||||
* @param err error code
|
||||
*
|
||||
* @return Error string
|
||||
*/
|
||||
string getErrMsg(UlError err);
|
||||
|
||||
/**
|
||||
* @brief Print error message to stderr
|
||||
*
|
||||
* @param errstr The string to print
|
||||
*/
|
||||
void showErr(UlError err);
|
||||
|
||||
/**
|
||||
* @brief Get a string representation of the error
|
||||
*
|
||||
* @param errstr
|
||||
*/
|
||||
void showErr(std::string errstr);
|
||||
|
||||
/**
|
||||
* @brief UlDaq-specific device information. Adds a copy of the underlying
|
||||
* DaqDeDaqDeviceDescriptor.
|
||||
*/
|
||||
class UlDaqDeviceInfo : public DeviceInfo {
|
||||
|
||||
public:
|
||||
DaqDeviceDescriptor _uldaqDescriptor;
|
||||
virtual std::unique_ptr<DeviceInfo> clone() const override {
|
||||
DEBUGTRACE_ENTER;
|
||||
return std::make_unique<UlDaqDeviceInfo>(*this);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief List of available sampling frequencies for DT9837A
|
||||
*/
|
||||
const std::vector<d> ULDAQ_SAMPLERATES = {8000, 10000, 11025, 16000, 20000,
|
||||
22050, 24000, 32000, 44056, 44100,
|
||||
47250, 48000, 50000, 50400, 51000};
|
||||
|
||||
|
||||
/** @} */
|
||||
/** @} */
|
212
cpp_src/device/uldaq/lasp_uldaq_impl.cpp
Normal file
212
cpp_src/device/uldaq/lasp_uldaq_impl.cpp
Normal file
@ -0,0 +1,212 @@
|
||||
/* #define DEBUGTRACE_ENABLED */
|
||||
#include "debugtrace.hpp"
|
||||
#include "lasp_config.h"
|
||||
|
||||
#if LASP_HAS_ULDAQ == 1
|
||||
#include "lasp_daqconfig.h"
|
||||
#include "lasp_uldaq.h"
|
||||
#include "lasp_uldaq_bufhandler.h"
|
||||
#include "lasp_uldaq_impl.h"
|
||||
|
||||
using namespace std::literals::chrono_literals;
|
||||
|
||||
DT9837A::~DT9837A() {
|
||||
DEBUGTRACE_ENTER;
|
||||
UlError err;
|
||||
if (isRunning()) {
|
||||
DEBUGTRACE_PRINT("Stop UlDAQ from destructor");
|
||||
stop();
|
||||
}
|
||||
|
||||
if (_handle) {
|
||||
DEBUGTRACE_PRINT("Disconnecting and releasing DaqDevice");
|
||||
/* err = ulDisconnectDaqDevice(_handle); */
|
||||
/* showErr(err); */
|
||||
err = ulReleaseDaqDevice(_handle);
|
||||
showErr(err);
|
||||
}
|
||||
}
|
||||
DT9837A::DT9837A(const UlDaqDeviceInfo &devinfo, const DaqConfiguration &config)
|
||||
: Daq(devinfo, config),
|
||||
_nFramesPerBlock(availableFramesPerBlock.at(framesPerBlockIndex)) {
|
||||
|
||||
const DaqDeviceDescriptor &descriptor = devinfo._uldaqDescriptor;
|
||||
DEBUGTRACE_PRINT(string("Device: ") + descriptor.productName);
|
||||
DEBUGTRACE_PRINT(string("Product id: ") + to_string(descriptor.productId));
|
||||
DEBUGTRACE_PRINT(string("Dev string: ") + descriptor.devString);
|
||||
DEBUGTRACE_PRINT(string("Unique id: ") + descriptor.uniqueId);
|
||||
|
||||
// get a handle to the DAQ device associated with the first descriptor
|
||||
_handle = ulCreateDaqDevice(descriptor);
|
||||
|
||||
if (_handle == 0) {
|
||||
throw rte("Unable to create a handle to the specified DAQ "
|
||||
"device. Is the device currently in use? Please make sure to set "
|
||||
"the DAQ configuration in duplex mode if simultaneous input and "
|
||||
"output is required.");
|
||||
}
|
||||
|
||||
UlError err = ulConnectDaqDevice(_handle);
|
||||
|
||||
if (err != ERR_NO_ERROR) {
|
||||
ulReleaseDaqDevice(_handle);
|
||||
_handle = 0;
|
||||
throw rte("Unable to connect to device: " + getErrMsg(err));
|
||||
}
|
||||
|
||||
/// Loop over input channels, set parameters
|
||||
for (us ch = 0; ch < 4; ch++) {
|
||||
|
||||
err = ulAISetConfigDbl(_handle, AI_CFG_CHAN_SENSOR_SENSITIVITY, ch, 1.0);
|
||||
showErr(err);
|
||||
if (err != ERR_NO_ERROR) {
|
||||
throw rte("Fatal: could normalize channel sensitivity");
|
||||
}
|
||||
|
||||
CouplingMode cm = inchannel_config.at(ch).ACCouplingMode ? CM_AC : CM_DC;
|
||||
err = ulAISetConfig(_handle, AI_CFG_CHAN_COUPLING_MODE, ch, cm);
|
||||
if (err != ERR_NO_ERROR) {
|
||||
showErr(err);
|
||||
throw rte("Fatal: could not set AC/DC coupling mode");
|
||||
}
|
||||
|
||||
IepeMode iepe =
|
||||
inchannel_config.at(ch).IEPEEnabled ? IEPE_ENABLED : IEPE_DISABLED;
|
||||
err = ulAISetConfig(_handle, AI_CFG_CHAN_IEPE_MODE, ch, iepe);
|
||||
if (err != ERR_NO_ERROR) {
|
||||
showErr(err);
|
||||
throw rte("Fatal: could not set IEPE mode");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool DT9837A::isRunning() const {
|
||||
DEBUGTRACE_ENTER;
|
||||
/* return _thread.joinable(); */
|
||||
StreamStatus status = _streamStatus;
|
||||
return status.isRunning;
|
||||
}
|
||||
void DT9837A::stop() {
|
||||
DEBUGTRACE_ENTER;
|
||||
StreamStatus status = _streamStatus;
|
||||
status.isRunning = true;
|
||||
_streamStatus = status;
|
||||
if (!isRunning()) {
|
||||
throw rte("No data acquisition running");
|
||||
}
|
||||
|
||||
// Stop the thread and join it
|
||||
_stopThread = true;
|
||||
assert(_thread.joinable());
|
||||
_thread.join();
|
||||
_stopThread = false;
|
||||
|
||||
// Update stream status
|
||||
status.isRunning = false;
|
||||
_streamStatus = status;
|
||||
}
|
||||
|
||||
void DT9837A::start(InDaqCallback inCallback, OutDaqCallback outCallback) {
|
||||
DEBUGTRACE_ENTER;
|
||||
if (isRunning()) {
|
||||
throw rte("DAQ is already running");
|
||||
}
|
||||
if (neninchannels() > 0) {
|
||||
if (!inCallback)
|
||||
throw rte("DAQ requires a callback for input data");
|
||||
}
|
||||
if (nenoutchannels() > 0) {
|
||||
if (!outCallback)
|
||||
throw rte("DAQ requires a callback for output data");
|
||||
}
|
||||
assert(neninchannels() + nenoutchannels() > 0);
|
||||
_thread = std::thread(&DT9837A::threadFcn, this, inCallback, outCallback);
|
||||
|
||||
}
|
||||
|
||||
void DT9837A::threadFcn(InDaqCallback inCallback, OutDaqCallback outCallback) {
|
||||
|
||||
DEBUGTRACE_ENTER;
|
||||
|
||||
try {
|
||||
|
||||
std::unique_ptr<OutBufHandler> obh;
|
||||
std::unique_ptr<InBufHandler> ibh;
|
||||
|
||||
StreamStatus status = _streamStatus;
|
||||
status.isRunning = true;
|
||||
_streamStatus = status;
|
||||
|
||||
if (nenoutchannels() > 0) {
|
||||
assert(outCallback);
|
||||
obh = std::make_unique<OutBufHandler>(*this, outCallback);
|
||||
}
|
||||
if (neninchannels() > 0) {
|
||||
assert(inCallback);
|
||||
ibh = std::make_unique<InBufHandler>(*this, inCallback);
|
||||
}
|
||||
if (obh)
|
||||
obh->start();
|
||||
if (ibh)
|
||||
ibh->start();
|
||||
|
||||
const double sleeptime_s =
|
||||
static_cast<double>(_nFramesPerBlock) / (16 * samplerate());
|
||||
const us sleeptime_us = static_cast<us>(sleeptime_s * 1e6);
|
||||
|
||||
while (!_stopThread) {
|
||||
if (ibh) {
|
||||
if (!(*ibh)()) {
|
||||
_stopThread = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (obh) {
|
||||
if (!(*obh)()) {
|
||||
_stopThread = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(sleeptime_us));
|
||||
}
|
||||
|
||||
/// Update stream status that we are not running anymore
|
||||
status.isRunning = false;
|
||||
_streamStatus = status;
|
||||
_stopThread = false;
|
||||
|
||||
} catch (StreamException &e) {
|
||||
|
||||
StreamStatus status = _streamStatus;
|
||||
// Copy over error type
|
||||
status.errorType = e.e;
|
||||
_streamStatus = status;
|
||||
|
||||
cerr << "\n******************\n";
|
||||
cerr << "Catched error in UlDAQ thread: " << e.what() << endl;
|
||||
cerr << "\n******************\n";
|
||||
}
|
||||
}
|
||||
|
||||
void DT9837A::sanityChecks() const {
|
||||
// Some sanity checks
|
||||
if (inchannel_config.size() != 4) {
|
||||
throw rte("Invalid length of enabled inChannels vector");
|
||||
}
|
||||
|
||||
if (outchannel_config.size() != 1) {
|
||||
throw rte("Invalid length of enabled outChannels vector");
|
||||
}
|
||||
|
||||
if (_nFramesPerBlock < 24 || _nFramesPerBlock > 8192) {
|
||||
throw rte("Unsensible number of samples per block chosen");
|
||||
}
|
||||
|
||||
if (samplerate() < ULDAQ_SAMPLERATES.at(0) ||
|
||||
samplerate() > ULDAQ_SAMPLERATES.at(ULDAQ_SAMPLERATES.size() - 1)) {
|
||||
throw rte("Invalid sample rate");
|
||||
}
|
||||
}
|
||||
|
||||
#endif // LASP_HAS_ULDAQ
|
110
cpp_src/device/uldaq/lasp_uldaq_impl.h
Normal file
110
cpp_src/device/uldaq/lasp_uldaq_impl.h
Normal file
@ -0,0 +1,110 @@
|
||||
#pragma once
|
||||
#include "debugtrace.hpp"
|
||||
#include "lasp_uldaq_common.h"
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include "lasp_daq.h"
|
||||
|
||||
using std::atomic;
|
||||
using std::cerr;
|
||||
using std::endl;
|
||||
using rte = std::runtime_error;
|
||||
|
||||
class InBufHandler;
|
||||
class OutBufHandler;
|
||||
|
||||
/** \addtogroup device
|
||||
* @{
|
||||
* \addtogroup uldaq
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Data translation DT9837A Daq device.
|
||||
*/
|
||||
class DT9837A : public Daq {
|
||||
|
||||
DaqDeviceHandle _handle = 0;
|
||||
std::mutex _daqmutex;
|
||||
|
||||
/**
|
||||
* @brief The thread that is doing I/O with UlDaq
|
||||
*/
|
||||
std::thread _thread;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Flag indicating the thread to stop processing.
|
||||
*/
|
||||
atomic<bool> _stopThread{false};
|
||||
/**
|
||||
* @brief Storage for exchanging information on the stream
|
||||
*/
|
||||
atomic<StreamStatus> _streamStatus;
|
||||
|
||||
const us _nFramesPerBlock;
|
||||
|
||||
/**
|
||||
* @brief The function that is running in a thread
|
||||
*
|
||||
* @param inCallback
|
||||
* @param outcallback
|
||||
*/
|
||||
void threadFcn(InDaqCallback inCallback, OutDaqCallback outcallback);
|
||||
|
||||
/**
|
||||
* @brief Obtain a handle to the underlying device
|
||||
*
|
||||
* @return Handle
|
||||
*/
|
||||
DaqDeviceHandle getHandle() const { return _handle; }
|
||||
/**
|
||||
* @brief Perform several sanity checks
|
||||
*/
|
||||
void sanityChecks() const;
|
||||
public:
|
||||
|
||||
/**
|
||||
* @brief Create a DT9837A instance.
|
||||
*
|
||||
* @param devinfo DeviceInfo to connect to
|
||||
* @param config DaqConfiguration settings
|
||||
*/
|
||||
DT9837A(const UlDaqDeviceInfo &devinfo, const DaqConfiguration &config);
|
||||
|
||||
virtual ~DT9837A();
|
||||
|
||||
/**
|
||||
* @brief Returns true when the stream is running
|
||||
*
|
||||
* @return as above stated
|
||||
*/
|
||||
bool isRunning() const;
|
||||
|
||||
/**
|
||||
* @brief Stop the data-acquisition
|
||||
*/
|
||||
void stop() override final;
|
||||
|
||||
friend class InBufHandler;
|
||||
friend class OutBufHandler;
|
||||
|
||||
virtual void start(InDaqCallback inCallback,
|
||||
OutDaqCallback outCallback) override final;
|
||||
|
||||
/**
|
||||
* @brief Obtain copy of stream status (thread-safe function)
|
||||
*
|
||||
* @return StreamStatus object
|
||||
*/
|
||||
virtual StreamStatus getStreamStatus() const override {
|
||||
return _streamStatus;
|
||||
}
|
||||
};
|
||||
|
||||
/** @} */
|
||||
/** @} */
|
@ -16,6 +16,7 @@ set(lasp_dsp_files
|
||||
lasp_threadedindatahandler.cpp
|
||||
lasp_ppm.cpp
|
||||
lasp_clip.cpp
|
||||
lasp_freqsmooth.cpp
|
||||
)
|
||||
|
||||
|
@ -2,12 +2,9 @@
|
||||
#include "lasp_avpowerspectra.h"
|
||||
#include "debugtrace.hpp"
|
||||
#include "lasp_mathtypes.h"
|
||||
#include <cmath>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
|
||||
using rte = std::runtime_error;
|
||||
using std::cerr;
|
||||
using std::endl;
|
||||
|
||||
PowerSpectra::PowerSpectra(const us nfft, const Window::WindowType w)
|
||||
: PowerSpectra(Window::create(w, nfft)) {}
|
@ -3,8 +3,6 @@
|
||||
#include "lasp_mathtypes.h"
|
||||
#include "lasp_timebuffer.h"
|
||||
#include "lasp_window.h"
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
/** \defgroup dsp Digital Signal Processing utilities
|
||||
* These are classes and functions used for processing raw signal data, to
|
@ -2,7 +2,6 @@
|
||||
#include "lasp_biquadbank.h"
|
||||
#include "debugtrace.hpp"
|
||||
#include "lasp_thread.h"
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
|
||||
using std::cerr;
|
||||
@ -44,19 +43,21 @@ SeriesBiquad::SeriesBiquad(const vd &filter_coefs) {
|
||||
|
||||
SeriesBiquad SeriesBiquad::firstOrderHighPass(const d fs, const d cuton_Hz) {
|
||||
|
||||
if(fs <= 0) {
|
||||
if (fs <= 0) {
|
||||
throw rte("Invalid sampling frequency: " + std::to_string(fs) + " [Hz]");
|
||||
}
|
||||
if(cuton_Hz <= 0) {
|
||||
if (cuton_Hz <= 0) {
|
||||
throw rte("Invalid cuton frequency: " + std::to_string(cuton_Hz) + " [Hz]");
|
||||
}
|
||||
if(cuton_Hz >= 0.98*fs/2) {
|
||||
throw rte("Invalid cuton frequency. We limit this to 0.98* fs / 2. Given value" + std::to_string(cuton_Hz) + " [Hz]");
|
||||
if (cuton_Hz >= 0.98 * fs / 2) {
|
||||
throw rte(
|
||||
"Invalid cuton frequency. We limit this to 0.98* fs / 2. Given value" +
|
||||
std::to_string(cuton_Hz) + " [Hz]");
|
||||
}
|
||||
|
||||
const d tau = 1/(2*arma::datum::pi*cuton_Hz);
|
||||
const d facnum = 2*fs*tau/(1+2*fs*tau);
|
||||
const d facden = (1-2*fs*tau)/(1+2*fs*tau);
|
||||
const d tau = 1 / (2 * arma::datum::pi * cuton_Hz);
|
||||
const d facnum = 2 * fs * tau / (1 + 2 * fs * tau);
|
||||
const d facden = (1 - 2 * fs * tau) / (1 + 2 * fs * tau);
|
||||
|
||||
vd coefs(6);
|
||||
// b0
|
||||
@ -76,10 +77,8 @@ SeriesBiquad SeriesBiquad::firstOrderHighPass(const d fs, const d cuton_Hz) {
|
||||
coefs(5) = 0;
|
||||
|
||||
return SeriesBiquad(coefs);
|
||||
|
||||
}
|
||||
|
||||
|
||||
std::unique_ptr<Filter> SeriesBiquad::clone() const {
|
||||
// sos.as_col() concatenates all columns, exactly what we want.
|
||||
return std::make_unique<SeriesBiquad>(sos.as_col());
|
||||
@ -124,7 +123,6 @@ BiquadBank::BiquadBank(const dmat &filters, const vd *gains) {
|
||||
* for use.
|
||||
*/
|
||||
lock lck(_mtx);
|
||||
getPool();
|
||||
|
||||
for (us i = 0; i < filters.n_cols; i++) {
|
||||
_filters.emplace_back(filters.col(i));
|
||||
@ -153,10 +151,9 @@ void BiquadBank::filter(vd &inout) {
|
||||
std::vector<std::future<vd>> futs;
|
||||
|
||||
#if 1
|
||||
auto &pool = getPool();
|
||||
vd inout_cpy = inout;
|
||||
for (us i = 0; i < _filters.size(); i++) {
|
||||
futs.emplace_back(pool.submit(
|
||||
futs.emplace_back(_pool.submit(
|
||||
[&](vd inout, us i) {
|
||||
_filters[i].filter(inout);
|
||||
return inout;
|
@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
#include "lasp_filter.h"
|
||||
#include "lasp_thread.h"
|
||||
|
||||
/**
|
||||
* \addtogroup dsp
|
||||
@ -60,6 +61,7 @@ public:
|
||||
class BiquadBank : public Filter {
|
||||
std::vector<SeriesBiquad> _filters;
|
||||
vd _gains;
|
||||
GlobalThreadPool _pool;
|
||||
mutable std::mutex _mtx;
|
||||
|
||||
public:
|
@ -1,6 +1,8 @@
|
||||
/* #define DEBUGTRACE_ENABLED */
|
||||
#include "debugtrace.hpp"
|
||||
#include "lasp_clip.h"
|
||||
#include "lasp_daqdata.h"
|
||||
#include "lasp_daq.h"
|
||||
#include <mutex>
|
||||
|
||||
using std::cerr;
|
||||
@ -9,13 +11,14 @@ using std::endl;
|
||||
using Lck = std::scoped_lock<std::mutex>;
|
||||
using rte = std::runtime_error;
|
||||
|
||||
ClipHandler::ClipHandler(StreamMgr &mgr)
|
||||
ClipHandler::ClipHandler(SmgrHandle mgr)
|
||||
: ThreadedInDataHandler(mgr){
|
||||
|
||||
DEBUGTRACE_ENTER;
|
||||
startThread();
|
||||
}
|
||||
|
||||
bool ClipHandler::inCallback_threaded(const DaqData &d) {
|
||||
void ClipHandler::inCallback(const DaqData &d) {
|
||||
|
||||
DEBUGTRACE_ENTER;
|
||||
Lck lck(_mtx);
|
||||
@ -49,7 +52,6 @@ bool ClipHandler::inCallback_threaded(const DaqData &d) {
|
||||
_clip_time(i) += _dt;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
arma::uvec ClipHandler::getCurrentValue() const {
|
||||
@ -89,6 +91,5 @@ void ClipHandler::reset(const Daq *daq) {
|
||||
|
||||
ClipHandler::~ClipHandler() {
|
||||
DEBUGTRACE_ENTER;
|
||||
Lck lck(_mtx);
|
||||
stop();
|
||||
stopThread();
|
||||
}
|
@ -21,7 +21,7 @@
|
||||
/**
|
||||
* @brief Clipping detector (Clip). Detects when a signal overdrives the input
|
||||
* */
|
||||
class ClipHandler: public ThreadedInDataHandler {
|
||||
class ClipHandler: public ThreadedInDataHandler<ClipHandler> {
|
||||
|
||||
/**
|
||||
* @brief Assuming full scale of a signal is +/- 1.0. If a value is found
|
||||
@ -58,7 +58,7 @@ class ClipHandler: public ThreadedInDataHandler {
|
||||
*
|
||||
* @param mgr Stream Mgr to operate on
|
||||
*/
|
||||
ClipHandler(StreamMgr& mgr);
|
||||
ClipHandler(SmgrHandle mgr);
|
||||
~ClipHandler();
|
||||
|
||||
/**
|
||||
@ -68,8 +68,8 @@ class ClipHandler: public ThreadedInDataHandler {
|
||||
*/
|
||||
arma::uvec getCurrentValue() const;
|
||||
|
||||
bool inCallback_threaded(const DaqData& ) override final;
|
||||
void reset(const Daq*) override final;
|
||||
void inCallback(const DaqData& );
|
||||
void reset(const Daq*);
|
||||
|
||||
};
|
||||
|
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);
|
||||
|
||||
/** @} */
|
@ -1,6 +1,8 @@
|
||||
/* #define DEBUGTRACE_ENABLED */
|
||||
#include "debugtrace.hpp"
|
||||
#include "lasp_ppm.h"
|
||||
#include "lasp_daqdata.h"
|
||||
#include "lasp_daq.h"
|
||||
#include <mutex>
|
||||
|
||||
using std::cerr;
|
||||
@ -9,20 +11,20 @@ using std::endl;
|
||||
using Lck = std::scoped_lock<std::mutex>;
|
||||
using rte = std::runtime_error;
|
||||
|
||||
PPMHandler::PPMHandler(StreamMgr &mgr, const d decay_dBps)
|
||||
: ThreadedInDataHandler(mgr), _decay_dBps(decay_dBps) {
|
||||
PPMHandler::PPMHandler(SmgrHandle mgr, const d decay_dBps)
|
||||
: ThreadedInDataHandler<PPMHandler>(mgr), _decay_dBps(decay_dBps) {
|
||||
|
||||
DEBUGTRACE_ENTER;
|
||||
startThread();
|
||||
}
|
||||
|
||||
bool PPMHandler::inCallback_threaded(const DaqData &d) {
|
||||
void PPMHandler::inCallback(const DaqData &d) {
|
||||
|
||||
DEBUGTRACE_ENTER;
|
||||
Lck lck(_mtx);
|
||||
|
||||
dmat data = d.toFloat();
|
||||
|
||||
|
||||
const us nchannels = d.nchannels;
|
||||
assert(data.n_cols == nchannels);
|
||||
|
||||
@ -62,12 +64,11 @@ bool PPMHandler::inCallback_threaded(const DaqData &d) {
|
||||
_cur_max(i) *= _alpha;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::tuple<vd, arma::uvec> PPMHandler::getCurrentValue() const {
|
||||
|
||||
DEBUGTRACE_ENTER;
|
||||
/* DEBUGTRACE_ENTER; */
|
||||
Lck lck(_mtx);
|
||||
|
||||
arma::uvec clips(_clip_time.size(), arma::fill::zeros);
|
||||
@ -83,9 +84,11 @@ void PPMHandler::reset(const Daq *daq) {
|
||||
|
||||
if (daq) {
|
||||
|
||||
DEBUGTRACE_PRINT("New daq found");
|
||||
_cur_max.fill(1e-80);
|
||||
|
||||
const us nchannels = daq->neninchannels();
|
||||
DEBUGTRACE_PRINT(nchannels);
|
||||
_max_range.resize(nchannels);
|
||||
|
||||
dvec ranges = daq->inputRangeForEnabledChannels();
|
||||
@ -107,6 +110,5 @@ void PPMHandler::reset(const Daq *daq) {
|
||||
|
||||
PPMHandler::~PPMHandler() {
|
||||
DEBUGTRACE_ENTER;
|
||||
Lck lck(_mtx);
|
||||
stop();
|
||||
stopThread();
|
||||
}
|
@ -4,7 +4,6 @@
|
||||
//
|
||||
// Description: Peak Programme Meter
|
||||
#pragma once
|
||||
#include <memory>
|
||||
#include "lasp_filter.h"
|
||||
#include "lasp_mathtypes.h"
|
||||
#include "lasp_threadedindatahandler.h"
|
||||
@ -23,7 +22,7 @@
|
||||
* with a certain amount of dB/s. If a new peak is found, it goes up again.
|
||||
* Also detects clipping.
|
||||
* */
|
||||
class PPMHandler: public ThreadedInDataHandler {
|
||||
class PPMHandler : public ThreadedInDataHandler<PPMHandler> {
|
||||
|
||||
/**
|
||||
* @brief Assuming full scale of a signal is +/- 1.0. If a value is found
|
||||
@ -69,11 +68,11 @@ class PPMHandler: public ThreadedInDataHandler {
|
||||
/**
|
||||
* @brief Constructs Peak Programme Meter
|
||||
*
|
||||
* @param mgr Stream Mgr to operate on
|
||||
* @param mgr Stream Mgr to install callbacks for
|
||||
* @param decay_dBps The level decay in units dB/s, after a peak has been
|
||||
* hit.
|
||||
*/
|
||||
PPMHandler(StreamMgr& mgr,const d decay_dBps = 20.0);
|
||||
PPMHandler(SmgrHandle mgr,const d decay_dBps = 20.0);
|
||||
~PPMHandler();
|
||||
|
||||
/**
|
||||
@ -91,8 +90,8 @@ class PPMHandler: public ThreadedInDataHandler {
|
||||
*
|
||||
* @return true when stream should continue.
|
||||
*/
|
||||
bool inCallback_threaded(const DaqData& d) override final;
|
||||
void reset(const Daq*) override final;
|
||||
void inCallback(const DaqData& d);
|
||||
void reset(const Daq*);
|
||||
|
||||
};
|
||||
|
@ -1,5 +1,7 @@
|
||||
/* #define DEBUGTRACE_ENABLED */
|
||||
#include "lasp_rtaps.h"
|
||||
#include "lasp_daqdata.h"
|
||||
#include "lasp_daq.h"
|
||||
#include "debugtrace.hpp"
|
||||
#include <mutex>
|
||||
|
||||
@ -7,7 +9,7 @@ using std::cerr;
|
||||
using std::endl;
|
||||
using Lck = std::scoped_lock<std::mutex>;
|
||||
|
||||
RtAps::RtAps(StreamMgr &mgr, const Filter *freqWeightingFilter,
|
||||
RtAps::RtAps(SmgrHandle mgr, const Filter *freqWeightingFilter,
|
||||
const us nfft,
|
||||
const Window::WindowType w,
|
||||
const d overlap_percentage, const d time_constant)
|
||||
@ -18,12 +20,12 @@ RtAps::RtAps(StreamMgr &mgr, const Filter *freqWeightingFilter,
|
||||
_filterPrototype = freqWeightingFilter->clone();
|
||||
}
|
||||
|
||||
startThread();
|
||||
}
|
||||
RtAps::~RtAps() {
|
||||
Lck lck(_ps_mtx);
|
||||
stop();
|
||||
stopThread();
|
||||
}
|
||||
bool RtAps::inCallback_threaded(const DaqData &data) {
|
||||
void RtAps::inCallback(const DaqData &data) {
|
||||
|
||||
DEBUGTRACE_ENTER;
|
||||
|
||||
@ -33,9 +35,9 @@ bool RtAps::inCallback_threaded(const DaqData &data) {
|
||||
const us nchannels = fltdata.n_cols;
|
||||
if(nchannels != _sens.size()) {
|
||||
cerr << "**** Error: sensitivity size does not match! *****" << endl;
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
fltdata.each_row() %= _sens.as_row();
|
||||
fltdata.each_row() /= _sens.as_row();
|
||||
|
||||
if (_filterPrototype) {
|
||||
|
||||
@ -61,7 +63,6 @@ bool RtAps::inCallback_threaded(const DaqData &data) {
|
||||
|
||||
_ps.compute(fltdata);
|
||||
|
||||
return true;
|
||||
}
|
||||
void RtAps::reset(const Daq *daq) {
|
||||
|
@ -23,7 +23,7 @@
|
||||
* @brief Real time spectral estimator using Welch method of spectral
|
||||
* estimation.
|
||||
*/
|
||||
class RtAps : public ThreadedInDataHandler {
|
||||
class RtAps : public ThreadedInDataHandler<RtAps> {
|
||||
|
||||
std::unique_ptr<Filter> _filterPrototype;
|
||||
std::vector<std::unique_ptr<Filter>> _freqWeightingFilters;
|
||||
@ -49,7 +49,7 @@ public:
|
||||
*
|
||||
* For all other arguments, see constructor of AvPowerSpectra
|
||||
*/
|
||||
RtAps(StreamMgr &mgr, const Filter *freqWeightingFilter, const us nfft = 2048,
|
||||
RtAps(SmgrHandle mgr, const Filter *freqWeightingFilter, const us nfft = 2048,
|
||||
const Window::WindowType w = Window::WindowType::Hann,
|
||||
const d overlap_percentage = 50., const d time_constant = -1);
|
||||
~RtAps();
|
||||
@ -69,8 +69,8 @@ public:
|
||||
*
|
||||
* @return true if stream should continue.
|
||||
*/
|
||||
bool inCallback_threaded(const DaqData & d) override final;
|
||||
void reset(const Daq *) override final;
|
||||
void inCallback(const DaqData & d);
|
||||
void reset(const Daq *);
|
||||
};
|
||||
|
||||
/** @} */
|
@ -1,5 +1,7 @@
|
||||
/* #define DEBUGTRACE_ENABLED */
|
||||
#include "debugtrace.hpp"
|
||||
#include "lasp_daqdata.h"
|
||||
#include "lasp_daq.h"
|
||||
#include "lasp_rtsignalviewer.h"
|
||||
#include <algorithm>
|
||||
#include <mutex>
|
||||
@ -9,7 +11,7 @@ using std::endl;
|
||||
using Lck = std::scoped_lock<std::mutex>;
|
||||
using rte = std::runtime_error;
|
||||
|
||||
RtSignalViewer::RtSignalViewer(StreamMgr &mgr, const d approx_time_hist,
|
||||
RtSignalViewer::RtSignalViewer(SmgrHandle mgr, const d approx_time_hist,
|
||||
const us resolution, const us channel)
|
||||
: ThreadedInDataHandler(mgr), _approx_time_hist(approx_time_hist),
|
||||
_resolution(resolution), _channel(channel) {
|
||||
@ -22,9 +24,10 @@ RtSignalViewer::RtSignalViewer(StreamMgr &mgr, const d approx_time_hist,
|
||||
if (resolution <= 1) {
|
||||
throw rte("Invalid resolution. Should be > 1");
|
||||
}
|
||||
startThread();
|
||||
}
|
||||
|
||||
bool RtSignalViewer::inCallback_threaded(const DaqData &data) {
|
||||
void RtSignalViewer::inCallback(const DaqData &data) {
|
||||
|
||||
DEBUGTRACE_ENTER;
|
||||
|
||||
@ -49,13 +52,10 @@ bool RtSignalViewer::inCallback_threaded(const DaqData &data) {
|
||||
_dat(_resolution-1, 1) = newmin;
|
||||
_dat(_resolution-1, 2) = newmax;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
RtSignalViewer::~RtSignalViewer() {
|
||||
Lck lck(_sv_mtx);
|
||||
stop();
|
||||
stopThread();
|
||||
}
|
||||
void RtSignalViewer::reset(const Daq *daq) {
|
||||
|
@ -4,12 +4,9 @@
|
||||
//
|
||||
// Description: Real Time Signal Viewer.
|
||||
#pragma once
|
||||
#include "lasp_avpowerspectra.h"
|
||||
#include "lasp_filter.h"
|
||||
#include "lasp_mathtypes.h"
|
||||
#include "lasp_threadedindatahandler.h"
|
||||
#include "lasp_timebuffer.h"
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
/**
|
||||
@ -24,7 +21,7 @@
|
||||
* @brief Real time signal viewer. Shows envelope of the signal based on amount
|
||||
* of history shown.
|
||||
*/
|
||||
class RtSignalViewer : public ThreadedInDataHandler {
|
||||
class RtSignalViewer : public ThreadedInDataHandler<RtSignalViewer> {
|
||||
|
||||
/**
|
||||
* @brief Storage for sensitivity values
|
||||
@ -71,7 +68,7 @@ public:
|
||||
* @param resolution Number of time points
|
||||
* @param channel The channel number
|
||||
*/
|
||||
RtSignalViewer(StreamMgr &mgr, const d approx_time_hist, const us resolution,
|
||||
RtSignalViewer(SmgrHandle mgr, const d approx_time_hist, const us resolution,
|
||||
const us channel);
|
||||
|
||||
~RtSignalViewer();
|
||||
@ -85,8 +82,8 @@ public:
|
||||
*/
|
||||
dmat getCurrentValue() const;
|
||||
|
||||
bool inCallback_threaded(const DaqData &) override final;
|
||||
void reset(const Daq *) override final;
|
||||
void inCallback(const DaqData &);
|
||||
void reset(const Daq *);
|
||||
};
|
||||
|
||||
/** @} */
|
@ -10,31 +10,62 @@ using rte = std::runtime_error;
|
||||
|
||||
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) {
|
||||
|
||||
DEBUGTRACE_ENTER;
|
||||
mutexlock lck(_mtx);
|
||||
slock lck(_mtx);
|
||||
|
||||
DEBUGTRACE_PRINT(nframes);
|
||||
vd signal(nframes, arma::fill::value(_dc_offset));
|
||||
|
||||
if (!_muted) {
|
||||
vd signal_dynamic = _level_linear * genSignalUnscaled(nframes);
|
||||
|
||||
// Filter signal
|
||||
for (auto f : _filters) {
|
||||
assert(f.second);
|
||||
f.second->filter(signal_dynamic);
|
||||
}
|
||||
signal += signal_dynamic;
|
||||
|
||||
// Check whether we are running / not for signal interruption.
|
||||
bool activated = false;
|
||||
if (_interrupt_period_s < 0) {
|
||||
activated = true;
|
||||
} else {
|
||||
if (_interruption_frame_count < _interrupt_period_s*_fs) {
|
||||
activated = true;
|
||||
}
|
||||
_interruption_frame_count += nframes;
|
||||
|
||||
if (_interruption_frame_count >= 2 * _interrupt_period_s*_fs) {
|
||||
_interruption_frame_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (activated) {
|
||||
signal += signal_dynamic;
|
||||
}
|
||||
} // end if(!_muted)
|
||||
|
||||
return signal;
|
||||
}
|
||||
void Siggen::setInterruptPeriod(const d newPeriod) {
|
||||
slock lck(_mtx);
|
||||
if (newPeriod == 0) {
|
||||
throw rte("Interruption period cannot be 0");
|
||||
}
|
||||
if (newPeriod < 0) {
|
||||
_interrupt_period_s = -1;
|
||||
} else {
|
||||
_interrupt_period_s = newPeriod;
|
||||
}
|
||||
}
|
||||
void Siggen::setFilter(const std::string &name,
|
||||
std::shared_ptr<Filter> filter) {
|
||||
DEBUGTRACE_ENTER;
|
||||
mutexlock lck(_mtx);
|
||||
slock lck(_mtx);
|
||||
if (filter) {
|
||||
_filters[name] = filter;
|
||||
} else if (_filters.find(name) != _filters.end()) {
|
||||
@ -43,21 +74,22 @@ void Siggen::setFilter(const std::string &name,
|
||||
}
|
||||
void Siggen::setDCOffset(const d offset) {
|
||||
DEBUGTRACE_ENTER;
|
||||
mutexlock lck(_mtx);
|
||||
slock lck(_mtx);
|
||||
_dc_offset = offset;
|
||||
}
|
||||
void Siggen::setLevel(const d level, bool dB) {
|
||||
DEBUGTRACE_ENTER;
|
||||
mutexlock lck(_mtx);
|
||||
slock lck(_mtx);
|
||||
_level_linear = dB ? level_amp(level) : level;
|
||||
}
|
||||
void Siggen::reset(const d newFs) {
|
||||
DEBUGTRACE_ENTER;
|
||||
mutexlock lck(_mtx);
|
||||
slock lck(_mtx);
|
||||
_fs = newFs;
|
||||
for (auto &f : _filters) {
|
||||
assert(f.second);
|
||||
f.second->reset();
|
||||
}
|
||||
_interruption_frame_count = 0;
|
||||
resetImpl();
|
||||
}
|
@ -27,8 +27,15 @@ private:
|
||||
bool _muted = false;
|
||||
|
||||
protected:
|
||||
std::mutex _mtx;
|
||||
mutable std::recursive_mutex _mtx;
|
||||
d _fs = 0;
|
||||
/**
|
||||
* @brief Interuption of period the signal. If set, the signal will be
|
||||
* periodically turned on / off. This is useful for measuring reverberation
|
||||
* times using the "interrupted noise method".
|
||||
*/
|
||||
int _interrupt_period_s = -1;
|
||||
int _interruption_frame_count = 0;
|
||||
|
||||
virtual void resetImpl() = 0;
|
||||
virtual vd genSignalUnscaled(const us nframes) = 0;
|
||||
@ -36,6 +43,15 @@ protected:
|
||||
public:
|
||||
virtual ~Siggen() = default;
|
||||
|
||||
/**
|
||||
* @brief Set the interruption period for interrupted signals.
|
||||
*
|
||||
* @param newPeriod If < 0, the interruption is disabled. If > 0, an
|
||||
* approximate interruption number of *buffers* is computed, rounded upwards
|
||||
* for which the signal generator is turned off.
|
||||
*/
|
||||
void setInterruptPeriod(const d newPeriod);
|
||||
|
||||
/**
|
||||
* @brief Set a filter on the signal. For example to EQ the signal, or
|
||||
* otherwise to shape the spectrum. Filters are stored in a map, and
|
||||
@ -59,7 +75,7 @@ public:
|
||||
*
|
||||
* @param mute if tre
|
||||
*/
|
||||
void setMute(bool mute = true) { _muted = mute; }
|
||||
void setMute(bool mute = true) { _muted = mute; _interruption_frame_count=0; }
|
||||
|
||||
/**
|
||||
* @brief Set the level of the signal generator
|
@ -7,11 +7,14 @@
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
/* #define DEBUGTRACE_ENABLED */
|
||||
#include "lasp_siggen_impl.h"
|
||||
#include "debugtrace.hpp"
|
||||
#include "lasp_mathtypes.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include "debugtrace.hpp"
|
||||
#include "lasp_mathtypes.h"
|
||||
|
||||
using rte = std::runtime_error;
|
||||
using slock = std::scoped_lock<std::recursive_mutex>;
|
||||
|
||||
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) {
|
||||
/* DEBUGTRACE_ENTER; */
|
||||
slock lck(_mtx);
|
||||
const d pi = arma::datum::pi;
|
||||
vd phase_vec =
|
||||
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 res(nframes);
|
||||
slock lck(_mtx);
|
||||
if (_signal.size() == 0) {
|
||||
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() {
|
||||
|
||||
DEBUGTRACE_ENTER;
|
||||
slock lck(_mtx);
|
||||
|
||||
_cur_pos = 0;
|
||||
|
||||
@ -166,7 +170,6 @@ void Sweep::resetImpl() {
|
||||
/* dVARTRACE(15, phase); */
|
||||
}
|
||||
} else if (flags & LogSweep) {
|
||||
|
||||
DEBUGTRACE_PRINT("Log sweep");
|
||||
if (forward_sweep || backward_sweep) {
|
||||
/* Forward or backward sweep */
|
||||
@ -194,7 +197,6 @@ void Sweep::resetImpl() {
|
||||
phase += 2 * number_pi * Dt * fn;
|
||||
}
|
||||
} else {
|
||||
|
||||
DEBUGTRACE_PRINT("Continuous sweep");
|
||||
|
||||
const us Nf = Ns / 2;
|
||||
@ -249,8 +251,7 @@ void Sweep::resetImpl() {
|
||||
/* dbgassert(fn >= 0, "BUG"); */
|
||||
|
||||
phase += 2 * number_pi * Dt * fn;
|
||||
while (phase > 2 * number_pi)
|
||||
phase -= 2 * number_pi;
|
||||
while (phase > 2 * number_pi) phase -= 2 * number_pi;
|
||||
/* dVARTRACE(17, phase); */
|
||||
}
|
||||
/* This should be a very small number!! */
|
||||
@ -260,6 +261,5 @@ void Sweep::resetImpl() {
|
||||
else {
|
||||
// Either log or linear sweep had to be given as flags.
|
||||
assert(false);
|
||||
|
||||
}
|
||||
}
|
@ -18,8 +18,8 @@ class Noise : public Siggen {
|
||||
d level_linear;
|
||||
virtual vd genSignalUnscaled(const us nframes) override;
|
||||
void resetImpl() override;
|
||||
public:
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a noise generator. If no filter is used, the output will
|
||||
* be white noise. By default, the output will be standard deviation = 1
|
||||
@ -28,7 +28,6 @@ class Noise : public Siggen {
|
||||
*/
|
||||
Noise();
|
||||
~Noise() = default;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
@ -37,13 +36,12 @@ class Noise : public Siggen {
|
||||
class Sine : public Siggen {
|
||||
d phase = 0;
|
||||
d omg;
|
||||
protected:
|
||||
|
||||
void resetImpl() override final { phase=0; }
|
||||
protected:
|
||||
void resetImpl() override final { phase = 0; }
|
||||
virtual vd genSignalUnscaled(const us nframes) override final;
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* @brief Create a sine wave generator
|
||||
*
|
||||
@ -51,7 +49,7 @@ class Sine : public Siggen {
|
||||
*/
|
||||
Sine(const d freq_Hz);
|
||||
~Sine() = default;
|
||||
void setFreq(const d newFreq) { omg = 2*arma::datum::pi*newFreq; } ;
|
||||
void setFreq(const d newFreq) { omg = 2 * arma::datum::pi * newFreq; };
|
||||
};
|
||||
|
||||
/**
|
||||
@ -87,7 +85,6 @@ class Sweep : public Periodic {
|
||||
void resetImpl() override;
|
||||
|
||||
public:
|
||||
|
||||
static constexpr int ForwardSweep = 1 << 0;
|
||||
static constexpr int BackwardSweep = 1 << 1;
|
||||
static constexpr int LinearSweep = 1 << 2;
|
||||
@ -103,11 +100,11 @@ class Sweep : public Periodic {
|
||||
* avoid temporal aliasing in case of measuring impulse responses.
|
||||
* @param[in] sweep_flags: Sweep period [s]
|
||||
*/
|
||||
Sweep(const d fl, const d fu, const d Ts, const d Tq,
|
||||
const us sweep_flags);
|
||||
Sweep(const d fl, const d fu, const d Ts, const d Tq, const us sweep_flags);
|
||||
|
||||
~Sweep() = default;
|
||||
|
||||
|
||||
};
|
||||
/** @} */
|
||||
/** @} */
|
@ -37,9 +37,6 @@ SLM::SLM(const d fs, const d Lref, const us downsampling_fac, const d tau,
|
||||
DEBUGTRACE_ENTER;
|
||||
DEBUGTRACE_PRINT(_alpha);
|
||||
|
||||
// Make sure thread pool is running
|
||||
getPool();
|
||||
|
||||
if (Lref <= 0) {
|
||||
throw rte("Invalid reference level");
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
#include "lasp_biquadbank.h"
|
||||
#include "lasp_filter.h"
|
||||
#include "lasp_thread.h"
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
@ -14,6 +15,7 @@
|
||||
* channel. A channel is the result of a filtered signal
|
||||
*/
|
||||
class SLM {
|
||||
GlobalThreadPool _pool;
|
||||
/**
|
||||
* @brief A, C or Z weighting, depending on the pre-filter installed.
|
||||
*/
|
36
cpp_src/dsp/lasp_thread.cpp
Normal file
36
cpp_src/dsp/lasp_thread.cpp
Normal file
@ -0,0 +1,36 @@
|
||||
/* #define DEBUGTRACE_ENABLED */
|
||||
#include "lasp_thread.h"
|
||||
#include "BS_thread_pool.hpp"
|
||||
#include "debugtrace.hpp"
|
||||
#include <memory>
|
||||
|
||||
/**
|
||||
* @brief Store a global weak_ptr, that is used to create new shared pointers
|
||||
* if any other shared pointers are still alive. If not, we create a new
|
||||
* instance.
|
||||
*/
|
||||
std::weak_ptr<BS::thread_pool> _global_weak_pool;
|
||||
|
||||
/**
|
||||
* @brief Global mutex, used to restrict the pool creation to a single thread
|
||||
* at once.
|
||||
*/
|
||||
std::mutex _mtx;
|
||||
|
||||
using Lck = std::scoped_lock<std::mutex>;
|
||||
using rte = std::runtime_error;
|
||||
|
||||
GlobalThreadPool::GlobalThreadPool() {
|
||||
DEBUGTRACE_ENTER;
|
||||
Lck lck(_mtx);
|
||||
/// See if we can get it from the global ptr. If not, time to allocate it.
|
||||
_pool = _global_weak_pool.lock();
|
||||
if (!_pool) {
|
||||
_pool = std::make_shared<BS::thread_pool>();
|
||||
if (!_pool) {
|
||||
throw rte("Fatal: could not allocate thread pool!");
|
||||
}
|
||||
// Update global weak pointer
|
||||
_global_weak_pool = _pool;
|
||||
}
|
||||
}
|
41
cpp_src/dsp/lasp_thread.h
Normal file
41
cpp_src/dsp/lasp_thread.h
Normal file
@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
#include "BS_thread_pool.hpp"
|
||||
|
||||
/**
|
||||
* @brief Simple wrapper around BS::thread_pool that makes a BS::thread_pool a
|
||||
* singleton, such that a thread pool can be used around in the code, and
|
||||
* safely spawn threads also from other threads. Only wraps a submit() and
|
||||
* push_task for now.
|
||||
*/
|
||||
class GlobalThreadPool {
|
||||
|
||||
/**
|
||||
* @brief Shared access to the thread pool.
|
||||
*/
|
||||
std::shared_ptr<BS::thread_pool> _pool;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Instantiate handle to the thread pool.
|
||||
*/
|
||||
GlobalThreadPool();
|
||||
GlobalThreadPool(const GlobalThreadPool &) = default;
|
||||
GlobalThreadPool &operator=(const GlobalThreadPool &) = default;
|
||||
|
||||
/**
|
||||
* @brief Wrapper around BS::thread_pool::submit(...)
|
||||
*/
|
||||
template <
|
||||
typename F, typename... A,
|
||||
typename R = std::invoke_result_t<std::decay_t<F>, std::decay_t<A>...>>
|
||||
[[nodiscard]] std::future<R> submit(F &&task, A &&...args) {
|
||||
|
||||
return _pool->submit(task, args...);
|
||||
}
|
||||
/**
|
||||
* @brief Wrapper around BS::thread_pool::push_task(...)
|
||||
*/
|
||||
template <typename F, typename... A> void push_task(F &&task, A &&...args) {
|
||||
_pool->push_task(task, args...);
|
||||
}
|
||||
};
|
142
cpp_src/dsp/lasp_threadedindatahandler.cpp
Normal file
142
cpp_src/dsp/lasp_threadedindatahandler.cpp
Normal file
@ -0,0 +1,142 @@
|
||||
// #define DEBUGTRACE_ENABLED
|
||||
#include "lasp_threadedindatahandler.h"
|
||||
|
||||
#include <future>
|
||||
#include <optional>
|
||||
#include <queue>
|
||||
#include <thread>
|
||||
|
||||
#include "debugtrace.hpp"
|
||||
#include "lasp_daqdata.h"
|
||||
#include "lasp_thread.h"
|
||||
|
||||
using namespace std::literals::chrono_literals;
|
||||
using lck = std::scoped_lock<std::mutex>;
|
||||
using rte = std::runtime_error;
|
||||
using std::cerr;
|
||||
using std::endl;
|
||||
using std::placeholders::_1;
|
||||
|
||||
class SafeQueue {
|
||||
std::queue<DaqData> _queue;
|
||||
std::mutex _mtx;
|
||||
std::atomic<uint32_t> _contents{0};
|
||||
|
||||
public:
|
||||
void push(const DaqData &d) {
|
||||
DEBUGTRACE_ENTER;
|
||||
lck lock(_mtx);
|
||||
_queue.push(d);
|
||||
_contents++;
|
||||
assert(_contents == _queue.size());
|
||||
}
|
||||
DaqData pop() {
|
||||
DEBUGTRACE_ENTER;
|
||||
if (empty()) {
|
||||
throw rte("BUG: Pop on empty queue");
|
||||
}
|
||||
lck lock(_mtx);
|
||||
|
||||
/* DaqData d(std::move(_queue.front())); */
|
||||
DaqData d(_queue.front());
|
||||
_queue.pop();
|
||||
_contents--;
|
||||
assert(_contents == _queue.size());
|
||||
return d;
|
||||
}
|
||||
/**
|
||||
* @brief Empty implemented using atomic var, safes some mutex lock/unlock
|
||||
* cycles.
|
||||
*
|
||||
* @return true if queue is empty
|
||||
*/
|
||||
bool empty() const { return _contents == 0; }
|
||||
};
|
||||
|
||||
ThreadedInDataHandlerBase::ThreadedInDataHandlerBase(SmgrHandle mgr,
|
||||
InCallbackType cb,
|
||||
ResetCallbackType reset)
|
||||
: _queue(std::make_unique<SafeQueue>()),
|
||||
inCallback(cb),
|
||||
resetCallback(reset),
|
||||
_smgr(mgr) {
|
||||
DEBUGTRACE_ENTER;
|
||||
}
|
||||
void ThreadedInDataHandlerBase::startThread() {
|
||||
DEBUGTRACE_ENTER;
|
||||
if (_indatahandler) {
|
||||
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(
|
||||
const DaqData &daqdata) {
|
||||
DEBUGTRACE_ENTER;
|
||||
|
||||
// Early return in case object is under DESTRUCTION
|
||||
if (!_thread_allowed_to_run) return;
|
||||
|
||||
_queue->push(daqdata);
|
||||
if (!_thread_running) {
|
||||
DEBUGTRACE_PRINT("Pushing new thread in pool");
|
||||
_thread_running = true;
|
||||
_pool.push_task(&ThreadedInDataHandlerBase::threadFcn, this);
|
||||
}
|
||||
}
|
||||
|
||||
void ThreadedInDataHandlerBase::stopThread() {
|
||||
DEBUGTRACE_ENTER;
|
||||
if (!_indatahandler) {
|
||||
throw rte("BUG: ThreadedIndataHandler not running");
|
||||
}
|
||||
|
||||
// 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.
|
||||
while (_thread_running) {
|
||||
std::this_thread::sleep_for(10us);
|
||||
}
|
||||
DEBUGTRACE_PRINT("Thread stopped");
|
||||
// Kill the handler
|
||||
DEBUGTRACE_PRINT("Handler resetted");
|
||||
}
|
||||
|
||||
ThreadedInDataHandlerBase::~ThreadedInDataHandlerBase() {
|
||||
DEBUGTRACE_ENTER;
|
||||
if (_thread_allowed_to_run) {
|
||||
stopThread();
|
||||
cerr << "*** BUG: InDataHandlers have not been all stopped, while "
|
||||
"StreamMgr destructor is called. This is a misuse BUG."
|
||||
<< endl;
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
void ThreadedInDataHandlerBase::threadFcn() {
|
||||
DEBUGTRACE_ENTER;
|
||||
|
||||
while (!_queue->empty() && _thread_allowed_to_run) {
|
||||
// Call inCallback_threaded
|
||||
inCallback(_queue->pop());
|
||||
}
|
||||
_thread_running = false;
|
||||
}
|
123
cpp_src/dsp/lasp_threadedindatahandler.h
Normal file
123
cpp_src/dsp/lasp_threadedindatahandler.h
Normal file
@ -0,0 +1,123 @@
|
||||
#pragma once
|
||||
#include "debugtrace.hpp"
|
||||
#include "lasp_indatahandler.h"
|
||||
#include "lasp_thread.h"
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
using std::placeholders::_1;
|
||||
const us RINGBUFFER_SIZE = 1024;
|
||||
|
||||
/**
|
||||
* \addtogroup dsp
|
||||
* @{
|
||||
*
|
||||
* \defgroup rt Real time signal handlers
|
||||
* @{
|
||||
*/
|
||||
|
||||
class SafeQueue;
|
||||
/**
|
||||
* @brief Threaded in data handler base. Buffers inCallback data and calls a
|
||||
* callback with the same signature on a different thread. The main function of
|
||||
* this is to offload the thread that handles the stream, such that expensive
|
||||
* computations do not result in stream buffer xruns.
|
||||
*/
|
||||
class ThreadedInDataHandlerBase {
|
||||
/**
|
||||
* @brief The queue used to push elements to the handling thread.
|
||||
*/
|
||||
|
||||
std::unique_ptr<SafeQueue> _queue;
|
||||
|
||||
/**
|
||||
* @brief Function pointer that is called when new DaqData arrives.
|
||||
*/
|
||||
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();
|
||||
|
||||
|
||||
/**
|
||||
* @brief Pushes a copy of the daqdata to the thread queue and returns.
|
||||
* Adds a thread to handle the queue, whihc will call inCallback();
|
||||
*
|
||||
* @param daqdata the daq info to push
|
||||
*
|
||||
* @return true, to continue with sampling.
|
||||
*/
|
||||
void _inCallbackFromInDataHandler(const DaqData &daqdata);
|
||||
|
||||
public:
|
||||
ThreadedInDataHandlerBase(SmgrHandle mgr, InCallbackType cb, ResetCallbackType reset);
|
||||
~ThreadedInDataHandlerBase();
|
||||
/**
|
||||
* @brief This method should be called from the derived class' constructor,
|
||||
* to start the thread and data is incoming.
|
||||
*/
|
||||
void startThread();
|
||||
/**
|
||||
* @brief This method SHOULD be called from all classes that derive on
|
||||
* ThreadedInDataHandler. It is to make sure the inCallback_threaded()
|
||||
* function is no longer called when the destructor of the derived class is
|
||||
* called. Not calling this function is regarded as a BUG.
|
||||
*/
|
||||
void stopThread();
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A bit of curiously recurring template pattern, to connect the
|
||||
* specific handlers and connect the proper callbacks in a type-agnostic way.
|
||||
* Using this class, each threaded handler should just implement its reset()
|
||||
* and inCallback() method. Ellides the virtual method calls.
|
||||
*
|
||||
* Usage: class XHandler: public ThreadedInDataHandler<XHandler> {
|
||||
* public:
|
||||
* XHandler(streammgr) : ThreadedInDataHandler(streammgr) {}
|
||||
* void inCallback(const DaqData& d) { ... do something with d }
|
||||
* void reset(const Daq* daq) { ... do something with daq }
|
||||
* };
|
||||
*
|
||||
* For examples, see PPMHandler, etc.
|
||||
*
|
||||
* @tparam Derived The
|
||||
*/
|
||||
template <typename Derived>
|
||||
class ThreadedInDataHandler : public ThreadedInDataHandlerBase {
|
||||
public:
|
||||
ThreadedInDataHandler(SmgrHandle mgr):
|
||||
ThreadedInDataHandlerBase(mgr,
|
||||
std::bind(&ThreadedInDataHandler::_inCallback, this, _1),
|
||||
std::bind(&ThreadedInDataHandler::_reset, this, _1))
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void _reset(const Daq* daq) {
|
||||
DEBUGTRACE_ENTER;
|
||||
return static_cast<Derived*>(this)->reset(daq);
|
||||
}
|
||||
void _inCallback(const DaqData& data) {
|
||||
DEBUGTRACE_ENTER;
|
||||
return static_cast<Derived*>(this)->inCallback(data);
|
||||
}
|
||||
};
|
||||
|
||||
/** @} */
|
||||
/** @} */
|
@ -11,7 +11,7 @@ using std::cerr;
|
||||
using std::endl;
|
||||
|
||||
// Safe some typing. Linspace form 0 up to (and NOT including N).
|
||||
#define lin0N arma::linspace(0, N - 1, N)
|
||||
#define lin0N arma::linspace<vd>(0, N - 1, N)
|
||||
|
||||
vd Window::hann(const us N) {
|
||||
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 a1 = 9240. / 18608.;
|
||||
d a2 = 1430. / 18608.;
|
||||
return a0 - a1 * d_cos((2 * number_pi/N) * lin0N) +
|
||||
a2 * d_cos((4 * number_pi / N)* lin0N );
|
||||
return a0 - a1 * arma::cos((2 * number_pi/N) * lin0N) +
|
||||
a2 * arma::cos((4 * number_pi / N)* lin0N );
|
||||
}
|
||||
|
||||
vd Window::rectangular(const us N) { return arma::ones(N); }
|
@ -9,9 +9,6 @@
|
||||
#ifndef LASP_CONFIG_H
|
||||
#define LASP_CONFIG_H
|
||||
|
||||
const int LASP_VERSION_MAJOR = @CMAKE_PROJECT_VERSION_MAJOR@;
|
||||
const int LASP_VERSION_MINOR = @CMAKE_PROJECT_VERSION_MINOR@;
|
||||
|
||||
/* Debug flag */
|
||||
#cmakedefine01 LASP_DEBUG
|
||||
|
||||
@ -31,6 +28,7 @@ const int LASP_VERSION_MINOR = @CMAKE_PROJECT_VERSION_MINOR@;
|
||||
|
||||
#cmakedefine LASP_FFT_BACKEND @LASP_FFT_BACKEND@
|
||||
#cmakedefine01 LASP_HAS_RTAUDIO
|
||||
#cmakedefine01 LASP_HAS_PORTAUDIO
|
||||
#cmakedefine01 LASP_HAS_ULDAQ
|
||||
#cmakedefine01 LASP_DOUBLE_PRECISION
|
||||
#cmakedefine01 LASP_USE_BLAS
|
@ -44,6 +44,12 @@ void init_siggen(py::module &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_deviceinfo(m);
|
||||
init_daqconfiguration(m);
|
||||
@ -51,13 +57,5 @@ PYBIND11_MODULE(lasp_cpp, m) {
|
||||
init_streammgr(m);
|
||||
init_datahandler(m);
|
||||
init_siggen(m);
|
||||
|
||||
// We store the version number of the code via CMake, and create an
|
||||
// attribute in the C++ code.
|
||||
m.attr("__version__") = std::to_string(LASP_VERSION_MAJOR) + "." +
|
||||
std::to_string(LASP_VERSION_MINOR);
|
||||
|
||||
m.attr("LASP_VERSION_MAJOR") = LASP_VERSION_MAJOR;
|
||||
m.attr("LASP_VERSION_MINOR") = LASP_VERSION_MINOR;
|
||||
}
|
||||
/** @} */
|
@ -1,5 +1,6 @@
|
||||
#include <pybind11/stl.h>
|
||||
#include <stdint.h>
|
||||
#include <iostream>
|
||||
#include "lasp_deviceinfo.h"
|
||||
|
||||
using std::cerr;
|
||||
@ -28,6 +29,9 @@ void init_deviceinfo(py::module& m) {
|
||||
devinfo.def_readonly("availableInputRanges",
|
||||
&DeviceInfo::availableInputRanges);
|
||||
devinfo.def_readonly("prefInputRangeIndex", &DeviceInfo::prefInputRangeIndex);
|
||||
devinfo.def_readonly("availableOutputRanges",
|
||||
&DeviceInfo::availableOutputRanges);
|
||||
devinfo.def_readonly("prefOutputRangeIndex", &DeviceInfo::prefOutputRangeIndex);
|
||||
|
||||
devinfo.def_readonly("ninchannels", &DeviceInfo::ninchannels);
|
||||
devinfo.def_readonly("noutchannels", &DeviceInfo::noutchannels);
|
||||
@ -35,7 +39,10 @@ void init_deviceinfo(py::module& m) {
|
||||
devinfo.def_readonly("hasInputACCouplingSwitch",
|
||||
&DeviceInfo::hasInputACCouplingSwitch);
|
||||
|
||||
devinfo.def_readonly("hasDuplexMode", &DeviceInfo::hasDuplexMode);
|
||||
devinfo.def_readonly("duplexModeForced", &DeviceInfo::duplexModeForced);
|
||||
devinfo.def_readonly("hasInternalOutputMonitor", &DeviceInfo::hasInternalOutputMonitor);
|
||||
|
||||
devinfo.def_readonly("physicalInputQty", &DeviceInfo::physicalInputQty);
|
||||
devinfo.def_readonly("physicalOutputQty", &DeviceInfo::physicalOutputQty);
|
||||
|
||||
}
|
||||
|
@ -1,13 +1,16 @@
|
||||
#include <pybind11/pybind11.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "arma_npy.h"
|
||||
#include "lasp_avpowerspectra.h"
|
||||
#include "lasp_biquadbank.h"
|
||||
#include "lasp_fft.h"
|
||||
#include "lasp_filter.h"
|
||||
#include "lasp_freqsmooth.h"
|
||||
#include "lasp_slm.h"
|
||||
#include "lasp_streammgr.h"
|
||||
#include "lasp_window.h"
|
||||
#include <iostream>
|
||||
#include <pybind11/pybind11.h>
|
||||
|
||||
using std::cerr;
|
||||
using std::endl;
|
||||
@ -27,7 +30,6 @@ using rte = std::runtime_error;
|
||||
*/
|
||||
|
||||
void init_dsp(py::module &m) {
|
||||
|
||||
py::class_<Fft> fft(m, "Fft");
|
||||
fft.def(py::init<us>());
|
||||
fft.def("fft", [](Fft &f, dpyarray dat) {
|
||||
@ -114,9 +116,10 @@ void init_dsp(py::module &m) {
|
||||
|
||||
aps.def("compute", [](AvPowerSpectra &aps, dpyarray timedata) {
|
||||
std::optional<ccube> res;
|
||||
dmat timedata_mat = NpyToMat<d, false>(timedata);
|
||||
{
|
||||
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)));
|
||||
@ -151,5 +154,12 @@ void init_dsp(py::module &m) {
|
||||
slm.def("Lmax", [](const SLM &slm) { return ColToNpy<d>(slm.Lmax()); });
|
||||
slm.def("Lpeak", [](const SLM &slm) { return ColToNpy<d>(slm.Lpeak()); });
|
||||
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));
|
||||
});
|
||||
}
|
||||
/** @} */
|
353
cpp_src/pybind11/lasp_pyindatahandler.cpp
Normal file
353
cpp_src/pybind11/lasp_pyindatahandler.cpp
Normal file
@ -0,0 +1,353 @@
|
||||
// #define DEBUGTRACE_ENABLED
|
||||
#include <pybind11/pybind11.h>
|
||||
#include <pybind11/pytypes.h>
|
||||
|
||||
#include <armadillo>
|
||||
#include <atomic>
|
||||
|
||||
#include "arma_npy.h"
|
||||
#include "debugtrace.hpp"
|
||||
#include "lasp_clip.h"
|
||||
#include "lasp_daq.h"
|
||||
#include "lasp_daqdata.h"
|
||||
#include "lasp_ppm.h"
|
||||
#include "lasp_rtaps.h"
|
||||
#include "lasp_rtsignalviewer.h"
|
||||
#include "lasp_streammgr.h"
|
||||
#include "lasp_threadedindatahandler.h"
|
||||
|
||||
using namespace std::literals::chrono_literals;
|
||||
using std::cerr;
|
||||
using std::endl;
|
||||
using rte = std::runtime_error;
|
||||
using Lck = std::scoped_lock<std::recursive_mutex>;
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
/**
|
||||
* @brief Generate a Numpy array from daqdata, does *NOT* create a copy of the
|
||||
* data!. Instead, it shares the data from the DaqData container.
|
||||
*
|
||||
* @tparam T The type of the stored sample
|
||||
* @param d The daqdata to convert
|
||||
*
|
||||
* @return Numpy array
|
||||
*/
|
||||
template <typename T, bool copy = false>
|
||||
py::array_t<T> getPyArrayNoCpy(const DaqData &d) {
|
||||
// https://github.com/pybind/pybind11/issues/323
|
||||
//
|
||||
// When a valid object is passed as 'base', it tells pybind not to take
|
||||
// ownership of the data, because 'base' will own it. In fact 'packet' will
|
||||
// own it, but - psss! - , we don't tell it to pybind... Alos note that ANY
|
||||
// valid object is good for this purpose, so I choose "str"...
|
||||
|
||||
py::str dummyDataOwner;
|
||||
/*
|
||||
* Signature:
|
||||
array_t(ShapeContainer shape,
|
||||
StridesContainer strides,
|
||||
const T *ptr = nullptr,
|
||||
handle base = handle());
|
||||
*/
|
||||
|
||||
return py::array_t<T>(
|
||||
py::array::ShapeContainer({d.nframes, d.nchannels}), // Shape
|
||||
|
||||
py::array::StridesContainer( // Strides
|
||||
{sizeof(T),
|
||||
sizeof(T) * d.nframes}), // Strides (in bytes) for each index
|
||||
|
||||
reinterpret_cast<T *>(
|
||||
const_cast<DaqData &>(d).raw_ptr()), // Pointer to buffer
|
||||
|
||||
dummyDataOwner // As stated above, now Numpy does not take ownership of
|
||||
// the data pointer.
|
||||
);
|
||||
}
|
||||
|
||||
template <typename T, bool copy = false>
|
||||
py::array_t<d> dmat_to_ndarray(const DaqData &d) {
|
||||
// https://github.com/pybind/pybind11/issues/323
|
||||
//
|
||||
// When a valid object is passed as 'base', it tells pybind not to take
|
||||
// ownership of the data, because 'base' will own it. In fact 'packet' will
|
||||
// own it, but - psss! - , we don't tell it to pybind... Alos note that ANY
|
||||
// valid object is good for this purpose, so I choose "str"...
|
||||
|
||||
py::str dummyDataOwner;
|
||||
/*
|
||||
* Signature:
|
||||
array_t(ShapeContainer shape,
|
||||
StridesContainer strides,
|
||||
const T *ptr = nullptr,
|
||||
handle base = handle());
|
||||
*/
|
||||
|
||||
return py::array_t<T>(
|
||||
py::array::ShapeContainer({d.nframes, d.nchannels}), // Shape
|
||||
|
||||
py::array::StridesContainer( // Strides
|
||||
{sizeof(T),
|
||||
sizeof(T) * d.nframes}), // Strides (in bytes) for each index
|
||||
|
||||
reinterpret_cast<T *>(
|
||||
const_cast<DaqData &>(d).raw_ptr()), // Pointer to buffer
|
||||
|
||||
dummyDataOwner // As stated above, now Numpy does not take ownership of
|
||||
// the data pointer.
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Wraps the ThreadedInDataHandler such that it calls a Python callback
|
||||
* with a buffer of sample data. Converts DaqData objects to Numpy arrays and
|
||||
* calls Python given as argument to the constructor
|
||||
*/
|
||||
class PyIndataHandler : public ThreadedInDataHandler<PyIndataHandler> {
|
||||
/**
|
||||
* @brief The callback functions that is called.
|
||||
*/
|
||||
py::object _cb, _reset_callback;
|
||||
std::atomic<bool> _done{false};
|
||||
std::recursive_mutex _mtx;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Initialize PyIndataHandler
|
||||
*
|
||||
* @param mgr StreamMgr handle
|
||||
* @param cb Python callback that is called with Numpy input data from device
|
||||
* @param reset_callback Python callback that is called with a Daq pointer.
|
||||
* Careful: do not store this handle, as it is only valid as long as reset()
|
||||
* is called, when a stream stops, this pointer / handle will dangle.
|
||||
*/
|
||||
PyIndataHandler(SmgrHandle mgr, py::function cb, py::function reset_callback)
|
||||
: ThreadedInDataHandler(mgr),
|
||||
_cb(py::weakref(cb)),
|
||||
_reset_callback(py::weakref(reset_callback)) {
|
||||
DEBUGTRACE_ENTER;
|
||||
// cerr << "Thread ID: " << std::this_thread::get_id() << endl;
|
||||
/// Start should be called externally, as at constructor time no virtual
|
||||
/// functions should be called.
|
||||
if (_cb().is_none() || _reset_callback().is_none()) {
|
||||
throw rte("cb or reset_callback is none!");
|
||||
}
|
||||
startThread();
|
||||
}
|
||||
~PyIndataHandler() {
|
||||
DEBUGTRACE_ENTER;
|
||||
// cerr << "Thread ID: " << std::this_thread::get_id() << endl;
|
||||
/// Callback cannot be called, which results in a deadlock on the GIL
|
||||
/// without this release.
|
||||
py::gil_scoped_release release;
|
||||
DEBUGTRACE_PRINT("Gil released");
|
||||
_done = true;
|
||||
stopThread();
|
||||
}
|
||||
/**
|
||||
* @brief Calls the reset callback in Python.
|
||||
*
|
||||
* @param daq Daq device, or nullptr in case no input stream is running.
|
||||
*/
|
||||
void reset(const Daq *daqi) {
|
||||
DEBUGTRACE_ENTER;
|
||||
// cerr << "Thread ID: " << std::this_thread::get_id() << endl;
|
||||
if (_done) return;
|
||||
{
|
||||
try {
|
||||
py::object reset_callback = _reset_callback();
|
||||
if (reset_callback.is_none()) {
|
||||
DEBUGTRACE_PRINT("reset_callback is none, weakref killed");
|
||||
_done = true;
|
||||
return;
|
||||
}
|
||||
if (daqi != nullptr) {
|
||||
assert(reset_callback);
|
||||
reset_callback(daqi);
|
||||
} else {
|
||||
assert(reset_callback);
|
||||
reset_callback(py::none());
|
||||
}
|
||||
} catch (py::error_already_set &e) {
|
||||
cerr << "*************** Error calling reset callback!\n";
|
||||
cerr << e.what() << endl;
|
||||
cerr << "*************** \n";
|
||||
/// Throwing a runtime error here does not work out one way or another.
|
||||
/// Therefore, it is better to dive out and prevent undefined behaviour
|
||||
abort();
|
||||
/* throw std::runtime_error(e.what()); */
|
||||
} catch (std::exception &e) {
|
||||
cerr << "Caught unknown exception in reset callback:" << e.what()
|
||||
<< endl;
|
||||
abort();
|
||||
}
|
||||
} // end of GIL scope
|
||||
} // end of function reset()
|
||||
|
||||
/**
|
||||
* @brief Calls the Python callback method / function with a Numpy array of
|
||||
* stream data.
|
||||
*/
|
||||
void inCallback(const DaqData &d) {
|
||||
DEBUGTRACE_ENTER;
|
||||
// cerr << "=== Enter incallback for thread ID: " << std::this_thread::get_id() << endl;
|
||||
|
||||
using DataType = DataTypeDescriptor::DataType;
|
||||
if (_done) {
|
||||
DEBUGTRACE_PRINT("Early stop, done");
|
||||
return;
|
||||
}
|
||||
{
|
||||
DEBUGTRACE_PRINT("================ TRYING TO OBTAIN GIL in inCallback...");
|
||||
py::gil_scoped_acquire acquire;
|
||||
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) {
|
||||
case (DataType::dtype_int8): {
|
||||
py_bool = cb(getPyArrayNoCpy<int8_t>(d));
|
||||
} break;
|
||||
case (DataType::dtype_int16): {
|
||||
py_bool = cb(getPyArrayNoCpy<int16_t>(d));
|
||||
} break;
|
||||
case (DataType::dtype_int32): {
|
||||
py_bool = cb(getPyArrayNoCpy<int32_t>(d));
|
||||
} break;
|
||||
case (DataType::dtype_fl32): {
|
||||
py_bool = cb(getPyArrayNoCpy<float>(d));
|
||||
} break;
|
||||
case (DataType::dtype_fl64): {
|
||||
py_bool = cb(getPyArrayNoCpy<double>(d));
|
||||
} break;
|
||||
default:
|
||||
throw std::runtime_error("BUG");
|
||||
} // End of switch
|
||||
|
||||
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) {
|
||||
cerr << "ERROR (BUG): Python raised exception from callback function: ";
|
||||
cerr << e.what() << endl;
|
||||
abort();
|
||||
} catch (py::cast_error &e) {
|
||||
cerr << e.what() << endl;
|
||||
cerr << "ERROR (BUG): Python callback does not return boolean value."
|
||||
<< endl;
|
||||
abort();
|
||||
} catch (std::exception &e) {
|
||||
cerr << "Caught unknown exception in Python callback:" << e.what()
|
||||
<< endl;
|
||||
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) {
|
||||
/// The C++ class is PyIndataHandler, but for Python, it is called
|
||||
/// InDataHandler
|
||||
py::class_<PyIndataHandler> pyidh(m, "InDataHandler");
|
||||
pyidh.def(py::init<SmgrHandle, py::function, py::function>());
|
||||
|
||||
/// Peak Programme Meter
|
||||
py::class_<PPMHandler> ppm(m, "PPMHandler");
|
||||
ppm.def(py::init<SmgrHandle, const d>());
|
||||
ppm.def(py::init<SmgrHandle>());
|
||||
|
||||
ppm.def("getCurrentValue", [](const PPMHandler &ppm) {
|
||||
std::tuple<vd, arma::uvec> tp;
|
||||
{
|
||||
py::gil_scoped_release release;
|
||||
tp = ppm.getCurrentValue();
|
||||
}
|
||||
|
||||
return py::make_tuple(ColToNpy<d>(std::get<0>(tp)),
|
||||
ColToNpy<arma::uword>(std::get<1>(tp)));
|
||||
});
|
||||
|
||||
/// Clip Detector
|
||||
py::class_<ClipHandler> clip(m, "ClipHandler");
|
||||
clip.def(py::init<SmgrHandle>());
|
||||
|
||||
clip.def("getCurrentValue", [](const ClipHandler &clip) {
|
||||
arma::uvec cval;
|
||||
{
|
||||
py::gil_scoped_release release;
|
||||
cval = clip.getCurrentValue();
|
||||
}
|
||||
|
||||
return ColToNpy<arma::uword>(cval); // something goes wrong here
|
||||
});
|
||||
|
||||
/// Real time Aps
|
||||
///
|
||||
py::class_<RtAps> rtaps(m, "RtAps");
|
||||
rtaps.def(py::init<SmgrHandle, // StreamMgr
|
||||
Filter *const, // FreqWeighting filter
|
||||
const us, // Nfft
|
||||
const Window::WindowType, // Window
|
||||
const d, // Overlap percentage 0<=o<100
|
||||
|
||||
const d // Time constant
|
||||
>(),
|
||||
py::arg("streammgr"), // StreamMgr
|
||||
py::arg("preFilter").none(true),
|
||||
/// Below list of arguments *SHOULD* be same as for
|
||||
|
||||
/// AvPowerSpectra constructor!
|
||||
py::arg("nfft") = 2048, //
|
||||
py::arg("windowType") = Window::WindowType::Hann, //
|
||||
py::arg("overlap_percentage") = 50.0, //
|
||||
py::arg("time_constant") = -1 //
|
||||
);
|
||||
|
||||
rtaps.def("getCurrentValue", [](RtAps &rt) {
|
||||
ccube val;
|
||||
{
|
||||
py::gil_scoped_release release;
|
||||
val = rt.getCurrentValue();
|
||||
}
|
||||
return CubeToNpy<c>(val);
|
||||
});
|
||||
|
||||
/// Real time Signal Viewer
|
||||
///
|
||||
py::class_<RtSignalViewer> rtsv(m, "RtSignalViewer");
|
||||
rtsv.def(py::init<SmgrHandle, // StreamMgr
|
||||
const d, // Time history
|
||||
const us, // Resolution
|
||||
const us // Channel number
|
||||
>());
|
||||
|
||||
rtsv.def("getCurrentValue", [](RtSignalViewer &rt) {
|
||||
dmat val;
|
||||
{
|
||||
py::gil_scoped_release release;
|
||||
val = rt.getCurrentValue();
|
||||
}
|
||||
return MatToNpy<d>(val);
|
||||
});
|
||||
}
|
@ -30,6 +30,7 @@ void init_siggen(py::module &m) {
|
||||
siggen.def("setLevel", &Siggen::setLevel, py::arg("newLevel"),
|
||||
py::arg("dB") = true);
|
||||
siggen.def("reset", &Siggen::reset);
|
||||
siggen.def("setInterruptPeriod", &Siggen::setInterruptPeriod);
|
||||
|
||||
siggen.def("setFilter", &Siggen::setFilter);
|
||||
|
@ -1,4 +1,5 @@
|
||||
#include "lasp_streammgr.h"
|
||||
#include "lasp_indatahandler.h"
|
||||
#include <pybind11/numpy.h>
|
||||
#include <pybind11/pybind11.h>
|
||||
#include <pybind11/stl.h>
|
||||
@ -12,7 +13,7 @@ void init_streammgr(py::module &m) {
|
||||
|
||||
/// The stream manager is a singleton, and the lifetime is managed elsewhere.
|
||||
// It should not be deleted.
|
||||
py::class_<StreamMgr, std::unique_ptr<StreamMgr, py::nodelete>> smgr(
|
||||
py::class_<StreamMgr, std::shared_ptr<StreamMgr>> smgr(
|
||||
m, "StreamMgr");
|
||||
|
||||
py::enum_<StreamMgr::StreamType>(smgr, "StreamType")
|
||||
@ -22,7 +23,7 @@ void init_streammgr(py::module &m) {
|
||||
smgr.def("startStream", &StreamMgr::startStream);
|
||||
smgr.def("stopStream", &StreamMgr::stopStream);
|
||||
smgr.def_static("getInstance", []() {
|
||||
return std::unique_ptr<StreamMgr, py::nodelete>(&StreamMgr::getInstance());
|
||||
return StreamMgr::getInstance();
|
||||
});
|
||||
smgr.def("stopAllStreams", &StreamMgr::stopAllStreams);
|
||||
|
49
examples/example_input.py
Executable file
49
examples/example_input.py
Executable file
@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env python3
|
||||
import lasp
|
||||
# Get handle to stream manager
|
||||
mgr = lasp.StreamMgr.getInstance()
|
||||
import time
|
||||
time.sleep(1)
|
||||
|
||||
ds = mgr.getDeviceInfo()
|
||||
# Search for a device
|
||||
for i, d in enumerate(ds):
|
||||
print(f'{i}: ' + d.device_name)
|
||||
|
||||
d = ds[0] # Create a configuration and enable some input channels
|
||||
config = lasp.DaqConfiguration(d)
|
||||
config.inchannel_config[0].enabled = True
|
||||
config.inchannel_config[1].enabled = True
|
||||
# Choose a different number of frames per block
|
||||
config.framesPerBlockIndex = 2
|
||||
|
||||
# Start a stream with a configuration
|
||||
mgr.startStream(config)
|
||||
|
||||
def reset_cb(daq):
|
||||
print('Reset called')
|
||||
|
||||
def cb(data):
|
||||
# Print something on callback
|
||||
print(data.shape)
|
||||
return True
|
||||
|
||||
|
||||
# Attach the indata handler to the stream
|
||||
#i = lasp.InDataHandler(mgr, cb, reset_cb)
|
||||
|
||||
ppm = lasp.PPMHandler(mgr)
|
||||
#del ppm
|
||||
del mgr
|
||||
|
||||
#del i
|
||||
try:
|
||||
while True:
|
||||
val, clip = ppm.getCurrentValue()
|
||||
print(val)
|
||||
time.sleep(0.1)
|
||||
|
||||
#print(f'{val[0]} {val[1]}', end='')
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
# mgr.stopStream(lasp.StreamMgr.StreamType.input)
|
61
pyproject.toml
Normal file
61
pyproject.toml
Normal file
@ -0,0 +1,61 @@
|
||||
[project]
|
||||
name = "lasp"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
description = "Library for Acoustic Signal Processing"
|
||||
license = { "file" = "LICENSE" }
|
||||
authors = [{ "name" = "J.A. de Jong", "email" = "j.a.dejong@ascee.nl" }]
|
||||
version = "1.6.8"
|
||||
|
||||
keywords = ["DSP", "DAQ", "Signal processing"]
|
||||
|
||||
classifiers = [
|
||||
"Development Status :: 3 - Alpha",
|
||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Natural Language :: English",
|
||||
"Topic :: Scientific/Engineering",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Operating System :: POSIX :: Linux",
|
||||
"Operating System :: Microsoft :: Windows",
|
||||
]
|
||||
urls = { "Documentation" = "https://lasp.ascee.nl" }
|
||||
|
||||
dependencies = [
|
||||
"scipy>=1.13.1",
|
||||
"matplotlib>=3.7.2",
|
||||
"appdirs",
|
||||
"dataclasses_json",
|
||||
"h5py",
|
||||
]
|
||||
|
||||
[build-system] # How pip and other frontends should build this project
|
||||
requires = ["py-build-cmake~=0.1.8", "pybind11"]
|
||||
build-backend = "py_build_cmake.build"
|
||||
|
||||
[tool.py-build-cmake.module] # Where to find the Python module to package
|
||||
directory = "python_src"
|
||||
|
||||
[tool.py-build-cmake.sdist] # What to include in source distributions
|
||||
include = [
|
||||
"CMakeLists.txt",
|
||||
"cmake",
|
||||
"cpp_src",
|
||||
"python_src",
|
||||
"img",
|
||||
"scripts",
|
||||
"third_party",
|
||||
]
|
||||
|
||||
[tool.py-build-cmake.cmake] # How to build the CMake project
|
||||
build_type = "Release"
|
||||
source_path = "."
|
||||
options = { "LASP_HAS_PORTAUDIO" = "ON", "LASP_HAS_RTAUDIO" = "OFF" }
|
||||
build_args = ["-j"]
|
||||
install_components = ["python_modules"]
|
||||
|
||||
[tool.py-build-cmake.editable]
|
||||
# This might not work properly on Windows. Comment this out when testing on
|
||||
# Windows.
|
||||
mode = "symlink"
|
@ -1,2 +1,2 @@
|
||||
[pytest]
|
||||
addopts = "--ignore=third_party"
|
||||
addopts = "--ignore=third_party --ignore=examples"
|
||||
|
@ -2,18 +2,20 @@
|
||||
LASP: Library for Acoustic Signal Processing
|
||||
|
||||
"""
|
||||
from .lasp_cpp import *
|
||||
import lasp.lasp_cpp
|
||||
|
||||
|
||||
from .lasp_version import __version__
|
||||
from .lasp_common import *
|
||||
__version__ = lasp_cpp.__version__
|
||||
from .lasp_cpp import *
|
||||
|
||||
# from .lasp_imptube import * # TwoMicImpedanceTube
|
||||
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_record import * # RecordStatus, Recording
|
||||
from .lasp_daqconfigs import *
|
||||
from .lasp_measurementset import *
|
||||
from .lasp_daqconfigs import * # DaqConfigurations
|
||||
from .lasp_measurementset import * # MeasurementSet
|
||||
|
||||
# from .lasp_siggen import * # SignalType, NoiseType, SiggenMessage, SiggenData, Siggen
|
||||
# from .lasp_weighcal import * # WeighCal
|
||||
# from .tools import * # SmoothingType, smoothSpectralData, SmoothingWidth
|
@ -23,11 +23,12 @@ y[n] = 1/ba[3] * ( ba[0] * x[n] + ba[1] * x[n-1] + ba[2] * x[n-2] +
|
||||
|
||||
"""
|
||||
__all__ = ['peaking', 'biquadTF', 'notch', 'lowpass', 'highpass',
|
||||
'highshelf', 'lowshelf']
|
||||
'highshelf', 'lowshelf', 'LP1compensator', 'LP2compensator',
|
||||
'HP1compensator', 'HP2compensator']
|
||||
|
||||
from numpy import array, cos, pi, sin, sqrt
|
||||
from scipy.interpolate import interp1d
|
||||
from scipy.signal import sosfreqz
|
||||
from scipy.signal import sosfreqz, bilinear_zpk, zpk2sos
|
||||
|
||||
|
||||
def peaking(fs, f0, Q, gain):
|
||||
@ -157,6 +158,129 @@ def lowshelf(fs, f0, Q, gain):
|
||||
a2 = (A+1) + (A-1)*cos(w0) - 2*sqrt(A)*alpha
|
||||
return array([b0/a0, b1/a0, b2/a0, a0/a0, a1/a0, a2/a0])
|
||||
|
||||
def LP1compensator(fs, f0o, f0n):
|
||||
"""
|
||||
Shelving type filter that, when multiplied with a first-order low-pass
|
||||
filter, alters the response of that filter to a different first-order
|
||||
low-pass filter.
|
||||
|
||||
Args:
|
||||
fs: Sampling frequency [Hz]
|
||||
f0o: Cut-off frequency of the original filter [Hz]
|
||||
f0n: Desired cut-off frequency [Hz]
|
||||
"""
|
||||
|
||||
omg0o = 2*pi*f0o
|
||||
omg0n = 2*pi*f0n
|
||||
|
||||
z = -omg0o
|
||||
p = -omg0n
|
||||
k = p/z
|
||||
|
||||
zd, pd, kd = bilinear_zpk(z, p, k, fs)
|
||||
|
||||
sos = zpk2sos(zd,pd,kd)
|
||||
|
||||
return sos[0]
|
||||
|
||||
def LP2compensator(fs, f0o, Qo, f0n, Qn):
|
||||
"""
|
||||
Shelving type filter that, when multiplied with a second-order low-pass
|
||||
filter, alters the response of that filter to a different second-order
|
||||
low-pass filter.
|
||||
|
||||
Args:
|
||||
fs: Sampling frequency [Hz]
|
||||
f0o: Cut-off frequency of the original filter [Hz]
|
||||
Qo: Quality factor of the original filter (~inverse of bandwidth)
|
||||
f0n: Desired cut-off frequency [Hz]
|
||||
Qn: Desired quality factor(~inverse of bandwidth)
|
||||
"""
|
||||
|
||||
omg0o = 2*pi*f0o
|
||||
omg0n = 2*pi*f0n
|
||||
|
||||
zRe = omg0o/(2*Qo)
|
||||
zIm = omg0o*sqrt(1-1/(4*Qo**2))
|
||||
z1 = -zRe + zIm*1j
|
||||
z2 = -zRe - zIm*1j
|
||||
|
||||
pRe = omg0n/(2*Qn)
|
||||
pIm = omg0n*sqrt(1-1/(4*Qn**2))
|
||||
p1 = -pRe + pIm*1j
|
||||
p2 = -pRe - pIm*1j
|
||||
|
||||
z= [z1, z2]
|
||||
p = [p1, p2]
|
||||
k = (pRe**2 + pIm**2)/(zRe**2 + zIm**2)
|
||||
|
||||
zd, pd, kd = bilinear_zpk(z, p, k, fs)
|
||||
|
||||
sos = zpk2sos(zd,pd,kd)
|
||||
|
||||
return sos[0]
|
||||
|
||||
def HP1compensator(fs, f0o, f0n):
|
||||
"""
|
||||
Shelving type filter that, when multiplied with a first-order high-pass
|
||||
filter, alters the response of that filter to a different first-order
|
||||
high-pass filter.
|
||||
|
||||
Args:
|
||||
fs: Sampling frequency [Hz]
|
||||
f0o: Cut-on frequency of the original filter [Hz]
|
||||
f0n: Desired cut-on frequency [Hz]
|
||||
"""
|
||||
|
||||
omg0o = 2*pi*f0o
|
||||
omg0n = 2*pi*f0n
|
||||
|
||||
z = -omg0o
|
||||
p = -omg0n
|
||||
k = 1
|
||||
|
||||
zd, pd, kd = bilinear_zpk(z, p, k, fs)
|
||||
|
||||
sos = zpk2sos(zd,pd,kd)
|
||||
|
||||
return sos[0]
|
||||
|
||||
def HP2compensator(fs, f0o, Qo, f0n, Qn):
|
||||
"""
|
||||
Shelving type filter that, when multiplied with a second-order high-pass
|
||||
filter, alters the response of that filter to a different second-order
|
||||
high-pass filter.
|
||||
|
||||
Args:
|
||||
fs: Sampling frequency [Hz]
|
||||
f0o: Cut-on frequency of the original filter [Hz]
|
||||
Qo: Quality factor of the original filter (~inverse of bandwidth)
|
||||
f0n: Desired cut-on frequency [Hz]
|
||||
Qn: Desired quality factor(~inverse of bandwidth)
|
||||
"""
|
||||
|
||||
omg0o = 2*pi*f0o
|
||||
omg0n = 2*pi*f0n
|
||||
|
||||
zRe = omg0o/(2*Qo)
|
||||
zIm = omg0o*sqrt(1-1/(4*Qo**2))
|
||||
z1 = -zRe + zIm*1j
|
||||
z2 = -zRe - zIm*1j
|
||||
|
||||
pRe = omg0n/(2*Qn)
|
||||
pIm = omg0n*sqrt(1-1/(4*Qn**2))
|
||||
p1 = -pRe + pIm*1j
|
||||
p2 = -pRe - pIm*1j
|
||||
|
||||
z= [z1, z2]
|
||||
p = [p1, p2]
|
||||
k = 1
|
||||
|
||||
zd, pd, kd = bilinear_zpk(z, p, k, fs)
|
||||
|
||||
sos = zpk2sos(zd,pd,kd)
|
||||
|
||||
return sos[0]
|
||||
|
||||
def biquadTF(fs, freq, sos):
|
||||
"""
|
@ -209,6 +209,7 @@ class FilterBankDesigner:
|
||||
Returns:
|
||||
h: Linear filter transfer function [-]
|
||||
"""
|
||||
fs = self.fs
|
||||
fir = self.createFirFilter(fs, x)
|
||||
|
||||
# Decimated sampling frequency [Hz]
|
@ -11,7 +11,9 @@ __all__ = ['freqResponse', 'bandpass_fir_design', 'lowpass_fir_design',
|
||||
'arbitrary_fir_design']
|
||||
|
||||
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.):
|
||||
@ -44,7 +46,7 @@ def bandpass_fir_design(L, fs, fl, fu, window=hann):
|
||||
Omg2 = 2*np.pi*fu/fs
|
||||
Omg1 = 2*np.pi*fl/fs
|
||||
|
||||
fir = np.empty(L, dtype=float)
|
||||
fir = empty(L, dtype=float)
|
||||
|
||||
# First Create ideal band-pass filter
|
||||
fir[L//2] = (Omg2-Omg1)/np.pi
|
||||
@ -64,7 +66,7 @@ def lowpass_fir_design(L, fs, fc, window=hann):
|
||||
" than upper cut-off"
|
||||
|
||||
Omgc = 2*np.pi*fc/fs
|
||||
fir = np.empty(L, dtype=float)
|
||||
fir = empty(L, dtype=float)
|
||||
|
||||
# First Create ideal band-pass filter
|
||||
fir[L//2] = Omgc/np.pi
|
@ -33,7 +33,7 @@ class Atomic:
|
||||
|
||||
def checkType(self, val):
|
||||
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):
|
||||
self.checkType(toadd)
|
@ -58,9 +58,14 @@ class AvType(Enum):
|
||||
@dataclass_json
|
||||
@dataclass(eq=False)
|
||||
class Qty:
|
||||
# Name, i.e.: Acoustic Pressure
|
||||
name: str
|
||||
# I.e.: Pascal
|
||||
unit_name: str
|
||||
# I.e.: -, Pa, V
|
||||
unit_symb: str
|
||||
|
||||
# I.e.: ('dB SPL') <== tuple of possible level units
|
||||
level_unit: object
|
||||
# Contains a tuple of possible level names, including its reference value.
|
||||
# For now, it is only able to compute for the first one. I.e.e we are not
|
||||
@ -88,6 +93,18 @@ class Qty:
|
||||
"""
|
||||
return self.cpp_enum.value
|
||||
|
||||
@property
|
||||
def unit_symb_eq(self):
|
||||
"""Unit symbol to be used in equations
|
||||
|
||||
Returns:
|
||||
String: V, Pa, 1,
|
||||
"""
|
||||
if self.unit_symb != '-':
|
||||
return self.unit_symb
|
||||
else:
|
||||
return '1'
|
||||
|
||||
|
||||
|
||||
@unique
|
||||
@ -104,7 +121,7 @@ class SIQtys(Enum):
|
||||
unit_name='Pascal',
|
||||
unit_symb='Pa',
|
||||
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),
|
||||
cpp_enum = DaqChannel.Qty.AcousticPressure
|
||||
)
|
43
python_src/lasp/lasp_config.py
Normal file
43
python_src/lasp/lasp_config.py
Normal file
@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""!
|
||||
Author: J.A. de Jong - ASCEE
|
||||
|
||||
Description: LASP configuration
|
||||
"""
|
||||
import numpy as np
|
||||
from .lasp_cpp import LASP_DOUBLE_PRECISION
|
||||
|
||||
if LASP_DOUBLE_PRECISION:
|
||||
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, dtype=float, 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, dtype=float, 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}")
|
||||
|
@ -1,5 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from .lasp_cpp import DaqConfiguration, LASP_VERSION_MAJOR
|
||||
from .lasp_cpp import DaqConfiguration
|
||||
from .lasp_version import LASP_VERSION_MAJOR
|
||||
|
||||
"""!
|
||||
Author: J.A. de Jong - ASCEE
|
1241
python_src/lasp/lasp_measurement.py
Normal file
1241
python_src/lasp/lasp_measurement.py
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user