-
Notifications
You must be signed in to change notification settings - Fork 21
/
fmdemod.py
61 lines (52 loc) · 1.76 KB
/
fmdemod.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#
# FM demodulation, from i/q to audio.
#
import numpy
import scipy
import scipy.signal
def butter_lowpass(cut, samplerate, order=5):
nyq = 0.5 * samplerate
cut = cut / nyq
b, a = scipy.signal.butter(order, cut, btype='lowpass')
return b, a
class FMDemod:
def __init__(self, rate):
self.rate = rate
self.width = 8000
self.order = 3
self.filter = None
self.zi = None
#
# input is complex i/q raw samples.
# output is (audio, amplitudes).
# audio is the demodulated audio.
# amplitudes are the i/q amplitudes, so the caller
# can compute SNR (if they can guess what is signal
# and what is noise).
#
def demod(self, samples):
# complex low-pass filter.
# passes fmwidth on either side of zero,
# so real width is 2*self.width.
if self.filter == None:
self.filter = butter_lowpass(self.width, self.rate, self.order)
self.zi = scipy.signal.lfiltic(self.filter[0],
self.filter[1],
[0])
bzi = scipy.signal.lfilter(self.filter[0],
self.filter[1],
samples,
zi=self.zi)
cc2 = bzi[0]
self.zi = bzi[1]
# quadrature fm demodulation, as in gnuradio gr_quadrature_demod_cf.
# seems to be the same as the Lyons scheme.
product = numpy.multiply(cc2[1:], numpy.conjugate(cc2[0:-1]))
diff = numpy.angle(product)
diff = numpy.append(diff, diff[-1])
# don't de-emphasize, since intended for aprs.
# calculate amplitude at each sample,
# for later snr calculation.
# calculated as post-filter length of complex vector.
amp = numpy.sqrt(numpy.add(numpy.square(cc2.real), numpy.square(cc2.imag)))
return (diff, amp)