Code cleanup. RescanDaqdevices changed API. DaqApi string returns only apiname. Added comments, added Doxygen groups, exported FFT wisdom load / store to Python. SLM stores reference level squared. Added comments on SLM Lpeak, Leq and Lmax, a lot of using rte = std::runtime_error, added Window string conversion, Pybind11 enum no longer exports values, added cpp_enum to convert Qty to an enumerated value in C++ code. Removed class and methods to fill comboboxes. Does not belong in LASP, but in ACME instead. DeviceInfo has operator string().

This commit is contained in:
Anne de Jong 2022-09-22 10:18:38 +02:00
parent b629edde4c
commit b200b465f6
24 changed files with 266 additions and 139 deletions

View File

@ -3,12 +3,9 @@
LASP: Library for Acoustic Signal Processing LASP: Library for Acoustic Signal Processing
""" """
from .lasp_common import (P_REF, FreqWeighting, TimeWeighting, getTime,
getFreq, Qty, SIQtys, Window, lasp_shelve,
this_lasp_shelve, W_REF, U_REF, I_REF, dBFS_REF,
AvType)
from .lasp_cpp import * from .lasp_cpp import *
import lasp.lasp_cpp
from .lasp_common import *
__version__ = lasp_cpp.__version__ __version__ = lasp_cpp.__version__
# from .lasp_imptube import * # TwoMicImpedanceTube # from .lasp_imptube import * # TwoMicImpedanceTube

View File

@ -125,7 +125,7 @@ public:
return (apiname == other.apiname && apicode == other.apicode && return (apiname == other.apiname && apicode == other.apicode &&
api_specific_subcode == other.api_specific_subcode); api_specific_subcode == other.api_specific_subcode);
} }
operator string() const { return apiname + ", code: " + to_string(apicode); } operator string() const { return apiname; }
static std::vector<DaqApi> getAvailableApis(); static std::vector<DaqApi> getAvailableApis();
}; };
@ -160,12 +160,9 @@ public:
/** /**
* @brief Possible physical quantities that are recorded. * @brief Possible physical quantities that are recorded.
*/ */
enum class Qty { enum class Qty { Number, AcousticPressure, Voltage, UserDefined };
Number,
AcousticPressure, DaqChannel() = default;
Voltage,
UserDefined
};
/** /**
* @brief Whether the channel is enabled. * @brief Whether the channel is enabled.

View File

@ -11,9 +11,7 @@ using std::cerr;
using std::endl; using std::endl;
using rte = std::runtime_error; using rte = std::runtime_error;
InDataHandler::InDataHandler(StreamMgr &mgr) : _mgr(mgr) { InDataHandler::InDataHandler(StreamMgr &mgr) : _mgr(mgr) { DEBUGTRACE_ENTER; }
DEBUGTRACE_ENTER;
}
void InDataHandler::start() { void InDataHandler::start() {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
_mgr.addInDataHandler(*this); _mgr.addInDataHandler(*this);
@ -57,9 +55,13 @@ void StreamMgr::checkRightThread() const {
} }
#endif #endif
void StreamMgr::rescanDAQDevices(std::function<void()> callback, void StreamMgr::rescanDAQDevices(bool background,
bool background) { std::function<void()> callback) {
checkRightThread(); checkRightThread();
if (_inputStream || _outputStream) {
throw rte("Rescanning DAQ devices only possible when no stream is running");
}
_devices.clear();
auto &pool = getPool(); auto &pool = getPool();
if (background) { if (background) {
pool.push_task(&StreamMgr::rescanDAQDevices_impl, this, callback); pool.push_task(&StreamMgr::rescanDAQDevices_impl, this, callback);
@ -192,9 +194,9 @@ void StreamMgr::startStream(const DaqConfiguration &config) {
{ {
std::scoped_lock lck(_devices_mtx); std::scoped_lock lck(_devices_mtx);
auto devinfo2 = auto devinfo2 = std::find_if(
std::find_if(_devices.cbegin(), _devices.cend(), _devices.cbegin(), _devices.cend(),
[&config](const DeviceInfo& d) { return config.match(d); }); [&config](const DeviceInfo &d) { return config.match(d); });
if (devinfo2 == std::cend(_devices)) { if (devinfo2 == std::cend(_devices)) {
throw rte("Could not find a device with name " + config.device_name + throw rte("Could not find a device with name " + config.device_name +
" in list of devices."); " in list of devices.");
@ -212,7 +214,7 @@ void StreamMgr::startStream(const DaqConfiguration &config) {
if (!isInput && !isOutput) { if (!isInput && !isOutput) {
throw rte("Neither input, nor output channels enabled for " throw rte("Neither input, nor output channels enabled for "
"stream. Cannotr start."); "stream. Cannot start.");
} }
if ((isDuplex || isInput) && _inputStream) { if ((isDuplex || isInput) && _inputStream) {

View File

@ -130,7 +130,7 @@ public:
* @return A copy of the internal stored list of devices * @return A copy of the internal stored list of devices
*/ */
std::vector<DeviceInfo> getDeviceInfo() const { std::vector<DeviceInfo> getDeviceInfo() const {
std::scoped_lock lck(const_cast<std::mutex&>(_devices_mtx)); std::scoped_lock lck(const_cast<std::mutex &>(_devices_mtx));
return _devices; return _devices;
} }
@ -138,16 +138,16 @@ public:
* @brief Triggers a background scan of the DAQ devices, which updates the * @brief Triggers a background scan of the DAQ devices, which updates the
* internally stored list of devices. * internally stored list of devices.
* *
* @param callback Function to call when complete.
* @param background Perform searching for DAQ devices in the background. If * @param background Perform searching for DAQ devices in the background. If
* set to true, the function returns immediately. * set to true, the function returns immediately.
* @param callback Function to call when complete.
*/ */
void void
rescanDAQDevices(std::function<void()> callback = std::function<void()>(), rescanDAQDevices(bool background = false,
bool background = false); std::function<void()> callback = std::function<void()>());
/** /**
* @brief Start a stream based on given configuration. * @brief Start a stream based on given configuration.
* *
* @param config Configuration of a device * @param config Configuration of a device
*/ */

View File

@ -1,10 +1,12 @@
#include <optional> /* #define DEBUGTRACE_ENABLED */
#define DEBUGTRACE_ENABLED
#include "debugtrace.hpp" #include "debugtrace.hpp"
#include <optional>
#include "lasp_avpowerspectra.h" #include "lasp_avpowerspectra.h"
#include <cmath> #include <cmath>
using std::runtime_error; using rte = std::runtime_error;
using std::cerr;
using std::endl;
PowerSpectra::PowerSpectra(const us nfft, const Window::WindowType w) PowerSpectra::PowerSpectra(const us nfft, const Window::WindowType w)
: PowerSpectra(Window::create(w, nfft)) {} : PowerSpectra(Window::create(w, nfft)) {}
@ -35,7 +37,7 @@ arma::Cube<c> PowerSpectra::compute(const dmat &input) {
_scale_fac * (rfft.col(i) % arma::conj(rfft.col(j))); _scale_fac * (rfft.col(i) % arma::conj(rfft.col(j)));
output.slice(j).col(i)(0) /= 2; output.slice(j).col(i)(0) /= 2;
output.slice(j).col(i)(nfft/2) /= 2; output.slice(j).col(i)(nfft / 2) /= 2;
} }
} }
return output; return output;
@ -48,13 +50,13 @@ AvPowerSpectra::AvPowerSpectra(const us nfft, const Window::WindowType w,
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
if (overlap_percentage >= 100 || overlap_percentage < 0) { if (overlap_percentage >= 100 || overlap_percentage < 0) {
throw runtime_error("Overlap percentage should be >= 0 and < 100"); throw rte("Overlap percentage should be >= 0 and < 100");
} }
_overlap_keep = (nfft * overlap_percentage) / 100; _overlap_keep = (nfft * overlap_percentage) / 100;
if (_overlap_keep >= nfft) { if (_overlap_keep >= nfft) {
throw runtime_error("Overlap is too high. Results in no jump. Please " throw rte("Overlap is too high. Results in no jump. Please "
"choose a smaller overlap percentage or a higher nfft"); "choose a smaller overlap percentage or a higher nfft");
} }
if (time_constant < 0) { if (time_constant < 0) {
_mode = Mode::Averaging; _mode = Mode::Averaging;
@ -67,24 +69,27 @@ AvPowerSpectra::AvPowerSpectra(const us nfft, const Window::WindowType w,
} }
std::optional<arma::cx_cube> AvPowerSpectra::compute(const dmat &timedata) { std::optional<arma::cx_cube> AvPowerSpectra::compute(const dmat &timedata) {
DEBUGTRACE_ENTER;
_timeBuf.push(timedata); _timeBuf.push(timedata);
std::optional<arma::cx_cube> res; std::optional<arma::cx_cube> res;
us i = 0;
while (auto samples = _timeBuf.pop(_ps.nfft, _overlap_keep)) { while (auto samples = _timeBuf.pop(_ps.nfft, _overlap_keep)) {
/* DEBUGTRACE_PRINT((int)_mode); */
switch (_mode) { switch (_mode) {
case (Mode::Spectrogram): { case (Mode::Spectrogram): {
res.emplace(_ps.compute(samples.value())); _est = _ps.compute(samples.value());
} break; } break;
case (Mode::Averaging): { case (Mode::Averaging): {
n_averages++; n_averages++;
if (n_averages == 1) { if (n_averages == 1) {
_est = _ps.compute(samples.value()); _est = _ps.compute(samples.value());
} else { } else {
_est = _est * (n_averages - 1) / n_averages + _est = _est * (static_cast<d>(n_averages - 1) / n_averages) +
_ps.compute(samples.value()) / n_averages; _ps.compute(samples.value()) / n_averages;
} }
res = _est;
} break; } break;
case (Mode::Leaking): { case (Mode::Leaking): {
if (arma::size(_est) == arma::size(0, 0, 0)) { if (arma::size(_est) == arma::size(0, 0, 0)) {
@ -92,11 +97,14 @@ std::optional<arma::cx_cube> AvPowerSpectra::compute(const dmat &timedata) {
} else { } else {
_est = _alpha * _est + (1 - _alpha) * _ps.compute(samples.value()); _est = _alpha * _est + (1 - _alpha) * _ps.compute(samples.value());
} }
res = _est;
} break; } break;
} // end switch mode } // end switch mode
i++;
} }
return res; if(i>0) {
return _est;
}
return std::nullopt;
} }
std::optional<arma::cx_cube> AvPowerSpectra::get_est() { std::optional<arma::cx_cube> AvPowerSpectra::get_est() {
if (_est.n_cols > 0) if (_est.n_cols > 0)

View File

@ -7,10 +7,13 @@
#include <optional> #include <optional>
/** \defgroup dsp Digital Signal Processing utilities /** \defgroup dsp Digital Signal Processing utilities
* \ingroup dsp
* @{ * @{
/** */
* @brief Computes single-sided cross-power spectra for a group of channels
/**
* @brief Computes single-sided cross-power spectra for a group of channels.
* Only a single block of length fft, no averaging. This class should normally
* not be used. Please refer to AvPowerSpectra instead.
*/ */
class PowerSpectra { class PowerSpectra {
public: public:

View File

@ -94,7 +94,7 @@ void BiquadBank::setGains(const vd &gains) {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
const us nfilters = _filters.size(); const us nfilters = _filters.size();
if (gains.size() != nfilters) { if (gains.size() != nfilters) {
throw runtime_error("Invalid number of gain values given."); throw rte("Invalid number of gain values given.");
} }
_gains = gains; _gains = gains;
} }

View File

@ -1,6 +1,11 @@
#pragma once #pragma once
#include "lasp_filter.h" #include "lasp_filter.h"
/**
* \ingroup dsp
* @{
*/
/** /**
* @brief A set of Biquad filters in series. * @brief A set of Biquad filters in series.
*/ */
@ -74,3 +79,4 @@ public:
void reset() override final; void reset() override final;
}; };
/** @} */

View File

@ -8,7 +8,7 @@
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
#include <memory> #include <memory>
#include <cassert> #include <cassert>
#define DEBUGTRACE_ENABLED /* #define DEBUGTRACE_ENABLED */
#include "lasp_fft.h" #include "lasp_fft.h"
#include "debugtrace.hpp" #include "debugtrace.hpp"
#include "lasp_config.h" #include "lasp_config.h"
@ -152,22 +152,29 @@ dmat Fft::ifft(const cmat &freqdata) {
return res; return res;
} }
void load_fft_wisdom(const char *wisdom) { void Fft::load_fft_wisdom(const std::string& wisdom) {
#if LASP_FFT_BACKEND == Armadillo #if LASP_FFT_BACKEND == Armadillo
#elif LASP_FFT_BACKEND == FFTW #elif LASP_FFT_BACKEND == FFTW
if (wisdom) { if (wisdom.length() > 0) {
int rv = fftw_import_wisdom_from_string(wisdom); int rv = fftw_import_wisdom_from_string(wisdom.c_str());
if (rv != 1) { if (rv != 1) {
fprintf(stderr, "Error loading FFTW wisdom"); throw rte("Error loading FFTW wisdom");
} }
} }
#endif #endif
} }
char *store_fft_wisdom() { std::string Fft::store_fft_wisdom() {
#if LASP_FFT_BACKEND == Armadillo #if LASP_FFT_BACKEND == Armadillo
return NULL; return "";
#elif LASP_FFT_BACKEND == FFTW #elif LASP_FFT_BACKEND == FFTW
return fftw_export_wisdom_to_string(); // It is not possible to let FFTW directly return a C++ string. We have to
// put it in this container by copying in. Not a good solution if this has to
// happen often.
// Fortunately, this function is only called at the end of the program.
char* wis = fftw_export_wisdom_to_string();
std::string res {wis};
free(wis);
return res;
#endif #endif
} }

View File

@ -88,4 +88,20 @@ class Fft {
*/ */
dmat ifft(const cmat& freqdata); dmat ifft(const cmat& freqdata);
/**
* @brief Load FFT wisdom from a wisdom string. Function does nothing if
* FFT backend is not FFTW
*
* @param wisdom Wisdom string content.
*/
static void load_fft_wisdom(const std::string& wisdom);
/**
* @brief Return a string containing FFT wisdom storage. String is empty
* for backend != FFTW
*
* @return FFT wisdom string
*/
static std::string store_fft_wisdom();
}; };

View File

@ -32,9 +32,9 @@ class Sine : public Siggen {
*/ */
Sine(const d freq_Hz); Sine(const d freq_Hz);
~Sine() = default; ~Sine() = default;
virtual vd genSignalUnscaled(const us nframes) override; virtual vd genSignalUnscaled(const us nframes) override final;
void setFreq(const d newFreq); void setFreq(const d newFreq);
void resetImpl() override { phase=0; } void resetImpl() override final { phase=0; }
}; };
class Periodic: public Siggen { class Periodic: public Siggen {
protected: protected:
@ -42,7 +42,7 @@ class Periodic: public Siggen {
us _cur_pos = 0; us _cur_pos = 0;
public: public:
virtual vd genSignalUnscaled(const us nframes) override; virtual vd genSignalUnscaled(const us nframes) override final;
~Periodic() = default; ~Periodic() = default;
}; };

View File

@ -21,7 +21,7 @@ SLM::SLM(const d fs, const d Lref, const us downsampling_fac, const d tau,
// components of // components of
// single pole low pass // single pole low pass
// filter // filter
Lref(Lref), // Reference level Lrefsq(Lref*Lref), // Reference level
downsampling_fac(downsampling_fac), downsampling_fac(downsampling_fac),
// Initalize mean square // Initalize mean square
@ -127,15 +127,7 @@ vd SLM::run_single(vd work,const us i) {
Pmax(i) = std::max(Pmax(i), arma::max(work)); Pmax(i) = std::max(Pmax(i), arma::max(work));
// Convert to levels in dB // Convert to levels in dB
/* work.transform([&](d val) { */ work = 10*arma::log10((work+arma::datum::eps)/Lrefsq);
/* return 10 * log10((val */
/* + arma::datum::eps // Add a bit of machine epsilon to */
/* // the values to not compute -inf. */
/* ) / */
/* (Lref * Lref)); */
/* }); */
work = 10*arma::log10((work+arma::datum::eps)/(Lref*Lref));
return work; return work;
} }

View File

@ -4,6 +4,11 @@
#include <memory> #include <memory>
#include <optional> #include <optional>
/**
* \ingroup dsp
* @{
*/
/** /**
* @brief Sound Level Meter implementation that gives a result for each * @brief Sound Level Meter implementation that gives a result for each
* channel. A channel is the result of a filtered signal * channel. A channel is the result of a filtered signal
@ -25,7 +30,7 @@ class SLM {
d _alpha = -1; d _alpha = -1;
vd _sp_storage; vd _sp_storage;
d Lref; /// Reference value for computing decibels d Lrefsq; /// Square of reference value for computing decibels
us downsampling_fac; /// Every x'th sample is returned. us downsampling_fac; /// Every x'th sample is returned.
us cur_offset = 0; /// Storage for offset point in input arrays us cur_offset = 0; /// Storage for offset point in input arrays
/// ///
@ -38,15 +43,15 @@ public:
* @brief Public storage for the maximum signal power, after single pole * @brief Public storage for the maximum signal power, after single pole
* low-pass filter. * low-pass filter.
*/ */
vd Pmax; /// Storage for maximum computed signal power so far. vd Pmax; /// Storage for maximum computed signal power so far.
/** /**
* @brief Public storage for the peak signal power, before single pole * @brief Public storage for the peak signal power, before single pole
* low-pass filter. * low-pass filter.
*/ */
vd Ppeak; vd Ppeak;
us N = 0; /// Counter for the number of time samples counted that came us N = 0; /// Counter for the number of time samples counted that came
/// in; /// in;
/** /**
* @brief Initialize a Sound Level Meter * @brief Initialize a Sound Level Meter
@ -118,13 +123,33 @@ public:
* *
* @param input Raw input data * @param input Raw input data
* *
* @return Filtered level data. * @return Filtered level data for each filtered channel.
*/ */
dmat run(const vd &input); dmat run(const vd &input);
vd Lpeak() const { return 10*arma::log10(Ppeak/Lref);};
vd Leq() const { return 10*arma::log10(Pm/Lref);}; /**
vd Lmax() const { return 10*arma::log10(Pmax/Lref);}; * @brief Calculates peak levels measured for each filter channel. The peak
* level is just the highest instantaneous measured power value.
*
* @return vector of peak level values
*/
vd Lpeak() const { return 10 * arma::log10(Ppeak / Lrefsq); };
/**
* @brief Calculates equivalent (time-averaged) levels measured for each
* filter channel
*
* @return vector of equivalent level values
*/
vd Leq() const { return 10 * arma::log10(Pm / Lrefsq); };
/**
* @brief Calculates max levels measured for each filter channel. The max
* value is the maximum time-filtered (Fast / Slow) power level.
*
* @return vector of max level values
*/
vd Lmax() const { return 10 * arma::log10(Pmax / Lrefsq); };
private: private:
vd run_single(vd input, const us filter_no); vd run_single(vd input, const us filter_no);
}; };
/** @} */

View File

@ -1,4 +1,4 @@
#define DEBUGTRACE_ENABLED /* #define DEBUGTRACE_ENABLED */
#include "lasp_thread.h" #include "lasp_thread.h"
#include "BS_thread_pool.hpp" #include "BS_thread_pool.hpp"
#include "debugtrace.hpp" #include "debugtrace.hpp"

View File

@ -8,7 +8,7 @@
#include <optional> #include <optional>
#include <stdexcept> #include <stdexcept>
using std::runtime_error; using rte = std::runtime_error;
class TimeBufferImp { class TimeBufferImp {
/** /**
@ -22,10 +22,11 @@ public:
_storage.clear(); _storage.clear();
} }
void push(const dmat &mat) { void push(const dmat &mat) {
DEBUGTRACE_ENTER;
#if LASP_DEBUG==1 #if LASP_DEBUG==1
if(!_storage.empty()) { if(!_storage.empty()) {
if(mat.n_cols != _storage.front().n_cols) { if(mat.n_cols != _storage.front().n_cols) {
throw runtime_error("Invalid number of channels in mat"); throw rte("Invalid number of channels in mat");
} }
} }
#endif #endif
@ -39,7 +40,7 @@ public:
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
if (keep > nsamples) if (keep > nsamples)
throw runtime_error("keep should be <= nsamples"); throw rte("keep should be <= nsamples");
if (nsamples <= n_frames()) { if (nsamples <= n_frames()) {

View File

@ -2,30 +2,36 @@
// //
// Author: J.A. de Jong - ASCEE // Author: J.A. de Jong - ASCEE
// //
/* #define DEBUGTRACE_ENABLED */
#include "debugtrace.hpp"
#include "lasp_window.h" #include "lasp_window.h"
using rte = std::runtime_error;
using std::cerr;
using std::endl;
// Safe some typing. Linspace form 0 up to (and NOT including N). // Safe some typing. Linspace form 0 up to (and NOT including N).
#define lin0N arma::linspace(0, N - 1, N) #define lin0N arma::linspace(0, N - 1, N)
vd Window::hann(const us N) { vd Window::hann(const us N) {
return arma::pow(arma::sin(number_pi * lin0N), 2); return arma::pow(arma::sin((arma::datum::pi/N) * lin0N), 2);
} }
vd Window::hamming(const us N) { vd Window::hamming(const us N) {
d alpha = 25.0 / 46.0; d alpha = 25.0 / 46.0;
return alpha - (1 - alpha) * arma::cos(2 * number_pi * lin0N / (N - 1)); return alpha - (1 - alpha) * arma::cos(2 * number_pi * lin0N / N);
} }
vd Window::blackman(const us N) { vd Window::blackman(const us N) {
d a0 = 7938. / 18608.; d a0 = 7938. / 18608.;
d a1 = 9240. / 18608.; d a1 = 9240. / 18608.;
d a2 = 1430. / 18608.; d a2 = 1430. / 18608.;
return a0 - a1 * d_cos(2 * number_pi * lin0N / (N - 1)) + return a0 - a1 * d_cos((2 * number_pi/N) * lin0N) +
a2 * d_cos(4 * number_pi * lin0N / (N - 1)); a2 * d_cos((4 * number_pi / N)* lin0N );
} }
vd Window::rectangular(const us N) { return arma::ones(N); } vd Window::rectangular(const us N) { return arma::ones(N); }
vd Window::bartlett(const us N) { vd Window::bartlett(const us N) {
return 1 - arma::abs(2 * (lin0N - (N - 1) / 2.) / (N - 1)); return 1 - arma::abs(2 * (lin0N - (N - 1) / 2.) / N);
} }
vd Window::create(const WindowType w, const us N) { vd Window::create(const WindowType w, const us N) {

View File

@ -15,6 +15,38 @@ class Window {
Blackman = 4, Blackman = 4,
}; };
/**
* @brief Convert a window type enum to its equivalent text.
*
* @param wt The window type to convert
*
* @return Text string
*/
static std::string toText(const WindowType wt) {
switch(wt) {
case(WindowType::Hann): {
return "Hann";
}
break;
case(WindowType::Hamming): {
return "Hamming";
}
break;
case(WindowType::Rectangular): {
return "Rectangular";
}
break;
case(WindowType::Bartlett): {
return "Bartlett";
}
break;
case(WindowType::Blackman): {
return "Blackman";
}
break;
}
throw std::runtime_error("Not implemenented window type");
}
/** /**
* @brief Dispatcher: create a window based on enum type and len * @brief Dispatcher: create a window based on enum type and len

View File

@ -6,7 +6,7 @@ from collections import namedtuple
from dataclasses import dataclass from dataclasses import dataclass
from dataclasses_json import dataclass_json from dataclasses_json import dataclass_json
from enum import Enum, unique, auto from enum import Enum, unique, auto
from .lasp_cpp import Window as wWindow from .lasp_cpp import DaqChannel
""" """
Common definitions used throughout the code. Common definitions used throughout the code.
@ -14,7 +14,7 @@ Common definitions used throughout the code.
__all__ = [ __all__ = [
'P_REF', 'FreqWeighting', 'TimeWeighting', 'getTime', 'getFreq', 'Qty', 'P_REF', 'FreqWeighting', 'TimeWeighting', 'getTime', 'getFreq', 'Qty',
'SIQtys', 'Window', 'SIQtys',
'lasp_shelve', 'this_lasp_shelve', 'W_REF', 'U_REF', 'I_REF', 'dBFS_REF', 'lasp_shelve', 'this_lasp_shelve', 'W_REF', 'U_REF', 'I_REF', 'dBFS_REF',
'AvType' 'AvType'
] ]
@ -39,6 +39,7 @@ U_REF = 5e-8 # 50 nano meter / s
# hence this is the reference level as specified below. # hence this is the reference level as specified below.
dBFS_REF = 0.5*2**0.5 # Which level would be -3.01 dBFS dBFS_REF = 0.5*2**0.5 # Which level would be -3.01 dBFS
@unique @unique
class AvType(Enum): class AvType(Enum):
"""Specificying the type of data, for adding and removing callbacks from """Specificying the type of data, for adding and removing callbacks from
@ -52,7 +53,6 @@ class AvType(Enum):
# Both input as well as output # Both input as well as output
audio_duplex = (2, 'duplex') audio_duplex = (2, 'duplex')
# video = 4
@dataclass_json @dataclass_json
@ -67,6 +67,7 @@ class Qty:
# yet able to compute `dBPa's' # yet able to compute `dBPa's'
level_ref_name: object level_ref_name: object
level_ref_value: object level_ref_value: object
cpp_enum: DaqChannel.Qty
def __str__(self): def __str__(self):
return f'{self.name} [{self.unit_symb}]' return f'{self.name} [{self.unit_symb}]'
@ -91,14 +92,16 @@ class SIQtys(Enum):
unit_symb='-', unit_symb='-',
level_unit=('dBFS',), level_unit=('dBFS',),
level_ref_name=('Relative to full scale sine wave',), level_ref_name=('Relative to full scale sine wave',),
level_ref_value=(dBFS_REF,) level_ref_value=(dBFS_REF,),
cpp_enum = DaqChannel.Qty.Number
) )
AP = Qty(name='Acoustic Pressure', AP = Qty(name='Acoustic Pressure',
unit_name='Pascal', unit_name='Pascal',
unit_symb='Pa', unit_symb='Pa',
level_unit=('dB SPL','dBPa'), level_unit=('dB SPL','dBPa'),
level_ref_name=('2 micropascal', '1 pascal',), level_ref_name=('2 micropascal', '1 pascal',),
level_ref_value=(P_REF, 1) level_ref_value=(P_REF, 1),
cpp_enum = DaqChannel.Qty.AcousticPressure
) )
V = Qty(name='Voltage', V = Qty(name='Voltage',
@ -107,6 +110,7 @@ class SIQtys(Enum):
level_unit=('dBV',), # dBV level_unit=('dBV',), # dBV
level_ref_name=('1V',), level_ref_name=('1V',),
level_ref_value=(1.0,), level_ref_value=(1.0,),
cpp_enum = DaqChannel.Qty.Voltage
) )
@staticmethod @staticmethod
@ -268,33 +272,6 @@ class this_lasp_shelve(Shelve):
node = platform.node() node = platform.node()
return os.path.join(lasp_appdir, f'{node}_config.shelve') return os.path.join(lasp_appdir, f'{node}_config.shelve')
@unique
class Window(Enum):
hann = (wWindow.Hann, 'Hann')
hamming = (wWindow.Hamming, 'Hamming')
rectangular = (wWindow.Rectangular, 'Rectangular')
bartlett = (wWindow.Bartlett, 'Bartlett')
blackman = (wWindow.Blackman, 'Blackman')
@staticmethod
def fillComboBox(cb):
"""
Fill Windows to a combobox
Args:
cb: QComboBox to fill
"""
cb.clear()
for w in list(Window):
cb.addItem(w.value[1], w)
cb.setCurrentIndex(0)
@staticmethod
def getCurrent(cb):
return list(Window)[cb.currentIndex()]
class TimeWeighting: class TimeWeighting:
none = (-1, 'Raw (no time weighting)') none = (-1, 'Raw (no time weighting)')
uufast = (1e-4, '0.1 ms') uufast = (1e-4, '0.1 ms')

View File

@ -18,6 +18,11 @@
* *
* */ * */
/**
* \defgroup pybind Pybind11 wrapper code
* @{
*
*/
namespace py = pybind11; namespace py = pybind11;
void init_dsp(py::module &m); void init_dsp(py::module &m);
@ -45,3 +50,4 @@ PYBIND11_MODULE(lasp_cpp, m) {
m.attr("LASP_VERSION_MAJOR") = LASP_VERSION_MAJOR; m.attr("LASP_VERSION_MAJOR") = LASP_VERSION_MAJOR;
m.attr("LASP_VERSION_MINOR") = LASP_VERSION_MINOR; m.attr("LASP_VERSION_MINOR") = LASP_VERSION_MINOR;
} }
/** @} */

View File

@ -27,8 +27,7 @@ void init_daq(py::module &m) {
.value("threadError", Daq::StreamStatus::StreamError::threadError) .value("threadError", Daq::StreamStatus::StreamError::threadError)
.value("logicError", Daq::StreamStatus::StreamError::logicError) .value("logicError", Daq::StreamStatus::StreamError::logicError)
.value("apiSpecificError", .value("apiSpecificError",
Daq::StreamStatus::StreamError::apiSpecificError) Daq::StreamStatus::StreamError::apiSpecificError);
.export_values();
/// Daq /// Daq
daq.def("neninchannels", &Daq::neninchannels); daq.def("neninchannels", &Daq::neninchannels);

View File

@ -1,5 +1,6 @@
#include "lasp_daqconfig.h" #include "lasp_daqconfig.h"
#include "lasp_deviceinfo.h" #include "lasp_deviceinfo.h"
#include <algorithm>
#include <iostream> #include <iostream>
#include <pybind11/numpy.h> #include <pybind11/numpy.h>
#include <pybind11/stl.h> #include <pybind11/stl.h>
@ -12,17 +13,31 @@ void init_daqconfiguration(py::module &m) {
/// DataType /// DataType
py::class_<DataTypeDescriptor> dtype_desc(m, "DataTypeDescriptor"); py::class_<DataTypeDescriptor> dtype_desc(m, "DataTypeDescriptor");
dtype_desc.def_readonly("name", &DataTypeDescriptor::name); dtype_desc.def_readonly("name", &DataTypeDescriptor::name);
dtype_desc.def_readonly("sw", &DataTypeDescriptor::sw); dtype_desc.def_readonly("sw", &DataTypeDescriptor::sw);
dtype_desc.def_readonly("is_floating", &DataTypeDescriptor::is_floating); dtype_desc.def_readonly("is_floating", &DataTypeDescriptor::is_floating);
dtype_desc.def_readonly("dtype", &DataTypeDescriptor::dtype);
py::enum_<DataTypeDescriptor::DataType>(dtype_desc, "DataType") py::enum_<DataTypeDescriptor::DataType>(dtype_desc, "DataType")
.value("dtype_fl32", DataTypeDescriptor::DataType::dtype_fl32) .value("dtype_fl32", DataTypeDescriptor::DataType::dtype_fl32)
.value("dtype_fl64", DataTypeDescriptor::DataType::dtype_fl64) .value("dtype_fl64", DataTypeDescriptor::DataType::dtype_fl64)
.value("dtype_int8", DataTypeDescriptor::DataType::dtype_int8) .value("dtype_int8", DataTypeDescriptor::DataType::dtype_int8)
.value("dtype_int16", DataTypeDescriptor::DataType::dtype_int16) .value("dtype_int16", DataTypeDescriptor::DataType::dtype_int16)
.value("dtype_int32", DataTypeDescriptor::DataType::dtype_int32) .value("dtype_int32", DataTypeDescriptor::DataType::dtype_int32);
.export_values();
dtype_desc.def_static("toStr", [](const DataTypeDescriptor::DataType d) {
return dtype_map.at(d).name;
});
dtype_desc.def_static("fromStr", [](const std::string &dtype_str) {
decltype(dtype_map.cbegin()) d = std::find_if(
dtype_map.cbegin(), dtype_map.cend(),
[&dtype_str](const auto &d) { return d.second.name == dtype_str; });
if (d != dtype_map.cend()) {
return d->first;
}
throw std::runtime_error(dtype_str + " not found in list of data types");
});
dtype_desc.def_readonly("dtype", &DataTypeDescriptor::dtype); dtype_desc.def_readonly("dtype", &DataTypeDescriptor::dtype);
@ -36,15 +51,8 @@ void init_daqconfiguration(py::module &m) {
/// DaqChannel, DaqConfiguration /// DaqChannel, DaqConfiguration
py::class_<DaqConfiguration> daqconfig(m, "DaqConfiguration"); py::class_<DaqConfiguration> daqconfig(m, "DaqConfiguration");
py::enum_<DaqChannel::Qty>(daqconfig, "Qty")
.value("Number", DaqChannel::Qty::Number)
.value("AcousticPressure", DaqChannel::Qty::AcousticPressure)
.value("Voltage", DaqChannel::Qty::Voltage)
.value("UserDefined", DaqChannel::Qty::UserDefined)
.export_values();
dtype_desc.def_readonly("dtype", &DataTypeDescriptor::dtype);
py::class_<DaqChannel> daqchannel(m, "DaqChannel"); py::class_<DaqChannel> daqchannel(m, "DaqChannel");
daqchannel.def(py::init<>());
daqchannel.def_readwrite("enabled", &DaqChannel::enabled); daqchannel.def_readwrite("enabled", &DaqChannel::enabled);
daqchannel.def_readwrite("name", &DaqChannel::name); daqchannel.def_readwrite("name", &DaqChannel::name);
daqchannel.def_readwrite("sensitivity", &DaqChannel::sensitivity); daqchannel.def_readwrite("sensitivity", &DaqChannel::sensitivity);
@ -53,12 +61,19 @@ void init_daqconfiguration(py::module &m) {
daqchannel.def_readwrite("digitalHighpassCutOn", daqchannel.def_readwrite("digitalHighpassCutOn",
&DaqChannel::digitalHighPassCutOn); &DaqChannel::digitalHighPassCutOn);
daqchannel.def_readwrite("rangeIndex", &DaqChannel::rangeIndex); daqchannel.def_readwrite("rangeIndex", &DaqChannel::rangeIndex);
py::enum_<DaqChannel::Qty>(daqchannel, "Qty")
.value("Number", DaqChannel::Qty::Number)
.value("AcousticPressure", DaqChannel::Qty::AcousticPressure)
.value("Voltage", DaqChannel::Qty::Voltage)
.value("UserDefined", DaqChannel::Qty::UserDefined);
daqchannel.def_readwrite("qty", &DaqChannel::qty); daqchannel.def_readwrite("qty", &DaqChannel::qty);
/// DaqConfiguration /// DaqConfiguration
daqconfig.def(py::init<>()); daqconfig.def(py::init<>());
daqconfig.def(py::init<const DeviceInfo &>()); daqconfig.def(py::init<const DeviceInfo &>());
daqconfig.def_readwrite("api", &DaqConfiguration::api);
daqconfig.def_readwrite("device_name", &DaqConfiguration::device_name);
daqconfig.def_readwrite("sampleRateIndex", daqconfig.def_readwrite("sampleRateIndex",
&DaqConfiguration::sampleRateIndex); &DaqConfiguration::sampleRateIndex);

View File

@ -10,6 +10,7 @@ void init_deviceinfo(py::module& m) {
/// DeviceInfo /// DeviceInfo
py::class_<DeviceInfo> devinfo(m, "DeviceInfo"); py::class_<DeviceInfo> devinfo(m, "DeviceInfo");
devinfo.def("__str__", [](const DeviceInfo& d) {return d.device_name;});
devinfo.def_readonly("api", &DeviceInfo::api); devinfo.def_readonly("api", &DeviceInfo::api);
devinfo.def_readonly("device_name", &DeviceInfo::device_name); devinfo.def_readonly("device_name", &DeviceInfo::device_name);

View File

@ -1,27 +1,41 @@
#include <carma> #include <carma>
#include "lasp_avpowerspectra.h"
#include "lasp_biquadbank.h" #include "lasp_biquadbank.h"
#include "lasp_fft.h"
#include "lasp_siggen.h" #include "lasp_siggen.h"
#include "lasp_siggen_impl.h" #include "lasp_siggen_impl.h"
#include "lasp_slm.h" #include "lasp_slm.h"
#include "lasp_avpowerspectra.h"
#include "lasp_window.h" #include "lasp_window.h"
#include "lasp_fft.h"
#include <iostream> #include <iostream>
#include <pybind11/pybind11.h> #include <pybind11/pybind11.h>
using std::cerr; using std::cerr;
namespace py = pybind11; namespace py = pybind11;
/**
* \ingroup pybind
* @{
*
*/
/**
* @brief Initialize DSP code
*
* @param m The Python module to add classes and methods to
*/
void init_dsp(py::module &m) { void init_dsp(py::module &m) {
py::class_<Fft> fft(m, "Fft"); py::class_<Fft> fft(m, "Fft");
fft.def(py::init<us>()); fft.def(py::init<us>());
fft.def("fft", py::overload_cast<const vd&>(&Fft::fft)); fft.def("fft", py::overload_cast<const vd &>(&Fft::fft));
fft.def("fft", py::overload_cast<const dmat&>(&Fft::fft)); fft.def("fft", py::overload_cast<const dmat &>(&Fft::fft));
fft.def("ifft", py::overload_cast<const vc&>(&Fft::ifft)); fft.def("ifft", py::overload_cast<const vc &>(&Fft::ifft));
fft.def("ifft", py::overload_cast<const cmat&>(&Fft::ifft)); fft.def("ifft", py::overload_cast<const cmat &>(&Fft::ifft));
fft.def_static("load_fft_wisdom", &Fft::load_fft_wisdom);
fft.def_static("store_fft_wisdom", &Fft::store_fft_wisdom);
/// Window
py::class_<Window> w(m, "Window"); py::class_<Window> w(m, "Window");
py::enum_<Window::WindowType>(w, "WindowType") py::enum_<Window::WindowType>(w, "WindowType")
@ -29,9 +43,11 @@ void init_dsp(py::module &m) {
.value("Hamming", Window::WindowType::Hamming) .value("Hamming", Window::WindowType::Hamming)
.value("Bartlett", Window::WindowType::Bartlett) .value("Bartlett", Window::WindowType::Bartlett)
.value("Blackman", Window::WindowType::Bartlett) .value("Blackman", Window::WindowType::Bartlett)
.value("Rectangular", Window::WindowType::Rectangular) .value("Rectangular", Window::WindowType::Rectangular);
.export_values();
w.def_static("toTxt", &Window::toText);
/// Siggen
py::class_<Siggen, std::shared_ptr<Siggen>> siggen(m, "Siggen"); py::class_<Siggen, std::shared_ptr<Siggen>> siggen(m, "Siggen");
siggen.def("setLevel", &Siggen::setLevel, siggen.def("setLevel", &Siggen::setLevel,
"Set the level of the signal generator"); "Set the level of the signal generator");
@ -39,6 +55,7 @@ void init_dsp(py::module &m) {
py::class_<Sine, std::shared_ptr<Sine>> sw(m, "Sine", siggen); py::class_<Sine, std::shared_ptr<Sine>> sw(m, "Sine", siggen);
sw.def(py::init<const d>()); sw.def(py::init<const d>());
/// SeriesBiquad
py::class_<SeriesBiquad> sbq(m, "SeriesBiquad"); py::class_<SeriesBiquad> sbq(m, "SeriesBiquad");
sbq.def(py::init<const vd &>()); sbq.def(py::init<const vd &>());
sbq.def("filter", [](SeriesBiquad &s, const vd &input) { sbq.def("filter", [](SeriesBiquad &s, const vd &input) {
@ -46,15 +63,28 @@ void init_dsp(py::module &m) {
s.filter(res); s.filter(res);
return res; return res;
}); });
/// BiquadBank
py::class_<BiquadBank> bqb(m, "BiquadBank");
bqb.def(py::init<const dmat&,const vd*>());
bqb.def("setGains",&BiquadBank::setGains);
bqb.def("filter",&BiquadBank::filter);
/// PowerSpectra
py::class_<PowerSpectra> ps(m, "PowerSpectra"); py::class_<PowerSpectra> ps(m, "PowerSpectra");
ps.def(py::init<us, const Window::WindowType>()); ps.def(py::init<const us, const Window::WindowType>());
ps.def("compute", &PowerSpectra::compute); ps.def("compute", &PowerSpectra::compute);
/// AvPowerSpectra
py::class_<AvPowerSpectra> aps(m, "AvPowerSpectra"); py::class_<AvPowerSpectra> aps(m, "AvPowerSpectra");
/* ps.def(py::init<us, const Window::WindowType>()); */ aps.def(py::init<const us, const Window::WindowType, const d, const int>(),
/* ps.def("compute", &PowerSpectra::compute); */ py::arg("nfft"), py::arg("WindowType"), py::arg("overlap_percentage"),
py::arg("time_constant"));
aps.def("compute", [](AvPowerSpectra &aps, const dmat &timedata) {
std::optional<arma::cx_cube> res = aps.compute(timedata);
return res.value_or(arma::cx_cube(0,0,0));
}
);
py::class_<SLM> slm(m, "SLM"); py::class_<SLM> slm(m, "SLM");
@ -70,4 +100,8 @@ void init_dsp(py::module &m) {
slm.def_readonly("Pm", &SLM::Pm); slm.def_readonly("Pm", &SLM::Pm);
slm.def_readonly("Pmax", &SLM::Pmax); slm.def_readonly("Pmax", &SLM::Pmax);
slm.def_readonly("Ppeak", &SLM::Ppeak); slm.def_readonly("Ppeak", &SLM::Ppeak);
slm.def("Lpeak", &SLM::Lpeak);
slm.def("Leq", &SLM::Leq);
slm.def("Lmax", &SLM::Lmax);
} }
/** @} */

View File

@ -9,13 +9,14 @@ namespace py = pybind11;
void init_streammgr(py::module &m) { 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::unique_ptr<StreamMgr, py::nodelete>> smgr(
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);
.export_values();
smgr.def("startStream", &StreamMgr::startStream); smgr.def("startStream", &StreamMgr::startStream);
smgr.def("stopStream", &StreamMgr::stopStream); smgr.def("stopStream", &StreamMgr::stopStream);
@ -26,4 +27,6 @@ void init_streammgr(py::module &m) {
smgr.def("setSiggen", &StreamMgr::setSiggen); smgr.def("setSiggen", &StreamMgr::setSiggen);
smgr.def("getDeviceInfo", &StreamMgr::getDeviceInfo); smgr.def("getDeviceInfo", &StreamMgr::getDeviceInfo);
smgr.def("getStreamStatus", &StreamMgr::getStreamStatus);
smgr.def("isStreamRunningOK", &StreamMgr::isStreamRunningOK);
} }