Compare commits

..

No commits in common. "master" and "v1.6.6" have entirely different histories.

11 changed files with 216 additions and 93 deletions

View File

@ -4,7 +4,6 @@ project(LASP LANGUAGES C CXX VERSION 1.6.3)
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED) set(CMAKE_CXX_STANDARD_REQUIRED)
# Experimental: support for Raspberry Pi (64-bit architectures)
if(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "aarch64") if(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "aarch64")
set(RPI TRUE) set(RPI TRUE)
else() else()
@ -56,6 +55,7 @@ cmake_policy(SET CMP0079 NEW)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake") set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake")
include_directories(/usr/include/python3.8)
include("BuildType") include("BuildType")
include("QueryPythonForPybind11") include("QueryPythonForPybind11")

View File

@ -44,17 +44,15 @@ in a sister repository [lasp-doc](https://code.ascee.nl/ascee/lasp-doc).
If you have any question(s), please feel free to contact us: [email](info@ascee.nl). If you have any question(s), please feel free to contact us: [email](info@ascee.nl).
# Installation - Linux (Ubuntu-based) # Installation - Linux (Ubuntu-based)
## Prerequisites ## Prerequisites
Run the following on the command line to install all prerequisites on Run the following on the command line to install all prerequisites on
Debian-based Linux, x86-64: Debian-based Linux:
- `sudo apt install python3-pip libfftw3-3 libopenblas-base libusb-1.0-0 libpulse0` - `sudo apt install python3-pip libfftw3-3 libopenblas-base libusb-1.0-0 libpulse0`
## Installation from wheel (recommended for non-developers) ## Installation from wheel (recommended for non-developers)
Go to: [LASP releases](https://code.ascee.nl/ASCEE/lasp/releases/latest/) and Go to: [LASP releases](https://code.ascee.nl/ASCEE/lasp/releases/latest/) and
@ -84,28 +82,6 @@ If building RtAudio with the Jack Audio Connection Kit (JACK) backend, you will
- `$ cd lasp` - `$ cd lasp`
- `pip install -e .` - `pip install -e .`
# Building and installation for Raspberry Pi (Raspberry Pi OS)
Run the following on the command line to install all prerequisites on
Raspberry Pi OS:
- `sudo apt install libfftw3-dev libopenblas64-dev libhdf5-dev libclalsadrv-dev`
In a virtualenv: install `build`
- `$ pip install build`
Then run:
- `$ git clone --recursive https://code.ascee.nl/ASCEE/lasp.git`
- `$ cd lasp`
- `$ pyproject-build`
Which will generate a `whl` in the `dist` folder, that is redistributable for Raspberry Pis that run Raspberry Pi OS.
When installing the `whl`, it appears that H5PY takes quite some time to install. To follow this process, run it it verbose mode.
# Installation - (x86_64) Windows (with WinPython), build with MSYS2 # Installation - (x86_64) Windows (with WinPython), build with MSYS2
## Prerequisites ## Prerequisites

View File

@ -60,4 +60,4 @@ if(DEFINED PY_BUILD_CMAKE_MODULE_NAME)
COMPONENT python_modules COMPONENT python_modules
DESTINATION ${PY_BUILD_CMAKE_MODULE_NAME}) DESTINATION ${PY_BUILD_CMAKE_MODULE_NAME})
endif() endif()

View File

@ -107,12 +107,37 @@ void StreamMgr::rescanDAQDevices(bool background,
_pool.push_task(&StreamMgr::rescanDAQDevices_impl, this, callback); _pool.push_task(&StreamMgr::rescanDAQDevices_impl, this, callback);
} }
} }
#if LASP_HAS_PORTAUDIO && LASP_HAS_PA_ALSA
#include <alsa/asoundlib.h>
void empty_handler(const char *file, int line, const char *function, int err,
const char *fmt, ...) {}
// Temporarily set the ALSA eror handler to something that does nothing, to
// prevent ALSA from spitting out all kinds of misconfiguration errors.
class MuteErrHandler {
private:
snd_lib_error_handler_t _default_handler;
public:
explicit MuteErrHandler() {
_default_handler = snd_lib_error;
snd_lib_error_set_handler(empty_handler);
}
~MuteErrHandler() { snd_lib_error_set_handler(_default_handler); }
};
#else
// Does nothin in case of no ALSA
class MuteErrHandler {};
#endif
void StreamMgr::rescanDAQDevices_impl(std::function<void()> callback) { void StreamMgr::rescanDAQDevices_impl(std::function<void()> callback) {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
assert(!_inputStream && !_outputStream); assert(!_inputStream && !_outputStream);
Lck lck(_mtx); Lck lck(_mtx);
// Alsa spits out annoying messages that are not useful // Alsa spits out annoying messages that are not useful
{ {
MuteErrHandler guard;
_devices = DeviceInfo::getDeviceInfo(); _devices = DeviceInfo::getDeviceInfo();
} }

View File

@ -16,33 +16,6 @@ using std::endl;
using std::string; using std::string;
using std::to_string; using std::to_string;
#if LASP_HAS_PA_ALSA
#include <alsa/asoundlib.h>
void empty_handler(const char *file, int line, const char *function, int err,
const char *fmt, ...) {
// cerr << "Test empty error handler...\n";
}
// Temporarily set the ALSA eror handler to something that does nothing, to
// prevent ALSA from spitting out all kinds of misconfiguration errors.
class MuteErrHandler {
private:
snd_lib_error_handler_t _default_handler;
public:
explicit MuteErrHandler() {
_default_handler = snd_lib_error;
snd_lib_error_set_handler(empty_handler);
}
~MuteErrHandler() { snd_lib_error_set_handler(_default_handler); }
};
#else
// Does nothin in case of no ALSA
class MuteErrHandler {};
#endif
inline void throwIfError(PaError e) { inline void throwIfError(PaError e) {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
if (e != paNoError) { if (e != paNoError) {
@ -71,7 +44,6 @@ class OurPaDeviceInfo : public DeviceInfo {
void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist) { void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist) {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
bool shouldPaTerminate = false; bool shouldPaTerminate = false;
MuteErrHandler guard;
try { try {
PaError err = Pa_Initialize(); PaError err = Pa_Initialize();
/// PortAudio says that Pa_Terminate() should not be called whenever there /// PortAudio says that Pa_Terminate() should not be called whenever there
@ -376,7 +348,6 @@ PortAudioDaq::PortAudioDaq(const OurPaDeviceInfo &devinfo_gen,
void PortAudioDaq::start(InDaqCallback inCallback, OutDaqCallback outCallback) { void PortAudioDaq::start(InDaqCallback inCallback, OutDaqCallback outCallback) {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
assert(_stream); assert(_stream);
MuteErrHandler guard;
if (Pa_IsStreamActive(_stream)) { if (Pa_IsStreamActive(_stream)) {
throw rte("Stream is already running"); throw rte("Stream is already running");

View File

@ -5,7 +5,7 @@ requires-python = ">=3.10"
description = "Library for Acoustic Signal Processing" description = "Library for Acoustic Signal Processing"
license = { "file" = "LICENSE" } license = { "file" = "LICENSE" }
authors = [{ "name" = "J.A. de Jong", "email" = "j.a.dejong@ascee.nl" }] authors = [{ "name" = "J.A. de Jong", "email" = "j.a.dejong@ascee.nl" }]
version = "1.6.8" version = "1.6.6"
keywords = ["DSP", "DAQ", "Signal processing"] keywords = ["DSP", "DAQ", "Signal processing"]

View File

@ -10,11 +10,11 @@ from .lasp_cpp import *
# from .lasp_imptube import * # TwoMicImpedanceTube # from .lasp_imptube import * # TwoMicImpedanceTube
from .lasp_measurement import * # Measurement, scaleBlockSens from .lasp_measurement import * # Measurement, scaleBlockSens
from .lasp_octavefilter import * # OverallFilterBank, SosOctaveFilterBank, SosThirdOctaveFilterBank from .lasp_octavefilter import *
from .lasp_slm import * # SLM, Dummy from .lasp_slm import * # SLM, Dummy
from .lasp_record import * # RecordStatus, Recording from .lasp_record import * # RecordStatus, Recording
from .lasp_daqconfigs import * # DaqConfigurations from .lasp_daqconfigs import *
from .lasp_measurementset import * # MeasurementSet from .lasp_measurementset import *
# from .lasp_siggen import * # SignalType, NoiseType, SiggenMessage, SiggenData, Siggen # from .lasp_siggen import * # SignalType, NoiseType, SiggenMessage, SiggenData, Siggen
# from .lasp_weighcal import * # WeighCal # from .lasp_weighcal import * # WeighCal

View File

@ -121,7 +121,7 @@ class SIQtys(Enum):
unit_name='Pascal', unit_name='Pascal',
unit_symb='Pa', unit_symb='Pa',
level_unit=('dB SPL','dBPa'), level_unit=('dB SPL','dBPa'),
level_ref_name=('20 micropascal', '1 pascal',), level_ref_name=('2 micropascal', '1 pascal',),
level_ref_value=(P_REF, 1), level_ref_value=(P_REF, 1),
cpp_enum = DaqChannel.Qty.AcousticPressure cpp_enum = DaqChannel.Qty.AcousticPressure
) )

View File

@ -16,28 +16,13 @@ else:
LASP_NUMPY_COMPLEX_TYPE = np.float64 LASP_NUMPY_COMPLEX_TYPE = np.float64
def zeros(shape, dtype=float, order='F'): def zeros(shape):
if dtype == float: return np.zeros(shape, dtype=LASP_NUMPY_FLOAT_TYPE, order='F')
return np.zeros(shape, dtype=LASP_NUMPY_FLOAT_TYPE, order=order)
elif dtype == complex:
return np.zeros(shape, dtype=LASP_NUMPY_COMPLEX_TYPE, order=order)
else:
raise RuntimeError(f"Unknown dtype: {dtype}")
def ones(shape, dtype=float, order='F'): def ones(shape):
if dtype == float: return np.ones(shape, dtype=LASP_NUMPY_FLOAT_TYPE, order='F')
return np.ones(shape, dtype=LASP_NUMPY_FLOAT_TYPE, order=order)
elif dtype == complex:
return np.ones(shape, dtype=LASP_NUMPY_COMPLEX_TYPE, order=order)
else:
raise RuntimeError(f"Unknown dtype: {dtype}")
def empty(shape, dtype=float, order='F'):
if dtype == float:
return np.empty(shape, dtype=LASP_NUMPY_FLOAT_TYPE, order=order)
elif dtype == complex:
return np.empty(shape, dtype=LASP_NUMPY_COMPLEX_TYPE, order=order)
else:
raise RuntimeError(f"Unknown dtype: {dtype}")
def empty(shape):
return np.empty(shape, dtype=LASP_NUMPY_FLOAT_TYPE, order='F')

View File

@ -77,9 +77,9 @@ class MeasurementType(Enum):
Measurement flags related to the measurement. Stored as bit flags in the measurement file. This is for possible changes in the API later. Measurement flags related to the measurement. Stored as bit flags in the measurement file. This is for possible changes in the API later.
""" """
# Not specific measurement type # Not specific measurement type
NotSpecific = 0 NotSpecific = 0
# Measurement serves as an insertion loss reference measurement # Measurement serves as an insertion loss reference measurement
ILReference = 1 << 0 ILReference = 1 << 0
@ -1156,8 +1156,11 @@ class Measurement:
if data.ndim != 2: if data.ndim != 2:
data = data[:, np.newaxis] data = data[:, np.newaxis]
if not (isinstance(sensitivity, np.ndarray) and sensitivity.ndim >= 1): try:
sensitivity = np.asarray(sensitivity)[np.newaxis] len(sensitivity)
except:
raise ValueError("Sensitivity should be given as array-like data type")
sensitivity = np.asarray(sensitivity)
nchannels = data.shape[1] nchannels = data.shape[1]
if nchannels != sensitivity.shape[0]: if nchannels != sensitivity.shape[0]:
@ -1170,7 +1173,7 @@ class Measurement:
raise RuntimeError("Illegal length of channelNames list given") raise RuntimeError("Illegal length of channelNames list given")
if qtys is None: if qtys is None:
qtys = [SIQtys.fromInt(1)] * nchannels # Acoustic pressure is default qtys = [SIQtys.AP] * nchannels
else: else:
if len(qtys) != nchannels: if len(qtys) != nchannels:
raise RuntimeError("Illegal length of qtys list given") raise RuntimeError("Illegal length of qtys list given")

View File

@ -6,10 +6,12 @@ Author: J.A. de Jong - ASCEE
Provides the implementations of (fractional) octave filter banks Provides the implementations of (fractional) octave filter banks
""" """
__all__ = ['FirOctaveFilterBank', 'FirThirdOctaveFilterBank',
'OverallFilterBank', 'SosOctaveFilterBank',
'SosThirdOctaveFilterBank']
__all__ = ["OverallFilterBank", "SosOctaveFilterBank", "SosThirdOctaveFilterBank"] from .filter.filterbank_design import (OctaveBankDesigner,
ThirdOctaveBankDesigner)
from .filter.filterbank_design import OctaveBankDesigner, ThirdOctaveBankDesigner
from .lasp_cpp import BiquadBank from .lasp_cpp import BiquadBank
import numpy as np import numpy as np
@ -17,7 +19,7 @@ import numpy as np
class OverallFilterBank: class OverallFilterBank:
""" """
Dummy type filter bank. Does nothing special, only returns output in a Dummy type filter bank. Does nothing special, only returns output in a
way compatible with SosFilterBank. sensible way
""" """
def __init__(self, fs): def __init__(self, fs):
@ -49,13 +51,174 @@ class OverallFilterBank:
t = np.linspace(tstart, tend, Ncur, endpoint=False) t = np.linspace(tstart, tend, Ncur, endpoint=False)
self.N += Ncur self.N += Ncur
output["Overall"] = {"t": t, "data": data, "x": 0} output['Overall'] = {'t': t, 'data': data, 'x': 0}
return output return output
def decimation(self, x): def decimation(self, x):
return [1] return [1]
class FirFilterBank:
"""
Single channel (fractional) octave filter bank implementation, based on FIR
filters and sample rate decimation.
"""
def __init__(self, fs, xmin, xmax):
"""
Initialize a OctaveFilterBank object.
Args:
fs: Sampling frequency of base signal
"""
assert np.isclose(fs, 48000), "Only sampling frequency" \
" available is 48 kHz"
self.fs = fs
self.xs = list(range(xmin, xmax + 1))
raise RuntimeError('Not working code anymore')
maxdecimation = self.designer.firDecimation(self.xs[0])
self.decimators = []
for dec in maxdecimation:
self.decimators.append(Decimator(1, dec))
xs_d1 = []
xs_d4 = []
xs_d16 = []
xs_d64 = []
xs_d256 = []
self.filterbanks = []
# Sort the x values in categories according to the required decimation
for x in self.xs:
dec = self.designer.firDecimation(x)
if len(dec) == 1 and dec[0] == 1:
xs_d1.append(x)
elif len(dec) == 1 and dec[0] == 4:
xs_d4.append(x)
elif len(dec) == 2:
xs_d16.append(x)
elif len(dec) == 3:
xs_d64.append(x)
elif len(dec) == 4:
xs_d256.append(x)
else:
raise ValueError(f'No decimation found for x={x}')
xs_all = [xs_d1, xs_d4, xs_d16, xs_d64, xs_d256]
for xs in xs_all:
nominals_txt = []
if len(xs) > 0:
firs = np.empty((self.designer.firFilterLength, len(xs)), order='F')
for i, x in enumerate(xs):
# These are the filters that do not require lasp_decimation
# prior to filtering
nominals_txt.append(self.designer.nominal_txt(x))
firs[:, i] = self.designer.createFirFilter(x)
filterbank = {'fb': pyxFilterBank(firs, 1024),
'xs': xs,
'nominals': nominals_txt}
self.filterbanks.append(filterbank)
# Sample input counter.
self.N = 0
self.dec = [1, 4, 16, 64, 256]
# Filter output counters
# These intial delays are found 'experimentally' using a toneburst
# response.
self.Nf = [915, 806, 780, 582, 338]
def filterd(self, dec_stage, data):
"""
Filter data for a given decimation stage
Args:
dec_stage: decimation stage
data: Pre-filtered data
"""
output = {}
if data.shape[0] == 0:
return output
filtered = self.filterbanks[dec_stage]['fb'].filter_(data)
Nf = filtered.shape[0]
if Nf > 0:
dec = self.dec[dec_stage]
fd = self.fs/dec
oldNf = self.Nf[dec_stage]
tstart = oldNf/fd
tend = tstart + Nf/fd
t = np.linspace(tstart, tend, Nf, endpoint=False)
self.Nf[dec_stage] += Nf
for i, nom_txt in enumerate(self.filterbanks[dec_stage]['nominals']):
x = self.designer.nominal_txt_tox(nom_txt)
output[nom_txt] = {'t': t, 'data': filtered[:, [i]], 'x': x}
return output
def filter_(self, data):
"""
Filter input data
"""
assert data.ndim == 2
assert data.shape[1] == 1, "invalid number of channels, should be 1"
if data.shape[0] == 0:
return {}
# Output given as a dictionary with x as the key
output = {}
output_unsorted = {}
self.N += data.shape[0]
output_unsorted = {**output_unsorted, **self.filterd(0, data)}
for i in range(len(self.decimators)):
dec_stage = i+1
if data.shape[0] > 0:
# Apply a decimation stage
data = self.decimators[i].decimate(data)
output_unsorted = {**output_unsorted,
**self.filterd(dec_stage, data)}
# Create sorted output
for x in self.xs:
nom_txt = self.designer.nominal_txt(x)
output[nom_txt] = output_unsorted[nom_txt]
return output
def decimation(self, x):
return self.designer.firDecimation(x)
class FirOctaveFilterBank(FirFilterBank):
"""
Filter bank which uses FIR filtering for each octave frequency band
"""
def __init__(self, fs, xmin, xmax):
self.designer = OctaveBankDesigner(fs)
FirFilterBank.__init__(self, fs, xmin, xmax)
class FirThirdOctaveFilterBank(FirFilterBank):
"""
Filter bank which uses FIR filtering for each one-third octave frequency
band.
"""
def __init__(self, fs, xmin, xmax):
self.designer = ThirdOctaveBankDesigner(fs)
FirFilterBank.__init__(self, fs, xmin, xmax)
class SosFilterBank: class SosFilterBank:
def __init__(self, fs, xmin, xmax): def __init__(self, fs, xmin, xmax):
""" """
@ -84,7 +247,7 @@ class SosFilterBank:
channel = self.designer.createSOSFilter(x) channel = self.designer.createSOSFilter(x)
if sos is None: if sos is None:
sos = empty((channel.size, len(self.xs))) sos = empty((channel.size, len(self.xs)))
sos[:, i] = channel.flatten() sos[:, i] = channel.flatten()
self._fb = BiquadBank(sos) self._fb = BiquadBank(sos)