Compare commits
4 Commits
master
...
Alternativ
Author | SHA1 | Date | |
---|---|---|---|
f5d137b679 | |||
5caddec583 | |||
1b46616607 | |||
672dcfee14 |
110
.drone.yml
Normal file
110
.drone.yml
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
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
|
@ -1,52 +0,0 @@
|
|||||||
name: Building, testing and releasing LASP if it has a tag
|
|
||||||
|
|
||||||
on:
|
|
||||||
- push
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
Build-Test-Ubuntu:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
container:
|
|
||||||
image: ascee/ubuntu_build:latest
|
|
||||||
volumes:
|
|
||||||
- lasp_dist:/dist
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
|
|
||||||
- name: Build and test
|
|
||||||
run: |
|
|
||||||
pip install build pytest
|
|
||||||
python3 -m build
|
|
||||||
pip install dist/lasp*.whl
|
|
||||||
pytest
|
|
||||||
|
|
||||||
- name: Cleanup old dist files and copy new to /dist dir
|
|
||||||
run: |-
|
|
||||||
rm -f /dist/*
|
|
||||||
cp -v dist/* /dist
|
|
||||||
|
|
||||||
Release-Ubuntu:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
container:
|
|
||||||
volumes:
|
|
||||||
- lasp_dist:/dist
|
|
||||||
needs: Build-Test-Ubuntu
|
|
||||||
if: startsWith(gitea.ref, 'refs/tags/v')
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: setup go
|
|
||||||
uses: https://github.com/actions/setup-go@v4
|
|
||||||
with:
|
|
||||||
go-version: '1.18'
|
|
||||||
- name: Release
|
|
||||||
uses: https://gitea.com/actions/release-action@main
|
|
||||||
working-directory: "/"
|
|
||||||
with:
|
|
||||||
files: |-
|
|
||||||
../../../../../dist/**
|
|
||||||
api_key: '${{secrets.RELEASE_TOKEN}}'
|
|
17
.gitignore
vendored
17
.gitignore
vendored
@ -1,26 +1,23 @@
|
|||||||
*.a
|
*.a
|
||||||
*.pyc
|
*.pyc
|
||||||
*.so
|
*.so
|
||||||
*.dll
|
|
||||||
*.pyd
|
|
||||||
.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
|
||||||
acme_log.log
|
|
||||||
.venv
|
|
||||||
.py-build-cmake_cache
|
|
||||||
cpp_src/lasp_config.h
|
|
||||||
.cache
|
.cache
|
||||||
.vscode
|
_skbuild
|
||||||
build
|
acme_log.log
|
||||||
|
7
.gitmodules
vendored
7
.gitmodules
vendored
@ -6,7 +6,7 @@
|
|||||||
url = https://github.com/gsl-lite/gsl-lite
|
url = https://github.com/gsl-lite/gsl-lite
|
||||||
[submodule "DebugTrace-cpp"]
|
[submodule "DebugTrace-cpp"]
|
||||||
path = third_party/DebugTrace-cpp
|
path = third_party/DebugTrace-cpp
|
||||||
url = https://github.com/asceenl/DebugTrace-cpp
|
url = https://github.com/MasatoKokubo/DebugTrace-cpp
|
||||||
[submodule "armadillo-code"]
|
[submodule "armadillo-code"]
|
||||||
path = third_party/armadillo-code
|
path = third_party/armadillo-code
|
||||||
url = https://gitlab.com/conradsnicta/armadillo-code
|
url = https://gitlab.com/conradsnicta/armadillo-code
|
||||||
@ -30,7 +30,4 @@
|
|||||||
url = https://github.com/boostorg/core
|
url = https://github.com/boostorg/core
|
||||||
[submodule "third_party/uldaq"]
|
[submodule "third_party/uldaq"]
|
||||||
path = third_party/uldaq
|
path = third_party/uldaq
|
||||||
url = https://github.com/asceenl/uldaq
|
url = https://github.com/mccdaq/uldaq
|
||||||
[submodule "third_party/portaudio"]
|
|
||||||
path = third_party/portaudio
|
|
||||||
url = https://github.com/PortAudio/portaudio
|
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
---
|
|
||||||
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
|
|
@ -1,7 +0,0 @@
|
|||||||
## v1.0.1 (2023-07-19)
|
|
||||||
|
|
||||||
### Fix
|
|
||||||
|
|
||||||
- Added patch number to semver in pyproject.toml
|
|
||||||
|
|
||||||
## v1.0.0 (2023-07-19)
|
|
@ -1,42 +1,13 @@
|
|||||||
cmake_minimum_required(VERSION 3.16)
|
cmake_minimum_required (VERSION 3.16)
|
||||||
project(LASP LANGUAGES C CXX VERSION 1.6.3)
|
project(LASP LANGUAGES C CXX VERSION 1.0)
|
||||||
|
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED)
|
set(CMAKE_CXX_STANDARD_REQUIRED)
|
||||||
|
|
||||||
# Experimental: support for Raspberry Pi (64-bit architectures)
|
option(LASP_DOUBLE_PRECISION "Compile as double precision floating point" ON)
|
||||||
if(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "aarch64")
|
option(LASP_HAS_RTAUDIO "Compile with RtAudio Daq backend" ON)
|
||||||
set(RPI TRUE)
|
option(LASP_HAS_ULDAQ "Compile with UlDaq backend" ON)
|
||||||
else()
|
|
||||||
set(RPI FALSE)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Setting defaults for PortAudio and RtAudio backend, depending on Linux /
|
|
||||||
# Windows.
|
|
||||||
set(DEFAULT_DOUBLE_PRECISION ON)
|
|
||||||
if(WIN32)
|
|
||||||
set(DEFAULT_RTAUDIO OFF)
|
|
||||||
set(DEFAULT_PORTAUDIO ON)
|
|
||||||
set(DEFAULT_ULDAQ OFF)
|
|
||||||
elseif(${RPI})
|
|
||||||
set(DEFAULT_RTAUDIO OFF)
|
|
||||||
set(DEFAULT_PORTAUDIO ON)
|
|
||||||
set(DEFAULT_ULDAQ OFF)
|
|
||||||
set(DEFAULT_DOUBLE_PRECISION OFF)
|
|
||||||
else()
|
|
||||||
set(DEFAULT_RTAUDIO OFF)
|
|
||||||
set(DEFAULT_PORTAUDIO ON)
|
|
||||||
set(DEFAULT_ULDAQ ON)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
|
|
||||||
option(LASP_DOUBLE_PRECISION "Compile as double precision floating point" ${DEFAULT_DOUBLE_PRECISION})
|
|
||||||
option(LASP_HAS_RTAUDIO "Compile with RtAudio Daq backend" ${DEFAULT_RTAUDIO})
|
|
||||||
option(LASP_HAS_PORTAUDIO "Compile with PortAudio Daq backend" ${DEFAULT_PORTAUDIO})
|
|
||||||
if(LASP_HAS_PORTAUDIO AND LASP_HAS_RTAUDIO)
|
|
||||||
message(FATAL_ERROR "Either PortAudio or RtAudio can be selected as audio backend")
|
|
||||||
endif()
|
|
||||||
option(LASP_HAS_ULDAQ "Compile with UlDaq backend" ${DEFAULT_ULDAQ})
|
|
||||||
option(LASP_BUILD_TUNED "Tune build for current machine (Experimental / untested)" OFF)
|
option(LASP_BUILD_TUNED "Tune build for current machine (Experimental / untested)" OFF)
|
||||||
option(LASP_WITH_OPENMP "Use OpenMP parallelization (Experimental: crashes SHOULD BE EXPECTED)" OFF)
|
option(LASP_WITH_OPENMP "Use OpenMP parallelization (Experimental: crashes SHOULD BE EXPECTED)" OFF)
|
||||||
set(LASP_MAX_NFFT "33554432" CACHE STRING "Max FFT size")
|
set(LASP_MAX_NFFT "33554432" CACHE STRING "Max FFT size")
|
||||||
@ -56,6 +27,7 @@ cmake_policy(SET CMP0079 NEW)
|
|||||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
|
|
||||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake")
|
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake")
|
||||||
|
include_directories(/usr/include/python3.8)
|
||||||
include("BuildType")
|
include("BuildType")
|
||||||
include("QueryPythonForPybind11")
|
include("QueryPythonForPybind11")
|
||||||
|
|
||||||
@ -116,29 +88,20 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
# ###################################### Compilation flags
|
# ###################################### Compilation flags
|
||||||
set(CMAKE_C_FLAGS_RELEASE "-O3 -flto -mtune=native \
|
set(CMAKE_C_FLAGS_RELEASE "-O3 -flto -mfpmath=sse -march=x86-64 -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")
|
||||||
|
|
||||||
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(uldaq)
|
include(uldaq)
|
||||||
#
|
#
|
||||||
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,16 +1,13 @@
|
|||||||
FROM archlinux:latest
|
FROM archlinux
|
||||||
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 python-virtualenv
|
RUN pacman --noconfirm -S git doxygen graphviz lighttpd python-pip
|
||||||
WORKDIR /root
|
|
||||||
ENV VIRTUAL_ENV=/root/testenv
|
|
||||||
RUN python3 -m venv $VIRTUAL_ENV
|
|
||||||
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
|
|
||||||
RUN pip install doxypypy
|
RUN pip install doxypypy
|
||||||
|
WORKDIR /root
|
||||||
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
|
||||||
|
|
||||||
|
154
README.md
154
README.md
@ -1,5 +1,7 @@
|
|||||||
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
|
||||||
@ -7,7 +9,6 @@ with a Python interface which is supposed to acquire and process (multi) sensor
|
|||||||
|
|
||||||
Current features that are implemented:
|
Current features that are implemented:
|
||||||
|
|
||||||
|
|
||||||
- Communication with data acquisition (DAQ) devices, of which:
|
- Communication with data acquisition (DAQ) devices, of which:
|
||||||
- Internal sound cards via the [RtAudio](http://www.music.mcgill.ca/~gary/rtaudio) backend. Many thanks to Gary P. Scavone et al.
|
- Internal sound cards via the [RtAudio](http://www.music.mcgill.ca/~gary/rtaudio) backend. Many thanks to Gary P. Scavone et al.
|
||||||
- [Measurement Computing](https://www.mccdaq.com) [DT9838A](https://www.mccdaq.com/Products/Sound-Vibration-DAQ/DT9837) signal analyzer.
|
- [Measurement Computing](https://www.mccdaq.com) [DT9838A](https://www.mccdaq.com/Products/Sound-Vibration-DAQ/DT9837) signal analyzer.
|
||||||
@ -31,127 +32,54 @@ Current features that are implemented:
|
|||||||
- Spectra data smoothing algorithms
|
- Spectra data smoothing algorithms
|
||||||
- Sensor calibration for microphones
|
- Sensor calibration for microphones
|
||||||
|
|
||||||
|
|
||||||
Future features (wish-list)
|
Future features (wish-list)
|
||||||
|
|
||||||
|
|
||||||
- Conventional and delay-and-sum beam-forming algorithms
|
- Conventional and delay-and-sum beam-forming algorithms
|
||||||
- Impedance tube measurement processing
|
- Impedance tube measurement processing
|
||||||
|
|
||||||
For now, the source code is well-documented on [lasp.ascee.nl](https://lasp.ascee.nl) but it requires some
|
For now, the source code is well-documented on [lasp.ascee.nl](https://lasp.ascee.nl) but it requires some
|
||||||
additional documentation (the math behind it). This is maintained
|
additional documentation (the math behind it). This is maintained
|
||||||
in a sister repository [lasp-doc](https://code.ascee.nl/ascee/lasp-doc).
|
in a sister repository [lasp-doc](https://code.ascee.nl/ascee/lasp-doc). The
|
||||||
|
most recent
|
||||||
|
|
||||||
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: info@ascee.nl.
|
||||||
|
|
||||||
|
# Installation
|
||||||
|
|
||||||
# Installation - Linux (Ubuntu-based)
|
## Dependencies
|
||||||
|
|
||||||
## Prerequisites
|
- `$ sudo apt install python3-pybind11 libopenblas-dev python3-pip python3-scipy libusb-1.0-0-dev libpulse-dev cmake-curses-gui python3-h5py`
|
||||||
|
- `$ pip3 install --user -r requirements.txt`
|
||||||
|
|
||||||
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:
|
||||||
|
|
||||||
- `sudo apt install libclalsadrv-dev`
|
- 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:
|
||||||
|
|
||||||
- `sudo apt install libjack-jackd2-dev`
|
- 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 .`
|
|
||||||
|
|
||||||
# Building and installation for Raspberry Pi (Raspberry Pi OS)
|
For a release build:
|
||||||
|
|
||||||
Run the following on the command line to install all prerequisites on
|
- `$ cmake .`
|
||||||
Raspberry Pi OS:
|
|
||||||
|
|
||||||
- `sudo apt install libfftw3-dev libopenblas64-dev libhdf5-dev libclalsadrv-dev`
|
or optionally for a custom build:
|
||||||
|
|
||||||
In a virtualenv: install `build`
|
- `$ ccmake .`
|
||||||
|
|
||||||
- `$ pip install build`
|
Configure and run:
|
||||||
|
|
||||||
Then run:
|
- `$ make -j`
|
||||||
|
|
||||||
- `$ git clone --recursive https://code.ascee.nl/ASCEE/lasp.git`
|
### Build documentation
|
||||||
- `$ cd lasp`
|
|
||||||
- `$ pyproject-build`
|
|
||||||
|
|
||||||
Which will generate a `whl` in the `dist` folder, that is redistributable for Raspberry Pis that run Raspberry Pi OS.
|
In directory:
|
||||||
|
|
||||||
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`
|
||||||
@ -164,29 +92,21 @@ This will build the documentation. It can be read by:
|
|||||||
|
|
||||||
`$ <YOUR-BROWSER> doc/html/index.html`
|
`$ <YOUR-BROWSER> doc/html/index.html`
|
||||||
|
|
||||||
# Usage
|
Or via docker:
|
||||||
|
|
||||||
|
`$ docker build -t lasp_ascee_nl:latest .`
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
For an editable install (while developing):
|
||||||
|
|
||||||
|
- `$ pip3 install --prefix=$HOME/.local -e .`
|
||||||
|
|
||||||
|
To install locally, for a fixed version:
|
||||||
|
|
||||||
|
- `$ pip3 install --prefix=$HOME/.local`
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
- 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
|
|
||||||
```
|
|
@ -1,11 +1,29 @@
|
|||||||
if(WIN32)
|
if(WIN32)
|
||||||
set(home $ENV{USERPROFILE})
|
set(home $ENV{USERPROFILE})
|
||||||
|
|
||||||
# set(miniconda_dir ${home}\\Miniconda3)
|
# set(miniconda_dir ${home}\\Miniconda3)
|
||||||
set(TARGET_OS_LINKLIBS winmm dsound setupapi ole32 uuid winmm)
|
|
||||||
|
|
||||||
message("Building for Windows")
|
message("Building for Windows")
|
||||||
|
include_directories(
|
||||||
|
..\\rtaudio
|
||||||
|
C:\\mingw\\mingw64\\include\\OpenBLAS
|
||||||
|
link_directories(${home}\\miniconda3\\Library\\include)
|
||||||
|
)
|
||||||
|
set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH} $miniconda_dir\\Lib\\cmake")
|
||||||
|
# include(
|
||||||
|
add_definitions(-DMS_WIN64)
|
||||||
|
link_directories(C:\\mingw\\mingw64\\lib)
|
||||||
|
link_directories(C:\\mingw\\mingw64\\bin)
|
||||||
|
link_directories(..\\rtaudio)
|
||||||
|
link_directories(${home}\\Miniconda3)
|
||||||
|
add_definitions(-DHAS_RTAUDIO_WIN_WASAPI_API)
|
||||||
else() # Linux compile
|
else() # Linux compile
|
||||||
message("Building for Linux :)")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wfatal-errors")
|
||||||
set(TARGET_OS_LINKLIBS "")
|
include_directories(/usr/local/include/rtaudio)
|
||||||
endif()
|
include_directories(/usr/include/rtaudio)
|
||||||
|
link_directories(/usr/local/lib)
|
||||||
|
# This should become optional later on, and be added to the windows list as
|
||||||
|
# well.
|
||||||
|
|
||||||
|
endif()
|
||||||
|
# The last argument here takes care of calling SIGABRT when an integer overflow
|
||||||
|
# occures.
|
||||||
|
@ -1,28 +0,0 @@
|
|||||||
# ###################################### RtAudio
|
|
||||||
if(LASP_HAS_PORTAUDIO)
|
|
||||||
message("Building with Portaudio backend")
|
|
||||||
if(WIN32)
|
|
||||||
set(PA_USE_ALSA FALSE CACHE BOOL "Build PortAudio with ALSA backend")
|
|
||||||
set(PA_USE_ASIO TRUE CACHE BOOL "Build PortAudio with ASIO backend")
|
|
||||||
set(PA_USE_DS FALSE CACHE BOOL "Build PortAudio with Directsound backend")
|
|
||||||
set(PA_USE_WMME FALSE CACHE BOOL "Build PortAudio with WMME backend")
|
|
||||||
set(PA_USE_WDMKS FALSE CACHE BOOL "Build PortAudio with WDMKS backend")
|
|
||||||
else()
|
|
||||||
# Unix
|
|
||||||
set(PA_USE_ALSA TRUE CACHE BOOL "Build PortAudio with ALSA backend")
|
|
||||||
set(PA_USE_JACK FALSE CACHE BOOL "Build PortAudio with Jack backend")
|
|
||||||
set(PA_USE_PULSEAUDIO FALSE CACHE BOOL "Build PortAudio with PulseAudio backend")
|
|
||||||
set(PA_BUILD_SHARED_LIBS FALSE CACHE BOOL "Build static library")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
add_subdirectory(third_party/portaudio)
|
|
||||||
include_directories(third_party/portaudio/include)
|
|
||||||
link_directories(third_party/portaudio)
|
|
||||||
|
|
||||||
if(PA_USE_ALSA)
|
|
||||||
add_definitions(-DLASP_HAS_PA_ALSA=1)
|
|
||||||
else()
|
|
||||||
add_definitions(-DLASP_HAS_PA_ALSA=0)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
endif()
|
|
@ -2,13 +2,12 @@
|
|||||||
if(LASP_HAS_RTAUDIO)
|
if(LASP_HAS_RTAUDIO)
|
||||||
message("Building RtAudio backend")
|
message("Building RtAudio backend")
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
set(RTAUDIO_API_WASAPI FALSE CACHE BOOL "Build for WASAPI backend")
|
set(RTAUDIO_API_WASAPI TRUE CACHE BOOL "Build for WASAPI" FORCE)
|
||||||
set(RTAUDIO_API_DS TRUE CACHE BOOL "Build for Directsound backend")
|
|
||||||
else()
|
else()
|
||||||
set(RTAUDIO_API_PULSE TRUE CACHE BOOL "Build with PulseAudio backend")
|
set(RTAUDIO_API_PULSE TRUE CACHE BOOL "Build with PulseAudio backend" FORCE)
|
||||||
set(RTAUDIO_API_ALSA OFF CACHE BOOL "Do not build with Alsa backend")
|
set(RTAUDIO_API_ALSA OFF CACHE BOOL "Do not build with Alsa backend" FORCE)
|
||||||
set(RTAUDIO_API_JACK OFF CACHE BOOL "Do not build with Jack backend")
|
set(RTAUDIO_API_JACK OFF CACHE BOOL "Do not build with Jack backend" FORCE)
|
||||||
endif()
|
endif()
|
||||||
set(RTAUDIO_BUILD_STATIC_LIBS ON CACHE BOOL "Build static libs for RtAudio" FORCE)
|
set(RTAUDIO_BUILD_STATIC_LIBS ON CACHE BOOL "Build static libs for RtAudio" FORCE)
|
||||||
add_subdirectory(${PROJECT_SOURCE_DIR}/third_party/rtaudio)
|
add_subdirectory(third_party/rtaudio)
|
||||||
endif()
|
endif()
|
||||||
|
@ -1,63 +0,0 @@
|
|||||||
# src/lasp/CMakeLists.txt
|
|
||||||
|
|
||||||
# Armadillo, don't build the wrapper lib, but instead directly link to
|
|
||||||
# openblas.
|
|
||||||
add_definitions(-DARMA_DONT_USE_WRAPPER)
|
|
||||||
|
|
||||||
configure_file(lasp_config.h.in lasp_config.h)
|
|
||||||
include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
|
||||||
include_directories(SYSTEM
|
|
||||||
${PROJECT_SOURCE_DIR}/third_party/armadillo-code/include)
|
|
||||||
|
|
||||||
include_directories(${PROJECT_SOURCE_DIR}/third_party/DebugTrace-cpp/include)
|
|
||||||
include_directories(${PROJECT_SOURCE_DIR}/third_party/gsl-lite/include)
|
|
||||||
include_directories(${PROJECT_SOURCE_DIR}/third_party/tomlplusplus/include)
|
|
||||||
include_directories(${PROJECT_SOURCE_DIR}/third_party/thread-pool)
|
|
||||||
|
|
||||||
if(LASP_HAS_RTAUDIO)
|
|
||||||
include_directories(${PROJECT_SOURCE_DIR}/third_party/rtaudio)
|
|
||||||
endif()
|
|
||||||
if(LASP_HAS_ULDAQ)
|
|
||||||
include_directories(${PROJECT_SOURCE_DIR}/third_party/uldaq/src)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
add_subdirectory(device)
|
|
||||||
add_subdirectory(dsp)
|
|
||||||
|
|
||||||
pybind11_add_module(lasp_cpp MODULE lasp_cpp.cpp
|
|
||||||
pybind11/lasp_deviceinfo.cpp
|
|
||||||
pybind11/lasp_daqconfig.cpp
|
|
||||||
pybind11//lasp_dsp_pybind.cpp
|
|
||||||
pybind11/lasp_streammgr.cpp
|
|
||||||
pybind11/lasp_daq.cpp
|
|
||||||
pybind11/lasp_deviceinfo.cpp
|
|
||||||
pybind11/lasp_pyindatahandler.cpp
|
|
||||||
pybind11/lasp_siggen.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
target_link_libraries(lasp_cpp PRIVATE lasp_device_lib lasp_dsp_lib
|
|
||||||
${OpenMP_CXX_LIBRARIES} ${LASP_FFT_LIBS} ${TARGET_OS_LINKLIBS})
|
|
||||||
|
|
||||||
target_compile_definitions(lasp_cpp PRIVATE
|
|
||||||
MODULE_NAME=$<TARGET_FILE_BASE_NAME:lasp_cpp>
|
|
||||||
VERSION_INFO="${PY_FULL_VERSION}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Hide all symbols by default (including external libraries on Linux)
|
|
||||||
if(CMAKE_BUILD_TYPE STREQUAL "Release")
|
|
||||||
set_target_properties(lasp_cpp PROPERTIES
|
|
||||||
CXX_VISIBILITY_PRESET "hidden"
|
|
||||||
VISIBILITY_INLINES_HIDDEN true)
|
|
||||||
if (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
|
||||||
target_link_options(lasp_cpp PRIVATE "LINKER:--exclude-libs,ALL")
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(DEFINED PY_BUILD_CMAKE_MODULE_NAME)
|
|
||||||
# Install the Python module
|
|
||||||
install(TARGETS lasp_cpp
|
|
||||||
EXCLUDE_FROM_ALL
|
|
||||||
COMPONENT python_modules
|
|
||||||
DESTINATION ${PY_BUILD_CMAKE_MODULE_NAME})
|
|
||||||
|
|
||||||
endif()
|
|
@ -1,61 +0,0 @@
|
|||||||
// #define DEBUGTRACE_ENABLED
|
|
||||||
#include "lasp_indatahandler.h"
|
|
||||||
#include "debugtrace.hpp"
|
|
||||||
#include "lasp_streammgr.h"
|
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
InDataHandler::InDataHandler(SmgrHandle mgr, const InCallbackType cb,
|
|
||||||
const ResetCallbackType resetfcn)
|
|
||||||
: _mgr(mgr), inCallback(cb), reset(resetfcn)
|
|
||||||
#if LASP_DEBUG == 1
|
|
||||||
,
|
|
||||||
main_thread_id(std::this_thread::get_id())
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
DEBUGTRACE_ENTER;
|
|
||||||
#if LASP_DEBUG == 1
|
|
||||||
assert(mgr->main_thread_id == main_thread_id);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
void InDataHandler::start() {
|
|
||||||
DEBUGTRACE_ENTER;
|
|
||||||
checkRightThread();
|
|
||||||
if (SmgrHandle handle = _mgr.lock()) {
|
|
||||||
handle->addInDataHandler(this);
|
|
||||||
#if LASP_DEBUG == 1
|
|
||||||
assert(handle->main_thread_id == main_thread_id);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void InDataHandler::stop() {
|
|
||||||
DEBUGTRACE_ENTER;
|
|
||||||
// checkRightThread();
|
|
||||||
#if LASP_DEBUG == 1
|
|
||||||
stopCalled = true;
|
|
||||||
#endif
|
|
||||||
if (SmgrHandle smgr = _mgr.lock()) {
|
|
||||||
smgr->removeInDataHandler(*this);
|
|
||||||
} else {
|
|
||||||
DEBUGTRACE_PRINT("No stream manager alive anymore!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
InDataHandler::~InDataHandler() {
|
|
||||||
|
|
||||||
DEBUGTRACE_ENTER;
|
|
||||||
#if LASP_DEBUG == 1
|
|
||||||
// checkRightThread();
|
|
||||||
if (!stopCalled) {
|
|
||||||
std::cerr << "************ BUG: Stop function not called while arriving at "
|
|
||||||
"InDataHandler's destructor. Fix this by calling "
|
|
||||||
"InDataHandler::stop()."
|
|
||||||
<< std::endl;
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
#if LASP_DEBUG == 1
|
|
||||||
void InDataHandler::checkRightThread() const {
|
|
||||||
assert(std::this_thread::get_id() == main_thread_id);
|
|
||||||
}
|
|
||||||
#endif
|
|
@ -1,79 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "lasp_types.h"
|
|
||||||
#include <atomic>
|
|
||||||
#include <functional>
|
|
||||||
#include <memory>
|
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
class StreamMgr;
|
|
||||||
using SmgrHandle = std::shared_ptr<StreamMgr>;
|
|
||||||
|
|
||||||
class DaqData;
|
|
||||||
class Daq;
|
|
||||||
|
|
||||||
/** \addtogroup device
|
|
||||||
* @{
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief The function definition of callbacks with incoming DAQ data
|
|
||||||
*/
|
|
||||||
using InCallbackType = std::function<void(const DaqData &)>;
|
|
||||||
/**
|
|
||||||
* @brief Function definition for the reset callback.
|
|
||||||
*/
|
|
||||||
using ResetCallbackType = std::function<void(const Daq *)>;
|
|
||||||
|
|
||||||
class InDataHandler {
|
|
||||||
|
|
||||||
protected:
|
|
||||||
std::weak_ptr<StreamMgr> _mgr;
|
|
||||||
#if LASP_DEBUG == 1
|
|
||||||
// This is a flag to indicate whether the method stop() is called for the
|
|
||||||
// current handler. It should call the method stop() from the derived class's
|
|
||||||
// destructor.
|
|
||||||
std::atomic<bool> stopCalled{false};
|
|
||||||
#endif
|
|
||||||
|
|
||||||
public:
|
|
||||||
~InDataHandler();
|
|
||||||
const InCallbackType inCallback;
|
|
||||||
const ResetCallbackType reset;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief When constructed, the handler is added to the stream manager, which
|
|
||||||
* will call the handlers's inCallback() until stop() is called.
|
|
||||||
*
|
|
||||||
* @param mgr Stream manager.
|
|
||||||
* @param cb The callback that is stored, and called on new DAQ data
|
|
||||||
* @param resetfcn The callback that is stored, and called when the DAQ
|
|
||||||
* changes state.
|
|
||||||
*/
|
|
||||||
InDataHandler(SmgrHandle mgr, InCallbackType cb,
|
|
||||||
ResetCallbackType resetfcn);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Adds the current InDataHandler to the list of handlers in the
|
|
||||||
* StreamMgr. After this happens, the reset() method stored in this
|
|
||||||
* object is called back. When the stream is running, right after this,
|
|
||||||
* inCallback() is called with DaqData.
|
|
||||||
*/
|
|
||||||
void start();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Removes the currend InDataHandler from the list of handlers in the
|
|
||||||
* StreamMgr. From that point on, the object can be safely destroyed. Not
|
|
||||||
* calling stop() before destruction of this object is considered a BUG. I.e.
|
|
||||||
* a class which *uses* an InDataHandler should always call stop() in its
|
|
||||||
* destructor.
|
|
||||||
*/
|
|
||||||
void stop();
|
|
||||||
|
|
||||||
#if LASP_DEBUG == 1
|
|
||||||
const std::thread::id main_thread_id;
|
|
||||||
void checkRightThread() const;
|
|
||||||
#else
|
|
||||||
void checkRightThread() const {}
|
|
||||||
#endif
|
|
||||||
};
|
|
||||||
/** @} */
|
|
@ -1,35 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "lasp_daq.h"
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
/** \addtogroup device
|
|
||||||
* @{
|
|
||||||
* \defgroup rtaudio RtAudio backend
|
|
||||||
* This code is used to interface with the RtAudio cross-platform audio
|
|
||||||
* interface.
|
|
||||||
*
|
|
||||||
* \addtogroup rtaudio
|
|
||||||
* @{
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Method called from Daq::createDaq.
|
|
||||||
*
|
|
||||||
* @param devinfo Device info
|
|
||||||
* @param config DAQ Configuration settings
|
|
||||||
*
|
|
||||||
* @return Pointer to Daq instance. Throws Runtime errors on error.
|
|
||||||
*/
|
|
||||||
std::unique_ptr<Daq> createRtAudioDevice(const DeviceInfo& devinfo,
|
|
||||||
const DaqConfiguration& config);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Append RtAudio backend devices to the list
|
|
||||||
*
|
|
||||||
* @param devinfolist List to append to
|
|
||||||
*/
|
|
||||||
void fillRtAudioDeviceInfo(DeviceInfoList &devinfolist);
|
|
||||||
|
|
||||||
/** @} */
|
|
||||||
/** @} */
|
|
@ -1,561 +0,0 @@
|
|||||||
// #define DEBUGTRACE_ENABLED
|
|
||||||
#include "debugtrace.hpp"
|
|
||||||
#include "lasp_config.h"
|
|
||||||
|
|
||||||
#if LASP_HAS_PORTAUDIO == 1
|
|
||||||
#include <gsl-lite/gsl-lite.hpp>
|
|
||||||
#include <mutex>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include "lasp_portaudiodaq.h"
|
|
||||||
#include "portaudio.h"
|
|
||||||
|
|
||||||
using rte = std::runtime_error;
|
|
||||||
using std::cerr;
|
|
||||||
using std::endl;
|
|
||||||
using std::string;
|
|
||||||
using std::to_string;
|
|
||||||
|
|
||||||
#if LASP_HAS_PA_ALSA
|
|
||||||
#include <alsa/asoundlib.h>
|
|
||||||
void empty_handler(const char *file, int line, const char *function, int err,
|
|
||||||
const char *fmt, ...) {
|
|
||||||
|
|
||||||
// cerr << "Test empty error handler...\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Temporarily set the ALSA eror handler to something that does nothing, to
|
|
||||||
// prevent ALSA from spitting out all kinds of misconfiguration errors.
|
|
||||||
class MuteErrHandler {
|
|
||||||
private:
|
|
||||||
snd_lib_error_handler_t _default_handler;
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit MuteErrHandler() {
|
|
||||||
_default_handler = snd_lib_error;
|
|
||||||
snd_lib_error_set_handler(empty_handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
~MuteErrHandler() { snd_lib_error_set_handler(_default_handler); }
|
|
||||||
};
|
|
||||||
#else
|
|
||||||
// Does nothin in case of no ALSA
|
|
||||||
class MuteErrHandler {};
|
|
||||||
#endif
|
|
||||||
|
|
||||||
inline void throwIfError(PaError e) {
|
|
||||||
DEBUGTRACE_ENTER;
|
|
||||||
if (e != paNoError) {
|
|
||||||
throw rte(string("PortAudio backend error: ") + Pa_GetErrorText(e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Device info, plus PortAudio stuff
|
|
||||||
*/
|
|
||||||
class OurPaDeviceInfo : public DeviceInfo {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Store instance to PaDeviceInfo.
|
|
||||||
*/
|
|
||||||
PaDeviceInfo _paDevInfo;
|
|
||||||
|
|
||||||
virtual std::unique_ptr<DeviceInfo> clone() const override final {
|
|
||||||
return std::make_unique<OurPaDeviceInfo>(*this);
|
|
||||||
}
|
|
||||||
OurPaDeviceInfo &operator=(const OurPaDeviceInfo &) = delete;
|
|
||||||
OurPaDeviceInfo(const OurPaDeviceInfo &) = default;
|
|
||||||
OurPaDeviceInfo(const PaDeviceInfo &o) : DeviceInfo(), _paDevInfo(o) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist) {
|
|
||||||
DEBUGTRACE_ENTER;
|
|
||||||
bool shouldPaTerminate = false;
|
|
||||||
MuteErrHandler guard;
|
|
||||||
try {
|
|
||||||
PaError err = Pa_Initialize();
|
|
||||||
/// PortAudio says that Pa_Terminate() should not be called whenever there
|
|
||||||
/// is an error in Pa_Initialize(). This is opposite to what most examples
|
|
||||||
/// of PortAudio show.
|
|
||||||
throwIfError(err);
|
|
||||||
shouldPaTerminate = true;
|
|
||||||
|
|
||||||
auto fin = gsl::finally([&err] {
|
|
||||||
DEBUGTRACE_PRINT("Terminating PortAudio instance");
|
|
||||||
err = Pa_Terminate();
|
|
||||||
if (err != paNoError) {
|
|
||||||
cerr << "Error terminating PortAudio. Do not know what to do." << endl;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const PaHostApiIndex apicount = Pa_GetHostApiCount();
|
|
||||||
if (apicount < 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
/* const PaDeviceInfo *deviceInfo; */
|
|
||||||
const int numDevices = Pa_GetDeviceCount();
|
|
||||||
if (numDevices < 0) {
|
|
||||||
throw rte("PortAudio could not find any devices");
|
|
||||||
}
|
|
||||||
for (us i = 0; i < (us)numDevices; i++) {
|
|
||||||
/* DEBUGTRACE_PRINT(i); */
|
|
||||||
bool hasDuplexMode = false;
|
|
||||||
|
|
||||||
const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(i);
|
|
||||||
if (!deviceInfo) {
|
|
||||||
throw rte("No device info struct returned");
|
|
||||||
}
|
|
||||||
OurPaDeviceInfo d(*deviceInfo);
|
|
||||||
// We store the name in d.device_name
|
|
||||||
d._paDevInfo.name = nullptr;
|
|
||||||
d.device_name = deviceInfo->name;
|
|
||||||
|
|
||||||
const PaHostApiInfo *hostapiinfo = Pa_GetHostApiInfo(deviceInfo->hostApi);
|
|
||||||
if (hostapiinfo == nullptr) {
|
|
||||||
throw std::runtime_error("Hostapi nullptr!");
|
|
||||||
}
|
|
||||||
switch (hostapiinfo->type) {
|
|
||||||
case paALSA:
|
|
||||||
// Duplex mode for alsa
|
|
||||||
hasDuplexMode = true;
|
|
||||||
d.api = portaudioALSAApi;
|
|
||||||
break;
|
|
||||||
case paASIO:
|
|
||||||
hasDuplexMode = true;
|
|
||||||
d.api = portaudioASIOApi;
|
|
||||||
break;
|
|
||||||
case paDirectSound:
|
|
||||||
d.api = portaudioDirectSoundApi;
|
|
||||||
break;
|
|
||||||
case paMME:
|
|
||||||
d.api = portaudioWMMEApi;
|
|
||||||
break;
|
|
||||||
case paWDMKS:
|
|
||||||
d.api = portaudioWDMKS;
|
|
||||||
break;
|
|
||||||
case paWASAPI:
|
|
||||||
d.api = portaudioWASAPIApi;
|
|
||||||
break;
|
|
||||||
case paPulseAudio:
|
|
||||||
d.api = portaudioPulseApi;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw rte("Unimplemented portaudio API!");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
d.availableDataTypes = {DataTypeDescriptor::DataType::dtype_int16,
|
|
||||||
DataTypeDescriptor::DataType::dtype_int32,
|
|
||||||
DataTypeDescriptor::DataType::dtype_fl32};
|
|
||||||
|
|
||||||
d.prefDataTypeIndex = 2;
|
|
||||||
|
|
||||||
d.availableSampleRates = {8000.0, 9600.0, 11025.0, 12000.0, 16000.0,
|
|
||||||
22050.0, 24000.0, 32000.0, 44100.0, 48000.0,
|
|
||||||
88200.0, 96000.0, 192000.0};
|
|
||||||
d.prefSampleRateIndex = 9;
|
|
||||||
|
|
||||||
d.availableFramesPerBlock = {512, 1024, 2048, 4096, 8192};
|
|
||||||
d.prefFramesPerBlockIndex = 2;
|
|
||||||
|
|
||||||
d.availableInputRanges = {1.0};
|
|
||||||
// d.prefInputRangeIndex = 0; // Constructor-defined
|
|
||||||
d.availableOutputRanges = {1.0};
|
|
||||||
// d.prefOutputRangeIndex = 0; // Constructor-defined
|
|
||||||
|
|
||||||
d.ninchannels = deviceInfo->maxInputChannels;
|
|
||||||
d.noutchannels = deviceInfo->maxOutputChannels;
|
|
||||||
|
|
||||||
// Duplex mode, only for ALSA devices
|
|
||||||
d.hasDuplexMode = hasDuplexMode;
|
|
||||||
|
|
||||||
devinfolist.push_back(std::make_unique<OurPaDeviceInfo>(d));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
catch (rte &e) {
|
|
||||||
if (shouldPaTerminate) {
|
|
||||||
PaError err = Pa_Terminate();
|
|
||||||
if (err != paNoError) {
|
|
||||||
cerr << "Error terminating PortAudio. Do not know what to do." << endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cerr << "PortAudio backend error: " << e.what() << std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Forward declaration of raw callback. Calls into
|
|
||||||
* PortAudioDaq->memberPaCallback. Undocumented parameters are specified
|
|
||||||
* in memberPaCallback
|
|
||||||
*
|
|
||||||
* @param inputBuffer
|
|
||||||
* @param outputBuffer
|
|
||||||
* @param framesPerBuffer
|
|
||||||
* @param timeInfo
|
|
||||||
* @param statusFlags
|
|
||||||
* @param userData Pointer to PortAudioDaq* instance.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
static int rawPaCallback(const void *inputBuffer, void *outputBuffer,
|
|
||||||
unsigned long framesPerBuffer,
|
|
||||||
const PaStreamCallbackTimeInfo *timeInfo,
|
|
||||||
PaStreamCallbackFlags statusFlags, void *userData);
|
|
||||||
|
|
||||||
class PortAudioDaq : public Daq {
|
|
||||||
PaStream *_stream = nullptr;
|
|
||||||
std::atomic<StreamStatus::StreamError> _streamError =
|
|
||||||
StreamStatus::StreamError::noError;
|
|
||||||
InDaqCallback _incallback;
|
|
||||||
OutDaqCallback _outcallback;
|
|
||||||
|
|
||||||
public:
|
|
||||||
PortAudioDaq(const OurPaDeviceInfo &devinfo_gen,
|
|
||||||
const DaqConfiguration &config);
|
|
||||||
|
|
||||||
void start(InDaqCallback inCallback,
|
|
||||||
OutDaqCallback outCallback) override final;
|
|
||||||
void stop() override final;
|
|
||||||
|
|
||||||
StreamStatus getStreamStatus() const override final;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Member va
|
|
||||||
*
|
|
||||||
* @param inputBuffer
|
|
||||||
* @param outputBuffer
|
|
||||||
* @param framesPerBuffer
|
|
||||||
* @param timeInfo
|
|
||||||
* @param statusFlags
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
int memberPaCallback(const void *inputBuffer, void *outputBuffer,
|
|
||||||
unsigned long framesPerBuffer,
|
|
||||||
const PaStreamCallbackTimeInfo *timeInfo,
|
|
||||||
PaStreamCallbackFlags statusFlags);
|
|
||||||
~PortAudioDaq();
|
|
||||||
};
|
|
||||||
|
|
||||||
std::unique_ptr<Daq> createPortAudioDevice(const DeviceInfo &devinfo,
|
|
||||||
const DaqConfiguration &config) {
|
|
||||||
DEBUGTRACE_ENTER;
|
|
||||||
const OurPaDeviceInfo *_info =
|
|
||||||
dynamic_cast<const OurPaDeviceInfo *>(&devinfo);
|
|
||||||
if (_info == nullptr) {
|
|
||||||
throw rte("BUG: Could not cast DeviceInfo to OurPaDeviceInfo");
|
|
||||||
}
|
|
||||||
return std::make_unique<PortAudioDaq>(*_info, config);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int rawPaCallback(const void *inputBuffer, void *outputBuffer,
|
|
||||||
unsigned long framesPerBuffer,
|
|
||||||
const PaStreamCallbackTimeInfo *timeInfo,
|
|
||||||
PaStreamCallbackFlags statusFlags, void *userData) {
|
|
||||||
return static_cast<PortAudioDaq *>(userData)->memberPaCallback(
|
|
||||||
inputBuffer, outputBuffer, framesPerBuffer, timeInfo, statusFlags);
|
|
||||||
}
|
|
||||||
|
|
||||||
PortAudioDaq::PortAudioDaq(const OurPaDeviceInfo &devinfo_gen,
|
|
||||||
const DaqConfiguration &config)
|
|
||||||
: Daq(devinfo_gen, config) {
|
|
||||||
DEBUGTRACE_ENTER;
|
|
||||||
bool shouldPaTerminate = false;
|
|
||||||
try {
|
|
||||||
PaError err = Pa_Initialize();
|
|
||||||
/// PortAudio says that Pa_Terminate() should not be called whenever there
|
|
||||||
/// is an error in Pa_Initialize(). This is opposite to what most examples
|
|
||||||
/// of PortAudio show.
|
|
||||||
throwIfError(err);
|
|
||||||
|
|
||||||
// OK, Pa_Initialize successfully finished, it means we have to clean up
|
|
||||||
// with Pa_Terminate in the destructor.
|
|
||||||
shouldPaTerminate = true;
|
|
||||||
|
|
||||||
// Going to find the device in the list. If its there, we have to retrieve
|
|
||||||
// the index, as this is required in the PaStreamParameters struct
|
|
||||||
int devindex = -1;
|
|
||||||
for (int i = 0; i < Pa_GetDeviceCount(); i++) {
|
|
||||||
// DEBUGTRACE_PRINT(i);
|
|
||||||
bool ok = true;
|
|
||||||
const PaDeviceInfo *info = Pa_GetDeviceInfo(i);
|
|
||||||
if (!info) {
|
|
||||||
throw rte("No device structure returned from PortAudio");
|
|
||||||
}
|
|
||||||
ok &= string(info->name) == devinfo_gen.device_name;
|
|
||||||
ok &= info->hostApi == devinfo_gen._paDevInfo.hostApi;
|
|
||||||
ok &= info->maxInputChannels == devinfo_gen._paDevInfo.maxInputChannels;
|
|
||||||
ok &= info->maxOutputChannels == devinfo_gen._paDevInfo.maxOutputChannels;
|
|
||||||
ok &= info->defaultSampleRate == devinfo_gen._paDevInfo.defaultSampleRate;
|
|
||||||
|
|
||||||
if (ok) {
|
|
||||||
devindex = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (devindex < 0) {
|
|
||||||
throw rte(string("Device not found: ") + string(devinfo_gen.device_name));
|
|
||||||
}
|
|
||||||
|
|
||||||
using Dtype = DataTypeDescriptor::DataType;
|
|
||||||
const Dtype dtype = dataType();
|
|
||||||
// Sample format is bit flag
|
|
||||||
PaSampleFormat format = paNonInterleaved;
|
|
||||||
switch (dtype) {
|
|
||||||
case Dtype::dtype_fl32:
|
|
||||||
DEBUGTRACE_PRINT("Datatype float32");
|
|
||||||
format |= paFloat32;
|
|
||||||
break;
|
|
||||||
case Dtype::dtype_fl64:
|
|
||||||
DEBUGTRACE_PRINT("Datatype float64");
|
|
||||||
throw rte("Invalid data type specified for DAQ stream.");
|
|
||||||
break;
|
|
||||||
case Dtype::dtype_int8:
|
|
||||||
DEBUGTRACE_PRINT("Datatype int8");
|
|
||||||
format |= paInt8;
|
|
||||||
break;
|
|
||||||
case Dtype::dtype_int16:
|
|
||||||
DEBUGTRACE_PRINT("Datatype int16");
|
|
||||||
format |= paInt16;
|
|
||||||
break;
|
|
||||||
case Dtype::dtype_int32:
|
|
||||||
DEBUGTRACE_PRINT("Datatype int32");
|
|
||||||
format |= paInt32;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw rte("Invalid data type specified for DAQ stream.");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<PaStreamParameters> instreamParams;
|
|
||||||
std::unique_ptr<PaStreamParameters> outstreamParams;
|
|
||||||
|
|
||||||
if (neninchannels() > 0) {
|
|
||||||
instreamParams = std::make_unique<PaStreamParameters>(PaStreamParameters(
|
|
||||||
{.device = devindex,
|
|
||||||
.channelCount = (int)getHighestEnabledInChannel() + 1,
|
|
||||||
.sampleFormat = format,
|
|
||||||
.suggestedLatency = framesPerBlock() / samplerate(),
|
|
||||||
.hostApiSpecificStreamInfo = nullptr}));
|
|
||||||
}
|
|
||||||
if (nenoutchannels() > 0) {
|
|
||||||
outstreamParams = std::make_unique<PaStreamParameters>(PaStreamParameters(
|
|
||||||
{.device = devindex,
|
|
||||||
.channelCount = (int)getHighestEnabledOutChannel() + 1,
|
|
||||||
.sampleFormat = format,
|
|
||||||
.suggestedLatency = framesPerBlock() / samplerate(),
|
|
||||||
.hostApiSpecificStreamInfo = nullptr}));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Next step: check whether we are OK
|
|
||||||
err = Pa_IsFormatSupported(instreamParams.get(), outstreamParams.get(),
|
|
||||||
samplerate());
|
|
||||||
throwIfError(err);
|
|
||||||
|
|
||||||
err = Pa_OpenStream(&_stream, // stream
|
|
||||||
instreamParams.get(), // inputParameters
|
|
||||||
outstreamParams.get(), // outputParameters
|
|
||||||
samplerate(), // yeah,
|
|
||||||
framesPerBlock(), // framesPerBuffer
|
|
||||||
paNoFlag, // streamFlags
|
|
||||||
rawPaCallback, this);
|
|
||||||
throwIfError(err);
|
|
||||||
assert(_stream);
|
|
||||||
} catch (rte &e) {
|
|
||||||
if (shouldPaTerminate) {
|
|
||||||
PaError err = Pa_Terminate();
|
|
||||||
if (err != paNoError) {
|
|
||||||
cerr << "Error terminating PortAudio. Do not know what to do." << endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PortAudioDaq::start(InDaqCallback inCallback, OutDaqCallback outCallback) {
|
|
||||||
DEBUGTRACE_ENTER;
|
|
||||||
assert(_stream);
|
|
||||||
MuteErrHandler guard;
|
|
||||||
|
|
||||||
if (Pa_IsStreamActive(_stream)) {
|
|
||||||
throw rte("Stream is already running");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (neninchannels() > 0) {
|
|
||||||
if (!inCallback) {
|
|
||||||
throw rte(
|
|
||||||
|
|
||||||
"Input callback given, but stream does not provide input data");
|
|
||||||
}
|
|
||||||
|
|
||||||
_incallback = inCallback;
|
|
||||||
}
|
|
||||||
if (nenoutchannels() > 0) {
|
|
||||||
if (!outCallback) {
|
|
||||||
throw rte(
|
|
||||||
"Output callback given, but stream does not provide output data");
|
|
||||||
}
|
|
||||||
_outcallback = outCallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
PaError err = Pa_StartStream(_stream);
|
|
||||||
throwIfError(err);
|
|
||||||
}
|
|
||||||
void PortAudioDaq::stop() {
|
|
||||||
DEBUGTRACE_ENTER;
|
|
||||||
assert(_stream);
|
|
||||||
if (Pa_IsStreamStopped(_stream) > 1) {
|
|
||||||
throw rte("Stream is already stopped");
|
|
||||||
}
|
|
||||||
PaError err = Pa_StopStream(_stream);
|
|
||||||
throwIfError(err);
|
|
||||||
}
|
|
||||||
Daq::StreamStatus PortAudioDaq::getStreamStatus() const {
|
|
||||||
DEBUGTRACE_ENTER;
|
|
||||||
// Stores an error type and whether the
|
|
||||||
Daq::StreamStatus status;
|
|
||||||
using StreamError = Daq::StreamStatus::StreamError;
|
|
||||||
Daq::StreamStatus::StreamError errortype = _streamError.load();
|
|
||||||
|
|
||||||
PaError err = Pa_IsStreamStopped(_stream);
|
|
||||||
if (err > 1) {
|
|
||||||
// Stream is stopped due to an error in the callback. The exact error type
|
|
||||||
// is filled in in the if-statement above
|
|
||||||
return status;
|
|
||||||
} else if (err == 0) {
|
|
||||||
// Still running
|
|
||||||
status.isRunning = true;
|
|
||||||
} else if (err < 0) {
|
|
||||||
// Stream encountered an error.
|
|
||||||
switch (err) {
|
|
||||||
case paInternalError:
|
|
||||||
errortype = StreamError::driverError;
|
|
||||||
break;
|
|
||||||
case paDeviceUnavailable:
|
|
||||||
errortype = StreamError::driverError;
|
|
||||||
break;
|
|
||||||
case paInputOverflowed:
|
|
||||||
errortype = StreamError::inputXRun;
|
|
||||||
break;
|
|
||||||
case paOutputUnderflowed:
|
|
||||||
errortype = StreamError::outputXRun;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
errortype = StreamError::driverError;
|
|
||||||
cerr << "Portaudio backend error:" << Pa_GetErrorText(err) << endl;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
status.errorType = errortype;
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
PortAudioDaq::~PortAudioDaq() {
|
|
||||||
DEBUGTRACE_ENTER;
|
|
||||||
PaError err;
|
|
||||||
assert(_stream);
|
|
||||||
if (Pa_IsStreamActive(_stream)) {
|
|
||||||
// Stop the stream first
|
|
||||||
stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
err = Pa_CloseStream(_stream);
|
|
||||||
_stream = nullptr;
|
|
||||||
if (err != paNoError) {
|
|
||||||
cerr << "Error closing PortAudio stream. Do not know what to do." << endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
err = Pa_Terminate();
|
|
||||||
if (err != paNoError) {
|
|
||||||
cerr << "Error terminating PortAudio. Do not know what to do." << endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int PortAudioDaq::memberPaCallback(const void *inputBuffer, void *outputBuffer,
|
|
||||||
unsigned long framesPerBuffer,
|
|
||||||
const PaStreamCallbackTimeInfo *timeInfo,
|
|
||||||
PaStreamCallbackFlags statusFlags) {
|
|
||||||
DEBUGTRACE_ENTER;
|
|
||||||
typedef Daq::StreamStatus::StreamError se;
|
|
||||||
if (statusFlags & paPrimingOutput) {
|
|
||||||
// Initial output buffers generated. So nothing with input yet
|
|
||||||
return paContinue;
|
|
||||||
}
|
|
||||||
if ((statusFlags & paInputUnderflow) || (statusFlags & paInputOverflow)) {
|
|
||||||
_streamError = se::inputXRun;
|
|
||||||
return paAbort;
|
|
||||||
}
|
|
||||||
if ((statusFlags & paOutputUnderflow) || (statusFlags & paOutputOverflow)) {
|
|
||||||
_streamError = se::outputXRun;
|
|
||||||
return paAbort;
|
|
||||||
}
|
|
||||||
if (framesPerBuffer != framesPerBlock()) {
|
|
||||||
cerr << "Logic error: expected a block size of: " << framesPerBlock()
|
|
||||||
<< endl;
|
|
||||||
_streamError = se::logicError;
|
|
||||||
return paAbort;
|
|
||||||
}
|
|
||||||
|
|
||||||
const us neninchannels = this->neninchannels();
|
|
||||||
const us nenoutchannels = this->nenoutchannels();
|
|
||||||
const auto &dtype_descr = dtypeDescr();
|
|
||||||
const auto dtype = dataType();
|
|
||||||
const us sw = dtype_descr.sw;
|
|
||||||
if (inputBuffer) {
|
|
||||||
assert(_incallback);
|
|
||||||
std::vector<byte_t *> ptrs;
|
|
||||||
ptrs.reserve(neninchannels);
|
|
||||||
|
|
||||||
const us ch_min = getLowestEnabledInChannel();
|
|
||||||
const us ch_max = getHighestEnabledInChannel();
|
|
||||||
assert(ch_min < ninchannels);
|
|
||||||
assert(ch_max < ninchannels);
|
|
||||||
|
|
||||||
/// Only pass on the pointers of the channels we want. inputBuffer is
|
|
||||||
/// noninterleaved, as specified in PortAudioDaq constructor.
|
|
||||||
for (us ch = ch_min; ch <= ch_max; ch++) {
|
|
||||||
if (inchannel_config.at(ch).enabled) {
|
|
||||||
byte_t *ch_ptr =
|
|
||||||
reinterpret_cast<byte_t **>(const_cast<void *>(inputBuffer))[ch];
|
|
||||||
ptrs.push_back(ch_ptr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DaqData d{framesPerBuffer, neninchannels, dtype};
|
|
||||||
d.copyInFromRaw(ptrs);
|
|
||||||
|
|
||||||
_incallback(d);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (outputBuffer) {
|
|
||||||
assert(_outcallback);
|
|
||||||
std::vector<byte_t *> ptrs;
|
|
||||||
ptrs.reserve(nenoutchannels);
|
|
||||||
|
|
||||||
/* outCallback */
|
|
||||||
|
|
||||||
const us ch_min = getLowestEnabledOutChannel();
|
|
||||||
const us ch_max = getHighestEnabledOutChannel();
|
|
||||||
assert(ch_min < noutchannels);
|
|
||||||
assert(ch_max < noutchannels);
|
|
||||||
/// Only pass on the pointers of the channels we want
|
|
||||||
for (us ch = ch_min; ch <= ch_max; ch++) {
|
|
||||||
if (outchannel_config.at(ch).enabled) {
|
|
||||||
byte_t *ch_ptr = reinterpret_cast<byte_t **>(outputBuffer)[ch];
|
|
||||||
ptrs.push_back(ch_ptr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DaqData d{framesPerBuffer, nenoutchannels, dtype};
|
|
||||||
|
|
||||||
_outcallback(d);
|
|
||||||
// Copy over the buffer
|
|
||||||
us j = 0;
|
|
||||||
for (auto ptr : ptrs) {
|
|
||||||
d.copyToRaw(j, ptr);
|
|
||||||
j++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return paContinue;
|
|
||||||
}
|
|
||||||
#endif
|
|
@ -1,35 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "lasp_daq.h"
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
/** \addtogroup device
|
|
||||||
* @{
|
|
||||||
* \defgroup portaudio PortAudio backend
|
|
||||||
* This code is used to interface with the PortAudio cross-platform audio
|
|
||||||
* interface.
|
|
||||||
*
|
|
||||||
* \addtogroup portaudio
|
|
||||||
* @{
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Method called from Daq::createDaq.
|
|
||||||
*
|
|
||||||
* @param devinfo Device info
|
|
||||||
* @param config DAQ Configuration settings
|
|
||||||
*
|
|
||||||
* @return Pointer to Daq instance. Throws Runtime errors on error.
|
|
||||||
*/
|
|
||||||
std::unique_ptr<Daq> createPortAudioDevice(const DeviceInfo& devinfo,
|
|
||||||
const DaqConfiguration& config);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Append PortAudio backend devices to the list
|
|
||||||
*
|
|
||||||
* @param devinfolist List to append to
|
|
||||||
*/
|
|
||||||
void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist);
|
|
||||||
|
|
||||||
/** @} */
|
|
||||||
/** @} */
|
|
@ -1,245 +0,0 @@
|
|||||||
/* #define DEBUGTRACE_ENABLED */
|
|
||||||
#include "debugtrace.hpp"
|
|
||||||
#include "lasp_config.h"
|
|
||||||
|
|
||||||
#if LASP_HAS_ULDAQ == 1
|
|
||||||
#include "lasp_uldaq_bufhandler.h"
|
|
||||||
#include "lasp_daq.h"
|
|
||||||
|
|
||||||
InBufHandler::InBufHandler(DT9837A &daq, InDaqCallback cb)
|
|
||||||
: BufHandler(daq, daq.neninchannels()), cb(cb)
|
|
||||||
|
|
||||||
{
|
|
||||||
DEBUGTRACE_ENTER;
|
|
||||||
assert(daq.getHandle() != 0);
|
|
||||||
|
|
||||||
monitorOutput = daq.monitorOutput;
|
|
||||||
|
|
||||||
DaqInScanFlag inscanflags = DAQINSCAN_FF_DEFAULT;
|
|
||||||
ScanOption scanoptions = SO_CONTINUOUS;
|
|
||||||
UlError err = ERR_NO_ERROR;
|
|
||||||
|
|
||||||
std::vector<DaqInChanDescriptor> indescs;
|
|
||||||
boolvec eninchannels_without_mon = daq.eninchannels(false);
|
|
||||||
|
|
||||||
// Set ranges for each input. Below asks only channels that are not a
|
|
||||||
// monitor channel (hence the false flag).
|
|
||||||
dvec ranges = daq.inputRangeForEnabledChannels(false);
|
|
||||||
|
|
||||||
us enabled_ch_counter = 0;
|
|
||||||
for (us chin = 0; chin < 4; chin++) {
|
|
||||||
if (eninchannels_without_mon[chin] == true) {
|
|
||||||
DaqInChanDescriptor indesc;
|
|
||||||
indesc.type = DAQI_ANALOG_SE;
|
|
||||||
indesc.channel = chin;
|
|
||||||
|
|
||||||
double rangeval = ranges.at(enabled_ch_counter);
|
|
||||||
Range rangenum;
|
|
||||||
if (fabs(rangeval - 1.0) < 1e-8) {
|
|
||||||
rangenum = BIP1VOLTS;
|
|
||||||
} else if (fabs(rangeval - 10.0) < 1e-8) {
|
|
||||||
rangenum = BIP10VOLTS;
|
|
||||||
} else {
|
|
||||||
throw Daq::StreamException(Daq::StreamStatus::StreamError::logicError);
|
|
||||||
std::cerr << "Fatal: input range value is invalid" << endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
indesc.range = rangenum;
|
|
||||||
indescs.push_back(indesc);
|
|
||||||
enabled_ch_counter++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add possibly last channel as monitor
|
|
||||||
if (monitorOutput) {
|
|
||||||
DaqInChanDescriptor indesc;
|
|
||||||
indesc.type = DAQI_DAC;
|
|
||||||
indesc.channel = 0;
|
|
||||||
/// The output only has a range of 10V, therefore the monitor of the
|
|
||||||
/// output also has to be set to this value.
|
|
||||||
indesc.range = BIP10VOLTS;
|
|
||||||
indescs.push_back(indesc);
|
|
||||||
}
|
|
||||||
assert(indescs.size() == nchannels);
|
|
||||||
|
|
||||||
DEBUGTRACE_MESSAGE("Starting input scan");
|
|
||||||
|
|
||||||
err = ulDaqInScan(daq.getHandle(), indescs.data(), nchannels,
|
|
||||||
2 * nFramesPerBlock, // Watch the 2 here!
|
|
||||||
&samplerate, scanoptions, inscanflags, buf.data());
|
|
||||||
throwOnPossibleUlException(err);
|
|
||||||
}
|
|
||||||
void InBufHandler::start() {
|
|
||||||
|
|
||||||
DEBUGTRACE_ENTER;
|
|
||||||
ScanStatus status;
|
|
||||||
TransferStatus transferStatus;
|
|
||||||
UlError err = ulDaqInScanStatus(daq.getHandle(), &status, &transferStatus);
|
|
||||||
throwOnPossibleUlException(err);
|
|
||||||
|
|
||||||
totalFramesCount = transferStatus.currentTotalCount;
|
|
||||||
topenqueued = true;
|
|
||||||
botenqueued = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool InBufHandler::operator()() {
|
|
||||||
|
|
||||||
/* DEBUGTRACE_ENTER; */
|
|
||||||
|
|
||||||
bool ret = true;
|
|
||||||
|
|
||||||
auto runCallback = ([&](us totalOffset) {
|
|
||||||
/* DEBUGTRACE_ENTER; */
|
|
||||||
|
|
||||||
DaqData data(nFramesPerBlock, nchannels, dtype_descr.dtype);
|
|
||||||
|
|
||||||
us monitorOffset = monitorOutput ? 1 : 0;
|
|
||||||
/* /// Put the output monitor in front */
|
|
||||||
if (monitorOutput) {
|
|
||||||
for (us frame = 0; frame < nFramesPerBlock; frame++) {
|
|
||||||
data.value<double>(frame, 0) =
|
|
||||||
buf[totalOffset // Offset to lowest part of the buffer, or not
|
|
||||||
+ (frame * nchannels) // Data is interleaved, so skip each
|
|
||||||
+ (nchannels - 1)] // Monitor comes as last in the channel list,
|
|
||||||
// but we want it first in the output data.
|
|
||||||
;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now, all normal channels
|
|
||||||
for (us channel = 0; channel < nchannels - monitorOffset; channel++) {
|
|
||||||
/* DEBUGTRACE_PRINT(channel); */
|
|
||||||
for (us frame = 0; frame < nFramesPerBlock; frame++) {
|
|
||||||
data.value<double>(frame, channel + monitorOffset) =
|
|
||||||
buf[totalOffset + (frame * nchannels) + channel];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cb(data);
|
|
||||||
});
|
|
||||||
|
|
||||||
ScanStatus status;
|
|
||||||
TransferStatus transferStatus;
|
|
||||||
|
|
||||||
UlError err = ulDaqInScanStatus(daq.getHandle(), &status, &transferStatus);
|
|
||||||
throwOnPossibleUlException(err);
|
|
||||||
|
|
||||||
us increment = transferStatus.currentTotalCount - totalFramesCount;
|
|
||||||
totalFramesCount += increment;
|
|
||||||
|
|
||||||
if (increment > nFramesPerBlock) {
|
|
||||||
throw Daq::StreamException(Daq::StreamStatus::StreamError::inputXRun);
|
|
||||||
}
|
|
||||||
assert(status == SS_RUNNING);
|
|
||||||
|
|
||||||
if (transferStatus.currentIndex < (long long)buffer_mid_idx) {
|
|
||||||
topenqueued = false;
|
|
||||||
if (!botenqueued) {
|
|
||||||
runCallback(nchannels * nFramesPerBlock);
|
|
||||||
botenqueued = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
botenqueued = false;
|
|
||||||
if (!topenqueued) {
|
|
||||||
runCallback(0);
|
|
||||||
topenqueued = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
InBufHandler::~InBufHandler() {
|
|
||||||
// At exit of the function, stop scanning.
|
|
||||||
DEBUGTRACE_ENTER;
|
|
||||||
UlError err = ulDaqInScanStop(daq.getHandle());
|
|
||||||
if (err != ERR_NO_ERROR) {
|
|
||||||
showErr(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
OutBufHandler::OutBufHandler(DT9837A &daq, OutDaqCallback cb)
|
|
||||||
: BufHandler(daq, daq.nenoutchannels()), cb(cb) {
|
|
||||||
|
|
||||||
DEBUGTRACE_MESSAGE("Starting output scan");
|
|
||||||
DEBUGTRACE_PRINT(nchannels);
|
|
||||||
AOutScanFlag outscanflags = AOUTSCAN_FF_DEFAULT;
|
|
||||||
ScanOption scanoptions = SO_CONTINUOUS;
|
|
||||||
UlError err = ulAOutScan(daq.getHandle(), 0, 0, BIP10VOLTS,
|
|
||||||
2 * nFramesPerBlock, // Watch the 2 here!
|
|
||||||
&samplerate, scanoptions, outscanflags, buf.data());
|
|
||||||
|
|
||||||
throwOnPossibleUlException(err);
|
|
||||||
}
|
|
||||||
void OutBufHandler::start() {
|
|
||||||
|
|
||||||
ScanStatus status;
|
|
||||||
TransferStatus transferStatus;
|
|
||||||
|
|
||||||
UlError err = ulAOutScanStatus(daq.getHandle(), &status, &transferStatus);
|
|
||||||
if (err != ERR_NO_ERROR) {
|
|
||||||
showErr(err);
|
|
||||||
throw rte("Unable to start output on DAQ");
|
|
||||||
}
|
|
||||||
if (status != SS_RUNNING) {
|
|
||||||
throw rte("Unable to start output on DAQ");
|
|
||||||
}
|
|
||||||
totalFramesCount = transferStatus.currentTotalCount;
|
|
||||||
topenqueued = true;
|
|
||||||
botenqueued = true;
|
|
||||||
}
|
|
||||||
bool OutBufHandler::operator()() {
|
|
||||||
|
|
||||||
DEBUGTRACE_ENTER;
|
|
||||||
bool res = true;
|
|
||||||
assert(daq.getHandle() != 0);
|
|
||||||
|
|
||||||
UlError err = ERR_NO_ERROR;
|
|
||||||
|
|
||||||
ScanStatus status;
|
|
||||||
TransferStatus transferStatus;
|
|
||||||
|
|
||||||
err = ulAOutScanStatus(daq.getHandle(), &status, &transferStatus);
|
|
||||||
throwOnPossibleUlException(err);
|
|
||||||
if (status != SS_RUNNING) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
us increment = transferStatus.currentTotalCount - totalFramesCount;
|
|
||||||
totalFramesCount += increment;
|
|
||||||
|
|
||||||
if (increment > nFramesPerBlock) {
|
|
||||||
cerr << "totalFramesCount: " << totalFramesCount << ". Detected output underrun" << endl;
|
|
||||||
/* throw Daq::StreamException(Daq::StreamStatus::StreamError::outputXRun); */
|
|
||||||
}
|
|
||||||
|
|
||||||
if (transferStatus.currentIndex < buffer_mid_idx) {
|
|
||||||
topenqueued = false;
|
|
||||||
if (!botenqueued) {
|
|
||||||
DaqData d(nFramesPerBlock, 1,// Only one output channel
|
|
||||||
dtype_descr.dtype);
|
|
||||||
// Receive data, run callback
|
|
||||||
cb(d);
|
|
||||||
d.copyToRaw(0, reinterpret_cast<byte_t *>(&(buf[buffer_mid_idx])));
|
|
||||||
|
|
||||||
botenqueued = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
botenqueued = false;
|
|
||||||
if (!topenqueued) {
|
|
||||||
DaqData d(nFramesPerBlock, 1,// Only one output channel
|
|
||||||
dtype_descr.dtype);
|
|
||||||
// Receive
|
|
||||||
cb(d);
|
|
||||||
d.copyToRaw(0, reinterpret_cast<byte_t *>(&(buf[0])));
|
|
||||||
|
|
||||||
topenqueued = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
OutBufHandler::~OutBufHandler() {
|
|
||||||
DEBUGTRACE_ENTER;
|
|
||||||
UlError err = ulAOutScanStop(daq.getHandle());
|
|
||||||
if (err != ERR_NO_ERROR) {
|
|
||||||
showErr(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
@ -1,46 +0,0 @@
|
|||||||
/* #define DEBUGTRACE_ENABLED */
|
|
||||||
#include "debugtrace.hpp"
|
|
||||||
#include "lasp_config.h"
|
|
||||||
|
|
||||||
#if LASP_HAS_ULDAQ == 1
|
|
||||||
#include "lasp_uldaq_common.h"
|
|
||||||
#include "lasp_daq.h"
|
|
||||||
|
|
||||||
string getErrMsg(UlError err) {
|
|
||||||
string errstr;
|
|
||||||
errstr.reserve(ERR_MSG_LEN);
|
|
||||||
char errmsg[ERR_MSG_LEN];
|
|
||||||
errstr = "UlDaq API Error: ";
|
|
||||||
ulGetErrMsg(err, errmsg);
|
|
||||||
errstr += errmsg;
|
|
||||||
return errstr;
|
|
||||||
}
|
|
||||||
void showErr(string errstr) {
|
|
||||||
std::cerr << "\b\n**************** UlDAQ backend error **********\n";
|
|
||||||
std::cerr << errstr << std::endl;
|
|
||||||
std::cerr << "***********************************************\n\n";
|
|
||||||
}
|
|
||||||
void showErr(UlError err) {
|
|
||||||
if (err != ERR_NO_ERROR)
|
|
||||||
showErr(getErrMsg(err));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void throwOnPossibleUlException(UlError err) {
|
|
||||||
if (err == ERR_NO_ERROR) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
string errstr = getErrMsg(err);
|
|
||||||
showErr(errstr);
|
|
||||||
Daq::StreamStatus::StreamError serr;
|
|
||||||
if ((int)err == 18) {
|
|
||||||
serr = Daq::StreamStatus::StreamError::inputXRun;
|
|
||||||
} else if ((int)err == 19) {
|
|
||||||
serr = Daq::StreamStatus::StreamError::outputXRun;
|
|
||||||
} else {
|
|
||||||
serr = Daq::StreamStatus::StreamError::driverError;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw Daq::StreamException(serr, errstr);
|
|
||||||
}
|
|
||||||
#endif
|
|
@ -1,66 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <uldaq.h>
|
|
||||||
#include <string>
|
|
||||||
#include "lasp_deviceinfo.h"
|
|
||||||
|
|
||||||
/** \addtogroup device
|
|
||||||
* @{
|
|
||||||
* \addtogroup uldaq
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @brief Throws an appropriate stream exception based on the UlError number.
|
|
||||||
* The mapping is based on the error numbers as given in uldaq.h. There are a
|
|
||||||
* log of errors definded here (109 in total). Except for some, we will map
|
|
||||||
* most of them to a driver error.
|
|
||||||
*
|
|
||||||
* @param e The backend error code.
|
|
||||||
*/
|
|
||||||
void throwOnPossibleUlException(UlError err);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Return a string corresponding to the UlDaq API error
|
|
||||||
*
|
|
||||||
* @param err error code
|
|
||||||
*
|
|
||||||
* @return Error string
|
|
||||||
*/
|
|
||||||
string getErrMsg(UlError err);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Print error message to stderr
|
|
||||||
*
|
|
||||||
* @param errstr The string to print
|
|
||||||
*/
|
|
||||||
void showErr(UlError err);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Get a string representation of the error
|
|
||||||
*
|
|
||||||
* @param errstr
|
|
||||||
*/
|
|
||||||
void showErr(std::string errstr);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief UlDaq-specific device information. Adds a copy of the underlying
|
|
||||||
* DaqDeDaqDeviceDescriptor.
|
|
||||||
*/
|
|
||||||
class UlDaqDeviceInfo : public DeviceInfo {
|
|
||||||
|
|
||||||
public:
|
|
||||||
DaqDeviceDescriptor _uldaqDescriptor;
|
|
||||||
virtual std::unique_ptr<DeviceInfo> clone() const override {
|
|
||||||
DEBUGTRACE_ENTER;
|
|
||||||
return std::make_unique<UlDaqDeviceInfo>(*this);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief List of available sampling frequencies for DT9837A
|
|
||||||
*/
|
|
||||||
const std::vector<d> ULDAQ_SAMPLERATES = {8000, 10000, 11025, 16000, 20000,
|
|
||||||
22050, 24000, 32000, 44056, 44100,
|
|
||||||
47250, 48000, 50000, 50400, 51000};
|
|
||||||
|
|
||||||
|
|
||||||
/** @} */
|
|
||||||
/** @} */
|
|
@ -1,212 +0,0 @@
|
|||||||
/* #define DEBUGTRACE_ENABLED */
|
|
||||||
#include "debugtrace.hpp"
|
|
||||||
#include "lasp_config.h"
|
|
||||||
|
|
||||||
#if LASP_HAS_ULDAQ == 1
|
|
||||||
#include "lasp_daqconfig.h"
|
|
||||||
#include "lasp_uldaq.h"
|
|
||||||
#include "lasp_uldaq_bufhandler.h"
|
|
||||||
#include "lasp_uldaq_impl.h"
|
|
||||||
|
|
||||||
using namespace std::literals::chrono_literals;
|
|
||||||
|
|
||||||
DT9837A::~DT9837A() {
|
|
||||||
DEBUGTRACE_ENTER;
|
|
||||||
UlError err;
|
|
||||||
if (isRunning()) {
|
|
||||||
DEBUGTRACE_PRINT("Stop UlDAQ from destructor");
|
|
||||||
stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_handle) {
|
|
||||||
DEBUGTRACE_PRINT("Disconnecting and releasing DaqDevice");
|
|
||||||
/* err = ulDisconnectDaqDevice(_handle); */
|
|
||||||
/* showErr(err); */
|
|
||||||
err = ulReleaseDaqDevice(_handle);
|
|
||||||
showErr(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DT9837A::DT9837A(const UlDaqDeviceInfo &devinfo, const DaqConfiguration &config)
|
|
||||||
: Daq(devinfo, config),
|
|
||||||
_nFramesPerBlock(availableFramesPerBlock.at(framesPerBlockIndex)) {
|
|
||||||
|
|
||||||
const DaqDeviceDescriptor &descriptor = devinfo._uldaqDescriptor;
|
|
||||||
DEBUGTRACE_PRINT(string("Device: ") + descriptor.productName);
|
|
||||||
DEBUGTRACE_PRINT(string("Product id: ") + to_string(descriptor.productId));
|
|
||||||
DEBUGTRACE_PRINT(string("Dev string: ") + descriptor.devString);
|
|
||||||
DEBUGTRACE_PRINT(string("Unique id: ") + descriptor.uniqueId);
|
|
||||||
|
|
||||||
// get a handle to the DAQ device associated with the first descriptor
|
|
||||||
_handle = ulCreateDaqDevice(descriptor);
|
|
||||||
|
|
||||||
if (_handle == 0) {
|
|
||||||
throw rte("Unable to create a handle to the specified DAQ "
|
|
||||||
"device. Is the device currently in use? Please make sure to set "
|
|
||||||
"the DAQ configuration in duplex mode if simultaneous input and "
|
|
||||||
"output is required.");
|
|
||||||
}
|
|
||||||
|
|
||||||
UlError err = ulConnectDaqDevice(_handle);
|
|
||||||
|
|
||||||
if (err != ERR_NO_ERROR) {
|
|
||||||
ulReleaseDaqDevice(_handle);
|
|
||||||
_handle = 0;
|
|
||||||
throw rte("Unable to connect to device: " + getErrMsg(err));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Loop over input channels, set parameters
|
|
||||||
for (us ch = 0; ch < 4; ch++) {
|
|
||||||
|
|
||||||
err = ulAISetConfigDbl(_handle, AI_CFG_CHAN_SENSOR_SENSITIVITY, ch, 1.0);
|
|
||||||
showErr(err);
|
|
||||||
if (err != ERR_NO_ERROR) {
|
|
||||||
throw rte("Fatal: could normalize channel sensitivity");
|
|
||||||
}
|
|
||||||
|
|
||||||
CouplingMode cm = inchannel_config.at(ch).ACCouplingMode ? CM_AC : CM_DC;
|
|
||||||
err = ulAISetConfig(_handle, AI_CFG_CHAN_COUPLING_MODE, ch, cm);
|
|
||||||
if (err != ERR_NO_ERROR) {
|
|
||||||
showErr(err);
|
|
||||||
throw rte("Fatal: could not set AC/DC coupling mode");
|
|
||||||
}
|
|
||||||
|
|
||||||
IepeMode iepe =
|
|
||||||
inchannel_config.at(ch).IEPEEnabled ? IEPE_ENABLED : IEPE_DISABLED;
|
|
||||||
err = ulAISetConfig(_handle, AI_CFG_CHAN_IEPE_MODE, ch, iepe);
|
|
||||||
if (err != ERR_NO_ERROR) {
|
|
||||||
showErr(err);
|
|
||||||
throw rte("Fatal: could not set IEPE mode");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DT9837A::isRunning() const {
|
|
||||||
DEBUGTRACE_ENTER;
|
|
||||||
/* return _thread.joinable(); */
|
|
||||||
StreamStatus status = _streamStatus;
|
|
||||||
return status.isRunning;
|
|
||||||
}
|
|
||||||
void DT9837A::stop() {
|
|
||||||
DEBUGTRACE_ENTER;
|
|
||||||
StreamStatus status = _streamStatus;
|
|
||||||
status.isRunning = true;
|
|
||||||
_streamStatus = status;
|
|
||||||
if (!isRunning()) {
|
|
||||||
throw rte("No data acquisition running");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop the thread and join it
|
|
||||||
_stopThread = true;
|
|
||||||
assert(_thread.joinable());
|
|
||||||
_thread.join();
|
|
||||||
_stopThread = false;
|
|
||||||
|
|
||||||
// Update stream status
|
|
||||||
status.isRunning = false;
|
|
||||||
_streamStatus = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DT9837A::start(InDaqCallback inCallback, OutDaqCallback outCallback) {
|
|
||||||
DEBUGTRACE_ENTER;
|
|
||||||
if (isRunning()) {
|
|
||||||
throw rte("DAQ is already running");
|
|
||||||
}
|
|
||||||
if (neninchannels() > 0) {
|
|
||||||
if (!inCallback)
|
|
||||||
throw rte("DAQ requires a callback for input data");
|
|
||||||
}
|
|
||||||
if (nenoutchannels() > 0) {
|
|
||||||
if (!outCallback)
|
|
||||||
throw rte("DAQ requires a callback for output data");
|
|
||||||
}
|
|
||||||
assert(neninchannels() + nenoutchannels() > 0);
|
|
||||||
_thread = std::thread(&DT9837A::threadFcn, this, inCallback, outCallback);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void DT9837A::threadFcn(InDaqCallback inCallback, OutDaqCallback outCallback) {
|
|
||||||
|
|
||||||
DEBUGTRACE_ENTER;
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
std::unique_ptr<OutBufHandler> obh;
|
|
||||||
std::unique_ptr<InBufHandler> ibh;
|
|
||||||
|
|
||||||
StreamStatus status = _streamStatus;
|
|
||||||
status.isRunning = true;
|
|
||||||
_streamStatus = status;
|
|
||||||
|
|
||||||
if (nenoutchannels() > 0) {
|
|
||||||
assert(outCallback);
|
|
||||||
obh = std::make_unique<OutBufHandler>(*this, outCallback);
|
|
||||||
}
|
|
||||||
if (neninchannels() > 0) {
|
|
||||||
assert(inCallback);
|
|
||||||
ibh = std::make_unique<InBufHandler>(*this, inCallback);
|
|
||||||
}
|
|
||||||
if (obh)
|
|
||||||
obh->start();
|
|
||||||
if (ibh)
|
|
||||||
ibh->start();
|
|
||||||
|
|
||||||
const double sleeptime_s =
|
|
||||||
static_cast<double>(_nFramesPerBlock) / (16 * samplerate());
|
|
||||||
const us sleeptime_us = static_cast<us>(sleeptime_s * 1e6);
|
|
||||||
|
|
||||||
while (!_stopThread) {
|
|
||||||
if (ibh) {
|
|
||||||
if (!(*ibh)()) {
|
|
||||||
_stopThread = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (obh) {
|
|
||||||
if (!(*obh)()) {
|
|
||||||
_stopThread = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::this_thread::sleep_for(std::chrono::microseconds(sleeptime_us));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Update stream status that we are not running anymore
|
|
||||||
status.isRunning = false;
|
|
||||||
_streamStatus = status;
|
|
||||||
_stopThread = false;
|
|
||||||
|
|
||||||
} catch (StreamException &e) {
|
|
||||||
|
|
||||||
StreamStatus status = _streamStatus;
|
|
||||||
// Copy over error type
|
|
||||||
status.errorType = e.e;
|
|
||||||
_streamStatus = status;
|
|
||||||
|
|
||||||
cerr << "\n******************\n";
|
|
||||||
cerr << "Catched error in UlDAQ thread: " << e.what() << endl;
|
|
||||||
cerr << "\n******************\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DT9837A::sanityChecks() const {
|
|
||||||
// Some sanity checks
|
|
||||||
if (inchannel_config.size() != 4) {
|
|
||||||
throw rte("Invalid length of enabled inChannels vector");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (outchannel_config.size() != 1) {
|
|
||||||
throw rte("Invalid length of enabled outChannels vector");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_nFramesPerBlock < 24 || _nFramesPerBlock > 8192) {
|
|
||||||
throw rte("Unsensible number of samples per block chosen");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (samplerate() < ULDAQ_SAMPLERATES.at(0) ||
|
|
||||||
samplerate() > ULDAQ_SAMPLERATES.at(ULDAQ_SAMPLERATES.size() - 1)) {
|
|
||||||
throw rte("Invalid sample rate");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // LASP_HAS_ULDAQ
|
|
@ -1,110 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "debugtrace.hpp"
|
|
||||||
#include "lasp_uldaq_common.h"
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cassert>
|
|
||||||
#include <chrono>
|
|
||||||
#include <iostream>
|
|
||||||
#include <stdexcept>
|
|
||||||
#include <thread>
|
|
||||||
#include <vector>
|
|
||||||
#include "lasp_daq.h"
|
|
||||||
|
|
||||||
using std::atomic;
|
|
||||||
using std::cerr;
|
|
||||||
using std::endl;
|
|
||||||
using rte = std::runtime_error;
|
|
||||||
|
|
||||||
class InBufHandler;
|
|
||||||
class OutBufHandler;
|
|
||||||
|
|
||||||
/** \addtogroup device
|
|
||||||
* @{
|
|
||||||
* \addtogroup uldaq
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Data translation DT9837A Daq device.
|
|
||||||
*/
|
|
||||||
class DT9837A : public Daq {
|
|
||||||
|
|
||||||
DaqDeviceHandle _handle = 0;
|
|
||||||
std::mutex _daqmutex;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief The thread that is doing I/O with UlDaq
|
|
||||||
*/
|
|
||||||
std::thread _thread;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Flag indicating the thread to stop processing.
|
|
||||||
*/
|
|
||||||
atomic<bool> _stopThread{false};
|
|
||||||
/**
|
|
||||||
* @brief Storage for exchanging information on the stream
|
|
||||||
*/
|
|
||||||
atomic<StreamStatus> _streamStatus;
|
|
||||||
|
|
||||||
const us _nFramesPerBlock;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief The function that is running in a thread
|
|
||||||
*
|
|
||||||
* @param inCallback
|
|
||||||
* @param outcallback
|
|
||||||
*/
|
|
||||||
void threadFcn(InDaqCallback inCallback, OutDaqCallback outcallback);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Obtain a handle to the underlying device
|
|
||||||
*
|
|
||||||
* @return Handle
|
|
||||||
*/
|
|
||||||
DaqDeviceHandle getHandle() const { return _handle; }
|
|
||||||
/**
|
|
||||||
* @brief Perform several sanity checks
|
|
||||||
*/
|
|
||||||
void sanityChecks() const;
|
|
||||||
public:
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Create a DT9837A instance.
|
|
||||||
*
|
|
||||||
* @param devinfo DeviceInfo to connect to
|
|
||||||
* @param config DaqConfiguration settings
|
|
||||||
*/
|
|
||||||
DT9837A(const UlDaqDeviceInfo &devinfo, const DaqConfiguration &config);
|
|
||||||
|
|
||||||
virtual ~DT9837A();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns true when the stream is running
|
|
||||||
*
|
|
||||||
* @return as above stated
|
|
||||||
*/
|
|
||||||
bool isRunning() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Stop the data-acquisition
|
|
||||||
*/
|
|
||||||
void stop() override final;
|
|
||||||
|
|
||||||
friend class InBufHandler;
|
|
||||||
friend class OutBufHandler;
|
|
||||||
|
|
||||||
virtual void start(InDaqCallback inCallback,
|
|
||||||
OutDaqCallback outCallback) override final;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Obtain copy of stream status (thread-safe function)
|
|
||||||
*
|
|
||||||
* @return StreamStatus object
|
|
||||||
*/
|
|
||||||
virtual StreamStatus getStreamStatus() const override {
|
|
||||||
return _streamStatus;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/** @} */
|
|
||||||
/** @} */
|
|
@ -1,133 +0,0 @@
|
|||||||
// #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;
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <memory>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "lasp_mathtypes.h"
|
|
||||||
#include "lasp_types.h"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* \addtogroup dsp
|
|
||||||
* @{
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Apply frequency domain smoothing to a Frequency domain (single
|
|
||||||
* sided)signal power spectrum
|
|
||||||
*
|
|
||||||
* @param freq Frequency range
|
|
||||||
* @param X Signal pwr
|
|
||||||
* @param w Parameter determining the smoothing with. 1 = 1/1 octave, 3 = 1/3th
|
|
||||||
* octave and so on
|
|
||||||
* @param power_correct Apply a correction to the whole spectrum to make the
|
|
||||||
* signal power equal to the unsmoothed signal power.
|
|
||||||
* @return vd Smoothed spectrum
|
|
||||||
*/
|
|
||||||
vd freqSmooth(const vd& freq, const vd& X, const unsigned w,
|
|
||||||
bool power_correct = false);
|
|
||||||
|
|
||||||
/** @} */
|
|
@ -1,36 +0,0 @@
|
|||||||
/* #define DEBUGTRACE_ENABLED */
|
|
||||||
#include "lasp_thread.h"
|
|
||||||
#include "BS_thread_pool.hpp"
|
|
||||||
#include "debugtrace.hpp"
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Store a global weak_ptr, that is used to create new shared pointers
|
|
||||||
* if any other shared pointers are still alive. If not, we create a new
|
|
||||||
* instance.
|
|
||||||
*/
|
|
||||||
std::weak_ptr<BS::thread_pool> _global_weak_pool;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Global mutex, used to restrict the pool creation to a single thread
|
|
||||||
* at once.
|
|
||||||
*/
|
|
||||||
std::mutex _mtx;
|
|
||||||
|
|
||||||
using Lck = std::scoped_lock<std::mutex>;
|
|
||||||
using rte = std::runtime_error;
|
|
||||||
|
|
||||||
GlobalThreadPool::GlobalThreadPool() {
|
|
||||||
DEBUGTRACE_ENTER;
|
|
||||||
Lck lck(_mtx);
|
|
||||||
/// See if we can get it from the global ptr. If not, time to allocate it.
|
|
||||||
_pool = _global_weak_pool.lock();
|
|
||||||
if (!_pool) {
|
|
||||||
_pool = std::make_shared<BS::thread_pool>();
|
|
||||||
if (!_pool) {
|
|
||||||
throw rte("Fatal: could not allocate thread pool!");
|
|
||||||
}
|
|
||||||
// Update global weak pointer
|
|
||||||
_global_weak_pool = _pool;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "BS_thread_pool.hpp"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Simple wrapper around BS::thread_pool that makes a BS::thread_pool a
|
|
||||||
* singleton, such that a thread pool can be used around in the code, and
|
|
||||||
* safely spawn threads also from other threads. Only wraps a submit() and
|
|
||||||
* push_task for now.
|
|
||||||
*/
|
|
||||||
class GlobalThreadPool {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Shared access to the thread pool.
|
|
||||||
*/
|
|
||||||
std::shared_ptr<BS::thread_pool> _pool;
|
|
||||||
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Instantiate handle to the thread pool.
|
|
||||||
*/
|
|
||||||
GlobalThreadPool();
|
|
||||||
GlobalThreadPool(const GlobalThreadPool &) = default;
|
|
||||||
GlobalThreadPool &operator=(const GlobalThreadPool &) = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Wrapper around BS::thread_pool::submit(...)
|
|
||||||
*/
|
|
||||||
template <
|
|
||||||
typename F, typename... A,
|
|
||||||
typename R = std::invoke_result_t<std::decay_t<F>, std::decay_t<A>...>>
|
|
||||||
[[nodiscard]] std::future<R> submit(F &&task, A &&...args) {
|
|
||||||
|
|
||||||
return _pool->submit(task, args...);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @brief Wrapper around BS::thread_pool::push_task(...)
|
|
||||||
*/
|
|
||||||
template <typename F, typename... A> void push_task(F &&task, A &&...args) {
|
|
||||||
_pool->push_task(task, args...);
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,142 +0,0 @@
|
|||||||
// #define DEBUGTRACE_ENABLED
|
|
||||||
#include "lasp_threadedindatahandler.h"
|
|
||||||
|
|
||||||
#include <future>
|
|
||||||
#include <optional>
|
|
||||||
#include <queue>
|
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
#include "debugtrace.hpp"
|
|
||||||
#include "lasp_daqdata.h"
|
|
||||||
#include "lasp_thread.h"
|
|
||||||
|
|
||||||
using namespace std::literals::chrono_literals;
|
|
||||||
using lck = std::scoped_lock<std::mutex>;
|
|
||||||
using rte = std::runtime_error;
|
|
||||||
using std::cerr;
|
|
||||||
using std::endl;
|
|
||||||
using std::placeholders::_1;
|
|
||||||
|
|
||||||
class SafeQueue {
|
|
||||||
std::queue<DaqData> _queue;
|
|
||||||
std::mutex _mtx;
|
|
||||||
std::atomic<uint32_t> _contents{0};
|
|
||||||
|
|
||||||
public:
|
|
||||||
void push(const DaqData &d) {
|
|
||||||
DEBUGTRACE_ENTER;
|
|
||||||
lck lock(_mtx);
|
|
||||||
_queue.push(d);
|
|
||||||
_contents++;
|
|
||||||
assert(_contents == _queue.size());
|
|
||||||
}
|
|
||||||
DaqData pop() {
|
|
||||||
DEBUGTRACE_ENTER;
|
|
||||||
if (empty()) {
|
|
||||||
throw rte("BUG: Pop on empty queue");
|
|
||||||
}
|
|
||||||
lck lock(_mtx);
|
|
||||||
|
|
||||||
/* DaqData d(std::move(_queue.front())); */
|
|
||||||
DaqData d(_queue.front());
|
|
||||||
_queue.pop();
|
|
||||||
_contents--;
|
|
||||||
assert(_contents == _queue.size());
|
|
||||||
return d;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @brief Empty implemented using atomic var, safes some mutex lock/unlock
|
|
||||||
* cycles.
|
|
||||||
*
|
|
||||||
* @return true if queue is empty
|
|
||||||
*/
|
|
||||||
bool empty() const { return _contents == 0; }
|
|
||||||
};
|
|
||||||
|
|
||||||
ThreadedInDataHandlerBase::ThreadedInDataHandlerBase(SmgrHandle mgr,
|
|
||||||
InCallbackType cb,
|
|
||||||
ResetCallbackType reset)
|
|
||||||
: _queue(std::make_unique<SafeQueue>()),
|
|
||||||
inCallback(cb),
|
|
||||||
resetCallback(reset),
|
|
||||||
_smgr(mgr) {
|
|
||||||
DEBUGTRACE_ENTER;
|
|
||||||
}
|
|
||||||
void ThreadedInDataHandlerBase::startThread() {
|
|
||||||
DEBUGTRACE_ENTER;
|
|
||||||
if (_indatahandler) {
|
|
||||||
throw rte("BUG: ThreadedIndataHandler already started");
|
|
||||||
}
|
|
||||||
SmgrHandle smgr = _smgr.lock();
|
|
||||||
if (!smgr) {
|
|
||||||
cerr << "Stream manager destructed" << endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_indatahandler = std::make_unique<InDataHandler>(
|
|
||||||
smgr,
|
|
||||||
std::bind(&ThreadedInDataHandlerBase::_inCallbackFromInDataHandler, this,
|
|
||||||
_1),
|
|
||||||
resetCallback);
|
|
||||||
|
|
||||||
_thread_allowed_to_run = true;
|
|
||||||
_indatahandler->start();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ThreadedInDataHandlerBase::_inCallbackFromInDataHandler(
|
|
||||||
const DaqData &daqdata) {
|
|
||||||
DEBUGTRACE_ENTER;
|
|
||||||
|
|
||||||
// Early return in case object is under DESTRUCTION
|
|
||||||
if (!_thread_allowed_to_run) return;
|
|
||||||
|
|
||||||
_queue->push(daqdata);
|
|
||||||
if (!_thread_running) {
|
|
||||||
DEBUGTRACE_PRINT("Pushing new thread in pool");
|
|
||||||
_thread_running = true;
|
|
||||||
_pool.push_task(&ThreadedInDataHandlerBase::threadFcn, this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ThreadedInDataHandlerBase::stopThread() {
|
|
||||||
DEBUGTRACE_ENTER;
|
|
||||||
if (!_indatahandler) {
|
|
||||||
throw rte("BUG: ThreadedIndataHandler not running");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop the existing thread
|
|
||||||
_thread_allowed_to_run = false;
|
|
||||||
|
|
||||||
// Make sure no new data arrives
|
|
||||||
_indatahandler->stop();
|
|
||||||
_indatahandler.reset();
|
|
||||||
|
|
||||||
DEBUGTRACE_PRINT("Indatahandler stopped. Waiting for thread to finish...");
|
|
||||||
// Then wait in steps for the thread to stop running.
|
|
||||||
while (_thread_running) {
|
|
||||||
std::this_thread::sleep_for(10us);
|
|
||||||
}
|
|
||||||
DEBUGTRACE_PRINT("Thread stopped");
|
|
||||||
// Kill the handler
|
|
||||||
DEBUGTRACE_PRINT("Handler resetted");
|
|
||||||
}
|
|
||||||
|
|
||||||
ThreadedInDataHandlerBase::~ThreadedInDataHandlerBase() {
|
|
||||||
DEBUGTRACE_ENTER;
|
|
||||||
if (_thread_allowed_to_run) {
|
|
||||||
stopThread();
|
|
||||||
cerr << "*** BUG: InDataHandlers have not been all stopped, while "
|
|
||||||
"StreamMgr destructor is called. This is a misuse BUG."
|
|
||||||
<< endl;
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ThreadedInDataHandlerBase::threadFcn() {
|
|
||||||
DEBUGTRACE_ENTER;
|
|
||||||
|
|
||||||
while (!_queue->empty() && _thread_allowed_to_run) {
|
|
||||||
// Call inCallback_threaded
|
|
||||||
inCallback(_queue->pop());
|
|
||||||
}
|
|
||||||
_thread_running = false;
|
|
||||||
}
|
|
@ -1,123 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "debugtrace.hpp"
|
|
||||||
#include "lasp_indatahandler.h"
|
|
||||||
#include "lasp_thread.h"
|
|
||||||
#include <atomic>
|
|
||||||
#include <memory>
|
|
||||||
#include <mutex>
|
|
||||||
|
|
||||||
using std::placeholders::_1;
|
|
||||||
const us RINGBUFFER_SIZE = 1024;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* \addtogroup dsp
|
|
||||||
* @{
|
|
||||||
*
|
|
||||||
* \defgroup rt Real time signal handlers
|
|
||||||
* @{
|
|
||||||
*/
|
|
||||||
|
|
||||||
class SafeQueue;
|
|
||||||
/**
|
|
||||||
* @brief Threaded in data handler base. Buffers inCallback data and calls a
|
|
||||||
* callback with the same signature on a different thread. The main function of
|
|
||||||
* this is to offload the thread that handles the stream, such that expensive
|
|
||||||
* computations do not result in stream buffer xruns.
|
|
||||||
*/
|
|
||||||
class ThreadedInDataHandlerBase {
|
|
||||||
/**
|
|
||||||
* @brief The queue used to push elements to the handling thread.
|
|
||||||
*/
|
|
||||||
|
|
||||||
std::unique_ptr<SafeQueue> _queue;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Function pointer that is called when new DaqData arrives.
|
|
||||||
*/
|
|
||||||
const InCallbackType inCallback;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Function pointer that is called when reset() is called.
|
|
||||||
*/
|
|
||||||
const ResetCallbackType resetCallback;
|
|
||||||
|
|
||||||
std::weak_ptr<StreamMgr> _smgr;
|
|
||||||
|
|
||||||
std::unique_ptr<InDataHandler> _indatahandler;
|
|
||||||
|
|
||||||
std::atomic<bool> _thread_running{false};
|
|
||||||
std::atomic<bool> _thread_allowed_to_run{false};
|
|
||||||
|
|
||||||
GlobalThreadPool _pool;
|
|
||||||
|
|
||||||
void threadFcn();
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Pushes a copy of the daqdata to the thread queue and returns.
|
|
||||||
* Adds a thread to handle the queue, whihc will call inCallback();
|
|
||||||
*
|
|
||||||
* @param daqdata the daq info to push
|
|
||||||
*
|
|
||||||
* @return true, to continue with sampling.
|
|
||||||
*/
|
|
||||||
void _inCallbackFromInDataHandler(const DaqData &daqdata);
|
|
||||||
|
|
||||||
public:
|
|
||||||
ThreadedInDataHandlerBase(SmgrHandle mgr, InCallbackType cb, ResetCallbackType reset);
|
|
||||||
~ThreadedInDataHandlerBase();
|
|
||||||
/**
|
|
||||||
* @brief This method should be called from the derived class' constructor,
|
|
||||||
* to start the thread and data is incoming.
|
|
||||||
*/
|
|
||||||
void startThread();
|
|
||||||
/**
|
|
||||||
* @brief This method SHOULD be called from all classes that derive on
|
|
||||||
* ThreadedInDataHandler. It is to make sure the inCallback_threaded()
|
|
||||||
* function is no longer called when the destructor of the derived class is
|
|
||||||
* called. Not calling this function is regarded as a BUG.
|
|
||||||
*/
|
|
||||||
void stopThread();
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief A bit of curiously recurring template pattern, to connect the
|
|
||||||
* specific handlers and connect the proper callbacks in a type-agnostic way.
|
|
||||||
* Using this class, each threaded handler should just implement its reset()
|
|
||||||
* and inCallback() method. Ellides the virtual method calls.
|
|
||||||
*
|
|
||||||
* Usage: class XHandler: public ThreadedInDataHandler<XHandler> {
|
|
||||||
* public:
|
|
||||||
* XHandler(streammgr) : ThreadedInDataHandler(streammgr) {}
|
|
||||||
* void inCallback(const DaqData& d) { ... do something with d }
|
|
||||||
* void reset(const Daq* daq) { ... do something with daq }
|
|
||||||
* };
|
|
||||||
*
|
|
||||||
* For examples, see PPMHandler, etc.
|
|
||||||
*
|
|
||||||
* @tparam Derived The
|
|
||||||
*/
|
|
||||||
template <typename Derived>
|
|
||||||
class ThreadedInDataHandler : public ThreadedInDataHandlerBase {
|
|
||||||
public:
|
|
||||||
ThreadedInDataHandler(SmgrHandle mgr):
|
|
||||||
ThreadedInDataHandlerBase(mgr,
|
|
||||||
std::bind(&ThreadedInDataHandler::_inCallback, this, _1),
|
|
||||||
std::bind(&ThreadedInDataHandler::_reset, this, _1))
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void _reset(const Daq* daq) {
|
|
||||||
DEBUGTRACE_ENTER;
|
|
||||||
return static_cast<Derived*>(this)->reset(daq);
|
|
||||||
}
|
|
||||||
void _inCallback(const DaqData& data) {
|
|
||||||
DEBUGTRACE_ENTER;
|
|
||||||
return static_cast<Derived*>(this)->inCallback(data);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/** @} */
|
|
||||||
/** @} */
|
|
@ -1,353 +0,0 @@
|
|||||||
// #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);
|
|
||||||
});
|
|
||||||
}
|
|
@ -1,49 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
import lasp
|
|
||||||
# Get handle to stream manager
|
|
||||||
mgr = lasp.StreamMgr.getInstance()
|
|
||||||
import time
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
ds = mgr.getDeviceInfo()
|
|
||||||
# Search for a device
|
|
||||||
for i, d in enumerate(ds):
|
|
||||||
print(f'{i}: ' + d.device_name)
|
|
||||||
|
|
||||||
d = ds[0] # Create a configuration and enable some input channels
|
|
||||||
config = lasp.DaqConfiguration(d)
|
|
||||||
config.inchannel_config[0].enabled = True
|
|
||||||
config.inchannel_config[1].enabled = True
|
|
||||||
# Choose a different number of frames per block
|
|
||||||
config.framesPerBlockIndex = 2
|
|
||||||
|
|
||||||
# Start a stream with a configuration
|
|
||||||
mgr.startStream(config)
|
|
||||||
|
|
||||||
def reset_cb(daq):
|
|
||||||
print('Reset called')
|
|
||||||
|
|
||||||
def cb(data):
|
|
||||||
# Print something on callback
|
|
||||||
print(data.shape)
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
# Attach the indata handler to the stream
|
|
||||||
#i = lasp.InDataHandler(mgr, cb, reset_cb)
|
|
||||||
|
|
||||||
ppm = lasp.PPMHandler(mgr)
|
|
||||||
#del ppm
|
|
||||||
del mgr
|
|
||||||
|
|
||||||
#del i
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
val, clip = ppm.getCurrentValue()
|
|
||||||
print(val)
|
|
||||||
time.sleep(0.1)
|
|
||||||
|
|
||||||
#print(f'{val[0]} {val[1]}', end='')
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
pass
|
|
||||||
# mgr.stopStream(lasp.StreamMgr.StreamType.input)
|
|
@ -1,61 +0,0 @@
|
|||||||
[project]
|
|
||||||
name = "lasp"
|
|
||||||
readme = "README.md"
|
|
||||||
requires-python = ">=3.10"
|
|
||||||
description = "Library for Acoustic Signal Processing"
|
|
||||||
license = { "file" = "LICENSE" }
|
|
||||||
authors = [{ "name" = "J.A. de Jong", "email" = "j.a.dejong@ascee.nl" }]
|
|
||||||
version = "1.6.8"
|
|
||||||
|
|
||||||
keywords = ["DSP", "DAQ", "Signal processing"]
|
|
||||||
|
|
||||||
classifiers = [
|
|
||||||
"Development Status :: 3 - Alpha",
|
|
||||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
||||||
"License :: OSI Approved :: MIT License",
|
|
||||||
"Natural Language :: English",
|
|
||||||
"Topic :: Scientific/Engineering",
|
|
||||||
"Programming Language :: Python :: 3.10",
|
|
||||||
"Programming Language :: Python :: 3.11",
|
|
||||||
"Operating System :: POSIX :: Linux",
|
|
||||||
"Operating System :: Microsoft :: Windows",
|
|
||||||
]
|
|
||||||
urls = { "Documentation" = "https://lasp.ascee.nl" }
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
"scipy>=1.13.1",
|
|
||||||
"matplotlib>=3.7.2",
|
|
||||||
"appdirs",
|
|
||||||
"dataclasses_json",
|
|
||||||
"h5py",
|
|
||||||
]
|
|
||||||
|
|
||||||
[build-system] # How pip and other frontends should build this project
|
|
||||||
requires = ["py-build-cmake~=0.1.8", "pybind11"]
|
|
||||||
build-backend = "py_build_cmake.build"
|
|
||||||
|
|
||||||
[tool.py-build-cmake.module] # Where to find the Python module to package
|
|
||||||
directory = "python_src"
|
|
||||||
|
|
||||||
[tool.py-build-cmake.sdist] # What to include in source distributions
|
|
||||||
include = [
|
|
||||||
"CMakeLists.txt",
|
|
||||||
"cmake",
|
|
||||||
"cpp_src",
|
|
||||||
"python_src",
|
|
||||||
"img",
|
|
||||||
"scripts",
|
|
||||||
"third_party",
|
|
||||||
]
|
|
||||||
|
|
||||||
[tool.py-build-cmake.cmake] # How to build the CMake project
|
|
||||||
build_type = "Release"
|
|
||||||
source_path = "."
|
|
||||||
options = { "LASP_HAS_PORTAUDIO" = "ON", "LASP_HAS_RTAUDIO" = "OFF" }
|
|
||||||
build_args = ["-j"]
|
|
||||||
install_components = ["python_modules"]
|
|
||||||
|
|
||||||
[tool.py-build-cmake.editable]
|
|
||||||
# This might not work properly on Windows. Comment this out when testing on
|
|
||||||
# Windows.
|
|
||||||
mode = "symlink"
|
|
@ -1,2 +1,2 @@
|
|||||||
[pytest]
|
[pytest]
|
||||||
addopts = "--ignore=third_party --ignore=examples"
|
addopts = "--ignore=third_party"
|
||||||
|
@ -1,43 +0,0 @@
|
|||||||
#!/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}")
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
@ -1,148 +0,0 @@
|
|||||||
"""
|
|
||||||
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
|
|
@ -1,166 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""!
|
|
||||||
Author: J.A. de Jong - ASCEE
|
|
||||||
|
|
||||||
Provides the implementations of (fractional) octave filter banks
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
__all__ = ["OverallFilterBank", "SosOctaveFilterBank", "SosThirdOctaveFilterBank"]
|
|
||||||
|
|
||||||
from .filter.filterbank_design import OctaveBankDesigner, ThirdOctaveBankDesigner
|
|
||||||
from .lasp_cpp import BiquadBank
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
|
|
||||||
class OverallFilterBank:
|
|
||||||
"""
|
|
||||||
Dummy type filter bank. Does nothing special, only returns output in a
|
|
||||||
way compatible with SosFilterBank.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, fs):
|
|
||||||
"""
|
|
||||||
Initialize overall filter bank
|
|
||||||
"""
|
|
||||||
self.fs = fs
|
|
||||||
self.N = 0
|
|
||||||
self.xs = [0]
|
|
||||||
|
|
||||||
def filter_(self, data):
|
|
||||||
"""
|
|
||||||
Filter input data
|
|
||||||
"""
|
|
||||||
if data.ndim == 2 and data.shape[1] != 1:
|
|
||||||
raise RuntimeError("invalid number of channels, should be 1")
|
|
||||||
|
|
||||||
if data.shape[0] == 0:
|
|
||||||
# No new time samples
|
|
||||||
return {}
|
|
||||||
|
|
||||||
# Output given as a dictionary with x as the key
|
|
||||||
output = {}
|
|
||||||
|
|
||||||
tstart = self.N / self.fs
|
|
||||||
Ncur = data.shape[0]
|
|
||||||
tend = tstart + Ncur / self.fs
|
|
||||||
|
|
||||||
t = np.linspace(tstart, tend, Ncur, endpoint=False)
|
|
||||||
self.N += Ncur
|
|
||||||
|
|
||||||
output["Overall"] = {"t": t, "data": data, "x": 0}
|
|
||||||
return output
|
|
||||||
|
|
||||||
def decimation(self, x):
|
|
||||||
return [1]
|
|
||||||
|
|
||||||
|
|
||||||
class SosFilterBank:
|
|
||||||
def __init__(self, fs, xmin, xmax):
|
|
||||||
"""
|
|
||||||
Initialize a second order sections filterbank
|
|
||||||
|
|
||||||
Args:
|
|
||||||
fs: Sampling frequency [Hz]
|
|
||||||
xmin: Minimum value for the bands
|
|
||||||
xmax: Maximum value for the bands
|
|
||||||
"""
|
|
||||||
if xmin is None:
|
|
||||||
xmin = self.designer.xs[0]
|
|
||||||
if xmax is None:
|
|
||||||
xmax = self.designer.xs[-1]
|
|
||||||
|
|
||||||
self.fs = fs
|
|
||||||
self.xs = list(range(xmin, xmax + 1))
|
|
||||||
|
|
||||||
# The number of parallel filters
|
|
||||||
nfilt = len(self.xs)
|
|
||||||
|
|
||||||
self.nfilt = nfilt
|
|
||||||
|
|
||||||
sos = None
|
|
||||||
for i, x in enumerate(self.xs):
|
|
||||||
channel = self.designer.createSOSFilter(x)
|
|
||||||
if sos is None:
|
|
||||||
sos = empty((channel.size, len(self.xs)))
|
|
||||||
sos[:, i] = channel.flatten()
|
|
||||||
|
|
||||||
self._fb = BiquadBank(sos)
|
|
||||||
|
|
||||||
self.xmin = xmin
|
|
||||||
self.xmax = xmax
|
|
||||||
self.N = 0
|
|
||||||
|
|
||||||
def filter_(self, data):
|
|
||||||
"""
|
|
||||||
Filter input data
|
|
||||||
"""
|
|
||||||
|
|
||||||
if data.ndim > 1 and data.shape[1] != 1:
|
|
||||||
raise RuntimeError("invalid number of channels, should be 1")
|
|
||||||
|
|
||||||
if data.shape[0] == 0:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
filtered_data = self._fb.filter(data)
|
|
||||||
if filtered_data.ndim == 1:
|
|
||||||
filtered_data = filtered_data[:, None]
|
|
||||||
|
|
||||||
# Output given as a dictionary with nom_txt as the key
|
|
||||||
output = {}
|
|
||||||
|
|
||||||
tstart = self.N / self.fs
|
|
||||||
Ncur = data.shape[0]
|
|
||||||
tend = tstart + Ncur / self.fs
|
|
||||||
|
|
||||||
t = np.linspace(tstart, tend, Ncur, endpoint=False)
|
|
||||||
self.N += Ncur
|
|
||||||
|
|
||||||
for i, x in enumerate(self.xs):
|
|
||||||
# '31.5' to '16k'
|
|
||||||
nom_txt = self.designer.nominal_txt(x)
|
|
||||||
output[nom_txt] = {'t': t, 'data': filtered_data[:, [i]], 'x': x}
|
|
||||||
|
|
||||||
return output
|
|
||||||
|
|
||||||
def decimation(self, x):
|
|
||||||
return [1]
|
|
||||||
|
|
||||||
|
|
||||||
class SosThirdOctaveFilterBank(SosFilterBank):
|
|
||||||
"""
|
|
||||||
Filter bank which uses FIR filtering for each one-third octave frequency
|
|
||||||
band.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, fs, xmin=None, xmax=None):
|
|
||||||
"""
|
|
||||||
Initialize a second order sections filterbank.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
fs: Sampling frequency [Hz]
|
|
||||||
xmin: Minimum value for the bands
|
|
||||||
xmax: Maximum value for the bands
|
|
||||||
"""
|
|
||||||
self.designer = ThirdOctaveBankDesigner(fs)
|
|
||||||
SosFilterBank.__init__(self, fs, xmin, xmax)
|
|
||||||
|
|
||||||
|
|
||||||
class SosOctaveFilterBank(SosFilterBank):
|
|
||||||
"""
|
|
||||||
Filter bank which uses FIR filtering for each one-third octave frequency
|
|
||||||
band.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, fs, xmin=None, xmax=None):
|
|
||||||
"""
|
|
||||||
Initialize a second order sections filterbank.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
fs: Sampling frequency [Hz]
|
|
||||||
xmin: Minimum value for the bands, if not specified, use minimum
|
|
||||||
xmax: Maximum value for the bands, if not specified, use maximum
|
|
||||||
"""
|
|
||||||
self.designer = OctaveBankDesigner(fs)
|
|
||||||
SosFilterBank.__init__(self, fs, xmin, xmax)
|
|
@ -1,354 +0,0 @@
|
|||||||
#!/usr/bin/python3.8
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
Read data from stream and record sound and video at the same time
|
|
||||||
"""
|
|
||||||
import dataclasses, logging, os, time, h5py, threading
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
from .lasp_atomic import Atomic
|
|
||||||
from enum import Enum, auto, unique
|
|
||||||
from .lasp_cpp import InDataHandler, StreamMgr
|
|
||||||
from .lasp_version import LASP_VERSION_MAJOR, LASP_VERSION_MINOR
|
|
||||||
import uuid
|
|
||||||
import logging
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
# logger.setLevel(logging.DEBUG)
|
|
||||||
logger.setLevel(logging.INFO)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass
|
|
||||||
class RecordStatus:
|
|
||||||
curT: float = 0
|
|
||||||
done: bool = False
|
|
||||||
|
|
||||||
|
|
||||||
class RecordingState(Enum):
|
|
||||||
"""Enumeration for the recording state"""
|
|
||||||
|
|
||||||
Waiting = auto()
|
|
||||||
Recording = auto()
|
|
||||||
AllDataStored = auto()
|
|
||||||
Finished = auto()
|
|
||||||
Error = auto()
|
|
||||||
|
|
||||||
|
|
||||||
class Recording:
|
|
||||||
"""
|
|
||||||
Class used to perform a recording. Recording data can come in from a
|
|
||||||
different thread, that is supposed to call the `inCallback` method, with
|
|
||||||
audio data as an argument.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
fn: str,
|
|
||||||
streammgr: StreamMgr,
|
|
||||||
rectime: float = None,
|
|
||||||
wait: bool = True,
|
|
||||||
progressCallback=None,
|
|
||||||
startDelay: float = 0,
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Start a recording. Blocks if wait is set to True.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
fn: Filename to record to. Extension is automatically added if not
|
|
||||||
provided.
|
|
||||||
stream: AvStream instance to record from. Should have input
|
|
||||||
channels!
|
|
||||||
rectime: Recording time [s], None for infinite, in seconds. If set
|
|
||||||
to None, or np.inf, the recording continues indefintely.
|
|
||||||
progressCallback: callable that is called with an instance of
|
|
||||||
RecordStatus instance as argument.
|
|
||||||
startDelay: Optional delay added before the recording is *actually*
|
|
||||||
started in [s].
|
|
||||||
"""
|
|
||||||
logger.debug("__init__()")
|
|
||||||
ext = ".h5"
|
|
||||||
if ext not in fn:
|
|
||||||
fn += ext
|
|
||||||
|
|
||||||
if os.path.exists(fn):
|
|
||||||
raise RuntimeError("Recording file name already exists / is in use")
|
|
||||||
|
|
||||||
self._smgr = streammgr
|
|
||||||
self._metadata = None
|
|
||||||
|
|
||||||
self._recState = RecordingState.Waiting
|
|
||||||
|
|
||||||
if startDelay < 0:
|
|
||||||
raise RuntimeError("Invalid start delay value. Should be >= 0")
|
|
||||||
|
|
||||||
self._startDelay = startDelay
|
|
||||||
|
|
||||||
# The amount of seconds (float) that is to be recorded
|
|
||||||
self._requiredRecordingLength = rectime
|
|
||||||
|
|
||||||
# The file name to store data to
|
|
||||||
self._fn = fn
|
|
||||||
|
|
||||||
# Counter of the number of blocks that have been recorded
|
|
||||||
self._recordedBlocks = 0
|
|
||||||
|
|
||||||
# Counter of the overall number of blocks that have passed (including
|
|
||||||
# the blocks that passed during waiting prior to recording)
|
|
||||||
self._allBlocks = 0
|
|
||||||
|
|
||||||
# Stop flag, set when recording is finished.
|
|
||||||
self._stop = Atomic(False)
|
|
||||||
|
|
||||||
# Mutex, on who is working with the H5py data and the class settings
|
|
||||||
self._rec_mutex = threading.RLock()
|
|
||||||
|
|
||||||
self._progressCallback = progressCallback
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Open the file
|
|
||||||
self._h5file = h5py.File(self._fn, "w", "stdio")
|
|
||||||
self._h5file.flush()
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error creating measurement file {e}")
|
|
||||||
raise
|
|
||||||
|
|
||||||
# This flag is used to delete the file on finish(), and can be used
|
|
||||||
# when a recording is canceled. It is set to True at start, as the file will be deleted when no data is in it.
|
|
||||||
self._deleteFile = True
|
|
||||||
|
|
||||||
# Try to obtain stream metadata
|
|
||||||
streamstatus = streammgr.getStreamStatus(StreamMgr.StreamType.input)
|
|
||||||
if not streamstatus.runningOK():
|
|
||||||
raise RuntimeError("Stream is not running properly. Cannot start recording")
|
|
||||||
|
|
||||||
# Audio dataset
|
|
||||||
self._ad = None
|
|
||||||
|
|
||||||
logger.debug("Starting record....")
|
|
||||||
|
|
||||||
# In the PyInDataHandler, a weak reference is stored to the python
|
|
||||||
# methods reset and incallback. One way or another, the weak ref is gone
|
|
||||||
# on the callback thread. If we store an "extra" ref to this method over
|
|
||||||
# here, the weak ref stays alive. We do not know whether this is a bug
|
|
||||||
# or a feature, but in any case storing this extra ref to inCallback
|
|
||||||
# solves the problem.
|
|
||||||
self._incalback_cpy = self.inCallback
|
|
||||||
self._indataHandler = InDataHandler(
|
|
||||||
streammgr, self._incalback_cpy, self.resetCallback
|
|
||||||
)
|
|
||||||
|
|
||||||
if wait:
|
|
||||||
logger.debug("Stop recording with CTRL-C")
|
|
||||||
try:
|
|
||||||
while not self._stop():
|
|
||||||
time.sleep(0.01)
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
logger.debug("Keyboard interrupt on record")
|
|
||||||
finally:
|
|
||||||
self.finish()
|
|
||||||
|
|
||||||
def curT(self):
|
|
||||||
"""Return currently recorded time as float"""
|
|
||||||
|
|
||||||
def resetCallback(self, daq):
|
|
||||||
"""
|
|
||||||
Function called with initial stream data.
|
|
||||||
"""
|
|
||||||
logger.debug(f"resetCallback({daq})")
|
|
||||||
with self._rec_mutex:
|
|
||||||
in_ch = daq.enabledInChannels()
|
|
||||||
blocksize = daq.framesPerBlock()
|
|
||||||
self._blocksize = blocksize
|
|
||||||
self._nchannels = daq.neninchannels()
|
|
||||||
self._fs = daq.samplerate()
|
|
||||||
|
|
||||||
f = self._h5file
|
|
||||||
|
|
||||||
f.attrs["LASP_VERSION_MAJOR"] = LASP_VERSION_MAJOR
|
|
||||||
f.attrs["LASP_VERSION_MINOR"] = LASP_VERSION_MINOR
|
|
||||||
|
|
||||||
# Set the bunch of attributes
|
|
||||||
f.attrs["samplerate"] = daq.samplerate()
|
|
||||||
f.attrs["nchannels"] = daq.neninchannels()
|
|
||||||
f.attrs["blocksize"] = blocksize
|
|
||||||
f.attrs["sensitivity"] = [ch.sensitivity for ch in in_ch]
|
|
||||||
f.attrs["channelNames"] = [ch.name for ch in in_ch]
|
|
||||||
f.attrs["UUID"] = str(uuid.uuid1())
|
|
||||||
|
|
||||||
# Add the start delay here, as firstFrames() is called right after the
|
|
||||||
# constructor is called. time.time() returns a floating point
|
|
||||||
# number of seconds after epoch.
|
|
||||||
f.attrs["time"] = time.time() + self._startDelay
|
|
||||||
|
|
||||||
# In V2, we do not store JSON metadata anymore, but just an enumeration
|
|
||||||
# index to a physical quantity.
|
|
||||||
f.attrs["qtys_enum_idx"] = [ch.qty.value for ch in in_ch]
|
|
||||||
|
|
||||||
# Measured physical quantity metadata
|
|
||||||
# This was how it was in LASP version < 1.0
|
|
||||||
# f.attrs['qtys'] = [ch.qty.to_json() for ch in in_ch]
|
|
||||||
f.flush()
|
|
||||||
|
|
||||||
def inCallback(self, adata):
|
|
||||||
"""
|
|
||||||
This method is called when a block of audio data from the stream is
|
|
||||||
available. It should return either True or False.
|
|
||||||
|
|
||||||
When returning False, it will stop the stream.
|
|
||||||
|
|
||||||
"""
|
|
||||||
logger.debug(f"inCallback()")
|
|
||||||
if self._stop():
|
|
||||||
logger.debug("Stop flag set, early return in inCallback")
|
|
||||||
# Stop flag is raised. We do not add any data anymore.
|
|
||||||
return False
|
|
||||||
|
|
||||||
with self._rec_mutex:
|
|
||||||
|
|
||||||
self._allBlocks += 1
|
|
||||||
|
|
||||||
match self._recState:
|
|
||||||
case RecordingState.Waiting:
|
|
||||||
if self._allBlocks * self._blocksize / self._fs > self._startDelay:
|
|
||||||
self._recState = RecordingState.Recording
|
|
||||||
|
|
||||||
case RecordingState.Recording:
|
|
||||||
if self._ad is None:
|
|
||||||
self.__addFirstFramesToFile(adata)
|
|
||||||
else:
|
|
||||||
self.__addTimeDataToFile(adata)
|
|
||||||
|
|
||||||
# Increase the block counter
|
|
||||||
self._recordedBlocks += 1
|
|
||||||
|
|
||||||
recstatus = RecordStatus(curT=self.recordedTime, done=False)
|
|
||||||
|
|
||||||
if (
|
|
||||||
self._requiredRecordingLength is not None
|
|
||||||
and self.recordedTime >= self._requiredRecordingLength
|
|
||||||
):
|
|
||||||
self._recState = RecordingState.AllDataStored
|
|
||||||
self._stop <<= True
|
|
||||||
recstatus.done = True
|
|
||||||
|
|
||||||
if self._progressCallback is not None:
|
|
||||||
self._progressCallback(recstatus)
|
|
||||||
|
|
||||||
case RecordingState.AllDataStored:
|
|
||||||
return False
|
|
||||||
|
|
||||||
case RecordingState.Finished:
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
@property
|
|
||||||
def recordedTime(self):
|
|
||||||
"""Return recorded time (not rounded) as float"""
|
|
||||||
with self._rec_mutex:
|
|
||||||
if self._ad is None:
|
|
||||||
return 0.0
|
|
||||||
return self._recordedBlocks * self._blocksize / self._fs
|
|
||||||
|
|
||||||
def __addFirstFramesToFile(self, adata):
|
|
||||||
"""
|
|
||||||
Set up the dataset in which to store the audio data. This will create
|
|
||||||
the attribute `self.ad` and flip around the _deleteFile flag.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
adata: Numpy array with data from DAQ
|
|
||||||
|
|
||||||
"""
|
|
||||||
with self._rec_mutex:
|
|
||||||
# The array data type cannot
|
|
||||||
# datatype = daq.dataType()
|
|
||||||
dtype = np.dtype(adata.dtype)
|
|
||||||
|
|
||||||
assert self._ad is None
|
|
||||||
|
|
||||||
self._ad = self._h5file.create_dataset(
|
|
||||||
"audio",
|
|
||||||
(1, self._blocksize, self._nchannels),
|
|
||||||
dtype=dtype,
|
|
||||||
maxshape=(
|
|
||||||
None, # This means, we can add blocks
|
|
||||||
# indefinitely
|
|
||||||
self._blocksize,
|
|
||||||
self._nchannels,
|
|
||||||
),
|
|
||||||
compression="gzip",
|
|
||||||
)
|
|
||||||
self._ad[0, :, :] = adata
|
|
||||||
|
|
||||||
self._h5file.flush()
|
|
||||||
self._deleteFile = False
|
|
||||||
|
|
||||||
def setDelete(self, val: bool):
|
|
||||||
"""
|
|
||||||
Set the delete flag. If set, measurement file is deleted at the end of
|
|
||||||
the recording. Typically used for cleaning up after canceling a
|
|
||||||
recording.
|
|
||||||
"""
|
|
||||||
with self._rec_mutex:
|
|
||||||
self._deleteFile = val
|
|
||||||
|
|
||||||
def finish(self):
|
|
||||||
"""
|
|
||||||
This method should be called to finish and a close a recording file,
|
|
||||||
remove the queue from the stream, etc.
|
|
||||||
|
|
||||||
"""
|
|
||||||
logger.debug("Recording::finish()")
|
|
||||||
|
|
||||||
self._stop <<= True
|
|
||||||
|
|
||||||
with self._rec_mutex:
|
|
||||||
|
|
||||||
if self._recState == RecordingState.Finished:
|
|
||||||
raise RuntimeError("Recording has already finished")
|
|
||||||
|
|
||||||
# Remove indata handler, which also should remove callback function
|
|
||||||
# from StreamMgr. This, however does not have to happen
|
|
||||||
# instantaneously. For which we have to implement extra mutex
|
|
||||||
# guards in this class
|
|
||||||
del self._indataHandler
|
|
||||||
|
|
||||||
self._h5file.flush()
|
|
||||||
|
|
||||||
# Remove handle to dataset otherwise the h5 file is not closed
|
|
||||||
# properly.
|
|
||||||
del self._ad
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Close the recording file
|
|
||||||
self._h5file.close()
|
|
||||||
del self._h5file
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error closing file: {e}")
|
|
||||||
|
|
||||||
logger.debug("Recording ended")
|
|
||||||
if self._deleteFile:
|
|
||||||
self.__deleteFile()
|
|
||||||
self._recState = RecordingState.Finished
|
|
||||||
|
|
||||||
def __deleteFile(self):
|
|
||||||
"""
|
|
||||||
Cleanup the recording file.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
os.remove(self._fn)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error deleting file: {self._fn}: {str(e)}")
|
|
||||||
|
|
||||||
def __addTimeDataToFile(self, indata):
|
|
||||||
"""
|
|
||||||
Called by handleQueue() and adds new time data to the storage file.
|
|
||||||
"""
|
|
||||||
with self._rec_mutex:
|
|
||||||
|
|
||||||
ablockno = self._recordedBlocks
|
|
||||||
|
|
||||||
# Add the data to the file, and resize the audio data blocks
|
|
||||||
self._ad.resize(ablockno + 1, axis=0)
|
|
||||||
self._ad[ablockno, :, :] = indata
|
|
||||||
self._h5file.flush()
|
|
@ -1,6 +0,0 @@
|
|||||||
import importlib.metadata
|
|
||||||
|
|
||||||
__version__ = importlib.metadata.version(__package__ or __name__)
|
|
||||||
LASP_VERSION_MAJOR, LASP_VERSION_MINOR, LASP_VERSION_PATCH = [
|
|
||||||
int(a) for a in __version__.split(".")
|
|
||||||
]
|
|
@ -1,323 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
Author: T. Hekman, C. Jansen, J.A. de Jong - ASCEE V.O.F.
|
|
||||||
|
|
||||||
Smooth data in the frequency domain.
|
|
||||||
|
|
||||||
TODO: This function is rather slow as it uses [for loops] in Python. Speed up.
|
|
||||||
NOTE: function requires lin frequency spaced input data
|
|
||||||
TODO: accept input data that is not lin spaced in frequency
|
|
||||||
TODO: it makes more sense to output data that is log spaced in frequency
|
|
||||||
TODO: Make SmoothSpectralData() multi-dimensional array aware.
|
|
||||||
TODO: Smoothing does not work due to complex numbers. Is it reasonable to
|
|
||||||
smooth complex data? If so, the data could be split in magnitude and phase.
|
|
||||||
"""
|
|
||||||
|
|
||||||
__all__ = ['SmoothingType', 'smoothSpectralData', 'SmoothingWidth']
|
|
||||||
|
|
||||||
from enum import Enum, unique
|
|
||||||
import copy
|
|
||||||
import numpy as np
|
|
||||||
from numpy import log2, pi, sin
|
|
||||||
from ..lasp_cpp import freqSmooth
|
|
||||||
from ..lasp_config import zeros
|
|
||||||
|
|
||||||
|
|
||||||
@unique
|
|
||||||
class SmoothingWidth(Enum):
|
|
||||||
none = (0, 'No smoothing')
|
|
||||||
one = (1, '1/1st octave smoothing')
|
|
||||||
two = (2, '1/2th octave smoothing')
|
|
||||||
three = (3, '1/3rd octave smoothing')
|
|
||||||
six = (6, '1/6th octave smoothing')
|
|
||||||
twelve = (12, '1/12th octave smoothing')
|
|
||||||
twfo = (24, '1/24th octave smoothing')
|
|
||||||
ftei = (48, '1/48th octave smoothing')
|
|
||||||
hundred = (100, '1/100th octave smoothing') # useful for removing 'grass'
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def fillComboBox(cb):
|
|
||||||
"""
|
|
||||||
Fill Windows to a combobox
|
|
||||||
|
|
||||||
Args:
|
|
||||||
cb: QComboBox to fill
|
|
||||||
"""
|
|
||||||
cb.clear()
|
|
||||||
for w in list(SmoothingWidth):
|
|
||||||
cb.addItem(w.value[1], w)
|
|
||||||
cb.setCurrentIndex(0)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def getCurrent(cb):
|
|
||||||
return list(SmoothingWidth)[cb.currentIndex()]
|
|
||||||
|
|
||||||
|
|
||||||
class SmoothingType:
|
|
||||||
levels = 'l', 'Levels' # [dB]
|
|
||||||
# tf = 'tf', 'Transfer function',
|
|
||||||
ps = 'ps', '(Auto) powers'
|
|
||||||
|
|
||||||
|
|
||||||
# TO DO: check if everything is correct
|
|
||||||
# TO DO: add possibility to insert data that is not lin spaced in frequency
|
|
||||||
# Integrated Hann window
|
|
||||||
|
|
||||||
def intHann(x1, x2):
|
|
||||||
"""
|
|
||||||
Calculate integral of (part of) Hann window.
|
|
||||||
If the args are vectors, the return value will match those.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
x1: lower bound [-0.5, 0.5]
|
|
||||||
x2: upper bound [-0.5, 0.5]
|
|
||||||
Return:
|
|
||||||
Integral of Hann window between x1 and x2
|
|
||||||
"""
|
|
||||||
x1 = np.clip(x1, -0.5, 0.5)
|
|
||||||
x2 = np.clip(x2, -0.5, 0.5)
|
|
||||||
return (sin(2*pi*x2) - sin(2*pi*x1))/(2*pi) + (x2-x1)
|
|
||||||
|
|
||||||
|
|
||||||
def smoothCalcMatrix(freq, sw: SmoothingWidth):
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
freq: array of frequencies of data points [Hz] - equally spaced
|
|
||||||
sw: SmoothingWidth
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
freq: array frequencies of data points [Hz]
|
|
||||||
Q: matrix to smooth, power: {fsm} = [Q] * {fraw}
|
|
||||||
|
|
||||||
Warning: this method does not work on levels (dB)
|
|
||||||
|
|
||||||
According to Tylka_JAES_SmoothingWeights.pdf
|
|
||||||
"A Generalized Method for Fractional-Octave Smoothing of Transfer Functions
|
|
||||||
that Preserves Log-Frequency Symmetry"
|
|
||||||
https://doi.org/10.17743/jaes.2016.0053
|
|
||||||
par 1.3
|
|
||||||
eq. 16
|
|
||||||
"""
|
|
||||||
# Settings
|
|
||||||
Noct = sw.value[0]
|
|
||||||
assert Noct > 0, "'Noct' must be absolute positive"
|
|
||||||
assert np.isclose(freq[-1]-freq[-2], freq[1]-freq[0]), "Input data must "\
|
|
||||||
"have a linear frequency spacing"
|
|
||||||
if Noct < 1:
|
|
||||||
raise Warning('Check if \'Noct\' is entered correctly')
|
|
||||||
|
|
||||||
# Initialize
|
|
||||||
L = len(freq)
|
|
||||||
Q = np.zeros(shape=(L, L), dtype=np.float16) # float16: keep size small
|
|
||||||
Q[0, 0] = 1 # in case first point is skipped
|
|
||||||
x0 = 1 if freq[0] == 0 else 0 # skip first data point if zero frequency
|
|
||||||
|
|
||||||
Noct /= 2 # corr. factor: window @ -3dB at Noct bounds (Noct/1.5 for -6dB)
|
|
||||||
ifreq = freq/(freq[1]-freq[0]) # frequency, normalized to step=1
|
|
||||||
ifreq = np.array(ifreq.astype(int))
|
|
||||||
ifreqMin = ifreq[x0] # min. freq, normalized to step=1
|
|
||||||
ifreqMax = ifreq[L-1] # max. freq, normalized to step=1
|
|
||||||
sfact = 2**((1/Noct)/2) # bounds are this factor from the center freq
|
|
||||||
|
|
||||||
kpmin = np.floor(ifreq/sfact).astype(int) # min freq of window
|
|
||||||
kpmax = np.ceil(ifreq*sfact).astype(int) # max freq of window
|
|
||||||
|
|
||||||
for ff in range(x0, L): # loop over input freq
|
|
||||||
# Find window bounds and actual smoothing width
|
|
||||||
# Keep window symmetrical if one side is truncated
|
|
||||||
if kpmin[ff] < ifreqMin:
|
|
||||||
kpmin[ff] = ifreqMin
|
|
||||||
kpmax[ff] = np.ceil(ifreq[ff]**2/ifreqMin) # decrease smooth width
|
|
||||||
if np.isclose(kpmin[ff], kpmax[ff]):
|
|
||||||
kpmax[ff] += 1
|
|
||||||
NoctAct = 1/log2(kpmax[ff]/kpmin[ff])
|
|
||||||
elif kpmax[ff] > ifreqMax:
|
|
||||||
kpmin[ff] = np.floor(ifreq[ff]**2/ifreqMax) # decrease smoothwidth
|
|
||||||
kpmax[ff] = ifreqMax
|
|
||||||
if np.isclose(kpmin[ff], kpmax[ff]):
|
|
||||||
kpmin[ff] -= 1
|
|
||||||
NoctAct = 1/log2(kpmax[ff]/kpmin[ff])
|
|
||||||
else:
|
|
||||||
NoctAct = Noct
|
|
||||||
|
|
||||||
kp = np.arange(kpmin[ff], kpmax[ff]+1) # freqs of window
|
|
||||||
Phi1 = log2((kp - 0.5)/ifreq[ff]) * NoctAct # integr. bounds Hann wind
|
|
||||||
Phi2 = log2((kp + 0.5)/ifreq[ff]) * NoctAct
|
|
||||||
W = intHann(Phi1, Phi2) # smoothing window
|
|
||||||
Q[ff, kpmin[ff]-ifreq[0]:kpmax[ff]-ifreq[0]+1] = W
|
|
||||||
|
|
||||||
# Normalize to conserve input power
|
|
||||||
Qpower = np.sum(Q, axis=0)
|
|
||||||
Q = Q / Qpower[np.newaxis, :]
|
|
||||||
|
|
||||||
return Q
|
|
||||||
|
|
||||||
|
|
||||||
def smoothSpectralData_old(freq, M, sw: SmoothingWidth,
|
|
||||||
st: SmoothingType = SmoothingType.levels):
|
|
||||||
"""
|
|
||||||
Apply fractional octave smoothing to data in the frequency domain.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
freq: array of frequencies of data points [Hz] - equally spaced
|
|
||||||
M: array of data, either power or dB
|
|
||||||
the smoothing type `st`, the smoothing is applied.
|
|
||||||
sw: smoothing width
|
|
||||||
st: smoothing type = data type of input data
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
freq : array frequencies of data points [Hz]
|
|
||||||
Msm : float smoothed magnitude of data points
|
|
||||||
"""
|
|
||||||
# Safety
|
|
||||||
MM = copy.deepcopy(M)
|
|
||||||
Noct = sw.value[0]
|
|
||||||
assert len(MM) > 0, "Smoothing function: input array is empty" # not sure if this works
|
|
||||||
assert Noct > 0, "'Noct' must be absolute positive"
|
|
||||||
if Noct < 1:
|
|
||||||
raise Warning('Check if \'Noct\' is entered correctly')
|
|
||||||
assert len(freq) == len(MM), "f and M should have equal length"
|
|
||||||
|
|
||||||
if st == SmoothingType.ps:
|
|
||||||
assert np.min(MM) >= 0, 'Power spectrum values cannot be negative'
|
|
||||||
if st == SmoothingType.levels and isinstance(MM.dtype, complex):
|
|
||||||
raise RuntimeError('Decibel input should be real-valued')
|
|
||||||
|
|
||||||
# Convert to power
|
|
||||||
if st == SmoothingType.levels:
|
|
||||||
P = 10**(MM/10)
|
|
||||||
elif st == SmoothingType.ps:
|
|
||||||
P = MM
|
|
||||||
else:
|
|
||||||
raise RuntimeError(f"Incorrect SmoothingType: {st}")
|
|
||||||
|
|
||||||
# P is power while smoothing. x are indices of P.
|
|
||||||
Psm = np.zeros_like(P) # Smoothed power - to be calculated
|
|
||||||
if freq[0] == 0:
|
|
||||||
Psm[0] = P[0] # Reuse old value in case first data..
|
|
||||||
# ..point is skipped. Not plotted any way.
|
|
||||||
|
|
||||||
# Re-use smoothing matrix Q if available. Otherwise, calculate.
|
|
||||||
# Store in dict 'Qdict'
|
|
||||||
nfft = int(2*(len(freq)-1))
|
|
||||||
key = f"nfft{nfft}_Noct{Noct}" # matrix name
|
|
||||||
|
|
||||||
if 'Qdict' not in globals(): # Guarantee Qdict exists
|
|
||||||
global Qdict
|
|
||||||
Qdict = {}
|
|
||||||
|
|
||||||
if key in Qdict:
|
|
||||||
Q = Qdict[key]
|
|
||||||
else:
|
|
||||||
Q = smoothCalcMatrix(freq, sw)
|
|
||||||
Qdict[key] = Q
|
|
||||||
|
|
||||||
# Apply smoothing
|
|
||||||
Psm = np.matmul(Q, P)
|
|
||||||
|
|
||||||
# Convert to original format
|
|
||||||
if st == SmoothingType.levels:
|
|
||||||
Psm = 10*np.log10(Psm)
|
|
||||||
|
|
||||||
return Psm
|
|
||||||
|
|
||||||
def smoothSpectralData(freq, M, sw: SmoothingWidth,
|
|
||||||
st: SmoothingType = SmoothingType.levels):
|
|
||||||
"""
|
|
||||||
Apply fractional octave smoothing to data in the frequency domain.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
freq: array of frequencies of data points [Hz] - equally spaced
|
|
||||||
M: array of data, either power or dB
|
|
||||||
the smoothing type `st`, the smoothing is applied.
|
|
||||||
sw: smoothing width
|
|
||||||
st: smoothing type = data type of input data
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
freq : array frequencies of data points [Hz]
|
|
||||||
Msm : float smoothed magnitude of data points
|
|
||||||
"""
|
|
||||||
# Safety
|
|
||||||
|
|
||||||
if st == SmoothingType.ps:
|
|
||||||
assert np.min(M) >= 0, 'Power spectrum values cannot be negative'
|
|
||||||
if st == SmoothingType.levels and isinstance(M.dtype, complex):
|
|
||||||
raise RuntimeError('Decibel input should be real-valued')
|
|
||||||
|
|
||||||
# Convert to power
|
|
||||||
if st == SmoothingType.levels:
|
|
||||||
P = 10**(M/10)
|
|
||||||
elif st == SmoothingType.ps:
|
|
||||||
P = M
|
|
||||||
else:
|
|
||||||
raise RuntimeError(f"Incorrect SmoothingType: {st}")
|
|
||||||
|
|
||||||
Psm = freqSmooth(freq, P, sw.value[0])
|
|
||||||
|
|
||||||
# Convert to original format
|
|
||||||
if st == SmoothingType.levels:
|
|
||||||
Psm = 10*np.log10(Psm)
|
|
||||||
|
|
||||||
return Psm
|
|
||||||
|
|
||||||
|
|
||||||
# %% Test
|
|
||||||
if __name__ == "__main__":
|
|
||||||
""" Test function for evaluation and debugging
|
|
||||||
|
|
||||||
Note: make a distinction between lin and log spaced (in frequency) data
|
|
||||||
points. They should be treated and weighted differently.
|
|
||||||
"""
|
|
||||||
import matplotlib.pyplot as plt
|
|
||||||
# Initialize
|
|
||||||
Noct = 2 # Noct = 6 for 1/6 oct. smoothing
|
|
||||||
|
|
||||||
# Create dummy data set 1: noise
|
|
||||||
fmin = 1e3 # [Hz] min freq
|
|
||||||
fmax = 24e3 # [Hz] max freq
|
|
||||||
Ndata = 200 # number of data points
|
|
||||||
freq = np.linspace(fmin, fmax, Ndata) # frequency points
|
|
||||||
# freq = np.hstack((0, freq))
|
|
||||||
M = abs(0.4*np.random.normal(size=(len(freq),)))+0.01 #
|
|
||||||
M = 20*np.log10(M)
|
|
||||||
|
|
||||||
# # Create dummy data set 2: dirac delta
|
|
||||||
# fmin = 3e3 # [Hz] min freq
|
|
||||||
# fmax = 24e3 # [Hz] max freq
|
|
||||||
# Ndata = 200 # number of data points
|
|
||||||
# freq = np.linspace(fmin, fmax, Ndata) # frequency points
|
|
||||||
# M = 0 * abs(1+0.4*np.random.normal(size=(Ndata,))) + 0.01 #
|
|
||||||
# M[int(100)] = 1
|
|
||||||
# M = 20*np.log10(M)
|
|
||||||
|
|
||||||
# Apply function
|
|
||||||
class sw:
|
|
||||||
value = [Noct]
|
|
||||||
st = SmoothingType.levels # so data is given in dB
|
|
||||||
# st = SmoothingType.ps # so data is given in power
|
|
||||||
|
|
||||||
# Smooth
|
|
||||||
Msm = smoothSpectralData(freq, M, sw, st)
|
|
||||||
fsm = freq
|
|
||||||
|
|
||||||
|
|
||||||
# Plot - lin frequency
|
|
||||||
plt.figure()
|
|
||||||
plt.plot(freq, M, '.b')
|
|
||||||
plt.plot(fsm, Msm, 'r')
|
|
||||||
plt.xlabel('f (Hz)')
|
|
||||||
plt.ylabel('magnitude')
|
|
||||||
plt.xlim([100, fmax])
|
|
||||||
plt.title('lin frequency')
|
|
||||||
plt.legend(['Raw', 'Smooth'])
|
|
||||||
|
|
||||||
# Plot - log frequency
|
|
||||||
plt.figure()
|
|
||||||
plt.semilogx(freq, M, '.b')
|
|
||||||
plt.semilogx(fsm, Msm, 'r')
|
|
||||||
plt.xlabel('f (Hz)')
|
|
||||||
plt.ylabel('magnitude')
|
|
||||||
plt.xlim([100, fmax])
|
|
||||||
plt.title('log frequency')
|
|
||||||
plt.legend(['Raw', 'Smooth 1'])
|
|
3
requirements.txt
Normal file
3
requirements.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
appdirs
|
||||||
|
dataclasses_json
|
||||||
|
matplotlib
|
@ -1,3 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# Test push workflow of Gitea runner
|
|
||||||
act push --workflows .gitea/workflows
|
|
@ -1,17 +0,0 @@
|
|||||||
#/bin/bash
|
|
||||||
# Build LASP on Arch Linux, assuming we start in a clean docker container.
|
|
||||||
# After that, test stuff.
|
|
||||||
# Assumptions
|
|
||||||
# - CWD is root of lasp repository
|
|
||||||
# - Nothing is installed
|
|
||||||
|
|
||||||
# Stop on first error
|
|
||||||
set -e
|
|
||||||
pacman -Syu --noconfirm base-devel git ccache openblas fftw pulseaudio python-build libusb
|
|
||||||
git submodule update --init --recursive
|
|
||||||
pyproject-build
|
|
||||||
python -m venv .venv
|
|
||||||
source .venv/bin/activate
|
|
||||||
pip install pytest
|
|
||||||
pip install dist/lasp-*linux_x86_64.whl
|
|
||||||
pytest
|
|
@ -1,25 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# Build LASP on Ubuntu Linux, assuming we start in a clean docker container.
|
|
||||||
# After that, test stuff.
|
|
||||||
# Assumptions
|
|
||||||
# - CWD is root of lasp repository
|
|
||||||
# - Nothing is installed
|
|
||||||
|
|
||||||
# Stop on first error
|
|
||||||
set -e
|
|
||||||
# Update cache
|
|
||||||
# apt update
|
|
||||||
# Install requirements, this is done inside the Docker container to safe some
|
|
||||||
# work!
|
|
||||||
# apt install -y git python3 python3-virtualenv python3-venv libopenblas-dev python3-pip libfftw3-dev libusb-1.0-0-dev libpulse-dev python3-build
|
|
||||||
#
|
|
||||||
# Build in venv
|
|
||||||
python3 -m venv .venv
|
|
||||||
# Activate venv
|
|
||||||
source .venv/bin/activate
|
|
||||||
# Build in venv
|
|
||||||
pip install build pytest
|
|
||||||
python3 -m build
|
|
||||||
pip install dist/lasp-*linux_x86_64.whl
|
|
||||||
# Test
|
|
||||||
pytest
|
|
@ -1,9 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
#
|
|
||||||
arch=mingw64
|
|
||||||
# DLL's that are required by lasp_cpp
|
|
||||||
files_to_cpy="libfftw3-3.dll libgcc_s_seh-1.dll libgfortran-5.dll libgomp-1.dll libopenblas.dll libquadmath-0.dll libstdc++-6.dll libwinpthread-1.dll"
|
|
||||||
|
|
||||||
for fn in ${files_to_cpy}; do
|
|
||||||
cp /c/msys64/${arch}/bin/${fn} python_src/lasp
|
|
||||||
done
|
|
@ -1,23 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# set default options for invoking pacman (in CI this variable is already set globally)
|
|
||||||
if [ -z $CI ]; then
|
|
||||||
PACMAN_OPTIONS="--needed --noconfirm"
|
|
||||||
fi
|
|
||||||
arch=mingw-w64-x86_64
|
|
||||||
# arch=mingw-w64-ucrt-x86_64
|
|
||||||
|
|
||||||
pacman -S ${PACMAN_OPTIONS} make
|
|
||||||
|
|
||||||
deps="gcc ninja ccache cmake openblas fftw"
|
|
||||||
for dep in $deps; do
|
|
||||||
pacman -S ${PACMAN_OPTIONS} ${arch}-${dep}
|
|
||||||
done
|
|
||||||
|
|
||||||
|
|
||||||
# install Python modules not provided as MSYS2/MinGW packages
|
|
||||||
#PACKAGES=""
|
|
||||||
#for arch in $(eval echo $ARCH); do
|
|
||||||
#/mingw64/bin/pip3 install --upgrade ${PACKAGES}
|
|
||||||
#done
|
|
@ -1,17 +0,0 @@
|
|||||||
#/bin/sh
|
|
||||||
# This script is run in the CI/CD system to test the code.
|
|
||||||
python3 -m venv testenv
|
|
||||||
|
|
||||||
# Activate environment
|
|
||||||
. testenv/bin/activate
|
|
||||||
|
|
||||||
pip install .
|
|
||||||
|
|
||||||
arch_os_line='NAME="Arch Linux"'
|
|
||||||
if [[ $(sed -n "/^${arch_os_line}/p;q" /etc/os-release) == ${arch_os_line} ]]; then
|
|
||||||
# Arch Linux
|
|
||||||
pytest
|
|
||||||
else
|
|
||||||
# Not Arch Linux, assuming Ubuntu
|
|
||||||
pytest-3
|
|
||||||
fi
|
|
53
setup.py
Normal file
53
setup.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import glob, os
|
||||||
|
import platform
|
||||||
|
|
||||||
|
from setuptools import setup
|
||||||
|
|
||||||
|
if 'Linux' in platform.platform():
|
||||||
|
ext_name_glob = 'lasp_cpp.cpython*'
|
||||||
|
extensions = list(glob.glob('src/lasp/' + ext_name_glob))
|
||||||
|
|
||||||
|
# Split of path from file.
|
||||||
|
ext_names = [os.path.split(a)[1] for a in extensions]
|
||||||
|
|
||||||
|
print(extensions)
|
||||||
|
if len(extensions) == 0:
|
||||||
|
raise RuntimeError('Please first run CMake to build extension')
|
||||||
|
elif len(extensions) > 1:
|
||||||
|
raise RuntimeError('Too many extension files found')
|
||||||
|
|
||||||
|
pkgdata = ext_names
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise RuntimeError('Not yet Windows-proof')
|
||||||
|
|
||||||
|
classifiers = [
|
||||||
|
"Topic :: Scientific/Engineering",
|
||||||
|
"Programming Language :: Python :: 3.8",
|
||||||
|
"Operating System :: POSIX :: Linux",
|
||||||
|
"Operating System :: Microsoft :: Windows",
|
||||||
|
]
|
||||||
|
|
||||||
|
keywords = ["DSP", "DAQ", "Signal processing"]
|
||||||
|
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name="lasp",
|
||||||
|
version="1.0",
|
||||||
|
description="LASP: Library for Acoustic Signal Processing",
|
||||||
|
|
||||||
|
author='J.A. de Jong (ASCEE / Redu-Sone)',
|
||||||
|
author_email='info@ascee.nl',
|
||||||
|
|
||||||
|
url='https://www.ascee.nl/lasp',
|
||||||
|
classifiers=classifiers,
|
||||||
|
keywords=keywords,
|
||||||
|
license="MIT",
|
||||||
|
dependencies=["numpy", "scipy", "appdirs", "h5py", "appdirs",
|
||||||
|
"dataclasses_json"],
|
||||||
|
package_dir={"": "src"},
|
||||||
|
packages=['lasp', 'lasp.filter', 'lasp.tools'],
|
||||||
|
include_package_data=True,
|
||||||
|
package_data={'lasp': pkgdata},
|
||||||
|
python_requires='>=3.8',
|
||||||
|
)
|
40
src/lasp/CMakeLists.txt
Normal file
40
src/lasp/CMakeLists.txt
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# src/lasp/CMakeLists.txt
|
||||||
|
|
||||||
|
# Armadillo
|
||||||
|
add_definitions(-DARMA_DONT_USE_WRAPPER)
|
||||||
|
|
||||||
|
configure_file(lasp_config.h.in lasp_config.h)
|
||||||
|
include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||||
|
include_directories(SYSTEM
|
||||||
|
${PROJECT_SOURCE_DIR}/third_party/armadillo-code/include)
|
||||||
|
|
||||||
|
include_directories(../../third_party/DebugTrace-cpp/include)
|
||||||
|
include_directories(../../third_party/gsl-lite/include)
|
||||||
|
include_directories(../../third_party/tomlplusplus/include)
|
||||||
|
include_directories(../../third_party/thread-pool)
|
||||||
|
|
||||||
|
if(LASP_HAS_RTAUDIO)
|
||||||
|
include_directories(../../third_party/rtaudio)
|
||||||
|
endif()
|
||||||
|
if(LASP_HAS_ULDAQ)
|
||||||
|
include_directories(../../third_party/uldaq/src)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_subdirectory(device)
|
||||||
|
add_subdirectory(dsp)
|
||||||
|
|
||||||
|
pybind11_add_module(lasp_cpp MODULE lasp_cpp.cpp
|
||||||
|
pybind11/lasp_deviceinfo.cpp
|
||||||
|
pybind11/lasp_daqconfig.cpp
|
||||||
|
pybind11//lasp_dsp_pybind.cpp
|
||||||
|
pybind11/lasp_streammgr.cpp
|
||||||
|
pybind11/lasp_daq.cpp
|
||||||
|
pybind11/lasp_deviceinfo.cpp
|
||||||
|
pybind11/lasp_pyindatahandler.cpp
|
||||||
|
pybind11/lasp_siggen.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(lasp_cpp PRIVATE lasp_device_lib lasp_dsp_lib
|
||||||
|
${OpenMP_CXX_LIBRARIES} ${LASP_FFT_LIBS})
|
||||||
|
|
||||||
|
install(TARGETS lasp_cpp DESTINATION .)
|
@ -2,20 +2,18 @@
|
|||||||
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
|
||||||
from .lasp_octavefilter import * # OverallFilterBank, SosOctaveFilterBank, SosThirdOctaveFilterBank
|
from .lasp_octavefilter import *
|
||||||
from .lasp_slm import * # SLM, Dummy
|
from .lasp_slm import * # SLM, Dummy
|
||||||
from .lasp_record import * # RecordStatus, Recording
|
from .lasp_record import * # RecordStatus, Recording
|
||||||
from .lasp_daqconfigs import * # DaqConfigurations
|
from .lasp_daqconfigs import *
|
||||||
from .lasp_measurementset import * # MeasurementSet
|
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
|
@ -1,6 +1,4 @@
|
|||||||
# src/lasp/device/CMakeLists.txt
|
# src/lasp/device/CMakeLists.txt
|
||||||
include_directories(uldaq)
|
|
||||||
include_directories(portaudio)
|
|
||||||
|
|
||||||
add_library(lasp_device_lib OBJECT
|
add_library(lasp_device_lib OBJECT
|
||||||
lasp_daq.cpp
|
lasp_daq.cpp
|
||||||
@ -9,12 +7,8 @@ add_library(lasp_device_lib OBJECT
|
|||||||
lasp_deviceinfo.cpp
|
lasp_deviceinfo.cpp
|
||||||
lasp_rtaudiodaq.cpp
|
lasp_rtaudiodaq.cpp
|
||||||
lasp_streammgr.cpp
|
lasp_streammgr.cpp
|
||||||
lasp_indatahandler.cpp
|
|
||||||
lasp_uldaq.cpp
|
lasp_uldaq.cpp
|
||||||
uldaq/lasp_uldaq_impl.cpp
|
lasp_uldaq_impl.cpp
|
||||||
uldaq/lasp_uldaq_bufhandler.cpp
|
|
||||||
uldaq/lasp_uldaq_common.cpp
|
|
||||||
portaudio/lasp_portaudiodaq.cpp
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Callback requires certain arguments that are not used by code. This disables
|
# Callback requires certain arguments that are not used by code. This disables
|
||||||
@ -33,13 +27,6 @@ endif()
|
|||||||
if(LASP_HAS_RTAUDIO)
|
if(LASP_HAS_RTAUDIO)
|
||||||
target_link_libraries(lasp_device_lib rtaudio)
|
target_link_libraries(lasp_device_lib rtaudio)
|
||||||
endif()
|
endif()
|
||||||
if(LASP_HAS_PORTAUDIO)
|
|
||||||
target_link_libraries(lasp_device_lib PortAudio)
|
|
||||||
if(WIN32)
|
|
||||||
else()
|
|
||||||
target_link_libraries(lasp_device_lib asound)
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
target_link_libraries(lasp_device_lib lasp_dsp_lib)
|
target_link_libraries(lasp_device_lib lasp_dsp_lib)
|
||||||
|
|
@ -1,8 +1,7 @@
|
|||||||
// #define DEBUGTRACE_ENABLED
|
/* #define DEBUGTRACE_ENABLED */
|
||||||
#include "debugtrace.hpp"
|
#include "debugtrace.hpp"
|
||||||
#include "lasp_daqconfig.h"
|
#include "lasp_daqconfig.h"
|
||||||
|
|
||||||
#include "lasp_config.h"
|
|
||||||
#include "lasp_daq.h"
|
#include "lasp_daq.h"
|
||||||
#if LASP_HAS_ULDAQ == 1
|
#if LASP_HAS_ULDAQ == 1
|
||||||
#include "lasp_uldaq.h"
|
#include "lasp_uldaq.h"
|
||||||
@ -10,9 +9,6 @@
|
|||||||
#if LASP_HAS_RTAUDIO == 1
|
#if LASP_HAS_RTAUDIO == 1
|
||||||
#include "lasp_rtaudiodaq.h"
|
#include "lasp_rtaudiodaq.h"
|
||||||
#endif
|
#endif
|
||||||
#if LASP_HAS_PORTAUDIO == 1
|
|
||||||
#include "lasp_portaudiodaq.h"
|
|
||||||
#endif
|
|
||||||
using rte = std::runtime_error;
|
using rte = std::runtime_error;
|
||||||
|
|
||||||
Daq::~Daq() { DEBUGTRACE_ENTER; }
|
Daq::~Daq() { DEBUGTRACE_ENTER; }
|
||||||
@ -31,11 +27,6 @@ std::unique_ptr<Daq> Daq::createDaq(const DeviceInfo &devinfo,
|
|||||||
if (devinfo.api.apicode == LASP_RTAUDIO_APICODE) {
|
if (devinfo.api.apicode == LASP_RTAUDIO_APICODE) {
|
||||||
return createRtAudioDevice(devinfo, config);
|
return createRtAudioDevice(devinfo, config);
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
#if LASP_HAS_PORTAUDIO == 1
|
|
||||||
if (devinfo.api.apicode == LASP_PORTAUDIO_APICODE) {
|
|
||||||
return createPortAudioDevice(devinfo, config);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
throw rte(string("Unable to match Device API: ") + devinfo.api.apiname);
|
throw rte(string("Unable to match Device API: ") + devinfo.api.apiname);
|
||||||
}
|
}
|
||||||
@ -44,41 +35,31 @@ Daq::Daq(const DeviceInfo &devinfo, const DaqConfiguration &config)
|
|||||||
: DaqConfiguration(config), DeviceInfo(devinfo) {
|
: DaqConfiguration(config), DeviceInfo(devinfo) {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
|
|
||||||
if(!duplexMode() && monitorOutput) {
|
if (duplexMode()) {
|
||||||
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 (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 (!hasInternalOutputMonitor && monitorOutput) {
|
if (!hasInternalOutputMonitor && monitorOutput) {
|
||||||
throw rte(
|
throw rte(
|
||||||
"Output monitor flag set, but device does not have hardware output monitor.");
|
"Output monitor flag set, but device does not have 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) {
|
||||||
{
|
|
||||||
const int hich = getHighestEnabledInChannel();
|
|
||||||
if (hich + 1 > devinfo.ninchannels)
|
|
||||||
{
|
|
||||||
throw rte(
|
throw rte(
|
||||||
string("Highest of enabled input channel: ") +
|
"Number of enabled input channels is higher than device capability");
|
||||||
to_string(hich) +
|
|
||||||
string(" is higher than device capability, which is: ") +
|
|
||||||
to_string(ninchannels) + ".");
|
|
||||||
}
|
}
|
||||||
}
|
if (nenoutchannels() > devinfo.noutchannels) {
|
||||||
|
|
||||||
{
|
|
||||||
const int hoch = getHighestEnabledOutChannel();
|
|
||||||
if (hoch + 1 > devinfo.noutchannels)
|
|
||||||
{
|
|
||||||
throw rte(
|
throw rte(
|
||||||
string("Highest of enabled output channel: ") +
|
"Number of enabled output channels is higher than device capability");
|
||||||
to_string(hoch) +
|
|
||||||
string(" is higher than device capability, which is: ") +
|
|
||||||
to_string(noutchannels) + ".");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5,8 +5,6 @@
|
|||||||
#include "lasp_types.h"
|
#include "lasp_types.h"
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
|
||||||
#include <atomic>
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \defgroup device Device interfacing
|
* \defgroup device Device interfacing
|
||||||
@ -15,12 +13,12 @@
|
|||||||
* @brief Callback of DAQ for input data. Callback should return
|
* @brief Callback of DAQ for input data. Callback should return
|
||||||
* false for a stop request.
|
* false for a stop request.
|
||||||
*/
|
*/
|
||||||
using InDaqCallback = std::function<void(const DaqData &)>;
|
using InDaqCallback = std::function<bool(const DaqData &)>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief
|
* @brief
|
||||||
*/
|
*/
|
||||||
using OutDaqCallback = std::function<void(DaqData &)>;
|
using OutDaqCallback = std::function<bool(DaqData &)>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Base cass for all DAQ (Data Acquisition) interfaces. A DAQ can be a
|
* @brief Base cass for all DAQ (Data Acquisition) interfaces. A DAQ can be a
|
||||||
@ -48,10 +46,6 @@ 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
|
||||||
*/
|
*/
|
||||||
@ -65,7 +59,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
|
||||||
*
|
*
|
||||||
@ -73,6 +67,8 @@ 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,14 +35,12 @@ 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++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,12 +54,10 @@ 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;
|
||||||
@ -70,15 +66,13 @@ int DaqConfiguration::getHighestEnabledInChannel() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int DaqConfiguration::getHighestEnabledOutChannel() const {
|
int DaqConfiguration::getHighestEnabledOutChannel() const {
|
||||||
DEBUGTRACE_ENTER;
|
for (us i = outchannel_config.size() - 1; i >= 0; i--) {
|
||||||
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;
|
@ -137,26 +137,15 @@ const DaqApi uldaqapi("UlDaq", 0);
|
|||||||
#include "RtAudio.h"
|
#include "RtAudio.h"
|
||||||
const us LASP_RTAUDIO_APICODE = 1;
|
const us LASP_RTAUDIO_APICODE = 1;
|
||||||
const DaqApi rtaudioAlsaApi("RtAudio Linux ALSA", 1, RtAudio::Api::LINUX_ALSA);
|
const DaqApi rtaudioAlsaApi("RtAudio Linux ALSA", 1, RtAudio::Api::LINUX_ALSA);
|
||||||
const DaqApi rtaudioPulseaudioApi("RtAudio Linux Pulseaudio", LASP_RTAUDIO_APICODE,
|
const DaqApi rtaudioPulseaudioApi("RtAudio Linux Pulseaudio", 1,
|
||||||
RtAudio::Api::LINUX_PULSE);
|
RtAudio::Api::LINUX_PULSE);
|
||||||
const DaqApi rtaudioWasapiApi("RtAudio Windows Wasapi", LASP_RTAUDIO_APICODE,
|
const DaqApi rtaudioWasapiApi("RtAudio Windows Wasapi", 1,
|
||||||
RtAudio::Api::WINDOWS_WASAPI);
|
RtAudio::Api::WINDOWS_WASAPI);
|
||||||
const DaqApi rtaudioDsApi("RtAudio Windows DirectSound", LASP_RTAUDIO_APICODE,
|
const DaqApi rtaudioDsApi("RtAudio Windows DirectSound", 1,
|
||||||
RtAudio::Api::WINDOWS_DS);
|
RtAudio::Api::WINDOWS_DS);
|
||||||
const DaqApi rtaudioAsioApi("RtAudio Windows ASIO", LASP_RTAUDIO_APICODE,
|
const DaqApi rtaudioAsioApi("RtAudio Windows ASIO", 1,
|
||||||
RtAudio::Api::WINDOWS_ASIO);
|
RtAudio::Api::WINDOWS_ASIO);
|
||||||
#endif
|
#endif
|
||||||
#if LASP_HAS_PORTAUDIO == 1
|
|
||||||
const us LASP_PORTAUDIO_APICODE = 2;
|
|
||||||
const DaqApi portaudioALSAApi("PortAudio Linux ALSA", LASP_PORTAUDIO_APICODE, 0);
|
|
||||||
const DaqApi portaudioPulseApi("PortAudio Linux PulseAudio", LASP_PORTAUDIO_APICODE, 1);
|
|
||||||
const DaqApi portaudioASIOApi("PortAudio Windows ASIO", LASP_PORTAUDIO_APICODE, 2);
|
|
||||||
const DaqApi portaudioDSApi("PortAudio Windows DirectSound", LASP_PORTAUDIO_APICODE, 3);
|
|
||||||
const DaqApi portaudioWMMEApi("PortAudio Windows WMME", LASP_PORTAUDIO_APICODE, 4);
|
|
||||||
const DaqApi portaudioWASAPIApi("PortAudio Windows WASAPI", LASP_PORTAUDIO_APICODE, 5);
|
|
||||||
const DaqApi portaudioWDMKS("PortAudio Windows WDMKS", LASP_PORTAUDIO_APICODE, 6);
|
|
||||||
const DaqApi portaudioDirectSoundApi("PortAudio Windows DirectSound", LASP_PORTAUDIO_APICODE, 7);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
class DeviceInfo;
|
class DeviceInfo;
|
||||||
|
|
@ -26,9 +26,7 @@ DaqData::DaqData(const us nframes, const us nchannels,
|
|||||||
DEBUGTRACE_PRINT(sw);
|
DEBUGTRACE_PRINT(sw);
|
||||||
|
|
||||||
assert(sw > 0 && sw <= 8);
|
assert(sw > 0 && sw <= 8);
|
||||||
_data = reinterpret_cast<byte_t *>(
|
_data = new (std::align_val_t{8}) byte_t[sw * nchannels * nframes];
|
||||||
new double[(sw * nchannels * nframes) / sizeof(double) + 1]);
|
|
||||||
|
|
||||||
if (!_data) {
|
if (!_data) {
|
||||||
throw rte("Could not allocate memory for DaqData!");
|
throw rte("Could not allocate memory for DaqData!");
|
||||||
}
|
}
|
||||||
@ -54,7 +52,7 @@ DaqData::DaqData(DaqData &&o)
|
|||||||
DaqData::~DaqData() {
|
DaqData::~DaqData() {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
if (_data)
|
if (_data)
|
||||||
delete[](reinterpret_cast<double *>(_data));
|
delete[] _data;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DaqData::copyInFromRaw(const std::vector<byte_t *> &ptrs) {
|
void DaqData::copyInFromRaw(const std::vector<byte_t *> &ptrs) {
|
@ -10,9 +10,6 @@
|
|||||||
#if LASP_HAS_RTAUDIO == 1
|
#if LASP_HAS_RTAUDIO == 1
|
||||||
#include "lasp_rtaudiodaq.h"
|
#include "lasp_rtaudiodaq.h"
|
||||||
#endif
|
#endif
|
||||||
#if LASP_HAS_PORTAUDIO == 1
|
|
||||||
#include "lasp_portaudiodaq.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
DeviceInfoList DeviceInfo::getDeviceInfo() {
|
DeviceInfoList DeviceInfo::getDeviceInfo() {
|
||||||
@ -24,9 +21,6 @@ DeviceInfoList DeviceInfo::getDeviceInfo() {
|
|||||||
#if LASP_HAS_RTAUDIO == 1
|
#if LASP_HAS_RTAUDIO == 1
|
||||||
fillRtAudioDeviceInfo(devs);
|
fillRtAudioDeviceInfo(devs);
|
||||||
#endif
|
#endif
|
||||||
#if LASP_HAS_PORTAUDIO == 1
|
|
||||||
fillPortAudioDeviceInfo(devs);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return devs;
|
return devs;
|
||||||
}
|
}
|
@ -19,7 +19,6 @@ public:
|
|||||||
* @brief Virtual desctructor. Can be derived class.
|
* @brief Virtual desctructor. Can be derived class.
|
||||||
*/
|
*/
|
||||||
virtual ~DeviceInfo() {}
|
virtual ~DeviceInfo() {}
|
||||||
DeviceInfo& operator=(const DeviceInfo&) = delete;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Clone a device info.
|
* @brief Clone a device info.
|
||||||
@ -71,23 +70,11 @@ public:
|
|||||||
* @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 Available ranges for the output, i.e. +/- 1V and/or +/- 10 V etc.
|
* @brief Its preffered range
|
||||||
*/
|
|
||||||
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
|
||||||
*/
|
*/
|
||||||
@ -137,29 +124,13 @@ public:
|
|||||||
bool duplexModeForced = false;
|
bool duplexModeForced = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Indicates whether the device is able to run in duplex mode. If false,
|
* @brief The physical quantity of the output signal. For 'normal' audio
|
||||||
* 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
|
||||||
* real DAQ devices however, the input quantity corresponds to a physical signal,
|
* devices however, the output 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,5 +1,5 @@
|
|||||||
/* #define DEBUGTRACE_ENABLED */
|
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
/* #define DEBUGTRACE_ENABLED */
|
||||||
#include "debugtrace.hpp"
|
#include "debugtrace.hpp"
|
||||||
#include "lasp_mathtypes.h"
|
#include "lasp_mathtypes.h"
|
||||||
|
|
||||||
@ -17,46 +17,37 @@ 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>;
|
||||||
|
|
||||||
const unsigned RTAUDIO_MAX_CHANNELS = 8;
|
class RtAudioDeviceInfo : public DeviceInfo {
|
||||||
|
|
||||||
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 ID; // Copy of RtAudio::DeviceInfo::ID
|
int _api_devindex;
|
||||||
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);
|
||||||
const us count = rtaudio.getDeviceCount();
|
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;
|
||||||
@ -79,49 +70,42 @@ void fillRtAudioDeviceInfo(DeviceInfoList &devinfolist)
|
|||||||
}
|
}
|
||||||
|
|
||||||
d.device_name = devinfo.name;
|
d.device_name = devinfo.name;
|
||||||
d.ID = id;
|
d._api_devindex = devno;
|
||||||
|
|
||||||
/// 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 = std::min(devinfo.outputChannels, RTAUDIO_MAX_CHANNELS);
|
d.noutchannels = devinfo.outputChannels;
|
||||||
d.ninchannels = std::min(devinfo.inputChannels, RTAUDIO_MAX_CHANNELS);
|
d.ninchannels = devinfo.inputChannels;
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
@ -129,18 +113,15 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,8 +139,9 @@ 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);
|
||||||
|
|
||||||
class RtAudioDaq : public Daq
|
static void myerrorcallback(RtAudioError::Type, const string &errorText);
|
||||||
{
|
|
||||||
|
class RtAudioDaq : public Daq {
|
||||||
|
|
||||||
RtAudio rtaudio;
|
RtAudio rtaudio;
|
||||||
const us nFramesPerBlock;
|
const us nFramesPerBlock;
|
||||||
@ -195,24 +177,24 @@ public:
|
|||||||
|
|
||||||
inParams = std::make_unique<RtAudio::StreamParameters>();
|
inParams = std::make_unique<RtAudio::StreamParameters>();
|
||||||
|
|
||||||
/// RtAudio lacks good bookkeeping when the first channel is not equal to
|
// +1 to get the count.
|
||||||
/// 0. For now, our fix is to shift out the channels we want, and let
|
inParams->nChannels = getHighestEnabledInChannel() + 1;
|
||||||
/// RtAudio pass on all channels.
|
if (inParams->nChannels < 1) {
|
||||||
inParams->firstChannel = 0;
|
throw rte("Invalid input number of channels");
|
||||||
inParams->nChannels = devinfo.ninchannels;
|
|
||||||
inParams->deviceId = devinfo.ID;
|
|
||||||
}
|
}
|
||||||
else
|
inParams->firstChannel = 0;
|
||||||
{
|
inParams->deviceId = devinfo._api_devindex;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
outParams = std::make_unique<RtAudio::StreamParameters>();
|
outParams = std::make_unique<RtAudio::StreamParameters>();
|
||||||
|
|
||||||
/// RtAudio lacks good bookkeeping when the first channel is not equal to
|
outParams->nChannels = getHighestEnabledOutChannel() + 1;
|
||||||
/// 0. For now, our fix is to shift out the channels we want, and let
|
if (outParams->nChannels < 1) {
|
||||||
/// RtAudio pass on all channels.
|
throw rte("Invalid output number of channels");
|
||||||
|
}
|
||||||
outParams->firstChannel = 0;
|
outParams->firstChannel = 0;
|
||||||
outParams->nChannels = devinfo.noutchannels;
|
outParams->deviceId = devinfo._api_devindex;
|
||||||
outParams->deviceId = devinfo.ID;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RtAudio::StreamOptions streamoptions;
|
RtAudio::StreamOptions streamoptions;
|
||||||
@ -225,8 +207,7 @@ 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;
|
||||||
@ -257,46 +238,36 @@ public:
|
|||||||
unsigned int nFramesPerBlock_copy = nFramesPerBlock;
|
unsigned int nFramesPerBlock_copy = nFramesPerBlock;
|
||||||
|
|
||||||
// Final step: open the stream.
|
// Final step: open the stream.
|
||||||
RtAudioErrorType err = rtaudio.openStream(outParams.get(), inParams.get(), format,
|
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,
|
||||||
if (err != RTAUDIO_NO_ERROR)
|
&myerrorcallback);
|
||||||
{
|
|
||||||
throw std::runtime_error(string("Error opening stream: ") + rtaudio.getErrorText());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nFramesPerBlock_copy != nFramesPerBlock)
|
if (nFramesPerBlock_copy != nFramesPerBlock) {
|
||||||
{
|
throw rte("Got different number of frames per block back from RtAudio "
|
||||||
throw rte(string("Got different number of frames per block back from RtAudio "
|
"backend. 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");
|
||||||
@ -304,10 +275,8 @@ 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");
|
||||||
}
|
}
|
||||||
@ -315,11 +284,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Start the stream. Throws on error.
|
// Start the stream. Throws on error.
|
||||||
const auto err = rtaudio.startStream();
|
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;
|
||||||
@ -329,15 +294,10 @@ 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;
|
||||||
@ -346,16 +306,14 @@ 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;
|
||||||
@ -364,8 +322,7 @@ 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;
|
||||||
@ -381,46 +338,44 @@ public:
|
|||||||
const auto &dtype_descr = dtypeDescr();
|
const auto &dtype_descr = dtypeDescr();
|
||||||
const auto dtype = dataType();
|
const auto dtype = dataType();
|
||||||
|
|
||||||
const us neninchannels = this->neninchannels();
|
us neninchannels = this->neninchannels();
|
||||||
const us nenoutchannels = this->nenoutchannels();
|
us nenoutchannels = this->nenoutchannels();
|
||||||
const us sw = dtype_descr.sw;
|
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);
|
||||||
|
|
||||||
const us ch_min = getLowestEnabledInChannel();
|
const us ch_min = getLowestEnabledInChannel();
|
||||||
const us ch_max = getHighestEnabledInChannel();
|
const us ch_max = getHighestEnabledInChannel();
|
||||||
assert(ch_min < ninchannels);
|
us i = 0;
|
||||||
assert(ch_max < ninchannels);
|
for (us ch = ch_min; ch <= ch_max; ch++) {
|
||||||
|
if (inchannel_config.at(ch).enabled) {
|
||||||
/// Only pass on the pointers of the channels we want
|
|
||||||
for (us ch = ch_min; ch <= ch_max; ch++)
|
|
||||||
{
|
|
||||||
if (inchannel_config.at(ch).enabled)
|
|
||||||
{
|
|
||||||
byte_t *ptr =
|
byte_t *ptr =
|
||||||
static_cast<byte_t *>(inputBuffer) + sw * ch * nFramesPerBlock;
|
static_cast<byte_t *>(inputBuffer) + sw * i * nFramesPerBlock;
|
||||||
|
DEBUGTRACE_PRINT((us)ptr);
|
||||||
ptrs.push_back(ptr);
|
ptrs.push_back(ptr);
|
||||||
}
|
}
|
||||||
|
i++;
|
||||||
}
|
}
|
||||||
DaqData d{nFramesPerBlock, neninchannels, dtype};
|
DaqData d{nFramesPerBlock, neninchannels, dtype};
|
||||||
d.copyInFromRaw(ptrs);
|
d.copyInFromRaw(ptrs);
|
||||||
|
|
||||||
_incallback(d);
|
bool ret = _incallback(d);
|
||||||
|
if (!ret) {
|
||||||
|
stopWithError(se::noError);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (outputBuffer)
|
if (outputBuffer) {
|
||||||
{
|
|
||||||
assert(_outcallback);
|
assert(_outcallback);
|
||||||
std::vector<byte_t *> ptrs;
|
std::vector<byte_t *> ptrs;
|
||||||
ptrs.reserve(nenoutchannels);
|
ptrs.reserve(nenoutchannels);
|
||||||
@ -429,24 +384,24 @@ public:
|
|||||||
|
|
||||||
const us ch_min = getLowestEnabledOutChannel();
|
const us ch_min = getLowestEnabledOutChannel();
|
||||||
const us ch_max = getHighestEnabledOutChannel();
|
const us ch_max = getHighestEnabledOutChannel();
|
||||||
assert(ch_min < noutchannels);
|
us i = 0;
|
||||||
assert(ch_max < noutchannels);
|
for (us ch = ch_min; ch <= ch_max; ch++) {
|
||||||
/// Only pass on the pointers of the channels we want
|
if (outchannel_config.at(ch).enabled) {
|
||||||
for (us ch = ch_min; ch <= ch_max; ch++)
|
|
||||||
{
|
|
||||||
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 * i * nFramesPerBlock);
|
||||||
}
|
}
|
||||||
|
i++;
|
||||||
}
|
}
|
||||||
DaqData d{nFramesPerBlock, nenoutchannels, dtype};
|
DaqData d{nFramesPerBlock, nenoutchannels, dtype};
|
||||||
|
|
||||||
_outcallback(d);
|
bool ret = _outcallback(d);
|
||||||
// Copy over the buffer
|
if (!ret) {
|
||||||
|
stopWithError(se::noError);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
us j = 0;
|
us j = 0;
|
||||||
for (auto ptr : ptrs)
|
for (auto ptr : ptrs) {
|
||||||
{
|
|
||||||
d.copyToRaw(j, ptr);
|
d.copyToRaw(j, ptr);
|
||||||
j++;
|
j++;
|
||||||
}
|
}
|
||||||
@ -462,16 +417,17 @@ 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);
|
14
src/lasp/device/lasp_rtaudiodaq.h
Normal file
14
src/lasp/device/lasp_rtaudiodaq.h
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "lasp_daq.h"
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
std::unique_ptr<Daq> createRtAudioDevice(const DeviceInfo& devinfo,
|
||||||
|
const DaqConfiguration& config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Append RtAudio backend devices to the list
|
||||||
|
*
|
||||||
|
* @param devinfolist List to append to
|
||||||
|
*/
|
||||||
|
void fillRtAudioDeviceInfo(DeviceInfoList &devinfolist);
|
||||||
|
|
@ -1,136 +1,103 @@
|
|||||||
// #define DEBUGTRACE_ENABLED
|
/* #define DEBUGTRACE_ENABLED */
|
||||||
#include "lasp_streammgr.h"
|
#include "lasp_streammgr.h"
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <functional>
|
|
||||||
#include <iostream>
|
|
||||||
#include <memory>
|
|
||||||
#include <mutex>
|
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
#include "debugtrace.hpp"
|
#include "debugtrace.hpp"
|
||||||
#include "lasp_biquadbank.h"
|
#include "lasp_biquadbank.h"
|
||||||
#include "lasp_indatahandler.h"
|
|
||||||
#include "lasp_thread.h"
|
#include "lasp_thread.h"
|
||||||
|
#include <algorithm>
|
||||||
using namespace std::literals::chrono_literals;
|
#include <assert.h>
|
||||||
|
#include <functional>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
using std::cerr;
|
using std::cerr;
|
||||||
using std::endl;
|
using std::endl;
|
||||||
using rte = std::runtime_error;
|
using rte = std::runtime_error;
|
||||||
|
|
||||||
/**
|
InDataHandler::InDataHandler(StreamMgr &mgr) : _mgr(mgr) { DEBUGTRACE_ENTER; }
|
||||||
* @brief The main global handle to a stream, stored in a weak pointer, if it
|
void InDataHandler::start() {
|
||||||
* does not yet exist, via StreamMgr::getInstance, a new stream mgr is created.
|
|
||||||
* It also makes sure that the stream manager is deleted once the latest handle
|
|
||||||
* to it has been destroyed (no global stuff left).
|
|
||||||
*/
|
|
||||||
std::weak_ptr<StreamMgr> _mgr;
|
|
||||||
std::mutex _mgr_mutex;
|
|
||||||
|
|
||||||
using Lck = std::scoped_lock<std::recursive_mutex>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief The only way to obtain a stream manager, can only be called from the
|
|
||||||
* thread that does it the first time.
|
|
||||||
*
|
|
||||||
* @return Stream manager handle
|
|
||||||
*/
|
|
||||||
SmgrHandle StreamMgr::getInstance() {
|
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
|
_mgr.addInDataHandler(*this);
|
||||||
std::scoped_lock<std::mutex> lck(_mgr_mutex);
|
}
|
||||||
auto mgr = _mgr.lock();
|
void InDataHandler::stop() {
|
||||||
if (!mgr) {
|
|
||||||
// Double Check Locking Pattern, if two threads would simultaneously
|
|
||||||
// instantiate the singleton instance.
|
|
||||||
|
|
||||||
auto mgr = _mgr.lock();
|
|
||||||
if (mgr) {
|
|
||||||
return mgr;
|
|
||||||
}
|
|
||||||
|
|
||||||
mgr = SmgrHandle(new StreamMgr());
|
|
||||||
if (!mgr) {
|
|
||||||
throw rte("Fatal: could not allocate stream manager!");
|
|
||||||
}
|
|
||||||
// Update global weak pointer
|
|
||||||
_mgr = mgr;
|
|
||||||
return mgr;
|
|
||||||
}
|
|
||||||
#if LASP_DEBUG == 1
|
#if LASP_DEBUG == 1
|
||||||
// Make sure we never ask for a new SmgrHandle from a different thread.
|
stopCalled = true;
|
||||||
assert(std::this_thread::get_id() == mgr->main_thread_id);
|
|
||||||
#endif
|
#endif
|
||||||
|
_mgr.removeInDataHandler(*this);
|
||||||
|
}
|
||||||
|
InDataHandler::~InDataHandler() {
|
||||||
|
|
||||||
return mgr;
|
DEBUGTRACE_ENTER;
|
||||||
|
#if LASP_DEBUG == 1
|
||||||
|
if (!stopCalled) {
|
||||||
|
cerr << "************ BUG: Stop function not called while arriving at "
|
||||||
|
"InDataHandler's destructor. Fix this by calling "
|
||||||
|
"InDataHandler::stop() from the derived class' destructor."
|
||||||
|
<< endl;
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
StreamMgr::StreamMgr()
|
StreamMgr &StreamMgr::getInstance() {
|
||||||
#if LASP_DEBUG == 1
|
|
||||||
: main_thread_id(std::this_thread::get_id())
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
|
static StreamMgr mgr;
|
||||||
|
return mgr;
|
||||||
|
}
|
||||||
|
StreamMgr::StreamMgr() {
|
||||||
|
DEBUGTRACE_ENTER;
|
||||||
|
#if LASP_DEBUG == 1
|
||||||
|
_main_thread_id = std::this_thread::get_id();
|
||||||
|
#endif
|
||||||
// Trigger a scan for the available devices, in the background.
|
// Trigger a scan for the available devices, in the background.
|
||||||
rescanDAQDevices(true);
|
rescanDAQDevices(true);
|
||||||
}
|
}
|
||||||
#if LASP_DEBUG == 1
|
#if LASP_DEBUG == 1
|
||||||
void StreamMgr::checkRightThread() const {
|
void StreamMgr::checkRightThread() const {
|
||||||
assert(std::this_thread::get_id() == main_thread_id);
|
assert(std::this_thread::get_id() == _main_thread_id);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void StreamMgr::rescanDAQDevices(bool background,
|
void StreamMgr::rescanDAQDevices(bool background,
|
||||||
std::function<void()> callback) {
|
std::function<void()> callback) {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
DEBUGTRACE_PRINT(background);
|
auto &pool = getPool();
|
||||||
if (_scanningDevices) {
|
|
||||||
throw rte("A background device scan is already busy");
|
|
||||||
}
|
|
||||||
|
|
||||||
Lck lck(_mtx);
|
|
||||||
checkRightThread();
|
checkRightThread();
|
||||||
|
if (!_devices_mtx.try_lock()) {
|
||||||
|
throw rte("A background DAQ device scan is probably already running");
|
||||||
|
}
|
||||||
|
_devices_mtx.unlock();
|
||||||
|
|
||||||
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");
|
||||||
}
|
}
|
||||||
|
|
||||||
_devices.clear();
|
_devices.clear();
|
||||||
|
/* auto &pool = getPool(); */
|
||||||
if (!background) {
|
if (!background) {
|
||||||
_scanningDevices = true;
|
|
||||||
rescanDAQDevices_impl(callback);
|
rescanDAQDevices_impl(callback);
|
||||||
} else {
|
} else {
|
||||||
DEBUGTRACE_PRINT("Rescanning DAQ devices on different thread...");
|
pool.push_task(&StreamMgr::rescanDAQDevices_impl, this, callback);
|
||||||
_scanningDevices = true;
|
|
||||||
_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;
|
||||||
assert(!_inputStream && !_outputStream);
|
std::scoped_lock lck(_devices_mtx);
|
||||||
Lck lck(_mtx);
|
|
||||||
// Alsa spits out annoying messages that are not useful
|
|
||||||
{
|
|
||||||
|
|
||||||
_devices = DeviceInfo::getDeviceInfo();
|
_devices = DeviceInfo::getDeviceInfo();
|
||||||
}
|
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
|
|
||||||
_scanningDevices = false;
|
|
||||||
}
|
}
|
||||||
void StreamMgr::inCallback(const DaqData &data) {
|
bool StreamMgr::inCallback(const DaqData &data) {
|
||||||
|
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
|
|
||||||
Lck lck(_mtx);
|
std::scoped_lock<std::mutex> lck(_inDataHandler_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.
|
||||||
|
|
||||||
@ -150,24 +117,33 @@ 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);
|
bool res = handler->inCallback(input_filtered);
|
||||||
|
if (!res) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} 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);
|
|
||||||
|
bool res = handler->inCallback(data);
|
||||||
|
if (!res) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void StreamMgr::setSiggen(std::shared_ptr<Siggen> siggen) {
|
void StreamMgr::setSiggen(std::shared_ptr<Siggen> siggen) {
|
||||||
|
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
checkRightThread();
|
checkRightThread();
|
||||||
|
|
||||||
Lck lck(_mtx);
|
std::scoped_lock<std::mutex> lck(_siggen_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) {
|
||||||
@ -190,8 +166,7 @@ void StreamMgr::setSiggen(std::shared_ptr<Siggen> siggen) {
|
|||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
template <typename T>
|
template <typename T> bool fillData(DaqData &data, const vd &signal) {
|
||||||
bool fillData(DaqData &data, const vd &signal) {
|
|
||||||
/* DEBUGTRACE_ENTER; */
|
/* DEBUGTRACE_ENTER; */
|
||||||
assert(data.nframes == signal.size());
|
assert(data.nframes == signal.size());
|
||||||
|
|
||||||
@ -223,10 +198,11 @@ bool fillData(DaqData &data, const vd &signal) {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
void StreamMgr::outCallback(DaqData &data) {
|
bool StreamMgr::outCallback(DaqData &data) {
|
||||||
DEBUGTRACE_ENTER;
|
|
||||||
|
|
||||||
Lck lck(_mtx);
|
/* DEBUGTRACE_ENTER; */
|
||||||
|
|
||||||
|
std::scoped_lock<std::mutex> lck(_siggen_mtx);
|
||||||
|
|
||||||
if (_siggen) {
|
if (_siggen) {
|
||||||
vd signal = _siggen->genSignal(data.nframes);
|
vd signal = _siggen->genSignal(data.nframes);
|
||||||
@ -251,72 +227,52 @@ void StreamMgr::outCallback(DaqData &data) {
|
|||||||
// Set all values to 0.
|
// Set all values to 0.
|
||||||
std::fill(data.raw_ptr(), data.raw_ptr() + data.size_bytes(), 0);
|
std::fill(data.raw_ptr(), data.raw_ptr() + data.size_bytes(), 0);
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
StreamMgr::~StreamMgr() {
|
StreamMgr::~StreamMgr() {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
while (_scanningDevices) {
|
|
||||||
std::this_thread::sleep_for(10us);
|
|
||||||
}
|
|
||||||
|
|
||||||
#if LASP_DEBUG == 1
|
|
||||||
{ // Careful, this lock needs to be released to make sure the streams can
|
|
||||||
// obtain a lock to the stream manager.
|
|
||||||
Lck lck(_mtx);
|
|
||||||
checkRightThread();
|
checkRightThread();
|
||||||
|
stopAllStreams();
|
||||||
|
if (!_inDataHandlers.empty()) {
|
||||||
|
cerr << "*** WARNING: InDataHandlers have not been all stopped, while "
|
||||||
|
"StreamMgr destructor is called. This is a misuse BUG"
|
||||||
|
<< endl;
|
||||||
|
abort();
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
// Stream manager now handled by shared pointer. Each indata handler gets a
|
|
||||||
// shared pointer to the stream manager, and stores a weak pointer to it.
|
|
||||||
// Hence, we do not have to do any cleanup here. It also makes sure that the
|
|
||||||
// order in which destructors are called does not matter anymore. As soon as
|
|
||||||
// the stream manager is destructed, the weak pointers loose there ref, and do
|
|
||||||
// not have to removeInDataHandler() anymore.
|
|
||||||
|
|
||||||
// Stop the streams in this phase, otherwise it might happen during the
|
|
||||||
// destruction of the Siggen, in which case we might get calls to pure
|
|
||||||
// virtual methods. This was really a bug.
|
|
||||||
_inputStream.reset();
|
|
||||||
_outputStream.reset();
|
|
||||||
}
|
}
|
||||||
void StreamMgr::stopAllStreams() {
|
void StreamMgr::stopAllStreams() {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
{
|
|
||||||
Lck lck(_mtx);
|
|
||||||
checkRightThread();
|
checkRightThread();
|
||||||
}
|
|
||||||
// No lock here!
|
|
||||||
_inputStream.reset();
|
_inputStream.reset();
|
||||||
_outputStream.reset();
|
_outputStream.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void StreamMgr::startStream(const DaqConfiguration &config) {
|
void StreamMgr::startStream(const DaqConfiguration &config) {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
if (_scanningDevices) {
|
|
||||||
throw rte("DAQ device scan is busy. Cannot start stream.");
|
|
||||||
}
|
|
||||||
Lck lck(_mtx);
|
|
||||||
checkRightThread();
|
checkRightThread();
|
||||||
|
|
||||||
bool isInput = std::count_if(config.inchannel_config.cbegin(),
|
bool isInput = std::count_if(config.inchannel_config.cbegin(),
|
||||||
config.inchannel_config.cend(),
|
config.inchannel_config.cend(),
|
||||||
[](auto &i) { return i.enabled; }) > 0;
|
[](auto &i) { return i.enabled; });
|
||||||
|
|
||||||
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; }) > 0;
|
[](auto &i) { return i.enabled; });
|
||||||
|
|
||||||
// Find the first device that matches with the configuration
|
// Find the first device that matches with the configuration
|
||||||
DeviceInfo *devinfo = nullptr;
|
std::scoped_lock lck(_devices_mtx);
|
||||||
|
|
||||||
|
DeviceInfo *devinfo = nullptr;
|
||||||
|
bool found = false;
|
||||||
|
|
||||||
// Match configuration to a device in the list of devices
|
|
||||||
for (auto &devinfoi : _devices) {
|
for (auto &devinfoi : _devices) {
|
||||||
if (config.match(*devinfoi)) {
|
if (config.match(*devinfoi)) {
|
||||||
devinfo = devinfoi.get();
|
devinfo = devinfoi.get();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (devinfo == nullptr) {
|
if (!devinfo) {
|
||||||
throw rte("Could not find a device with name " + config.device_name +
|
throw rte("Could not find a device with name " + config.device_name +
|
||||||
" in list of devices.");
|
" in list of devices.");
|
||||||
}
|
}
|
||||||
@ -327,23 +283,19 @@ void StreamMgr::startStream(const DaqConfiguration &config) {
|
|||||||
bool isDuplex = isInput && isOutput;
|
bool isDuplex = isInput && isOutput;
|
||||||
|
|
||||||
if (!isInput && !isOutput) {
|
if (!isInput && !isOutput) {
|
||||||
throw rte(
|
throw rte("Neither input, nor output channels enabled for "
|
||||||
"Attempted stream start failed, stream does not have any enabled "
|
"stream. Cannot start.");
|
||||||
"channels. Please first enable channels in the channel configuration.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isInput && _inputStream) {
|
if (isInput && _inputStream) {
|
||||||
throw rte(
|
throw rte("Error: an input stream is already running. Please "
|
||||||
"Error: an input stream is already running. Please "
|
|
||||||
"first stop existing stream");
|
"first stop existing stream");
|
||||||
} else if (isOutput && _outputStream) {
|
} else if (isOutput && _outputStream) {
|
||||||
throw rte(
|
throw rte("Error: output stream is already running. Please "
|
||||||
"Error: output stream is already running. Please "
|
|
||||||
"first stop existing stream");
|
"first stop existing stream");
|
||||||
} else if (_inputStream) {
|
} else if (_inputStream) {
|
||||||
if (_inputStream->duplexMode() && isOutput) {
|
if (_inputStream->duplexMode() && isOutput) {
|
||||||
throw rte(
|
throw rte("Error: output stream is already running (in duplex mode). "
|
||||||
"Error: output stream is already running (in duplex mode). "
|
|
||||||
"Please "
|
"Please "
|
||||||
"first stop existing stream");
|
"first stop existing stream");
|
||||||
}
|
}
|
||||||
@ -351,15 +303,13 @@ void StreamMgr::startStream(const DaqConfiguration &config) {
|
|||||||
|
|
||||||
if (_outputStream && isInput && _outputStream->duplexModeForced &&
|
if (_outputStream && isInput && _outputStream->duplexModeForced &&
|
||||||
config.match(*_outputStream)) {
|
config.match(*_outputStream)) {
|
||||||
throw rte(
|
throw rte("This device is already opened for output. If input is also "
|
||||||
"This device is already opened for output. If input is also "
|
|
||||||
"required, please enable duplex mode for this device");
|
"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(
|
throw rte("This device is already opened for input. If output is also "
|
||||||
"This device is already opened for input. If output is also "
|
|
||||||
"required, please enable duplex mode for this device");
|
"required, please enable duplex mode for this device");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -383,13 +333,10 @@ 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.
|
||||||
/// No input filter for monitor channel, which comes as the first input
|
|
||||||
/// 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) {
|
||||||
@ -429,76 +376,61 @@ void StreamMgr::startStream(const DaqConfiguration &config) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
void StreamMgr::stopStream(const StreamType t) {
|
void StreamMgr::stopStream(const StreamType t) {
|
||||||
|
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
checkRightThread();
|
checkRightThread();
|
||||||
bool resetHandlers = false;
|
|
||||||
std::unique_ptr<Daq> *streamToStop = nullptr;
|
|
||||||
|
|
||||||
{ // Mutex locked in this scope
|
|
||||||
Lck lck(_mtx);
|
|
||||||
if (t == StreamType::input) {
|
if (t == StreamType::input) {
|
||||||
if (!_inputStream) {
|
if (!_inputStream) {
|
||||||
throw rte("Input stream is not running");
|
throw rte("Input stream is not running");
|
||||||
}
|
}
|
||||||
streamToStop = std::addressof(_inputStream);
|
/// Kills input stream
|
||||||
resetHandlers = true;
|
_inputStream.reset();
|
||||||
|
/// Send reset to all in data handlers
|
||||||
|
for (auto &handler : _inDataHandlers) {
|
||||||
|
handler->reset(nullptr);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
/// t == output
|
/// t == output
|
||||||
|
|
||||||
/// Kill input stream in case that one is a duplex stream
|
/// Kill input stream in case that one is a duplex stream
|
||||||
if (_inputStream && _inputStream->duplexMode()) {
|
if (_inputStream && _inputStream->duplexMode()) {
|
||||||
streamToStop = std::addressof(_inputStream);
|
_inputStream.reset();
|
||||||
} else {
|
} else {
|
||||||
if (!_outputStream) {
|
if (!_outputStream) {
|
||||||
throw rte("Output stream is not running");
|
throw rte("Output stream is not running");
|
||||||
}
|
}
|
||||||
streamToStop = std::addressof(_outputStream);
|
_outputStream.reset();
|
||||||
} // end else
|
} // end else
|
||||||
}
|
}
|
||||||
} // End of mutex lock. When stopping stream, mutex should be unlocked.
|
|
||||||
|
|
||||||
// If we arrive here, we should have a stream to stop.
|
|
||||||
assert(streamToStop != nullptr);
|
|
||||||
streamToStop->reset();
|
|
||||||
|
|
||||||
/// Send reset to all in data handlers
|
|
||||||
if (resetHandlers) {
|
|
||||||
Lck lck(_mtx);
|
|
||||||
for (auto &handler : _inDataHandlers) {
|
|
||||||
handler->reset(nullptr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void StreamMgr::addInDataHandler(InDataHandler *handler) {
|
void StreamMgr::addInDataHandler(InDataHandler &handler) {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
Lck lck(_mtx);
|
|
||||||
checkRightThread();
|
checkRightThread();
|
||||||
assert(handler);
|
std::scoped_lock<std::mutex> lck(_inDataHandler_mtx);
|
||||||
handler->reset(_inputStream.get());
|
if (_inputStream) {
|
||||||
|
handler.reset(_inputStream.get());
|
||||||
if (std::find(_inDataHandlers.cbegin(), _inDataHandlers.cend(), handler) !=
|
} else {
|
||||||
|
handler.reset(nullptr);
|
||||||
|
}
|
||||||
|
if (std::find(_inDataHandlers.cbegin(), _inDataHandlers.cend(), &handler) !=
|
||||||
_inDataHandlers.cend()) {
|
_inDataHandlers.cend()) {
|
||||||
throw std::runtime_error(
|
throw std::runtime_error("Error: handler already added. Probably start() "
|
||||||
"Error: handler already added. Probably start() "
|
|
||||||
"is called more than once on a handler object");
|
"is called more than once on a handler object");
|
||||||
}
|
}
|
||||||
_inDataHandlers.push_back(handler);
|
_inDataHandlers.push_back(&handler);
|
||||||
DEBUGTRACE_PRINT(_inDataHandlers.size());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void StreamMgr::removeInDataHandler(InDataHandler &handler) {
|
void StreamMgr::removeInDataHandler(InDataHandler &handler) {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
Lck lck(_mtx);
|
checkRightThread();
|
||||||
// checkRightThread();
|
std::scoped_lock<std::mutex> lck(_inDataHandler_mtx);
|
||||||
_inDataHandlers.remove(&handler);
|
_inDataHandlers.remove(&handler);
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
@ -511,7 +443,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,18 +1,69 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include "lasp_daq.h"
|
||||||
|
#include "lasp_siggen.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 {
|
||||||
|
|
||||||
|
protected:
|
||||||
|
StreamMgr &_mgr;
|
||||||
|
#if LASP_DEBUG == 1
|
||||||
|
std::atomic<bool> stopCalled{false};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual ~InDataHandler();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief When constructed, the handler is added to the stream manager, which
|
||||||
|
* will call the handlers's inCallback() until stop() is called.
|
||||||
|
*
|
||||||
|
* @param mgr Stream manager.
|
||||||
|
*/
|
||||||
|
InDataHandler(StreamMgr &mgr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief This function is called when input data from a DAQ is available.
|
||||||
|
*
|
||||||
|
* @param daqdata Input data from DAQ
|
||||||
|
*
|
||||||
|
* @return true if no error. False to stop the stream from running.
|
||||||
|
*/
|
||||||
|
virtual bool inCallback(const DaqData &daqdata) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reset in-data handler.
|
||||||
|
*
|
||||||
|
* @param daq New DAQ configuration of inCallback(). If nullptr is given,
|
||||||
|
* it means that the stream is stopped.
|
||||||
|
*/
|
||||||
|
virtual void reset(const Daq *daq = nullptr) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief This function should be called from the constructor of the
|
||||||
|
* implementation of InDataHandler. It will start the stream's calling of
|
||||||
|
* inCallback().
|
||||||
|
*/
|
||||||
|
void start();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief This function should be called from the destructor of derived
|
||||||
|
* classes, to disable the calls to inCallback(), such that proper
|
||||||
|
* destruction of the object is allowed and no other threads call methods
|
||||||
|
* from the object. It removes the inCallback() from the callback list of the
|
||||||
|
* StreamMgr(). **Failing to call this function results in deadlocks, errors
|
||||||
|
* like "pure virtual function called", or other**.
|
||||||
|
*/
|
||||||
|
void stop();
|
||||||
|
};
|
||||||
|
|
||||||
class SeriesBiquad;
|
class SeriesBiquad;
|
||||||
|
|
||||||
@ -25,23 +76,22 @@ class SeriesBiquad;
|
|||||||
* fact is asserted.
|
* fact is asserted.
|
||||||
*/
|
*/
|
||||||
class StreamMgr {
|
class StreamMgr {
|
||||||
mutable std::recursive_mutex _mtx;
|
#if LASP_DEBUG == 1
|
||||||
|
std::thread::id _main_thread_id;
|
||||||
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @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;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief All indata handlers are called when input data is available. Note
|
* @brief All indata handlers are called when input data is available. Note
|
||||||
* that they can be called from different threads and should take care of
|
* that they can be called from different threads and should take care of
|
||||||
* thread-safety.
|
* thread-safety.
|
||||||
*/
|
*/
|
||||||
std::list<InDataHandler *> _inDataHandlers;
|
std::list<InDataHandler *> _inDataHandlers;
|
||||||
|
std::mutex _inDataHandler_mtx;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Signal generator in use to generate output data. Currently
|
* @brief Signal generator in use to generate output data. Currently
|
||||||
@ -54,21 +104,20 @@ class StreamMgr {
|
|||||||
*/
|
*/
|
||||||
std::vector<std::unique_ptr<SeriesBiquad>> _inputFilters;
|
std::vector<std::unique_ptr<SeriesBiquad>> _inputFilters;
|
||||||
|
|
||||||
/**
|
std::mutex _siggen_mtx;
|
||||||
* @brief Current storage for the device list
|
|
||||||
*/
|
std::mutex _devices_mtx;
|
||||||
DeviceInfoList _devices;
|
DeviceInfoList _devices;
|
||||||
|
|
||||||
// Singleton, no public constructor. Can only be obtained using
|
|
||||||
// getInstance();
|
|
||||||
StreamMgr();
|
StreamMgr();
|
||||||
|
|
||||||
friend class InDataHandler;
|
friend class InDataHandler;
|
||||||
friend class Siggen;
|
friend class Siggen;
|
||||||
|
|
||||||
public:
|
// Singleton, no public destructor
|
||||||
~StreamMgr();
|
~StreamMgr();
|
||||||
|
|
||||||
|
public:
|
||||||
enum class StreamType : us {
|
enum class StreamType : us {
|
||||||
/**
|
/**
|
||||||
* @brief Input stream
|
* @brief Input stream
|
||||||
@ -88,7 +137,7 @@ class StreamMgr {
|
|||||||
*
|
*
|
||||||
* @return Reference to stream manager.
|
* @return Reference to stream manager.
|
||||||
*/
|
*/
|
||||||
static std::shared_ptr<StreamMgr> getInstance();
|
static StreamMgr &getInstance();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Obtain a list of devices currently available. When the StreamMgr is
|
* @brief Obtain a list of devices currently available. When the StreamMgr is
|
||||||
@ -97,10 +146,9 @@ 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(_mtx);
|
std::scoped_lock lck(const_cast<std::mutex &>(_devices_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;
|
||||||
@ -109,15 +157,14 @@ class StreamMgr {
|
|||||||
/**
|
/**
|
||||||
* @brief Triggers a background scan of the DAQ devices, which updates the
|
* @brief Triggers a background scan of the DAQ devices, which updates the
|
||||||
* internally stored list of devices. Throws a runtime error when a
|
* internally stored list of devices. Throws a runtime error when a
|
||||||
* background thread is already scanning for devices, or if a stream is
|
* background thread is already scanning for devices.
|
||||||
* running.
|
|
||||||
*
|
*
|
||||||
* @param background Perform searching for DAQ devices in the background. If
|
* @param background Perform searching for DAQ devices in the background. If
|
||||||
* 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 rescanDAQDevices(
|
void
|
||||||
bool background = false,
|
rescanDAQDevices(bool background = false,
|
||||||
std::function<void()> callback = std::function<void()>());
|
std::function<void()> callback = std::function<void()>());
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -184,16 +231,17 @@ class StreamMgr {
|
|||||||
/**
|
/**
|
||||||
* @brief Set active signal generator for output streams. Only one `Siggen'
|
* @brief Set active signal generator for output streams. Only one `Siggen'
|
||||||
* is active at the same time. Siggen controls its own data race protection
|
* is active at the same time. Siggen controls its own data race protection
|
||||||
* using a mutex. If no Siggen is there, and an output stream is running, it
|
* using a mutex.
|
||||||
* will send a default signal of 0.
|
|
||||||
*
|
*
|
||||||
* @param s New Siggen pointer
|
* @param s New Siggen pointer
|
||||||
*/
|
*/
|
||||||
void setSiggen(std::shared_ptr<Siggen> s);
|
void setSiggen(std::shared_ptr<Siggen> s);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void inCallback(const DaqData &data);
|
bool inCallback(const DaqData &data);
|
||||||
void outCallback(DaqData &data);
|
bool outCallback(DaqData &data);
|
||||||
|
|
||||||
|
void removeInDataHandler(InDataHandler &handler);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Add an input data handler. The handler's inCallback() function is
|
* @brief Add an input data handler. The handler's inCallback() function is
|
||||||
@ -203,14 +251,8 @@ class StreamMgr {
|
|||||||
*
|
*
|
||||||
* @param handler The handler to add.
|
* @param handler The handler to add.
|
||||||
*/
|
*/
|
||||||
void addInDataHandler(InDataHandler *handler);
|
void addInDataHandler(InDataHandler &handler);
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Remove InDataHandler from the list.
|
|
||||||
*
|
|
||||||
* @param handler
|
|
||||||
*/
|
|
||||||
void removeInDataHandler(InDataHandler &handler);
|
|
||||||
/**
|
/**
|
||||||
* @brief Do the actual rescanning.
|
* @brief Do the actual rescanning.
|
||||||
*
|
*
|
||||||
@ -219,7 +261,6 @@ class StreamMgr {
|
|||||||
void rescanDAQDevices_impl(std::function<void()> callback);
|
void rescanDAQDevices_impl(std::function<void()> callback);
|
||||||
|
|
||||||
#if LASP_DEBUG == 1
|
#if LASP_DEBUG == 1
|
||||||
const std::thread::id main_thread_id;
|
|
||||||
void checkRightThread() const;
|
void checkRightThread() const;
|
||||||
#else
|
#else
|
||||||
void checkRightThread() const {}
|
void checkRightThread() const {}
|
@ -1,22 +1,18 @@
|
|||||||
/* #define DEBUGTRACE_ENABLED */
|
/* #define DEBUGTRACE_ENABLED */
|
||||||
#include "debugtrace.hpp"
|
#include "debugtrace.hpp"
|
||||||
#include "lasp_config.h"
|
#include "lasp_config.h"
|
||||||
|
|
||||||
#if LASP_HAS_ULDAQ == 1
|
#if LASP_HAS_ULDAQ == 1
|
||||||
#include "lasp_uldaq.h"
|
#include "lasp_uldaq.h"
|
||||||
#include "lasp_uldaq_impl.h"
|
#include "lasp_uldaq_impl.h"
|
||||||
#include <uldaq.h>
|
#include <uldaq.h>
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief The maximum number of devices that can be enumerated when calling
|
|
||||||
* ulGetDaqDeviceInventory()
|
|
||||||
*/
|
|
||||||
const us MAX_ULDAQ_DEV_COUNT_PER_API = 100;
|
|
||||||
|
|
||||||
void fillUlDaqDeviceInfo(DeviceInfoList &devinfolist) {
|
void fillUlDaqDeviceInfo(DeviceInfoList &devinfolist) {
|
||||||
|
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
|
|
||||||
|
|
||||||
UlError err;
|
UlError err;
|
||||||
unsigned int numdevs = MAX_ULDAQ_DEV_COUNT_PER_API;
|
unsigned int numdevs = MAX_ULDAQ_DEV_COUNT_PER_API;
|
||||||
|
|
||||||
@ -24,13 +20,13 @@ void fillUlDaqDeviceInfo(DeviceInfoList &devinfolist) {
|
|||||||
DaqDeviceDescriptor descriptor;
|
DaqDeviceDescriptor descriptor;
|
||||||
DaqDeviceInterface interfaceType = ANY_IFC;
|
DaqDeviceInterface interfaceType = ANY_IFC;
|
||||||
|
|
||||||
err = ulGetDaqDeviceInventory(interfaceType, devdescriptors, &numdevs);
|
err = ulGetDaqDeviceInventory(interfaceType, devdescriptors,
|
||||||
|
static_cast<unsigned *>(&numdevs));
|
||||||
|
|
||||||
if (err != ERR_NO_ERROR) {
|
if (err != ERR_NO_ERROR) {
|
||||||
throw rte("UlDaq device inventarization failed");
|
throw rte("UlDaq device inventarization failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
DEBUGTRACE_PRINT(string("Number of devices: ") + std::to_string(numdevs));
|
|
||||||
for (unsigned i = 0; i < numdevs; i++) {
|
for (unsigned i = 0; i < numdevs; i++) {
|
||||||
|
|
||||||
descriptor = devdescriptors[i];
|
descriptor = devdescriptors[i];
|
||||||
@ -39,8 +35,7 @@ void fillUlDaqDeviceInfo(DeviceInfoList &devinfolist) {
|
|||||||
devinfo._uldaqDescriptor = descriptor;
|
devinfo._uldaqDescriptor = descriptor;
|
||||||
|
|
||||||
devinfo.api = uldaqapi;
|
devinfo.api = uldaqapi;
|
||||||
{
|
string name, interface;
|
||||||
string name;
|
|
||||||
string productname = descriptor.productName;
|
string productname = descriptor.productName;
|
||||||
if (productname != "DT9837A") {
|
if (productname != "DT9837A") {
|
||||||
throw rte("Unknown UlDAQ type: " + productname);
|
throw rte("Unknown UlDAQ type: " + productname);
|
||||||
@ -63,26 +58,25 @@ void fillUlDaqDeviceInfo(DeviceInfoList &devinfolist) {
|
|||||||
name = "Uknown interface = ";
|
name = "Uknown interface = ";
|
||||||
}
|
}
|
||||||
|
|
||||||
name += productname + " " + string(descriptor.uniqueId);
|
name += string(descriptor.productName) + " " + string(descriptor.uniqueId);
|
||||||
devinfo.device_name = name;
|
devinfo.device_name = std::move(name);
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
||||||
devinfo.prefDataTypeIndex = 0;
|
devinfo.prefDataTypeIndex = 0;
|
||||||
|
|
||||||
devinfo.availableSampleRates = ULDAQ_SAMPLERATES;
|
devinfo.availableSampleRates = {8000, 10000, 11025, 16000, 20000,
|
||||||
|
22050, 24000, 32000, 44056, 44100,
|
||||||
|
47250, 48000, 50000, 50400, 51000};
|
||||||
|
|
||||||
devinfo.prefSampleRateIndex = 11;
|
devinfo.prefSampleRateIndex = 11;
|
||||||
|
|
||||||
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;
|
||||||
@ -93,7 +87,6 @@ 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
|
||||||
@ -103,12 +96,7 @@ void fillUlDaqDeviceInfo(DeviceInfoList &devinfolist) {
|
|||||||
|
|
||||||
std::unique_ptr<Daq> createUlDaqDevice(const DeviceInfo &devinfo,
|
std::unique_ptr<Daq> createUlDaqDevice(const DeviceInfo &devinfo,
|
||||||
const DaqConfiguration &config) {
|
const DaqConfiguration &config) {
|
||||||
const UlDaqDeviceInfo *_info =
|
return std::make_unique<DT9837A>(devinfo, config);
|
||||||
dynamic_cast<const UlDaqDeviceInfo *>(&devinfo);
|
|
||||||
if (_info == nullptr) {
|
|
||||||
throw rte("BUG: Could not cast DeviceInfo to UlDaqDeviceInfo");
|
|
||||||
}
|
|
||||||
return std::make_unique<DT9837A>(*_info, config);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // LASP_HAS_ULDAQ
|
#endif // LASP_HAS_ULDAQ
|
@ -1,24 +1,18 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "lasp_daq.h"
|
#include "lasp_daq.h"
|
||||||
|
|
||||||
/** \addtogroup device
|
/**
|
||||||
* \defgroup uldaq UlDAQ specific code
|
* @brief The maximum number of devices that can be enumerated when calling
|
||||||
* This code is used to interface with UlDAQ compatible devices. It is only
|
* ulGetDaqDeviceInventory()
|
||||||
* tested on Linux.
|
|
||||||
* @{
|
|
||||||
* \addtogroup uldaq
|
|
||||||
* @{
|
|
||||||
*/
|
*/
|
||||||
|
const us MAX_ULDAQ_DEV_COUNT_PER_API = 100;
|
||||||
|
|
||||||
std::unique_ptr<Daq> createUlDaqDevice(const DeviceInfo &devinfo,
|
std::unique_ptr<Daq> createUlDaqDevice(const DeviceInfo &devinfo,
|
||||||
const DaqConfiguration &config);
|
const DaqConfiguration &config);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Append device info list with UlDaq specific devices, if any.
|
* @brief Fill device info list with UlDaq specific devices, if any.
|
||||||
*
|
*
|
||||||
* @param devinfolist Info list to append to.
|
* @param devinfolist Info list to append to.
|
||||||
*/
|
*/
|
||||||
void fillUlDaqDeviceInfo(DeviceInfoList& devinfolist);
|
void fillUlDaqDeviceInfo(DeviceInfoList& devinfolist);
|
||||||
|
|
||||||
/** @} */
|
|
||||||
/** @} */
|
|
480
src/lasp/device/lasp_uldaq_impl.cpp
Normal file
480
src/lasp/device/lasp_uldaq_impl.cpp
Normal file
@ -0,0 +1,480 @@
|
|||||||
|
/* #define DEBUGTRACE_ENABLED */
|
||||||
|
#include "debugtrace.hpp"
|
||||||
|
#include "lasp_config.h"
|
||||||
|
|
||||||
|
#if LASP_HAS_ULDAQ == 1
|
||||||
|
#include "lasp_daqconfig.h"
|
||||||
|
#include "lasp_uldaq.h"
|
||||||
|
#include "lasp_uldaq_impl.h"
|
||||||
|
|
||||||
|
using namespace std::literals::chrono_literals;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reserve some space for an error message from UlDaq
|
||||||
|
*/
|
||||||
|
const us UL_ERR_MSG_LEN = 512;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Return a string corresponding to the UlDaq API error
|
||||||
|
*
|
||||||
|
* @param err error code
|
||||||
|
*
|
||||||
|
* @return Error string
|
||||||
|
*/
|
||||||
|
string getErrMsg(UlError err) {
|
||||||
|
string errstr;
|
||||||
|
errstr.reserve(UL_ERR_MSG_LEN);
|
||||||
|
char errmsg[UL_ERR_MSG_LEN];
|
||||||
|
errstr = "UlDaq API Error: ";
|
||||||
|
ulGetErrMsg(err, errmsg);
|
||||||
|
errstr += errmsg;
|
||||||
|
return errstr;
|
||||||
|
}
|
||||||
|
inline void showErr(string errstr) {
|
||||||
|
std::cerr << "\b\n**************** UlDAQ backend error **********\n";
|
||||||
|
std::cerr << errstr << std::endl;
|
||||||
|
std::cerr << "***********************************************\n\n";
|
||||||
|
}
|
||||||
|
inline void showErr(UlError err) {
|
||||||
|
if (err != ERR_NO_ERROR)
|
||||||
|
showErr(getErrMsg(err));
|
||||||
|
}
|
||||||
|
|
||||||
|
DT9837A::~DT9837A() {
|
||||||
|
UlError err;
|
||||||
|
if (isRunning()) {
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_handle) {
|
||||||
|
err = ulDisconnectDaqDevice(_handle);
|
||||||
|
showErr(err);
|
||||||
|
err = ulReleaseDaqDevice(_handle);
|
||||||
|
showErr(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DT9837A::DT9837A(const DeviceInfo &devinfo, const DaqConfiguration &config)
|
||||||
|
: Daq(devinfo, config),
|
||||||
|
_nFramesPerBlock(availableFramesPerBlock.at(framesPerBlockIndex)) {
|
||||||
|
|
||||||
|
// Some sanity checks
|
||||||
|
if (inchannel_config.size() != 4) {
|
||||||
|
throw rte("Invalid length of enabled inChannels vector");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outchannel_config.size() != 1) {
|
||||||
|
throw rte("Invalid length of enabled outChannels vector");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_nFramesPerBlock < 24 || _nFramesPerBlock > 8192) {
|
||||||
|
throw rte("Unsensible number of samples per block chosen");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (samplerate() < 10000 || samplerate() > 51000) {
|
||||||
|
throw rte("Invalid sample rate");
|
||||||
|
}
|
||||||
|
|
||||||
|
const UlDaqDeviceInfo *_info =
|
||||||
|
dynamic_cast<const UlDaqDeviceInfo *>(&devinfo);
|
||||||
|
if (_info == nullptr) {
|
||||||
|
throw rte("BUG: Could not cast DeviceInfo to UlDaqDeviceInfo");
|
||||||
|
}
|
||||||
|
|
||||||
|
// get a handle to the DAQ device associated with the first descriptor
|
||||||
|
_handle = ulCreateDaqDevice(_info->_uldaqDescriptor);
|
||||||
|
|
||||||
|
if (_handle == 0) {
|
||||||
|
throw rte("Unable to create a handle to the specified DAQ "
|
||||||
|
"device. Is the device currently in use? Please make sure to set "
|
||||||
|
"the DAQ configuration in duplex mode if simultaneous input and "
|
||||||
|
"output is required.");
|
||||||
|
}
|
||||||
|
|
||||||
|
UlError err = ulConnectDaqDevice(_handle);
|
||||||
|
|
||||||
|
if (err != ERR_NO_ERROR) {
|
||||||
|
ulReleaseDaqDevice(_handle);
|
||||||
|
_handle = 0;
|
||||||
|
throw rte("Unable to connect to device: " + getErrMsg(err));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Loop over input channels, set parameters
|
||||||
|
for (us ch = 0; ch < 4; ch++) {
|
||||||
|
|
||||||
|
err = ulAISetConfigDbl(_handle, AI_CFG_CHAN_SENSOR_SENSITIVITY, ch, 1.0);
|
||||||
|
showErr(err);
|
||||||
|
if (err != ERR_NO_ERROR) {
|
||||||
|
throw rte("Fatal: could normalize channel sensitivity");
|
||||||
|
}
|
||||||
|
|
||||||
|
CouplingMode cm = inchannel_config.at(ch).ACCouplingMode ? CM_AC : CM_DC;
|
||||||
|
err = ulAISetConfig(_handle, AI_CFG_CHAN_COUPLING_MODE, ch, cm);
|
||||||
|
if (err != ERR_NO_ERROR) {
|
||||||
|
showErr(err);
|
||||||
|
throw rte("Fatal: could not set AC/DC coupling mode");
|
||||||
|
}
|
||||||
|
|
||||||
|
IepeMode iepe =
|
||||||
|
inchannel_config.at(ch).IEPEEnabled ? IEPE_ENABLED : IEPE_DISABLED;
|
||||||
|
err = ulAISetConfig(_handle, AI_CFG_CHAN_IEPE_MODE, ch, iepe);
|
||||||
|
if (err != ERR_NO_ERROR) {
|
||||||
|
showErr(err);
|
||||||
|
throw rte("Fatal: could not set IEPE mode");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DT9837A::isRunning() const {
|
||||||
|
DEBUGTRACE_ENTER;
|
||||||
|
return _thread.joinable();
|
||||||
|
}
|
||||||
|
void DT9837A::stop() {
|
||||||
|
DEBUGTRACE_ENTER;
|
||||||
|
StreamStatus status = _streamStatus;
|
||||||
|
if (!isRunning()) {
|
||||||
|
throw rte("No data acquisition running");
|
||||||
|
}
|
||||||
|
|
||||||
|
_stopThread = true;
|
||||||
|
if (_thread.joinable()) {
|
||||||
|
_thread.join();
|
||||||
|
}
|
||||||
|
_stopThread = false;
|
||||||
|
status.isRunning = false;
|
||||||
|
_streamStatus = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Throws an appropriate stream exception based on the UlError number.
|
||||||
|
* The mapping is based on the error numbers as given in uldaq.h. There are a
|
||||||
|
* log of errors definded here (109 in total). Except for some, we will map
|
||||||
|
* most of them to a driver error.
|
||||||
|
*
|
||||||
|
* @param e
|
||||||
|
*/
|
||||||
|
inline void throwUlException(UlError err) {
|
||||||
|
if (err == ERR_NO_ERROR) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
string errstr = getErrMsg(err);
|
||||||
|
showErr(errstr);
|
||||||
|
Daq::StreamStatus::StreamError serr;
|
||||||
|
if ((int)err == 18) {
|
||||||
|
serr = Daq::StreamStatus::StreamError::inputXRun;
|
||||||
|
} else if ((int)err == 19) {
|
||||||
|
serr = Daq::StreamStatus::StreamError::outputXRun;
|
||||||
|
} else {
|
||||||
|
serr = Daq::StreamStatus::StreamError::driverError;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw Daq::StreamException(serr, errstr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DT9837A::start(InDaqCallback inCallback, OutDaqCallback outCallback) {
|
||||||
|
DEBUGTRACE_ENTER;
|
||||||
|
if (isRunning()) {
|
||||||
|
throw rte("DAQ is already running");
|
||||||
|
}
|
||||||
|
if (neninchannels() > 0) {
|
||||||
|
if (!inCallback)
|
||||||
|
throw rte("DAQ requires a callback for input data");
|
||||||
|
}
|
||||||
|
if (nenoutchannels() > 0) {
|
||||||
|
if (!outCallback)
|
||||||
|
throw rte("DAQ requires a callback for output data");
|
||||||
|
}
|
||||||
|
assert(neninchannels() + nenoutchannels() > 0);
|
||||||
|
_thread = std::thread(&DT9837A::threadFcn, this, inCallback, outCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
InBufHandler::InBufHandler(DT9837A &daq, InDaqCallback cb)
|
||||||
|
: BufHandler(daq, daq.neninchannels()), cb(cb)
|
||||||
|
|
||||||
|
{
|
||||||
|
DEBUGTRACE_ENTER;
|
||||||
|
assert(daq.getHandle() != 0);
|
||||||
|
|
||||||
|
monitorOutput = daq.monitorOutput;
|
||||||
|
|
||||||
|
DaqInScanFlag inscanflags = DAQINSCAN_FF_DEFAULT;
|
||||||
|
ScanOption scanoptions = SO_CONTINUOUS;
|
||||||
|
UlError err = ERR_NO_ERROR;
|
||||||
|
|
||||||
|
std::vector<DaqInChanDescriptor> indescs;
|
||||||
|
boolvec eninchannels_without_mon = daq.eninchannels(false);
|
||||||
|
|
||||||
|
// Set ranges for each input. Below asks only channels that are not a
|
||||||
|
// monitor channel (hence the false flag).
|
||||||
|
dvec ranges = daq.inputRangeForEnabledChannels(false);
|
||||||
|
for (us chin = 0; chin < 4; chin++) {
|
||||||
|
if (eninchannels_without_mon[chin] == true) {
|
||||||
|
DaqInChanDescriptor indesc;
|
||||||
|
indesc.type = DAQI_ANALOG_SE;
|
||||||
|
indesc.channel = chin;
|
||||||
|
|
||||||
|
double rangeval = ranges.at(chin);
|
||||||
|
Range rangenum;
|
||||||
|
if (fabs(rangeval - 1.0) < 1e-8) {
|
||||||
|
rangenum = BIP1VOLTS;
|
||||||
|
} else if (fabs(rangeval - 10.0) < 1e-8) {
|
||||||
|
rangenum = BIP10VOLTS;
|
||||||
|
} else {
|
||||||
|
throw Daq::StreamException(Daq::StreamStatus::StreamError::logicError);
|
||||||
|
std::cerr << "Fatal: input range value is invalid" << endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
indesc.range = rangenum;
|
||||||
|
indescs.push_back(indesc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add possibly last channel as monitor
|
||||||
|
if (monitorOutput) {
|
||||||
|
DaqInChanDescriptor indesc;
|
||||||
|
indesc.type = DAQI_DAC;
|
||||||
|
indesc.channel = 0;
|
||||||
|
/// The output only has a range of 10V, therefore the monitor of the
|
||||||
|
/// output also has to be set to this value.
|
||||||
|
indesc.range = BIP10VOLTS;
|
||||||
|
indescs.push_back(indesc);
|
||||||
|
}
|
||||||
|
assert(indescs.size() == nchannels);
|
||||||
|
|
||||||
|
DEBUGTRACE_MESSAGE("Starting input scan");
|
||||||
|
|
||||||
|
err = ulDaqInScan(daq.getHandle(), indescs.data(), nchannels,
|
||||||
|
2 * nFramesPerBlock, // Watch the 2 here!
|
||||||
|
&samplerate, scanoptions, inscanflags, buf.data());
|
||||||
|
throwUlException(err);
|
||||||
|
}
|
||||||
|
void InBufHandler::start() {
|
||||||
|
|
||||||
|
ScanStatus status;
|
||||||
|
TransferStatus transferStatus;
|
||||||
|
UlError err = ulDaqInScanStatus(daq.getHandle(), &status, &transferStatus);
|
||||||
|
throwUlException(err);
|
||||||
|
|
||||||
|
totalFramesCount = transferStatus.currentTotalCount;
|
||||||
|
topenqueued = true;
|
||||||
|
botenqueued = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InBufHandler::operator()() {
|
||||||
|
|
||||||
|
/* DEBUGTRACE_ENTER; */
|
||||||
|
|
||||||
|
bool ret = true;
|
||||||
|
|
||||||
|
auto runCallback = ([&](us totalOffset) {
|
||||||
|
/* DEBUGTRACE_ENTER; */
|
||||||
|
|
||||||
|
DaqData data(nFramesPerBlock, nchannels, dtype_descr.dtype);
|
||||||
|
|
||||||
|
us monitorOffset = monitorOutput ? 1 : 0;
|
||||||
|
/* /// Put the output monitor in front */
|
||||||
|
if (monitorOutput) {
|
||||||
|
for (us frame = 0; frame < nFramesPerBlock; frame++) {
|
||||||
|
data.value<double>(frame, 0) =
|
||||||
|
buf[totalOffset + (frame * nchannels) + (nchannels - 1)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (us channel = 0; channel < nchannels - monitorOffset; channel++) {
|
||||||
|
/* DEBUGTRACE_PRINT(channel); */
|
||||||
|
for (us frame = 0; frame < nFramesPerBlock; frame++) {
|
||||||
|
data.value<double>(frame, channel + monitorOffset) =
|
||||||
|
buf[totalOffset + (frame * nchannels) + channel];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cb(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
ScanStatus status;
|
||||||
|
TransferStatus transferStatus;
|
||||||
|
|
||||||
|
UlError err = ulDaqInScanStatus(daq.getHandle(), &status, &transferStatus);
|
||||||
|
throwUlException(err);
|
||||||
|
|
||||||
|
us increment = transferStatus.currentTotalCount - totalFramesCount;
|
||||||
|
totalFramesCount += increment;
|
||||||
|
|
||||||
|
if (increment > nFramesPerBlock) {
|
||||||
|
throw Daq::StreamException(Daq::StreamStatus::StreamError::inputXRun);
|
||||||
|
}
|
||||||
|
assert(status == SS_RUNNING);
|
||||||
|
|
||||||
|
if (transferStatus.currentIndex < (long long)buffer_mid_idx) {
|
||||||
|
topenqueued = false;
|
||||||
|
if (!botenqueued) {
|
||||||
|
ret = runCallback(nchannels * nFramesPerBlock);
|
||||||
|
botenqueued = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
botenqueued = false;
|
||||||
|
if (!topenqueued) {
|
||||||
|
ret = runCallback(0);
|
||||||
|
topenqueued = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
InBufHandler::~InBufHandler() {
|
||||||
|
// At exit of the function, stop scanning.
|
||||||
|
DEBUGTRACE_ENTER;
|
||||||
|
UlError err = ulDaqInScanStop(daq.getHandle());
|
||||||
|
if (err != ERR_NO_ERROR) {
|
||||||
|
showErr(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OutBufHandler::OutBufHandler(DT9837A &daq, OutDaqCallback cb)
|
||||||
|
: BufHandler(daq, daq.nenoutchannels()), cb(cb) {
|
||||||
|
|
||||||
|
DEBUGTRACE_MESSAGE("Starting output scan");
|
||||||
|
DEBUGTRACE_PRINT(nchannels);
|
||||||
|
AOutScanFlag outscanflags = AOUTSCAN_FF_DEFAULT;
|
||||||
|
ScanOption scanoptions = SO_CONTINUOUS;
|
||||||
|
UlError err = ulAOutScan(daq.getHandle(), 0, 0, BIP10VOLTS,
|
||||||
|
2 * nFramesPerBlock, // Watch the 2 here!
|
||||||
|
&samplerate, scanoptions, outscanflags, buf.data());
|
||||||
|
|
||||||
|
throwUlException(err);
|
||||||
|
}
|
||||||
|
void OutBufHandler::start() {
|
||||||
|
|
||||||
|
ScanStatus status;
|
||||||
|
TransferStatus transferStatus;
|
||||||
|
|
||||||
|
UlError err = ulAOutScanStatus(daq.getHandle(), &status, &transferStatus);
|
||||||
|
if (err != ERR_NO_ERROR) {
|
||||||
|
showErr(err);
|
||||||
|
throw rte("Unable to start output on DAQ");
|
||||||
|
}
|
||||||
|
if (status != SS_RUNNING) {
|
||||||
|
throw rte("Unable to start output on DAQ");
|
||||||
|
}
|
||||||
|
totalFramesCount = transferStatus.currentTotalCount;
|
||||||
|
topenqueued = true;
|
||||||
|
botenqueued = true;
|
||||||
|
}
|
||||||
|
bool OutBufHandler::operator()() {
|
||||||
|
|
||||||
|
/* DEBUGTRACE_ENTER; */
|
||||||
|
bool res = true;
|
||||||
|
assert(daq.getHandle() != 0);
|
||||||
|
|
||||||
|
UlError err = ERR_NO_ERROR;
|
||||||
|
|
||||||
|
ScanStatus status;
|
||||||
|
TransferStatus transferStatus;
|
||||||
|
|
||||||
|
err = ulAOutScanStatus(daq.getHandle(), &status, &transferStatus);
|
||||||
|
throwUlException(err);
|
||||||
|
if (status != SS_RUNNING) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
us increment = transferStatus.currentTotalCount - totalFramesCount;
|
||||||
|
totalFramesCount += increment;
|
||||||
|
|
||||||
|
if (increment > nFramesPerBlock) {
|
||||||
|
throw Daq::StreamException(Daq::StreamStatus::StreamError::outputXRun);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (transferStatus.currentIndex < buffer_mid_idx) {
|
||||||
|
topenqueued = false;
|
||||||
|
if (!botenqueued) {
|
||||||
|
DaqData d(nFramesPerBlock, 1, dtype_descr.dtype);
|
||||||
|
res = cb(d);
|
||||||
|
d.copyToRaw(0, reinterpret_cast<byte_t *>(&(buf[buffer_mid_idx])));
|
||||||
|
|
||||||
|
botenqueued = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
botenqueued = false;
|
||||||
|
if (!topenqueued) {
|
||||||
|
DaqData d(nFramesPerBlock, 1, dtype_descr.dtype);
|
||||||
|
res = cb(d);
|
||||||
|
d.copyToRaw(0, reinterpret_cast<byte_t *>(&(buf[0])));
|
||||||
|
|
||||||
|
topenqueued = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
OutBufHandler::~OutBufHandler() {
|
||||||
|
DEBUGTRACE_ENTER;
|
||||||
|
UlError err = ulAOutScanStop(daq.getHandle());
|
||||||
|
if (err != ERR_NO_ERROR) {
|
||||||
|
showErr(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DT9837A::threadFcn(InDaqCallback inCallback, OutDaqCallback outCallback) {
|
||||||
|
|
||||||
|
DEBUGTRACE_ENTER;
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
std::unique_ptr<OutBufHandler> obh;
|
||||||
|
std::unique_ptr<InBufHandler> ibh;
|
||||||
|
|
||||||
|
StreamStatus status = _streamStatus;
|
||||||
|
status.isRunning = true;
|
||||||
|
_streamStatus = status;
|
||||||
|
|
||||||
|
if (nenoutchannels() > 0) {
|
||||||
|
assert(outCallback);
|
||||||
|
obh = std::make_unique<OutBufHandler>(*this, outCallback);
|
||||||
|
}
|
||||||
|
if (neninchannels() > 0) {
|
||||||
|
assert(inCallback);
|
||||||
|
ibh = std::make_unique<InBufHandler>(*this, inCallback);
|
||||||
|
}
|
||||||
|
if (obh)
|
||||||
|
obh->start();
|
||||||
|
if (ibh)
|
||||||
|
ibh->start();
|
||||||
|
|
||||||
|
const double sleeptime_s =
|
||||||
|
static_cast<double>(_nFramesPerBlock) / (16 * samplerate());
|
||||||
|
const us sleeptime_us = static_cast<us>(sleeptime_s * 1e6);
|
||||||
|
|
||||||
|
while (!_stopThread) {
|
||||||
|
if (ibh) {
|
||||||
|
if (!(*ibh)()) {
|
||||||
|
_stopThread = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (obh) {
|
||||||
|
if (!(*obh)()) {
|
||||||
|
_stopThread = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
std::this_thread::sleep_for(std::chrono::microseconds(sleeptime_us));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update stream status that we are not running anymore
|
||||||
|
status.isRunning = false;
|
||||||
|
_streamStatus = status;
|
||||||
|
_stopThread = false;
|
||||||
|
|
||||||
|
} catch (StreamException &e) {
|
||||||
|
|
||||||
|
StreamStatus status = _streamStatus;
|
||||||
|
// Copy over error type
|
||||||
|
status.errorType = e.e;
|
||||||
|
_streamStatus = status;
|
||||||
|
|
||||||
|
/*
|
||||||
|
cerr << "\n******************\n";
|
||||||
|
cerr << "Catched error in UlDAQ thread: " << e.what() << endl;
|
||||||
|
cerr << "\n******************\n";
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // LASP_HAS_ULDAQ
|
@ -1,20 +1,75 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include "lasp_daq.h"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cassert>
|
||||||
|
#include <chrono>
|
||||||
|
#include <iostream>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <thread>
|
||||||
#include <uldaq.h>
|
#include <uldaq.h>
|
||||||
#include "lasp_types.h"
|
#include <vector>
|
||||||
#include "lasp_uldaq_impl.h"
|
|
||||||
#include "lasp_uldaq_common.h"
|
|
||||||
|
|
||||||
|
using std::atomic;
|
||||||
|
using std::cerr;
|
||||||
|
using std::endl;
|
||||||
|
using rte = std::runtime_error;
|
||||||
|
|
||||||
|
/**
|
||||||
/** \addtogroup device
|
* @brief UlDaq-specific device information. Adds a copy of the underlying
|
||||||
* @{
|
* DaqDeDaqDeviceDescriptor.
|
||||||
* \addtogroup uldaq
|
|
||||||
*/
|
*/
|
||||||
|
class UlDaqDeviceInfo : public DeviceInfo {
|
||||||
|
|
||||||
|
public:
|
||||||
|
DaqDeviceDescriptor _uldaqDescriptor;
|
||||||
|
virtual std::unique_ptr<DeviceInfo> clone() const {
|
||||||
|
return std::make_unique<UlDaqDeviceInfo>(*this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class DT9837A : public Daq {
|
||||||
|
|
||||||
|
DaqDeviceHandle _handle = 0;
|
||||||
|
std::mutex _daqmutex;
|
||||||
|
|
||||||
|
std::thread _thread;
|
||||||
|
atomic<bool> _stopThread{false};
|
||||||
|
atomic<StreamStatus> _streamStatus;
|
||||||
|
|
||||||
|
const us _nFramesPerBlock;
|
||||||
|
|
||||||
|
void threadFcn(InDaqCallback inCallback, OutDaqCallback outcallback);
|
||||||
|
|
||||||
|
public:
|
||||||
|
DaqDeviceHandle getHandle() const { return _handle; }
|
||||||
|
/**
|
||||||
|
* @brief Create a DT9837A instance.
|
||||||
|
*
|
||||||
|
* @param devinfo DeviceInfo to connect to
|
||||||
|
* @param config DaqConfiguration settings
|
||||||
|
*/
|
||||||
|
DT9837A(const DeviceInfo &devinfo, const DaqConfiguration &config);
|
||||||
|
|
||||||
|
virtual ~DT9837A();
|
||||||
|
|
||||||
|
bool isRunning() const;
|
||||||
|
|
||||||
|
void stop() override final;
|
||||||
|
|
||||||
|
friend class InBufHandler;
|
||||||
|
friend class OutBufHandler;
|
||||||
|
|
||||||
|
virtual void start(InDaqCallback inCallback,
|
||||||
|
OutDaqCallback outCallback) override final;
|
||||||
|
|
||||||
|
virtual StreamStatus getStreamStatus() const override {
|
||||||
|
return _streamStatus;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Helper class for managing input and output samples of the DAQ device.
|
* @brief Helper class for managing input and output samples of the DAQ device.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class DT9837A;
|
|
||||||
class BufHandler {
|
class BufHandler {
|
||||||
protected:
|
protected:
|
||||||
/**
|
/**
|
||||||
@ -33,19 +88,12 @@ protected:
|
|||||||
* @brief Sampling frequency in Hz
|
* @brief Sampling frequency in Hz
|
||||||
*/
|
*/
|
||||||
double samplerate;
|
double samplerate;
|
||||||
/**
|
|
||||||
* @brief Storage capacity for the DAQ I/O.
|
|
||||||
*/
|
|
||||||
std::vector<double> buf;
|
std::vector<double> buf;
|
||||||
/**
|
/**
|
||||||
* @brief Whether the top part of the buffer is enqueued
|
* @brief Whether the top / bottom part of the buffer are ready to be
|
||||||
*/
|
|
||||||
bool topenqueued = false;
|
|
||||||
/**
|
|
||||||
* @brief Whether the bottom part of the buffer is enqueued
|
|
||||||
* enqueued
|
* enqueued
|
||||||
*/
|
*/
|
||||||
bool botenqueued = false;
|
bool topenqueued, botenqueued;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Counter for the total number of frames acquired / sent since the
|
* @brief Counter for the total number of frames acquired / sent since the
|
||||||
@ -69,7 +117,6 @@ public:
|
|||||||
0),
|
0),
|
||||||
buffer_mid_idx(nchannels * nFramesPerBlock) {
|
buffer_mid_idx(nchannels * nFramesPerBlock) {
|
||||||
assert(nchannels > 0);
|
assert(nchannels > 0);
|
||||||
assert(nFramesPerBlock > 0);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
@ -105,5 +152,3 @@ public:
|
|||||||
|
|
||||||
~OutBufHandler();
|
~OutBufHandler();
|
||||||
};
|
};
|
||||||
/** @} */
|
|
||||||
/** @} */
|
|
@ -16,7 +16,6 @@ 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,9 +2,12 @@
|
|||||||
#include "lasp_avpowerspectra.h"
|
#include "lasp_avpowerspectra.h"
|
||||||
#include "debugtrace.hpp"
|
#include "debugtrace.hpp"
|
||||||
#include "lasp_mathtypes.h"
|
#include "lasp_mathtypes.h"
|
||||||
#include <stdexcept>
|
#include <cmath>
|
||||||
|
#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,6 +3,8 @@
|
|||||||
#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,6 +2,7 @@
|
|||||||
#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;
|
||||||
@ -43,21 +44,19 @@ SeriesBiquad::SeriesBiquad(const vd &filter_coefs) {
|
|||||||
|
|
||||||
SeriesBiquad SeriesBiquad::firstOrderHighPass(const d fs, const d cuton_Hz) {
|
SeriesBiquad SeriesBiquad::firstOrderHighPass(const d fs, const d cuton_Hz) {
|
||||||
|
|
||||||
if (fs <= 0) {
|
if(fs <= 0) {
|
||||||
throw rte("Invalid sampling frequency: " + std::to_string(fs) + " [Hz]");
|
throw rte("Invalid sampling frequency: " + std::to_string(fs) + " [Hz]");
|
||||||
}
|
}
|
||||||
if (cuton_Hz <= 0) {
|
if(cuton_Hz <= 0) {
|
||||||
throw rte("Invalid cuton frequency: " + std::to_string(cuton_Hz) + " [Hz]");
|
throw rte("Invalid cuton frequency: " + std::to_string(cuton_Hz) + " [Hz]");
|
||||||
}
|
}
|
||||||
if (cuton_Hz >= 0.98 * fs / 2) {
|
if(cuton_Hz >= 0.98*fs/2) {
|
||||||
throw rte(
|
throw rte("Invalid cuton frequency. We limit this to 0.98* fs / 2. Given value" + std::to_string(cuton_Hz) + " [Hz]");
|
||||||
"Invalid cuton frequency. We limit this to 0.98* fs / 2. Given value" +
|
|
||||||
std::to_string(cuton_Hz) + " [Hz]");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const d tau = 1 / (2 * arma::datum::pi * cuton_Hz);
|
const d tau = 1/(2*arma::datum::pi*cuton_Hz);
|
||||||
const d facnum = 2 * fs * tau / (1 + 2 * fs * tau);
|
const d facnum = 2*fs*tau/(1+2*fs*tau);
|
||||||
const d facden = (1 - 2 * fs * tau) / (1 + 2 * fs * tau);
|
const d facden = (1-2*fs*tau)/(1+2*fs*tau);
|
||||||
|
|
||||||
vd coefs(6);
|
vd coefs(6);
|
||||||
// b0
|
// b0
|
||||||
@ -77,8 +76,10 @@ SeriesBiquad SeriesBiquad::firstOrderHighPass(const d fs, const d cuton_Hz) {
|
|||||||
coefs(5) = 0;
|
coefs(5) = 0;
|
||||||
|
|
||||||
return SeriesBiquad(coefs);
|
return SeriesBiquad(coefs);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::unique_ptr<Filter> SeriesBiquad::clone() const {
|
std::unique_ptr<Filter> SeriesBiquad::clone() const {
|
||||||
// sos.as_col() concatenates all columns, exactly what we want.
|
// sos.as_col() concatenates all columns, exactly what we want.
|
||||||
return std::make_unique<SeriesBiquad>(sos.as_col());
|
return std::make_unique<SeriesBiquad>(sos.as_col());
|
||||||
@ -123,6 +124,7 @@ BiquadBank::BiquadBank(const dmat &filters, const vd *gains) {
|
|||||||
* for use.
|
* for use.
|
||||||
*/
|
*/
|
||||||
lock lck(_mtx);
|
lock lck(_mtx);
|
||||||
|
getPool();
|
||||||
|
|
||||||
for (us i = 0; i < filters.n_cols; i++) {
|
for (us i = 0; i < filters.n_cols; i++) {
|
||||||
_filters.emplace_back(filters.col(i));
|
_filters.emplace_back(filters.col(i));
|
||||||
@ -151,9 +153,10 @@ void BiquadBank::filter(vd &inout) {
|
|||||||
std::vector<std::future<vd>> futs;
|
std::vector<std::future<vd>> futs;
|
||||||
|
|
||||||
#if 1
|
#if 1
|
||||||
|
auto &pool = getPool();
|
||||||
vd inout_cpy = inout;
|
vd inout_cpy = inout;
|
||||||
for (us i = 0; i < _filters.size(); i++) {
|
for (us i = 0; i < _filters.size(); i++) {
|
||||||
futs.emplace_back(_pool.submit(
|
futs.emplace_back(pool.submit(
|
||||||
[&](vd inout, us i) {
|
[&](vd inout, us i) {
|
||||||
_filters[i].filter(inout);
|
_filters[i].filter(inout);
|
||||||
return inout;
|
return inout;
|
@ -1,6 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "lasp_filter.h"
|
#include "lasp_filter.h"
|
||||||
#include "lasp_thread.h"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \addtogroup dsp
|
* \addtogroup dsp
|
||||||
@ -61,7 +60,6 @@ public:
|
|||||||
class BiquadBank : public Filter {
|
class BiquadBank : public Filter {
|
||||||
std::vector<SeriesBiquad> _filters;
|
std::vector<SeriesBiquad> _filters;
|
||||||
vd _gains;
|
vd _gains;
|
||||||
GlobalThreadPool _pool;
|
|
||||||
mutable std::mutex _mtx;
|
mutable std::mutex _mtx;
|
||||||
|
|
||||||
public:
|
public:
|
@ -1,8 +1,6 @@
|
|||||||
/* #define DEBUGTRACE_ENABLED */
|
/* #define DEBUGTRACE_ENABLED */
|
||||||
#include "debugtrace.hpp"
|
#include "debugtrace.hpp"
|
||||||
#include "lasp_clip.h"
|
#include "lasp_clip.h"
|
||||||
#include "lasp_daqdata.h"
|
|
||||||
#include "lasp_daq.h"
|
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
||||||
using std::cerr;
|
using std::cerr;
|
||||||
@ -11,14 +9,13 @@ using std::endl;
|
|||||||
using Lck = std::scoped_lock<std::mutex>;
|
using Lck = std::scoped_lock<std::mutex>;
|
||||||
using rte = std::runtime_error;
|
using rte = std::runtime_error;
|
||||||
|
|
||||||
ClipHandler::ClipHandler(SmgrHandle mgr)
|
ClipHandler::ClipHandler(StreamMgr &mgr)
|
||||||
: ThreadedInDataHandler(mgr){
|
: ThreadedInDataHandler(mgr){
|
||||||
|
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
startThread();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClipHandler::inCallback(const DaqData &d) {
|
bool ClipHandler::inCallback_threaded(const DaqData &d) {
|
||||||
|
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
Lck lck(_mtx);
|
Lck lck(_mtx);
|
||||||
@ -52,6 +49,7 @@ void ClipHandler::inCallback(const DaqData &d) {
|
|||||||
_clip_time(i) += _dt;
|
_clip_time(i) += _dt;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
arma::uvec ClipHandler::getCurrentValue() const {
|
arma::uvec ClipHandler::getCurrentValue() const {
|
||||||
@ -91,5 +89,6 @@ void ClipHandler::reset(const Daq *daq) {
|
|||||||
|
|
||||||
ClipHandler::~ClipHandler() {
|
ClipHandler::~ClipHandler() {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
stopThread();
|
Lck lck(_mtx);
|
||||||
|
stop();
|
||||||
}
|
}
|
@ -21,7 +21,7 @@
|
|||||||
/**
|
/**
|
||||||
* @brief Clipping detector (Clip). Detects when a signal overdrives the input
|
* @brief Clipping detector (Clip). Detects when a signal overdrives the input
|
||||||
* */
|
* */
|
||||||
class ClipHandler: public ThreadedInDataHandler<ClipHandler> {
|
class ClipHandler: public ThreadedInDataHandler {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Assuming full scale of a signal is +/- 1.0. If a value is found
|
* @brief Assuming full scale of a signal is +/- 1.0. If a value is found
|
||||||
@ -58,7 +58,7 @@ class ClipHandler: public ThreadedInDataHandler<ClipHandler> {
|
|||||||
*
|
*
|
||||||
* @param mgr Stream Mgr to operate on
|
* @param mgr Stream Mgr to operate on
|
||||||
*/
|
*/
|
||||||
ClipHandler(SmgrHandle mgr);
|
ClipHandler(StreamMgr& mgr);
|
||||||
~ClipHandler();
|
~ClipHandler();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -68,8 +68,8 @@ class ClipHandler: public ThreadedInDataHandler<ClipHandler> {
|
|||||||
*/
|
*/
|
||||||
arma::uvec getCurrentValue() const;
|
arma::uvec getCurrentValue() const;
|
||||||
|
|
||||||
void inCallback(const DaqData& );
|
bool inCallback_threaded(const DaqData& ) override final;
|
||||||
void reset(const Daq*);
|
void reset(const Daq*) override final;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
@ -1,8 +1,6 @@
|
|||||||
/* #define DEBUGTRACE_ENABLED */
|
/* #define DEBUGTRACE_ENABLED */
|
||||||
#include "debugtrace.hpp"
|
#include "debugtrace.hpp"
|
||||||
#include "lasp_ppm.h"
|
#include "lasp_ppm.h"
|
||||||
#include "lasp_daqdata.h"
|
|
||||||
#include "lasp_daq.h"
|
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
||||||
using std::cerr;
|
using std::cerr;
|
||||||
@ -11,20 +9,20 @@ using std::endl;
|
|||||||
using Lck = std::scoped_lock<std::mutex>;
|
using Lck = std::scoped_lock<std::mutex>;
|
||||||
using rte = std::runtime_error;
|
using rte = std::runtime_error;
|
||||||
|
|
||||||
PPMHandler::PPMHandler(SmgrHandle mgr, const d decay_dBps)
|
PPMHandler::PPMHandler(StreamMgr &mgr, const d decay_dBps)
|
||||||
: ThreadedInDataHandler<PPMHandler>(mgr), _decay_dBps(decay_dBps) {
|
: ThreadedInDataHandler(mgr), _decay_dBps(decay_dBps) {
|
||||||
|
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
startThread();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PPMHandler::inCallback(const DaqData &d) {
|
bool PPMHandler::inCallback_threaded(const DaqData &d) {
|
||||||
|
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
Lck lck(_mtx);
|
Lck lck(_mtx);
|
||||||
|
|
||||||
dmat data = d.toFloat();
|
dmat data = d.toFloat();
|
||||||
|
|
||||||
|
|
||||||
const us nchannels = d.nchannels;
|
const us nchannels = d.nchannels;
|
||||||
assert(data.n_cols == nchannels);
|
assert(data.n_cols == nchannels);
|
||||||
|
|
||||||
@ -64,11 +62,12 @@ void PPMHandler::inCallback(const DaqData &d) {
|
|||||||
_cur_max(i) *= _alpha;
|
_cur_max(i) *= _alpha;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::tuple<vd, arma::uvec> PPMHandler::getCurrentValue() const {
|
std::tuple<vd, arma::uvec> PPMHandler::getCurrentValue() const {
|
||||||
|
|
||||||
/* DEBUGTRACE_ENTER; */
|
DEBUGTRACE_ENTER;
|
||||||
Lck lck(_mtx);
|
Lck lck(_mtx);
|
||||||
|
|
||||||
arma::uvec clips(_clip_time.size(), arma::fill::zeros);
|
arma::uvec clips(_clip_time.size(), arma::fill::zeros);
|
||||||
@ -84,11 +83,9 @@ void PPMHandler::reset(const Daq *daq) {
|
|||||||
|
|
||||||
if (daq) {
|
if (daq) {
|
||||||
|
|
||||||
DEBUGTRACE_PRINT("New daq found");
|
|
||||||
_cur_max.fill(1e-80);
|
_cur_max.fill(1e-80);
|
||||||
|
|
||||||
const us nchannels = daq->neninchannels();
|
const us nchannels = daq->neninchannels();
|
||||||
DEBUGTRACE_PRINT(nchannels);
|
|
||||||
_max_range.resize(nchannels);
|
_max_range.resize(nchannels);
|
||||||
|
|
||||||
dvec ranges = daq->inputRangeForEnabledChannels();
|
dvec ranges = daq->inputRangeForEnabledChannels();
|
||||||
@ -110,5 +107,6 @@ void PPMHandler::reset(const Daq *daq) {
|
|||||||
|
|
||||||
PPMHandler::~PPMHandler() {
|
PPMHandler::~PPMHandler() {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
stopThread();
|
Lck lck(_mtx);
|
||||||
|
stop();
|
||||||
}
|
}
|
@ -4,6 +4,7 @@
|
|||||||
//
|
//
|
||||||
// Description: Peak Programme Meter
|
// Description: Peak Programme Meter
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include <memory>
|
||||||
#include "lasp_filter.h"
|
#include "lasp_filter.h"
|
||||||
#include "lasp_mathtypes.h"
|
#include "lasp_mathtypes.h"
|
||||||
#include "lasp_threadedindatahandler.h"
|
#include "lasp_threadedindatahandler.h"
|
||||||
@ -22,7 +23,7 @@
|
|||||||
* with a certain amount of dB/s. If a new peak is found, it goes up again.
|
* with a certain amount of dB/s. If a new peak is found, it goes up again.
|
||||||
* Also detects clipping.
|
* Also detects clipping.
|
||||||
* */
|
* */
|
||||||
class PPMHandler : public ThreadedInDataHandler<PPMHandler> {
|
class PPMHandler: public ThreadedInDataHandler {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Assuming full scale of a signal is +/- 1.0. If a value is found
|
* @brief Assuming full scale of a signal is +/- 1.0. If a value is found
|
||||||
@ -68,11 +69,11 @@ class PPMHandler : public ThreadedInDataHandler<PPMHandler> {
|
|||||||
/**
|
/**
|
||||||
* @brief Constructs Peak Programme Meter
|
* @brief Constructs Peak Programme Meter
|
||||||
*
|
*
|
||||||
* @param mgr Stream Mgr to install callbacks for
|
* @param mgr Stream Mgr to operate on
|
||||||
* @param decay_dBps The level decay in units dB/s, after a peak has been
|
* @param decay_dBps The level decay in units dB/s, after a peak has been
|
||||||
* hit.
|
* hit.
|
||||||
*/
|
*/
|
||||||
PPMHandler(SmgrHandle mgr,const d decay_dBps = 20.0);
|
PPMHandler(StreamMgr& mgr,const d decay_dBps = 20.0);
|
||||||
~PPMHandler();
|
~PPMHandler();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -90,8 +91,8 @@ class PPMHandler : public ThreadedInDataHandler<PPMHandler> {
|
|||||||
*
|
*
|
||||||
* @return true when stream should continue.
|
* @return true when stream should continue.
|
||||||
*/
|
*/
|
||||||
void inCallback(const DaqData& d);
|
bool inCallback_threaded(const DaqData& d) override final;
|
||||||
void reset(const Daq*);
|
void reset(const Daq*) override final;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
@ -1,7 +1,5 @@
|
|||||||
/* #define DEBUGTRACE_ENABLED */
|
/* #define DEBUGTRACE_ENABLED */
|
||||||
#include "lasp_rtaps.h"
|
#include "lasp_rtaps.h"
|
||||||
#include "lasp_daqdata.h"
|
|
||||||
#include "lasp_daq.h"
|
|
||||||
#include "debugtrace.hpp"
|
#include "debugtrace.hpp"
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
||||||
@ -9,7 +7,7 @@ using std::cerr;
|
|||||||
using std::endl;
|
using std::endl;
|
||||||
using Lck = std::scoped_lock<std::mutex>;
|
using Lck = std::scoped_lock<std::mutex>;
|
||||||
|
|
||||||
RtAps::RtAps(SmgrHandle mgr, const Filter *freqWeightingFilter,
|
RtAps::RtAps(StreamMgr &mgr, const Filter *freqWeightingFilter,
|
||||||
const us nfft,
|
const us nfft,
|
||||||
const Window::WindowType w,
|
const Window::WindowType w,
|
||||||
const d overlap_percentage, const d time_constant)
|
const d overlap_percentage, const d time_constant)
|
||||||
@ -20,12 +18,12 @@ RtAps::RtAps(SmgrHandle mgr, const Filter *freqWeightingFilter,
|
|||||||
_filterPrototype = freqWeightingFilter->clone();
|
_filterPrototype = freqWeightingFilter->clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
startThread();
|
|
||||||
}
|
}
|
||||||
RtAps::~RtAps() {
|
RtAps::~RtAps() {
|
||||||
stopThread();
|
Lck lck(_ps_mtx);
|
||||||
|
stop();
|
||||||
}
|
}
|
||||||
void RtAps::inCallback(const DaqData &data) {
|
bool RtAps::inCallback_threaded(const DaqData &data) {
|
||||||
|
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
|
|
||||||
@ -35,9 +33,9 @@ void RtAps::inCallback(const DaqData &data) {
|
|||||||
const us nchannels = fltdata.n_cols;
|
const us nchannels = fltdata.n_cols;
|
||||||
if(nchannels != _sens.size()) {
|
if(nchannels != _sens.size()) {
|
||||||
cerr << "**** Error: sensitivity size does not match! *****" << endl;
|
cerr << "**** Error: sensitivity size does not match! *****" << endl;
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
fltdata.each_row() /= _sens.as_row();
|
fltdata.each_row() %= _sens.as_row();
|
||||||
|
|
||||||
if (_filterPrototype) {
|
if (_filterPrototype) {
|
||||||
|
|
||||||
@ -63,6 +61,7 @@ void RtAps::inCallback(const DaqData &data) {
|
|||||||
|
|
||||||
_ps.compute(fltdata);
|
_ps.compute(fltdata);
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
void RtAps::reset(const Daq *daq) {
|
void RtAps::reset(const Daq *daq) {
|
||||||
|
|
@ -23,7 +23,7 @@
|
|||||||
* @brief Real time spectral estimator using Welch method of spectral
|
* @brief Real time spectral estimator using Welch method of spectral
|
||||||
* estimation.
|
* estimation.
|
||||||
*/
|
*/
|
||||||
class RtAps : public ThreadedInDataHandler<RtAps> {
|
class RtAps : public ThreadedInDataHandler {
|
||||||
|
|
||||||
std::unique_ptr<Filter> _filterPrototype;
|
std::unique_ptr<Filter> _filterPrototype;
|
||||||
std::vector<std::unique_ptr<Filter>> _freqWeightingFilters;
|
std::vector<std::unique_ptr<Filter>> _freqWeightingFilters;
|
||||||
@ -49,7 +49,7 @@ public:
|
|||||||
*
|
*
|
||||||
* For all other arguments, see constructor of AvPowerSpectra
|
* For all other arguments, see constructor of AvPowerSpectra
|
||||||
*/
|
*/
|
||||||
RtAps(SmgrHandle mgr, const Filter *freqWeightingFilter, const us nfft = 2048,
|
RtAps(StreamMgr &mgr, const Filter *freqWeightingFilter, const us nfft = 2048,
|
||||||
const Window::WindowType w = Window::WindowType::Hann,
|
const Window::WindowType w = Window::WindowType::Hann,
|
||||||
const d overlap_percentage = 50., const d time_constant = -1);
|
const d overlap_percentage = 50., const d time_constant = -1);
|
||||||
~RtAps();
|
~RtAps();
|
||||||
@ -69,8 +69,8 @@ public:
|
|||||||
*
|
*
|
||||||
* @return true if stream should continue.
|
* @return true if stream should continue.
|
||||||
*/
|
*/
|
||||||
void inCallback(const DaqData & d);
|
bool inCallback_threaded(const DaqData & d) override final;
|
||||||
void reset(const Daq *);
|
void reset(const Daq *) override final;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @} */
|
/** @} */
|
@ -1,7 +1,5 @@
|
|||||||
/* #define DEBUGTRACE_ENABLED */
|
/* #define DEBUGTRACE_ENABLED */
|
||||||
#include "debugtrace.hpp"
|
#include "debugtrace.hpp"
|
||||||
#include "lasp_daqdata.h"
|
|
||||||
#include "lasp_daq.h"
|
|
||||||
#include "lasp_rtsignalviewer.h"
|
#include "lasp_rtsignalviewer.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
@ -11,7 +9,7 @@ using std::endl;
|
|||||||
using Lck = std::scoped_lock<std::mutex>;
|
using Lck = std::scoped_lock<std::mutex>;
|
||||||
using rte = std::runtime_error;
|
using rte = std::runtime_error;
|
||||||
|
|
||||||
RtSignalViewer::RtSignalViewer(SmgrHandle mgr, const d approx_time_hist,
|
RtSignalViewer::RtSignalViewer(StreamMgr &mgr, const d approx_time_hist,
|
||||||
const us resolution, const us channel)
|
const us resolution, const us channel)
|
||||||
: ThreadedInDataHandler(mgr), _approx_time_hist(approx_time_hist),
|
: ThreadedInDataHandler(mgr), _approx_time_hist(approx_time_hist),
|
||||||
_resolution(resolution), _channel(channel) {
|
_resolution(resolution), _channel(channel) {
|
||||||
@ -24,10 +22,9 @@ RtSignalViewer::RtSignalViewer(SmgrHandle mgr, const d approx_time_hist,
|
|||||||
if (resolution <= 1) {
|
if (resolution <= 1) {
|
||||||
throw rte("Invalid resolution. Should be > 1");
|
throw rte("Invalid resolution. Should be > 1");
|
||||||
}
|
}
|
||||||
startThread();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RtSignalViewer::inCallback(const DaqData &data) {
|
bool RtSignalViewer::inCallback_threaded(const DaqData &data) {
|
||||||
|
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
|
|
||||||
@ -52,10 +49,13 @@ void RtSignalViewer::inCallback(const DaqData &data) {
|
|||||||
_dat(_resolution-1, 1) = newmin;
|
_dat(_resolution-1, 1) = newmin;
|
||||||
_dat(_resolution-1, 2) = newmax;
|
_dat(_resolution-1, 2) = newmax;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
RtSignalViewer::~RtSignalViewer() {
|
RtSignalViewer::~RtSignalViewer() {
|
||||||
stopThread();
|
Lck lck(_sv_mtx);
|
||||||
|
stop();
|
||||||
}
|
}
|
||||||
void RtSignalViewer::reset(const Daq *daq) {
|
void RtSignalViewer::reset(const Daq *daq) {
|
||||||
|
|
@ -4,9 +4,12 @@
|
|||||||
//
|
//
|
||||||
// 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>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -21,7 +24,7 @@
|
|||||||
* @brief Real time signal viewer. Shows envelope of the signal based on amount
|
* @brief Real time signal viewer. Shows envelope of the signal based on amount
|
||||||
* of history shown.
|
* of history shown.
|
||||||
*/
|
*/
|
||||||
class RtSignalViewer : public ThreadedInDataHandler<RtSignalViewer> {
|
class RtSignalViewer : public ThreadedInDataHandler {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Storage for sensitivity values
|
* @brief Storage for sensitivity values
|
||||||
@ -68,7 +71,7 @@ public:
|
|||||||
* @param resolution Number of time points
|
* @param resolution Number of time points
|
||||||
* @param channel The channel number
|
* @param channel The channel number
|
||||||
*/
|
*/
|
||||||
RtSignalViewer(SmgrHandle mgr, const d approx_time_hist, const us resolution,
|
RtSignalViewer(StreamMgr &mgr, const d approx_time_hist, const us resolution,
|
||||||
const us channel);
|
const us channel);
|
||||||
|
|
||||||
~RtSignalViewer();
|
~RtSignalViewer();
|
||||||
@ -82,8 +85,8 @@ public:
|
|||||||
*/
|
*/
|
||||||
dmat getCurrentValue() const;
|
dmat getCurrentValue() const;
|
||||||
|
|
||||||
void inCallback(const DaqData &);
|
bool inCallback_threaded(const DaqData &) override final;
|
||||||
void reset(const Daq *);
|
void reset(const Daq *) override final;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @} */
|
/** @} */
|
@ -10,62 +10,31 @@ 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 slock = std::scoped_lock<std::recursive_mutex>;
|
using mutexlock = std::scoped_lock<std::mutex>;
|
||||||
|
|
||||||
vd Siggen::genSignal(const us nframes) {
|
vd Siggen::genSignal(const us nframes) {
|
||||||
|
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
slock lck(_mtx);
|
mutexlock 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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;
|
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;
|
||||||
slock lck(_mtx);
|
mutexlock lck(_mtx);
|
||||||
if (filter) {
|
if (filter) {
|
||||||
_filters[name] = filter;
|
_filters[name] = filter;
|
||||||
} else if (_filters.find(name) != _filters.end()) {
|
} else if (_filters.find(name) != _filters.end()) {
|
||||||
@ -74,22 +43,21 @@ void Siggen::setFilter(const std::string &name,
|
|||||||
}
|
}
|
||||||
void Siggen::setDCOffset(const d offset) {
|
void Siggen::setDCOffset(const d offset) {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
slock lck(_mtx);
|
mutexlock 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;
|
||||||
slock lck(_mtx);
|
mutexlock 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;
|
||||||
slock lck(_mtx);
|
mutexlock 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,15 +27,8 @@ private:
|
|||||||
bool _muted = false;
|
bool _muted = false;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
mutable std::recursive_mutex _mtx;
|
std::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;
|
||||||
@ -43,15 +36,6 @@ 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
|
||||||
@ -75,7 +59,7 @@ public:
|
|||||||
*
|
*
|
||||||
* @param mute if tre
|
* @param mute if tre
|
||||||
*/
|
*/
|
||||||
void setMute(bool mute = true) { _muted = mute; _interruption_frame_count=0; }
|
void setMute(bool mute = true) { _muted = mute; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Set the level of the signal generator
|
* @brief Set the level of the signal generator
|
@ -7,14 +7,11 @@
|
|||||||
//////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////
|
||||||
/* #define DEBUGTRACE_ENABLED */
|
/* #define DEBUGTRACE_ENABLED */
|
||||||
#include "lasp_siggen_impl.h"
|
#include "lasp_siggen_impl.h"
|
||||||
|
|
||||||
#include <cassert>
|
|
||||||
|
|
||||||
#include "debugtrace.hpp"
|
#include "debugtrace.hpp"
|
||||||
#include "lasp_mathtypes.h"
|
#include "lasp_mathtypes.h"
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
using rte = std::runtime_error;
|
using rte = std::runtime_error;
|
||||||
using slock = std::scoped_lock<std::recursive_mutex>;
|
|
||||||
|
|
||||||
DEBUGTRACE_VARIABLES;
|
DEBUGTRACE_VARIABLES;
|
||||||
|
|
||||||
@ -33,7 +30,6 @@ 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);
|
||||||
@ -45,8 +41,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");
|
||||||
}
|
}
|
||||||
@ -78,8 +74,8 @@ Sweep::Sweep(const d fl, const d fu, const d Ts, const d Tq, const us flags)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Sweep::resetImpl() {
|
void Sweep::resetImpl() {
|
||||||
|
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
slock lck(_mtx);
|
|
||||||
|
|
||||||
_cur_pos = 0;
|
_cur_pos = 0;
|
||||||
|
|
||||||
@ -170,6 +166,7 @@ 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 */
|
||||||
@ -197,6 +194,7 @@ 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;
|
||||||
@ -251,7 +249,8 @@ 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) phase -= 2 * number_pi;
|
while (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!! */
|
||||||
@ -261,5 +260,6 @@ void Sweep::resetImpl() {
|
|||||||
else {
|
else {
|
||||||
// Either log or linear sweep had to be given as flags.
|
// Either log or linear sweep had to be given as flags.
|
||||||
assert(false);
|
assert(false);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -18,8 +18,8 @@ class Noise : public Siggen {
|
|||||||
d level_linear;
|
d level_linear;
|
||||||
virtual vd genSignalUnscaled(const us nframes) override;
|
virtual vd genSignalUnscaled(const us nframes) override;
|
||||||
void resetImpl() override;
|
void resetImpl() override;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Constructs a noise generator. If no filter is used, the output will
|
* @brief Constructs a noise generator. If no filter is used, the output will
|
||||||
* be white noise. By default, the output will be standard deviation = 1
|
* be white noise. By default, the output will be standard deviation = 1
|
||||||
@ -28,6 +28,7 @@ class Noise : public Siggen {
|
|||||||
*/
|
*/
|
||||||
Noise();
|
Noise();
|
||||||
~Noise() = default;
|
~Noise() = default;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -36,12 +37,13 @@ class Noise : public Siggen {
|
|||||||
class Sine : public Siggen {
|
class Sine : public Siggen {
|
||||||
d phase = 0;
|
d phase = 0;
|
||||||
d omg;
|
d omg;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void resetImpl() override final { phase = 0; }
|
|
||||||
|
void resetImpl() override final { phase=0; }
|
||||||
virtual vd genSignalUnscaled(const us nframes) override final;
|
virtual vd genSignalUnscaled(const us nframes) override final;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Create a sine wave generator
|
* @brief Create a sine wave generator
|
||||||
*
|
*
|
||||||
@ -49,7 +51,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; } ;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -85,6 +87,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;
|
||||||
@ -100,11 +103,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, const us sweep_flags);
|
Sweep(const d fl, const d fu, const d Ts, const d Tq,
|
||||||
|
const us sweep_flags);
|
||||||
|
|
||||||
~Sweep() = default;
|
~Sweep() = default;
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
/** @} */
|
/** @} */
|
||||||
/** @} */
|
/** @} */
|
@ -37,6 +37,9 @@ SLM::SLM(const d fs, const d Lref, const us downsampling_fac, const d tau,
|
|||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
DEBUGTRACE_PRINT(_alpha);
|
DEBUGTRACE_PRINT(_alpha);
|
||||||
|
|
||||||
|
// Make sure thread pool is running
|
||||||
|
getPool();
|
||||||
|
|
||||||
if (Lref <= 0) {
|
if (Lref <= 0) {
|
||||||
throw rte("Invalid reference level");
|
throw rte("Invalid reference level");
|
||||||
}
|
}
|
@ -1,7 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "lasp_biquadbank.h"
|
#include "lasp_biquadbank.h"
|
||||||
#include "lasp_filter.h"
|
#include "lasp_filter.h"
|
||||||
#include "lasp_thread.h"
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
@ -15,7 +14,6 @@
|
|||||||
* channel. A channel is the result of a filtered signal
|
* channel. A channel is the result of a filtered signal
|
||||||
*/
|
*/
|
||||||
class SLM {
|
class SLM {
|
||||||
GlobalThreadPool _pool;
|
|
||||||
/**
|
/**
|
||||||
* @brief A, C or Z weighting, depending on the pre-filter installed.
|
* @brief A, C or Z weighting, depending on the pre-filter installed.
|
||||||
*/
|
*/
|
25
src/lasp/dsp/lasp_thread.cpp
Normal file
25
src/lasp/dsp/lasp_thread.cpp
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/* #define DEBUGTRACE_ENABLED */
|
||||||
|
#include "lasp_thread.h"
|
||||||
|
#include "BS_thread_pool.hpp"
|
||||||
|
#include "debugtrace.hpp"
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief It seems to work much better in cooperation with Pybind11 when this
|
||||||
|
* singleton is implemented with a unique_ptr.
|
||||||
|
*/
|
||||||
|
std::unique_ptr<BS::thread_pool> _static_storage_threadpool;
|
||||||
|
|
||||||
|
void destroyThreadPool() {
|
||||||
|
DEBUGTRACE_ENTER;
|
||||||
|
_static_storage_threadpool = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
BS::thread_pool &getPool() {
|
||||||
|
/* DEBUGTRACE_ENTER; */
|
||||||
|
if (!_static_storage_threadpool) {
|
||||||
|
DEBUGTRACE_PRINT("Creating new thread pool");
|
||||||
|
_static_storage_threadpool = std::make_unique<BS::thread_pool>();
|
||||||
|
}
|
||||||
|
return *_static_storage_threadpool;
|
||||||
|
}
|
19
src/lasp/dsp/lasp_thread.h
Normal file
19
src/lasp/dsp/lasp_thread.h
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "BS_thread_pool.hpp"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Return reference to global (singleton) thread pool. The threadpool is
|
||||||
|
* created using the default argument, which results in exactly
|
||||||
|
* hardware_concurrency() amount of threads.
|
||||||
|
*
|
||||||
|
* @return Thread pool ref.
|
||||||
|
*/
|
||||||
|
BS::thread_pool& getPool();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The global thread pool is stored in a unique_ptr, so in normal C++
|
||||||
|
* code the thread pool is deleted at the end of main(). However this does not
|
||||||
|
* hold when LASP code is run
|
||||||
|
*/
|
||||||
|
void destroyThreadPool();
|
103
src/lasp/dsp/lasp_threadedindatahandler.cpp
Normal file
103
src/lasp/dsp/lasp_threadedindatahandler.cpp
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
/* #define DEBUGTRACE_ENABLED */
|
||||||
|
#include "lasp_threadedindatahandler.h"
|
||||||
|
#include "debugtrace.hpp"
|
||||||
|
#include "lasp_thread.h"
|
||||||
|
#include <future>
|
||||||
|
#include <thread>
|
||||||
|
#include <queue>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
using namespace std::literals::chrono_literals;
|
||||||
|
using lck = std::scoped_lock<std::mutex>;
|
||||||
|
using rte = std::runtime_error;
|
||||||
|
using std::cerr;
|
||||||
|
using std::endl;
|
||||||
|
|
||||||
|
class SafeQueue {
|
||||||
|
std::queue<DaqData> _queue;
|
||||||
|
std::mutex _mtx;
|
||||||
|
std::atomic_int32_t _contents {0};
|
||||||
|
public:
|
||||||
|
void push(const DaqData& d) {
|
||||||
|
DEBUGTRACE_ENTER;
|
||||||
|
lck lock(_mtx);
|
||||||
|
_queue.push(d);
|
||||||
|
_contents++;
|
||||||
|
}
|
||||||
|
DaqData pop() {
|
||||||
|
DEBUGTRACE_ENTER;
|
||||||
|
if (empty()) {
|
||||||
|
throw rte("BUG: Pop on empty queue");
|
||||||
|
}
|
||||||
|
lck lock(_mtx);
|
||||||
|
|
||||||
|
/* DaqData d(std::move(_queue.front())); */
|
||||||
|
DaqData d(_queue.front());
|
||||||
|
_queue.pop();
|
||||||
|
_contents--;
|
||||||
|
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @brief Empty implemented using atomic var, safes some mutex lock/unlock
|
||||||
|
* cycles.
|
||||||
|
*
|
||||||
|
* @return true if queue is empty
|
||||||
|
*/
|
||||||
|
bool empty() const { return _contents == 0; }
|
||||||
|
};
|
||||||
|
|
||||||
|
ThreadedInDataHandler::ThreadedInDataHandler(StreamMgr &mgr)
|
||||||
|
: InDataHandler(mgr), _queue(std::make_unique<SafeQueue>()) {
|
||||||
|
|
||||||
|
DEBUGTRACE_ENTER;
|
||||||
|
|
||||||
|
// Initialize thread pool, if not already done
|
||||||
|
getPool();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ThreadedInDataHandler::inCallback(const DaqData &daqdata) {
|
||||||
|
DEBUGTRACE_ENTER;
|
||||||
|
|
||||||
|
if (!_lastCallbackResult) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_queue->push(daqdata);
|
||||||
|
|
||||||
|
if (!_thread_running && (!_stopThread) && _lastCallbackResult) {
|
||||||
|
auto &pool = getPool();
|
||||||
|
DEBUGTRACE_PRINT("Pushing new thread in pool");
|
||||||
|
_thread_running = true;
|
||||||
|
pool.push_task(&ThreadedInDataHandler::threadFcn, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _lastCallbackResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
ThreadedInDataHandler::~ThreadedInDataHandler() {
|
||||||
|
|
||||||
|
DEBUGTRACE_ENTER;
|
||||||
|
_stopThread = true;
|
||||||
|
|
||||||
|
// Then wait in steps for the thread to stop running.
|
||||||
|
while (_thread_running) {
|
||||||
|
std::this_thread::sleep_for(10us);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ThreadedInDataHandler::threadFcn() {
|
||||||
|
|
||||||
|
DEBUGTRACE_ENTER;
|
||||||
|
|
||||||
|
while(!_queue->empty() && !_stopThread) {
|
||||||
|
|
||||||
|
// Call inCallback_threaded
|
||||||
|
if (!inCallback_threaded(_queue->pop())) {
|
||||||
|
cerr << "*********** Callback result returned false! *************"
|
||||||
|
<< endl;
|
||||||
|
_lastCallbackResult = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_thread_running = false;
|
||||||
|
}
|
64
src/lasp/dsp/lasp_threadedindatahandler.h
Normal file
64
src/lasp/dsp/lasp_threadedindatahandler.h
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "lasp_streammgr.h"
|
||||||
|
|
||||||
|
const us RINGBUFFER_SIZE = 1024;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \addtogroup dsp
|
||||||
|
* @{
|
||||||
|
*
|
||||||
|
* \defgroup rt Real time signal handlers
|
||||||
|
* @{
|
||||||
|
*/
|
||||||
|
|
||||||
|
class SafeQueue;
|
||||||
|
/**
|
||||||
|
* @brief Threaded in data handler. Buffers inCallback data and calls a
|
||||||
|
* callback with the same signature on a different thread.
|
||||||
|
*/
|
||||||
|
class ThreadedInDataHandler: public InDataHandler {
|
||||||
|
/**
|
||||||
|
* @brief The queue used to push elements to the handling thread.
|
||||||
|
*/
|
||||||
|
std::unique_ptr<SafeQueue> _queue;
|
||||||
|
|
||||||
|
std::atomic<bool> _thread_running{false};
|
||||||
|
std::atomic<bool> _stopThread{false};
|
||||||
|
std::atomic<bool> _lastCallbackResult{true};
|
||||||
|
|
||||||
|
void threadFcn();
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Initialize a ThreadedInDataHandler
|
||||||
|
*
|
||||||
|
* @param mgr StreamMgr singleton reference
|
||||||
|
*/
|
||||||
|
ThreadedInDataHandler(StreamMgr& mgr);
|
||||||
|
~ThreadedInDataHandler();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Pushes a copy of the daqdata to the thread queue and returns
|
||||||
|
*
|
||||||
|
* @param daqdata the daq info to push
|
||||||
|
*
|
||||||
|
* @return true, to continue with sampling.
|
||||||
|
*/
|
||||||
|
virtual bool inCallback(const DaqData &daqdata) override final;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief This function should be overridden with an actual implementation,
|
||||||
|
* of what should happen on a different thread.
|
||||||
|
*
|
||||||
|
* @param d Input daq data
|
||||||
|
*
|
||||||
|
* @return true on succes. False when an error occured.
|
||||||
|
*/
|
||||||
|
virtual bool inCallback_threaded(const DaqData& d) = 0;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/** @} */
|
||||||
|
/** @} */
|
@ -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<vd>(0, N - 1, N)
|
#define lin0N arma::linspace(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 * arma::cos((2 * number_pi/N) * lin0N) +
|
return a0 - a1 * d_cos((2 * number_pi/N) * lin0N) +
|
||||||
a2 * arma::cos((4 * number_pi / N)* lin0N );
|
a2 * d_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); }
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user