Audio Processing Framework (APF) version 0.5.0
convolver.h
Go to the documentation of this file.
1/******************************************************************************
2 Copyright (c) 2012-2016 Institut für Nachrichtentechnik, Universität Rostock
3 Copyright (c) 2006-2012 Quality & Usability Lab
4 Deutsche Telekom Laboratories, TU Berlin
5
6 Permission is hereby granted, free of charge, to any person obtaining a copy
7 of this software and associated documentation files (the "Software"), to deal
8 in the Software without restriction, including without limitation the rights
9 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 copies of the Software, and to permit persons to whom the Software is
11 furnished to do so, subject to the following conditions:
12
13 The above copyright notice and this permission notice shall be included in
14 all copies or substantial portions of the Software.
15
16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 THE SOFTWARE.
23*******************************************************************************/
24
25// https://AudioProcessingFramework.github.io/
26
29
30#ifndef APF_CONVOLVER_H
31#define APF_CONVOLVER_H
32
33#include <algorithm> // for std::transform()
34#include <functional> // for std::bind()
35#include <cassert>
36
37#ifdef __SSE__
38#include <xmmintrin.h> // for SSE instrinsics
39#endif
40
41#include "apf/math.h"
42#include "apf/fftwtools.h" // for fftw_allocator and fftw traits
43#include "apf/container.h" // for fixed_vector, fixed_list
44#include "apf/iterator.h" // for make_*_iterator()
45
46namespace apf
47{
48
59namespace conv
60{
61
63static size_t min_partitions(size_t block_size, size_t filter_size)
64{
65 assert(block_size > 0);
66 assert(filter_size > 0);
67 return (filter_size + block_size - 1) / block_size;
68}
69
71struct fft_node : fixed_vector<float, fftw_allocator<float>>
72{
73 explicit fft_node(size_t n)
75 , zero(true)
76 {}
77
78 fft_node(const fft_node&) = delete;
79 fft_node(fft_node&&) = default;
80
81 fft_node& operator=(const fft_node& rhs)
82 {
83 assert(this->size() == rhs.size());
84
85 if (rhs.zero)
86 {
87 this->zero = true;
88 }
89 else
90 {
91 std::copy(rhs.begin(), rhs.end(), this->begin());
92 this->zero = false;
93 }
94 return *this;
95 }
96
97 // WARNING: The 'zero' flag allows saving computation power, but it also
98 // raises the risk of programming errors! Handle with care!
99
102 bool zero;
103};
104
106struct Filter : fixed_vector<fft_node>
107{
109 Filter(size_t block_size_, size_t partitions_)
110 : fixed_vector<fft_node>(partitions_, block_size_ * 2)
111 {
112 assert(this->partitions() > 0);
113 }
114
116 template<typename In>
117 Filter(size_t block_size_, In first, In last, size_t partitions_ = 0);
118 // Implementation below, after definition of Transform
119
120 size_t block_size() const { return this->front().size() / 2; }
121 size_t partition_size() const { return this->front().size(); }
122 size_t partitions() const { return this->size(); }
123};
124
127{
128 public:
129 template<typename In>
130 void prepare_filter(In first, In last, Filter& filter) const;
131
132 size_t block_size() const { return _block_size; }
133 size_t partition_size() const { return _partition_size; }
134
135 template<typename In>
136 In prepare_partition(In first, In last, fft_node& partition) const;
137
138 protected:
139 explicit TransformBase(size_t block_size_);
140
141 TransformBase(TransformBase&&) = default;
142 ~TransformBase() = default;
143
144 using scoped_plan = fftw<float>::scoped_plan;
145 using plan_ptr = std::unique_ptr<scoped_plan>;
146
147 plan_ptr _create_plan(float* array) const;
148
150 void _fft(float* first) const
151 {
152 fftw<float>::execute_r2r(*_fft_plan, first, first);
153 _sort_coefficients(first);
154 }
155
156 plan_ptr _fft_plan;
157
158 private:
159 void _sort_coefficients(float* first) const;
160
161 const size_t _block_size;
162 const size_t _partition_size;
163};
164
165TransformBase::TransformBase(size_t block_size_)
166 : _block_size(block_size_)
167 , _partition_size(2 * _block_size)
168{
169 if (_block_size % 8 != 0)
170 {
171 throw std::logic_error("Convolver: block size must be a multiple of 8!");
172 }
173}
174
182TransformBase::plan_ptr
184{
185 return plan_ptr(new scoped_plan(fftw<float>::plan_r2r_1d, int(_partition_size)
186 , array, array, FFTW_R2HC, FFTW_PATIENT));
187}
188
196template<typename In>
197void
198TransformBase::prepare_filter(In first, In last, Filter& filter) const
199{
200 for (auto& partition: filter)
201 {
202 first = this->prepare_partition(first, last, partition);
203 }
204}
205
215template<typename In>
216In
217TransformBase::prepare_partition(In first, In last, fft_node& partition) const
218{
219 assert(std::distance(partition.begin(), partition.end())
220 == static_cast<fft_node::difference_type>(_partition_size));
221
222 using difference_type = typename std::iterator_traits<In>::difference_type;
223 auto chunk = std::min(
224 static_cast<difference_type>(_block_size), std::distance(first, last));
225
226 // This also works for the case chunk==0:
227 if (math::has_only_zeros(first, first + chunk))
228 {
229 partition.zero = true;
230 // No FFT has to be done (FFT of zero is also zero)
231 }
232 else
233 {
234 std::copy(first, first + chunk, partition.begin());
235 std::fill(partition.begin() + chunk, partition.end(), 0.0f); // zero padding
236 _fft(partition.data());
237 partition.zero = false;
238 }
239 return first + chunk;
240}
241
245void
246TransformBase::_sort_coefficients(float* data) const
247{
248 auto buffer = fixed_vector<float>(_partition_size);
249
250 size_t base = 8;
251
252 buffer[0] = data[0];
253 buffer[1] = data[1];
254 buffer[2] = data[2];
255 buffer[3] = data[3];
256 buffer[4] = data[_block_size];
257 buffer[5] = data[_partition_size - 1];
258 buffer[6] = data[_partition_size - 2];
259 buffer[7] = data[_partition_size - 3];
260
261 for (size_t i = 0; i < (_partition_size / 8-1); i++)
262 {
263 for (size_t ii = 0; ii < 4; ii++)
264 {
265 buffer[base+ii] = data[base/2+ii];
266 }
267
268 for (size_t ii = 0; ii < 4; ii++)
269 {
270 buffer[base+4+ii] = data[_partition_size-base/2-ii];
271 }
272
273 base += 8;
274 }
275
276 std::copy(buffer.begin(), buffer.end(), data);
277}
278
281{
282 Transform(size_t block_size_)
283 : TransformBase(block_size_)
284 {
285 // Temporary memory area for FFTW planning routines
286 fft_node planning_space(this->partition_size());
287 _fft_plan = _create_plan(planning_space.data());
288 }
289};
290
291template<typename In>
292Filter::Filter(size_t block_size_, In first, In last, size_t partitions_)
293 : fixed_vector<fft_node>(partitions_ ? partitions_
294 : min_partitions(block_size_, size_t(std::distance(first, last)))
295 , block_size_ * 2)
296{
297 assert(std::distance(first, last) > 0);
298 assert(this->partitions() > 0);
299
300 Transform(block_size_).prepare_filter(first, last, *this);
301}
302
307{
310 Input(size_t block_size_, size_t partitions_)
311 : TransformBase(block_size_)
312 // One additional list element for preparing the upcoming partition:
313 , spectra(partitions_ + 1, this->partition_size())
314 {
315 assert(partitions_ > 0);
316
317 _fft_plan = _create_plan(spectra.front().data());
318 }
319
320 template<typename In>
321 void add_block(In first);
322
323 size_t partitions() const { return spectra.size() - 1; }
324
328};
329
334template<typename In>
335void
337{
338 In last = first;
339 std::advance(last, static_cast<std::ptrdiff_t>(this->block_size()));
340
341 // rotate buffers (this->spectra.size() is always at least 2)
342 this->spectra.move(--this->spectra.end(), this->spectra.begin());
343
344 auto& current = this->spectra.front();
345 auto& next = this->spectra.back();
346
347 auto offset = static_cast<fft_node::difference_type>(this->block_size());
348
349 if (math::has_only_zeros(first, last))
350 {
351 next.zero = true;
352
353 if (current.zero)
354 {
355 // Nothing to be done, actual data is ignored
356 }
357 else
358 {
359 // If first half is not zero, second half must be filled with zeros
360 std::fill(current.begin() + offset, current.end(), 0.0f);
361 }
362 }
363 else
364 {
365 if (current.zero)
366 {
367 // First half must be actually filled with zeros
368 std::fill(current.begin(), current.begin() + offset, 0.0f);
369 }
370
371 // Copy data to second half of the current partition
372 std::copy(first, last, current.begin() + offset);
373 current.zero = false;
374 // Copy data to first half of the upcoming partition
375 std::copy(first, last, next.begin());
376 next.zero = false;
377 }
378
379 if (current.zero)
380 {
381 // Nothing to be done, FFT of zero is also zero
382 }
383 else
384 {
385 _fft(current.data());
386 }
387}
388
391{
392 public:
393 float* convolve(float weight = 1.0f);
394
395 size_t block_size() const { return _input.block_size(); }
396 size_t partitions() const { return _filter_ptrs.size(); }
397
398 protected:
399 explicit OutputBase(const Input& input);
400
401 // This is non-const to allow automatic move-constructor:
402 fft_node _empty_partition;
403
405 filter_ptrs_t _filter_ptrs;
406
407 private:
408 void _multiply_spectra();
409 void _multiply_partition_cpp(const float* signal, const float* filter);
410#ifdef __SSE__
411 void _multiply_partition_simd(const float* signal, const float* filter);
412#endif
413
414 void _unsort_coefficients();
415
416 void _ifft();
417
418 const Input& _input;
419
420 const size_t _partition_size;
421
422 fft_node _output_buffer;
423 fftw<float>::scoped_plan _ifft_plan;
424};
425
426OutputBase::OutputBase(const Input& input)
427 : _empty_partition(0)
428 // Initialize with empty partition
429 , _filter_ptrs(input.partitions(), &_empty_partition)
430 , _input(input)
431 , _partition_size(input.partition_size())
432 , _output_buffer(_partition_size)
433 , _ifft_plan(fftw<float>::plan_r2r_1d, int(_partition_size)
434 , _output_buffer.data()
435 , _output_buffer.data(), FFTW_HC2R, FFTW_PATIENT)
436{
437 assert(_filter_ptrs.size() > 0);
438}
439
447float*
449{
450 _multiply_spectra();
451
452 auto offset = static_cast<fft_node::difference_type>(_input.block_size());
453
454 // The first half will be discarded
455 auto second_half = make_begin_and_end(
456 _output_buffer.begin() + offset, _output_buffer.end());
457
458 assert(std::distance(second_half.begin(), second_half.end()) == offset);
459
460 if (_output_buffer.zero)
461 {
462 // Nothing to be done, IFFT of zero is also zero.
463 // _output_buffer was already reset to zero in _multiply_spectra().
464 }
465 else
466 {
467 _ifft();
468
469 // normalize buffer (fftw3 does not do this)
470 const auto norm = weight / float(_partition_size);
471 for (auto& x: second_half)
472 {
473 x *= norm;
474 }
475 }
476 return &second_half[0];
477}
478
479void
480OutputBase::_multiply_partition_cpp(const float* signal, const float* filter)
481{
482 // see http://www.ludd.luth.se/~torger/brutefir.html#bruteconv_4
483
484 auto d1s = _output_buffer[0] + signal[0] * filter[0];
485 auto d2s = _output_buffer[4] + signal[4] * filter[4];
486
487 for (size_t nn = 0; nn < _partition_size; nn += 8)
488 {
489 // real parts
490 _output_buffer[nn+0] += signal[nn+0] * filter[nn + 0] -
491 signal[nn+4] * filter[nn + 4];
492 _output_buffer[nn+1] += signal[nn+1] * filter[nn + 1] -
493 signal[nn+5] * filter[nn + 5];
494 _output_buffer[nn+2] += signal[nn+2] * filter[nn + 2] -
495 signal[nn+6] * filter[nn + 6];
496 _output_buffer[nn+3] += signal[nn+3] * filter[nn + 3] -
497 signal[nn+7] * filter[nn + 7];
498
499 // imaginary parts
500 _output_buffer[nn+4] += signal[nn+0] * filter[nn + 4] +
501 signal[nn+4] * filter[nn + 0];
502 _output_buffer[nn+5] += signal[nn+1] * filter[nn + 5] +
503 signal[nn+5] * filter[nn + 1];
504 _output_buffer[nn+6] += signal[nn+2] * filter[nn + 6] +
505 signal[nn+6] * filter[nn + 2];
506 _output_buffer[nn+7] += signal[nn+3] * filter[nn + 7] +
507 signal[nn+7] * filter[nn + 3];
508
509 } // for
510
511 _output_buffer[0] = d1s;
512 _output_buffer[4] = d2s;
513}
514
515#ifdef __SSE__
516void
517OutputBase::_multiply_partition_simd(const float* signal, const float* filter)
518{
519 // 16 byte alignment is needed for _mm_load_ps()!
520 // This should be the case anyway because fftwf_malloc() is used.
521
522 auto dc = _output_buffer[0] + signal[0] * filter[0];
523 auto ny = _output_buffer[4] + signal[4] * filter[4];
524
525 for(size_t i = 0; i < _partition_size; i += 8)
526 {
527 // load real and imaginary parts of signal and filter
528 __m128 sigr = _mm_load_ps(signal + i);
529 __m128 sigi = _mm_load_ps(signal + i + 4);
530 __m128 filtr = _mm_load_ps(filter + i);
531 __m128 filti = _mm_load_ps(filter + i + 4);
532
533 // multiply and subtract
534 __m128 res1 = _mm_sub_ps(_mm_mul_ps(sigr, filtr), _mm_mul_ps(sigi, filti));
535
536 // multiply and add
537 __m128 res2 = _mm_add_ps(_mm_mul_ps(sigr, filti), _mm_mul_ps(sigi, filtr));
538
539 // load output data for accumulation
540 __m128 acc1 = _mm_load_ps(&_output_buffer[i]);
541 __m128 acc2 = _mm_load_ps(&_output_buffer[i + 4]);
542
543 // accumulate
544 acc1 = _mm_add_ps(acc1, res1);
545 acc2 = _mm_add_ps(acc2, res2);
546
547 // store output data
548 _mm_store_ps(&_output_buffer[i], acc1);
549 _mm_store_ps(&_output_buffer[i + 4], acc2);
550 }
551
552 _output_buffer[0] = dc;
553 _output_buffer[4] = ny;
554}
555#endif
556
558void
559OutputBase::_multiply_spectra()
560{
561 // Clear IFFT buffer (must be actually filled with zeros!)
562 std::fill(_output_buffer.begin(), _output_buffer.end(), 0.0f);
563 _output_buffer.zero = true;
564
565 assert(_filter_ptrs.size() == _input.partitions());
566
567 auto input = _input.spectra.begin();
568
569 for (const auto* filter: _filter_ptrs)
570 {
571 assert(filter != nullptr);
572
573 if (input->zero || filter->zero)
574 {
575 // do nothing. There is no contribution if either is zero.
576 }
577 else
578 {
579#ifdef __SSE__
580 _multiply_partition_simd(input->data(), filter->data());
581#else
582 _multiply_partition_cpp(input->data(), filter->data());
583#endif
584 _output_buffer.zero = false;
585 }
586 ++input;
587 }
588}
589
590void
591OutputBase::_unsort_coefficients()
592{
593 fixed_vector<float> buffer(_partition_size);
594
595 size_t base = 8;
596
597 buffer[0] = _output_buffer[0];
598 buffer[1] = _output_buffer[1];
599 buffer[2] = _output_buffer[2];
600 buffer[3] = _output_buffer[3];
601 buffer[_input.block_size()] = _output_buffer[4];
602 buffer[_partition_size-1] = _output_buffer[5];
603 buffer[_partition_size-2] = _output_buffer[6];
604 buffer[_partition_size-3] = _output_buffer[7];
605
606 for (size_t i=0; i < (_partition_size / 8-1); i++)
607 {
608 for (size_t ii = 0; ii < 4; ii++)
609 {
610 buffer[base/2+ii] = _output_buffer[base+ii];
611 }
612
613 for (size_t ii = 0; ii < 4; ii++)
614 {
615 buffer[_partition_size-base/2-ii] = _output_buffer[base+4+ii];
616 }
617
618 base += 8;
619 }
620
621 std::copy(buffer.begin(), buffer.end(), _output_buffer.begin());
622}
623
624void
625OutputBase::_ifft()
626{
627 _unsort_coefficients();
628 fftw<float>::execute(_ifft_plan);
629}
630
634class Output : public OutputBase
635{
636 public:
637 Output(const Input& input)
638 : OutputBase(input)
639 , _queues(apf::make_index_iterator(size_t(1))
640 , apf::make_index_iterator(input.partitions()))
641 {}
642
643 void set_filter(const Filter& filter);
644
645 bool queues_empty() const;
646 void rotate_queues();
647
648 private:
650};
651
658void
660{
661 auto partition = filter.begin();
662
663 // First partition has no queue and is updated immediately
664 if (partition != filter.end())
665 {
666 _filter_ptrs.front() = &*partition++;
667 }
668
669 for (size_t i = 0; i < _queues.size(); ++i)
670 {
671 _queues[i][i]
672 = (partition == filter.end()) ? &_empty_partition : &*partition++;
673 }
674}
675
682bool
684{
685 if (_queues.empty()) return true;
686
687 // It may not be obvious, but that's what the following code does:
688 // If some non-null pointer is found in the last queue, return false
689
690 auto first = _queues.rbegin()->begin();
691 auto last = _queues.rbegin()->end();
692 return std::find_if(first, last, math::identity<const fft_node*>()) == last;
693}
694
699void
701{
702 auto target = _filter_ptrs.begin();
703 // Skip first element, it doesn't have a queue
704 ++target;
705
706 for (auto& queue: _queues)
707 {
708 // If first element is valid, use it
709 if (queue.front()) *target = queue.front();
710
711 std::copy(queue.begin() + 1, queue.end(), queue.begin());
712 *queue.rbegin() = nullptr;
713 ++target;
714 }
715}
716
722{
723 public:
725 template<typename In>
726 StaticOutput(const Input& input, In first, In last)
727 : OutputBase(input)
728 {
729 _filter.reset(new Filter(input.block_size(), first, last
730 , input.partitions()));
731
732 _set_filter(*_filter);
733 }
734
738 StaticOutput(const Input& input, const Filter& filter)
739 : OutputBase(input)
740 {
741 _set_filter(filter);
742 }
743
744 private:
745 void _set_filter(const Filter& filter)
746 {
747 auto from = filter.begin();
748
749 for (auto& to: _filter_ptrs)
750 {
751 // If less partitions are given, the rest is set to zero
752 to = (from == filter.end()) ? &_empty_partition : &*from++;
753 }
754 // If further partitions are available, they are ignored
755 }
756
757 // This is only used for the first constructor!
758 std::unique_ptr<Filter> _filter;
759};
760
763{
764 Convolver(size_t block_size_, size_t partitions_)
765 : Input(block_size_, partitions_)
766 // static_cast to resolve ambiguity
767 , Output(*static_cast<Input*>(this))
768 {}
769};
770
773{
774 template<typename In>
775 StaticConvolver(size_t block_size_, In first, In last, size_t partitions_ = 0)
776 : Input(block_size_, partitions_ ? partitions_
777 : min_partitions(block_size_, size_t(std::distance(first, last))))
778 , StaticOutput(*this, first, last)
779 {
780 assert(std::distance(first, last) > 0);
781 }
782
783 StaticConvolver(const Filter& filter, size_t partitions_ = 0)
784 : Input(filter.block_size()
785 , partitions_ ? partitions_ : filter.partitions())
786 , StaticOutput(*this, filter)
787 {}
788};
789
791template<typename BinaryFunction>
792void transform_nested(const Filter& in1, const Filter& in2, Filter& out
793 , BinaryFunction f)
794{
795 auto it1 = in1.begin();
796 auto it2 = in2.begin();
797
798 for (auto& result: out)
799 {
800 if (it1 == in1.end() || it1->zero)
801 {
802 if (it2 == in2.end() || it2->zero)
803 {
804 result.zero = true;
805 }
806 else
807 {
808 assert(it2->size() == result.size());
809 std::transform(it2->begin(), it2->end(), result.begin()
810 , std::bind(f, 0, std::placeholders::_1));
811 result.zero = false;
812 }
813 }
814 else
815 {
816 if (it2 == in2.end() || it2->zero)
817 {
818 assert(it1->size() == result.size());
819 std::transform(it1->begin(), it1->end(), result.begin()
820 , std::bind(f, std::placeholders::_1, 0));
821 result.zero = false;
822 }
823 else
824 {
825 assert(it1->size() == it2->size());
826 assert(it1->size() == result.size());
827 std::transform(it1->begin(), it1->end(), it2->begin(), result.begin()
828 , f);
829 result.zero = false;
830 }
831 }
832 if (it1 != in1.end()) ++it1;
833 if (it2 != in2.end()) ++it2;
834 }
835}
836
837} // namespace conv
838
839} // namespace apf
840
841#endif
Base class for Output and StaticOutput.
Definition: convolver.h:391
float * convolve(float weight=1.0f)
Fast convolution of one audio block.
Definition: convolver.h:448
Convolution engine (output part).
Definition: convolver.h:635
void rotate_queues()
Update filter queues.
Definition: convolver.h:700
bool queues_empty() const
Check if there are still valid partitions in the queues.
Definition: convolver.h:683
void set_filter(const Filter &filter)
Set a new filter.
Definition: convolver.h:659
Convolver output stage with static filter.
Definition: convolver.h:722
StaticOutput(const Input &input, In first, In last)
Constructor from time domain samples.
Definition: convolver.h:726
StaticOutput(const Input &input, const Filter &filter)
Constructor from existing frequency domain filter coefficients.
Definition: convolver.h:738
Forward-FFT-related functions.
Definition: convolver.h:127
void prepare_filter(In first, In last, Filter &filter) const
Transform time-domain samples.
Definition: convolver.h:198
plan_ptr _create_plan(float *array) const
Create in-place FFT plan for halfcomplex data format.
Definition: convolver.h:183
void _fft(float *first) const
In-place FFT.
Definition: convolver.h:150
In prepare_partition(In first, In last, fft_node &partition) const
FFT of one block.
Definition: convolver.h:217
Derived from std::list, but without re-sizing.
Definition: container.h:240
Derived from std::vector, but without memory re-allocations.
Definition: container.h:83
Some containers.
Some tools for the use with the FFTW library.
index_iterator< T > make_index_iterator(T start)
Helper function to create an index_iterator.
Definition: iterator.h:1034
Several more or less useful iterators and some macros.
Mathematical constants and helper functions.
void transform_nested(const Filter &in1, const Filter &in2, Filter &out, BinaryFunction f)
Apply std::transform to a container of fft_nodes.
Definition: convolver.h:792
static size_t min_partitions(size_t block_size, size_t filter_size)
Calculate necessary number of partitions for a given filter length.
Definition: convolver.h:63
bool has_only_zeros(I first, I last)
Check if there are only zeros in a range.
Definition: math.h:238
Audio Processing Framework.
Definition: iterator.h:61
Combination of Input and Output.
Definition: convolver.h:763
Container holding a number of FFT blocks.
Definition: convolver.h:107
Filter(size_t block_size_, size_t partitions_)
Constructor; create empty filter.
Definition: convolver.h:109
Input stage of convolution.
Definition: convolver.h:307
void add_block(In first)
Add a block of time-domain input samples.
Definition: convolver.h:336
Input(size_t block_size_, size_t partitions_)
Definition: convolver.h:310
fixed_list< fft_node > spectra
Spectra of the partitions (double-blocks) of the input signal to be convolved.
Definition: convolver.h:327
Combination of Input and StaticOutput.
Definition: convolver.h:773
Helper class to prepare filters.
Definition: convolver.h:281
Two blocks of time-domain or FFT (half-complex) data.
Definition: convolver.h:72
bool zero
To avoid unnecessary FFTs and filling buffers with zeros.
Definition: convolver.h:102
Traits class to select float/double/long double versions of FFTW functions.
Definition: fftwtools.h:44
Identity function object. Function call returns a const reference to input.
Definition: math.h:333