Compare commits

...

59 Commits

Author SHA1 Message Date
bf5d006aef Merge branch 'master' into develop
All checks were successful
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in -54s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2024-06-24 16:34:29 +02:00
a443be6e39 Updated readme for RPI install
Some checks failed
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Blocked by required conditions
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Has been cancelled
2024-06-24 16:33:31 +02:00
27da426d66 Bump v1.6.6
All checks were successful
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in -55s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Successful in -5m20s
2024-06-24 02:11:56 -07:00
fa51d6e81c Bump 1.6.5
All checks were successful
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in -54s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2024-06-24 10:00:56 +02:00
832302bba2 Some warnings fixed. The rest is markup suggestions from black, of which part is committed 2024-06-24 09:56:35 +02:00
dd3aa5a0d6 Merge remote-tracking branch 'origin/develop' into develop 2024-06-24 09:45:25 +02:00
a1781fa66c Bump version due to bugfix
All checks were successful
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in -59s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Successful in -5m16s
2024-06-24 09:45:10 +02:00
1cc6b9106d Bugfix: float129 -> complex128 2024-06-24 09:44:19 +02:00
9bcc2e4173 Update comments + minor change
All checks were successful
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in -52s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2024-06-21 14:05:53 +02:00
c8a4ded750 Checkout v3. v4 seems to be buggy.
All checks were successful
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in -48s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Successful in -4m36s
2024-06-19 10:15:28 +02:00
c7f8ac7122 Added extra readme
Some checks failed
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after -5m21s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2024-06-18 09:14:58 +02:00
afffa0b2ca Bugfix to accomodate scipy versions >=1.13. Fixed the version of scipy to be at least this one. Updated readme regarding version bumping
Some checks failed
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after -5m20s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2024-06-18 09:11:46 +02:00
a1802686d1 Removed lasp_imptube. Old code not of use anymore 2024-06-18 09:01:44 +02:00
96b3fd5371 Merged in rpi. 2024-06-17 15:51:21 +02:00
74bfdef921 Updated portaudio
Some checks failed
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after -5m18s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2024-06-17 15:50:33 +02:00
e3bcfa30ce Updated portaudio
Some checks failed
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after -5m18s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2024-06-17 12:10:31 +02:00
41e748c2f5 Made code to compile and probably work with 32-bits floating point. This requires quite some testing to be done
Some checks failed
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after -4m54s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2024-06-03 17:28:51 +02:00
d24b5fb00b Merge remote-tracking branch 'origin/develop' into rpi
Some checks failed
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after -4m55s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2024-06-03 16:58:28 +02:00
92fb5c1d76 Updated CMakeLists to compile with correct flags and settings
Some checks failed
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after -4m54s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2024-06-03 16:57:12 +02:00
35dc6885aa Bump 1.6.1 version lock scipy
All checks were successful
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 1m16s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2024-04-05 07:12:04 -07:00
b10564dc49 Scipy version locked to 1.12. Needs a fix in scipy.signal. TBD 2024-04-05 07:11:39 -07:00
3738012c3e Merge remote-tracking branch 'origin/develop' into develop
Some checks failed
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after -4m54s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2024-04-05 06:49:57 -07:00
a91640cd8d Explicit picking of driver for windows. 2024-04-05 06:49:48 -07:00
0bf621e45c Updated documentation for Windows installation
Some checks failed
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Blocked by required conditions
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Has been cancelled
2024-04-05 15:49:20 +02:00
1f7deca3fd Updated scripts for building. Documentation follows
All checks were successful
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 1m26s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2024-03-29 04:34:24 -07:00
1fb98412b2 Removed hard-coded Numpy include dir
All checks were successful
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 1m35s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2024-03-27 13:55:24 +01:00
d50dd35745 Silence warnings from portaudio ALSA backend during device enumeration. Do device enumeration on background thread
All checks were successful
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 1m30s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2024-03-27 13:45:13 +01:00
1765042d20 Downgraded a logging.info() to logging.debug
All checks were successful
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 1m45s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2024-03-19 14:17:59 +01:00
46d1eda94d Merged in develop
All checks were successful
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 1m51s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2024-03-19 13:40:39 +01:00
3005f17400 Added extra getReferemenceMeasurements() method to MeasurementSet. Bumped therefore to v1.6.0 2024-03-19 13:39:17 +01:00
33439354f8 Sort MeasurementSet by time stamp
All checks were successful
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 1m56s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2024-03-14 11:31:07 +01:00
da023273d8 Bump 1.5.1
Some checks failed
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Blocked by required conditions
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Has been cancelled
2024-03-14 08:47:32 +01:00
84db689e56 Ignore error on rm when no files in build copy dir
Some checks failed
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Blocked by required conditions
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Has been cancelled
2024-03-14 08:43:47 +01:00
83c7aa6ade More subtle locking and unlocking of mutexes in stopstream
Some checks failed
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after 2m1s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2024-03-14 08:25:47 +01:00
3c16e33453 Removed deadlock in output stream deletion
Some checks failed
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after 2m1s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2024-03-13 13:29:29 +01:00
e973f14884 Weak refs to Recording methods. Made the mutexes more simple for stream manager. Added extra guards and statements here and there. Code passes a sever stress test.
Some checks failed
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after 2m3s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2024-03-13 12:19:24 +01:00
e24cac2805 Some more bugfixes: weak references stored in indatahandler, to avoid calling destructor from wrong thread. Removed some unneccessary include statements on the way 2024-03-12 21:13:13 +01:00
d0d494fcb2 Added some stuff to gitignore, removed explicit dependency on Numpy
Some checks failed
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after 2m1s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2024-03-12 15:53:37 +01:00
15cd62baf8 New smoothing algorithm - minor version bump
Some checks failed
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after 2m6s
2024-03-12 11:20:31 +01:00
ab080910fc Made power correction in smoothing algorithm optional. Window decreases in size symmetrically around the edged of the frequency spectrum
Some checks failed
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Blocked by required conditions
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Has been cancelled
2024-03-12 11:19:52 +01:00
6799ee9287 Bugfix new smoother, including ac signal power correction
Some checks failed
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after 2m1s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2024-03-12 09:21:07 +01:00
f9cf059c90 Forgot to actually commit the Cpp files of the smoother
Some checks failed
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after 8s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2024-03-11 16:33:28 +01:00
3ec15ec645 New smoothing implementation, that runs a bit faster
Some checks failed
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after -1m19s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2024-03-11 16:04:24 +01:00
48d262fbf0 Bugfix in sensitivity correction of realtime spectra
Some checks failed
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after 2m8s
2024-03-07 09:36:50 +01:00
204e431d79 Bugfix on GIL release
Some checks failed
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after 2m9s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2024-03-06 22:12:42 +01:00
bf06402b11 BUGfix of segfault. Very subtle. ThreadedInDataHandler could be deleted, while a task was just pushed to the thread pool. Then, when the task is finally run, the object could be deleted, as the _thread_running flag was not set. Besides this, we made some fixes that makes sure that the handles to a Recording class are stored as a weakref inside of the C++ code. This makes it easier to garbage-collect a recording, even when the IndataHandler is still running. 2024-03-06 21:41:04 +01:00
26eef040a4 More locks on signal generator.
Some checks failed
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after 1m33s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2024-03-04 15:49:29 +01:00
b61e836f35 Bumped 1.4.6
Some checks failed
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after 1m38s
2024-03-04 14:44:37 +01:00
0841dbd73b Create InDataHandler only from the moment startThread() is called. This is safer, and might fix a segfault 2024-03-04 14:44:00 +01:00
5e8e40db7a Updated tag. forgot in previous tag updates
Some checks failed
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after 1m47s
2024-02-29 20:05:10 +01:00
3b2f2f7c41 Bugfix record indefinitely
Some checks failed
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after 1m44s
2024-02-27 11:02:45 +01:00
878da3369b Bugfix (delete measurement when no data is in it) and cleanup of recording code
Some checks failed
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after 1m49s
2024-02-26 11:51:59 +01:00
e9f500d460 Small change in portaudio.cmake
Some checks failed
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after 2m42s
2024-02-20 15:47:12 +01:00
6bda124196 Allow duplex mode for PortAudio ALSA devices
All checks were successful
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 2m53s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2024-02-06 15:02:25 +01:00
7ce45e9c82 Some comment improvements, and portaudio API improvements. Also, disabled PortAudio PulseAudio backend as it is not working properly.
All checks were successful
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 2m35s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Successful in -31s
2024-02-06 14:59:51 +01:00
7c8e6368ba Removed accidental use of wrong time weighting for impulse (35 ms). 2024-02-06 11:22:31 +01:00
7430e2c600 Updated armadillo
All checks were successful
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 3m0s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2024-01-30 14:40:43 +01:00
6b337df2a9 Bugfix in channel counter for getHighestEnabledOutChannel, added Api -subapis for Portaudio backend. Switch to defaulting Portaudio as audio backend. Added PulseAudio as extra sub-api to default compile in portaudio
All checks were successful
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 3m2s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2024-01-25 15:31:53 +01:00
c713806bbe RtAudio not updated to 6.0.1?
All checks were successful
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 2m52s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2024-01-23 15:18:04 +01:00
52 changed files with 1490 additions and 1043 deletions

View File

@ -12,7 +12,7 @@ jobs:
- lasp_dist:/dist - lasp_dist:/dist
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v3
with: with:
submodules: true submodules: true
@ -25,7 +25,7 @@ jobs:
- name: Cleanup old dist files and copy new to /dist dir - name: Cleanup old dist files and copy new to /dist dir
run: |- run: |-
rm /dist/* rm -f /dist/*
cp -v dist/* /dist cp -v dist/* /dist
Release-Ubuntu: Release-Ubuntu:

3
.gitignore vendored
View File

@ -21,3 +21,6 @@ acme_log.log
.venv .venv
.py-build-cmake_cache .py-build-cmake_cache
cpp_src/lasp_config.h cpp_src/lasp_config.h
.cache
.vscode
build

View File

@ -1,24 +1,35 @@
cmake_minimum_required(VERSION 3.16) cmake_minimum_required(VERSION 3.16)
project(LASP LANGUAGES C CXX VERSION 1.1) project(LASP LANGUAGES C CXX VERSION 1.6.3)
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED) set(CMAKE_CXX_STANDARD_REQUIRED)
option(LASP_DOUBLE_PRECISION "Compile as double precision floating point" ON) if(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "aarch64")
set(RPI TRUE)
else()
set(RPI FALSE)
endif()
# Setting defaults for PortAudio and RtAudio backend, depending on Linux / # Setting defaults for PortAudio and RtAudio backend, depending on Linux /
# Windows. # Windows.
set(DEFAULT_DOUBLE_PRECISION ON)
if(WIN32) if(WIN32)
set(DEFAULT_RTAUDIO OFF) set(DEFAULT_RTAUDIO OFF)
set(DEFAULT_PORTAUDIO ON) set(DEFAULT_PORTAUDIO ON)
set(DEFAULT_ULDAQ OFF) set(DEFAULT_ULDAQ OFF)
elseif(${RPI})
set(DEFAULT_RTAUDIO OFF)
set(DEFAULT_PORTAUDIO ON)
set(DEFAULT_ULDAQ OFF)
set(DEFAULT_DOUBLE_PRECISION OFF)
else() else()
set(DEFAULT_RTAUDIO ON) set(DEFAULT_RTAUDIO OFF)
set(DEFAULT_PORTAUDIO OFF) set(DEFAULT_PORTAUDIO ON)
set(DEFAULT_ULDAQ ON) set(DEFAULT_ULDAQ ON)
endif() endif()
option(LASP_DOUBLE_PRECISION "Compile as double precision floating point" ${DEFAULT_DOUBLE_PRECISION})
option(LASP_HAS_RTAUDIO "Compile with RtAudio Daq backend" ${DEFAULT_RTAUDIO}) option(LASP_HAS_RTAUDIO "Compile with RtAudio Daq backend" ${DEFAULT_RTAUDIO})
option(LASP_HAS_PORTAUDIO "Compile with PortAudio Daq backend" ${DEFAULT_PORTAUDIO}) option(LASP_HAS_PORTAUDIO "Compile with PortAudio Daq backend" ${DEFAULT_PORTAUDIO})
if(LASP_HAS_PORTAUDIO AND LASP_HAS_RTAUDIO) if(LASP_HAS_PORTAUDIO AND LASP_HAS_RTAUDIO)
@ -105,13 +116,20 @@ else()
endif() endif()
# ###################################### Compilation flags # ###################################### Compilation flags
set(CMAKE_C_FLAGS_RELEASE "-O3 -flto -mfpmath=sse -march=x86-64 -mtune=native \ set(CMAKE_C_FLAGS_RELEASE "-O3 -flto -mtune=native \
-fdata-sections -ffunction-sections -fomit-frame-pointer -finline-functions") -fdata-sections -ffunction-sections -fomit-frame-pointer -finline-functions")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-type-limits -Werror=return-type") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-type-limits -Werror=return-type")
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -flto -mtune=native \
-fdata-sections -ffunction-sections -fomit-frame-pointer -finline-functions")
if(NOT ${RPI})
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -mfpmath=sse -march=x86-64")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -mfpmath=sse -march=x86-64")
endif()
set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g -Wall ")
# ############################# End compilation flags # ############################# End compilation flags
include_directories(/usr/lib/python3.10/site-packages/numpy/core/include)
# ####################################### End of user-adjustable variables section # ####################################### End of user-adjustable variables section
include(OSSpecific) include(OSSpecific)

112
README.md
View File

@ -1,4 +1,5 @@
# Library for Acoustic Signal Processing Library for Acoustic Signal Processing
======================================
Welcome to LASP: Library for Acoustic Signal Processing. LASP is a C++ library Welcome to LASP: Library for Acoustic Signal Processing. LASP is a C++ library
@ -43,19 +44,18 @@ in a sister repository [lasp-doc](https://code.ascee.nl/ascee/lasp-doc).
If you have any question(s), please feel free to contact us: [email](info@ascee.nl). If you have any question(s), please feel free to contact us: [email](info@ascee.nl).
# Installation - Linux (Ubuntu-based) # Installation - Linux (Ubuntu-based)
## From wheel (recommended for non-developers) ## Prerequisites
### Prerequisites
Run the following on the command line to install all prerequisites on Run the following on the command line to install all prerequisites on
Debian-based Linux: Debian-based Linux, x86-64:
- `sudo apt install python3-pip libfftw3-3 libopenblas-base libusb-1.0-0 - `sudo apt install python3-pip libfftw3-3 libopenblas-base libusb-1.0-0 libpulse0`
libpulse0`
### Download and install LASP
## Installation from wheel (recommended for non-developers)
Go to: [LASP releases](https://code.ascee.nl/ASCEE/lasp/releases/latest/) and Go to: [LASP releases](https://code.ascee.nl/ASCEE/lasp/releases/latest/) and
download the latest `.whl`. Then run: download the latest `.whl`. Then run:
@ -84,35 +84,65 @@ If building RtAudio with the Jack Audio Connection Kit (JACK) backend, you will
- `$ cd lasp` - `$ cd lasp`
- `pip install -e .` - `pip install -e .`
# Installation - (x86_64) Windows (with WinPython), build with MSYS2 (NOT YET UPDATED!!) # Building and installation for Raspberry Pi (Raspberry Pi OS)
Run the following on the command line to install all prerequisites on
Raspberry Pi OS:
- `sudo apt install libfftw3-dev libopenblas64-dev libhdf5-dev libclalsadrv-dev`
In a virtualenv: install `build`
- `$ pip install build`
Then run:
- `$ git clone --recursive https://code.ascee.nl/ASCEE/lasp.git`
- `$ cd lasp`
- `$ pyproject-build`
Which will generate a `whl` in the `dist` folder, that is redistributable for Raspberry Pis that run Raspberry Pi OS.
When installing the `whl`, it appears that H5PY takes quite some time to install. To follow this process, run it it verbose mode.
# Installation - (x86_64) Windows (with WinPython), build with MSYS2
## Prerequisites ## Prerequisites
- Download and install [WinPython](https://winpython.github.io) - Download and install [WinPython](https://winpython.github.io)
## From wheel
- Download latest wheel from [LASP releases](https://code.ascee.nl/ASCEE/lasp/releases/latest/) and
download the latest `.whl`. Then install with `pip`.
## From source
- Download and install [MSYS2](https://msys2.org). Make sure to install the - Download and install [MSYS2](https://msys2.org). Make sure to install the
x86_64 version. x86_64 version.
- Download and install [Git for Windows](https://git-scm.com)
- When unzipping WinPython, make sure to choose a proper and simple path, i.e. - When unzipping WinPython, make sure to choose a proper and simple path, i.e.
C:\winpython C:\winpython
- Append C:\winpython\ to the PATH environment variable. - Download and install [Git for Windows](https://git-scm.com)
- Run Python and install Pybind11 - Open an MSYS2 **MINGW64** terminal, and install some tools we require:
- `python -m pip install pybind11` - `$ pacman -S git`
- Create a new virtualenv:
- `$ /c/winpython/<py-distr-dir>/python.exe -m venv venv`
- Open a msys2 **MINGW64** terminal. And run: - Add the venv-python to the path (eases a lot of commands)
- `$ export PATH=$PATH:~/venv/Scripts`
- `pacman -S git` - Install `build`:
- `$ pip install build`
- Then clone the LASP repo: - Clone LASP:
- `$ git clone --recurse-submodules https://code.ascee.nl/ascee/lasp && cd lasp`
- `git clone https://code.ascee.nl/ascee/lasp` - If run for the first time, we have to install the libraries we depend on in
- `cd lasp` MSYS2 (this only has to be done on a fresh MSYS2 installation):
- `$ scripts/install_msys2_builddeps.sh`
- Configure MSYS2 further, and run cmake: - Copy over required DLL's to be included in distribution:
- `scripts/copy_windows_dlls.sh`
- `scripts/install_msys2_buiddeps.sh` - And... build!
- `scripts/configur_cmake_msys2.sh` - `pyproject-build`
- Lastly: the generated wheel can be installed in the current virtualenv:
- `pip install dist/lasp*.whl`
# Documentation # Documentation
@ -121,7 +151,7 @@ If building RtAudio with the Jack Audio Connection Kit (JACK) backend, you will
[Online LASP documentation](https://lasp.ascee.nl/). [Online LASP documentation](https://lasp.ascee.nl/).
## In directory ## In directory (Linux/Debian)
`$ sudo apt install doxygen graphviz` `$ sudo apt install doxygen graphviz`
`$ pip install doxypypy` `$ pip install doxypypy`
@ -138,3 +168,25 @@ This will build the documentation. It can be read by:
- See examples directories for IPython notebooks. - See examples directories for IPython notebooks.
- Please refer to the [documentation](https://lasp.ascee.nl/) for features. - Please refer to the [documentation](https://lasp.ascee.nl/) for features.
# Development docs
## Bumping version number
When bumping the version number, please update the number in
- `pyproject.toml`
- `CMakeLists.txt`
Then, create a commit with tag `vX.X.X`, and push it.
## Updating to latest version (editable mode)
When updating to the latest version of LASP in editable mode:
```bash
- $ git pull
- $ git submodule update
- $ pip install -e . -v
```

View File

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

View File

@ -38,16 +38,26 @@ pybind11_add_module(lasp_cpp MODULE lasp_cpp.cpp
target_link_libraries(lasp_cpp PRIVATE lasp_device_lib lasp_dsp_lib target_link_libraries(lasp_cpp PRIVATE lasp_device_lib lasp_dsp_lib
${OpenMP_CXX_LIBRARIES} ${LASP_FFT_LIBS} ${TARGET_OS_LINKLIBS}) ${OpenMP_CXX_LIBRARIES} ${LASP_FFT_LIBS} ${TARGET_OS_LINKLIBS})
target_compile_definitions(lasp_cpp PRIVATE
MODULE_NAME=$<TARGET_FILE_BASE_NAME:lasp_cpp>
VERSION_INFO="${PY_FULL_VERSION}"
)
# Hide all symbols by default (including external libraries on Linux)
if(CMAKE_BUILD_TYPE STREQUAL "Release")
set_target_properties(lasp_cpp PROPERTIES
CXX_VISIBILITY_PRESET "hidden"
VISIBILITY_INLINES_HIDDEN true)
if (CMAKE_SYSTEM_NAME MATCHES "Linux")
target_link_options(lasp_cpp PRIVATE "LINKER:--exclude-libs,ALL")
endif()
endif()
if(DEFINED PY_BUILD_CMAKE_MODULE_NAME)
# Install the Python module # Install the Python module
install(TARGETS lasp_cpp install(TARGETS lasp_cpp
EXCLUDE_FROM_ALL EXCLUDE_FROM_ALL
COMPONENT python_modules COMPONENT python_modules
DESTINATION ${PY_BUILD_CMAKE_MODULE_NAME}) DESTINATION ${PY_BUILD_CMAKE_MODULE_NAME})
# Install the debug file for the Python module (Windows only)
if (WIN32)
install(FILES $<TARGET_PDB_FILE:_add_module>
EXCLUDE_FROM_ALL
COMPONENT python_modules
DESTINATION ${PY_BUILD_CMAKE_MODULE_NAME}
OPTIONAL)
endif() endif()

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"
@ -56,13 +56,29 @@ Daq::Daq(const DeviceInfo &devinfo, const DaqConfiguration &config)
if (!config.match(devinfo)) { if (!config.match(devinfo)) {
throw rte("DaqConfiguration does not match device info"); throw rte("DaqConfiguration does not match device info");
} }
if (neninchannels(false) > devinfo.ninchannels) {
{
const int hich = getHighestEnabledInChannel();
if (hich + 1 > devinfo.ninchannels)
{
throw rte( throw rte(
"Number of enabled input channels is higher than device capability"); string("Highest of enabled input channel: ") +
to_string(hich) +
string(" is higher than device capability, which is: ") +
to_string(ninchannels) + ".");
} }
if (nenoutchannels() > devinfo.noutchannels) { }
{
const int hoch = getHighestEnabledOutChannel();
if (hoch + 1 > devinfo.noutchannels)
{
throw rte( throw rte(
"Number of enabled output channels is higher than device capability"); string("Highest of enabled output channel: ") +
to_string(hoch) +
string(" is higher than device capability, which is: ") +
to_string(noutchannels) + ".");
}
} }
} }

View File

@ -48,6 +48,10 @@ public:
logicError, logicError,
}; };
// Below the only members of this class, which are public.
bool isRunning = false;
StreamError errorType{StreamError::noError};
/** /**
* @brief Map between error types and messages * @brief Map between error types and messages
*/ */
@ -61,7 +65,7 @@ public:
{StreamError::logicError, "Logic error (probably a bug)"}, {StreamError::logicError, "Logic error (probably a bug)"},
}; };
bool isRunning = false;
/** /**
* @brief Check if stream has error * @brief Check if stream has error
* *
@ -69,8 +73,6 @@ public:
*/ */
bool error() const { return errorType != StreamError::noError; }; bool error() const { return errorType != StreamError::noError; };
StreamError errorType{StreamError::noError};
std::string errorMsg() const { return errorMessages.at(errorType); } std::string errorMsg() const { return errorMessages.at(errorType); }
/** /**

View File

@ -56,10 +56,12 @@ DaqConfiguration::DaqConfiguration(const DeviceInfo &device) {
} }
bool DaqConfiguration::match(const DeviceInfo &dev) const { bool DaqConfiguration::match(const DeviceInfo &dev) const {
DEBUGTRACE_ENTER;
return (dev.device_name == device_name && dev.api == api); return (dev.device_name == device_name && dev.api == api);
} }
int DaqConfiguration::getHighestEnabledInChannel() const { int DaqConfiguration::getHighestEnabledInChannel() const {
DEBUGTRACE_ENTER;
for (int i = inchannel_config.size() - 1; i > -1; i--) { for (int i = inchannel_config.size() - 1; i > -1; i--) {
if (inchannel_config.at(i).enabled) if (inchannel_config.at(i).enabled)
return i; return i;
@ -68,13 +70,15 @@ int DaqConfiguration::getHighestEnabledInChannel() const {
} }
int DaqConfiguration::getHighestEnabledOutChannel() const { int DaqConfiguration::getHighestEnabledOutChannel() const {
for (us i = outchannel_config.size() - 1; i >= 0; i--) { DEBUGTRACE_ENTER;
for (int i = outchannel_config.size() - 1; i > -1; i--) {
if (outchannel_config.at(i).enabled) if (outchannel_config.at(i).enabled)
return i; return i;
} }
return -1; return -1;
} }
int DaqConfiguration::getLowestEnabledInChannel() const { int DaqConfiguration::getLowestEnabledInChannel() const {
DEBUGTRACE_ENTER;
for (us i = 0; i < inchannel_config.size(); i++) { for (us i = 0; i < inchannel_config.size(); i++) {
if (inchannel_config.at(i).enabled) if (inchannel_config.at(i).enabled)
return i; return i;

View File

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

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

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

View File

@ -1,15 +1,21 @@
/* #define DEBUGTRACE_ENABLED */ // #define DEBUGTRACE_ENABLED
#include "lasp_streammgr.h" #include "lasp_streammgr.h"
#include "debugtrace.hpp"
#include "lasp_biquadbank.h"
#include "lasp_indatahandler.h"
#include "lasp_thread.h"
#include <algorithm>
#include <assert.h> #include <assert.h>
#include <algorithm>
#include <functional> #include <functional>
#include <iostream> #include <iostream>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <thread>
#include "debugtrace.hpp"
#include "lasp_biquadbank.h"
#include "lasp_indatahandler.h"
#include "lasp_thread.h"
using namespace std::literals::chrono_literals;
using std::cerr; using std::cerr;
using std::endl; using std::endl;
@ -24,7 +30,7 @@ using rte = std::runtime_error;
std::weak_ptr<StreamMgr> _mgr; std::weak_ptr<StreamMgr> _mgr;
std::mutex _mgr_mutex; std::mutex _mgr_mutex;
using Lck = std::scoped_lock<std::mutex>; using Lck = std::scoped_lock<std::recursive_mutex>;
/** /**
* @brief The only way to obtain a stream manager, can only be called from the * @brief The only way to obtain a stream manager, can only be called from the
@ -35,11 +41,11 @@ using Lck = std::scoped_lock<std::mutex>;
SmgrHandle StreamMgr::getInstance() { SmgrHandle StreamMgr::getInstance() {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
std::scoped_lock<std::mutex> lck(_mgr_mutex);
auto mgr = _mgr.lock(); auto mgr = _mgr.lock();
if (!mgr) { if (!mgr) {
// Double Check Locking Pattern, if two threads would simultaneously // Double Check Locking Pattern, if two threads would simultaneously
// instantiate the singleton instance. // instantiate the singleton instance.
Lck lck(_mgr_mutex);
auto mgr = _mgr.lock(); auto mgr = _mgr.lock();
if (mgr) { if (mgr) {
@ -81,44 +87,75 @@ 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);
} }
} }
#if LASP_HAS_PORTAUDIO && LASP_HAS_PA_ALSA
#include <alsa/asoundlib.h>
void empty_handler(const char *file, int line, const char *function, int err,
const char *fmt, ...) {}
// 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
void StreamMgr::rescanDAQDevices_impl(std::function<void()> callback) { void StreamMgr::rescanDAQDevices_impl(std::function<void()> callback) {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
std::scoped_lock lck(_devices_mtx); assert(!_inputStream && !_outputStream);
Lck lck(_mtx);
// Alsa spits out annoying messages that are not useful
{
MuteErrHandler guard;
_devices = DeviceInfo::getDeviceInfo(); _devices = DeviceInfo::getDeviceInfo();
}
if (callback) { if (callback) {
callback(); callback();
} }
_scanningDevices = false;
} }
void StreamMgr::inCallback(const DaqData &data) { void StreamMgr::inCallback(const DaqData &data) {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
std::scoped_lock<std::mutex> lck(_inDataHandler_mtx); Lck lck(_mtx);
assert(_inputFilters.size() == data.nchannels); assert(_inputFilters.size() == data.nchannels);
if (std::count_if(_inputFilters.cbegin(), _inputFilters.cend(), if (std::count_if(_inputFilters.cbegin(), _inputFilters.cend(),
[](const auto &a) { return bool(a); }) > 0) { [](const auto &a) { return bool(a); }) > 0) {
/// Found a filter in vector of input filters. So we have to apply the /// Found a filter in vector of input filters. So we have to apply the
/// filters to each channel. /// filters to each channel.
@ -138,12 +175,13 @@ void StreamMgr::inCallback(const DaqData &data) {
} }
} }
DEBUGTRACE_PRINT("Calling incallback for handlers (filtered)...");
for (auto &handler : _inDataHandlers) { for (auto &handler : _inDataHandlers) {
handler->inCallback(input_filtered); handler->inCallback(input_filtered);
} }
} else { } else {
/// No input filters /// No input filters
DEBUGTRACE_PRINT("Calling incallback for handlers...");
for (auto &handler : _inDataHandlers) { for (auto &handler : _inDataHandlers) {
handler->inCallback(data); handler->inCallback(data);
} }
@ -151,12 +189,10 @@ void StreamMgr::inCallback(const DaqData &data) {
} }
void StreamMgr::setSiggen(std::shared_ptr<Siggen> siggen) { void StreamMgr::setSiggen(std::shared_ptr<Siggen> siggen) {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
checkRightThread(); checkRightThread();
std::scoped_lock<std::mutex> lck(_siggen_mtx); Lck lck(_mtx);
// If not set to nullptr, and a stream is running, we update the signal // If not set to nullptr, and a stream is running, we update the signal
// generator by resetting it. // generator by resetting it.
if (isStreamRunningOK(StreamType::output) && siggen) { if (isStreamRunningOK(StreamType::output) && siggen) {
@ -179,7 +215,8 @@ void StreamMgr::setSiggen(std::shared_ptr<Siggen> siggen) {
* *
* @return * @return
*/ */
template <typename T> bool fillData(DaqData &data, const vd &signal) { template <typename T>
bool fillData(DaqData &data, const vd &signal) {
/* DEBUGTRACE_ENTER; */ /* DEBUGTRACE_ENTER; */
assert(data.nframes == signal.size()); assert(data.nframes == signal.size());
@ -212,10 +249,9 @@ template <typename T> bool fillData(DaqData &data, const vd &signal) {
return true; return true;
} }
void StreamMgr::outCallback(DaqData &data) { void StreamMgr::outCallback(DaqData &data) {
DEBUGTRACE_ENTER;
/* DEBUGTRACE_ENTER; */ Lck lck(_mtx);
std::scoped_lock<std::mutex> lck(_siggen_mtx);
if (_siggen) { if (_siggen) {
vd signal = _siggen->genSignal(data.nframes); vd signal = _siggen->genSignal(data.nframes);
@ -244,7 +280,17 @@ void StreamMgr::outCallback(DaqData &data) {
StreamMgr::~StreamMgr() { StreamMgr::~StreamMgr() {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
while (_scanningDevices) {
std::this_thread::sleep_for(10us);
}
#if LASP_DEBUG == 1
{ // Careful, this lock needs to be released to make sure the streams can
// obtain a lock to the stream manager.
Lck lck(_mtx);
checkRightThread(); checkRightThread();
}
#endif
// Stream manager now handled by shared pointer. Each indata handler gets a // Stream manager now handled by shared pointer. Each indata handler gets a
// shared pointer to the stream manager, and stores a weak pointer to it. // shared pointer to the stream manager, and stores a weak pointer to it.
// Hence, we do not have to do any cleanup here. It also makes sure that the // Hence, we do not have to do any cleanup here. It also makes sure that the
@ -257,31 +303,35 @@ StreamMgr::~StreamMgr() {
// virtual methods. This was really a bug. // virtual methods. This was really a bug.
_inputStream.reset(); _inputStream.reset();
_outputStream.reset(); _outputStream.reset();
} }
void StreamMgr::stopAllStreams() { void StreamMgr::stopAllStreams() {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
{
Lck lck(_mtx);
checkRightThread(); checkRightThread();
}
// No lock here!
_inputStream.reset(); _inputStream.reset();
_outputStream.reset(); _outputStream.reset();
} }
void StreamMgr::startStream(const DaqConfiguration &config) { void StreamMgr::startStream(const DaqConfiguration &config) {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
if (_scanningDevices) {
throw rte("DAQ device scan is busy. Cannot start stream.");
}
Lck lck(_mtx);
checkRightThread(); checkRightThread();
bool isInput = std::count_if(config.inchannel_config.cbegin(), bool isInput = std::count_if(config.inchannel_config.cbegin(),
config.inchannel_config.cend(), config.inchannel_config.cend(),
[](auto &i) { return i.enabled; }); [](auto &i) { return i.enabled; }) > 0;
bool isOutput = std::count_if(config.outchannel_config.cbegin(), bool isOutput = std::count_if(config.outchannel_config.cbegin(),
config.outchannel_config.cend(), config.outchannel_config.cend(),
[](auto &i) { return i.enabled; }); [](auto &i) { return i.enabled; }) > 0;
// Find the first device that matches with the configuration // Find the first device that matches with the configuration
std::scoped_lock lck(_devices_mtx);
DeviceInfo *devinfo = nullptr; DeviceInfo *devinfo = nullptr;
// Match configuration to a device in the list of devices // Match configuration to a device in the list of devices
@ -302,19 +352,23 @@ void StreamMgr::startStream(const DaqConfiguration &config) {
bool isDuplex = isInput && isOutput; bool isDuplex = isInput && isOutput;
if (!isInput && !isOutput) { if (!isInput && !isOutput) {
throw rte("Neither input, nor output channels enabled for " throw rte(
"stream. Cannot start."); "Attempted stream start failed, stream does not have any enabled "
"channels. Please first enable channels in the channel configuration.");
} }
if (isInput && _inputStream) { if (isInput && _inputStream) {
throw rte("Error: an input stream is already running. Please " throw rte(
"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("Error: output stream is already running. Please " throw rte(
"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("Error: output stream is already running (in duplex mode). " throw rte(
"Error: output stream is already running (in duplex mode). "
"Please " "Please "
"first stop existing stream"); "first stop existing stream");
} }
@ -322,13 +376,15 @@ void StreamMgr::startStream(const DaqConfiguration &config) {
if (_outputStream && isInput && _outputStream->duplexModeForced && if (_outputStream && isInput && _outputStream->duplexModeForced &&
config.match(*_outputStream)) { config.match(*_outputStream)) {
throw rte("This device is already opened for output. If input is also " throw rte(
"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("This device is already opened for input. If output is also " throw rte(
"This device is already opened for input. If output is also "
"required, please enable duplex mode for this device"); "required, please enable duplex mode for this device");
} }
@ -353,8 +409,8 @@ void StreamMgr::startStream(const DaqConfiguration &config) {
/// Create input filters /// Create input filters
_inputFilters.clear(); _inputFilters.clear();
/// No input filter for monitor channel, which comes as the first input channel /// No input filter for monitor channel, which comes as the first input
/// In the list /// channel In the list
if (config.monitorOutput && devinfo->hasInternalOutputMonitor) { if (config.monitorOutput && devinfo->hasInternalOutputMonitor) {
_inputFilters.push_back(nullptr); _inputFilters.push_back(nullptr);
} }
@ -398,45 +454,57 @@ void StreamMgr::startStream(const DaqConfiguration &config) {
} }
} }
void StreamMgr::stopStream(const StreamType t) { void StreamMgr::stopStream(const StreamType t) {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
checkRightThread(); checkRightThread();
bool resetHandlers = false;
std::unique_ptr<Daq> *streamToStop = nullptr;
{ // Mutex locked in this scope
Lck lck(_mtx);
if (t == StreamType::input) { if (t == StreamType::input) {
if (!_inputStream) { if (!_inputStream) {
throw rte("Input stream is not running"); throw rte("Input stream is not running");
} }
/// Kills input stream streamToStop = std::addressof(_inputStream);
_inputStream.reset(); resetHandlers = true;
/// Send reset to all in data handlers
for (auto &handler : _inDataHandlers) {
handler->reset(nullptr);
}
} else { } else {
/// t == output /// t == output
/// Kill input stream in case that one is a duplex stream /// Kill input stream in case that one is a duplex stream
if (_inputStream && _inputStream->duplexMode()) { if (_inputStream && _inputStream->duplexMode()) {
_inputStream.reset(); streamToStop = std::addressof(_inputStream);
} else { } else {
if (!_outputStream) { if (!_outputStream) {
throw rte("Output stream is not running"); throw rte("Output stream is not running");
} }
_outputStream.reset(); streamToStop = std::addressof(_outputStream);
} // end else } // end else
} }
} // End of mutex lock. When stopping stream, mutex should be unlocked.
// If we arrive here, we should have a stream to stop.
assert(streamToStop != nullptr);
streamToStop->reset();
/// Send reset to all in data handlers
if (resetHandlers) {
Lck lck(_mtx);
for (auto &handler : _inDataHandlers) {
handler->reset(nullptr);
}
}
} }
void StreamMgr::addInDataHandler(InDataHandler *handler) { void StreamMgr::addInDataHandler(InDataHandler *handler) {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
Lck lck(_mtx);
checkRightThread(); checkRightThread();
assert(handler); assert(handler);
std::scoped_lock<std::mutex> lck(_inDataHandler_mtx);
handler->reset(_inputStream.get()); handler->reset(_inputStream.get());
if (std::find(_inDataHandlers.cbegin(), _inDataHandlers.cend(), handler) != if (std::find(_inDataHandlers.cbegin(), _inDataHandlers.cend(), handler) !=
_inDataHandlers.cend()) { _inDataHandlers.cend()) {
throw std::runtime_error("Error: handler already added. Probably start() " throw std::runtime_error(
"Error: handler already added. Probably start() "
"is called more than once on a handler object"); "is called more than once on a handler object");
} }
_inDataHandlers.push_back(handler); _inDataHandlers.push_back(handler);
@ -445,16 +513,17 @@ void StreamMgr::addInDataHandler(InDataHandler *handler) {
void StreamMgr::removeInDataHandler(InDataHandler &handler) { void StreamMgr::removeInDataHandler(InDataHandler &handler) {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
checkRightThread(); Lck lck(_mtx);
std::scoped_lock<std::mutex> lck(_inDataHandler_mtx); // checkRightThread();
_inDataHandlers.remove(&handler); _inDataHandlers.remove(&handler);
DEBUGTRACE_PRINT(_inDataHandlers.size()); DEBUGTRACE_PRINT(_inDataHandlers.size());
} }
Daq::StreamStatus StreamMgr::getStreamStatus(const StreamType type) const { Daq::StreamStatus StreamMgr::getStreamStatus(const StreamType type) const {
/* DEBUGTRACE_ENTER; */ DEBUGTRACE_ENTER;
Lck lck(_mtx);
checkRightThread(); checkRightThread();
// Default constructor, says stream is not running, but also no errors // Default constructor, says stream is not running, but also no errors
@ -467,7 +536,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,12 +25,15 @@ class SeriesBiquad;
* fact is asserted. * fact is asserted.
*/ */
class StreamMgr { class StreamMgr {
mutable std::recursive_mutex _mtx;
/** /**
* @brief Storage for streams. * @brief Storage for streams.
*/ */
std::unique_ptr<Daq> _inputStream, _outputStream; std::unique_ptr<Daq> _inputStream, _outputStream;
std::atomic<bool> _scanningDevices{false};
GlobalThreadPool _pool; GlobalThreadPool _pool;
/** /**
@ -39,22 +42,18 @@ class StreamMgr {
* thread-safety. * thread-safety.
*/ */
std::list<InDataHandler *> _inDataHandlers; std::list<InDataHandler *> _inDataHandlers;
mutable std::mutex _inDataHandler_mtx;
/** /**
* @brief Signal generator in use to generate output data. Currently * @brief Signal generator in use to generate output data. Currently
* implemented as to generate the same data for all output channels. * implemented as to generate the same data for all output channels.
*/ */
std::shared_ptr<Siggen> _siggen; std::shared_ptr<Siggen> _siggen;
std::mutex _siggen_mtx;
/** /**
* @brief Filters on input stream. For example, a digital high pass filter. * @brief Filters on input stream. For example, a digital high pass filter.
*/ */
std::vector<std::unique_ptr<SeriesBiquad>> _inputFilters; std::vector<std::unique_ptr<SeriesBiquad>> _inputFilters;
mutable std::recursive_mutex _devices_mtx;
/** /**
* @brief Current storage for the device list * @brief Current storage for the device list
*/ */
@ -67,9 +66,7 @@ class StreamMgr {
friend class InDataHandler; friend class InDataHandler;
friend class Siggen; friend class Siggen;
public: public:
~StreamMgr(); ~StreamMgr();
enum class StreamType : us { enum class StreamType : us {
@ -100,9 +97,10 @@ class StreamMgr {
* @return A copy of the internal stored list of devices * @return A copy of the internal stored list of devices
*/ */
DeviceInfoList getDeviceInfo() const { DeviceInfoList getDeviceInfo() const {
std::scoped_lock lck(_devices_mtx); std::scoped_lock lck(_mtx);
DeviceInfoList d2; DeviceInfoList d2;
for (const auto &dev : _devices) { for (const auto &dev : _devices) {
assert(dev != nullptr);
d2.push_back(dev->clone()); d2.push_back(dev->clone());
} }
return d2; return d2;
@ -118,8 +116,8 @@ class StreamMgr {
* set to true, the function returns immediately. * set to true, the function returns immediately.
* @param callback Function to call when complete. * @param callback Function to call when complete.
*/ */
void void rescanDAQDevices(
rescanDAQDevices(bool background = false, bool background = false,
std::function<void()> callback = std::function<void()>()); std::function<void()> callback = std::function<void()>());
/** /**
@ -197,7 +195,6 @@ private:
void inCallback(const DaqData &data); void inCallback(const DaqData &data);
void outCallback(DaqData &data); void outCallback(DaqData &data);
/** /**
* @brief Add an input data handler. The handler's inCallback() function is * @brief Add an input data handler. The handler's inCallback() function is
* called with data when available. This function should *NOT* be called by * called with data when available. This function should *NOT* be called by

View File

@ -1,25 +1,24 @@
/* #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;
inline void throwIfError(PaError e) inline void throwIfError(PaError e) {
{
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
if (e != paNoError) if (e != paNoError) {
{
throw rte(string("PortAudio backend error: ") + Pa_GetErrorText(e)); throw rte(string("PortAudio backend error: ") + Pa_GetErrorText(e));
} }
} }
@ -27,62 +26,96 @@ 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.
*/ */
PaDeviceInfo _paDevInfo; PaDeviceInfo _paDevInfo;
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;
try bool shouldPaTerminate = false;
{ 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");
err = Pa_Terminate(); err = Pa_Terminate();
if (err != paNoError) { if (err != paNoError) {
cerr << "Error terminating PortAudio. Do not know what to do." << endl; cerr << "Error terminating PortAudio. Do not know what to do." << endl;
} }); }
});
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) {
{
throw rte("PortAudio could not find any devices"); throw rte("PortAudio could not find any devices");
} }
for (us i = 0; i < (us)numDevices; i++) for (us i = 0; i < (us)numDevices; i++) {
{
/* DEBUGTRACE_PRINT(i); */ /* DEBUGTRACE_PRINT(i); */
bool hasDuplexMode = false;
const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(i); const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(i);
if (!deviceInfo) if (!deviceInfo) {
{
throw rte("No device info struct returned"); throw rte("No device info struct returned");
} }
OurPaDeviceInfo d; OurPaDeviceInfo d(*deviceInfo);
d._paDevInfo = *deviceInfo;
// We store the name in d.device_name // We store the name in d.device_name
d._paDevInfo.name = nullptr; 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};
@ -105,12 +138,20 @@ void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist)
d.ninchannels = deviceInfo->maxInputChannels; d.ninchannels = deviceInfo->maxInputChannels;
d.noutchannels = deviceInfo->maxOutputChannels; d.noutchannels = deviceInfo->maxOutputChannels;
// Duplex mode, only for ALSA devices
d.hasDuplexMode = hasDuplexMode;
devinfolist.push_back(std::make_unique<OurPaDeviceInfo>(d)); devinfolist.push_back(std::make_unique<OurPaDeviceInfo>(d));
} }
} }
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;
} }
@ -135,9 +176,7 @@ static int rawPaCallback(const void *inputBuffer, void *outputBuffer,
const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackTimeInfo *timeInfo,
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;
@ -173,13 +212,11 @@ public:
}; };
std::unique_ptr<Daq> createPortAudioDevice(const DeviceInfo &devinfo, std::unique_ptr<Daq> createPortAudioDevice(const DeviceInfo &devinfo,
const DaqConfiguration &config) const DaqConfiguration &config) {
{ DEBUGTRACE_ENTER;
const OurPaDeviceInfo *_info = const OurPaDeviceInfo *_info =
dynamic_cast<const OurPaDeviceInfo *>(&devinfo); dynamic_cast<const OurPaDeviceInfo *>(&devinfo);
if (_info == nullptr) if (_info == nullptr) {
{
throw rte("BUG: Could not cast DeviceInfo to OurPaDeviceInfo"); throw rte("BUG: Could not cast DeviceInfo to OurPaDeviceInfo");
} }
return std::make_unique<PortAudioDaq>(*_info, config); return std::make_unique<PortAudioDaq>(*_info, config);
@ -188,38 +225,35 @@ std::unique_ptr<Daq> createPortAudioDevice(const DeviceInfo &devinfo,
static int rawPaCallback(const void *inputBuffer, void *outputBuffer, static int rawPaCallback(const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer, unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackTimeInfo *timeInfo,
PaStreamCallbackFlags statusFlags, void *userData) PaStreamCallbackFlags statusFlags, void *userData) {
{
return static_cast<PortAudioDaq *>(userData)->memberPaCallback( return static_cast<PortAudioDaq *>(userData)->memberPaCallback(
inputBuffer, outputBuffer, framesPerBuffer, timeInfo, statusFlags); inputBuffer, outputBuffer, framesPerBuffer, timeInfo, statusFlags);
} }
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;
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);
// OK, Pa_Initialize successfully finished, it means we have to clean up with // OK, Pa_Initialize successfully finished, it means we have to clean up
// Pa_Terminate in the destructor. // with Pa_Terminate in the destructor.
_shouldPaTerminate = true; shouldPaTerminate = true;
// Going to find the device in the list. If its there, we have to retrieve // Going to find the device in the list. If its there, we have to retrieve
// the index, as this is required in the PaStreamParameters struct // the index, as this is required in the PaStreamParameters struct
int devindex = -1; int devindex = -1;
for (int i = 0; i < Pa_GetDeviceCount(); i++) for (int i = 0; i < Pa_GetDeviceCount(); i++) {
{
// DEBUGTRACE_PRINT(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 &= string(info->name) == devinfo_gen.device_name;
@ -228,13 +262,11 @@ PortAudioDaq::PortAudioDaq(const OurPaDeviceInfo &devinfo_gen,
ok &= info->maxOutputChannels == devinfo_gen._paDevInfo.maxOutputChannels; ok &= info->maxOutputChannels == devinfo_gen._paDevInfo.maxOutputChannels;
ok &= info->defaultSampleRate == devinfo_gen._paDevInfo.defaultSampleRate; ok &= info->defaultSampleRate == devinfo_gen._paDevInfo.defaultSampleRate;
if (ok) if (ok) {
{
devindex = i; devindex = i;
} }
} }
if (devindex < 0) if (devindex < 0) {
{
throw rte(string("Device not found: ") + string(devinfo_gen.device_name)); throw rte(string("Device not found: ") + string(devinfo_gen.device_name));
} }
@ -242,8 +274,7 @@ PortAudioDaq::PortAudioDaq(const OurPaDeviceInfo &devinfo_gen,
const Dtype dtype = dataType(); const Dtype dtype = dataType();
// Sample format is bit flag // Sample format is bit flag
PaSampleFormat format = paNonInterleaved; PaSampleFormat format = paNonInterleaved;
switch (dtype) switch (dtype) {
{
case Dtype::dtype_fl32: case Dtype::dtype_fl32:
DEBUGTRACE_PRINT("Datatype float32"); DEBUGTRACE_PRINT("Datatype float32");
format |= paFloat32; format |= paFloat32;
@ -272,19 +303,17 @@ PortAudioDaq::PortAudioDaq(const OurPaDeviceInfo &devinfo_gen,
std::unique_ptr<PaStreamParameters> instreamParams; std::unique_ptr<PaStreamParameters> instreamParams;
std::unique_ptr<PaStreamParameters> outstreamParams; std::unique_ptr<PaStreamParameters> outstreamParams;
if (neninchannels() > 0) if (neninchannels() > 0) {
{ instreamParams = std::make_unique<PaStreamParameters>(PaStreamParameters(
instreamParams = std::make_unique<PaStreamParameters>( {.device = devindex,
PaStreamParameters({.device = devindex,
.channelCount = (int)getHighestEnabledInChannel() + 1, .channelCount = (int)getHighestEnabledInChannel() + 1,
.sampleFormat = format, .sampleFormat = format,
.suggestedLatency = framesPerBlock() / samplerate(), .suggestedLatency = framesPerBlock() / samplerate(),
.hostApiSpecificStreamInfo = nullptr})); .hostApiSpecificStreamInfo = nullptr}));
} }
if (nenoutchannels() > 0) if (nenoutchannels() > 0) {
{ outstreamParams = std::make_unique<PaStreamParameters>(PaStreamParameters(
outstreamParams = std::make_unique<PaStreamParameters>( {.device = devindex,
PaStreamParameters({.device = devindex,
.channelCount = (int)getHighestEnabledOutChannel() + 1, .channelCount = (int)getHighestEnabledOutChannel() + 1,
.sampleFormat = format, .sampleFormat = format,
.suggestedLatency = framesPerBlock() / samplerate(), .suggestedLatency = framesPerBlock() / samplerate(),
@ -304,28 +333,28 @@ PortAudioDaq::PortAudioDaq(const OurPaDeviceInfo &devinfo_gen,
paNoFlag, // streamFlags paNoFlag, // streamFlags
rawPaCallback, this); rawPaCallback, this);
throwIfError(err); throwIfError(err);
assert(_stream);
} catch (rte &e) {
if (shouldPaTerminate) {
PaError err = Pa_Terminate();
if (err != paNoError) {
cerr << "Error terminating PortAudio. Do not know what to do." << endl;
}
}
throw;
}
} }
void PortAudioDaq::start(InDaqCallback inCallback, OutDaqCallback outCallback) void PortAudioDaq::start(InDaqCallback inCallback, OutDaqCallback outCallback) {
{
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
assert(_stream); assert(_stream);
if (Pa_IsStreamActive(_stream))
{ if (Pa_IsStreamActive(_stream)) {
throw rte("Stream is already running"); throw rte("Stream is already running");
} }
// Logical XOR if (neninchannels() > 0) {
if (inCallback && outCallback) if (!inCallback) {
{
throw rte("Either input or output stream possible for RtAudio. "
"Stream duplex mode not provided.");
}
if (neninchannels() > 0)
{
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");
@ -333,10 +362,8 @@ void PortAudioDaq::start(InDaqCallback inCallback, OutDaqCallback outCallback)
_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");
} }
@ -346,86 +373,95 @@ void PortAudioDaq::start(InDaqCallback inCallback, OutDaqCallback outCallback)
PaError err = Pa_StartStream(_stream); PaError err = Pa_StartStream(_stream);
throwIfError(err); throwIfError(err);
} }
void PortAudioDaq::stop() void PortAudioDaq::stop() {
{
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
assert(_stream); assert(_stream);
if (Pa_IsStreamStopped(_stream)) if (Pa_IsStreamStopped(_stream) > 1) {
{
throw rte("Stream is already stopped"); throw rte("Stream is already stopped");
} }
PaError err = Pa_StopStream(_stream); PaError err = Pa_StopStream(_stream);
throwIfError(err); throwIfError(err);
} }
Daq::StreamStatus PortAudioDaq::getStreamStatus() const Daq::StreamStatus PortAudioDaq::getStreamStatus() const {
{ DEBUGTRACE_ENTER;
// Stores an error type and whether the
Daq::StreamStatus status; Daq::StreamStatus status;
// Copy over atomic flag. using StreamError = Daq::StreamStatus::StreamError;
status.errorType = _streamError; Daq::StreamStatus::StreamError errortype = _streamError.load();
// Check if stream is still running.
if (_stream) PaError err = Pa_IsStreamStopped(_stream);
{ if (err > 1) {
if (Pa_IsStreamActive(_stream)) // Stream is stopped due to an error in the callback. The exact error type
{ // is filled in in the if-statement above
return status;
} else if (err == 0) {
// Still running
status.isRunning = true; status.isRunning = true;
} else if (err < 0) {
// Stream encountered an error.
switch (err) {
case paInternalError:
errortype = StreamError::driverError;
break;
case paDeviceUnavailable:
errortype = StreamError::driverError;
break;
case paInputOverflowed:
errortype = StreamError::inputXRun;
break;
case paOutputUnderflowed:
errortype = StreamError::outputXRun;
break;
default:
errortype = StreamError::driverError;
cerr << "Portaudio backend error:" << Pa_GetErrorText(err) << endl;
break;
} }
} }
status.errorType = errortype;
return status; return status;
} }
PortAudioDaq::~PortAudioDaq() PortAudioDaq::~PortAudioDaq() {
{ DEBUGTRACE_ENTER;
PaError err; PaError err;
if (_stream) assert(_stream);
{ if (Pa_IsStreamActive(_stream)) {
if (Pa_IsStreamActive(_stream)) // Stop the stream first
{
stop(); stop();
} }
err = Pa_CloseStream(_stream); err = Pa_CloseStream(_stream);
_stream = nullptr; _stream = nullptr;
if (err != paNoError) if (err != paNoError) {
{
cerr << "Error closing PortAudio stream. Do not know what to do." << endl; cerr << "Error closing PortAudio stream. Do not know what to do." << endl;
} }
assert(_shouldPaTerminate);
}
if (_shouldPaTerminate)
{
err = Pa_Terminate(); err = Pa_Terminate();
if (err != paNoError) if (err != paNoError) {
{
cerr << "Error terminating PortAudio. Do not know what to do." << endl; 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) {
{
// Initial output buffers generated. So nothing with input yet // Initial output buffers generated. So nothing with input yet
return paContinue; return paContinue;
} }
if ((statusFlags & paInputUnderflow) || (statusFlags & paInputOverflow)) if ((statusFlags & paInputUnderflow) || (statusFlags & paInputOverflow)) {
{
_streamError = se::inputXRun; _streamError = se::inputXRun;
return paAbort; return paAbort;
} }
if ((statusFlags & paOutputUnderflow) || (statusFlags & paOutputOverflow)) if ((statusFlags & paOutputUnderflow) || (statusFlags & paOutputOverflow)) {
{
_streamError = se::outputXRun; _streamError = se::outputXRun;
return paAbort; return paAbort;
} }
if (framesPerBuffer != framesPerBlock()) if (framesPerBuffer != framesPerBlock()) {
{
cerr << "Logic error: expected a block size of: " << framesPerBlock() cerr << "Logic error: expected a block size of: " << framesPerBlock()
<< endl; << endl;
_streamError = se::logicError; _streamError = se::logicError;
@ -437,8 +473,7 @@ int PortAudioDaq::memberPaCallback(const void *inputBuffer, void *outputBuffer,
const auto &dtype_descr = dtypeDescr(); const auto &dtype_descr = dtypeDescr();
const auto dtype = dataType(); const auto dtype = dataType();
const us sw = dtype_descr.sw; const us sw = dtype_descr.sw;
if (inputBuffer) if (inputBuffer) {
{
assert(_incallback); assert(_incallback);
std::vector<byte_t *> ptrs; std::vector<byte_t *> ptrs;
ptrs.reserve(neninchannels); ptrs.reserve(neninchannels);
@ -450,10 +485,8 @@ int PortAudioDaq::memberPaCallback(const void *inputBuffer, void *outputBuffer,
/// Only pass on the pointers of the channels we want. inputBuffer is /// Only pass on the pointers of the channels we want. inputBuffer is
/// noninterleaved, as specified in PortAudioDaq constructor. /// noninterleaved, as specified in PortAudioDaq constructor.
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 *ch_ptr = byte_t *ch_ptr =
reinterpret_cast<byte_t **>(const_cast<void *>(inputBuffer))[ch]; reinterpret_cast<byte_t **>(const_cast<void *>(inputBuffer))[ch];
ptrs.push_back(ch_ptr); ptrs.push_back(ch_ptr);
@ -465,8 +498,7 @@ int PortAudioDaq::memberPaCallback(const void *inputBuffer, void *outputBuffer,
_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);
@ -478,10 +510,8 @@ int PortAudioDaq::memberPaCallback(const void *inputBuffer, void *outputBuffer,
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)
{
byte_t *ch_ptr = reinterpret_cast<byte_t **>(outputBuffer)[ch]; byte_t *ch_ptr = reinterpret_cast<byte_t **>(outputBuffer)[ch];
ptrs.push_back(ch_ptr); ptrs.push_back(ch_ptr);
} }
@ -491,8 +521,7 @@ int PortAudioDaq::memberPaCallback(const void *inputBuffer, void *outputBuffer,
_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++;
} }

View File

@ -16,6 +16,7 @@ set(lasp_dsp_files
lasp_threadedindatahandler.cpp lasp_threadedindatahandler.cpp
lasp_ppm.cpp lasp_ppm.cpp
lasp_clip.cpp lasp_clip.cpp
lasp_freqsmooth.cpp
) )

View File

@ -2,12 +2,9 @@
#include "lasp_avpowerspectra.h" #include "lasp_avpowerspectra.h"
#include "debugtrace.hpp" #include "debugtrace.hpp"
#include "lasp_mathtypes.h" #include "lasp_mathtypes.h"
#include <cmath> #include <stdexcept>
#include <optional>
using rte = std::runtime_error; using rte = std::runtime_error;
using std::cerr;
using std::endl;
PowerSpectra::PowerSpectra(const us nfft, const Window::WindowType w) PowerSpectra::PowerSpectra(const us nfft, const Window::WindowType w)
: PowerSpectra(Window::create(w, nfft)) {} : PowerSpectra(Window::create(w, nfft)) {}

View File

@ -3,8 +3,6 @@
#include "lasp_mathtypes.h" #include "lasp_mathtypes.h"
#include "lasp_timebuffer.h" #include "lasp_timebuffer.h"
#include "lasp_window.h" #include "lasp_window.h"
#include <memory>
#include <optional>
/** \defgroup dsp Digital Signal Processing utilities /** \defgroup dsp Digital Signal Processing utilities
* These are classes and functions used for processing raw signal data, to * These are classes and functions used for processing raw signal data, to

View File

@ -2,7 +2,6 @@
#include "lasp_biquadbank.h" #include "lasp_biquadbank.h"
#include "debugtrace.hpp" #include "debugtrace.hpp"
#include "lasp_thread.h" #include "lasp_thread.h"
#include <cassert>
#include <vector> #include <vector>
using std::cerr; using std::cerr;

View File

@ -0,0 +1,133 @@
// #define DEBUGTRACE_ENABLED
#include "lasp_freqsmooth.h"
#include <cassert>
#include "debugtrace.hpp"
using rte = std::runtime_error;
vd freqSmooth(const vd& freq, const vd& X, const unsigned w,
bool power_correct) {
DEBUGTRACE_ENTER;
if (freq.size() < 2) {
throw rte("Invalid frequency size. Should be > 2");
}
if (freq.size() != X.size()) {
throw rte("Sizes of freq and X do not match");
}
if (freq.size() > std::numeric_limits<long>::max() / 2) {
throw rte("Frequency size limit for smoothing is 2^30");
}
if (w == 0) {
throw rte("Invalid number of octaves");
}
const us Nfreq = freq.size();
// Smoothing width in unit of number of octaves
const d Delta = 1 / d(w);
// Minimum frequency and maximum frequency to smooth on (frequency range that
// is interpolated to a log scale)
d freq_min;
const d freq_max = freq(Nfreq - 1);
const bool firstFreqEqZero = (d_abs(freq(0)) < 1e-15);
// AC-signal power
d ac_pwr;
if (firstFreqEqZero) {
freq_min = freq(1);
if (power_correct) {
ac_pwr = arma::sum(X.subvec(1, Nfreq - 1));
}
} else {
freq_min = freq(0);
if (power_correct) {
ac_pwr = arma::sum(X);
}
}
DEBUGTRACE_PRINT(freq_min);
DEBUGTRACE_PRINT(freq_max);
const vd freq_log =
arma::logspace(d_log10(freq_min), d_log10(freq_max), 10 * Nfreq);
DEBUGTRACE_PRINT("freq_log = ");
const long Nfreq_sm = freq_log.size();
// Interpolate X to logscale
vd X_log;
DEBUGTRACE_PRINT("X_log = :");
arma::interp1(freq, X, freq_log, X_log, "*linear");
// First and last point are not interpolated well, could be minimally out of
// the interpolation range, due to roundoff errors. Armadillo sets these
// points to nan, so we have to manually "interpolate" them.
X_log(Nfreq_sm - 1) = X(X.size() - 1);
if (firstFreqEqZero) {
X_log(0) = X(1);
} else {
X_log(0) = X(0);
}
// Allocate space for smoothed X on log scale
vd Xsm_log(freq_log.size());
const d beta = d_log10(Nfreq_sm) / d_log10(2) / (Nfreq_sm - 1);
// int rounds down
const long mu = int(Delta / d(2) / beta);
DEBUGTRACE_PRINT(mu);
// Long is at least 32 bits. So +/- 2M points length
for (long k = 0; k < Nfreq_sm; k++) {
// const d fcur = freq_log(k);
long idx_start = std::max(k - mu, 0l);
long idx_stop = std::min(k + mu, Nfreq_sm - 1);
// Make window smaller at the sides (close to the end of the array)
if (idx_start == 0 || idx_stop == Nfreq_sm - 1) {
const long mu_edge = std::min(k - idx_start, idx_stop - k);
idx_start = k - mu_edge;
idx_stop = k + mu_edge;
}
assert(idx_stop < Nfreq_sm);
assert(idx_start < Nfreq_sm);
DEBUGTRACE_PRINT(idx_start)
DEBUGTRACE_PRINT(idx_stop);
Xsm_log(k) = arma::mean(X_log.subvec(idx_start, idx_stop));
}
DEBUGTRACE_PRINT("Xsm_log:");
// std::cerr << Xsm_log << std::endl;
// Back-interpolate to a linear scale, and be wary of nans at the start end
// and range. Also interpolates power
vd Xsm(Nfreq);
if (firstFreqEqZero) {
vd Xsm_gt0;
arma::interp1(freq_log, Xsm_log, freq.subvec(1, Nfreq - 1), Xsm_gt0,
"*linear");
Xsm(0) = X(0);
Xsm.subvec(1, Nfreq - 1) = Xsm_gt0;
Xsm(1) = Xsm_log(1);
Xsm(Nfreq - 1) = Xsm_log(Nfreq_sm - 1);
// Final step: power-correct smoothed spectrum
if (power_correct) {
d new_acpwr = arma::sum(Xsm.subvec(1, Nfreq - 1));
Xsm.subvec(1, Nfreq - 1) *= ac_pwr / new_acpwr;
}
} else {
arma::interp1(freq_log, Xsm_log, freq, Xsm, "*linear");
Xsm(0) = X(0);
Xsm(Nfreq - 1) = Xsm_log(Nfreq_sm - 1);
// Final step: power-correct smoothed spectrum
if (power_correct) {
d new_acpwr = arma::sum(Xsm);
Xsm *= ac_pwr / new_acpwr;
}
}
return Xsm;
}

View File

@ -0,0 +1,28 @@
#pragma once
#include <memory>
#include <vector>
#include "lasp_mathtypes.h"
#include "lasp_types.h"
/**
* \addtogroup dsp
* @{
*/
/**
* @brief Apply frequency domain smoothing to a Frequency domain (single
* sided)signal power spectrum
*
* @param freq Frequency range
* @param X Signal pwr
* @param w Parameter determining the smoothing with. 1 = 1/1 octave, 3 = 1/3th
* octave and so on
* @param power_correct Apply a correction to the whole spectrum to make the
* signal power equal to the unsmoothed signal power.
* @return vd Smoothed spectrum
*/
vd freqSmooth(const vd& freq, const vd& X, const unsigned w,
bool power_correct = false);
/** @} */

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,12 +4,9 @@
// //
// Description: Real Time Signal Viewer. // Description: Real Time Signal Viewer.
#pragma once #pragma once
#include "lasp_avpowerspectra.h"
#include "lasp_filter.h"
#include "lasp_mathtypes.h" #include "lasp_mathtypes.h"
#include "lasp_threadedindatahandler.h" #include "lasp_threadedindatahandler.h"
#include "lasp_timebuffer.h" #include "lasp_timebuffer.h"
#include <memory>
#include <mutex> #include <mutex>
/** /**

View File

@ -10,12 +10,12 @@ using rte = std::runtime_error;
inline d level_amp(d level_dB) { return pow(10, level_dB / 20); } inline d level_amp(d level_dB) { return pow(10, level_dB / 20); }
using mutexlock = std::scoped_lock<std::mutex>; using slock = std::scoped_lock<std::recursive_mutex>;
vd Siggen::genSignal(const us nframes) { vd Siggen::genSignal(const us nframes) {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
mutexlock lck(_mtx); slock lck(_mtx);
DEBUGTRACE_PRINT(nframes); DEBUGTRACE_PRINT(nframes);
vd signal(nframes, arma::fill::value(_dc_offset)); vd signal(nframes, arma::fill::value(_dc_offset));
@ -52,7 +52,7 @@ vd Siggen::genSignal(const us nframes) {
return signal; return signal;
} }
void Siggen::setInterruptPeriod(const d newPeriod) { void Siggen::setInterruptPeriod(const d newPeriod) {
mutexlock lck(_mtx); slock lck(_mtx);
if (newPeriod == 0) { if (newPeriod == 0) {
throw rte("Interruption period cannot be 0"); throw rte("Interruption period cannot be 0");
} }
@ -65,7 +65,7 @@ void Siggen::setInterruptPeriod(const d newPeriod) {
void Siggen::setFilter(const std::string &name, void Siggen::setFilter(const std::string &name,
std::shared_ptr<Filter> filter) { std::shared_ptr<Filter> filter) {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
mutexlock lck(_mtx); slock lck(_mtx);
if (filter) { if (filter) {
_filters[name] = filter; _filters[name] = filter;
} else if (_filters.find(name) != _filters.end()) { } else if (_filters.find(name) != _filters.end()) {
@ -74,17 +74,17 @@ void Siggen::setFilter(const std::string &name,
} }
void Siggen::setDCOffset(const d offset) { void Siggen::setDCOffset(const d offset) {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
mutexlock lck(_mtx); slock lck(_mtx);
_dc_offset = offset; _dc_offset = offset;
} }
void Siggen::setLevel(const d level, bool dB) { void Siggen::setLevel(const d level, bool dB) {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
mutexlock lck(_mtx); slock lck(_mtx);
_level_linear = dB ? level_amp(level) : level; _level_linear = dB ? level_amp(level) : level;
} }
void Siggen::reset(const d newFs) { void Siggen::reset(const d newFs) {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
mutexlock lck(_mtx); slock lck(_mtx);
_fs = newFs; _fs = newFs;
for (auto &f : _filters) { for (auto &f : _filters) {
assert(f.second); assert(f.second);

View File

@ -27,7 +27,7 @@ private:
bool _muted = false; bool _muted = false;
protected: protected:
std::mutex _mtx; mutable std::recursive_mutex _mtx;
d _fs = 0; d _fs = 0;
/** /**
* @brief Interuption of period the signal. If set, the signal will be * @brief Interuption of period the signal. If set, the signal will be

View File

@ -7,11 +7,14 @@
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
/* #define DEBUGTRACE_ENABLED */ /* #define DEBUGTRACE_ENABLED */
#include "lasp_siggen_impl.h" #include "lasp_siggen_impl.h"
#include "debugtrace.hpp"
#include "lasp_mathtypes.h"
#include <cassert> #include <cassert>
#include "debugtrace.hpp"
#include "lasp_mathtypes.h"
using rte = std::runtime_error; using rte = std::runtime_error;
using slock = std::scoped_lock<std::recursive_mutex>;
DEBUGTRACE_VARIABLES; DEBUGTRACE_VARIABLES;
@ -30,6 +33,7 @@ Sine::Sine(const d freq) : omg(2 * arma::datum::pi * freq) { DEBUGTRACE_ENTER; }
vd Sine::genSignalUnscaled(const us nframes) { vd Sine::genSignalUnscaled(const us nframes) {
/* DEBUGTRACE_ENTER; */ /* DEBUGTRACE_ENTER; */
slock lck(_mtx);
const d pi = arma::datum::pi; const d pi = arma::datum::pi;
vd phase_vec = vd phase_vec =
arma::linspace(phase, phase + omg * (nframes - 1) / _fs, nframes); arma::linspace(phase, phase + omg * (nframes - 1) / _fs, nframes);
@ -41,8 +45,8 @@ vd Sine::genSignalUnscaled(const us nframes) {
} }
vd Periodic::genSignalUnscaled(const us nframes) { vd Periodic::genSignalUnscaled(const us nframes) {
vd res(nframes); vd res(nframes);
slock lck(_mtx);
if (_signal.size() == 0) { if (_signal.size() == 0) {
throw rte("No signal defined while calling"); throw rte("No signal defined while calling");
} }
@ -74,8 +78,8 @@ Sweep::Sweep(const d fl, const d fu, const d Ts, const d Tq, const us flags)
} }
void Sweep::resetImpl() { void Sweep::resetImpl() {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
slock lck(_mtx);
_cur_pos = 0; _cur_pos = 0;
@ -166,7 +170,6 @@ void Sweep::resetImpl() {
/* dVARTRACE(15, phase); */ /* dVARTRACE(15, phase); */
} }
} else if (flags & LogSweep) { } else if (flags & LogSweep) {
DEBUGTRACE_PRINT("Log sweep"); DEBUGTRACE_PRINT("Log sweep");
if (forward_sweep || backward_sweep) { if (forward_sweep || backward_sweep) {
/* Forward or backward sweep */ /* Forward or backward sweep */
@ -194,7 +197,6 @@ void Sweep::resetImpl() {
phase += 2 * number_pi * Dt * fn; phase += 2 * number_pi * Dt * fn;
} }
} else { } else {
DEBUGTRACE_PRINT("Continuous sweep"); DEBUGTRACE_PRINT("Continuous sweep");
const us Nf = Ns / 2; const us Nf = Ns / 2;
@ -249,8 +251,7 @@ void Sweep::resetImpl() {
/* dbgassert(fn >= 0, "BUG"); */ /* dbgassert(fn >= 0, "BUG"); */
phase += 2 * number_pi * Dt * fn; phase += 2 * number_pi * Dt * fn;
while (phase > 2 * number_pi) while (phase > 2 * number_pi) phase -= 2 * number_pi;
phase -= 2 * number_pi;
/* dVARTRACE(17, phase); */ /* dVARTRACE(17, phase); */
} }
/* This should be a very small number!! */ /* This should be a very small number!! */
@ -260,6 +261,5 @@ void Sweep::resetImpl() {
else { else {
// Either log or linear sweep had to be given as flags. // Either log or linear sweep had to be given as flags.
assert(false); assert(false);
} }
} }

View File

@ -18,8 +18,8 @@ class Noise : public Siggen {
d level_linear; d level_linear;
virtual vd genSignalUnscaled(const us nframes) override; virtual vd genSignalUnscaled(const us nframes) override;
void resetImpl() override; void resetImpl() override;
public:
public:
/** /**
* @brief Constructs a noise generator. If no filter is used, the output will * @brief Constructs a noise generator. If no filter is used, the output will
* be white noise. By default, the output will be standard deviation = 1 * be white noise. By default, the output will be standard deviation = 1
@ -28,7 +28,6 @@ class Noise : public Siggen {
*/ */
Noise(); Noise();
~Noise() = default; ~Noise() = default;
}; };
/** /**
@ -37,13 +36,12 @@ class Noise : public Siggen {
class Sine : public Siggen { class Sine : public Siggen {
d phase = 0; d phase = 0;
d omg; d omg;
protected:
protected:
void resetImpl() override final { phase = 0; } void resetImpl() override final { phase = 0; }
virtual vd genSignalUnscaled(const us nframes) override final; virtual vd genSignalUnscaled(const us nframes) override final;
public: public:
/** /**
* @brief Create a sine wave generator * @brief Create a sine wave generator
* *
@ -87,7 +85,6 @@ class Sweep : public Periodic {
void resetImpl() override; void resetImpl() override;
public: public:
static constexpr int ForwardSweep = 1 << 0; static constexpr int ForwardSweep = 1 << 0;
static constexpr int BackwardSweep = 1 << 1; static constexpr int BackwardSweep = 1 << 1;
static constexpr int LinearSweep = 1 << 2; static constexpr int LinearSweep = 1 << 2;
@ -103,11 +100,11 @@ class Sweep : public Periodic {
* avoid temporal aliasing in case of measuring impulse responses. * avoid temporal aliasing in case of measuring impulse responses.
* @param[in] sweep_flags: Sweep period [s] * @param[in] sweep_flags: Sweep period [s]
*/ */
Sweep(const d fl, const d fu, const d Ts, const d Tq, Sweep(const d fl, const d fu, const d Ts, const d Tq, const us sweep_flags);
const us sweep_flags);
~Sweep() = default; ~Sweep() = default;
}; };
/** @} */ /** @} */
/** @} */ /** @} */

View File

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

View File

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

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(0, N - 1, N) #define lin0N arma::linspace<vd>(0, N - 1, N)
vd Window::hann(const us N) { vd Window::hann(const us N) {
return arma::pow(arma::sin((arma::datum::pi/N) * lin0N), 2); return arma::pow(arma::sin((arma::datum::pi/N) * lin0N), 2);
@ -24,8 +24,8 @@ vd Window::blackman(const us N) {
d a0 = 7938. / 18608.; d a0 = 7938. / 18608.;
d a1 = 9240. / 18608.; d a1 = 9240. / 18608.;
d a2 = 1430. / 18608.; d a2 = 1430. / 18608.;
return a0 - a1 * d_cos((2 * number_pi/N) * lin0N) + return a0 - a1 * arma::cos((2 * number_pi/N) * lin0N) +
a2 * d_cos((4 * number_pi / N)* lin0N ); a2 * arma::cos((4 * number_pi / N)* lin0N );
} }
vd Window::rectangular(const us N) { return arma::ones(N); } vd Window::rectangular(const us N) { return arma::ones(N); }

View File

@ -44,6 +44,12 @@ void init_siggen(py::module &m);
PYBIND11_MODULE(lasp_cpp, m) { PYBIND11_MODULE(lasp_cpp, m) {
#if LASP_DOUBLE_PRECISION == 1
m.attr("LASP_DOUBLE_PRECISION") = true;
#else
m.attr("LASP_DOUBLE_PRECISION") = false;
#endif
init_dsp(m); init_dsp(m);
init_deviceinfo(m); init_deviceinfo(m);
init_daqconfiguration(m); init_daqconfiguration(m);
@ -51,6 +57,5 @@ PYBIND11_MODULE(lasp_cpp, m) {
init_streammgr(m); init_streammgr(m);
init_datahandler(m); init_datahandler(m);
init_siggen(m); init_siggen(m);
} }
/** @} */ /** @} */

View File

@ -1,13 +1,16 @@
#include <pybind11/pybind11.h>
#include <iostream>
#include "arma_npy.h" #include "arma_npy.h"
#include "lasp_avpowerspectra.h" #include "lasp_avpowerspectra.h"
#include "lasp_biquadbank.h" #include "lasp_biquadbank.h"
#include "lasp_fft.h" #include "lasp_fft.h"
#include "lasp_filter.h" #include "lasp_filter.h"
#include "lasp_freqsmooth.h"
#include "lasp_slm.h" #include "lasp_slm.h"
#include "lasp_streammgr.h" #include "lasp_streammgr.h"
#include "lasp_window.h" #include "lasp_window.h"
#include <iostream>
#include <pybind11/pybind11.h>
using std::cerr; using std::cerr;
using std::endl; using std::endl;
@ -27,7 +30,6 @@ using rte = std::runtime_error;
*/ */
void init_dsp(py::module &m) { void init_dsp(py::module &m) {
py::class_<Fft> fft(m, "Fft"); py::class_<Fft> fft(m, "Fft");
fft.def(py::init<us>()); fft.def(py::init<us>());
fft.def("fft", [](Fft &f, dpyarray dat) { fft.def("fft", [](Fft &f, dpyarray dat) {
@ -114,9 +116,10 @@ void init_dsp(py::module &m) {
aps.def("compute", [](AvPowerSpectra &aps, dpyarray timedata) { aps.def("compute", [](AvPowerSpectra &aps, dpyarray timedata) {
std::optional<ccube> res; std::optional<ccube> res;
dmat timedata_mat = NpyToMat<d, false>(timedata);
{ {
py::gil_scoped_release release; py::gil_scoped_release release;
res = aps.compute(NpyToMat<d, false>(timedata)); res = aps.compute(timedata_mat);
} }
return CubeToNpy<c>(res.value_or(ccube(0, 0, 0))); return CubeToNpy<c>(res.value_or(ccube(0, 0, 0)));
@ -151,5 +154,12 @@ void init_dsp(py::module &m) {
slm.def("Lmax", [](const SLM &slm) { return ColToNpy<d>(slm.Lmax()); }); slm.def("Lmax", [](const SLM &slm) { return ColToNpy<d>(slm.Lmax()); });
slm.def("Lpeak", [](const SLM &slm) { return ColToNpy<d>(slm.Lpeak()); }); slm.def("Lpeak", [](const SLM &slm) { return ColToNpy<d>(slm.Lpeak()); });
slm.def_static("suggestedDownSamplingFac", &SLM::suggestedDownSamplingFac); slm.def_static("suggestedDownSamplingFac", &SLM::suggestedDownSamplingFac);
// Frequency smoother
m.def("freqSmooth", [](dpyarray freq, dpyarray X, unsigned w) {
vd freqa = NpyToCol<d, false>(freq);
vd Xa = NpyToCol<d, false>(X);
return ColToNpy(freqSmooth(freqa, Xa, w));
});
} }
/** @} */ /** @} */

View File

@ -1,4 +1,10 @@
/* #define DEBUGTRACE_ENABLED */ // #define DEBUGTRACE_ENABLED
#include <pybind11/pybind11.h>
#include <pybind11/pytypes.h>
#include <armadillo>
#include <atomic>
#include "arma_npy.h" #include "arma_npy.h"
#include "debugtrace.hpp" #include "debugtrace.hpp"
#include "lasp_clip.h" #include "lasp_clip.h"
@ -9,14 +15,12 @@
#include "lasp_rtsignalviewer.h" #include "lasp_rtsignalviewer.h"
#include "lasp_streammgr.h" #include "lasp_streammgr.h"
#include "lasp_threadedindatahandler.h" #include "lasp_threadedindatahandler.h"
#include <armadillo>
#include <atomic>
#include <chrono>
#include <pybind11/pybind11.h>
using namespace std::literals::chrono_literals; using namespace std::literals::chrono_literals;
using std::cerr; using std::cerr;
using std::endl; using std::endl;
using rte = std::runtime_error;
using Lck = std::scoped_lock<std::recursive_mutex>;
namespace py = pybind11; namespace py = pybind11;
@ -104,7 +108,9 @@ class PyIndataHandler : public ThreadedInDataHandler<PyIndataHandler> {
/** /**
* @brief The callback functions that is called. * @brief The callback functions that is called.
*/ */
py::function cb, reset_callback; py::object _cb, _reset_callback;
std::atomic<bool> _done{false};
std::recursive_mutex _mtx;
public: public:
/** /**
@ -117,19 +123,26 @@ public:
* is called, when a stream stops, this pointer / handle will dangle. * is called, when a stream stops, this pointer / handle will dangle.
*/ */
PyIndataHandler(SmgrHandle mgr, py::function cb, py::function reset_callback) PyIndataHandler(SmgrHandle mgr, py::function cb, py::function reset_callback)
: ThreadedInDataHandler(mgr), cb(cb), reset_callback(reset_callback) { : ThreadedInDataHandler(mgr),
_cb(py::weakref(cb)),
_reset_callback(py::weakref(reset_callback)) {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
// cerr << "Thread ID: " << std::this_thread::get_id() << endl;
/// Start should be called externally, as at constructor time no virtual /// Start should be called externally, as at constructor time no virtual
/// functions should be called. /// functions should be called.
py::gil_scoped_release release; if (_cb().is_none() || _reset_callback().is_none()) {
throw rte("cb or reset_callback is none!");
}
startThread(); startThread();
} }
~PyIndataHandler() { ~PyIndataHandler() {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
// cerr << "Thread ID: " << std::this_thread::get_id() << endl;
/// Callback cannot be called, which results in a deadlock on the GIL /// Callback cannot be called, which results in a deadlock on the GIL
/// without this release. /// without this release.
py::gil_scoped_release release; py::gil_scoped_release release;
DEBUGTRACE_PRINT("Gil released");
_done = true;
stopThread(); stopThread();
} }
/** /**
@ -137,13 +150,23 @@ public:
* *
* @param daq Daq device, or nullptr in case no input stream is running. * @param daq Daq device, or nullptr in case no input stream is running.
*/ */
void reset(const Daq *daq) { void reset(const Daq *daqi) {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
// cerr << "Thread ID: " << std::this_thread::get_id() << endl;
if (_done) return;
{
try { try {
py::gil_scoped_acquire acquire; py::object reset_callback = _reset_callback();
if (daq) { if (reset_callback.is_none()) {
reset_callback(daq); DEBUGTRACE_PRINT("reset_callback is none, weakref killed");
_done = true;
return;
}
if (daqi != nullptr) {
assert(reset_callback);
reset_callback(daqi);
} else { } else {
assert(reset_callback);
reset_callback(py::none()); reset_callback(py::none());
} }
} catch (py::error_already_set &e) { } catch (py::error_already_set &e) {
@ -155,63 +178,95 @@ public:
abort(); abort();
/* throw std::runtime_error(e.what()); */ /* throw std::runtime_error(e.what()); */
} catch (std::exception &e) { } catch (std::exception &e) {
cerr << "Caught unknown exception in reset callback:" << e.what() << endl; cerr << "Caught unknown exception in reset callback:" << e.what()
<< endl;
abort(); abort();
} }
} } // end of GIL scope
} // end of function reset()
/** /**
* @brief Calls the Python callback method / function with a Numpy array of * @brief Calls the Python callback method / function with a Numpy array of
* stream data. * stream data.
*/ */
void inCallback(const DaqData &d) { void inCallback(const DaqData &d) {
DEBUGTRACE_ENTER;
/* DEBUGTRACE_ENTER; */ // cerr << "=== Enter incallback for thread ID: " << std::this_thread::get_id() << endl;
using DataType = DataTypeDescriptor::DataType; using DataType = DataTypeDescriptor::DataType;
if (_done) {
try { DEBUGTRACE_PRINT("Early stop, done");
return;
}
{
DEBUGTRACE_PRINT("================ TRYING TO OBTAIN GIL in inCallback...");
py::gil_scoped_acquire acquire; py::gil_scoped_acquire acquire;
py::object bool_val; try {
py::object py_bool;
py::object cb = _cb();
if (cb.is_none()) {
DEBUGTRACE_PRINT("cb is none, weakref killed");
_done = true;
return;
}
switch (d.dtype) { switch (d.dtype) {
case (DataType::dtype_int8): { case (DataType::dtype_int8): {
bool_val = cb(getPyArrayNoCpy<int8_t>(d)); py_bool = cb(getPyArrayNoCpy<int8_t>(d));
} break; } break;
case (DataType::dtype_int16): { case (DataType::dtype_int16): {
bool_val = cb(getPyArrayNoCpy<int16_t>(d)); py_bool = cb(getPyArrayNoCpy<int16_t>(d));
} break; } break;
case (DataType::dtype_int32): { case (DataType::dtype_int32): {
bool_val = cb(getPyArrayNoCpy<int32_t>(d)); py_bool = cb(getPyArrayNoCpy<int32_t>(d));
} break; } break;
case (DataType::dtype_fl32): { case (DataType::dtype_fl32): {
bool_val = cb(getPyArrayNoCpy<float>(d)); py_bool = cb(getPyArrayNoCpy<float>(d));
} break; } break;
case (DataType::dtype_fl64): { case (DataType::dtype_fl64): {
bool_val = cb(getPyArrayNoCpy<double>(d)); py_bool = cb(getPyArrayNoCpy<double>(d));
} break; } break;
default: default:
throw std::runtime_error("BUG"); throw std::runtime_error("BUG");
} // End of switch } // End of switch
bool res = bool_val.cast<bool>(); bool res = py_bool.cast<bool>();
if (res == false) {
DEBUGTRACE_PRINT("Setting callbacks to None")
_done = true;
// By doing this, we remove the references, but in the mean time this
// might also trigger removing Python objects. Including itself, as
// there is no reference to it anymore. The consequence is that the
// current object might be destroyed from this thread. However, if we
// do not remove these references and in lasp_record.py finish() is
// not called, we end up with not-garbage collected recordings in
// memory. This is also not good. How can we force Python to not yet
// destroy this object?
// cb.reset();
// reset_callback.reset();
}
} catch (py::error_already_set &e) { } catch (py::error_already_set &e) {
cerr << "ERROR: Python raised exception from callback function: "; cerr << "ERROR (BUG): Python raised exception from callback function: ";
cerr << e.what() << endl; cerr << e.what() << endl;
abort(); abort();
} catch (py::cast_error &e) { } catch (py::cast_error &e) {
cerr << e.what() << endl; cerr << e.what() << endl;
cerr << "ERROR: Python callback does not return boolean value." << endl; cerr << "ERROR (BUG): Python callback does not return boolean value."
<< endl;
abort(); abort();
} catch (std::exception &e) { } catch (std::exception &e) {
cerr << "Caught unknown exception in Python callback:" << e.what() cerr << "Caught unknown exception in Python callback:" << e.what()
<< endl; << endl;
abort(); abort();
} }
}
} // End of scope in which the GIL is acquired
// cerr << "=== LEAVE incallback for thread ID: " << std::this_thread::get_id() << endl;
} // End of function inCallback()
}; };
void init_datahandler(py::module &m) { void init_datahandler(py::module &m) {
/// The C++ class is PyIndataHandler, but for Python, it is called /// The C++ class is PyIndataHandler, but for Python, it is called
/// InDataHandler /// InDataHandler
py::class_<PyIndataHandler> pyidh(m, "InDataHandler"); py::class_<PyIndataHandler> pyidh(m, "InDataHandler");

View File

@ -5,7 +5,7 @@ requires-python = ">=3.10"
description = "Library for Acoustic Signal Processing" description = "Library for Acoustic Signal Processing"
license = { "file" = "LICENSE" } license = { "file" = "LICENSE" }
authors = [{ "name" = "J.A. de Jong", "email" = "j.a.dejong@ascee.nl" }] authors = [{ "name" = "J.A. de Jong", "email" = "j.a.dejong@ascee.nl" }]
version = "1.3.1" version = "1.6.6"
keywords = ["DSP", "DAQ", "Signal processing"] keywords = ["DSP", "DAQ", "Signal processing"]
@ -23,8 +23,7 @@ classifiers = [
urls = { "Documentation" = "https://lasp.ascee.nl" } urls = { "Documentation" = "https://lasp.ascee.nl" }
dependencies = [ dependencies = [
"scipy", "scipy>=1.13.1",
"numpy",
"matplotlib>=3.7.2", "matplotlib>=3.7.2",
"appdirs", "appdirs",
"dataclasses_json", "dataclasses_json",
@ -53,17 +52,10 @@ include = [
build_type = "Release" build_type = "Release"
source_path = "." source_path = "."
options = { "LASP_HAS_PORTAUDIO" = "ON", "LASP_HAS_RTAUDIO" = "OFF" } options = { "LASP_HAS_PORTAUDIO" = "ON", "LASP_HAS_RTAUDIO" = "OFF" }
build_args = ["-j12"] build_args = ["-j"]
install_components = ["python_modules"] install_components = ["python_modules"]
[tool.py-build-cmake.editable] [tool.py-build-cmake.editable]
# This might not work properly on Windows. Comment this out when testing on # This might not work properly on Windows. Comment this out when testing on
# Windows. # Windows.
mode = "symlink" mode = "symlink"
[tool.commitizen]
name = "cz_conventional_commits"
tag_format = "v$version"
version_scheme = "semver"
version_provider = "pep621"
update_changelog_on_bump = true

View File

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

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 RuntimeError("Invalid type for Atomic") raise ValueError("Invalid type for Atomic")
def __iadd__(self, toadd): def __iadd__(self, toadd):
self.checkType(toadd) self.checkType(toadd)

View File

@ -6,8 +6,14 @@ Author: J.A. de Jong - ASCEE
Description: LASP configuration Description: LASP configuration
""" """
import numpy as np import numpy as np
from .lasp_cpp import LASP_DOUBLE_PRECISION
if LASP_DOUBLE_PRECISION:
LASP_NUMPY_FLOAT_TYPE = np.float64 LASP_NUMPY_FLOAT_TYPE = np.float64
LASP_NUMPY_COMPLEX_TYPE = np.complex128
else:
LASP_NUMPY_FLOAT_TYPE = np.float32
LASP_NUMPY_COMPLEX_TYPE = np.float64
def zeros(shape): def zeros(shape):

View File

@ -1,173 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""!
Author: J.A. de Jong - ASCEE
Description: Two-microphone impedance tube methods
"""
__all__ = ['TwoMicImpedanceTube']
# from lrftubes import Air
from .lasp_measurement import Measurement
from numpy import pi, sqrt, exp
import numpy as np
from scipy.interpolate import UnivariateSpline
# from lrftubes import PrsDuct
from functools import lru_cache
class TwoMicImpedanceTube:
def __init__(self, mnormal: Measurement,
mswitched: Measurement,
s: float,
d1: float,
d2: float,
fl: float = None,
fu: float = None,
periodic_method=False,
# mat= Air(),
D_imptube = 50e-3,
thermoviscous = True,
**kwargs):
"""
Initialize two-microphone impedance tube methods
Args:
mnormal: Measurement in normal configuration
mswitched: Measurement in normal configuration
s: Microphone distance
fl: Lower evaluation frequency
fu: Lower evaluation frequency
kwargs: tuple with extra arguments, of which:
N: Period length of periodic excitation *obligatory*
chan0: Measurement channel index of mic 0
chan1: Measurement channel index of mic 1
"""
self.mnormal = mnormal
self.mswitched = mswitched
self.mat = mat
self.s = s
self.d1 = d1
self.d2 = d2
self.fl = fl
if fl is None:
ksmin = 0.1*pi
kmin = ksmin/s
self.fl = kmin*mat.c0/2/pi
self.fu = fu
if fu is None:
ksmax = 0.8*pi
kmax = ksmax/s
self.fu = kmax*mat.c0/2/pi
self.thermoviscous = thermoviscous
self.D_imptube = D_imptube
self.periodic_method = periodic_method
self.channels = [kwargs.pop('chan0', 0), kwargs.pop('chan1', 1)]
# Compute calibration correction
if periodic_method:
self.N = kwargs.pop('N')
freq, C1 = mnormal.periodicCPS(self.N, channels=self.channels)
freq, C2 = mswitched.periodicCPS(self.N, channels=self.channels)
else:
self.nfft = kwargs.pop('nfft', 16000)
freq, C1 = mnormal.CPS(nfft=self.nfft, channels=self.channels)
freq, C2 = mswitched.CPS(nfft=self.nfft, channels=self.channels)
# Store this, as it is often used for just a single sample.
self.C1 = C1
self.freq = freq
self.il = np.where(self.freq<= self.fl)[0][-1]
self.ul = np.where(self.freq > self.fu)[0][0]
# Calibration correction factor
# self.K = 0*self.freq + 1.0
K = sqrt(C2[:,0,1]*C1[:,0,0]/(C2[:,1,1]*C1[:,1,0]))
# self.K = UnivariateSpline(self.freq, K.real)(self.freq) +\
# 1j*UnivariateSpline(self.freq, K.imag)(self.freq)
self.K = K
def cut_to_limits(self, ar):
return ar[self.il:self.ul]
def getFreq(self):
"""
Array of frequencies, cut to limits of validity
"""
return self.cut_to_limits(self.freq)
@lru_cache
def G_AB(self, meas):
if meas is self.mnormal:
C = self.C1
freq = self.freq
else:
if self.periodic_method:
freq, C = meas.periodicCPS(self.N, self.channels)
else:
freq, C = meas.CPS(nfft=self.nfft, channels=self.channels)
# Microphone transfer function
G_AB = self.K*C[:,1,0]/C[:,0,0]
return self.getFreq(), self.cut_to_limits(G_AB)
def k(self, freq):
"""
Wave number, or thermoviscous wave number
"""
if self.thermoviscous:
D = self.D_imptube
S = pi/4*D**2
d = PrsDuct(0, S=S, rh=D/4, cs='circ')
d.mat = self.mat
omg = 2*pi*freq
G, Z = d.GammaZc(omg)
return G
else:
return 2*pi*freq/self.mat.c0
def R(self, meas):
freq, G_AB = self.G_AB(meas)
s = self.s
k = self.k(freq)
d1 = self.d1
RpA = (G_AB - exp(-1j*k*s))/(exp(1j*k*s)-G_AB)
R = RpA*exp(2*1j*k*(s+d1))
return freq, R
def alpha(self, meas):
"""
Acoustic absorption coefficient
"""
freq, R = self.R(meas)
return freq, 1 - np.abs(R)**2
def z(self, meas):
"""
Acoustic impedance at the position of the sample, in front of the sample
"""
freq, R = self.R(meas)
return freq, self.mat.z0*(1+R)/(1-R)
def zs(self, meas):
"""
Sample impedance jump, assuming a cavity behind the sample with
thickness d2
"""
freq, R = self.R(meas)
z0 = self.mat.z0
k = 2*pi*freq/self.mat.c0
d2 = self.d2
zs = 2*z0*(1-R*exp(2*1j*k*d2))/((R-1)*(exp(2*d2*k*1j)-1))
return freq, zs

View File

@ -1,6 +1,24 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import annotations from __future__ import annotations
from contextlib import contextmanager
from weakref import WeakValueDictionary
import h5py as h5
import uuid
import pathlib
import glob
import itertools
import numpy as np
from enum import Enum, unique
from .lasp_config import LASP_NUMPY_FLOAT_TYPE
from scipy.io import wavfile
import os, time, wave, logging
from .lasp_common import SIQtys, Qty, getFreq
from .lasp_version import LASP_VERSION_MAJOR, LASP_VERSION_MINOR
from .lasp_cpp import Window, DaqChannel, AvPowerSpectra
from typing import List
from functools import lru_cache
from .lasp_config import ones
"""! """!
Author: J.A. de Jong - ASCEE Author: J.A. de Jong - ASCEE
@ -46,28 +64,11 @@ The video dataset can possibly be not present in the data.
""" """
__all__ = ["Measurement", "scaleBlockSens", "MeasurementType"]
from contextlib import contextmanager
from weakref import WeakValueDictionary
import h5py as h5
import uuid
import pathlib
import glob
import itertools
import numpy as np
from enum import Enum, unique
from .lasp_config import LASP_NUMPY_FLOAT_TYPE
from scipy.io import wavfile
import os, time, wave, logging
from .lasp_common import SIQtys, Qty, getFreq
from .lasp_version import LASP_VERSION_MAJOR, LASP_VERSION_MINOR
from .lasp_cpp import Window, DaqChannel, AvPowerSpectra
from typing import List
from functools import lru_cache
__all__ = ["Measurement", "scaleBlockSens", "MeasurementType"]
# Measurement file extension # Measurement file extension
MEXT = 'h5' MEXT = "h5"
DOTMEXT = f'.{MEXT}' DOTMEXT = f".{MEXT}"
@unique @unique
@ -223,7 +224,7 @@ class IterData(IterRawData):
def __init__(self, fa, channels, sensitivity, **kwargs): def __init__(self, fa, channels, sensitivity, **kwargs):
super().__init__(fa, channels, **kwargs) super().__init__(fa, channels, **kwargs)
self.sens = np.asarray(sensitivity)[self.channels] self.sens = np.asarray(sensitivity, dtype=LASP_NUMPY_FLOAT_TYPE)[self.channels]
assert self.sens.ndim == 1 assert self.sens.ndim == 1
def __next__(self): def __next__(self):
@ -235,7 +236,8 @@ class Measurement:
"""Provides access to measurement data stored in the h5 measurement file """Provides access to measurement data stored in the h5 measurement file
format.""" format."""
# Store a dict of open measurements, with uuid string as a key. We store them as a weak ref. # Store a dict of open measurements, with uuid string as a key. We store
# them as a weak ref.
uuid_s = WeakValueDictionary() uuid_s = WeakValueDictionary()
def __init__(self, fn): def __init__(self, fn):
@ -277,7 +279,7 @@ class Measurement:
try: try:
# Try to catch UUID (Universally Unique IDentifier) # Try to catch UUID (Universally Unique IDentifier)
self._UUID = f.attrs['UUID'] self._UUID = f.attrs["UUID"]
# Flag indicating we have to add a new UUID # Flag indicating we have to add a new UUID
create_new_uuid = False create_new_uuid = False
except KeyError: except KeyError:
@ -290,17 +292,17 @@ class Measurement:
# The last filename is a filename that *probably* is the reference measurement with # The last filename is a filename that *probably* is the reference measurement with
# given UUID. If it is not, we will search for it in the same directory as `this` measurement. # given UUID. If it is not, we will search for it in the same directory as `this` measurement.
# If we cannot find it there, we will give up, and remove the field corresponding to this reference measurement type. # If we cannot find it there, we will give up, and remove the field corresponding to this reference measurement type.
refMeas_list = f.attrs['refMeas'] refMeas_list = f.attrs["refMeas"]
# Build a tuple string from it # Build a tuple string from it
self._refMeas = {} self._refMeas = {}
for (key, val, name) in refMeas_list: for key, val, name in refMeas_list:
self._refMeas[MeasurementType(int(key))] = (val, name) self._refMeas[MeasurementType(int(key))] = (val, name)
except KeyError: except KeyError:
self._refMeas = {} self._refMeas = {}
try: try:
self._type_int = f.attrs['type_int'] self._type_int = f.attrs["type_int"]
except KeyError: except KeyError:
self._type_int = 0 self._type_int = 0
@ -329,10 +331,10 @@ class Measurement:
try: try:
sens = f.attrs["sensitivity"] sens = f.attrs["sensitivity"]
self._sens = ( self._sens = (
sens * np.ones(self.nchannels) if isinstance(sens, float) else sens sens * ones(self.nchannels) if isinstance(sens, float) else sens
) )
except KeyError: except KeyError:
self._sens = np.ones(self.nchannels) self._sens = ones(self.nchannels)
# The time is cached AND ALWAYS ASSUMED TO BE AN IMMUTABLE OBJECT. # The time is cached AND ALWAYS ASSUMED TO BE AN IMMUTABLE OBJECT.
# It is also cached. Changing the measurement timestamp should not # It is also cached. Changing the measurement timestamp should not
@ -367,7 +369,9 @@ class Measurement:
self.genNewUUID() self.genNewUUID()
else: else:
if self.UUID in Measurement.uuid_s.keys(): if self.UUID in Measurement.uuid_s.keys():
raise RuntimeError(f"Measurement '{self.name}' is already opened. Cannot open measurement twice. Note: this error can happen when measurements are manually copied.") raise RuntimeError(
f"Measurement '{self.name}' is already opened. Cannot open measurement twice. Note: this error can happen when measurements are manually copied."
)
# Store weak reference to 'self' in list of UUID's. They are removed when no file is open anymore # Store weak reference to 'self' in list of UUID's. They are removed when no file is open anymore
Measurement.uuid_s[self._UUID] = self Measurement.uuid_s[self._UUID] = self
@ -396,7 +400,7 @@ class Measurement:
""" """
Create new UUID for measurement and store in file. Create new UUID for measurement and store in file.
""" """
self.setAttribute('UUID', str(uuid.uuid1())) self.setAttribute("UUID", str(uuid.uuid1()))
@property @property
def UUID(self): def UUID(self):
@ -423,40 +427,54 @@ class Measurement:
# Try to find it in the dictionary of of open measurements # Try to find it in the dictionary of of open measurements
if required_uuid in Measurement.uuid_s.keys(): if required_uuid in Measurement.uuid_s.keys():
m = Measurement.uuid_s[required_uuid] m = Measurement.uuid_s[required_uuid]
logging.info(f'Returned reference measurement {m.name} from list of open measurements') logging.debug(
f"Returned reference measurement {m.name} from list of open measurements"
)
# Not found in list of openend measurements. See if we can open it using its last stored file name we know of # Not found in list of openend measurements. See if we can open it using its last stored file name we know of
if m is None: if m is None:
try: try:
m = Measurement(possible_name) m = Measurement(possible_name)
if m.UUID == required_uuid: if m.UUID == required_uuid:
logging.info(f'Opened reference measurement {m.name} by name') logging.info(f"Opened reference measurement {m.name} by name")
except Exception as e: except Exception as e:
logging.error(f'Could not find reference measurement using file name: {possible_name}') logging.error(
f"Could not find reference measurement using file name: {possible_name}"
)
# Last resort, see if we can find the right measurement in the same folder # Last resort, see if we can find the right measurement in the same folder
if m is None: if m is None:
try: try:
folder, _ = os.path.split(self.fn) folder, _ = os.path.split(self.fn)
m = Measurement.fromFolderWithUUID(required_uuid, folder, skip=[self.name]) m = Measurement.fromFolderWithUUID(
logging.info('Found reference measurement in folder with correct UUID. Updating name of reference measurement') required_uuid, folder, skip=[self.name]
)
logging.info(
"Found reference measurement in folder with correct UUID. Updating name of reference measurement"
)
# Update the measurement file name in the list, such that next time it # Update the measurement file name in the list, such that next time it
# can be opened just by its name. # can be opened just by its name.
self.setRefMeas(m) self.setRefMeas(m)
except: except:
logging.error("Could not find the reference measurement. Is it deleted?") logging.error(
"Could not find the reference measurement. Is it deleted?"
)
# Well, we found it. Now make sure the reference measurement actually has the right type (User could have marked it as a NotSpecific for example in the mean time). # Well, we found it. Now make sure the reference measurement actually has the right type (User could have marked it as a NotSpecific for example in the mean time).
if m is not None: if m is not None:
if m.measurementType() != mtype: if m.measurementType() != mtype:
m.removeRefMeas(mtype) m.removeRefMeas(mtype)
raise RuntimeError(f"Reference measurement for {self.name} is not a proper reference (anymore).") raise RuntimeError(
f"Reference measurement for {self.name} is not a proper reference (anymore)."
)
# Whow, we passed all security checks, here we go! # Whow, we passed all security checks, here we go!
return m return m
else: else:
# Nope, not there. # Nope, not there.
raise RuntimeError(f"Could not find the reference measurement for '{self.name}'. Is it deleted?") raise RuntimeError(
f"Could not find the reference measurement for '{self.name}'. Is it deleted?"
)
def removeRefMeas(self, mtype: MeasurementType): def removeRefMeas(self, mtype: MeasurementType):
""" """
@ -477,9 +495,12 @@ class Measurement:
with self.file("r+") as f: with self.file("r+") as f:
# Update attribute in file. Stored as a flat list of string tuples: # Update attribute in file. Stored as a flat list of string tuples:
# [(ref_value1, uuid_1, name_1), (ref_value2, uuid_2, name_2), ...] # [(ref_value1, uuid_1, name_1), (ref_value2, uuid_2, name_2), ...]
reflist = list((str(key.value), val1, val2) for key, (val1, val2) in self._refMeas.items()) reflist = list(
(str(key.value), val1, val2)
for key, (val1, val2) in self._refMeas.items()
)
# print(reflist) # print(reflist)
f.attrs['refMeas'] = reflist f.attrs["refMeas"] = reflist
def setRefMeas(self, m: Measurement): def setRefMeas(self, m: Measurement):
""" """
@ -489,19 +510,21 @@ class Measurement:
""" """
mtype = m.measurementType() mtype = m.measurementType()
if mtype == MeasurementType.NotSpecific: if mtype == MeasurementType.NotSpecific:
raise ValueError('Measurement to be set as reference is not a reference measurement') raise ValueError(
"Measurement to be set as reference is not a reference measurement"
)
self._refMeas[mtype] = (m.UUID, m.name) self._refMeas[mtype] = (m.UUID, m.name)
self.__storeReafMeas() self.__storeReafMeas()
@staticmethod @staticmethod
def fromFolderWithUUID(uuid_str: str, folder: str='', skip=[]): def fromFolderWithUUID(uuid_str: str, folder: str = "", skip=[]):
""" """
Returns Measurement object from a given UUID string. It first tries to find whether there Returns Measurement object from a given UUID string. It first tries to find whether there
is an uuid in the static list of weak references. If not, it will try to open files in is an uuid in the static list of weak references. If not, it will try to open files in
the current file path. the current file path.
""" """
for fn in glob.glob(str(pathlib.Path(folder)) + f'/*{DOTMEXT}'): for fn in glob.glob(str(pathlib.Path(folder)) + f"/*{DOTMEXT}"):
# Do not try to open this file in case it is in the 'skip' list. # Do not try to open this file in case it is in the 'skip' list.
if len(list(filter(lambda a: a in fn, skip))) > 0: if len(list(filter(lambda a: a in fn, skip))) > 0:
continue continue
@ -512,9 +535,11 @@ class Measurement:
# Update 'last_fn' attribute in dict of stored reference measurements # Update 'last_fn' attribute in dict of stored reference measurements
return m return m
except Exception as e: except Exception as e:
logging.error(f'Possible measurement file {fn} returned error {e} when opening.') logging.error(
f"Possible measurement file {fn} returned error {e} when opening."
)
raise RuntimeError(f'Measurement with UUID {uuid_str} could not be found.') raise RuntimeError(f"Measurement with UUID {uuid_str} could not be found.")
def setAttribute(self, attrname: str, value): def setAttribute(self, attrname: str, value):
""" """
@ -534,7 +559,7 @@ class Measurement:
""" """
Returns True when a measurement is flagged as being of a certaint "MeasurementType" Returns True when a measurement is flagged as being of a certaint "MeasurementType"
""" """
if (type_.value & self._type_int): if type_.value & self._type_int:
return True return True
elif type_.value == self._type_int == 0: elif type_.value == self._type_int == 0:
return True return True
@ -544,7 +569,7 @@ class Measurement:
""" """
Set the measurement type to given type Set the measurement type to given type
""" """
self.setAttribute('type_int', type_.value) self.setAttribute("type_int", type_.value)
def measurementType(self): def measurementType(self):
""" """
@ -885,7 +910,7 @@ class Measurement:
""" """
if isinstance(sens, float): if isinstance(sens, float):
# Put all sensitivities equal # Put all sensitivities equal
sens = sens * np.ones(self.nchannels) sens = sens * ones(self.nchannels)
elif isinstance(sens, list): elif isinstance(sens, list):
sens = np.asarray(sens) sens = np.asarray(sens)
@ -998,9 +1023,9 @@ class Measurement:
happen that a Measurement object is created twice for the same backing file, which we do not allow. happen that a Measurement object is created twice for the same backing file, which we do not allow.
""" """
# See if the base part of the filename is referring to a file that is already open # See if the base part of the filename is referring to a file that is already open
with h5.File(fn, 'r') as f: with h5.File(fn, "r") as f:
try: try:
theuuid = f.attrs['UUID'] theuuid = f.attrs["UUID"]
except KeyError: except KeyError:
# No UUID stored in measurement. This is an old measurement that did not have UUID's # No UUID stored in measurement. This is an old measurement that did not have UUID's
# We create a new UUID here such that the file is opened from the filesystem # We create a new UUID here such that the file is opened from the filesystem
@ -1161,7 +1186,7 @@ class Measurement:
hf.attrs["nchannels"] = nchannels hf.attrs["nchannels"] = nchannels
# Add physical quantity indices # Add physical quantity indices
hf.attrs['qtys_enum_idx'] = [qty.toInt() for qty in qtys] hf.attrs["qtys_enum_idx"] = [qty.toInt() for qty in qtys]
# Add channel names in case given # Add channel names in case given
if channelNames is not None: if channelNames is not None:
@ -1199,7 +1224,7 @@ class Measurement:
nchannels = 1 nchannels = 1
nframes = len(data) nframes = len(data)
data = data[:, np.newaxis] data = data[:, np.newaxis]
sensitivity = np.ones(nchannels) sensitivity = ones(nchannels)
with h5.File(newfn, "w") as hf: with h5.File(newfn, "w") as hf:
hf.attrs["samplerate"] = samplerate hf.attrs["samplerate"] = samplerate
@ -1217,4 +1242,3 @@ class Measurement:
ad[0] = data ad[0] = data
return Measurement(newfn) return Measurement(newfn)

View File

@ -3,7 +3,8 @@ Provides class MeasurementSet, a class used to perform checks and adjustments
on a group of measurements at the same time. on a group of measurements at the same time.
""" """
__all__ = ['MeasurementSet']
__all__ = ["MeasurementSet"]
from .lasp_measurement import Measurement, MeasurementType from .lasp_measurement import Measurement, MeasurementType
from typing import List from typing import List
import time import time
@ -13,28 +14,35 @@ class MeasurementSet(list):
""" """
Group of measurements that have some correspondence to one another. Class Group of measurements that have some correspondence to one another. Class
is used to operate on multiple measurements at once. is used to operate on multiple measurements at once.
""" """
def __init__(self, mlist: List[Measurement] = []): def __init__(self, mlist: List[Measurement] = []):
""" """
Initialize a measurement set Initialize a measurement set
Args: Arg:
mlist: Measurement list mlist: Measurement list
""" """
if any([not isinstance(i, Measurement) for i in mlist]):
raise TypeError('Object in list should be of Measurement type')
if any([not isinstance(i, Measurement) for i in mlist]):
raise TypeError("Object in list should be of Measurement type")
# Sort by time stamp, otherwise the order is random
mlist.sort(key=lambda x: x.time, reverse=True)
super().__init__(mlist) super().__init__(mlist)
def getNewestReferenceMeasurement(self, mtype: MeasurementType): def getNewestReferenceMeasurement(self, mtype: MeasurementType):
"""Return the newest (in time) measurement in the current list of a certain type. Returns None in case no measurement could be found.
Args:
mtype (MeasurementType): The type required.
""" """
Get the NEWEST ref. measurement of a current type, in the current set.
Arg:
mtype (MeasurementType): The type required.
Return:
- The newest (in time) measurement in the current list of a certain type.
- None, in case no measurement could be found.
"""
mnewest = None mnewest = None
for m in self: for m in self:
if m.measurementType() == mtype: if m.measurementType() == mtype:
@ -45,14 +53,33 @@ class MeasurementSet(list):
mnewest = m mnewest = m
return mnewest return mnewest
def getReferenceMeasurements(self, mtype: MeasurementType):
"""Get ALL ref. measurements of a certain type, in the current set.
Arg:
mtype (MeasurementType): The type of which to list
Return:
A new measurement set, including all measurements of a certain type
"""
return [m for m in self if m.measurementType() == mtype]
def getNewestReferenceMeasurements(self): def getNewestReferenceMeasurements(self):
"""Returns a dictionary with newest measurement of each type that is not specific returns None in case no measurement is found.""" """
Get the NEWEST ref. measurement of all types, in the current set.
Return:
- A dictionary with the newest measurement of each type that is not specific
- None, in case no measurement is found.
"""
newest = {} newest = {}
for m in self: for m in self:
mtype = m.measurementType() mtype = m.measurementType()
if mtype == MeasurementType.NotSpecific: if mtype == MeasurementType.NotSpecific:
continue continue
if not mtype in newest: if mtype not in newest:
newest[mtype] = m newest[mtype] = m
else: else:
if m.time > newest[mtype].time: if m.time > newest[mtype].time:
@ -60,8 +87,17 @@ class MeasurementSet(list):
return newest return newest
def newestReferenceOlderThan(self, secs): def newestReferenceOlderThan(self, secs):
"""Returns a dictionary of references with the newest reference, that is still """
older than `secs` seconds. """ Get a dictionary of reference measurements which are older than a
specified threshold. Only one of each type is returned.
Args:
- secs: time threshold, in seconds
Return:
- a dictionary of references with the newest reference, that is still
older than `secs` seconds
"""
curtime = time.time() curtime = time.time()
newest = self.getNewestReferenceMeasurements() newest = self.getNewestReferenceMeasurements()
newest_older_than = {} newest_older_than = {}
@ -70,28 +106,31 @@ class MeasurementSet(list):
newest_older_than[key] = m newest_older_than[key] = m
return newest_older_than return newest_older_than
def measTimeSame(self): def measTimeSame(self):
""" """
Returns True if all measurements have the same measurement Returns True if all measurements have the same measurement length and
time (recorded time) sample rate
""" """
if len(self) > 0: if len(self) > 0:
first = self[0].N firstN = self[0].N # samples
return all([first == meas.N for meas in self]) firstFS = self[0].samplerate # sample rate
sameN = all([firstN == meas.N for meas in self])
sameFS = all([firstFS == meas.samplerate for meas in self])
return sameN and sameFS
else: else:
return False return False
def measSimilar(self): def measSimilar(self):
""" """
Similar means: channel metadata is the same, and the measurement time Similar means: channel metadata is the same, and the measurement time
is the same. It means that the recorded data is, of course, different. is the same.
Returns: Returns:
True if measChannelsSame() and measTimeSame() else False - True if measChannelsSame() and measTimeSame()
- False otherwise
""" """
return self.measTimeSame() and self.measChannelsSame() return self.measTimeSame() and self.measChannelsSame()
def measChannelsSame(self): def measChannelsSame(self):
@ -101,9 +140,9 @@ class MeasurementSet(list):
a set of measurements, simultaneously. If the channel data is the same a set of measurements, simultaneously. If the channel data is the same
(name, sensitivity, ...) it returns True. (name, sensitivity, ...) it returns True.
""" """
if len(self) > 0: if len(self) > 0:
first = self[0].channelConfig first = self[0].channelConfig
return all([first == meas.channelConfig for meas in self]) return all([first == meas.channelConfig for meas in self])
else: else:
return False return False

View File

@ -77,6 +77,7 @@ class FirFilterBank:
self.fs = fs self.fs = fs
self.xs = list(range(xmin, xmax + 1)) self.xs = list(range(xmin, xmax + 1))
raise RuntimeError('Not working code anymore')
maxdecimation = self.designer.firDecimation(self.xs[0]) maxdecimation = self.designer.firDecimation(self.xs[0])
self.decimators = [] self.decimators = []
@ -245,7 +246,7 @@ class SosFilterBank:
for i, x in enumerate(self.xs): for i, x in enumerate(self.xs):
channel = self.designer.createSOSFilter(x) channel = self.designer.createSOSFilter(x)
if sos is None: if sos is None:
sos = np.empty((channel.size, len(self.xs))) sos = empty((channel.size, len(self.xs)))
sos[:, i] = channel.flatten() sos[:, i] = channel.flatten()
self._fb = BiquadBank(sos) self._fb = BiquadBank(sos)

View File

@ -7,9 +7,15 @@ import dataclasses, logging, os, time, h5py, threading
import numpy as np import numpy as np
from .lasp_atomic import Atomic from .lasp_atomic import Atomic
from enum import Enum, auto, unique
from .lasp_cpp import InDataHandler, StreamMgr from .lasp_cpp import InDataHandler, StreamMgr
from .lasp_version import LASP_VERSION_MAJOR, LASP_VERSION_MINOR from .lasp_version import LASP_VERSION_MAJOR, LASP_VERSION_MINOR
import uuid import uuid
import logging
logger = logging.getLogger(__name__)
# logger.setLevel(logging.DEBUG)
logger.setLevel(logging.INFO)
@dataclasses.dataclass @dataclasses.dataclass
@ -18,6 +24,16 @@ class RecordStatus:
done: bool = False done: bool = False
class RecordingState(Enum):
"""Enumeration for the recording state"""
Waiting = auto()
Recording = auto()
AllDataStored = auto()
Finished = auto()
Error = auto()
class Recording: class Recording:
""" """
Class used to perform a recording. Recording data can come in from a Class used to perform a recording. Recording data can come in from a
@ -49,87 +65,104 @@ class Recording:
startDelay: Optional delay added before the recording is *actually* startDelay: Optional delay added before the recording is *actually*
started in [s]. started in [s].
""" """
logger.debug("__init__()")
ext = ".h5" ext = ".h5"
if ext not in fn: if ext not in fn:
fn += ext fn += ext
self.smgr = streammgr if os.path.exists(fn):
self.metadata = None raise RuntimeError("Recording file name already exists / is in use")
self._smgr = streammgr
self._metadata = None
self._recState = RecordingState.Waiting
if startDelay < 0: if startDelay < 0:
raise RuntimeError("Invalid start delay value. Should be >= 0") raise RuntimeError("Invalid start delay value. Should be >= 0")
self.startDelay = startDelay self._startDelay = startDelay
# Flag used to indicate that we have passed the start delay
self.startDelay_passed = False
# The amount of seconds (float) that is to be recorded # The amount of seconds (float) that is to be recorded
self.rectime = rectime self._requiredRecordingLength = rectime
# The file name to store data to # The file name to store data to
self.fn = fn self._fn = fn
self.curT_rounded_to_seconds = 0 # Counter of the number of blocks that have been recorded
self._recordedBlocks = 0
# Counter of the number of blocks # Counter of the overall number of blocks that have passed (including
self.ablockno = Atomic(0) # the blocks that passed during waiting prior to recording)
self._allBlocks = 0
# Stop flag, set when recording is finished. # Stop flag, set when recording is finished.
self.stop = Atomic(False) self._stop = Atomic(False)
# Mutex, on who is working with the H5py data # Mutex, on who is working with the H5py data and the class settings
self.file_mtx = threading.Lock() self._rec_mutex = threading.RLock()
self.progressCallback = progressCallback self._progressCallback = progressCallback
try: try:
# Open the file # Open the file
self.f = h5py.File(self.fn, "w", "stdio") self._h5file = h5py.File(self._fn, "w", "stdio")
self.f.flush() self._h5file.flush()
except Exception as e: except Exception as e:
logging.error(f"Error creating measurement file {e}") logger.error(f"Error creating measurement file {e}")
raise raise
# This flag is used to delete the file on finish(), and can be used # This flag is used to delete the file on finish(), and can be used
# when a recording is canceled. # when a recording is canceled. It is set to True at start, as the file will be deleted when no data is in it.
self.deleteFile = False self._deleteFile = True
# Try to obtain stream metadata # Try to obtain stream metadata
streamstatus = streammgr.getStreamStatus(StreamMgr.StreamType.input) streamstatus = streammgr.getStreamStatus(StreamMgr.StreamType.input)
if not streamstatus.runningOK(): if not streamstatus.runningOK():
raise RuntimeError( raise RuntimeError("Stream is not running properly. Cannot start recording")
"Stream is not running properly. Please first start the stream"
# Audio dataset
self._ad = None
logger.debug("Starting record....")
# In the PyInDataHandler, a weak reference is stored to the python
# methods reset and incallback. One way or another, the weak ref is gone
# on the callback thread. If we store an "extra" ref to this method over
# here, the weak ref stays alive. We do not know whether this is a bug
# or a feature, but in any case storing this extra ref to inCallback
# solves the problem.
self._incalback_cpy = self.inCallback
self._indataHandler = InDataHandler(
streammgr, self._incalback_cpy, self.resetCallback
) )
self.ad = None
logging.debug("Starting record....")
self.indh = InDataHandler(streammgr, self.inCallback, self.resetCallback)
if wait: if wait:
logging.debug("Stop recording with CTRL-C") logger.debug("Stop recording with CTRL-C")
try: try:
while not self.stop(): while not self._stop():
time.sleep(0.01) time.sleep(0.01)
except KeyboardInterrupt: except KeyboardInterrupt:
logging.debug("Keyboard interrupt on record") logger.debug("Keyboard interrupt on record")
finally: finally:
self.finish() self.finish()
def curT(self):
"""Return currently recorded time as float"""
def resetCallback(self, daq): def resetCallback(self, daq):
""" """
Function called with initial stream data. Function called with initial stream data.
""" """
with self.file_mtx: logger.debug(f"resetCallback({daq})")
with self._rec_mutex:
in_ch = daq.enabledInChannels() in_ch = daq.enabledInChannels()
blocksize = daq.framesPerBlock() blocksize = daq.framesPerBlock()
self.blocksize = blocksize self._blocksize = blocksize
self.nchannels = daq.neninchannels() self._nchannels = daq.neninchannels()
self.fs = daq.samplerate() self._fs = daq.samplerate()
f = self.f f = self._h5file
f.attrs["LASP_VERSION_MAJOR"] = LASP_VERSION_MAJOR f.attrs["LASP_VERSION_MAJOR"] = LASP_VERSION_MAJOR
f.attrs["LASP_VERSION_MINOR"] = LASP_VERSION_MINOR f.attrs["LASP_VERSION_MINOR"] = LASP_VERSION_MINOR
@ -145,7 +178,7 @@ class Recording:
# Add the start delay here, as firstFrames() is called right after the # Add the start delay here, as firstFrames() is called right after the
# constructor is called. time.time() returns a floating point # constructor is called. time.time() returns a floating point
# number of seconds after epoch. # number of seconds after epoch.
f.attrs["time"] = time.time() + self.startDelay f.attrs["time"] = time.time() + self._startDelay
# In V2, we do not store JSON metadata anymore, but just an enumeration # In V2, we do not store JSON metadata anymore, but just an enumeration
# index to a physical quantity. # index to a physical quantity.
@ -156,34 +189,6 @@ class Recording:
# f.attrs['qtys'] = [ch.qty.to_json() for ch in in_ch] # f.attrs['qtys'] = [ch.qty.to_json() for ch in in_ch]
f.flush() f.flush()
def firstFrames(self, adata):
"""
Set up the dataset in which to store the audio data. This will create
the attribute `self.ad`
Args:
adata: Numpy array with data from DAQ
"""
# The array data type cannot
# datatype = daq.dataType()
dtype = np.dtype(adata.dtype)
self.ad = self.f.create_dataset(
"audio",
(1, self.blocksize, self.nchannels),
dtype=dtype,
maxshape=(
None, # This means, we can add blocks
# indefinitely
self.blocksize,
self.nchannels,
),
compression="gzip",
)
self.f.flush()
def inCallback(self, adata): def inCallback(self, adata):
""" """
This method is called when a block of audio data from the stream is This method is called when a block of audio data from the stream is
@ -192,18 +197,91 @@ class Recording:
When returning False, it will stop the stream. When returning False, it will stop the stream.
""" """
if self.stop(): logger.debug(f"inCallback()")
logging.debug("Stop flag set, early return in inCallback") if self._stop():
logger.debug("Stop flag set, early return in inCallback")
# Stop flag is raised. We do not add any data anymore. # Stop flag is raised. We do not add any data anymore.
return False
with self._rec_mutex:
self._allBlocks += 1
match self._recState:
case RecordingState.Waiting:
if self._allBlocks * self._blocksize / self._fs > self._startDelay:
self._recState = RecordingState.Recording
case RecordingState.Recording:
if self._ad is None:
self.__addFirstFramesToFile(adata)
else:
self.__addTimeDataToFile(adata)
# Increase the block counter
self._recordedBlocks += 1
recstatus = RecordStatus(curT=self.recordedTime, done=False)
if (
self._requiredRecordingLength is not None
and self.recordedTime >= self._requiredRecordingLength
):
self._recState = RecordingState.AllDataStored
self._stop <<= True
recstatus.done = True
if self._progressCallback is not None:
self._progressCallback(recstatus)
case RecordingState.AllDataStored:
return False
case RecordingState.Finished:
return False
return True return True
with self.file_mtx: @property
def recordedTime(self):
"""Return recorded time (not rounded) as float"""
with self._rec_mutex:
if self._ad is None:
return 0.0
return self._recordedBlocks * self._blocksize / self._fs
if self.ad is None: def __addFirstFramesToFile(self, adata):
self.firstFrames(adata) """
Set up the dataset in which to store the audio data. This will create
the attribute `self.ad` and flip around the _deleteFile flag.
self.__addTimeData(adata) Args:
return True adata: Numpy array with data from DAQ
"""
with self._rec_mutex:
# The array data type cannot
# datatype = daq.dataType()
dtype = np.dtype(adata.dtype)
assert self._ad is None
self._ad = self._h5file.create_dataset(
"audio",
(1, self._blocksize, self._nchannels),
dtype=dtype,
maxshape=(
None, # This means, we can add blocks
# indefinitely
self._blocksize,
self._nchannels,
),
compression="gzip",
)
self._ad[0, :, :] = adata
self._h5file.flush()
self._deleteFile = False
def setDelete(self, val: bool): def setDelete(self, val: bool):
""" """
@ -211,8 +289,8 @@ class Recording:
the recording. Typically used for cleaning up after canceling a the recording. Typically used for cleaning up after canceling a
recording. recording.
""" """
with self.file_mtx: with self._rec_mutex:
self.deleteFile = val self._deleteFile = val
def finish(self): def finish(self):
""" """
@ -220,88 +298,57 @@ class Recording:
remove the queue from the stream, etc. remove the queue from the stream, etc.
""" """
logging.debug("Recording::finish()") logger.debug("Recording::finish()")
self.stop <<= True self._stop <<= True
with self._rec_mutex:
if self._recState == RecordingState.Finished:
raise RuntimeError("Recording has already finished")
with self.file_mtx:
self.f.flush()
# Remove indata handler, which also should remove callback function # Remove indata handler, which also should remove callback function
# from StreamMgr. This, however does not have to happen # from StreamMgr. This, however does not have to happen
# instantaneously. For which we have to implement extra mutex # instantaneously. For which we have to implement extra mutex
# guards in this class # guards in this class
del self.indh del self._indataHandler
self.indh = None
self._h5file.flush()
# Remove handle to dataset otherwise the h5 file is not closed # Remove handle to dataset otherwise the h5 file is not closed
# properly. # properly.
del self.ad del self._ad
self.ad = None
try: try:
# Close the recording file # Close the recording file
self.f.close() self._h5file.close()
del self.f del self._h5file
except Exception as e: except Exception as e:
logging.error(f"Error closing file: {e}") logger.error(f"Error closing file: {e}")
logging.debug("Recording ended") logger.debug("Recording ended")
if self.deleteFile: if self._deleteFile:
self.__deleteFile() self.__deleteFile()
self._recState = RecordingState.Finished
def __deleteFile(self): def __deleteFile(self):
""" """
Cleanup the recording file. Cleanup the recording file.
""" """
try: try:
os.remove(self.fn) os.remove(self._fn)
except Exception as e: except Exception as e:
logging.error(f"Error deleting file: {self.fn}: {str(e)}") logger.error(f"Error deleting file: {self._fn}: {str(e)}")
def __addTimeData(self, indata): def __addTimeDataToFile(self, indata):
""" """
Called by handleQueue() and adds new time data to the storage file. Called by handleQueue() and adds new time data to the storage file.
""" """
# logging.debug('Recording::__addTimeData()') with self._rec_mutex:
curT = self.ablockno() * self.blocksize / self.fs ablockno = self._recordedBlocks
# Increase the block counter
self.ablockno += 1
if curT < self.startDelay and not self.startDelay_passed:
# Start delay has not been passed
return
elif curT >= 0 and not self.startDelay_passed:
# Start delay passed, switch the flag!
self.startDelay_passed = True
# Reset the audio block counter and the recording time
self.ablockno = Atomic(1)
curT = 0
ablockno = self.ablockno()
recstatus = RecordStatus(curT=curT, done=False)
if self.progressCallback is not None:
self.progressCallback(recstatus)
curT_rounded_to_seconds = int(curT)
if curT_rounded_to_seconds > self.curT_rounded_to_seconds:
self.curT_rounded_to_seconds = curT_rounded_to_seconds
print(f"{curT_rounded_to_seconds}", end="", flush=True)
else:
print(".", end="", flush=True)
if self.rectime is not None and curT > self.rectime:
# We are done!
if self.progressCallback is not None:
recstatus.done = True
self.progressCallback(recstatus)
self.stop <<= True
return
# Add the data to the file, and resize the audio data blocks # Add the data to the file, and resize the audio data blocks
self.ad.resize(ablockno, axis=0) self._ad.resize(ablockno + 1, axis=0)
self.ad[ablockno - 1, :, :] = indata self._ad[ablockno, :, :] = indata
self.f.flush() self._h5file.flush()

View File

@ -6,6 +6,7 @@ Description:
Reverberation time estimation tool using least squares Reverberation time estimation tool using least squares
""" """
from .lasp_common import getTime from .lasp_common import getTime
from .lasp_config import ones
import numpy as np import numpy as np
@ -56,7 +57,7 @@ class ReverbTime:
x = self._t[istart:istop][:, np.newaxis] x = self._t[istart:istop][:, np.newaxis]
# Solve the least-squares problem, by creating a matrix of # Solve the least-squares problem, by creating a matrix of
A = np.hstack([x, np.ones(x.shape)]) A = np.hstack([x, ones(x.shape)])
# print(A.shape) # print(A.shape)
# print(points.shape) # print(points.shape)

View File

@ -5,6 +5,7 @@ Sound level meter implementation
@author: J.A. de Jong - ASCEE @author: J.A. de Jong - ASCEE
""" """
from .lasp_cpp import cppSLM from .lasp_cpp import cppSLM
from .lasp_config import empty
import numpy as np import numpy as np
from .lasp_common import (TimeWeighting, FreqWeighting, P_REF) from .lasp_common import (TimeWeighting, FreqWeighting, P_REF)
from .filter import SPLFilterDesigner from .filter import SPLFilterDesigner
@ -58,6 +59,9 @@ class SLM:
offset_t: Offset to be added to output time data [s] offset_t: Offset to be added to output time data [s]
""" """
if tw == TimeWeighting.ufast:
raise RuntimeError('The current implementation of impulse time weigthing is incorrect, as it does not have the proper decay time constant.')
self.fbdesigner = fbdesigner self.fbdesigner = fbdesigner
if xmin is None and fbdesigner is not None: if xmin is None and fbdesigner is not None:
xmin = fbdesigner.xs[0] xmin = fbdesigner.xs[0]
@ -98,7 +102,7 @@ class SLM:
assert fbdesigner.fs == fs assert fbdesigner.fs == fs
sos_firstx = fbdesigner.createSOSFilter(self.xs[0]).flatten() sos_firstx = fbdesigner.createSOSFilter(self.xs[0]).flatten()
self.nom_txt.append(fbdesigner.nominal_txt(self.xs[0])) self.nom_txt.append(fbdesigner.nominal_txt(self.xs[0]))
sos = np.empty((sos_firstx.size, nfilters), dtype=float, order='C') sos = empty((sos_firstx.size, nfilters), dtype=float, order='C')
sos[:, 0] = sos_firstx sos[:, 0] = sos_firstx
for i, x in enumerate(self.xs[1:]): for i, x in enumerate(self.xs[1:]):

View File

@ -47,7 +47,7 @@ class WeighCal:
P = 2048 # Filter length (number of taps) P = 2048 # Filter length (number of taps)
self._firs = np.empty((P, self.nchannels)) self._firs = empty((P, self.nchannels))
self._fbs = [] self._fbs = []
for chan in range(self.nchannels): for chan in range(self.nchannels):
fir = arbitrary_fir_design(fs, P, freq_design, fir = arbitrary_fir_design(fs, P, freq_design,

View File

@ -20,6 +20,8 @@ from enum import Enum, unique
import copy import copy
import numpy as np import numpy as np
from numpy import log2, pi, sin from numpy import log2, pi, sin
from ..lasp_cpp import freqSmooth
from ..lasp_config import zeros
@unique @unique
@ -152,7 +154,7 @@ def smoothCalcMatrix(freq, sw: SmoothingWidth):
return Q return Q
def smoothSpectralData(freq, M, sw: SmoothingWidth, def smoothSpectralData_old(freq, M, sw: SmoothingWidth,
st: SmoothingType = SmoothingType.levels): st: SmoothingType = SmoothingType.levels):
""" """
Apply fractional octave smoothing to data in the frequency domain. Apply fractional octave smoothing to data in the frequency domain.
@ -220,6 +222,45 @@ def smoothSpectralData(freq, M, sw: SmoothingWidth,
return Psm return Psm
def smoothSpectralData(freq, M, sw: SmoothingWidth,
st: SmoothingType = SmoothingType.levels):
"""
Apply fractional octave smoothing to data in the frequency domain.
Args:
freq: array of frequencies of data points [Hz] - equally spaced
M: array of data, either power or dB
the smoothing type `st`, the smoothing is applied.
sw: smoothing width
st: smoothing type = data type of input data
Returns:
freq : array frequencies of data points [Hz]
Msm : float smoothed magnitude of data points
"""
# Safety
if st == SmoothingType.ps:
assert np.min(M) >= 0, 'Power spectrum values cannot be negative'
if st == SmoothingType.levels and isinstance(M.dtype, complex):
raise RuntimeError('Decibel input should be real-valued')
# Convert to power
if st == SmoothingType.levels:
P = 10**(M/10)
elif st == SmoothingType.ps:
P = M
else:
raise RuntimeError(f"Incorrect SmoothingType: {st}")
Psm = freqSmooth(freq, P, sw.value[0])
# Convert to original format
if st == SmoothingType.levels:
Psm = 10*np.log10(Psm)
return Psm
# %% Test # %% Test
if __name__ == "__main__": if __name__ == "__main__":

View File

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

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

@ -31,7 +31,7 @@ def test_backward_fft():
nfft = 2048 nfft = 2048
freq = getFreq(nfft, nfft) freq = getFreq(nfft, nfft)
# Sig = np.zeros(nfft//2+1, dtype=complex) # Sig = zeros(nfft//2+1, dtype=complex)
Sigr = np.random.randn(nfft//2+1) Sigr = np.random.randn(nfft//2+1)
Sigi = np.random.randn(nfft//2+1) Sigi = np.random.randn(nfft//2+1)

@ -1 +1 @@
Subproject commit b6e810f2d33bcc234d67db5277d027949fec82f8 Subproject commit 614f2a9c68998b11010dc1734a77f84fcbd6fa2d

@ -1 +1 @@
Subproject commit daaf637f6f9fce670031221abfd7dfde92e5cce3 Subproject commit 18a606e1f928852bfc29639d9539ae74d37b5dee

2
third_party/rtaudio vendored

@ -1 +1 @@
Subproject commit 46b01b5b134f33d8ddc3dab76829d4b1350e0522 Subproject commit b4f04903312e0e0efffbe77655172e0f060dc085