Skip to content

Commit 724df96

Browse files
committed
External FFT support (#79)
* Support for external FFT impl Added the ability to disable the default FFT implementation and use a custom one during the linking process. * FFT cache disable support. * Move `czt` from fft module. * Add dsplib::interface target * Fix nextpow2/ispow2 as constexpr * Update docs
1 parent df48e6d commit 724df96

30 files changed

+478
-324
lines changed

CMakeLists.txt

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,31 +8,76 @@ set(CMAKE_CXX_EXTENSIONS OFF)
88
option(DSPLIB_USE_FLOAT32 "Use float32 for base type dsplib::real_t" OFF)
99
option(DSPLIB_NO_EXCEPTIONS "Use the abort() function instead throw" OFF)
1010
set(DSPLIB_FFT_CACHE_SIZE "4" CACHE STRING "LRU cache size for FFT plans")
11+
option(DSPLIB_EXCLUDE_FFT "Exclude FFT (must be implemented external)" OFF)
1112

1213
option(DSPLIB_BUILD_TESTS "Build dsplib tests" OFF)
1314
option(DSPLIB_ASAN_ENABLED "Address sanitizer enabled" OFF)
1415
option(DSPLIB_BUILD_EXAMPLES "Build dsplib examples" OFF)
1516
option(DSPLIB_BUILD_BENCHS "Build dsplib benchs" OFF)
1617
option(DSPLIB_BUILD_DOXYGEN "Build dsplib documentation" OFF)
1718

18-
file(GLOB_RECURSE DSPLIB_SOURCES
19-
"lib/*.cpp"
20-
"lib/*.h"
21-
"include/*.h")
19+
set(DSPLIB_SOURCES
20+
lib/agc.cpp
21+
lib/awgn.cpp
22+
lib/corr.cpp
23+
lib/detector.cpp
24+
lib/findpeaks.cpp
25+
lib/fir.cpp
26+
lib/gccphat.cpp
27+
lib/hilbert.cpp
28+
lib/math.cpp
29+
lib/medfilt.cpp
30+
lib/mscohere.cpp
31+
lib/primes.cpp
32+
lib/snr.cpp
33+
lib/random.cpp
34+
lib/spectrum.cpp
35+
lib/stft.cpp
36+
lib/stream.cpp
37+
lib/types.cpp
38+
lib/utils.cpp
39+
lib/window.cpp
40+
lib/xcorr.cpp
41+
lib/resample/fir-decimator.cpp
42+
lib/resample/fir-interpolator.cpp
43+
lib/resample/fir-rate-converter.cpp
44+
lib/resample/resample.cpp
45+
lib/fft.cpp
46+
lib/ifft.cpp
47+
lib/czt.cpp
48+
)
49+
50+
if (DSPLIB_EXCLUDE_FFT)
51+
message(STATUS "dsplib: use custom fft implementation")
52+
else()
53+
list(APPEND DSPLIB_SOURCES
54+
lib/fft/fact-fft.cpp
55+
lib/fft/factory.cpp
56+
lib/fft/pow2-fft.cpp
57+
lib/fft/real-fft.cpp
58+
lib/fft/real-ifft.cpp
59+
)
60+
endif()
2261

2362
add_library(${PROJECT_NAME} ${DSPLIB_SOURCES})
2463

2564
target_compile_definitions(${PROJECT_NAME} PRIVATE
2665
"DSPLIB_FFT_CACHE_SIZE=${DSPLIB_FFT_CACHE_SIZE}")
2766

28-
target_include_directories(${PROJECT_NAME}
29-
PUBLIC
67+
# add INTERFACE target
68+
add_library(${PROJECT_NAME}-interface INTERFACE)
69+
add_library(${PROJECT_NAME}::interface ALIAS ${PROJECT_NAME}-interface)
70+
71+
target_include_directories(${PROJECT_NAME}-interface
72+
INTERFACE
3073
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
3174
"$<BUILD_INTERFACE:${CMAKE_BINARY_DIR}>"
3275
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
33-
PRIVATE lib
3476
)
3577

78+
target_link_libraries(${PROJECT_NAME} PUBLIC ${PROJECT_NAME}-interface)
79+
target_include_directories(${PROJECT_NAME} PRIVATE lib)
80+
3681
# check root project
3782
if("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_LIST_DIR}")
3883
set(DSPLIB_IS_ROOT ON)

cmake/sanitizer.cmake

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@ function(enable_address_sanitizer)
1717
target_compile_options(${Sanitizer_TARGET} PRIVATE -fno-sanitize=signed-integer-overflow)
1818
target_compile_options(${Sanitizer_TARGET} PRIVATE -fno-sanitize-recover=all)
1919
target_compile_options(${Sanitizer_TARGET} PRIVATE -fno-omit-frame-pointer)
20-
target_link_libraries(${Sanitizer_TARGET} -fsanitize=address,undefined)
20+
target_link_libraries(${Sanitizer_TARGET} PRIVATE -fsanitize=address,undefined)
2121

2222
if(DEFINED Sanitizer_BLACKLIST)
2323
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU")
2424
message(WARNING "gcc does`t support blacklist")
2525
return()
2626
endif()
2727
target_compile_options(${Sanitizer_TARGET} PRIVATE -fsanitize-blacklist=${Sanitizer_BLACKLIST})
28-
target_link_libraries(${Sanitizer_TARGET} -fsanitize-blacklist=${Sanitizer_BLACKLIST})
28+
target_link_libraries(${Sanitizer_TARGET} PRIVATE -fsanitize-blacklist=${Sanitizer_BLACKLIST})
2929
endif()
3030
endfunction()

examples/CMakeLists.txt

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,11 @@
1-
cmake_minimum_required(VERSION 3.10)
2-
project(dsplib-example LANGUAGES CXX)
3-
4-
set(CMAKE_CXX_STANDARD 17)
5-
set(CMAKE_CXX_STANDARD_REQUIRED ON)
6-
7-
include(${CMAKE_SOURCE_DIR}/cmake/CPM.cmake)
8-
9-
CPMAddPackage(NAME matplot
10-
GIT_REPOSITORY "https://github.com/alandefreitas/matplotplusplus.git"
11-
VERSION 1.1.0
12-
OPTIONS "BUILD_WITH_PEDANTIC_WARNINGS OFF"
13-
EXCLUDE_FROM_ALL ON
14-
)
15-
16-
file(GLOB_RECURSE SOURCES "*.cpp" "*.h")
17-
add_executable(${PROJECT_NAME} ${SOURCES})
18-
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_LIST_DIR})
19-
target_link_libraries(${PROJECT_NAME} PUBLIC dsplib matplot)
1+
project(examples)
2+
3+
include(${CMAKE_SOURCE_DIR}/cmake/FindFFTW.cmake)
4+
5+
FindFFTW()
6+
7+
if (DSPLIB_EXCLUDE_FFT AND TARGET FFTW:Double)
8+
add_subdirectory(fftw-backend)
9+
else()
10+
add_subdirectory(basics)
11+
endif()

examples/basics/CMakeLists.txt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
cmake_minimum_required(VERSION 3.10)
2+
project(dsplib-example LANGUAGES CXX)
3+
4+
set(CMAKE_CXX_STANDARD 17)
5+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
6+
7+
include(${CMAKE_SOURCE_DIR}/cmake/CPM.cmake)
8+
9+
CPMAddPackage(NAME matplot
10+
GIT_REPOSITORY "https://github.com/alandefreitas/matplotplusplus.git"
11+
VERSION 1.1.0
12+
OPTIONS "BUILD_WITH_PEDANTIC_WARNINGS OFF"
13+
EXCLUDE_FROM_ALL ON
14+
)
15+
16+
file(GLOB_RECURSE SOURCES "*.cpp" "*.h")
17+
add_executable(${PROJECT_NAME} ${SOURCES})
18+
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_LIST_DIR})
19+
target_link_libraries(${PROJECT_NAME} PUBLIC dsplib matplot)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
cmake_minimum_required(VERSION 3.10)
2+
project(fftw-backend-example LANGUAGES CXX)
3+
4+
set(CMAKE_CXX_STANDARD 17)
5+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
6+
7+
add_library(dsplib-fftw fft.cpp)
8+
target_link_libraries(dsplib-fftw PUBLIC dsplib::interface FFTW:Double)
9+
10+
cmake_policy(SET CMP0079 NEW)
11+
target_link_libraries(dsplib PRIVATE dsplib-fftw)
12+
13+
add_executable(${PROJECT_NAME} main.cpp)
14+
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_LIST_DIR})
15+
target_link_libraries(${PROJECT_NAME} PUBLIC dsplib)

examples/fftw-backend/fft.cpp

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
#include <dsplib/fft.h>
2+
#include <dsplib/ifft.h>
3+
4+
#include "dsplib/array.h"
5+
#include "fftw3.h"
6+
7+
namespace dsplib {
8+
9+
namespace {
10+
11+
//------------------------------------------------------------------------------------------------------------------------
12+
//complex FFT/IFFT
13+
class FFTWPlanC : public FftPlanC
14+
{
15+
public:
16+
explicit FFTWPlanC(int n, bool forward = true)
17+
: n_{n}
18+
, scale_(forward ? 1 : (1.0 / n_))
19+
, out_(n) {
20+
const int sign = forward ? FFTW_FORWARD : FFTW_BACKWARD;
21+
in_ = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * n);
22+
plan_ = fftw_plan_dft_1d(n, in_, reinterpret_cast<fftw_complex*>(out_.data()), sign,
23+
FFTW_MEASURE | FFTW_DESTROY_INPUT);
24+
}
25+
26+
virtual ~FFTWPlanC() {
27+
fftw_destroy_plan(plan_);
28+
fftw_free(in_);
29+
}
30+
31+
dsplib::arr_cmplx solve(const dsplib::arr_cmplx& x) const final {
32+
DSPLIB_ASSERT(x.size() == n_, "input size must be equal `n`");
33+
std::memcpy(in_, x.data(), n_ * sizeof(x[0]));
34+
fftw_execute(plan_);
35+
out_ *= scale_;
36+
return out_;
37+
}
38+
39+
int size() const noexcept final {
40+
return n_;
41+
}
42+
43+
private:
44+
const int n_;
45+
double scale_{1.0};
46+
fftw_complex* in_{nullptr};
47+
mutable dsplib::arr_cmplx out_;
48+
fftw_plan plan_;
49+
};
50+
51+
//------------------------------------------------------------------------------------------------------------------------
52+
//real FFT
53+
class FFTWPlanR : public FftPlanR
54+
{
55+
public:
56+
explicit FFTWPlanR(int n)
57+
: n_{n}
58+
, out_(n) {
59+
in_ = (double*)fftw_malloc(sizeof(double) * n);
60+
plan_ =
61+
fftw_plan_dft_r2c_1d(n, in_, reinterpret_cast<fftw_complex*>(out_.data()), FFTW_MEASURE | FFTW_DESTROY_INPUT);
62+
}
63+
64+
virtual ~FFTWPlanR() {
65+
fftw_destroy_plan(plan_);
66+
fftw_free(in_);
67+
}
68+
69+
dsplib::arr_cmplx solve(const dsplib::arr_real& x) const final {
70+
DSPLIB_ASSERT(x.size() == n_, "input size must be equal `n`");
71+
std::memcpy(in_, x.data(), n_ * sizeof(x[0]));
72+
fftw_execute(plan_);
73+
for (size_t i = 1; i < n_ / 2; ++i) {
74+
out_[n_ - i] = out_[i].conj();
75+
}
76+
return out_;
77+
}
78+
79+
int size() const noexcept final {
80+
return n_;
81+
}
82+
83+
private:
84+
const int n_;
85+
double* in_{nullptr};
86+
mutable dsplib::arr_cmplx out_;
87+
fftw_plan plan_;
88+
};
89+
90+
//------------------------------------------------------------------------------------------------------------------------
91+
//real IFFT
92+
class IFFTWPlanR : public IfftPlanR
93+
{
94+
public:
95+
explicit IFFTWPlanR(int n)
96+
: n_{n}
97+
, scale_{1.0 / n_}
98+
, out_(n) {
99+
in_ = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * (n_ / 2 + 1));
100+
plan_ = fftw_plan_dft_c2r_1d(n, in_, out_.data(), FFTW_MEASURE | FFTW_DESTROY_INPUT);
101+
}
102+
103+
virtual ~IFFTWPlanR() {
104+
fftw_destroy_plan(plan_);
105+
fftw_free(in_);
106+
}
107+
108+
dsplib::arr_real solve(const dsplib::arr_cmplx& x) const final {
109+
const int n2 = n_ / 2 + 1;
110+
DSPLIB_ASSERT((x.size() == n_) || (x.size() == n2), "input size must be equal `n` or `n/2+1`");
111+
std::memcpy(in_, x.data(), n2 * sizeof(x[0]));
112+
fftw_execute(plan_);
113+
out_ *= scale_;
114+
return out_;
115+
}
116+
117+
int size() const noexcept final {
118+
return n_;
119+
}
120+
121+
private:
122+
const int n_;
123+
const double scale_;
124+
fftw_complex* in_{nullptr};
125+
mutable dsplib::arr_real out_;
126+
fftw_plan plan_;
127+
};
128+
129+
} // namespace
130+
131+
std::shared_ptr<FftPlanC> fft_plan_c(int n) {
132+
return std::make_shared<FFTWPlanC>(n, true);
133+
}
134+
135+
std::shared_ptr<FftPlanR> fft_plan_r(int n) {
136+
return std::make_shared<FFTWPlanR>(n);
137+
}
138+
139+
std::shared_ptr<IfftPlanC> ifft_plan_c(int n) {
140+
return std::make_shared<FFTWPlanC>(n, false);
141+
}
142+
143+
std::shared_ptr<IfftPlanR> ifft_plan_r(int n) {
144+
return std::make_shared<IFFTWPlanR>(n);
145+
}
146+
147+
} // namespace dsplib

examples/fftw-backend/main.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#include <dsplib.h>
2+
3+
int main() {
4+
using namespace std::complex_literals;
5+
{
6+
dsplib::arr_real x = {1, 2, 3, 4};
7+
auto y = dsplib::fft(x);
8+
dsplib::arr_cmplx d = {10.0000 + 0.0000i, -2.0000 + 2.0000i, -2.0000 + 0.0000i, -2.0000 - 2.0000i};
9+
auto e = dsplib::max(dsplib::abs(y - d));
10+
DSPLIB_ASSERT(e < dsplib::eps(), "transform error");
11+
12+
auto ix = dsplib::irfft(y);
13+
auto ie = dsplib::max(dsplib::abs(ix - x));
14+
DSPLIB_ASSERT(ie < dsplib::eps(), "transform error");
15+
}
16+
17+
{
18+
dsplib::arr_cmplx x = {1 + 1i, 2 + 2i, 3 + 3i, 4 + 4i};
19+
auto y = dsplib::fft(x);
20+
dsplib::arr_cmplx d = {10.0000 + 10.0000i, -4.0000 + 0.0000i, -2.0000 - 2.0000i, 0.0000 - 4.0000i};
21+
auto e = dsplib::max(dsplib::abs(y - d));
22+
DSPLIB_ASSERT(e < dsplib::eps(), "transform error");
23+
24+
auto ix = dsplib::ifft(y);
25+
auto ie = dsplib::max(dsplib::abs(ix - x));
26+
DSPLIB_ASSERT(ie < dsplib::eps(), "transform error");
27+
}
28+
29+
return 0;
30+
}

include/dsplib/czt.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ namespace dsplib {
77

88
class CztPlanImpl;
99

10-
class CztPlan : public BaseFftPlanC
10+
class CztPlan : public FftPlanC
1111
{
1212
public:
1313
explicit CztPlan(int n, int m, cmplx_t w, cmplx_t a = 1);

0 commit comments

Comments
 (0)