SDDC_Driver
Loading...
Searching...
No Matches
fft_mt_r2iq_impl.h
1/*
2 * This file is part of SDDC_Driver.
3 *
4 * =====================
5 * MIT License
6 * =====================
7 *
8 * Copyright (C) 2021 - Howard Su
9 * Copyright (C) 2025 - RenardSpark
10 *
11 * Permission is hereby granted, free of charge, to any person obtaining a copy
12 * of this software and associated documentation files (the "Software"), to deal
13 * in the Software without restriction, including without limitation the rights
14 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15 * copies of the Software, and to permit persons to whom the Software is
16 * furnished to do so, subject to the following conditions:
17
18 * The above copyright notice and this permission notice shall be included in all
19 * copies or substantial portions of the Software.
20
21 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27 * SOFTWARE.
28 */
29
30#define TAG "fft_mt_r2iq_def"
31
32{
33 TracePrintln(TAG, "%p", th);
34 DebugPrintln(TAG, "Initialization...");
35
36 const int deci_ratio = decimation_ratio[decimation];
37
38 const int deci_fft_scrap_size = (BASE_FFT_SCRAP_SIZE / 2) / deci_ratio;
39 const int fft_output_size = this->fft_size_per_decimation[decimation];
40 const int fft_output_half_size = fft_output_size / 2;
41 const int fft_useful_size = fft_output_size - deci_fft_scrap_size;
42
43 DebugPrintln(TAG, "Decimation : %d (index %d)", deci_ratio, decimation);
44 DebugPrintln(TAG, "Scrap size : %d", deci_fft_scrap_size);
45 DebugPrintln(TAG, "FFT output size : %d", fft_output_size);
46 DebugPrintln(TAG, "FFT useful output size : %d", fft_useful_size);
47 DebugPrintln(TAG, "Initialization done");
48
49 const fftwf_complex* filter = filterHw[decimation];
50 const auto filter2 = &filter[BASE_FFT_HALF_SIZE - fft_output_half_size];
51
52 plan_freq2time = &plan_freq2time_per_decimation[decimation];
53 int decimate_count = 0;
54
55 std::vector<float> iq_output(inputbuffer_block_size);
56 size_t output_buffer_offset = 0;
57
58 // Pointer to the current input block
59 std::vector<int16_t> input_current_block;
60 // Pointer to the end of the previous input block minus BASE_FFT_SCRAP_SIZE
61 // (input_previous_block + inputbuffer_block_size - BASE_FFT_SCRAP_SIZE)
62 std::vector<int16_t> last_block_end(BASE_FFT_SCRAP_SIZE);
63
64 while(r2iqOn)
65 {
66 {
67 std::unique_lock<std::mutex> lk(mutexR2iqControl);
68 input_current_block = inputbuffer->pop();
69
70 if (!r2iqOn)
71 return 0;
72 }
73
74 // @todo: move the following int16_t conversion to (32-bit) float
75 // directly inside the following loop (for "k < ffts_per_blocks")
76 // just before the forward fft "fftwf_execute_dft_r2c" is called
77 // idea: this should improve cache/memory locality
78#if PRINT_INPUT_RANGE
79 std::pair<int16_t, int16_t> blockMinMax = std::make_pair<int16_t, int16_t>(0, 0);
80#endif
81 if (!this->getRand()) // plain samples no ADC rand set
82 {
83 convert_float<false>(
84 /*source=*/last_block_end.data(),
85 /*dest=*/th->ADCinTime,
86 /*len=*/BASE_FFT_SCRAP_SIZE
87 );
88#if PRINT_INPUT_RANGE
89 auto minmax = std::minmax_element(input_current_block, input_current_block + inputbuffer_block_size);
90 blockMinMax.first = *minmax.first;
91 blockMinMax.second = *minmax.second;
92#endif
93 convert_float<false>(
94 /*source=*/input_current_block.data(),
95 /*dest=*/th->ADCinTime + BASE_FFT_SCRAP_SIZE,
96 /*len=*/inputbuffer_block_size
97 );
98 }
99 else
100 {
101 convert_float<true>(
102 /*source=*/last_block_end.data(),
103 /*dest=*/th->ADCinTime,
104 /*len=*/BASE_FFT_SCRAP_SIZE
105 );
106 convert_float<true>(
107 /*source=*/input_current_block.data(),
108 /*dest=*/th->ADCinTime + BASE_FFT_SCRAP_SIZE,
109 /*len=*/inputbuffer_block_size
110 );
111 }
112
113 last_block_end = std::vector<int16_t>(
114 input_current_block.data() + inputbuffer_block_size - BASE_FFT_SCRAP_SIZE,
115 input_current_block.data() + inputbuffer_block_size
116 );
117
118#if PRINT_INPUT_RANGE
119 th->MinValue = std::min(blockMinMax.first, th->MinValue);
120 th->MaxValue = std::max(blockMinMax.second, th->MaxValue);
121 ++th->MinMaxBlockCount;
122 if (th->MinMaxBlockCount * processor_count / 3 >= DEFAULT_TRANSFERS_PER_SEC )
123 {
124 float minBits = (th->MinValue < 0) ? (log10f((float)(-th->MinValue)) / log10f(2.0f)) : -1.0f;
125 float maxBits = (th->MaxValue > 0) ? (log10f((float)(th->MaxValue)) / log10f(2.0f)) : -1.0f;
126 printf("r2iq: min = %d (%.1f bits) %.2f%%, max = %d (%.1f bits) %.2f%%\n",
127 (int)th->MinValue, minBits, th->MinValue *-100.0f / 32768.0f,
128 (int)th->MaxValue, maxBits, th->MaxValue * 100.0f / 32768.0f);
129 th->MinValue = 0;
130 th->MaxValue = 0;
131 th->MinMaxBlockCount = 0;
132 }
133#endif
134
135 // decimate in frequency plus tuning
136
137 const int _center_frequency_bin = this->center_frequency_bin;
138
139 // Calculate the parameters for the first half
140 // Includes all frequencies above _center_frequency_bin
141 const auto upper_frequencies_source = &th->ADCinFreq[_center_frequency_bin];
142 const auto upper_frequencies_len = std::min(
143 BASE_FFT_HALF_SIZE - _center_frequency_bin, // Desired value
144 fft_output_half_size // Overflow protection
145 );
146
147 // Calculate the parameters for the second half
148 // Includes all frequencies below _center_frequency_bin
149 const auto lower_frequencies_source = &th->ADCinFreq[_center_frequency_bin - fft_output_half_size];
150 const auto lower_frequencies_start = std::max(
151 fft_output_half_size - _center_frequency_bin,
152 0
153 );
154
155 // Main processing loop based on overlap-save method
156 // It also includes filtering and decimation
157 for (int k = 0; k < ffts_per_blocks; k++)
158 {
159 size_t start_address = k * BASE_FFT_USEFUL_SIZE;
160 size_t scrap_size = BASE_FFT_SCRAP_SIZE;
161 if(start_address + BASE_FFT_SIZE > inputbuffer_block_size + BASE_FFT_SCRAP_SIZE)
162 {
163 size_t new_address = (inputbuffer_block_size + BASE_FFT_SCRAP_SIZE) - BASE_FFT_SIZE;
164 scrap_size += start_address - new_address;
165 start_address = new_address;
166 }
167 scrap_size /= (deci_ratio*2);
168
169 // core of fast convolution including filter and decimation
170 // main part is 'overlap-scrap' (IMHO better name for 'overlap-save'), see
171 // https://en.wikipedia.org/wiki/Overlap%E2%80%93save_method
172 {
173 // FFT first stage: time to frequency, real to complex
174 // Input buffer: th->ADCinTime + k * (BASE_FFT_USEFUL_SIZE)
175 // Transformation size: BASE_FFT_SIZE
176 // Output buffer: th->ADCinFreq[]
177 // Output size: BASE_FFT_HALF_SIZE + 1
178
179 fftwf_execute_dft_r2c(plan_time2freq_r2c, th->ADCinTime + start_address, th->ADCinFreq);
180
181 // circular shift (mixing in full bins) and low/bandpass filtering (complex multiplication)
182 {
183 // circular shift tune fs/2 first half array into th->inFreqTmp[]
184 shift_freq(
185 /*destination=*/th->inFreqTmp,
186 /*source1=*/upper_frequencies_source,
187 /*source2=*/filter,
188 /*start=*/0,
189 /*end=*/upper_frequencies_len
190 );
191
192 // Pad with zeroes if needed
193 if(fft_output_half_size != upper_frequencies_len)
194 memset(th->inFreqTmp[upper_frequencies_len], 0, (fft_output_half_size - upper_frequencies_len) * sizeof(fftwf_complex));
195
196 // circular shift tune fs/2 second half array
197 shift_freq(
198 /*destination=*/&th->inFreqTmp[fft_output_half_size],
199 /*source1=*/lower_frequencies_source,
200 /*source2=*/filter2,
201 /*start=*/lower_frequencies_start,
202 /*end=*/fft_output_half_size
203 );
204
205 if (lower_frequencies_start != 0)
206 memset(th->inFreqTmp[fft_output_half_size], 0, lower_frequencies_start * sizeof(fftwf_complex));
207 }
208 // result now in th->inFreqTmp[]
209 // Size: fft_output_size (depending on the decimation)
210
211 // 'shorter' inverse FFT transform (decimation) -> frequency (back) to COMPLEX time domain
212 // transform size: fft_output_size (depending on the decimation)
213 fftwf_execute_dft(*plan_freq2time, th->inFreqTmp, th->inFreqTmp);
214 // result now in th->inFreqTmp[]
215 }
216
217 // postprocessing
218 // @todo: is it possible to ..
219 // 1)
220 // let inverse FFT produce/save it's result directly
221 // in "this->obuffers[modx] + offset" (pout)
222 // ( obuffers[] would need to have additional space ..;
223 // need to move 'scrap' of 'ovelap-scrap'? )
224 // at least FFTW would allow so,
225 // see http://www.fftw.org/fftw3_doc/New_002darray-Execute-Functions.html
226 // attention: multithreading!
227 // 2)
228 // could mirroring (lower sideband) get calculated together
229 // with fine mixer - modifying the mixer frequency? (fs - fc)/fs
230 // (this would reduce one memory pass)
231
232 // Last processing step, put data in the output buffer after removing the end (scrap)
233 // The part saved must be the beginning of the sample
234
235 // Use IFFT output from its beginning, unless the scrap has been
236 // made bigger to accomodate for the end of the frame
237 size_t begin = scrap_size-deci_fft_scrap_size;
238 size_t len = (fft_output_size - scrap_size);
239
240
241 if (this->getSideband()) // lower sideband
242 {
243 // mirror just by negating the imaginary Q of complex I/Q
244 copy<true>((fftwf_complex*)&iq_output.data()[output_buffer_offset*2], &th->inFreqTmp[begin], len);
245 }
246 else // upper sideband
247 {
248 copy<false>((fftwf_complex*)&iq_output.data()[output_buffer_offset*2], &th->inFreqTmp[begin], len);
249 }
250
251 output_buffer_offset += len;
252 }
253
254 decimate_count = (decimate_count + 1) & (deci_ratio - 1);
255 if (decimate_count == 0) {
256 outputbuffer->push(iq_output);
257 output_buffer_offset = 0;
258 }
259 else
260 {
261 //output_buffer_offset += fft_output_half_size + fft_useful_size * (ffts_per_blocks-1);
262 }
263 }
264 return 0;
265}