Compare commits

..

1 Commits

128 changed files with 2178 additions and 3099 deletions

110
.drone.yml Normal file
View 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

View File

@ -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}}'

13
.gitignore vendored
View File

@ -6,21 +6,22 @@
.ninja* .ninja*
build.ninja build.ninja
dist dist
src/lasp.egg-info
test/.ipynb_checkpoints
src/lasp/lasp_config.h
_deps _deps
compile_commands.json compile_commands.json
CMakeFiles CMakeFiles
CMakeCache.txt CMakeCache.txt
cmake_install.cmake cmake_install.cmake
Makefile Makefile
build
__pycache__ __pycache__
cython_debug
doc doc
.ropeproject .ropeproject
.ipynb_checkpoints .ipynb_checkpoints
.spyproject .spyproject
acme_log.log
.venv
.py-build-cmake_cache
cpp_src/lasp_config.h
.cache .cache
.vscode _skbuild
build acme_log.log

View File

@ -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

View File

@ -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)

View File

@ -1,36 +1,25 @@
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")
set(RPI TRUE)
else()
set(RPI FALSE)
endif()
# Setting defaults for PortAudio and RtAudio backend, depending on Linux / # Setting defaults for PortAudio and RtAudio backend, depending on Linux /
# Windows. # Windows.
set(DEFAULT_DOUBLE_PRECISION ON)
if(WIN32) if(WIN32)
set(DEFAULT_RTAUDIO OFF) set(DEFAULT_RTAUDIO OFF)
set(DEFAULT_PORTAUDIO ON) set(DEFAULT_PORTAUDIO ON)
set(DEFAULT_ULDAQ OFF) set(DEFAULT_ULDAQ OFF)
elseif(${RPI})
set(DEFAULT_RTAUDIO OFF)
set(DEFAULT_PORTAUDIO ON)
set(DEFAULT_ULDAQ OFF)
set(DEFAULT_DOUBLE_PRECISION OFF)
else() else()
set(DEFAULT_RTAUDIO OFF) set(DEFAULT_RTAUDIO ON)
set(DEFAULT_PORTAUDIO ON) set(DEFAULT_PORTAUDIO OFF)
set(DEFAULT_ULDAQ ON) set(DEFAULT_ULDAQ ON)
endif() endif()
option(LASP_DOUBLE_PRECISION "Compile as double precision floating point" ${DEFAULT_DOUBLE_PRECISION})
option(LASP_HAS_RTAUDIO "Compile with RtAudio Daq backend" ${DEFAULT_RTAUDIO}) option(LASP_HAS_RTAUDIO "Compile with RtAudio Daq backend" ${DEFAULT_RTAUDIO})
option(LASP_HAS_PORTAUDIO "Compile with PortAudio Daq backend" ${DEFAULT_PORTAUDIO}) option(LASP_HAS_PORTAUDIO "Compile with PortAudio Daq backend" ${DEFAULT_PORTAUDIO})
if(LASP_HAS_PORTAUDIO AND LASP_HAS_RTAUDIO) if(LASP_HAS_PORTAUDIO AND LASP_HAS_RTAUDIO)
@ -46,7 +35,7 @@ option(LASP_BUILD_CPP_TESTS "Build CPP test code" OFF)
find_program(CCACHE_PROGRAM ccache) find_program(CCACHE_PROGRAM ccache)
if(CCACHE_PROGRAM) if(CCACHE_PROGRAM)
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE_PROGRAM}") set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE_PROGRAM}")
set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK "${CCACHE_PROGRAM}") set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK "${CCACHE_PROGRAM}")
endif() endif()
# To allow linking to static libs from other directories # To allow linking to static libs from other directories
@ -56,6 +45,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")
@ -91,7 +81,7 @@ endif()
# Tune for current machine # Tune for current machine
if(LASP_BUILD_TUNED) if(LASP_BUILD_TUNED)
if(CMAKE_BUILD_TYPE STREQUAL "Debug") if(CMAKE_BUILD_TYPE STREQUAL "Debug")
message(FATAL_ERROR message(FATAL_ERROR
"Cannot build optimized and tuned code when debug is switched on") "Cannot build optimized and tuned code when debug is switched on")
endif() endif()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native -mtune=native") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native -mtune=native")
@ -116,29 +106,22 @@ 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 -Werror=return-type")
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -flto -mtune=native \
-fdata-sections -ffunction-sections -fomit-frame-pointer -finline-functions")
if(NOT ${RPI})
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -mfpmath=sse -march=x86-64")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -mfpmath=sse -march=x86-64")
endif()
set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g -Wall ")
# ############################# End compilation flags # ############################# End compilation flags
include_directories(/usr/lib/python3.10/site-packages/numpy/core/include)
# ####################################### End of user-adjustable variables section # ####################################### End of user-adjustable variables section
include(OSSpecific) include(OSSpecific)
include(rtaudio) include(rtaudio)
include(portaudio) include(portaudio)
include(uldaq) include(uldaq)
# #
add_definitions(-Dgsl_CONFIG_DEFAULTS_VERSION=1) add_definitions(-Dgsl_CONFIG_DEFAULTS_VERSION=1)
add_subdirectory(cpp_src) add_subdirectory(src/lasp)
if(LASP_BUILD_CPP_TESTS) if(LASP_BUILD_CPP_TESTS)
add_subdirectory(test) add_subdirectory(test)
endif() endif()

View File

@ -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

146
README.md
View File

@ -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
@ -44,114 +46,42 @@ in a sister repository [lasp-doc](https://code.ascee.nl/ascee/lasp-doc).
If you have any question(s), please feel free to contact us: [email](info@ascee.nl). If you have any question(s), please feel free to contact us: [email](info@ascee.nl).
# Installation - Linux (Debian-based)
# 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 +94,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
```

View File

@ -2,27 +2,13 @@
if(LASP_HAS_PORTAUDIO) if(LASP_HAS_PORTAUDIO)
message("Building with Portaudio backend") message("Building with Portaudio backend")
if(WIN32) if(WIN32)
set(PA_USE_ALSA FALSE CACHE BOOL "Build PortAudio with ALSA backend")
set(PA_USE_ASIO TRUE CACHE BOOL "Build PortAudio with ASIO backend")
set(PA_USE_DS FALSE CACHE BOOL "Build PortAudio with Directsound backend")
set(PA_USE_WMME FALSE CACHE BOOL "Build PortAudio with WMME backend")
set(PA_USE_WDMKS FALSE CACHE BOOL "Build PortAudio with WDMKS backend")
else() else()
# Unix
set(PA_USE_ALSA TRUE CACHE BOOL "Build PortAudio with ALSA backend") set(PA_USE_ALSA TRUE CACHE BOOL "Build PortAudio with ALSA backend")
set(PA_USE_JACK FALSE CACHE BOOL "Build PortAudio with Jack backend") set(PA_USE_JACK TRUE CACHE BOOL "Build PortAudio with Jack backend")
set(PA_USE_PULSEAUDIO FALSE CACHE BOOL "Build PortAudio with PulseAudio backend") # set(PA_ALSA_DYNAMIC FALSE CACHE BOOL "Build static library of ALSA")
set(PA_BUILD_SHARED_LIBS FALSE CACHE BOOL "Build static library") set(PA_BUILD_SHARED_LIBS FALSE CACHE BOOL "Build static library")
endif() endif()
add_subdirectory(third_party/portaudio) add_subdirectory(third_party/portaudio)
include_directories(third_party/portaudio/include) include_directories(third_party/portaudio/include)
link_directories(third_party/portaudio) link_directories(third_party/portaudio)
if(PA_USE_ALSA)
add_definitions(-DLASP_HAS_PA_ALSA=1)
else()
add_definitions(-DLASP_HAS_PA_ALSA=0)
endif()
endif() endif()

View File

@ -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()

View File

@ -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;
}

View File

@ -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);
/** @} */

View File

@ -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);
});
}

View File

@ -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"

View File

@ -1,2 +1,2 @@
[pytest] [pytest]
addopts = "--ignore=third_party --ignore=examples" addopts = "--ignore=third_party"

View File

@ -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}")

View File

@ -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

View File

@ -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)

View File

@ -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()

View File

@ -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(".")
]

3
requirements.txt Normal file
View File

@ -0,0 +1,3 @@
appdirs
dataclasses_json
matplotlib

View File

@ -1,3 +0,0 @@
#!/bin/bash
# Test push workflow of Gitea runner
act push --workflows .gitea/workflows

View File

@ -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

View File

@ -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

View File

@ -1,9 +1,11 @@
#!/bin/bash #!/bin/bash
# #
arch=mingw64 cmake . -G"Ninja" -DLASP_HAS_ULDAQ=OFF -DPython3_ROOT_DIR=C:\\winpython\\python-3.10.9.amd64
# DLL's that are required by lasp_cpp arch=ucrt64
#arch=mingw64
files_to_cpy="libfftw3-3.dll libgcc_s_seh-1.dll libgfortran-5.dll libgomp-1.dll libopenblas.dll libquadmath-0.dll libstdc++-6.dll libwinpthread-1.dll" files_to_cpy="libfftw3-3.dll libgcc_s_seh-1.dll libgfortran-5.dll libgomp-1.dll libopenblas.dll libquadmath-0.dll libstdc++-6.dll libwinpthread-1.dll"
for fn in ${files_to_cpy}; do for fn in ${files_to_cpy}; do
cp /c/msys64/${arch}/bin/${fn} python_src/lasp cp /c/msys64/${arch}/bin/${fn} src/lasp
done done

View File

@ -5,8 +5,8 @@
if [ -z $CI ]; then if [ -z $CI ]; then
PACMAN_OPTIONS="--needed --noconfirm" PACMAN_OPTIONS="--needed --noconfirm"
fi fi
arch=mingw-w64-x86_64 # arch=mingw-w64-x86_64
# arch=mingw-w64-ucrt-x86_64 arch=mingw-w64-ucrt-x86_64
pacman -S ${PACMAN_OPTIONS} make pacman -S ${PACMAN_OPTIONS} make

View File

@ -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

58
setup.py Normal file
View File

@ -0,0 +1,58 @@
import glob, os
import platform
from setuptools import setup
pf = platform.platform()
if 'Linux' in pf:
ext_name_glob = 'lasp_cpp.cpython*'
# Split of path from file.
extensions = list(glob.glob('src/lasp/' + ext_name_glob))
elif 'Windows' in pf:
ext_name_glob = 'lasp_cpp*.cp*.pyd'
# Split of path from file.
extensions = list(glob.glob('src\\lasp/' + ext_name_glob))
else:
raise RuntimeError('Not yet Windows-proof')
print(extensions)
ext_names = [os.path.split(a)[1] for a in 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
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
View 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} ${TARGET_OS_LINKLIBS})
install(TARGETS lasp_cpp DESTINATION .)

View File

@ -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

View File

@ -34,7 +34,7 @@ if(LASP_HAS_RTAUDIO)
target_link_libraries(lasp_device_lib rtaudio) target_link_libraries(lasp_device_lib rtaudio)
endif() endif()
if(LASP_HAS_PORTAUDIO) if(LASP_HAS_PORTAUDIO)
target_link_libraries(lasp_device_lib PortAudio) target_link_libraries(lasp_device_lib portaudio)
if(WIN32) if(WIN32)
else() else()
target_link_libraries(lasp_device_lib asound) target_link_libraries(lasp_device_lib asound)

View File

@ -1,4 +1,4 @@
// #define DEBUGTRACE_ENABLED /* #define DEBUGTRACE_ENABLED */
#include "debugtrace.hpp" #include "debugtrace.hpp"
#include "lasp_daqconfig.h" #include "lasp_daqconfig.h"
@ -44,41 +44,34 @@ Daq::Daq(const DeviceInfo &devinfo, const DaqConfiguration &config)
: DaqConfiguration(config), DeviceInfo(devinfo) { : DaqConfiguration(config), DeviceInfo(devinfo) {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
if (duplexMode()) {
if (neninchannels() == 0) {
throw rte("Duplex mode enabled, but no input channels enabled");
}
if (nenoutchannels() == 0) {
throw rte("Duplex mode enabled, but no output channels enabled");
}
}
if(!duplexMode() && monitorOutput) { if(!duplexMode() && monitorOutput) {
throw rte("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."); throw rte("Output monitoring only allowed when running in duplex mode");
} }
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) {
{ throw rte(
const int hich = getHighestEnabledInChannel(); "Number of enabled input channels is higher than device capability");
if (hich + 1 > devinfo.ninchannels)
{
throw rte(
string("Highest of enabled input channel: ") +
to_string(hich) +
string(" is higher than device capability, which is: ") +
to_string(ninchannels) + ".");
}
} }
if (nenoutchannels() > devinfo.noutchannels) {
{ throw rte(
const int hoch = getHighestEnabledOutChannel(); "Number of enabled output channels is higher than device capability");
if (hoch + 1 > devinfo.noutchannels)
{
throw rte(
string("Highest of enabled output channel: ") +
to_string(hoch) +
string(" is higher than device capability, which is: ") +
to_string(noutchannels) + ".");
}
} }
} }

View File

@ -48,10 +48,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 +61,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 +69,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); }
/** /**

View File

@ -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;

View File

@ -148,14 +148,7 @@ const DaqApi rtaudioAsioApi("RtAudio Windows ASIO", LASP_RTAUDIO_APICODE,
#endif #endif
#if LASP_HAS_PORTAUDIO == 1 #if LASP_HAS_PORTAUDIO == 1
const us LASP_PORTAUDIO_APICODE = 2; const us LASP_PORTAUDIO_APICODE = 2;
const DaqApi portaudioALSAApi("PortAudio Linux ALSA", LASP_PORTAUDIO_APICODE, 0); const DaqApi portaudioApi("PortAudio Linux ALSA", LASP_PORTAUDIO_APICODE, 0);
const DaqApi portaudioPulseApi("PortAudio Linux PulseAudio", LASP_PORTAUDIO_APICODE, 1);
const DaqApi portaudioASIOApi("PortAudio Windows ASIO", LASP_PORTAUDIO_APICODE, 2);
const DaqApi portaudioDSApi("PortAudio Windows DirectSound", LASP_PORTAUDIO_APICODE, 3);
const DaqApi portaudioWMMEApi("PortAudio Windows WMME", LASP_PORTAUDIO_APICODE, 4);
const DaqApi portaudioWASAPIApi("PortAudio Windows WASAPI", LASP_PORTAUDIO_APICODE, 5);
const DaqApi portaudioWDMKS("PortAudio Windows WDMKS", LASP_PORTAUDIO_APICODE, 6);
const DaqApi portaudioDirectSoundApi("PortAudio Windows DirectSound", LASP_PORTAUDIO_APICODE, 7);
#endif #endif
class DeviceInfo; class DeviceInfo;

View File

@ -68,26 +68,14 @@ public:
us prefFramesPerBlockIndex = 0; us prefFramesPerBlockIndex = 0;
/** /**
* @brief Available ranges for the input, i.e. +/- 1V and/or +/- 10 V etc. * @brief Available ranges for the input, i.e. +/- 1V and/or +/- 10 V etc.
*/ */
dvec availableInputRanges; dvec availableInputRanges;
/** /**
* @brief 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 +125,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
* *

View File

@ -1,11 +1,11 @@
// #define DEBUGTRACE_ENABLED /* #define DEBUGTRACE_ENABLED */
#include "lasp_indatahandler.h" #include "lasp_indatahandler.h"
#include "debugtrace.hpp" #include "debugtrace.hpp"
#include "lasp_streammgr.h" #include "lasp_streammgr.h"
#include <thread> #include <thread>
InDataHandler::InDataHandler(SmgrHandle mgr, const InCallbackType cb, InDataHandler::InDataHandler(SmgrHandle mgr, const InCallbackType cb,
const ResetCallbackType resetfcn) const InResetType resetfcn)
: _mgr(mgr), inCallback(cb), reset(resetfcn) : _mgr(mgr), inCallback(cb), reset(resetfcn)
#if LASP_DEBUG == 1 #if LASP_DEBUG == 1
, ,
@ -29,22 +29,20 @@ void InDataHandler::start() {
} }
void InDataHandler::stop() { void InDataHandler::stop() {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
// checkRightThread(); checkRightThread();
#if LASP_DEBUG == 1 #if LASP_DEBUG == 1
stopCalled = true; stopCalled = true;
#endif #endif
if (SmgrHandle smgr = _mgr.lock()) { if (SmgrHandle handle = _mgr.lock()) {
smgr->removeInDataHandler(*this); handle->removeInDataHandler(*this);
} else {
DEBUGTRACE_PRINT("No stream manager alive anymore!");
} }
} }
InDataHandler::~InDataHandler() { InDataHandler::~InDataHandler() {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
checkRightThread();
#if LASP_DEBUG == 1 #if LASP_DEBUG == 1
// checkRightThread();
if (!stopCalled) { if (!stopCalled) {
std::cerr << "************ BUG: Stop function not called while arriving at " std::cerr << "************ BUG: Stop function not called while arriving at "
"InDataHandler's destructor. Fix this by calling " "InDataHandler's destructor. Fix this by calling "

View File

@ -22,7 +22,7 @@ using InCallbackType = std::function<void(const DaqData &)>;
/** /**
* @brief Function definition for the reset callback. * @brief Function definition for the reset callback.
*/ */
using ResetCallbackType = std::function<void(const Daq *)>; using InResetType = std::function<void(const Daq *)>;
class InDataHandler { class InDataHandler {
@ -38,7 +38,7 @@ protected:
public: public:
~InDataHandler(); ~InDataHandler();
const InCallbackType inCallback; const InCallbackType inCallback;
const ResetCallbackType reset; const InResetType reset;
/** /**
* @brief When constructed, the handler is added to the stream manager, which * @brief When constructed, the handler is added to the stream manager, which
@ -50,7 +50,7 @@ public:
* changes state. * changes state.
*/ */
InDataHandler(SmgrHandle mgr, InCallbackType cb, InDataHandler(SmgrHandle mgr, InCallbackType cb,
ResetCallbackType resetfcn); InResetType resetfcn);
/** /**
* @brief Adds the current InDataHandler to the list of handlers in the * @brief Adds the current InDataHandler to the list of handlers in the

View File

@ -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;
@ -199,11 +181,10 @@ public:
/// 0. For now, our fix is to shift out the channels we want, and let /// 0. For now, our fix is to shift out the channels we want, and let
/// RtAudio pass on all channels. /// RtAudio pass on all channels.
inParams->firstChannel = 0; inParams->firstChannel = 0;
inParams->nChannels = devinfo.ninchannels; inParams->nChannels = devinfo_gen.ninchannels;
inParams->deviceId = devinfo.ID; inParams->deviceId = devinfo._api_devindex;
}
else } else {
{
outParams = std::make_unique<RtAudio::StreamParameters>(); outParams = std::make_unique<RtAudio::StreamParameters>();
@ -211,8 +192,8 @@ public:
/// 0. For now, our fix is to shift out the channels we want, and let /// 0. For now, our fix is to shift out the channels we want, and let
/// RtAudio pass on all channels. /// RtAudio pass on all channels.
outParams->firstChannel = 0; outParams->firstChannel = 0;
outParams->nChannels = devinfo.noutchannels; outParams->nChannels = devinfo_gen.noutchannels;
outParams->deviceId = devinfo.ID; outParams->deviceId = devinfo._api_devindex;
} }
RtAudio::StreamOptions streamoptions; RtAudio::StreamOptions streamoptions;
@ -225,8 +206,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 +237,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(string("Got different number of frames per block back from RtAudio " throw rte(string("Got different number of frames per block back from RtAudio "
"backend: ") + "backend: ") + std::to_string(nFramesPerBlock_copy) + ". I do not know what to do.");
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 +274,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 +283,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 +293,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 +305,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 +321,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;
@ -384,16 +340,14 @@ public:
const us neninchannels = this->neninchannels(); const us neninchannels = this->neninchannels();
const us nenoutchannels = this->nenoutchannels(); const us nenoutchannels = this->nenoutchannels();
const us sw = dtype_descr.sw; const us sw = dtype_descr.sw;
if (nFrames != nFramesPerBlock) if (nFrames != nFramesPerBlock) {
{
cerr << "RtAudio backend error: nFrames does not match block size!" cerr << "RtAudio backend error: nFrames does not match block size!"
<< endl; << endl;
stopWithError(se::logicError); stopWithError(se::logicError);
return 1; return 1;
} }
if (inputBuffer) if (inputBuffer) {
{
assert(_incallback); assert(_incallback);
std::vector<byte_t *> ptrs; std::vector<byte_t *> ptrs;
ptrs.reserve(neninchannels); ptrs.reserve(neninchannels);
@ -404,10 +358,8 @@ public:
assert(ch_max < ninchannels); assert(ch_max < ninchannels);
/// Only pass on the pointers of the channels we want /// Only pass on the pointers of the channels we want
for (us ch = ch_min; ch <= ch_max; ch++) for (us ch = ch_min; ch <= ch_max; ch++) {
{ if (inchannel_config.at(ch).enabled) {
if (inchannel_config.at(ch).enabled)
{
byte_t *ptr = byte_t *ptr =
static_cast<byte_t *>(inputBuffer) + sw * ch * nFramesPerBlock; static_cast<byte_t *>(inputBuffer) + sw * ch * nFramesPerBlock;
ptrs.push_back(ptr); ptrs.push_back(ptr);
@ -416,11 +368,10 @@ public:
DaqData d{nFramesPerBlock, neninchannels, dtype}; DaqData d{nFramesPerBlock, neninchannels, dtype};
d.copyInFromRaw(ptrs); d.copyInFromRaw(ptrs);
_incallback(d); _incallback(d);
} }
if (outputBuffer) if (outputBuffer) {
{
assert(_outcallback); assert(_outcallback);
std::vector<byte_t *> ptrs; std::vector<byte_t *> ptrs;
ptrs.reserve(nenoutchannels); ptrs.reserve(nenoutchannels);
@ -432,10 +383,8 @@ public:
assert(ch_min < noutchannels); assert(ch_min < noutchannels);
assert(ch_max < noutchannels); assert(ch_max < noutchannels);
/// Only pass on the pointers of the channels we want /// Only pass on the pointers of the channels we want
for (us ch = ch_min; ch <= ch_max; ch++) for (us ch = ch_min; ch <= ch_max; ch++) {
{ if (outchannel_config.at(ch).enabled) {
if (outchannel_config.at(ch).enabled)
{
ptrs.push_back(static_cast<byte_t *>(outputBuffer) + ptrs.push_back(static_cast<byte_t *>(outputBuffer) +
sw * ch * nFramesPerBlock); sw * ch * nFramesPerBlock);
} }
@ -445,8 +394,7 @@ public:
_outcallback(d); _outcallback(d);
// Copy over the buffer // Copy over the buffer
us j = 0; us j = 0;
for (auto ptr : ptrs) for (auto ptr : ptrs) {
{
d.copyToRaw(j, ptr); d.copyToRaw(j, ptr);
j++; j++;
} }
@ -462,16 +410,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);

View File

@ -1,21 +1,15 @@
// #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_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>
#include <memory>
#include <mutex>
using std::cerr; using std::cerr;
using std::endl; using std::endl;
@ -30,7 +24,7 @@ using rte = std::runtime_error;
std::weak_ptr<StreamMgr> _mgr; std::weak_ptr<StreamMgr> _mgr;
std::mutex _mgr_mutex; std::mutex _mgr_mutex;
using Lck = std::scoped_lock<std::recursive_mutex>; using Lck = std::scoped_lock<std::mutex>;
/** /**
* @brief The only way to obtain a stream manager, can only be called from the * @brief The only way to obtain a stream manager, can only be called from the
@ -41,11 +35,11 @@ using Lck = std::scoped_lock<std::recursive_mutex>;
SmgrHandle StreamMgr::getInstance() { SmgrHandle StreamMgr::getInstance() {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
std::scoped_lock<std::mutex> lck(_mgr_mutex);
auto mgr = _mgr.lock(); auto mgr = _mgr.lock();
if (!mgr) { if (!mgr) {
// Double Check Locking Pattern, if two threads would simultaneously // Double Check Locking Pattern, if two threads would simultaneously
// instantiate the singleton instance. // instantiate the singleton instance.
Lck lck(_mgr_mutex);
auto mgr = _mgr.lock(); auto mgr = _mgr.lock();
if (mgr) { if (mgr) {
@ -87,50 +81,44 @@ void StreamMgr::rescanDAQDevices(bool background,
std::function<void()> callback) { std::function<void()> callback) {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
DEBUGTRACE_PRINT(background); DEBUGTRACE_PRINT(background);
if (_scanningDevices) {
throw rte("A background device scan is already busy");
}
Lck lck(_mtx);
checkRightThread(); checkRightThread();
if (_inputStream || _outputStream) { if (_inputStream || _outputStream) {
throw rte("Rescanning DAQ devices only possible when no stream is running"); throw rte("Rescanning DAQ devices only possible when no stream is running");
} }
if (!_devices_mtx.try_lock()) {
throw rte("A background DAQ device scan is probably already running");
}
_devices_mtx.unlock();
std::scoped_lock lck(_devices_mtx);
_devices.clear(); _devices.clear();
if (!background) { if (!background) {
_scanningDevices = true;
rescanDAQDevices_impl(callback); rescanDAQDevices_impl(callback);
} else { } else {
DEBUGTRACE_PRINT("Rescanning DAQ devices on different thread..."); DEBUGTRACE_PRINT("Rescanning DAQ devices on different thread...");
_scanningDevices = true;
_pool.push_task(&StreamMgr::rescanDAQDevices_impl, this, callback); _pool.push_task(&StreamMgr::rescanDAQDevices_impl, this, callback);
} }
} }
void StreamMgr::rescanDAQDevices_impl(std::function<void()> callback) { void StreamMgr::rescanDAQDevices_impl(std::function<void()> callback) {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
assert(!_inputStream && !_outputStream); std::scoped_lock lck(_devices_mtx);
Lck lck(_mtx); _devices = DeviceInfo::getDeviceInfo();
// Alsa spits out annoying messages that are not useful
{
_devices = DeviceInfo::getDeviceInfo();
}
if (callback) { if (callback) {
callback(); callback();
} }
_scanningDevices = false;
} }
void StreamMgr::inCallback(const DaqData &data) { void StreamMgr::inCallback(const DaqData &data) {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
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,13 +138,12 @@ void StreamMgr::inCallback(const DaqData &data) {
} }
} }
DEBUGTRACE_PRINT("Calling incallback for handlers (filtered)...");
for (auto &handler : _inDataHandlers) { for (auto &handler : _inDataHandlers) {
handler->inCallback(input_filtered); handler->inCallback(input_filtered);
} }
} else { } else {
/// No input filters /// No input filters
DEBUGTRACE_PRINT("Calling incallback for handlers...");
for (auto &handler : _inDataHandlers) { for (auto &handler : _inDataHandlers) {
handler->inCallback(data); handler->inCallback(data);
} }
@ -164,10 +151,12 @@ void StreamMgr::inCallback(const DaqData &data) {
} }
void StreamMgr::setSiggen(std::shared_ptr<Siggen> siggen) { void StreamMgr::setSiggen(std::shared_ptr<Siggen> siggen) {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
checkRightThread(); checkRightThread();
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 +179,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());
@ -224,28 +212,29 @@ bool fillData(DaqData &data, const vd &signal) {
return true; return true;
} }
void StreamMgr::outCallback(DaqData &data) { void 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);
switch (data.dtype) { switch (data.dtype) {
case (DataTypeDescriptor::DataType::dtype_fl32): case (DataTypeDescriptor::DataType::dtype_fl32):
fillData<float>(data, signal); fillData<float>(data, signal);
break; break;
case (DataTypeDescriptor::DataType::dtype_fl64): case (DataTypeDescriptor::DataType::dtype_fl64):
fillData<double>(data, signal); fillData<double>(data, signal);
break; break;
case (DataTypeDescriptor::DataType::dtype_int8): case (DataTypeDescriptor::DataType::dtype_int8):
fillData<int8_t>(data, signal); fillData<int8_t>(data, signal);
break; break;
case (DataTypeDescriptor::DataType::dtype_int16): case (DataTypeDescriptor::DataType::dtype_int16):
fillData<int16_t>(data, signal); fillData<int16_t>(data, signal);
break; break;
case (DataTypeDescriptor::DataType::dtype_int32): case (DataTypeDescriptor::DataType::dtype_int32):
fillData<int32_t>(data, signal); fillData<int32_t>(data, signal);
break; break;
} }
} else { } else {
// Set all values to 0. // Set all values to 0.
@ -255,17 +244,7 @@ void StreamMgr::outCallback(DaqData &data) {
StreamMgr::~StreamMgr() { StreamMgr::~StreamMgr() {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
while (_scanningDevices) { checkRightThread();
std::this_thread::sleep_for(10us);
}
#if LASP_DEBUG == 1
{ // Careful, this lock needs to be released to make sure the streams can
// obtain a lock to the stream manager.
Lck lck(_mtx);
checkRightThread();
}
#endif
// Stream manager now handled by shared pointer. Each indata handler gets a // Stream manager now handled by shared pointer. Each indata handler gets a
// shared pointer to the stream manager, and stores a weak pointer to it. // shared pointer to the stream manager, and stores a weak pointer to it.
// Hence, we do not have to do any cleanup here. It also makes sure that the // Hence, we do not have to do any cleanup here. It also makes sure that the
@ -278,35 +257,31 @@ StreamMgr::~StreamMgr() {
// virtual methods. This was really a bug. // virtual methods. This was really a bug.
_inputStream.reset(); _inputStream.reset();
_outputStream.reset(); _outputStream.reset();
} }
void StreamMgr::stopAllStreams() { void StreamMgr::stopAllStreams() {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
{ checkRightThread();
Lck lck(_mtx);
checkRightThread();
}
// No lock here!
_inputStream.reset(); _inputStream.reset();
_outputStream.reset(); _outputStream.reset();
} }
void StreamMgr::startStream(const DaqConfiguration &config) { void StreamMgr::startStream(const DaqConfiguration &config) {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
if (_scanningDevices) {
throw rte("DAQ device scan is busy. Cannot start stream.");
}
Lck lck(_mtx);
checkRightThread(); checkRightThread();
bool isInput = std::count_if(config.inchannel_config.cbegin(), bool isInput = std::count_if(config.inchannel_config.cbegin(),
config.inchannel_config.cend(), config.inchannel_config.cend(),
[](auto &i) { return i.enabled; }) > 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
std::scoped_lock lck(_devices_mtx);
DeviceInfo *devinfo = nullptr; DeviceInfo *devinfo = nullptr;
// Match configuration to a device in the list of devices // Match configuration to a device in the list of devices
@ -327,40 +302,34 @@ 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");
} }
} }
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");
} }
InDaqCallback inCallback; InDaqCallback inCallback;
@ -383,13 +352,13 @@ void StreamMgr::startStream(const DaqConfiguration &config) {
d fs = daq->samplerate(); d fs = daq->samplerate();
/// Create input filters /// Create input filters
_inputFilters.clear(); _inputFilters.clear();
/// No input filter for monitor channel, which comes as the first input /// No input filter for monitor channel, which comes as the first input channel
/// channel In the list /// 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) {
@ -402,7 +371,7 @@ void StreamMgr::startStream(const DaqConfiguration &config) {
SeriesBiquad::firstOrderHighPass(fs, ch.digitalHighPassCutOn))); SeriesBiquad::firstOrderHighPass(fs, ch.digitalHighPassCutOn)));
} }
} }
} // End of input filter creation } // End of input filter creation
} }
if (isOutput) { if (isOutput) {
@ -429,58 +398,46 @@ 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 if (t == StreamType::input) {
Lck lck(_mtx); if (!_inputStream) {
if (t == StreamType::input) { throw rte("Input stream is not running");
if (!_inputStream) {
throw rte("Input stream is not running");
}
streamToStop = std::addressof(_inputStream);
resetHandlers = true;
} else {
/// t == output
/// Kill input stream in case that one is a duplex stream
if (_inputStream && _inputStream->duplexMode()) {
streamToStop = std::addressof(_inputStream);
} else {
if (!_outputStream) {
throw rte("Output stream is not running");
}
streamToStop = std::addressof(_outputStream);
} // end else
} }
} // End of mutex lock. When stopping stream, mutex should be unlocked. /// Kills input stream
_inputStream.reset();
// If we arrive here, we should have a stream to stop. /// Send reset to all in data handlers
assert(streamToStop != nullptr);
streamToStop->reset();
/// Send reset to all in data handlers
if (resetHandlers) {
Lck lck(_mtx);
for (auto &handler : _inDataHandlers) { for (auto &handler : _inDataHandlers) {
handler->reset(nullptr); handler->reset(nullptr);
} }
} else {
/// t == output
/// Kill input stream in case that one is a duplex stream
if (_inputStream && _inputStream->duplexMode()) {
_inputStream.reset();
} else {
if (!_outputStream) {
throw rte("Output stream is not running");
}
_outputStream.reset();
} // end else
} }
} }
void StreamMgr::addInDataHandler(InDataHandler *handler) { void StreamMgr::addInDataHandler(InDataHandler *handler) {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
Lck lck(_mtx);
checkRightThread(); checkRightThread();
assert(handler); assert(handler);
std::scoped_lock<std::mutex> lck(_inDataHandler_mtx);
handler->reset(_inputStream.get()); handler->reset(_inputStream.get());
if (std::find(_inDataHandlers.cbegin(), _inDataHandlers.cend(), handler) != if (std::find(_inDataHandlers.cbegin(), _inDataHandlers.cend(), handler) !=
_inDataHandlers.cend()) { _inDataHandlers.cend()) {
throw std::runtime_error( 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()); DEBUGTRACE_PRINT(_inDataHandlers.size());
@ -488,17 +445,16 @@ void StreamMgr::addInDataHandler(InDataHandler *handler) {
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()); 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 +467,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) {

View File

@ -1,19 +1,19 @@
#pragma once #pragma once
#include "lasp_daq.h"
#include "lasp_siggen.h"
#include "lasp_thread.h"
#include <list> #include <list>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <thread> #include <thread>
#include "lasp_daq.h"
#include "lasp_siggen.h"
#include "lasp_thread.h"
/** \addtogroup device /** \addtogroup device
* @{ * @{
*/ */
class StreamMgr; class StreamMgr;
class InDataHandler; class InDataHandler;
class SeriesBiquad; class SeriesBiquad;
/** /**
@ -25,15 +25,12 @@ class SeriesBiquad;
* fact is asserted. * fact is asserted.
*/ */
class StreamMgr { class StreamMgr {
mutable std::recursive_mutex _mtx;
/** /**
* @brief Storage for streams. * @brief Storage for streams.
*/ */
std::unique_ptr<Daq> _inputStream, _outputStream; std::unique_ptr<Daq> _inputStream, _outputStream;
std::atomic<bool> _scanningDevices{false};
GlobalThreadPool _pool; GlobalThreadPool _pool;
/** /**
@ -42,18 +39,22 @@ class StreamMgr {
* thread-safety. * thread-safety.
*/ */
std::list<InDataHandler *> _inDataHandlers; std::list<InDataHandler *> _inDataHandlers;
mutable std::mutex _inDataHandler_mtx;
/** /**
* @brief Signal generator in use to generate output data. Currently * @brief Signal generator in use to generate output data. Currently
* implemented as to generate the same data for all output channels. * implemented as to generate the same data for all output channels.
*/ */
std::shared_ptr<Siggen> _siggen; std::shared_ptr<Siggen> _siggen;
std::mutex _siggen_mtx;
/** /**
* @brief Filters on input stream. For example, a digital high pass filter. * @brief Filters on input stream. For example, a digital high pass filter.
*/ */
std::vector<std::unique_ptr<SeriesBiquad>> _inputFilters; std::vector<std::unique_ptr<SeriesBiquad>> _inputFilters;
mutable std::recursive_mutex _devices_mtx;
/** /**
* @brief Current storage for the device list * @brief Current storage for the device list
*/ */
@ -66,7 +67,9 @@ class StreamMgr {
friend class InDataHandler; friend class InDataHandler;
friend class Siggen; friend class Siggen;
public:
public:
~StreamMgr(); ~StreamMgr();
enum class StreamType : us { enum class StreamType : us {
@ -97,10 +100,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(_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;
@ -116,9 +118,9 @@ class StreamMgr {
* set to true, the function returns immediately. * set to true, the function returns immediately.
* @param callback Function to call when complete. * @param callback Function to call when complete.
*/ */
void rescanDAQDevices( void
bool background = false, rescanDAQDevices(bool background = false,
std::function<void()> callback = std::function<void()>()); std::function<void()> callback = std::function<void()>());
/** /**
* @brief Start a stream based on given configuration. * @brief Start a stream based on given configuration.
@ -139,12 +141,12 @@ class StreamMgr {
} }
bool isStreamRunning(const StreamType type) const { bool isStreamRunning(const StreamType type) const {
switch (type) { switch (type) {
case (StreamType::input): case (StreamType::input):
return bool(_inputStream); return bool(_inputStream);
break; break;
case (StreamType::output): case (StreamType::output):
return bool(_outputStream); return bool(_outputStream);
break; break;
} }
return false; return false;
} }
@ -191,10 +193,11 @@ class StreamMgr {
*/ */
void setSiggen(std::shared_ptr<Siggen> s); void setSiggen(std::shared_ptr<Siggen> s);
private: private:
void inCallback(const DaqData &data); void inCallback(const DaqData &data);
void outCallback(DaqData &data); void outCallback(DaqData &data);
/** /**
* @brief Add an input data handler. The handler's inCallback() function is * @brief Add an input data handler. The handler's inCallback() function is
* called with data when available. This function should *NOT* be called by * called with data when available. This function should *NOT* be called by

View File

@ -68,7 +68,6 @@ void fillUlDaqDeviceInfo(DeviceInfoList &devinfolist) {
} }
devinfo.physicalOutputQty = DaqChannel::Qty::Voltage; devinfo.physicalOutputQty = DaqChannel::Qty::Voltage;
devinfo.physicalInputQty = DaqChannel::Qty::Voltage;
devinfo.availableDataTypes.push_back( devinfo.availableDataTypes.push_back(
DataTypeDescriptor::DataType::dtype_fl64); DataTypeDescriptor::DataType::dtype_fl64);
@ -80,9 +79,7 @@ void fillUlDaqDeviceInfo(DeviceInfoList &devinfolist) {
devinfo.availableFramesPerBlock = {512, 1024, 2048, 4096, 8192}; devinfo.availableFramesPerBlock = {512, 1024, 2048, 4096, 8192};
devinfo.availableInputRanges = {1.0, 10.0}; devinfo.availableInputRanges = {1.0, 10.0};
devinfo.availableOutputRanges = {10.0};
devinfo.prefInputRangeIndex = 0; devinfo.prefInputRangeIndex = 0;
devinfo.prefOutputRangeIndex = 0;
devinfo.ninchannels = 4; devinfo.ninchannels = 4;
devinfo.noutchannels = 1; devinfo.noutchannels = 1;
@ -93,7 +90,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

View File

@ -1,48 +1,20 @@
// #define DEBUGTRACE_ENABLED /* #define DEBUGTRACE_ENABLED */
#include "debugtrace.hpp" #include "debugtrace.hpp"
#include "lasp_config.h" #include "lasp_config.h"
#if LASP_HAS_PORTAUDIO == 1 #if LASP_HAS_PORTAUDIO == 1
#include "lasp_portaudiodaq.h"
#include "portaudio.h"
#include <gsl-lite/gsl-lite.hpp> #include <gsl-lite/gsl-lite.hpp>
#include <mutex> #include <mutex>
#include <string> #include <string>
#include "lasp_portaudiodaq.h"
#include "portaudio.h"
using rte = std::runtime_error; using rte = std::runtime_error;
using std::cerr; using std::cerr;
using std::endl; using std::endl;
using std::string; using std::string;
using std::to_string; using std::to_string;
#if LASP_HAS_PA_ALSA
#include <alsa/asoundlib.h>
void empty_handler(const char *file, int line, const char *function, int err,
const char *fmt, ...) {
// cerr << "Test empty error handler...\n";
}
// Temporarily set the ALSA eror handler to something that does nothing, to
// prevent ALSA from spitting out all kinds of misconfiguration errors.
class MuteErrHandler {
private:
snd_lib_error_handler_t _default_handler;
public:
explicit MuteErrHandler() {
_default_handler = snd_lib_error;
snd_lib_error_set_handler(empty_handler);
}
~MuteErrHandler() { snd_lib_error_set_handler(_default_handler); }
};
#else
// Does nothin in case of no ALSA
class MuteErrHandler {};
#endif
inline void throwIfError(PaError e) { inline void throwIfError(PaError e) {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
if (e != paNoError) { if (e != paNoError) {
@ -54,7 +26,7 @@ inline void throwIfError(PaError e) {
* @brief Device info, plus PortAudio stuff * @brief Device info, plus PortAudio stuff
*/ */
class OurPaDeviceInfo : public DeviceInfo { class OurPaDeviceInfo : public DeviceInfo {
public: public:
/** /**
* @brief Store instance to PaDeviceInfo. * @brief Store instance to PaDeviceInfo.
*/ */
@ -63,22 +35,17 @@ class OurPaDeviceInfo : public DeviceInfo {
virtual std::unique_ptr<DeviceInfo> clone() const override final { virtual std::unique_ptr<DeviceInfo> clone() const override final {
return std::make_unique<OurPaDeviceInfo>(*this); return std::make_unique<OurPaDeviceInfo>(*this);
} }
OurPaDeviceInfo &operator=(const OurPaDeviceInfo &) = delete;
OurPaDeviceInfo(const OurPaDeviceInfo &) = default;
OurPaDeviceInfo(const PaDeviceInfo &o) : DeviceInfo(), _paDevInfo(o) {}
}; };
void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist) { void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist) {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
bool shouldPaTerminate = false;
MuteErrHandler guard;
try { try {
PaError err = Pa_Initialize(); PaError err = Pa_Initialize();
/// PortAudio says that Pa_Terminate() should not be called whenever there /// PortAudio says that Pa_Terminate() should not be called whenever there
/// is an error in Pa_Initialize(). This is opposite to what most examples /// is an error in Pa_Initialize(). This is opposite to what most examples
/// of PortAudio show. /// of PortAudio show.
throwIfError(err); throwIfError(err);
shouldPaTerminate = true;
auto fin = gsl::finally([&err] { auto fin = gsl::finally([&err] {
DEBUGTRACE_PRINT("Terminating PortAudio instance"); DEBUGTRACE_PRINT("Terminating PortAudio instance");
@ -88,10 +55,6 @@ void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist) {
} }
}); });
const PaHostApiIndex apicount = Pa_GetHostApiCount();
if (apicount < 0) {
return;
}
/* const PaDeviceInfo *deviceInfo; */ /* const PaDeviceInfo *deviceInfo; */
const int numDevices = Pa_GetDeviceCount(); const int numDevices = Pa_GetDeviceCount();
if (numDevices < 0) { if (numDevices < 0) {
@ -99,51 +62,16 @@ void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist) {
} }
for (us i = 0; i < (us)numDevices; i++) { for (us i = 0; i < (us)numDevices; i++) {
/* DEBUGTRACE_PRINT(i); */ /* DEBUGTRACE_PRINT(i); */
bool hasDuplexMode = false;
const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(i); const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(i);
if (!deviceInfo) { if (!deviceInfo) {
throw rte("No device info struct returned"); throw rte("No device info struct returned");
} }
OurPaDeviceInfo d(*deviceInfo); OurPaDeviceInfo d;
// We store the name in d.device_name d._paDevInfo = *deviceInfo;
d._paDevInfo.name = nullptr; d.api = portaudioApi;
d.device_name = deviceInfo->name; d.device_name = deviceInfo->name;
const PaHostApiInfo *hostapiinfo = Pa_GetHostApiInfo(deviceInfo->hostApi);
if (hostapiinfo == nullptr) {
throw std::runtime_error("Hostapi nullptr!");
}
switch (hostapiinfo->type) {
case paALSA:
// Duplex mode for alsa
hasDuplexMode = true;
d.api = portaudioALSAApi;
break;
case paASIO:
hasDuplexMode = true;
d.api = portaudioASIOApi;
break;
case paDirectSound:
d.api = portaudioDirectSoundApi;
break;
case paMME:
d.api = portaudioWMMEApi;
break;
case paWDMKS:
d.api = portaudioWDMKS;
break;
case paWASAPI:
d.api = portaudioWASAPIApi;
break;
case paPulseAudio:
d.api = portaudioPulseApi;
break;
default:
throw rte("Unimplemented portaudio API!");
break;
}
d.availableDataTypes = {DataTypeDescriptor::DataType::dtype_int16, d.availableDataTypes = {DataTypeDescriptor::DataType::dtype_int16,
DataTypeDescriptor::DataType::dtype_int32, DataTypeDescriptor::DataType::dtype_int32,
DataTypeDescriptor::DataType::dtype_fl32}; DataTypeDescriptor::DataType::dtype_fl32};
@ -159,27 +87,15 @@ void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist) {
d.prefFramesPerBlockIndex = 2; d.prefFramesPerBlockIndex = 2;
d.availableInputRanges = {1.0}; d.availableInputRanges = {1.0};
// d.prefInputRangeIndex = 0; // Constructor-defined
d.availableOutputRanges = {1.0};
// d.prefOutputRangeIndex = 0; // Constructor-defined
d.ninchannels = deviceInfo->maxInputChannels; d.ninchannels = deviceInfo->maxInputChannels;
d.noutchannels = deviceInfo->maxOutputChannels; d.noutchannels = deviceInfo->maxOutputChannels;
// Duplex mode, only for ALSA devices
d.hasDuplexMode = hasDuplexMode;
devinfolist.push_back(std::make_unique<OurPaDeviceInfo>(d)); devinfolist.push_back(std::make_unique<OurPaDeviceInfo>(d));
} }
} }
catch (rte &e) { catch (rte &e) {
if (shouldPaTerminate) {
PaError err = Pa_Terminate();
if (err != paNoError) {
cerr << "Error terminating PortAudio. Do not know what to do." << endl;
}
}
cerr << "PortAudio backend error: " << e.what() << std::endl; cerr << "PortAudio backend error: " << e.what() << std::endl;
return; return;
} }
@ -205,13 +121,14 @@ static int rawPaCallback(const void *inputBuffer, void *outputBuffer,
PaStreamCallbackFlags statusFlags, void *userData); PaStreamCallbackFlags statusFlags, void *userData);
class PortAudioDaq : public Daq { class PortAudioDaq : public Daq {
bool _shouldPaTerminate = false;
PaStream *_stream = nullptr; PaStream *_stream = nullptr;
std::atomic<StreamStatus::StreamError> _streamError = std::atomic<StreamStatus::StreamError> _streamError =
StreamStatus::StreamError::noError; StreamStatus::StreamError::noError;
InDaqCallback _incallback; InDaqCallback _incallback;
OutDaqCallback _outcallback; OutDaqCallback _outcallback;
public: public:
PortAudioDaq(const OurPaDeviceInfo &devinfo_gen, PortAudioDaq(const OurPaDeviceInfo &devinfo_gen,
const DaqConfiguration &config); const DaqConfiguration &config);
@ -241,7 +158,7 @@ class PortAudioDaq : public Daq {
std::unique_ptr<Daq> createPortAudioDevice(const DeviceInfo &devinfo, std::unique_ptr<Daq> createPortAudioDevice(const DeviceInfo &devinfo,
const DaqConfiguration &config) { const DaqConfiguration &config) {
DEBUGTRACE_ENTER;
const OurPaDeviceInfo *_info = const OurPaDeviceInfo *_info =
dynamic_cast<const OurPaDeviceInfo *>(&devinfo); dynamic_cast<const OurPaDeviceInfo *>(&devinfo);
if (_info == nullptr) { if (_info == nullptr) {
@ -261,127 +178,119 @@ static int rawPaCallback(const void *inputBuffer, void *outputBuffer,
PortAudioDaq::PortAudioDaq(const OurPaDeviceInfo &devinfo_gen, PortAudioDaq::PortAudioDaq(const OurPaDeviceInfo &devinfo_gen,
const DaqConfiguration &config) const DaqConfiguration &config)
: Daq(devinfo_gen, config) { : Daq(devinfo_gen, config) {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
bool shouldPaTerminate = false; PaError err = Pa_Initialize();
try { /// PortAudio says that Pa_Terminate() should not be called whenever there
PaError err = Pa_Initialize(); /// is an error in Pa_Initialize(). This is opposite to what most examples
/// PortAudio says that Pa_Terminate() should not be called whenever there /// of PortAudio show.
/// is an error in Pa_Initialize(). This is opposite to what most examples throwIfError(err);
/// of PortAudio show.
throwIfError(err);
// OK, Pa_Initialize successfully finished, it means we have to clean up // OK, Pa_Initialize successfully finished, it means we have to clean up with
// with Pa_Terminate in the destructor. // Pa_Terminate in the destructor.
shouldPaTerminate = true; _shouldPaTerminate = true;
// Going to find the device in the list. If its there, we have to retrieve // Going to find the device in the list. If its there, we have to retrieve
// the index, as this is required in the PaStreamParameters struct // the index, as this is required in the PaStreamParameters struct
int devindex = -1; int devindex = -1;
for (int i = 0; i < Pa_GetDeviceCount(); i++) { for (int i = 0; i < Pa_GetDeviceCount(); i++) {
// DEBUGTRACE_PRINT(i); bool ok = true;
bool ok = true; const PaDeviceInfo *info = Pa_GetDeviceInfo(i);
const PaDeviceInfo *info = Pa_GetDeviceInfo(i); if (!info) {
if (!info) { throw rte("No device structure returned from PortAudio");
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));
} }
ok &= string(info->name) == devinfo_gen._paDevInfo.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;
using Dtype = DataTypeDescriptor::DataType; if (ok) {
const Dtype dtype = dataType(); devindex = i;
// 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;
} }
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)neninchannels(),
.sampleFormat = format,
.suggestedLatency = framesPerBlock() / samplerate(),
.hostApiSpecificStreamInfo = nullptr}));
}
if (nenoutchannels() > 0) {
outstreamParams = std::make_unique<PaStreamParameters>(
PaStreamParameters({.device = devindex,
.channelCount = (int)nenoutchannels(),
.sampleFormat = format,
.suggestedLatency = framesPerBlock() / samplerate(),
.hostApiSpecificStreamInfo = nullptr}));
}
// Next step: check whether we are OK
err = Pa_IsFormatSupported(instreamParams.get(), outstreamParams.get(),
samplerate());
throwIfError(err);
err = Pa_OpenStream(&_stream, // stream
instreamParams.get(), // inputParameters
outstreamParams.get(), // outputParameters
samplerate(), // yeah,
framesPerBlock(), // framesPerBuffer
paNoFlag, // streamFlags
rawPaCallback, this);
throwIfError(err);
} }
void PortAudioDaq::start(InDaqCallback inCallback, OutDaqCallback outCallback) { void PortAudioDaq::start(InDaqCallback inCallback, OutDaqCallback outCallback) {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
assert(_stream); assert(_stream);
MuteErrHandler guard;
if (Pa_IsStreamActive(_stream)) { if (Pa_IsStreamActive(_stream)) {
throw rte("Stream is already running"); throw rte("Stream is already running");
} }
// Logical XOR
if (inCallback && outCallback) {
throw rte("Either input or output stream possible for RtAudio. "
"Stream duplex mode not provided.");
}
if (neninchannels() > 0) { if (neninchannels() > 0) {
if (!inCallback) { if (!inCallback) {
throw rte( throw rte(
@ -405,77 +314,52 @@ void PortAudioDaq::start(InDaqCallback inCallback, OutDaqCallback outCallback) {
void PortAudioDaq::stop() { void PortAudioDaq::stop() {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
assert(_stream); assert(_stream);
if (Pa_IsStreamStopped(_stream) > 1) { if (Pa_IsStreamStopped(_stream)) {
throw rte("Stream is already stopped"); throw rte("Stream is already stopped");
} }
PaError err = Pa_StopStream(_stream); PaError err = Pa_StopStream(_stream);
throwIfError(err); throwIfError(err);
} }
Daq::StreamStatus PortAudioDaq::getStreamStatus() const { Daq::StreamStatus PortAudioDaq::getStreamStatus() const {
DEBUGTRACE_ENTER;
// Stores an error type and whether the
Daq::StreamStatus status; Daq::StreamStatus status;
using StreamError = Daq::StreamStatus::StreamError; // Copy over atomic flag.
Daq::StreamStatus::StreamError errortype = _streamError.load(); status.errorType = _streamError;
// Check if stream is still running.
PaError err = Pa_IsStreamStopped(_stream); if (_stream) {
if (err > 1) { if (Pa_IsStreamActive(_stream)) {
// Stream is stopped due to an error in the callback. The exact error type status.isRunning = true;
// is filled in in the if-statement above
return status;
} else if (err == 0) {
// Still running
status.isRunning = true;
} else if (err < 0) {
// Stream encountered an error.
switch (err) {
case paInternalError:
errortype = StreamError::driverError;
break;
case paDeviceUnavailable:
errortype = StreamError::driverError;
break;
case paInputOverflowed:
errortype = StreamError::inputXRun;
break;
case paOutputUnderflowed:
errortype = StreamError::outputXRun;
break;
default:
errortype = StreamError::driverError;
cerr << "Portaudio backend error:" << Pa_GetErrorText(err) << endl;
break;
} }
} }
status.errorType = errortype;
return status; return status;
} }
PortAudioDaq::~PortAudioDaq() { PortAudioDaq::~PortAudioDaq() {
DEBUGTRACE_ENTER;
PaError err; PaError err;
assert(_stream); if (_stream) {
if (Pa_IsStreamActive(_stream)) { if (Pa_IsStreamActive(_stream)) {
// Stop the stream first stop();
stop(); }
err = Pa_CloseStream(_stream);
_stream = nullptr;
if (err != paNoError) {
cerr << "Error closing PortAudio stream. Do not know what to do." << endl;
}
assert(_shouldPaTerminate);
} }
err = Pa_CloseStream(_stream); if (_shouldPaTerminate) {
_stream = nullptr; err = Pa_Terminate();
if (err != paNoError) { if (err != paNoError) {
cerr << "Error closing PortAudio stream. Do not know what to do." << endl; cerr << "Error terminating PortAudio. Do not know what to do." << endl;
} }
err = Pa_Terminate();
if (err != paNoError) {
cerr << "Error terminating PortAudio. Do not know what to do." << endl;
} }
} }
int PortAudioDaq::memberPaCallback(const void *inputBuffer, void *outputBuffer, int PortAudioDaq::memberPaCallback(const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer, unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackTimeInfo *timeInfo,
PaStreamCallbackFlags statusFlags) { PaStreamCallbackFlags statusFlags) {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
typedef Daq::StreamStatus::StreamError se; typedef Daq::StreamStatus::StreamError se;
if (statusFlags & paPrimingOutput) { if (statusFlags & paPrimingOutput) {

View File

@ -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
) )

View File

@ -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)) {}

View File

@ -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

View File

@ -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;

View File

@ -37,7 +37,7 @@ void RtAps::inCallback(const DaqData &data) {
cerr << "**** Error: sensitivity size does not match! *****" << endl; cerr << "**** Error: sensitivity size does not match! *****" << endl;
return; return;
} }
fltdata.each_row() /= _sens.as_row(); fltdata.each_row() %= _sens.as_row();
if (_filterPrototype) { if (_filterPrototype) {

View File

@ -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>
/** /**

View File

@ -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);
} }
signal += signal_dynamic;
// Check whether we are running / not for signal interruption. }
bool activated = false;
if (_interrupt_period_s < 0) {
activated = true;
} else {
if (_interruption_frame_count < _interrupt_period_s*_fs) {
activated = true;
}
_interruption_frame_count += nframes;
if (_interruption_frame_count >= 2 * _interrupt_period_s*_fs) {
_interruption_frame_count = 0;
}
}
if (activated) {
signal += signal_dynamic;
}
} // end if(!_muted)
return signal; return signal;
} }
void Siggen::setInterruptPeriod(const d newPeriod) {
slock lck(_mtx);
if (newPeriod == 0) {
throw rte("Interruption period cannot be 0");
}
if (newPeriod < 0) {
_interrupt_period_s = -1;
} else {
_interrupt_period_s = newPeriod;
}
}
void Siggen::setFilter(const std::string &name, void Siggen::setFilter(const std::string &name,
std::shared_ptr<Filter> filter) { std::shared_ptr<Filter> filter) {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
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();
} }

View File

@ -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

View File

@ -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,15 +74,15 @@ Sweep::Sweep(const d fl, const d fu, const d Ts, const d Tq, const us flags)
} }
void Sweep::resetImpl() { void Sweep::resetImpl() {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
slock lck(_mtx);
_cur_pos = 0; _cur_pos = 0;
bool forward_sweep = flags & ForwardSweep; bool forward_sweep = flags & ForwardSweep;
bool backward_sweep = flags & BackwardSweep; bool backward_sweep = flags & BackwardSweep;
const d Dt = 1 / _fs; // Deltat const d Dt = 1 / _fs; // Deltat
// Estimate N, the number of samples in the sweep part (non-quiescent part): // Estimate N, the number of samples in the sweep part (non-quiescent part):
const us Ns = (us)(Ts * _fs); const us Ns = (us)(Ts * _fs);
@ -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,15 +249,17 @@ 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!! */
DEBUGTRACE_PRINT(phase); DEBUGTRACE_PRINT(phase);
} }
} // End of log sweep } // End of log sweep
else { else {
// Either log or linear sweep had to be given as flags. // Either log or linear sweep had to be given as flags.
assert(false); assert(false);
} }
} }

View File

@ -7,7 +7,7 @@
*/ */
/** /**
* \addtogroup siggen * \addtogroup siggen
* @{ * @{
*/ */
@ -18,8 +18,8 @@ class Noise : public Siggen {
d level_linear; d level_linear;
virtual vd genSignalUnscaled(const us nframes) override; virtual vd genSignalUnscaled(const us nframes) override;
void resetImpl() override; void resetImpl() override;
public:
public:
/** /**
* @brief Constructs a noise generator. If no filter is used, the output will * @brief Constructs a noise generator. If no filter is used, the output will
* be white noise. By default, the output will be standard deviation = 1 * be white noise. By default, the output will be standard deviation = 1
@ -28,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; } ;
}; };
/** /**
@ -84,7 +86,8 @@ 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;
}; };
/** @} */ /** @} */
/** @} */ /** @} */

View File

@ -1,15 +1,13 @@
// #define DEBUGTRACE_ENABLED /* #define DEBUGTRACE_ENABLED */
#include "lasp_threadedindatahandler.h" #include "lasp_threadedindatahandler.h"
#include "debugtrace.hpp"
#include "lasp_daqdata.h"
#include "lasp_thread.h"
#include <future> #include <future>
#include <optional> #include <optional>
#include <queue> #include <queue>
#include <thread> #include <thread>
#include "debugtrace.hpp"
#include "lasp_daqdata.h"
#include "lasp_thread.h"
using namespace std::literals::chrono_literals; using namespace std::literals::chrono_literals;
using lck = std::scoped_lock<std::mutex>; using lck = std::scoped_lock<std::mutex>;
using rte = std::runtime_error; using rte = std::runtime_error;
@ -22,26 +20,26 @@ class SafeQueue {
std::mutex _mtx; std::mutex _mtx;
std::atomic<uint32_t> _contents{0}; std::atomic<uint32_t> _contents{0};
public: public:
void push(const DaqData &d) { void push(const DaqData &d) {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
lck lock(_mtx); lck lock(_mtx);
_queue.push(d); _queue.push(d);
_contents++; _contents++;
assert(_contents == _queue.size()); assert(_contents == _queue.size());
} }
DaqData pop() { DaqData pop() {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
if (empty()) { if (empty()) {
throw rte("BUG: Pop on empty queue"); throw rte("BUG: Pop on empty queue");
} }
lck lock(_mtx); lck lock(_mtx);
/* DaqData d(std::move(_queue.front())); */ /* DaqData d(std::move(_queue.front())); */
DaqData d(_queue.front()); DaqData d(_queue.front());
_queue.pop(); _queue.pop();
_contents--; _contents--;
assert(_contents == _queue.size()); assert(_contents == _queue.size());
return d; return d;
} }
/** /**
@ -54,75 +52,58 @@ class SafeQueue {
}; };
ThreadedInDataHandlerBase::ThreadedInDataHandlerBase(SmgrHandle mgr, ThreadedInDataHandlerBase::ThreadedInDataHandlerBase(SmgrHandle mgr,
InCallbackType cb, InCallbackType cb,
ResetCallbackType reset) InResetType reset)
: _queue(std::make_unique<SafeQueue>()), : _indatahandler(
inCallback(cb), mgr,
resetCallback(reset), std::bind(&ThreadedInDataHandlerBase::_inCallbackFromInDataHandler, this,
_smgr(mgr) { _1),
reset),
_queue(std::make_unique<SafeQueue>()), inCallback(cb) {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
} }
void ThreadedInDataHandlerBase::startThread() { void ThreadedInDataHandlerBase::startThread() {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
if (_indatahandler) { _thread_can_safely_run = true;
throw rte("BUG: ThreadedIndataHandler already started"); _indatahandler.start();
}
SmgrHandle smgr = _smgr.lock();
if (!smgr) {
cerr << "Stream manager destructed" << endl;
return;
}
_indatahandler = std::make_unique<InDataHandler>(
smgr,
std::bind(&ThreadedInDataHandlerBase::_inCallbackFromInDataHandler, this,
_1),
resetCallback);
_thread_allowed_to_run = true;
_indatahandler->start();
} }
void ThreadedInDataHandlerBase::_inCallbackFromInDataHandler( void ThreadedInDataHandlerBase::_inCallbackFromInDataHandler(
const DaqData &daqdata) { const DaqData &daqdata) {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
std::scoped_lock lck(_mtx);
// Early return in case object is under DESTRUCTION // Early return in case object is under DESTRUCTION
if (!_thread_allowed_to_run) return; if (!_thread_can_safely_run)
return;
_queue->push(daqdata); _queue->push(daqdata);
if (!_thread_running) { if (!_thread_running) {
DEBUGTRACE_PRINT("Pushing new thread in pool"); DEBUGTRACE_PRINT("Pushing new thread in pool");
_thread_running = true;
_pool.push_task(&ThreadedInDataHandlerBase::threadFcn, this); _pool.push_task(&ThreadedInDataHandlerBase::threadFcn, this);
} }
} }
void ThreadedInDataHandlerBase::stopThread() { void ThreadedInDataHandlerBase::stopThread() {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
if (!_indatahandler) { // Make sure inCallback is no longer called
throw rte("BUG: ThreadedIndataHandler not running"); _thread_can_safely_run = false;
} _indatahandler.stop();
// Stop the existing thread std::scoped_lock lck(_mtx);
_thread_allowed_to_run = false;
// Make sure no new data arrives
_indatahandler->stop();
_indatahandler.reset();
DEBUGTRACE_PRINT("Indatahandler stopped. Waiting for thread to finish...");
// Then wait in steps for the thread to stop running. // Then wait in steps for the thread to stop running.
while (_thread_running) { while (_thread_running) {
std::this_thread::sleep_for(10us); std::this_thread::sleep_for(10us);
} }
DEBUGTRACE_PRINT("Thread stopped");
// Kill the handler
DEBUGTRACE_PRINT("Handler resetted");
} }
ThreadedInDataHandlerBase::~ThreadedInDataHandlerBase() { ThreadedInDataHandlerBase::~ThreadedInDataHandlerBase() {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
if (_thread_allowed_to_run) { if (_thread_can_safely_run) {
stopThread(); stopThread();
cerr << "*** BUG: InDataHandlers have not been all stopped, while " cerr << "*** BUG: InDataHandlers have not been all stopped, while "
"StreamMgr destructor is called. This is a misuse BUG." "StreamMgr destructor is called. This is a misuse BUG."
@ -132,9 +113,12 @@ ThreadedInDataHandlerBase::~ThreadedInDataHandlerBase() {
} }
void ThreadedInDataHandlerBase::threadFcn() { void ThreadedInDataHandlerBase::threadFcn() {
DEBUGTRACE_ENTER;
while (!_queue->empty() && _thread_allowed_to_run) { DEBUGTRACE_ENTER;
_thread_running = true;
while (!_queue->empty() && _thread_can_safely_run) {
// Call inCallback_threaded // Call inCallback_threaded
inCallback(_queue->pop()); inCallback(_queue->pop());
} }

View File

@ -29,27 +29,21 @@ class ThreadedInDataHandlerBase {
* @brief The queue used to push elements to the handling thread. * @brief The queue used to push elements to the handling thread.
*/ */
InDataHandler _indatahandler;
std::unique_ptr<SafeQueue> _queue; std::unique_ptr<SafeQueue> _queue;
mutable std::recursive_mutex _mtx;
std::atomic<bool> _thread_running{false};
std::atomic<bool> _thread_can_safely_run{false};
GlobalThreadPool _pool;
/** /**
* @brief Function pointer that is called when new DaqData arrives. * @brief Function pointer that is called when new DaqData arrives.
*/ */
const InCallbackType inCallback; const InCallbackType inCallback;
/**
* @brief Function pointer that is called when reset() is called.
*/
const ResetCallbackType resetCallback;
std::weak_ptr<StreamMgr> _smgr;
std::unique_ptr<InDataHandler> _indatahandler;
std::atomic<bool> _thread_running{false};
std::atomic<bool> _thread_allowed_to_run{false};
GlobalThreadPool _pool;
void threadFcn(); void threadFcn();
@ -64,7 +58,7 @@ class ThreadedInDataHandlerBase {
void _inCallbackFromInDataHandler(const DaqData &daqdata); void _inCallbackFromInDataHandler(const DaqData &daqdata);
public: public:
ThreadedInDataHandlerBase(SmgrHandle mgr, InCallbackType cb, ResetCallbackType reset); ThreadedInDataHandlerBase(SmgrHandle mgr, InCallbackType cb, InResetType reset);
~ThreadedInDataHandlerBase(); ~ThreadedInDataHandlerBase();
/** /**
* @brief This method should be called from the derived class' constructor, * @brief This method should be called from the derived class' constructor,

View File

@ -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); }

View File

@ -209,7 +209,6 @@ class FilterBankDesigner:
Returns: Returns:
h: Linear filter transfer function [-] h: Linear filter transfer function [-]
""" """
fs = self.fs
fir = self.createFirFilter(fs, x) fir = self.createFirFilter(fs, x)
# Decimated sampling frequency [Hz] # Decimated sampling frequency [Hz]

View File

@ -11,9 +11,7 @@ __all__ = ['freqResponse', 'bandpass_fir_design', 'lowpass_fir_design',
'arbitrary_fir_design'] 'arbitrary_fir_design']
import numpy as np import numpy as np
from scipy.signal import freqz, firwin2 from scipy.signal import freqz, hann, firwin2
from scipy.signal.windows import hann
from ..lasp_config import empty
def freqResponse(fs, freq, coefs_b, coefs_a=1.): def freqResponse(fs, freq, coefs_b, coefs_a=1.):
@ -46,7 +44,7 @@ def bandpass_fir_design(L, fs, fl, fu, window=hann):
Omg2 = 2*np.pi*fu/fs Omg2 = 2*np.pi*fu/fs
Omg1 = 2*np.pi*fl/fs Omg1 = 2*np.pi*fl/fs
fir = empty(L, dtype=float) fir = np.empty(L, dtype=float)
# First Create ideal band-pass filter # First Create ideal band-pass filter
fir[L//2] = (Omg2-Omg1)/np.pi fir[L//2] = (Omg2-Omg1)/np.pi
@ -66,7 +64,7 @@ def lowpass_fir_design(L, fs, fc, window=hann):
" than upper cut-off" " than upper cut-off"
Omgc = 2*np.pi*fc/fs Omgc = 2*np.pi*fc/fs
fir = empty(L, dtype=float) fir = np.empty(L, dtype=float)
# First Create ideal band-pass filter # First Create ideal band-pass filter
fir[L//2] = Omgc/np.pi fir[L//2] = Omgc/np.pi

View File

@ -33,7 +33,7 @@ class Atomic:
def checkType(self, val): def checkType(self, val):
if not (type(val) == bool or type(val) == int): if not (type(val) == bool or type(val) == int):
raise ValueError("Invalid type for Atomic") raise RuntimeError("Invalid type for Atomic")
def __iadd__(self, toadd): def __iadd__(self, toadd):
self.checkType(toadd) self.checkType(toadd)

View File

@ -62,9 +62,8 @@ class Qty:
name: str name: str
# I.e.: Pascal # I.e.: Pascal
unit_name: str unit_name: str
# I.e.: -, Pa, V # I.e.: Pa
unit_symb: str unit_symb: str
# I.e.: ('dB SPL') <== tuple of possible level units # I.e.: ('dB SPL') <== tuple of possible level units
level_unit: object level_unit: object
# Contains a tuple of possible level names, including its reference value. # Contains a tuple of possible level names, including its reference value.
@ -93,18 +92,6 @@ class Qty:
""" """
return self.cpp_enum.value return self.cpp_enum.value
@property
def unit_symb_eq(self):
"""Unit symbol to be used in equations
Returns:
String: V, Pa, 1,
"""
if self.unit_symb != '-':
return self.unit_symb
else:
return '1'
@unique @unique
@ -121,7 +108,7 @@ class SIQtys(Enum):
unit_name='Pascal', unit_name='Pascal',
unit_symb='Pa', unit_symb='Pa',
level_unit=('dB SPL','dBPa'), level_unit=('dB SPL','dBPa'),
level_ref_name=('20 micropascal', '1 pascal',), level_ref_name=('2 micropascal', '1 pascal',),
level_ref_value=(P_REF, 1), level_ref_value=(P_REF, 1),
cpp_enum = DaqChannel.Qty.AcousticPressure cpp_enum = DaqChannel.Qty.AcousticPressure
) )

View File

@ -9,6 +9,9 @@
#ifndef LASP_CONFIG_H #ifndef LASP_CONFIG_H
#define LASP_CONFIG_H #define LASP_CONFIG_H
const int LASP_VERSION_MAJOR = @CMAKE_PROJECT_VERSION_MAJOR@;
const int LASP_VERSION_MINOR = @CMAKE_PROJECT_VERSION_MINOR@;
/* Debug flag */ /* Debug flag */
#cmakedefine01 LASP_DEBUG #cmakedefine01 LASP_DEBUG

22
src/lasp/lasp_config.py Normal file
View File

@ -0,0 +1,22 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""!
Author: J.A. de Jong - ASCEE
Description: LASP configuration
"""
import numpy as np
LASP_NUMPY_FLOAT_TYPE = np.float64
def zeros(shape):
return np.zeros(shape, dtype=LASP_NUMPY_FLOAT_TYPE, order='F')
def ones(shape):
return np.ones(shape, dtype=LASP_NUMPY_FLOAT_TYPE, order='F')
def empty(shape):
return np.empty(shape, dtype=LASP_NUMPY_FLOAT_TYPE, order='F')

Some files were not shown because too many files have changed in this diff Show More