radio 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -15,49 +15,280 @@
15
15
 
16
16
  class Radio
17
17
  class Filter
18
-
19
- module FirSetup
20
- # It's ok that not all filter patterns use all instance variables.
21
- def setup data
22
- @mix_phase = 0.0
23
- @mix_phase_inc = @options[:mix]
24
- @dec_pos = @dec_size = @options[:decimate]
25
- coef = @options[:fir]
26
- @fir_pos = 0
27
- @fir_size = coef.size
28
- @fir_coef = NArray.to_na coef.reverse*2
29
- @fir_buf = NArray.complex @fir_size
18
+
19
+ module SetupFir
20
+
21
+ def interpolation_mix= mix
22
+ @interpolation_mix = mix
23
+ @interpolation_fir_pos = 0 # sync to mixer
24
+ @interpolation_phase, @interpolation_inc =
25
+ new_mixer @interpolation_mix, @interpolation_size
26
+ setup_interpolation if @interpolation_fir_orig
27
+ end
28
+
29
+ def interpolation_fir= coef
30
+ # expand interpolation filter for matrix by padding with 0s
31
+ remainder = coef.size % @interpolation_size
32
+ if remainder > 0
33
+ coef = coef.to_a + [0]*(@interpolation_size-remainder)
34
+ end
35
+ if @interpolation_fir_orig
36
+ raise "can't grow filter" if coef.size > @interpolation_fir_size
37
+ else
38
+ @interpolation_fir_pos = 0
39
+ @interpolation_fir_size = coef.size / @interpolation_size
40
+ @interpolation_buf_f = NArray.sfloat @interpolation_fir_size
41
+ @interpolation_buf_c = NArray.scomplex @interpolation_fir_size
42
+ end
43
+ @interpolation_fir_orig = coef
44
+ setup_interpolation
45
+ end
46
+
47
+ def decimation_mix= mix
48
+ @decimation_mix = mix
49
+ @decimation_fir_pos = 0 # sync to mixer
50
+ @decimation_phase, @decimation_inc =
51
+ new_mixer @decimation_mix, @decimation_size
52
+ setup_decimation if @decimation_fir_orig
53
+ end
54
+ alias :mix= :decimation_mix=
55
+
56
+ def decimation_fir= coef
57
+ if @decimation_fir_orig
58
+ raise "can't grow filter" if coef.size > @decimation_fir_size
59
+ else
60
+ @decimation_fir_pos = 0
61
+ @decimation_fir_size = coef.size
62
+ @decimation_buf = NArray.scomplex @decimation_fir_size
63
+ # decimation allows fractions for digital work
64
+ if Float === @decimation_size
65
+ @decimation_pos = 0.0
66
+ else
67
+ @decimation_pos = 0
68
+ end
69
+ end
70
+ @decimation_fir_orig = coef
71
+ setup_decimation
72
+ end
73
+ alias :fir= :decimation_fir=
74
+
75
+ private
76
+
77
+ def setup
78
+ if @interpolation_size = @options[:interpolate]
79
+ @interpolation_size = @interpolation_size.to_i
80
+ end
81
+ @decimation_size = @options[:decimate]
82
+ if mix = @options[:mix]
83
+ if @interpolation_size and @decimation_size
84
+ self.interpolation_mix = mix[0]
85
+ self.decimation_mix = mix[1]
86
+ elsif @interpolation_size
87
+ self.interpolation_mix = mix
88
+ else
89
+ self.decimation_mix = mix
90
+ end
91
+ end
92
+ if coef = @options[:fir]
93
+ if @interpolation_size and @decimation_size
94
+ self.interpolation_fir = coef[0]
95
+ self.decimation_fir = coef[1]
96
+ elsif @interpolation_size
97
+ self.interpolation_fir = coef
98
+ else # decimation (use for 1:1 too)
99
+ self.decimation_fir = coef
100
+ end
101
+ end
30
102
  super
31
103
  end
104
+
105
+ def setup_interpolation
106
+ staging = double_filter premix_filter @interpolation_fir_orig, @interpolation_mix
107
+ # interpolation is shaped to avoid 0*0 ops
108
+ ranks = staging.size / @interpolation_size
109
+ pivot = staging.reshape(@interpolation_size, ranks)
110
+ staging.reshape!(ranks, @interpolation_size)
111
+ @interpolation_size.times {|rank| staging[true,rank] = pivot[rank,true]}
112
+ @interpolation_fir_coef = preslice_filter staging
113
+ end
114
+
115
+ def setup_decimation
116
+ coef = premix_filter @decimation_fir_orig, @decimation_mix
117
+ @decimation_fir_coef = preslice_filter double_filter coef
118
+ end
119
+
120
+ # There's no obviously easy way to get NArray to
121
+ # mul_accum on a slice without making a temporary
122
+ # object. So we make them all and store them in a
123
+ # regular Ruby array.
124
+ def preslice_filter filter
125
+ slices = []
126
+ steps = filter.shape[0] / 2
127
+ steps.times do |i|
128
+ f_start = steps-i
129
+ f_end = -1-i
130
+ if filter.rank == 2
131
+ slices << filter[f_start..f_end, true]
132
+ else
133
+ slices << filter[f_start..f_end]
134
+ end
135
+ end
136
+ slices
137
+ end
138
+
139
+ # We build filters with two copies of data so a
140
+ # circular buffer can mul_accum on a slice.
141
+ def double_filter coef
142
+ coef = coef.to_a
143
+ if Complex === coef[0]
144
+ new_coef = NArray.scomplex coef.size * 2
145
+ else
146
+ new_coef = NArray.sfloat coef.size * 2
147
+ end
148
+ # reverse into position
149
+ new_coef[coef.size-1..0] = coef
150
+ new_coef[-1..coef.size] = coef
151
+ new_coef
152
+ end
153
+
154
+ # The signal is pre-mixed into the filter.
155
+ # We adjust the master phase every time we filter.
156
+ # mix_phase *= mix_phase_inc # faster than sin+cos
157
+ # Take out the rounding errors once in a while with:
158
+ # mix_phase /= mix_phase.abs
159
+ def premix_filter coef, mix
160
+ return coef unless mix and mix != 0
161
+ rate = PI2 * mix
162
+ i = coef.size
163
+ coef.collect do |coef|
164
+ i -= 1
165
+ coef * Math.exp(Complex(0.0,rate*i))
166
+ end
167
+ end
168
+
169
+ def new_mixer mix, size
170
+ size ||= 1
171
+ return [Complex(1.0,1.0)/Complex(1.0,1.0).abs,
172
+ Math.exp(Complex(0.0, PI2 * mix * size))]
173
+ end
174
+
32
175
  end
33
176
 
34
- module FloatEachMixDecimateFir
35
- include FirSetup
36
- def call data
37
- data.each do |energy|
38
- @fir_pos = @fir_size if @fir_pos == 0
39
- @fir_pos -= 1
40
- @fir_buf[@fir_pos] = Complex(Math.cos(@mix_phase)*energy, -Math.sin(@mix_phase)*energy)
41
- @mix_phase += @mix_phase_inc
42
- @mix_phase -= PI2 if @mix_phase >= PI2
43
- @dec_pos -= 1
44
- if @dec_pos == 0
45
- @dec_pos = @dec_size
46
- iq = @fir_buf.mul_accum @fir_coef[@fir_size-@fir_pos..-1-@fir_pos],0
47
- yield iq[0]
177
+ module Fir
178
+ include SetupFir
179
+ module Complex
180
+ def call data
181
+ out = NArray.scomplex data.size
182
+ data.size.times do |i|
183
+ @decimation_fir_pos = @decimation_fir_size if @decimation_fir_pos == 0
184
+ @decimation_fir_pos -= 1
185
+ @decimation_buf[@decimation_fir_pos] = data[i..i]
186
+ out[i] = @decimation_fir_coef[@decimation_fir_pos].mul_accum @decimation_buf, 0
48
187
  end
188
+ yield out
49
189
  end
50
190
  end
51
191
  end
52
192
 
53
- module ComplexFir
54
- include FirSetup
193
+ module Mix
194
+ include SetupFir
195
+ def call data, &block
196
+ call! data.dup, &block
197
+ end
198
+ def call! data
199
+ @decimation_phase /= @decimation_phase.abs
200
+ yield(data.collect! do |v|
201
+ v * @decimation_phase *= @decimation_inc
202
+ end)
203
+ end
204
+ end
205
+
206
+ module MixFir
207
+ include SetupFir
208
+ def call data, &block
209
+ call! data.dup, &block
210
+ end
211
+ def call! data
212
+ @decimation_phase /= @decimation_phase.abs
213
+ data.collect! do |value|
214
+ @decimation_buf[@decimation_fir_pos] = value
215
+ @decimation_fir_pos += 1
216
+ @decimation_fir_pos = 0 if @decimation_fir_pos == @decimation_fir_size
217
+ @decimation_phase *= @decimation_inc
218
+ value = @decimation_fir_coef[@decimation_fir_pos].mul_accum @decimation_buf, 0
219
+ value[0] * @decimation_phase
220
+ end
221
+ yield data
222
+ end
223
+ end
224
+
225
+ module MixDecimateFir
226
+ include SetupFir
55
227
  def call data
56
- @fir_pos = @fir_size if @fir_pos == 0
57
- @fir_pos -= 1
58
- @fir_buf[@fir_pos] = data
59
- iq = @fir_buf.mul_accum @fir_coef[@fir_size-@fir_pos..-1-@fir_pos],0
60
- yield iq[0]
228
+ data_size = data.size
229
+ @decimation_phase /= @decimation_phase.abs
230
+ out_size = data_size / @decimation_size
231
+ out_size += 1 if @decimation_size - @decimation_pos <= data_size % @decimation_size
232
+ out = NArray.scomplex out_size
233
+ out_count = 0
234
+ i = 0
235
+ while i < data_size
236
+ want = (@decimation_size - @decimation_pos).round
237
+ remaining = data_size - i
238
+ space = @decimation_fir_size - @decimation_fir_pos
239
+ actual = [want,remaining,space].min
240
+ new_fir_pos = @decimation_fir_pos + actual
241
+ @decimation_buf[@decimation_fir_pos...new_fir_pos] = data[i...i+actual]
242
+ @decimation_fir_pos = new_fir_pos
243
+ @decimation_fir_pos = 0 if @decimation_fir_pos == @decimation_fir_size
244
+ @decimation_pos += actual
245
+ if @decimation_pos >= @decimation_size
246
+ @decimation_pos -= @decimation_size
247
+ j = @decimation_fir_coef[@decimation_fir_pos].mul_accum @decimation_buf, 0
248
+ out[out_count] = j * @decimation_phase *= @decimation_inc
249
+ out_count += 1
250
+ end
251
+ i += actual
252
+ end
253
+ yield out unless out.empty?
254
+ end
255
+ end
256
+
257
+ module InterpolateFir
258
+ include SetupFir
259
+ module Float
260
+ def call data
261
+ out = NArray.sfloat @interpolation_size, data.size
262
+ index = 0
263
+ data.each do |value|
264
+ @interpolation_buf_f[@interpolation_fir_pos] = value
265
+ @interpolation_fir_pos += 1
266
+ @interpolation_fir_pos = 0 if @interpolation_fir_pos == @interpolation_fir_size
267
+ iq = @interpolation_fir_coef[@interpolation_fir_pos].mul_accum @interpolation_buf_f, 0
268
+ out[true,index] = iq.reshape!(iq.size).mul!(@interpolation_size)
269
+ index += 1
270
+ end
271
+ yield out.reshape!(out.size)
272
+ end
273
+ end
274
+ end
275
+
276
+ module MixInterpolateFir
277
+ include SetupFir
278
+ def call data
279
+ @interpolation_phase /= @interpolation_phase.abs
280
+ out = NArray.scomplex @interpolation_size, data.size
281
+ index = 0
282
+ data.each do |value|
283
+ @interpolation_buf_c[@interpolation_fir_pos] = value
284
+ @interpolation_fir_pos += 1
285
+ @interpolation_fir_pos = 0 if @interpolation_fir_pos == @interpolation_fir_size
286
+ iq = @interpolation_fir_coef[@interpolation_fir_pos].mul_accum @interpolation_buf_c, 0
287
+ @interpolation_phase *= @interpolation_inc
288
+ out[true,index] = iq.reshape!(iq.size).mul!(@interpolation_phase * @interpolation_size)
289
+ index += 1
290
+ end
291
+ yield out.reshape!(out.size)
61
292
  end
62
293
  end
63
294
 
@@ -0,0 +1,142 @@
1
+ # Copyright 2012 The ham21/radio Authors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ class Radio
17
+ class Filter
18
+
19
+ module Iq
20
+
21
+ def setup
22
+ @bins = 512
23
+ @tries = 5
24
+ @dc_rate = 0.00001
25
+ @iq_rate = 0.0001
26
+ @biasI = 0.0
27
+ @biasQ = 0.0
28
+ @gain = 1.0
29
+ @phase = 0.0
30
+ @fft = NArray.scomplex @bins
31
+ @fft_pos = 0
32
+ end
33
+
34
+ module Complex
35
+ def call data, &block
36
+ call! data.dup, &block
37
+ end
38
+
39
+ def call! data
40
+ remove_dc_bias! data
41
+ collect data
42
+ adjust! data, @phase, @gain
43
+ yield data
44
+ analyze # this is slow
45
+ end
46
+ end
47
+
48
+ # This maintains a buffer of recent data
49
+ # that we can grab for analysis
50
+ def collect data
51
+ i = 0
52
+ data_size = data.size
53
+ while i < data_size
54
+ remaining = data_size - i
55
+ space = @bins - @fft_pos
56
+ actual = [remaining,space].min
57
+ new_fft_pos = @fft_pos + actual
58
+ @fft[@fft_pos...new_fft_pos] = data[i...i+actual]
59
+ @fft_pos = new_fft_pos
60
+ if @fft_pos == @bins
61
+ @fft_pos = 0
62
+ @next_fft = @fft.dup
63
+ end
64
+ i += actual
65
+ end
66
+ end
67
+
68
+ # Once per call, we will do an FFT to either start a
69
+ # new round of guessing or try again on the current set.
70
+ def analyze
71
+ return unless @cur_fft or @next_fft
72
+ if !@cur_fft_count or @cur_fft_count > @tries
73
+ @cur_fft = @next_fft
74
+ @cur_fft_count = 0
75
+ fft = @cur_fft.dup
76
+ adjust! fft, @phase, @gain
77
+ @cur_fft_best = detect_energies FFTW3::fft(fft)
78
+ else
79
+ @cur_fft_count += 1
80
+ phaseIncrement = @iq_rate * rand_direction
81
+ gainIncrement = @iq_rate * rand_direction
82
+ fft = @cur_fft.dup
83
+ adjust! fft, @phase + phaseIncrement, @gain + gainIncrement
84
+ det = detect_energies FFTW3::fft(fft)
85
+ if det > @cur_fft_best
86
+ @cur_fft_best = det
87
+ @gain += gainIncrement
88
+ @phase += phaseIncrement
89
+ end
90
+ end
91
+ end
92
+
93
+
94
+ def remove_dc_bias! data
95
+ data.collect! do |v|
96
+ temp = @biasI * (1 - @dc_rate) + v.real * @dc_rate
97
+ @biasI = temp unless temp.nan?
98
+ real = v.real - @biasQ
99
+ temp = @biasQ * (1 - @dc_rate) + v.imag * @dc_rate
100
+ @biasQ = temp unless temp.nan?
101
+ imag = v.imag - @biasQ
102
+ Complex(real,imag)
103
+ end
104
+ end
105
+
106
+ def adjust! data, phase, gain
107
+ data.collect! do |v|
108
+ Complex(v.real + phase * v.imag, v.imag * gain)
109
+ end
110
+ end
111
+
112
+ def detect_energies spectrum
113
+ result = 0.0
114
+ length = @bins.size
115
+ halfLength = length / 2
116
+ start = (0.20 * halfLength).round
117
+ finish = (0.90 * halfLength).round
118
+ spectrum = spectrum.abs
119
+ frag = spectrum[start..finish]
120
+ min = frag.min
121
+ max = frag.max
122
+ threshold = max - (max-min) * 0.5
123
+ (start..finish).each do |i|
124
+ cur = spectrum[length - 1 - i]
125
+ next unless cur > threshold
126
+ diff = cur - spectrum[i]
127
+ next unless diff > 0
128
+ result += diff
129
+ end
130
+ result
131
+ end
132
+
133
+ # this is the IQ detection idea from the sdr# project
134
+ def rand_direction
135
+ rand*2-1
136
+ end
137
+
138
+ end
139
+
140
+ end
141
+ end
142
+