@@ -242,17 +242,27 @@ cdef np.ndarray[np.float32_t, ndim=1] gauss_fir(float sample_rate, uint32_t samp
242
242
- (((np.sqrt(2 ) * np.pi) / np.sqrt(np.log(2 )) * bt * k / samples_per_symbol) ** 2 ))
243
243
return h / h.sum()
244
244
245
- cdef void phase_demod(IQ samples, float [::1 ] result, float noise_sqrd, bool qam, long long num_samples):
246
- cdef long long i = 0
247
- cdef float real = 0 , imag = 0 , magnitude = 0
245
+ cdef float clamp(float x) nogil:
246
+ if x < - 1.0 :
247
+ x = - 1.0
248
+ elif x > 1.0 :
249
+ x = 1.0
250
+ return x
251
+
252
+ cdef float [::1 ] costa_demod(IQ samples, float noise_sqrd, int loop_order, float bandwidth = 0.1 , float damping = sqrt(2.0 ) / 2.0 ):
253
+ cdef float alpha = (4 * damping * bandwidth) / (1.0 + 2.0 * damping * bandwidth + bandwidth * bandwidth)
254
+ cdef float beta = (4 * bandwidth * bandwidth) / (1.0 + 2.0 * damping * bandwidth + bandwidth * bandwidth)
255
+
256
+ cdef long long i = 0 , num_samples = len (samples)
257
+ cdef float real = 0 , imag = 0
248
258
249
259
cdef float scale, shift, real_float, imag_float, ref_real, ref_imag
250
260
251
- cdef float phi = 0 , current_arg = 0 , f_curr = 0 , f_prev = 0
261
+ cdef float f1, f2, costa_freq = 0 , costa_error = 0 , costa_phase = 1.5
252
262
253
- cdef float complex current_sample, conj_previous_sample, current_nco
263
+ cdef float complex current_sample, nco_out, nco_times_sample
254
264
255
- cdef float alpha = 0.1
265
+ cdef float [:: 1 ] result = np.empty(num_samples, dtype = np.float32)
256
266
257
267
if str (cython.typeof(samples)) == " char[:, ::1]" :
258
268
scale = 127.5
@@ -272,62 +282,64 @@ cdef void phase_demod(IQ samples, float[::1] result, float noise_sqrd, bool qam,
272
282
else :
273
283
raise ValueError (" Unsupported dtype" )
274
284
285
+ if loop_order > 4 :
286
+ # TODO: Adapt this when PSK demodulation with order > 4 shall be supported
287
+ loop_order = 4
288
+
275
289
for i in range (1 , num_samples):
276
290
real = samples[i, 0 ]
277
291
imag = samples[i, 1 ]
278
-
279
- magnitude = real * real + imag * imag
280
- if magnitude <= noise_sqrd:
292
+
293
+ if real * real + imag * imag <= noise_sqrd:
281
294
result[i] = NOISE_FSK_PSK
282
295
continue
283
296
284
297
real_float = (real + shift) / scale
285
298
imag_float = (imag + shift) / scale
286
299
287
300
current_sample = real_float + imag_unit * imag_float
288
- conj_previous_sample = (samples[i - 1 , 0 ] + shift) / scale - imag_unit * ((samples[i - 1 , 1 ] + shift) / scale )
289
- f_curr = arg(current_sample * conj_previous_sample)
301
+ nco_out = cosf( - costa_phase) + imag_unit * sinf( - costa_phase )
302
+ nco_times_sample = nco_out * current_sample
290
303
291
- if abs (f_curr) < M_PI / 4 : # TODO: For PSK with order > 4 this needs to be adapted
292
- f_prev = f_curr
293
- current_arg += f_curr
294
- else :
295
- current_arg += f_prev
304
+ if loop_order == 2 :
305
+ costa_error = nco_times_sample.imag * nco_times_sample.real
306
+ elif loop_order == 4 :
307
+ f1 = 1.0 if nco_times_sample.real > 0.0 else - 1.0
308
+ f2 = 1.0 if nco_times_sample.imag > 0.0 else - 1.0
309
+ costa_error = f1 * nco_times_sample.imag - f2 * nco_times_sample.real
296
310
297
- # Reference oscillator cos(current_arg) + j * sin(current_arg)
298
- current_nco = cosf(current_arg) + imag_unit * sinf(current_arg)
299
- phi = arg(current_sample * conj(current_nco))
311
+ costa_error = clamp(costa_error)
312
+
313
+ # advance the loop
314
+ costa_freq += beta * costa_error
315
+ costa_phase += costa_freq + alpha * costa_error
316
+
317
+ # wrap the phase
318
+ while costa_phase > (2 * M_PI):
319
+ costa_phase -= 2 * M_PI
320
+ while costa_phase < (- 2 * M_PI):
321
+ costa_phase += 2 * M_PI
322
+
323
+ costa_freq = clamp(costa_freq)
324
+
325
+ if loop_order == 2 :
326
+ result[i] = nco_times_sample.real
327
+ elif loop_order == 4 :
328
+ result[i] = 2 * nco_times_sample.real + nco_times_sample.imag
329
+
330
+ return result
300
331
301
- if qam:
302
- result[i] = phi * magnitude
303
- else :
304
- result[i] = phi
305
332
306
- cpdef np.ndarray[np.float32_t, ndim= 1 ] afp_demod(IQ samples, float noise_mag, str mod_type):
333
+ cpdef np.ndarray[np.float32_t, ndim= 1 ] afp_demod(IQ samples, float noise_mag,
334
+ str mod_type, int mod_order, float costas_loop_bandwidth = 0.1 ):
307
335
if len (samples) <= 2 :
308
336
return np.zeros(len (samples), dtype = np.float32)
309
337
310
338
cdef long long i = 0 , ns = len (samples)
311
- cdef float current_arg = 0
312
- cdef float noise_sqrd = 0
313
- cdef float complex_phase = 0
314
- cdef float prev_phase = 0
315
- cdef float NOISE = 0
316
- cdef float real = 0
317
- cdef float imag = 0
318
-
319
- cdef float [::1 ] result = np.zeros(ns, dtype = np.float32, order = " C" )
320
- cdef float costa_freq = 0
321
- cdef float costa_phase = 0
322
- cdef complex nco_out = 0
339
+ cdef float NOISE = get_noise_for_mod_type(mod_type)
340
+ cdef float noise_sqrd = noise_mag * noise_mag, real = 0 , imag = 0 , magnitude = 0 , max_magnitude
323
341
cdef float complex tmp
324
- cdef float phase_error = 0
325
- cdef float costa_alpha = 0
326
- cdef float costa_beta = 0
327
- cdef complex nco_times_sample = 0
328
- cdef float magnitude = 0
329
342
330
- cdef float max_magnitude # ensure all magnitudes of ASK demod between 0 and 1
331
343
if str (cython.typeof(samples)) == " char[:, ::1]" :
332
344
max_magnitude = sqrt(127 * 127 + 128 * 128 )
333
345
elif str (cython.typeof(samples)) == " unsigned char[:, ::1]" :
@@ -341,35 +353,27 @@ cpdef np.ndarray[np.float32_t, ndim=1] afp_demod(IQ samples, float noise_mag, st
341
353
else :
342
354
raise ValueError (" Unsupported dtype" )
343
355
344
- # Atan2 yields values from -Pi to Pi
345
- # We use the Magic Constant NOISE_FSK_PSK to cut off noise
346
- noise_sqrd = noise_mag * noise_mag
347
- NOISE = get_noise_for_mod_type(mod_type)
348
- result[0 ] = NOISE
349
-
350
- cdef bool qam = False
351
356
352
- if mod_type in (" PSK" , " QAM" , " OQPSK" ):
353
- if mod_type == " QAM" :
354
- qam = True
357
+ if mod_type == " PSK" :
358
+ return np.asarray(costa_demod(samples, noise_sqrd, mod_order, bandwidth = costas_loop_bandwidth))
355
359
356
- phase_demod(samples, result, noise_sqrd, qam, ns)
360
+ cdef float [::1 ] result = np.zeros(ns, dtype = np.float32, order = " C" )
361
+ result[0 ] = NOISE
357
362
358
- else :
359
- for i in prange(1 , ns, nogil = True , schedule = " static" ):
360
- real = samples[i, 0 ]
361
- imag = samples[i, 1 ]
362
- magnitude = real * real + imag * imag
363
- if magnitude <= noise_sqrd: # |c| <= mag_treshold
364
- result[i] = NOISE
365
- continue
363
+ for i in prange(1 , ns, nogil = True , schedule = " static" ):
364
+ real = samples[i, 0 ]
365
+ imag = samples[i, 1 ]
366
+ magnitude = real * real + imag * imag
367
+ if magnitude <= noise_sqrd: # |c| <= mag_treshold
368
+ result[i] = NOISE
369
+ continue
366
370
367
- if mod_type == " ASK" :
368
- result[i] = sqrt(magnitude) / max_magnitude
369
- elif mod_type == " FSK" :
370
- # tmp = samples[i - 1].conjugate() * c
371
- tmp = (samples[i- 1 , 0 ] - imag_unit * samples[i- 1 , 1 ]) * (real + imag_unit * imag)
372
- result[i] = atan2(tmp.imag, tmp.real) # Freq
371
+ if mod_type == " ASK" :
372
+ result[i] = sqrt(magnitude) / max_magnitude
373
+ elif mod_type == " FSK" :
374
+ # tmp = samples[i - 1].conjugate() * c
375
+ tmp = (samples[i- 1 , 0 ] - imag_unit * samples[i- 1 , 1 ]) * (real + imag_unit * imag)
376
+ result[i] = atan2(tmp.imag, tmp.real) # Freq
373
377
374
378
return np.asarray(result)
375
379
0 commit comments