First work on avpowerspectra implementation. Added stubs for all other code that needs to be implemented in C++ as well.
This commit is contained in:
parent
12cf9586eb
commit
7ca52695da
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -16,3 +16,6 @@
|
|||||||
[submodule "third_party/lockfree"]
|
[submodule "third_party/lockfree"]
|
||||||
path = third_party/lockfree
|
path = third_party/lockfree
|
||||||
url = https://github.com/boostorg/lockfree
|
url = https://github.com/boostorg/lockfree
|
||||||
|
[submodule "third_party/carma"]
|
||||||
|
path = third_party/carma
|
||||||
|
url = https://github.com/RUrlus/carma
|
||||||
|
@ -26,7 +26,7 @@ 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)
|
||||||
|
|
||||||
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" "FFTPack" "None")
|
set_property(CACHE LASP_FFT_BACKEND PROPERTY STRINGS "FFTW" "Armadillo")
|
||||||
|
|
||||||
set(PY_FULL_VERSION ${PROJECT_VERSION}${PY_VERSION_SUFFIX})
|
set(PY_FULL_VERSION ${PROJECT_VERSION}${PY_VERSION_SUFFIX})
|
||||||
|
|
||||||
@ -52,10 +52,16 @@ include(OSSpecific)
|
|||||||
|
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-type-limits")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-type-limits")
|
||||||
|
|
||||||
set(CMAKE_C_FLAGS_RELEASE "-O3 -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
|
||||||
|
add_subdirectory(third_party/carma)
|
||||||
|
|
||||||
add_subdirectory(third_party)
|
if(LASP_FFT_BACKEND STREQUAL "FFTW")
|
||||||
|
find_library(FFTW_LIBRARY NAMES fftw3 fftw)
|
||||||
|
set(FFT_LIBRARIES "${FFTW_LIBRARY}")
|
||||||
|
elseif(LASP_FFT_BACKEND STREQUAL "Armadillo")
|
||||||
|
endif()
|
||||||
add_subdirectory(src/lasp)
|
add_subdirectory(src/lasp)
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ from .lasp_measurement import * # Measurement, scaleBlockSens
|
|||||||
from .lasp_octavefilter import *
|
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_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
|
||||||
# from .tools import * # SmoothingType, smoothSpectralData, SmoothingWidth
|
# from .tools import * # SmoothingType, smoothSpectralData, SmoothingWidth
|
||||||
|
@ -1,263 +0,0 @@
|
|||||||
// lasp_fft.c
|
|
||||||
//
|
|
||||||
// Author: J.A. de Jong - ASCEE
|
|
||||||
//
|
|
||||||
// Description:
|
|
||||||
//
|
|
||||||
//////////////////////////////////////////////////////////////////////
|
|
||||||
#define TRACERPLUS (-5)
|
|
||||||
#include "lasp_types.h"
|
|
||||||
#include "lasp_config.h"
|
|
||||||
#include "lasp_tracer.h"
|
|
||||||
#include "lasp_fft.h"
|
|
||||||
|
|
||||||
#if LASP_FFT_BACKEND == FFTPack
|
|
||||||
#include "fftpack.h"
|
|
||||||
typedef struct Fft_s {
|
|
||||||
us nfft;
|
|
||||||
vd fft_work; // Storage memory for fftpack
|
|
||||||
} Fft_s;
|
|
||||||
#elif LASP_FFT_BACKEND == FFTW
|
|
||||||
#include <fftw3.h>
|
|
||||||
|
|
||||||
typedef struct Fft_s {
|
|
||||||
us nfft;
|
|
||||||
fftw_plan forward_plan;
|
|
||||||
fftw_plan reverse_plan;
|
|
||||||
c* complex_storage;
|
|
||||||
d* real_storage;
|
|
||||||
} Fft_s;
|
|
||||||
#else
|
|
||||||
#error "Cannot compile lasp_ffc.c, no FFT backend specified. Should either be FFTPack, or FFTW"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void load_fft_wisdom(const char* wisdom) {
|
|
||||||
#if LASP_FFT_BACKEND == FFTPack
|
|
||||||
#elif LASP_FFT_BACKEND == FFTW
|
|
||||||
if(wisdom) {
|
|
||||||
int rv= fftw_import_wisdom_from_string(wisdom);
|
|
||||||
if(rv != 1) {
|
|
||||||
fprintf(stderr, "Error loading FFTW wisdom");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
char* store_fft_wisdom() {
|
|
||||||
#if LASP_FFT_BACKEND == FFTPack
|
|
||||||
return NULL;
|
|
||||||
#elif LASP_FFT_BACKEND == FFTW
|
|
||||||
return fftw_export_wisdom_to_string();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
Fft* Fft_create(const us nfft) {
|
|
||||||
fsTRACE(15);
|
|
||||||
if(nfft == 0) {
|
|
||||||
WARN("nfft should be > 0");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
Fft* fft = a_malloc(sizeof(Fft));
|
|
||||||
|
|
||||||
fft->nfft = nfft;
|
|
||||||
|
|
||||||
#if LASP_FFT_BACKEND == FFTPack
|
|
||||||
/* Initialize foreign fft lib */
|
|
||||||
fft->fft_work = vd_alloc(2*nfft+15);
|
|
||||||
npy_rffti(nfft,getvdval(&fft->fft_work,0));
|
|
||||||
check_overflow_vx(fft->fft_work);
|
|
||||||
#elif LASP_FFT_BACKEND == FFTW
|
|
||||||
fft->complex_storage = fftw_malloc(sizeof(c) * (nfft/2 + 1));
|
|
||||||
fft->real_storage = fftw_malloc(sizeof(d) * nfft);
|
|
||||||
|
|
||||||
fft->forward_plan = fftw_plan_dft_r2c_1d(nfft,
|
|
||||||
fft->real_storage,
|
|
||||||
fft->complex_storage,
|
|
||||||
FFTW_MEASURE);
|
|
||||||
fft->reverse_plan = fftw_plan_dft_c2r_1d(nfft,
|
|
||||||
fft->complex_storage,
|
|
||||||
fft->real_storage,
|
|
||||||
FFTW_MEASURE);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* print_vd(&fft->fft_work); */
|
|
||||||
|
|
||||||
feTRACE(15);
|
|
||||||
return fft;
|
|
||||||
}
|
|
||||||
void Fft_free(Fft* fft) {
|
|
||||||
fsTRACE(15);
|
|
||||||
dbgassert(fft,NULLPTRDEREF);
|
|
||||||
#if LASP_FFT_BACKEND == FFTPack
|
|
||||||
vd_free(&fft->fft_work);
|
|
||||||
#elif LASP_FFT_BACKEND == FFTW
|
|
||||||
fftw_free(fft->complex_storage);
|
|
||||||
fftw_free(fft->real_storage);
|
|
||||||
fftw_destroy_plan(fft->forward_plan);
|
|
||||||
fftw_destroy_plan(fft->reverse_plan);
|
|
||||||
#endif
|
|
||||||
a_free(fft);
|
|
||||||
feTRACE(15);
|
|
||||||
}
|
|
||||||
|
|
||||||
us Fft_nfft(const Fft* fft) {return fft->nfft;}
|
|
||||||
|
|
||||||
void Fft_ifft_single(const Fft* fft,const vc* freqdata,vd* result) {
|
|
||||||
fsTRACE(15);
|
|
||||||
dbgassert(fft && freqdata && result,NULLPTRDEREF);
|
|
||||||
const us nfft = fft->nfft;
|
|
||||||
dbgassert(result->n_rows == nfft,
|
|
||||||
"Invalid size for time data rows."
|
|
||||||
" Should be equal to nfft");
|
|
||||||
|
|
||||||
dbgassert(freqdata->n_rows == (nfft/2+1),"Invalid number of rows in"
|
|
||||||
" result array");
|
|
||||||
|
|
||||||
|
|
||||||
d* result_ptr = getvdval(result,0);
|
|
||||||
|
|
||||||
#if LASP_FFT_BACKEND == FFTPack
|
|
||||||
d* freqdata_ptr = (d*) getvcval(freqdata,0);
|
|
||||||
/* Copy freqdata, to fft_result. */
|
|
||||||
d_copy(&result_ptr[1],&freqdata_ptr[2],nfft-1,1,1);
|
|
||||||
result_ptr[0] = freqdata_ptr[0];
|
|
||||||
|
|
||||||
/* Perform inplace backward transform */
|
|
||||||
npy_rfftb(nfft,
|
|
||||||
result_ptr,
|
|
||||||
getvdval(&fft->fft_work,0));
|
|
||||||
|
|
||||||
|
|
||||||
#elif LASP_FFT_BACKEND == FFTW
|
|
||||||
c* freqdata_ptr = (c*) getvcval(freqdata,0);
|
|
||||||
|
|
||||||
c_copy(fft->complex_storage, freqdata_ptr,nfft/2+1);
|
|
||||||
|
|
||||||
fftw_execute(fft->reverse_plan);
|
|
||||||
|
|
||||||
d_copy(result_ptr, fft->real_storage, nfft, 1, 1);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
check_overflow_vx(*result);
|
|
||||||
|
|
||||||
/* Scale by dividing by nfft. Checked with numpy implementation
|
|
||||||
* that this indeed needs to be done for FFTpack. */
|
|
||||||
d_scale(result_ptr,1/((d) nfft),nfft);
|
|
||||||
feTRACE(15);
|
|
||||||
}
|
|
||||||
void Fft_ifft(const Fft* fft,const cmat* freqdata,dmat* timedata) {
|
|
||||||
fsTRACE(15);
|
|
||||||
|
|
||||||
dbgassert(fft && timedata && freqdata,NULLPTRDEREF);
|
|
||||||
|
|
||||||
const us nchannels = timedata->n_cols;
|
|
||||||
dbgassert(timedata->n_cols == freqdata->n_cols,
|
|
||||||
"Number of columns in timedata and result"
|
|
||||||
" should be equal.");
|
|
||||||
|
|
||||||
for(us col=0;col<nchannels;col++) {
|
|
||||||
|
|
||||||
vd timedata_col = dmat_column(timedata,col);
|
|
||||||
vc freqdata_col = cmat_column((cmat*)freqdata,col);
|
|
||||||
|
|
||||||
Fft_ifft_single(fft,&freqdata_col,&timedata_col);
|
|
||||||
|
|
||||||
vd_free(&timedata_col);
|
|
||||||
vc_free(&freqdata_col);
|
|
||||||
}
|
|
||||||
check_overflow_xmat(*timedata);
|
|
||||||
check_overflow_xmat(*freqdata);
|
|
||||||
|
|
||||||
feTRACE(15);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Fft_fft_single(const Fft* fft,const vd* timedata,vc* result) {
|
|
||||||
|
|
||||||
fsTRACE(15);
|
|
||||||
dbgassert(fft && timedata && result,NULLPTRDEREF);
|
|
||||||
|
|
||||||
const us nfft = fft->nfft;
|
|
||||||
assert_vx(timedata);
|
|
||||||
assert_vx(result);
|
|
||||||
dbgassert(timedata->n_rows == nfft,
|
|
||||||
"Invalid size for time data rows."
|
|
||||||
" Should be equal to nfft");
|
|
||||||
|
|
||||||
dbgassert(result->n_rows == (nfft/2+1),"Invalid number of rows in"
|
|
||||||
" result array");
|
|
||||||
|
|
||||||
|
|
||||||
#if LASP_FFT_BACKEND == FFTPack
|
|
||||||
d* result_ptr = (d*) getvcval(result,0);
|
|
||||||
|
|
||||||
/* Fftpack stores the data a bit strange, the resulting array
|
|
||||||
* has the DC value at 0,the first cosine at 1, the first sine
|
|
||||||
* at 2 etc. 1
|
|
||||||
* resulting matrix, as for the complex data, the imaginary
|
|
||||||
* part of the DC component equals zero. */
|
|
||||||
|
|
||||||
/* Copy timedata, as it will be overwritten in the fft pass. */
|
|
||||||
d_copy(&result_ptr[1],getvdval(timedata,0),nfft,1,1);
|
|
||||||
|
|
||||||
|
|
||||||
/* Perform fft */
|
|
||||||
npy_rfftf(nfft,&result_ptr[1],
|
|
||||||
getvdval(&fft->fft_work,0));
|
|
||||||
|
|
||||||
/* Set real part of DC component to first index of the rfft
|
|
||||||
* routine */
|
|
||||||
result_ptr[0] = result_ptr[1];
|
|
||||||
|
|
||||||
result_ptr[1] = 0; /* Set imaginary part of DC component
|
|
||||||
* to zero */
|
|
||||||
|
|
||||||
/* For an even fft, the imaginary part of the Nyquist frequency
|
|
||||||
* bin equals zero.*/
|
|
||||||
if(islikely(nfft%2 == 0)) {
|
|
||||||
result_ptr[nfft+1] = 0;
|
|
||||||
}
|
|
||||||
check_overflow_vx(fft->fft_work);
|
|
||||||
#elif LASP_FFT_BACKEND == FFTW
|
|
||||||
|
|
||||||
d* timedata_ptr = getvdval(timedata,0);
|
|
||||||
c* result_ptr = getvcval(result,0);
|
|
||||||
d_copy(fft->real_storage,timedata_ptr, nfft, 1, 1);
|
|
||||||
|
|
||||||
fftw_execute(fft->forward_plan);
|
|
||||||
|
|
||||||
c_copy(result_ptr, fft->complex_storage, nfft/2+1);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
check_overflow_vx(*result);
|
|
||||||
feTRACE(15);
|
|
||||||
|
|
||||||
}
|
|
||||||
void Fft_fft(const Fft* fft,const dmat* timedata,cmat* result) {
|
|
||||||
fsTRACE(15);
|
|
||||||
|
|
||||||
dbgassert(fft && timedata && result,NULLPTRDEREF);
|
|
||||||
|
|
||||||
const us nchannels = timedata->n_cols;
|
|
||||||
dbgassert(timedata->n_cols == result->n_cols,
|
|
||||||
"Number of columns in timedata and result"
|
|
||||||
" should be equal.");
|
|
||||||
|
|
||||||
for(us col=0;col<nchannels;col++) {
|
|
||||||
|
|
||||||
vd timedata_col = dmat_column((dmat*) timedata,col);
|
|
||||||
vc result_col = cmat_column(result,col);
|
|
||||||
|
|
||||||
Fft_fft_single(fft,&timedata_col,&result_col);
|
|
||||||
|
|
||||||
vd_free(&timedata_col);
|
|
||||||
vc_free(&result_col);
|
|
||||||
}
|
|
||||||
check_overflow_xmat(*timedata);
|
|
||||||
check_overflow_xmat(*result);
|
|
||||||
|
|
||||||
feTRACE(15);
|
|
||||||
}
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////
|
|
@ -4,7 +4,6 @@
|
|||||||
#include "lasp_deviceinfo.h"
|
#include "lasp_deviceinfo.h"
|
||||||
#include "lasp_types.h"
|
#include "lasp_types.h"
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <gsl/gsl-lite.hpp>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -5,6 +5,8 @@ set(lasp_dsp_files
|
|||||||
lasp_siggen.cpp
|
lasp_siggen.cpp
|
||||||
lasp_siggen_impl.cpp
|
lasp_siggen_impl.cpp
|
||||||
lasp_window.cpp
|
lasp_window.cpp
|
||||||
|
lasp_fft.cpp
|
||||||
|
lasp_avpowerspectra.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
68
src/lasp/dsp/lasp_avpowerspectra.cpp
Normal file
68
src/lasp/dsp/lasp_avpowerspectra.cpp
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
#define DEBUGTRACE_ENABLED
|
||||||
|
#include "lasp_avpowerspectra.h"
|
||||||
|
#include "debugtrace.hpp"
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
using std::runtime_error;
|
||||||
|
|
||||||
|
PowerSpectra::PowerSpectra(const us nfft, const Window::WindowType w)
|
||||||
|
: PowerSpectra(Window::create(w, nfft)) {}
|
||||||
|
|
||||||
|
PowerSpectra::PowerSpectra(const vd &window)
|
||||||
|
: nfft(window.size()), _fft(nfft), _window(window) {
|
||||||
|
|
||||||
|
d win_pow = arma::sum(window % window);
|
||||||
|
|
||||||
|
/* Scale fft such that power is easily computed */
|
||||||
|
_scale_fac = sqrt(2 / win_pow) / nfft;
|
||||||
|
}
|
||||||
|
arma::Cube<c> PowerSpectra::compute(const dmat &input) {
|
||||||
|
|
||||||
|
dmat input_tmp = 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;
|
||||||
|
|
||||||
|
arma::cx_cube output(rfft.n_rows, input.n_cols, input.n_cols);
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
AvPowerSpectra::AvPowerSpectra(const us nfft, const Window::WindowType w,
|
||||||
|
const d overlap_percentage,
|
||||||
|
const int time_constant)
|
||||||
|
: _ps(nfft, w) {
|
||||||
|
|
||||||
|
DEBUGTRACE_ENTER;
|
||||||
|
if (overlap_percentage >= 100 || overlap_percentage < 0) {
|
||||||
|
throw runtime_error("Overlap percentage should be >= 0 and < 100");
|
||||||
|
}
|
||||||
|
|
||||||
|
_overlap_jump = nfft - (nfft * overlap_percentage) / 100;
|
||||||
|
if (_overlap_jump == 0) {
|
||||||
|
throw runtime_error("Overlap is too high. Results in no jump. Please "
|
||||||
|
"choose a smaller overlap percentage or a higher nfft");
|
||||||
|
}
|
||||||
|
if(time_constant < 0) {
|
||||||
|
_mode = Mode::Averaging;
|
||||||
|
} else if(time_constant == 0) {
|
||||||
|
_mode = Mode::Spectrogram;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
_mode = Mode::Leaking;
|
||||||
|
_alpha = exp(-static_cast<d>(_overlap_jump)/time_constant);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
arma::cx_cube* AvPowerSpectra::compute(const dmat& timedata) {
|
||||||
|
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
155
src/lasp/dsp/lasp_avpowerspectra.h
Normal file
155
src/lasp/dsp/lasp_avpowerspectra.h
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <memory>
|
||||||
|
#include "lasp_fft.h"
|
||||||
|
#include "lasp_mathtypes.h"
|
||||||
|
#include "lasp_window.h"
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Computes single-sided cross-power spectra for a group of channels
|
||||||
|
*/
|
||||||
|
class PowerSpectra {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief The FFT length
|
||||||
|
*/
|
||||||
|
us nfft;
|
||||||
|
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* @brief Instance to compute actual FFT's
|
||||||
|
*/
|
||||||
|
Fft _fft;
|
||||||
|
|
||||||
|
vd _window;
|
||||||
|
c _scale_fac;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Initalize power spectra computer
|
||||||
|
*
|
||||||
|
* @param nfft The fft length
|
||||||
|
* @param w The window type
|
||||||
|
*/
|
||||||
|
PowerSpectra(const us nfft, const Window::WindowType w);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initalize power spectra computer
|
||||||
|
*
|
||||||
|
* @param window Uses the window length to compute fft length, and uses the
|
||||||
|
* window shape as the actual window.
|
||||||
|
*/
|
||||||
|
PowerSpectra(const vd &window);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Computes the spectra. Data is normalized by the (spectral) power of
|
||||||
|
* the window, to compensate for the effect of the window.
|
||||||
|
*
|
||||||
|
* @param input Input data, first axis is assumed the sample, second axis the
|
||||||
|
* channel. Input first dimension should be equal to nfft, otherwise a
|
||||||
|
* runtime error is thrown.
|
||||||
|
*
|
||||||
|
* @return Cross-power-spectra. Cubic array with size indices C_fij, where
|
||||||
|
* f is the frequency index, i is the row and j is the column. An element
|
||||||
|
* can be accessed by: C(f, i, j). Storage is such that frequency components
|
||||||
|
* are contiguous.
|
||||||
|
*/
|
||||||
|
arma::Cube<c> compute(const dmat &input);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Estimate cross-power spectra using Welch' method of spectral
|
||||||
|
* estimation. The exact amount of overlap in Welch' method is rounded up to a
|
||||||
|
* certain amount of samples.
|
||||||
|
*/
|
||||||
|
class AvPowerSpectra {
|
||||||
|
|
||||||
|
enum class Mode {
|
||||||
|
Averaging = 0, // Averaging all time date
|
||||||
|
Leaking = 1, // Exponential weighting of an "instantaneous cps"
|
||||||
|
Spectrogram = 2 // Instantenous spectrum, no averaging
|
||||||
|
};
|
||||||
|
|
||||||
|
Mode _mode;
|
||||||
|
d _alpha; // Only valid in case of 'Leaking'
|
||||||
|
|
||||||
|
PowerSpectra _ps;
|
||||||
|
/**
|
||||||
|
* @brief Current estimate of cross-spectral density
|
||||||
|
*/
|
||||||
|
arma::cx_cube _est;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Buffer of storage of time data.
|
||||||
|
*/
|
||||||
|
dmat _timeBuf;
|
||||||
|
/**
|
||||||
|
* @brief Current time index in ring buffer
|
||||||
|
*/
|
||||||
|
us _curTimeIdx = 0;
|
||||||
|
/**
|
||||||
|
* @brief The amount of samples to jump in the overlap
|
||||||
|
*/
|
||||||
|
us _overlap_jump;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Initalize averaged power spectra computer. If a time constant is
|
||||||
|
* given > 0, it is used in a kind of exponential weighting.
|
||||||
|
*
|
||||||
|
* @param nfft The fft length
|
||||||
|
* @param w The window type.
|
||||||
|
* @param overlap_percentage A number 0 < overlap_percentage <= 100. It
|
||||||
|
* determines the amount of overlap used in Welch' method. A typical value is
|
||||||
|
* 50 %, i.e. 50.
|
||||||
|
* @param time_constant Value should either be < 0, indicating that the
|
||||||
|
* estimate is averages over all time data.
|
||||||
|
* For a value = 0 the instantaneous power spectrum is returned, which can be
|
||||||
|
* interpreted as the spectrogram. For a value > 0 a exponential forgetting is
|
||||||
|
* used, where the value is used as the time constant such that the decay
|
||||||
|
* follows approximately the trend exp(-n/time_constant), where n is the
|
||||||
|
* sample number in the power spectra. To choose 'fast' time weighting, set
|
||||||
|
* time_constant to the value of fs*0.125, where fs denotes the sampling
|
||||||
|
* frequency.
|
||||||
|
**/
|
||||||
|
AvPowerSpectra(const us nfft = 2048,
|
||||||
|
const Window::WindowType w = Window::WindowType::Hann,
|
||||||
|
const d overlap_percentage = 50.,
|
||||||
|
const int time_constant = -1);
|
||||||
|
|
||||||
|
AvPowerSpectra(const AvPowerSpectra &) = delete;
|
||||||
|
AvPowerSpectra &operator=(const AvPowerSpectra &) = delete;
|
||||||
|
|
||||||
|
void reset() { _est.reset(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Comnpute an update of the power spectra based on given time data.
|
||||||
|
* Note that the number of channels is determined from the first time this
|
||||||
|
* function is called. If a later call has an incompatible number of
|
||||||
|
* channels, a runtime error is thrown.
|
||||||
|
*
|
||||||
|
* @param timedata
|
||||||
|
*
|
||||||
|
* @return Optionally, a reference (NOT OWNING POINTER) to a new estimate of
|
||||||
|
* the power spectra. An update is only given if the amount of new time data
|
||||||
|
* is enough to compute a new estimate. This is the case when the amount of
|
||||||
|
* new time data is enough.
|
||||||
|
*/
|
||||||
|
arma::cx_cube *compute(const dmat &timedata);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the latest estimate of cps (cross-power spectra.
|
||||||
|
*
|
||||||
|
* @return Pointer (reference, not owning!) to spectral estimate date, or
|
||||||
|
* nullptr, in case nothing could yet be computed.
|
||||||
|
*/
|
||||||
|
arma::cx_cube *get_est();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The overlap is rounded to a certain amount of time samples. This
|
||||||
|
* function returns that value.
|
||||||
|
*
|
||||||
|
* @return The amount of samples in overlapping.
|
||||||
|
*/
|
||||||
|
us exactOverlapSamples() const { return _ps.nfft - _overlap_jump; }
|
||||||
|
};
|
0
src/lasp/dsp/lasp_biquadbank.cpp
Normal file
0
src/lasp/dsp/lasp_biquadbank.cpp
Normal file
0
src/lasp/dsp/lasp_biquadbank.h
Normal file
0
src/lasp/dsp/lasp_biquadbank.h
Normal file
151
src/lasp/dsp/lasp_fft.cpp
Normal file
151
src/lasp/dsp/lasp_fft.cpp
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
// lasp_fft.c
|
||||||
|
//
|
||||||
|
// Author: J.A. de Jong - ASCEE
|
||||||
|
//
|
||||||
|
// Description:
|
||||||
|
// FFt implementation
|
||||||
|
//
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
#include <memory>
|
||||||
|
#define DEBUGTRACE_ENABLED
|
||||||
|
#include "lasp_fft.h"
|
||||||
|
#include "debugtrace.hpp"
|
||||||
|
#include "lasp_config.h"
|
||||||
|
using 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.");
|
||||||
|
|
||||||
|
DEBUGTRACE_ENTER;
|
||||||
|
}
|
||||||
|
|
||||||
|
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>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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;
|
||||||
|
|
||||||
|
Fft_impl(const us nfft) : nfft(nfft) {
|
||||||
|
|
||||||
|
timeDomain = (d *)fftw_malloc(sizeof(d) * nfft);
|
||||||
|
|
||||||
|
frequencyDomain =
|
||||||
|
(fftw_complex *)fftw_malloc(sizeof(fftw_complex) * (nfft / 2 + 1));
|
||||||
|
|
||||||
|
forward_plan =
|
||||||
|
fftw_plan_dft_r2c_1d(nfft, timeDomain, frequencyDomain, FFTW_MEASURE);
|
||||||
|
|
||||||
|
reverse_plan =
|
||||||
|
fftw_plan_dft_c2r_1d(nfft, frequencyDomain, timeDomain, FFTW_MEASURE);
|
||||||
|
};
|
||||||
|
|
||||||
|
~Fft_impl() {
|
||||||
|
fftw_destroy_plan(forward_plan);
|
||||||
|
fftw_destroy_plan(reverse_plan);
|
||||||
|
fftw_free(frequencyDomain);
|
||||||
|
fftw_free(timeDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
vc fft(const vd &time) {
|
||||||
|
|
||||||
|
vc res(nfft / 2 + 1);
|
||||||
|
memcpy(timeDomain, time.memptr(), sizeof(d) * nfft);
|
||||||
|
|
||||||
|
fftw_execute(forward_plan);
|
||||||
|
|
||||||
|
memcpy(reinterpret_cast<void *>(res.memptr()), frequencyDomain,
|
||||||
|
sizeof(d) * (nfft / 2 + 1));
|
||||||
|
|
||||||
|
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));
|
||||||
|
|
||||||
|
fftw_execute(reverse_plan);
|
||||||
|
|
||||||
|
memcpy(res.memptr(), frequencyDomain, nfft * sizeof(d));
|
||||||
|
|
||||||
|
/* Scale by dividing by nfft. Checked with numpy implementation
|
||||||
|
* that this indeed needs to be done for FFTW. */
|
||||||
|
res *= 1 / nfft;
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#else
|
||||||
|
#error \
|
||||||
|
"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");
|
||||||
|
}
|
||||||
|
if (nfft >= LASP_MAX_NFFT) {
|
||||||
|
throw std::runtime_error("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; }
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
return _impl->ifft(freqdata);
|
||||||
|
}
|
||||||
|
dmat Fft::ifft(const cmat &freqdata) {
|
||||||
|
dmat res(_impl->nfft, freqdata.n_cols);
|
||||||
|
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
|
||||||
|
#elif LASP_FFT_BACKEND == FFTW
|
||||||
|
if (wisdom) {
|
||||||
|
int rv = fftw_import_wisdom_from_string(wisdom);
|
||||||
|
if (rv != 1) {
|
||||||
|
fprintf(stderr, "Error loading FFTW wisdom");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
char *store_fft_wisdom() {
|
||||||
|
#if LASP_FFT_BACKEND == Armadillo
|
||||||
|
return NULL;
|
||||||
|
#elif LASP_FFT_BACKEND == FFTW
|
||||||
|
return fftw_export_wisdom_to_string();
|
||||||
|
#endif
|
||||||
|
}
|
90
src/lasp/dsp/lasp_fft.h
Normal file
90
src/lasp/dsp/lasp_fft.h
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
// lasp_fft.h
|
||||||
|
//
|
||||||
|
// Author: J.A. de Jong - ASCEE
|
||||||
|
//
|
||||||
|
// Description: FFT class
|
||||||
|
// Interface to the FFT library, multiple channel FFT's
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
#pragma once
|
||||||
|
#include <memory>
|
||||||
|
#include "lasp_mathtypes.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load wisdom data from a NULL-terminated character string. Note that at this
|
||||||
|
* point in time this is only used by the FFTW fft backend.
|
||||||
|
*
|
||||||
|
* @param[in] wisdom: Wisdom string
|
||||||
|
*/
|
||||||
|
void load_fft_wisdom(const char* wisdom);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a NULL-terminated string containing FFTW wisdom, or state knowledge
|
||||||
|
* from other libraries.
|
||||||
|
*
|
||||||
|
* @returns A NULL-terminated string. *Should be deallocated after being used*
|
||||||
|
*/
|
||||||
|
char* store_fft_wisdom(void);
|
||||||
|
|
||||||
|
class Fft_impl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform forward FFT's on real time data. Computes single-sided spectra.
|
||||||
|
*/
|
||||||
|
class Fft {
|
||||||
|
std::unique_ptr<Fft_impl> _impl;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Initialize FFT
|
||||||
|
*
|
||||||
|
* @param nfft The length of nfft
|
||||||
|
*/
|
||||||
|
Fft(const us nfft);
|
||||||
|
~Fft();
|
||||||
|
|
||||||
|
Fft(const Fft&) = delete;
|
||||||
|
Fft& operator=(const Fft&) = delete;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Return nfft
|
||||||
|
*
|
||||||
|
* @return nfft
|
||||||
|
*/
|
||||||
|
us nfft() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the fft for a single channel of data.
|
||||||
|
*
|
||||||
|
* @param[in] timedata Input time data, should have size nfft
|
||||||
|
* @returns result Result complex vector
|
||||||
|
* */
|
||||||
|
vc fft(const vd& timedata);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the fft of the data matrix, first axis is assumed to be
|
||||||
|
* the time axis.
|
||||||
|
*
|
||||||
|
* @param[in] timedata Input time data, should have size nfft. First axis
|
||||||
|
* is time, second axis is channel
|
||||||
|
* @returns Result complex array
|
||||||
|
*/
|
||||||
|
cmat fft(const dmat& timedata);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform inverse fft on a single channel.
|
||||||
|
*
|
||||||
|
* @param[in] freqdata Frequency domain input data, to be iFft'th. Should
|
||||||
|
* have size nfft/2+1
|
||||||
|
* @returns timedata: iFft't data, size nfft.
|
||||||
|
*/
|
||||||
|
vd ifft(const vc& freqdata);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform inverse FFT
|
||||||
|
*
|
||||||
|
* @param[in] freqdata Frequency domain data
|
||||||
|
* @param[out] timedata Time domain result
|
||||||
|
*/
|
||||||
|
dmat ifft(const cmat& freqdata);
|
||||||
|
|
||||||
|
};
|
@ -40,6 +40,8 @@
|
|||||||
#endif // LASP_DOUBLE_PRECISION
|
#endif // LASP_DOUBLE_PRECISION
|
||||||
|
|
||||||
using vd = arma::Col<d>;
|
using vd = arma::Col<d>;
|
||||||
|
using vc = arma::Col<c>;
|
||||||
using dmat = arma::Mat<d>;
|
using dmat = arma::Mat<d>;
|
||||||
|
using cmat = arma::Mat<c>;
|
||||||
|
|
||||||
const d number_pi = arma::datum::pi;
|
const d number_pi = arma::datum::pi;
|
||||||
|
@ -1,5 +1,23 @@
|
|||||||
#include "lasp_config.h"
|
#include "lasp_config.h"
|
||||||
#include <pybind11/pybind11.h>
|
#include <pybind11/pybind11.h>
|
||||||
|
|
||||||
|
/*! \mainpage
|
||||||
|
*
|
||||||
|
* \section intro_sec Introduction
|
||||||
|
*
|
||||||
|
* Welcome to the LASP (Library for Acoustic Signal Processing) code
|
||||||
|
* documentation. The code comprises a part which is written in C++, a part
|
||||||
|
* that is written in Python, and a part that functions as glue, which is
|
||||||
|
* Pybind11 C++ glue code. An example of such a file is the current one.
|
||||||
|
*
|
||||||
|
* \section Installation
|
||||||
|
*
|
||||||
|
* For the installation manual, please refer to the README.md of the Git
|
||||||
|
* repository. It is recommended to install the software from source.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* */
|
||||||
|
|
||||||
namespace py = pybind11;
|
namespace py = pybind11;
|
||||||
|
|
||||||
void init_dsp(py::module &m);
|
void init_dsp(py::module &m);
|
||||||
@ -18,9 +36,12 @@ PYBIND11_MODULE(lasp_cpp, m) {
|
|||||||
init_streammgr(m);
|
init_streammgr(m);
|
||||||
init_datahandler(m);
|
init_datahandler(m);
|
||||||
|
|
||||||
|
// We store the version number of the code via CMake, and create an
|
||||||
|
// attribute in the C++ code.
|
||||||
m.attr("__version__") = std::to_string(LASP_VERSION_MAJOR) + "." +
|
m.attr("__version__") = std::to_string(LASP_VERSION_MAJOR) + "." +
|
||||||
std::to_string(LASP_VERSION_MINOR);
|
std::to_string(LASP_VERSION_MINOR);
|
||||||
|
|
||||||
|
|
||||||
m.attr("LASP_VERSION_MAJOR") = LASP_VERSION_MAJOR;
|
m.attr("LASP_VERSION_MAJOR") = LASP_VERSION_MAJOR;
|
||||||
m.attr("LASP_VERSION_MINOR") = LASP_VERSION_MINOR;
|
m.attr("LASP_VERSION_MINOR") = LASP_VERSION_MINOR;
|
||||||
}
|
}
|
||||||
|
10
third_party/CMakeLists.txt
vendored
10
third_party/CMakeLists.txt
vendored
@ -1,10 +0,0 @@
|
|||||||
# third_party/CMakeLists.txt
|
|
||||||
|
|
||||||
if(LASP_FFT_BACKEND STREQUAL "FFTW")
|
|
||||||
find_library(FFTW_LIBRARY NAMES fftw3 fftw)
|
|
||||||
set(FFT_LIBRARIES "${FFTW_LIBRARY}")
|
|
||||||
elseif(LASP_FFT_BACKEND STREQUAL "FFTPack")
|
|
||||||
include_directories(fftpack)
|
|
||||||
set(FFT_LIBRARIES fftpack)
|
|
||||||
add_subdirectory(fftpack)
|
|
||||||
endif()
|
|
1
third_party/carma
vendored
Submodule
1
third_party/carma
vendored
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 5673bc713e9515a00fba0c7378375fc075bdc693
|
9
third_party/fftpack/CMakeLists.txt
vendored
9
third_party/fftpack/CMakeLists.txt
vendored
@ -1,9 +0,0 @@
|
|||||||
# We borrow Numpy's implementation for doing the Fast Fourier Transform.
|
|
||||||
# This FFT code appears to be faster than KISSFFT.
|
|
||||||
add_library(fftpack
|
|
||||||
fftpack.c
|
|
||||||
)
|
|
||||||
|
|
||||||
# Ling fft to math
|
|
||||||
target_link_libraries(fftpack PRIVATE m)
|
|
||||||
target_include_directories(fftpack PUBLIC .)
|
|
1501
third_party/fftpack/fftpack.c
vendored
1501
third_party/fftpack/fftpack.c
vendored
File diff suppressed because it is too large
Load Diff
27
third_party/fftpack/fftpack.h
vendored
27
third_party/fftpack/fftpack.h
vendored
@ -1,27 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of tela the Tensor Language.
|
|
||||||
* Copyright (c) 1994-1995 Pekka Janhunen
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
#define NPY_VISIBILITY_HIDDEN
|
|
||||||
|
|
||||||
#ifdef LASP_DOUBLE_PRECISION
|
|
||||||
#define Treal double
|
|
||||||
#else
|
|
||||||
#define Treal float
|
|
||||||
#endif
|
|
||||||
|
|
||||||
extern NPY_VISIBILITY_HIDDEN void npy_cfftf(int N, Treal data[], const Treal wrk[]);
|
|
||||||
extern NPY_VISIBILITY_HIDDEN void npy_cfftb(int N, Treal data[], const Treal wrk[]);
|
|
||||||
extern NPY_VISIBILITY_HIDDEN void npy_cffti(int N, Treal wrk[]);
|
|
||||||
|
|
||||||
extern NPY_VISIBILITY_HIDDEN void npy_rfftf(int N, Treal data[], const Treal wrk[]);
|
|
||||||
extern NPY_VISIBILITY_HIDDEN void npy_rfftb(int N, Treal data[], const Treal wrk[]);
|
|
||||||
extern NPY_VISIBILITY_HIDDEN void npy_rffti(int N, Treal wrk[]);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
Loading…
Reference in New Issue
Block a user