radio 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +1 -0
- data/lib/radio.rb +14 -0
- data/lib/radio/psk31/bit_detect.rb +46 -0
- data/lib/radio/psk31/decoder.rb +65 -0
- data/lib/radio/psk31/filters.rb +220 -0
- data/lib/radio/psk31/fir_coef.rb +287 -0
- data/lib/radio/psk31/rx.rb +52 -0
- data/lib/radio/psk31/varicode.rb +275 -0
- metadata +54 -0
data/README.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Amateur radio software
|
data/lib/radio.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
class Radio
|
2
|
+
|
3
|
+
class PSK31
|
4
|
+
|
5
|
+
class BitDetect
|
6
|
+
|
7
|
+
AVG_SAMPLES = 50.freeze
|
8
|
+
CHANGE_DELAY = 5
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@averages = Array.new 21
|
12
|
+
reset
|
13
|
+
end
|
14
|
+
|
15
|
+
def reset
|
16
|
+
@averages.fill 0.0
|
17
|
+
@phase = 0
|
18
|
+
@peak = 0
|
19
|
+
@next_peak = 0
|
20
|
+
@change_at = 0
|
21
|
+
end
|
22
|
+
|
23
|
+
def call sample_x, sample_y
|
24
|
+
yield if @phase == @peak
|
25
|
+
@peak = @next_peak if @phase == @change_at
|
26
|
+
energy = sample_x**2 + sample_y**2
|
27
|
+
@averages[@phase] = (1.0-1.0/AVG_SAMPLES)*@averages[@phase] + (1.0/AVG_SAMPLES)*energy
|
28
|
+
@phase += 1
|
29
|
+
if @phase > 15
|
30
|
+
@phase = 0
|
31
|
+
max = -1e10
|
32
|
+
for i in 0...16
|
33
|
+
energy = @averages[i]
|
34
|
+
if energy > max
|
35
|
+
@next_peak = i
|
36
|
+
@change_at = (i + CHANGE_DELAY) & 0x0F
|
37
|
+
max = energy
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
class Radio
|
2
|
+
class PSK31
|
3
|
+
|
4
|
+
class Decoder
|
5
|
+
|
6
|
+
attr_accessor :mode # :bpsk or :qpsk or :qpsklsb
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@mode = :bpsk
|
10
|
+
reset
|
11
|
+
end
|
12
|
+
|
13
|
+
def reset
|
14
|
+
@prev_i = 0.0
|
15
|
+
@prev_q = 0.0
|
16
|
+
@this_i = 0.0
|
17
|
+
@this_q = 0.0
|
18
|
+
@code = 0
|
19
|
+
@prev_bit = false
|
20
|
+
end
|
21
|
+
|
22
|
+
def call new_i, new_q
|
23
|
+
@prev_i = @this_i
|
24
|
+
@prev_q = @this_q
|
25
|
+
@this_i = new_i
|
26
|
+
@this_q = new_q
|
27
|
+
vect_y = @prev_i*@this_i + @prev_q*@this_q
|
28
|
+
if @mode == :bpsk
|
29
|
+
bit = vect_y >= 0.0 ? 1 : 0
|
30
|
+
else
|
31
|
+
vect_x = @prev_i*@this_q - @this_i*@prev_q
|
32
|
+
if vect_y == 0.0 # atan2 errors on (0,0)
|
33
|
+
angle = PI
|
34
|
+
elsif @mode == :qpsklsb
|
35
|
+
angle = PI + Math.atan2(vect_y, -vect_x)
|
36
|
+
else # :qpsk or :bpsk
|
37
|
+
angle = PI + Math.atan2(vect_y, vect_x)
|
38
|
+
end
|
39
|
+
bit = viterbi angle
|
40
|
+
end
|
41
|
+
if bit==0 and @prev_bit==0
|
42
|
+
if @code != 0
|
43
|
+
@code >>= 2
|
44
|
+
@code &= 0x07FF
|
45
|
+
ch = VARICODE_DECODE_TABLE[@code]
|
46
|
+
yield ch if ch
|
47
|
+
@code = 0
|
48
|
+
end
|
49
|
+
else
|
50
|
+
@code <<= 1
|
51
|
+
@code |= bit
|
52
|
+
@prev_bit = bit
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def viterbi
|
59
|
+
raise 'todo'
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,220 @@
|
|
1
|
+
class Radio
|
2
|
+
|
3
|
+
class PSK31
|
4
|
+
|
5
|
+
# An instance of Filters handles everything from 48kHz to 500Hz.
|
6
|
+
# PSK31/PSK63/PSK125 emit at 500Hz/1000Hz/2000Hz respectively.
|
7
|
+
|
8
|
+
class Filters
|
9
|
+
|
10
|
+
attr_accessor :frequency
|
11
|
+
attr_accessor :clock # 8000.0 +/-
|
12
|
+
attr_accessor :phase_inc
|
13
|
+
attr_accessor :speed # 31 | 63 | 125
|
14
|
+
|
15
|
+
# Format of the input data stream is specified in the same
|
16
|
+
# format as String#unpack. Here are some examples:
|
17
|
+
# 'C' 8-bit unsigned mono
|
18
|
+
# 'xxv' Little-endian 16-bit right channel
|
19
|
+
# Not everything is supported outside this native Ruby implementation.
|
20
|
+
# The following are guaranteed to be in a C or Java implementation.
|
21
|
+
# x - ignores a byte (in an unused channel)
|
22
|
+
# C/c - Unsigned/signed bytes
|
23
|
+
# n/v - Unsigned 16-bit words in big/little endian format
|
24
|
+
|
25
|
+
# You must supply at least one of the dec16, dec8, or dec4 filters and
|
26
|
+
# it must be appropriate for the speed you desire. The dec4 filter can
|
27
|
+
# be used for all speeds, dec8 only for PSK63, and dec16 only for PSK31.
|
28
|
+
|
29
|
+
def initialize sample_rate, format, frequency, filters
|
30
|
+
@do_dec6 = case sample_rate
|
31
|
+
when 8000 then false
|
32
|
+
when 48000 then true
|
33
|
+
else raise 'only 8000 and 48000 Hz sample rates are supported'
|
34
|
+
end
|
35
|
+
@format = format
|
36
|
+
@sample_size = [0].pack(format).size
|
37
|
+
case ("\x80"*16).unpack(format)[0]
|
38
|
+
when 128
|
39
|
+
@max = 128
|
40
|
+
@offset = -128
|
41
|
+
when -128
|
42
|
+
@max = 128
|
43
|
+
@offset = 0
|
44
|
+
when 32768 + 128
|
45
|
+
@max = 32768
|
46
|
+
@offset = -32768
|
47
|
+
when -32768 + 128
|
48
|
+
@max = 32768
|
49
|
+
@offset = 0
|
50
|
+
else
|
51
|
+
raise 'unable to interpret format'
|
52
|
+
end
|
53
|
+
@frequency = frequency
|
54
|
+
@clock = 8000.0
|
55
|
+
@phase = 0.0
|
56
|
+
@speed = 31
|
57
|
+
@pulse = 0
|
58
|
+
if filters[:dec6]
|
59
|
+
@dec6_coef = NArray.to_na filters[:dec6]*2
|
60
|
+
@dec6_data = NArray.float filters[:dec6].size
|
61
|
+
@dec6_pos = 0
|
62
|
+
@dec6_pulse = 6
|
63
|
+
elsif @do_dec6
|
64
|
+
raise 'no 48000 Hz filter found'
|
65
|
+
end
|
66
|
+
if filters[:dec16]
|
67
|
+
@dec16_coef = NArray.to_na filters[:dec16]*2
|
68
|
+
@dec16_sin = NArray.float filters[:dec16].size
|
69
|
+
@dec16_cos = NArray.float filters[:dec16].size
|
70
|
+
@dec16_pos = 0
|
71
|
+
else
|
72
|
+
@dec16_coef = nil
|
73
|
+
end
|
74
|
+
if filters[:dec8]
|
75
|
+
@dec8_coef = NArray.to_na filters[:dec8]*2
|
76
|
+
@dec8_sin = NArray.float filters[:dec8].size
|
77
|
+
@dec8_cos = NArray.float filters[:dec8].size
|
78
|
+
@dec8_pos = 0
|
79
|
+
else
|
80
|
+
@dec8_coef = nil
|
81
|
+
end
|
82
|
+
if filters[:dec4]
|
83
|
+
@dec4_coef = NArray.to_na filters[:dec4]*2
|
84
|
+
@dec4a_sin = NArray.float filters[:dec4].size
|
85
|
+
@dec4a_cos = NArray.float filters[:dec4].size
|
86
|
+
@dec4a_pos = 0
|
87
|
+
@dec4b_sin = NArray.float filters[:dec4].size
|
88
|
+
@dec4b_cos = NArray.float filters[:dec4].size
|
89
|
+
@dec4b_pos = 0
|
90
|
+
else
|
91
|
+
@dec4_coef = nil
|
92
|
+
end
|
93
|
+
@bit_coef = NArray.to_na filters[:bit]*2
|
94
|
+
@bit_sin = NArray.float filters[:bit].size
|
95
|
+
@bit_cos = NArray.float filters[:bit].size
|
96
|
+
@bit_pos = 0
|
97
|
+
recalc_phase_inc
|
98
|
+
end
|
99
|
+
|
100
|
+
def reset
|
101
|
+
if @dec6_coef
|
102
|
+
@dec6_data.fill 0.0
|
103
|
+
end
|
104
|
+
if @dec16_coef
|
105
|
+
@dec16_sin_data.fill 0.0
|
106
|
+
@dec16_cos_data.fill 0.0
|
107
|
+
end
|
108
|
+
if @dec8_coef
|
109
|
+
@dec8_sin_data.fill 0.0
|
110
|
+
@dec8_cos_data.fill 0.0
|
111
|
+
end
|
112
|
+
if @dec4_coef
|
113
|
+
@dec4a_sin.fill 0.0
|
114
|
+
@dec4a_cos.fill 0.0
|
115
|
+
@dec4b_sin.fill 0.0
|
116
|
+
@dec4b_cos.fill 0.0
|
117
|
+
end
|
118
|
+
@bit_sin.fill 0.0
|
119
|
+
@bit_cos.fill 0.0
|
120
|
+
end
|
121
|
+
|
122
|
+
def recalc_phase_inc
|
123
|
+
@phase_inc = PI2 * @frequency / @clock
|
124
|
+
end
|
125
|
+
|
126
|
+
def call sample_data
|
127
|
+
raise 'alignment error' unless sample_data.size % @sample_size == 0
|
128
|
+
sample_data.force_encoding('binary') # Ensure slice is fast like Ruby 1.9.3 byteslice
|
129
|
+
mod16_8 = @speed == 63 ? 8 : 16
|
130
|
+
pos = 0
|
131
|
+
while pos < sample_data.size
|
132
|
+
pos += @sample_size
|
133
|
+
sample = sample_data.slice(pos,@sample_size).unpack(@format)[0] || 0
|
134
|
+
sample = (sample + @offset).to_f / @max
|
135
|
+
if @do_dec6
|
136
|
+
@dec6_pos = @dec6_data.size if @dec6_pos == 0
|
137
|
+
@dec6_pos -= 1
|
138
|
+
@dec6_data[@dec6_pos] = sample
|
139
|
+
@dec6_pulse -= 1
|
140
|
+
next unless @dec6_pulse == 0
|
141
|
+
@dec6_pulse = 6
|
142
|
+
sample = @dec6_data.fir(@dec6_coef, @dec6_pos)
|
143
|
+
end
|
144
|
+
@phase += @phase_inc
|
145
|
+
@phase -= PI2 if @phase > PI2
|
146
|
+
if @dec16_coef and @speed == 31
|
147
|
+
@dec16_pos = @dec16_sin.size if @dec16_pos == 0
|
148
|
+
@dec16_pos -= 1
|
149
|
+
@dec16_sin[@dec16_pos] = sample * Math.sin(@phase)
|
150
|
+
@dec16_cos[@dec16_pos] = sample * Math.cos(@phase)
|
151
|
+
next unless ((@pulse = @pulse + 1 & 0xFF) % 16) == 0
|
152
|
+
ival = @dec16_sin.fir(@dec16_coef, @dec16_pos)
|
153
|
+
qval = @dec16_cos.fir(@dec16_coef, @dec16_pos)
|
154
|
+
elsif @dec8_coef and @speed == 63
|
155
|
+
@dec8_pos = @dec8_sin.size if @dec8_pos == 0
|
156
|
+
@dec8_pos -= 1
|
157
|
+
@dec8_sin[@dec8_pos] = sample * Math.sin(@phase)
|
158
|
+
@dec8_cos[@dec8_pos] = sample * Math.cos(@phase)
|
159
|
+
next unless ((@pulse = @pulse + 1 & 0xFF) % 8) == 0
|
160
|
+
ival = @dec8_sin.fir(@dec8_coef, @dec8_pos)
|
161
|
+
qval = @dec8_cos.fir(@dec8_coef, @dec8_pos)
|
162
|
+
elsif @dec4_coef
|
163
|
+
@dec4a_pos = @dec4a_sin.size if @dec4a_pos == 0
|
164
|
+
@dec4a_pos -= 1
|
165
|
+
@dec4a_sin[@dec4a_pos] = sample * Math.sin(@phase)
|
166
|
+
@dec4a_cos[@dec4a_pos] = sample * Math.cos(@phase)
|
167
|
+
next unless ((@pulse = @pulse + 1 & 0xFF) % 4) == 0
|
168
|
+
@dec4b_pos = @dec4b_sin.size if @dec4b_pos == 0
|
169
|
+
@dec4b_pos -= 1
|
170
|
+
ival = @dec4b_sin[@dec4b_pos] = @dec4a_sin.fir(@dec4_coef, @dec4a_pos)
|
171
|
+
qval = @dec4b_cos[@dec4b_pos] = @dec4a_cos.fir(@dec4_coef, @dec4a_pos)
|
172
|
+
next unless @speed == 125 or (@pulse % mod16_8 == 0)
|
173
|
+
unless @speed == 125
|
174
|
+
ival = @dec4b_sin.fir(@dec4_coef, @dec4b_pos)
|
175
|
+
qval = @dec4b_cos.fir(@dec4_coef, @dec4b_pos)
|
176
|
+
end
|
177
|
+
else
|
178
|
+
raise 'no suitable filter found'
|
179
|
+
end
|
180
|
+
@bit_pos = @bit_sin.size if @bit_pos == 0
|
181
|
+
@bit_pos -= 1
|
182
|
+
@bit_sin[@bit_pos] = ival
|
183
|
+
@bit_cos[@bit_pos] = qval
|
184
|
+
yield @bit_sin.fir(@bit_coef, @bit_pos), @bit_cos.fir(@bit_coef, @bit_pos)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# NArray can easily double filter performance
|
189
|
+
begin
|
190
|
+
require 'narray'
|
191
|
+
class ::NArray
|
192
|
+
def fir filter, pos
|
193
|
+
(self * filter[size-pos..-1-pos]).sum
|
194
|
+
end
|
195
|
+
end
|
196
|
+
rescue LoadError => e
|
197
|
+
# Pure Ruby fake NArray
|
198
|
+
class NArray < Array
|
199
|
+
def self.float arg
|
200
|
+
new arg, 0.0
|
201
|
+
end
|
202
|
+
def self.to_na arg
|
203
|
+
new(arg).freeze
|
204
|
+
end
|
205
|
+
def fir filter, pos
|
206
|
+
acc = 0.0
|
207
|
+
index = size - pos
|
208
|
+
each do |val|
|
209
|
+
acc += val * filter[index]
|
210
|
+
index += 1
|
211
|
+
end
|
212
|
+
acc
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
@@ -0,0 +1,287 @@
|
|
1
|
+
class Radio
|
2
|
+
class PSK31
|
3
|
+
|
4
|
+
FIR_DEC6 = [
|
5
|
+
-0.001026086585,
|
6
|
+
-0.002131424398,
|
7
|
+
-0.003473651912,
|
8
|
+
-0.005415991401,
|
9
|
+
-0.007094896149,
|
10
|
+
-0.008685456592,
|
11
|
+
-0.009325598368,
|
12
|
+
-0.009096149588,
|
13
|
+
-0.007453841473,
|
14
|
+
-0.004787743788,
|
15
|
+
-0.001155271744,
|
16
|
+
0.002594565142,
|
17
|
+
0.005930466661,
|
18
|
+
0.007902971637,
|
19
|
+
0.008117998323,
|
20
|
+
0.006209532708,
|
21
|
+
0.002565394219,
|
22
|
+
-0.002211033166,
|
23
|
+
-0.006928668876,
|
24
|
+
-0.010456837484,
|
25
|
+
-0.011638240040,
|
26
|
+
-0.009915015895,
|
27
|
+
-0.005261804435,
|
28
|
+
0.001469200215,
|
29
|
+
0.008865519609,
|
30
|
+
0.015027698257,
|
31
|
+
0.018157576105,
|
32
|
+
0.016869453853,
|
33
|
+
0.010745489111,
|
34
|
+
0.000474997075,
|
35
|
+
-0.012015135186,
|
36
|
+
-0.023901641616,
|
37
|
+
-0.031878023716,
|
38
|
+
-0.032876349281,
|
39
|
+
-0.024689692215,
|
40
|
+
-0.006586586958,
|
41
|
+
0.020417464549,
|
42
|
+
0.053543134171,
|
43
|
+
0.088652482149,
|
44
|
+
0.120891532319,
|
45
|
+
0.145553464727,
|
46
|
+
0.158910442030,
|
47
|
+
0.158910442030,
|
48
|
+
0.145553464727,
|
49
|
+
0.120891532319,
|
50
|
+
0.088652482149,
|
51
|
+
0.053543134171,
|
52
|
+
0.020417464549,
|
53
|
+
-0.006586586958,
|
54
|
+
-0.024689692215,
|
55
|
+
-0.032876349281,
|
56
|
+
-0.031878023716,
|
57
|
+
-0.023901641616,
|
58
|
+
-0.012015135186,
|
59
|
+
0.000474997075,
|
60
|
+
0.010745489111,
|
61
|
+
0.016869453853,
|
62
|
+
0.018157576105,
|
63
|
+
0.015027698257,
|
64
|
+
0.008865519609,
|
65
|
+
0.001469200215,
|
66
|
+
-0.005261804435,
|
67
|
+
-0.009915015895,
|
68
|
+
-0.011638240040,
|
69
|
+
-0.010456837484,
|
70
|
+
-0.006928668876,
|
71
|
+
-0.002211033166,
|
72
|
+
0.002565394219,
|
73
|
+
0.006209532708,
|
74
|
+
0.008117998323,
|
75
|
+
0.007902971637,
|
76
|
+
0.005930466661,
|
77
|
+
0.002594565142,
|
78
|
+
-0.001155271744,
|
79
|
+
-0.004787743788,
|
80
|
+
-0.007453841473,
|
81
|
+
-0.009096149588,
|
82
|
+
-0.009325598368,
|
83
|
+
-0.008685456592,
|
84
|
+
-0.007094896149,
|
85
|
+
-0.005415991401,
|
86
|
+
-0.003473651912,
|
87
|
+
-0.002131424398,
|
88
|
+
-0.001026086585
|
89
|
+
].freeze
|
90
|
+
|
91
|
+
|
92
|
+
# 64-tap raised-cosine
|
93
|
+
FIR_DEC16 = [
|
94
|
+
0.000000,
|
95
|
+
0.000038,
|
96
|
+
0.000150,
|
97
|
+
0.000336,
|
98
|
+
0.000595,
|
99
|
+
0.000922,
|
100
|
+
0.001317,
|
101
|
+
0.001773,
|
102
|
+
0.002288,
|
103
|
+
0.002856,
|
104
|
+
0.003472,
|
105
|
+
0.004130,
|
106
|
+
0.004823,
|
107
|
+
0.005545,
|
108
|
+
0.006288,
|
109
|
+
0.007047,
|
110
|
+
0.007812,
|
111
|
+
0.008578,
|
112
|
+
0.009337,
|
113
|
+
0.010080,
|
114
|
+
0.010802,
|
115
|
+
0.011495,
|
116
|
+
0.012153,
|
117
|
+
0.012769,
|
118
|
+
0.013337,
|
119
|
+
0.013852,
|
120
|
+
0.014308,
|
121
|
+
0.014703,
|
122
|
+
0.015030,
|
123
|
+
0.015289,
|
124
|
+
0.015475,
|
125
|
+
0.015587,
|
126
|
+
0.015625,
|
127
|
+
0.015587,
|
128
|
+
0.015475,
|
129
|
+
0.015289,
|
130
|
+
0.015030,
|
131
|
+
0.014703,
|
132
|
+
0.014308,
|
133
|
+
0.013852,
|
134
|
+
0.013337,
|
135
|
+
0.012769,
|
136
|
+
0.012153,
|
137
|
+
0.011495,
|
138
|
+
0.010802,
|
139
|
+
0.010080,
|
140
|
+
0.009337,
|
141
|
+
0.008578,
|
142
|
+
0.007813,
|
143
|
+
0.007047,
|
144
|
+
0.006288,
|
145
|
+
0.005545,
|
146
|
+
0.004823,
|
147
|
+
0.004130,
|
148
|
+
0.003472,
|
149
|
+
0.002856,
|
150
|
+
0.002288,
|
151
|
+
0.001773,
|
152
|
+
0.001317,
|
153
|
+
0.000922,
|
154
|
+
0.000595,
|
155
|
+
0.000336,
|
156
|
+
0.000150,
|
157
|
+
0.000038
|
158
|
+
].freeze
|
159
|
+
|
160
|
+
|
161
|
+
# Design method: Parks-McClellan method
|
162
|
+
# Number of taps = 35
|
163
|
+
# Number of bands = 2
|
164
|
+
# Band Lower Upper Value Weight
|
165
|
+
# edge edge
|
166
|
+
# 1 0.0 .0125 1.0 1
|
167
|
+
# 2 .125 .5 .000001 10
|
168
|
+
FIR_DEC4 = [
|
169
|
+
-0.00021203644,
|
170
|
+
-0.00070252426,
|
171
|
+
-0.0016680526,
|
172
|
+
-0.0031934799,
|
173
|
+
-0.0051899752,
|
174
|
+
-0.0072862086,
|
175
|
+
-0.0087714235,
|
176
|
+
-0.0086272102,
|
177
|
+
-0.0056735648,
|
178
|
+
0.0011784719,
|
179
|
+
0.01261353,
|
180
|
+
0.028615709,
|
181
|
+
0.048280707,
|
182
|
+
0.069812051,
|
183
|
+
0.090735013,
|
184
|
+
0.10830381,
|
185
|
+
0.12001897,
|
186
|
+
0.12413413,
|
187
|
+
0.12001897,
|
188
|
+
0.10830381,
|
189
|
+
0.090735013,
|
190
|
+
0.069812051,
|
191
|
+
0.048280707,
|
192
|
+
0.028615709,
|
193
|
+
0.01261353,
|
194
|
+
0.0011784719,
|
195
|
+
-0.0056735648,
|
196
|
+
-0.0086272102,
|
197
|
+
-0.0087714235,
|
198
|
+
-0.0072862086,
|
199
|
+
-0.0051899752,
|
200
|
+
-0.0031934799,
|
201
|
+
-0.0016680526,
|
202
|
+
-0.00070252426,
|
203
|
+
-0.00021203644
|
204
|
+
].freeze
|
205
|
+
|
206
|
+
|
207
|
+
# 16 Hz bw LP filter for data recovery
|
208
|
+
# Filter type: Multiband filter
|
209
|
+
# Design method: Parks-McClellan method
|
210
|
+
# Number of taps = 65
|
211
|
+
# Number of bands = 2
|
212
|
+
# Band Lower Upper Value Weight
|
213
|
+
# edge edge
|
214
|
+
#
|
215
|
+
# 1 0.0 .03125 1. 1.
|
216
|
+
# 2 .0625 .5 .0000 286.
|
217
|
+
FIR_BIT = [
|
218
|
+
4.3453566e-005,
|
219
|
+
-0.00049122414,
|
220
|
+
-0.00078771292,
|
221
|
+
-0.0013507826,
|
222
|
+
-0.0021287814,
|
223
|
+
-0.003133466,
|
224
|
+
-0.004366817,
|
225
|
+
-0.0058112187,
|
226
|
+
-0.0074249976,
|
227
|
+
-0.0091398882,
|
228
|
+
-0.010860157,
|
229
|
+
-0.012464086,
|
230
|
+
-0.013807772,
|
231
|
+
-0.014731191,
|
232
|
+
-0.015067057,
|
233
|
+
-0.014650894,
|
234
|
+
-0.013333425,
|
235
|
+
-0.01099166,
|
236
|
+
-0.0075431246,
|
237
|
+
-0.0029527849,
|
238
|
+
0.0027546292,
|
239
|
+
0.0094932775,
|
240
|
+
0.017113308,
|
241
|
+
0.025403511,
|
242
|
+
0.034099681,
|
243
|
+
0.042895839,
|
244
|
+
0.051458575,
|
245
|
+
0.059444853,
|
246
|
+
0.066521003,
|
247
|
+
0.072381617,
|
248
|
+
0.076767694,
|
249
|
+
0.079481619,
|
250
|
+
0.080420311,
|
251
|
+
0.079481619,
|
252
|
+
0.076767694,
|
253
|
+
0.072381617,
|
254
|
+
0.066521003,
|
255
|
+
0.059444853,
|
256
|
+
0.051458575,
|
257
|
+
0.042895839,
|
258
|
+
0.034099681,
|
259
|
+
0.025403511,
|
260
|
+
0.017113308,
|
261
|
+
0.0094932775,
|
262
|
+
0.0027546292,
|
263
|
+
-0.0029527849,
|
264
|
+
-0.0075431246,
|
265
|
+
-0.01099166,
|
266
|
+
-0.013333425,
|
267
|
+
-0.014650894,
|
268
|
+
-0.015067057,
|
269
|
+
-0.014731191,
|
270
|
+
-0.013807772,
|
271
|
+
-0.012464086,
|
272
|
+
-0.010860157,
|
273
|
+
-0.0091398882,
|
274
|
+
-0.0074249976,
|
275
|
+
-0.0058112187,
|
276
|
+
-0.004366817,
|
277
|
+
-0.003133466,
|
278
|
+
-0.0021287814,
|
279
|
+
-0.0013507826,
|
280
|
+
-0.00078771292,
|
281
|
+
-0.00049122414,
|
282
|
+
4.3453566e-005
|
283
|
+
].freeze
|
284
|
+
|
285
|
+
|
286
|
+
end
|
287
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
class Radio
|
2
|
+
|
3
|
+
class PSK31
|
4
|
+
|
5
|
+
class Rx
|
6
|
+
|
7
|
+
def initialize sample_rate, format, frequency
|
8
|
+
@filter = Filters.new sample_rate, format, frequency,
|
9
|
+
:dec6 => FIR_DEC6,
|
10
|
+
:dec16 => FIR_DEC16,
|
11
|
+
:dec4 => FIR_DEC4,
|
12
|
+
:bit => FIR_BIT
|
13
|
+
@bit_detect = BitDetect.new
|
14
|
+
@decoder = Decoder.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def call sample_data
|
18
|
+
decoded = ''
|
19
|
+
@filter.call sample_data do |i, q|
|
20
|
+
@bit_detect.call i, q do
|
21
|
+
@decoder.call i, q do |symbol|
|
22
|
+
decoded += symbol
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
decoded
|
27
|
+
end
|
28
|
+
|
29
|
+
def frequency= frequency
|
30
|
+
if frequency != @filter.frequency
|
31
|
+
@filter.frequency = frequency
|
32
|
+
@filter.recalc_phase_inc
|
33
|
+
reset
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# To compensate for bad clock in A-D conversion
|
38
|
+
def adjust_sample_clock ppm
|
39
|
+
@filter.clock = 8000.0 * ppm / 1000000 + 8000
|
40
|
+
@filter.recalc_phase_inc
|
41
|
+
end
|
42
|
+
|
43
|
+
def reset
|
44
|
+
@filter.reset
|
45
|
+
@bit_detect.reset
|
46
|
+
@decoder.reset
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,275 @@
|
|
1
|
+
class Radio
|
2
|
+
|
3
|
+
class PSK31
|
4
|
+
|
5
|
+
VARICODE_TABLE = [
|
6
|
+
0xAAC0, # 0 1010101011
|
7
|
+
0xB6C0, # 1 1011011011
|
8
|
+
0xBB40, # 2 1011101101
|
9
|
+
0xDDC0, # 3 1101110111
|
10
|
+
0xBAC0, # 4 1011101011
|
11
|
+
0xD7C0, # 5 1101011111
|
12
|
+
0xBBC0, # 6 1011101111
|
13
|
+
0xBF40, # 7 1011111101
|
14
|
+
0xBFC0, # 8 1011111111
|
15
|
+
0xEF00, # 9 11101111
|
16
|
+
0xE800, # 10 11101
|
17
|
+
0xDBC0, # 11 1101101111
|
18
|
+
0xB740, # 12 1011011101
|
19
|
+
0xF800, # 13 11111
|
20
|
+
0xDD40, # 14 1101110101
|
21
|
+
0xEAC0, # 15 1110101011
|
22
|
+
0xBDC0, # 16 1011110111
|
23
|
+
0xBD40, # 17 1011110101
|
24
|
+
0xEB40, # 18 1110101101
|
25
|
+
0xEBC0, # 19 1110101111
|
26
|
+
0xD6C0, # 20 1101011011
|
27
|
+
0xDAC0, # 21 1101101011
|
28
|
+
0xDB40, # 22 1101101101
|
29
|
+
0xD5C0, # 23 1101010111
|
30
|
+
0xDEC0, # 24 1101111011
|
31
|
+
0xDF40, # 25 1101111101
|
32
|
+
0xEDC0, # 26 1110110111
|
33
|
+
0xD540, # 27 1101010101
|
34
|
+
0xD740, # 28 1101011101
|
35
|
+
0xEEC0, # 29 1110111011
|
36
|
+
0xBEC0, # 30 1011111011
|
37
|
+
0xDFC0, # 31 1101111111
|
38
|
+
0x8000, # ' ' 1
|
39
|
+
0xFF80, # '!' 111111111
|
40
|
+
0xAF80, # '"' 101011111
|
41
|
+
0xFA80, # '#' 111110101
|
42
|
+
0xED80, # '$' 111011011
|
43
|
+
0xB540, # '%' 1011010101
|
44
|
+
0xAEC0, # '&' 1010111011
|
45
|
+
0xBF80, # ''' 101111111
|
46
|
+
0xFB00, # '(' 11111011
|
47
|
+
0xF700, # ')' 11110111
|
48
|
+
0xB780, # '*' 101101111
|
49
|
+
0xEF80, # '+' 111011111
|
50
|
+
0xEA00, # ',' 1110101
|
51
|
+
0xD400, # '-' 110101
|
52
|
+
0xAE00, # '.' 1010111
|
53
|
+
0xD780, # '/' 110101111
|
54
|
+
0xB700, # '0' 10110111
|
55
|
+
0xBD00, # '1' 10111101
|
56
|
+
0xED00, # '2' 11101101
|
57
|
+
0xFF00, # '3' 11111111
|
58
|
+
0xBB80, # '4' 101110111
|
59
|
+
0xAD80, # '5' 101011011
|
60
|
+
0xB580, # '6' 101101011
|
61
|
+
0xD680, # '7' 110101101
|
62
|
+
0xD580, # '8' 110101011
|
63
|
+
0xDB80, # '9' 110110111
|
64
|
+
0xF500, # ':' 11110101
|
65
|
+
0xDE80, # ';' 110111101
|
66
|
+
0xF680, # '<' 111101101
|
67
|
+
0xAA00, # '=' 1010101
|
68
|
+
0xEB80, # '>' 111010111
|
69
|
+
0xABC0, # '?' 1010101111
|
70
|
+
0xAF40, # '@' 1010111101
|
71
|
+
0xFA00, # 'A' 1111101
|
72
|
+
0xEB00, # 'B' 11101011
|
73
|
+
0xAD00, # 'C' 10101101
|
74
|
+
0xB500, # 'D' 10110101
|
75
|
+
0xEE00, # 'E' 1110111
|
76
|
+
0xDB00, # 'F' 11011011
|
77
|
+
0xFD00, # 'G' 11111101
|
78
|
+
0xAA80, # 'H' 101010101
|
79
|
+
0xFE00, # 'I' 1111111
|
80
|
+
0xFE80, # 'J' 111111101
|
81
|
+
0xBE80, # 'K' 101111101
|
82
|
+
0xD700, # 'L' 11010111
|
83
|
+
0xBB00, # 'M' 10111011
|
84
|
+
0xDD00, # 'N' 11011101
|
85
|
+
0xAB00, # 'O' 10101011
|
86
|
+
0xD500, # 'P' 11010101
|
87
|
+
0xEE80, # 'Q' 111011101
|
88
|
+
0xAF00, # 'R' 10101111
|
89
|
+
0xDE00, # 'S' 1101111
|
90
|
+
0xDA00, # 'T' 1101101
|
91
|
+
0xAB80, # 'U' 101010111
|
92
|
+
0xDA80, # 'V' 110110101
|
93
|
+
0xAE80, # 'W' 101011101
|
94
|
+
0xBA80, # 'X' 101110101
|
95
|
+
0xBD80, # 'Y' 101111011
|
96
|
+
0xAB40, # 'Z' 1010101101
|
97
|
+
0xFB80, # '[' 111110111
|
98
|
+
0xF780, # '\' 111101111
|
99
|
+
0xFD80, # ']' 111111011
|
100
|
+
0xAFC0, # '^' 1010111111
|
101
|
+
0xB680, # '_' 101101101
|
102
|
+
0xB7C0, # '`' 1011011111
|
103
|
+
0xB000, # 'a' 1011
|
104
|
+
0xBE00, # 'b' 1011111
|
105
|
+
0xBC00, # 'c' 101111
|
106
|
+
0xB400, # 'd' 101101
|
107
|
+
0xC000, # 'e' 11
|
108
|
+
0xF400, # 'f' 111101
|
109
|
+
0xB600, # 'g' 1011011
|
110
|
+
0xAC00, # 'h' 101011
|
111
|
+
0xD000, # 'i' 1101
|
112
|
+
0xF580, # 'j' 111101011
|
113
|
+
0xBF00, # 'k' 10111111
|
114
|
+
0xD800, # 'l' 11011
|
115
|
+
0xEC00, # 'm' 111011
|
116
|
+
0xF000, # 'n' 1111
|
117
|
+
0xE000, # 'o' 111
|
118
|
+
0xFC00, # 'p' 111111
|
119
|
+
0xDF80, # 'q' 110111111
|
120
|
+
0xA800, # 'r' 10101
|
121
|
+
0xB800, # 's' 10111
|
122
|
+
0xA000, # 't' 101
|
123
|
+
0xDC00, # 'u' 110111
|
124
|
+
0xF600, # 'v' 1111011
|
125
|
+
0xD600, # 'w' 1101011
|
126
|
+
0xDF00, # 'x' 11011111
|
127
|
+
0xBA00, # 'y' 1011101
|
128
|
+
0xEA80, # 'z' 111010101
|
129
|
+
0xADC0, # '{' 1010110111
|
130
|
+
0xDD80, # '|' 110111011
|
131
|
+
0xAD40, # '}' 1010110101
|
132
|
+
0xB5C0, # '~' 1011010111
|
133
|
+
0xED40, # 127 1110110101
|
134
|
+
0xEF40, # 128 1110111101
|
135
|
+
0xEFC0, # 129 1110111111
|
136
|
+
0xF540, # 130 1111010101
|
137
|
+
0xF5C0, # 131 1111010111
|
138
|
+
0xF6C0, # 132 1111011011
|
139
|
+
0xF740, # 133 1111011101
|
140
|
+
0xF7C0, # 134 1111011111
|
141
|
+
0xFAC0, # 135 1111101011
|
142
|
+
0xFB40, # 136 1111101101
|
143
|
+
0xFBC0, # 137 1111101111
|
144
|
+
0xFD40, # 138 1111110101
|
145
|
+
0xFDC0, # 139 1111110111
|
146
|
+
0xFEC0, # 140 1111111011
|
147
|
+
0xFF40, # 141 1111111101
|
148
|
+
0xFFC0, # 142 1111111111
|
149
|
+
0xAAA0, # 143 10101010101
|
150
|
+
0xAAE0, # 144 10101010111
|
151
|
+
0xAB60, # 145 10101011011
|
152
|
+
0xABA0, # 146 10101011101
|
153
|
+
0xABE0, # 147 10101011111
|
154
|
+
0xAD60, # 148 10101101011
|
155
|
+
0xADA0, # 149 10101101101
|
156
|
+
0xADE0, # 150 10101101111
|
157
|
+
0xAEA0, # 151 10101110101
|
158
|
+
0xAEE0, # 152 10101110111
|
159
|
+
0xAF60, # 153 10101111011
|
160
|
+
0xAFA0, # 154 10101111101
|
161
|
+
0xAFE0, # 155 10101111111
|
162
|
+
0xB560, # 156 10110101011
|
163
|
+
0xB5A0, # 157 10110101101
|
164
|
+
0xB5E0, # 158 10110101111
|
165
|
+
0xB6A0, # 159 10110110101
|
166
|
+
0xB6E0, # 160 10110110111
|
167
|
+
0xB760, # 161 10110111011
|
168
|
+
0xB7A0, # 162 10110111101
|
169
|
+
0xB7E0, # 163 10110111111
|
170
|
+
0xBAA0, # 164 10111010101
|
171
|
+
0xBAE0, # 165 10111010111
|
172
|
+
0xBB60, # 166 10111011011
|
173
|
+
0xBBA0, # 167 10111011101
|
174
|
+
0xBBE0, # 168 10111011111
|
175
|
+
0xBD60, # 169 10111101011
|
176
|
+
0xBDA0, # 170 10111101101
|
177
|
+
0xBDE0, # 171 10111101111
|
178
|
+
0xBEA0, # 172 10111110101
|
179
|
+
0xBEE0, # 173 10111110111
|
180
|
+
0xBF60, # 174 10111111011
|
181
|
+
0xBFA0, # 175 10111111101
|
182
|
+
0xBFE0, # 176 10111111111
|
183
|
+
0xD560, # 177 11010101011
|
184
|
+
0xD5A0, # 178 11010101101
|
185
|
+
0xD5E0, # 179 11010101111
|
186
|
+
0xD6A0, # 180 11010110101
|
187
|
+
0xD6E0, # 181 11010110111
|
188
|
+
0xD760, # 182 11010111011
|
189
|
+
0xD7A0, # 183 11010111101
|
190
|
+
0xD7E0, # 184 11010111111
|
191
|
+
0xDAA0, # 185 11011010101
|
192
|
+
0xDAE0, # 186 11011010111
|
193
|
+
0xDB60, # 187 11011011011
|
194
|
+
0xDBA0, # 188 11011011101
|
195
|
+
0xDBE0, # 189 11011011111
|
196
|
+
0xDD60, # 190 11011101011
|
197
|
+
0xDDA0, # 191 11011101101
|
198
|
+
0xDDE0, # 192 11011101111
|
199
|
+
0xDEA0, # 193 11011110101
|
200
|
+
0xDEE0, # 194 11011110111
|
201
|
+
0xDF60, # 195 11011111011
|
202
|
+
0xDFA0, # 196 11011111101
|
203
|
+
0xDFE0, # 197 11011111111
|
204
|
+
0xEAA0, # 198 11101010101
|
205
|
+
0xEAE0, # 199 11101010111
|
206
|
+
0xEB60, # 200 11101011011
|
207
|
+
0xEBA0, # 201 11101011101
|
208
|
+
0xEBE0, # 202 11101011111
|
209
|
+
0xED60, # 203 11101101011
|
210
|
+
0xEDA0, # 204 11101101101
|
211
|
+
0xEDE0, # 205 11101101111
|
212
|
+
0xEEA0, # 206 11101110101
|
213
|
+
0xEEE0, # 207 11101110111
|
214
|
+
0xEF60, # 208 11101111011
|
215
|
+
0xEFA0, # 209 11101111101
|
216
|
+
0xEFE0, # 210 11101111111
|
217
|
+
0xF560, # 211 11110101011
|
218
|
+
0xF5A0, # 212 11110101101
|
219
|
+
0xF5E0, # 213 11110101111
|
220
|
+
0xF6A0, # 214 11110110101
|
221
|
+
0xF6E0, # 215 11110110111
|
222
|
+
0xF760, # 216 11110111011
|
223
|
+
0xF7A0, # 217 11110111101
|
224
|
+
0xF7E0, # 218 11110111111
|
225
|
+
0xFAA0, # 219 11111010101
|
226
|
+
0xFAE0, # 220 11111010111
|
227
|
+
0xFB60, # 221 11111011011
|
228
|
+
0xFBA0, # 222 11111011101
|
229
|
+
0xFBE0, # 223 11111011111
|
230
|
+
0xFD60, # 224 11111101011
|
231
|
+
0xFDA0, # 225 11111101101
|
232
|
+
0xFDE0, # 226 11111101111
|
233
|
+
0xFEA0, # 227 11111110101
|
234
|
+
0xFEE0, # 228 11111110111
|
235
|
+
0xFF60, # 229 11111111011
|
236
|
+
0xFFA0, # 230 11111111101
|
237
|
+
0xFFE0, # 231 11111111111
|
238
|
+
0xAAB0, # 232 101010101011
|
239
|
+
0xAAD0, # 233 101010101101
|
240
|
+
0xAAF0, # 234 101010101111
|
241
|
+
0xAB50, # 235 101010110101
|
242
|
+
0xAB70, # 236 101010110111
|
243
|
+
0xABB0, # 237 101010111011
|
244
|
+
0xABD0, # 238 101010111101
|
245
|
+
0xABF0, # 239 101010111111
|
246
|
+
0xAD50, # 240 101011010101
|
247
|
+
0xAD70, # 241 101011010111
|
248
|
+
0xADB0, # 242 101011011011
|
249
|
+
0xADD0, # 243 101011011101
|
250
|
+
0xADF0, # 244 101011011111
|
251
|
+
0xAEB0, # 245 101011101011
|
252
|
+
0xAED0, # 246 101011101101
|
253
|
+
0xAEF0, # 247 101011101111
|
254
|
+
0xAF50, # 248 101011110101
|
255
|
+
0xAF70, # 249 101011110111
|
256
|
+
0xAFB0, # 250 101011111011
|
257
|
+
0xAFD0, # 251 101011111101
|
258
|
+
0xAFF0, # 252 101011111111
|
259
|
+
0xB550, # 253 101101010101
|
260
|
+
0xB570, # 254 101101010111
|
261
|
+
0xB5B0 # 255 101101011011
|
262
|
+
].freeze
|
263
|
+
|
264
|
+
VARICODE_DECODE_TABLE = []
|
265
|
+
for i in 0...256
|
266
|
+
wTemp = VARICODE_TABLE[i]
|
267
|
+
wTemp >>= 4
|
268
|
+
wTemp >>= 1 until (wTemp&1 == 1)
|
269
|
+
wTemp >>= 1
|
270
|
+
VARICODE_DECODE_TABLE[wTemp] = i.chr
|
271
|
+
end
|
272
|
+
VARICODE_DECODE_TABLE.freeze
|
273
|
+
|
274
|
+
end
|
275
|
+
end
|
metadata
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: radio
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- David Turnbull
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-11-11 00:00:00.000000000Z
|
13
|
+
dependencies: []
|
14
|
+
description:
|
15
|
+
email:
|
16
|
+
- dturnbull@gmail.com
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- lib/radio/psk31/bit_detect.rb
|
22
|
+
- lib/radio/psk31/decoder.rb
|
23
|
+
- lib/radio/psk31/filters.rb
|
24
|
+
- lib/radio/psk31/fir_coef.rb
|
25
|
+
- lib/radio/psk31/rx.rb
|
26
|
+
- lib/radio/psk31/varicode.rb
|
27
|
+
- lib/radio.rb
|
28
|
+
- README.md
|
29
|
+
homepage: https://github.com/dturnbull/radio
|
30
|
+
licenses: []
|
31
|
+
post_install_message:
|
32
|
+
rdoc_options: []
|
33
|
+
require_paths:
|
34
|
+
- lib
|
35
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
36
|
+
none: false
|
37
|
+
requirements:
|
38
|
+
- - ! '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ! '>='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
requirements: []
|
48
|
+
rubyforge_project:
|
49
|
+
rubygems_version: 1.8.6
|
50
|
+
signing_key:
|
51
|
+
specification_version: 3
|
52
|
+
summary: Amateur radio software
|
53
|
+
test_files: []
|
54
|
+
has_rdoc:
|