Compare commits

...

193 Commits

Author SHA1 Message Date
Anne de Jong 3738012c3e Merge remote-tracking branch 'origin/develop' into develop
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after 1m13s Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped Details
2024-04-05 06:49:57 -07:00
Anne de Jong a91640cd8d Explicit picking of driver for windows. 2024-04-05 06:49:48 -07:00
Anne de Jong 0bf621e45c Updated documentation for Windows installation
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Blocked by required conditions Details
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Has been cancelled Details
2024-04-05 15:49:20 +02:00
Anne de Jong 1f7deca3fd Updated scripts for building. Documentation follows
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 1m26s Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped Details
2024-03-29 04:34:24 -07:00
Anne de Jong 1fb98412b2 Removed hard-coded Numpy include dir
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 1m35s Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped Details
2024-03-27 13:55:24 +01:00
Anne de Jong d50dd35745 Silence warnings from portaudio ALSA backend during device enumeration. Do device enumeration on background thread
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 1m30s Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped Details
2024-03-27 13:45:13 +01:00
Anne de Jong 1765042d20 Downgraded a logging.info() to logging.debug
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 1m45s Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped Details
2024-03-19 14:17:59 +01:00
Anne de Jong 46d1eda94d Merged in develop
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 1m51s Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped Details
2024-03-19 13:40:39 +01:00
Anne de Jong 3005f17400 Added extra getReferemenceMeasurements() method to MeasurementSet. Bumped therefore to v1.6.0 2024-03-19 13:39:17 +01:00
Casper Jansen 33439354f8 Sort MeasurementSet by time stamp
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 1m56s Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped Details
2024-03-14 11:31:07 +01:00
Anne de Jong da023273d8 Bump 1.5.1
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Blocked by required conditions Details
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Has been cancelled Details
2024-03-14 08:47:32 +01:00
Anne de Jong 84db689e56 Ignore error on rm when no files in build copy dir
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Blocked by required conditions Details
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Has been cancelled Details
2024-03-14 08:43:47 +01:00
Anne de Jong 83c7aa6ade More subtle locking and unlocking of mutexes in stopstream
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after 2m1s Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped Details
2024-03-14 08:25:47 +01:00
Anne de Jong 3c16e33453 Removed deadlock in output stream deletion
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after 2m1s Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped Details
2024-03-13 13:29:29 +01:00
Anne de Jong 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.
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after 2m3s Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped Details
2024-03-13 12:19:24 +01:00
Anne de Jong 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
Anne de Jong d0d494fcb2 Added some stuff to gitignore, removed explicit dependency on Numpy
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after 2m1s Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped Details
2024-03-12 15:53:37 +01:00
Anne de Jong 15cd62baf8 New smoothing algorithm - minor version bump
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped Details
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after 2m6s Details
2024-03-12 11:20:31 +01:00
Anne de Jong ab080910fc Made power correction in smoothing algorithm optional. Window decreases in size symmetrically around the edged of the frequency spectrum
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Blocked by required conditions Details
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Has been cancelled Details
2024-03-12 11:19:52 +01:00
Anne de Jong 6799ee9287 Bugfix new smoother, including ac signal power correction
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after 2m1s Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped Details
2024-03-12 09:21:07 +01:00
Anne de Jong f9cf059c90 Forgot to actually commit the Cpp files of the smoother
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after 8s Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped Details
2024-03-11 16:33:28 +01:00
Anne de Jong 3ec15ec645 New smoothing implementation, that runs a bit faster
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after -1m19s Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped Details
2024-03-11 16:04:24 +01:00
Anne de Jong 48d262fbf0 Bugfix in sensitivity correction of realtime spectra
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped Details
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after 2m8s Details
2024-03-07 09:36:50 +01:00
Anne de Jong 204e431d79 Bugfix on GIL release
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after 2m9s Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped Details
2024-03-06 22:12:42 +01:00
Anne de Jong 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
Anne de Jong 26eef040a4 More locks on signal generator.
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after 1m33s Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped Details
2024-03-04 15:49:29 +01:00
Anne de Jong b61e836f35 Bumped 1.4.6
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped Details
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after 1m38s Details
2024-03-04 14:44:37 +01:00
Anne de Jong 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
Anne de Jong 5e8e40db7a Updated tag. forgot in previous tag updates
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped Details
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after 1m47s Details
2024-02-29 20:05:10 +01:00
Casper Jansen 3b2f2f7c41 Bugfix record indefinitely
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped Details
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after 1m44s Details
2024-02-27 11:02:45 +01:00
Anne de Jong 878da3369b Bugfix (delete measurement when no data is in it) and cleanup of recording code
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped Details
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after 1m49s Details
2024-02-26 11:51:59 +01:00
Anne de Jong e9f500d460 Small change in portaudio.cmake
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped Details
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after 2m42s Details
2024-02-20 15:47:12 +01:00
Anne de Jong 6bda124196 Allow duplex mode for PortAudio ALSA devices
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 2m53s Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped Details
2024-02-06 15:02:25 +01:00
Anne de Jong 7ce45e9c82 Some comment improvements, and portaudio API improvements. Also, disabled PortAudio PulseAudio backend as it is not working properly.
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 2m35s Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Successful in -31s Details
2024-02-06 14:59:51 +01:00
Anne de Jong 7c8e6368ba Removed accidental use of wrong time weighting for impulse (35 ms). 2024-02-06 11:22:31 +01:00
Anne de Jong 7430e2c600 Updated armadillo
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 3m0s Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped Details
2024-01-30 14:40:43 +01:00
Anne de Jong 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
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 3m2s Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped Details
2024-01-25 15:31:53 +01:00
Anne de Jong c713806bbe RtAudio not updated to 6.0.1?
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 2m52s Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped Details
2024-01-23 15:18:04 +01:00
Anne de Jong 08010e56dd From now on build default LASP with Portaudio backend. Also on Linux. Code cleanup of Portaudio glue code
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 3m3s Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped Details
2024-01-20 11:52:16 +01:00
Anne de Jong 292a9d5938 Merge branch 'RtAudioV6' into develop 2024-01-19 12:37:16 +01:00
Anne de Jong 373dcfb60f Bugfixes (that could potentially segfault) in PortAudio backend.
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 2m54s Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Successful in 2s Details
2024-01-19 12:35:56 +01:00
Anne de Jong e8408ab53d Updated to RtAudio V6 2024-01-19 12:32:45 +01:00
Anne de Jong 0d152f6c14 Maded API changes to match RtAudio V6 2024-01-19 12:32:03 +01:00
Anne de Jong 46bef007ca Added muZ series impedance reference as measurementtype
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Has been cancelled Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been cancelled Details
2024-01-15 16:41:29 +01:00
Anne de Jong fd8366c362 Bugfix in measurementset
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Waiting to run Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Blocked by required conditions Details
2024-01-12 15:18:58 +01:00
Anne de Jong 6d5899c880 Merged in develop
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 2m39s Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped Details
2024-01-10 17:31:59 +01:00
Anne de Jong 061beaf88b Small bugfix of some dead code 2024-01-10 17:30:56 +01:00
Thijs Hekman e8ba3b86bf Bugfix on bugfix. KeyError instead of AttributeError
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 2m29s Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped Details
2024-01-10 13:14:54 +01:00
Anne de Jong 14ab3d9dfe Version bump: bugfix
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 2m27s Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped Details
2024-01-10 13:01:38 +01:00
Anne de Jong 695a05b262 BUGFIX: Prevent corrupting all files when no UUID is yet stored in a file 2024-01-10 13:01:07 +01:00
Anne de Jong 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
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 2m49s Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped Details
2024-01-10 12:26:38 +01:00
Anne de Jong 0be8dd71d9 Bugfixes: store UUID attribute early when recording is done. Some small improvements
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 2m15s Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Successful in -1m38s Details
2023-12-19 14:34:47 +01:00
Anne de Jong 2cd4c616b3 Bump 1.2.0
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 2m11s Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Successful in -1m25s Details
2023-12-19 14:04:43 +01:00
Anne de Jong 311a1274bf Added fromFile() method to overcome problem of multiple times opening same file 2023-12-19 14:03:46 +01:00
Anne de Jong 936f2d5708 Merge remote-tracking branch 'origin/develop' into develop
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 3m18s Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped Details
2023-12-19 12:28:18 +01:00
Anne de Jong 98d4e8dad2 More checks on refMeas. Renamed method refMeas to getRefMeas. Added removeRefMeas. Stabilized API? 2023-12-19 12:27:44 +01:00
Anne de Jong 87283e4aba Changed pure random UUID of measurement to time-based UUID 2023-12-19 12:26:22 +01:00
Thijs Hekman e5c40c6af3 Merged in lasp from remote
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 3m8s Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped Details
2023-12-19 11:24:04 +01:00
Thijs Hekman 8c7dbed606 Bugfix in qty input of Measurement.fromnpy() 2023-12-19 11:21:13 +01:00
Anne de Jong c610c6350d Implemented reference measurements, and renaming as method in Measurement object. 2023-12-18 12:37:46 +01:00
Anne de Jong 44fe7f2689 Bump 1.0.4
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 2m13s Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Successful in -46s Details
2023-10-27 15:02:44 +02:00
Anne de Jong f72a635cc7 Cleanup volume before copying
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 2m14s Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped Details
2023-10-26 15:17:50 +02:00
Anne de Jong e9d7f0561e Revert "Possible bugfix for pyinstaller"
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 2m16s Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped Details
This reverts commit 17319c4925.
2023-10-24 22:38:34 +02:00
Anne de Jong 17319c4925 Possible bugfix for pyinstaller
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped Details
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after 2m7s Details
2023-10-24 20:56:07 +02:00
Anne de Jong a7b219a1e1 Updated readme
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 2m55s Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped Details
2023-10-24 19:49:56 +02:00
Anne de Jong e4f887dc5b Renamed test_input.py to example_input.py
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Has been cancelled Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been cancelled Details
2023-10-24 19:47:35 +02:00
Anne de Jong ee7e5fbba9 Added test_input.py
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after 2m13s Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped Details
2023-10-24 15:14:37 +02:00
Anne de Jong bfa6704360 New armadillo version, some updates in pyproject and README
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 2m12s Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped Details
2023-10-24 15:13:06 +02:00
Anne de Jong 152d6d635d Apparently path may not be absolute. What a inflexible release system!
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 2m38s Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped Details
2023-10-16 15:54:08 +02:00
Anne de Jong de3ef1b4c1 Apparently path may not be absolute. What a inflexible release system!
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after -1m26s Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped Details
2023-10-16 15:53:06 +02:00
Anne de Jong 5ca899fa0a Volume not mounted absolute, but in repo.
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after -1m26s Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped Details
2023-10-16 15:43:17 +02:00
Anne de Jong ab103b74f7 Set working directory correct for release action. Is apparently required
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 2m36s Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Failing after -32s Details
2023-10-16 15:36:15 +02:00
Anne de Jong a72284880d Twice container key
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 2m44s Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Failing after -35s Details
2023-10-16 15:28:11 +02:00
Anne de Jong 67ff10b5e5 Forgot dash 2023-10-16 15:26:42 +02:00
Anne de Jong 5b76ff007c Added volumes 2023-10-16 15:24:20 +02:00
Anne de Jong 592448eea9 Release when tag. That is the only way the release action is working
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after 2m41s Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped Details
2023-10-16 15:17:00 +02:00
Anne de Jong 402425ebd1 Test with only release. Removed last line
Building and releasing LASP (master branch) / Build-Test-Ubuntu (push) Successful in 2m45s Details
Building and releasing LASP (master branch) / Release-Ubuntu (push) Failing after -1m23s Details
2023-10-16 14:58:00 +02:00
Anne de Jong 07b2d97e7a Sigh. requires go 1.18
Building and releasing LASP (master branch) / Build-Test-Ubuntu (push) Successful in 2m34s Details
Building and releasing LASP (master branch) / Release-Ubuntu (push) Failing after -24s Details
2023-10-16 14:26:02 +02:00
Anne de Jong db9a1a28a5 Volumes should be specified in runner config
Building and releasing LASP (master branch) / Build-Test-Ubuntu (push) Successful in 2m38s Details
Building and releasing LASP (master branch) / Release-Ubuntu (push) Failing after -47s Details
2023-10-16 14:14:36 +02:00
Anne de Jong 24f849d9ee Should be host volume
Building and releasing LASP (master branch) / Build-Test-Ubuntu (push) Failing after 2m29s Details
Building and releasing LASP (master branch) / Release-Ubuntu (push) Has been skipped Details
2023-10-16 13:49:04 +02:00
Anne de Jong bcbb5b0720 More explicit name of volume
Building and releasing LASP (master branch) / Build-Test-Ubuntu (push) Failing after 2m27s Details
Building and releasing LASP (master branch) / Release-Ubuntu (push) Has been skipped Details
2023-10-16 13:44:55 +02:00
Anne de Jong bf6a18bcf8 Use a volume to share state
Building and releasing LASP (master branch) / Build-Test-Ubuntu (push) Failing after 2m43s Details
Building and releasing LASP (master branch) / Release-Ubuntu (push) Has been skipped Details
2023-10-16 13:37:18 +02:00
Anne de Jong 749d5354c9 Install golang manually
Building and releasing LASP (master branch) / Build-Test-Ubuntu (push) Successful in 2m39s Details
Building and releasing LASP (master branch) / Release-Ubuntu (push) Failing after 16s Details
2023-10-13 12:30:31 +02:00
Anne de Jong 941b83abe8 Try with catthehacker image
Building and releasing LASP (master branch) / Build-Test-Ubuntu (push) Successful in 2m46s Details
Building and releasing LASP (master branch) / Release-Ubuntu (push) Failing after -1m9s Details
2023-10-13 11:34:10 +02:00
Anne de Jong 7857c3aed5 Go v3?
Building and releasing LASP (master branch) / Build-Test-Ubuntu (push) Successful in 2m53s Details
Building and releasing LASP (master branch) / Release-Ubuntu (push) Failing after -52s Details
2023-10-13 11:26:36 +02:00
Anne de Jong 5565b10fb2 Upload and download artifact test 2
Building and releasing LASP (master branch) / Build-Test-Ubuntu (push) Successful in 2m53s Details
Building and releasing LASP (master branch) / Release-Ubuntu (push) Failing after -1m6s Details
2023-10-12 16:28:02 +02:00
Anne de Jong 44c3782b46 Apparently release action requires golang
Building and releasing LASP (master branch) / Build-Test-Ubuntu (push) Successful in 2m43s Details
Building and releasing LASP (master branch) / Release-Ubuntu (push) Failing after 32s Details
2023-10-11 18:14:32 +02:00
Anne de Jong abbb3fee2c Specify release action from gitea
Building and releasing LASP (master branch) / Build-Test-Ubuntu (push) Successful in 2m45s Details
Building and releasing LASP (master branch) / Release-Ubuntu (push) Failing after -1m8s Details
2023-10-11 18:04:43 +02:00
Anne de Jong 02b95f9aa8 Upload / download artifact. Test2
Building and releasing LASP (master branch) / Build-Test-Ubuntu (push) Successful in 3m23s Details
Building and releasing LASP (master branch) / Release-Ubuntu (push) Failing after -1m9s Details
2023-10-11 17:36:44 +02:00
Anne de Jong b9c42c1c24 Checkout before
Building and releasing LASP (master branch) / Build-Test-Ubuntu (push) Failing after 2m38s Details
Building and releasing LASP (master branch) / Release-Ubuntu (push) Failing after -1m10s Details
2023-10-11 17:26:02 +02:00
Anne de Jong 2206f47cff Forgot pytest
Building and releasing LASP (master branch) / Release-Ubuntu (push) Has been cancelled Details
Building and releasing LASP (master branch) / Build-Test-Ubuntu (push) Has been cancelled Details
2023-10-11 17:24:08 +02:00
Anne de Jong 03513b6502 With upload and download artifacts - now with yaml syntax correct
Building and releasing LASP (master branch) / Release-Ubuntu (push) Has been cancelled Details
Building and releasing LASP (master branch) / Build-Test-Ubuntu (push) Has been cancelled Details
2023-10-11 17:23:31 +02:00
Anne de Jong 8ef5fd5a64 With upload and download artifacts 2023-10-11 17:22:35 +02:00
Anne de Jong bab69c8018 Sigh............... Does this work?
Building and releasing LASP (master branch) / Build-Test-Ubuntu (push) Failing after 2m28s Details
Building and releasing LASP (master branch) / Release-Ubuntu (push) Failing after -1m15s Details
2023-10-11 17:18:09 +02:00
Anne de Jong 6be9e14c13 Sigh............... Does this work? 2023-10-11 17:13:59 +02:00
Anne de Jong 78181ebca9 Split two files. No other way possible?
Building and releasing LASP (master branch) / Build-Test-Release-Ubuntu (push) Failing after 2m4s Details
2023-10-11 17:03:55 +02:00
Anne de Jong 9b44ff7262 if on different level
Building and testing LASP / Build-Test-Ubuntu (push) Successful in 1m55s Details
Building and testing LASP / Release-Ubuntu (push) Failing after -1m10s Details
2023-10-11 16:45:04 +02:00
Anne de Jong 1bf32fe81d gitub ref?
Building and testing LASP / Build-Test-Ubuntu (push) Successful in 2m5s Details
Building and testing LASP / Release-Ubuntu (push) Failing after -1m9s Details
2023-10-11 16:21:12 +02:00
Anne de Jong 2b060b7708 release action from gitea fork
Building and testing LASP / Build-Test-Ubuntu (push) Successful in 2m11s Details
Building and testing LASP / Release-Ubuntu (push) Failing after -1m7s Details
2023-10-09 21:51:52 +02:00
Anne de Jong 2d0d24a35a Removed constraints on tag
Building and testing LASP / Build-Test-Ubuntu (push) Successful in 2m20s Details
Building and testing LASP / Release-Ubuntu (push) Failing after -1m9s Details
2023-10-05 21:52:51 +02:00
Anne de Jong 6bb15965d6 Update .gitea/workflows/workflow.yml 2023-10-05 19:50:19 +00:00
Anne de Jong 31208db325 Action roll... 2023-10-05 21:39:41 +02:00
Anne de Jong 7993d81808 Merge branch 'drone_check_publish' into develop 2023-10-04 21:30:32 +02:00
Anne de Jong 586dfb38b3 Some fix for building on Ubuntu 2023-10-04 21:30:22 +02:00
Anne de Jong 5b1051bf99 Check whether publishing works
continuous-integration/drone/push Build is pending Details
2023-09-27 20:52:36 +02:00
Anne de Jong 63122b8a42 build(Not-sh-but-bash): Changed from sh to bash shell
continuous-integration/drone/push Build is passing Details
continuous-integration/drone Build is pending Details
2023-09-17 19:52:31 +02:00
Anne de Jong df4e2cb573 build(Build-docu-only-on-master,-python3-venv-(sigh)..): Added extra install ubuntu dept, trigger documentation build only on branch master
continuous-integration/drone/push Build is failing Details
2023-09-17 17:34:21 +02:00
Anne de Jong 0425195ffd build(python3-virtualenv): Not python3.10-virtualenv but python3-virtualenv
continuous-integration/drone/push Build is failing Details
2023-09-17 17:19:43 +02:00
Anne de Jong e51463a6cc build(Build-on-Ubuntu-fuix): hopefully fixed build error for Ubuntu builds
continuous-integration/drone/push Build was killed Details
2023-09-17 17:14:55 +02:00
Anne de Jong 628ba898c9 Merged in develop.
continuous-integration/drone/push Build is failing Details
2023-08-30 21:44:20 +02:00
Anne de Jong dbd9c7c1af Updated drone to use all cpus available
continuous-integration/drone/push Build encountered an error Details
2023-08-30 16:01:08 +02:00
Thijs Hekman 77a7e46f9d Reset the interrupt frame counter when muting signal generator
continuous-integration/drone Build is running Details
2023-08-29 16:23:18 +02:00
Anne de Jong 376e0dc85c Working interrupted noise 2023-08-29 15:46:51 +02:00
Anne de Jong 1bf022d648 ci: Ubuntu build: added git to list of install packages
continuous-integration/drone/push Build was killed Details
continuous-integration/drone Build is failing Details
2023-07-24 20:02:18 +02:00
Anne de Jong c9243b1143 build(Pre-commit-drone-file-cannot-be-checked): Removed check-yaml hook, updated drone config
continuous-integration/drone/push Build is failing Details
2023-07-24 18:11:45 +02:00
Anne de Jong 8397779a2a ci: Fixed build script for Ubuntu build (2)
continuous-integration/drone/push Build was killed Details
2023-07-24 18:01:51 +02:00
Anne de Jong 94f0ec1d84 ci(scripts/build_ubuntu.sh): Fixed build script for Ubuntu builds 2023-07-24 18:01:02 +02:00
Anne de Jong a29f72a592 Updated tests with build scripts in docker containers
continuous-integration/drone/push Build is failing Details
2023-07-24 17:24:12 +02:00
Anne de Jong 89b303497b fix(lasp_version.py): Added patch to tuple unpack
continuous-integration/drone/push Build is failing Details
2023-07-23 15:24:17 +02:00
Anne de Jong aa0803e2f1 build(pyproject.toml): Added pybind11 build dependency
continuous-integration/drone/push Build is failing Details
2023-07-20 15:22:56 +02:00
Anne de Jong 01a6c35f6e bump: version 1.0.0 → 1.0.1
continuous-integration/drone/push Build is failing Details
2023-07-19 17:00:27 +02:00
Anne de Jong ff1cfddf97 fix: Added patch number to semver in pyproject.toml 2023-07-19 17:00:22 +02:00
Anne de Jong 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
Anne de Jong 37048c54fa build(Testing-with-pre-commit): Not yet 2023-07-18 17:17:28 +02:00
Anne de Jong 9f81db8eeb Bla 2023-07-18 17:12:34 +02:00
Anne de Jong 914da89819 build(Testing): Not yet 2023-07-18 17:06:40 +02:00
Anne de Jong 72716ecd39 Updated .gitignore 2023-07-14 16:42:05 +02:00
Anne de Jong 790eb41a26 Switch to pyproject build structure 2023-07-14 16:40:57 +02:00
Anne de Jong 2727bb5582 Docs for build on Windows with MSYS2
continuous-integration/drone/push Build is failing Details
2023-07-14 13:57:02 +02:00
Anne de Jong a70f124f89 Updated README 2023-07-14 13:54:26 +02:00
Anne de Jong 26343fda79 Doc improvement Measurement.fromnpy
continuous-integration/drone/push Build is failing Details
2023-07-14 13:48:26 +02:00
Anne de Jong da80dbf075 Some improvements before stepping over to pyproject.toml
continuous-integration/drone/push Build is failing Details
2023-07-14 13:40:43 +02:00
Anne de Jong 33132e2c9d Execute permissions on build scripts
continuous-integration/drone/push Build is failing Details
continuous-integration/drone Build is failing Details
2023-07-14 10:13:45 +02:00
Anne de Jong fc681f3b6c Test virtualenvs and build envs
continuous-integration/drone/push Build is failing Details
2023-07-14 10:12:42 +02:00
Anne de Jong ddbb842c14 Fixed Doxkerfile for documentation to build in a virtualenv. This is required for Archlinux
continuous-integration/drone/push Build is failing Details
2023-07-14 09:38:51 +02:00
Thijs Hekman 30ce35d29b Completed all flush statements. Now on Thijs machine, it looks like it does not crash anymore under normal circumstances
continuous-integration/drone/push Build is failing Details
continuous-integration/drone Build was killed Details
2023-06-21 11:00:38 +02:00
Thijs Hekman 839ca4f77c Added extra flush statements for hdf5 file. This helps for Thijs' machine
continuous-integration/drone/push Build is failing Details
2023-06-21 10:57:03 +02:00
Anne de Jong 8711c6c57d More GIL releases while possibly waiting for a lock. Improves throughput, when also PyIndataHandlers are running (in case of a recording, for example
continuous-integration/drone/push Build is failing Details
2023-06-20 17:16:56 +02:00
Anne de Jong 4ca8866cb7 Avoid a deadlock: GIL release in constructor and destructor of threadedindatahandler.
continuous-integration/drone/push Build is failing Details
2023-06-20 17:08:55 +02:00
Anne de Jong 2420e6cb28 Bugfix: stop the streams in the destructor of streammgr to avoid that the signal generator is detructed while the streams are still running
continuous-integration/drone/push Build is failing Details
2023-06-18 21:13:09 +02:00
Anne de Jong 3681e7adac Changed hard-coded 8 to sizeof(double) 2023-06-18 21:05:47 +02:00
Anne de Jong 1a22a33c0f Finally: some align_val_t that did not work on Windows!
continuous-integration/drone/push Build is failing Details
2023-06-17 07:03:14 -07:00
Anne de Jong f160b696fb Merge branch 'windows_ready' of ssh://code.ascee.nl:12001/ASCEE/lasp into windows_ready 2023-06-15 10:42:18 -07:00
Anne de Jong 6353282e24 Merge branch 'develop' into windows_ready
continuous-integration/drone/push Build is failing Details
2023-06-15 10:00:40 +02:00
Anne de Jong d9a3cfd627 Made default preset for compiled in DAQ backends in CMakeLists
continuous-integration/drone/push Build is failing Details
2023-06-15 09:57:06 +02:00
Anne de Jong 64a268e277 Portaudio backend seems to be working. No extensive checks performed yet. 2023-06-15 09:48:45 +02:00
Anne de Jong 77b1848bb4 Made functions without correct return type compile time error 2023-06-14 21:24:29 +02:00
Anne de Jong 88624764e7 Rudymentary portaudio support. A stream callback is running. 2023-06-14 21:23:53 +02:00
Anne de Jong 303e15e2d6 Renamed ThreadSafeThreadPool to GlobalThreadPool. As of https://github.com/bshoshany/thread-pool/issues/112, the thread pool itself is thread-safe, so we removed the (extra, unnecessary) mutexes around it.
continuous-integration/drone/push Build is failing Details
2023-06-12 09:11:08 +02:00
Anne de Jong e61d71b08a Merge remote-tracking branch 'origin/develop' into windows_ready 2023-06-11 05:45:50 -07:00
Anne de Jong ee4b230947 Bugfix for compiling without uldaq
continuous-integration/drone/push Build is failing Details
2023-06-11 05:45:28 -07:00
Anne de Jong 3904abfcf9 Merged in develop 2023-06-11 05:45:02 -07:00
Anne de Jong a58be3ab87 Simple input tests script
continuous-integration/drone/push Build is failing Details
2023-06-11 14:44:15 +02:00
Anne de Jong f9640a5f99 Some small changes. 2023-06-11 05:33:16 -07:00
Anne de Jong 9b724ab9d5 Made thread pool itself thread safe. Besides, added some extra safety for StreamMgr singleton instance allocation.
continuous-integration/drone/push Build is failing Details
2023-06-10 15:47:52 +02:00
Anne de Jong 21df1bc6cf Incallbacks should not return anything anymore. From inheritance to composition for InDataHandler code. StreamMgr singleton only weak ptr stored, this makes sure destruction from Python is more often done. UlDAQ code back to working.
continuous-integration/drone/push Build is passing Details
2023-06-09 10:43:04 +02:00
Anne de Jong 028bed9229 One forgotten debugtrace back to disabled 2023-06-07 21:51:03 +02:00
Anne de Jong c87a5cec25 StreamMgr handle now via shared pointers. InDataHandler stores weak pointers. Reset callback in PyInDataHandler could be problematic. Refactored the UlDaq code and moved to a subfolder. 2023-06-07 21:49:07 +02:00
Anne de Jong 6fc1bd90b1 Moved IndataHandler to its own implementation file. Refactored some code. Fixed race conditions when starting and stopping indatahandlers. It appears that this does not solve the segfault, but is at least mitigates some race conditions when constructors are not ready on an object, and avoiding the call of virtual functions of an object which destructor has already been called. Added some extra assert check that a function is called from the right thread. Put explicit start and stop methods in constructor / destructor of PyInDataHandler. WARNING: this means all .start() and .stop() methods should be removed. THIS IS AN API break!
continuous-integration/drone/push Build is passing Details
2023-06-06 16:05:24 +02:00
Anne de Jong dd2bbb5973 Some improvements in the clearyness of meaning in uldaq code. No bugs found. 2023-06-06 15:57:20 +02:00
Anne de Jong ae3f8043e0 Bugfix: not cleanup done of h5 dataset in recording. That might be problematic
continuous-integration/drone/push Build is passing Details
2023-06-02 14:25:17 +02:00
Anne de Jong 0d02779f2e Merge branch 'develop' of ssh://code.ascee.nl:12001/ASCEE/lasp into windows_ready
continuous-integration/drone/push Build is passing Details
2023-05-25 11:43:14 -07:00
Anne de Jong 9617da3ad9 Updated libs. Some comments and tests on cmake with msys2.
continuous-integration/drone/push Build is failing Details
2023-05-25 11:41:16 -07:00
Anne de Jong 43cf2427ea Workaround for bug in RtAudio when first channel not equal to 0
continuous-integration/drone/push Build is passing Details
2023-05-25 16:52:55 +02:00
Anne de Jong 9ec2abeced Changed remote of uldaq to asceenl
continuous-integration/drone/push Build is passing Details
2023-05-16 12:13:21 +02:00
Anne de Jong a1a7b411f1 Updates and bugfixes on fromnpy in Measurement
continuous-integration/drone/push Build is passing Details
2023-05-16 12:12:36 +02:00
Anne de Jong 34729cf9c0 Destroythreadpool waits forever. Try with ucrt64
continuous-integration/drone/push Build is passing Details
2023-05-12 08:27:35 -07:00
Anne de Jong 93619a344c Bugfix in debugtrace such that it works with MinGW
continuous-integration/drone/push Build is passing Details
2023-05-12 07:00:12 -07:00
Anne de Jong 318a565e17 Not forcing any RtAudio backend. This is still te be found out what works properly
continuous-integration/drone/push Build is passing Details
2023-05-12 06:41:16 -07:00
Anne de Jong 3e6c8cf3b2 Working to execute cpp code of LASP on Windows. Streams are not working in ACME
continuous-integration/drone/push Build is passing Details
2023-05-03 12:44:32 -07:00
Anne de Jong a0bbeea24d Merge branch 'develop' into windows_ready
continuous-integration/drone/push Build is passing Details
2023-04-21 13:30:53 +02:00
Anne de Jong 24de84a4f7 Some doc update
continuous-integration/drone/push Build is passing Details
2023-04-21 13:27:45 +02:00
Anne de Jong ebf5ac3de4 Merge branch 'develop' into windows_ready
continuous-integration/drone/push Build is passing Details
2023-04-19 12:36:39 -07:00
Anne de Jong ad864ddb4a Added scripts and debugged cmake to make compile windows ready. Now it compiles, bug the problem is, we have DLL load failed
continuous-integration/drone/push Build is passing Details
2023-04-19 12:36:32 -07:00
Anne de Jong 3844827505 Merge remote-tracking branch 'origin/develop' into develop
continuous-integration/drone/push Build is passing Details
2023-04-19 15:56:35 +02:00
Anne de Jong fb9920d00a Bugfix: Made Uldaq sample rate check depending on actually provided sample rates 2023-04-18 11:09:01 +02:00
Casper Jansen e09b00d801 Changed argument of Measurement.exportAsWave() from newsampwidth to dtype, to allow export as float
continuous-integration/drone/push Build is passing Details
2023-04-14 17:04:27 +02:00
Anne de Jong f1348ede80 Cached time string of measurement time stamp
continuous-integration/drone/push Build is passing Details
2023-04-03 13:16:39 +02:00
Thijs Hekman bdef0b45f3 Added first-order HP and LP compensation filters to the biquad class
continuous-integration/drone/push Build is passing Details
2023-03-22 16:23:57 +01:00
Anne de Jong 3f0d9f4b00 Bugfix 1 in Uldaq code
continuous-integration/drone/push Build is passing Details
2023-03-15 15:53:02 +01:00
Anne de Jong ad0076e1c9 Changed remote of Uldaq to our fork
continuous-integration/drone/push Build is passing Details
2023-03-15 15:42:40 +01:00
Anne de Jong ad62917aab Merge remote-tracking branch 'origin/develop' into develop
continuous-integration/drone/push Build is passing Details
2023-03-13 10:38:23 +01:00
Anne de Jong 14126c8b9c Added extra newlines in README. Hopefully this fixes the markdown problems 2023-03-13 10:38:09 +01:00
Anne de Jong f164aa2e71 Important BUG: opening a measurement tried to load the comment from the wrong place. Was stored as an attributed but was tried to recover as a dataset.
continuous-integration/drone/push Build is passing Details
2023-03-12 13:02:18 +01:00
Anne de Jong ec2a933e20 Important BUGfix: effectively there was no sleep period in the thread handling the data from the UlDAQ
continuous-integration/drone/push Build is passing Details
2023-03-10 16:27:51 +01:00
Anne de Jong 617eded04e Bugfix for non-consecutive channel counts in UlDAQ
continuous-integration/drone/push Build is passing Details
2023-03-10 15:53:55 +01:00
Anne de Jong 2b22af5d2c Merge remote-tracking branch 'origin/develop' into develop
continuous-integration/drone/push Build is passing Details
2023-03-10 15:44:48 +01:00
Anne de Jong 92f5b18481 Not allowing a monitor when not running in duplex mode 2023-03-10 15:44:19 +01:00
Thijs Hekman 8547d0915a Added low-pass and high-pass compensator filters
continuous-integration/drone/push Build is passing Details
2023-03-09 10:16:44 +01:00
Casper Jansen fa8f5e64ad Update to new smoothing algorithm. Should be made faster.
continuous-integration/drone/push Build is passing Details
2023-02-23 18:10:06 +01:00
Casper Jansen b3fb7ddb6d Smoothing: minor bug fix
continuous-integration/drone/push Build is passing Details
2023-02-23 16:37:08 +01:00
Anne de Jong 28d540b667 Merge remote-tracking branch 'origin/rt_signalviewer' into rt_signalviewer
continuous-integration/drone/push Build is failing Details
2023-02-20 10:03:25 +01:00
Anne de Jong 480b036e02 Merge branch 'develop' into rt_signalviewer 2023-02-19 11:08:43 +01:00
140 changed files with 5773 additions and 3328 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@v4
with:
submodules: true
- name: Build and test
run: |
pip install build pytest
python3 -m build
pip install dist/lasp*.whl
pytest
- name: Cleanup old dist files and copy new to /dist dir
run: |-
rm -f /dist/*
cp -v dist/* /dist
Release-Ubuntu:
runs-on: ubuntu-latest
container:
volumes:
- lasp_dist:/dist
needs: Build-Test-Ubuntu
if: startsWith(gitea.ref, 'refs/tags/v')
steps:
- name: Checkout
uses: actions/checkout@v4
- name: setup go
uses: https://github.com/actions/setup-go@v4
with:
go-version: '1.18'
- name: Release
uses: https://gitea.com/actions/release-action@main
working-directory: "/"
with:
files: |-
../../../../../dist/**
api_key: '${{secrets.RELEASE_TOKEN}}'

17
.gitignore vendored
View File

@ -1,23 +1,26 @@
*.a
*.pyc
*.so
*.dll
*.pyd
.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

7
.gitmodules vendored
View File

@ -6,7 +6,7 @@
url = https://github.com/gsl-lite/gsl-lite
[submodule "DebugTrace-cpp"]
path = third_party/DebugTrace-cpp
url = https://github.com/MasatoKokubo/DebugTrace-cpp
url = https://github.com/asceenl/DebugTrace-cpp
[submodule "armadillo-code"]
path = third_party/armadillo-code
url = https://gitlab.com/conradsnicta/armadillo-code
@ -30,4 +30,7 @@
url = https://github.com/boostorg/core
[submodule "third_party/uldaq"]
path = third_party/uldaq
url = https://github.com/mccdaq/uldaq
url = https://github.com/asceenl/uldaq
[submodule "third_party/portaudio"]
path = third_party/portaudio
url = https://github.com/PortAudio/portaudio

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,13 +1,30 @@
cmake_minimum_required (VERSION 3.16)
project(LASP LANGUAGES C CXX VERSION 1.0)
project(LASP LANGUAGES C CXX VERSION 1.1)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED)
option(LASP_DOUBLE_PRECISION "Compile as double precision floating point" ON)
option(LASP_HAS_RTAUDIO "Compile with RtAudio Daq backend" ON)
option(LASP_HAS_ULDAQ "Compile with UlDaq backend" ON)
# Setting defaults for PortAudio and RtAudio backend, depending on Linux /
# Windows.
if(WIN32)
set(DEFAULT_RTAUDIO OFF)
set(DEFAULT_PORTAUDIO ON)
set(DEFAULT_ULDAQ OFF)
else()
set(DEFAULT_RTAUDIO OFF)
set(DEFAULT_PORTAUDIO ON)
set(DEFAULT_ULDAQ ON)
endif()
option(LASP_HAS_RTAUDIO "Compile with RtAudio Daq backend" ${DEFAULT_RTAUDIO})
option(LASP_HAS_PORTAUDIO "Compile with PortAudio Daq backend" ${DEFAULT_PORTAUDIO})
if(LASP_HAS_PORTAUDIO AND LASP_HAS_RTAUDIO)
message(FATAL_ERROR "Either PortAudio or RtAudio can be selected as audio backend")
endif()
option(LASP_HAS_ULDAQ "Compile with UlDaq backend" ${DEFAULT_ULDAQ})
option(LASP_BUILD_TUNED "Tune build for current machine (Experimental / untested)" OFF)
option(LASP_WITH_OPENMP "Use OpenMP parallelization (Experimental: crashes SHOULD BE EXPECTED)" OFF)
set(LASP_MAX_NFFT "33554432" CACHE STRING "Max FFT size")
@ -90,18 +107,23 @@ endif()
# ###################################### Compilation flags
set(CMAKE_C_FLAGS_RELEASE "-O3 -flto -mfpmath=sse -march=x86-64 -mtune=native \
-fdata-sections -ffunction-sections -fomit-frame-pointer -finline-functions")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-type-limits")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-type-limits -Werror=return-type")
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -flto -mfpmath=sse -march=x86-64 -mtune=native \
-fdata-sections -ffunction-sections -fomit-frame-pointer -finline-functions")
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_subdirectory(src/lasp)
add_definitions(-Dgsl_CONFIG_DEFAULTS_VERSION=1)
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

108
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
@ -9,6 +7,7 @@ with a Python interface which is supposed to acquire and process (multi) sensor
Current features that are implemented:
- Communication with data acquisition (DAQ) devices, of which:
- Internal sound cards via the [RtAudio](http://www.music.mcgill.ca/~gary/rtaudio) backend. Many thanks to Gary P. Scavone et al.
- [Measurement Computing](https://www.mccdaq.com) [DT9838A](https://www.mccdaq.com/Products/Sound-Vibration-DAQ/DT9837) signal analyzer.
@ -32,54 +31,103 @@ Current features that are implemented:
- Spectra data smoothing algorithms
- Sensor calibration for microphones
Future features (wish-list)
- Conventional and delay-and-sum beam-forming algorithms
- Impedance tube measurement processing
For now, the source code is well-documented on [lasp.ascee.nl](https://lasp.ascee.nl) but it requires some
additional documentation (the math behind it). This is maintained
in a sister repository [lasp-doc](https://code.ascee.nl/ascee/lasp-doc). The
most recent
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: info@ascee.nl.
If you have any question(s), please feel free to contact us: [email](info@ascee.nl).
# Installation
# Installation - Linux (Ubuntu-based)
## Dependencies
## Prerequisites
- `$ sudo apt install python3-pybind11 libopenblas-dev python3-pip python3-scipy libusb-1.0-0-dev libpulse-dev cmake-curses-gui python3-h5py`
- `$ pip3 install --user -r requirements.txt`
Run the following on the command line to install all prerequisites on
Debian-based Linux:
- `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:
# Installation - (x86_64) Windows (with WinPython), build with MSYS2
- `$ cmake .`
## Prerequisites
or optionally for a custom build:
- Download and install [WinPython](https://winpython.github.io)
- `$ ccmake .`
## From wheel
Configure and run:
- Download latest wheel from [LASP releases](https://code.ascee.nl/ASCEE/lasp/releases/latest/) and
download the latest `.whl`. Then install with `pip`.
- `$ make -j`
## From source
### Build documentation
- 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`
In directory:
# Documentation
## Online
[Online LASP documentation](https://lasp.ascee.nl/).
## In directory (Linux/Debian)
`$ sudo apt install doxygen graphviz`
`$ pip install doxypypy`
@ -92,21 +140,7 @@ 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.

View File

@ -1,29 +1,11 @@
if(WIN32)
set(home $ENV{USERPROFILE})
# set(miniconda_dir ${home}\\Miniconda3)
message("Building for Windows")
include_directories(
..\\rtaudio
C:\\mingw\\mingw64\\include\\OpenBLAS
link_directories(${home}\\miniconda3\\Library\\include)
)
set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH} $miniconda_dir\\Lib\\cmake")
# include(
add_definitions(-DMS_WIN64)
link_directories(C:\\mingw\\mingw64\\lib)
link_directories(C:\\mingw\\mingw64\\bin)
link_directories(..\\rtaudio)
link_directories(${home}\\Miniconda3)
add_definitions(-DHAS_RTAUDIO_WIN_WASAPI_API)
else() # Linux compile
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wfatal-errors")
include_directories(/usr/local/include/rtaudio)
include_directories(/usr/include/rtaudio)
link_directories(/usr/local/lib)
# This should become optional later on, and be added to the windows list as
# well.
set(TARGET_OS_LINKLIBS winmm dsound setupapi ole32 uuid winmm)
message("Building for Windows")
else() # Linux compile
message("Building for Linux :)")
set(TARGET_OS_LINKLIBS "")
endif()
# The last argument here takes care of calling SIGABRT when an integer overflow
# occures.

28
cmake/portaudio.cmake Normal file
View File

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

View File

@ -2,12 +2,13 @@
if(LASP_HAS_RTAUDIO)
message("Building RtAudio backend")
if(WIN32)
set(RTAUDIO_API_WASAPI TRUE CACHE BOOL "Build for WASAPI" FORCE)
set(RTAUDIO_API_WASAPI FALSE CACHE BOOL "Build for WASAPI backend")
set(RTAUDIO_API_DS TRUE CACHE BOOL "Build for Directsound backend")
else()
set(RTAUDIO_API_PULSE TRUE CACHE BOOL "Build with PulseAudio backend" FORCE)
set(RTAUDIO_API_ALSA OFF CACHE BOOL "Do not build with Alsa backend" FORCE)
set(RTAUDIO_API_JACK OFF CACHE BOOL "Do not build with Jack backend" FORCE)
set(RTAUDIO_API_PULSE TRUE CACHE BOOL "Build with PulseAudio backend")
set(RTAUDIO_API_ALSA OFF CACHE BOOL "Do not build with Alsa backend")
set(RTAUDIO_API_JACK OFF CACHE BOOL "Do not build with Jack backend")
endif()
set(RTAUDIO_BUILD_STATIC_LIBS ON CACHE BOOL "Build static libs for RtAudio" FORCE)
add_subdirectory(third_party/rtaudio)
add_subdirectory(${PROJECT_SOURCE_DIR}/third_party/rtaudio)
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

@ -1,4 +1,6 @@
# src/lasp/device/CMakeLists.txt
include_directories(uldaq)
include_directories(portaudio)
add_library(lasp_device_lib OBJECT
lasp_daq.cpp
@ -7,8 +9,12 @@ add_library(lasp_device_lib OBJECT
lasp_deviceinfo.cpp
lasp_rtaudiodaq.cpp
lasp_streammgr.cpp
lasp_indatahandler.cpp
lasp_uldaq.cpp
lasp_uldaq_impl.cpp
uldaq/lasp_uldaq_impl.cpp
uldaq/lasp_uldaq_bufhandler.cpp
uldaq/lasp_uldaq_common.cpp
portaudio/lasp_portaudiodaq.cpp
)
# Callback requires certain arguments that are not used by code. This disables
@ -27,6 +33,13 @@ endif()
if(LASP_HAS_RTAUDIO)
target_link_libraries(lasp_device_lib rtaudio)
endif()
if(LASP_HAS_PORTAUDIO)
target_link_libraries(lasp_device_lib PortAudio)
if(WIN32)
else()
target_link_libraries(lasp_device_lib asound)
endif()
endif()
target_link_libraries(lasp_device_lib lasp_dsp_lib)

View File

@ -1,7 +1,8 @@
/* #define DEBUGTRACE_ENABLED */
// #define DEBUGTRACE_ENABLED
#include "debugtrace.hpp"
#include "lasp_daqconfig.h"
#include "lasp_config.h"
#include "lasp_daq.h"
#if LASP_HAS_ULDAQ == 1
#include "lasp_uldaq.h"
@ -9,6 +10,9 @@
#if LASP_HAS_RTAUDIO == 1
#include "lasp_rtaudiodaq.h"
#endif
#if LASP_HAS_PORTAUDIO == 1
#include "lasp_portaudiodaq.h"
#endif
using rte = std::runtime_error;
Daq::~Daq() { DEBUGTRACE_ENTER; }
@ -27,6 +31,11 @@ std::unique_ptr<Daq> Daq::createDaq(const DeviceInfo &devinfo,
if (devinfo.api.apicode == LASP_RTAUDIO_APICODE) {
return createRtAudioDevice(devinfo, config);
}
#endif
#if LASP_HAS_PORTAUDIO == 1
if (devinfo.api.apicode == LASP_PORTAUDIO_APICODE) {
return createPortAudioDevice(devinfo, config);
}
#endif
throw rte(string("Unable to match Device API: ") + devinfo.api.apiname);
}
@ -35,31 +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("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

@ -5,6 +5,8 @@
#include "lasp_types.h"
#include <functional>
#include <memory>
#include <mutex>
#include <atomic>
/**
* \defgroup device Device interfacing
@ -13,12 +15,12 @@
* @brief Callback of DAQ for input data. Callback should return
* false for a stop request.
*/
using InDaqCallback = std::function<bool(const DaqData &)>;
using InDaqCallback = std::function<void(const DaqData &)>;
/**
* @brief
*/
using OutDaqCallback = std::function<bool(DaqData &)>;
using OutDaqCallback = std::function<void(DaqData &)>;
/**
* @brief Base cass for all DAQ (Data Acquisition) interfaces. A DAQ can be a
@ -46,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
*/
@ -59,7 +65,7 @@ public:
{StreamError::logicError, "Logic error (probably a bug)"},
};
bool isRunning = false;
/**
* @brief Check if stream has error
*
@ -67,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

@ -137,15 +137,26 @@ const DaqApi uldaqapi("UlDaq", 0);
#include "RtAudio.h"
const us LASP_RTAUDIO_APICODE = 1;
const DaqApi rtaudioAlsaApi("RtAudio Linux ALSA", 1, RtAudio::Api::LINUX_ALSA);
const DaqApi rtaudioPulseaudioApi("RtAudio Linux Pulseaudio", 1,
const DaqApi rtaudioPulseaudioApi("RtAudio Linux Pulseaudio", LASP_RTAUDIO_APICODE,
RtAudio::Api::LINUX_PULSE);
const DaqApi rtaudioWasapiApi("RtAudio Windows Wasapi", 1,
const DaqApi rtaudioWasapiApi("RtAudio Windows Wasapi", LASP_RTAUDIO_APICODE,
RtAudio::Api::WINDOWS_WASAPI);
const DaqApi rtaudioDsApi("RtAudio Windows DirectSound", 1,
const DaqApi rtaudioDsApi("RtAudio Windows DirectSound", LASP_RTAUDIO_APICODE,
RtAudio::Api::WINDOWS_DS);
const DaqApi rtaudioAsioApi("RtAudio Windows ASIO", 1,
const DaqApi rtaudioAsioApi("RtAudio Windows ASIO", LASP_RTAUDIO_APICODE,
RtAudio::Api::WINDOWS_ASIO);
#endif
#if LASP_HAS_PORTAUDIO == 1
const us LASP_PORTAUDIO_APICODE = 2;
const DaqApi portaudioALSAApi("PortAudio Linux ALSA", LASP_PORTAUDIO_APICODE, 0);
const DaqApi portaudioPulseApi("PortAudio Linux PulseAudio", LASP_PORTAUDIO_APICODE, 1);
const DaqApi portaudioASIOApi("PortAudio Windows ASIO", LASP_PORTAUDIO_APICODE, 2);
const DaqApi portaudioDSApi("PortAudio Windows DirectSound", LASP_PORTAUDIO_APICODE, 3);
const DaqApi portaudioWMMEApi("PortAudio Windows WMME", LASP_PORTAUDIO_APICODE, 4);
const DaqApi portaudioWASAPIApi("PortAudio Windows WASAPI", LASP_PORTAUDIO_APICODE, 5);
const DaqApi portaudioWDMKS("PortAudio Windows WDMKS", LASP_PORTAUDIO_APICODE, 6);
const DaqApi portaudioDirectSoundApi("PortAudio Windows DirectSound", LASP_PORTAUDIO_APICODE, 7);
#endif
class DeviceInfo;

View File

@ -26,7 +26,9 @@ DaqData::DaqData(const us nframes, const us nchannels,
DEBUGTRACE_PRINT(sw);
assert(sw > 0 && sw <= 8);
_data = new (std::align_val_t{8}) byte_t[sw * nchannels * nframes];
_data = reinterpret_cast<byte_t *>(
new double[(sw * nchannels * nframes) / sizeof(double) + 1]);
if (!_data) {
throw rte("Could not allocate memory for DaqData!");
}
@ -52,7 +54,7 @@ DaqData::DaqData(DaqData &&o)
DaqData::~DaqData() {
DEBUGTRACE_ENTER;
if (_data)
delete[] _data;
delete[](reinterpret_cast<double *>(_data));
}
void DaqData::copyInFromRaw(const std::vector<byte_t *> &ptrs) {

View File

@ -10,6 +10,9 @@
#if LASP_HAS_RTAUDIO == 1
#include "lasp_rtaudiodaq.h"
#endif
#if LASP_HAS_PORTAUDIO == 1
#include "lasp_portaudiodaq.h"
#endif
DeviceInfoList DeviceInfo::getDeviceInfo() {
@ -21,6 +24,9 @@ DeviceInfoList DeviceInfo::getDeviceInfo() {
#if LASP_HAS_RTAUDIO == 1
fillRtAudioDeviceInfo(devs);
#endif
#if LASP_HAS_PORTAUDIO == 1
fillPortAudioDeviceInfo(devs);
#endif
return devs;
}

View File

@ -19,6 +19,7 @@ public:
* @brief Virtual desctructor. Can be derived class.
*/
virtual ~DeviceInfo() {}
DeviceInfo& operator=(const DeviceInfo&) = delete;
/**
* @brief Clone a device info.
@ -67,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
*/
@ -124,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

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

View File

@ -0,0 +1,79 @@
#pragma once
#include "lasp_types.h"
#include <atomic>
#include <functional>
#include <memory>
#include <thread>
class StreamMgr;
using SmgrHandle = std::shared_ptr<StreamMgr>;
class DaqData;
class Daq;
/** \addtogroup device
* @{
*/
/**
* @brief The function definition of callbacks with incoming DAQ data
*/
using InCallbackType = std::function<void(const DaqData &)>;
/**
* @brief Function definition for the reset callback.
*/
using ResetCallbackType = std::function<void(const Daq *)>;
class InDataHandler {
protected:
std::weak_ptr<StreamMgr> _mgr;
#if LASP_DEBUG == 1
// This is a flag to indicate whether the method stop() is called for the
// current handler. It should call the method stop() from the derived class's
// destructor.
std::atomic<bool> stopCalled{false};
#endif
public:
~InDataHandler();
const InCallbackType inCallback;
const ResetCallbackType reset;
/**
* @brief When constructed, the handler is added to the stream manager, which
* will call the handlers's inCallback() until stop() is called.
*
* @param mgr Stream manager.
* @param cb The callback that is stored, and called on new DAQ data
* @param resetfcn The callback that is stored, and called when the DAQ
* changes state.
*/
InDataHandler(SmgrHandle mgr, InCallbackType cb,
ResetCallbackType resetfcn);
/**
* @brief Adds the current InDataHandler to the list of handlers in the
* StreamMgr. After this happens, the reset() method stored in this
* object is called back. When the stream is running, right after this,
* inCallback() is called with DaqData.
*/
void start();
/**
* @brief Removes the currend InDataHandler from the list of handlers in the
* StreamMgr. From that point on, the object can be safely destroyed. Not
* calling stop() before destruction of this object is considered a BUG. I.e.
* a class which *uses* an InDataHandler should always call stop() in its
* destructor.
*/
void stop();
#if LASP_DEBUG == 1
const std::thread::id main_thread_id;
void checkRightThread() const;
#else
void checkRightThread() const {}
#endif
};
/** @} */

View File

@ -1,5 +1,5 @@
#include <mutex>
/* #define DEBUGTRACE_ENABLED */
#include <mutex>
#include "debugtrace.hpp"
#include "lasp_mathtypes.h"
@ -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;
@ -177,24 +195,24 @@ public:
inParams = std::make_unique<RtAudio::StreamParameters>();
// +1 to get the count.
inParams->nChannels = getHighestEnabledInChannel() + 1;
if (inParams->nChannels < 1) {
throw rte("Invalid input number of channels");
}
/// RtAudio lacks good bookkeeping when the first channel is not equal to
/// 0. For now, our fix is to shift out the channels we want, and let
/// RtAudio pass on all channels.
inParams->firstChannel = 0;
inParams->deviceId = devinfo._api_devindex;
} else {
inParams->nChannels = devinfo.ninchannels;
inParams->deviceId = devinfo.ID;
}
else
{
outParams = std::make_unique<RtAudio::StreamParameters>();
outParams->nChannels = getHighestEnabledOutChannel() + 1;
if (outParams->nChannels < 1) {
throw rte("Invalid output number of channels");
}
/// RtAudio lacks good bookkeeping when the first channel is not equal to
/// 0. For now, our fix is to shift out the channels we want, and let
/// RtAudio pass on all channels.
outParams->firstChannel = 0;
outParams->deviceId = devinfo._api_devindex;
outParams->nChannels = devinfo.noutchannels;
outParams->deviceId = devinfo.ID;
}
RtAudio::StreamOptions streamoptions;
@ -207,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;
@ -238,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) {
throw rte("Got different number of frames per block back from RtAudio "
"backend. I do not know what to do.");
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.");
}
}
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");
@ -275,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");
}
@ -284,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;
@ -294,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;
@ -306,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;
@ -322,7 +364,8 @@ public:
rval = 1;
};
switch (status) {
switch (status)
{
case RTAUDIO_INPUT_OVERFLOW:
stopWithError(se::inputXRun);
return 1;
@ -338,44 +381,46 @@ public:
const auto &dtype_descr = dtypeDescr();
const auto dtype = dataType();
us neninchannels = this->neninchannels();
us nenoutchannels = this->nenoutchannels();
us sw = dtype_descr.sw;
if (nFrames != nFramesPerBlock) {
const us neninchannels = this->neninchannels();
const us nenoutchannels = this->nenoutchannels();
const us sw = dtype_descr.sw;
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);
const us ch_min = getLowestEnabledInChannel();
const us ch_max = getHighestEnabledInChannel();
us i = 0;
for (us ch = ch_min; ch <= ch_max; ch++) {
if (inchannel_config.at(ch).enabled) {
assert(ch_min < ninchannels);
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)
{
byte_t *ptr =
static_cast<byte_t *>(inputBuffer) + sw * i * nFramesPerBlock;
DEBUGTRACE_PRINT((us)ptr);
static_cast<byte_t *>(inputBuffer) + sw * ch * nFramesPerBlock;
ptrs.push_back(ptr);
}
i++;
}
DaqData d{nFramesPerBlock, neninchannels, dtype};
d.copyInFromRaw(ptrs);
bool ret = _incallback(d);
if (!ret) {
stopWithError(se::noError);
return 1;
}
_incallback(d);
}
if (outputBuffer) {
if (outputBuffer)
{
assert(_outcallback);
std::vector<byte_t *> ptrs;
ptrs.reserve(nenoutchannels);
@ -384,24 +429,24 @@ public:
const us ch_min = getLowestEnabledOutChannel();
const us ch_max = getHighestEnabledOutChannel();
us i = 0;
for (us ch = ch_min; ch <= ch_max; ch++) {
if (outchannel_config.at(ch).enabled) {
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)
{
ptrs.push_back(static_cast<byte_t *>(outputBuffer) +
sw * i * nFramesPerBlock);
sw * ch * nFramesPerBlock);
}
i++;
}
DaqData d{nFramesPerBlock, nenoutchannels, dtype};
bool ret = _outcallback(d);
if (!ret) {
stopWithError(se::noError);
return 1;
}
_outcallback(d);
// Copy over the buffer
us j = 0;
for (auto ptr : ptrs) {
for (auto ptr : ptrs)
{
d.copyToRaw(j, ptr);
j++;
}
@ -417,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

@ -0,0 +1,35 @@
#pragma once
#include "lasp_daq.h"
#include <memory>
/** \addtogroup device
* @{
* \defgroup rtaudio RtAudio backend
* This code is used to interface with the RtAudio cross-platform audio
* interface.
*
* \addtogroup rtaudio
* @{
*/
/**
* @brief Method called from Daq::createDaq.
*
* @param devinfo Device info
* @param config DAQ Configuration settings
*
* @return Pointer to Daq instance. Throws Runtime errors on error.
*/
std::unique_ptr<Daq> createRtAudioDevice(const DeviceInfo& devinfo,
const DaqConfiguration& config);
/**
* @brief Append RtAudio backend devices to the list
*
* @param devinfolist List to append to
*/
void fillRtAudioDeviceInfo(DeviceInfoList &devinfolist);
/** @} */
/** @} */

View File

@ -1,103 +1,161 @@
/* #define DEBUGTRACE_ENABLED */
// #define DEBUGTRACE_ENABLED
#include "lasp_streammgr.h"
#include "debugtrace.hpp"
#include "lasp_biquadbank.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;
using rte = std::runtime_error;
InDataHandler::InDataHandler(StreamMgr &mgr) : _mgr(mgr) { DEBUGTRACE_ENTER; }
void InDataHandler::start() {
DEBUGTRACE_ENTER;
_mgr.addInDataHandler(*this);
}
void InDataHandler::stop() {
#if LASP_DEBUG == 1
stopCalled = true;
#endif
_mgr.removeInDataHandler(*this);
}
InDataHandler::~InDataHandler() {
/**
* @brief The main global handle to a stream, stored in a weak pointer, if it
* does not yet exist, via StreamMgr::getInstance, a new stream mgr is created.
* It also makes sure that the stream manager is deleted once the latest handle
* to it has been destroyed (no global stuff left).
*/
std::weak_ptr<StreamMgr> _mgr;
std::mutex _mgr_mutex;
using Lck = std::scoped_lock<std::recursive_mutex>;
/**
* @brief The only way to obtain a stream manager, can only be called from the
* thread that does it the first time.
*
* @return Stream manager handle
*/
SmgrHandle StreamMgr::getInstance() {
DEBUGTRACE_ENTER;
#if LASP_DEBUG == 1
if (!stopCalled) {
cerr << "************ BUG: Stop function not called while arriving at "
"InDataHandler's destructor. Fix this by calling "
"InDataHandler::stop() from the derived class' destructor."
<< endl;
abort();
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.
auto mgr = _mgr.lock();
if (mgr) {
return mgr;
}
mgr = SmgrHandle(new StreamMgr());
if (!mgr) {
throw rte("Fatal: could not allocate stream manager!");
}
// Update global weak pointer
_mgr = mgr;
return mgr;
}
#if LASP_DEBUG == 1
// Make sure we never ask for a new SmgrHandle from a different thread.
assert(std::this_thread::get_id() == mgr->main_thread_id);
#endif
}
StreamMgr &StreamMgr::getInstance() {
DEBUGTRACE_ENTER;
static StreamMgr mgr;
return mgr;
}
StreamMgr::StreamMgr() {
DEBUGTRACE_ENTER;
StreamMgr::StreamMgr()
#if LASP_DEBUG == 1
_main_thread_id = std::this_thread::get_id();
: main_thread_id(std::this_thread::get_id())
#endif
{
DEBUGTRACE_ENTER;
// Trigger a scan for the available devices, in the background.
rescanDAQDevices(true);
}
#if LASP_DEBUG == 1
void StreamMgr::checkRightThread() const {
assert(std::this_thread::get_id() == _main_thread_id);
assert(std::this_thread::get_id() == main_thread_id);
}
#endif
void StreamMgr::rescanDAQDevices(bool background,
std::function<void()> callback) {
DEBUGTRACE_ENTER;
auto &pool = getPool();
checkRightThread();
if (!_devices_mtx.try_lock()) {
throw rte("A background DAQ device scan is probably already running");
DEBUGTRACE_PRINT(background);
if (_scanningDevices) {
throw rte("A background device scan is already busy");
}
_devices_mtx.unlock();
Lck lck(_mtx);
checkRightThread();
if (_inputStream || _outputStream) {
throw rte("Rescanning DAQ devices only possible when no stream is running");
}
_devices.clear();
/* auto &pool = getPool(); */
if (!background) {
_scanningDevices = true;
rescanDAQDevices_impl(callback);
} else {
pool.push_task(&StreamMgr::rescanDAQDevices_impl, this, callback);
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();
}
}
bool StreamMgr::inCallback(const DaqData &data) {
_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.
@ -117,33 +175,24 @@ bool StreamMgr::inCallback(const DaqData &data) {
}
}
DEBUGTRACE_PRINT("Calling incallback for handlers (filtered)...");
for (auto &handler : _inDataHandlers) {
bool res = handler->inCallback(input_filtered);
if (!res) {
return false;
}
handler->inCallback(input_filtered);
}
} else {
/// No input filters
DEBUGTRACE_PRINT("Calling incallback for handlers...");
for (auto &handler : _inDataHandlers) {
bool res = handler->inCallback(data);
if (!res) {
return false;
}
handler->inCallback(data);
}
}
return true;
}
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) {
@ -166,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());
@ -198,81 +248,100 @@ template <typename T> bool fillData(DaqData &data, const vd &signal) {
return true;
}
bool StreamMgr::outCallback(DaqData &data) {
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.
std::fill(data.raw_ptr(), data.raw_ptr() + data.size_bytes(), 0);
}
return true;
}
StreamMgr::~StreamMgr() {
DEBUGTRACE_ENTER;
checkRightThread();
stopAllStreams();
if (!_inDataHandlers.empty()) {
cerr << "*** WARNING: InDataHandlers have not been all stopped, while "
"StreamMgr destructor is called. This is a misuse BUG"
<< endl;
abort();
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
// order in which destructors are called does not matter anymore. As soon as
// the stream manager is destructed, the weak pointers loose there ref, and do
// not have to removeInDataHandler() anymore.
// Stop the streams in this phase, otherwise it might happen during the
// destruction of the Siggen, in which case we might get calls to pure
// virtual methods. This was really a bug.
_inputStream.reset();
_outputStream.reset();
}
void StreamMgr::stopAllStreams() {
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;
bool found = false;
// Match configuration to a device in the list of devices
for (auto &devinfoi : _devices) {
if (config.match(*devinfoi)) {
devinfo = devinfoi.get();
break;
}
}
if (!devinfo) {
if (devinfo == nullptr) {
throw rte("Could not find a device with name " + config.device_name +
" in list of devices.");
}
@ -283,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;
@ -333,10 +408,13 @@ void StreamMgr::startStream(const DaqConfiguration &config) {
d fs = daq->samplerate();
/// Create input filters
_inputFilters.clear();
/// No input filter for monitor channel.
/// No input filter for monitor channel, which comes as the first input
/// channel In the list
if (config.monitorOutput && devinfo->hasInternalOutputMonitor) {
_inputFilters.push_back(nullptr);
}
for (auto &ch : daq->inchannel_config) {
if (ch.enabled) {
if (ch.digitalHighPassCutOn < 0) {
@ -349,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) {
@ -376,61 +454,76 @@ 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) {
void StreamMgr::addInDataHandler(InDataHandler *handler) {
DEBUGTRACE_ENTER;
Lck lck(_mtx);
checkRightThread();
std::scoped_lock<std::mutex> lck(_inDataHandler_mtx);
if (_inputStream) {
handler.reset(_inputStream.get());
} else {
handler.reset(nullptr);
}
if (std::find(_inDataHandlers.cbegin(), _inDataHandlers.cend(), &handler) !=
assert(handler);
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);
_inDataHandlers.push_back(handler);
DEBUGTRACE_PRINT(_inDataHandlers.size());
}
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
@ -443,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,69 +1,18 @@
#pragma once
#include "lasp_daq.h"
#include "lasp_siggen.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 {
protected:
StreamMgr &_mgr;
#if LASP_DEBUG == 1
std::atomic<bool> stopCalled{false};
#endif
public:
virtual ~InDataHandler();
/**
* @brief When constructed, the handler is added to the stream manager, which
* will call the handlers's inCallback() until stop() is called.
*
* @param mgr Stream manager.
*/
InDataHandler(StreamMgr &mgr);
/**
* @brief This function is called when input data from a DAQ is available.
*
* @param daqdata Input data from DAQ
*
* @return true if no error. False to stop the stream from running.
*/
virtual bool inCallback(const DaqData &daqdata) = 0;
/**
* @brief Reset in-data handler.
*
* @param daq New DAQ configuration of inCallback(). If nullptr is given,
* it means that the stream is stopped.
*/
virtual void reset(const Daq *daq = nullptr) = 0;
/**
* @brief This function should be called from the constructor of the
* implementation of InDataHandler. It will start the stream's calling of
* inCallback().
*/
void start();
/**
* @brief This function should be called from the destructor of derived
* classes, to disable the calls to inCallback(), such that proper
* destruction of the object is allowed and no other threads call methods
* from the object. It removes the inCallback() from the callback list of the
* StreamMgr(). **Failing to call this function results in deadlocks, errors
* like "pure virtual function called", or other**.
*/
void stop();
};
class InDataHandler;
class SeriesBiquad;
@ -76,22 +25,23 @@ class SeriesBiquad;
* fact is asserted.
*/
class StreamMgr {
#if LASP_DEBUG == 1
std::thread::id _main_thread_id;
#endif
mutable std::recursive_mutex _mtx;
/**
* @brief Storage for streams.
*/
std::unique_ptr<Daq> _inputStream, _outputStream;
std::atomic<bool> _scanningDevices{false};
GlobalThreadPool _pool;
/**
* @brief All indata handlers are called when input data is available. Note
* that they can be called from different threads and should take care of
* thread-safety.
*/
std::list<InDataHandler *> _inDataHandlers;
std::mutex _inDataHandler_mtx;
/**
* @brief Signal generator in use to generate output data. Currently
@ -104,20 +54,21 @@ class StreamMgr {
*/
std::vector<std::unique_ptr<SeriesBiquad>> _inputFilters;
std::mutex _siggen_mtx;
std::mutex _devices_mtx;
/**
* @brief Current storage for the device list
*/
DeviceInfoList _devices;
// Singleton, no public constructor. Can only be obtained using
// getInstance();
StreamMgr();
friend class InDataHandler;
friend class Siggen;
// Singleton, no public destructor
public:
~StreamMgr();
public:
enum class StreamType : us {
/**
* @brief Input stream
@ -137,7 +88,7 @@ class StreamMgr {
*
* @return Reference to stream manager.
*/
static StreamMgr &getInstance();
static std::shared_ptr<StreamMgr> getInstance();
/**
* @brief Obtain a list of devices currently available. When the StreamMgr is
@ -146,9 +97,10 @@ class StreamMgr {
* @return A copy of the internal stored list of devices
*/
DeviceInfoList getDeviceInfo() const {
std::scoped_lock lck(const_cast<std::mutex &>(_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;
@ -157,15 +109,16 @@ class StreamMgr {
/**
* @brief Triggers a background scan of the DAQ devices, which updates the
* internally stored list of devices. Throws a runtime error when a
* background thread is already scanning for devices.
* background thread is already scanning for devices, or if a stream is
* running.
*
* @param background Perform searching for DAQ devices in the background. If
* 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.
@ -186,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;
}
@ -231,17 +184,16 @@ class StreamMgr {
/**
* @brief Set active signal generator for output streams. Only one `Siggen'
* is active at the same time. Siggen controls its own data race protection
* using a mutex.
* using a mutex. If no Siggen is there, and an output stream is running, it
* will send a default signal of 0.
*
* @param s New Siggen pointer
*/
void setSiggen(std::shared_ptr<Siggen> s);
private:
bool inCallback(const DaqData &data);
bool outCallback(DaqData &data);
void removeInDataHandler(InDataHandler &handler);
private:
void inCallback(const DaqData &data);
void outCallback(DaqData &data);
/**
* @brief Add an input data handler. The handler's inCallback() function is
@ -251,8 +203,14 @@ private:
*
* @param handler The handler to add.
*/
void addInDataHandler(InDataHandler &handler);
void addInDataHandler(InDataHandler *handler);
/**
* @brief Remove InDataHandler from the list.
*
* @param handler
*/
void removeInDataHandler(InDataHandler &handler);
/**
* @brief Do the actual rescanning.
*
@ -261,6 +219,7 @@ private:
void rescanDAQDevices_impl(std::function<void()> callback);
#if LASP_DEBUG == 1
const std::thread::id main_thread_id;
void checkRightThread() const;
#else
void checkRightThread() const {}

View File

@ -1,18 +1,22 @@
/* #define DEBUGTRACE_ENABLED */
#include "debugtrace.hpp"
#include "lasp_config.h"
#if LASP_HAS_ULDAQ == 1
#include "lasp_uldaq.h"
#include "lasp_uldaq_impl.h"
#include <uldaq.h>
/**
* @brief The maximum number of devices that can be enumerated when calling
* ulGetDaqDeviceInventory()
*/
const us MAX_ULDAQ_DEV_COUNT_PER_API = 100;
void fillUlDaqDeviceInfo(DeviceInfoList &devinfolist) {
DEBUGTRACE_ENTER;
UlError err;
unsigned int numdevs = MAX_ULDAQ_DEV_COUNT_PER_API;
@ -20,13 +24,13 @@ void fillUlDaqDeviceInfo(DeviceInfoList &devinfolist) {
DaqDeviceDescriptor descriptor;
DaqDeviceInterface interfaceType = ANY_IFC;
err = ulGetDaqDeviceInventory(interfaceType, devdescriptors,
static_cast<unsigned *>(&numdevs));
err = ulGetDaqDeviceInventory(interfaceType, devdescriptors, &numdevs);
if (err != ERR_NO_ERROR) {
throw rte("UlDaq device inventarization failed");
}
DEBUGTRACE_PRINT(string("Number of devices: ") + std::to_string(numdevs));
for (unsigned i = 0; i < numdevs; i++) {
descriptor = devdescriptors[i];
@ -35,48 +39,50 @@ void fillUlDaqDeviceInfo(DeviceInfoList &devinfolist) {
devinfo._uldaqDescriptor = descriptor;
devinfo.api = uldaqapi;
string name, interface;
string productname = descriptor.productName;
if (productname != "DT9837A") {
throw rte("Unknown UlDAQ type: " + productname);
{
string name;
string productname = descriptor.productName;
if (productname != "DT9837A") {
throw rte("Unknown UlDAQ type: " + productname);
}
switch (descriptor.devInterface) {
case USB_IFC:
name = "USB - ";
break;
case BLUETOOTH_IFC:
/* devinfo. */
name = "Bluetooth - ";
break;
case ETHERNET_IFC:
/* devinfo. */
name = "Ethernet - ";
break;
default:
name = "Uknown interface = ";
}
name += productname + " " + string(descriptor.uniqueId);
devinfo.device_name = name;
}
switch (descriptor.devInterface) {
case USB_IFC:
name = "USB - ";
break;
case BLUETOOTH_IFC:
/* devinfo. */
name = "Bluetooth - ";
break;
case ETHERNET_IFC:
/* devinfo. */
name = "Ethernet - ";
break;
default:
name = "Uknown interface = ";
}
name += string(descriptor.productName) + " " + string(descriptor.uniqueId);
devinfo.device_name = std::move(name);
devinfo.physicalOutputQty = DaqChannel::Qty::Voltage;
devinfo.physicalInputQty = DaqChannel::Qty::Voltage;
devinfo.availableDataTypes.push_back(
DataTypeDescriptor::DataType::dtype_fl64);
devinfo.prefDataTypeIndex = 0;
devinfo.availableSampleRates = {8000, 10000, 11025, 16000, 20000,
22050, 24000, 32000, 44056, 44100,
47250, 48000, 50000, 50400, 51000};
devinfo.availableSampleRates = ULDAQ_SAMPLERATES;
devinfo.prefSampleRateIndex = 11;
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;
@ -87,6 +93,7 @@ void fillUlDaqDeviceInfo(DeviceInfoList &devinfolist) {
devinfo.hasInternalOutputMonitor = true;
devinfo.hasDuplexMode = true;
devinfo.duplexModeForced = true;
// Finally, this devinfo is pushed back in list
@ -96,7 +103,12 @@ void fillUlDaqDeviceInfo(DeviceInfoList &devinfolist) {
std::unique_ptr<Daq> createUlDaqDevice(const DeviceInfo &devinfo,
const DaqConfiguration &config) {
return std::make_unique<DT9837A>(devinfo, config);
const UlDaqDeviceInfo *_info =
dynamic_cast<const UlDaqDeviceInfo *>(&devinfo);
if (_info == nullptr) {
throw rte("BUG: Could not cast DeviceInfo to UlDaqDeviceInfo");
}
return std::make_unique<DT9837A>(*_info, config);
}
#endif // LASP_HAS_ULDAQ

View File

@ -1,18 +1,24 @@
#pragma once
#include "lasp_daq.h"
/**
* @brief The maximum number of devices that can be enumerated when calling
* ulGetDaqDeviceInventory()
/** \addtogroup device
* \defgroup uldaq UlDAQ specific code
* This code is used to interface with UlDAQ compatible devices. It is only
* tested on Linux.
* @{
* \addtogroup uldaq
* @{
*/
const us MAX_ULDAQ_DEV_COUNT_PER_API = 100;
std::unique_ptr<Daq> createUlDaqDevice(const DeviceInfo &devinfo,
const DaqConfiguration &config);
/**
* @brief Fill device info list with UlDaq specific devices, if any.
* @brief Append device info list with UlDaq specific devices, if any.
*
* @param devinfolist Info list to append to.
*/
void fillUlDaqDeviceInfo(DeviceInfoList& devinfolist);
/** @} */
/** @} */

View File

@ -0,0 +1,532 @@
// #define DEBUGTRACE_ENABLED
#include "debugtrace.hpp"
#include "lasp_config.h"
#if LASP_HAS_PORTAUDIO == 1
#include <gsl-lite/gsl-lite.hpp>
#include <mutex>
#include <string>
#include "lasp_portaudiodaq.h"
#include "portaudio.h"
using rte = std::runtime_error;
using std::cerr;
using std::endl;
using std::string;
using std::to_string;
inline void throwIfError(PaError e) {
DEBUGTRACE_ENTER;
if (e != paNoError) {
throw rte(string("PortAudio backend error: ") + Pa_GetErrorText(e));
}
}
/**
* @brief Device info, plus PortAudio stuff
*/
class OurPaDeviceInfo : public DeviceInfo {
public:
/**
* @brief Store instance to PaDeviceInfo.
*/
PaDeviceInfo _paDevInfo;
virtual std::unique_ptr<DeviceInfo> clone() const override final {
return std::make_unique<OurPaDeviceInfo>(*this);
}
OurPaDeviceInfo &operator=(const OurPaDeviceInfo &) = delete;
OurPaDeviceInfo(const OurPaDeviceInfo &) = default;
OurPaDeviceInfo(const PaDeviceInfo &o) : DeviceInfo(), _paDevInfo(o) {}
};
void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist) {
DEBUGTRACE_ENTER;
bool shouldPaTerminate = false;
try {
PaError err = Pa_Initialize();
/// PortAudio says that Pa_Terminate() should not be called whenever there
/// is an error in Pa_Initialize(). This is opposite to what most examples
/// of PortAudio show.
throwIfError(err);
shouldPaTerminate = true;
auto fin = gsl::finally([&err] {
DEBUGTRACE_PRINT("Terminating PortAudio instance");
err = Pa_Terminate();
if (err != paNoError) {
cerr << "Error terminating PortAudio. Do not know what to do." << endl;
}
});
const PaHostApiIndex apicount = Pa_GetHostApiCount();
if (apicount < 0) {
return;
}
/* const PaDeviceInfo *deviceInfo; */
const int numDevices = Pa_GetDeviceCount();
if (numDevices < 0) {
throw rte("PortAudio could not find any devices");
}
for (us i = 0; i < (us)numDevices; i++) {
/* DEBUGTRACE_PRINT(i); */
bool hasDuplexMode = false;
const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(i);
if (!deviceInfo) {
throw rte("No device info struct returned");
}
OurPaDeviceInfo d(*deviceInfo);
// We store the name in d.device_name
d._paDevInfo.name = nullptr;
d.device_name = deviceInfo->name;
const PaHostApiInfo *hostapiinfo = Pa_GetHostApiInfo(deviceInfo->hostApi);
if (hostapiinfo == nullptr) {
throw std::runtime_error("Hostapi nullptr!");
}
switch (hostapiinfo->type) {
case paALSA:
// Duplex mode for alsa
hasDuplexMode = true;
d.api = portaudioALSAApi;
break;
case paASIO:
hasDuplexMode = true;
d.api = portaudioASIOApi;
break;
case paDirectSound:
d.api = portaudioDirectSoundApi;
break;
case paMME:
d.api = portaudioWMMEApi;
break;
case paWDMKS:
d.api = portaudioWDMKS;
break;
case paWASAPI:
d.api = portaudioWASAPIApi;
break;
case paPulseAudio:
d.api = portaudioPulseApi;
break;
default:
throw rte("Unimplemented portaudio API!");
break;
}
d.availableDataTypes = {DataTypeDescriptor::DataType::dtype_int16,
DataTypeDescriptor::DataType::dtype_int32,
DataTypeDescriptor::DataType::dtype_fl32};
d.prefDataTypeIndex = 2;
d.availableSampleRates = {8000.0, 9600.0, 11025.0, 12000.0, 16000.0,
22050.0, 24000.0, 32000.0, 44100.0, 48000.0,
88200.0, 96000.0, 192000.0};
d.prefSampleRateIndex = 9;
d.availableFramesPerBlock = {512, 1024, 2048, 4096, 8192};
d.prefFramesPerBlockIndex = 2;
d.availableInputRanges = {1.0};
// d.prefInputRangeIndex = 0; // Constructor-defined
d.availableOutputRanges = {1.0};
// d.prefOutputRangeIndex = 0; // Constructor-defined
d.ninchannels = deviceInfo->maxInputChannels;
d.noutchannels = deviceInfo->maxOutputChannels;
// Duplex mode, only for ALSA devices
d.hasDuplexMode = hasDuplexMode;
devinfolist.push_back(std::make_unique<OurPaDeviceInfo>(d));
}
}
catch (rte &e) {
if (shouldPaTerminate) {
PaError err = Pa_Terminate();
if (err != paNoError) {
cerr << "Error terminating PortAudio. Do not know what to do." << endl;
}
}
cerr << "PortAudio backend error: " << e.what() << std::endl;
return;
}
}
/**
* @brief Forward declaration of raw callback. Calls into
* PortAudioDaq->memberPaCallback. Undocumented parameters are specified
* in memberPaCallback
*
* @param inputBuffer
* @param outputBuffer
* @param framesPerBuffer
* @param timeInfo
* @param statusFlags
* @param userData Pointer to PortAudioDaq* instance.
*
* @return
*/
static int rawPaCallback(const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo *timeInfo,
PaStreamCallbackFlags statusFlags, void *userData);
class PortAudioDaq : public Daq {
PaStream *_stream = nullptr;
std::atomic<StreamStatus::StreamError> _streamError =
StreamStatus::StreamError::noError;
InDaqCallback _incallback;
OutDaqCallback _outcallback;
public:
PortAudioDaq(const OurPaDeviceInfo &devinfo_gen,
const DaqConfiguration &config);
void start(InDaqCallback inCallback,
OutDaqCallback outCallback) override final;
void stop() override final;
StreamStatus getStreamStatus() const override final;
/**
* @brief Member va
*
* @param inputBuffer
* @param outputBuffer
* @param framesPerBuffer
* @param timeInfo
* @param statusFlags
*
* @return
*/
int memberPaCallback(const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo *timeInfo,
PaStreamCallbackFlags statusFlags);
~PortAudioDaq();
};
std::unique_ptr<Daq> createPortAudioDevice(const DeviceInfo &devinfo,
const DaqConfiguration &config) {
DEBUGTRACE_ENTER;
const OurPaDeviceInfo *_info =
dynamic_cast<const OurPaDeviceInfo *>(&devinfo);
if (_info == nullptr) {
throw rte("BUG: Could not cast DeviceInfo to OurPaDeviceInfo");
}
return std::make_unique<PortAudioDaq>(*_info, config);
}
static int rawPaCallback(const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo *timeInfo,
PaStreamCallbackFlags statusFlags, void *userData) {
return static_cast<PortAudioDaq *>(userData)->memberPaCallback(
inputBuffer, outputBuffer, framesPerBuffer, timeInfo, statusFlags);
}
PortAudioDaq::PortAudioDaq(const OurPaDeviceInfo &devinfo_gen,
const DaqConfiguration &config)
: Daq(devinfo_gen, config) {
DEBUGTRACE_ENTER;
bool shouldPaTerminate = false;
try {
PaError err = Pa_Initialize();
/// PortAudio says that Pa_Terminate() should not be called whenever there
/// is an error in Pa_Initialize(). This is opposite to what most examples
/// of PortAudio show.
throwIfError(err);
// OK, Pa_Initialize successfully finished, it means we have to clean up
// with Pa_Terminate in the destructor.
shouldPaTerminate = true;
// Going to find the device in the list. If its there, we have to retrieve
// the index, as this is required in the PaStreamParameters struct
int devindex = -1;
for (int i = 0; i < Pa_GetDeviceCount(); i++) {
// DEBUGTRACE_PRINT(i);
bool ok = true;
const PaDeviceInfo *info = Pa_GetDeviceInfo(i);
if (!info) {
throw rte("No device structure returned from PortAudio");
}
ok &= string(info->name) == devinfo_gen.device_name;
ok &= info->hostApi == devinfo_gen._paDevInfo.hostApi;
ok &= info->maxInputChannels == devinfo_gen._paDevInfo.maxInputChannels;
ok &= info->maxOutputChannels == devinfo_gen._paDevInfo.maxOutputChannels;
ok &= info->defaultSampleRate == devinfo_gen._paDevInfo.defaultSampleRate;
if (ok) {
devindex = i;
}
}
if (devindex < 0) {
throw rte(string("Device not found: ") + string(devinfo_gen.device_name));
}
using Dtype = DataTypeDescriptor::DataType;
const Dtype dtype = dataType();
// Sample format is bit flag
PaSampleFormat format = paNonInterleaved;
switch (dtype) {
case Dtype::dtype_fl32:
DEBUGTRACE_PRINT("Datatype float32");
format |= paFloat32;
break;
case Dtype::dtype_fl64:
DEBUGTRACE_PRINT("Datatype float64");
throw rte("Invalid data type specified for DAQ stream.");
break;
case Dtype::dtype_int8:
DEBUGTRACE_PRINT("Datatype int8");
format |= paInt8;
break;
case Dtype::dtype_int16:
DEBUGTRACE_PRINT("Datatype int16");
format |= paInt16;
break;
case Dtype::dtype_int32:
DEBUGTRACE_PRINT("Datatype int32");
format |= paInt32;
break;
default:
throw rte("Invalid data type specified for DAQ stream.");
break;
}
std::unique_ptr<PaStreamParameters> instreamParams;
std::unique_ptr<PaStreamParameters> outstreamParams;
if (neninchannels() > 0) {
instreamParams = std::make_unique<PaStreamParameters>(PaStreamParameters(
{.device = devindex,
.channelCount = (int)getHighestEnabledInChannel() + 1,
.sampleFormat = format,
.suggestedLatency = framesPerBlock() / samplerate(),
.hostApiSpecificStreamInfo = nullptr}));
}
if (nenoutchannels() > 0) {
outstreamParams = std::make_unique<PaStreamParameters>(PaStreamParameters(
{.device = devindex,
.channelCount = (int)getHighestEnabledOutChannel() + 1,
.sampleFormat = format,
.suggestedLatency = framesPerBlock() / samplerate(),
.hostApiSpecificStreamInfo = nullptr}));
}
// Next step: check whether we are OK
err = Pa_IsFormatSupported(instreamParams.get(), outstreamParams.get(),
samplerate());
throwIfError(err);
err = Pa_OpenStream(&_stream, // stream
instreamParams.get(), // inputParameters
outstreamParams.get(), // outputParameters
samplerate(), // yeah,
framesPerBlock(), // framesPerBuffer
paNoFlag, // streamFlags
rawPaCallback, this);
throwIfError(err);
assert(_stream);
} catch (rte &e) {
if (shouldPaTerminate) {
PaError err = Pa_Terminate();
if (err != paNoError) {
cerr << "Error terminating PortAudio. Do not know what to do." << endl;
}
}
throw;
}
}
void PortAudioDaq::start(InDaqCallback inCallback, OutDaqCallback outCallback) {
DEBUGTRACE_ENTER;
assert(_stream);
if (Pa_IsStreamActive(_stream)) {
throw rte("Stream is already running");
}
if (neninchannels() > 0) {
if (!inCallback) {
throw rte(
"Input callback given, but stream does not provide input data");
}
_incallback = inCallback;
}
if (nenoutchannels() > 0) {
if (!outCallback) {
throw rte(
"Output callback given, but stream does not provide output data");
}
_outcallback = outCallback;
}
PaError err = Pa_StartStream(_stream);
throwIfError(err);
}
void PortAudioDaq::stop() {
DEBUGTRACE_ENTER;
assert(_stream);
if (Pa_IsStreamStopped(_stream) > 1) {
throw rte("Stream is already stopped");
}
PaError err = Pa_StopStream(_stream);
throwIfError(err);
}
Daq::StreamStatus PortAudioDaq::getStreamStatus() const {
DEBUGTRACE_ENTER;
// Stores an error type and whether the
Daq::StreamStatus status;
using StreamError = Daq::StreamStatus::StreamError;
Daq::StreamStatus::StreamError errortype = _streamError.load();
PaError err = Pa_IsStreamStopped(_stream);
if (err > 1) {
// Stream is stopped due to an error in the callback. The exact error type
// is filled in in the if-statement above
return status;
} else if (err == 0) {
// Still running
status.isRunning = true;
} else if (err < 0) {
// Stream encountered an error.
switch (err) {
case paInternalError:
errortype = StreamError::driverError;
break;
case paDeviceUnavailable:
errortype = StreamError::driverError;
break;
case paInputOverflowed:
errortype = StreamError::inputXRun;
break;
case paOutputUnderflowed:
errortype = StreamError::outputXRun;
break;
default:
errortype = StreamError::driverError;
cerr << "Portaudio backend error:" << Pa_GetErrorText(err) << endl;
break;
}
}
status.errorType = errortype;
return status;
}
PortAudioDaq::~PortAudioDaq() {
DEBUGTRACE_ENTER;
PaError err;
assert(_stream);
if (Pa_IsStreamActive(_stream)) {
// Stop the stream first
stop();
}
err = Pa_CloseStream(_stream);
_stream = nullptr;
if (err != paNoError) {
cerr << "Error closing PortAudio stream. Do not know what to do." << endl;
}
err = Pa_Terminate();
if (err != paNoError) {
cerr << "Error terminating PortAudio. Do not know what to do." << endl;
}
}
int PortAudioDaq::memberPaCallback(const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo *timeInfo,
PaStreamCallbackFlags statusFlags) {
DEBUGTRACE_ENTER;
typedef Daq::StreamStatus::StreamError se;
if (statusFlags & paPrimingOutput) {
// Initial output buffers generated. So nothing with input yet
return paContinue;
}
if ((statusFlags & paInputUnderflow) || (statusFlags & paInputOverflow)) {
_streamError = se::inputXRun;
return paAbort;
}
if ((statusFlags & paOutputUnderflow) || (statusFlags & paOutputOverflow)) {
_streamError = se::outputXRun;
return paAbort;
}
if (framesPerBuffer != framesPerBlock()) {
cerr << "Logic error: expected a block size of: " << framesPerBlock()
<< endl;
_streamError = se::logicError;
return paAbort;
}
const us neninchannels = this->neninchannels();
const us nenoutchannels = this->nenoutchannels();
const auto &dtype_descr = dtypeDescr();
const auto dtype = dataType();
const us sw = dtype_descr.sw;
if (inputBuffer) {
assert(_incallback);
std::vector<byte_t *> ptrs;
ptrs.reserve(neninchannels);
const us ch_min = getLowestEnabledInChannel();
const us ch_max = getHighestEnabledInChannel();
assert(ch_min < ninchannels);
assert(ch_max < ninchannels);
/// Only pass on the pointers of the channels we want. inputBuffer is
/// noninterleaved, as specified in PortAudioDaq constructor.
for (us ch = ch_min; ch <= ch_max; ch++) {
if (inchannel_config.at(ch).enabled) {
byte_t *ch_ptr =
reinterpret_cast<byte_t **>(const_cast<void *>(inputBuffer))[ch];
ptrs.push_back(ch_ptr);
}
}
DaqData d{framesPerBuffer, neninchannels, dtype};
d.copyInFromRaw(ptrs);
_incallback(d);
}
if (outputBuffer) {
assert(_outcallback);
std::vector<byte_t *> ptrs;
ptrs.reserve(nenoutchannels);
/* outCallback */
const us ch_min = getLowestEnabledOutChannel();
const us ch_max = getHighestEnabledOutChannel();
assert(ch_min < noutchannels);
assert(ch_max < noutchannels);
/// Only pass on the pointers of the channels we want
for (us ch = ch_min; ch <= ch_max; ch++) {
if (outchannel_config.at(ch).enabled) {
byte_t *ch_ptr = reinterpret_cast<byte_t **>(outputBuffer)[ch];
ptrs.push_back(ch_ptr);
}
}
DaqData d{framesPerBuffer, nenoutchannels, dtype};
_outcallback(d);
// Copy over the buffer
us j = 0;
for (auto ptr : ptrs) {
d.copyToRaw(j, ptr);
j++;
}
}
return paContinue;
}
#endif

View File

@ -0,0 +1,35 @@
#pragma once
#include "lasp_daq.h"
#include <memory>
/** \addtogroup device
* @{
* \defgroup portaudio PortAudio backend
* This code is used to interface with the PortAudio cross-platform audio
* interface.
*
* \addtogroup portaudio
* @{
*/
/**
* @brief Method called from Daq::createDaq.
*
* @param devinfo Device info
* @param config DAQ Configuration settings
*
* @return Pointer to Daq instance. Throws Runtime errors on error.
*/
std::unique_ptr<Daq> createPortAudioDevice(const DeviceInfo& devinfo,
const DaqConfiguration& config);
/**
* @brief Append PortAudio backend devices to the list
*
* @param devinfolist List to append to
*/
void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist);
/** @} */
/** @} */

View File

@ -0,0 +1,245 @@
/* #define DEBUGTRACE_ENABLED */
#include "debugtrace.hpp"
#include "lasp_config.h"
#if LASP_HAS_ULDAQ == 1
#include "lasp_uldaq_bufhandler.h"
#include "lasp_daq.h"
InBufHandler::InBufHandler(DT9837A &daq, InDaqCallback cb)
: BufHandler(daq, daq.neninchannels()), cb(cb)
{
DEBUGTRACE_ENTER;
assert(daq.getHandle() != 0);
monitorOutput = daq.monitorOutput;
DaqInScanFlag inscanflags = DAQINSCAN_FF_DEFAULT;
ScanOption scanoptions = SO_CONTINUOUS;
UlError err = ERR_NO_ERROR;
std::vector<DaqInChanDescriptor> indescs;
boolvec eninchannels_without_mon = daq.eninchannels(false);
// Set ranges for each input. Below asks only channels that are not a
// monitor channel (hence the false flag).
dvec ranges = daq.inputRangeForEnabledChannels(false);
us enabled_ch_counter = 0;
for (us chin = 0; chin < 4; chin++) {
if (eninchannels_without_mon[chin] == true) {
DaqInChanDescriptor indesc;
indesc.type = DAQI_ANALOG_SE;
indesc.channel = chin;
double rangeval = ranges.at(enabled_ch_counter);
Range rangenum;
if (fabs(rangeval - 1.0) < 1e-8) {
rangenum = BIP1VOLTS;
} else if (fabs(rangeval - 10.0) < 1e-8) {
rangenum = BIP10VOLTS;
} else {
throw Daq::StreamException(Daq::StreamStatus::StreamError::logicError);
std::cerr << "Fatal: input range value is invalid" << endl;
return;
}
indesc.range = rangenum;
indescs.push_back(indesc);
enabled_ch_counter++;
}
}
// Add possibly last channel as monitor
if (monitorOutput) {
DaqInChanDescriptor indesc;
indesc.type = DAQI_DAC;
indesc.channel = 0;
/// The output only has a range of 10V, therefore the monitor of the
/// output also has to be set to this value.
indesc.range = BIP10VOLTS;
indescs.push_back(indesc);
}
assert(indescs.size() == nchannels);
DEBUGTRACE_MESSAGE("Starting input scan");
err = ulDaqInScan(daq.getHandle(), indescs.data(), nchannels,
2 * nFramesPerBlock, // Watch the 2 here!
&samplerate, scanoptions, inscanflags, buf.data());
throwOnPossibleUlException(err);
}
void InBufHandler::start() {
DEBUGTRACE_ENTER;
ScanStatus status;
TransferStatus transferStatus;
UlError err = ulDaqInScanStatus(daq.getHandle(), &status, &transferStatus);
throwOnPossibleUlException(err);
totalFramesCount = transferStatus.currentTotalCount;
topenqueued = true;
botenqueued = true;
}
bool InBufHandler::operator()() {
/* DEBUGTRACE_ENTER; */
bool ret = true;
auto runCallback = ([&](us totalOffset) {
/* DEBUGTRACE_ENTER; */
DaqData data(nFramesPerBlock, nchannels, dtype_descr.dtype);
us monitorOffset = monitorOutput ? 1 : 0;
/* /// Put the output monitor in front */
if (monitorOutput) {
for (us frame = 0; frame < nFramesPerBlock; frame++) {
data.value<double>(frame, 0) =
buf[totalOffset // Offset to lowest part of the buffer, or not
+ (frame * nchannels) // Data is interleaved, so skip each
+ (nchannels - 1)] // Monitor comes as last in the channel list,
// but we want it first in the output data.
;
}
}
// Now, all normal channels
for (us channel = 0; channel < nchannels - monitorOffset; channel++) {
/* DEBUGTRACE_PRINT(channel); */
for (us frame = 0; frame < nFramesPerBlock; frame++) {
data.value<double>(frame, channel + monitorOffset) =
buf[totalOffset + (frame * nchannels) + channel];
}
}
return cb(data);
});
ScanStatus status;
TransferStatus transferStatus;
UlError err = ulDaqInScanStatus(daq.getHandle(), &status, &transferStatus);
throwOnPossibleUlException(err);
us increment = transferStatus.currentTotalCount - totalFramesCount;
totalFramesCount += increment;
if (increment > nFramesPerBlock) {
throw Daq::StreamException(Daq::StreamStatus::StreamError::inputXRun);
}
assert(status == SS_RUNNING);
if (transferStatus.currentIndex < (long long)buffer_mid_idx) {
topenqueued = false;
if (!botenqueued) {
runCallback(nchannels * nFramesPerBlock);
botenqueued = true;
}
} else {
botenqueued = false;
if (!topenqueued) {
runCallback(0);
topenqueued = true;
}
}
return ret;
}
InBufHandler::~InBufHandler() {
// At exit of the function, stop scanning.
DEBUGTRACE_ENTER;
UlError err = ulDaqInScanStop(daq.getHandle());
if (err != ERR_NO_ERROR) {
showErr(err);
}
}
OutBufHandler::OutBufHandler(DT9837A &daq, OutDaqCallback cb)
: BufHandler(daq, daq.nenoutchannels()), cb(cb) {
DEBUGTRACE_MESSAGE("Starting output scan");
DEBUGTRACE_PRINT(nchannels);
AOutScanFlag outscanflags = AOUTSCAN_FF_DEFAULT;
ScanOption scanoptions = SO_CONTINUOUS;
UlError err = ulAOutScan(daq.getHandle(), 0, 0, BIP10VOLTS,
2 * nFramesPerBlock, // Watch the 2 here!
&samplerate, scanoptions, outscanflags, buf.data());
throwOnPossibleUlException(err);
}
void OutBufHandler::start() {
ScanStatus status;
TransferStatus transferStatus;
UlError err = ulAOutScanStatus(daq.getHandle(), &status, &transferStatus);
if (err != ERR_NO_ERROR) {
showErr(err);
throw rte("Unable to start output on DAQ");
}
if (status != SS_RUNNING) {
throw rte("Unable to start output on DAQ");
}
totalFramesCount = transferStatus.currentTotalCount;
topenqueued = true;
botenqueued = true;
}
bool OutBufHandler::operator()() {
DEBUGTRACE_ENTER;
bool res = true;
assert(daq.getHandle() != 0);
UlError err = ERR_NO_ERROR;
ScanStatus status;
TransferStatus transferStatus;
err = ulAOutScanStatus(daq.getHandle(), &status, &transferStatus);
throwOnPossibleUlException(err);
if (status != SS_RUNNING) {
return false;
}
us increment = transferStatus.currentTotalCount - totalFramesCount;
totalFramesCount += increment;
if (increment > nFramesPerBlock) {
cerr << "totalFramesCount: " << totalFramesCount << ". Detected output underrun" << endl;
/* throw Daq::StreamException(Daq::StreamStatus::StreamError::outputXRun); */
}
if (transferStatus.currentIndex < buffer_mid_idx) {
topenqueued = false;
if (!botenqueued) {
DaqData d(nFramesPerBlock, 1,// Only one output channel
dtype_descr.dtype);
// Receive data, run callback
cb(d);
d.copyToRaw(0, reinterpret_cast<byte_t *>(&(buf[buffer_mid_idx])));
botenqueued = true;
}
} else {
botenqueued = false;
if (!topenqueued) {
DaqData d(nFramesPerBlock, 1,// Only one output channel
dtype_descr.dtype);
// Receive
cb(d);
d.copyToRaw(0, reinterpret_cast<byte_t *>(&(buf[0])));
topenqueued = true;
}
}
return res;
}
OutBufHandler::~OutBufHandler() {
DEBUGTRACE_ENTER;
UlError err = ulAOutScanStop(daq.getHandle());
if (err != ERR_NO_ERROR) {
showErr(err);
}
}
#endif

View File

@ -1,75 +1,20 @@
#pragma once
#include "lasp_daq.h"
#include <algorithm>
#include <cassert>
#include <chrono>
#include <iostream>
#include <stdexcept>
#include <thread>
#include <uldaq.h>
#include <vector>
#include "lasp_types.h"
#include "lasp_uldaq_impl.h"
#include "lasp_uldaq_common.h"
using std::atomic;
using std::cerr;
using std::endl;
using rte = std::runtime_error;
/**
* @brief UlDaq-specific device information. Adds a copy of the underlying
* DaqDeDaqDeviceDescriptor.
/** \addtogroup device
* @{
* \addtogroup uldaq
*/
class UlDaqDeviceInfo : public DeviceInfo {
public:
DaqDeviceDescriptor _uldaqDescriptor;
virtual std::unique_ptr<DeviceInfo> clone() const {
return std::make_unique<UlDaqDeviceInfo>(*this);
}
};
class DT9837A : public Daq {
DaqDeviceHandle _handle = 0;
std::mutex _daqmutex;
std::thread _thread;
atomic<bool> _stopThread{false};
atomic<StreamStatus> _streamStatus;
const us _nFramesPerBlock;
void threadFcn(InDaqCallback inCallback, OutDaqCallback outcallback);
public:
DaqDeviceHandle getHandle() const { return _handle; }
/**
* @brief Create a DT9837A instance.
*
* @param devinfo DeviceInfo to connect to
* @param config DaqConfiguration settings
*/
DT9837A(const DeviceInfo &devinfo, const DaqConfiguration &config);
virtual ~DT9837A();
bool isRunning() const;
void stop() override final;
friend class InBufHandler;
friend class OutBufHandler;
virtual void start(InDaqCallback inCallback,
OutDaqCallback outCallback) override final;
virtual StreamStatus getStreamStatus() const override {
return _streamStatus;
}
};
/**
* @brief Helper class for managing input and output samples of the DAQ device.
*/
class DT9837A;
class BufHandler {
protected:
/**
@ -88,12 +33,19 @@ protected:
* @brief Sampling frequency in Hz
*/
double samplerate;
/**
* @brief Storage capacity for the DAQ I/O.
*/
std::vector<double> buf;
/**
* @brief Whether the top / bottom part of the buffer are ready to be
* @brief Whether the top part of the buffer is enqueued
*/
bool topenqueued = false;
/**
* @brief Whether the bottom part of the buffer is enqueued
* enqueued
*/
bool topenqueued, botenqueued;
bool botenqueued = false;
/**
* @brief Counter for the total number of frames acquired / sent since the
@ -117,6 +69,7 @@ public:
0),
buffer_mid_idx(nchannels * nFramesPerBlock) {
assert(nchannels > 0);
assert(nFramesPerBlock > 0);
}
};
/**
@ -152,3 +105,5 @@ public:
~OutBufHandler();
};
/** @} */
/** @} */

View File

@ -0,0 +1,46 @@
/* #define DEBUGTRACE_ENABLED */
#include "debugtrace.hpp"
#include "lasp_config.h"
#if LASP_HAS_ULDAQ == 1
#include "lasp_uldaq_common.h"
#include "lasp_daq.h"
string getErrMsg(UlError err) {
string errstr;
errstr.reserve(ERR_MSG_LEN);
char errmsg[ERR_MSG_LEN];
errstr = "UlDaq API Error: ";
ulGetErrMsg(err, errmsg);
errstr += errmsg;
return errstr;
}
void showErr(string errstr) {
std::cerr << "\b\n**************** UlDAQ backend error **********\n";
std::cerr << errstr << std::endl;
std::cerr << "***********************************************\n\n";
}
void showErr(UlError err) {
if (err != ERR_NO_ERROR)
showErr(getErrMsg(err));
}
void throwOnPossibleUlException(UlError err) {
if (err == ERR_NO_ERROR) {
return;
}
string errstr = getErrMsg(err);
showErr(errstr);
Daq::StreamStatus::StreamError serr;
if ((int)err == 18) {
serr = Daq::StreamStatus::StreamError::inputXRun;
} else if ((int)err == 19) {
serr = Daq::StreamStatus::StreamError::outputXRun;
} else {
serr = Daq::StreamStatus::StreamError::driverError;
}
throw Daq::StreamException(serr, errstr);
}
#endif

View File

@ -0,0 +1,66 @@
#pragma once
#include <uldaq.h>
#include <string>
#include "lasp_deviceinfo.h"
/** \addtogroup device
* @{
* \addtogroup uldaq
*/
/**
* @brief Throws an appropriate stream exception based on the UlError number.
* The mapping is based on the error numbers as given in uldaq.h. There are a
* log of errors definded here (109 in total). Except for some, we will map
* most of them to a driver error.
*
* @param e The backend error code.
*/
void throwOnPossibleUlException(UlError err);
/**
* @brief Return a string corresponding to the UlDaq API error
*
* @param err error code
*
* @return Error string
*/
string getErrMsg(UlError err);
/**
* @brief Print error message to stderr
*
* @param errstr The string to print
*/
void showErr(UlError err);
/**
* @brief Get a string representation of the error
*
* @param errstr
*/
void showErr(std::string errstr);
/**
* @brief UlDaq-specific device information. Adds a copy of the underlying
* DaqDeDaqDeviceDescriptor.
*/
class UlDaqDeviceInfo : public DeviceInfo {
public:
DaqDeviceDescriptor _uldaqDescriptor;
virtual std::unique_ptr<DeviceInfo> clone() const override {
DEBUGTRACE_ENTER;
return std::make_unique<UlDaqDeviceInfo>(*this);
}
};
/**
* @brief List of available sampling frequencies for DT9837A
*/
const std::vector<d> ULDAQ_SAMPLERATES = {8000, 10000, 11025, 16000, 20000,
22050, 24000, 32000, 44056, 44100,
47250, 48000, 50000, 50400, 51000};
/** @} */
/** @} */

View File

@ -0,0 +1,212 @@
/* #define DEBUGTRACE_ENABLED */
#include "debugtrace.hpp"
#include "lasp_config.h"
#if LASP_HAS_ULDAQ == 1
#include "lasp_daqconfig.h"
#include "lasp_uldaq.h"
#include "lasp_uldaq_bufhandler.h"
#include "lasp_uldaq_impl.h"
using namespace std::literals::chrono_literals;
DT9837A::~DT9837A() {
DEBUGTRACE_ENTER;
UlError err;
if (isRunning()) {
DEBUGTRACE_PRINT("Stop UlDAQ from destructor");
stop();
}
if (_handle) {
DEBUGTRACE_PRINT("Disconnecting and releasing DaqDevice");
/* err = ulDisconnectDaqDevice(_handle); */
/* showErr(err); */
err = ulReleaseDaqDevice(_handle);
showErr(err);
}
}
DT9837A::DT9837A(const UlDaqDeviceInfo &devinfo, const DaqConfiguration &config)
: Daq(devinfo, config),
_nFramesPerBlock(availableFramesPerBlock.at(framesPerBlockIndex)) {
const DaqDeviceDescriptor &descriptor = devinfo._uldaqDescriptor;
DEBUGTRACE_PRINT(string("Device: ") + descriptor.productName);
DEBUGTRACE_PRINT(string("Product id: ") + to_string(descriptor.productId));
DEBUGTRACE_PRINT(string("Dev string: ") + descriptor.devString);
DEBUGTRACE_PRINT(string("Unique id: ") + descriptor.uniqueId);
// get a handle to the DAQ device associated with the first descriptor
_handle = ulCreateDaqDevice(descriptor);
if (_handle == 0) {
throw rte("Unable to create a handle to the specified DAQ "
"device. Is the device currently in use? Please make sure to set "
"the DAQ configuration in duplex mode if simultaneous input and "
"output is required.");
}
UlError err = ulConnectDaqDevice(_handle);
if (err != ERR_NO_ERROR) {
ulReleaseDaqDevice(_handle);
_handle = 0;
throw rte("Unable to connect to device: " + getErrMsg(err));
}
/// Loop over input channels, set parameters
for (us ch = 0; ch < 4; ch++) {
err = ulAISetConfigDbl(_handle, AI_CFG_CHAN_SENSOR_SENSITIVITY, ch, 1.0);
showErr(err);
if (err != ERR_NO_ERROR) {
throw rte("Fatal: could normalize channel sensitivity");
}
CouplingMode cm = inchannel_config.at(ch).ACCouplingMode ? CM_AC : CM_DC;
err = ulAISetConfig(_handle, AI_CFG_CHAN_COUPLING_MODE, ch, cm);
if (err != ERR_NO_ERROR) {
showErr(err);
throw rte("Fatal: could not set AC/DC coupling mode");
}
IepeMode iepe =
inchannel_config.at(ch).IEPEEnabled ? IEPE_ENABLED : IEPE_DISABLED;
err = ulAISetConfig(_handle, AI_CFG_CHAN_IEPE_MODE, ch, iepe);
if (err != ERR_NO_ERROR) {
showErr(err);
throw rte("Fatal: could not set IEPE mode");
}
}
}
bool DT9837A::isRunning() const {
DEBUGTRACE_ENTER;
/* return _thread.joinable(); */
StreamStatus status = _streamStatus;
return status.isRunning;
}
void DT9837A::stop() {
DEBUGTRACE_ENTER;
StreamStatus status = _streamStatus;
status.isRunning = true;
_streamStatus = status;
if (!isRunning()) {
throw rte("No data acquisition running");
}
// Stop the thread and join it
_stopThread = true;
assert(_thread.joinable());
_thread.join();
_stopThread = false;
// Update stream status
status.isRunning = false;
_streamStatus = status;
}
void DT9837A::start(InDaqCallback inCallback, OutDaqCallback outCallback) {
DEBUGTRACE_ENTER;
if (isRunning()) {
throw rte("DAQ is already running");
}
if (neninchannels() > 0) {
if (!inCallback)
throw rte("DAQ requires a callback for input data");
}
if (nenoutchannels() > 0) {
if (!outCallback)
throw rte("DAQ requires a callback for output data");
}
assert(neninchannels() + nenoutchannels() > 0);
_thread = std::thread(&DT9837A::threadFcn, this, inCallback, outCallback);
}
void DT9837A::threadFcn(InDaqCallback inCallback, OutDaqCallback outCallback) {
DEBUGTRACE_ENTER;
try {
std::unique_ptr<OutBufHandler> obh;
std::unique_ptr<InBufHandler> ibh;
StreamStatus status = _streamStatus;
status.isRunning = true;
_streamStatus = status;
if (nenoutchannels() > 0) {
assert(outCallback);
obh = std::make_unique<OutBufHandler>(*this, outCallback);
}
if (neninchannels() > 0) {
assert(inCallback);
ibh = std::make_unique<InBufHandler>(*this, inCallback);
}
if (obh)
obh->start();
if (ibh)
ibh->start();
const double sleeptime_s =
static_cast<double>(_nFramesPerBlock) / (16 * samplerate());
const us sleeptime_us = static_cast<us>(sleeptime_s * 1e6);
while (!_stopThread) {
if (ibh) {
if (!(*ibh)()) {
_stopThread = true;
break;
}
}
if (obh) {
if (!(*obh)()) {
_stopThread = true;
break;
}
}
std::this_thread::sleep_for(std::chrono::microseconds(sleeptime_us));
}
/// Update stream status that we are not running anymore
status.isRunning = false;
_streamStatus = status;
_stopThread = false;
} catch (StreamException &e) {
StreamStatus status = _streamStatus;
// Copy over error type
status.errorType = e.e;
_streamStatus = status;
cerr << "\n******************\n";
cerr << "Catched error in UlDAQ thread: " << e.what() << endl;
cerr << "\n******************\n";
}
}
void DT9837A::sanityChecks() const {
// Some sanity checks
if (inchannel_config.size() != 4) {
throw rte("Invalid length of enabled inChannels vector");
}
if (outchannel_config.size() != 1) {
throw rte("Invalid length of enabled outChannels vector");
}
if (_nFramesPerBlock < 24 || _nFramesPerBlock > 8192) {
throw rte("Unsensible number of samples per block chosen");
}
if (samplerate() < ULDAQ_SAMPLERATES.at(0) ||
samplerate() > ULDAQ_SAMPLERATES.at(ULDAQ_SAMPLERATES.size() - 1)) {
throw rte("Invalid sample rate");
}
}
#endif // LASP_HAS_ULDAQ

View File

@ -0,0 +1,110 @@
#pragma once
#include "debugtrace.hpp"
#include "lasp_uldaq_common.h"
#include <algorithm>
#include <cassert>
#include <chrono>
#include <iostream>
#include <stdexcept>
#include <thread>
#include <vector>
#include "lasp_daq.h"
using std::atomic;
using std::cerr;
using std::endl;
using rte = std::runtime_error;
class InBufHandler;
class OutBufHandler;
/** \addtogroup device
* @{
* \addtogroup uldaq
*/
/**
* @brief Data translation DT9837A Daq device.
*/
class DT9837A : public Daq {
DaqDeviceHandle _handle = 0;
std::mutex _daqmutex;
/**
* @brief The thread that is doing I/O with UlDaq
*/
std::thread _thread;
/**
* @brief Flag indicating the thread to stop processing.
*/
atomic<bool> _stopThread{false};
/**
* @brief Storage for exchanging information on the stream
*/
atomic<StreamStatus> _streamStatus;
const us _nFramesPerBlock;
/**
* @brief The function that is running in a thread
*
* @param inCallback
* @param outcallback
*/
void threadFcn(InDaqCallback inCallback, OutDaqCallback outcallback);
/**
* @brief Obtain a handle to the underlying device
*
* @return Handle
*/
DaqDeviceHandle getHandle() const { return _handle; }
/**
* @brief Perform several sanity checks
*/
void sanityChecks() const;
public:
/**
* @brief Create a DT9837A instance.
*
* @param devinfo DeviceInfo to connect to
* @param config DaqConfiguration settings
*/
DT9837A(const UlDaqDeviceInfo &devinfo, const DaqConfiguration &config);
virtual ~DT9837A();
/**
* @brief Returns true when the stream is running
*
* @return as above stated
*/
bool isRunning() const;
/**
* @brief Stop the data-acquisition
*/
void stop() override final;
friend class InBufHandler;
friend class OutBufHandler;
virtual void start(InDaqCallback inCallback,
OutDaqCallback outCallback) override final;
/**
* @brief Obtain copy of stream status (thread-safe function)
*
* @return StreamStatus object
*/
virtual StreamStatus getStreamStatus() const override {
return _streamStatus;
}
};
/** @} */
/** @} */

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;
@ -44,19 +43,21 @@ SeriesBiquad::SeriesBiquad(const vd &filter_coefs) {
SeriesBiquad SeriesBiquad::firstOrderHighPass(const d fs, const d cuton_Hz) {
if(fs <= 0) {
if (fs <= 0) {
throw rte("Invalid sampling frequency: " + std::to_string(fs) + " [Hz]");
}
if(cuton_Hz <= 0) {
if (cuton_Hz <= 0) {
throw rte("Invalid cuton frequency: " + std::to_string(cuton_Hz) + " [Hz]");
}
if(cuton_Hz >= 0.98*fs/2) {
throw rte("Invalid cuton frequency. We limit this to 0.98* fs / 2. Given value" + std::to_string(cuton_Hz) + " [Hz]");
if (cuton_Hz >= 0.98 * fs / 2) {
throw rte(
"Invalid cuton frequency. We limit this to 0.98* fs / 2. Given value" +
std::to_string(cuton_Hz) + " [Hz]");
}
const d tau = 1/(2*arma::datum::pi*cuton_Hz);
const d facnum = 2*fs*tau/(1+2*fs*tau);
const d facden = (1-2*fs*tau)/(1+2*fs*tau);
const d tau = 1 / (2 * arma::datum::pi * cuton_Hz);
const d facnum = 2 * fs * tau / (1 + 2 * fs * tau);
const d facden = (1 - 2 * fs * tau) / (1 + 2 * fs * tau);
vd coefs(6);
// b0
@ -76,10 +77,8 @@ SeriesBiquad SeriesBiquad::firstOrderHighPass(const d fs, const d cuton_Hz) {
coefs(5) = 0;
return SeriesBiquad(coefs);
}
std::unique_ptr<Filter> SeriesBiquad::clone() const {
// sos.as_col() concatenates all columns, exactly what we want.
return std::make_unique<SeriesBiquad>(sos.as_col());
@ -124,7 +123,6 @@ BiquadBank::BiquadBank(const dmat &filters, const vd *gains) {
* for use.
*/
lock lck(_mtx);
getPool();
for (us i = 0; i < filters.n_cols; i++) {
_filters.emplace_back(filters.col(i));
@ -153,16 +151,15 @@ void BiquadBank::filter(vd &inout) {
std::vector<std::future<vd>> futs;
#if 1
auto &pool = getPool();
vd inout_cpy = inout;
for (us i = 0; i < _filters.size(); i++) {
futs.emplace_back(pool.submit(
[&](vd inout, us i) {
futs.emplace_back(_pool.submit(
[&](vd inout, us i) {
_filters[i].filter(inout);
return inout;
}, // Launch a task to filter.
inout_cpy, i // Column i as argument to the lambda function above.
));
}, // Launch a task to filter.
inout_cpy, i // Column i as argument to the lambda function above.
));
}
// Zero-out in-out and sum-up the filtered values

View File

@ -1,5 +1,6 @@
#pragma once
#include "lasp_filter.h"
#include "lasp_thread.h"
/**
* \addtogroup dsp
@ -60,6 +61,7 @@ public:
class BiquadBank : public Filter {
std::vector<SeriesBiquad> _filters;
vd _gains;
GlobalThreadPool _pool;
mutable std::mutex _mtx;
public:

View File

@ -1,6 +1,8 @@
/* #define DEBUGTRACE_ENABLED */
#include "debugtrace.hpp"
#include "lasp_clip.h"
#include "lasp_daqdata.h"
#include "lasp_daq.h"
#include <mutex>
using std::cerr;
@ -9,13 +11,14 @@ using std::endl;
using Lck = std::scoped_lock<std::mutex>;
using rte = std::runtime_error;
ClipHandler::ClipHandler(StreamMgr &mgr)
ClipHandler::ClipHandler(SmgrHandle mgr)
: ThreadedInDataHandler(mgr){
DEBUGTRACE_ENTER;
startThread();
}
bool ClipHandler::inCallback_threaded(const DaqData &d) {
void ClipHandler::inCallback(const DaqData &d) {
DEBUGTRACE_ENTER;
Lck lck(_mtx);
@ -49,7 +52,6 @@ bool ClipHandler::inCallback_threaded(const DaqData &d) {
_clip_time(i) += _dt;
}
}
return true;
}
arma::uvec ClipHandler::getCurrentValue() const {
@ -89,6 +91,5 @@ void ClipHandler::reset(const Daq *daq) {
ClipHandler::~ClipHandler() {
DEBUGTRACE_ENTER;
Lck lck(_mtx);
stop();
stopThread();
}

View File

@ -21,7 +21,7 @@
/**
* @brief Clipping detector (Clip). Detects when a signal overdrives the input
* */
class ClipHandler: public ThreadedInDataHandler {
class ClipHandler: public ThreadedInDataHandler<ClipHandler> {
/**
* @brief Assuming full scale of a signal is +/- 1.0. If a value is found
@ -58,7 +58,7 @@ class ClipHandler: public ThreadedInDataHandler {
*
* @param mgr Stream Mgr to operate on
*/
ClipHandler(StreamMgr& mgr);
ClipHandler(SmgrHandle mgr);
~ClipHandler();
/**
@ -68,8 +68,8 @@ class ClipHandler: public ThreadedInDataHandler {
*/
arma::uvec getCurrentValue() const;
bool inCallback_threaded(const DaqData& ) override final;
void reset(const Daq*) override final;
void inCallback(const DaqData& );
void reset(const Daq*);
};

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

@ -1,6 +1,8 @@
/* #define DEBUGTRACE_ENABLED */
#include "debugtrace.hpp"
#include "lasp_ppm.h"
#include "lasp_daqdata.h"
#include "lasp_daq.h"
#include <mutex>
using std::cerr;
@ -9,20 +11,20 @@ using std::endl;
using Lck = std::scoped_lock<std::mutex>;
using rte = std::runtime_error;
PPMHandler::PPMHandler(StreamMgr &mgr, const d decay_dBps)
: ThreadedInDataHandler(mgr), _decay_dBps(decay_dBps) {
PPMHandler::PPMHandler(SmgrHandle mgr, const d decay_dBps)
: ThreadedInDataHandler<PPMHandler>(mgr), _decay_dBps(decay_dBps) {
DEBUGTRACE_ENTER;
startThread();
}
bool PPMHandler::inCallback_threaded(const DaqData &d) {
void PPMHandler::inCallback(const DaqData &d) {
DEBUGTRACE_ENTER;
Lck lck(_mtx);
dmat data = d.toFloat();
const us nchannels = d.nchannels;
assert(data.n_cols == nchannels);
@ -62,12 +64,11 @@ bool PPMHandler::inCallback_threaded(const DaqData &d) {
_cur_max(i) *= _alpha;
}
}
return true;
}
std::tuple<vd, arma::uvec> PPMHandler::getCurrentValue() const {
DEBUGTRACE_ENTER;
/* DEBUGTRACE_ENTER; */
Lck lck(_mtx);
arma::uvec clips(_clip_time.size(), arma::fill::zeros);
@ -83,9 +84,11 @@ void PPMHandler::reset(const Daq *daq) {
if (daq) {
DEBUGTRACE_PRINT("New daq found");
_cur_max.fill(1e-80);
const us nchannels = daq->neninchannels();
DEBUGTRACE_PRINT(nchannels);
_max_range.resize(nchannels);
dvec ranges = daq->inputRangeForEnabledChannels();
@ -107,6 +110,5 @@ void PPMHandler::reset(const Daq *daq) {
PPMHandler::~PPMHandler() {
DEBUGTRACE_ENTER;
Lck lck(_mtx);
stop();
stopThread();
}

View File

@ -4,7 +4,6 @@
//
// Description: Peak Programme Meter
#pragma once
#include <memory>
#include "lasp_filter.h"
#include "lasp_mathtypes.h"
#include "lasp_threadedindatahandler.h"
@ -23,7 +22,7 @@
* with a certain amount of dB/s. If a new peak is found, it goes up again.
* Also detects clipping.
* */
class PPMHandler: public ThreadedInDataHandler {
class PPMHandler : public ThreadedInDataHandler<PPMHandler> {
/**
* @brief Assuming full scale of a signal is +/- 1.0. If a value is found
@ -69,11 +68,11 @@ class PPMHandler: public ThreadedInDataHandler {
/**
* @brief Constructs Peak Programme Meter
*
* @param mgr Stream Mgr to operate on
* @param mgr Stream Mgr to install callbacks for
* @param decay_dBps The level decay in units dB/s, after a peak has been
* hit.
*/
PPMHandler(StreamMgr& mgr,const d decay_dBps = 20.0);
PPMHandler(SmgrHandle mgr,const d decay_dBps = 20.0);
~PPMHandler();
/**
@ -91,8 +90,8 @@ class PPMHandler: public ThreadedInDataHandler {
*
* @return true when stream should continue.
*/
bool inCallback_threaded(const DaqData& d) override final;
void reset(const Daq*) override final;
void inCallback(const DaqData& d);
void reset(const Daq*);
};

View File

@ -1,5 +1,7 @@
/* #define DEBUGTRACE_ENABLED */
#include "lasp_rtaps.h"
#include "lasp_daqdata.h"
#include "lasp_daq.h"
#include "debugtrace.hpp"
#include <mutex>
@ -7,7 +9,7 @@ using std::cerr;
using std::endl;
using Lck = std::scoped_lock<std::mutex>;
RtAps::RtAps(StreamMgr &mgr, const Filter *freqWeightingFilter,
RtAps::RtAps(SmgrHandle mgr, const Filter *freqWeightingFilter,
const us nfft,
const Window::WindowType w,
const d overlap_percentage, const d time_constant)
@ -18,12 +20,12 @@ RtAps::RtAps(StreamMgr &mgr, const Filter *freqWeightingFilter,
_filterPrototype = freqWeightingFilter->clone();
}
startThread();
}
RtAps::~RtAps() {
Lck lck(_ps_mtx);
stop();
stopThread();
}
bool RtAps::inCallback_threaded(const DaqData &data) {
void RtAps::inCallback(const DaqData &data) {
DEBUGTRACE_ENTER;
@ -33,9 +35,9 @@ bool RtAps::inCallback_threaded(const DaqData &data) {
const us nchannels = fltdata.n_cols;
if(nchannels != _sens.size()) {
cerr << "**** Error: sensitivity size does not match! *****" << endl;
return false;
return;
}
fltdata.each_row() %= _sens.as_row();
fltdata.each_row() /= _sens.as_row();
if (_filterPrototype) {
@ -61,7 +63,6 @@ bool RtAps::inCallback_threaded(const DaqData &data) {
_ps.compute(fltdata);
return true;
}
void RtAps::reset(const Daq *daq) {

View File

@ -23,7 +23,7 @@
* @brief Real time spectral estimator using Welch method of spectral
* estimation.
*/
class RtAps : public ThreadedInDataHandler {
class RtAps : public ThreadedInDataHandler<RtAps> {
std::unique_ptr<Filter> _filterPrototype;
std::vector<std::unique_ptr<Filter>> _freqWeightingFilters;
@ -49,7 +49,7 @@ public:
*
* For all other arguments, see constructor of AvPowerSpectra
*/
RtAps(StreamMgr &mgr, const Filter *freqWeightingFilter, const us nfft = 2048,
RtAps(SmgrHandle mgr, const Filter *freqWeightingFilter, const us nfft = 2048,
const Window::WindowType w = Window::WindowType::Hann,
const d overlap_percentage = 50., const d time_constant = -1);
~RtAps();
@ -69,8 +69,8 @@ public:
*
* @return true if stream should continue.
*/
bool inCallback_threaded(const DaqData & d) override final;
void reset(const Daq *) override final;
void inCallback(const DaqData & d);
void reset(const Daq *);
};
/** @} */

View File

@ -1,5 +1,7 @@
/* #define DEBUGTRACE_ENABLED */
#include "debugtrace.hpp"
#include "lasp_daqdata.h"
#include "lasp_daq.h"
#include "lasp_rtsignalviewer.h"
#include <algorithm>
#include <mutex>
@ -9,7 +11,7 @@ using std::endl;
using Lck = std::scoped_lock<std::mutex>;
using rte = std::runtime_error;
RtSignalViewer::RtSignalViewer(StreamMgr &mgr, const d approx_time_hist,
RtSignalViewer::RtSignalViewer(SmgrHandle mgr, const d approx_time_hist,
const us resolution, const us channel)
: ThreadedInDataHandler(mgr), _approx_time_hist(approx_time_hist),
_resolution(resolution), _channel(channel) {
@ -22,9 +24,10 @@ RtSignalViewer::RtSignalViewer(StreamMgr &mgr, const d approx_time_hist,
if (resolution <= 1) {
throw rte("Invalid resolution. Should be > 1");
}
startThread();
}
bool RtSignalViewer::inCallback_threaded(const DaqData &data) {
void RtSignalViewer::inCallback(const DaqData &data) {
DEBUGTRACE_ENTER;
@ -49,13 +52,10 @@ bool RtSignalViewer::inCallback_threaded(const DaqData &data) {
_dat(_resolution-1, 1) = newmin;
_dat(_resolution-1, 2) = newmax;
}
return true;
}
RtSignalViewer::~RtSignalViewer() {
Lck lck(_sv_mtx);
stop();
stopThread();
}
void RtSignalViewer::reset(const Daq *daq) {

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>
/**
@ -24,7 +21,7 @@
* @brief Real time signal viewer. Shows envelope of the signal based on amount
* of history shown.
*/
class RtSignalViewer : public ThreadedInDataHandler {
class RtSignalViewer : public ThreadedInDataHandler<RtSignalViewer> {
/**
* @brief Storage for sensitivity values
@ -71,7 +68,7 @@ public:
* @param resolution Number of time points
* @param channel The channel number
*/
RtSignalViewer(StreamMgr &mgr, const d approx_time_hist, const us resolution,
RtSignalViewer(SmgrHandle mgr, const d approx_time_hist, const us resolution,
const us channel);
~RtSignalViewer();
@ -85,8 +82,8 @@ public:
*/
dmat getCurrentValue() const;
bool inCallback_threaded(const DaqData &) override final;
void reset(const Daq *) override final;
void inCallback(const DaqData &);
void reset(const Daq *);
};
/** @} */

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

@ -37,9 +37,6 @@ SLM::SLM(const d fs, const d Lref, const us downsampling_fac, const d tau,
DEBUGTRACE_ENTER;
DEBUGTRACE_PRINT(_alpha);
// Make sure thread pool is running
getPool();
if (Lref <= 0) {
throw rte("Invalid reference level");
}

View File

@ -1,6 +1,7 @@
#pragma once
#include "lasp_biquadbank.h"
#include "lasp_filter.h"
#include "lasp_thread.h"
#include <memory>
#include <optional>
@ -14,6 +15,7 @@
* channel. A channel is the result of a filtered signal
*/
class SLM {
GlobalThreadPool _pool;
/**
* @brief A, C or Z weighting, depending on the pre-filter installed.
*/

View File

@ -0,0 +1,36 @@
/* #define DEBUGTRACE_ENABLED */
#include "lasp_thread.h"
#include "BS_thread_pool.hpp"
#include "debugtrace.hpp"
#include <memory>
/**
* @brief Store a global weak_ptr, that is used to create new shared pointers
* if any other shared pointers are still alive. If not, we create a new
* instance.
*/
std::weak_ptr<BS::thread_pool> _global_weak_pool;
/**
* @brief Global mutex, used to restrict the pool creation to a single thread
* at once.
*/
std::mutex _mtx;
using Lck = std::scoped_lock<std::mutex>;
using rte = std::runtime_error;
GlobalThreadPool::GlobalThreadPool() {
DEBUGTRACE_ENTER;
Lck lck(_mtx);
/// See if we can get it from the global ptr. If not, time to allocate it.
_pool = _global_weak_pool.lock();
if (!_pool) {
_pool = std::make_shared<BS::thread_pool>();
if (!_pool) {
throw rte("Fatal: could not allocate thread pool!");
}
// Update global weak pointer
_global_weak_pool = _pool;
}
}

41
cpp_src/dsp/lasp_thread.h Normal file
View File

@ -0,0 +1,41 @@
#pragma once
#include "BS_thread_pool.hpp"
/**
* @brief Simple wrapper around BS::thread_pool that makes a BS::thread_pool a
* singleton, such that a thread pool can be used around in the code, and
* safely spawn threads also from other threads. Only wraps a submit() and
* push_task for now.
*/
class GlobalThreadPool {
/**
* @brief Shared access to the thread pool.
*/
std::shared_ptr<BS::thread_pool> _pool;
public:
/**
* @brief Instantiate handle to the thread pool.
*/
GlobalThreadPool();
GlobalThreadPool(const GlobalThreadPool &) = default;
GlobalThreadPool &operator=(const GlobalThreadPool &) = default;
/**
* @brief Wrapper around BS::thread_pool::submit(...)
*/
template <
typename F, typename... A,
typename R = std::invoke_result_t<std::decay_t<F>, std::decay_t<A>...>>
[[nodiscard]] std::future<R> submit(F &&task, A &&...args) {
return _pool->submit(task, args...);
}
/**
* @brief Wrapper around BS::thread_pool::push_task(...)
*/
template <typename F, typename... A> void push_task(F &&task, A &&...args) {
_pool->push_task(task, args...);
}
};

View File

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

View File

@ -0,0 +1,123 @@
#pragma once
#include "debugtrace.hpp"
#include "lasp_indatahandler.h"
#include "lasp_thread.h"
#include <atomic>
#include <memory>
#include <mutex>
using std::placeholders::_1;
const us RINGBUFFER_SIZE = 1024;
/**
* \addtogroup dsp
* @{
*
* \defgroup rt Real time signal handlers
* @{
*/
class SafeQueue;
/**
* @brief Threaded in data handler base. Buffers inCallback data and calls a
* callback with the same signature on a different thread. The main function of
* this is to offload the thread that handles the stream, such that expensive
* computations do not result in stream buffer xruns.
*/
class ThreadedInDataHandlerBase {
/**
* @brief The queue used to push elements to the handling thread.
*/
std::unique_ptr<SafeQueue> _queue;
/**
* @brief Function pointer that is called when new DaqData arrives.
*/
const InCallbackType inCallback;
/**
* @brief Function pointer that is called when reset() is called.
*/
const ResetCallbackType resetCallback;
std::weak_ptr<StreamMgr> _smgr;
std::unique_ptr<InDataHandler> _indatahandler;
std::atomic<bool> _thread_running{false};
std::atomic<bool> _thread_allowed_to_run{false};
GlobalThreadPool _pool;
void threadFcn();
/**
* @brief Pushes a copy of the daqdata to the thread queue and returns.
* Adds a thread to handle the queue, whihc will call inCallback();
*
* @param daqdata the daq info to push
*
* @return true, to continue with sampling.
*/
void _inCallbackFromInDataHandler(const DaqData &daqdata);
public:
ThreadedInDataHandlerBase(SmgrHandle mgr, InCallbackType cb, ResetCallbackType reset);
~ThreadedInDataHandlerBase();
/**
* @brief This method should be called from the derived class' constructor,
* to start the thread and data is incoming.
*/
void startThread();
/**
* @brief This method SHOULD be called from all classes that derive on
* ThreadedInDataHandler. It is to make sure the inCallback_threaded()
* function is no longer called when the destructor of the derived class is
* called. Not calling this function is regarded as a BUG.
*/
void stopThread();
};
/**
* @brief A bit of curiously recurring template pattern, to connect the
* specific handlers and connect the proper callbacks in a type-agnostic way.
* Using this class, each threaded handler should just implement its reset()
* and inCallback() method. Ellides the virtual method calls.
*
* Usage: class XHandler: public ThreadedInDataHandler<XHandler> {
* public:
* XHandler(streammgr) : ThreadedInDataHandler(streammgr) {}
* void inCallback(const DaqData& d) { ... do something with d }
* void reset(const Daq* daq) { ... do something with daq }
* };
*
* For examples, see PPMHandler, etc.
*
* @tparam Derived The
*/
template <typename Derived>
class ThreadedInDataHandler : public ThreadedInDataHandlerBase {
public:
ThreadedInDataHandler(SmgrHandle mgr):
ThreadedInDataHandlerBase(mgr,
std::bind(&ThreadedInDataHandler::_inCallback, this, _1),
std::bind(&ThreadedInDataHandler::_reset, this, _1))
{
}
void _reset(const Daq* daq) {
DEBUGTRACE_ENTER;
return static_cast<Derived*>(this)->reset(daq);
}
void _inCallback(const DaqData& data) {
DEBUGTRACE_ENTER;
return static_cast<Derived*>(this)->inCallback(data);
}
};
/** @} */
/** @} */

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
@ -31,6 +28,7 @@ const int LASP_VERSION_MINOR = @CMAKE_PROJECT_VERSION_MINOR@;
#cmakedefine LASP_FFT_BACKEND @LASP_FFT_BACKEND@
#cmakedefine01 LASP_HAS_RTAUDIO
#cmakedefine01 LASP_HAS_PORTAUDIO
#cmakedefine01 LASP_HAS_ULDAQ
#cmakedefine01 LASP_DOUBLE_PRECISION
#cmakedefine01 LASP_USE_BLAS

View File

@ -52,12 +52,5 @@ PYBIND11_MODULE(lasp_cpp, 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

@ -1,5 +1,6 @@
#include <pybind11/stl.h>
#include <stdint.h>
#include <iostream>
#include "lasp_deviceinfo.h"
using std::cerr;
@ -28,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);
@ -35,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);

View File

@ -1,4 +1,5 @@
#include "lasp_streammgr.h"
#include "lasp_indatahandler.h"
#include <pybind11/numpy.h>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
@ -12,7 +13,7 @@ void init_streammgr(py::module &m) {
/// The stream manager is a singleton, and the lifetime is managed elsewhere.
// It should not be deleted.
py::class_<StreamMgr, std::unique_ptr<StreamMgr, py::nodelete>> smgr(
py::class_<StreamMgr, std::shared_ptr<StreamMgr>> smgr(
m, "StreamMgr");
py::enum_<StreamMgr::StreamType>(smgr, "StreamType")
@ -22,7 +23,7 @@ void init_streammgr(py::module &m) {
smgr.def("startStream", &StreamMgr::startStream);
smgr.def("stopStream", &StreamMgr::stopStream);
smgr.def_static("getInstance", []() {
return std::unique_ptr<StreamMgr, py::nodelete>(&StreamMgr::getInstance());
return StreamMgr::getInstance();
});
smgr.def("stopAllStreams", &StreamMgr::stopAllStreams);

49
examples/example_input.py Executable file
View File

@ -0,0 +1,49 @@
#!/usr/bin/env python3
import lasp
# Get handle to stream manager
mgr = lasp.StreamMgr.getInstance()
import time
time.sleep(1)
ds = mgr.getDeviceInfo()
# Search for a device
for i, d in enumerate(ds):
print(f'{i}: ' + d.device_name)
d = ds[0] # Create a configuration and enable some input channels
config = lasp.DaqConfiguration(d)
config.inchannel_config[0].enabled = True
config.inchannel_config[1].enabled = True
# Choose a different number of frames per block
config.framesPerBlockIndex = 2
# Start a stream with a configuration
mgr.startStream(config)
def reset_cb(daq):
print('Reset called')
def cb(data):
# Print something on callback
print(data.shape)
return True
# Attach the indata handler to the stream
#i = lasp.InDataHandler(mgr, cb, reset_cb)
ppm = lasp.PPMHandler(mgr)
#del ppm
del mgr
#del i
try:
while True:
val, clip = ppm.getCurrentValue()
print(val)
time.sleep(0.1)
#print(f'{val[0]} {val[1]}', end='')
except KeyboardInterrupt:
pass
# mgr.stopStream(lasp.StreamMgr.StreamType.input)

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.0"
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",
"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

@ -23,11 +23,12 @@ y[n] = 1/ba[3] * ( ba[0] * x[n] + ba[1] * x[n-1] + ba[2] * x[n-2] +
"""
__all__ = ['peaking', 'biquadTF', 'notch', 'lowpass', 'highpass',
'highshelf', 'lowshelf']
'highshelf', 'lowshelf', 'LP1compensator', 'LP2compensator',
'HP1compensator', 'HP2compensator']
from numpy import array, cos, pi, sin, sqrt
from scipy.interpolate import interp1d
from scipy.signal import sosfreqz
from scipy.signal import sosfreqz, bilinear_zpk, zpk2sos
def peaking(fs, f0, Q, gain):
@ -157,6 +158,129 @@ def lowshelf(fs, f0, Q, gain):
a2 = (A+1) + (A-1)*cos(w0) - 2*sqrt(A)*alpha
return array([b0/a0, b1/a0, b2/a0, a0/a0, a1/a0, a2/a0])
def LP1compensator(fs, f0o, f0n):
"""
Shelving type filter that, when multiplied with a first-order low-pass
filter, alters the response of that filter to a different first-order
low-pass filter.
Args:
fs: Sampling frequency [Hz]
f0o: Cut-off frequency of the original filter [Hz]
f0n: Desired cut-off frequency [Hz]
"""
omg0o = 2*pi*f0o
omg0n = 2*pi*f0n
z = -omg0o
p = -omg0n
k = p/z
zd, pd, kd = bilinear_zpk(z, p, k, fs)
sos = zpk2sos(zd,pd,kd)
return sos[0]
def LP2compensator(fs, f0o, Qo, f0n, Qn):
"""
Shelving type filter that, when multiplied with a second-order low-pass
filter, alters the response of that filter to a different second-order
low-pass filter.
Args:
fs: Sampling frequency [Hz]
f0o: Cut-off frequency of the original filter [Hz]
Qo: Quality factor of the original filter (~inverse of bandwidth)
f0n: Desired cut-off frequency [Hz]
Qn: Desired quality factor(~inverse of bandwidth)
"""
omg0o = 2*pi*f0o
omg0n = 2*pi*f0n
zRe = omg0o/(2*Qo)
zIm = omg0o*sqrt(1-1/(4*Qo**2))
z1 = -zRe + zIm*1j
z2 = -zRe - zIm*1j
pRe = omg0n/(2*Qn)
pIm = omg0n*sqrt(1-1/(4*Qn**2))
p1 = -pRe + pIm*1j
p2 = -pRe - pIm*1j
z= [z1, z2]
p = [p1, p2]
k = (pRe**2 + pIm**2)/(zRe**2 + zIm**2)
zd, pd, kd = bilinear_zpk(z, p, k, fs)
sos = zpk2sos(zd,pd,kd)
return sos[0]
def HP1compensator(fs, f0o, f0n):
"""
Shelving type filter that, when multiplied with a first-order high-pass
filter, alters the response of that filter to a different first-order
high-pass filter.
Args:
fs: Sampling frequency [Hz]
f0o: Cut-on frequency of the original filter [Hz]
f0n: Desired cut-on frequency [Hz]
"""
omg0o = 2*pi*f0o
omg0n = 2*pi*f0n
z = -omg0o
p = -omg0n
k = 1
zd, pd, kd = bilinear_zpk(z, p, k, fs)
sos = zpk2sos(zd,pd,kd)
return sos[0]
def HP2compensator(fs, f0o, Qo, f0n, Qn):
"""
Shelving type filter that, when multiplied with a second-order high-pass
filter, alters the response of that filter to a different second-order
high-pass filter.
Args:
fs: Sampling frequency [Hz]
f0o: Cut-on frequency of the original filter [Hz]
Qo: Quality factor of the original filter (~inverse of bandwidth)
f0n: Desired cut-on frequency [Hz]
Qn: Desired quality factor(~inverse of bandwidth)
"""
omg0o = 2*pi*f0o
omg0n = 2*pi*f0n
zRe = omg0o/(2*Qo)
zIm = omg0o*sqrt(1-1/(4*Qo**2))
z1 = -zRe + zIm*1j
z2 = -zRe - zIm*1j
pRe = omg0n/(2*Qn)
pIm = omg0n*sqrt(1-1/(4*Qn**2))
p1 = -pRe + pIm*1j
p2 = -pRe - pIm*1j
z= [z1, z2]
p = [p1, p2]
k = 1
zd, pd, kd = bilinear_zpk(z, p, k, fs)
sos = zpk2sos(zd,pd,kd)
return sos[0]
def biquadTF(fs, freq, sos):
"""

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

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

@ -58,9 +58,14 @@ class AvType(Enum):
@dataclass_json
@dataclass(eq=False)
class Qty:
# Name, i.e.: Acoustic Pressure
name: str
# I.e.: Pascal
unit_name: str
# 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.
# For now, it is only able to compute for the first one. I.e.e we are not
@ -88,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

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

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