Switched to OpenMP for parallellizing for loops. Bugfixes in PowerSpectra() class. Added tests to check Parsevall hold. Improved lots of comments. Added module groups. Use CMake to specify code version number. Device info should be obtained from StreamMgr in Python code.

This commit is contained in:
Anne de Jong 2022-09-03 20:59:14 +02:00
parent 10749137ec
commit 70891ceaf4
36 changed files with 699 additions and 426 deletions

View File

@ -1,22 +1,6 @@
cmake_minimum_required (VERSION 3.16)
project(LASP LANGUAGES C CXX VERSION 1.0)
project(LASP LANGUAGES C CXX)
set(LASP_VERSION_MAJOR 1)
set(LASP_VERSION_MINOR 0)
# To allow linking to static libs from other directories
cmake_policy(SET CMP0079 NEW)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake")
include("BuildType")
include("QueryPythonForPybind11")
# Find the pybind11 package
find_pybind11_python_first()
# This is used for code completion in vim
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED)
@ -24,6 +8,29 @@ set(CMAKE_CXX_STANDARD_REQUIRED)
option(LASP_DOUBLE_PRECISION "Compile as double precision floating point" ON)
option(LASP_HAS_RTAUDIO "Compile with RtAudio Daq backend" ON)
option(LASP_HAS_ULDAQ "Compile with UlDaq backend" ON)
option(LASP_BUILD_TUNED "Tune build for current machine" OFF)
# Use ccache if available
find_program(CCACHE_PROGRAM ccache)
if(CCACHE_PROGRAM)
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE_PROGRAM}")
set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK "${CCACHE_PROGRAM}")
endif()
# To allow linking to static libs from other directories
cmake_policy(SET CMP0079 NEW)
# This is used for code completion in vim
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake")
include("BuildType")
include("QueryPythonForPybind11")
# Find the pybind11 package
find_pybind11_python_first()
find_package(OpenMP REQUIRED COMPONENTS CXX)
set(LASP_FFT_BACKEND "FFTW" CACHE STRING "FFT Library backend")
set_property(CACHE LASP_FFT_BACKEND PROPERTY STRINGS "FFTW" "Armadillo")
@ -33,12 +40,21 @@ set(PY_FULL_VERSION ${PROJECT_VERSION}${PY_VERSION_SUFFIX})
# Required for PYBIND11
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
if(CMAKE_BUILD_TYPE STREQUAL Debug)
if(${CMAKE_BUILD_TYPE} STREQUAL "Debug")
set(LASP_DEBUG True)
else()
set(LASP_DEBUG False)
endif()
# Tune for current machine
if(LASP_BUILD_TUNED)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
message(FATAL_ERROR
"Cannot build optimized and tuned code when debug is switched on")
endif()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native -mtune=native")
endif()
# link openblas
set(BLA_VENDOR OpenBLAS)
find_package(BLAS REQUIRED)
@ -49,18 +65,21 @@ add_definitions(-DLASP_MAX_NFFT=33554432) # 2**25
# ####################################### End of user-adjustable variables section
include(OSSpecific)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-type-limits")
# Enable openmp
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS} -Wall -Wextra -Wno-type-limits")
set(CMAKE_C_FLAGS_RELEASE "-O3 -flto -mfpmath=sse -march=x86-64 -mtune=native \
-fdata-sections -ffunction-sections -fomit-frame-pointer -finline-functions")
# ############################# End compilation flags
include_directories(/usr/lib/python3.10/site-packages/numpy/core/include)
add_subdirectory(third_party/carma)
if(LASP_FFT_BACKEND STREQUAL "FFTW")
find_library(FFTW_LIBRARY NAMES fftw3 fftw)
set(FFT_LIBRARIES "${FFTW_LIBRARY}")
find_library(fftw3 REQUIRED NAMES fftw fftw3)
set(LASP_FFT_LIBS "fftw3")
message("LASP_FFT_LIBS = ${LASP_FFT_LIBS}")
elseif(LASP_FFT_BACKEND STREQUAL "Armadillo")
endif()
add_subdirectory(src/lasp)

View File

@ -2,7 +2,7 @@
"cells": [
{
"cell_type": "code",
"execution_count": 8,
"execution_count": 1,
"metadata": {},
"outputs": [
{
@ -22,26 +22,20 @@
},
{
"cell_type": "code",
"execution_count": 9,
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"2022-08-16 21:20:04+0200 \n",
"2022-08-16 21:20:04+0200 Enter fromBiquads (lasp_slm.cpp: 82)\n",
"2022-08-16 21:20:04+0200 | Enter createBandPass (lasp_slm.cpp: 62)\n",
"2022-08-16 21:20:04+0200 | | Enter SeriesBiquad (lasp_biquadbank.cpp: 12)\n",
"2022-08-16 21:20:04+0200 | | Leave SeriesBiquad (lasp_biquadbank.cpp)\n",
"2022-08-16 21:20:04+0200 | | \n",
"2022-08-16 21:20:04+0200 | | Enter SeriesBiquad (lasp_biquadbank.cpp: 12)\n",
"2022-08-16 21:20:04+0200 | | Leave SeriesBiquad (lasp_biquadbank.cpp)\n",
"2022-08-16 21:20:04+0200 | Leave createBandPass (lasp_slm.cpp)\n",
"2022-08-16 21:20:04+0200 | \n",
"2022-08-16 21:20:04+0200 | Enter SLM (lasp_slm.cpp: 37)\n",
"2022-08-16 21:20:04+0200 | Leave SLM (lasp_slm.cpp)\n",
"2022-08-16 21:20:04+0200 Leave fromBiquads (lasp_slm.cpp)\n"
"2022-09-03 17:21:56+0200 DebugTrace-cpp 2.0.0a2 (g++ 12.2.0)\n",
"2022-09-03 17:21:56+0200 \n",
"2022-09-03 17:21:56+0200 Enter SeriesBiquad (lasp_biquadbank.cpp: 12)\n",
"2022-09-03 17:21:56+0200 Leave SeriesBiquad (lasp_biquadbank.cpp)\n",
"2022-09-03 17:21:56+0200 \n",
"2022-09-03 17:21:56+0200 Enter SeriesBiquad (lasp_biquadbank.cpp: 12)\n",
"2022-09-03 17:21:56+0200 Leave SeriesBiquad (lasp_biquadbank.cpp)\n"
]
}
],
@ -64,7 +58,7 @@
},
{
"cell_type": "code",
"execution_count": 10,
"execution_count": 3,
"metadata": {},
"outputs": [
{
@ -78,7 +72,7 @@
" [0., 0.]])"
]
},
"execution_count": 10,
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
@ -89,32 +83,9 @@
},
{
"cell_type": "code",
"execution_count": 11,
"execution_count": null,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"(20100, 2)\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"2022-08-16 21:20:04+0200 \n",
"2022-08-16 21:20:04+0200 Enter run (lasp_slm.cpp: 151)\n",
"2022-08-16 21:20:04+0200 | Enter filter (lasp_biquadbank.cpp: 44)\n",
"2022-08-16 21:20:04+0200 | | Enter filter (lasp_biquadbank.cpp: 44)\n",
"2022-08-16 21:20:04+0200 | | Leave filter (lasp_biquadbank.cpp)\n",
"2022-08-16 21:20:04+0200 | Leave filter (lasp_biquadbank.cpp)\n",
"2022-08-16 21:20:04+0200 | N = 0ul\n",
"2022-08-16 21:20:04+0200 | N = 0ul\n",
"2022-08-16 21:20:04+0200 Leave run (lasp_slm.cpp)\n"
]
}
],
"outputs": [],
"source": [
"x = np.zeros(20100, dtype=float)\n",
"x[:] = 1\n",
@ -221,7 +192,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.5"
"version": "3.10.6"
}
},
"nbformat": 4,

View File

@ -12,16 +12,17 @@
},
{
"cell_type": "code",
"execution_count": 6,
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"2022-08-16 15:02:57+0200 \n",
"2022-08-16 15:02:57+0200 Enter SeriesBiquad (lasp_biquadbank.cpp: 12)\n",
"2022-08-16 15:02:57+0200 Leave SeriesBiquad (lasp_biquadbank.cpp)\n"
"2022-09-03 17:21:46+0200 DebugTrace-cpp 2.0.0a2 (g++ 12.2.0)\n",
"2022-09-03 17:21:46+0200 \n",
"2022-09-03 17:21:46+0200 Enter SeriesBiquad (lasp_biquadbank.cpp: 12)\n",
"2022-09-03 17:21:46+0200 Leave SeriesBiquad (lasp_biquadbank.cpp)\n"
]
}
],
@ -32,16 +33,16 @@
},
{
"cell_type": "code",
"execution_count": 7,
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"2022-08-16 15:02:57+0200 \n",
"2022-08-16 15:02:57+0200 Enter filter (lasp_biquadbank.cpp: 41)\n",
"2022-08-16 15:02:57+0200 Leave filter (lasp_biquadbank.cpp)\n"
"2022-09-03 17:21:47+0200 \n",
"2022-09-03 17:21:47+0200 Enter filter (lasp_biquadbank.cpp: 44)\n",
"2022-09-03 17:21:47+0200 Leave filter (lasp_biquadbank.cpp)\n"
]
}
],
@ -54,7 +55,7 @@
},
{
"cell_type": "code",
"execution_count": 8,
"execution_count": 4,
"metadata": {},
"outputs": [
{
@ -72,7 +73,7 @@
" [0.38742049]])"
]
},
"execution_count": 8,
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
@ -91,7 +92,7 @@
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
@ -105,7 +106,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.10"
"version": "3.10.6"
}
},
"nbformat": 4,

View File

@ -1,33 +1,48 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"id": "b0d15138",
"cell_type": "markdown",
"metadata": {},
"outputs": [],
"source": [
"!make -j -C ~/wip/mycode/lasp"
"# Test DAQ input on a device"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# !make -j -C ~/wip/mycode/lasp"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1787e24c",
"metadata": {},
"outputs": [],
"source": [
"import lasp\n",
"ds = lasp.DeviceInfo.getDeviceInfo()"
"# Get handle to stream manager\n",
"mgr = lasp.StreamMgr.getInstance()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "22ae99b1",
"metadata": {},
"outputs": [],
"source": [
"ds = mgr.getDeviceInfo()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Search for a device\n",
"for i, d in enumerate(ds):\n",
" print(f'{i}: ' + d.device_name)\n",
"d = ds[1]"
@ -36,30 +51,30 @@
{
"cell_type": "code",
"execution_count": null,
"id": "b1321c4a",
"metadata": {},
"outputs": [],
"source": [
"# Check the available block sizes\n",
"d.availableFramesPerBlock"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "47385b02",
"metadata": {},
"outputs": [],
"source": [
"# Create a configuration and enable some input channels\n",
"config = lasp.DaqConfiguration(d)\n",
"config.inchannel_config[0].enabled = True\n",
"config.inchannel_config[1].enabled = True\n",
"# Choose a different number of frames per block\n",
"config.framesPerBlockIndex = 4"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d12f84b7",
"metadata": {},
"outputs": [],
"source": [
@ -70,27 +85,16 @@
{
"cell_type": "code",
"execution_count": null,
"id": "902ce309",
"metadata": {},
"outputs": [],
"source": [
"mgr = lasp.StreamMgr.getInstance()"
"# Start a stream with a configuration\n",
"mgr.startStream(config)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b209294b",
"metadata": {},
"outputs": [],
"source": [
"mgr.startStream(d, config)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0830ffb5",
"metadata": {},
"outputs": [],
"source": [
@ -100,7 +104,6 @@
{
"cell_type": "code",
"execution_count": null,
"id": "a7fddc19",
"metadata": {},
"outputs": [],
"source": [
@ -110,7 +113,6 @@
{
"cell_type": "code",
"execution_count": null,
"id": "d11c7dae",
"metadata": {},
"outputs": [],
"source": [
@ -122,7 +124,6 @@
{
"cell_type": "code",
"execution_count": null,
"id": "0eeb2311",
"metadata": {},
"outputs": [],
"source": [
@ -134,7 +135,6 @@
{
"cell_type": "code",
"execution_count": null,
"id": "f893b639",
"metadata": {
"scrolled": true
},
@ -142,6 +142,13 @@
"source": [
"del i"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
@ -160,7 +167,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.5"
"version": "3.8.10"
}
},
"nbformat": 4,

View File

@ -26,4 +26,3 @@ requires = [
]
build-backend = "setuptools.build_meta"

View File

@ -31,7 +31,6 @@ pybind11_add_module(lasp_cpp MODULE lasp_cpp.cpp
)
target_link_libraries(lasp_cpp PRIVATE lasp_device_lib lasp_dsp_lib
carma::carma
${OpenMP_CXX_LIBRARIES} ${LASP_FFT_LIBS})
install(TARGETS lasp_cpp DESTINATION .)

View File

@ -7,6 +7,9 @@
#include <memory>
/**
* \defgroup device Device interfacing
* \addtogroup device
* @{
* @brief Callback of DAQ for input data. Callback should return
* false for a stop request.
*/
@ -18,7 +21,8 @@ using InDaqCallback = std::function<bool(const DaqData &)>;
using OutDaqCallback = std::function<bool(DaqData &)>;
/**
* @brief Base cass for all DAQ instances
* @brief Base cass for all DAQ (Data Acquisition) interfaces. A DAQ can be a
* custom device, or for example a sound card.
*/
class Daq : public DaqConfiguration, public DeviceInfo {
@ -90,13 +94,13 @@ public:
/**
* @brief Start the Daq.
*
* @param cb Callback function that is called with input data as argument,
* and which expects output data as a return value. If no output data is
* required, the return value of the function should be nullptr. If no input
* data is presented, the function is called with a nullptr as argument.
* @param inCallback Function that is called with input data as argument,
* and which expects output data as a return value. If no input data is
* required, this callback should be unset.
* @param outCallback Function that is called when output data is required.
* If the stream does not require output data, this callback should be unset.
*/
virtual void start(InDaqCallback inCallback,
OutDaqCallback outCallback) = 0;
virtual void start(InDaqCallback inCallback, OutDaqCallback outCallback) = 0;
/**
* @brief Stop the Daq device. Throws an exception if the device is not
@ -199,3 +203,4 @@ public:
return (neninchannels() > 0 && nenoutchannels() > 0);
}
};
/** @} */

View File

@ -4,6 +4,10 @@
#include <map>
#include <vector>
/** \addtogroup device
* @{
*/
using std::string;
using std::to_string;
@ -67,17 +71,30 @@ public:
};
const DataTypeDescriptor dtype_desc_fl32 = {
"32-bits floating point", 4, true,
DataTypeDescriptor::DataType::dtype_fl32};
.name = "32-bits floating point",
.sw = 4,
.is_floating = true,
.dtype = DataTypeDescriptor::DataType::dtype_fl32};
const DataTypeDescriptor dtype_desc_fl64 = {
"64-bits floating point", 8, true,
DataTypeDescriptor::DataType::dtype_fl64};
.name = "64-bits floating point",
.sw = 8,
.is_floating = true,
.dtype = DataTypeDescriptor::DataType::dtype_fl64};
const DataTypeDescriptor dtype_desc_int8 = {
"8-bits integer", 1, false, DataTypeDescriptor::DataType::dtype_int8};
.name = "8-bits integer",
.sw = 1,
.is_floating = false,
.dtype = DataTypeDescriptor::DataType::dtype_int8};
const DataTypeDescriptor dtype_desc_int16 = {
"16-bits integer", 2, false, DataTypeDescriptor::DataType::dtype_int16};
.name = "16-bits integer",
.sw = 2,
.is_floating = false,
.dtype = DataTypeDescriptor::DataType::dtype_int16};
const DataTypeDescriptor dtype_desc_int32 = {
"32-bits integer", 4, false, DataTypeDescriptor::DataType::dtype_int32};
.name = "32-bits integer",
.sw = 4,
.is_floating = false,
.dtype = DataTypeDescriptor::DataType::dtype_int32};
const std::map<DataTypeDescriptor::DataType, const DataTypeDescriptor>
dtype_map = {
@ -88,6 +105,12 @@ const std::map<DataTypeDescriptor::DataType, const DataTypeDescriptor>
{DataTypeDescriptor::DataType::dtype_int32, dtype_desc_int32},
};
/**
* @brief Class that specifies API related information. An API configuration is
* part of the DAQConfiguration and used to address a certain physical device.
* For that, a specific backend needs to be compiled in. Examples of API's are
* RtAudio and UlDaq.
*/
class DaqApi {
public:
string apiname = "Invalid API";
@ -127,7 +150,9 @@ const DaqApi rtaudioAsioApi("RtAudio Windows ASIO", 1,
class DeviceInfo;
/**
* @brief Stores channel configuration data for each channel.
* @brief Stores channel configuration data for each channel. I.e. the physical
* quantity measured, sensitivity, device-specific channel settings, a channel
* name and others.
*/
class DaqChannel {
@ -245,7 +270,7 @@ public:
*
* @param deviceinfo DeviceInfo structure
*/
DaqConfiguration(const DeviceInfo &DeviceInfo);
DaqConfiguration(const DeviceInfo &deviceinfo);
DaqConfiguration() {}
@ -294,3 +319,6 @@ public:
*/
int getLowestOutChannel() const;
};
/**
* @}
*/

View File

@ -5,6 +5,10 @@
#include <gsl/gsl-lite.hpp>
#include <memory>
/** \addtogroup device
* @{
*/
/**
* @brief Data coming from / going to DAQ. **Non-interleaved format**, which
* means data in buffer is ordered by channel: _ptr[frame+channel*nframes]
@ -126,3 +130,4 @@ public:
DaqData::copyToRaw(channel, reinterpret_cast<uint8_t *>(buffer));
}
};
/** @} */

View File

@ -3,6 +3,9 @@
#include "lasp_types.h"
#include "lasp_daqconfig.h"
/** \addtogroup device
* @{
*/
/**
* @brief Structure containing device info parameters
@ -123,3 +126,4 @@ public:
static std::vector<DeviceInfo> getDeviceInfo();
};
/** @} */

View File

@ -1,7 +1,7 @@
#define DEBUGTRACE_ENABLED
#include "lasp_streammgr.h"
#include "debugtrace.hpp"
#include "lasp_thread.h"
#include "lasp_streammgr.h"
#include <algorithm>
#include <assert.h>
#include <functional>
@ -9,6 +9,7 @@
using std::cerr;
using std::endl;
using rte = std::runtime_error;
InDataHandler::InDataHandler(StreamMgr &mgr) : _mgr(mgr) {
DEBUGTRACE_ENTER;
@ -39,12 +40,40 @@ InDataHandler::~InDataHandler() {
StreamMgr &StreamMgr::getInstance() {
DEBUGTRACE_ENTER;
getPool();
static StreamMgr mgr;
return mgr;
}
StreamMgr::StreamMgr() { DEBUGTRACE_ENTER; }
StreamMgr::StreamMgr() {
DEBUGTRACE_ENTER;
#if LASP_DEBUG == 1
_main_thread_id = std::this_thread::get_id();
#endif
// Trigger a scan for the available devices, in the background.
rescanDAQDevices();
}
#if LASP_DEBUG == 1
void StreamMgr::checkRightThread() const {
assert(std::this_thread::get_id() == _main_thread_id);
}
#endif
void StreamMgr::rescanDAQDevices(std::function<void()> callback,
bool background) {
checkRightThread();
auto &pool = getPool();
if (background) {
pool.push_task(&StreamMgr::rescanDAQDevices_impl, this, callback);
} else {
rescanDAQDevices_impl(callback);
}
}
void StreamMgr::rescanDAQDevices_impl(std::function<void()> callback) {
std::scoped_lock lck(_devices_mtx);
_devices = DeviceInfo::getDeviceInfo();
if (callback) {
callback();
}
}
bool StreamMgr::inCallback(const DaqData &data) {
/* DEBUGTRACE_ENTER; */
@ -91,6 +120,7 @@ template <typename T> bool fillData(DaqData &data, const vd &signal) {
}
void StreamMgr::setSiggen(std::shared_ptr<Siggen> siggen) {
checkRightThread();
std::scoped_lock<std::mutex> lck(_siggen_mtx);
_siggen = siggen;
@ -138,19 +168,40 @@ bool StreamMgr::outCallback(DaqData &data) {
StreamMgr::~StreamMgr() {
DEBUGTRACE_ENTER;
checkRightThread();
stopAllStreams();
}
void StreamMgr::stopAllStreams() {
DEBUGTRACE_ENTER;
checkRightThread();
_inputStream.reset();
_outputStream.reset();
}
void StreamMgr::startStream(const DeviceInfo &devinfo,
const DaqConfiguration &config) {
void StreamMgr::startStream(const DaqConfiguration &config) {
DEBUGTRACE_ENTER;
checkRightThread();
bool isInput = std::count_if(config.inchannel_config.cbegin(),
config.inchannel_config.cend(),
[](auto &i) { return i.enabled; });
// Find the first device that matches with the configuration
DeviceInfo devinfo;
{
std::scoped_lock lck(_devices_mtx);
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.");
}
devinfo = *devinfo2;
}
isInput |= config.monitorOutput && devinfo.hasInternalOutputMonitor;
bool isOutput = std::count_if(config.outchannel_config.cbegin(),
@ -160,17 +211,16 @@ void StreamMgr::startStream(const DeviceInfo &devinfo,
bool isDuplex = isInput && isOutput;
if (!isInput && !isOutput) {
throw std::runtime_error("Neither input, nor output channels enabled for "
"stream. Cannotr start.");
throw rte("Neither input, nor output channels enabled for "
"stream. Cannotr start.");
}
if ((isDuplex || isInput) && _inputStream) {
throw std::runtime_error(
"Error: an input stream is already running. Please "
"first stop existing stream");
throw rte("Error: an input stream is already running. Please "
"first stop existing stream");
} else if (isOutput && _outputStream) {
throw std::runtime_error("Error: output stream is already running. Please "
"first stop existing stream");
throw rte("Error: output stream is already running. Please "
"first stop existing stream");
}
InDaqCallback inCallback;
@ -202,10 +252,12 @@ void StreamMgr::startStream(const DeviceInfo &devinfo,
}
}
void StreamMgr::stopStream(const StreamType t) {
DEBUGTRACE_ENTER;
checkRightThread();
switch (t) {
case (StreamType::input): {
if (!_inputStream) {
throw std::runtime_error("Input stream is not running");
throw rte("Input stream is not running");
}
_inputStream = nullptr;
} break;
@ -214,28 +266,32 @@ void StreamMgr::stopStream(const StreamType t) {
_inputStream = nullptr;
} else {
if (!_outputStream) {
throw std::runtime_error("Output stream is not running");
throw rte("Output stream is not running");
}
_outputStream = nullptr;
} // end else
} break;
default:
throw std::runtime_error("BUG");
throw rte("BUG");
break;
}
}
void StreamMgr::addInDataHandler(InDataHandler &handler) {
checkRightThread();
std::scoped_lock<std::mutex> lck(_inDataHandler_mtx);
_inDataHandlers.push_back(&handler);
}
void StreamMgr::removeInDataHandler(InDataHandler &handler) {
checkRightThread();
std::scoped_lock<std::mutex> lck(_inDataHandler_mtx);
_inDataHandlers.remove(&handler);
}
Daq::StreamStatus StreamMgr::getStreamStatus(const StreamType type) const {
DEBUGTRACE_ENTER;
checkRightThread();
// Default constructor, says stream is not running, but also no errors
Daq::StreamStatus s;

View File

@ -4,7 +4,11 @@
#include <list>
#include <memory>
#include <mutex>
#include <thread>
/** \addtogroup device
* @{
*/
class StreamMgr;
class InDataHandler {
@ -12,7 +16,7 @@ class InDataHandler {
protected:
StreamMgr &_mgr;
#if LASP_DEBUG == 1
std::atomic<bool> stopCalled {false};
std::atomic<bool> stopCalled{false};
#endif
public:
@ -51,15 +55,20 @@ public:
* like "pure virtual function called", or other**.
*/
void stop();
};
/**
* @brief Stream manager. Used to manage the input and output streams.
* Implemented as a singleton: only one stream manager can be in existance for
* a given program.
* a given program. The thread that instantiates a stream manager is the only
* thread that should interact with the stream manager. If this is not true,
* undefined behavior can be expected. If LASP is compiled in debug mode, this
* fact is asserted.
*/
class StreamMgr {
#if LASP_DEBUG == 1
std::thread::id _main_thread_id;
#endif
/**
* @brief Storage for streams.
@ -81,6 +90,9 @@ class StreamMgr {
std::shared_ptr<Siggen> _siggen;
std::mutex _siggen_mtx;
std::mutex _devices_mtx;
std::vector<DeviceInfo> _devices;
StreamMgr();
friend class InDataHandler;
@ -90,7 +102,6 @@ class StreamMgr {
~StreamMgr();
public:
enum class StreamType : us {
/**
* @brief Input stream
@ -113,12 +124,34 @@ public:
static StreamMgr &getInstance();
/**
* @brief Start a stream.
* @brief Obtain a list of devices currently available. When the StreamMgr is
* first created, this
*
* @param devinfo Device information of device
* @param config Configuration of device
* @return A copy of the internal stored list of devices
*/
void startStream(const DeviceInfo &devinfo, const DaqConfiguration &config);
std::vector<DeviceInfo> getDeviceInfo() const {
std::scoped_lock lck(const_cast<std::mutex&>(_devices_mtx));
return _devices;
}
/**
* @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.
*/
void
rescanDAQDevices(std::function<void()> callback = std::function<void()>(),
bool background = false);
/**
* @brief Start a stream based on given configuration.
*
* @param config Configuration of a device
*/
void startStream(const DaqConfiguration &config);
/**
* @brief Check if a certain stream is running. If running with no errors, it
@ -187,4 +220,19 @@ private:
* @param handler The handler to add.
*/
void addInDataHandler(InDataHandler &handler);
/**
* @brief Do the actual rescanning.
*
* @param callback
*/
void rescanDAQDevices_impl(std::function<void()> callback);
#if LASP_DEBUG == 1
void checkRightThread() const;
#else
void checkRightThread() const {}
#endif
};
/** @} */

View File

@ -12,10 +12,10 @@ PowerSpectra::PowerSpectra(const us nfft, const Window::WindowType w)
PowerSpectra::PowerSpectra(const vd &window)
: nfft(window.size()), _fft(nfft), _window(window) {
d win_pow = arma::sum(window % window);
d win_pow = arma::sum(window % window) / window.size();
/* Scale fft such that power is easily computed */
_scale_fac = sqrt(2 / win_pow) / nfft;
_scale_fac = 2 / (win_pow * nfft * nfft);
}
arma::Cube<c> PowerSpectra::compute(const dmat &input) {
@ -25,12 +25,17 @@ arma::Cube<c> PowerSpectra::compute(const dmat &input) {
// Multiply each column of the inputs element-wise with the window.
input_tmp.each_col() %= _window;
cmat rfft = _fft.fft(input_tmp) * _scale_fac;
cmat rfft = _fft.fft(input_tmp);
arma::cx_cube output(rfft.n_rows, input.n_cols, input.n_cols);
#pragma omp parallel for
for (us i = 0; i < input.n_cols; i++) {
for (us j = 0; j < input.n_cols; j++) {
output.slice(j).col(i) = rfft.col(i) % arma::conj(rfft.col(j));
output.slice(j).col(i) =
_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;
}
}
return output;

View File

@ -6,6 +6,9 @@
#include <memory>
#include <optional>
/** \defgroup dsp Digital Signal Processing utilities
* \ingroup dsp
* @{
/**
* @brief Computes single-sided cross-power spectra for a group of channels
*/
@ -151,3 +154,4 @@ public:
*/
us exactOverlapSamples() const { return _ps.nfft - _overlap_keep; }
};
/** @} */

View File

@ -6,15 +6,19 @@
using std::cerr;
using std::endl;
using std::runtime_error;
using rte = std::runtime_error;
SeriesBiquad::SeriesBiquad(const vd &filter_coefs) {
DEBUGTRACE_ENTER;
if (filter_coefs.n_cols != 1) {
throw rte("Expected filter coefficients for a single SeriesBiquad as a "
"single column with length 6 x n_filters");
}
if (filter_coefs.n_rows % 6 != 0) {
cerr << "Number of rows given: " << filter_coefs.n_rows << endl;
throw runtime_error("filter_coefs should be multiple of 6, given: " +
std::to_string(filter_coefs.n_rows));
throw rte("filter_coefs should be multiple of 6, given: " +
std::to_string(filter_coefs.n_rows));
}
us nfilters = filter_coefs.n_rows / 6;
@ -31,7 +35,7 @@ SeriesBiquad::SeriesBiquad(const vd &filter_coefs) {
"absdiff", 1e-9)) {
std::cerr << "Read row: " << sos.row(3) << endl;
throw std::runtime_error(
throw rte(
"Filter coefficients should have fourth element (a0) equal to 1.0");
}
}

View File

@ -7,27 +7,28 @@
//
//////////////////////////////////////////////////////////////////////
#include <memory>
#include <cassert>
#define DEBUGTRACE_ENABLED
#include "lasp_fft.h"
#include "debugtrace.hpp"
#include "lasp_config.h"
using std::runtime_error;
using rte = std::runtime_error;
#if LASP_FFT_BACKEND == Armadillo
#include "fftpack.h"
class Fft_impl {
public:
us nfft;
Fft_impl(const us nfft) : nfft(nfft) {
throw runtime_error(
"This code does not output correct results, as it computes the full "
"FFT. It needs to be reworked to single-sided spectra.");
public:
us nfft;
Fft_impl(const us nfft) : nfft(nfft) {
throw runtime_error(
"This code does not output correct results, as it computes the full "
"FFT. It needs to be reworked to single-sided spectra.");
DEBUGTRACE_ENTER;
}
DEBUGTRACE_ENTER;
}
vd ifft(const vc &f) { return arma::ifft(f); }
vc fft(const vd &time) { return arma::fft(time); }
vd ifft(const vc &f) { return arma::ifft(f); }
vc fft(const vd &time) { return arma::fft(time); }
};
#elif LASP_FFT_BACKEND == FFTW
#include <fftw3.h>
@ -36,99 +37,120 @@ public:
* @brief FFTW implementation
*/
class Fft_impl {
public:
us nfft;
fftw_plan forward_plan = nullptr;
fftw_plan reverse_plan = nullptr;
fftw_complex *frequencyDomain = nullptr;
d *timeDomain = nullptr;
public:
us nfft;
fftw_plan forward_plan = nullptr;
fftw_plan reverse_plan = nullptr;
fftw_complex *frequencyDomain = nullptr;
d *timeDomain = nullptr;
Fft_impl(const us nfft) : nfft(nfft) {
Fft_impl(const us nfft) : nfft(nfft) {
timeDomain = (d *)fftw_malloc(sizeof(d) * nfft);
timeDomain = (d *)fftw_malloc(sizeof(d) * nfft);
frequencyDomain =
frequencyDomain =
(fftw_complex *)fftw_malloc(sizeof(fftw_complex) * (nfft / 2 + 1));
forward_plan =
forward_plan =
fftw_plan_dft_r2c_1d(nfft, timeDomain, frequencyDomain, FFTW_MEASURE);
reverse_plan =
reverse_plan =
fftw_plan_dft_c2r_1d(nfft, frequencyDomain, timeDomain, FFTW_MEASURE);
};
if(!forward_plan || !reverse_plan || !timeDomain || !frequencyDomain) {
throw rte("Error allocating FFT");
}
~Fft_impl() {
fftw_destroy_plan(forward_plan);
fftw_destroy_plan(reverse_plan);
fftw_free(frequencyDomain);
fftw_free(timeDomain);
}
};
vc fft(const vd &time) {
~Fft_impl() {
fftw_destroy_plan(forward_plan);
fftw_destroy_plan(reverse_plan);
fftw_free(frequencyDomain);
fftw_free(timeDomain);
}
vc res(nfft / 2 + 1);
memcpy(timeDomain, time.memptr(), sizeof(d) * nfft);
vc fft(const vd &time) {
if(time.n_rows != nfft) {
throw rte("Invalid size of input vector, should be equal to nfft");
}
fftw_execute(forward_plan);
vc res(nfft / 2 + 1);
memcpy(timeDomain, time.memptr(), sizeof(d) * nfft);
memcpy(reinterpret_cast<void *>(res.memptr()), frequencyDomain,
sizeof(d) * (nfft / 2 + 1));
fftw_execute(forward_plan);
return res;
}
vd ifft(const vc &f) {
vd res(nfft);
memcpy(frequencyDomain,
reinterpret_cast<void *>(const_cast<c *>(f.memptr())),
(nfft / 2 + 1) * sizeof(c));
memcpy(reinterpret_cast<void *>(res.memptr()), frequencyDomain,
sizeof(c) * (nfft / 2 + 1));
fftw_execute(reverse_plan);
return res;
}
vd ifft(const vc &f) {
DEBUGTRACE_ENTER;
if(f.n_rows != nfft/2+1) {
throw rte("Invalid size of input vector, should be equal to nfft/2+1");
}
memcpy(frequencyDomain,
reinterpret_cast<void *>(const_cast<c *>(f.memptr())),
(nfft / 2 + 1) * sizeof(c));
memcpy(res.memptr(), frequencyDomain, nfft * sizeof(d));
fftw_execute(reverse_plan);
/* Scale by dividing by nfft. Checked with numpy implementation
* that this indeed needs to be done for FFTW. */
res *= 1 / nfft;
vd res(nfft);
memcpy(res.memptr(), timeDomain, nfft * sizeof(d));
return res;
}
/* Scale by dividing by nfft. Checked with numpy implementation
* that this indeed needs to be done for FFTW. */
res *= 1.0 / nfft;
return res;
}
};
#else
#error \
"Cannot compile lasp_ffc.c, no FFT backend specified. Should either be FFTPack, or FFTW"
"Cannot compile lasp_ffc.c, no FFT backend specified. Should either be FFTPack, or FFTW"
#endif
Fft::Fft(const us nfft) {
DEBUGTRACE_ENTER;
if (nfft == 0) {
throw std::runtime_error("Invalid nfft: 0");
throw rte("Invalid nfft: 0");
}
if (nfft >= LASP_MAX_NFFT) {
throw std::runtime_error("Invalid nfft, should be smaller than: " +
std::to_string(LASP_MAX_NFFT));
throw rte("Invalid nfft, should be smaller than: " +
std::to_string(LASP_MAX_NFFT));
}
_impl = std::make_unique<Fft_impl>(nfft);
}
Fft::~Fft() { }
us Fft::nfft() const { return _impl->nfft; }
us Fft::nfft() const { assert(_impl); return _impl->nfft; }
vc Fft::fft(const vd &timedata) { assert(_impl); return _impl->fft(timedata); }
cmat Fft::fft(const dmat &freqdata) {
DEBUGTRACE_ENTER;
assert(_impl);
cmat res(_impl->nfft/2+1, freqdata.n_cols);
#pragma omp parallel for
for (us colno = 0; colno < freqdata.n_cols; colno++) {
res.col(colno) = _impl->fft(freqdata.col(colno));
}
return res;
}
vd Fft::ifft(const vc &freqdata) {
DEBUGTRACE_ENTER;
if (freqdata.size() != (_impl->nfft / 2 + 1)) {
throw runtime_error("Invalid size for frequency domain data rows."
" Should be equal to floor(nfft/2)+1");
}
assert(_impl);
return _impl->ifft(freqdata);
}
dmat Fft::ifft(const cmat &freqdata) {
dmat res(_impl->nfft, freqdata.n_cols);
#pragma omp parallel for
for (us colno = 0; colno < freqdata.n_cols; colno++) {
res.col(colno) = _impl->ifft(freqdata.col(colno));
}
return res;
}
vc Fft::fft(const vd &timedata) { return _impl->fft(timedata); }
void load_fft_wisdom(const char *wisdom) {
#if LASP_FFT_BACKEND == Armadillo

View File

@ -28,7 +28,8 @@ char* store_fft_wisdom(void);
class Fft_impl;
/**
* Perform forward FFT's on real time data. Computes single-sided spectra.
* Perform forward FFT's on real time data. Computes single-sided spectra,
* equivalent to Numpy's rfft and irfft functions.
*/
class Fft {
std::unique_ptr<Fft_impl> _impl;
@ -48,7 +49,7 @@ class Fft {
/**
* @brief Return nfft
*
* @return nfft
* @return nfft NFFT (lenght of the DFT transform)
*/
us nfft() const;
@ -56,7 +57,7 @@ class Fft {
* Compute the fft for a single channel of data.
*
* @param[in] timedata Input time data, should have size nfft
* @returns result Result complex vector
* @return Result complex vector
* */
vc fft(const vd& timedata);
@ -82,8 +83,8 @@ class Fft {
/**
* Perform inverse FFT
*
* @param[in] freqdata Frequency domain data
* @param[out] timedata Time domain result
* @param freqdata Frequency domain data
* @return timedata Time domain result
*/
dmat ifft(const cmat& freqdata);

View File

@ -28,8 +28,7 @@ class Sine : public Siggen {
/**
* @brief Create a sine wave generator
*
* @param freq_Hz
* @param level_dB
* @param freq_Hz The frequency in Hz
*/
Sine(const d freq_Hz);
~Sine() = default;

View File

@ -1,6 +1,6 @@
#define DEBUGTRACE_ENABLED
#include "lasp_slm.h"
#include "debugtrace.hpp"
#include "lasp_slm.h"
#include "lasp_thread.h"
#include <algorithm>
#include <cmath>
@ -87,13 +87,7 @@ SLM SLM::fromBiquads(const d fs, const d Lref, const us downsampling_fac,
);
}
vd SLM::run_single(const us i, d *_work, const us size) {
vd work(_work, // Aux Memory pointer
size, // Number of elements
false, // Copy aux mem
true // Stric, means we keep being bound to the memory
);
vd SLM::run_single(vd work,const us i) {
// Filter input in-place
_bandpass[i]->filter(work);
@ -156,35 +150,22 @@ dmat SLM::run(const vd &input_orig) {
_pre_filter->filter(input);
}
std::vector<std::future<vd>> futs;
auto &pool = getPool();
// Fan out over multiple threads, as it is typically a heavy load
dmat res(input.n_rows, _bandpass.size());
// Perform operations in-place.
#pragma omp parallel for
for (us i = 0; i < _bandpass.size(); i++) {
res.col(i) = input;
/// It is not possible to forward a vector of values from this array in a
/// sensible way to a different thread. We therefore break it down to
/// good-old pointers and values.
futs.emplace_back(
pool.submit(&SLM::run_single, this, i, res.colptr(i), res.n_rows));
}
res.col(i) = run_single(input, i);
/* DEBUGTRACE_PRINT(_bandpass.size()); */
/* DEBUGTRACE_PRINT(res.n_cols); */
/* DEBUGTRACE_PRINT(res.n_rows); */
/* DEBUGTRACE_PRINT(futs.size()); */
// Wait for all threads to complete
for (us i = 0; i < _bandpass.size(); i++) {
futs[i].get();
}
// Update the total number of samples harvested so far. NOTE: *This should be
// done AFTER the threads are done!!!*
}
N += input.n_rows;
// Downsample, if applicable

View File

@ -126,5 +126,5 @@ public:
vd Lmax() const { return 10*arma::log10(Pmax/Lref);};
private:
vd run_single(const us i,d* inout,const us size);
vd run_single(vd input, const us filter_no);
};

View File

@ -4,6 +4,10 @@
const us RINGBUFFER_SIZE = 1024;
/**
* @brief Threaded in data handler. Buffers inCallback data and calls a
* callback with the same signature on a different thread.
*/
class ThreadedInDataHandler: public InDataHandler {
/**
* @brief The queue used to push elements to the handling thread.

View File

@ -1,3 +1,5 @@
/* #define DEBUGTRACE_ENABLED */
#include "debugtrace.hpp"
#include "lasp_timebuffer.h"
#include <algorithm>
#include <cassert>
@ -16,6 +18,7 @@ class TimeBufferImp {
public:
void reset() {
DEBUGTRACE_ENTER;
_storage.clear();
}
void push(const dmat &mat) {
@ -32,6 +35,9 @@ public:
}
std::optional<dmat> pop(const us nsamples, us keep) {
DEBUGTRACE_ENTER;
if (keep > nsamples)
throw runtime_error("keep should be <= nsamples");

View File

@ -7,8 +7,6 @@
// needed.
//////////////////////////////////////////////////////////////////////
#pragma once
#ifndef LASP_TYPES_H
#define LASP_TYPES_H
#include "lasp_config.h"
#include <stddef.h>
@ -26,31 +24,30 @@
#include <stdbool.h> // true, false
#include <stddef.h>
#endif
typedef size_t us; /* Size type I always use */
// To change the whole code to 32-bit floating points, change this to
// float.
#if LASP_FLOAT_SIZE == 32
typedef float d; /* Shortcut for double */
typedef float d; /* Shortcut for floating point - single precision */
#elif LASP_FLOAT_SIZE == 64
typedef double d; /* Shortcut for double */
typedef double d; /* Shortcut for floating point - double precision */
#else
#error LASP_FLOAT_SIZE should be either 32 or 64
#endif
#ifdef __cplusplus
#include <complex>
typedef std::complex<d> c;
#include <complex>
typedef std::complex<d> c;
#else
#include <complex.h>
#if LASP_FLOAT_SIZE == 32
typedef float complex c;
#else
typedef double complex c;
#include <complex.h>
#if LASP_FLOAT_SIZE == 32
typedef float complex c;
#else
typedef double complex c;
#endif
#endif
#endif // LASP_TYPES_H
//////////////////////////////////////////////////////////////////////

View File

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

View File

@ -11,12 +11,12 @@ LASP_NUMPY_FLOAT_TYPE = np.float64
def zeros(shape):
return np.zeros(shape, dtype=LASP_NUMPY_FLOAT_TYPE)
return np.zeros(shape, dtype=LASP_NUMPY_FLOAT_TYPE, order='F')
def ones(shape):
return np.ones(shape, dtype=LASP_NUMPY_FLOAT_TYPE)
return np.ones(shape, dtype=LASP_NUMPY_FLOAT_TYPE, order='F')
def empty(shape):
return np.empty(shape, dtype=LASP_NUMPY_FLOAT_TYPE)
return np.empty(shape, dtype=LASP_NUMPY_FLOAT_TYPE, order='F')

View File

@ -4,7 +4,7 @@
Sound level meter implementation
@author: J.A. de Jong - ASCEE
"""
from .wrappers import Slm as pyxSlm
from .lasp_cpp import SLM as cppSLM
import numpy as np
from .lasp_common import (TimeWeighting, FreqWeighting, P_REF)
from .filter import SPLFilterDesigner
@ -117,7 +117,7 @@ class SLM:
sos = sos_overall[np.newaxis,:]
self.nom_txt.append('overall')
self.slm = pyxSlm(prefilter, sos,
self.slm = cppSLM(fs, prefilter, sos,
fs, tw[0], level_ref_value)
dsfac = self.slm.downsampling_fac

View File

@ -35,7 +35,5 @@ void init_deviceinfo(py::module& m) {
devinfo.def_readonly("hasInputACCouplingSwitch",
&DeviceInfo::hasInputACCouplingSwitch);
devinfo.def_static("getDeviceInfo", DeviceInfo::getDeviceInfo);
}

View File

@ -1,9 +1,11 @@
#include "carma"
#include <carma>
#include "lasp_biquadbank.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 <iostream>
#include <pybind11/pybind11.h>
@ -12,6 +14,14 @@ namespace py = pybind11;
void init_dsp(py::module &m) {
py::class_<Fft> fft(m, "Fft");
fft.def(py::init<us>());
fft.def("fft", py::overload_cast<const vd&>(&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 cmat&>(&Fft::ifft));
py::class_<Window> w(m, "Window");
py::enum_<Window::WindowType>(w, "WindowType")
@ -37,7 +47,17 @@ void init_dsp(py::module &m) {
return res;
});
py::class_<PowerSpectra> ps(m, "PowerSpectra");
ps.def(py::init<us, const Window::WindowType>());
ps.def("compute", &PowerSpectra::compute);
py::class_<AvPowerSpectra> aps(m, "AvPowerSpectra");
/* ps.def(py::init<us, const Window::WindowType>()); */
/* ps.def("compute", &PowerSpectra::compute); */
py::class_<SLM> slm(m, "SLM");
slm.def_static(
"fromBiquads",
py::overload_cast<const d, const d, const us, const d, const dmat &>(

View File

@ -25,4 +25,5 @@ void init_streammgr(py::module &m) {
smgr.def("stopAllStreams", &StreamMgr::stopAllStreams);
smgr.def("setSiggen", &StreamMgr::setSiggen);
smgr.def("getDeviceInfo", &StreamMgr::getDeviceInfo);
}

40
test/test_biquadbank.py Normal file
View File

@ -0,0 +1,40 @@
#!/usr/bin/python3
import numpy as np
from lasp import SeriesBiquad, AvPowerSpectra
from lasp.filter import SPLFilterDesigner
import matplotlib.pyplot as plt
from scipy.signal import sosfreqz
# plt.close('all')
# def test_cppslm2():
# """
# Generate a sine wave, now A-weighted
# """
fs = 48000
omg = 2*np.pi*1000
filt = SPLFilterDesigner(fs).A_Sos_design()
bq = SeriesBiquad(filt.flatten())
tend=10
t = np.linspace(0, int((tend*fs)//fs), int(tend*fs), endpoint=False)
in_ = np.random.randn(t.size)
out = bq.filter(in_)
from scipy.signal import welch
nfft = 48000
freq, H1 = welch(out[:,0],
# scaling
fs=fs,nfft=nfft)
freq, H2 = welch(in_,
# scaling
fs=fs,nfft=nfft)
# plt.figure()
plt.semilogx(freq,10*np.log10(np.abs(H1/H2)))
omg, H_ex = sosfreqz(filt)
plt.semilogx(omg/(2*np.pi)*fs, 20*np.log10(np.abs(H_ex)))

75
test/test_cppslm.py Normal file
View File

@ -0,0 +1,75 @@
#!/usr/bin/python3
import numpy as np
from lasp import SLM
from lasp.filter import SPLFilterDesigner
import matplotlib.pyplot as plt
def test_cppslm1():
"""
Generate a sine wave
"""
fs = 48000
omg = 2*np.pi*1000
slm = SLM.fromBiquads(fs, 2e-5, 1, 0.125, [1.,0,0,1,0,0])
t = np.linspace(0, 10, 10*fs, endpoint=False)
# Input signal with an rms of 1 Pa
in_ = np.sin(omg*t)*np.sqrt(2)
# Compute overall RMS
rms = np.sqrt(np.sum(in_**2)/in_.size)
# Compute overall level
level = 20*np.log10(rms/2e-5)
# Output of SLM
out = slm.run(in_)
# Output of SLM should be close to theoretical
# level, at least for reasonable time constants
# (Fast, Slow etc)
assert(np.isclose(out[-1,0], level))
def test_cppslm2():
"""
Generate a sine wave, now A-weighted
"""
fs = 48000
omg = 2*np.pi*1000
filt = SPLFilterDesigner(fs).A_Sos_design()
slm = SLM.fromBiquads(fs, 2e-5, 0, 0.125, filt.flatten(), [1.,0,0,1,0,0])
t = np.linspace(0, 10, 10*fs, endpoint=False)
# Input signal with an rms of 1 Pa
in_ = np.sin(omg*t) *np.sqrt(2)
# Compute overall RMS
rms = np.sqrt(np.sum(in_**2)/in_.size)
# Compute overall level
level = 20*np.log10(rms/2e-5)
# Output of SLM
out = slm.run(in_)
# Output of SLM should be close to theoretical
# level, at least for reasonable time constants
# (Fast, Slow etc)
assert np.isclose(out[-1,0], level, atol=1e-2)
if __name__ == '__main__':
test_cppslm1()
test_cppslm2()
# plt.plot(t,out[:,0])
# plt.close('all')
# from scipy.signal import sosfreqz
# omg, H = sosfreqz(filt)
# freq = omg / (2*np.pi)*fs
# plt.figure()
# plt.semilogx(freq[1:],20*np.log10(np.abs(H[1:])))

View File

@ -1,33 +0,0 @@
// test_bf.c
//
// Author: J.A. de Jong -ASCEE
//
// Description:
//
//////////////////////////////////////////////////////////////////////
#include "lasp_fft.h"
#include "lasp_tracer.h"
int main() {
setTracerLevel(0);
iVARTRACE(15,getTracerLevel());
Fft* fft = Fft_create(100000);
/* Fft_fft(fft,NULL,NULL); */
Fft_free(fft);
return 0;
}
//////////////////////////////////////////////////////////////////////

View File

@ -43,3 +43,6 @@ def test_backward_fft():
assert(np.isclose(np.linalg.norm(sig_py- sig_lasp), 0))
if __name__ == '__main__':
test_forward_fft()
# test_backward_fft()()

View File

@ -1,50 +0,0 @@
// test_bf.c
//
// Author: J.A. de Jong -ASCEE
//
// Description:
//
//////////////////////////////////////////////////////////////////////
#include "lasp_fft.h"
#include "lasp_mat.h"
#include "lasp_tracer.h"
#include "lasp_alg.h"
int main() {
iVARTRACE(15,getTracerLevel());
c a[5];
c_set(a,1-I,5);
/* print_vc(&a); */
vc b = vc_alloc(5);
vc_set(&b,2);
printf("b:\n");
print_vc(&b);
vc c1 = vc_alloc(5);
/* vc_set(&c1,10); */
/* c_add_to(c1.ptr,a.ptr,1,3); */
c_hadamard(c1._data,a,b._data,5);
printf("c1:\n");
print_vc(&c1);
vc_free(&b);
return 0;
}
//////////////////////////////////////////////////////////////////////

View File

@ -6,51 +6,140 @@ Created on Mon Jan 15 19:45:33 2018
@author: anne
"""
import numpy as np
from beamforming import Fft, PowerSpectra, cls
cls
nfft=2048
print('nfft:',nfft)
#print(nfft)
nchannels = 2
from lasp import PowerSpectra, Window
import matplotlib.pyplot as plt
plt.close('all')
# def test_ps():
t = np.linspace(0,1,nfft+1)
# print(t)
x1 = (np.cos(4*np.pi*t[:-1])+3.2*np.sin(6*np.pi*t[:-1]))[:,np.newaxis]+10
x = np.vstack([x1.T]*nchannels).T
# Using transpose to get the strides right
x = np.random.randn(nchannels,nfft).T
print("strides: ",x.strides)
# x.strides = (8,nfft*8)x
# print("signal:",x)
nfft = 8
t = np.linspace(0, 1.0, nfft, endpoint=False)
xms = np.sum(x**2,axis=0)/nfft
print('Total signal power time domain: ', xms)
ps = PowerSpectra(nfft, Window.Rectangular)
X = np.fft.rfft(x,axis=0)
# X =np.fft.fft(x)
#X =np.fft.rfft(x)
# print(X)
Xs = 2*X/nfft
Xs[np.where(np.abs(Xs) < 1e-10)] = 0
Xs[0] /= np.sqrt(2)
Xs[-1] /= np.sqrt(2)
# print('single sided amplitude spectrum:\n',Xs)
sig = np.random.randn(nfft)
freq = 4
omg = 2*np.pi*freq
# sig = 8*np.cos(omg*t)
power = Xs*np.conj(Xs)/2
cps = ps.compute(sig)
# print('Frequency domain signal power\n', power)
print('Total signal power', np.sum(power,axis=0).real)
pow1 = np.sum(sig**2)/sig.size
pow2 = np.sum((cps[:,0,0]).real)
pstest = PowerSpectra(nfft,nchannels)
ps = pstest.compute(x)
# print(pow1)
# print(pow2)
plt.plot(cps[:,0,0])
assert np.isclose(pow2 - pow1,0, atol=1e-1)
# test_ps()
# plt.plot(res_lasp.real-res_npy.real)
# plt.plot(res_lasp.imag-res_npy.imag)
# plt.plot(res_npy.real)
# plt.plot(res_npy.imag)
# plt.plot(t, sig)
# print('nfft:',nfft)
# #print(nfft)
# nchannels = 2
fft = Fft(nfft,nchannels)
fft.fft(x)
# t = np.linspace(0,1,nfft+1)
# # print(t)
# x1 = (np.cos(4*np.pi*t[:-1])+3.2*np.sin(6*np.pi*t[:-1]))[:,np.newaxis]+10
# x = np.vstack([x1.T]*nchannels).T
# # Using transpose to get the strides right
# x = np.random.randn(nchannels,nfft).T
# print("strides: ",x.strides)
# # x.strides = (8,nfft*8)x
# # print("signal:",x)
ps[np.where(np.abs(ps) < 1e-10)] = 0+0j
# xms = np.sum(x**2,axis=0)/nfft
# print('Total signal power time domain: ', xms)
print('our ps: \n' , ps)
# X = np.fft.rfft(x,axis=0)
# # X =np.fft.fft(x)
# #X =np.fft.rfft(x)
print('Our total signal power: ',np.sum(ps,axis=0).real)
# # print(X)
# Xs = 2*X/nfft
# Xs[np.where(np.abs(Xs) < 1e-10)] = 0
# Xs[0] /= np.sqrt(2)
# Xs[-1] /= np.sqrt(2)
# # print('single sided amplitude spectrum:\n',Xs)
# power = Xs*np.conj(Xs)/2
# # print('Frequency domain signal power\n', power)
# print('Total signal power', np.sum(power,axis=0).real)
# pstest = PowerSpectra(nfft,nchannels)
# ps = pstest.compute(x)
# fft = Fft(nfft,nchannels)
# fft.fft(x)
# ps[np.where(np.abs(ps) < 1e-10)] = 0+0j
# print('our ps: \n' , ps)
# print('Our total signal power: ',np.sum(ps,axis=0).real)
# if __name__ == '__main__':
# nfft=2048
# fs = 2048
# ps = PowerSpectra(nfft, Window.Rectangular)
# t = np.linspace(0, 1.0, nfft, endpoint=False)
# freq = 10
# omg = 2*np.pi*freq
# sig = np.sin(omg*t)
# res = ps.compute(sig)
# plt.plot(res[:,0,0])
# # plt.plot(t, sig)
# print('nfft:',nfft)
# #print(nfft)
# nchannels = 2
# t = np.linspace(0,1,nfft+1)
# # print(t)
# x1 = (np.cos(4*np.pi*t[:-1])+3.2*np.sin(6*np.pi*t[:-1]))[:,np.newaxis]+10
# x = np.vstack([x1.T]*nchannels).T
# # Using transpose to get the strides right
# x = np.random.randn(nchannels,nfft).T
# print("strides: ",x.strides)
# # x.strides = (8,nfft*8)x
# # print("signal:",x)
# xms = np.sum(x**2,axis=0)/nfft
# print('Total signal power time domain: ', xms)
# X = np.fft.rfft(x,axis=0)
# # X =np.fft.fft(x)
# #X =np.fft.rfft(x)
# # print(X)
# Xs = 2*X/nfft
# Xs[np.where(np.abs(Xs) < 1e-10)] = 0
# Xs[0] /= np.sqrt(2)
# Xs[-1] /= np.sqrt(2)
# # print('single sided amplitude spectrum:\n',Xs)
# power = Xs*np.conj(Xs)/2
# # print('Frequency domain signal power\n', power)
# print('Total signal power', np.sum(power,axis=0).real)
# pstest = PowerSpectra(nfft,nchannels)
# ps = pstest.compute(x)
# fft = Fft(nfft,nchannels)
# fft.fft(x)
# ps[np.where(np.abs(ps) < 1e-10)] = 0+0j
# print('our ps: \n' , ps)
# print('Our total signal power: ',np.sum(ps,axis=0).real)

View File

@ -1,35 +0,0 @@
#!/usr/bin/python3
import numpy as np
from lasp_rtaudio import RtAudio, SampleFormat, Format_SINT32, Format_FLOAT64
import time
nframes = 0
samplerate = 48000
omg = 2*np.pi*1000
def mycallback(input_, nframes, streamtime):
t = np.linspace(streamtime, streamtime + nframes/samplerate,
nframes)[np.newaxis,:]
outp = 0.1*np.sin(omg*t)
return outp, 0
if __name__ == '__main__':
pa = RtAudio()
count = pa.getDeviceCount()
# dev = pa.getDeviceInfo(0)
for i in range(count):
dev = pa.getDeviceInfo(i)
print(dev)
outputparams = {'deviceid': 0, 'nchannels': 1, 'firstchannel': 0}
pa.openStream(outputparams, None , Format_FLOAT64,samplerate, 512, mycallback)
pa.startStream()
input()
pa.stopStream()
pa.closeStream()