Compare commits
161 Commits
hdf5_samet
...
master
Author | SHA1 | Date | |
---|---|---|---|
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 |
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}}'
|
13
.gitignore
vendored
13
.gitignore
vendored
|
@ -6,22 +6,21 @@
|
||||||
.ninja*
|
.ninja*
|
||||||
build.ninja
|
build.ninja
|
||||||
dist
|
dist
|
||||||
src/lasp.egg-info
|
|
||||||
test/.ipynb_checkpoints
|
|
||||||
src/lasp/lasp_config.h
|
|
||||||
_deps
|
_deps
|
||||||
compile_commands.json
|
compile_commands.json
|
||||||
CMakeFiles
|
CMakeFiles
|
||||||
CMakeCache.txt
|
CMakeCache.txt
|
||||||
cmake_install.cmake
|
cmake_install.cmake
|
||||||
Makefile
|
Makefile
|
||||||
build
|
|
||||||
__pycache__
|
__pycache__
|
||||||
cython_debug
|
|
||||||
doc
|
doc
|
||||||
.ropeproject
|
.ropeproject
|
||||||
.ipynb_checkpoints
|
.ipynb_checkpoints
|
||||||
.spyproject
|
.spyproject
|
||||||
.cache
|
|
||||||
_skbuild
|
|
||||||
acme_log.log
|
acme_log.log
|
||||||
|
.venv
|
||||||
|
.py-build-cmake_cache
|
||||||
|
cpp_src/lasp_config.h
|
||||||
|
.cache
|
||||||
|
.vscode
|
||||||
|
build
|
||||||
|
|
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,25 +1,35 @@
|
||||||
cmake_minimum_required (VERSION 3.16)
|
cmake_minimum_required(VERSION 3.16)
|
||||||
project(LASP LANGUAGES C CXX VERSION 1.0)
|
project(LASP LANGUAGES C CXX VERSION 1.6.3)
|
||||||
|
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED)
|
set(CMAKE_CXX_STANDARD_REQUIRED)
|
||||||
|
|
||||||
option(LASP_DOUBLE_PRECISION "Compile as double precision floating point" ON)
|
if(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "aarch64")
|
||||||
|
set(RPI TRUE)
|
||||||
|
else()
|
||||||
|
set(RPI FALSE)
|
||||||
|
endif()
|
||||||
|
|
||||||
# Setting defaults for PortAudio and RtAudio backend, depending on Linux /
|
# Setting defaults for PortAudio and RtAudio backend, depending on Linux /
|
||||||
# Windows.
|
# Windows.
|
||||||
|
set(DEFAULT_DOUBLE_PRECISION ON)
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
set(DEFAULT_RTAUDIO OFF)
|
set(DEFAULT_RTAUDIO OFF)
|
||||||
set(DEFAULT_PORTAUDIO ON)
|
set(DEFAULT_PORTAUDIO ON)
|
||||||
set(DEFAULT_ULDAQ OFF)
|
set(DEFAULT_ULDAQ OFF)
|
||||||
|
elseif(${RPI})
|
||||||
|
set(DEFAULT_RTAUDIO OFF)
|
||||||
|
set(DEFAULT_PORTAUDIO ON)
|
||||||
|
set(DEFAULT_ULDAQ OFF)
|
||||||
|
set(DEFAULT_DOUBLE_PRECISION OFF)
|
||||||
else()
|
else()
|
||||||
set(DEFAULT_RTAUDIO ON)
|
set(DEFAULT_RTAUDIO OFF)
|
||||||
set(DEFAULT_PORTAUDIO OFF)
|
set(DEFAULT_PORTAUDIO ON)
|
||||||
set(DEFAULT_ULDAQ ON)
|
set(DEFAULT_ULDAQ ON)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
||||||
|
option(LASP_DOUBLE_PRECISION "Compile as double precision floating point" ${DEFAULT_DOUBLE_PRECISION})
|
||||||
option(LASP_HAS_RTAUDIO "Compile with RtAudio Daq backend" ${DEFAULT_RTAUDIO})
|
option(LASP_HAS_RTAUDIO "Compile with RtAudio Daq backend" ${DEFAULT_RTAUDIO})
|
||||||
option(LASP_HAS_PORTAUDIO "Compile with PortAudio Daq backend" ${DEFAULT_PORTAUDIO})
|
option(LASP_HAS_PORTAUDIO "Compile with PortAudio Daq backend" ${DEFAULT_PORTAUDIO})
|
||||||
if(LASP_HAS_PORTAUDIO AND LASP_HAS_RTAUDIO)
|
if(LASP_HAS_PORTAUDIO AND LASP_HAS_RTAUDIO)
|
||||||
|
@ -35,7 +45,7 @@ option(LASP_BUILD_CPP_TESTS "Build CPP test code" OFF)
|
||||||
find_program(CCACHE_PROGRAM ccache)
|
find_program(CCACHE_PROGRAM ccache)
|
||||||
if(CCACHE_PROGRAM)
|
if(CCACHE_PROGRAM)
|
||||||
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE_PROGRAM}")
|
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE_PROGRAM}")
|
||||||
set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK "${CCACHE_PROGRAM}")
|
set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK "${CCACHE_PROGRAM}")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# To allow linking to static libs from other directories
|
# To allow linking to static libs from other directories
|
||||||
|
@ -81,7 +91,7 @@ endif()
|
||||||
# Tune for current machine
|
# Tune for current machine
|
||||||
if(LASP_BUILD_TUNED)
|
if(LASP_BUILD_TUNED)
|
||||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||||
message(FATAL_ERROR
|
message(FATAL_ERROR
|
||||||
"Cannot build optimized and tuned code when debug is switched on")
|
"Cannot build optimized and tuned code when debug is switched on")
|
||||||
endif()
|
endif()
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native -mtune=native")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native -mtune=native")
|
||||||
|
@ -106,22 +116,29 @@ else()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# ###################################### Compilation flags
|
# ###################################### Compilation flags
|
||||||
set(CMAKE_C_FLAGS_RELEASE "-O3 -flto -mfpmath=sse -march=x86-64 -mtune=native \
|
set(CMAKE_C_FLAGS_RELEASE "-O3 -flto -mtune=native \
|
||||||
-fdata-sections -ffunction-sections -fomit-frame-pointer -finline-functions")
|
-fdata-sections -ffunction-sections -fomit-frame-pointer -finline-functions")
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-type-limits -Werror=return-type")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-type-limits -Werror=return-type")
|
||||||
|
|
||||||
|
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -flto -mtune=native \
|
||||||
|
-fdata-sections -ffunction-sections -fomit-frame-pointer -finline-functions")
|
||||||
|
|
||||||
|
if(NOT ${RPI})
|
||||||
|
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -mfpmath=sse -march=x86-64")
|
||||||
|
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -mfpmath=sse -march=x86-64")
|
||||||
|
endif()
|
||||||
|
set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g -Wall ")
|
||||||
|
|
||||||
# ############################# End compilation flags
|
# ############################# End compilation flags
|
||||||
include_directories(/usr/lib/python3.10/site-packages/numpy/core/include)
|
|
||||||
|
|
||||||
# ####################################### End of user-adjustable variables section
|
# ####################################### End of user-adjustable variables section
|
||||||
include(OSSpecific)
|
include(OSSpecific)
|
||||||
include(rtaudio)
|
include(rtaudio)
|
||||||
include(portaudio)
|
include(portaudio)
|
||||||
include(uldaq)
|
include(uldaq)
|
||||||
#
|
#
|
||||||
add_definitions(-Dgsl_CONFIG_DEFAULTS_VERSION=1)
|
add_definitions(-Dgsl_CONFIG_DEFAULTS_VERSION=1)
|
||||||
add_subdirectory(src/lasp)
|
add_subdirectory(cpp_src)
|
||||||
if(LASP_BUILD_CPP_TESTS)
|
if(LASP_BUILD_CPP_TESTS)
|
||||||
add_subdirectory(test)
|
add_subdirectory(test)
|
||||||
endif()
|
endif()
|
||||||
|
|
11
Dockerfile
11
Dockerfile
|
@ -1,13 +1,16 @@
|
||||||
FROM archlinux
|
FROM archlinux:latest
|
||||||
MAINTAINER J.A. de Jong - j.a.dejong@ascee.nl
|
MAINTAINER J.A. de Jong - j.a.dejong@ascee.nl
|
||||||
RUN pacman --noconfirm -Sy
|
RUN pacman --noconfirm -Sy
|
||||||
RUN pacman --noconfirm -S git doxygen graphviz lighttpd python-pip
|
RUN pacman --noconfirm -S git doxygen graphviz lighttpd python-pip python-virtualenv
|
||||||
RUN pip install doxypypy
|
|
||||||
WORKDIR /root
|
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
|
RUN git clone https://code.ascee.nl/ascee/lasp
|
||||||
WORKDIR /root/lasp
|
WORKDIR /root/lasp
|
||||||
RUN doxygen
|
RUN doxygen
|
||||||
RUN rm -rf /srv//http
|
RUN rm -rf /srv/http
|
||||||
RUN mv doc/html /srv/http
|
RUN mv doc/html /srv/http
|
||||||
CMD /usr/bin/lighttpd -D -f /etc/lighttpd/lighttpd.conf
|
CMD /usr/bin/lighttpd -D -f /etc/lighttpd/lighttpd.conf
|
||||||
|
|
||||||
|
|
146
README.md
146
README.md
|
@ -1,7 +1,5 @@
|
||||||
# Library for Acoustic Signal Processing
|
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)
|
|
||||||
|
|
||||||
|
|
||||||
Welcome to LASP: Library for Acoustic Signal Processing. LASP is a C++ library
|
Welcome to LASP: Library for Acoustic Signal Processing. LASP is a C++ library
|
||||||
|
@ -46,42 +44,114 @@ in a sister repository [lasp-doc](https://code.ascee.nl/ascee/lasp-doc).
|
||||||
|
|
||||||
If you have any question(s), please feel free to contact us: [email](info@ascee.nl).
|
If you have any question(s), please feel free to contact us: [email](info@ascee.nl).
|
||||||
|
|
||||||
# Installation - Linux (Debian-based)
|
|
||||||
|
|
||||||
## 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`
|
## Prerequisites
|
||||||
- `$ pip3 install --user -r requirements.txt`
|
|
||||||
|
|
||||||
|
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:
|
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:
|
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`
|
- `$ git clone --recursive https://code.ascee.nl/ASCEE/lasp.git`
|
||||||
- `$ cd lasp`
|
- `$ 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`
|
`$ sudo apt install doxygen graphviz`
|
||||||
`$ pip install doxypypy`
|
`$ pip install doxypypy`
|
||||||
|
@ -94,21 +164,29 @@ This will build the documentation. It can be read by:
|
||||||
|
|
||||||
`$ <YOUR-BROWSER> doc/html/index.html`
|
`$ <YOUR-BROWSER> doc/html/index.html`
|
||||||
|
|
||||||
Or via docker:
|
# Usage
|
||||||
|
|
||||||
`$ 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
|
|
||||||
|
|
||||||
- See examples directories for IPython notebooks.
|
- See examples directories for IPython notebooks.
|
||||||
- Please refer to the [documentation](https://lasp.ascee.nl/) for features.
|
- Please refer to the [documentation](https://lasp.ascee.nl/) for features.
|
||||||
|
|
||||||
|
|
||||||
|
# Development docs
|
||||||
|
|
||||||
|
## Bumping version number
|
||||||
|
|
||||||
|
When bumping the version number, please update the number in
|
||||||
|
|
||||||
|
- `pyproject.toml`
|
||||||
|
- `CMakeLists.txt`
|
||||||
|
|
||||||
|
Then, create a commit with tag `vX.X.X`, and push it.
|
||||||
|
|
||||||
|
## Updating to latest version (editable mode)
|
||||||
|
|
||||||
|
When updating to the latest version of LASP in editable mode:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
- $ git pull
|
||||||
|
- $ git submodule update
|
||||||
|
- $ pip install -e . -v
|
||||||
|
```
|
|
@ -2,13 +2,27 @@
|
||||||
if(LASP_HAS_PORTAUDIO)
|
if(LASP_HAS_PORTAUDIO)
|
||||||
message("Building with Portaudio backend")
|
message("Building with Portaudio backend")
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
|
set(PA_USE_ALSA FALSE CACHE BOOL "Build PortAudio with ALSA backend")
|
||||||
|
set(PA_USE_ASIO TRUE CACHE BOOL "Build PortAudio with ASIO backend")
|
||||||
|
set(PA_USE_DS FALSE CACHE BOOL "Build PortAudio with Directsound backend")
|
||||||
|
set(PA_USE_WMME FALSE CACHE BOOL "Build PortAudio with WMME backend")
|
||||||
|
set(PA_USE_WDMKS FALSE CACHE BOOL "Build PortAudio with WDMKS backend")
|
||||||
else()
|
else()
|
||||||
|
# Unix
|
||||||
set(PA_USE_ALSA TRUE CACHE BOOL "Build PortAudio with ALSA backend")
|
set(PA_USE_ALSA TRUE CACHE BOOL "Build PortAudio with ALSA backend")
|
||||||
set(PA_USE_JACK TRUE CACHE BOOL "Build PortAudio with Jack backend")
|
set(PA_USE_JACK FALSE CACHE BOOL "Build PortAudio with Jack backend")
|
||||||
# set(PA_ALSA_DYNAMIC FALSE CACHE BOOL "Build static library of ALSA")
|
set(PA_USE_PULSEAUDIO FALSE CACHE BOOL "Build PortAudio with PulseAudio backend")
|
||||||
set(PA_BUILD_SHARED_LIBS FALSE CACHE BOOL "Build static library")
|
set(PA_BUILD_SHARED_LIBS FALSE CACHE BOOL "Build static library")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_subdirectory(third_party/portaudio)
|
add_subdirectory(third_party/portaudio)
|
||||||
include_directories(third_party/portaudio/include)
|
include_directories(third_party/portaudio/include)
|
||||||
link_directories(third_party/portaudio)
|
link_directories(third_party/portaudio)
|
||||||
|
|
||||||
|
if(PA_USE_ALSA)
|
||||||
|
add_definitions(-DLASP_HAS_PA_ALSA=1)
|
||||||
|
else()
|
||||||
|
add_definitions(-DLASP_HAS_PA_ALSA=0)
|
||||||
|
endif()
|
||||||
|
|
||||||
endif()
|
endif()
|
||||||
|
|
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()
|
|
@ -34,7 +34,7 @@ if(LASP_HAS_RTAUDIO)
|
||||||
target_link_libraries(lasp_device_lib rtaudio)
|
target_link_libraries(lasp_device_lib rtaudio)
|
||||||
endif()
|
endif()
|
||||||
if(LASP_HAS_PORTAUDIO)
|
if(LASP_HAS_PORTAUDIO)
|
||||||
target_link_libraries(lasp_device_lib portaudio)
|
target_link_libraries(lasp_device_lib PortAudio)
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
else()
|
else()
|
||||||
target_link_libraries(lasp_device_lib asound)
|
target_link_libraries(lasp_device_lib asound)
|
|
@ -1,4 +1,4 @@
|
||||||
/* #define DEBUGTRACE_ENABLED */
|
// #define DEBUGTRACE_ENABLED
|
||||||
#include "debugtrace.hpp"
|
#include "debugtrace.hpp"
|
||||||
#include "lasp_daqconfig.h"
|
#include "lasp_daqconfig.h"
|
||||||
|
|
||||||
|
@ -44,34 +44,41 @@ Daq::Daq(const DeviceInfo &devinfo, const DaqConfiguration &config)
|
||||||
: DaqConfiguration(config), DeviceInfo(devinfo) {
|
: DaqConfiguration(config), DeviceInfo(devinfo) {
|
||||||
DEBUGTRACE_ENTER;
|
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) {
|
if(!duplexMode() && monitorOutput) {
|
||||||
throw rte("Output monitoring only allowed when running in duplex mode");
|
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) {
|
if (!hasInternalOutputMonitor && monitorOutput) {
|
||||||
throw rte(
|
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)) {
|
if (!config.match(devinfo)) {
|
||||||
throw rte("DaqConfiguration does not match device info");
|
throw rte("DaqConfiguration does not match device info");
|
||||||
}
|
}
|
||||||
if (neninchannels(false) > devinfo.ninchannels) {
|
|
||||||
throw rte(
|
{
|
||||||
"Number of enabled input channels is higher than device capability");
|
const int hich = getHighestEnabledInChannel();
|
||||||
|
if (hich + 1 > devinfo.ninchannels)
|
||||||
|
{
|
||||||
|
throw rte(
|
||||||
|
string("Highest of enabled input channel: ") +
|
||||||
|
to_string(hich) +
|
||||||
|
string(" is higher than device capability, which is: ") +
|
||||||
|
to_string(ninchannels) + ".");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (nenoutchannels() > devinfo.noutchannels) {
|
|
||||||
throw rte(
|
{
|
||||||
"Number of enabled output channels is higher than device capability");
|
const int hoch = getHighestEnabledOutChannel();
|
||||||
|
if (hoch + 1 > devinfo.noutchannels)
|
||||||
|
{
|
||||||
|
throw rte(
|
||||||
|
string("Highest of enabled output channel: ") +
|
||||||
|
to_string(hoch) +
|
||||||
|
string(" is higher than device capability, which is: ") +
|
||||||
|
to_string(noutchannels) + ".");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,10 @@ public:
|
||||||
logicError,
|
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
|
* @brief Map between error types and messages
|
||||||
*/
|
*/
|
||||||
|
@ -61,7 +65,7 @@ public:
|
||||||
{StreamError::logicError, "Logic error (probably a bug)"},
|
{StreamError::logicError, "Logic error (probably a bug)"},
|
||||||
};
|
};
|
||||||
|
|
||||||
bool isRunning = false;
|
|
||||||
/**
|
/**
|
||||||
* @brief Check if stream has error
|
* @brief Check if stream has error
|
||||||
*
|
*
|
||||||
|
@ -69,8 +73,6 @@ public:
|
||||||
*/
|
*/
|
||||||
bool error() const { return errorType != StreamError::noError; };
|
bool error() const { return errorType != StreamError::noError; };
|
||||||
|
|
||||||
StreamError errorType{StreamError::noError};
|
|
||||||
|
|
||||||
std::string errorMsg() const { return errorMessages.at(errorType); }
|
std::string errorMsg() const { return errorMessages.at(errorType); }
|
||||||
|
|
||||||
/**
|
/**
|
|
@ -35,12 +35,14 @@ DaqConfiguration::DaqConfiguration(const DeviceInfo &device) {
|
||||||
us i = 0;
|
us i = 0;
|
||||||
for (auto &inch : inchannel_config) {
|
for (auto &inch : inchannel_config) {
|
||||||
inch.name = "Unnamed input channel " + std::to_string(i);
|
inch.name = "Unnamed input channel " + std::to_string(i);
|
||||||
|
inch.rangeIndex = device.prefInputRangeIndex;
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
i = 0;
|
i = 0;
|
||||||
for (auto &outch : outchannel_config) {
|
for (auto &outch : outchannel_config) {
|
||||||
outch.name = "Unnamed output channel " + std::to_string(i);
|
outch.name = "Unnamed output channel " + std::to_string(i);
|
||||||
|
outch.rangeIndex = device.prefOutputRangeIndex;
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,10 +56,12 @@ DaqConfiguration::DaqConfiguration(const DeviceInfo &device) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DaqConfiguration::match(const DeviceInfo &dev) const {
|
bool DaqConfiguration::match(const DeviceInfo &dev) const {
|
||||||
|
DEBUGTRACE_ENTER;
|
||||||
return (dev.device_name == device_name && dev.api == api);
|
return (dev.device_name == device_name && dev.api == api);
|
||||||
}
|
}
|
||||||
|
|
||||||
int DaqConfiguration::getHighestEnabledInChannel() const {
|
int DaqConfiguration::getHighestEnabledInChannel() const {
|
||||||
|
DEBUGTRACE_ENTER;
|
||||||
for (int i = inchannel_config.size() - 1; i > -1; i--) {
|
for (int i = inchannel_config.size() - 1; i > -1; i--) {
|
||||||
if (inchannel_config.at(i).enabled)
|
if (inchannel_config.at(i).enabled)
|
||||||
return i;
|
return i;
|
||||||
|
@ -66,13 +70,15 @@ int DaqConfiguration::getHighestEnabledInChannel() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
int DaqConfiguration::getHighestEnabledOutChannel() 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)
|
if (outchannel_config.at(i).enabled)
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
int DaqConfiguration::getLowestEnabledInChannel() const {
|
int DaqConfiguration::getLowestEnabledInChannel() const {
|
||||||
|
DEBUGTRACE_ENTER;
|
||||||
for (us i = 0; i < inchannel_config.size(); i++) {
|
for (us i = 0; i < inchannel_config.size(); i++) {
|
||||||
if (inchannel_config.at(i).enabled)
|
if (inchannel_config.at(i).enabled)
|
||||||
return i;
|
return i;
|
|
@ -148,7 +148,14 @@ const DaqApi rtaudioAsioApi("RtAudio Windows ASIO", LASP_RTAUDIO_APICODE,
|
||||||
#endif
|
#endif
|
||||||
#if LASP_HAS_PORTAUDIO == 1
|
#if LASP_HAS_PORTAUDIO == 1
|
||||||
const us LASP_PORTAUDIO_APICODE = 2;
|
const us LASP_PORTAUDIO_APICODE = 2;
|
||||||
const DaqApi portaudioApi("PortAudio Linux ALSA", LASP_PORTAUDIO_APICODE, 0);
|
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
|
#endif
|
||||||
|
|
||||||
class DeviceInfo;
|
class DeviceInfo;
|
|
@ -68,14 +68,26 @@ public:
|
||||||
us prefFramesPerBlockIndex = 0;
|
us prefFramesPerBlockIndex = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Available ranges for the input, i.e. +/- 1V and/or +/- 10 V etc.
|
* @brief Available ranges for the input, i.e. +/- 1V and/or +/- 10 V etc.
|
||||||
*/
|
*/
|
||||||
dvec availableInputRanges;
|
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;
|
int prefInputRangeIndex = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Its preffered output range
|
||||||
|
*/
|
||||||
|
int prefOutputRangeIndex = 0;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The number of input channels available for the device
|
* @brief The number of input channels available for the device
|
||||||
*/
|
*/
|
||||||
|
@ -125,13 +137,29 @@ public:
|
||||||
bool duplexModeForced = false;
|
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, 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.
|
* such a Volts.
|
||||||
*/
|
*/
|
||||||
DaqChannel::Qty physicalOutputQty = DaqChannel::Qty::Number;
|
DaqChannel::Qty physicalOutputQty = DaqChannel::Qty::Number;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief String representation of DeviceInfo
|
* @brief String representation of DeviceInfo
|
||||||
*
|
*
|
|
@ -1,11 +1,11 @@
|
||||||
/* #define DEBUGTRACE_ENABLED */
|
// #define DEBUGTRACE_ENABLED
|
||||||
#include "lasp_indatahandler.h"
|
#include "lasp_indatahandler.h"
|
||||||
#include "debugtrace.hpp"
|
#include "debugtrace.hpp"
|
||||||
#include "lasp_streammgr.h"
|
#include "lasp_streammgr.h"
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
InDataHandler::InDataHandler(SmgrHandle mgr, const InCallbackType cb,
|
InDataHandler::InDataHandler(SmgrHandle mgr, const InCallbackType cb,
|
||||||
const InResetType resetfcn)
|
const ResetCallbackType resetfcn)
|
||||||
: _mgr(mgr), inCallback(cb), reset(resetfcn)
|
: _mgr(mgr), inCallback(cb), reset(resetfcn)
|
||||||
#if LASP_DEBUG == 1
|
#if LASP_DEBUG == 1
|
||||||
,
|
,
|
||||||
|
@ -29,20 +29,22 @@ void InDataHandler::start() {
|
||||||
}
|
}
|
||||||
void InDataHandler::stop() {
|
void InDataHandler::stop() {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
checkRightThread();
|
// checkRightThread();
|
||||||
#if LASP_DEBUG == 1
|
#if LASP_DEBUG == 1
|
||||||
stopCalled = true;
|
stopCalled = true;
|
||||||
#endif
|
#endif
|
||||||
if (SmgrHandle handle = _mgr.lock()) {
|
if (SmgrHandle smgr = _mgr.lock()) {
|
||||||
handle->removeInDataHandler(*this);
|
smgr->removeInDataHandler(*this);
|
||||||
|
} else {
|
||||||
|
DEBUGTRACE_PRINT("No stream manager alive anymore!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
InDataHandler::~InDataHandler() {
|
InDataHandler::~InDataHandler() {
|
||||||
|
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
checkRightThread();
|
|
||||||
#if LASP_DEBUG == 1
|
#if LASP_DEBUG == 1
|
||||||
|
// checkRightThread();
|
||||||
if (!stopCalled) {
|
if (!stopCalled) {
|
||||||
std::cerr << "************ BUG: Stop function not called while arriving at "
|
std::cerr << "************ BUG: Stop function not called while arriving at "
|
||||||
"InDataHandler's destructor. Fix this by calling "
|
"InDataHandler's destructor. Fix this by calling "
|
|
@ -22,7 +22,7 @@ using InCallbackType = std::function<void(const DaqData &)>;
|
||||||
/**
|
/**
|
||||||
* @brief Function definition for the reset callback.
|
* @brief Function definition for the reset callback.
|
||||||
*/
|
*/
|
||||||
using InResetType = std::function<void(const Daq *)>;
|
using ResetCallbackType = std::function<void(const Daq *)>;
|
||||||
|
|
||||||
class InDataHandler {
|
class InDataHandler {
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ protected:
|
||||||
public:
|
public:
|
||||||
~InDataHandler();
|
~InDataHandler();
|
||||||
const InCallbackType inCallback;
|
const InCallbackType inCallback;
|
||||||
const InResetType reset;
|
const ResetCallbackType reset;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief When constructed, the handler is added to the stream manager, which
|
* @brief When constructed, the handler is added to the stream manager, which
|
||||||
|
@ -50,7 +50,7 @@ public:
|
||||||
* changes state.
|
* changes state.
|
||||||
*/
|
*/
|
||||||
InDataHandler(SmgrHandle mgr, InCallbackType cb,
|
InDataHandler(SmgrHandle mgr, InCallbackType cb,
|
||||||
InResetType resetfcn);
|
ResetCallbackType resetfcn);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Adds the current InDataHandler to the list of handlers in the
|
* @brief Adds the current InDataHandler to the list of handlers in the
|
|
@ -17,37 +17,46 @@ using rte = std::runtime_error;
|
||||||
using std::vector;
|
using std::vector;
|
||||||
using lck = std::scoped_lock<std::mutex>;
|
using lck = std::scoped_lock<std::mutex>;
|
||||||
|
|
||||||
class RtAudioDeviceInfo : public DeviceInfo {
|
const unsigned RTAUDIO_MAX_CHANNELS = 8;
|
||||||
|
|
||||||
|
class RtAudioDeviceInfo : public DeviceInfo
|
||||||
|
{
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* @brief Specific for the device (Sub-API). Important for the RtAudio
|
* @brief Specific for the device (Sub-API). Important for the RtAudio
|
||||||
* backend, as RtAudio is able to handle different API's.
|
* backend, as RtAudio is able to handle different API's.
|
||||||
*/
|
*/
|
||||||
int _api_devindex;
|
int ID; // Copy of RtAudio::DeviceInfo::ID
|
||||||
virtual std::unique_ptr<DeviceInfo> clone() const override {
|
virtual std::unique_ptr<DeviceInfo> clone() const override
|
||||||
|
{
|
||||||
return std::make_unique<DeviceInfo>(*this);
|
return std::make_unique<DeviceInfo>(*this);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
void fillRtAudioDeviceInfo(DeviceInfoList &devinfolist) {
|
void fillRtAudioDeviceInfo(DeviceInfoList &devinfolist)
|
||||||
|
{
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
|
|
||||||
vector<RtAudio::Api> apis;
|
vector<RtAudio::Api> apis;
|
||||||
RtAudio::getCompiledApi(apis);
|
RtAudio::getCompiledApi(apis);
|
||||||
|
|
||||||
for (auto api : apis) {
|
for (auto api : apis)
|
||||||
|
{
|
||||||
RtAudio rtaudio(api);
|
RtAudio rtaudio(api);
|
||||||
us count = rtaudio.getDeviceCount();
|
const us count = rtaudio.getDeviceCount();
|
||||||
for (us devno = 0; devno < count; devno++) {
|
|
||||||
|
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"
|
// "Our device info struct"
|
||||||
RtAudioDeviceInfo d;
|
RtAudioDeviceInfo d;
|
||||||
switch (api) {
|
switch (api)
|
||||||
|
{
|
||||||
case RtAudio::LINUX_ALSA:
|
case RtAudio::LINUX_ALSA:
|
||||||
d.api = rtaudioAlsaApi;
|
d.api = rtaudioAlsaApi;
|
||||||
break;
|
break;
|
||||||
|
@ -70,42 +79,49 @@ void fillRtAudioDeviceInfo(DeviceInfoList &devinfolist) {
|
||||||
}
|
}
|
||||||
|
|
||||||
d.device_name = devinfo.name;
|
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
|
/// When 48k is available we overwrite the default sample rate with the 48
|
||||||
/// kHz value, which is our preffered rate,
|
/// kHz value, which is our preffered rate,
|
||||||
bool rate_48k_found = false;
|
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];
|
us rate_int = devinfo.sampleRates[j];
|
||||||
|
|
||||||
d.availableSampleRates.push_back((double)rate_int);
|
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;
|
d.prefSampleRateIndex = j;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rate_int == 48000) {
|
if (rate_int == 48000)
|
||||||
|
{
|
||||||
d.prefSampleRateIndex = j;
|
d.prefSampleRateIndex = j;
|
||||||
rate_48k_found = true;
|
rate_48k_found = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
d.noutchannels = devinfo.outputChannels;
|
d.noutchannels = std::min(devinfo.outputChannels, RTAUDIO_MAX_CHANNELS);
|
||||||
d.ninchannels = devinfo.inputChannels;
|
d.ninchannels = std::min(devinfo.inputChannels, RTAUDIO_MAX_CHANNELS);
|
||||||
|
|
||||||
d.availableInputRanges = {1.0};
|
d.availableInputRanges = {1.0};
|
||||||
|
d.availableOutputRanges = {1.0};
|
||||||
|
|
||||||
RtAudioFormat formats = devinfo.nativeFormats;
|
RtAudioFormat formats = devinfo.nativeFormats;
|
||||||
if (formats & RTAUDIO_SINT8) {
|
if (formats & RTAUDIO_SINT8)
|
||||||
|
{
|
||||||
d.availableDataTypes.push_back(
|
d.availableDataTypes.push_back(
|
||||||
DataTypeDescriptor::DataType::dtype_int8);
|
DataTypeDescriptor::DataType::dtype_int8);
|
||||||
}
|
}
|
||||||
if (formats & RTAUDIO_SINT16) {
|
if (formats & RTAUDIO_SINT16)
|
||||||
|
{
|
||||||
d.availableDataTypes.push_back(
|
d.availableDataTypes.push_back(
|
||||||
DataTypeDescriptor::DataType::dtype_int16);
|
DataTypeDescriptor::DataType::dtype_int16);
|
||||||
}
|
}
|
||||||
|
@ -113,15 +129,18 @@ void fillRtAudioDeviceInfo(DeviceInfoList &devinfolist) {
|
||||||
/* d.availableDataTypes.push_back(DataTypeDescriptor::DataType::dtype_int24);
|
/* d.availableDataTypes.push_back(DataTypeDescriptor::DataType::dtype_int24);
|
||||||
*/
|
*/
|
||||||
/* } */
|
/* } */
|
||||||
if (formats & RTAUDIO_SINT32) {
|
if (formats & RTAUDIO_SINT32)
|
||||||
|
{
|
||||||
d.availableDataTypes.push_back(
|
d.availableDataTypes.push_back(
|
||||||
DataTypeDescriptor::DataType::dtype_fl32);
|
DataTypeDescriptor::DataType::dtype_fl32);
|
||||||
}
|
}
|
||||||
if (formats & RTAUDIO_FLOAT64) {
|
if (formats & RTAUDIO_FLOAT64)
|
||||||
|
{
|
||||||
d.availableDataTypes.push_back(
|
d.availableDataTypes.push_back(
|
||||||
DataTypeDescriptor::DataType::dtype_fl64);
|
DataTypeDescriptor::DataType::dtype_fl64);
|
||||||
}
|
}
|
||||||
if (d.availableDataTypes.size() == 0) {
|
if (d.availableDataTypes.size() == 0)
|
||||||
|
{
|
||||||
std::cerr << "RtAudio: No data types found in device!" << endl;
|
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,
|
unsigned int nFrames, double streamTime,
|
||||||
RtAudioStreamStatus status, void *userData);
|
RtAudioStreamStatus status, void *userData);
|
||||||
|
|
||||||
static void myerrorcallback(RtAudioError::Type, const string &errorText);
|
class RtAudioDaq : public Daq
|
||||||
|
{
|
||||||
class RtAudioDaq : public Daq {
|
|
||||||
|
|
||||||
RtAudio rtaudio;
|
RtAudio rtaudio;
|
||||||
const us nFramesPerBlock;
|
const us nFramesPerBlock;
|
||||||
|
@ -181,10 +199,11 @@ public:
|
||||||
/// 0. For now, our fix is to shift out the channels we want, and let
|
/// 0. For now, our fix is to shift out the channels we want, and let
|
||||||
/// RtAudio pass on all channels.
|
/// RtAudio pass on all channels.
|
||||||
inParams->firstChannel = 0;
|
inParams->firstChannel = 0;
|
||||||
inParams->nChannels = devinfo_gen.ninchannels;
|
inParams->nChannels = devinfo.ninchannels;
|
||||||
inParams->deviceId = devinfo._api_devindex;
|
inParams->deviceId = devinfo.ID;
|
||||||
|
}
|
||||||
} else {
|
else
|
||||||
|
{
|
||||||
|
|
||||||
outParams = std::make_unique<RtAudio::StreamParameters>();
|
outParams = std::make_unique<RtAudio::StreamParameters>();
|
||||||
|
|
||||||
|
@ -192,8 +211,8 @@ public:
|
||||||
/// 0. For now, our fix is to shift out the channels we want, and let
|
/// 0. For now, our fix is to shift out the channels we want, and let
|
||||||
/// RtAudio pass on all channels.
|
/// RtAudio pass on all channels.
|
||||||
outParams->firstChannel = 0;
|
outParams->firstChannel = 0;
|
||||||
outParams->nChannels = devinfo_gen.noutchannels;
|
outParams->nChannels = devinfo.noutchannels;
|
||||||
outParams->deviceId = devinfo._api_devindex;
|
outParams->deviceId = devinfo.ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
RtAudio::StreamOptions streamoptions;
|
RtAudio::StreamOptions streamoptions;
|
||||||
|
@ -206,7 +225,8 @@ public:
|
||||||
RtAudioFormat format;
|
RtAudioFormat format;
|
||||||
using Dtype = DataTypeDescriptor::DataType;
|
using Dtype = DataTypeDescriptor::DataType;
|
||||||
const Dtype dtype = dataType();
|
const Dtype dtype = dataType();
|
||||||
switch (dtype) {
|
switch (dtype)
|
||||||
|
{
|
||||||
case Dtype::dtype_fl32:
|
case Dtype::dtype_fl32:
|
||||||
DEBUGTRACE_PRINT("Datatype float32");
|
DEBUGTRACE_PRINT("Datatype float32");
|
||||||
format = RTAUDIO_FLOAT32;
|
format = RTAUDIO_FLOAT32;
|
||||||
|
@ -237,36 +257,46 @@ public:
|
||||||
unsigned int nFramesPerBlock_copy = nFramesPerBlock;
|
unsigned int nFramesPerBlock_copy = nFramesPerBlock;
|
||||||
|
|
||||||
// Final step: open the stream.
|
// 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,
|
static_cast<us>(samplerate()), &nFramesPerBlock_copy,
|
||||||
mycallback, (void *)this, &streamoptions,
|
mycallback, (void *)this, &streamoptions);
|
||||||
&myerrorcallback);
|
if (err != RTAUDIO_NO_ERROR)
|
||||||
|
{
|
||||||
|
throw std::runtime_error(string("Error opening stream: ") + rtaudio.getErrorText());
|
||||||
|
}
|
||||||
|
|
||||||
if (nFramesPerBlock_copy != nFramesPerBlock) {
|
if (nFramesPerBlock_copy != nFramesPerBlock)
|
||||||
|
{
|
||||||
throw rte(string("Got different number of frames per block back from RtAudio "
|
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.");
|
"backend: ") +
|
||||||
|
std::to_string(nFramesPerBlock_copy) + ". I do not know what to do.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void start(InDaqCallback inCallback,
|
virtual void start(InDaqCallback inCallback,
|
||||||
OutDaqCallback outCallback) override final {
|
OutDaqCallback outCallback) override final
|
||||||
|
{
|
||||||
|
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
|
|
||||||
assert(!monitorOutput);
|
assert(!monitorOutput);
|
||||||
|
|
||||||
if (getStreamStatus().runningOK()) {
|
if (getStreamStatus().runningOK())
|
||||||
|
{
|
||||||
throw rte("Stream already running");
|
throw rte("Stream already running");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logical XOR
|
// Logical XOR
|
||||||
if (inCallback && outCallback) {
|
if (inCallback && outCallback)
|
||||||
|
{
|
||||||
throw rte("Either input or output stream possible for RtAudio. "
|
throw rte("Either input or output stream possible for RtAudio. "
|
||||||
"Stream duplex mode not provided.");
|
"Stream duplex mode not provided.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (neninchannels() > 0) {
|
if (neninchannels() > 0)
|
||||||
if (!inCallback) {
|
{
|
||||||
|
if (!inCallback)
|
||||||
|
{
|
||||||
throw rte(
|
throw rte(
|
||||||
|
|
||||||
"Input callback given, but stream does not provide input data");
|
"Input callback given, but stream does not provide input data");
|
||||||
|
@ -274,8 +304,10 @@ public:
|
||||||
|
|
||||||
_incallback = inCallback;
|
_incallback = inCallback;
|
||||||
}
|
}
|
||||||
if (nenoutchannels() > 0) {
|
if (nenoutchannels() > 0)
|
||||||
if (!outCallback) {
|
{
|
||||||
|
if (!outCallback)
|
||||||
|
{
|
||||||
throw rte(
|
throw rte(
|
||||||
"Output callback given, but stream does not provide output data");
|
"Output callback given, but stream does not provide output data");
|
||||||
}
|
}
|
||||||
|
@ -283,7 +315,11 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the stream. Throws on error.
|
// 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.
|
// If we are here, we are running without errors.
|
||||||
StreamStatus status;
|
StreamStatus status;
|
||||||
|
@ -293,10 +329,15 @@ public:
|
||||||
|
|
||||||
StreamStatus getStreamStatus() const override final { return _streamStatus; }
|
StreamStatus getStreamStatus() const override final { return _streamStatus; }
|
||||||
|
|
||||||
void stop() override final {
|
void stop() override final
|
||||||
|
{
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
if (getStreamStatus().runningOK()) {
|
if (getStreamStatus().runningOK())
|
||||||
rtaudio.stopStream();
|
{
|
||||||
|
const auto err = rtaudio.stopStream();
|
||||||
|
if(err != RTAUDIO_NO_ERROR) {
|
||||||
|
std::cerr << "Error occured while stopping the stream: " << rtaudio.getErrorText() << endl;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
StreamStatus s = _streamStatus;
|
StreamStatus s = _streamStatus;
|
||||||
s.isRunning = false;
|
s.isRunning = false;
|
||||||
|
@ -305,14 +346,16 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
int streamCallback(void *outputBuffer, void *inputBuffer,
|
int streamCallback(void *outputBuffer, void *inputBuffer,
|
||||||
unsigned int nFrames, RtAudioStreamStatus status) {
|
unsigned int nFrames, RtAudioStreamStatus status)
|
||||||
|
{
|
||||||
|
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
|
|
||||||
using se = StreamStatus::StreamError;
|
using se = StreamStatus::StreamError;
|
||||||
|
|
||||||
int rval = 0;
|
int rval = 0;
|
||||||
auto stopWithError = [&](se e) {
|
auto stopWithError = [&](se e)
|
||||||
|
{
|
||||||
DEBUGTRACE_PRINT("stopWithError");
|
DEBUGTRACE_PRINT("stopWithError");
|
||||||
StreamStatus stat = _streamStatus;
|
StreamStatus stat = _streamStatus;
|
||||||
stat.errorType = e;
|
stat.errorType = e;
|
||||||
|
@ -321,7 +364,8 @@ public:
|
||||||
rval = 1;
|
rval = 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
switch (status) {
|
switch (status)
|
||||||
|
{
|
||||||
case RTAUDIO_INPUT_OVERFLOW:
|
case RTAUDIO_INPUT_OVERFLOW:
|
||||||
stopWithError(se::inputXRun);
|
stopWithError(se::inputXRun);
|
||||||
return 1;
|
return 1;
|
||||||
|
@ -340,14 +384,16 @@ public:
|
||||||
const us neninchannels = this->neninchannels();
|
const us neninchannels = this->neninchannels();
|
||||||
const us nenoutchannels = this->nenoutchannels();
|
const us nenoutchannels = this->nenoutchannels();
|
||||||
const us sw = dtype_descr.sw;
|
const us sw = dtype_descr.sw;
|
||||||
if (nFrames != nFramesPerBlock) {
|
if (nFrames != nFramesPerBlock)
|
||||||
|
{
|
||||||
cerr << "RtAudio backend error: nFrames does not match block size!"
|
cerr << "RtAudio backend error: nFrames does not match block size!"
|
||||||
<< endl;
|
<< endl;
|
||||||
stopWithError(se::logicError);
|
stopWithError(se::logicError);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inputBuffer) {
|
if (inputBuffer)
|
||||||
|
{
|
||||||
assert(_incallback);
|
assert(_incallback);
|
||||||
std::vector<byte_t *> ptrs;
|
std::vector<byte_t *> ptrs;
|
||||||
ptrs.reserve(neninchannels);
|
ptrs.reserve(neninchannels);
|
||||||
|
@ -358,8 +404,10 @@ public:
|
||||||
assert(ch_max < ninchannels);
|
assert(ch_max < ninchannels);
|
||||||
|
|
||||||
/// Only pass on the pointers of the channels we want
|
/// Only pass on the pointers of the channels we want
|
||||||
for (us ch = ch_min; ch <= ch_max; ch++) {
|
for (us ch = ch_min; ch <= ch_max; ch++)
|
||||||
if (inchannel_config.at(ch).enabled) {
|
{
|
||||||
|
if (inchannel_config.at(ch).enabled)
|
||||||
|
{
|
||||||
byte_t *ptr =
|
byte_t *ptr =
|
||||||
static_cast<byte_t *>(inputBuffer) + sw * ch * nFramesPerBlock;
|
static_cast<byte_t *>(inputBuffer) + sw * ch * nFramesPerBlock;
|
||||||
ptrs.push_back(ptr);
|
ptrs.push_back(ptr);
|
||||||
|
@ -368,10 +416,11 @@ public:
|
||||||
DaqData d{nFramesPerBlock, neninchannels, dtype};
|
DaqData d{nFramesPerBlock, neninchannels, dtype};
|
||||||
d.copyInFromRaw(ptrs);
|
d.copyInFromRaw(ptrs);
|
||||||
|
|
||||||
_incallback(d);
|
_incallback(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (outputBuffer) {
|
if (outputBuffer)
|
||||||
|
{
|
||||||
assert(_outcallback);
|
assert(_outcallback);
|
||||||
std::vector<byte_t *> ptrs;
|
std::vector<byte_t *> ptrs;
|
||||||
ptrs.reserve(nenoutchannels);
|
ptrs.reserve(nenoutchannels);
|
||||||
|
@ -383,8 +432,10 @@ public:
|
||||||
assert(ch_min < noutchannels);
|
assert(ch_min < noutchannels);
|
||||||
assert(ch_max < noutchannels);
|
assert(ch_max < noutchannels);
|
||||||
/// Only pass on the pointers of the channels we want
|
/// Only pass on the pointers of the channels we want
|
||||||
for (us ch = ch_min; ch <= ch_max; ch++) {
|
for (us ch = ch_min; ch <= ch_max; ch++)
|
||||||
if (outchannel_config.at(ch).enabled) {
|
{
|
||||||
|
if (outchannel_config.at(ch).enabled)
|
||||||
|
{
|
||||||
ptrs.push_back(static_cast<byte_t *>(outputBuffer) +
|
ptrs.push_back(static_cast<byte_t *>(outputBuffer) +
|
||||||
sw * ch * nFramesPerBlock);
|
sw * ch * nFramesPerBlock);
|
||||||
}
|
}
|
||||||
|
@ -394,7 +445,8 @@ public:
|
||||||
_outcallback(d);
|
_outcallback(d);
|
||||||
// Copy over the buffer
|
// Copy over the buffer
|
||||||
us j = 0;
|
us j = 0;
|
||||||
for (auto ptr : ptrs) {
|
for (auto ptr : ptrs)
|
||||||
|
{
|
||||||
d.copyToRaw(j, ptr);
|
d.copyToRaw(j, ptr);
|
||||||
j++;
|
j++;
|
||||||
}
|
}
|
||||||
|
@ -410,17 +462,16 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
std::unique_ptr<Daq> createRtAudioDevice(const DeviceInfo &devinfo,
|
std::unique_ptr<Daq> createRtAudioDevice(const DeviceInfo &devinfo,
|
||||||
const DaqConfiguration &config) {
|
const DaqConfiguration &config)
|
||||||
|
{
|
||||||
return std::make_unique<RtAudioDaq>(devinfo, config);
|
return std::make_unique<RtAudioDaq>(devinfo, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
void myerrorcallback(RtAudioError::Type, const string &errorText) {
|
|
||||||
cerr << "RtAudio backend stream error: " << errorText << endl;
|
|
||||||
}
|
|
||||||
int mycallback(
|
int mycallback(
|
||||||
void *outputBuffer, void *inputBuffer, unsigned int nFrames,
|
void *outputBuffer, void *inputBuffer, unsigned int nFrames,
|
||||||
__attribute__((unused)) double streamTime, // Not used parameter streamTime
|
__attribute__((unused)) double streamTime, // Not used parameter streamTime
|
||||||
RtAudioStreamStatus status, void *userData) {
|
RtAudioStreamStatus status, void *userData)
|
||||||
|
{
|
||||||
|
|
||||||
return static_cast<RtAudioDaq *>(userData)->streamCallback(
|
return static_cast<RtAudioDaq *>(userData)->streamCallback(
|
||||||
outputBuffer, inputBuffer, nFrames, status);
|
outputBuffer, inputBuffer, nFrames, status);
|
|
@ -1,15 +1,21 @@
|
||||||
/* #define DEBUGTRACE_ENABLED */
|
// #define DEBUGTRACE_ENABLED
|
||||||
#include "lasp_streammgr.h"
|
#include "lasp_streammgr.h"
|
||||||
#include "debugtrace.hpp"
|
|
||||||
#include "lasp_biquadbank.h"
|
|
||||||
#include "lasp_indatahandler.h"
|
|
||||||
#include "lasp_thread.h"
|
|
||||||
#include <algorithm>
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#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::cerr;
|
||||||
using std::endl;
|
using std::endl;
|
||||||
|
@ -24,7 +30,7 @@ using rte = std::runtime_error;
|
||||||
std::weak_ptr<StreamMgr> _mgr;
|
std::weak_ptr<StreamMgr> _mgr;
|
||||||
std::mutex _mgr_mutex;
|
std::mutex _mgr_mutex;
|
||||||
|
|
||||||
using Lck = std::scoped_lock<std::mutex>;
|
using Lck = std::scoped_lock<std::recursive_mutex>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The only way to obtain a stream manager, can only be called from the
|
* @brief The only way to obtain a stream manager, can only be called from the
|
||||||
|
@ -35,11 +41,11 @@ using Lck = std::scoped_lock<std::mutex>;
|
||||||
SmgrHandle StreamMgr::getInstance() {
|
SmgrHandle StreamMgr::getInstance() {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
|
|
||||||
|
std::scoped_lock<std::mutex> lck(_mgr_mutex);
|
||||||
auto mgr = _mgr.lock();
|
auto mgr = _mgr.lock();
|
||||||
if (!mgr) {
|
if (!mgr) {
|
||||||
// Double Check Locking Pattern, if two threads would simultaneously
|
// Double Check Locking Pattern, if two threads would simultaneously
|
||||||
// instantiate the singleton instance.
|
// instantiate the singleton instance.
|
||||||
Lck lck(_mgr_mutex);
|
|
||||||
|
|
||||||
auto mgr = _mgr.lock();
|
auto mgr = _mgr.lock();
|
||||||
if (mgr) {
|
if (mgr) {
|
||||||
|
@ -81,44 +87,50 @@ void StreamMgr::rescanDAQDevices(bool background,
|
||||||
std::function<void()> callback) {
|
std::function<void()> callback) {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
DEBUGTRACE_PRINT(background);
|
DEBUGTRACE_PRINT(background);
|
||||||
|
if (_scanningDevices) {
|
||||||
|
throw rte("A background device scan is already busy");
|
||||||
|
}
|
||||||
|
|
||||||
|
Lck lck(_mtx);
|
||||||
checkRightThread();
|
checkRightThread();
|
||||||
if (_inputStream || _outputStream) {
|
if (_inputStream || _outputStream) {
|
||||||
throw rte("Rescanning DAQ devices only possible when no stream is running");
|
throw rte("Rescanning DAQ devices only possible when no stream is running");
|
||||||
}
|
}
|
||||||
if (!_devices_mtx.try_lock()) {
|
|
||||||
throw rte("A background DAQ device scan is probably already running");
|
|
||||||
}
|
|
||||||
_devices_mtx.unlock();
|
|
||||||
|
|
||||||
std::scoped_lock lck(_devices_mtx);
|
|
||||||
_devices.clear();
|
_devices.clear();
|
||||||
if (!background) {
|
if (!background) {
|
||||||
|
_scanningDevices = true;
|
||||||
rescanDAQDevices_impl(callback);
|
rescanDAQDevices_impl(callback);
|
||||||
} else {
|
} else {
|
||||||
DEBUGTRACE_PRINT("Rescanning DAQ devices on different thread...");
|
DEBUGTRACE_PRINT("Rescanning DAQ devices on different thread...");
|
||||||
|
_scanningDevices = true;
|
||||||
_pool.push_task(&StreamMgr::rescanDAQDevices_impl, this, callback);
|
_pool.push_task(&StreamMgr::rescanDAQDevices_impl, this, callback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void StreamMgr::rescanDAQDevices_impl(std::function<void()> callback) {
|
void StreamMgr::rescanDAQDevices_impl(std::function<void()> callback) {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
std::scoped_lock lck(_devices_mtx);
|
assert(!_inputStream && !_outputStream);
|
||||||
_devices = DeviceInfo::getDeviceInfo();
|
Lck lck(_mtx);
|
||||||
|
// Alsa spits out annoying messages that are not useful
|
||||||
|
{
|
||||||
|
|
||||||
|
_devices = DeviceInfo::getDeviceInfo();
|
||||||
|
}
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_scanningDevices = false;
|
||||||
}
|
}
|
||||||
void StreamMgr::inCallback(const DaqData &data) {
|
void StreamMgr::inCallback(const DaqData &data) {
|
||||||
|
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
|
|
||||||
std::scoped_lock<std::mutex> lck(_inDataHandler_mtx);
|
Lck lck(_mtx);
|
||||||
|
|
||||||
assert(_inputFilters.size() == data.nchannels);
|
assert(_inputFilters.size() == data.nchannels);
|
||||||
|
|
||||||
if (std::count_if(_inputFilters.cbegin(), _inputFilters.cend(),
|
if (std::count_if(_inputFilters.cbegin(), _inputFilters.cend(),
|
||||||
[](const auto &a) { return bool(a); }) > 0) {
|
[](const auto &a) { return bool(a); }) > 0) {
|
||||||
|
|
||||||
/// Found a filter in vector of input filters. So we have to apply the
|
/// Found a filter in vector of input filters. So we have to apply the
|
||||||
/// filters to each channel.
|
/// filters to each channel.
|
||||||
|
|
||||||
|
@ -138,12 +150,13 @@ void StreamMgr::inCallback(const DaqData &data) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DEBUGTRACE_PRINT("Calling incallback for handlers (filtered)...");
|
||||||
for (auto &handler : _inDataHandlers) {
|
for (auto &handler : _inDataHandlers) {
|
||||||
handler->inCallback(input_filtered);
|
handler->inCallback(input_filtered);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
/// No input filters
|
/// No input filters
|
||||||
|
DEBUGTRACE_PRINT("Calling incallback for handlers...");
|
||||||
for (auto &handler : _inDataHandlers) {
|
for (auto &handler : _inDataHandlers) {
|
||||||
handler->inCallback(data);
|
handler->inCallback(data);
|
||||||
}
|
}
|
||||||
|
@ -151,12 +164,10 @@ void StreamMgr::inCallback(const DaqData &data) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void StreamMgr::setSiggen(std::shared_ptr<Siggen> siggen) {
|
void StreamMgr::setSiggen(std::shared_ptr<Siggen> siggen) {
|
||||||
|
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
checkRightThread();
|
checkRightThread();
|
||||||
|
|
||||||
std::scoped_lock<std::mutex> lck(_siggen_mtx);
|
Lck lck(_mtx);
|
||||||
|
|
||||||
// If not set to nullptr, and a stream is running, we update the signal
|
// If not set to nullptr, and a stream is running, we update the signal
|
||||||
// generator by resetting it.
|
// generator by resetting it.
|
||||||
if (isStreamRunningOK(StreamType::output) && siggen) {
|
if (isStreamRunningOK(StreamType::output) && siggen) {
|
||||||
|
@ -179,7 +190,8 @@ void StreamMgr::setSiggen(std::shared_ptr<Siggen> siggen) {
|
||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
template <typename T> bool fillData(DaqData &data, const vd &signal) {
|
template <typename T>
|
||||||
|
bool fillData(DaqData &data, const vd &signal) {
|
||||||
/* DEBUGTRACE_ENTER; */
|
/* DEBUGTRACE_ENTER; */
|
||||||
assert(data.nframes == signal.size());
|
assert(data.nframes == signal.size());
|
||||||
|
|
||||||
|
@ -212,29 +224,28 @@ template <typename T> bool fillData(DaqData &data, const vd &signal) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
void StreamMgr::outCallback(DaqData &data) {
|
void StreamMgr::outCallback(DaqData &data) {
|
||||||
|
DEBUGTRACE_ENTER;
|
||||||
|
|
||||||
/* DEBUGTRACE_ENTER; */
|
Lck lck(_mtx);
|
||||||
|
|
||||||
std::scoped_lock<std::mutex> lck(_siggen_mtx);
|
|
||||||
|
|
||||||
if (_siggen) {
|
if (_siggen) {
|
||||||
vd signal = _siggen->genSignal(data.nframes);
|
vd signal = _siggen->genSignal(data.nframes);
|
||||||
switch (data.dtype) {
|
switch (data.dtype) {
|
||||||
case (DataTypeDescriptor::DataType::dtype_fl32):
|
case (DataTypeDescriptor::DataType::dtype_fl32):
|
||||||
fillData<float>(data, signal);
|
fillData<float>(data, signal);
|
||||||
break;
|
break;
|
||||||
case (DataTypeDescriptor::DataType::dtype_fl64):
|
case (DataTypeDescriptor::DataType::dtype_fl64):
|
||||||
fillData<double>(data, signal);
|
fillData<double>(data, signal);
|
||||||
break;
|
break;
|
||||||
case (DataTypeDescriptor::DataType::dtype_int8):
|
case (DataTypeDescriptor::DataType::dtype_int8):
|
||||||
fillData<int8_t>(data, signal);
|
fillData<int8_t>(data, signal);
|
||||||
break;
|
break;
|
||||||
case (DataTypeDescriptor::DataType::dtype_int16):
|
case (DataTypeDescriptor::DataType::dtype_int16):
|
||||||
fillData<int16_t>(data, signal);
|
fillData<int16_t>(data, signal);
|
||||||
break;
|
break;
|
||||||
case (DataTypeDescriptor::DataType::dtype_int32):
|
case (DataTypeDescriptor::DataType::dtype_int32):
|
||||||
fillData<int32_t>(data, signal);
|
fillData<int32_t>(data, signal);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Set all values to 0.
|
// Set all values to 0.
|
||||||
|
@ -244,7 +255,17 @@ void StreamMgr::outCallback(DaqData &data) {
|
||||||
|
|
||||||
StreamMgr::~StreamMgr() {
|
StreamMgr::~StreamMgr() {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
checkRightThread();
|
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
|
// Stream manager now handled by shared pointer. Each indata handler gets a
|
||||||
// shared pointer to the stream manager, and stores a weak pointer to it.
|
// shared pointer to the stream manager, and stores a weak pointer to it.
|
||||||
// Hence, we do not have to do any cleanup here. It also makes sure that the
|
// Hence, we do not have to do any cleanup here. It also makes sure that the
|
||||||
|
@ -257,31 +278,35 @@ StreamMgr::~StreamMgr() {
|
||||||
// virtual methods. This was really a bug.
|
// virtual methods. This was really a bug.
|
||||||
_inputStream.reset();
|
_inputStream.reset();
|
||||||
_outputStream.reset();
|
_outputStream.reset();
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
void StreamMgr::stopAllStreams() {
|
void StreamMgr::stopAllStreams() {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
checkRightThread();
|
{
|
||||||
|
Lck lck(_mtx);
|
||||||
|
checkRightThread();
|
||||||
|
}
|
||||||
|
// No lock here!
|
||||||
_inputStream.reset();
|
_inputStream.reset();
|
||||||
_outputStream.reset();
|
_outputStream.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void StreamMgr::startStream(const DaqConfiguration &config) {
|
void StreamMgr::startStream(const DaqConfiguration &config) {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
|
if (_scanningDevices) {
|
||||||
|
throw rte("DAQ device scan is busy. Cannot start stream.");
|
||||||
|
}
|
||||||
|
Lck lck(_mtx);
|
||||||
checkRightThread();
|
checkRightThread();
|
||||||
|
|
||||||
bool isInput = std::count_if(config.inchannel_config.cbegin(),
|
bool isInput = std::count_if(config.inchannel_config.cbegin(),
|
||||||
config.inchannel_config.cend(),
|
config.inchannel_config.cend(),
|
||||||
[](auto &i) { return i.enabled; });
|
[](auto &i) { return i.enabled; }) > 0;
|
||||||
|
|
||||||
bool isOutput = std::count_if(config.outchannel_config.cbegin(),
|
bool isOutput = std::count_if(config.outchannel_config.cbegin(),
|
||||||
config.outchannel_config.cend(),
|
config.outchannel_config.cend(),
|
||||||
[](auto &i) { return i.enabled; });
|
[](auto &i) { return i.enabled; }) > 0;
|
||||||
|
|
||||||
// Find the first device that matches with the configuration
|
// Find the first device that matches with the configuration
|
||||||
std::scoped_lock lck(_devices_mtx);
|
|
||||||
|
|
||||||
DeviceInfo *devinfo = nullptr;
|
DeviceInfo *devinfo = nullptr;
|
||||||
|
|
||||||
// Match configuration to a device in the list of devices
|
// Match configuration to a device in the list of devices
|
||||||
|
@ -302,34 +327,40 @@ void StreamMgr::startStream(const DaqConfiguration &config) {
|
||||||
bool isDuplex = isInput && isOutput;
|
bool isDuplex = isInput && isOutput;
|
||||||
|
|
||||||
if (!isInput && !isOutput) {
|
if (!isInput && !isOutput) {
|
||||||
throw rte("Neither input, nor output channels enabled for "
|
throw rte(
|
||||||
"stream. Cannot start.");
|
"Attempted stream start failed, stream does not have any enabled "
|
||||||
|
"channels. Please first enable channels in the channel configuration.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isInput && _inputStream) {
|
if (isInput && _inputStream) {
|
||||||
throw rte("Error: an input stream is already running. Please "
|
throw rte(
|
||||||
"first stop existing stream");
|
"Error: an input stream is already running. Please "
|
||||||
|
"first stop existing stream");
|
||||||
} else if (isOutput && _outputStream) {
|
} else if (isOutput && _outputStream) {
|
||||||
throw rte("Error: output stream is already running. Please "
|
throw rte(
|
||||||
"first stop existing stream");
|
"Error: output stream is already running. Please "
|
||||||
|
"first stop existing stream");
|
||||||
} else if (_inputStream) {
|
} else if (_inputStream) {
|
||||||
if (_inputStream->duplexMode() && isOutput) {
|
if (_inputStream->duplexMode() && isOutput) {
|
||||||
throw rte("Error: output stream is already running (in duplex mode). "
|
throw rte(
|
||||||
"Please "
|
"Error: output stream is already running (in duplex mode). "
|
||||||
"first stop existing stream");
|
"Please "
|
||||||
|
"first stop existing stream");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_outputStream && isInput && _outputStream->duplexModeForced &&
|
if (_outputStream && isInput && _outputStream->duplexModeForced &&
|
||||||
config.match(*_outputStream)) {
|
config.match(*_outputStream)) {
|
||||||
throw rte("This device is already opened for output. If input is also "
|
throw rte(
|
||||||
"required, please enable duplex mode for this device");
|
"This device is already opened for output. If input is also "
|
||||||
|
"required, please enable duplex mode for this device");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_inputStream && isOutput && _inputStream->duplexModeForced &&
|
if (_inputStream && isOutput && _inputStream->duplexModeForced &&
|
||||||
config.match(*_inputStream)) {
|
config.match(*_inputStream)) {
|
||||||
throw rte("This device is already opened for input. If output is also "
|
throw rte(
|
||||||
"required, please enable duplex mode for this device");
|
"This device is already opened for input. If output is also "
|
||||||
|
"required, please enable duplex mode for this device");
|
||||||
}
|
}
|
||||||
|
|
||||||
InDaqCallback inCallback;
|
InDaqCallback inCallback;
|
||||||
|
@ -352,13 +383,13 @@ void StreamMgr::startStream(const DaqConfiguration &config) {
|
||||||
d fs = daq->samplerate();
|
d fs = daq->samplerate();
|
||||||
/// Create input filters
|
/// Create input filters
|
||||||
_inputFilters.clear();
|
_inputFilters.clear();
|
||||||
|
|
||||||
/// No input filter for monitor channel, which comes as the first input channel
|
/// No input filter for monitor channel, which comes as the first input
|
||||||
/// In the list
|
/// channel In the list
|
||||||
if (config.monitorOutput && devinfo->hasInternalOutputMonitor) {
|
if (config.monitorOutput && devinfo->hasInternalOutputMonitor) {
|
||||||
_inputFilters.push_back(nullptr);
|
_inputFilters.push_back(nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto &ch : daq->inchannel_config) {
|
for (auto &ch : daq->inchannel_config) {
|
||||||
if (ch.enabled) {
|
if (ch.enabled) {
|
||||||
if (ch.digitalHighPassCutOn < 0) {
|
if (ch.digitalHighPassCutOn < 0) {
|
||||||
|
@ -371,7 +402,7 @@ void StreamMgr::startStream(const DaqConfiguration &config) {
|
||||||
SeriesBiquad::firstOrderHighPass(fs, ch.digitalHighPassCutOn)));
|
SeriesBiquad::firstOrderHighPass(fs, ch.digitalHighPassCutOn)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} // End of input filter creation
|
} // End of input filter creation
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isOutput) {
|
if (isOutput) {
|
||||||
|
@ -398,46 +429,58 @@ void StreamMgr::startStream(const DaqConfiguration &config) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void StreamMgr::stopStream(const StreamType t) {
|
void StreamMgr::stopStream(const StreamType t) {
|
||||||
|
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
checkRightThread();
|
checkRightThread();
|
||||||
|
bool resetHandlers = false;
|
||||||
|
std::unique_ptr<Daq> *streamToStop = nullptr;
|
||||||
|
|
||||||
if (t == StreamType::input) {
|
{ // Mutex locked in this scope
|
||||||
if (!_inputStream) {
|
Lck lck(_mtx);
|
||||||
throw rte("Input stream is not running");
|
if (t == StreamType::input) {
|
||||||
|
if (!_inputStream) {
|
||||||
|
throw rte("Input stream is not running");
|
||||||
|
}
|
||||||
|
streamToStop = std::addressof(_inputStream);
|
||||||
|
resetHandlers = true;
|
||||||
|
} else {
|
||||||
|
/// t == output
|
||||||
|
/// Kill input stream in case that one is a duplex stream
|
||||||
|
if (_inputStream && _inputStream->duplexMode()) {
|
||||||
|
streamToStop = std::addressof(_inputStream);
|
||||||
|
} else {
|
||||||
|
if (!_outputStream) {
|
||||||
|
throw rte("Output stream is not running");
|
||||||
|
}
|
||||||
|
streamToStop = std::addressof(_outputStream);
|
||||||
|
} // end else
|
||||||
}
|
}
|
||||||
/// Kills input stream
|
} // End of mutex lock. When stopping stream, mutex should be unlocked.
|
||||||
_inputStream.reset();
|
|
||||||
/// Send reset to all in data handlers
|
// 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) {
|
for (auto &handler : _inDataHandlers) {
|
||||||
handler->reset(nullptr);
|
handler->reset(nullptr);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
/// t == output
|
|
||||||
|
|
||||||
/// Kill input stream in case that one is a duplex stream
|
|
||||||
if (_inputStream && _inputStream->duplexMode()) {
|
|
||||||
_inputStream.reset();
|
|
||||||
} else {
|
|
||||||
if (!_outputStream) {
|
|
||||||
throw rte("Output stream is not running");
|
|
||||||
}
|
|
||||||
_outputStream.reset();
|
|
||||||
} // end else
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void StreamMgr::addInDataHandler(InDataHandler *handler) {
|
void StreamMgr::addInDataHandler(InDataHandler *handler) {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
|
Lck lck(_mtx);
|
||||||
checkRightThread();
|
checkRightThread();
|
||||||
assert(handler);
|
assert(handler);
|
||||||
std::scoped_lock<std::mutex> lck(_inDataHandler_mtx);
|
|
||||||
handler->reset(_inputStream.get());
|
handler->reset(_inputStream.get());
|
||||||
|
|
||||||
if (std::find(_inDataHandlers.cbegin(), _inDataHandlers.cend(), handler) !=
|
if (std::find(_inDataHandlers.cbegin(), _inDataHandlers.cend(), handler) !=
|
||||||
_inDataHandlers.cend()) {
|
_inDataHandlers.cend()) {
|
||||||
throw std::runtime_error("Error: handler already added. Probably start() "
|
throw std::runtime_error(
|
||||||
"is called more than once on a handler object");
|
"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());
|
DEBUGTRACE_PRINT(_inDataHandlers.size());
|
||||||
|
@ -445,16 +488,17 @@ void StreamMgr::addInDataHandler(InDataHandler *handler) {
|
||||||
|
|
||||||
void StreamMgr::removeInDataHandler(InDataHandler &handler) {
|
void StreamMgr::removeInDataHandler(InDataHandler &handler) {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
checkRightThread();
|
Lck lck(_mtx);
|
||||||
std::scoped_lock<std::mutex> lck(_inDataHandler_mtx);
|
// checkRightThread();
|
||||||
_inDataHandlers.remove(&handler);
|
_inDataHandlers.remove(&handler);
|
||||||
|
|
||||||
DEBUGTRACE_PRINT(_inDataHandlers.size());
|
DEBUGTRACE_PRINT(_inDataHandlers.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
Daq::StreamStatus StreamMgr::getStreamStatus(const StreamType type) const {
|
Daq::StreamStatus StreamMgr::getStreamStatus(const StreamType type) const {
|
||||||
/* DEBUGTRACE_ENTER; */
|
DEBUGTRACE_ENTER;
|
||||||
|
|
||||||
|
Lck lck(_mtx);
|
||||||
checkRightThread();
|
checkRightThread();
|
||||||
// Default constructor, says stream is not running, but also no errors
|
// Default constructor, says stream is not running, but also no errors
|
||||||
|
|
||||||
|
@ -467,7 +511,7 @@ Daq::StreamStatus StreamMgr::getStreamStatus(const StreamType type) const {
|
||||||
}
|
}
|
||||||
|
|
||||||
const Daq *StreamMgr::getDaq(StreamType type) const {
|
const Daq *StreamMgr::getDaq(StreamType type) const {
|
||||||
|
Lck lck(_mtx);
|
||||||
checkRightThread();
|
checkRightThread();
|
||||||
|
|
||||||
if (type == StreamType::input) {
|
if (type == StreamType::input) {
|
|
@ -1,19 +1,19 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "lasp_daq.h"
|
|
||||||
#include "lasp_siggen.h"
|
|
||||||
#include "lasp_thread.h"
|
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
|
#include "lasp_daq.h"
|
||||||
|
#include "lasp_siggen.h"
|
||||||
|
#include "lasp_thread.h"
|
||||||
|
|
||||||
/** \addtogroup device
|
/** \addtogroup device
|
||||||
* @{
|
* @{
|
||||||
*/
|
*/
|
||||||
class StreamMgr;
|
class StreamMgr;
|
||||||
class InDataHandler;
|
class InDataHandler;
|
||||||
|
|
||||||
|
|
||||||
class SeriesBiquad;
|
class SeriesBiquad;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -25,12 +25,15 @@ class SeriesBiquad;
|
||||||
* fact is asserted.
|
* fact is asserted.
|
||||||
*/
|
*/
|
||||||
class StreamMgr {
|
class StreamMgr {
|
||||||
|
mutable std::recursive_mutex _mtx;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Storage for streams.
|
* @brief Storage for streams.
|
||||||
*/
|
*/
|
||||||
std::unique_ptr<Daq> _inputStream, _outputStream;
|
std::unique_ptr<Daq> _inputStream, _outputStream;
|
||||||
|
|
||||||
|
std::atomic<bool> _scanningDevices{false};
|
||||||
|
|
||||||
GlobalThreadPool _pool;
|
GlobalThreadPool _pool;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -39,22 +42,18 @@ class StreamMgr {
|
||||||
* thread-safety.
|
* thread-safety.
|
||||||
*/
|
*/
|
||||||
std::list<InDataHandler *> _inDataHandlers;
|
std::list<InDataHandler *> _inDataHandlers;
|
||||||
mutable std::mutex _inDataHandler_mtx;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Signal generator in use to generate output data. Currently
|
* @brief Signal generator in use to generate output data. Currently
|
||||||
* implemented as to generate the same data for all output channels.
|
* implemented as to generate the same data for all output channels.
|
||||||
*/
|
*/
|
||||||
std::shared_ptr<Siggen> _siggen;
|
std::shared_ptr<Siggen> _siggen;
|
||||||
std::mutex _siggen_mtx;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Filters on input stream. For example, a digital high pass filter.
|
* @brief Filters on input stream. For example, a digital high pass filter.
|
||||||
*/
|
*/
|
||||||
std::vector<std::unique_ptr<SeriesBiquad>> _inputFilters;
|
std::vector<std::unique_ptr<SeriesBiquad>> _inputFilters;
|
||||||
|
|
||||||
|
|
||||||
mutable std::recursive_mutex _devices_mtx;
|
|
||||||
/**
|
/**
|
||||||
* @brief Current storage for the device list
|
* @brief Current storage for the device list
|
||||||
*/
|
*/
|
||||||
|
@ -67,9 +66,7 @@ class StreamMgr {
|
||||||
friend class InDataHandler;
|
friend class InDataHandler;
|
||||||
friend class Siggen;
|
friend class Siggen;
|
||||||
|
|
||||||
|
public:
|
||||||
public:
|
|
||||||
|
|
||||||
~StreamMgr();
|
~StreamMgr();
|
||||||
|
|
||||||
enum class StreamType : us {
|
enum class StreamType : us {
|
||||||
|
@ -100,9 +97,10 @@ class StreamMgr {
|
||||||
* @return A copy of the internal stored list of devices
|
* @return A copy of the internal stored list of devices
|
||||||
*/
|
*/
|
||||||
DeviceInfoList getDeviceInfo() const {
|
DeviceInfoList getDeviceInfo() const {
|
||||||
std::scoped_lock lck(_devices_mtx);
|
std::scoped_lock lck(_mtx);
|
||||||
DeviceInfoList d2;
|
DeviceInfoList d2;
|
||||||
for(const auto& dev: _devices) {
|
for (const auto &dev : _devices) {
|
||||||
|
assert(dev != nullptr);
|
||||||
d2.push_back(dev->clone());
|
d2.push_back(dev->clone());
|
||||||
}
|
}
|
||||||
return d2;
|
return d2;
|
||||||
|
@ -118,9 +116,9 @@ class StreamMgr {
|
||||||
* set to true, the function returns immediately.
|
* set to true, the function returns immediately.
|
||||||
* @param callback Function to call when complete.
|
* @param callback Function to call when complete.
|
||||||
*/
|
*/
|
||||||
void
|
void rescanDAQDevices(
|
||||||
rescanDAQDevices(bool background = false,
|
bool background = false,
|
||||||
std::function<void()> callback = std::function<void()>());
|
std::function<void()> callback = std::function<void()>());
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Start a stream based on given configuration.
|
* @brief Start a stream based on given configuration.
|
||||||
|
@ -141,12 +139,12 @@ class StreamMgr {
|
||||||
}
|
}
|
||||||
bool isStreamRunning(const StreamType type) const {
|
bool isStreamRunning(const StreamType type) const {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case (StreamType::input):
|
case (StreamType::input):
|
||||||
return bool(_inputStream);
|
return bool(_inputStream);
|
||||||
break;
|
break;
|
||||||
case (StreamType::output):
|
case (StreamType::output):
|
||||||
return bool(_outputStream);
|
return bool(_outputStream);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -193,11 +191,10 @@ class StreamMgr {
|
||||||
*/
|
*/
|
||||||
void setSiggen(std::shared_ptr<Siggen> s);
|
void setSiggen(std::shared_ptr<Siggen> s);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void inCallback(const DaqData &data);
|
void inCallback(const DaqData &data);
|
||||||
void outCallback(DaqData &data);
|
void outCallback(DaqData &data);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Add an input data handler. The handler's inCallback() function is
|
* @brief Add an input data handler. The handler's inCallback() function is
|
||||||
* called with data when available. This function should *NOT* be called by
|
* called with data when available. This function should *NOT* be called by
|
|
@ -68,6 +68,7 @@ void fillUlDaqDeviceInfo(DeviceInfoList &devinfolist) {
|
||||||
}
|
}
|
||||||
|
|
||||||
devinfo.physicalOutputQty = DaqChannel::Qty::Voltage;
|
devinfo.physicalOutputQty = DaqChannel::Qty::Voltage;
|
||||||
|
devinfo.physicalInputQty = DaqChannel::Qty::Voltage;
|
||||||
|
|
||||||
devinfo.availableDataTypes.push_back(
|
devinfo.availableDataTypes.push_back(
|
||||||
DataTypeDescriptor::DataType::dtype_fl64);
|
DataTypeDescriptor::DataType::dtype_fl64);
|
||||||
|
@ -79,7 +80,9 @@ void fillUlDaqDeviceInfo(DeviceInfoList &devinfolist) {
|
||||||
devinfo.availableFramesPerBlock = {512, 1024, 2048, 4096, 8192};
|
devinfo.availableFramesPerBlock = {512, 1024, 2048, 4096, 8192};
|
||||||
|
|
||||||
devinfo.availableInputRanges = {1.0, 10.0};
|
devinfo.availableInputRanges = {1.0, 10.0};
|
||||||
|
devinfo.availableOutputRanges = {10.0};
|
||||||
devinfo.prefInputRangeIndex = 0;
|
devinfo.prefInputRangeIndex = 0;
|
||||||
|
devinfo.prefOutputRangeIndex = 0;
|
||||||
|
|
||||||
devinfo.ninchannels = 4;
|
devinfo.ninchannels = 4;
|
||||||
devinfo.noutchannels = 1;
|
devinfo.noutchannels = 1;
|
||||||
|
@ -90,6 +93,7 @@ void fillUlDaqDeviceInfo(DeviceInfoList &devinfolist) {
|
||||||
|
|
||||||
devinfo.hasInternalOutputMonitor = true;
|
devinfo.hasInternalOutputMonitor = true;
|
||||||
|
|
||||||
|
devinfo.hasDuplexMode = true;
|
||||||
devinfo.duplexModeForced = true;
|
devinfo.duplexModeForced = true;
|
||||||
|
|
||||||
// Finally, this devinfo is pushed back in list
|
// Finally, this devinfo is pushed back in list
|
|
@ -1,20 +1,48 @@
|
||||||
/* #define DEBUGTRACE_ENABLED */
|
// #define DEBUGTRACE_ENABLED
|
||||||
#include "debugtrace.hpp"
|
#include "debugtrace.hpp"
|
||||||
#include "lasp_config.h"
|
#include "lasp_config.h"
|
||||||
|
|
||||||
#if LASP_HAS_PORTAUDIO == 1
|
#if LASP_HAS_PORTAUDIO == 1
|
||||||
#include "lasp_portaudiodaq.h"
|
|
||||||
#include "portaudio.h"
|
|
||||||
#include <gsl-lite/gsl-lite.hpp>
|
#include <gsl-lite/gsl-lite.hpp>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include "lasp_portaudiodaq.h"
|
||||||
|
#include "portaudio.h"
|
||||||
|
|
||||||
using rte = std::runtime_error;
|
using rte = std::runtime_error;
|
||||||
using std::cerr;
|
using std::cerr;
|
||||||
using std::endl;
|
using std::endl;
|
||||||
using std::string;
|
using std::string;
|
||||||
using std::to_string;
|
using std::to_string;
|
||||||
|
|
||||||
|
#if LASP_HAS_PA_ALSA
|
||||||
|
#include <alsa/asoundlib.h>
|
||||||
|
void empty_handler(const char *file, int line, const char *function, int err,
|
||||||
|
const char *fmt, ...) {
|
||||||
|
|
||||||
|
// cerr << "Test empty error handler...\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Temporarily set the ALSA eror handler to something that does nothing, to
|
||||||
|
// prevent ALSA from spitting out all kinds of misconfiguration errors.
|
||||||
|
class MuteErrHandler {
|
||||||
|
private:
|
||||||
|
snd_lib_error_handler_t _default_handler;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit MuteErrHandler() {
|
||||||
|
_default_handler = snd_lib_error;
|
||||||
|
snd_lib_error_set_handler(empty_handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
~MuteErrHandler() { snd_lib_error_set_handler(_default_handler); }
|
||||||
|
};
|
||||||
|
#else
|
||||||
|
// Does nothin in case of no ALSA
|
||||||
|
class MuteErrHandler {};
|
||||||
|
#endif
|
||||||
|
|
||||||
inline void throwIfError(PaError e) {
|
inline void throwIfError(PaError e) {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
if (e != paNoError) {
|
if (e != paNoError) {
|
||||||
|
@ -26,7 +54,7 @@ inline void throwIfError(PaError e) {
|
||||||
* @brief Device info, plus PortAudio stuff
|
* @brief Device info, plus PortAudio stuff
|
||||||
*/
|
*/
|
||||||
class OurPaDeviceInfo : public DeviceInfo {
|
class OurPaDeviceInfo : public DeviceInfo {
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* @brief Store instance to PaDeviceInfo.
|
* @brief Store instance to PaDeviceInfo.
|
||||||
*/
|
*/
|
||||||
|
@ -35,17 +63,22 @@ public:
|
||||||
virtual std::unique_ptr<DeviceInfo> clone() const override final {
|
virtual std::unique_ptr<DeviceInfo> clone() const override final {
|
||||||
return std::make_unique<OurPaDeviceInfo>(*this);
|
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) {
|
void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist) {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
|
bool shouldPaTerminate = false;
|
||||||
|
MuteErrHandler guard;
|
||||||
try {
|
try {
|
||||||
|
|
||||||
PaError err = Pa_Initialize();
|
PaError err = Pa_Initialize();
|
||||||
/// PortAudio says that Pa_Terminate() should not be called whenever there
|
/// PortAudio says that Pa_Terminate() should not be called whenever there
|
||||||
/// is an error in Pa_Initialize(). This is opposite to what most examples
|
/// is an error in Pa_Initialize(). This is opposite to what most examples
|
||||||
/// of PortAudio show.
|
/// of PortAudio show.
|
||||||
throwIfError(err);
|
throwIfError(err);
|
||||||
|
shouldPaTerminate = true;
|
||||||
|
|
||||||
auto fin = gsl::finally([&err] {
|
auto fin = gsl::finally([&err] {
|
||||||
DEBUGTRACE_PRINT("Terminating PortAudio instance");
|
DEBUGTRACE_PRINT("Terminating PortAudio instance");
|
||||||
|
@ -55,6 +88,10 @@ void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const PaHostApiIndex apicount = Pa_GetHostApiCount();
|
||||||
|
if (apicount < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
/* const PaDeviceInfo *deviceInfo; */
|
/* const PaDeviceInfo *deviceInfo; */
|
||||||
const int numDevices = Pa_GetDeviceCount();
|
const int numDevices = Pa_GetDeviceCount();
|
||||||
if (numDevices < 0) {
|
if (numDevices < 0) {
|
||||||
|
@ -62,16 +99,51 @@ void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist) {
|
||||||
}
|
}
|
||||||
for (us i = 0; i < (us)numDevices; i++) {
|
for (us i = 0; i < (us)numDevices; i++) {
|
||||||
/* DEBUGTRACE_PRINT(i); */
|
/* DEBUGTRACE_PRINT(i); */
|
||||||
|
bool hasDuplexMode = false;
|
||||||
|
|
||||||
const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(i);
|
const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(i);
|
||||||
if (!deviceInfo) {
|
if (!deviceInfo) {
|
||||||
throw rte("No device info struct returned");
|
throw rte("No device info struct returned");
|
||||||
}
|
}
|
||||||
OurPaDeviceInfo d;
|
OurPaDeviceInfo d(*deviceInfo);
|
||||||
d._paDevInfo = *deviceInfo;
|
// We store the name in d.device_name
|
||||||
d.api = portaudioApi;
|
d._paDevInfo.name = nullptr;
|
||||||
d.device_name = deviceInfo->name;
|
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,
|
d.availableDataTypes = {DataTypeDescriptor::DataType::dtype_int16,
|
||||||
DataTypeDescriptor::DataType::dtype_int32,
|
DataTypeDescriptor::DataType::dtype_int32,
|
||||||
DataTypeDescriptor::DataType::dtype_fl32};
|
DataTypeDescriptor::DataType::dtype_fl32};
|
||||||
|
@ -87,15 +159,27 @@ void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist) {
|
||||||
d.prefFramesPerBlockIndex = 2;
|
d.prefFramesPerBlockIndex = 2;
|
||||||
|
|
||||||
d.availableInputRanges = {1.0};
|
d.availableInputRanges = {1.0};
|
||||||
|
// d.prefInputRangeIndex = 0; // Constructor-defined
|
||||||
|
d.availableOutputRanges = {1.0};
|
||||||
|
// d.prefOutputRangeIndex = 0; // Constructor-defined
|
||||||
|
|
||||||
d.ninchannels = deviceInfo->maxInputChannels;
|
d.ninchannels = deviceInfo->maxInputChannels;
|
||||||
d.noutchannels = deviceInfo->maxOutputChannels;
|
d.noutchannels = deviceInfo->maxOutputChannels;
|
||||||
|
|
||||||
|
// Duplex mode, only for ALSA devices
|
||||||
|
d.hasDuplexMode = hasDuplexMode;
|
||||||
|
|
||||||
devinfolist.push_back(std::make_unique<OurPaDeviceInfo>(d));
|
devinfolist.push_back(std::make_unique<OurPaDeviceInfo>(d));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (rte &e) {
|
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;
|
cerr << "PortAudio backend error: " << e.what() << std::endl;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -121,14 +205,13 @@ static int rawPaCallback(const void *inputBuffer, void *outputBuffer,
|
||||||
PaStreamCallbackFlags statusFlags, void *userData);
|
PaStreamCallbackFlags statusFlags, void *userData);
|
||||||
|
|
||||||
class PortAudioDaq : public Daq {
|
class PortAudioDaq : public Daq {
|
||||||
bool _shouldPaTerminate = false;
|
|
||||||
PaStream *_stream = nullptr;
|
PaStream *_stream = nullptr;
|
||||||
std::atomic<StreamStatus::StreamError> _streamError =
|
std::atomic<StreamStatus::StreamError> _streamError =
|
||||||
StreamStatus::StreamError::noError;
|
StreamStatus::StreamError::noError;
|
||||||
InDaqCallback _incallback;
|
InDaqCallback _incallback;
|
||||||
OutDaqCallback _outcallback;
|
OutDaqCallback _outcallback;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PortAudioDaq(const OurPaDeviceInfo &devinfo_gen,
|
PortAudioDaq(const OurPaDeviceInfo &devinfo_gen,
|
||||||
const DaqConfiguration &config);
|
const DaqConfiguration &config);
|
||||||
|
|
||||||
|
@ -158,7 +241,7 @@ public:
|
||||||
|
|
||||||
std::unique_ptr<Daq> createPortAudioDevice(const DeviceInfo &devinfo,
|
std::unique_ptr<Daq> createPortAudioDevice(const DeviceInfo &devinfo,
|
||||||
const DaqConfiguration &config) {
|
const DaqConfiguration &config) {
|
||||||
|
DEBUGTRACE_ENTER;
|
||||||
const OurPaDeviceInfo *_info =
|
const OurPaDeviceInfo *_info =
|
||||||
dynamic_cast<const OurPaDeviceInfo *>(&devinfo);
|
dynamic_cast<const OurPaDeviceInfo *>(&devinfo);
|
||||||
if (_info == nullptr) {
|
if (_info == nullptr) {
|
||||||
|
@ -178,119 +261,127 @@ static int rawPaCallback(const void *inputBuffer, void *outputBuffer,
|
||||||
PortAudioDaq::PortAudioDaq(const OurPaDeviceInfo &devinfo_gen,
|
PortAudioDaq::PortAudioDaq(const OurPaDeviceInfo &devinfo_gen,
|
||||||
const DaqConfiguration &config)
|
const DaqConfiguration &config)
|
||||||
: Daq(devinfo_gen, config) {
|
: Daq(devinfo_gen, config) {
|
||||||
|
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
PaError err = Pa_Initialize();
|
bool shouldPaTerminate = false;
|
||||||
/// PortAudio says that Pa_Terminate() should not be called whenever there
|
try {
|
||||||
/// is an error in Pa_Initialize(). This is opposite to what most examples
|
PaError err = Pa_Initialize();
|
||||||
/// of PortAudio show.
|
/// PortAudio says that Pa_Terminate() should not be called whenever there
|
||||||
throwIfError(err);
|
/// 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
|
// OK, Pa_Initialize successfully finished, it means we have to clean up
|
||||||
// Pa_Terminate in the destructor.
|
// with Pa_Terminate in the destructor.
|
||||||
_shouldPaTerminate = true;
|
shouldPaTerminate = true;
|
||||||
|
|
||||||
// Going to find the device in the list. If its there, we have to retrieve
|
// 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
|
// the index, as this is required in the PaStreamParameters struct
|
||||||
int devindex = -1;
|
int devindex = -1;
|
||||||
for (int i = 0; i < Pa_GetDeviceCount(); i++) {
|
for (int i = 0; i < Pa_GetDeviceCount(); i++) {
|
||||||
bool ok = true;
|
// DEBUGTRACE_PRINT(i);
|
||||||
const PaDeviceInfo *info = Pa_GetDeviceInfo(i);
|
bool ok = true;
|
||||||
if (!info) {
|
const PaDeviceInfo *info = Pa_GetDeviceInfo(i);
|
||||||
throw rte("No device structure returned from PortAudio");
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ok &= string(info->name) == devinfo_gen._paDevInfo.name;
|
if (devindex < 0) {
|
||||||
ok &= info->hostApi == devinfo_gen._paDevInfo.hostApi;
|
throw rte(string("Device not found: ") + string(devinfo_gen.device_name));
|
||||||
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;
|
using Dtype = DataTypeDescriptor::DataType;
|
||||||
const Dtype dtype = dataType();
|
const Dtype dtype = dataType();
|
||||||
// Sample format is bit flag
|
// Sample format is bit flag
|
||||||
PaSampleFormat format = paNonInterleaved;
|
PaSampleFormat format = paNonInterleaved;
|
||||||
switch (dtype) {
|
switch (dtype) {
|
||||||
case Dtype::dtype_fl32:
|
case Dtype::dtype_fl32:
|
||||||
DEBUGTRACE_PRINT("Datatype float32");
|
DEBUGTRACE_PRINT("Datatype float32");
|
||||||
format |= paFloat32;
|
format |= paFloat32;
|
||||||
break;
|
break;
|
||||||
case Dtype::dtype_fl64:
|
case Dtype::dtype_fl64:
|
||||||
DEBUGTRACE_PRINT("Datatype float64");
|
DEBUGTRACE_PRINT("Datatype float64");
|
||||||
throw rte("Invalid data type specified for DAQ stream.");
|
throw rte("Invalid data type specified for DAQ stream.");
|
||||||
break;
|
break;
|
||||||
case Dtype::dtype_int8:
|
case Dtype::dtype_int8:
|
||||||
DEBUGTRACE_PRINT("Datatype int8");
|
DEBUGTRACE_PRINT("Datatype int8");
|
||||||
format |= paInt8;
|
format |= paInt8;
|
||||||
break;
|
break;
|
||||||
case Dtype::dtype_int16:
|
case Dtype::dtype_int16:
|
||||||
DEBUGTRACE_PRINT("Datatype int16");
|
DEBUGTRACE_PRINT("Datatype int16");
|
||||||
format |= paInt16;
|
format |= paInt16;
|
||||||
break;
|
break;
|
||||||
case Dtype::dtype_int32:
|
case Dtype::dtype_int32:
|
||||||
DEBUGTRACE_PRINT("Datatype int32");
|
DEBUGTRACE_PRINT("Datatype int32");
|
||||||
format |= paInt32;
|
format |= paInt32;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw rte("Invalid data type specified for DAQ stream.");
|
throw rte("Invalid data type specified for DAQ stream.");
|
||||||
break;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<PaStreamParameters> instreamParams;
|
|
||||||
std::unique_ptr<PaStreamParameters> outstreamParams;
|
|
||||||
|
|
||||||
if (neninchannels() > 0) {
|
|
||||||
instreamParams = std::make_unique<PaStreamParameters>(
|
|
||||||
PaStreamParameters({.device = devindex,
|
|
||||||
.channelCount = (int)neninchannels(),
|
|
||||||
.sampleFormat = format,
|
|
||||||
.suggestedLatency = framesPerBlock() / samplerate(),
|
|
||||||
.hostApiSpecificStreamInfo = nullptr}));
|
|
||||||
}
|
|
||||||
if (nenoutchannels() > 0) {
|
|
||||||
outstreamParams = std::make_unique<PaStreamParameters>(
|
|
||||||
PaStreamParameters({.device = devindex,
|
|
||||||
.channelCount = (int)nenoutchannels(),
|
|
||||||
.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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PortAudioDaq::start(InDaqCallback inCallback, OutDaqCallback outCallback) {
|
void PortAudioDaq::start(InDaqCallback inCallback, OutDaqCallback outCallback) {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
assert(_stream);
|
assert(_stream);
|
||||||
|
MuteErrHandler guard;
|
||||||
|
|
||||||
if (Pa_IsStreamActive(_stream)) {
|
if (Pa_IsStreamActive(_stream)) {
|
||||||
throw rte("Stream is already running");
|
throw rte("Stream is already running");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logical XOR
|
|
||||||
if (inCallback && outCallback) {
|
|
||||||
throw rte("Either input or output stream possible for RtAudio. "
|
|
||||||
"Stream duplex mode not provided.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (neninchannels() > 0) {
|
if (neninchannels() > 0) {
|
||||||
if (!inCallback) {
|
if (!inCallback) {
|
||||||
throw rte(
|
throw rte(
|
||||||
|
@ -314,52 +405,77 @@ void PortAudioDaq::start(InDaqCallback inCallback, OutDaqCallback outCallback) {
|
||||||
void PortAudioDaq::stop() {
|
void PortAudioDaq::stop() {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
assert(_stream);
|
assert(_stream);
|
||||||
if (Pa_IsStreamStopped(_stream)) {
|
if (Pa_IsStreamStopped(_stream) > 1) {
|
||||||
throw rte("Stream is already stopped");
|
throw rte("Stream is already stopped");
|
||||||
}
|
}
|
||||||
PaError err = Pa_StopStream(_stream);
|
PaError err = Pa_StopStream(_stream);
|
||||||
throwIfError(err);
|
throwIfError(err);
|
||||||
}
|
}
|
||||||
Daq::StreamStatus PortAudioDaq::getStreamStatus() const {
|
Daq::StreamStatus PortAudioDaq::getStreamStatus() const {
|
||||||
|
DEBUGTRACE_ENTER;
|
||||||
|
// Stores an error type and whether the
|
||||||
Daq::StreamStatus status;
|
Daq::StreamStatus status;
|
||||||
// Copy over atomic flag.
|
using StreamError = Daq::StreamStatus::StreamError;
|
||||||
status.errorType = _streamError;
|
Daq::StreamStatus::StreamError errortype = _streamError.load();
|
||||||
// Check if stream is still running.
|
|
||||||
if (_stream) {
|
PaError err = Pa_IsStreamStopped(_stream);
|
||||||
if (Pa_IsStreamActive(_stream)) {
|
if (err > 1) {
|
||||||
status.isRunning = true;
|
// 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;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
PortAudioDaq::~PortAudioDaq() {
|
PortAudioDaq::~PortAudioDaq() {
|
||||||
|
DEBUGTRACE_ENTER;
|
||||||
PaError err;
|
PaError err;
|
||||||
if (_stream) {
|
assert(_stream);
|
||||||
if (Pa_IsStreamActive(_stream)) {
|
if (Pa_IsStreamActive(_stream)) {
|
||||||
stop();
|
// 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;
|
|
||||||
}
|
|
||||||
assert(_shouldPaTerminate);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_shouldPaTerminate) {
|
err = Pa_CloseStream(_stream);
|
||||||
err = Pa_Terminate();
|
_stream = nullptr;
|
||||||
if (err != paNoError) {
|
if (err != paNoError) {
|
||||||
cerr << "Error terminating PortAudio. Do not know what to do." << endl;
|
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,
|
int PortAudioDaq::memberPaCallback(const void *inputBuffer, void *outputBuffer,
|
||||||
unsigned long framesPerBuffer,
|
unsigned long framesPerBuffer,
|
||||||
const PaStreamCallbackTimeInfo *timeInfo,
|
const PaStreamCallbackTimeInfo *timeInfo,
|
||||||
PaStreamCallbackFlags statusFlags) {
|
PaStreamCallbackFlags statusFlags) {
|
||||||
|
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
typedef Daq::StreamStatus::StreamError se;
|
typedef Daq::StreamStatus::StreamError se;
|
||||||
if (statusFlags & paPrimingOutput) {
|
if (statusFlags & paPrimingOutput) {
|
|
@ -16,6 +16,7 @@ set(lasp_dsp_files
|
||||||
lasp_threadedindatahandler.cpp
|
lasp_threadedindatahandler.cpp
|
||||||
lasp_ppm.cpp
|
lasp_ppm.cpp
|
||||||
lasp_clip.cpp
|
lasp_clip.cpp
|
||||||
|
lasp_freqsmooth.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -2,12 +2,9 @@
|
||||||
#include "lasp_avpowerspectra.h"
|
#include "lasp_avpowerspectra.h"
|
||||||
#include "debugtrace.hpp"
|
#include "debugtrace.hpp"
|
||||||
#include "lasp_mathtypes.h"
|
#include "lasp_mathtypes.h"
|
||||||
#include <cmath>
|
#include <stdexcept>
|
||||||
#include <optional>
|
|
||||||
|
|
||||||
using rte = std::runtime_error;
|
using rte = std::runtime_error;
|
||||||
using std::cerr;
|
|
||||||
using std::endl;
|
|
||||||
|
|
||||||
PowerSpectra::PowerSpectra(const us nfft, const Window::WindowType w)
|
PowerSpectra::PowerSpectra(const us nfft, const Window::WindowType w)
|
||||||
: PowerSpectra(Window::create(w, nfft)) {}
|
: PowerSpectra(Window::create(w, nfft)) {}
|
|
@ -3,8 +3,6 @@
|
||||||
#include "lasp_mathtypes.h"
|
#include "lasp_mathtypes.h"
|
||||||
#include "lasp_timebuffer.h"
|
#include "lasp_timebuffer.h"
|
||||||
#include "lasp_window.h"
|
#include "lasp_window.h"
|
||||||
#include <memory>
|
|
||||||
#include <optional>
|
|
||||||
|
|
||||||
/** \defgroup dsp Digital Signal Processing utilities
|
/** \defgroup dsp Digital Signal Processing utilities
|
||||||
* These are classes and functions used for processing raw signal data, to
|
* These are classes and functions used for processing raw signal data, to
|
|
@ -2,7 +2,6 @@
|
||||||
#include "lasp_biquadbank.h"
|
#include "lasp_biquadbank.h"
|
||||||
#include "debugtrace.hpp"
|
#include "debugtrace.hpp"
|
||||||
#include "lasp_thread.h"
|
#include "lasp_thread.h"
|
||||||
#include <cassert>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
using std::cerr;
|
using std::cerr;
|
133
cpp_src/dsp/lasp_freqsmooth.cpp
Normal file
133
cpp_src/dsp/lasp_freqsmooth.cpp
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
// #define DEBUGTRACE_ENABLED
|
||||||
|
#include "lasp_freqsmooth.h"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
#include "debugtrace.hpp"
|
||||||
|
|
||||||
|
using rte = std::runtime_error;
|
||||||
|
|
||||||
|
vd freqSmooth(const vd& freq, const vd& X, const unsigned w,
|
||||||
|
bool power_correct) {
|
||||||
|
DEBUGTRACE_ENTER;
|
||||||
|
if (freq.size() < 2) {
|
||||||
|
throw rte("Invalid frequency size. Should be > 2");
|
||||||
|
}
|
||||||
|
if (freq.size() != X.size()) {
|
||||||
|
throw rte("Sizes of freq and X do not match");
|
||||||
|
}
|
||||||
|
if (freq.size() > std::numeric_limits<long>::max() / 2) {
|
||||||
|
throw rte("Frequency size limit for smoothing is 2^30");
|
||||||
|
}
|
||||||
|
if (w == 0) {
|
||||||
|
throw rte("Invalid number of octaves");
|
||||||
|
}
|
||||||
|
const us Nfreq = freq.size();
|
||||||
|
|
||||||
|
// Smoothing width in unit of number of octaves
|
||||||
|
const d Delta = 1 / d(w);
|
||||||
|
|
||||||
|
// Minimum frequency and maximum frequency to smooth on (frequency range that
|
||||||
|
// is interpolated to a log scale)
|
||||||
|
d freq_min;
|
||||||
|
const d freq_max = freq(Nfreq - 1);
|
||||||
|
const bool firstFreqEqZero = (d_abs(freq(0)) < 1e-15);
|
||||||
|
|
||||||
|
// AC-signal power
|
||||||
|
d ac_pwr;
|
||||||
|
if (firstFreqEqZero) {
|
||||||
|
freq_min = freq(1);
|
||||||
|
if (power_correct) {
|
||||||
|
ac_pwr = arma::sum(X.subvec(1, Nfreq - 1));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
freq_min = freq(0);
|
||||||
|
if (power_correct) {
|
||||||
|
ac_pwr = arma::sum(X);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DEBUGTRACE_PRINT(freq_min);
|
||||||
|
DEBUGTRACE_PRINT(freq_max);
|
||||||
|
const vd freq_log =
|
||||||
|
arma::logspace(d_log10(freq_min), d_log10(freq_max), 10 * Nfreq);
|
||||||
|
DEBUGTRACE_PRINT("freq_log = ");
|
||||||
|
|
||||||
|
const long Nfreq_sm = freq_log.size();
|
||||||
|
|
||||||
|
// Interpolate X to logscale
|
||||||
|
vd X_log;
|
||||||
|
DEBUGTRACE_PRINT("X_log = :");
|
||||||
|
arma::interp1(freq, X, freq_log, X_log, "*linear");
|
||||||
|
|
||||||
|
// First and last point are not interpolated well, could be minimally out of
|
||||||
|
// the interpolation range, due to roundoff errors. Armadillo sets these
|
||||||
|
// points to nan, so we have to manually "interpolate" them.
|
||||||
|
X_log(Nfreq_sm - 1) = X(X.size() - 1);
|
||||||
|
if (firstFreqEqZero) {
|
||||||
|
X_log(0) = X(1);
|
||||||
|
} else {
|
||||||
|
X_log(0) = X(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate space for smoothed X on log scale
|
||||||
|
vd Xsm_log(freq_log.size());
|
||||||
|
const d beta = d_log10(Nfreq_sm) / d_log10(2) / (Nfreq_sm - 1);
|
||||||
|
// int rounds down
|
||||||
|
const long mu = int(Delta / d(2) / beta);
|
||||||
|
DEBUGTRACE_PRINT(mu);
|
||||||
|
|
||||||
|
// Long is at least 32 bits. So +/- 2M points length
|
||||||
|
for (long k = 0; k < Nfreq_sm; k++) {
|
||||||
|
// const d fcur = freq_log(k);
|
||||||
|
long idx_start = std::max(k - mu, 0l);
|
||||||
|
long idx_stop = std::min(k + mu, Nfreq_sm - 1);
|
||||||
|
|
||||||
|
// Make window smaller at the sides (close to the end of the array)
|
||||||
|
if (idx_start == 0 || idx_stop == Nfreq_sm - 1) {
|
||||||
|
const long mu_edge = std::min(k - idx_start, idx_stop - k);
|
||||||
|
idx_start = k - mu_edge;
|
||||||
|
idx_stop = k + mu_edge;
|
||||||
|
}
|
||||||
|
assert(idx_stop < Nfreq_sm);
|
||||||
|
assert(idx_start < Nfreq_sm);
|
||||||
|
|
||||||
|
DEBUGTRACE_PRINT(idx_start)
|
||||||
|
DEBUGTRACE_PRINT(idx_stop);
|
||||||
|
|
||||||
|
Xsm_log(k) = arma::mean(X_log.subvec(idx_start, idx_stop));
|
||||||
|
}
|
||||||
|
DEBUGTRACE_PRINT("Xsm_log:");
|
||||||
|
// std::cerr << Xsm_log << std::endl;
|
||||||
|
|
||||||
|
// Back-interpolate to a linear scale, and be wary of nans at the start end
|
||||||
|
// and range. Also interpolates power
|
||||||
|
vd Xsm(Nfreq);
|
||||||
|
if (firstFreqEqZero) {
|
||||||
|
vd Xsm_gt0;
|
||||||
|
arma::interp1(freq_log, Xsm_log, freq.subvec(1, Nfreq - 1), Xsm_gt0,
|
||||||
|
"*linear");
|
||||||
|
Xsm(0) = X(0);
|
||||||
|
Xsm.subvec(1, Nfreq - 1) = Xsm_gt0;
|
||||||
|
Xsm(1) = Xsm_log(1);
|
||||||
|
Xsm(Nfreq - 1) = Xsm_log(Nfreq_sm - 1);
|
||||||
|
|
||||||
|
// Final step: power-correct smoothed spectrum
|
||||||
|
if (power_correct) {
|
||||||
|
d new_acpwr = arma::sum(Xsm.subvec(1, Nfreq - 1));
|
||||||
|
Xsm.subvec(1, Nfreq - 1) *= ac_pwr / new_acpwr;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
arma::interp1(freq_log, Xsm_log, freq, Xsm, "*linear");
|
||||||
|
Xsm(0) = X(0);
|
||||||
|
Xsm(Nfreq - 1) = Xsm_log(Nfreq_sm - 1);
|
||||||
|
|
||||||
|
// Final step: power-correct smoothed spectrum
|
||||||
|
if (power_correct) {
|
||||||
|
d new_acpwr = arma::sum(Xsm);
|
||||||
|
Xsm *= ac_pwr / new_acpwr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Xsm;
|
||||||
|
}
|
28
cpp_src/dsp/lasp_freqsmooth.h
Normal file
28
cpp_src/dsp/lasp_freqsmooth.h
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
#pragma once
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "lasp_mathtypes.h"
|
||||||
|
#include "lasp_types.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \addtogroup dsp
|
||||||
|
* @{
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Apply frequency domain smoothing to a Frequency domain (single
|
||||||
|
* sided)signal power spectrum
|
||||||
|
*
|
||||||
|
* @param freq Frequency range
|
||||||
|
* @param X Signal pwr
|
||||||
|
* @param w Parameter determining the smoothing with. 1 = 1/1 octave, 3 = 1/3th
|
||||||
|
* octave and so on
|
||||||
|
* @param power_correct Apply a correction to the whole spectrum to make the
|
||||||
|
* signal power equal to the unsmoothed signal power.
|
||||||
|
* @return vd Smoothed spectrum
|
||||||
|
*/
|
||||||
|
vd freqSmooth(const vd& freq, const vd& X, const unsigned w,
|
||||||
|
bool power_correct = false);
|
||||||
|
|
||||||
|
/** @} */
|
|
@ -37,7 +37,7 @@ void RtAps::inCallback(const DaqData &data) {
|
||||||
cerr << "**** Error: sensitivity size does not match! *****" << endl;
|
cerr << "**** Error: sensitivity size does not match! *****" << endl;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fltdata.each_row() %= _sens.as_row();
|
fltdata.each_row() /= _sens.as_row();
|
||||||
|
|
||||||
if (_filterPrototype) {
|
if (_filterPrototype) {
|
||||||
|
|
|
@ -4,12 +4,9 @@
|
||||||
//
|
//
|
||||||
// Description: Real Time Signal Viewer.
|
// Description: Real Time Signal Viewer.
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "lasp_avpowerspectra.h"
|
|
||||||
#include "lasp_filter.h"
|
|
||||||
#include "lasp_mathtypes.h"
|
#include "lasp_mathtypes.h"
|
||||||
#include "lasp_threadedindatahandler.h"
|
#include "lasp_threadedindatahandler.h"
|
||||||
#include "lasp_timebuffer.h"
|
#include "lasp_timebuffer.h"
|
||||||
#include <memory>
|
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
||||||
/**
|
/**
|
|
@ -10,31 +10,62 @@ using rte = std::runtime_error;
|
||||||
|
|
||||||
inline d level_amp(d level_dB) { return pow(10, level_dB / 20); }
|
inline d level_amp(d level_dB) { return pow(10, level_dB / 20); }
|
||||||
|
|
||||||
using mutexlock = std::scoped_lock<std::mutex>;
|
using slock = std::scoped_lock<std::recursive_mutex>;
|
||||||
|
|
||||||
vd Siggen::genSignal(const us nframes) {
|
vd Siggen::genSignal(const us nframes) {
|
||||||
|
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
mutexlock lck(_mtx);
|
slock lck(_mtx);
|
||||||
|
|
||||||
DEBUGTRACE_PRINT(nframes);
|
DEBUGTRACE_PRINT(nframes);
|
||||||
vd signal(nframes, arma::fill::value(_dc_offset));
|
vd signal(nframes, arma::fill::value(_dc_offset));
|
||||||
|
|
||||||
if (!_muted) {
|
if (!_muted) {
|
||||||
vd signal_dynamic = _level_linear * genSignalUnscaled(nframes);
|
vd signal_dynamic = _level_linear * genSignalUnscaled(nframes);
|
||||||
|
|
||||||
|
// Filter signal
|
||||||
for (auto f : _filters) {
|
for (auto f : _filters) {
|
||||||
assert(f.second);
|
assert(f.second);
|
||||||
f.second->filter(signal_dynamic);
|
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;
|
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,
|
void Siggen::setFilter(const std::string &name,
|
||||||
std::shared_ptr<Filter> filter) {
|
std::shared_ptr<Filter> filter) {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
mutexlock lck(_mtx);
|
slock lck(_mtx);
|
||||||
if (filter) {
|
if (filter) {
|
||||||
_filters[name] = filter;
|
_filters[name] = filter;
|
||||||
} else if (_filters.find(name) != _filters.end()) {
|
} else if (_filters.find(name) != _filters.end()) {
|
||||||
|
@ -43,21 +74,22 @@ void Siggen::setFilter(const std::string &name,
|
||||||
}
|
}
|
||||||
void Siggen::setDCOffset(const d offset) {
|
void Siggen::setDCOffset(const d offset) {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
mutexlock lck(_mtx);
|
slock lck(_mtx);
|
||||||
_dc_offset = offset;
|
_dc_offset = offset;
|
||||||
}
|
}
|
||||||
void Siggen::setLevel(const d level, bool dB) {
|
void Siggen::setLevel(const d level, bool dB) {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
mutexlock lck(_mtx);
|
slock lck(_mtx);
|
||||||
_level_linear = dB ? level_amp(level) : level;
|
_level_linear = dB ? level_amp(level) : level;
|
||||||
}
|
}
|
||||||
void Siggen::reset(const d newFs) {
|
void Siggen::reset(const d newFs) {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
mutexlock lck(_mtx);
|
slock lck(_mtx);
|
||||||
_fs = newFs;
|
_fs = newFs;
|
||||||
for (auto &f : _filters) {
|
for (auto &f : _filters) {
|
||||||
assert(f.second);
|
assert(f.second);
|
||||||
f.second->reset();
|
f.second->reset();
|
||||||
}
|
}
|
||||||
|
_interruption_frame_count = 0;
|
||||||
resetImpl();
|
resetImpl();
|
||||||
}
|
}
|
|
@ -27,8 +27,15 @@ private:
|
||||||
bool _muted = false;
|
bool _muted = false;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::mutex _mtx;
|
mutable std::recursive_mutex _mtx;
|
||||||
d _fs = 0;
|
d _fs = 0;
|
||||||
|
/**
|
||||||
|
* @brief Interuption of period the signal. If set, the signal will be
|
||||||
|
* 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 void resetImpl() = 0;
|
||||||
virtual vd genSignalUnscaled(const us nframes) = 0;
|
virtual vd genSignalUnscaled(const us nframes) = 0;
|
||||||
|
@ -36,6 +43,15 @@ protected:
|
||||||
public:
|
public:
|
||||||
virtual ~Siggen() = default;
|
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
|
* @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
|
* otherwise to shape the spectrum. Filters are stored in a map, and
|
||||||
|
@ -59,7 +75,7 @@ public:
|
||||||
*
|
*
|
||||||
* @param mute if tre
|
* @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
|
* @brief Set the level of the signal generator
|
|
@ -7,11 +7,14 @@
|
||||||
//////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////
|
||||||
/* #define DEBUGTRACE_ENABLED */
|
/* #define DEBUGTRACE_ENABLED */
|
||||||
#include "lasp_siggen_impl.h"
|
#include "lasp_siggen_impl.h"
|
||||||
#include "debugtrace.hpp"
|
|
||||||
#include "lasp_mathtypes.h"
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
|
||||||
|
#include "debugtrace.hpp"
|
||||||
|
#include "lasp_mathtypes.h"
|
||||||
|
|
||||||
using rte = std::runtime_error;
|
using rte = std::runtime_error;
|
||||||
|
using slock = std::scoped_lock<std::recursive_mutex>;
|
||||||
|
|
||||||
DEBUGTRACE_VARIABLES;
|
DEBUGTRACE_VARIABLES;
|
||||||
|
|
||||||
|
@ -30,6 +33,7 @@ Sine::Sine(const d freq) : omg(2 * arma::datum::pi * freq) { DEBUGTRACE_ENTER; }
|
||||||
|
|
||||||
vd Sine::genSignalUnscaled(const us nframes) {
|
vd Sine::genSignalUnscaled(const us nframes) {
|
||||||
/* DEBUGTRACE_ENTER; */
|
/* DEBUGTRACE_ENTER; */
|
||||||
|
slock lck(_mtx);
|
||||||
const d pi = arma::datum::pi;
|
const d pi = arma::datum::pi;
|
||||||
vd phase_vec =
|
vd phase_vec =
|
||||||
arma::linspace(phase, phase + omg * (nframes - 1) / _fs, nframes);
|
arma::linspace(phase, phase + omg * (nframes - 1) / _fs, nframes);
|
||||||
|
@ -41,8 +45,8 @@ vd Sine::genSignalUnscaled(const us nframes) {
|
||||||
}
|
}
|
||||||
|
|
||||||
vd Periodic::genSignalUnscaled(const us nframes) {
|
vd Periodic::genSignalUnscaled(const us nframes) {
|
||||||
|
|
||||||
vd res(nframes);
|
vd res(nframes);
|
||||||
|
slock lck(_mtx);
|
||||||
if (_signal.size() == 0) {
|
if (_signal.size() == 0) {
|
||||||
throw rte("No signal defined while calling");
|
throw rte("No signal defined while calling");
|
||||||
}
|
}
|
||||||
|
@ -74,15 +78,15 @@ Sweep::Sweep(const d fl, const d fu, const d Ts, const d Tq, const us flags)
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sweep::resetImpl() {
|
void Sweep::resetImpl() {
|
||||||
|
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
|
slock lck(_mtx);
|
||||||
|
|
||||||
_cur_pos = 0;
|
_cur_pos = 0;
|
||||||
|
|
||||||
bool forward_sweep = flags & ForwardSweep;
|
bool forward_sweep = flags & ForwardSweep;
|
||||||
bool backward_sweep = flags & BackwardSweep;
|
bool backward_sweep = flags & BackwardSweep;
|
||||||
|
|
||||||
const d Dt = 1 / _fs; // Deltat
|
const d Dt = 1 / _fs; // Deltat
|
||||||
|
|
||||||
// Estimate N, the number of samples in the sweep part (non-quiescent part):
|
// Estimate N, the number of samples in the sweep part (non-quiescent part):
|
||||||
const us Ns = (us)(Ts * _fs);
|
const us Ns = (us)(Ts * _fs);
|
||||||
|
@ -166,7 +170,6 @@ void Sweep::resetImpl() {
|
||||||
/* dVARTRACE(15, phase); */
|
/* dVARTRACE(15, phase); */
|
||||||
}
|
}
|
||||||
} else if (flags & LogSweep) {
|
} else if (flags & LogSweep) {
|
||||||
|
|
||||||
DEBUGTRACE_PRINT("Log sweep");
|
DEBUGTRACE_PRINT("Log sweep");
|
||||||
if (forward_sweep || backward_sweep) {
|
if (forward_sweep || backward_sweep) {
|
||||||
/* Forward or backward sweep */
|
/* Forward or backward sweep */
|
||||||
|
@ -194,7 +197,6 @@ void Sweep::resetImpl() {
|
||||||
phase += 2 * number_pi * Dt * fn;
|
phase += 2 * number_pi * Dt * fn;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
DEBUGTRACE_PRINT("Continuous sweep");
|
DEBUGTRACE_PRINT("Continuous sweep");
|
||||||
|
|
||||||
const us Nf = Ns / 2;
|
const us Nf = Ns / 2;
|
||||||
|
@ -249,17 +251,15 @@ void Sweep::resetImpl() {
|
||||||
/* dbgassert(fn >= 0, "BUG"); */
|
/* dbgassert(fn >= 0, "BUG"); */
|
||||||
|
|
||||||
phase += 2 * number_pi * Dt * fn;
|
phase += 2 * number_pi * Dt * fn;
|
||||||
while (phase > 2 * number_pi)
|
while (phase > 2 * number_pi) phase -= 2 * number_pi;
|
||||||
phase -= 2 * number_pi;
|
|
||||||
/* dVARTRACE(17, phase); */
|
/* dVARTRACE(17, phase); */
|
||||||
}
|
}
|
||||||
/* This should be a very small number!! */
|
/* This should be a very small number!! */
|
||||||
DEBUGTRACE_PRINT(phase);
|
DEBUGTRACE_PRINT(phase);
|
||||||
}
|
}
|
||||||
} // End of log sweep
|
} // End of log sweep
|
||||||
else {
|
else {
|
||||||
// Either log or linear sweep had to be given as flags.
|
// Either log or linear sweep had to be given as flags.
|
||||||
assert(false);
|
assert(false);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -7,7 +7,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \addtogroup siggen
|
* \addtogroup siggen
|
||||||
* @{
|
* @{
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -18,8 +18,8 @@ class Noise : public Siggen {
|
||||||
d level_linear;
|
d level_linear;
|
||||||
virtual vd genSignalUnscaled(const us nframes) override;
|
virtual vd genSignalUnscaled(const us nframes) override;
|
||||||
void resetImpl() override;
|
void resetImpl() override;
|
||||||
public:
|
|
||||||
|
|
||||||
|
public:
|
||||||
/**
|
/**
|
||||||
* @brief Constructs a noise generator. If no filter is used, the output will
|
* @brief Constructs a noise generator. If no filter is used, the output will
|
||||||
* be white noise. By default, the output will be standard deviation = 1
|
* be white noise. By default, the output will be standard deviation = 1
|
||||||
|
@ -28,7 +28,6 @@ class Noise : public Siggen {
|
||||||
*/
|
*/
|
||||||
Noise();
|
Noise();
|
||||||
~Noise() = default;
|
~Noise() = default;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -37,13 +36,12 @@ class Noise : public Siggen {
|
||||||
class Sine : public Siggen {
|
class Sine : public Siggen {
|
||||||
d phase = 0;
|
d phase = 0;
|
||||||
d omg;
|
d omg;
|
||||||
protected:
|
|
||||||
|
|
||||||
void resetImpl() override final { phase=0; }
|
protected:
|
||||||
|
void resetImpl() override final { phase = 0; }
|
||||||
virtual vd genSignalUnscaled(const us nframes) override final;
|
virtual vd genSignalUnscaled(const us nframes) override final;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Create a sine wave generator
|
* @brief Create a sine wave generator
|
||||||
*
|
*
|
||||||
|
@ -51,7 +49,7 @@ class Sine : public Siggen {
|
||||||
*/
|
*/
|
||||||
Sine(const d freq_Hz);
|
Sine(const d freq_Hz);
|
||||||
~Sine() = default;
|
~Sine() = default;
|
||||||
void setFreq(const d newFreq) { omg = 2*arma::datum::pi*newFreq; } ;
|
void setFreq(const d newFreq) { omg = 2 * arma::datum::pi * newFreq; };
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -86,8 +84,7 @@ class Sweep : public Periodic {
|
||||||
|
|
||||||
void resetImpl() override;
|
void resetImpl() override;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
static constexpr int ForwardSweep = 1 << 0;
|
static constexpr int ForwardSweep = 1 << 0;
|
||||||
static constexpr int BackwardSweep = 1 << 1;
|
static constexpr int BackwardSweep = 1 << 1;
|
||||||
static constexpr int LinearSweep = 1 << 2;
|
static constexpr int LinearSweep = 1 << 2;
|
||||||
|
@ -103,11 +100,11 @@ class Sweep : public Periodic {
|
||||||
* avoid temporal aliasing in case of measuring impulse responses.
|
* avoid temporal aliasing in case of measuring impulse responses.
|
||||||
* @param[in] sweep_flags: Sweep period [s]
|
* @param[in] sweep_flags: Sweep period [s]
|
||||||
*/
|
*/
|
||||||
Sweep(const d fl, const d fu, const d Ts, const d Tq,
|
Sweep(const d fl, const d fu, const d Ts, const d Tq, const us sweep_flags);
|
||||||
const us sweep_flags);
|
|
||||||
|
|
||||||
~Sweep() = default;
|
~Sweep() = default;
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
/** @} */
|
/** @} */
|
||||||
/** @} */
|
/** @} */
|
|
@ -1,13 +1,15 @@
|
||||||
/* #define DEBUGTRACE_ENABLED */
|
// #define DEBUGTRACE_ENABLED
|
||||||
#include "lasp_threadedindatahandler.h"
|
#include "lasp_threadedindatahandler.h"
|
||||||
#include "debugtrace.hpp"
|
|
||||||
#include "lasp_daqdata.h"
|
|
||||||
#include "lasp_thread.h"
|
|
||||||
#include <future>
|
#include <future>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
|
#include "debugtrace.hpp"
|
||||||
|
#include "lasp_daqdata.h"
|
||||||
|
#include "lasp_thread.h"
|
||||||
|
|
||||||
using namespace std::literals::chrono_literals;
|
using namespace std::literals::chrono_literals;
|
||||||
using lck = std::scoped_lock<std::mutex>;
|
using lck = std::scoped_lock<std::mutex>;
|
||||||
using rte = std::runtime_error;
|
using rte = std::runtime_error;
|
||||||
|
@ -20,26 +22,26 @@ class SafeQueue {
|
||||||
std::mutex _mtx;
|
std::mutex _mtx;
|
||||||
std::atomic<uint32_t> _contents{0};
|
std::atomic<uint32_t> _contents{0};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void push(const DaqData &d) {
|
void push(const DaqData &d) {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
lck lock(_mtx);
|
lck lock(_mtx);
|
||||||
_queue.push(d);
|
_queue.push(d);
|
||||||
_contents++;
|
_contents++;
|
||||||
assert(_contents == _queue.size());
|
assert(_contents == _queue.size());
|
||||||
}
|
}
|
||||||
DaqData pop() {
|
DaqData pop() {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
if (empty()) {
|
if (empty()) {
|
||||||
throw rte("BUG: Pop on empty queue");
|
throw rte("BUG: Pop on empty queue");
|
||||||
}
|
}
|
||||||
lck lock(_mtx);
|
lck lock(_mtx);
|
||||||
|
|
||||||
/* DaqData d(std::move(_queue.front())); */
|
/* DaqData d(std::move(_queue.front())); */
|
||||||
DaqData d(_queue.front());
|
DaqData d(_queue.front());
|
||||||
_queue.pop();
|
_queue.pop();
|
||||||
_contents--;
|
_contents--;
|
||||||
assert(_contents == _queue.size());
|
assert(_contents == _queue.size());
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
@ -52,58 +54,75 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
ThreadedInDataHandlerBase::ThreadedInDataHandlerBase(SmgrHandle mgr,
|
ThreadedInDataHandlerBase::ThreadedInDataHandlerBase(SmgrHandle mgr,
|
||||||
InCallbackType cb,
|
InCallbackType cb,
|
||||||
InResetType reset)
|
ResetCallbackType reset)
|
||||||
: _indatahandler(
|
: _queue(std::make_unique<SafeQueue>()),
|
||||||
mgr,
|
inCallback(cb),
|
||||||
std::bind(&ThreadedInDataHandlerBase::_inCallbackFromInDataHandler, this,
|
resetCallback(reset),
|
||||||
_1),
|
_smgr(mgr) {
|
||||||
reset),
|
|
||||||
_queue(std::make_unique<SafeQueue>()), inCallback(cb) {
|
|
||||||
|
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
|
|
||||||
}
|
}
|
||||||
void ThreadedInDataHandlerBase::startThread() {
|
void ThreadedInDataHandlerBase::startThread() {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
_thread_can_safely_run = true;
|
if (_indatahandler) {
|
||||||
_indatahandler.start();
|
throw rte("BUG: ThreadedIndataHandler already started");
|
||||||
|
}
|
||||||
|
SmgrHandle smgr = _smgr.lock();
|
||||||
|
if (!smgr) {
|
||||||
|
cerr << "Stream manager destructed" << endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_indatahandler = std::make_unique<InDataHandler>(
|
||||||
|
smgr,
|
||||||
|
std::bind(&ThreadedInDataHandlerBase::_inCallbackFromInDataHandler, this,
|
||||||
|
_1),
|
||||||
|
resetCallback);
|
||||||
|
|
||||||
|
_thread_allowed_to_run = true;
|
||||||
|
_indatahandler->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ThreadedInDataHandlerBase::_inCallbackFromInDataHandler(
|
void ThreadedInDataHandlerBase::_inCallbackFromInDataHandler(
|
||||||
const DaqData &daqdata) {
|
const DaqData &daqdata) {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
std::scoped_lock lck(_mtx);
|
|
||||||
|
|
||||||
// Early return in case object is under DESTRUCTION
|
// Early return in case object is under DESTRUCTION
|
||||||
if (!_thread_can_safely_run)
|
if (!_thread_allowed_to_run) return;
|
||||||
return;
|
|
||||||
|
|
||||||
_queue->push(daqdata);
|
_queue->push(daqdata);
|
||||||
if (!_thread_running) {
|
if (!_thread_running) {
|
||||||
DEBUGTRACE_PRINT("Pushing new thread in pool");
|
DEBUGTRACE_PRINT("Pushing new thread in pool");
|
||||||
|
_thread_running = true;
|
||||||
_pool.push_task(&ThreadedInDataHandlerBase::threadFcn, this);
|
_pool.push_task(&ThreadedInDataHandlerBase::threadFcn, this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ThreadedInDataHandlerBase::stopThread() {
|
void ThreadedInDataHandlerBase::stopThread() {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
// Make sure inCallback is no longer called
|
if (!_indatahandler) {
|
||||||
_thread_can_safely_run = false;
|
throw rte("BUG: ThreadedIndataHandler not running");
|
||||||
_indatahandler.stop();
|
}
|
||||||
|
|
||||||
std::scoped_lock lck(_mtx);
|
// Stop the existing thread
|
||||||
|
_thread_allowed_to_run = false;
|
||||||
|
|
||||||
|
// Make sure no new data arrives
|
||||||
|
_indatahandler->stop();
|
||||||
|
_indatahandler.reset();
|
||||||
|
|
||||||
|
DEBUGTRACE_PRINT("Indatahandler stopped. Waiting for thread to finish...");
|
||||||
// Then wait in steps for the thread to stop running.
|
// Then wait in steps for the thread to stop running.
|
||||||
while (_thread_running) {
|
while (_thread_running) {
|
||||||
std::this_thread::sleep_for(10us);
|
std::this_thread::sleep_for(10us);
|
||||||
}
|
}
|
||||||
|
DEBUGTRACE_PRINT("Thread stopped");
|
||||||
|
// Kill the handler
|
||||||
|
DEBUGTRACE_PRINT("Handler resetted");
|
||||||
}
|
}
|
||||||
|
|
||||||
ThreadedInDataHandlerBase::~ThreadedInDataHandlerBase() {
|
ThreadedInDataHandlerBase::~ThreadedInDataHandlerBase() {
|
||||||
|
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
if (_thread_can_safely_run) {
|
if (_thread_allowed_to_run) {
|
||||||
stopThread();
|
stopThread();
|
||||||
cerr << "*** BUG: InDataHandlers have not been all stopped, while "
|
cerr << "*** BUG: InDataHandlers have not been all stopped, while "
|
||||||
"StreamMgr destructor is called. This is a misuse BUG."
|
"StreamMgr destructor is called. This is a misuse BUG."
|
||||||
|
@ -113,12 +132,9 @@ ThreadedInDataHandlerBase::~ThreadedInDataHandlerBase() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ThreadedInDataHandlerBase::threadFcn() {
|
void ThreadedInDataHandlerBase::threadFcn() {
|
||||||
|
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
_thread_running = true;
|
|
||||||
|
|
||||||
while (!_queue->empty() && _thread_can_safely_run) {
|
|
||||||
|
|
||||||
|
while (!_queue->empty() && _thread_allowed_to_run) {
|
||||||
// Call inCallback_threaded
|
// Call inCallback_threaded
|
||||||
inCallback(_queue->pop());
|
inCallback(_queue->pop());
|
||||||
}
|
}
|
|
@ -29,21 +29,27 @@ class ThreadedInDataHandlerBase {
|
||||||
* @brief The queue used to push elements to the handling thread.
|
* @brief The queue used to push elements to the handling thread.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
InDataHandler _indatahandler;
|
|
||||||
std::unique_ptr<SafeQueue> _queue;
|
std::unique_ptr<SafeQueue> _queue;
|
||||||
|
|
||||||
mutable std::recursive_mutex _mtx;
|
|
||||||
|
|
||||||
std::atomic<bool> _thread_running{false};
|
|
||||||
std::atomic<bool> _thread_can_safely_run{false};
|
|
||||||
|
|
||||||
GlobalThreadPool _pool;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Function pointer that is called when new DaqData arrives.
|
* @brief Function pointer that is called when new DaqData arrives.
|
||||||
*/
|
*/
|
||||||
const InCallbackType inCallback;
|
const InCallbackType inCallback;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Function pointer that is called when reset() is called.
|
||||||
|
*/
|
||||||
|
const ResetCallbackType resetCallback;
|
||||||
|
|
||||||
|
std::weak_ptr<StreamMgr> _smgr;
|
||||||
|
|
||||||
|
std::unique_ptr<InDataHandler> _indatahandler;
|
||||||
|
|
||||||
|
std::atomic<bool> _thread_running{false};
|
||||||
|
std::atomic<bool> _thread_allowed_to_run{false};
|
||||||
|
|
||||||
|
GlobalThreadPool _pool;
|
||||||
|
|
||||||
void threadFcn();
|
void threadFcn();
|
||||||
|
|
||||||
|
|
||||||
|
@ -58,7 +64,7 @@ class ThreadedInDataHandlerBase {
|
||||||
void _inCallbackFromInDataHandler(const DaqData &daqdata);
|
void _inCallbackFromInDataHandler(const DaqData &daqdata);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ThreadedInDataHandlerBase(SmgrHandle mgr, InCallbackType cb, InResetType reset);
|
ThreadedInDataHandlerBase(SmgrHandle mgr, InCallbackType cb, ResetCallbackType reset);
|
||||||
~ThreadedInDataHandlerBase();
|
~ThreadedInDataHandlerBase();
|
||||||
/**
|
/**
|
||||||
* @brief This method should be called from the derived class' constructor,
|
* @brief This method should be called from the derived class' constructor,
|
|
@ -11,7 +11,7 @@ using std::cerr;
|
||||||
using std::endl;
|
using std::endl;
|
||||||
|
|
||||||
// Safe some typing. Linspace form 0 up to (and NOT including N).
|
// Safe some typing. Linspace form 0 up to (and NOT including N).
|
||||||
#define lin0N arma::linspace(0, N - 1, N)
|
#define lin0N arma::linspace<vd>(0, N - 1, N)
|
||||||
|
|
||||||
vd Window::hann(const us N) {
|
vd Window::hann(const us N) {
|
||||||
return arma::pow(arma::sin((arma::datum::pi/N) * lin0N), 2);
|
return arma::pow(arma::sin((arma::datum::pi/N) * lin0N), 2);
|
||||||
|
@ -24,8 +24,8 @@ vd Window::blackman(const us N) {
|
||||||
d a0 = 7938. / 18608.;
|
d a0 = 7938. / 18608.;
|
||||||
d a1 = 9240. / 18608.;
|
d a1 = 9240. / 18608.;
|
||||||
d a2 = 1430. / 18608.;
|
d a2 = 1430. / 18608.;
|
||||||
return a0 - a1 * d_cos((2 * number_pi/N) * lin0N) +
|
return a0 - a1 * arma::cos((2 * number_pi/N) * lin0N) +
|
||||||
a2 * d_cos((4 * number_pi / N)* lin0N );
|
a2 * arma::cos((4 * number_pi / N)* lin0N );
|
||||||
}
|
}
|
||||||
|
|
||||||
vd Window::rectangular(const us N) { return arma::ones(N); }
|
vd Window::rectangular(const us N) { return arma::ones(N); }
|
|
@ -9,9 +9,6 @@
|
||||||
#ifndef LASP_CONFIG_H
|
#ifndef LASP_CONFIG_H
|
||||||
#define 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 */
|
/* Debug flag */
|
||||||
#cmakedefine01 LASP_DEBUG
|
#cmakedefine01 LASP_DEBUG
|
||||||
|
|
|
@ -44,6 +44,12 @@ void init_siggen(py::module &m);
|
||||||
|
|
||||||
PYBIND11_MODULE(lasp_cpp, m) {
|
PYBIND11_MODULE(lasp_cpp, m) {
|
||||||
|
|
||||||
|
#if LASP_DOUBLE_PRECISION == 1
|
||||||
|
m.attr("LASP_DOUBLE_PRECISION") = true;
|
||||||
|
#else
|
||||||
|
m.attr("LASP_DOUBLE_PRECISION") = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
init_dsp(m);
|
init_dsp(m);
|
||||||
init_deviceinfo(m);
|
init_deviceinfo(m);
|
||||||
init_daqconfiguration(m);
|
init_daqconfiguration(m);
|
||||||
|
@ -51,13 +57,5 @@ PYBIND11_MODULE(lasp_cpp, m) {
|
||||||
init_streammgr(m);
|
init_streammgr(m);
|
||||||
init_datahandler(m);
|
init_datahandler(m);
|
||||||
init_siggen(m);
|
init_siggen(m);
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
/** @} */
|
/** @} */
|
|
@ -29,6 +29,9 @@ void init_deviceinfo(py::module& m) {
|
||||||
devinfo.def_readonly("availableInputRanges",
|
devinfo.def_readonly("availableInputRanges",
|
||||||
&DeviceInfo::availableInputRanges);
|
&DeviceInfo::availableInputRanges);
|
||||||
devinfo.def_readonly("prefInputRangeIndex", &DeviceInfo::prefInputRangeIndex);
|
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("ninchannels", &DeviceInfo::ninchannels);
|
||||||
devinfo.def_readonly("noutchannels", &DeviceInfo::noutchannels);
|
devinfo.def_readonly("noutchannels", &DeviceInfo::noutchannels);
|
||||||
|
@ -36,7 +39,10 @@ void init_deviceinfo(py::module& m) {
|
||||||
devinfo.def_readonly("hasInputACCouplingSwitch",
|
devinfo.def_readonly("hasInputACCouplingSwitch",
|
||||||
&DeviceInfo::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);
|
devinfo.def_readonly("physicalOutputQty", &DeviceInfo::physicalOutputQty);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
|
#include <pybind11/pybind11.h>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
#include "arma_npy.h"
|
#include "arma_npy.h"
|
||||||
#include "lasp_avpowerspectra.h"
|
#include "lasp_avpowerspectra.h"
|
||||||
#include "lasp_biquadbank.h"
|
#include "lasp_biquadbank.h"
|
||||||
#include "lasp_fft.h"
|
#include "lasp_fft.h"
|
||||||
#include "lasp_filter.h"
|
#include "lasp_filter.h"
|
||||||
|
#include "lasp_freqsmooth.h"
|
||||||
#include "lasp_slm.h"
|
#include "lasp_slm.h"
|
||||||
#include "lasp_streammgr.h"
|
#include "lasp_streammgr.h"
|
||||||
#include "lasp_window.h"
|
#include "lasp_window.h"
|
||||||
#include <iostream>
|
|
||||||
#include <pybind11/pybind11.h>
|
|
||||||
|
|
||||||
using std::cerr;
|
using std::cerr;
|
||||||
using std::endl;
|
using std::endl;
|
||||||
|
@ -27,7 +30,6 @@ using rte = std::runtime_error;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
void init_dsp(py::module &m) {
|
void init_dsp(py::module &m) {
|
||||||
|
|
||||||
py::class_<Fft> fft(m, "Fft");
|
py::class_<Fft> fft(m, "Fft");
|
||||||
fft.def(py::init<us>());
|
fft.def(py::init<us>());
|
||||||
fft.def("fft", [](Fft &f, dpyarray dat) {
|
fft.def("fft", [](Fft &f, dpyarray dat) {
|
||||||
|
@ -114,9 +116,10 @@ void init_dsp(py::module &m) {
|
||||||
|
|
||||||
aps.def("compute", [](AvPowerSpectra &aps, dpyarray timedata) {
|
aps.def("compute", [](AvPowerSpectra &aps, dpyarray timedata) {
|
||||||
std::optional<ccube> res;
|
std::optional<ccube> res;
|
||||||
|
dmat timedata_mat = NpyToMat<d, false>(timedata);
|
||||||
{
|
{
|
||||||
py::gil_scoped_release release;
|
py::gil_scoped_release release;
|
||||||
res = aps.compute(NpyToMat<d, false>(timedata));
|
res = aps.compute(timedata_mat);
|
||||||
}
|
}
|
||||||
|
|
||||||
return CubeToNpy<c>(res.value_or(ccube(0, 0, 0)));
|
return CubeToNpy<c>(res.value_or(ccube(0, 0, 0)));
|
||||||
|
@ -151,5 +154,12 @@ void init_dsp(py::module &m) {
|
||||||
slm.def("Lmax", [](const SLM &slm) { return ColToNpy<d>(slm.Lmax()); });
|
slm.def("Lmax", [](const SLM &slm) { return ColToNpy<d>(slm.Lmax()); });
|
||||||
slm.def("Lpeak", [](const SLM &slm) { return ColToNpy<d>(slm.Lpeak()); });
|
slm.def("Lpeak", [](const SLM &slm) { return ColToNpy<d>(slm.Lpeak()); });
|
||||||
slm.def_static("suggestedDownSamplingFac", &SLM::suggestedDownSamplingFac);
|
slm.def_static("suggestedDownSamplingFac", &SLM::suggestedDownSamplingFac);
|
||||||
|
|
||||||
|
// Frequency smoother
|
||||||
|
m.def("freqSmooth", [](dpyarray freq, dpyarray X, unsigned w) {
|
||||||
|
vd freqa = NpyToCol<d, false>(freq);
|
||||||
|
vd Xa = NpyToCol<d, false>(X);
|
||||||
|
return ColToNpy(freqSmooth(freqa, Xa, w));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
/** @} */
|
/** @} */
|
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"),
|
siggen.def("setLevel", &Siggen::setLevel, py::arg("newLevel"),
|
||||||
py::arg("dB") = true);
|
py::arg("dB") = true);
|
||||||
siggen.def("reset", &Siggen::reset);
|
siggen.def("reset", &Siggen::reset);
|
||||||
|
siggen.def("setInterruptPeriod", &Siggen::setInterruptPeriod);
|
||||||
|
|
||||||
siggen.def("setFilter", &Siggen::setFilter);
|
siggen.def("setFilter", &Siggen::setFilter);
|
||||||
|
|
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.7"
|
||||||
|
|
||||||
|
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]
|
[pytest]
|
||||||
addopts = "--ignore=third_party"
|
addopts = "--ignore=third_party --ignore=examples"
|
||||||
|
|
|
@ -2,10 +2,11 @@
|
||||||
LASP: Library for Acoustic Signal Processing
|
LASP: Library for Acoustic Signal Processing
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
from .lasp_version import __version__
|
||||||
|
from .lasp_common import *
|
||||||
from .lasp_cpp import *
|
from .lasp_cpp import *
|
||||||
import lasp.lasp_cpp
|
|
||||||
from .lasp_common import *
|
|
||||||
__version__ = lasp_cpp.__version__
|
|
||||||
|
|
||||||
# from .lasp_imptube import * # TwoMicImpedanceTube
|
# from .lasp_imptube import * # TwoMicImpedanceTube
|
||||||
from .lasp_measurement import * # Measurement, scaleBlockSens
|
from .lasp_measurement import * # Measurement, scaleBlockSens
|
||||||
|
@ -14,6 +15,7 @@ from .lasp_slm import * # SLM, Dummy
|
||||||
from .lasp_record import * # RecordStatus, Recording
|
from .lasp_record import * # RecordStatus, Recording
|
||||||
from .lasp_daqconfigs import *
|
from .lasp_daqconfigs import *
|
||||||
from .lasp_measurementset import *
|
from .lasp_measurementset import *
|
||||||
|
|
||||||
# from .lasp_siggen import * # SignalType, NoiseType, SiggenMessage, SiggenData, Siggen
|
# from .lasp_siggen import * # SignalType, NoiseType, SiggenMessage, SiggenData, Siggen
|
||||||
# from .lasp_weighcal import * # WeighCal
|
# from .lasp_weighcal import * # WeighCal
|
||||||
# from .tools import * # SmoothingType, smoothSpectralData, SmoothingWidth
|
# from .tools import * # SmoothingType, smoothSpectralData, SmoothingWidth
|
|
@ -209,6 +209,7 @@ class FilterBankDesigner:
|
||||||
Returns:
|
Returns:
|
||||||
h: Linear filter transfer function [-]
|
h: Linear filter transfer function [-]
|
||||||
"""
|
"""
|
||||||
|
fs = self.fs
|
||||||
fir = self.createFirFilter(fs, x)
|
fir = self.createFirFilter(fs, x)
|
||||||
|
|
||||||
# Decimated sampling frequency [Hz]
|
# Decimated sampling frequency [Hz]
|
|
@ -11,7 +11,9 @@ __all__ = ['freqResponse', 'bandpass_fir_design', 'lowpass_fir_design',
|
||||||
'arbitrary_fir_design']
|
'arbitrary_fir_design']
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from scipy.signal import freqz, hann, firwin2
|
from scipy.signal import freqz, firwin2
|
||||||
|
from scipy.signal.windows import hann
|
||||||
|
from ..lasp_config import empty
|
||||||
|
|
||||||
|
|
||||||
def freqResponse(fs, freq, coefs_b, coefs_a=1.):
|
def freqResponse(fs, freq, coefs_b, coefs_a=1.):
|
||||||
|
@ -44,7 +46,7 @@ def bandpass_fir_design(L, fs, fl, fu, window=hann):
|
||||||
Omg2 = 2*np.pi*fu/fs
|
Omg2 = 2*np.pi*fu/fs
|
||||||
Omg1 = 2*np.pi*fl/fs
|
Omg1 = 2*np.pi*fl/fs
|
||||||
|
|
||||||
fir = np.empty(L, dtype=float)
|
fir = empty(L, dtype=float)
|
||||||
|
|
||||||
# First Create ideal band-pass filter
|
# First Create ideal band-pass filter
|
||||||
fir[L//2] = (Omg2-Omg1)/np.pi
|
fir[L//2] = (Omg2-Omg1)/np.pi
|
||||||
|
@ -64,7 +66,7 @@ def lowpass_fir_design(L, fs, fc, window=hann):
|
||||||
" than upper cut-off"
|
" than upper cut-off"
|
||||||
|
|
||||||
Omgc = 2*np.pi*fc/fs
|
Omgc = 2*np.pi*fc/fs
|
||||||
fir = np.empty(L, dtype=float)
|
fir = empty(L, dtype=float)
|
||||||
|
|
||||||
# First Create ideal band-pass filter
|
# First Create ideal band-pass filter
|
||||||
fir[L//2] = Omgc/np.pi
|
fir[L//2] = Omgc/np.pi
|
|
@ -33,7 +33,7 @@ class Atomic:
|
||||||
|
|
||||||
def checkType(self, val):
|
def checkType(self, val):
|
||||||
if not (type(val) == bool or type(val) == int):
|
if not (type(val) == bool or type(val) == int):
|
||||||
raise RuntimeError("Invalid type for Atomic")
|
raise ValueError("Invalid type for Atomic")
|
||||||
|
|
||||||
def __iadd__(self, toadd):
|
def __iadd__(self, toadd):
|
||||||
self.checkType(toadd)
|
self.checkType(toadd)
|
|
@ -62,8 +62,9 @@ class Qty:
|
||||||
name: str
|
name: str
|
||||||
# I.e.: Pascal
|
# I.e.: Pascal
|
||||||
unit_name: str
|
unit_name: str
|
||||||
# I.e.: Pa
|
# I.e.: -, Pa, V
|
||||||
unit_symb: str
|
unit_symb: str
|
||||||
|
|
||||||
# I.e.: ('dB SPL') <== tuple of possible level units
|
# I.e.: ('dB SPL') <== tuple of possible level units
|
||||||
level_unit: object
|
level_unit: object
|
||||||
# Contains a tuple of possible level names, including its reference value.
|
# Contains a tuple of possible level names, including its reference value.
|
||||||
|
@ -92,6 +93,18 @@ class Qty:
|
||||||
"""
|
"""
|
||||||
return self.cpp_enum.value
|
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
|
@unique
|
||||||
|
@ -108,7 +121,7 @@ class SIQtys(Enum):
|
||||||
unit_name='Pascal',
|
unit_name='Pascal',
|
||||||
unit_symb='Pa',
|
unit_symb='Pa',
|
||||||
level_unit=('dB SPL','dBPa'),
|
level_unit=('dB SPL','dBPa'),
|
||||||
level_ref_name=('2 micropascal', '1 pascal',),
|
level_ref_name=('20 micropascal', '1 pascal',),
|
||||||
level_ref_value=(P_REF, 1),
|
level_ref_value=(P_REF, 1),
|
||||||
cpp_enum = DaqChannel.Qty.AcousticPressure
|
cpp_enum = DaqChannel.Qty.AcousticPressure
|
||||||
)
|
)
|
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 -*-
|
# -*- 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
|
Author: J.A. de Jong - ASCEE
|
File diff suppressed because it is too large
Load Diff
148
python_src/lasp/lasp_measurementset.py
Normal file
148
python_src/lasp/lasp_measurementset.py
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
"""
|
||||||
|
Provides class MeasurementSet, a class used to perform checks and adjustments
|
||||||
|
on a group of measurements at the same time.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
__all__ = ["MeasurementSet"]
|
||||||
|
from .lasp_measurement import Measurement, MeasurementType
|
||||||
|
from typing import List
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
class MeasurementSet(list):
|
||||||
|
"""
|
||||||
|
Group of measurements that have some correspondence to one another. Class
|
||||||
|
is used to operate on multiple measurements at once.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, mlist: List[Measurement] = []):
|
||||||
|
"""
|
||||||
|
Initialize a measurement set
|
||||||
|
|
||||||
|
Arg:
|
||||||
|
mlist: Measurement list
|
||||||
|
"""
|
||||||
|
|
||||||
|
if any([not isinstance(i, Measurement) for i in mlist]):
|
||||||
|
raise TypeError("Object in list should be of Measurement type")
|
||||||
|
|
||||||
|
# Sort by time stamp, otherwise the order is random
|
||||||
|
mlist.sort(key=lambda x: x.time, reverse=True)
|
||||||
|
super().__init__(mlist)
|
||||||
|
|
||||||
|
def getNewestReferenceMeasurement(self, mtype: MeasurementType):
|
||||||
|
"""
|
||||||
|
Get the NEWEST ref. measurement of a current type, in the current set.
|
||||||
|
|
||||||
|
Arg:
|
||||||
|
mtype (MeasurementType): The type required.
|
||||||
|
|
||||||
|
Return:
|
||||||
|
- The newest (in time) measurement in the current list of a certain type.
|
||||||
|
- None, in case no measurement could be found.
|
||||||
|
"""
|
||||||
|
|
||||||
|
mnewest = None
|
||||||
|
for m in self:
|
||||||
|
if m.measurementType() == mtype:
|
||||||
|
if mnewest is None:
|
||||||
|
mnewest = m
|
||||||
|
else:
|
||||||
|
if mnewest.time < m.time:
|
||||||
|
mnewest = m
|
||||||
|
return mnewest
|
||||||
|
|
||||||
|
def getReferenceMeasurements(self, mtype: MeasurementType):
|
||||||
|
"""Get ALL ref. measurements of a certain type, in the current set.
|
||||||
|
|
||||||
|
Arg:
|
||||||
|
mtype (MeasurementType): The type of which to list
|
||||||
|
|
||||||
|
Return:
|
||||||
|
A new measurement set, including all measurements of a certain type
|
||||||
|
"""
|
||||||
|
|
||||||
|
return [m for m in self if m.measurementType() == mtype]
|
||||||
|
|
||||||
|
def getNewestReferenceMeasurements(self):
|
||||||
|
"""
|
||||||
|
Get the NEWEST ref. measurement of all types, in the current set.
|
||||||
|
|
||||||
|
Return:
|
||||||
|
- A dictionary with the newest measurement of each type that is not specific
|
||||||
|
- None, in case no measurement is found.
|
||||||
|
"""
|
||||||
|
|
||||||
|
newest = {}
|
||||||
|
for m in self:
|
||||||
|
mtype = m.measurementType()
|
||||||
|
if mtype == MeasurementType.NotSpecific:
|
||||||
|
continue
|
||||||
|
if mtype not in newest:
|
||||||
|
newest[mtype] = m
|
||||||
|
else:
|
||||||
|
if m.time > newest[mtype].time:
|
||||||
|
newest[mtype] = m
|
||||||
|
return newest
|
||||||
|
|
||||||
|
def newestReferenceOlderThan(self, secs):
|
||||||
|
"""
|
||||||
|
Get a dictionary of reference measurements which are older than a
|
||||||
|
specified threshold. Only one of each type is returned.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
- secs: time threshold, in seconds
|
||||||
|
|
||||||
|
Return:
|
||||||
|
- a dictionary of references with the newest reference, that is still
|
||||||
|
older than `secs` seconds
|
||||||
|
"""
|
||||||
|
curtime = time.time()
|
||||||
|
newest = self.getNewestReferenceMeasurements()
|
||||||
|
newest_older_than = {}
|
||||||
|
for key, m in newest.items():
|
||||||
|
if curtime - m.time >= secs:
|
||||||
|
newest_older_than[key] = m
|
||||||
|
return newest_older_than
|
||||||
|
|
||||||
|
def measTimeSame(self):
|
||||||
|
"""
|
||||||
|
Returns True if all measurements have the same measurement length and
|
||||||
|
sample rate
|
||||||
|
"""
|
||||||
|
|
||||||
|
if len(self) > 0:
|
||||||
|
firstN = self[0].N # samples
|
||||||
|
firstFS = self[0].samplerate # sample rate
|
||||||
|
sameN = all([firstN == meas.N for meas in self])
|
||||||
|
sameFS = all([firstFS == meas.samplerate for meas in self])
|
||||||
|
return sameN and sameFS
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def measSimilar(self):
|
||||||
|
"""
|
||||||
|
Similar means: channel metadata is the same, and the measurement time
|
||||||
|
is the same.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- True if measChannelsSame() and measTimeSame()
|
||||||
|
- False otherwise
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self.measTimeSame() and self.measChannelsSame()
|
||||||
|
|
||||||
|
def measChannelsSame(self):
|
||||||
|
"""
|
||||||
|
This method is used to check whether a set of measurements can be
|
||||||
|
accessed in a loop, i.e. for computing power spectra or sound levels on
|
||||||
|
a set of measurements, simultaneously. If the channel data is the same
|
||||||
|
(name, sensitivity, ...) it returns True.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if len(self) > 0:
|
||||||
|
first = self[0].channelConfig
|
||||||
|
return all([first == meas.channelConfig for meas in self])
|
||||||
|
else:
|
||||||
|
return False
|
|
@ -77,6 +77,7 @@ class FirFilterBank:
|
||||||
|
|
||||||
self.fs = fs
|
self.fs = fs
|
||||||
self.xs = list(range(xmin, xmax + 1))
|
self.xs = list(range(xmin, xmax + 1))
|
||||||
|
raise RuntimeError('Not working code anymore')
|
||||||
|
|
||||||
maxdecimation = self.designer.firDecimation(self.xs[0])
|
maxdecimation = self.designer.firDecimation(self.xs[0])
|
||||||
self.decimators = []
|
self.decimators = []
|
||||||
|
@ -245,7 +246,7 @@ class SosFilterBank:
|
||||||
for i, x in enumerate(self.xs):
|
for i, x in enumerate(self.xs):
|
||||||
channel = self.designer.createSOSFilter(x)
|
channel = self.designer.createSOSFilter(x)
|
||||||
if sos is None:
|
if sos is None:
|
||||||
sos = np.empty((channel.size, len(self.xs)))
|
sos = empty((channel.size, len(self.xs)))
|
||||||
sos[:, i] = channel.flatten()
|
sos[:, i] = channel.flatten()
|
||||||
|
|
||||||
self._fb = BiquadBank(sos)
|
self._fb = BiquadBank(sos)
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user