diff --git a/src/lasp/__init__.py b/src/lasp/__init__.py index a101f99..8725df6 100644 --- a/src/lasp/__init__.py +++ b/src/lasp/__init__.py @@ -3,12 +3,9 @@ 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 * - +import lasp.lasp_cpp +from .lasp_common import * __version__ = lasp_cpp.__version__ # from .lasp_imptube import * # TwoMicImpedanceTube diff --git a/src/lasp/device/lasp_daqconfig.h b/src/lasp/device/lasp_daqconfig.h index f11fec4..fa377a9 100644 --- a/src/lasp/device/lasp_daqconfig.h +++ b/src/lasp/device/lasp_daqconfig.h @@ -125,7 +125,7 @@ public: return (apiname == other.apiname && apicode == other.apicode && api_specific_subcode == other.api_specific_subcode); } - operator string() const { return apiname + ", code: " + to_string(apicode); } + operator string() const { return apiname; } static std::vector getAvailableApis(); }; @@ -160,12 +160,9 @@ public: /** * @brief Possible physical quantities that are recorded. */ - enum class Qty { - Number, - AcousticPressure, - Voltage, - UserDefined - }; + enum class Qty { Number, AcousticPressure, Voltage, UserDefined }; + + DaqChannel() = default; /** * @brief Whether the channel is enabled. diff --git a/src/lasp/device/lasp_streammgr.cpp b/src/lasp/device/lasp_streammgr.cpp index 9b96c0e..3f817cf 100644 --- a/src/lasp/device/lasp_streammgr.cpp +++ b/src/lasp/device/lasp_streammgr.cpp @@ -11,9 +11,7 @@ using std::cerr; using std::endl; using rte = std::runtime_error; -InDataHandler::InDataHandler(StreamMgr &mgr) : _mgr(mgr) { - DEBUGTRACE_ENTER; -} +InDataHandler::InDataHandler(StreamMgr &mgr) : _mgr(mgr) { DEBUGTRACE_ENTER; } void InDataHandler::start() { DEBUGTRACE_ENTER; _mgr.addInDataHandler(*this); @@ -57,9 +55,13 @@ void StreamMgr::checkRightThread() const { } #endif -void StreamMgr::rescanDAQDevices(std::function callback, - bool background) { +void StreamMgr::rescanDAQDevices(bool background, + std::function callback) { checkRightThread(); + if (_inputStream || _outputStream) { + throw rte("Rescanning DAQ devices only possible when no stream is running"); + } + _devices.clear(); auto &pool = getPool(); if (background) { pool.push_task(&StreamMgr::rescanDAQDevices_impl, this, callback); @@ -192,9 +194,9 @@ void StreamMgr::startStream(const DaqConfiguration &config) { { std::scoped_lock lck(_devices_mtx); - auto devinfo2 = - std::find_if(_devices.cbegin(), _devices.cend(), - [&config](const DeviceInfo& d) { return config.match(d); }); + auto devinfo2 = std::find_if( + _devices.cbegin(), _devices.cend(), + [&config](const DeviceInfo &d) { return config.match(d); }); if (devinfo2 == std::cend(_devices)) { throw rte("Could not find a device with name " + config.device_name + " in list of devices."); @@ -212,7 +214,7 @@ void StreamMgr::startStream(const DaqConfiguration &config) { if (!isInput && !isOutput) { throw rte("Neither input, nor output channels enabled for " - "stream. Cannotr start."); + "stream. Cannot start."); } if ((isDuplex || isInput) && _inputStream) { diff --git a/src/lasp/device/lasp_streammgr.h b/src/lasp/device/lasp_streammgr.h index b182445..c003a0b 100644 --- a/src/lasp/device/lasp_streammgr.h +++ b/src/lasp/device/lasp_streammgr.h @@ -130,7 +130,7 @@ public: * @return A copy of the internal stored list of devices */ std::vector getDeviceInfo() const { - std::scoped_lock lck(const_cast(_devices_mtx)); + std::scoped_lock lck(const_cast(_devices_mtx)); return _devices; } @@ -138,16 +138,16 @@ public: * @brief Triggers a background scan of the DAQ devices, which updates the * internally stored list of devices. * - * @param callback Function to call when complete. * @param background Perform searching for DAQ devices in the background. If * set to true, the function returns immediately. + * @param callback Function to call when complete. */ void - rescanDAQDevices(std::function callback = std::function(), - bool background = false); + rescanDAQDevices(bool background = false, + std::function callback = std::function()); /** - * @brief Start a stream based on given configuration. + * @brief Start a stream based on given configuration. * * @param config Configuration of a device */ diff --git a/src/lasp/dsp/lasp_avpowerspectra.cpp b/src/lasp/dsp/lasp_avpowerspectra.cpp index a0b6c32..fc52a71 100644 --- a/src/lasp/dsp/lasp_avpowerspectra.cpp +++ b/src/lasp/dsp/lasp_avpowerspectra.cpp @@ -1,10 +1,12 @@ -#include -#define DEBUGTRACE_ENABLED +/* #define DEBUGTRACE_ENABLED */ #include "debugtrace.hpp" +#include #include "lasp_avpowerspectra.h" #include -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(Window::create(w, nfft)) {} @@ -35,7 +37,7 @@ arma::Cube PowerSpectra::compute(const dmat &input) { _scale_fac * (rfft.col(i) % arma::conj(rfft.col(j))); 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; @@ -48,13 +50,13 @@ AvPowerSpectra::AvPowerSpectra(const us nfft, const Window::WindowType w, DEBUGTRACE_ENTER; 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; if (_overlap_keep >= nfft) { - throw runtime_error("Overlap is too high. Results in no jump. Please " - "choose a smaller overlap percentage or a higher nfft"); + throw rte("Overlap is too high. Results in no jump. Please " + "choose a smaller overlap percentage or a higher nfft"); } if (time_constant < 0) { _mode = Mode::Averaging; @@ -67,24 +69,27 @@ AvPowerSpectra::AvPowerSpectra(const us nfft, const Window::WindowType w, } std::optional AvPowerSpectra::compute(const dmat &timedata) { + DEBUGTRACE_ENTER; _timeBuf.push(timedata); + std::optional res; + us i = 0; while (auto samples = _timeBuf.pop(_ps.nfft, _overlap_keep)) { + /* DEBUGTRACE_PRINT((int)_mode); */ switch (_mode) { case (Mode::Spectrogram): { - res.emplace(_ps.compute(samples.value())); + _est = _ps.compute(samples.value()); } break; case (Mode::Averaging): { n_averages++; if (n_averages == 1) { _est = _ps.compute(samples.value()); } else { - _est = _est * (n_averages - 1) / n_averages + + _est = _est * (static_cast(n_averages - 1) / n_averages) + _ps.compute(samples.value()) / n_averages; } - res = _est; } break; case (Mode::Leaking): { if (arma::size(_est) == arma::size(0, 0, 0)) { @@ -92,11 +97,14 @@ std::optional AvPowerSpectra::compute(const dmat &timedata) { } else { _est = _alpha * _est + (1 - _alpha) * _ps.compute(samples.value()); } - res = _est; } break; } // end switch mode + i++; } - return res; + if(i>0) { + return _est; + } + return std::nullopt; } std::optional AvPowerSpectra::get_est() { if (_est.n_cols > 0) diff --git a/src/lasp/dsp/lasp_avpowerspectra.h b/src/lasp/dsp/lasp_avpowerspectra.h index 4537e7d..835218e 100644 --- a/src/lasp/dsp/lasp_avpowerspectra.h +++ b/src/lasp/dsp/lasp_avpowerspectra.h @@ -7,10 +7,13 @@ #include /** \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 { public: diff --git a/src/lasp/dsp/lasp_biquadbank.cpp b/src/lasp/dsp/lasp_biquadbank.cpp index e84350e..51fc976 100644 --- a/src/lasp/dsp/lasp_biquadbank.cpp +++ b/src/lasp/dsp/lasp_biquadbank.cpp @@ -94,7 +94,7 @@ void BiquadBank::setGains(const vd &gains) { DEBUGTRACE_ENTER; const us nfilters = _filters.size(); if (gains.size() != nfilters) { - throw runtime_error("Invalid number of gain values given."); + throw rte("Invalid number of gain values given."); } _gains = gains; } diff --git a/src/lasp/dsp/lasp_biquadbank.h b/src/lasp/dsp/lasp_biquadbank.h index a2cfbee..21ac0c0 100644 --- a/src/lasp/dsp/lasp_biquadbank.h +++ b/src/lasp/dsp/lasp_biquadbank.h @@ -1,6 +1,11 @@ #pragma once #include "lasp_filter.h" +/** + * \ingroup dsp + * @{ + */ + /** * @brief A set of Biquad filters in series. */ @@ -74,3 +79,4 @@ public: void reset() override final; }; +/** @} */ diff --git a/src/lasp/dsp/lasp_fft.cpp b/src/lasp/dsp/lasp_fft.cpp index 74e90df..0e8fd1b 100644 --- a/src/lasp/dsp/lasp_fft.cpp +++ b/src/lasp/dsp/lasp_fft.cpp @@ -8,7 +8,7 @@ ////////////////////////////////////////////////////////////////////// #include #include -#define DEBUGTRACE_ENABLED +/* #define DEBUGTRACE_ENABLED */ #include "lasp_fft.h" #include "debugtrace.hpp" #include "lasp_config.h" @@ -152,22 +152,29 @@ dmat Fft::ifft(const cmat &freqdata) { return res; } -void load_fft_wisdom(const char *wisdom) { +void Fft::load_fft_wisdom(const std::string& wisdom) { #if LASP_FFT_BACKEND == Armadillo #elif LASP_FFT_BACKEND == FFTW - if (wisdom) { - int rv = fftw_import_wisdom_from_string(wisdom); + if (wisdom.length() > 0) { + int rv = fftw_import_wisdom_from_string(wisdom.c_str()); if (rv != 1) { - fprintf(stderr, "Error loading FFTW wisdom"); + throw rte("Error loading FFTW wisdom"); } } #endif } -char *store_fft_wisdom() { +std::string Fft::store_fft_wisdom() { #if LASP_FFT_BACKEND == Armadillo - return NULL; + return ""; #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 } diff --git a/src/lasp/dsp/lasp_fft.h b/src/lasp/dsp/lasp_fft.h index 6fe397b..099784d 100644 --- a/src/lasp/dsp/lasp_fft.h +++ b/src/lasp/dsp/lasp_fft.h @@ -88,4 +88,20 @@ class Fft { */ 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(); + }; diff --git a/src/lasp/dsp/lasp_siggen_impl.h b/src/lasp/dsp/lasp_siggen_impl.h index 21817e4..8d57561 100644 --- a/src/lasp/dsp/lasp_siggen_impl.h +++ b/src/lasp/dsp/lasp_siggen_impl.h @@ -32,9 +32,9 @@ class Sine : public Siggen { */ Sine(const d freq_Hz); ~Sine() = default; - virtual vd genSignalUnscaled(const us nframes) override; + virtual vd genSignalUnscaled(const us nframes) override final; void setFreq(const d newFreq); - void resetImpl() override { phase=0; } + void resetImpl() override final { phase=0; } }; class Periodic: public Siggen { protected: @@ -42,7 +42,7 @@ class Periodic: public Siggen { us _cur_pos = 0; public: - virtual vd genSignalUnscaled(const us nframes) override; + virtual vd genSignalUnscaled(const us nframes) override final; ~Periodic() = default; }; diff --git a/src/lasp/dsp/lasp_slm.cpp b/src/lasp/dsp/lasp_slm.cpp index d35014f..a1d8887 100644 --- a/src/lasp/dsp/lasp_slm.cpp +++ b/src/lasp/dsp/lasp_slm.cpp @@ -21,7 +21,7 @@ SLM::SLM(const d fs, const d Lref, const us downsampling_fac, const d tau, // components of // single pole low pass // filter - Lref(Lref), // Reference level + Lrefsq(Lref*Lref), // Reference level downsampling_fac(downsampling_fac), // 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)); // Convert to levels in dB - /* work.transform([&](d val) { */ - /* 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)); + work = 10*arma::log10((work+arma::datum::eps)/Lrefsq); return work; } diff --git a/src/lasp/dsp/lasp_slm.h b/src/lasp/dsp/lasp_slm.h index d49fbcd..3e348b5 100644 --- a/src/lasp/dsp/lasp_slm.h +++ b/src/lasp/dsp/lasp_slm.h @@ -4,6 +4,11 @@ #include #include +/** + * \ingroup dsp + * @{ + */ + /** * @brief Sound Level Meter implementation that gives a result for each * channel. A channel is the result of a filtered signal @@ -25,7 +30,7 @@ class SLM { d _alpha = -1; 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 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 * 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 * low-pass filter. */ vd Ppeak; - us N = 0; /// Counter for the number of time samples counted that came - /// in; + us N = 0; /// Counter for the number of time samples counted that came + /// in; /** * @brief Initialize a Sound Level Meter @@ -118,13 +123,33 @@ public: * * @param input Raw input data * - * @return Filtered level data. + * @return Filtered level data for each filtered channel. */ 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: vd run_single(vd input, const us filter_no); }; +/** @} */ diff --git a/src/lasp/dsp/lasp_thread.cpp b/src/lasp/dsp/lasp_thread.cpp index c2c98e0..76bc400 100644 --- a/src/lasp/dsp/lasp_thread.cpp +++ b/src/lasp/dsp/lasp_thread.cpp @@ -1,4 +1,4 @@ -#define DEBUGTRACE_ENABLED +/* #define DEBUGTRACE_ENABLED */ #include "lasp_thread.h" #include "BS_thread_pool.hpp" #include "debugtrace.hpp" diff --git a/src/lasp/dsp/lasp_timebuffer.cpp b/src/lasp/dsp/lasp_timebuffer.cpp index e9d515e..ffcf29d 100644 --- a/src/lasp/dsp/lasp_timebuffer.cpp +++ b/src/lasp/dsp/lasp_timebuffer.cpp @@ -8,7 +8,7 @@ #include #include -using std::runtime_error; +using rte = std::runtime_error; class TimeBufferImp { /** @@ -22,10 +22,11 @@ public: _storage.clear(); } void push(const dmat &mat) { + DEBUGTRACE_ENTER; #if LASP_DEBUG==1 if(!_storage.empty()) { 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 @@ -39,7 +40,7 @@ public: DEBUGTRACE_ENTER; if (keep > nsamples) - throw runtime_error("keep should be <= nsamples"); + throw rte("keep should be <= nsamples"); if (nsamples <= n_frames()) { diff --git a/src/lasp/dsp/lasp_window.cpp b/src/lasp/dsp/lasp_window.cpp index 50a5860..9dedb95 100644 --- a/src/lasp/dsp/lasp_window.cpp +++ b/src/lasp/dsp/lasp_window.cpp @@ -2,30 +2,36 @@ // // Author: J.A. de Jong - ASCEE // +/* #define DEBUGTRACE_ENABLED */ +#include "debugtrace.hpp" #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). #define lin0N arma::linspace(0, N - 1, 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) { 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) { d a0 = 7938. / 18608.; d a1 = 9240. / 18608.; d a2 = 1430. / 18608.; - return a0 - a1 * d_cos(2 * number_pi * lin0N / (N - 1)) + - a2 * d_cos(4 * number_pi * lin0N / (N - 1)); + return a0 - a1 * d_cos((2 * number_pi/N) * lin0N) + + a2 * d_cos((4 * number_pi / N)* lin0N ); } vd Window::rectangular(const us N) { return arma::ones(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) { diff --git a/src/lasp/dsp/lasp_window.h b/src/lasp/dsp/lasp_window.h index 514dbfe..81f043e 100644 --- a/src/lasp/dsp/lasp_window.h +++ b/src/lasp/dsp/lasp_window.h @@ -15,6 +15,38 @@ class Window { 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 diff --git a/src/lasp/lasp_common.py b/src/lasp/lasp_common.py index 6267f08..9ff72b5 100644 --- a/src/lasp/lasp_common.py +++ b/src/lasp/lasp_common.py @@ -6,7 +6,7 @@ from collections import namedtuple from dataclasses import dataclass from dataclasses_json import dataclass_json from enum import Enum, unique, auto -from .lasp_cpp import Window as wWindow +from .lasp_cpp import DaqChannel """ Common definitions used throughout the code. @@ -14,7 +14,7 @@ Common definitions used throughout the code. __all__ = [ 'P_REF', 'FreqWeighting', 'TimeWeighting', 'getTime', 'getFreq', 'Qty', - 'SIQtys', 'Window', + 'SIQtys', 'lasp_shelve', 'this_lasp_shelve', 'W_REF', 'U_REF', 'I_REF', 'dBFS_REF', 'AvType' ] @@ -39,6 +39,7 @@ U_REF = 5e-8 # 50 nano meter / s # hence this is the reference level as specified below. dBFS_REF = 0.5*2**0.5 # Which level would be -3.01 dBFS + @unique class AvType(Enum): """Specificying the type of data, for adding and removing callbacks from @@ -52,7 +53,6 @@ class AvType(Enum): # Both input as well as output audio_duplex = (2, 'duplex') - # video = 4 @dataclass_json @@ -67,6 +67,7 @@ class Qty: # yet able to compute `dBPa's' level_ref_name: object level_ref_value: object + cpp_enum: DaqChannel.Qty def __str__(self): return f'{self.name} [{self.unit_symb}]' @@ -91,14 +92,16 @@ class SIQtys(Enum): unit_symb='-', level_unit=('dBFS',), 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', unit_name='Pascal', unit_symb='Pa', level_unit=('dB SPL','dBPa'), 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', @@ -107,6 +110,7 @@ class SIQtys(Enum): level_unit=('dBV',), # dBV level_ref_name=('1V',), level_ref_value=(1.0,), + cpp_enum = DaqChannel.Qty.Voltage ) @staticmethod @@ -268,33 +272,6 @@ class this_lasp_shelve(Shelve): node = platform.node() 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: none = (-1, 'Raw (no time weighting)') uufast = (1e-4, '0.1 ms') diff --git a/src/lasp/lasp_cpp.cpp b/src/lasp/lasp_cpp.cpp index fc3ff3c..b9eea40 100644 --- a/src/lasp/lasp_cpp.cpp +++ b/src/lasp/lasp_cpp.cpp @@ -18,6 +18,11 @@ * * */ +/** + * \defgroup pybind Pybind11 wrapper code + * @{ + * + */ namespace py = pybind11; 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_MINOR") = LASP_VERSION_MINOR; } +/** @} */ diff --git a/src/lasp/pybind11/lasp_daq.cpp b/src/lasp/pybind11/lasp_daq.cpp index 80578f8..38aeb89 100644 --- a/src/lasp/pybind11/lasp_daq.cpp +++ b/src/lasp/pybind11/lasp_daq.cpp @@ -27,8 +27,7 @@ void init_daq(py::module &m) { .value("threadError", Daq::StreamStatus::StreamError::threadError) .value("logicError", Daq::StreamStatus::StreamError::logicError) .value("apiSpecificError", - Daq::StreamStatus::StreamError::apiSpecificError) - .export_values(); + Daq::StreamStatus::StreamError::apiSpecificError); /// Daq daq.def("neninchannels", &Daq::neninchannels); diff --git a/src/lasp/pybind11/lasp_daqconfig.cpp b/src/lasp/pybind11/lasp_daqconfig.cpp index 031ae06..be97adf 100644 --- a/src/lasp/pybind11/lasp_daqconfig.cpp +++ b/src/lasp/pybind11/lasp_daqconfig.cpp @@ -1,5 +1,6 @@ #include "lasp_daqconfig.h" #include "lasp_deviceinfo.h" +#include #include #include #include @@ -12,17 +13,31 @@ void init_daqconfiguration(py::module &m) { /// DataType py::class_ dtype_desc(m, "DataTypeDescriptor"); + dtype_desc.def_readonly("name", &DataTypeDescriptor::name); dtype_desc.def_readonly("sw", &DataTypeDescriptor::sw); dtype_desc.def_readonly("is_floating", &DataTypeDescriptor::is_floating); + dtype_desc.def_readonly("dtype", &DataTypeDescriptor::dtype); py::enum_(dtype_desc, "DataType") .value("dtype_fl32", DataTypeDescriptor::DataType::dtype_fl32) .value("dtype_fl64", DataTypeDescriptor::DataType::dtype_fl64) .value("dtype_int8", DataTypeDescriptor::DataType::dtype_int8) .value("dtype_int16", DataTypeDescriptor::DataType::dtype_int16) - .value("dtype_int32", DataTypeDescriptor::DataType::dtype_int32) - .export_values(); + .value("dtype_int32", DataTypeDescriptor::DataType::dtype_int32); + + 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); @@ -36,15 +51,8 @@ void init_daqconfiguration(py::module &m) { /// DaqChannel, DaqConfiguration py::class_ daqconfig(m, "DaqConfiguration"); - py::enum_(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(m, "DaqChannel"); + daqchannel.def(py::init<>()); daqchannel.def_readwrite("enabled", &DaqChannel::enabled); daqchannel.def_readwrite("name", &DaqChannel::name); daqchannel.def_readwrite("sensitivity", &DaqChannel::sensitivity); @@ -53,12 +61,19 @@ void init_daqconfiguration(py::module &m) { daqchannel.def_readwrite("digitalHighpassCutOn", &DaqChannel::digitalHighPassCutOn); daqchannel.def_readwrite("rangeIndex", &DaqChannel::rangeIndex); + + py::enum_(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); /// DaqConfiguration daqconfig.def(py::init<>()); daqconfig.def(py::init()); - + daqconfig.def_readwrite("api", &DaqConfiguration::api); + daqconfig.def_readwrite("device_name", &DaqConfiguration::device_name); daqconfig.def_readwrite("sampleRateIndex", &DaqConfiguration::sampleRateIndex); diff --git a/src/lasp/pybind11/lasp_deviceinfo.cpp b/src/lasp/pybind11/lasp_deviceinfo.cpp index a794e29..3d0ad4a 100644 --- a/src/lasp/pybind11/lasp_deviceinfo.cpp +++ b/src/lasp/pybind11/lasp_deviceinfo.cpp @@ -10,6 +10,7 @@ void init_deviceinfo(py::module& m) { /// DeviceInfo py::class_ devinfo(m, "DeviceInfo"); + devinfo.def("__str__", [](const DeviceInfo& d) {return d.device_name;}); devinfo.def_readonly("api", &DeviceInfo::api); devinfo.def_readonly("device_name", &DeviceInfo::device_name); diff --git a/src/lasp/pybind11/lasp_dsp_pybind.cpp b/src/lasp/pybind11/lasp_dsp_pybind.cpp index 59eae4d..876da50 100644 --- a/src/lasp/pybind11/lasp_dsp_pybind.cpp +++ b/src/lasp/pybind11/lasp_dsp_pybind.cpp @@ -1,27 +1,41 @@ #include +#include "lasp_avpowerspectra.h" #include "lasp_biquadbank.h" +#include "lasp_fft.h" #include "lasp_siggen.h" #include "lasp_siggen_impl.h" #include "lasp_slm.h" -#include "lasp_avpowerspectra.h" #include "lasp_window.h" -#include "lasp_fft.h" #include #include using std::cerr; 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) { py::class_ fft(m, "Fft"); fft.def(py::init()); - fft.def("fft", py::overload_cast(&Fft::fft)); - fft.def("fft", py::overload_cast(&Fft::fft)); + fft.def("fft", py::overload_cast(&Fft::fft)); + fft.def("fft", py::overload_cast(&Fft::fft)); - fft.def("ifft", py::overload_cast(&Fft::ifft)); - fft.def("ifft", py::overload_cast(&Fft::ifft)); + fft.def("ifft", py::overload_cast(&Fft::ifft)); + fft.def("ifft", py::overload_cast(&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_ w(m, "Window"); py::enum_(w, "WindowType") @@ -29,9 +43,11 @@ void init_dsp(py::module &m) { .value("Hamming", Window::WindowType::Hamming) .value("Bartlett", Window::WindowType::Bartlett) .value("Blackman", Window::WindowType::Bartlett) - .value("Rectangular", Window::WindowType::Rectangular) - .export_values(); + .value("Rectangular", Window::WindowType::Rectangular); + w.def_static("toTxt", &Window::toText); + + /// Siggen py::class_> siggen(m, "Siggen"); siggen.def("setLevel", &Siggen::setLevel, "Set the level of the signal generator"); @@ -39,6 +55,7 @@ void init_dsp(py::module &m) { py::class_> sw(m, "Sine", siggen); sw.def(py::init()); + /// SeriesBiquad py::class_ sbq(m, "SeriesBiquad"); sbq.def(py::init()); sbq.def("filter", [](SeriesBiquad &s, const vd &input) { @@ -46,15 +63,28 @@ void init_dsp(py::module &m) { s.filter(res); return res; }); + + /// BiquadBank + py::class_ bqb(m, "BiquadBank"); + bqb.def(py::init()); + bqb.def("setGains",&BiquadBank::setGains); + bqb.def("filter",&BiquadBank::filter); + /// PowerSpectra py::class_ ps(m, "PowerSpectra"); - ps.def(py::init()); + ps.def(py::init()); ps.def("compute", &PowerSpectra::compute); + /// AvPowerSpectra py::class_ aps(m, "AvPowerSpectra"); - /* ps.def(py::init()); */ - /* ps.def("compute", &PowerSpectra::compute); */ - + aps.def(py::init(), + py::arg("nfft"), py::arg("WindowType"), py::arg("overlap_percentage"), + py::arg("time_constant")); + aps.def("compute", [](AvPowerSpectra &aps, const dmat &timedata) { + std::optional res = aps.compute(timedata); + return res.value_or(arma::cx_cube(0,0,0)); + } + ); py::class_ slm(m, "SLM"); @@ -70,4 +100,8 @@ void init_dsp(py::module &m) { slm.def_readonly("Pm", &SLM::Pm); slm.def_readonly("Pmax", &SLM::Pmax); slm.def_readonly("Ppeak", &SLM::Ppeak); + slm.def("Lpeak", &SLM::Lpeak); + slm.def("Leq", &SLM::Leq); + slm.def("Lmax", &SLM::Lmax); } +/** @} */ diff --git a/src/lasp/pybind11/lasp_streammgr.cpp b/src/lasp/pybind11/lasp_streammgr.cpp index bcfb7c8..15bc6d0 100644 --- a/src/lasp/pybind11/lasp_streammgr.cpp +++ b/src/lasp/pybind11/lasp_streammgr.cpp @@ -9,13 +9,14 @@ namespace py = pybind11; 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_> smgr( m, "StreamMgr"); py::enum_(smgr, "StreamType") .value("input", StreamMgr::StreamType::input) - .value("output", StreamMgr::StreamType::output) - .export_values(); + .value("output", StreamMgr::StreamType::output); smgr.def("startStream", &StreamMgr::startStream); smgr.def("stopStream", &StreamMgr::stopStream); @@ -26,4 +27,6 @@ void init_streammgr(py::module &m) { smgr.def("setSiggen", &StreamMgr::setSiggen); smgr.def("getDeviceInfo", &StreamMgr::getDeviceInfo); + smgr.def("getStreamStatus", &StreamMgr::getStreamStatus); + smgr.def("isStreamRunningOK", &StreamMgr::isStreamRunningOK); }