Textual improvements. Formatting (Neoformat) improvements. Let DataTypeDescriptor be a reference (to a global const std map in Daq class. Improved naming of certain functions. Better DaqData implementation, now we make sure memory alignment is good at all times. Switched functions arguments in DaqData constructor to comply with all other cases of first frame, then channel. Better naming of stream in RtAudio. Better handling of faulty function calling in RtAudio start(). Bugfix in RtAudio, did call right Daq::dtypeDescr() function --> result was that only first channel was copied to all channels. Added extra check in StreamMgr. Removed unnecessary TypedDaqData class. Use a safe queue in threaded in data handler. We can now remove the Boost code for that.

This commit is contained in:
Anne de Jong 2022-10-10 19:17:38 +02:00
parent e7f80ce741
commit fae906884e
23 changed files with 590 additions and 296 deletions

View File

@ -45,9 +45,14 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON)
if(${CMAKE_BUILD_TYPE} STREQUAL "Debug") if(${CMAKE_BUILD_TYPE} STREQUAL "Debug")
set(LASP_DEBUG True) set(LASP_DEBUG True)
# This does not work nicely with RtAudio. However, if we compile it # This does not work nicely with RtAudio if it is precompiled (undefined
# symbols). However, if we compile it
# ourselves as a third_party ref, this works nicely together. # ourselves as a third_party ref, this works nicely together.
add_definitions(-D_GLIBCXX_DEBUG)
# This flag gives "floating point exception: core dumped"
# add_definitions(-D_GLIBCXX_DEBUG)
# add_definitions(ARMA_EXTRA_DEBUG)
else() else()
set(LASP_DEBUG False) set(LASP_DEBUG False)

View File

@ -1,13 +1,11 @@
# Library for Acoustic Signal Processing # Library for Acoustic Signal Processing
Welcome to LASP: Library for Acoustic Signal Processing. LASP is a C library Welcome to LASP: Library for Acoustic Signal Processing. LASP is a C++ library
with a Python interface which is supposed to process (multi-) microphone with a Python interface which is supposed to process (multi-) microphone
acoustic data in real time on a PC and output results. acoustic data in real time on a PC and output results.
The main goal of this library will be the processing of data from an At the point in time of this writing, we are yet unsure whether the Raspberry
array of microphones real time, on a Raspberry PI. At the point in PI will have enough computational power to this end, but may be by the time it
time of this writing, we are yet unsure whether the Raspberry PI will
have enough computational power to this end, but may be by the time it
is finished, we have a new faster generation :). is finished, we have a new faster generation :).
Current features that are implemented: Current features that are implemented:
@ -41,7 +39,7 @@ If you have any question(s), please feel free to contact us: info@ascee.nl.
Two commands that install all requirements (for Ubuntu / Linux Mint) Two commands that install all requirements (for Ubuntu / Linux Mint)
- `pip install scipy numpy build scikit-build appdirs` - `pip install scipy numpy build scikit-build appdirs`
- `sudo apt install libusb-dev - `sudo apt install libusb-dev libpulse-dev libboost-dev`
## Runtime dependencies (Linux) ## Runtime dependencies (Linux)
@ -52,14 +50,12 @@ Two commands that install all requirements (for Ubuntu / Linux Mint)
- RtAudio, for Audio DAQ backends - RtAudio, for Audio DAQ backends
- libusb - libusb
- BLAS (OpenBLAS, other). - BLAS (OpenBLAS, other).
- RtAudio (optional)
- UlDaq (optional)
## Editable install ## Editable install
In the root directory of the repository, run: In the root directory of the repository, run:
- `pip3 isntall --user --prefix=~/.local -e .` - `pip3 isntall --user -e .`
- `cmake .` - `cmake .`
- `make -j` - `make -j`
@ -72,7 +68,6 @@ Optional dependencies, which can be turned ON/OFF using CMake:
- Build tools: compiler [http://cmake.org](CMake), the Python packages: - Build tools: compiler [http://cmake.org](CMake), the Python packages:
- Scipy - Scipy
- Numpy - Numpy
- py-build-cmake
- appdirs - appdirs
These can all be installed using: These can all be installed using:
@ -103,7 +98,11 @@ compilation:
- cython - cython
- python3-numpy - python3-numpy
- libopenblas - libopenblas
If building RtAudio with the ALSA backend:
- libclalsadrv-dev - libclalsadrv-dev
- libopenblas-base - libopenblas-base
- libopenblas-dev - libopenblas-dev
- libusb-1.0-dev

View File

@ -6,6 +6,7 @@ if(LASP_HAS_RTAUDIO)
else() else()
set(RTAUDIO_API_PULSE TRUE CACHE BOOL "Build with PulseAudio backend" FORCE) 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_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)
endif() endif()
set(RTAUDIO_BUILD_STATIC_LIBS ON CACHE BOOL "Build static libs for RtAudio" FORCE) set(RTAUDIO_BUILD_STATIC_LIBS ON CACHE BOOL "Build static libs for RtAudio" FORCE)
add_subdirectory(third_party/rtaudio) add_subdirectory(third_party/rtaudio)

View File

@ -4,7 +4,6 @@ readme = "README.md"
requires-python = ">=3.8" requires-python = ">=3.8"
license = { "file" = "LICENSE" } license = { "file" = "LICENSE" }
authors = [{ "name" = "J.A. de Jong et al.", "email" = "info@ascee.nl" }] authors = [{ "name" = "J.A. de Jong et al.", "email" = "info@ascee.nl" }]
keywords = ["DSP", "DAQ", "Signal processing"]
classifiers = [ classifiers = [
"Topic :: Scientific/Engineering", "Topic :: Scientific/Engineering",
@ -14,15 +13,5 @@ classifiers = [
] ]
# urls = { "Documentation" = "https://" } # urls = { "Documentation" = "https://" }
dependencies = ["numpy", "scipy", "appdirs", "h5py", "appdirs", "dataclasses_json", ]
dynamic = ["version", "description"] dynamic = ["version", "description"]
[build-system] # How pip and other frontends should build this project
requires = [
"setuptools>=42",
"wheel",
"scikit-build",
"cmake",
]
build-backend = "setuptools.build_meta"

View File

@ -1,15 +1,51 @@
from skbuild import setup import glob
import platform
from setuptools import setup
if 'Linux' in platform.platform():
extension = list(glob.glob('src/lasp/lasp_cpp.cpython*'))
if len(extension) == 0:
raise RuntimeError('Please first run CMake to build extension')
elif len(extension) > 1:
raise RuntimeError('Too many extension files found')
pkgdata = extension
else:
raise RuntimeError('Not yet Windows-proof')
classifiers = [
"Topic :: Scientific/Engineering",
"Programming Language :: Python :: 3.8",
"Operating System :: POSIX :: Linux",
"Operating System :: Microsoft :: Windows",
]
keywords = ["DSP", "DAQ", "Signal processing"]
with open('README.md', 'r') as f:
readme = f.read()
setup( setup(
name="lasp", name="lasp",
version="1.0", version="1.0",
description="LASP Library of Acoustic Signal Processing", description="LASP: Library for Acoustic Signal Processing",
author='J.A. de Jong (ASCEE / Redu-Sone)', author='J.A. de Jong (ASCEE / Redu-Sone)',
author_email='info@ascee.nl', author_email='info@ascee.nl',
url='https://www.ascee.nl/lasp',
classifiers=classifiers,
keywords=keywords,
license="MIT", license="MIT",
packages=['lasp'], readme=readme,
package_dir= {'': 'src'}, dependencies=["numpy", "scipy", "appdirs", "h5py", "appdirs",
cmake_install_dir='src/lasp', "dataclasses_json"],
# cmake_install_target='src', packages=['lasp', 'lasp.filter', 'lasp.tools'],
data_files = pkgdata,
include_package_data=True,
package_dir={'': 'src'},
python_requires='>=3.8', python_requires='>=3.8',
) )

View File

@ -14,7 +14,7 @@ using std::runtime_error;
Daq::~Daq() { DEBUGTRACE_ENTER; } Daq::~Daq() { DEBUGTRACE_ENTER; }
std::unique_ptr<Daq> Daq::createDaq(const DeviceInfo &devinfo, std::unique_ptr<Daq> Daq::createDaq(const DeviceInfo &devinfo,
const DaqConfiguration &config) { const DaqConfiguration &config) {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
#if LASP_HAS_ULDAQ == 1 #if LASP_HAS_ULDAQ == 1
@ -23,37 +23,37 @@ std::unique_ptr<Daq> Daq::createDaq(const DeviceInfo &devinfo,
} }
#endif #endif
#if LASP_HAS_RTAUDIO == 1 #if LASP_HAS_RTAUDIO == 1
// See lasp_daqconfig.h:114 ALSA, up to // See lasp_daqconfig.h:114 ALSA, up to
if (devinfo.api.apicode == LASP_RTAUDIO_APICODE) { if (devinfo.api.apicode == LASP_RTAUDIO_APICODE) {
return createRtAudioDevice(devinfo, config); return createRtAudioDevice(devinfo, config);
} }
#endif #endif
throw std::runtime_error(string("Unable to match Device API: ") + throw std::runtime_error(string("Unable to match Device API: ") +
devinfo.api.apiname); devinfo.api.apiname);
} }
Daq::Daq(const DeviceInfo &devinfo, const DaqConfiguration &config) Daq::Daq(const DeviceInfo &devinfo, const DaqConfiguration &config)
: DaqConfiguration(config), DeviceInfo(devinfo) { : DaqConfiguration(config), DeviceInfo(devinfo) {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
if (!hasInternalOutputMonitor && monitorOutput) { if (!hasInternalOutputMonitor && monitorOutput) {
throw std::runtime_error( throw std::runtime_error(
"Output monitor flag set, but device does not have output monitor"); "Output monitor flag set, but device does not have output monitor");
}
if (!config.match(devinfo)) {
throw std::runtime_error("DaqConfiguration does not match device info");
}
if (neninchannels(false) > ninchannels) {
throw std::runtime_error(
"Number of enabled input channels is higher than device capability");
}
if (nenoutchannels() > noutchannels) {
throw std::runtime_error(
"Number of enabled output channels is higher than device capability");
}
} }
if (!config.match(devinfo)) {
throw std::runtime_error("DaqConfiguration does not match device info");
}
if (neninchannels(false) > ninchannels) {
throw std::runtime_error(
"Number of enabled input channels is higher than device capability");
}
if (nenoutchannels() > noutchannels) {
throw std::runtime_error(
"Number of enabled output channels is higher than device capability");
}
}
double Daq::samplerate() const { double Daq::samplerate() const {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
return availableSampleRates.at(sampleRateIndex); return availableSampleRates.at(sampleRateIndex);
@ -62,7 +62,9 @@ double Daq::samplerate() const {
DataTypeDescriptor::DataType Daq::dataType() const { DataTypeDescriptor::DataType Daq::dataType() const {
return availableDataTypes.at(dataTypeIndex); return availableDataTypes.at(dataTypeIndex);
} }
DataTypeDescriptor Daq::dtypeDescr() const { return dtype_map.at(dataType()); } const DataTypeDescriptor &Daq::dtypeDescr() const {
return dtype_map.at(dataType());
}
double Daq::inputRangeForChannel(us ch) const { double Daq::inputRangeForChannel(us ch) const {
if (!(ch < ninchannels)) { if (!(ch < ninchannels)) {

View File

@ -176,7 +176,7 @@ public:
* *
* @return A DataTypeDescriptor * @return A DataTypeDescriptor
*/ */
DataTypeDescriptor dtypeDescr() const; const DataTypeDescriptor& dtypeDescr() const;
/** /**
* @brief The number of frames that is send in a block of DaqData. * @brief The number of frames that is send in a block of DaqData.

View File

@ -57,7 +57,7 @@ bool DaqConfiguration::match(const DeviceInfo &dev) const {
return (dev.device_name == device_name && dev.api == api); return (dev.device_name == device_name && dev.api == api);
} }
int DaqConfiguration::getHighestInChannel() const { int DaqConfiguration::getHighestEnabledInChannel() const {
for (int i = inchannel_config.size() - 1; i > -1; i--) { for (int i = inchannel_config.size() - 1; i > -1; i--) {
if (inchannel_config.at(i).enabled) if (inchannel_config.at(i).enabled)
return i; return i;
@ -65,21 +65,21 @@ int DaqConfiguration::getHighestInChannel() const {
return -1; return -1;
} }
int DaqConfiguration::getHighestOutChannel() const { int DaqConfiguration::getHighestEnabledOutChannel() const {
for (us i = outchannel_config.size() - 1; i >= 0; i--) { for (us i = outchannel_config.size() - 1; i >= 0; i--) {
if (outchannel_config.at(i).enabled) if (outchannel_config.at(i).enabled)
return i; return i;
} }
return -1; return -1;
} }
int DaqConfiguration::getLowestInChannel() const { int DaqConfiguration::getLowestEnabledInChannel() const {
for (us i = 0; i < inchannel_config.size(); i++) { for (us i = 0; i < inchannel_config.size(); i++) {
if (inchannel_config.at(i).enabled) if (inchannel_config.at(i).enabled)
return i; return i;
} }
return -1; return -1;
} }
int DaqConfiguration::getLowestOutChannel() const { int DaqConfiguration::getLowestEnabledOutChannel() const {
for (us i = 0; i < outchannel_config.size(); i++) { for (us i = 0; i < outchannel_config.size(); i++) {
if (outchannel_config.at(i).enabled) if (outchannel_config.at(i).enabled)
return i; return i;

View File

@ -303,13 +303,13 @@ public:
bool match(const DeviceInfo &devinfo) const; bool match(const DeviceInfo &devinfo) const;
/** /**
* @brief Get the highest channel number from the list of enabled input * @brief Get the enabled highest channel number from the list of enabled input
* channels. * channels.
* *
* @return Index to the highest input channel. -1 if no input channels are * @return Index to the highest input channel. -1 if no input channels are
* enabled. * enabled.
*/ */
int getHighestInChannel() const; int getHighestEnabledInChannel() const;
/** /**
* @brief Get the highest channel number from the list of enabled output * @brief Get the highest channel number from the list of enabled output
* channels. * channels.
@ -317,7 +317,7 @@ public:
* @return Index to the highest input channel. -1 if no output channels are * @return Index to the highest input channel. -1 if no output channels are
* enabled. * enabled.
*/ */
int getHighestOutChannel() const; int getHighestEnabledOutChannel() const;
/** /**
* @brief Get the lowest channel number from the list of enabled input * @brief Get the lowest channel number from the list of enabled input
@ -326,15 +326,15 @@ public:
* @return Index to the lowest input channel. -1 if no input channels are * @return Index to the lowest input channel. -1 if no input channels are
* enabled. * enabled.
*/ */
int getLowestInChannel() const; int getLowestEnabledInChannel() const;
/** /**
* @brief Get the lowest channel number from the list of enabled output * @brief Get the lowest channel number from the list of enabled output
* channels. * channels.
* *
* @return Index to the lowest input channel. -1 if no output channels are * @return Index to the lowest output channel. -1 if no output channels are
* enabled. * enabled.
*/ */
int getLowestOutChannel() const; int getLowestEnabledOutChannel() const;
/** /**
* @brief Set all input channels to enabled / disabled state. * @brief Set all input channels to enabled / disabled state.

View File

@ -1,106 +1,210 @@
/* #define DEBUGTRACE_ENABLED */ /* #define DEBUGTRACE_ENABLED */
#include "lasp_daqdata.h"
#include "debugtrace.hpp" #include "debugtrace.hpp"
#include <armadillo>
#include "lasp_daqdata.h"
#include "lasp_mathtypes.h" #include "lasp_mathtypes.h"
#include <cassert> #include <cassert>
#include <memory>
using std::cerr;
using std::cout;
using std::endl;
using rte = std::runtime_error;
static_assert(sizeof(byte_t) == 1, "Invalid char size");
DEBUGTRACE_VARIABLES; DEBUGTRACE_VARIABLES;
DaqData::DaqData(const us nchannels, const us nframes, /// Constructors and destructors
DaqData::DaqData(const us nframes, const us nchannels,
const DataTypeDescriptor::DataType dtype) const DataTypeDescriptor::DataType dtype)
: nchannels(nchannels), nframes(nframes), dtype(dtype),
dtype_descr(dtype_map.at(dtype)), sw(dtype_descr.sw) {
static_assert(sizeof(char) == 1, "Invalid char size");
const DataTypeDescriptor &desc = dtype_map.at(dtype); : nframes(nframes), nchannels(nchannels), dtype(dtype),
_data.resize(nframes * nchannels * desc.sw); dtype_descr(dtype_map.at(dtype)), sw(dtype_descr.sw) {
DEBUGTRACE_ENTER;
DEBUGTRACE_PRINT(sw);
_data = new (std::align_val_t{8}) byte_t[sw * nchannels * nframes];
if (!_data) {
throw rte("Could not allocate memory for DaqData!");
}
}
DaqData::DaqData(const DaqData &o) : DaqData(o.nframes, o.nchannels, o.dtype) {
DEBUGTRACE_ENTER;
/* std::copy(o._data, &o._data[sw * nchannels * nframes], _data); */
memcpy(_data, o._data, sw * nchannels * nframes);
} }
void DaqData::copyInFromRaw(const std::vector<uint8_t *> &ptrs) { /* DaqData::DaqData(DaqData &&o) */
/* : nframes(o.nframes), nchannels(o.nchannels), dtype(o.dtype), */
/* dtype_descr(std::move(o.dtype_descr)), sw(o.sw) { */
/* DEBUGTRACE_ENTER; */
/* _data = o._data; */
/* /// Nullptrs do not get deleted */
/* o._data = nullptr; */
/* } */
DaqData::~DaqData() {
DEBUGTRACE_ENTER;
if (_data)
delete[] _data;
}
void DaqData::copyInFromRaw(const std::vector<byte_t *> &ptrs) {
DEBUGTRACE_ENTER;
us ch = 0; us ch = 0;
assert(ptrs.size() == nchannels); assert(ptrs.size() == nchannels);
for (auto ptr : ptrs) { for (auto& ptr : ptrs) {
std::copy(ptr, ptr + sw * nframes, &_data[sw * ch * nframes]); assert(ch < nchannels);
memcpy(&_data[sw * ch * nframes], ptr, sw * nframes);
/* std::copy(ptr, ptr + sw * nframes, &_data[sw * ch * nframes]); */
ch++; ch++;
} }
dmat data2(nframes, nchannels);
} }
void DaqData::copyToRaw(const us channel, uint8_t *ptr) { void DaqData::copyToRaw(const us channel, byte_t *ptr) {
std::copy(raw_ptr(0, channel), /* std::copy(raw_ptr(0, channel), raw_ptr(nframes, channel), ptr); */
raw_ptr(nframes, channel), ptr); memcpy(ptr, raw_ptr(0, channel), sw*nframes);
} }
template <typename int_type> d convertToFloat(const int_type val) { template <typename T>
if constexpr (std::is_integral<int_type>::value) { d DaqData::toFloat(const us frame, const us channel) const {
return static_cast<d>(val) / std::numeric_limits<int_type>::max(); DEBUGTRACE_ENTER;
if constexpr (std::is_integral<T>::value) {
return static_cast<d>(value<T>(frame, channel)) /
std::numeric_limits<T>::max();
} else { } else {
return static_cast<d>(val); return static_cast<d>(value<T>(frame, channel));
} }
} }
template <typename int_type>
inline vd channelToFloat(const byte_t *vals, const us nframes) { template <typename T> vd DaqData::toFloat(const us channel) const {
DEBUGTRACE_ENTER;
#if LASP_DEBUG == 1
check_type<T>();
#endif
vd res(nframes); vd res(nframes);
for (us i = 0; i < nframes; i++) { for (us i = 0; i < nframes; i++) {
res(i) = convertToFloat(reinterpret_cast<const int_type *>(vals)[i]); res(i) = toFloat<T>(i, channel);
} }
return res; return res;
} }
template <typename int_type> template <typename T> dmat DaqData::toFloat() const {
inline dmat allToFloat(const byte_t *vals, const us nframes, const us nchannels) {
DEBUGTRACE_ENTER;
#if LASP_DEBUG == 1
check_type<T>();
#endif
dmat res(nframes, nchannels); dmat res(nframes, nchannels);
for (us j = 0; j < nchannels; j++) {
for (us i = 0; i < nframes; i++) { for (us i = 0; i < nframes; i++) {
res(i, j) = convertToFloat( for (us j = 0; j < nchannels; j++) {
reinterpret_cast<const int_type *>(vals)[i + j * nframes]); res(i, j) = toFloat<T>(i, j);
} }
} }
return res; return res;
} }
vd DaqData::toFloat(const us channel_no) const { d DaqData::toFloat(const us frame, const us channel) const {
DEBUGTRACE_ENTER;
using DataType = DataTypeDescriptor::DataType; using DataType = DataTypeDescriptor::DataType;
switch (dtype) { switch (dtype) {
case (DataType::dtype_int8):
return toFloat<int8_t>(frame, channel);
break;
case (DataType::dtype_int16):
return toFloat<int16_t>(frame, channel);
break;
case (DataType::dtype_int32):
return toFloat<int32_t>(frame, channel);
break;
case (DataType::dtype_fl32):
return toFloat<float>(frame, channel);
break;
case (DataType::dtype_fl64):
return toFloat<double>(frame, channel);
break;
default:
throw std::runtime_error("BUG");
} // End of switch
// Never arrives her
return 0;
}
vd DaqData::toFloat(const us channel_no) const {
DEBUGTRACE_ENTER;
using DataType = DataTypeDescriptor::DataType;
cerr << (int) dtype << endl;
switch (dtype) {
case (DataType::dtype_int8): { case (DataType::dtype_int8): {
return channelToFloat<int8_t>(raw_ptr(0, channel_no), nframes); return toFloat<int8_t>(channel_no);
} break; } break;
case (DataType::dtype_int16): { case (DataType::dtype_int16): {
return channelToFloat<int16_t>(raw_ptr(0, channel_no), nframes); return toFloat<int16_t>(channel_no);
} break; } break;
case (DataType::dtype_int32): { case (DataType::dtype_int32): {
return channelToFloat<int32_t>(raw_ptr(0, channel_no), nframes); return toFloat<int32_t>(channel_no);
} break; } break;
case (DataType::dtype_fl32): { case (DataType::dtype_fl32): {
return channelToFloat<float>(raw_ptr(0, channel_no), nframes); return toFloat<float>(channel_no);
} break; } break;
case (DataType::dtype_fl64): { case (DataType::dtype_fl64): {
return channelToFloat<double>(raw_ptr(0, channel_no), nframes); return toFloat<double>(channel_no);
} break; } break;
default: default:
throw std::runtime_error("BUG"); throw std::runtime_error("BUG");
} // End of switch } // End of switch
// Never arrives here
return vd();
} }
dmat DaqData::toFloat() const { dmat DaqData::toFloat() const {
dmat result(nframes, nchannels);
DEBUGTRACE_ENTER;
/* DEBUGTRACE_PRINT(nframes); */
/* DEBUGTRACE_PRINT(nchannels); */
using DataType = DataTypeDescriptor::DataType; using DataType = DataTypeDescriptor::DataType;
/* cerr << "DataType: " << (int) dtype << endl; */
switch (dtype) { switch (dtype) {
case (DataType::dtype_int8): { case (DataType::dtype_int8):
return allToFloat<int8_t>(raw_ptr(0), nframes, nchannels); return toFloat<int8_t>();
} break; break;
case (DataType::dtype_int16): { case (DataType::dtype_int16):
return allToFloat<int16_t>(raw_ptr(0), nframes, nchannels); return toFloat<int16_t>();
} break; break;
case (DataType::dtype_int32): { case (DataType::dtype_int32):
return allToFloat<int32_t>(raw_ptr(0), nframes, nchannels); return toFloat<int32_t>();
} break; break;
case (DataType::dtype_fl32): { case (DataType::dtype_fl32):
return allToFloat<float>(raw_ptr(0), nframes, nchannels); return toFloat<float>();
} break; break;
case (DataType::dtype_fl64): { case (DataType::dtype_fl64):
return allToFloat<double>(raw_ptr(0), nframes, nchannels); return toFloat<double>();
} break; break;
default: default:
throw std::runtime_error("BUG"); throw std::runtime_error("BUG");
} // End of switch } // End of switch
// Never reached
return dmat();
}
void DaqData::print() const {
cout << "Number of frames: " << nframes << endl;
cout << "Number of channels: " << nchannels << endl;
cout << "DataType: " << dtype_map.at(dtype).name << endl;
cout << "First sample of first channel (as float)" << toFloat(0,0) << endl;
cout << "Last sample of first channel (as float)" << toFloat(nframes-1,0) << endl;
cout << "Last sample of last channel (as float)" << toFloat(nframes-1,nchannels-1) << endl;
} }

View File

@ -1,10 +1,12 @@
#pragma once #pragma once
#include "lasp_daqconfig.h" #include "lasp_daqconfig.h"
#include "lasp_types.h" #include "lasp_types.h"
#include <armadillo>
#include <cassert>
#include <functional> #include <functional>
#include <gsl/gsl-lite.hpp> #include <gsl/gsl-lite.hpp>
#include <memory> #include <memory>
#include <armadillo> #include <type_traits>
/** \addtogroup device /** \addtogroup device
* @{ * @{
@ -22,19 +24,19 @@ protected:
/** /**
* @brief Storage for the actual data. * @brief Storage for the actual data.
*/ */
std::vector<byte_t> _data; byte_t *_data;
public: public:
/**
* @brief The number of channels
*/
us nchannels;
/** /**
* @brief The number of frames in this block of data. * @brief The number of frames in this block of data.
*/ */
us nframes; us nframes;
/**
* @brief The number of channels
*/
us nchannels;
/** /**
* @brief The data type corresponding to a sample * @brief The data type corresponding to a sample
*/ */
@ -53,22 +55,23 @@ public:
/** /**
* @brief Initialize an empty frame of data * @brief Initialize an empty frame of data
* *
* @param nchannels The number of channels
* @param nframes The number of frames * @param nframes The number of frames
* @param nchannels The number of channels
* @param dtype The data type * @param dtype The data type
*/ */
DaqData(const us nchannels, const us nframes, DaqData(const us nframes, const us nchannels,
const DataTypeDescriptor::DataType dtype); const DataTypeDescriptor::DataType dtype);
/** /**
* @brief Initialize using no allocation * @brief Initialize using no allocation
*/ */
DaqData() DaqData(const DaqData &);
: DaqData(0, 0, DataTypeDescriptor::DataType::dtype_int8) {} /* DaqData(DaqData &&); */
virtual ~DaqData() = default; DaqData &operator=(const DaqData &) = delete;
DaqData &operator=(const DaqData &) = default; virtual ~DaqData();
/** /**
* @brief Return pointer to the raw data corresponding to a certain sample. * @brief Return pointer to the raw data corresponding to a certain sample
* (frame, channel combo).
* *
* @param frame The frame number * @param frame The frame number
* @param channel The channel number * @param channel The channel number
@ -76,16 +79,14 @@ public:
* @return Pointer to sample, not casted to final type * @return Pointer to sample, not casted to final type
*/ */
byte_t *raw_ptr(const us frame = 0, const us channel = 0) { byte_t *raw_ptr(const us frame = 0, const us channel = 0) {
return &(_data.data()[sw * (frame + channel * nframes)]); assert(frame < nframes);
assert(channel < nchannels);
return &(_data[sw * (frame + channel * nframes)]);
} }
const byte_t *raw_ptr(const us frame = 0, const us channel = 0) const { const byte_t *raw_ptr(const us frame = 0, const us channel = 0) const {
return &(_data.data()[sw * (frame + channel * nframes)]); assert(frame < nframes);
} assert(channel < nchannels);
return &(_data[sw * (frame + channel * nframes)]);
void setSlow(const us frame, const us channel, const int8_t *val) {
for (us i = 0; i < sw; i++) {
_data.at(i + sw * (frame + channel * nframes)) = val[i];
}
} }
/** /**
@ -93,7 +94,7 @@ public:
* *
* @return Number of bytes of data. * @return Number of bytes of data.
*/ */
us size_bytes() const { return _data.size(); } us size_bytes() const { return sw * nchannels * nframes; }
/** /**
* @brief Copy data from a set of raw pointers of *uninterleaved* data. * @brief Copy data from a set of raw pointers of *uninterleaved* data.
@ -101,7 +102,7 @@ public:
* *
* @param ptrs Pointers to data from channels * @param ptrs Pointers to data from channels
*/ */
void copyInFromRaw(const std::vector<uint8_t *> &ptrs); void copyInFromRaw(const std::vector<byte_t *> &ptrs);
/** /**
* @brief Copy contents of DaqData for a certain channel to a raw pointer. * @brief Copy contents of DaqData for a certain channel to a raw pointer.
@ -109,7 +110,7 @@ public:
* @param channel The channel to copy. * @param channel The channel to copy.
* @param ptr The pointer where data is copied to. * @param ptr The pointer where data is copied to.
*/ */
void copyToRaw(const us channel, uint8_t *ptr); void copyToRaw(const us channel, byte_t *ptr);
/** /**
* @brief Convert samples to floating point values and return a nframes x * @brief Convert samples to floating point values and return a nframes x
@ -121,7 +122,7 @@ public:
arma::Mat<d> toFloat() const; arma::Mat<d> toFloat() const;
/** /**
* @brief Convert samples to floating point values and return a nframes * @brief Convert samples to floating point value; and return a nframes
* column vector of floats. For data that is not already floating-point, * column vector of floats. For data that is not already floating-point,
* the data is scaled back from MAX_INT to +1.0. * the data is scaled back from MAX_INT to +1.0.
* *
@ -130,39 +131,73 @@ public:
* @return Array of floats * @return Array of floats
*/ */
arma::Col<d> toFloat(const us channel_no) const; arma::Col<d> toFloat(const us channel_no) const;
};
template <typename T> class TypedDaqData : public DaqData {
public:
TypedDaqData(const us nchannels, const us nframes,
const DataTypeDescriptor::DataType dtype_descr)
: DaqData(nchannels, nframes, dtype_descr) {}
T &operator[](const us i) { return _data[sw * i]; }
/** /**
* @brief Reference of sample, casted to the right type. Same as raw_ptr(), * @brief Convert single sample to floating point value; and return a nframes
* but then as reference, and corresponding to right type. * column vector of floats. For data that is not already floating-point,
* the data is scaled back from MAX_INT to +1.0.
* *
* @param channel The channel to convert
* @param frame_no The frame number to convert
*
* @return Float value
*/
d toFloat(const us frame_no, const us channel_no) const;
// Return value based on type
template <typename T> T &value(const us frame, const us channel) {
#if LASP_DEBUG == 1
check_type<T>();
#endif
return *reinterpret_cast<T *>(raw_ptr(frame, channel));
}
template <typename T> const T &value(const us frame, const us channel) const {
#if LASP_DEBUG == 1
check_type<T>();
#endif
return *reinterpret_cast<const T *>(raw_ptr(frame, channel));
}
/**
* @brief For debugging purposes: prints some stats
*/
void print() const;
protected:
template <typename T> void check_type() const {
using DataType = DataTypeDescriptor::DataType;
bool correct = false;
static_assert(std::is_arithmetic<T>::value);
if constexpr (std::is_floating_point<T>::value) {
correct |= sizeof(T) == 4 && dtype == DataType::dtype_fl32;
correct |= sizeof(T) == 8 && dtype == DataType::dtype_fl64;
} else {
correct |= sizeof(T) == 1 && dtype == DataType::dtype_int8;
correct |= sizeof(T) == 2 && dtype == DataType::dtype_int16;
correct |= sizeof(T) == 4 && dtype == DataType::dtype_int32;
}
if (!correct) {
throw std::runtime_error("Wrong datatype for template argument");
}
}
template <typename T> arma::Mat<d> toFloat() const;
template <typename T> arma::Col<d> toFloat(const us channel_no) const;
template <typename T> d toFloat(const us frame_no, const us channel_no) const;
/**
* @brief Return a value as floating point. Does a conversion from integer to
* float, if the stored type is integer. Also scales by its maximum value.
*
* @tparam T The original type
* @param frame Frame number * @param frame Frame number
* @param channel Channel number * @param channel Channel number
* *
* @return * @return Value converted to floating point
*/ */
T &operator()(const us frame = 0, const us channel = 0) {
return reinterpret_cast<T &>(*raw_ptr(frame, channel));
}
/**
* @brief Copy out the data for a certain channel to a buffer
*
* @param channel The channel to copy
* @param buffer The buffer to copy to. *Make sure* it is allocated with at
* least `sw*nframes` bytes.
*/
void copyToRaw(const us channel, T *buffer) {
DaqData::copyToRaw(channel, reinterpret_cast<uint8_t *>(buffer));
}
}; };
/** @} */ /** @} */

View File

@ -1,18 +1,21 @@
/* #define DEBUGTRACE_ENABLED */ #include <mutex>
#define DEBUGTRACE_ENABLED
#include "debugtrace.hpp" #include "debugtrace.hpp"
#include "lasp_mathtypes.h"
#include "lasp_rtaudiodaq.h" #include "lasp_rtaudiodaq.h"
#if LASP_HAS_RTAUDIO == 1 #if LASP_HAS_RTAUDIO == 1
#include "lasp_daq.h"
#include "RtAudio.h" #include "RtAudio.h"
#include "lasp_daq.h"
#include <atomic> #include <atomic>
#include <cassert> #include <cassert>
using std::atomic; using std::atomic;
using std::cerr; using std::cerr;
using std::endl; using std::endl;
using std::runtime_error; using rte = std::runtime_error;
using std::vector; using std::vector;
using lck = std::scoped_lock<std::mutex>;
DEBUGTRACE_VARIABLES; DEBUGTRACE_VARIABLES;
@ -81,7 +84,7 @@ void fillRtAudioDeviceInfo(vector<DeviceInfo> &devinfolist) {
d.availableDataTypes.push_back( d.availableDataTypes.push_back(
DataTypeDescriptor::DataType::dtype_int16); DataTypeDescriptor::DataType::dtype_int16);
} }
/* if (formats & RTAUDIO_SINT32) { */ /* if (formats & RTAUDIO_SINT24) { *1/ */
/* d.availableDataTypes.push_back(DataTypeDescriptor::DataType::dtype_int24); /* d.availableDataTypes.push_back(DataTypeDescriptor::DataType::dtype_int24);
*/ */
/* } */ /* } */
@ -137,7 +140,7 @@ public:
// We make sure not to run RtAudio in duplex mode. This seems to be buggy // We make sure not to run RtAudio in duplex mode. This seems to be buggy
// and untested. Better to use a hardware-type loopback into the system. // and untested. Better to use a hardware-type loopback into the system.
if (duplexMode()) { if (duplexMode()) {
throw runtime_error("RtAudio backend cannot run in duplex mode."); throw rte("RtAudio backend cannot run in duplex mode.");
} }
assert(!monitorOutput); assert(!monitorOutput);
@ -148,9 +151,9 @@ public:
inParams = std::make_unique<RtAudio::StreamParameters>(); inParams = std::make_unique<RtAudio::StreamParameters>();
// +1 to get the count. // +1 to get the count.
inParams->nChannels = getHighestInChannel() + 1; inParams->nChannels = getHighestEnabledInChannel() + 1;
if (inParams->nChannels < 1) { if (inParams->nChannels < 1) {
throw runtime_error("Invalid input number of channels"); throw rte("Invalid input number of channels");
} }
inParams->firstChannel = 0; inParams->firstChannel = 0;
inParams->deviceId = devinfo.api_specific_devindex; inParams->deviceId = devinfo.api_specific_devindex;
@ -159,9 +162,9 @@ public:
outParams = std::make_unique<RtAudio::StreamParameters>(); outParams = std::make_unique<RtAudio::StreamParameters>();
outParams->nChannels = getHighestOutChannel() + 1; outParams->nChannels = getHighestEnabledOutChannel() + 1;
if (outParams->nChannels < 1) { if (outParams->nChannels < 1) {
throw runtime_error("Invalid output number of channels"); throw rte("Invalid output number of channels");
} }
outParams->firstChannel = 0; outParams->firstChannel = 0;
outParams->deviceId = devinfo.api_specific_devindex; outParams->deviceId = devinfo.api_specific_devindex;
@ -171,7 +174,7 @@ public:
streamoptions.flags = RTAUDIO_HOG_DEVICE | RTAUDIO_NONINTERLEAVED; streamoptions.flags = RTAUDIO_HOG_DEVICE | RTAUDIO_NONINTERLEAVED;
streamoptions.numberOfBuffers = 2; streamoptions.numberOfBuffers = 2;
streamoptions.streamName = "RtAudio stream"; streamoptions.streamName = "LASP RtAudio DAQ stream";
streamoptions.priority = 0; streamoptions.priority = 0;
RtAudioFormat format; RtAudioFormat format;
@ -179,22 +182,27 @@ public:
const Dtype dtype = dataType(); const Dtype dtype = dataType();
switch (dtype) { switch (dtype) {
case Dtype::dtype_fl32: case Dtype::dtype_fl32:
DEBUGTRACE_PRINT("Datatype float32");
format = RTAUDIO_FLOAT32; format = RTAUDIO_FLOAT32;
break; break;
case Dtype::dtype_fl64: case Dtype::dtype_fl64:
DEBUGTRACE_PRINT("Datatype float64");
format = RTAUDIO_FLOAT64; format = RTAUDIO_FLOAT64;
break; break;
case Dtype::dtype_int8: case Dtype::dtype_int8:
DEBUGTRACE_PRINT("Datatype int8");
format = RTAUDIO_SINT8; format = RTAUDIO_SINT8;
break; break;
case Dtype::dtype_int16: case Dtype::dtype_int16:
DEBUGTRACE_PRINT("Datatype int16");
format = RTAUDIO_SINT16; format = RTAUDIO_SINT16;
break; break;
case Dtype::dtype_int32: case Dtype::dtype_int32:
DEBUGTRACE_PRINT("Datatype int32");
format = RTAUDIO_SINT32; format = RTAUDIO_SINT32;
break; break;
default: default:
throw runtime_error("Invalid data type specified for DAQ stream."); throw rte("Invalid data type specified for DAQ stream.");
break; break;
} }
@ -207,38 +215,46 @@ public:
static_cast<us>(samplerate()), &nFramesPerBlock_copy, static_cast<us>(samplerate()), &nFramesPerBlock_copy,
mycallback, (void *)this, &streamoptions, mycallback, (void *)this, &streamoptions,
&myerrorcallback); &myerrorcallback);
if (nFramesPerBlock_copy != nFramesPerBlock) {
throw rte("Got different number of frames per block back from RtAudio "
"backend. Do not know what to do");
}
} }
virtual void start(InDaqCallback inCallback, virtual void start(InDaqCallback inCallback,
OutDaqCallback outCallback) override final { OutDaqCallback outCallback) override final {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
lck lock(_mtx);
assert(!monitorOutput); assert(!monitorOutput);
if (StreamStatus().runningOK()) { if (getStreamStatus().runningOK()) {
throw runtime_error("Stream already running"); throw rte("Stream already running");
} }
// Logical XOR // Logical XOR
if (inCallback && outCallback) { if (inCallback && outCallback) {
throw runtime_error("Either input or output stream possible for RtAudio. " throw rte("Either input or output stream possible for RtAudio. "
"Stream duplex mode not provided."); "Stream duplex mode not provided.");
} }
if (inCallback) { if (neninchannels() > 0) {
_incallback = inCallback; if (!inCallback) {
if (neninchannels() == 0) { throw rte(
throw runtime_error(
"Input callback given, but stream does not provide input data"); "Input callback given, but stream does not provide input data");
} }
_incallback = inCallback;
} }
if (outCallback) { if (nenoutchannels() > 0) {
_outcallback = outCallback; if (!outCallback) {
if (nenoutchannels() == 0) { throw rte(
throw runtime_error(
"Output callback given, but stream does not provide output data"); "Output callback given, but stream does not provide output data");
} }
_outcallback = outCallback;
} }
// Start the stream. Throws on error. // Start the stream. Throws on error.
@ -264,10 +280,9 @@ public:
} }
int streamCallback(void *outputBuffer, void *inputBuffer, int streamCallback(void *outputBuffer, void *inputBuffer,
unsigned int nFrames, double streamTime, unsigned int nFrames, RtAudioStreamStatus status) {
RtAudioStreamStatus status) {
/* DEBUGTRACE_ENTER; */ DEBUGTRACE_ENTER;
using se = StreamStatus::StreamError; using se = StreamStatus::StreamError;
@ -294,8 +309,9 @@ public:
break; break;
} }
const auto &dtype_descr = DataTypeDescriptor(); const auto &dtype_descr = dtypeDescr();
const auto dtype = dataType(); const auto dtype = dataType();
us neninchannels = this->neninchannels(); us neninchannels = this->neninchannels();
us nenoutchannels = this->nenoutchannels(); us nenoutchannels = this->nenoutchannels();
us sw = dtype_descr.sw; us sw = dtype_descr.sw;
@ -308,18 +324,22 @@ public:
if (inputBuffer) { if (inputBuffer) {
assert(_incallback); assert(_incallback);
std::vector<uint8_t *> ptrs; std::vector<byte_t *> ptrs;
ptrs.reserve(neninchannels); ptrs.reserve(neninchannels);
const us ch_min = getLowestEnabledInChannel();
const us ch_max = getHighestEnabledInChannel();
us i = 0; us i = 0;
for (int ch = getLowestInChannel(); ch <= getHighestInChannel(); ch++) { for (us ch = ch_min; ch <= ch_max; ch++) {
if (inchannel_config.at(ch).enabled) { if (inchannel_config.at(ch).enabled) {
ptrs.push_back(static_cast<uint8_t *>(inputBuffer) + byte_t *ptr =
sw * i * nFramesPerBlock); static_cast<byte_t *>(inputBuffer) + sw * i * nFramesPerBlock;
i++; DEBUGTRACE_PRINT((us)ptr);
ptrs.push_back(ptr);
} }
i++;
} }
DaqData d{neninchannels, nFramesPerBlock, dtype}; DaqData d{nFramesPerBlock, neninchannels, dtype};
d.copyInFromRaw(ptrs); d.copyInFromRaw(ptrs);
bool ret = _incallback(d); bool ret = _incallback(d);
@ -331,22 +351,23 @@ public:
if (outputBuffer) { if (outputBuffer) {
assert(_outcallback); assert(_outcallback);
std::vector<uint8_t *> ptrs; std::vector<byte_t *> ptrs;
ptrs.reserve(nenoutchannels); ptrs.reserve(nenoutchannels);
/* outCallback */ /* outCallback */
us i = 0;
for (int ch = getLowestOutChannel(); ch <= getHighestOutChannel(); ch++) {
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) { if (outchannel_config.at(ch).enabled) {
ptrs.push_back(static_cast<uint8_t *>(outputBuffer) + ptrs.push_back(static_cast<byte_t *>(outputBuffer) +
sw * i * nFramesPerBlock); sw * i * nFramesPerBlock);
i++;
} }
i++;
} }
DaqData d{nenoutchannels, nFramesPerBlock, dtype}; DaqData d{nFramesPerBlock, nenoutchannels, dtype};
bool ret = _outcallback(d); bool ret = _outcallback(d);
if (!ret) { if (!ret) {
@ -381,7 +402,7 @@ int mycallback(void *outputBuffer, void *inputBuffer, unsigned int nFrames,
double streamTime, RtAudioStreamStatus status, void *userData) { double streamTime, RtAudioStreamStatus status, void *userData) {
return static_cast<RtAudioDaq *>(userData)->streamCallback( return static_cast<RtAudioDaq *>(userData)->streamCallback(
outputBuffer, inputBuffer, nFrames, streamTime, status); outputBuffer, inputBuffer, nFrames, status);
} }
#endif // LASP_HAS_RTAUDIO == 1 #endif // LASP_HAS_RTAUDIO == 1

View File

@ -31,6 +31,7 @@ InDataHandler::~InDataHandler() {
"InDataHandler's destructor. Fix this by calling " "InDataHandler's destructor. Fix this by calling "
"InDataHandler::stop() from the derived class' destructor." "InDataHandler::stop() from the derived class' destructor."
<< endl; << endl;
abort();
} }
#endif #endif
} }
@ -60,7 +61,7 @@ void StreamMgr::rescanDAQDevices(bool background,
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
checkRightThread(); checkRightThread();
if(!_devices_mtx.try_lock()) { if (!_devices_mtx.try_lock()) {
throw rte("A background DAQ device scan is probably already running"); throw rte("A background DAQ device scan is probably already running");
} }
_devices_mtx.unlock(); _devices_mtx.unlock();
@ -69,11 +70,11 @@ void StreamMgr::rescanDAQDevices(bool background,
throw rte("Rescanning DAQ devices only possible when no stream is running"); throw rte("Rescanning DAQ devices only possible when no stream is running");
} }
_devices.clear(); _devices.clear();
auto &pool = getPool(); /* auto &pool = getPool(); */
if (background) { if (!background) {
pool.push_task(&StreamMgr::rescanDAQDevices_impl, this, callback);
} else {
rescanDAQDevices_impl(callback); rescanDAQDevices_impl(callback);
} else {
/* pool.push_task(&StreamMgr::rescanDAQDevices_impl, this, callback); */
} }
} }
void StreamMgr::rescanDAQDevices_impl(std::function<void()> callback) { void StreamMgr::rescanDAQDevices_impl(std::function<void()> callback) {
@ -86,7 +87,7 @@ void StreamMgr::rescanDAQDevices_impl(std::function<void()> callback) {
} }
bool StreamMgr::inCallback(const DaqData &data) { bool StreamMgr::inCallback(const DaqData &data) {
/* DEBUGTRACE_ENTER; */ DEBUGTRACE_ENTER;
std::scoped_lock<std::mutex> lck(_inDataHandler_mtx); std::scoped_lock<std::mutex> lck(_inDataHandler_mtx);
@ -95,7 +96,6 @@ bool StreamMgr::inCallback(const DaqData &data) {
bool res = handler->inCallback(data); bool res = handler->inCallback(data);
if (!res) if (!res)
return false; return false;
} }
return true; return true;
} }
@ -197,6 +197,12 @@ StreamMgr::~StreamMgr() {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
checkRightThread(); checkRightThread();
stopAllStreams(); stopAllStreams();
if (!_inDataHandlers.empty()) {
cerr << "*** WARNING: InDataHandlers have not been all stopped, while "
"StreamMgr destructor is called. This is a misuse BUG"
<< endl;
abort();
}
} }
void StreamMgr::stopAllStreams() { void StreamMgr::stopAllStreams() {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
@ -213,6 +219,10 @@ void StreamMgr::startStream(const DaqConfiguration &config) {
config.inchannel_config.cend(), config.inchannel_config.cend(),
[](auto &i) { return i.enabled; }); [](auto &i) { return i.enabled; });
bool isOutput = std::count_if(config.outchannel_config.cbegin(),
config.outchannel_config.cend(),
[](auto &i) { return i.enabled; });
// Find the first device that matches with the configuration // Find the first device that matches with the configuration
DeviceInfo devinfo; DeviceInfo devinfo;
@ -231,10 +241,6 @@ void StreamMgr::startStream(const DaqConfiguration &config) {
isInput |= config.monitorOutput && devinfo.hasInternalOutputMonitor; isInput |= config.monitorOutput && devinfo.hasInternalOutputMonitor;
bool isOutput = std::count_if(config.outchannel_config.cbegin(),
config.outchannel_config.cend(),
[](auto &i) { return i.enabled; });
bool isDuplex = isInput && isOutput; bool isDuplex = isInput && isOutput;
if (!isInput && !isOutput) { if (!isInput && !isOutput) {
@ -242,12 +248,18 @@ void StreamMgr::startStream(const DaqConfiguration &config) {
"stream. Cannot start."); "stream. Cannot start.");
} }
if ((isDuplex || isInput) && _inputStream) { if (isInput && _inputStream) {
throw rte("Error: an input stream is already running. Please " throw rte("Error: an input stream is already running. Please "
"first stop existing stream"); "first stop existing stream");
} else if (isOutput && _outputStream) { } else if (isOutput && _outputStream) {
throw rte("Error: output stream is already running. Please " throw rte("Error: output stream is already running. Please "
"first stop existing stream"); "first stop existing stream");
} else if (_inputStream) {
if (_inputStream->duplexMode() && isOutput) {
throw rte(
"Error: output stream is already running (in duplex mode). Please "
"first stop existing stream");
}
} }
InDaqCallback inCallback; InDaqCallback inCallback;
@ -275,7 +287,7 @@ void StreamMgr::startStream(const DaqConfiguration &config) {
if (isInput) { if (isInput) {
_inputStream = std::move(daq); _inputStream = std::move(daq);
for(auto& handler: _inDataHandlers) { for (auto &handler : _inDataHandlers) {
handler->reset(_inputStream.get()); handler->reset(_inputStream.get());
} }
if (_inputStream->duplexMode()) { if (_inputStream->duplexMode()) {
@ -297,7 +309,7 @@ void StreamMgr::stopStream(const StreamType t) {
/// Kills input stream /// Kills input stream
_inputStream = nullptr; _inputStream = nullptr;
/// Send reset to all in data handlers /// Send reset to all in data handlers
for(auto& handler: _inDataHandlers) { for (auto &handler : _inDataHandlers) {
handler->reset(nullptr); handler->reset(nullptr);
} }
} break; } break;
@ -305,6 +317,7 @@ void StreamMgr::stopStream(const StreamType t) {
if (_inputStream && _inputStream->duplexMode()) { if (_inputStream && _inputStream->duplexMode()) {
_inputStream = nullptr; _inputStream = nullptr;
} else { } else {
if (!_outputStream) { if (!_outputStream) {
throw rte("Output stream is not running"); throw rte("Output stream is not running");
} }
@ -322,7 +335,7 @@ void StreamMgr::addInDataHandler(InDataHandler &handler) {
checkRightThread(); checkRightThread();
std::scoped_lock<std::mutex> lck(_inDataHandler_mtx); std::scoped_lock<std::mutex> lck(_inDataHandler_mtx);
_inDataHandlers.push_back(&handler); _inDataHandlers.push_back(&handler);
if(_inputStream) { if (_inputStream) {
handler.reset(_inputStream.get()); handler.reset(_inputStream.get());
} else { } else {
handler.reset(nullptr); handler.reset(nullptr);
@ -340,16 +353,19 @@ Daq::StreamStatus StreamMgr::getStreamStatus(const StreamType type) const {
checkRightThread(); checkRightThread();
// Default constructor, says stream is not running, but also no errors // Default constructor, says stream is not running, but also no errors
Daq::StreamStatus s;
const Daq *daq = getDaq(type); const Daq *daq = getDaq(type);
if (daq) { if (daq) {
s = daq->getStreamStatus(); return daq->getStreamStatus();
} else {
return Daq::StreamStatus();
} }
return s;
} }
const Daq *StreamMgr::getDaq(StreamType type) const { const Daq *StreamMgr::getDaq(StreamType type) const {
checkRightThread();
if (type == StreamType::input) { if (type == StreamType::input) {
return _inputStream.get(); return _inputStream.get();
} else { } else {

View File

@ -254,22 +254,22 @@ public:
auto runCallback = ([&](us totalOffset) { auto runCallback = ([&](us totalOffset) {
/* DEBUGTRACE_ENTER; */ /* DEBUGTRACE_ENTER; */
TypedDaqData<double> data(nchannels, nFramesPerBlock, DaqData data(nFramesPerBlock, nchannels,
DataTypeDescriptor::DataType::dtype_fl64); DataTypeDescriptor::DataType::dtype_fl64);
us monitorOffset = monitorOutput ? 1 : 0; us monitorOffset = monitorOutput ? 1 : 0;
/* /// Put the output monitor in front */ /* /// Put the output monitor in front */
if (monitorOutput) { if (monitorOutput) {
for (us sample = 0; sample < nFramesPerBlock; sample++) { for (us sample = 0; sample < nFramesPerBlock; sample++) {
data(sample, 0) = data.value<double>(sample, 0) =
buf[totalOffset + sample * nchannels + (nchannels - 1)]; buf[totalOffset + sample * nchannels + (nchannels - 1)];
} }
} }
for (us channel = 0; channel < nchannels-monitorOffset; channel++) { for (us channel = 0; channel < nchannels - monitorOffset; channel++) {
/* DEBUGTRACE_PRINT(channel); */ /* DEBUGTRACE_PRINT(channel); */
for (us frame = 0; frame < nFramesPerBlock; frame++) { for (us frame = 0; frame < nFramesPerBlock; frame++) {
data(frame, channel + monitorOffset) = data.value<double>(frame, channel + monitorOffset) =
buf[totalOffset + (frame * nchannels) + channel]; buf[totalOffset + (frame * nchannels) + channel];
} }
} }
@ -392,20 +392,20 @@ public:
if (transferStatus.currentIndex < buffer_mid_idx) { if (transferStatus.currentIndex < buffer_mid_idx) {
topenqueued = false; topenqueued = false;
if (!botenqueued) { if (!botenqueued) {
TypedDaqData<double> d(1, nFramesPerBlock, DaqData d(nFramesPerBlock,1,
DataTypeDescriptor::DataType::dtype_fl64); DataTypeDescriptor::DataType::dtype_fl64);
res = cb(d); res = cb(d);
d.copyToRaw(0, &buf[buffer_mid_idx]); d.copyToRaw(0, reinterpret_cast<byte_t*>(&(buf[buffer_mid_idx])));
botenqueued = true; botenqueued = true;
} }
} else { } else {
botenqueued = false; botenqueued = false;
if (!topenqueued) { if (!topenqueued) {
TypedDaqData<double> d(1, nFramesPerBlock, DaqData d(nFramesPerBlock,1,
DataTypeDescriptor::DataType::dtype_fl64); DataTypeDescriptor::DataType::dtype_fl64);
res = cb(d); res = cb(d);
d.copyToRaw(0, buf.data()); d.copyToRaw(0, reinterpret_cast<byte_t*>(&(buf[0])));
topenqueued = true; topenqueued = true;
} }

View File

@ -1,6 +1,6 @@
/* #define DEBUGTRACE_ENABLED */ /* #define DEBUGTRACE_ENABLED */
#include "lasp_ppm.h"
#include "debugtrace.hpp" #include "debugtrace.hpp"
#include "lasp_ppm.h"
#include <mutex> #include <mutex>
using std::cerr; using std::cerr;
@ -11,26 +11,40 @@ using rte = std::runtime_error;
PPMHandler::PPMHandler(StreamMgr &mgr, const d decay_dBps) PPMHandler::PPMHandler(StreamMgr &mgr, const d decay_dBps)
: ThreadedInDataHandler(mgr), _decay_dBps(decay_dBps) { : ThreadedInDataHandler(mgr), _decay_dBps(decay_dBps) {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
// This results in a deadlock!
/* std::scoped_lock<std::mutex> lck(_mtx); */
start();
} }
bool PPMHandler::inCallback_threaded(const DaqData &d) { bool PPMHandler::inCallback_threaded(const DaqData &d) {
/* DEBUGTRACE_ENTER; */
DEBUGTRACE_ENTER;
std::scoped_lock<std::mutex> lck(_mtx); std::scoped_lock<std::mutex> lck(_mtx);
dmat data = d.toFloat(); dmat data = d.toFloat();
/* data.print(); */
const us nchannels = _cur_max.size(); const us nchannels = d.nchannels;
assert(data.n_cols == nchannels);
vrd maxabs = arma::max(arma::abs(data)); if (nchannels != _cur_max.size()) {
DEBUGTRACE_PRINT("Resizing clip and cur max");
_cur_max = vd(nchannels, arma::fill::value(1e-80));
_clip_time = vd(nchannels, arma::fill::value(-1));
}
assert(_clip_time.size() == _cur_max.size());
/// Obtain max abs values
vd maxabs = arma::max(arma::abs(data), 0).as_col();
/* maxabs.print(); */
arma::uvec clip_indices = arma::find(maxabs > clip_point); arma::uvec clip_indices = arma::find(maxabs > clip_point);
arma::uvec clip(nchannels, arma::fill::zeros); arma::uvec clip(nchannels, arma::fill::zeros);
clip.elem(clip_indices).fill(1); clip.elem(clip_indices).fill(1);
arma::uvec update_max_idx = arma::find(maxabs > _cur_max); arma::uvec update_max_idx = arma::find(maxabs > _cur_max);
/* update_max_idx.print(); */
arma::uvec update_max(nchannels, arma::fill::zeros); arma::uvec update_max(nchannels, arma::fill::zeros);
/* update_max.print(); */
update_max.elem(update_max_idx).fill(1); update_max.elem(update_max_idx).fill(1);
assert(_cur_max.size() == _clip_time.size()); assert(_cur_max.size() == _clip_time.size());
@ -47,8 +61,6 @@ bool PPMHandler::inCallback_threaded(const DaqData &d) {
_clip_time(i) += _dt; _clip_time(i) += _dt;
} }
/* cerr << "maxabs(i)" << maxabs(i) << endl; */
/* cerr << "curmax(i)" << _cur_max(i) << endl; */
if (update_max(i)) { if (update_max(i)) {
_cur_max(i) = maxabs(i); _cur_max(i) = maxabs(i);
} else { } else {
@ -60,7 +72,7 @@ bool PPMHandler::inCallback_threaded(const DaqData &d) {
std::tuple<vd, arma::uvec> PPMHandler::getCurrentValue() const { std::tuple<vd, arma::uvec> PPMHandler::getCurrentValue() const {
/* DEBUGTRACE_ENTER; */ DEBUGTRACE_ENTER;
std::scoped_lock<std::mutex> lck(_mtx); std::scoped_lock<std::mutex> lck(_mtx);
arma::uvec clips(_clip_time.size(), arma::fill::zeros); arma::uvec clips(_clip_time.size(), arma::fill::zeros);
@ -71,24 +83,21 @@ std::tuple<vd, arma::uvec> PPMHandler::getCurrentValue() const {
void PPMHandler::reset(const Daq *daq) { void PPMHandler::reset(const Daq *daq) {
DEBUGTRACE_ENTER; /* DEBUGTRACE_ENTER; */
std::scoped_lock<std::mutex> lck(_mtx); std::scoped_lock<std::mutex> lck(_mtx);
if (daq) { if (daq) {
_cur_max = vrd(daq->neninchannels(), arma::fill::zeros); _cur_max.fill(1e-80);
;
_clip_time.fill(-1);
_clip_time = vd(daq->neninchannels(), arma::fill::value(-1));
const d fs = daq->samplerate(); const d fs = daq->samplerate();
DEBUGTRACE_PRINT(fs); /* DEBUGTRACE_PRINT(fs); */
_dt = daq->framesPerBlock() / fs; _dt = daq->framesPerBlock() / fs;
_alpha = std::max<d>(d_pow(10, -_dt * _decay_dBps / (20)), 0); _alpha = std::max<d>(d_pow(10, -_dt * _decay_dBps / (20)), 0);
DEBUGTRACE_PRINT(_alpha); /* DEBUGTRACE_PRINT(_alpha); */
} else {
_cur_max.clear();
_clip_time.clear();
} }
} }

View File

@ -52,7 +52,7 @@ class PPMHandler: public ThreadedInDataHandler {
/** /**
* @brief Current maximum values * @brief Current maximum values
*/ */
vrd _cur_max; vd _cur_max;
/** /**
* @brief How long ago the last clip has happened. Negative in case no clip * @brief How long ago the last clip has happened. Negative in case no clip

View File

@ -4,25 +4,62 @@
#include "lasp_thread.h" #include "lasp_thread.h"
#include <future> #include <future>
#include <thread> #include <thread>
#include <queue>
#include <optional>
using namespace std::literals::chrono_literals; using namespace std::literals::chrono_literals;
using lck = std::scoped_lock<std::mutex>;
using rte = std::runtime_error;
class SafeQueue {
std::queue<DaqData> _queue;
std::mutex _mtx;
std::atomic_int32_t _contents {0};
public:
void push(const DaqData& d) {
DEBUGTRACE_ENTER;
lck lock(_mtx);
_queue.push(d);
_contents++;
}
DaqData pop() {
DEBUGTRACE_ENTER;
if(_contents ==0) {
throw rte("BUG: Pop on empty queue");
}
lck lock(_mtx);
/* DaqData d(std::move(_queue.front())); */
DaqData d(_queue.front());
_queue.pop();
_contents--;
return d;
}
bool empty() const {
return _contents == 0;
}
};
ThreadedInDataHandler::ThreadedInDataHandler(StreamMgr &mgr) ThreadedInDataHandler::ThreadedInDataHandler(StreamMgr &mgr)
: InDataHandler(mgr) { : InDataHandler(mgr), _queue(std::make_unique<SafeQueue>()) {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
// Initialize thread pool, if not already done // Initialize thread pool, if not already done
getPool(); getPool();
} }
bool ThreadedInDataHandler::inCallback(const DaqData &daqdata) { bool ThreadedInDataHandler::inCallback(const DaqData &daqdata) {
/* DEBUGTRACE_ENTER; */ DEBUGTRACE_ENTER;
if (!_lastCallbackResult) { if (!_lastCallbackResult) {
return false; return false;
} }
dataqueue.push(daqdata);
_queue->push(daqdata);
auto &pool = getPool(); auto &pool = getPool();
@ -46,12 +83,12 @@ ThreadedInDataHandler::~ThreadedInDataHandler() {
void ThreadedInDataHandler::threadFcn() { void ThreadedInDataHandler::threadFcn() {
/* DEBUGTRACE_ENTER; */ DEBUGTRACE_ENTER;
_thread_running = true; _thread_running = true;
DaqData d{};
if (dataqueue.pop(d) && !_stopThread) { if (!_queue->empty() && !_stopThread) {
DaqData d(_queue->pop());
// Call inCallback_threaded // Call inCallback_threaded
if (inCallback_threaded(d) == false) { if (inCallback_threaded(d) == false) {

View File

@ -13,7 +13,7 @@ const us RINGBUFFER_SIZE = 1024;
* @{ * @{
*/ */
class SafeQueue;
/** /**
* @brief Threaded in data handler. Buffers inCallback data and calls a * @brief Threaded in data handler. Buffers inCallback data and calls a
* callback with the same signature on a different thread. * callback with the same signature on a different thread.
@ -22,9 +22,7 @@ class ThreadedInDataHandler: public InDataHandler {
/** /**
* @brief The queue used to push elements to the handling thread. * @brief The queue used to push elements to the handling thread.
*/ */
boost::lockfree::spsc_queue<DaqData, std::unique_ptr<SafeQueue> _queue;
boost::lockfree::capacity<RINGBUFFER_SIZE>>
dataqueue;
std::atomic<bool> _thread_running{false}; std::atomic<bool> _thread_running{false};
std::atomic<bool> _stopThread{false}; std::atomic<bool> _stopThread{false};

View File

@ -25,6 +25,7 @@
#include <stddef.h> #include <stddef.h>
#endif #endif
static_assert(sizeof(size_t) == 8);
typedef size_t us; /* Size type I always use */ typedef size_t us; /* Size type I always use */
// To change the whole code to 32-bit floating points, change this to // To change the whole code to 32-bit floating points, change this to

View File

@ -99,6 +99,7 @@ class Recording:
logging.debug("Starting record....") logging.debug("Starting record....")
self.indh = InDataHandler(streammgr, self.inCallback, self.resetCallback) self.indh = InDataHandler(streammgr, self.inCallback, self.resetCallback)
self.indh.start()
if wait: if wait:
logging.debug("Stop recording with CTRL-C") logging.debug("Stop recording with CTRL-C")

View File

@ -25,13 +25,45 @@ namespace py = pybind11;
* *
* @return Numpy array * @return Numpy array
*/ */
template <typename T> py::array_t<T> getPyArray(const DaqData &d) { template <typename T,bool copy=false> py::array_t<T> getPyArrayNoCpy(const DaqData &d) {
// https://github.com/pybind/pybind11/issues/323 // https://github.com/pybind/pybind11/issues/323
// //
// When a valid object is passed as 'base', it tells pybind not to take // 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 // 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 // own it, but - psss! - , we don't tell it to pybind... Alos note that ANY
// valid object is good for this purpose, so I chose "str"... // 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; py::str dummyDataOwner;
/* /*
@ -73,12 +105,9 @@ public:
: ThreadedInDataHandler(mgr), cb(cb), reset_callback(reset_callback) { : ThreadedInDataHandler(mgr), cb(cb), reset_callback(reset_callback) {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
/// TODO: Note that if start() throws an exception, which means that the /// Start should be called externally, as at constructor time no virtual
/// destructor of PyIndataHandler is not called and the thread destructor /// functions should be called.
/// calls terminate(). It is a kind of rude way to crash, but it is also /* start(); */
/// *very* unlikely to happen, as start() does only add a reference to this
/// handler to a list in the stream mgr.
start();
} }
~PyIndataHandler() { ~PyIndataHandler() {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
@ -100,19 +129,19 @@ public:
py::object bool_val; py::object bool_val;
switch (d.dtype) { switch (d.dtype) {
case (DataType::dtype_int8): { case (DataType::dtype_int8): {
bool_val = cb(getPyArray<int8_t>(d)); bool_val = cb(getPyArrayNoCpy<int8_t>(d));
} break; } break;
case (DataType::dtype_int16): { case (DataType::dtype_int16): {
bool_val = cb(getPyArray<int16_t>(d)); bool_val = cb(getPyArrayNoCpy<int16_t>(d));
} break; } break;
case (DataType::dtype_int32): { case (DataType::dtype_int32): {
bool_val = cb(getPyArray<int32_t>(d)); bool_val = cb(getPyArrayNoCpy<int32_t>(d));
} break; } break;
case (DataType::dtype_fl32): { case (DataType::dtype_fl32): {
bool_val = cb(getPyArray<float>(d)); bool_val = cb(getPyArrayNoCpy<float>(d));
} break; } break;
case (DataType::dtype_fl64): { case (DataType::dtype_fl64): {
bool_val = cb(getPyArray<double>(d)); bool_val = cb(getPyArrayNoCpy<double>(d));
} break; } break;
default: default:
throw std::runtime_error("BUG"); throw std::runtime_error("BUG");
@ -134,13 +163,22 @@ public:
} }
}; };
void init_datahandler(py::module &m) { void init_datahandler(py::module &m) {
py::class_<PyIndataHandler> h(m, "InDataHandler");
h.def(py::init<StreamMgr &, py::function, py::function>()); py::class_<InDataHandler> idh(m, "InDataHandler_base");
idh.def("start", &InDataHandler::start);
idh.def("stop", &InDataHandler::stop);
py::class_<ThreadedInDataHandler, InDataHandler> tidh(
m, "ThreadedInDataHandler");
/// The C++ class is PyIndataHandler, but for Python, it is called
/// InDataHandler
py::class_<PyIndataHandler, ThreadedInDataHandler> pyidh(m, "InDataHandler");
pyidh.def(py::init<StreamMgr &, py::function, py::function>());
/// Peak Programme Meter /// Peak Programme Meter
py::class_<PPMHandler> ppm(m, "PPMHandler"); py::class_<PPMHandler, ThreadedInDataHandler> ppm(m, "PPMHandler");
ppm.def(py::init<StreamMgr &, const d>()); ppm.def(py::init<StreamMgr &, const d>());
ppm.def(py::init<StreamMgr &>()); ppm.def(py::init<StreamMgr &>());
@ -153,8 +191,8 @@ void init_datahandler(py::module &m) {
/// Real time Aps /// Real time Aps
/// ///
py::class_<RtAps> rtaps(m, "RtAps"); py::class_<RtAps, ThreadedInDataHandler> rtaps(m, "RtAps");
rtaps.def(py::init<StreamMgr &, // StreamMgr rtaps.def(py::init<StreamMgr &, // StreamMgr
Filter *const, // FreqWeighting filter Filter *const, // FreqWeighting filter
const us, // Nfft const us, // Nfft
const Window::WindowType, // Window const Window::WindowType, // Window

View File

@ -16,14 +16,14 @@ void init_streammgr(py::module &m) {
m, "StreamMgr"); m, "StreamMgr");
py::enum_<StreamMgr::StreamType>(smgr, "StreamType") py::enum_<StreamMgr::StreamType>(smgr, "StreamType")
.value("input", StreamMgr::StreamType::input) .value("input", StreamMgr::StreamType::input)
.value("output", StreamMgr::StreamType::output); .value("output", StreamMgr::StreamType::output);
smgr.def("startStream", &StreamMgr::startStream); smgr.def("startStream", &StreamMgr::startStream);
smgr.def("stopStream", &StreamMgr::stopStream); smgr.def("stopStream", &StreamMgr::stopStream);
smgr.def_static("getInstance", []() { smgr.def_static("getInstance", []() {
return std::unique_ptr<StreamMgr, py::nodelete>(&StreamMgr::getInstance()); return std::unique_ptr<StreamMgr, py::nodelete>(&StreamMgr::getInstance());
}); });
smgr.def("stopAllStreams", &StreamMgr::stopAllStreams); smgr.def("stopAllStreams", &StreamMgr::stopAllStreams);
smgr.def("setSiggen", &StreamMgr::setSiggen); smgr.def("setSiggen", &StreamMgr::setSiggen);
@ -32,11 +32,13 @@ void init_streammgr(py::module &m) {
smgr.def("isStreamRunningOK", &StreamMgr::isStreamRunningOK); smgr.def("isStreamRunningOK", &StreamMgr::isStreamRunningOK);
smgr.def("isStreamRunning", &StreamMgr::isStreamRunning); smgr.def("isStreamRunning", &StreamMgr::isStreamRunning);
smgr.def("getDaq", &StreamMgr::getDaq, py::return_value_policy::reference); smgr.def("getDaq", &StreamMgr::getDaq, py::return_value_policy::reference);
smgr.def("rescanDAQDevices", [](StreamMgr& smgr, bool background) { smgr.def(
// A pure C++ callback is the second argument to rescanDAQDevices, which "rescanDAQDevices",
// cannot be wrapped to Pybind11. Only the one without callback is [](StreamMgr &smgr, bool background) {
// forwarded here to Python code. // A pure C++ callback is the second argument to rescanDAQDevices, which
smgr.rescanDAQDevices(background); // cannot be wrapped to Pybind11. Only the one without callback is
// forwarded here to Python code.
smgr.rescanDAQDevices(background);
}, },
py::arg("background")=false); py::arg("background") = false);
} }

@ -1 +1 @@
Subproject commit b5727433d342afca53aca0ee16ecf1caaa661821 Subproject commit 3865a0520d577ac293d88c4fd726a41bda949869