Compare commits

..

156 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
08010e56dd From now on build default LASP with Portaudio backend. Also on Linux. Code cleanup of Portaudio glue code
All checks were successful
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 3m3s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2024-01-20 11:52:16 +01:00
292a9d5938 Merge branch 'RtAudioV6' into develop 2024-01-19 12:37:16 +01:00
373dcfb60f Bugfixes (that could potentially segfault) in PortAudio backend.
All checks were successful
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 2m54s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Successful in 2s
2024-01-19 12:35:56 +01:00
e8408ab53d Updated to RtAudio V6 2024-01-19 12:32:45 +01:00
0d152f6c14 Maded API changes to match RtAudio V6 2024-01-19 12:32:03 +01:00
46bef007ca Added muZ series impedance reference as measurementtype
Some checks failed
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Has been cancelled
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been cancelled
2024-01-15 16:41:29 +01:00
fd8366c362 Bugfix in measurementset
Some checks are pending
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Waiting to run
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Blocked by required conditions
2024-01-12 15:18:58 +01:00
6d5899c880 Merged in develop
All checks were successful
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 2m39s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2024-01-10 17:31:59 +01:00
061beaf88b Small bugfix of some dead code 2024-01-10 17:30:56 +01:00
e8ba3b86bf Bugfix on bugfix. KeyError instead of AttributeError
All checks were successful
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 2m29s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2024-01-10 13:14:54 +01:00
14ab3d9dfe Version bump: bugfix
All checks were successful
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 2m27s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2024-01-10 13:01:38 +01:00
695a05b262 BUGFIX: Prevent corrupting all files when no UUID is yet stored in a file 2024-01-10 13:01:07 +01:00
514ed1aa32 Added physicalOutputQty for daq devices, added possibility to inspect from Python whether device has monitor. Added unit for equation in Qtys. Version bump 1.3.0
All checks were successful
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 2m49s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2024-01-10 12:26:38 +01:00
0be8dd71d9 Bugfixes: store UUID attribute early when recording is done. Some small improvements
All checks were successful
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 2m15s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Successful in -1m38s
2023-12-19 14:34:47 +01:00
2cd4c616b3 Bump 1.2.0
All checks were successful
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 2m11s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Successful in -1m25s
2023-12-19 14:04:43 +01:00
311a1274bf Added fromFile() method to overcome problem of multiple times opening same file 2023-12-19 14:03:46 +01:00
936f2d5708 Merge remote-tracking branch 'origin/develop' into develop
All checks were successful
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 3m18s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2023-12-19 12:28:18 +01:00
98d4e8dad2 More checks on refMeas. Renamed method refMeas to getRefMeas. Added removeRefMeas. Stabilized API? 2023-12-19 12:27:44 +01:00
87283e4aba Changed pure random UUID of measurement to time-based UUID 2023-12-19 12:26:22 +01:00
e5c40c6af3 Merged in lasp from remote
All checks were successful
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 3m8s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2023-12-19 11:24:04 +01:00
8c7dbed606 Bugfix in qty input of Measurement.fromnpy() 2023-12-19 11:21:13 +01:00
c610c6350d Implemented reference measurements, and renaming as method in Measurement object. 2023-12-18 12:37:46 +01:00
44fe7f2689 Bump 1.0.4
All checks were successful
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 2m13s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Successful in -46s
2023-10-27 15:02:44 +02:00
f72a635cc7 Cleanup volume before copying
All checks were successful
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 2m14s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2023-10-26 15:17:50 +02:00
e9d7f0561e Revert "Possible bugfix for pyinstaller"
All checks were successful
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 2m16s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
This reverts commit 17319c4925.
2023-10-24 22:38:34 +02:00
17319c4925 Possible bugfix for pyinstaller
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 2m7s
2023-10-24 20:56:07 +02:00
a7b219a1e1 Updated readme
All checks were successful
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 2m55s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2023-10-24 19:49:56 +02:00
e4f887dc5b Renamed test_input.py to example_input.py
Some checks reported warnings
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Has been cancelled
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been cancelled
2023-10-24 19:47:35 +02:00
ee7e5fbba9 Added test_input.py
Some checks failed
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after 2m13s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2023-10-24 15:14:37 +02:00
bfa6704360 New armadillo version, some updates in pyproject and README
All checks were successful
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 2m12s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2023-10-24 15:13:06 +02:00
152d6d635d Apparently path may not be absolute. What a inflexible release system!
All checks were successful
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 2m38s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2023-10-16 15:54:08 +02:00
de3ef1b4c1 Apparently path may not be absolute. What a inflexible release system!
Some checks failed
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after -1m26s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2023-10-16 15:53:06 +02:00
5ca899fa0a Volume not mounted absolute, but in repo.
Some checks failed
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after -1m26s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2023-10-16 15:43:17 +02:00
ab103b74f7 Set working directory correct for release action. Is apparently required
Some checks failed
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 2m36s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Failing after -32s
2023-10-16 15:36:15 +02:00
a72284880d Twice container key
Some checks failed
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 2m44s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Failing after -35s
2023-10-16 15:28:11 +02:00
67ff10b5e5 Forgot dash 2023-10-16 15:26:42 +02:00
5b76ff007c Added volumes 2023-10-16 15:24:20 +02:00
592448eea9 Release when tag. That is the only way the release action is working
Some checks failed
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after 2m41s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2023-10-16 15:17:00 +02:00
402425ebd1 Test with only release. Removed last line
Some checks failed
Building and releasing LASP (master branch) / Build-Test-Ubuntu (push) Successful in 2m45s
Building and releasing LASP (master branch) / Release-Ubuntu (push) Failing after -1m23s
2023-10-16 14:58:00 +02:00
07b2d97e7a Sigh. requires go 1.18
Some checks failed
Building and releasing LASP (master branch) / Build-Test-Ubuntu (push) Successful in 2m34s
Building and releasing LASP (master branch) / Release-Ubuntu (push) Failing after -24s
2023-10-16 14:26:02 +02:00
db9a1a28a5 Volumes should be specified in runner config
Some checks failed
Building and releasing LASP (master branch) / Build-Test-Ubuntu (push) Successful in 2m38s
Building and releasing LASP (master branch) / Release-Ubuntu (push) Failing after -47s
2023-10-16 14:14:36 +02:00
24f849d9ee Should be host volume
Some checks failed
Building and releasing LASP (master branch) / Build-Test-Ubuntu (push) Failing after 2m29s
Building and releasing LASP (master branch) / Release-Ubuntu (push) Has been skipped
2023-10-16 13:49:04 +02:00
bcbb5b0720 More explicit name of volume
Some checks failed
Building and releasing LASP (master branch) / Build-Test-Ubuntu (push) Failing after 2m27s
Building and releasing LASP (master branch) / Release-Ubuntu (push) Has been skipped
2023-10-16 13:44:55 +02:00
bf6a18bcf8 Use a volume to share state
Some checks failed
Building and releasing LASP (master branch) / Build-Test-Ubuntu (push) Failing after 2m43s
Building and releasing LASP (master branch) / Release-Ubuntu (push) Has been skipped
2023-10-16 13:37:18 +02:00
749d5354c9 Install golang manually
Some checks failed
Building and releasing LASP (master branch) / Build-Test-Ubuntu (push) Successful in 2m39s
Building and releasing LASP (master branch) / Release-Ubuntu (push) Failing after 16s
2023-10-13 12:30:31 +02:00
941b83abe8 Try with catthehacker image
Some checks failed
Building and releasing LASP (master branch) / Build-Test-Ubuntu (push) Successful in 2m46s
Building and releasing LASP (master branch) / Release-Ubuntu (push) Failing after -1m9s
2023-10-13 11:34:10 +02:00
7857c3aed5 Go v3?
Some checks failed
Building and releasing LASP (master branch) / Build-Test-Ubuntu (push) Successful in 2m53s
Building and releasing LASP (master branch) / Release-Ubuntu (push) Failing after -52s
2023-10-13 11:26:36 +02:00
5565b10fb2 Upload and download artifact test 2
Some checks failed
Building and releasing LASP (master branch) / Build-Test-Ubuntu (push) Successful in 2m53s
Building and releasing LASP (master branch) / Release-Ubuntu (push) Failing after -1m6s
2023-10-12 16:28:02 +02:00
44c3782b46 Apparently release action requires golang
Some checks failed
Building and releasing LASP (master branch) / Build-Test-Ubuntu (push) Successful in 2m43s
Building and releasing LASP (master branch) / Release-Ubuntu (push) Failing after 32s
2023-10-11 18:14:32 +02:00
abbb3fee2c Specify release action from gitea
Some checks failed
Building and releasing LASP (master branch) / Build-Test-Ubuntu (push) Successful in 2m45s
Building and releasing LASP (master branch) / Release-Ubuntu (push) Failing after -1m8s
2023-10-11 18:04:43 +02:00
02b95f9aa8 Upload / download artifact. Test2
Some checks failed
Building and releasing LASP (master branch) / Build-Test-Ubuntu (push) Successful in 3m23s
Building and releasing LASP (master branch) / Release-Ubuntu (push) Failing after -1m9s
2023-10-11 17:36:44 +02:00
b9c42c1c24 Checkout before
Some checks failed
Building and releasing LASP (master branch) / Build-Test-Ubuntu (push) Failing after 2m38s
Building and releasing LASP (master branch) / Release-Ubuntu (push) Failing after -1m10s
2023-10-11 17:26:02 +02:00
2206f47cff Forgot pytest
Some checks reported warnings
Building and releasing LASP (master branch) / Release-Ubuntu (push) Has been cancelled
Building and releasing LASP (master branch) / Build-Test-Ubuntu (push) Has been cancelled
2023-10-11 17:24:08 +02:00
03513b6502 With upload and download artifacts - now with yaml syntax correct
Some checks reported warnings
Building and releasing LASP (master branch) / Release-Ubuntu (push) Has been cancelled
Building and releasing LASP (master branch) / Build-Test-Ubuntu (push) Has been cancelled
2023-10-11 17:23:31 +02:00
8ef5fd5a64 With upload and download artifacts 2023-10-11 17:22:35 +02:00
bab69c8018 Sigh............... Does this work?
Some checks failed
Building and releasing LASP (master branch) / Build-Test-Ubuntu (push) Failing after 2m28s
Building and releasing LASP (master branch) / Release-Ubuntu (push) Failing after -1m15s
2023-10-11 17:18:09 +02:00
6be9e14c13 Sigh............... Does this work? 2023-10-11 17:13:59 +02:00
78181ebca9 Split two files. No other way possible?
Some checks failed
Building and releasing LASP (master branch) / Build-Test-Release-Ubuntu (push) Failing after 2m4s
2023-10-11 17:03:55 +02:00
9b44ff7262 if on different level
Some checks failed
Building and testing LASP / Build-Test-Ubuntu (push) Successful in 1m55s
Building and testing LASP / Release-Ubuntu (push) Failing after -1m10s
2023-10-11 16:45:04 +02:00
1bf32fe81d gitub ref?
Some checks failed
Building and testing LASP / Build-Test-Ubuntu (push) Successful in 2m5s
Building and testing LASP / Release-Ubuntu (push) Failing after -1m9s
2023-10-11 16:21:12 +02:00
2b060b7708 release action from gitea fork
Some checks failed
Building and testing LASP / Build-Test-Ubuntu (push) Successful in 2m11s
Building and testing LASP / Release-Ubuntu (push) Failing after -1m7s
2023-10-09 21:51:52 +02:00
2d0d24a35a Removed constraints on tag
Some checks failed
Building and testing LASP / Build-Test-Ubuntu (push) Successful in 2m20s
Building and testing LASP / Release-Ubuntu (push) Failing after -1m9s
2023-10-05 21:52:51 +02:00
6bb15965d6 Update .gitea/workflows/workflow.yml 2023-10-05 19:50:19 +00:00
31208db325 Action roll... 2023-10-05 21:39:41 +02:00
7993d81808 Merge branch 'drone_check_publish' into develop 2023-10-04 21:30:32 +02:00
586dfb38b3 Some fix for building on Ubuntu 2023-10-04 21:30:22 +02:00
5b1051bf99 Check whether publishing works
Some checks are pending
continuous-integration/drone/push Build is pending
2023-09-27 20:52:36 +02:00
63122b8a42 build(Not-sh-but-bash): Changed from sh to bash shell
Some checks are pending
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is pending
2023-09-17 19:52:31 +02:00
df4e2cb573 build(Build-docu-only-on-master,-python3-venv-(sigh)..): Added extra install ubuntu dept, trigger documentation build only on branch master
Some checks failed
continuous-integration/drone/push Build is failing
2023-09-17 17:34:21 +02:00
0425195ffd build(python3-virtualenv): Not python3.10-virtualenv but python3-virtualenv
Some checks failed
continuous-integration/drone/push Build is failing
2023-09-17 17:19:43 +02:00
e51463a6cc build(Build-on-Ubuntu-fuix): hopefully fixed build error for Ubuntu builds
Some checks reported errors
continuous-integration/drone/push Build was killed
2023-09-17 17:14:55 +02:00
628ba898c9 Merged in develop.
Some checks failed
continuous-integration/drone/push Build is failing
2023-08-30 21:44:20 +02:00
dbd9c7c1af Updated drone to use all cpus available
Some checks reported errors
continuous-integration/drone/push Build encountered an error
2023-08-30 16:01:08 +02:00
77a7e46f9d Reset the interrupt frame counter when muting signal generator
Some checks are pending
continuous-integration/drone Build is running
2023-08-29 16:23:18 +02:00
376e0dc85c Working interrupted noise 2023-08-29 15:46:51 +02:00
1bf022d648 ci: Ubuntu build: added git to list of install packages
Some checks reported errors
continuous-integration/drone/push Build was killed
continuous-integration/drone Build is failing
2023-07-24 20:02:18 +02:00
c9243b1143 build(Pre-commit-drone-file-cannot-be-checked): Removed check-yaml hook, updated drone config
Some checks failed
continuous-integration/drone/push Build is failing
2023-07-24 18:11:45 +02:00
8397779a2a ci: Fixed build script for Ubuntu build (2)
Some checks reported errors
continuous-integration/drone/push Build was killed
2023-07-24 18:01:51 +02:00
94f0ec1d84 ci(scripts/build_ubuntu.sh): Fixed build script for Ubuntu builds 2023-07-24 18:01:02 +02:00
a29f72a592 Updated tests with build scripts in docker containers
Some checks failed
continuous-integration/drone/push Build is failing
2023-07-24 17:24:12 +02:00
89b303497b fix(lasp_version.py): Added patch to tuple unpack
Some checks failed
continuous-integration/drone/push Build is failing
2023-07-23 15:24:17 +02:00
aa0803e2f1 build(pyproject.toml): Added pybind11 build dependency
Some checks failed
continuous-integration/drone/push Build is failing
2023-07-20 15:22:56 +02:00
01a6c35f6e bump: version 1.0.0 → 1.0.1
Some checks failed
continuous-integration/drone/push Build is failing
2023-07-19 17:00:27 +02:00
ff1cfddf97 fix: Added patch number to semver in pyproject.toml 2023-07-19 17:00:22 +02:00
d96c591183 build(Removed-requirements,-they-are-in-pyproject.toml.-Added-lasp_version.py-that-will-obtain-version-from-metadata.): Black changed single quotes to double quotes. This commit should be tested now we have a patch number in the version 2023-07-19 16:56:50 +02:00
37048c54fa build(Testing-with-pre-commit): Not yet 2023-07-18 17:17:28 +02:00
9f81db8eeb Bla 2023-07-18 17:12:34 +02:00
914da89819 build(Testing): Not yet 2023-07-18 17:06:40 +02:00
72716ecd39 Updated .gitignore 2023-07-14 16:42:05 +02:00
790eb41a26 Switch to pyproject build structure 2023-07-14 16:40:57 +02:00
2727bb5582 Docs for build on Windows with MSYS2
Some checks failed
continuous-integration/drone/push Build is failing
2023-07-14 13:57:02 +02:00
a70f124f89 Updated README 2023-07-14 13:54:26 +02:00
26343fda79 Doc improvement Measurement.fromnpy
Some checks failed
continuous-integration/drone/push Build is failing
2023-07-14 13:48:26 +02:00
da80dbf075 Some improvements before stepping over to pyproject.toml
Some checks failed
continuous-integration/drone/push Build is failing
2023-07-14 13:40:43 +02:00
33132e2c9d Execute permissions on build scripts
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone Build is failing
2023-07-14 10:13:45 +02:00
fc681f3b6c Test virtualenvs and build envs
Some checks failed
continuous-integration/drone/push Build is failing
2023-07-14 10:12:42 +02:00
ddbb842c14 Fixed Doxkerfile for documentation to build in a virtualenv. This is required for Archlinux
Some checks failed
continuous-integration/drone/push Build is failing
2023-07-14 09:38:51 +02:00
126 changed files with 2888 additions and 1820 deletions

View File

@ -1,110 +0,0 @@
kind: pipeline
type: docker
name: archlinux
clone:
depth: 50
steps:
- name: archlinux_build
image: archlinux_build:latest
pull: if-not-exists
volumes:
- name: archlinux_ccache
path: /root/.ccache
commands:
# The following command is not required, we included this in the docker
# image of archlinux_build
# - pacman -S --noconfirm ccache openblas fftw pulseaudio pybind11
- git submodule update --init --recursive
- cmake .
# More than two makes ascee2 irresponsive for now
- make -j2
- name: archlinux_test
image: archlinux_build:latest
pull: if-not-exists
commands:
# The following command is not required, we included this in the docker
# image of archlinux_build
# - pacman -S --noconfirm openblas python-pytest fftw pulseaudio python-pip python-scipy python-h5py
- pip install -r requirements.txt
- pip install .
- pytest
# - name: release-arch
# commands:
# -
volumes:
- name: archlinux_ccache
host:
path: /tmp/archlinux_ccache
---
kind: pipeline
type: docker
name: ubuntu
clone:
depth: 3
volumes:
- name: archlinux_ccache
path: /root/.ccache
steps:
- name: ubuntu_build
image: ubuntu_build:latest
pull: if-not-exists
volumes:
- name: ubuntu_ccache
path: /root/.ccache
commands:
# The following commands are not required, we included this in the docker
# image of ubuntu_build
#- apt update
#- apt install -y git cmake python3-pybind11 libopenblas-dev python3-pip python3-scipy libusb-1.0-0-dev libpulse-dev python3-h5py fftw-dev
- git submodule update --init --recursive
- cmake .
# More than two makes ascee2 irresponsive for now
- make -j2
- name: ubuntu_test
image: ubuntu_build:latest
pull: if-not-exists
commands:
# The following commands are not required, we included this in the docker
# image of ubuntu_build
#- apt update
#- apt install -y python3-pytest fftw pulseaudio python3-pip python3-scipy python3-h5py
- pip install -r requirements.txt
- pip install .
- pytest-3
volumes:
- name: ubuntu_ccache
host:
path: /tmp/ubuntu_ccache
---
kind: pipeline
type: docker
name: documentation_build
clone:
depth: 3
steps:
- name: build_docker_master
image: plugins/docker
settings:
repo: ascee/lasp_ascee_nl
tags: latest
username:
from_secret: docker_username
password:
from_secret: docker_password
when:
branch: master

View File

@ -0,0 +1,52 @@
name: Building, testing and releasing LASP if it has a tag
on:
- push
jobs:
Build-Test-Ubuntu:
runs-on: ubuntu-latest
container:
image: ascee/ubuntu_build:latest
volumes:
- lasp_dist:/dist
steps:
- name: Checkout
uses: actions/checkout@v3
with:
submodules: true
- name: Build and test
run: |
pip install build pytest
python3 -m build
pip install dist/lasp*.whl
pytest
- name: Cleanup old dist files and copy new to /dist dir
run: |-
rm -f /dist/*
cp -v dist/* /dist
Release-Ubuntu:
runs-on: ubuntu-latest
container:
volumes:
- lasp_dist:/dist
needs: Build-Test-Ubuntu
if: startsWith(gitea.ref, 'refs/tags/v')
steps:
- name: Checkout
uses: actions/checkout@v4
- name: setup go
uses: https://github.com/actions/setup-go@v4
with:
go-version: '1.18'
- name: Release
uses: https://gitea.com/actions/release-action@main
working-directory: "/"
with:
files: |-
../../../../../dist/**
api_key: '${{secrets.RELEASE_TOKEN}}'

13
.gitignore vendored
View File

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

18
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,18 @@
---
repos:
- repo: https://github.com/commitizen-tools/commitizen
rev: 3.5.3
hooks:
- id: commitizen
- id: commitizen-branch
stages: [push]
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.3.0
hooks:
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/psf/black
rev: 22.10.0
hooks:
- id: black

7
CHANGELOG.md Normal file
View File

@ -0,0 +1,7 @@
## v1.0.1 (2023-07-19)
### Fix
- Added patch number to semver in pyproject.toml
## v1.0.0 (2023-07-19)

View File

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

View File

@ -1,13 +1,16 @@
FROM archlinux
FROM archlinux:latest
MAINTAINER J.A. de Jong - j.a.dejong@ascee.nl
RUN pacman --noconfirm -Sy
RUN pacman --noconfirm -S git doxygen graphviz lighttpd python-pip
RUN pip install doxypypy
RUN pacman --noconfirm -S git doxygen graphviz lighttpd python-pip python-virtualenv
WORKDIR /root
ENV VIRTUAL_ENV=/root/testenv
RUN python3 -m venv $VIRTUAL_ENV
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
RUN pip install doxypypy
RUN git clone https://code.ascee.nl/ascee/lasp
WORKDIR /root/lasp
RUN doxygen
RUN rm -rf /srv//http
RUN rm -rf /srv/http
RUN mv doc/html /srv/http
CMD /usr/bin/lighttpd -D -f /etc/lighttpd/lighttpd.conf

146
README.md
View File

@ -1,7 +1,5 @@
# Library for Acoustic Signal Processing
- Master branch: [![Build Status](https://drone.ascee.nl/api/badges/ASCEE/lasp/status.svg?ref=refs/heads/master)](https://drone.ascee.nl/ASCEE/lasp)
- Develop branch: [![Build Status](https://drone.ascee.nl/api/badges/ASCEE/lasp/status.svg?ref=refs/heads/develop)](https://drone.ascee.nl/ASCEE/lasp)
Library for Acoustic Signal Processing
======================================
Welcome to LASP: Library for Acoustic Signal Processing. LASP is a C++ library
@ -46,42 +44,114 @@ in a sister repository [lasp-doc](https://code.ascee.nl/ascee/lasp-doc).
If you have any question(s), please feel free to contact us: [email](info@ascee.nl).
# Installation - Linux (Debian-based)
## Dependencies
# Installation - Linux (Ubuntu-based)
- `$ sudo apt install python3-pybind11 libopenblas-dev python3-pip python3-scipy libusb-1.0-0-dev libpulse-dev cmake-curses-gui python3-h5py`
- `$ pip3 install --user -r requirements.txt`
## Prerequisites
Run the following on the command line to install all prerequisites on
Debian-based Linux, x86-64:
- `sudo apt install python3-pip libfftw3-3 libopenblas-base libusb-1.0-0 libpulse0`
## Installation from wheel (recommended for non-developers)
Go to: [LASP releases](https://code.ascee.nl/ASCEE/lasp/releases/latest/) and
download the latest `.whl`. Then run:
- `pip install lasp-*-linux_x86_64.whl`
## From source (Ubuntu-based)
### Prerequisites
Run the following one-liner:
- `sudo apt install -y git python3 python3-virtualenv python3-venv libopenblas-dev python3-pip libfftw3-dev libusb-1.0-0-dev libpulse-dev python3-build`
If building RtAudio with the ALSA backend, you will also require the following packages:
- libclalsadrv-dev
- `sudo apt install libclalsadrv-dev`
If building RtAudio with the Jack Audio Connection Kit (JACK) backend, you will also require the following packages:
- libjack-jackd2-dev
- `sudo apt install libjack-jackd2-dev`
## Download & build
### Download & build
- `$ git clone --recursive https://code.ascee.nl/ASCEE/lasp.git`
- `$ cd lasp`
- `pip install -e .`
For a release build:
# Building and installation for Raspberry Pi (Raspberry Pi OS)
- `$ cmake .`
Run the following on the command line to install all prerequisites on
Raspberry Pi OS:
or optionally for a custom build:
- `sudo apt install libfftw3-dev libopenblas64-dev libhdf5-dev libclalsadrv-dev`
- `$ ccmake .`
In a virtualenv: install `build`
Configure and run:
- `$ pip install build`
- `$ make -j`
Then run:
### Build documentation
- `$ git clone --recursive https://code.ascee.nl/ASCEE/lasp.git`
- `$ cd lasp`
- `$ pyproject-build`
In directory:
Which will generate a `whl` in the `dist` folder, that is redistributable for Raspberry Pis that run Raspberry Pi OS.
When installing the `whl`, it appears that H5PY takes quite some time to install. To follow this process, run it it verbose mode.
# Installation - (x86_64) Windows (with WinPython), build with MSYS2
## Prerequisites
- Download and install [WinPython](https://winpython.github.io)
## From wheel
- Download latest wheel from [LASP releases](https://code.ascee.nl/ASCEE/lasp/releases/latest/) and
download the latest `.whl`. Then install with `pip`.
## From source
- Download and install [MSYS2](https://msys2.org). Make sure to install the
x86_64 version.
- When unzipping WinPython, make sure to choose a proper and simple path, i.e.
C:\winpython
- Download and install [Git for Windows](https://git-scm.com)
- Open an MSYS2 **MINGW64** terminal, and install some tools we require:
- `$ pacman -S git`
- Create a new virtualenv:
- `$ /c/winpython/<py-distr-dir>/python.exe -m venv venv`
- Add the venv-python to the path (eases a lot of commands)
- `$ export PATH=$PATH:~/venv/Scripts`
- Install `build`:
- `$ pip install build`
- Clone LASP:
- `$ git clone --recurse-submodules https://code.ascee.nl/ascee/lasp && cd lasp`
- If run for the first time, we have to install the libraries we depend on in
MSYS2 (this only has to be done on a fresh MSYS2 installation):
- `$ scripts/install_msys2_builddeps.sh`
- Copy over required DLL's to be included in distribution:
- `scripts/copy_windows_dlls.sh`
- And... build!
- `pyproject-build`
- Lastly: the generated wheel can be installed in the current virtualenv:
- `pip install dist/lasp*.whl`
# Documentation
## Online
[Online LASP documentation](https://lasp.ascee.nl/).
## In directory (Linux/Debian)
`$ sudo apt install doxygen graphviz`
`$ pip install doxypypy`
@ -94,21 +164,29 @@ This will build the documentation. It can be read by:
`$ <YOUR-BROWSER> doc/html/index.html`
Or via docker:
`$ docker build -t lasp_ascee_nl:latest .`
## Install
For an editable install (while developing):
- `$ pip3 install --prefix=$HOME/.local -e .`
To install locally, for a fixed version:
- `$ pip3 install --prefix=$HOME/.local`
## Usage
# Usage
- See examples directories for IPython notebooks.
- Please refer to the [documentation](https://lasp.ascee.nl/) for features.
# Development docs
## Bumping version number
When bumping the version number, please update the number in
- `pyproject.toml`
- `CMakeLists.txt`
Then, create a commit with tag `vX.X.X`, and push it.
## Updating to latest version (editable mode)
When updating to the latest version of LASP in editable mode:
```bash
- $ git pull
- $ git submodule update
- $ pip install -e . -v
```

View File

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

63
cpp_src/CMakeLists.txt Normal file
View File

@ -0,0 +1,63 @@
# src/lasp/CMakeLists.txt
# Armadillo, don't build the wrapper lib, but instead directly link to
# openblas.
add_definitions(-DARMA_DONT_USE_WRAPPER)
configure_file(lasp_config.h.in lasp_config.h)
include_directories(${CMAKE_CURRENT_BINARY_DIR})
include_directories(SYSTEM
${PROJECT_SOURCE_DIR}/third_party/armadillo-code/include)
include_directories(${PROJECT_SOURCE_DIR}/third_party/DebugTrace-cpp/include)
include_directories(${PROJECT_SOURCE_DIR}/third_party/gsl-lite/include)
include_directories(${PROJECT_SOURCE_DIR}/third_party/tomlplusplus/include)
include_directories(${PROJECT_SOURCE_DIR}/third_party/thread-pool)
if(LASP_HAS_RTAUDIO)
include_directories(${PROJECT_SOURCE_DIR}/third_party/rtaudio)
endif()
if(LASP_HAS_ULDAQ)
include_directories(${PROJECT_SOURCE_DIR}/third_party/uldaq/src)
endif()
add_subdirectory(device)
add_subdirectory(dsp)
pybind11_add_module(lasp_cpp MODULE lasp_cpp.cpp
pybind11/lasp_deviceinfo.cpp
pybind11/lasp_daqconfig.cpp
pybind11//lasp_dsp_pybind.cpp
pybind11/lasp_streammgr.cpp
pybind11/lasp_daq.cpp
pybind11/lasp_deviceinfo.cpp
pybind11/lasp_pyindatahandler.cpp
pybind11/lasp_siggen.cpp
)
target_link_libraries(lasp_cpp PRIVATE lasp_device_lib lasp_dsp_lib
${OpenMP_CXX_LIBRARIES} ${LASP_FFT_LIBS} ${TARGET_OS_LINKLIBS})
target_compile_definitions(lasp_cpp PRIVATE
MODULE_NAME=$<TARGET_FILE_BASE_NAME:lasp_cpp>
VERSION_INFO="${PY_FULL_VERSION}"
)
# Hide all symbols by default (including external libraries on Linux)
if(CMAKE_BUILD_TYPE STREQUAL "Release")
set_target_properties(lasp_cpp PROPERTIES
CXX_VISIBILITY_PRESET "hidden"
VISIBILITY_INLINES_HIDDEN true)
if (CMAKE_SYSTEM_NAME MATCHES "Linux")
target_link_options(lasp_cpp PRIVATE "LINKER:--exclude-libs,ALL")
endif()
endif()
if(DEFINED PY_BUILD_CMAKE_MODULE_NAME)
# Install the Python module
install(TARGETS lasp_cpp
EXCLUDE_FROM_ALL
COMPONENT python_modules
DESTINATION ${PY_BUILD_CMAKE_MODULE_NAME})
endif()

View File

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

View File

@ -1,4 +1,4 @@
/* #define DEBUGTRACE_ENABLED */
// #define DEBUGTRACE_ENABLED
#include "debugtrace.hpp"
#include "lasp_daqconfig.h"
@ -44,34 +44,41 @@ Daq::Daq(const DeviceInfo &devinfo, const DaqConfiguration &config)
: DaqConfiguration(config), DeviceInfo(devinfo) {
DEBUGTRACE_ENTER;
if (duplexMode()) {
if (neninchannels() == 0) {
throw rte("Duplex mode enabled, but no input channels enabled");
}
if (nenoutchannels() == 0) {
throw rte("Duplex mode enabled, but no output channels enabled");
}
}
if(!duplexMode() && monitorOutput) {
throw rte("Output monitoring only allowed when running in duplex mode");
throw rte("Duplex mode requires enabling both input and output channels. Please make sure at least one output channel is enabled, or disable hardware output loopback in DAQ configuration.");
}
if (!hasInternalOutputMonitor && monitorOutput) {
throw rte(
"Output monitor flag set, but device does not have output monitor");
"Output monitor flag set, but device does not have hardware output monitor.");
}
if (!config.match(devinfo)) {
throw rte("DaqConfiguration does not match device info");
}
if (neninchannels(false) > devinfo.ninchannels) {
throw rte(
"Number of enabled input channels is higher than device capability");
{
const int hich = getHighestEnabledInChannel();
if (hich + 1 > devinfo.ninchannels)
{
throw rte(
string("Highest of enabled input channel: ") +
to_string(hich) +
string(" is higher than device capability, which is: ") +
to_string(ninchannels) + ".");
}
}
if (nenoutchannels() > devinfo.noutchannels) {
throw rte(
"Number of enabled output channels is higher than device capability");
{
const int hoch = getHighestEnabledOutChannel();
if (hoch + 1 > devinfo.noutchannels)
{
throw rte(
string("Highest of enabled output channel: ") +
to_string(hoch) +
string(" is higher than device capability, which is: ") +
to_string(noutchannels) + ".");
}
}
}

View File

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

View File

@ -35,12 +35,14 @@ DaqConfiguration::DaqConfiguration(const DeviceInfo &device) {
us i = 0;
for (auto &inch : inchannel_config) {
inch.name = "Unnamed input channel " + std::to_string(i);
inch.rangeIndex = device.prefInputRangeIndex;
i++;
}
i = 0;
for (auto &outch : outchannel_config) {
outch.name = "Unnamed output channel " + std::to_string(i);
outch.rangeIndex = device.prefOutputRangeIndex;
i++;
}
@ -54,10 +56,12 @@ DaqConfiguration::DaqConfiguration(const DeviceInfo &device) {
}
bool DaqConfiguration::match(const DeviceInfo &dev) const {
DEBUGTRACE_ENTER;
return (dev.device_name == device_name && dev.api == api);
}
int DaqConfiguration::getHighestEnabledInChannel() const {
DEBUGTRACE_ENTER;
for (int i = inchannel_config.size() - 1; i > -1; i--) {
if (inchannel_config.at(i).enabled)
return i;
@ -66,13 +70,15 @@ int DaqConfiguration::getHighestEnabledInChannel() const {
}
int DaqConfiguration::getHighestEnabledOutChannel() const {
for (us i = outchannel_config.size() - 1; i >= 0; i--) {
DEBUGTRACE_ENTER;
for (int i = outchannel_config.size() - 1; i > -1; i--) {
if (outchannel_config.at(i).enabled)
return i;
}
return -1;
}
int DaqConfiguration::getLowestEnabledInChannel() const {
DEBUGTRACE_ENTER;
for (us i = 0; i < inchannel_config.size(); i++) {
if (inchannel_config.at(i).enabled)
return i;

View File

@ -148,7 +148,14 @@ const DaqApi rtaudioAsioApi("RtAudio Windows ASIO", LASP_RTAUDIO_APICODE,
#endif
#if LASP_HAS_PORTAUDIO == 1
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
class DeviceInfo;

View File

@ -68,14 +68,26 @@ public:
us prefFramesPerBlockIndex = 0;
/**
* @brief Available ranges for the input, i.e. +/- 1V and/or +/- 10 V etc.
* @brief Available ranges for the input, i.e. +/- 1V and/or +/- 10 V etc.
*/
dvec availableInputRanges;
/**
* @brief Its preffered range
* @brief Available ranges for the output, i.e. +/- 1V and/or +/- 10 V etc.
*/
dvec availableOutputRanges;
/**
* @brief Its preffered input range
*/
int prefInputRangeIndex = 0;
/**
* @brief Its preffered output range
*/
int prefOutputRangeIndex = 0;
/**
* @brief The number of input channels available for the device
*/
@ -125,13 +137,29 @@ public:
bool duplexModeForced = false;
/**
* @brief The physical quantity of the output signal. For 'normal' audio
* @brief Indicates whether the device is able to run in duplex mode. If false,
* devices cannot run in duplex mode, and the `duplexModeForced` flag is meaningless.
*/
bool hasDuplexMode = false;
/**
* @brief The physical quantity of the input signal from DAQ. For 'normal' audio
* interfaces, this is typically a 'number' between +/- full scale. For some
* real DAQ devices however, the input quantity corresponds to a physical signal,
* such a Volts.
*/
DaqChannel::Qty physicalInputQty = DaqChannel::Qty::Number;
/**
* @brief The physical quantity of the output signal from DAQ. For 'normal' audio
* devices, this is typically a 'number' between +/- full scale. For some
* devices however, the output quantity corresponds to a physical signal,
* real DAQ devices however, the input quantity corresponds to a physical signal,
* such a Volts.
*/
DaqChannel::Qty physicalOutputQty = DaqChannel::Qty::Number;
/**
* @brief String representation of DeviceInfo
*

View File

@ -1,11 +1,11 @@
/* #define DEBUGTRACE_ENABLED */
// #define DEBUGTRACE_ENABLED
#include "lasp_indatahandler.h"
#include "debugtrace.hpp"
#include "lasp_streammgr.h"
#include <thread>
InDataHandler::InDataHandler(SmgrHandle mgr, const InCallbackType cb,
const InResetType resetfcn)
const ResetCallbackType resetfcn)
: _mgr(mgr), inCallback(cb), reset(resetfcn)
#if LASP_DEBUG == 1
,
@ -29,20 +29,22 @@ void InDataHandler::start() {
}
void InDataHandler::stop() {
DEBUGTRACE_ENTER;
checkRightThread();
// checkRightThread();
#if LASP_DEBUG == 1
stopCalled = true;
#endif
if (SmgrHandle handle = _mgr.lock()) {
handle->removeInDataHandler(*this);
if (SmgrHandle smgr = _mgr.lock()) {
smgr->removeInDataHandler(*this);
} else {
DEBUGTRACE_PRINT("No stream manager alive anymore!");
}
}
InDataHandler::~InDataHandler() {
DEBUGTRACE_ENTER;
checkRightThread();
#if LASP_DEBUG == 1
// checkRightThread();
if (!stopCalled) {
std::cerr << "************ BUG: Stop function not called while arriving at "
"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.
*/
using InResetType = std::function<void(const Daq *)>;
using ResetCallbackType = std::function<void(const Daq *)>;
class InDataHandler {
@ -38,7 +38,7 @@ protected:
public:
~InDataHandler();
const InCallbackType inCallback;
const InResetType reset;
const ResetCallbackType reset;
/**
* @brief When constructed, the handler is added to the stream manager, which
@ -50,7 +50,7 @@ public:
* changes state.
*/
InDataHandler(SmgrHandle mgr, InCallbackType cb,
InResetType resetfcn);
ResetCallbackType resetfcn);
/**
* @brief Adds the current InDataHandler to the list of handlers in the

View File

@ -17,37 +17,46 @@ using rte = std::runtime_error;
using std::vector;
using lck = std::scoped_lock<std::mutex>;
class RtAudioDeviceInfo : public DeviceInfo {
const unsigned RTAUDIO_MAX_CHANNELS = 8;
class RtAudioDeviceInfo : public DeviceInfo
{
public:
/**
* @brief Specific for the device (Sub-API). Important for the RtAudio
* backend, as RtAudio is able to handle different API's.
*/
int _api_devindex;
virtual std::unique_ptr<DeviceInfo> clone() const override {
int ID; // Copy of RtAudio::DeviceInfo::ID
virtual std::unique_ptr<DeviceInfo> clone() const override
{
return std::make_unique<DeviceInfo>(*this);
}
};
void fillRtAudioDeviceInfo(DeviceInfoList &devinfolist) {
void fillRtAudioDeviceInfo(DeviceInfoList &devinfolist)
{
DEBUGTRACE_ENTER;
vector<RtAudio::Api> apis;
RtAudio::getCompiledApi(apis);
for (auto api : apis) {
for (auto api : apis)
{
RtAudio rtaudio(api);
us count = rtaudio.getDeviceCount();
for (us devno = 0; devno < count; devno++) {
const us count = rtaudio.getDeviceCount();
const auto ids = rtaudio.getDeviceIds();
for (us i = 0; i < count; i++)
{
us id = ids.at(i);
RtAudio::DeviceInfo devinfo = rtaudio.getDeviceInfo(id);
RtAudio::DeviceInfo devinfo = rtaudio.getDeviceInfo(devno);
if (!devinfo.probed) {
// Device capabilities not successfully probed. Continue to next
continue;
}
// "Our device info struct"
RtAudioDeviceInfo d;
switch (api) {
switch (api)
{
case RtAudio::LINUX_ALSA:
d.api = rtaudioAlsaApi;
break;
@ -70,42 +79,49 @@ void fillRtAudioDeviceInfo(DeviceInfoList &devinfolist) {
}
d.device_name = devinfo.name;
d._api_devindex = devno;
d.ID = id;
/// When 48k is available we overwrite the default sample rate with the 48
/// kHz value, which is our preffered rate,
bool rate_48k_found = false;
for (us j = 0; j < devinfo.sampleRates.size(); j++) {
for (us j = 0; j < devinfo.sampleRates.size(); j++)
{
us rate_int = devinfo.sampleRates[j];
d.availableSampleRates.push_back((double)rate_int);
if (!rate_48k_found) {
if (!rate_48k_found)
{
if (devinfo.preferredSampleRate == rate_int) {
if (devinfo.preferredSampleRate == rate_int)
{
d.prefSampleRateIndex = j;
}
if (rate_int == 48000) {
if (rate_int == 48000)
{
d.prefSampleRateIndex = j;
rate_48k_found = true;
}
}
}
d.noutchannels = devinfo.outputChannels;
d.ninchannels = devinfo.inputChannels;
d.noutchannels = std::min(devinfo.outputChannels, RTAUDIO_MAX_CHANNELS);
d.ninchannels = std::min(devinfo.inputChannels, RTAUDIO_MAX_CHANNELS);
d.availableInputRanges = {1.0};
d.availableOutputRanges = {1.0};
RtAudioFormat formats = devinfo.nativeFormats;
if (formats & RTAUDIO_SINT8) {
if (formats & RTAUDIO_SINT8)
{
d.availableDataTypes.push_back(
DataTypeDescriptor::DataType::dtype_int8);
}
if (formats & RTAUDIO_SINT16) {
if (formats & RTAUDIO_SINT16)
{
d.availableDataTypes.push_back(
DataTypeDescriptor::DataType::dtype_int16);
}
@ -113,15 +129,18 @@ void fillRtAudioDeviceInfo(DeviceInfoList &devinfolist) {
/* d.availableDataTypes.push_back(DataTypeDescriptor::DataType::dtype_int24);
*/
/* } */
if (formats & RTAUDIO_SINT32) {
if (formats & RTAUDIO_SINT32)
{
d.availableDataTypes.push_back(
DataTypeDescriptor::DataType::dtype_fl32);
}
if (formats & RTAUDIO_FLOAT64) {
if (formats & RTAUDIO_FLOAT64)
{
d.availableDataTypes.push_back(
DataTypeDescriptor::DataType::dtype_fl64);
}
if (d.availableDataTypes.size() == 0) {
if (d.availableDataTypes.size() == 0)
{
std::cerr << "RtAudio: No data types found in device!" << endl;
}
@ -139,9 +158,8 @@ static int mycallback(void *outputBuffer, void *inputBuffer,
unsigned int nFrames, double streamTime,
RtAudioStreamStatus status, void *userData);
static void myerrorcallback(RtAudioError::Type, const string &errorText);
class RtAudioDaq : public Daq {
class RtAudioDaq : public Daq
{
RtAudio rtaudio;
const us nFramesPerBlock;
@ -181,10 +199,11 @@ public:
/// 0. For now, our fix is to shift out the channels we want, and let
/// RtAudio pass on all channels.
inParams->firstChannel = 0;
inParams->nChannels = devinfo_gen.ninchannels;
inParams->deviceId = devinfo._api_devindex;
} else {
inParams->nChannels = devinfo.ninchannels;
inParams->deviceId = devinfo.ID;
}
else
{
outParams = std::make_unique<RtAudio::StreamParameters>();
@ -192,8 +211,8 @@ public:
/// 0. For now, our fix is to shift out the channels we want, and let
/// RtAudio pass on all channels.
outParams->firstChannel = 0;
outParams->nChannels = devinfo_gen.noutchannels;
outParams->deviceId = devinfo._api_devindex;
outParams->nChannels = devinfo.noutchannels;
outParams->deviceId = devinfo.ID;
}
RtAudio::StreamOptions streamoptions;
@ -206,7 +225,8 @@ public:
RtAudioFormat format;
using Dtype = DataTypeDescriptor::DataType;
const Dtype dtype = dataType();
switch (dtype) {
switch (dtype)
{
case Dtype::dtype_fl32:
DEBUGTRACE_PRINT("Datatype float32");
format = RTAUDIO_FLOAT32;
@ -237,36 +257,46 @@ public:
unsigned int nFramesPerBlock_copy = nFramesPerBlock;
// Final step: open the stream.
rtaudio.openStream(outParams.get(), inParams.get(), format,
static_cast<us>(samplerate()), &nFramesPerBlock_copy,
mycallback, (void *)this, &streamoptions,
&myerrorcallback);
RtAudioErrorType err = rtaudio.openStream(outParams.get(), inParams.get(), format,
static_cast<us>(samplerate()), &nFramesPerBlock_copy,
mycallback, (void *)this, &streamoptions);
if (err != RTAUDIO_NO_ERROR)
{
throw std::runtime_error(string("Error opening stream: ") + rtaudio.getErrorText());
}
if (nFramesPerBlock_copy != nFramesPerBlock) {
if (nFramesPerBlock_copy != nFramesPerBlock)
{
throw rte(string("Got different number of frames per block back from RtAudio "
"backend: ") + std::to_string(nFramesPerBlock_copy) + ". I do not know what to do.");
"backend: ") +
std::to_string(nFramesPerBlock_copy) + ". I do not know what to do.");
}
}
virtual void start(InDaqCallback inCallback,
OutDaqCallback outCallback) override final {
OutDaqCallback outCallback) override final
{
DEBUGTRACE_ENTER;
assert(!monitorOutput);
if (getStreamStatus().runningOK()) {
if (getStreamStatus().runningOK())
{
throw rte("Stream already running");
}
// Logical XOR
if (inCallback && outCallback) {
if (inCallback && outCallback)
{
throw rte("Either input or output stream possible for RtAudio. "
"Stream duplex mode not provided.");
}
if (neninchannels() > 0) {
if (!inCallback) {
if (neninchannels() > 0)
{
if (!inCallback)
{
throw rte(
"Input callback given, but stream does not provide input data");
@ -274,8 +304,10 @@ public:
_incallback = inCallback;
}
if (nenoutchannels() > 0) {
if (!outCallback) {
if (nenoutchannels() > 0)
{
if (!outCallback)
{
throw rte(
"Output callback given, but stream does not provide output data");
}
@ -283,7 +315,11 @@ public:
}
// Start the stream. Throws on error.
rtaudio.startStream();
const auto err = rtaudio.startStream();
if (err != RTAUDIO_NO_ERROR)
{
throw std::runtime_error(string("Error starting stream: ") + rtaudio.getErrorText());
}
// If we are here, we are running without errors.
StreamStatus status;
@ -293,10 +329,15 @@ public:
StreamStatus getStreamStatus() const override final { return _streamStatus; }
void stop() override final {
void stop() override final
{
DEBUGTRACE_ENTER;
if (getStreamStatus().runningOK()) {
rtaudio.stopStream();
if (getStreamStatus().runningOK())
{
const auto err = rtaudio.stopStream();
if(err != RTAUDIO_NO_ERROR) {
std::cerr << "Error occured while stopping the stream: " << rtaudio.getErrorText() << endl;
}
}
StreamStatus s = _streamStatus;
s.isRunning = false;
@ -305,14 +346,16 @@ public:
}
int streamCallback(void *outputBuffer, void *inputBuffer,
unsigned int nFrames, RtAudioStreamStatus status) {
unsigned int nFrames, RtAudioStreamStatus status)
{
DEBUGTRACE_ENTER;
using se = StreamStatus::StreamError;
int rval = 0;
auto stopWithError = [&](se e) {
auto stopWithError = [&](se e)
{
DEBUGTRACE_PRINT("stopWithError");
StreamStatus stat = _streamStatus;
stat.errorType = e;
@ -321,7 +364,8 @@ public:
rval = 1;
};
switch (status) {
switch (status)
{
case RTAUDIO_INPUT_OVERFLOW:
stopWithError(se::inputXRun);
return 1;
@ -340,14 +384,16 @@ public:
const us neninchannels = this->neninchannels();
const us nenoutchannels = this->nenoutchannels();
const us sw = dtype_descr.sw;
if (nFrames != nFramesPerBlock) {
if (nFrames != nFramesPerBlock)
{
cerr << "RtAudio backend error: nFrames does not match block size!"
<< endl;
stopWithError(se::logicError);
return 1;
}
if (inputBuffer) {
if (inputBuffer)
{
assert(_incallback);
std::vector<byte_t *> ptrs;
ptrs.reserve(neninchannels);
@ -358,8 +404,10 @@ public:
assert(ch_max < ninchannels);
/// Only pass on the pointers of the channels we want
for (us ch = ch_min; ch <= ch_max; ch++) {
if (inchannel_config.at(ch).enabled) {
for (us ch = ch_min; ch <= ch_max; ch++)
{
if (inchannel_config.at(ch).enabled)
{
byte_t *ptr =
static_cast<byte_t *>(inputBuffer) + sw * ch * nFramesPerBlock;
ptrs.push_back(ptr);
@ -368,10 +416,11 @@ public:
DaqData d{nFramesPerBlock, neninchannels, dtype};
d.copyInFromRaw(ptrs);
_incallback(d);
_incallback(d);
}
if (outputBuffer) {
if (outputBuffer)
{
assert(_outcallback);
std::vector<byte_t *> ptrs;
ptrs.reserve(nenoutchannels);
@ -383,8 +432,10 @@ public:
assert(ch_min < noutchannels);
assert(ch_max < noutchannels);
/// Only pass on the pointers of the channels we want
for (us ch = ch_min; ch <= ch_max; ch++) {
if (outchannel_config.at(ch).enabled) {
for (us ch = ch_min; ch <= ch_max; ch++)
{
if (outchannel_config.at(ch).enabled)
{
ptrs.push_back(static_cast<byte_t *>(outputBuffer) +
sw * ch * nFramesPerBlock);
}
@ -394,7 +445,8 @@ public:
_outcallback(d);
// Copy over the buffer
us j = 0;
for (auto ptr : ptrs) {
for (auto ptr : ptrs)
{
d.copyToRaw(j, ptr);
j++;
}
@ -410,17 +462,16 @@ public:
};
std::unique_ptr<Daq> createRtAudioDevice(const DeviceInfo &devinfo,
const DaqConfiguration &config) {
const DaqConfiguration &config)
{
return std::make_unique<RtAudioDaq>(devinfo, config);
}
void myerrorcallback(RtAudioError::Type, const string &errorText) {
cerr << "RtAudio backend stream error: " << errorText << endl;
}
int mycallback(
void *outputBuffer, void *inputBuffer, unsigned int nFrames,
__attribute__((unused)) double streamTime, // Not used parameter streamTime
RtAudioStreamStatus status, void *userData) {
RtAudioStreamStatus status, void *userData)
{
return static_cast<RtAudioDaq *>(userData)->streamCallback(
outputBuffer, inputBuffer, nFrames, status);

View File

@ -1,15 +1,21 @@
/* #define DEBUGTRACE_ENABLED */
// #define DEBUGTRACE_ENABLED
#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 <algorithm>
#include <functional>
#include <iostream>
#include <memory>
#include <mutex>
#include <thread>
#include "debugtrace.hpp"
#include "lasp_biquadbank.h"
#include "lasp_indatahandler.h"
#include "lasp_thread.h"
using namespace std::literals::chrono_literals;
using std::cerr;
using std::endl;
@ -24,7 +30,7 @@ using rte = std::runtime_error;
std::weak_ptr<StreamMgr> _mgr;
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
@ -35,11 +41,11 @@ using Lck = std::scoped_lock<std::mutex>;
SmgrHandle StreamMgr::getInstance() {
DEBUGTRACE_ENTER;
std::scoped_lock<std::mutex> lck(_mgr_mutex);
auto mgr = _mgr.lock();
if (!mgr) {
// Double Check Locking Pattern, if two threads would simultaneously
// instantiate the singleton instance.
Lck lck(_mgr_mutex);
auto mgr = _mgr.lock();
if (mgr) {
@ -81,44 +87,75 @@ void StreamMgr::rescanDAQDevices(bool background,
std::function<void()> callback) {
DEBUGTRACE_ENTER;
DEBUGTRACE_PRINT(background);
if (_scanningDevices) {
throw rte("A background device scan is already busy");
}
Lck lck(_mtx);
checkRightThread();
if (_inputStream || _outputStream) {
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();
if (!background) {
_scanningDevices = true;
rescanDAQDevices_impl(callback);
} else {
DEBUGTRACE_PRINT("Rescanning DAQ devices on different thread...");
_scanningDevices = true;
_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) {
DEBUGTRACE_ENTER;
std::scoped_lock lck(_devices_mtx);
_devices = DeviceInfo::getDeviceInfo();
assert(!_inputStream && !_outputStream);
Lck lck(_mtx);
// Alsa spits out annoying messages that are not useful
{
MuteErrHandler guard;
_devices = DeviceInfo::getDeviceInfo();
}
if (callback) {
callback();
}
_scanningDevices = false;
}
void StreamMgr::inCallback(const DaqData &data) {
DEBUGTRACE_ENTER;
std::scoped_lock<std::mutex> lck(_inDataHandler_mtx);
Lck lck(_mtx);
assert(_inputFilters.size() == data.nchannels);
if (std::count_if(_inputFilters.cbegin(), _inputFilters.cend(),
[](const auto &a) { return bool(a); }) > 0) {
/// Found a filter in vector of input filters. So we have to apply the
/// filters to each channel.
@ -138,12 +175,13 @@ void StreamMgr::inCallback(const DaqData &data) {
}
}
DEBUGTRACE_PRINT("Calling incallback for handlers (filtered)...");
for (auto &handler : _inDataHandlers) {
handler->inCallback(input_filtered);
}
} else {
/// No input filters
DEBUGTRACE_PRINT("Calling incallback for handlers...");
for (auto &handler : _inDataHandlers) {
handler->inCallback(data);
}
@ -151,12 +189,10 @@ void StreamMgr::inCallback(const DaqData &data) {
}
void StreamMgr::setSiggen(std::shared_ptr<Siggen> siggen) {
DEBUGTRACE_ENTER;
checkRightThread();
std::scoped_lock<std::mutex> lck(_siggen_mtx);
Lck lck(_mtx);
// If not set to nullptr, and a stream is running, we update the signal
// generator by resetting it.
if (isStreamRunningOK(StreamType::output) && siggen) {
@ -179,7 +215,8 @@ void StreamMgr::setSiggen(std::shared_ptr<Siggen> siggen) {
*
* @return
*/
template <typename T> bool fillData(DaqData &data, const vd &signal) {
template <typename T>
bool fillData(DaqData &data, const vd &signal) {
/* DEBUGTRACE_ENTER; */
assert(data.nframes == signal.size());
@ -212,29 +249,28 @@ template <typename T> bool fillData(DaqData &data, const vd &signal) {
return true;
}
void StreamMgr::outCallback(DaqData &data) {
DEBUGTRACE_ENTER;
/* DEBUGTRACE_ENTER; */
std::scoped_lock<std::mutex> lck(_siggen_mtx);
Lck lck(_mtx);
if (_siggen) {
vd signal = _siggen->genSignal(data.nframes);
switch (data.dtype) {
case (DataTypeDescriptor::DataType::dtype_fl32):
fillData<float>(data, signal);
break;
case (DataTypeDescriptor::DataType::dtype_fl64):
fillData<double>(data, signal);
break;
case (DataTypeDescriptor::DataType::dtype_int8):
fillData<int8_t>(data, signal);
break;
case (DataTypeDescriptor::DataType::dtype_int16):
fillData<int16_t>(data, signal);
break;
case (DataTypeDescriptor::DataType::dtype_int32):
fillData<int32_t>(data, signal);
break;
case (DataTypeDescriptor::DataType::dtype_fl32):
fillData<float>(data, signal);
break;
case (DataTypeDescriptor::DataType::dtype_fl64):
fillData<double>(data, signal);
break;
case (DataTypeDescriptor::DataType::dtype_int8):
fillData<int8_t>(data, signal);
break;
case (DataTypeDescriptor::DataType::dtype_int16):
fillData<int16_t>(data, signal);
break;
case (DataTypeDescriptor::DataType::dtype_int32):
fillData<int32_t>(data, signal);
break;
}
} else {
// Set all values to 0.
@ -244,7 +280,17 @@ void StreamMgr::outCallback(DaqData &data) {
StreamMgr::~StreamMgr() {
DEBUGTRACE_ENTER;
checkRightThread();
while (_scanningDevices) {
std::this_thread::sleep_for(10us);
}
#if LASP_DEBUG == 1
{ // Careful, this lock needs to be released to make sure the streams can
// obtain a lock to the stream manager.
Lck lck(_mtx);
checkRightThread();
}
#endif
// Stream manager now handled by shared pointer. Each indata handler gets a
// 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
@ -257,31 +303,35 @@ StreamMgr::~StreamMgr() {
// virtual methods. This was really a bug.
_inputStream.reset();
_outputStream.reset();
}
void StreamMgr::stopAllStreams() {
DEBUGTRACE_ENTER;
checkRightThread();
{
Lck lck(_mtx);
checkRightThread();
}
// No lock here!
_inputStream.reset();
_outputStream.reset();
}
void StreamMgr::startStream(const DaqConfiguration &config) {
DEBUGTRACE_ENTER;
if (_scanningDevices) {
throw rte("DAQ device scan is busy. Cannot start stream.");
}
Lck lck(_mtx);
checkRightThread();
bool isInput = std::count_if(config.inchannel_config.cbegin(),
config.inchannel_config.cend(),
[](auto &i) { return i.enabled; });
[](auto &i) { return i.enabled; }) > 0;
bool isOutput = std::count_if(config.outchannel_config.cbegin(),
config.outchannel_config.cend(),
[](auto &i) { return i.enabled; });
[](auto &i) { return i.enabled; }) > 0;
// Find the first device that matches with the configuration
std::scoped_lock lck(_devices_mtx);
DeviceInfo *devinfo = nullptr;
// Match configuration to a device in the list of devices
@ -302,34 +352,40 @@ void StreamMgr::startStream(const DaqConfiguration &config) {
bool isDuplex = isInput && isOutput;
if (!isInput && !isOutput) {
throw rte("Neither input, nor output channels enabled for "
"stream. Cannot start.");
throw rte(
"Attempted stream start failed, stream does not have any enabled "
"channels. Please first enable channels in the channel configuration.");
}
if (isInput && _inputStream) {
throw rte("Error: an input stream is already running. Please "
"first stop existing stream");
throw rte(
"Error: an input stream is already running. Please "
"first stop existing stream");
} else if (isOutput && _outputStream) {
throw rte("Error: output stream is already running. Please "
"first stop existing stream");
throw rte(
"Error: output stream is already running. Please "
"first stop existing stream");
} else if (_inputStream) {
if (_inputStream->duplexMode() && isOutput) {
throw rte("Error: output stream is already running (in duplex mode). "
"Please "
"first stop existing stream");
throw rte(
"Error: output stream is already running (in duplex mode). "
"Please "
"first stop existing stream");
}
}
if (_outputStream && isInput && _outputStream->duplexModeForced &&
config.match(*_outputStream)) {
throw rte("This device is already opened for output. If input is also "
"required, please enable duplex mode for this device");
throw rte(
"This device is already opened for output. If input is also "
"required, please enable duplex mode for this device");
}
if (_inputStream && isOutput && _inputStream->duplexModeForced &&
config.match(*_inputStream)) {
throw rte("This device is already opened for input. If output is also "
"required, please enable duplex mode for this device");
throw rte(
"This device is already opened for input. If output is also "
"required, please enable duplex mode for this device");
}
InDaqCallback inCallback;
@ -352,13 +408,13 @@ void StreamMgr::startStream(const DaqConfiguration &config) {
d fs = daq->samplerate();
/// Create input filters
_inputFilters.clear();
/// No input filter for monitor channel, which comes as the first input channel
/// In the list
/// No input filter for monitor channel, which comes as the first input
/// channel In the list
if (config.monitorOutput && devinfo->hasInternalOutputMonitor) {
_inputFilters.push_back(nullptr);
}
for (auto &ch : daq->inchannel_config) {
if (ch.enabled) {
if (ch.digitalHighPassCutOn < 0) {
@ -371,7 +427,7 @@ void StreamMgr::startStream(const DaqConfiguration &config) {
SeriesBiquad::firstOrderHighPass(fs, ch.digitalHighPassCutOn)));
}
}
} // End of input filter creation
} // End of input filter creation
}
if (isOutput) {
@ -398,46 +454,58 @@ void StreamMgr::startStream(const DaqConfiguration &config) {
}
}
void StreamMgr::stopStream(const StreamType t) {
DEBUGTRACE_ENTER;
checkRightThread();
bool resetHandlers = false;
std::unique_ptr<Daq> *streamToStop = nullptr;
if (t == StreamType::input) {
if (!_inputStream) {
throw rte("Input stream is not running");
{ // Mutex locked in this scope
Lck lck(_mtx);
if (t == StreamType::input) {
if (!_inputStream) {
throw rte("Input stream is not running");
}
streamToStop = std::addressof(_inputStream);
resetHandlers = true;
} else {
/// t == output
/// Kill input stream in case that one is a duplex stream
if (_inputStream && _inputStream->duplexMode()) {
streamToStop = std::addressof(_inputStream);
} else {
if (!_outputStream) {
throw rte("Output stream is not running");
}
streamToStop = std::addressof(_outputStream);
} // end else
}
/// Kills input stream
_inputStream.reset();
/// Send reset to all in data handlers
} // 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);
}
} else {
/// t == output
/// Kill input stream in case that one is a duplex stream
if (_inputStream && _inputStream->duplexMode()) {
_inputStream.reset();
} else {
if (!_outputStream) {
throw rte("Output stream is not running");
}
_outputStream.reset();
} // end else
}
}
void StreamMgr::addInDataHandler(InDataHandler *handler) {
DEBUGTRACE_ENTER;
Lck lck(_mtx);
checkRightThread();
assert(handler);
std::scoped_lock<std::mutex> lck(_inDataHandler_mtx);
handler->reset(_inputStream.get());
if (std::find(_inDataHandlers.cbegin(), _inDataHandlers.cend(), handler) !=
_inDataHandlers.cend()) {
throw std::runtime_error("Error: handler already added. Probably start() "
"is called more than once on a handler object");
throw std::runtime_error(
"Error: handler already added. Probably start() "
"is called more than once on a handler object");
}
_inDataHandlers.push_back(handler);
DEBUGTRACE_PRINT(_inDataHandlers.size());
@ -445,16 +513,17 @@ void StreamMgr::addInDataHandler(InDataHandler *handler) {
void StreamMgr::removeInDataHandler(InDataHandler &handler) {
DEBUGTRACE_ENTER;
checkRightThread();
std::scoped_lock<std::mutex> lck(_inDataHandler_mtx);
Lck lck(_mtx);
// checkRightThread();
_inDataHandlers.remove(&handler);
DEBUGTRACE_PRINT(_inDataHandlers.size());
}
Daq::StreamStatus StreamMgr::getStreamStatus(const StreamType type) const {
/* DEBUGTRACE_ENTER; */
DEBUGTRACE_ENTER;
Lck lck(_mtx);
checkRightThread();
// Default constructor, says stream is not running, but also no errors
@ -467,7 +536,7 @@ Daq::StreamStatus StreamMgr::getStreamStatus(const StreamType type) const {
}
const Daq *StreamMgr::getDaq(StreamType type) const {
Lck lck(_mtx);
checkRightThread();
if (type == StreamType::input) {

View File

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

View File

@ -68,6 +68,7 @@ void fillUlDaqDeviceInfo(DeviceInfoList &devinfolist) {
}
devinfo.physicalOutputQty = DaqChannel::Qty::Voltage;
devinfo.physicalInputQty = DaqChannel::Qty::Voltage;
devinfo.availableDataTypes.push_back(
DataTypeDescriptor::DataType::dtype_fl64);
@ -79,7 +80,9 @@ void fillUlDaqDeviceInfo(DeviceInfoList &devinfolist) {
devinfo.availableFramesPerBlock = {512, 1024, 2048, 4096, 8192};
devinfo.availableInputRanges = {1.0, 10.0};
devinfo.availableOutputRanges = {10.0};
devinfo.prefInputRangeIndex = 0;
devinfo.prefOutputRangeIndex = 0;
devinfo.ninchannels = 4;
devinfo.noutchannels = 1;
@ -90,6 +93,7 @@ void fillUlDaqDeviceInfo(DeviceInfoList &devinfolist) {
devinfo.hasInternalOutputMonitor = true;
devinfo.hasDuplexMode = true;
devinfo.duplexModeForced = true;
// Finally, this devinfo is pushed back in list

View File

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

View File

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

View File

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

View File

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

View File

@ -2,7 +2,6 @@
#include "lasp_biquadbank.h"
#include "debugtrace.hpp"
#include "lasp_thread.h"
#include <cassert>
#include <vector>
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;
return;
}
fltdata.each_row() %= _sens.as_row();
fltdata.each_row() /= _sens.as_row();
if (_filterPrototype) {

View File

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

View File

@ -10,31 +10,62 @@ using rte = std::runtime_error;
inline d level_amp(d level_dB) { return pow(10, level_dB / 20); }
using mutexlock = std::scoped_lock<std::mutex>;
using slock = std::scoped_lock<std::recursive_mutex>;
vd Siggen::genSignal(const us nframes) {
DEBUGTRACE_ENTER;
mutexlock lck(_mtx);
slock lck(_mtx);
DEBUGTRACE_PRINT(nframes);
vd signal(nframes, arma::fill::value(_dc_offset));
if (!_muted) {
vd signal_dynamic = _level_linear * genSignalUnscaled(nframes);
// Filter signal
for (auto f : _filters) {
assert(f.second);
f.second->filter(signal_dynamic);
}
signal += signal_dynamic;
}
// Check whether we are running / not for signal interruption.
bool activated = false;
if (_interrupt_period_s < 0) {
activated = true;
} else {
if (_interruption_frame_count < _interrupt_period_s*_fs) {
activated = true;
}
_interruption_frame_count += nframes;
if (_interruption_frame_count >= 2 * _interrupt_period_s*_fs) {
_interruption_frame_count = 0;
}
}
if (activated) {
signal += signal_dynamic;
}
} // end if(!_muted)
return signal;
}
void Siggen::setInterruptPeriod(const d newPeriod) {
slock lck(_mtx);
if (newPeriod == 0) {
throw rte("Interruption period cannot be 0");
}
if (newPeriod < 0) {
_interrupt_period_s = -1;
} else {
_interrupt_period_s = newPeriod;
}
}
void Siggen::setFilter(const std::string &name,
std::shared_ptr<Filter> filter) {
DEBUGTRACE_ENTER;
mutexlock lck(_mtx);
slock lck(_mtx);
if (filter) {
_filters[name] = filter;
} else if (_filters.find(name) != _filters.end()) {
@ -43,21 +74,22 @@ void Siggen::setFilter(const std::string &name,
}
void Siggen::setDCOffset(const d offset) {
DEBUGTRACE_ENTER;
mutexlock lck(_mtx);
slock lck(_mtx);
_dc_offset = offset;
}
void Siggen::setLevel(const d level, bool dB) {
DEBUGTRACE_ENTER;
mutexlock lck(_mtx);
slock lck(_mtx);
_level_linear = dB ? level_amp(level) : level;
}
void Siggen::reset(const d newFs) {
DEBUGTRACE_ENTER;
mutexlock lck(_mtx);
slock lck(_mtx);
_fs = newFs;
for (auto &f : _filters) {
assert(f.second);
f.second->reset();
}
_interruption_frame_count = 0;
resetImpl();
}

View File

@ -27,8 +27,15 @@ private:
bool _muted = false;
protected:
std::mutex _mtx;
mutable std::recursive_mutex _mtx;
d _fs = 0;
/**
* @brief Interuption of period the signal. If set, the signal will be
* periodically turned on / off. This is useful for measuring reverberation
* times using the "interrupted noise method".
*/
int _interrupt_period_s = -1;
int _interruption_frame_count = 0;
virtual void resetImpl() = 0;
virtual vd genSignalUnscaled(const us nframes) = 0;
@ -36,6 +43,15 @@ protected:
public:
virtual ~Siggen() = default;
/**
* @brief Set the interruption period for interrupted signals.
*
* @param newPeriod If < 0, the interruption is disabled. If > 0, an
* approximate interruption number of *buffers* is computed, rounded upwards
* for which the signal generator is turned off.
*/
void setInterruptPeriod(const d newPeriod);
/**
* @brief Set a filter on the signal. For example to EQ the signal, or
* otherwise to shape the spectrum. Filters are stored in a map, and
@ -59,7 +75,7 @@ public:
*
* @param mute if tre
*/
void setMute(bool mute = true) { _muted = mute; }
void setMute(bool mute = true) { _muted = mute; _interruption_frame_count=0; }
/**
* @brief Set the level of the signal generator

View File

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

View File

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

View File

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

View File

@ -29,21 +29,27 @@ class ThreadedInDataHandlerBase {
* @brief The queue used to push elements to the handling thread.
*/
InDataHandler _indatahandler;
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.
*/
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();
@ -58,7 +64,7 @@ class ThreadedInDataHandlerBase {
void _inCallbackFromInDataHandler(const DaqData &daqdata);
public:
ThreadedInDataHandlerBase(SmgrHandle mgr, InCallbackType cb, InResetType reset);
ThreadedInDataHandlerBase(SmgrHandle mgr, InCallbackType cb, ResetCallbackType reset);
~ThreadedInDataHandlerBase();
/**
* @brief This method should be called from the derived class' constructor,

View File

@ -11,7 +11,7 @@ using std::cerr;
using std::endl;
// Safe some typing. Linspace form 0 up to (and NOT including N).
#define lin0N arma::linspace(0, N - 1, N)
#define lin0N arma::linspace<vd>(0, N - 1, N)
vd Window::hann(const us N) {
return arma::pow(arma::sin((arma::datum::pi/N) * lin0N), 2);
@ -24,8 +24,8 @@ vd Window::blackman(const us N) {
d a0 = 7938. / 18608.;
d a1 = 9240. / 18608.;
d a2 = 1430. / 18608.;
return a0 - a1 * d_cos((2 * number_pi/N) * lin0N) +
a2 * d_cos((4 * number_pi / N)* lin0N );
return a0 - a1 * arma::cos((2 * number_pi/N) * lin0N) +
a2 * arma::cos((4 * number_pi / N)* lin0N );
}
vd Window::rectangular(const us N) { return arma::ones(N); }

View File

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

View File

@ -44,6 +44,12 @@ void init_siggen(py::module &m);
PYBIND11_MODULE(lasp_cpp, m) {
#if LASP_DOUBLE_PRECISION == 1
m.attr("LASP_DOUBLE_PRECISION") = true;
#else
m.attr("LASP_DOUBLE_PRECISION") = false;
#endif
init_dsp(m);
init_deviceinfo(m);
init_daqconfiguration(m);
@ -51,13 +57,5 @@ PYBIND11_MODULE(lasp_cpp, m) {
init_streammgr(m);
init_datahandler(m);
init_siggen(m);
// We store the version number of the code via CMake, and create an
// attribute in the C++ code.
m.attr("__version__") = std::to_string(LASP_VERSION_MAJOR) + "." +
std::to_string(LASP_VERSION_MINOR);
m.attr("LASP_VERSION_MAJOR") = LASP_VERSION_MAJOR;
m.attr("LASP_VERSION_MINOR") = LASP_VERSION_MINOR;
}
/** @} */
/** @} */

View File

@ -29,6 +29,9 @@ void init_deviceinfo(py::module& m) {
devinfo.def_readonly("availableInputRanges",
&DeviceInfo::availableInputRanges);
devinfo.def_readonly("prefInputRangeIndex", &DeviceInfo::prefInputRangeIndex);
devinfo.def_readonly("availableOutputRanges",
&DeviceInfo::availableOutputRanges);
devinfo.def_readonly("prefOutputRangeIndex", &DeviceInfo::prefOutputRangeIndex);
devinfo.def_readonly("ninchannels", &DeviceInfo::ninchannels);
devinfo.def_readonly("noutchannels", &DeviceInfo::noutchannels);
@ -36,7 +39,10 @@ void init_deviceinfo(py::module& m) {
devinfo.def_readonly("hasInputACCouplingSwitch",
&DeviceInfo::hasInputACCouplingSwitch);
devinfo.def_readonly("hasDuplexMode", &DeviceInfo::hasDuplexMode);
devinfo.def_readonly("duplexModeForced", &DeviceInfo::duplexModeForced);
devinfo.def_readonly("hasInternalOutputMonitor", &DeviceInfo::hasInternalOutputMonitor);
devinfo.def_readonly("physicalInputQty", &DeviceInfo::physicalInputQty);
devinfo.def_readonly("physicalOutputQty", &DeviceInfo::physicalOutputQty);
}

View File

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

View File

@ -0,0 +1,353 @@
// #define DEBUGTRACE_ENABLED
#include <pybind11/pybind11.h>
#include <pybind11/pytypes.h>
#include <armadillo>
#include <atomic>
#include "arma_npy.h"
#include "debugtrace.hpp"
#include "lasp_clip.h"
#include "lasp_daq.h"
#include "lasp_daqdata.h"
#include "lasp_ppm.h"
#include "lasp_rtaps.h"
#include "lasp_rtsignalviewer.h"
#include "lasp_streammgr.h"
#include "lasp_threadedindatahandler.h"
using namespace std::literals::chrono_literals;
using std::cerr;
using std::endl;
using rte = std::runtime_error;
using Lck = std::scoped_lock<std::recursive_mutex>;
namespace py = pybind11;
/**
* @brief Generate a Numpy array from daqdata, does *NOT* create a copy of the
* data!. Instead, it shares the data from the DaqData container.
*
* @tparam T The type of the stored sample
* @param d The daqdata to convert
*
* @return Numpy array
*/
template <typename T, bool copy = false>
py::array_t<T> getPyArrayNoCpy(const DaqData &d) {
// https://github.com/pybind/pybind11/issues/323
//
// When a valid object is passed as 'base', it tells pybind not to take
// ownership of the data, because 'base' will own it. In fact 'packet' will
// own it, but - psss! - , we don't tell it to pybind... Alos note that ANY
// valid object is good for this purpose, so I choose "str"...
py::str dummyDataOwner;
/*
* Signature:
array_t(ShapeContainer shape,
StridesContainer strides,
const T *ptr = nullptr,
handle base = handle());
*/
return py::array_t<T>(
py::array::ShapeContainer({d.nframes, d.nchannels}), // Shape
py::array::StridesContainer( // Strides
{sizeof(T),
sizeof(T) * d.nframes}), // Strides (in bytes) for each index
reinterpret_cast<T *>(
const_cast<DaqData &>(d).raw_ptr()), // Pointer to buffer
dummyDataOwner // As stated above, now Numpy does not take ownership of
// the data pointer.
);
}
template <typename T, bool copy = false>
py::array_t<d> dmat_to_ndarray(const DaqData &d) {
// https://github.com/pybind/pybind11/issues/323
//
// When a valid object is passed as 'base', it tells pybind not to take
// ownership of the data, because 'base' will own it. In fact 'packet' will
// own it, but - psss! - , we don't tell it to pybind... Alos note that ANY
// valid object is good for this purpose, so I choose "str"...
py::str dummyDataOwner;
/*
* Signature:
array_t(ShapeContainer shape,
StridesContainer strides,
const T *ptr = nullptr,
handle base = handle());
*/
return py::array_t<T>(
py::array::ShapeContainer({d.nframes, d.nchannels}), // Shape
py::array::StridesContainer( // Strides
{sizeof(T),
sizeof(T) * d.nframes}), // Strides (in bytes) for each index
reinterpret_cast<T *>(
const_cast<DaqData &>(d).raw_ptr()), // Pointer to buffer
dummyDataOwner // As stated above, now Numpy does not take ownership of
// the data pointer.
);
}
/**
* @brief Wraps the ThreadedInDataHandler such that it calls a Python callback
* with a buffer of sample data. Converts DaqData objects to Numpy arrays and
* calls Python given as argument to the constructor
*/
class PyIndataHandler : public ThreadedInDataHandler<PyIndataHandler> {
/**
* @brief The callback functions that is called.
*/
py::object _cb, _reset_callback;
std::atomic<bool> _done{false};
std::recursive_mutex _mtx;
public:
/**
* @brief Initialize PyIndataHandler
*
* @param mgr StreamMgr handle
* @param cb Python callback that is called with Numpy input data from device
* @param reset_callback Python callback that is called with a Daq pointer.
* Careful: do not store this handle, as it is only valid as long as reset()
* is called, when a stream stops, this pointer / handle will dangle.
*/
PyIndataHandler(SmgrHandle mgr, py::function cb, py::function reset_callback)
: ThreadedInDataHandler(mgr),
_cb(py::weakref(cb)),
_reset_callback(py::weakref(reset_callback)) {
DEBUGTRACE_ENTER;
// cerr << "Thread ID: " << std::this_thread::get_id() << endl;
/// Start should be called externally, as at constructor time no virtual
/// functions should be called.
if (_cb().is_none() || _reset_callback().is_none()) {
throw rte("cb or reset_callback is none!");
}
startThread();
}
~PyIndataHandler() {
DEBUGTRACE_ENTER;
// cerr << "Thread ID: " << std::this_thread::get_id() << endl;
/// Callback cannot be called, which results in a deadlock on the GIL
/// without this release.
py::gil_scoped_release release;
DEBUGTRACE_PRINT("Gil released");
_done = true;
stopThread();
}
/**
* @brief Calls the reset callback in Python.
*
* @param daq Daq device, or nullptr in case no input stream is running.
*/
void reset(const Daq *daqi) {
DEBUGTRACE_ENTER;
// cerr << "Thread ID: " << std::this_thread::get_id() << endl;
if (_done) return;
{
try {
py::object reset_callback = _reset_callback();
if (reset_callback.is_none()) {
DEBUGTRACE_PRINT("reset_callback is none, weakref killed");
_done = true;
return;
}
if (daqi != nullptr) {
assert(reset_callback);
reset_callback(daqi);
} else {
assert(reset_callback);
reset_callback(py::none());
}
} catch (py::error_already_set &e) {
cerr << "*************** Error calling reset callback!\n";
cerr << e.what() << endl;
cerr << "*************** \n";
/// Throwing a runtime error here does not work out one way or another.
/// Therefore, it is better to dive out and prevent undefined behaviour
abort();
/* throw std::runtime_error(e.what()); */
} catch (std::exception &e) {
cerr << "Caught unknown exception in reset callback:" << e.what()
<< endl;
abort();
}
} // end of GIL scope
} // end of function reset()
/**
* @brief Calls the Python callback method / function with a Numpy array of
* stream data.
*/
void inCallback(const DaqData &d) {
DEBUGTRACE_ENTER;
// cerr << "=== Enter incallback for thread ID: " << std::this_thread::get_id() << endl;
using DataType = DataTypeDescriptor::DataType;
if (_done) {
DEBUGTRACE_PRINT("Early stop, done");
return;
}
{
DEBUGTRACE_PRINT("================ TRYING TO OBTAIN GIL in inCallback...");
py::gil_scoped_acquire acquire;
try {
py::object py_bool;
py::object cb = _cb();
if (cb.is_none()) {
DEBUGTRACE_PRINT("cb is none, weakref killed");
_done = true;
return;
}
switch (d.dtype) {
case (DataType::dtype_int8): {
py_bool = cb(getPyArrayNoCpy<int8_t>(d));
} break;
case (DataType::dtype_int16): {
py_bool = cb(getPyArrayNoCpy<int16_t>(d));
} break;
case (DataType::dtype_int32): {
py_bool = cb(getPyArrayNoCpy<int32_t>(d));
} break;
case (DataType::dtype_fl32): {
py_bool = cb(getPyArrayNoCpy<float>(d));
} break;
case (DataType::dtype_fl64): {
py_bool = cb(getPyArrayNoCpy<double>(d));
} break;
default:
throw std::runtime_error("BUG");
} // End of switch
bool res = py_bool.cast<bool>();
if (res == false) {
DEBUGTRACE_PRINT("Setting callbacks to None")
_done = true;
// By doing this, we remove the references, but in the mean time this
// might also trigger removing Python objects. Including itself, as
// there is no reference to it anymore. The consequence is that the
// current object might be destroyed from this thread. However, if we
// do not remove these references and in lasp_record.py finish() is
// not called, we end up with not-garbage collected recordings in
// memory. This is also not good. How can we force Python to not yet
// destroy this object?
// cb.reset();
// reset_callback.reset();
}
} catch (py::error_already_set &e) {
cerr << "ERROR (BUG): Python raised exception from callback function: ";
cerr << e.what() << endl;
abort();
} catch (py::cast_error &e) {
cerr << e.what() << endl;
cerr << "ERROR (BUG): Python callback does not return boolean value."
<< endl;
abort();
} catch (std::exception &e) {
cerr << "Caught unknown exception in Python callback:" << e.what()
<< endl;
abort();
}
} // End of scope in which the GIL is acquired
// cerr << "=== LEAVE incallback for thread ID: " << std::this_thread::get_id() << endl;
} // End of function inCallback()
};
void init_datahandler(py::module &m) {
/// The C++ class is PyIndataHandler, but for Python, it is called
/// InDataHandler
py::class_<PyIndataHandler> pyidh(m, "InDataHandler");
pyidh.def(py::init<SmgrHandle, py::function, py::function>());
/// Peak Programme Meter
py::class_<PPMHandler> ppm(m, "PPMHandler");
ppm.def(py::init<SmgrHandle, const d>());
ppm.def(py::init<SmgrHandle>());
ppm.def("getCurrentValue", [](const PPMHandler &ppm) {
std::tuple<vd, arma::uvec> tp;
{
py::gil_scoped_release release;
tp = ppm.getCurrentValue();
}
return py::make_tuple(ColToNpy<d>(std::get<0>(tp)),
ColToNpy<arma::uword>(std::get<1>(tp)));
});
/// Clip Detector
py::class_<ClipHandler> clip(m, "ClipHandler");
clip.def(py::init<SmgrHandle>());
clip.def("getCurrentValue", [](const ClipHandler &clip) {
arma::uvec cval;
{
py::gil_scoped_release release;
cval = clip.getCurrentValue();
}
return ColToNpy<arma::uword>(cval); // something goes wrong here
});
/// Real time Aps
///
py::class_<RtAps> rtaps(m, "RtAps");
rtaps.def(py::init<SmgrHandle, // StreamMgr
Filter *const, // FreqWeighting filter
const us, // Nfft
const Window::WindowType, // Window
const d, // Overlap percentage 0<=o<100
const d // Time constant
>(),
py::arg("streammgr"), // StreamMgr
py::arg("preFilter").none(true),
/// Below list of arguments *SHOULD* be same as for
/// AvPowerSpectra constructor!
py::arg("nfft") = 2048, //
py::arg("windowType") = Window::WindowType::Hann, //
py::arg("overlap_percentage") = 50.0, //
py::arg("time_constant") = -1 //
);
rtaps.def("getCurrentValue", [](RtAps &rt) {
ccube val;
{
py::gil_scoped_release release;
val = rt.getCurrentValue();
}
return CubeToNpy<c>(val);
});
/// Real time Signal Viewer
///
py::class_<RtSignalViewer> rtsv(m, "RtSignalViewer");
rtsv.def(py::init<SmgrHandle, // StreamMgr
const d, // Time history
const us, // Resolution
const us // Channel number
>());
rtsv.def("getCurrentValue", [](RtSignalViewer &rt) {
dmat val;
{
py::gil_scoped_release release;
val = rt.getCurrentValue();
}
return MatToNpy<d>(val);
});
}

View File

@ -30,6 +30,7 @@ void init_siggen(py::module &m) {
siggen.def("setLevel", &Siggen::setLevel, py::arg("newLevel"),
py::arg("dB") = true);
siggen.def("reset", &Siggen::reset);
siggen.def("setInterruptPeriod", &Siggen::setInterruptPeriod);
siggen.def("setFilter", &Siggen::setFilter);

61
pyproject.toml Normal file
View File

@ -0,0 +1,61 @@
[project]
name = "lasp"
readme = "README.md"
requires-python = ">=3.10"
description = "Library for Acoustic Signal Processing"
license = { "file" = "LICENSE" }
authors = [{ "name" = "J.A. de Jong", "email" = "j.a.dejong@ascee.nl" }]
version = "1.6.6"
keywords = ["DSP", "DAQ", "Signal processing"]
classifiers = [
"Development Status :: 3 - Alpha",
"Topic :: Software Development :: Libraries :: Python Modules",
"License :: OSI Approved :: MIT License",
"Natural Language :: English",
"Topic :: Scientific/Engineering",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Operating System :: POSIX :: Linux",
"Operating System :: Microsoft :: Windows",
]
urls = { "Documentation" = "https://lasp.ascee.nl" }
dependencies = [
"scipy>=1.13.1",
"matplotlib>=3.7.2",
"appdirs",
"dataclasses_json",
"h5py",
]
[build-system] # How pip and other frontends should build this project
requires = ["py-build-cmake~=0.1.8", "pybind11"]
build-backend = "py_build_cmake.build"
[tool.py-build-cmake.module] # Where to find the Python module to package
directory = "python_src"
[tool.py-build-cmake.sdist] # What to include in source distributions
include = [
"CMakeLists.txt",
"cmake",
"cpp_src",
"python_src",
"img",
"scripts",
"third_party",
]
[tool.py-build-cmake.cmake] # How to build the CMake project
build_type = "Release"
source_path = "."
options = { "LASP_HAS_PORTAUDIO" = "ON", "LASP_HAS_RTAUDIO" = "OFF" }
build_args = ["-j"]
install_components = ["python_modules"]
[tool.py-build-cmake.editable]
# This might not work properly on Windows. Comment this out when testing on
# Windows.
mode = "symlink"

View File

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

View File

@ -2,10 +2,11 @@
LASP: Library for Acoustic Signal Processing
"""
from .lasp_version import __version__
from .lasp_common import *
from .lasp_cpp import *
import lasp.lasp_cpp
from .lasp_common import *
__version__ = lasp_cpp.__version__
# from .lasp_imptube import * # TwoMicImpedanceTube
from .lasp_measurement import * # Measurement, scaleBlockSens
@ -14,6 +15,7 @@ from .lasp_slm import * # SLM, Dummy
from .lasp_record import * # RecordStatus, Recording
from .lasp_daqconfigs import *
from .lasp_measurementset import *
# from .lasp_siggen import * # SignalType, NoiseType, SiggenMessage, SiggenData, Siggen
# from .lasp_weighcal import * # WeighCal
# from .tools import * # SmoothingType, smoothSpectralData, SmoothingWidth

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
from .lasp_cpp import DaqConfiguration, LASP_VERSION_MAJOR
from .lasp_cpp import DaqConfiguration
from .lasp_version import LASP_VERSION_MAJOR
"""!
Author: J.A. de Jong - ASCEE

View File

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

View File

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

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