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) 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 17)
set(CMAKE_CXX_STANDARD_REQUIRED) 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_DOUBLE_PRECISION "Compile as double precision floating point" ON)
option(LASP_HAS_RTAUDIO "Compile with RtAudio Daq backend" ON) option(LASP_HAS_RTAUDIO "Compile with RtAudio Daq backend" ON)
option(LASP_HAS_ULDAQ "Compile with UlDaq 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(LASP_FFT_BACKEND "FFTW" CACHE STRING "FFT Library backend")
set_property(CACHE LASP_FFT_BACKEND PROPERTY STRINGS "FFTW" "Armadillo") 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 # Required for PYBIND11
set(CMAKE_POSITION_INDEPENDENT_CODE ON) set(CMAKE_POSITION_INDEPENDENT_CODE ON)
if(CMAKE_BUILD_TYPE STREQUAL Debug) if(${CMAKE_BUILD_TYPE} STREQUAL "Debug")
set(LASP_DEBUG True) set(LASP_DEBUG True)
else() else()
set(LASP_DEBUG False) set(LASP_DEBUG False)
endif() 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 # link openblas
set(BLA_VENDOR OpenBLAS) set(BLA_VENDOR OpenBLAS)
find_package(BLAS REQUIRED) find_package(BLAS REQUIRED)
@ -49,18 +65,21 @@ add_definitions(-DLASP_MAX_NFFT=33554432) # 2**25
# ####################################### End of user-adjustable variables section # ####################################### End of user-adjustable variables section
include(OSSpecific) include(OSSpecific)
# Enable openmp
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-type-limits") 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 \ set(CMAKE_C_FLAGS_RELEASE "-O3 -flto -mfpmath=sse -march=x86-64 -mtune=native \
-fdata-sections -ffunction-sections -fomit-frame-pointer -finline-functions") -fdata-sections -ffunction-sections -fomit-frame-pointer -finline-functions")
# ############################# End compilation flags # ############################# End compilation flags
include_directories(/usr/lib/python3.10/site-packages/numpy/core/include)
add_subdirectory(third_party/carma) add_subdirectory(third_party/carma)
if(LASP_FFT_BACKEND STREQUAL "FFTW") if(LASP_FFT_BACKEND STREQUAL "FFTW")
find_library(FFTW_LIBRARY NAMES fftw3 fftw) find_library(fftw3 REQUIRED NAMES fftw fftw3)
set(FFT_LIBRARIES "${FFTW_LIBRARY}") set(LASP_FFT_LIBS "fftw3")
message("LASP_FFT_LIBS = ${LASP_FFT_LIBS}")
elseif(LASP_FFT_BACKEND STREQUAL "Armadillo") elseif(LASP_FFT_BACKEND STREQUAL "Armadillo")
endif() endif()
add_subdirectory(src/lasp) add_subdirectory(src/lasp)

View File

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

View File

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

View File

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

View File

@ -26,4 +26,3 @@ requires = [
] ]
build-backend = "setuptools.build_meta" 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 target_link_libraries(lasp_cpp PRIVATE lasp_device_lib lasp_dsp_lib
carma::carma
${OpenMP_CXX_LIBRARIES} ${LASP_FFT_LIBS}) ${OpenMP_CXX_LIBRARIES} ${LASP_FFT_LIBS})
install(TARGETS lasp_cpp DESTINATION .) install(TARGETS lasp_cpp DESTINATION .)

View File

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

View File

@ -4,6 +4,10 @@
#include <map> #include <map>
#include <vector> #include <vector>
/** \addtogroup device
* @{
*/
using std::string; using std::string;
using std::to_string; using std::to_string;
@ -67,17 +71,30 @@ public:
}; };
const DataTypeDescriptor dtype_desc_fl32 = { const DataTypeDescriptor dtype_desc_fl32 = {
"32-bits floating point", 4, true, .name = "32-bits floating point",
DataTypeDescriptor::DataType::dtype_fl32}; .sw = 4,
.is_floating = true,
.dtype = DataTypeDescriptor::DataType::dtype_fl32};
const DataTypeDescriptor dtype_desc_fl64 = { const DataTypeDescriptor dtype_desc_fl64 = {
"64-bits floating point", 8, true, .name = "64-bits floating point",
DataTypeDescriptor::DataType::dtype_fl64}; .sw = 8,
.is_floating = true,
.dtype = DataTypeDescriptor::DataType::dtype_fl64};
const DataTypeDescriptor dtype_desc_int8 = { 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 = { 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 = { 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> const std::map<DataTypeDescriptor::DataType, const DataTypeDescriptor>
dtype_map = { dtype_map = {
@ -88,6 +105,12 @@ const std::map<DataTypeDescriptor::DataType, const DataTypeDescriptor>
{DataTypeDescriptor::DataType::dtype_int32, dtype_desc_int32}, {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 { class DaqApi {
public: public:
string apiname = "Invalid API"; string apiname = "Invalid API";
@ -127,7 +150,9 @@ const DaqApi rtaudioAsioApi("RtAudio Windows ASIO", 1,
class DeviceInfo; 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 { class DaqChannel {
@ -245,7 +270,7 @@ public:
* *
* @param deviceinfo DeviceInfo structure * @param deviceinfo DeviceInfo structure
*/ */
DaqConfiguration(const DeviceInfo &DeviceInfo); DaqConfiguration(const DeviceInfo &deviceinfo);
DaqConfiguration() {} DaqConfiguration() {}
@ -294,3 +319,6 @@ public:
*/ */
int getLowestOutChannel() const; int getLowestOutChannel() const;
}; };
/**
* @}
*/

View File

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

View File

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

View File

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

View File

@ -4,7 +4,11 @@
#include <list> #include <list>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <thread>
/** \addtogroup device
* @{
*/
class StreamMgr; class StreamMgr;
class InDataHandler { class InDataHandler {
@ -51,15 +55,20 @@ public:
* like "pure virtual function called", or other**. * like "pure virtual function called", or other**.
*/ */
void stop(); void stop();
}; };
/** /**
* @brief Stream manager. Used to manage the input and output streams. * @brief Stream manager. Used to manage the input and output streams.
* Implemented as a singleton: only one stream manager can be in existance for * 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 { class StreamMgr {
#if LASP_DEBUG == 1
std::thread::id _main_thread_id;
#endif
/** /**
* @brief Storage for streams. * @brief Storage for streams.
@ -81,6 +90,9 @@ class StreamMgr {
std::shared_ptr<Siggen> _siggen; std::shared_ptr<Siggen> _siggen;
std::mutex _siggen_mtx; std::mutex _siggen_mtx;
std::mutex _devices_mtx;
std::vector<DeviceInfo> _devices;
StreamMgr(); StreamMgr();
friend class InDataHandler; friend class InDataHandler;
@ -90,7 +102,6 @@ class StreamMgr {
~StreamMgr(); ~StreamMgr();
public: public:
enum class StreamType : us { enum class StreamType : us {
/** /**
* @brief Input stream * @brief Input stream
@ -113,12 +124,34 @@ public:
static StreamMgr &getInstance(); 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 * @return A copy of the internal stored list of devices
* @param config Configuration of device
*/ */
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 * @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. * @param handler The handler to add.
*/ */
void addInDataHandler(InDataHandler &handler); 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) PowerSpectra::PowerSpectra(const vd &window)
: nfft(window.size()), _fft(nfft), _window(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 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) { 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. // Multiply each column of the inputs element-wise with the window.
input_tmp.each_col() %= _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); 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 i = 0; i < input.n_cols; i++) {
for (us j = 0; j < input.n_cols; j++) { 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; return output;

View File

@ -6,6 +6,9 @@
#include <memory> #include <memory>
#include <optional> #include <optional>
/** \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
*/ */
@ -151,3 +154,4 @@ public:
*/ */
us exactOverlapSamples() const { return _ps.nfft - _overlap_keep; } us exactOverlapSamples() const { return _ps.nfft - _overlap_keep; }
}; };
/** @} */

View File

@ -6,14 +6,18 @@
using std::cerr; using std::cerr;
using std::endl; using std::endl;
using std::runtime_error; using rte = std::runtime_error;
SeriesBiquad::SeriesBiquad(const vd &filter_coefs) { SeriesBiquad::SeriesBiquad(const vd &filter_coefs) {
DEBUGTRACE_ENTER; 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) { if (filter_coefs.n_rows % 6 != 0) {
cerr << "Number of rows given: " << filter_coefs.n_rows << endl; cerr << "Number of rows given: " << filter_coefs.n_rows << endl;
throw runtime_error("filter_coefs should be multiple of 6, given: " + throw rte("filter_coefs should be multiple of 6, given: " +
std::to_string(filter_coefs.n_rows)); std::to_string(filter_coefs.n_rows));
} }
us nfilters = filter_coefs.n_rows / 6; us nfilters = filter_coefs.n_rows / 6;
@ -31,7 +35,7 @@ SeriesBiquad::SeriesBiquad(const vd &filter_coefs) {
"absdiff", 1e-9)) { "absdiff", 1e-9)) {
std::cerr << "Read row: " << sos.row(3) << endl; 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"); "Filter coefficients should have fourth element (a0) equal to 1.0");
} }
} }

View File

@ -7,11 +7,12 @@
// //
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
#include <memory> #include <memory>
#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"
using std::runtime_error; using rte = std::runtime_error;
#if LASP_FFT_BACKEND == Armadillo #if LASP_FFT_BACKEND == Armadillo
#include "fftpack.h" #include "fftpack.h"
@ -55,6 +56,10 @@ public:
reverse_plan = reverse_plan =
fftw_plan_dft_c2r_1d(nfft, frequencyDomain, timeDomain, FFTW_MEASURE); fftw_plan_dft_c2r_1d(nfft, frequencyDomain, timeDomain, FFTW_MEASURE);
if(!forward_plan || !reverse_plan || !timeDomain || !frequencyDomain) {
throw rte("Error allocating FFT");
}
}; };
~Fft_impl() { ~Fft_impl() {
@ -65,6 +70,9 @@ public:
} }
vc fft(const vd &time) { vc fft(const vd &time) {
if(time.n_rows != nfft) {
throw rte("Invalid size of input vector, should be equal to nfft");
}
vc res(nfft / 2 + 1); vc res(nfft / 2 + 1);
memcpy(timeDomain, time.memptr(), sizeof(d) * nfft); memcpy(timeDomain, time.memptr(), sizeof(d) * nfft);
@ -72,23 +80,27 @@ public:
fftw_execute(forward_plan); fftw_execute(forward_plan);
memcpy(reinterpret_cast<void *>(res.memptr()), frequencyDomain, memcpy(reinterpret_cast<void *>(res.memptr()), frequencyDomain,
sizeof(d) * (nfft / 2 + 1)); sizeof(c) * (nfft / 2 + 1));
return res; return res;
} }
vd ifft(const vc &f) { vd ifft(const vc &f) {
vd res(nfft); 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, memcpy(frequencyDomain,
reinterpret_cast<void *>(const_cast<c *>(f.memptr())), reinterpret_cast<void *>(const_cast<c *>(f.memptr())),
(nfft / 2 + 1) * sizeof(c)); (nfft / 2 + 1) * sizeof(c));
fftw_execute(reverse_plan); fftw_execute(reverse_plan);
memcpy(res.memptr(), frequencyDomain, nfft * sizeof(d)); vd res(nfft);
memcpy(res.memptr(), timeDomain, nfft * sizeof(d));
/* Scale by dividing by nfft. Checked with numpy implementation /* Scale by dividing by nfft. Checked with numpy implementation
* that this indeed needs to be done for FFTW. */ * that this indeed needs to be done for FFTW. */
res *= 1 / nfft; res *= 1.0 / nfft;
return res; return res;
} }
@ -101,34 +113,44 @@ public:
Fft::Fft(const us nfft) { Fft::Fft(const us nfft) {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
if (nfft == 0) { if (nfft == 0) {
throw std::runtime_error("Invalid nfft: 0"); throw rte("Invalid nfft: 0");
} }
if (nfft >= LASP_MAX_NFFT) { if (nfft >= LASP_MAX_NFFT) {
throw std::runtime_error("Invalid nfft, should be smaller than: " + throw rte("Invalid nfft, should be smaller than: " +
std::to_string(LASP_MAX_NFFT)); std::to_string(LASP_MAX_NFFT));
} }
_impl = std::make_unique<Fft_impl>(nfft); _impl = std::make_unique<Fft_impl>(nfft);
} }
Fft::~Fft() { } 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) { vd Fft::ifft(const vc &freqdata) {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
if (freqdata.size() != (_impl->nfft / 2 + 1)) { assert(_impl);
throw runtime_error("Invalid size for frequency domain data rows."
" Should be equal to floor(nfft/2)+1");
}
return _impl->ifft(freqdata); return _impl->ifft(freqdata);
} }
dmat Fft::ifft(const cmat &freqdata) { dmat Fft::ifft(const cmat &freqdata) {
dmat res(_impl->nfft, freqdata.n_cols); dmat res(_impl->nfft, freqdata.n_cols);
#pragma omp parallel for
for (us colno = 0; colno < freqdata.n_cols; colno++) { for (us colno = 0; colno < freqdata.n_cols; colno++) {
res.col(colno) = _impl->ifft(freqdata.col(colno)); res.col(colno) = _impl->ifft(freqdata.col(colno));
} }
return res; return res;
} }
vc Fft::fft(const vd &timedata) { return _impl->fft(timedata); }
void load_fft_wisdom(const char *wisdom) { void load_fft_wisdom(const char *wisdom) {
#if LASP_FFT_BACKEND == Armadillo #if LASP_FFT_BACKEND == Armadillo

View File

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

View File

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

View File

@ -1,6 +1,6 @@
#define DEBUGTRACE_ENABLED #define DEBUGTRACE_ENABLED
#include "lasp_slm.h"
#include "debugtrace.hpp" #include "debugtrace.hpp"
#include "lasp_slm.h"
#include "lasp_thread.h" #include "lasp_thread.h"
#include <algorithm> #include <algorithm>
#include <cmath> #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 SLM::run_single(vd work,const us i) {
vd work(_work, // Aux Memory pointer
size, // Number of elements
false, // Copy aux mem
true // Stric, means we keep being bound to the memory
);
// Filter input in-place // Filter input in-place
_bandpass[i]->filter(work); _bandpass[i]->filter(work);
@ -156,35 +150,22 @@ dmat SLM::run(const vd &input_orig) {
_pre_filter->filter(input); _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 // Fan out over multiple threads, as it is typically a heavy load
dmat res(input.n_rows, _bandpass.size()); dmat res(input.n_rows, _bandpass.size());
// Perform operations in-place. // Perform operations in-place.
#pragma omp parallel for
for (us i = 0; i < _bandpass.size(); i++) { for (us i = 0; i < _bandpass.size(); i++) {
res.col(i) = input; res.col(i) = run_single(input, i);
/// 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));
}
/* DEBUGTRACE_PRINT(_bandpass.size()); */ /* DEBUGTRACE_PRINT(_bandpass.size()); */
/* DEBUGTRACE_PRINT(res.n_cols); */ /* DEBUGTRACE_PRINT(res.n_cols); */
/* DEBUGTRACE_PRINT(res.n_rows); */ /* DEBUGTRACE_PRINT(res.n_rows); */
/* DEBUGTRACE_PRINT(futs.size()); */ /* 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 // Update the total number of samples harvested so far. NOTE: *This should be
// done AFTER the threads are done!!!* // done AFTER the threads are done!!!*
}
N += input.n_rows; N += input.n_rows;
// Downsample, if applicable // Downsample, if applicable

View File

@ -126,5 +126,5 @@ public:
vd Lmax() const { return 10*arma::log10(Pmax/Lref);}; vd Lmax() const { return 10*arma::log10(Pmax/Lref);};
private: 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; 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 { class ThreadedInDataHandler: public InDataHandler {
/** /**
* @brief The queue used to push elements to the handling thread. * @brief The queue used to push elements to the handling thread.

View File

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

View File

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

View File

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

View File

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

View File

@ -35,7 +35,5 @@ void init_deviceinfo(py::module& m) {
devinfo.def_readonly("hasInputACCouplingSwitch", devinfo.def_readonly("hasInputACCouplingSwitch",
&DeviceInfo::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_biquadbank.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>
@ -12,6 +14,14 @@ namespace py = pybind11;
void init_dsp(py::module &m) { 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::class_<Window> w(m, "Window");
py::enum_<Window::WindowType>(w, "WindowType") py::enum_<Window::WindowType>(w, "WindowType")
@ -37,7 +47,17 @@ void init_dsp(py::module &m) {
return res; 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"); py::class_<SLM> slm(m, "SLM");
slm.def_static( slm.def_static(
"fromBiquads", "fromBiquads",
py::overload_cast<const d, const d, const us, const d, const dmat &>( 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("stopAllStreams", &StreamMgr::stopAllStreams);
smgr.def("setSiggen", &StreamMgr::setSiggen); 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)) 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 @author: anne
""" """
import numpy as np import numpy as np
from beamforming import Fft, PowerSpectra, cls from lasp import PowerSpectra, Window
cls import matplotlib.pyplot as plt
nfft=2048 plt.close('all')
print('nfft:',nfft) # def test_ps():
#print(nfft)
nchannels = 2
t = np.linspace(0,1,nfft+1) nfft = 8
# print(t) t = np.linspace(0, 1.0, nfft, endpoint=False)
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 ps = PowerSpectra(nfft, Window.Rectangular)
print('Total signal power time domain: ', xms)
X = np.fft.rfft(x,axis=0) sig = np.random.randn(nfft)
# X =np.fft.fft(x) freq = 4
#X =np.fft.rfft(x) omg = 2*np.pi*freq
# sig = 8*np.cos(omg*t)
# 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 cps = ps.compute(sig)
# print('Frequency domain signal power\n', power) pow1 = np.sum(sig**2)/sig.size
print('Total signal power', np.sum(power,axis=0).real) pow2 = np.sum((cps[:,0,0]).real)
pstest = PowerSpectra(nfft,nchannels) # print(pow1)
ps = pstest.compute(x) # 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) # t = np.linspace(0,1,nfft+1)
fft.fft(x) # # 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()