From a97702eb38411499b2ae6163d83d3adcbcfc9263 Mon Sep 17 00:00:00 2001 From: Galen Lynch Date: Thu, 10 Sep 2026 08:08:26 -0700 Subject: [PATCH 1/4] build: preserve the existing CMake minimum The packaging change does not require every OpenEphysFFTW build to use a newer CMake release. Keep the top-level project at its existing 3.5 minimum and apply newer requirements only to the isolated tools that need them. Automatic staging now requires CMake 3.19 for archive extraction and fatal subprocess errors. The dependency smoke project requires CMake 3.22 for its test-environment modification. --- CMakeLists.txt | 2 +- DependencyTests/CMakeLists.txt | 2 +- README.md | 4 ++-- tools/PrepareFFTWBundle.cmake | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a0569ef..a4ecebb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.15) +cmake_minimum_required(VERSION 3.5.0) if (NOT DEFINED GUI_BASE_DIR) if (DEFINED ENV{GUI_BASE_DIR}) set(GUI_BASE_DIR $ENV{GUI_BASE_DIR}) diff --git a/DependencyTests/CMakeLists.txt b/DependencyTests/CMakeLists.txt index 167c41b..7df81c3 100644 --- a/DependencyTests/CMakeLists.txt +++ b/DependencyTests/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.15) +cmake_minimum_required(VERSION 3.22) project(OpenEphysFFTWDependencySmokeTest LANGUAGES C) if(NOT DEFINED FFTW_ROOT) diff --git a/README.md b/README.md index f387150..545c065 100644 --- a/README.md +++ b/README.md @@ -24,8 +24,8 @@ previously staged platform directory: cmake -S . -B Build -DFFTW_ROOT=/path/to/libs/linux ``` -Automatic staging requires CMake 3.24 or newer. Builds using `FFTW_ROOT` retain -the project's CMake 3.15 minimum. +Automatic staging requires CMake 3.19 or newer. Builds using `FFTW_ROOT` retain +the project's existing CMake 3.5 minimum. The bundles intentionally omit the separate long-double, MPI, OpenMP, and threaded FFTW libraries. The `fftw3` and `fftw3f` libraries themselves are not diff --git a/tools/PrepareFFTWBundle.cmake b/tools/PrepareFFTWBundle.cmake index 918813d..19c7c43 100644 --- a/tools/PrepareFFTWBundle.cmake +++ b/tools/PrepareFFTWBundle.cmake @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.24) +cmake_minimum_required(VERSION 3.19) if(NOT DEFINED PLATFORM OR NOT PLATFORM MATCHES "^(linux|macos|windows)$") message(FATAL_ERROR "Set PLATFORM to linux, macos, or windows") From db30fcabcf65282f48b79deecc6fc9b4005ae488 Mon Sep 17 00:00:00 2001 From: Galen Lynch Date: Sun, 6 Sep 2026 12:29:11 -0700 Subject: [PATCH 2/4] feat: add batched forward real FFT plans Callers currently manage one FFT at a time, which prevents channel-by-taper workloads from sharing plans and aligned storage. Add move-only float and double real-to-complex plans with transform-major buffers. Reuse FFTW plan-many objects so execution requires no allocation. --- README.md | 5 + Source/OpenEphysFFTW.cpp | 2 +- Source/OpenEphysFFTW.h | 4 +- Source/OpenEphysFFTWBatch.cpp | 248 ++++++++++++++++++++++++++++++++++ Source/OpenEphysFFTWBatch.h | 108 +++++++++++++++ 5 files changed, 365 insertions(+), 2 deletions(-) create mode 100644 Source/OpenEphysFFTWBatch.cpp create mode 100644 Source/OpenEphysFFTWBatch.h diff --git a/README.md b/README.md index 545c065..6662820 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,11 @@ This is a small library to wrap FFTW functionality useful for Open Ephys. It is currently used by the [Phase Calculator](https://github.com/tne-lab/phase-calculator) and Real-Time Coherence plugins (under development). +`OpenEphysFFTWBatch.h` provides a lightweight, JUCE-independent batch API for +aligned single- and double-precision real-to-complex transforms. Batch input is +transform-major, with contiguous rows of `N` real values; output rows contain +`floor(N/2)+1` complex values. Create plans off real-time threads and reuse them. + ## Dependency OpenEphysFFTW uses complete non-threaded, shared double- and single-precision diff --git a/Source/OpenEphysFFTW.cpp b/Source/OpenEphysFFTW.cpp index 4a1fb18..fb58c45 100644 --- a/Source/OpenEphysFFTW.cpp +++ b/Source/OpenEphysFFTW.cpp @@ -353,4 +353,4 @@ void FFTWTransformableArray::hilbert() fftReal(); freqDomainHilbert(); ifft(); -} \ No newline at end of file +} diff --git a/Source/OpenEphysFFTW.h b/Source/OpenEphysFFTW.h index 36d913d..8331fab 100644 --- a/Source/OpenEphysFFTW.h +++ b/Source/OpenEphysFFTW.h @@ -32,6 +32,8 @@ transform library #include #include +#include "OpenEphysFFTWBatch.h" + // forward-declare: struct fftw_plan_s; @@ -174,4 +176,4 @@ class FFTWTransformableArrayUsing : public FFTWTransformableArray {} }; -#endif // OEP_FFTW_H_INCLUDED \ No newline at end of file +#endif // OEP_FFTW_H_INCLUDED diff --git a/Source/OpenEphysFFTWBatch.cpp b/Source/OpenEphysFFTWBatch.cpp new file mode 100644 index 0000000..5ed8d0c --- /dev/null +++ b/Source/OpenEphysFFTWBatch.cpp @@ -0,0 +1,248 @@ +/* +------------------------------------------------------------------ + +This file is part of a library for the Open Ephys GUI +Copyright (C) 2026 Open Ephys + +------------------------------------------------------------------ + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +*/ + +#include "OpenEphysFFTWBatch.h" + +#include + +#include +#include +#include + +namespace +{ +std::mutex plannerMutex; + +std::size_t checkedElementCount (int elementsPerTransform, int transformCount) +{ + if (elementsPerTransform <= 0 || transformCount <= 0) + throw std::invalid_argument ("FFTW batch dimensions must be positive"); + + const auto elements = static_cast (elementsPerTransform); + const auto transforms = static_cast (transformCount); + if (transforms > std::numeric_limits::max() / elements) + throw std::length_error ("FFTW batch allocation is too large"); + return elements * transforms; +} + +bool validTransformIndex (int transformIndex, int transformCount) +{ + return transformIndex >= 0 && transformIndex < transformCount; +} +} // namespace + +struct FFTWRealToComplexBatchFloat::Impl +{ + Impl (int length, int count, unsigned int flags) + : transformLength (length), transformCount (count), binCount (length > 0 ? length / 2 + 1 : 0), input (fftwf_alloc_real (checkedElementCount (length, count))), output (fftwf_alloc_complex (checkedElementCount (binCount, count))) + { + if (input == nullptr || output == nullptr) + { + fftwf_free (output); + fftwf_free (input); + throw std::bad_alloc(); + } + + const int dimensions[] { transformLength }; + { + const std::lock_guard lock (plannerMutex); + plan = fftwf_plan_many_dft_r2c (1, dimensions, transformCount, input, nullptr, 1, transformLength, output, nullptr, 1, binCount, flags); + } + if (plan == nullptr) + { + fftwf_free (output); + fftwf_free (input); + throw std::runtime_error ( + "Unable to create FFTW single-precision batch plan"); + } + } + + ~Impl() + { + if (plan != nullptr) + { + const std::lock_guard lock (plannerMutex); + fftwf_destroy_plan (plan); + } + fftwf_free (output); + fftwf_free (input); + } + + const int transformLength; + const int transformCount; + const int binCount; + float* input = nullptr; + fftwf_complex* output = nullptr; + fftwf_plan plan = nullptr; +}; + +FFTWRealToComplexBatchFloat::FFTWRealToComplexBatchFloat (int transformLength, + int transformCount, + unsigned int flags) + : impl (new Impl (transformLength, transformCount, flags)) {} + +FFTWRealToComplexBatchFloat::~FFTWRealToComplexBatchFloat() = default; + +float* FFTWRealToComplexBatchFloat::getInputPointer (int transformIndex) +{ + return validTransformIndex (transformIndex, impl->transformCount) + ? impl->input + transformIndex * impl->transformLength + : nullptr; +} + +const float* + FFTWRealToComplexBatchFloat::getInputPointer (int transformIndex) const +{ + return validTransformIndex (transformIndex, impl->transformCount) + ? impl->input + transformIndex * impl->transformLength + : nullptr; +} + +std::complex* + FFTWRealToComplexBatchFloat::getOutputPointer (int transformIndex) +{ + return validTransformIndex (transformIndex, impl->transformCount) + ? reinterpret_cast*> (impl->output) + transformIndex * impl->binCount + : nullptr; +} + +const std::complex* + FFTWRealToComplexBatchFloat::getOutputPointer (int transformIndex) const +{ + return validTransformIndex (transformIndex, impl->transformCount) + ? reinterpret_cast*> (impl->output) + transformIndex * impl->binCount + : nullptr; +} + +int FFTWRealToComplexBatchFloat::getTransformLength() const noexcept +{ + return impl->transformLength; +} +int FFTWRealToComplexBatchFloat::getTransformCount() const noexcept +{ + return impl->transformCount; +} +int FFTWRealToComplexBatchFloat::getBinCount() const noexcept +{ + return impl->binCount; +} + +void FFTWRealToComplexBatchFloat::execute() { fftwf_execute (impl->plan); } + +struct FFTWRealToComplexBatchDouble::Impl +{ + Impl (int length, int count, unsigned int flags) + : transformLength (length), transformCount (count), binCount (length > 0 ? length / 2 + 1 : 0), input (fftw_alloc_real (checkedElementCount (length, count))), output (fftw_alloc_complex (checkedElementCount (binCount, count))) + { + if (input == nullptr || output == nullptr) + { + fftw_free (output); + fftw_free (input); + throw std::bad_alloc(); + } + + const int dimensions[] { transformLength }; + { + const std::lock_guard lock (plannerMutex); + plan = fftw_plan_many_dft_r2c (1, dimensions, transformCount, input, nullptr, 1, transformLength, output, nullptr, 1, binCount, flags); + } + if (plan == nullptr) + { + fftw_free (output); + fftw_free (input); + throw std::runtime_error ( + "Unable to create FFTW double-precision batch plan"); + } + } + + ~Impl() + { + if (plan != nullptr) + { + const std::lock_guard lock (plannerMutex); + fftw_destroy_plan (plan); + } + fftw_free (output); + fftw_free (input); + } + + const int transformLength; + const int transformCount; + const int binCount; + double* input = nullptr; + fftw_complex* output = nullptr; + fftw_plan plan = nullptr; +}; + +FFTWRealToComplexBatchDouble::FFTWRealToComplexBatchDouble (int transformLength, + int transformCount, + unsigned int flags) + : impl (new Impl (transformLength, transformCount, flags)) {} + +FFTWRealToComplexBatchDouble::~FFTWRealToComplexBatchDouble() = default; + +double* FFTWRealToComplexBatchDouble::getInputPointer (int transformIndex) +{ + return validTransformIndex (transformIndex, impl->transformCount) + ? impl->input + transformIndex * impl->transformLength + : nullptr; +} + +const double* + FFTWRealToComplexBatchDouble::getInputPointer (int transformIndex) const +{ + return validTransformIndex (transformIndex, impl->transformCount) + ? impl->input + transformIndex * impl->transformLength + : nullptr; +} + +std::complex* + FFTWRealToComplexBatchDouble::getOutputPointer (int transformIndex) +{ + return validTransformIndex (transformIndex, impl->transformCount) + ? reinterpret_cast*> (impl->output) + transformIndex * impl->binCount + : nullptr; +} + +const std::complex* + FFTWRealToComplexBatchDouble::getOutputPointer (int transformIndex) const +{ + return validTransformIndex (transformIndex, impl->transformCount) + ? reinterpret_cast*> (impl->output) + transformIndex * impl->binCount + : nullptr; +} + +int FFTWRealToComplexBatchDouble::getTransformLength() const noexcept +{ + return impl->transformLength; +} +int FFTWRealToComplexBatchDouble::getTransformCount() const noexcept +{ + return impl->transformCount; +} +int FFTWRealToComplexBatchDouble::getBinCount() const noexcept +{ + return impl->binCount; +} + +void FFTWRealToComplexBatchDouble::execute() { fftw_execute (impl->plan); } diff --git a/Source/OpenEphysFFTWBatch.h b/Source/OpenEphysFFTWBatch.h new file mode 100644 index 0000000..783caeb --- /dev/null +++ b/Source/OpenEphysFFTWBatch.h @@ -0,0 +1,108 @@ +/* +------------------------------------------------------------------ + +This file is part of a library for the Open Ephys GUI +Copyright (C) 2026 Open Ephys + +------------------------------------------------------------------ + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +*/ + +#ifndef OEP_FFTW_BATCH_H_INCLUDED +#define OEP_FFTW_BATCH_H_INCLUDED + +#include +#include + +#if defined(_WIN32) +#if defined(OEPLUGIN) +#define OEP_FFTW_API __declspec (dllimport) +#else +#define OEP_FFTW_API __declspec (dllexport) +#endif +#else +#define OEP_FFTW_API __attribute__ ((visibility ("default"))) +#endif + +/** + Owns aligned storage and a reusable single-precision real-to-complex batch. + + Each transform occupies one contiguous row of transformLength real inputs + and produces transformLength / 2 + 1 contiguous complex outputs. Planning + and allocation occur in the constructor; execute() performs no allocation. + The internal FFTW planning strategy is not part of this API contract. + Planning is serialized across batch instances. Concurrent execution is safe + for distinct instances, but callers must not execute one instance twice at + the same time because its input and output storage are shared. +*/ +class OEP_FFTW_API FFTWRealToComplexBatchFloat +{ +public: + FFTWRealToComplexBatchFloat (int transformLength, + int transformCount, + unsigned int flags = 0U /* FFTW_MEASURE */); + ~FFTWRealToComplexBatchFloat(); + + float* getInputPointer (int transformIndex = 0); + const float* getInputPointer (int transformIndex = 0) const; + std::complex* getOutputPointer (int transformIndex = 0); + const std::complex* getOutputPointer (int transformIndex = 0) const; + + int getTransformLength() const noexcept; + int getTransformCount() const noexcept; + int getBinCount() const noexcept; + + void execute(); + +private: + struct Impl; + std::unique_ptr impl; + + FFTWRealToComplexBatchFloat (const FFTWRealToComplexBatchFloat&) = delete; + FFTWRealToComplexBatchFloat& operator= (const FFTWRealToComplexBatchFloat&) = delete; +}; + +/** Double-precision counterpart to FFTWRealToComplexBatchFloat. */ +class OEP_FFTW_API FFTWRealToComplexBatchDouble +{ +public: + FFTWRealToComplexBatchDouble (int transformLength, + int transformCount, + unsigned int flags = 0U /* FFTW_MEASURE */); + ~FFTWRealToComplexBatchDouble(); + + double* getInputPointer (int transformIndex = 0); + const double* getInputPointer (int transformIndex = 0) const; + std::complex* getOutputPointer (int transformIndex = 0); + const std::complex* getOutputPointer (int transformIndex = 0) const; + + int getTransformLength() const noexcept; + int getTransformCount() const noexcept; + int getBinCount() const noexcept; + + void execute(); + +private: + struct Impl; + std::unique_ptr impl; + + FFTWRealToComplexBatchDouble (const FFTWRealToComplexBatchDouble&) = delete; + FFTWRealToComplexBatchDouble& operator= (const FFTWRealToComplexBatchDouble&) = delete; +}; + +#undef OEP_FFTW_API + +#endif // OEP_FFTW_BATCH_H_INCLUDED From 2ad19433fedfa63cd036f98f7237ae0688d63fd0 Mon Sep 17 00:00:00 2001 From: Galen Lynch Date: Sun, 6 Sep 2026 13:57:31 -0700 Subject: [PATCH 3/4] feat: add batched inverse real FFT plans Forward-only batches cannot reconstruct real signals with the same reusable storage and planning model. Add move-only float and double complex-to-real plans with transform-major buffers. Preserve FFTW's unnormalized inverse convention so callers control normalization. --- README.md | 7 +- Source/OpenEphysFFTWBatch.cpp | 174 ++++++++++++++++++++++++++++++++++ Source/OpenEphysFFTWBatch.h | 62 ++++++++++++ 3 files changed, 240 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6662820..015c3ae 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,10 @@ This is a small library to wrap FFTW functionality useful for Open Ephys. It is currently used by the [Phase Calculator](https://github.com/tne-lab/phase-calculator) and Real-Time Coherence plugins (under development). `OpenEphysFFTWBatch.h` provides a lightweight, JUCE-independent batch API for -aligned single- and double-precision real-to-complex transforms. Batch input is -transform-major, with contiguous rows of `N` real values; output rows contain -`floor(N/2)+1` complex values. Create plans off real-time threads and reuse them. +aligned single- and double-precision real-to-complex and complex-to-real +transforms. Batches are transform-major, with contiguous rows. Inverse +transforms follow FFTW's convention and are not normalized. Create plans off +real-time threads and reuse them. ## Dependency diff --git a/Source/OpenEphysFFTWBatch.cpp b/Source/OpenEphysFFTWBatch.cpp index 5ed8d0c..046f9b4 100644 --- a/Source/OpenEphysFFTWBatch.cpp +++ b/Source/OpenEphysFFTWBatch.cpp @@ -246,3 +246,177 @@ int FFTWRealToComplexBatchDouble::getBinCount() const noexcept } void FFTWRealToComplexBatchDouble::execute() { fftw_execute (impl->plan); } + +struct FFTWComplexToRealBatchFloat::Impl +{ + Impl (int length, int count, unsigned int flags) + : transformLength (length), transformCount (count), binCount (length > 0 ? length / 2 + 1 : 0), input (fftwf_alloc_complex (checkedElementCount (binCount, count))), output (fftwf_alloc_real (checkedElementCount (length, count))) + { + if (input == nullptr || output == nullptr) + { + fftwf_free (output); + fftwf_free (input); + throw std::bad_alloc(); + } + + const int dimensions[] { transformLength }; + { + const std::lock_guard lock (plannerMutex); + plan = fftwf_plan_many_dft_c2r (1, dimensions, transformCount, input, nullptr, 1, binCount, output, nullptr, 1, transformLength, flags); + } + if (plan == nullptr) + { + fftwf_free (output); + fftwf_free (input); + throw std::runtime_error ( + "Unable to create FFTW single-precision inverse batch plan"); + } + } + + ~Impl() + { + if (plan != nullptr) + { + const std::lock_guard lock (plannerMutex); + fftwf_destroy_plan (plan); + } + fftwf_free (output); + fftwf_free (input); + } + + const int transformLength; + const int transformCount; + const int binCount; + fftwf_complex* input = nullptr; + float* output = nullptr; + fftwf_plan plan = nullptr; +}; + +FFTWComplexToRealBatchFloat::FFTWComplexToRealBatchFloat (int transformLength, + int transformCount, + unsigned int flags) + : impl (new Impl (transformLength, transformCount, flags)) {} + +FFTWComplexToRealBatchFloat::~FFTWComplexToRealBatchFloat() = default; + +std::complex* FFTWComplexToRealBatchFloat::getInputPointer (int transformIndex) +{ + return validTransformIndex (transformIndex, impl->transformCount) + ? reinterpret_cast*> (impl->input) + transformIndex * impl->binCount + : nullptr; +} + +const std::complex* + FFTWComplexToRealBatchFloat::getInputPointer (int transformIndex) const +{ + return validTransformIndex (transformIndex, impl->transformCount) + ? reinterpret_cast*> (impl->input) + transformIndex * impl->binCount + : nullptr; +} + +float* FFTWComplexToRealBatchFloat::getOutputPointer (int transformIndex) +{ + return validTransformIndex (transformIndex, impl->transformCount) + ? impl->output + transformIndex * impl->transformLength + : nullptr; +} + +const float* FFTWComplexToRealBatchFloat::getOutputPointer (int transformIndex) const +{ + return validTransformIndex (transformIndex, impl->transformCount) + ? impl->output + transformIndex * impl->transformLength + : nullptr; +} + +int FFTWComplexToRealBatchFloat::getTransformLength() const noexcept { return impl->transformLength; } +int FFTWComplexToRealBatchFloat::getTransformCount() const noexcept { return impl->transformCount; } +int FFTWComplexToRealBatchFloat::getBinCount() const noexcept { return impl->binCount; } + +void FFTWComplexToRealBatchFloat::execute() { fftwf_execute (impl->plan); } + +struct FFTWComplexToRealBatchDouble::Impl +{ + Impl (int length, int count, unsigned int flags) + : transformLength (length), transformCount (count), binCount (length > 0 ? length / 2 + 1 : 0), input (fftw_alloc_complex (checkedElementCount (binCount, count))), output (fftw_alloc_real (checkedElementCount (length, count))) + { + if (input == nullptr || output == nullptr) + { + fftw_free (output); + fftw_free (input); + throw std::bad_alloc(); + } + + const int dimensions[] { transformLength }; + { + const std::lock_guard lock (plannerMutex); + plan = fftw_plan_many_dft_c2r (1, dimensions, transformCount, input, nullptr, 1, binCount, output, nullptr, 1, transformLength, flags); + } + if (plan == nullptr) + { + fftw_free (output); + fftw_free (input); + throw std::runtime_error ( + "Unable to create FFTW double-precision inverse batch plan"); + } + } + + ~Impl() + { + if (plan != nullptr) + { + const std::lock_guard lock (plannerMutex); + fftw_destroy_plan (plan); + } + fftw_free (output); + fftw_free (input); + } + + const int transformLength; + const int transformCount; + const int binCount; + fftw_complex* input = nullptr; + double* output = nullptr; + fftw_plan plan = nullptr; +}; + +FFTWComplexToRealBatchDouble::FFTWComplexToRealBatchDouble (int transformLength, + int transformCount, + unsigned int flags) + : impl (new Impl (transformLength, transformCount, flags)) {} + +FFTWComplexToRealBatchDouble::~FFTWComplexToRealBatchDouble() = default; + +std::complex* FFTWComplexToRealBatchDouble::getInputPointer (int transformIndex) +{ + return validTransformIndex (transformIndex, impl->transformCount) + ? reinterpret_cast*> (impl->input) + transformIndex * impl->binCount + : nullptr; +} + +const std::complex* + FFTWComplexToRealBatchDouble::getInputPointer (int transformIndex) const +{ + return validTransformIndex (transformIndex, impl->transformCount) + ? reinterpret_cast*> (impl->input) + transformIndex * impl->binCount + : nullptr; +} + +double* FFTWComplexToRealBatchDouble::getOutputPointer (int transformIndex) +{ + return validTransformIndex (transformIndex, impl->transformCount) + ? impl->output + transformIndex * impl->transformLength + : nullptr; +} + +const double* FFTWComplexToRealBatchDouble::getOutputPointer (int transformIndex) const +{ + return validTransformIndex (transformIndex, impl->transformCount) + ? impl->output + transformIndex * impl->transformLength + : nullptr; +} + +int FFTWComplexToRealBatchDouble::getTransformLength() const noexcept { return impl->transformLength; } +int FFTWComplexToRealBatchDouble::getTransformCount() const noexcept { return impl->transformCount; } +int FFTWComplexToRealBatchDouble::getBinCount() const noexcept { return impl->binCount; } + +void FFTWComplexToRealBatchDouble::execute() { fftw_execute (impl->plan); } diff --git a/Source/OpenEphysFFTWBatch.h b/Source/OpenEphysFFTWBatch.h index 783caeb..02ae937 100644 --- a/Source/OpenEphysFFTWBatch.h +++ b/Source/OpenEphysFFTWBatch.h @@ -103,6 +103,68 @@ class OEP_FFTW_API FFTWRealToComplexBatchDouble FFTWRealToComplexBatchDouble& operator= (const FFTWRealToComplexBatchDouble&) = delete; }; +/** + Reusable single-precision complex-to-real batch. + + Input rows contain transformLength / 2 + 1 complex values in FFTW's + Hermitian half-spectrum layout; output rows contain transformLength real + values. Like FFTW itself, execute() does not normalize the inverse transform. +*/ +class OEP_FFTW_API FFTWComplexToRealBatchFloat +{ +public: + FFTWComplexToRealBatchFloat (int transformLength, + int transformCount, + unsigned int flags = 0U /* FFTW_MEASURE */); + ~FFTWComplexToRealBatchFloat(); + + std::complex* getInputPointer (int transformIndex = 0); + const std::complex* getInputPointer (int transformIndex = 0) const; + float* getOutputPointer (int transformIndex = 0); + const float* getOutputPointer (int transformIndex = 0) const; + + int getTransformLength() const noexcept; + int getTransformCount() const noexcept; + int getBinCount() const noexcept; + + void execute(); + +private: + struct Impl; + std::unique_ptr impl; + + FFTWComplexToRealBatchFloat (const FFTWComplexToRealBatchFloat&) = delete; + FFTWComplexToRealBatchFloat& operator= (const FFTWComplexToRealBatchFloat&) = delete; +}; + +/** Double-precision counterpart to FFTWComplexToRealBatchFloat. */ +class OEP_FFTW_API FFTWComplexToRealBatchDouble +{ +public: + FFTWComplexToRealBatchDouble (int transformLength, + int transformCount, + unsigned int flags = 0U /* FFTW_MEASURE */); + ~FFTWComplexToRealBatchDouble(); + + std::complex* getInputPointer (int transformIndex = 0); + const std::complex* getInputPointer (int transformIndex = 0) const; + double* getOutputPointer (int transformIndex = 0); + const double* getOutputPointer (int transformIndex = 0) const; + + int getTransformLength() const noexcept; + int getTransformCount() const noexcept; + int getBinCount() const noexcept; + + void execute(); + +private: + struct Impl; + std::unique_ptr impl; + + FFTWComplexToRealBatchDouble (const FFTWComplexToRealBatchDouble&) = delete; + FFTWComplexToRealBatchDouble& operator= (const FFTWComplexToRealBatchDouble&) = delete; +}; + #undef OEP_FFTW_API #endif // OEP_FFTW_BATCH_H_INCLUDED From 2db859c490dc5e36abc4c41fb67eca41b78b2e16 Mon Sep 17 00:00:00 2001 From: Anjal Doshi Date: Fri, 11 Sep 2026 14:53:41 -0700 Subject: [PATCH 4/4] Replace OEP_FFTW_API with COMMON_LIB for batch FFTW classes Remove the redundant OEP_FFTW_API macro and use COMMON_LIB, which already provides the required export/import behavior. --- Source/OpenEphysFFTWBatch.h | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/Source/OpenEphysFFTWBatch.h b/Source/OpenEphysFFTWBatch.h index 02ae937..f404f48 100644 --- a/Source/OpenEphysFFTWBatch.h +++ b/Source/OpenEphysFFTWBatch.h @@ -24,19 +24,10 @@ along with this program. If not, see . #ifndef OEP_FFTW_BATCH_H_INCLUDED #define OEP_FFTW_BATCH_H_INCLUDED +#include #include #include -#if defined(_WIN32) -#if defined(OEPLUGIN) -#define OEP_FFTW_API __declspec (dllimport) -#else -#define OEP_FFTW_API __declspec (dllexport) -#endif -#else -#define OEP_FFTW_API __attribute__ ((visibility ("default"))) -#endif - /** Owns aligned storage and a reusable single-precision real-to-complex batch. @@ -48,7 +39,7 @@ along with this program. If not, see . for distinct instances, but callers must not execute one instance twice at the same time because its input and output storage are shared. */ -class OEP_FFTW_API FFTWRealToComplexBatchFloat +class COMMON_LIB FFTWRealToComplexBatchFloat { public: FFTWRealToComplexBatchFloat (int transformLength, @@ -76,7 +67,7 @@ class OEP_FFTW_API FFTWRealToComplexBatchFloat }; /** Double-precision counterpart to FFTWRealToComplexBatchFloat. */ -class OEP_FFTW_API FFTWRealToComplexBatchDouble +class COMMON_LIB FFTWRealToComplexBatchDouble { public: FFTWRealToComplexBatchDouble (int transformLength, @@ -110,7 +101,7 @@ class OEP_FFTW_API FFTWRealToComplexBatchDouble Hermitian half-spectrum layout; output rows contain transformLength real values. Like FFTW itself, execute() does not normalize the inverse transform. */ -class OEP_FFTW_API FFTWComplexToRealBatchFloat +class COMMON_LIB FFTWComplexToRealBatchFloat { public: FFTWComplexToRealBatchFloat (int transformLength, @@ -138,7 +129,7 @@ class OEP_FFTW_API FFTWComplexToRealBatchFloat }; /** Double-precision counterpart to FFTWComplexToRealBatchFloat. */ -class OEP_FFTW_API FFTWComplexToRealBatchDouble +class COMMON_LIB FFTWComplexToRealBatchDouble { public: FFTWComplexToRealBatchDouble (int transformLength, @@ -165,6 +156,4 @@ class OEP_FFTW_API FFTWComplexToRealBatchDouble FFTWComplexToRealBatchDouble& operator= (const FFTWComplexToRealBatchDouble&) = delete; }; -#undef OEP_FFTW_API - #endif // OEP_FFTW_BATCH_H_INCLUDED