diff --git a/ft8/fft.cpp b/ft8/fft.cpp index a93f50e2e..d24ae00ca 100644 --- a/ft8/fft.cpp +++ b/ft8/fft.cpp @@ -20,39 +20,20 @@ /////////////////////////////////////////////////////////////////////////////////// #include "fft.h" -#include -// #include #include -// #include -// #include -// #include #include "util.h" #define TIMING 0 namespace FT8 { -// MEASURE=0, ESTIMATE=64, PATIENT=32 -int fftw_type = FFTW_ESTIMATE; - -static std::mutex plansmu; -static std::mutex plansmu2; -static Plan *plans[1000]; -static int nplans; -// static int plan_master_pid = 0; - -Plan *get_plan(int n, const char *why) +FFTEngine::Plan *FFTEngine::get_plan(int n, const char *why) { // cache fftw plans in the parent process, // so they will already be there for fork()ed children. plansmu.lock(); - // if (plan_master_pid == 0) - // { - // plan_master_pid = getpid(); - // } - for (int i = 0; i < nplans; i++) { if (plans[i]->n_ == n && plans[i]->type_ == fftw_type @@ -73,16 +54,6 @@ Plan *get_plan(int n, const char *why) #endif // fftw_make_planner_thread_safe(); - - // the fftw planner is not thread-safe. - // can't rely on plansmu because both ft8.so - // and snd.so may be using separate copies of fft.cc. - // the lock file really should be per process. - // int lockfd = creat("/tmp/fft-plan-lock", 0666); - // assert(lockfd >= 0); - // fchmod(lockfd, 0666); - // int lockret = flock(lockfd, LOCK_EX); - // assert(lockret == 0); plansmu2.lock(); fftwf_set_timelimit(5); @@ -109,10 +80,6 @@ Plan *get_plan(int n, const char *why) // FFTW_PATIENT // FFTW_EXHAUSTIVE int type = fftw_type; - // if (getpid() != plan_master_pid) - // { - // type = FFTW_ESTIMATE; - // } p->type_ = type; p->fwd_ = fftwf_plan_dft_r2c_1d(n, p->r_, p->c_, type); assert(p->fwd_); @@ -131,14 +98,11 @@ Plan *get_plan(int n, const char *why) p->crev_ = fftwf_plan_dft_1d(n, p->cc2_, p->cc1_, FFTW_BACKWARD, type); assert(p->crev_); - // flock(lockfd, LOCK_UN); - // close(lockfd); plansmu2.unlock(); assert(nplans + 1 < 1000); plans[nplans] = p; - // __sync_synchronize(); nplans += 1; #if TIMING @@ -160,12 +124,12 @@ Plan *get_plan(int n, const char *why) // real inputs, complex outputs. // output has (block / 2) + 1 points. // -std::vector> one_fft( +std::vector> FFTEngine::one_fft( const std::vector &samples, int i0, int block, const char *why, - Plan *p + FFTEngine::Plan *p ) { assert(i0 >= 0); @@ -242,7 +206,7 @@ std::vector> one_fft( // do a full set of FFTs, one per symbol-time. // bins[time][frequency] // -ffts_t ffts(const std::vector &samples, int i0, int block, const char *why) +FFTEngine::ffts_t FFTEngine::ffts(const std::vector &samples, int i0, int block, const char *why) { assert(i0 >= 0); assert(block > 1 && (block % 2) == 0); @@ -313,7 +277,7 @@ ffts_t ffts(const std::vector &samples, int i0, int block, const char *wh // real inputs, complex outputs. // output has block points. // -std::vector> one_fft_c( +std::vector> FFTEngine::one_fft_c( const std::vector &samples, int i0, int block, @@ -373,7 +337,7 @@ std::vector> one_fft_c( return out; } -std::vector> one_fft_cc( +std::vector> FFTEngine::one_fft_cc( const std::vector> &samples, int i0, int block, @@ -434,7 +398,7 @@ std::vector> one_fft_cc( return out; } -std::vector> one_ifft_cc( +std::vector> FFTEngine::one_ifft_cc( const std::vector> &bins, const char *why ) @@ -483,7 +447,7 @@ std::vector> one_ifft_cc( return out; } -std::vector one_ifft(const std::vector> &bins, const char *why) +std::vector FFTEngine::one_ifft(const std::vector> &bins, const char *why) { int nbins = bins.size(); int block = (nbins - 1) * 2; @@ -531,7 +495,7 @@ std::vector one_ifft(const std::vector> &bins, const // // the return value is x + iy, where y is the hilbert transform of x. // -std::vector> analytic(const std::vector &x, const char *why) +std::vector> FFTEngine::analytic(const std::vector &x, const char *why) { ulong n = x.size(); @@ -573,7 +537,7 @@ std::vector> analytic(const std::vector &x, const cha // // like weakutil.py's freq_shift(). // -std::vector hilbert_shift(const std::vector &x, float hz0, float hz1, int rate) +std::vector FFTEngine::hilbert_shift(const std::vector &x, float hz0, float hz1, int rate) { // y = scipy.signal.hilbert(x) std::vector> y = analytic(x, "hilbert_shift"); @@ -595,7 +559,7 @@ std::vector hilbert_shift(const std::vector &x, float hz0, float h return ret; } -void fft_stats() +void FFTEngine::fft_stats() { for (int i = 0; i < nplans; i++) { diff --git a/ft8/fft.h b/ft8/fft.h index 907d9f504..473be626f 100644 --- a/ft8/fft.h +++ b/ft8/fft.h @@ -22,56 +22,73 @@ #ifndef FFT_H #define FFT_H +#include #include #include #include namespace FT8 { -// a cached fftw plan, for both of: -// fftwf_plan_dft_r2c_1d(n, m_in, m_out, FFTW_ESTIMATE); -// fftwf_plan_dft_c2r_1d(n, m_in, m_out, FFTW_ESTIMATE); -class Plan +class FFTEngine { public: - int n_; - int type_; + // a cached fftw plan, for both of: + // fftwf_plan_dft_r2c_1d(n, m_in, m_out, FFTW_ESTIMATE); + // fftwf_plan_dft_c2r_1d(n, m_in, m_out, FFTW_ESTIMATE); + class Plan + { + public: + int n_; + int type_; - // - // real -> complex - // - fftwf_complex *c_; // (n_ / 2) + 1 of these - float *r_; // n_ of these - fftwf_plan fwd_; // forward plan - fftwf_plan rev_; // reverse plan + // + // real -> complex + // + fftwf_complex *c_; // (n_ / 2) + 1 of these + float *r_; // n_ of these + fftwf_plan fwd_; // forward plan + fftwf_plan rev_; // reverse plan - // - // complex -> complex - // - fftwf_complex *cc1_; // n - fftwf_complex *cc2_; // n - fftwf_plan cfwd_; // forward plan - fftwf_plan crev_; // reverse plan + // + // complex -> complex + // + fftwf_complex *cc1_; // n + fftwf_complex *cc2_; // n + fftwf_plan cfwd_; // forward plan + fftwf_plan crev_; // reverse plan - // how much CPU time spent in FFTs that use this plan. -#if TIMING - double time_; -#endif - const char *why_; - int uses_; -}; + // how much CPU time spent in FFTs that use this plan. + #if TIMING + double time_; + #endif + const char *why_; + int uses_; + }; // Plan -Plan *get_plan(int n, const char *why); + FFTEngine() : nplans(0) + {} -std::vector> one_fft(const std::vector &samples, int i0, int block, const char *why, Plan *p); -std::vector one_ifft(const std::vector> &bins, const char *why); -typedef std::vector>> ffts_t; -ffts_t ffts(const std::vector &samples, int i0, int block, const char *why); -std::vector> one_fft_c(const std::vector &samples, int i0, int block, const char *why); -std::vector> one_fft_cc(const std::vector> &samples, int i0, int block, const char *why); -std::vector> one_ifft_cc(const std::vector> &bins, const char *why); -std::vector> analytic(const std::vector &x, const char *why); -std::vector hilbert_shift(const std::vector &x, float hz0, float hz1, int rate); + Plan *get_plan(int n, const char *why); + + std::vector> one_fft(const std::vector &samples, int i0, int block, const char *why, Plan *p); + std::vector one_ifft(const std::vector> &bins, const char *why); + typedef std::vector>> ffts_t; + ffts_t ffts(const std::vector &samples, int i0, int block, const char *why); + std::vector> one_fft_c(const std::vector &samples, int i0, int block, const char *why); + std::vector> one_fft_cc(const std::vector> &samples, int i0, int block, const char *why); + std::vector> one_ifft_cc(const std::vector> &bins, const char *why); + std::vector> analytic(const std::vector &x, const char *why); + std::vector hilbert_shift(const std::vector &x, float hz0, float hz1, int rate); + +private: + void fft_stats(); + std::mutex plansmu; + std::mutex plansmu2; + Plan *plans[1000]; + int nplans; + // MEASURE=0, ESTIMATE=64, PATIENT=32 + static const int fftw_type = FFTW_ESTIMATE; +}; // FFTEngine } // namespace FT8 diff --git a/ft8/ft8.cpp b/ft8/ft8.cpp index f3d2a9ee1..4e7f1709f 100644 --- a/ft8/ft8.cpp +++ b/ft8/ft8.cpp @@ -326,7 +326,8 @@ FT8::FT8( double deadline, double final_deadline, CallbackInterface *cb, - std::vector prevdecs + std::vector prevdecs, + FFTEngine *fftEngine ) { samples_ = samples; @@ -349,11 +350,12 @@ FT8::FT8( } hack_size_ = -1; - hack_data_ = 0; + hack_data_ = nullptr; hack_off_ = -1; hack_len_ = -1; - plan32_ = 0; + plan32_ = nullptr; + fftEngine_ = fftEngine; } FT8::~FT8() @@ -362,7 +364,7 @@ FT8::~FT8() // strength of costas block of signal with tone 0 at bi0, // and symbol zero at si0. -float FT8::one_coarse_strength(const ffts_t &bins, int bi0, int si0) +float FT8::one_coarse_strength(const FFTEngine::ffts_t &bins, int bi0, int si0) { int costas[] = {3, 1, 4, 0, 6, 5, 2}; @@ -491,7 +493,7 @@ int FT8::blocksize(int rate) // look for potential signals by searching FFT bins for Costas symbol // blocks. returns a vector of candidate positions. // -std::vector FT8::coarse(const ffts_t &bins, int si0, int si1) +std::vector FT8::coarse(const FFTEngine::ffts_t &bins, int si0, int si1) { int block = blocksize(rate_); int nbins = bins[0].size(); @@ -579,8 +581,8 @@ std::vector FT8::reduce_rate( } int alen = a.size(); - std::vector> bins1 = one_fft(a, 0, alen, - "reduce_rate1", 0); + std::vector> bins1 = fftEngine_->one_fft( + a, 0, alen, "reduce_rate1", 0); int nbins1 = bins1.size(); float bin_hz = arate / (float)alen; @@ -630,7 +632,7 @@ std::vector FT8::reduce_rate( } // use ifft to reduce the rate. - std::vector vvv = one_ifft(bbins, "reduce_rate2"); + std::vector vvv = fftEngine_->one_ifft(bbins, "reduce_rate2"); delta_hz = delta * bin_hz; @@ -640,7 +642,7 @@ std::vector FT8::reduce_rate( void FT8::go(int npasses) { // cache to avoid cost of fftw planner mutex. - plan32_ = get_plan(32, "cache32"); + plan32_ = fftEngine_->get_plan(32, "cache32"); if (0) { @@ -831,8 +833,8 @@ void FT8::go(int npasses) // just do this once, re-use for every fractional fft_shift // and down_v7_f() to 200 sps. - std::vector> bins = one_fft(samples_, 0, samples_.size(), - "go1", 0); + std::vector> bins = fftEngine_->one_fft( + samples_, 0, samples_.size(), "go1", 0); for (int hz_frac_i = 0; hz_frac_i < params.coarse_hz_n; hz_frac_i++) { @@ -851,7 +853,7 @@ void FT8::go(int npasses) for (int off_frac_i = 0; off_frac_i < params.coarse_off_n; off_frac_i++) { int off_frac = off_frac_i * (block / params.coarse_off_n); - ffts_t bins = ffts(samples1, off_frac, block, "go2"); + FFTEngine::ffts_t bins = fftEngine_->ffts(samples1, off_frac, block, "go2"); std::vector oo = coarse(bins, si0, si1); for (int i = 0; i < (int)oo.size(); i++) { @@ -927,7 +929,7 @@ float FT8::one_strength(const std::vector &samples200, float hz, int off) int start = starts[which]; for (int si = 0; si < 7; si++) { - auto fft = one_fft(samples200, off + (si + start) * 32, 32, "one_strength", plan32_); + auto fft = fftEngine_->one_fft(samples200, off + (si + start) * 32, 32, "one_strength", plan32_); for (int bi = 0; bi < 8; bi++) { float x = std::abs(fft[bin0 + bi]); @@ -1004,7 +1006,8 @@ float FT8::one_strength_known( for (int si = 0; si < 79; si += params.known_sparse) { - auto fft = one_fft(samples, off + si * block, block, "one_strength_known", 0); + auto fft = fftEngine_->one_fft(samples, off + si * block, block, "one_strength_known", 0); + if (params.known_strength_how == 7) { std::complex c = fft[bin0 + syms[si]]; @@ -1226,7 +1229,7 @@ void FT8::search_both_known( int best_off = 0; float best_strength = 0; - std::vector> bins = one_fft(samples, 0, samples.size(), "stfk", 0); + std::vector> bins = fftEngine_->one_fft(samples, 0, samples.size(), "stfk", 0); float hz_start, hz_inc, hz_end; if (params.third_hz_n > 1) @@ -1290,7 +1293,7 @@ std::vector FT8::fft_shift( } else { - bins = one_fft(samples, off, len, "fft_shift", 0); + bins = fftEngine_->one_fft(samples, off, len, "fft_shift", 0); hack_bins_ = bins; hack_size_ = samples.size(); hack_off_ = off; @@ -1331,7 +1334,7 @@ std::vector FT8::fft_shift_f( bins1[i] = 0; } } - std::vector out = one_ifft(bins1, "fft_shift"); + std::vector out = fftEngine_->one_ifft(bins1, "fft_shift"); return out; } @@ -1356,12 +1359,12 @@ std::vector FT8::shift200( } // returns a mini-FFT of 79 8-tone symbols. -ffts_t FT8::extract(const std::vector &samples200, float, int off) +FFTEngine::ffts_t FT8::extract(const std::vector &samples200, float, int off) { - ffts_t bins3 = ffts(samples200, off, 32, "extract"); + FFTEngine::ffts_t bins3 = fftEngine_->ffts(samples200, off, 32, "extract"); + FFTEngine::ffts_t m79(79); - ffts_t m79(79); for (int si = 0; si < 79; si++) { m79[si].resize(8); @@ -1388,9 +1391,9 @@ ffts_t FT8::extract(const std::vector &samples200, float, int off) // // m79 is a 79x8 array of complex. // -ffts_t FT8::un_gray_code_c(const ffts_t &m79) +FFTEngine::ffts_t FT8::un_gray_code_c(const FFTEngine::ffts_t &m79) { - ffts_t m79a(79); + FFTEngine::ffts_t m79a(79); int map[] = {0, 1, 3, 2, 6, 4, 5, 7}; for (int si = 0; si < 79; si++) @@ -1687,7 +1690,7 @@ void FT8::make_stats( // number of cycles and thus preserves phase from one symbol to the // next. // -std::vector> FT8::soft_c2m(const ffts_t &c79) +std::vector> FT8::soft_c2m(const FFTEngine::ffts_t &c79) { std::vector> m79(79); std::vector raw_phases(79); // of strongest tone in each symbol time @@ -1859,7 +1862,7 @@ float FT8::bayes( // // c79 is 79x8 complex tones, before un-gray-coding. // -void FT8::soft_decode(const ffts_t &c79, float ll174[]) +void FT8::soft_decode(const FFTEngine::ffts_t &c79, float ll174[]) { std::vector> m79(79); @@ -1974,9 +1977,9 @@ void FT8::soft_decode(const ffts_t &c79, float ll174[]) // // c79 is 79x8 complex tones, before un-gray-coding. // -void FT8::c_soft_decode(const ffts_t &c79x, float ll174[]) +void FT8::c_soft_decode(const FFTEngine::ffts_t &c79x, float ll174[]) { - ffts_t c79 = c_convert_to_snr(c79x); + FFTEngine::ffts_t c79 = c_convert_to_snr(c79x); int costas[] = {3, 1, 4, 0, 6, 5, 2}; std::complex maxes[79]; @@ -2185,11 +2188,11 @@ std::vector FT8::extract_bits(const std::vector &syms, const std::ve // that they have the same phase, by summing the complex // correlations for each possible pair and using the max. void FT8::soft_decode_pairs( - const ffts_t &m79x, + const FFTEngine::ffts_t &m79x, float ll174[] ) { - ffts_t m79 = c_convert_to_snr(m79x); + FFTEngine::ffts_t m79 = c_convert_to_snr(m79x); struct BitInfo { @@ -2319,11 +2322,11 @@ void FT8::soft_decode_pairs( } void FT8::soft_decode_triples( - const ffts_t &m79x, + const FFTEngine::ffts_t &m79x, float ll174[] ) { - ffts_t m79 = c_convert_to_snr(m79x); + FFTEngine::ffts_t m79 = c_convert_to_snr(m79x); struct BitInfo { @@ -2610,7 +2613,7 @@ std::vector> FT8::fbandpass( std::vector FT8::down_v7(const std::vector &samples, float hz) { int len = samples.size(); - std::vector> bins = one_fft(samples, 0, len, "down_v7a", 0); + std::vector> bins = fftEngine_->one_fft(samples, 0, len, "down_v7a", 0); return down_v7_f(bins, len, hz); } @@ -2655,7 +2658,7 @@ std::vector FT8::down_v7_f(const std::vector> &bins, std::vector> bbins(blen / 2 + 1); for (int i = 0; i < (int)bbins.size(); i++) bbins[i] = bins1[i]; - std::vector out = one_ifft(bbins, "down_v7b"); + std::vector out = fftEngine_->one_ifft(bbins, "down_v7b"); return out; } @@ -2731,7 +2734,7 @@ int FT8::one_iter(const std::vector &samples200, int best_off, float hz_f // estimate SNR, yielding numbers vaguely similar to WSJT-X. // m79 is a 79x8 complex FFT output. // -float FT8::guess_snr(const ffts_t &m79) +float FT8::guess_snr(const FFTEngine::ffts_t &m79) { int costas[] = {3, 1, 4, 0, 6, 5, 2}; float noises = 0; @@ -2798,7 +2801,7 @@ float FT8::guess_snr(const ffts_t &m79) // adj_off is the amount to change the offset, in samples. // should be subtracted from offset. // -void FT8::fine(const ffts_t &m79, int, float &adj_hz, float &adj_off) +void FT8::fine(const FFTEngine::ffts_t &m79, int, float &adj_hz, float &adj_off) { adj_hz = 0.0; adj_off = 0.0; @@ -2991,7 +2994,7 @@ int FT8::one_iter1( best_hz); // mini 79x8 FFT. - ffts_t m79 = extract(samples200, 25, best_off); + FFTEngine::ffts_t m79 = extract(samples200, 25, best_off); // look at symbol-to-symbol phase change to try // to improve best_hz and best_off. @@ -3157,9 +3160,9 @@ void FT8::subtract( // move nsamples so that signal is centered in bin0. float diff0 = (bin0 * bin_hz) - hz0; float diff1 = (bin0 * bin_hz) - hz1; - std::vector moved = hilbert_shift(nsamples_, diff0, diff1, rate_); + std::vector moved = fftEngine_->hilbert_shift(nsamples_, diff0, diff1, rate_); - ffts_t bins = ffts(moved, off0, block, "subtract"); + FFTEngine::ffts_t bins = fftEngine_->ffts(moved, off0, block, "subtract"); if (bin0 + 8 > (int)bins[0].size()) return; @@ -3291,7 +3294,7 @@ void FT8::subtract( } } - nsamples_ = hilbert_shift(moved, -diff0, -diff1, rate_); + nsamples_ = fftEngine_->hilbert_shift(moved, -diff0, -diff1, rate_); } // @@ -3311,7 +3314,7 @@ int FT8::try_decode( float, int use_osd, const char *comment1, - const ffts_t &m79 + const FFTEngine::ffts_t &m79 ) { int a174[174]; @@ -3467,6 +3470,7 @@ void FT8Decoder::entry( double t0 = now(); double deadline = t0 + time_left; double final_deadline = t0 + total_time_left; + FFTEngine fftEngine; // decodes from previous runs, for subtraction. std::vector prevdecs; @@ -3520,7 +3524,8 @@ void FT8Decoder::entry( deadline, final_deadline, cb, - prevdecs + prevdecs, + &fftEngine ); ft8->getParams() = getParams(); // transfer parameters diff --git a/ft8/ft8.h b/ft8/ft8.h index 83fba63d1..de8c2bb48 100644 --- a/ft8/ft8.h +++ b/ft8/ft8.h @@ -292,7 +292,7 @@ public: std::vector> hack_bins_; std::vector prevdecs_; - Plan *plan32_; + FFTEngine::Plan *plan32_; FT8( const std::vector &samples, @@ -305,12 +305,13 @@ public: double deadline, double final_deadline, CallbackInterface *cb, - std::vector prevdecs + std::vector prevdecs, + FFTEngine *fftEngine ); ~FT8(); // strength of costas block of signal with tone 0 at bi0, // and symbol zero at si0. - float one_coarse_strength(const ffts_t &bins, int bi0, int si0); + float one_coarse_strength(const FFTEngine::ffts_t &bins, int bi0, int si0); // return symbol length in samples at the given rate. // insist on integer symbol lengths so that we can // use whole FFT bins. @@ -319,7 +320,7 @@ public: // look for potential signals by searching FFT bins for Costas symbol // blocks. returns a vector of candidate positions. // - std::vector coarse(const ffts_t &bins, int si0, int si1); + std::vector coarse(const FFTEngine::ffts_t &bins, int si0, int si1); // // reduce the sample rate from arate to brate. // center hz0..hz1 in the new nyquist range. @@ -425,11 +426,11 @@ public: float hz ); // returns a mini-FFT of 79 8-tone symbols. - ffts_t extract(const std::vector &samples200, float, int off); + FFTEngine::ffts_t extract(const std::vector &samples200, float, int off); // // m79 is a 79x8 array of complex. // - ffts_t un_gray_code_c(const ffts_t &m79); + FFTEngine::ffts_t un_gray_code_c(const FFTEngine::ffts_t &m79); // // m79 is a 79x8 array of float. // @@ -468,7 +469,7 @@ public: // number of cycles and thus preserves phase from one symbol to the // next. // - std::vector> soft_c2m(const ffts_t &c79); + std::vector> soft_c2m(const FFTEngine::ffts_t &c79); // // guess the probability that a bit is zero vs one, // based on strengths of strongest tones that would @@ -486,11 +487,11 @@ public: // // c79 is 79x8 complex tones, before un-gray-coding. // - void soft_decode(const ffts_t &c79, float ll174[]); + void soft_decode(const FFTEngine::ffts_t &c79, float ll174[]); // // c79 is 79x8 complex tones, before un-gray-coding. // - void c_soft_decode(const ffts_t &c79x, float ll174[]); + void c_soft_decode(const FFTEngine::ffts_t &c79x, float ll174[]); // // turn 79 symbol numbers into 174 bits. // strip out the three Costas sync blocks, @@ -506,11 +507,11 @@ public: // that they have the same phase, by summing the complex // correlations for each possible pair and using the max. void soft_decode_pairs( - const ffts_t &m79x, + const FFTEngine::ffts_t &m79x, float ll174[] ); void soft_decode_triples( - const ffts_t &m79x, + const FFTEngine::ffts_t &m79x, float ll174[] ); // @@ -563,7 +564,7 @@ public: // estimate SNR, yielding numbers vaguely similar to WSJT-X. // m79 is a 79x8 complex FFT output. // - float guess_snr(const ffts_t &m79); + float guess_snr(const FFTEngine::ffts_t &m79); // // compare phases of successive symbols to guess whether // the starting offset is a little too high or low. @@ -584,7 +585,7 @@ public: // adj_off is the amount to change the offset, in samples. // should be subtracted from offset. // - void fine(const ffts_t &m79, int, float &adj_hz, float &adj_off); + void fine(const FFTEngine::ffts_t &m79, int, float &adj_hz, float &adj_off); // // subtract a corrected decoded signal from nsamples_, // perhaps revealing a weaker signal underneath, @@ -615,7 +616,7 @@ public: float, int use_osd, const char *comment1, - const ffts_t &m79 + const FFTEngine::ffts_t &m79 ); // // given 174 bits corrected by LDPC, work @@ -644,6 +645,7 @@ public: FT8Params& getParams() { return params; } private: FT8Params params; + FFTEngine *fftEngine_; static const double apriori174[]; }; // class FT8